1. We have added the ability to collapse/expand forum categories and widgets on forum home.
    Dismiss Notice
  2. Photobucket has changed its policy concerning hotlinking images and now requires an account with a $399.00 annual fee to allow hotlink. More information is available at: this link.
    Dismiss Notice
  3. All Civ avatars are brought back and available for selection in the Avatar Gallery! There are 945 avatars total.
    Dismiss Notice
  4. To make the site more secure, we have installed SSL certificates and enabled HTTPS for both the main site and forums.
    Dismiss Notice
  5. Civ6 is released! Order now! (Amazon US | Amazon UK | Amazon CA | Amazon DE | Amazon FR)
    Dismiss Notice
  6. Dismiss Notice
  7. Forum account upgrades are available for ad-free browsing.
    Dismiss Notice

An Idiots Guide to Editing the DLL

Discussion in 'Civ4 - Modding Tutorials & Reference' started by xienwolf, Mar 16, 2009.

  1. xienwolf

    xienwolf Chieftain

    Joined:
    Oct 4, 2007
    Messages:
    10,589
    Location:
    Location! Location!

    ==================================================
    ================ Table of Contents ===================
    ==================================================


    1. When to Mod the DLL
    2. Cloning an already existing Boolean XML tag (Moving <bNoBadGoodies> from UnitInfos to PromotionInfos)
      (Absolute beginners start here, we will discuss a lot more detail about C++ in general during this walkthrough than during any future walkthroughs)

    3. Creating a completely new Boolean XML tag (Adding <bUnique> to UnitClassInfos AND BuildingClassInfos - This tag will allow us to create modular civilizations more easily making Unique Units which do not replace anything else)
    4. Cloning an already existing Integer XML tag (Basically the same as a Boolean, but now to pass a number instead of an absolute, replace "is" with "get" and "bool" with "int")
      • Schema & XML
      • CvInfos
      • CvGameTextMgr
      • Functional Parts
      • Exposing to Python

    5. Creating a completely new Integer XML tag
      • Schema & XML
      • CvInfos
      • CvGameTextMgr
      • Functional Parts
      • Exposing to Python

    6. Cloning an already existing String XML tag (Actually treated as an integer in the code, but then "cast" to an enumerator later. Need to use getInfoTypeFor commands to find the right number to save, then treat it just like an integer)
      • Schema & XML
      • CvInfos
      • CvGameTextMgr
      • Functional Parts
      • Exposing to Python

    7. Creating a completely new String XML tag
      • Schema & XML
      • CvInfos
      • CvGameTextMgr
      • Functional Parts
      • Exposing to Python

    8. Cloning an already existing Array XML tag (Moving <TradeYieldModifiers> from TraitInfos & CivicInfos to TechInfos)
    9. Creating a completely new Array XML tag
      • Schema & XML
      • CvInfos
      • CvGameTextMgr
      • Functional Parts
      • Exposing to Python

    10. Other Functions:
      • Adding a Global Define
      • Adding a GameOption
      • Creating a "Python Callback"
      • Creating a Popup
      • Adding a new item (Widget) to the interface screen




    In Civ, you have 3 choices of where to Mod: XML, Python, and DLL. I'll briefly describe what sets each one apart from the other.

    XML

    Modding in XML is pretty easy. The only tool you need is a text editor (You COULD just use Notepad, but most people prefer fancier tools like Notepad++, which are still free and much more friendly to use), and the only things you really need to understand are which file to look in, and how to tell one entry apart from another.

    Editing XML is what you do when you think a value should be slightly different (hills should be a 30% defensive bonus, not a 25%), or on a different object (Warriors should get better results from Goody Huts, not Scouts). It is also required to add a completely new object to the game (now there is a new Technology: Saturday Morning Cartoons!).

    What XML editing cannot do, is change the fundamentals of how things work (you can follow up to 5 religions!), or how things interact (My Mustketmen gain more movement if I own Horses).

    If you change something in XML, it will not have any effect until the next time you launch your Mod (from Desktop or main BtS menu).

    Python

    Python is a location where you can do almost anything. It can be a bit tricky to get accustomed to because you have to know what commands will actually do something, the proper format for those commands, and the proper format for Python in general. The dependance on "white space" can be frustrating (errors because you didn't indent a line properly), but can also be liberating (you don't have to always remember a stupid symbol at the end of each and every line). Editing Python can be done with any text editor, just like editing XML. It doesn't look nearly as bad in Notepad as XML does, but Notepad++ and other more complicated programs still look far better, and often offer tools to help you properly format everything.

    Python allows you to change hundreds of things in the game. The primary limitation is that you do not always have access to every piece of information you might like to know, you can only act when called upon to act, and that you cannot easily save your data.

    90% of the "new functionality" which someone might dream up can be handled with python editing. XML is still required to add new items to the game (additional units, new Civilizations, more Technologies...), but Python is able to adjust most of the values set in XML dynamically during the course of the game.

    A major drawback to python is that things you do here aren't automatically parsed into the Civilopedia, so you have to be careful to write your own documentation, preferably making it readily available in game, but at the least making it easily located along with your download. A second significant drawback is that it is nearly impossible to teach the computer how to understand what you have done in Python and how to take advantage of it in the way a Human might. The final significant drawback to python is that it can really slow down the gameplay if you use too much of it.

    Changes in python can be made while the game is in progress. There are a few things which will cause significant errors when you return to the game, but the majority of what you do can be edited, saved, and then loads itself into the game immediately for testing. This makes doing small adjustments to find the exact right values painlessly simple.

    DLL

    The DLL is the most traditionally intimidating manner in which to mod Civ. This is completely due to the requirement that you compile what you have done before you can test it in game. This is a process which can finish in 2 minutes for easy changes, or take up to 20 minutes for more involved changes. Plus the not-to-insignificant time invested in setting up the tools required to edit and compile. But with walkthroughs readily available on the forums, it is more about taking some time to sit down and carefully follow directions than it is about actually having technical knowledge.

    As with Python, the DLL doesn't exist for the purpose of adding new items to the game, that is completely the territory of the XML. But unlike Python, the DLL is able to be used to add new CLASSES of items into the game (I know we have units and buildings and cities and junk, but I want to also make Clothing, Vehicles, and Rural Communities!). There are very few limitations on what can be done in the DLL. You can interrupt any portion of the game at absolutely any point you want (if combat is happening between two units, and both of them have lost 50% of the health they had at the start of battle, I want them to stop and play Rock, Paper, Scissors!)

    The only real limitations in the DLL are unlikely to matter to most modders. But some things do remain inaccessible. Most of these deal with graphics on a level which is unlikely to come up due to few people having the graphical AND programming skills at the same time to contemplate such changes. The other limitations are more a matter of practicality. It is considered "bad form" to "Hardcode" the DLL. That is, to have it directly refer to a specific item in your XML structure (like UNIT_WARRIOR) and treat it differently than it would anything else. It is preferred in such cases as that either to use Python, or to create a new XML field (like <bIAmSpecialSoThere>).

    Many things can be accomplished in either Python OR the DLL just as easily. In those cases it is usually a question of how often it will be used, and how certain you are that you will keep it the way it is to decide which to use. If you will often change things to find the precise proper balance, keep it in python so that you can change it easily (no recompiling required). If it will be done VERY often (every time a unit moves), or does a LOT at once (checks every plot of the map), it is usually best to place it in the DLL.

    In rare cases, you have functions which take a lot of time, so should be in the DLL, but are also VERY specific, so should be done in Python. One might be tempted to break custom and hardcode the DLL for these cases, but instead you can use BOTH the DLL and Python. You do that by creating a "Callback" function, which we will cover at the very end of these tutorials.

    Changes in the DLL require that you compile the DLL, replace the previous version of the DLL, and then reload the mod.



    If you were able to understand python, you can understand C++. Firaxis was kind enough to use function and variable names which are fairly easy to understand, and it all works much like python does, you have functions which do things, and very often these functions call each other.



    This is a nice section to write. Kael and Refar are your friends. Both options are free too.

    You can set up Codeblocks with Kael's instructions. Codeblocks is pretty user friendly and nice to get used to at the start. There are some "wrong ways" to compile the SDK, and with Codeblocks, it is very hard not to do it right.

    The alternative method is to set up Visual Studio 2008 Express with Refar's guide. This one requires quite a bit more work to set up properly, but the guide still holds your hand nicely and it isn't too hard overall. I would actually encourage you to use Codeblocks and get comfortable with modding the DLL, then "graduate" to VS2008+. The reason that you WILL move on to using 2008 sometime is that you can create what is known as a "Debug DLL." This is a method of finding problems in your code far better than you ever dreamt of before, but you have to understand things pretty well before it is worthwhile.

    Due to the release of 3.19, and the updating of Codeblocks and Visual Studios since each of these guides were written, you can look in the Wiki for better updated information, primarily the 3.19 version of the Makefile for Visual Studio



    Yes, there are lots of files. But there are lots of XML and python files as well. The point is that you won't use quite a few of them. I'll introduce you to a couple of the files which will be your friends, you can stumble into the others later on. The experience you'll have by then will make answering your questions easier.


    Any file that ends with .h is what we call a "Header file." Functions that you create have to be mentioned here, and new variables you create to track data need mentioned here as well (not EVERY variable you ever use, just things you want to save longterm.

    Files which end in .cpp are the main files, called "Source files." What happens in here actually does something and controls the game.

    Though you might not deal with them until you are quite a bit more comfortable with modifications in the DLL, files ending with AI.cpp focus mostly on controlling how the computer plays. Very little in here actually does something (changes things in game that is), instead it is focused on making the AI decide what to do about life in general.

    The beginning of the file names is important as well. If the file starts with Cv, it actually does something. If it starts with Cy, it allows python to do things. This is a process known as "exposing" your functions to python. Most of the time you are trying to allow python to get information (like pUnit.getFirstStrikes()). It can also be used to allow access to functions which do things (like pUnit.changeName("Ralph")). But it doesn't enable functions in python like "def onUnitLost()," that is a bit more complicated and will be covered later (MUCH later). For the time being, we will assume that you don't bother to use python anymore. You are in C++ now, learn it, love it, live it :p


    Ok, so let's begin the introductions!

    1. CvInfos.cpp & CvInfos.h
      • Every time that you add something to the XML (a new field), you will have to add it to this file first. What you do in here is always pretty spread out and can get a bit confusing. But that is what this thread is all about
    2. CvGameTextMgr.cpp & CvGameTextMgr.h
      • This is another file where you have to modify it for almost everything that you do. Unlike most files, you will rarely add new functions or variables, so you will rarely have to modify the header file (remember, that is the file ending with .h). Doing changes here will always be fairly voluntary, but the people who play your mod will love you for it. This is how you set up automated documentation in the pedia ;)
    3. CvCity.cpp, CvCityAI.cpp & CvCity.h
      • As I said, Firaxis was nice about the names they chose to use. You should be able to understand what the next couple of files do on your own. I'll be explaining a few of the things you can't just guess from the filename (and for those of you a bit slower on the uptake, CvCity files are for changing data about a city ;))
      • You may have played Civ long enough to realize that buildings kind of act like Promotions for your city. They enhance your stats/capability in some way. So one might suspect that most of the information from a building will be included in CvCity. That is about 70% correct. However, a lot of information is left on the building (CvInfos), and a fair amount is actually placed on the Player (CvPlayer) or the Team (CvTeam). But buildings will be one of the most important things we deal with in CvCity. Specialists and general terrain will rank in at a tie for second most frequent, and when you start to work with AI
      • The AI file here is mostly focused on 2 things: What to build in the city (unit/building/project), and what to build on the map (improvements)
    4. CvPlayer.cpp, CvPlayerAI.cpp & CvPlayer.h
      • Ok, so now we have the Player data. This file can be a bit tricky because it interacts with so many other files, and some things you might think belong in here may actually belong in one of them.
      • Mostly, Player data will contain information about your traits, your Civilization, and your Civics/Religion. It might also contain some information about Wonders you own though, as well as some other more creative things which a person may mod into the game
      • Player data holds a couple of "raw number" type of information as well, like a list of what events you have already had, which feats you have accomplished, your current treasury size, and espionage point data
      • One thing you might suspect belongs here would be Technologies. But you are completely halfway wrong on that one ;) Techs go on your Team, with a very few exceptions.
      • The AI file here contains quite a bit of information, mostly for a beginning modder you will probably care that it deals with decisions about what civics to select, which tech to research, and attitude values toward other leaders, possibly you also care that this is where the AI decides how/where to found cities, what to agree on in diplomacy and which units to delete if it needs to
    5. CvTeam.cpp, CvTeamAI.cpp & CvTeam.h
      • Unfortunately those of you who play mostly single-player games and don't often form permanent alliances will not have a very natural grasp of what belongs to the team, and what belongs to the player. So if that is the case for you, just remember to always check in both sets of files for information you want to find/modify
      • Team data tracks what techs you have, and is quite important for war and UN matters
      • Includes capitulation mechanics (such as land target definition, power measurements, etc), voluntary vassalage, trade denials, etc. When in CvTeamAI, DENIAL_[insert phrase] simply refers to the message the AI will give when an option is 'redded out'. Thus, NO_DENIAL (always with human, and with some trade options relating to vassals) means that the option is never 'redded-out'. The rest tend to be self-explanatory.
    6. CvUnit.cpp, CvUnitAI.cpp & CvUnit.h
      • This and CvCity are really where most of modding will take place. While buildings enhance a city, promotions enhance a unit. In general, information from the promotion is loaded onto the unit when it gains the promotion, but in a few cases it is left in CvInfos and called on when required (in VERY few cases). Also, the data for the unit itself winds up being split between CvInfos and CvUnit. If the information isn't expected to ever change all game (like if a unit can move on land or on water), then it is left in CvInfos and called from there. But if information is likely to change (like number of movement points, which can be augmented by a promotion), then it is most likely loaded onto the unit when it is created. This is a fairly important distinction for a modder, and many times you will probably have to move things from being just in CvInfos to being just in CvUnit. I won't go into the specifics of what is in CvUnit really as most players are quite familiar with what a unit can do
      • AI here is again pretty complicated, but it actually gets scattered across a LOT of files. CvSelectionGroupAI.cpp handles quite a bit about the specifics of a unit, while CvUnitAI.cpp will handle mostly logistics and short term decisions that it may need to make, like where to move, what promotion to take, and if it should upgrade or not



    I will not be including any comments in the code because it just inflates the character count and gets in the way of reading what I post in the [ CODE ] blocks here on the forums, but in your own DLL you really REALLY want to comment EVERYTHING, and make it something noticeable.

    I personally stole my comment style from the World of Civilization team. It is quite a nice setup, easy to locate when scrolling through a file, stands out nicely in WinMerge, and allows you to revert your code quickly. But in the end, you can use whatever you really want to use, or nothing at all.

    The comment style I like to use is this (trust me, it looks better in C++ where the lines aren't broken up by silly character wrapping):

    Code:
    /*************************************************************************************************/
    /**	Xienwolf Tweak							03/07/09											**/
    /**																								**/
    /**																								**/
    /*************************************************************************************************/
    /**								---- Start Original Code ----									**
    /**								----  End Original Code  ----									**/
    /*************************************************************************************************/
    /**	Tweak									END													**/
    /*************************************************************************************************/
    
    This allows me to record the date I wrote the changes (which I frequently don't update and actually never refer to... as I said, use it how you want to use it :p), and to place a nice searchable name. In the two blank lines at the top I will write what I am doing with the edit, like in this example:
    Code:
    [COLOR="DarkOrchid"]/*************************************************************************************************/
    /**	Xienwolf Tweak							09/06/08											**/
    /**																								**/
    /**			Allows for Multiple Buildings with the Hide Units or See Invisible Tags				**/
    /*************************************************************************************************/
    /**								---- Start Original Code ----									**
    	m_bHideUnits = false;
    	m_bSeeInvisible = false;
    /**								----  End Original Code  ----									**/[/COLOR]
    	m_iHideUnits = 0;
    	m_iSeeInvisible = 0;
    [color="DarkOrchid"]/*************************************************************************************************/
    /**	Tweak									END													**/
    /*************************************************************************************************/
    [/COLOR]
    Everything in Purple is ignored by the compiler (commented out)

    Since using the characters: /* starts a comment of unlimited size, the code itself ignores everything else until you use the characters */. So if you look closely you will notice that at the end of each line I terminate the comment, EXCEPT the line which reads "Start Original Code." Since I do not have the final slash mark, the comment doesn't end, and those two lines are ignored by the compiler (it is as good as deleting them, the final DLL filesize will be no larger than normal if I transcribed all of Leo Tolstoy's "War and Peace" inside a comment block. So be verbose, make sure you remember what you did and why).


    As I said though, I like this style because it is very flexible. Look at how little I have to change to undo this edit completely (marked in orange)
    Code:
    /*************************************************************************************************/
    /**	Xienwolf Tweak							09/06/08											**/
    /**																								**/
    /**			Allows for Multiple Buildings with the Hide Units or See Invisible Tags				**/
    /*************************************************************************************************/
    /**								---- Start Original Code ----									**[COLOR="#ff8c00"]/[/COLOR]
    	m_bHideUnits = false;
    	m_bSeeInvisible = false;
    /**								----  End Original Code  ----									[COLOR="#ff8c00"]**[/COLOR]
    	m_iHideUnits = 0;
    	m_iSeeInvisible = 0;
    /*************************************************************************************************/
    /**	Tweak									END													**/
    /*************************************************************************************************/
    
    So, I added a single slash and removed one other slash. With that small change, the code goes back to seeing what was originally written, and I have my editted information still available for some day in the future when I decide that I want to fix whatever bug convinced me to remove these lines. I would of course change my comment to say when I have undone my edit here so that 3 years down the road when I stumble across this I don't wonder about my sanity (well... not any more than normal at least...)


    The other thing which is useful is to keep a regular pattern to how you fill in your comments, this allows you to search much quicker. For instance, every edit that I make contains my name in it somewhere, but only once. This allows me to search for my name (which is quite unlikely to show up anywhere other than my changes) and instantly find EVERYTHING that I have EVER changed in the sourcecode (quite a hefty list now -- 1087 entries in 74 of the 188 total source files --, so quite useless of late. But I had to use it quite often early in my programming experience when bugs happened that I knew were my fault, but had no clue how to fix. I just searched for my name, swapped out the two slash marks to remove all my edits, and slowly added them back in one at a time. Took forever, but it worked)


    The format I like to use for adding new XML tags (which require lots of editting in the same places for just "paperwork" instead of actually working code. You'll see...) is as follows:
    Code:
    /*************************************************************************************************/
    /**	New Tag Defs	(VictoryInfos)			01/16/09								Xienwolf	**/
    /**																								**/
    /**										Initial Values											**/
    *************************************************************************************************/
    /*************************************************************************************************/
    /**	New Tag Defs							END													**/
    /*************************************************************************************************/
    
     
  2. xienwolf

    xienwolf Chieftain

    Joined:
    Oct 4, 2007
    Messages:
    10,589
    Location:
    Location! Location!
    I change the first line so that the (VictoryInfos) reads whatever file name I added the new XML into. Then if I ever add a new XML field to a file which I know I have worked in before, I can just search for (VictoryInfos) or (PromotionInfos) or whatever is appropriate and it'll quickly take me to each location I know that I need to edit.

    This format also allows you to give credit for modcomps which you import (which not only passes on the other modders name to people who look at your source, but also lets you know who to pester when you find a bug that wasn't your own fault :p):
    Code:
    /*************************************************************************************************/
    /**	DeathPython								11/30/08	Written: Pep		Imported: Xienwolf	**/
    /**																								**/
    /**																								**/
    /*************************************************************************************************/
    /**								---- Start Original Code ----									**
    /**								----  End Original Code  ----									**/
    /*************************************************************************************************/
    /**	DeathPython								END													**/
    /*************************************************************************************************/
    
    As you can see here, I assigned the keyword "DeathPython" to this group of imported changes, and the original author was someone named "Pep". I included my own name for the reason stated above, it lets me quickly find absolutely every change I have ever made in the DLL.

    And just to finish off the list of saved formats, here is a sample of using a keyword for the functional pieces of my own code so that I can find everything that deals with a specific change, incase I change my mind, or need to find a bug and want to search for it quickly:

    Code:
    /*************************************************************************************************/
    /**	LairLimit								12/30/08								Xienwolf	**/
    /**																								**/
    /**			Tracks Units Spawned from each Improvement to limit the potential spawns			**/
    /*************************************************************************************************/
    /**								---- Start Original Code ----									**
    /**								----  End Original Code  ----									**/
    /*************************************************************************************************/
    /**	LairLimit								END													**/
    /*************************************************************************************************/
    
    So I can just search for "/** LairLimit" and I will find absolutely everything which involved this particular change (except the "maintenance" work which was required to create any new XML tags)



    The easiest tag to add will be a boolean tag. These can only have the values of 1 (active) or 0 (inactive) in the XML, but in the DLL we need to track them as integers (count the total number of active ones we have) or funny things happen eventually. Like if a unit has a promotion which makes him invisible, and gains a second promotion which ALSO makes him invisible, but then later removes the first promotion. If we tracked as a boolean, we would then say "You lost a promotion to make you invisible, so you are now visible," but if we tracked as an integer, then we would say "You have lost a promotion to make you invisible, but you still have 1 other promotion somewhere to do the same, so nothing actually changes."
    While it is good to learn modding with something simple like Booleans, be careful with how crazy you get with adding booleans. Lots of things which SOUND like a simple ON/OFF decision right now will in the future prove to have been better as a percentage chance or some other more graduated system. If that happens, you have to undo a bunch of code and make it work for the new setup, which can be mildly frustrating, especially if you forget a piece or two.
    For the purposes of illustration by example, during this post we will copy the field <bNoBadGoodies> from CIV4UnitInfos.xml to CIV4PromotionInfos.xml



    The first thing which is required for any addition of a new tag to the XML structure is making XML accept the tag. To do this we must modify what are known as "Schema files." These can get a bit touchy about how you handle them, so we will try to form some good habits right away for making our lives easier when the mod gets to be quite large down the road.

    First, identify the proper schema file to edit. This is listed on the first "real line" of the XML file, in our case that is:
    Code:
    <Civ4PromotionInfos xmlns="x-schema:CIV4UnitSchema.xml">
    This tells us that we need to look for a file (it will always be in the same folder) called "CIV4UnitSchema.xml" to add our new field.

    So, open up CIV4UnitSchema.xml and you'll see a ton of lines which all read something along the lines of:
    Code:
    	<ElementType name="_________" content="textOnly"/>
    And many of them will also have an extra bit on the end, either to indicate that the field uses an integer:
    Code:
    	<ElementType name="_________" content="textOnly" dt:type="[b][u][i]int[/i][/u][/b]"/>
    or to indicate that the field uses a boolean:
    Code:
    	<ElementType name="_________" content="textOnly" dt:type="[b][u][i]boolean[/i][/u][/b]"/>
    If you don't have either of these, then it means you are allowed to type in letters/words for the field information (like PROMOTION_COMBAT1).

    It won't appear quite as often, but you can also have some fields which don't hold any data on their own, but instead hold other data tags (these are arrays):
    Code:
    	<ElementType name="_________" content="[b][u][i]eltOnly[/i][/u][/b]">
    {Notice that this didn't replace "boolean" or "int", but rather replaced the "textOnly" to show that it can't have anything at all but other elements in it)


    A very important note: These <ElementType name="_______"... fields can only appear ONCE per schema file for each specific name! Even if both times it appears it says to use the same kind of data, having it listed twice will breka things! So always make sure you are using a completely new name, or if you are recycling a name, make sure you use it in the same way that it was already being used (as an INT, Boolean, Array (of the same elements) or String) and that you don't re-declare it.


    Now, ever field which appears as <ElementType name="_______"... will also appear somewhere else as <element type="_______"/>. Again, we have extra pieces we can add to the line. These always appear inside of one of those "eltOnly" fields to show that it is included in an array (each individual unit or promotion is one huge array, and might have more arrays within it)

    Again, we can add extra bits to change how these behave. You can make a field optional (doesn't HAVE to be included in EVERY item in the XML file) by writing:
    Code:
    		<element type="_______" [b][u][i]minOccurs="0"[/i][/u][/b]/>
    Or you can make it have a limit on how often it can be used by adding:
    Code:
    		<element type="_______" [b][u][i]maxOccurs="3"[/i][/u][/b]/>
    And you can use both fields at once if you really want to be picky about how often it is used.

    These <element type="_______"/> fields can appear as often as you want them to. The important thing to watch here is just what order they appear in, and which tag you add them under. For a new promotion, we need to add it to the list of: <ElementType name="PromotionInfo" content="eltOnly"> (notice there are already quite a few fields listed here, and the order matches exactly with the order you will find in CIV4PromotionInfos.xml file for each and every promotion)
    The fact that order is important is why we need to develop some good habits quickly. Because it can be annoying to add your new XML tags to every single promotion that already exists, we are going to take advantage of the 'minOccurs="0"' tag so that we only need to add our new field to the couple of new promotions we had in mind that required us to write it. But if we ever want to use it in the future for another promotion, we have to be able to remember WHERE to add the field, preferably without having to look in the schema. So we will insist on always adding our new fields to the very bottom of the XML structure, and making them alphabetically sorted when we add more than 1 new tag to a single XML file. If you really want to invest the extra time, you can make all of the tags Firaxis used include the 'minOccurs="0"' keyword and delete anything which uses the default value (0, NONE, /> or ><). This makes XML very easy to read, but can make it very HARD to modify (you have to remember what order to add the tags in. Again, if you want to invest the time, you could alphabetize Firaxis's fields, but that would take a LOT of time since you have to re-sort the main XML file as well. They are already somewhat sorted, but not in the greatest of designs overall)


    So, the tag we want to add is <bNoBadGoodies>.

    First thing we do is open up CIV4PromotionInfos.xml (the file we want to add to), and check what Schema file it uses. In this case it is CIV4UnitSchema.xml. So we open up that file and we search for "bNoBadGoodies" to see if it already exists. We do get a result, so we know that we should not add our own '<ElementType name="bNoBadGoodies" content="textOnly" dt:type="boolean"/>' line, otherwise bad things happen. So we look back at CIV4PromotionInfos.xml to figure out where we need to add this new field.
    We notice that the first tag used for each promotion is "<PromotionInfo>", so that is where we need to add our new tag. Return to the CIV4UnitSchema.xml file now and search for "PromotionInfo." We get 2 results, one of them tells the game that "PromotionInfo" is included as a sub-element of "PromotionInfos", which we saw was the case in CIV4PromotionInfos.xml. The other time it appears is when it is telling the game what fields to expect under <PromotionInfo>, this is a very long list already, and we will be making it longer by one.
    So, the last item listed under "PromotionInfo" is:
    Code:
    		<element type="iOrderPriority" minOccurs="0"/>
    which as we know means that it is an optional field (minOccurs="0"). We want our new field to also be optional, so we make a new line right after that which reads:
    Code:
    		<element type="iOrderPriority" minOccurs="0"/>
    [COLOR="PaleGreen"]		<element type="bNoBadGoodies" minOccurs="0"/>[/COLOR]
    	</ElementType>
    And now we return to CIV4PromotionInfos.xml and we add our new promotion, complete with this extra field. If you need help with this part, I think that this tutorial should help (Please let me know if there is a better one to link to instead)
    Now, for XML, you promotion is complete and should work wonderfully. But of course, the game doesn't acknowledge your new field yet. However, since we have already done a few things which can cause the game to fail in loading, we will now launch your mod to make sure it loads and plays without issue. (so, go launch your mod, and if you get any errors, review what you did and the post up to this point for any errors).

    Remember, at this point, your mod doesn't do anything new yet. We are only launching to make sure that nothing has changed. If you hadn't added your promotion before now, you should see the new promotion and it should do everything you wrote it to do EXCEPT provide immunity to bad results from Tribal Villages.



    Ok, so now that things work so fantastically in the XML, we are ready to move on to the bigger steps. We need to tell the DLL that this new field exists and ask it politely to load it into the game so that we might do something with it someday. This happens in CvInfos.cpp, so open up your IDE (Codeblocks or VS2008+ or whatever you happened to install) and from within your project open up the source file CvInfos.cpp (never directly open the files from explorer, always open your project in the IDE, THEN access the file so that you can compile as soon as you finish writting)
    First step, search for NoBadGoodies. Since we are right now copying a field which already exists, we get the benefit of seeing how it is handled in the other file. The following 7 cases are what you will find, and hence we will have to make these same 7 appear, but this time for promotions instead of units.
    Code:
    m_bNoBadGoodies(false),
    This happens in CvUnitInfo::CvUnitInfo(), which just sets the initial values for each variable. Since we made it optional, whatever value we use here (false), will be the value applied to all promotions which don't include our new XML tag. We will copy this field into CvPromotionInfo::CvPromotionInfo() in a little while.
    Code:
    bool CvUnitInfo::isNoBadGoodies() const
    {
    	return m_bNoBadGoodies;
    }
    These 2 cases can actually happen anywhere you want them to because they are completely new functions, but for simplicity we will always try to add our new functions in a certain location, it makes it easier to find where our work was done if we ever come back to change it, remove it, or debug it. Also, since we are going to be making a new function (bool CvPromotionInfo::isNoBadGoodies() const) eventually, we will be needing to add some information to CvInfos.h (the header file). That's fine because we also have to modify it for the new variable we will have to make anyway.
    Code:
    	stream->Read(&m_bNoBadGoodies);
    This happens in void CvUnitInfo::read(FDataStreamBase* stream), which loads the data from save files. Not EVERY file you add XML tags to will have a save/load routine in CvInfos.cpp. These only really matter for when you check the GameOption "Lock Modified Assets" and is loaded to verify that your current XML data matches your previous data. We will eventually add this same line to void CvPromotionInfo::read(FDataStreamBase* stream) since for Promotions you also check the data in this one case.
    WARNING! I will warn you again when we actually add the fields, but for ::read and ::write functions, everything must happen in exactly the same order, and nothing can be included in one without being included in the other. If you make one of these mistakes then your savegames will never be able to load (this is what happens when a patch breaks saves. They added something to the ::read function which wasn't in your ::write function before the patch when you made the save. So now the data doesn't line up and the save is corrupt).
    Code:
    	stream->Write(m_bNoBadGoodies);
    This happens in void CvUnitInfo::write(FDataStreamBase* stream), which writes the data to save files. Again, not EVERY file you add XML tags to will have a save/load routine in CvInfos.cpp, it is only used in a couple of places to work with the GameOption "Lock Modified Assets." We will eventually add this same line to void CvPromotionInfo::write(FDataStreamBase* stream).
    WARNING! I will warn you again when we actually add the fields, but for ::read and ::write functions, everything must happen in exactly the same order, and nothing can be included in one without being included in the other. If you make one of these mistakes then your savegames will never be able to load (this is what happens when a patch breaks saves. They added something to the ::read function which wasn't in your ::write function before the patch when you made the save. So now the data doesn't line up and the save is corrupt).
    Code:
    	pXML->GetChildXmlValByName(&m_bNoBadGoodies, "bNoBadGoodies");
    These last 2 cases happen in bool CvUnitInfo::read(CvXMLLoadUtility* pXML) and we will eventually copy into bool CvPromotionInfo::read(CvXMLLoadUtility* pXML). We will discuss it more when we get into adding a String, but this function is one of the functions which actually opens the XML file and reads information out of it. This can happen in 3 different ways, and the other 2 methods only matter when you are dealing with strings (hence we'll wait to talk about them till we get to strings). The part in quotes needs to match EXACTLY to the field as you added it to the schema/XML files, and if it finds such a field then it loads whatever information it finds there into the variable identified just before it. If it does not find the XML field, then it doesn't do anything at all and just moves on.
    Also note, the order of these entries doesn't matter at all. The only time that order matters is in the Schema/XML files (match each other) and in the ::write(stream) & ::read(stream) files (again, match each other). The ::read(XML) file doesn't care about order at all (well, mostly doesn't, the only special case is for how Firaxis wrote Yields/Commerces to load, but you won't ever really have to deal with that directly)

    Continuing with our exploration of where the fields already exist, I need you to now open up CvInfos.h (the header file) This time when you search for NoBadGoodies you will get 2 results.
    Code:
    	DllExport bool isNoBadGoodies() const;				// Exposed to Python
    This declares our function that we create (bool CvUnitInfo::isNoBadGoodies() const right now, but bool CvPromotionInfo::isNoBadGoodies() const when we do our new function) I'll break down exactly what everything in this line means:
    DllExport: This allows the exe to access this function (I think...) and as such you won't ever need to use it. But it doesn't hurt if you use it, so whatever you want really.
    bool: This tells us what value is returned by the function we write. Anything which calls this function gets back a value of this type. If you aren't going to return any value, then you said void
    isNoBadGoodies: This is just what we made up as our function name, it can be anything, it just has to match here with what you used in the .cpp. You will notice that it doesn't now include the CvUnitUnifo:: chunk. That is because we are declaring it in the section of CvInfos.h which matches up with CvUnitInfo, and so it is already understood. As such, declarations have to be done in the correct locations in the .h files so they match up properly. Though outside of CvInfos you don't need to worry too much about this fact.
    (): This is where you would hold the information if you passed any variables into the function. In this case, nothing is passed in, but we need to include the () anyway because the program insist on it. When we get to Arrays we'll have to include something here, but for now we can mostly ignore it.
    const: This assures the programming that we are not going to change any variables while this function runs. It assures the programming that things will be quick and makes it unnoticeably quicker. You can go without this frequently if you want to, sometimes including it makes life harder. But it does help to keep you from making some mistakes on occasion, so it can help.
    ; // Exposed to Python: The semi-colon ends a line, and allows you to do new commands. If you really wanted to you could write your entire program on a single line, or give every single word or symbol a line to itself. All that matters is where you use the semicolons (unlike in python, white space means NOTHING in C++, but the tradeoff is that you have to remember to use your semi-colons). The last bit, the // is an indicator that you are starting a comment. It tells the program to ignore absolutely everything on this line after this point. As such, the mention of "Exposed to Python" is just there for your benefit, to let you know that the function is available to python access already. You can include this note yourself, but it is ONLY for the benefit of people reading your source files.
    Code:
    	bool m_bNoBadGoodies;
    This declares our variable so that we can use it. If you only use a variable inside of a single function, you don't bother with declarations out here. You only need to do this with variables that are saved, or otherwise maintained outside of functions.
    bool: This states what kind of variable it is that we are dealing with here. There are a couple possibilities, but mostly we'll encournter bool, int, and CvString
    m_bNoBadGoodies;: This is whatever variable name we made up. Starting with the m_ is a convention which indicates that you are dealing with a variable which exists for the entire current file, rather than one you created locally for just a single function. After that we place the b to indicate it is a boolean, or an i to indicate integer, sz means String. But these are just conventions, the only thing that matters is the word that comes first, after that it is your choice (but these conventions DO prove useful, so it is nice to stick with them).

    Ok, explanations are over, let's add the functions we need now.

    So, find CvPromotionInfo::CvPromotionInfo() and let's go ahead and add our new variable at the very start. Remember, we are setting the initial value here and trying to make the field be optional, so this should be the value you want the majority of the promotions in the game to have (all those you don't specifically tell to act differently)
    Code:
    CvPromotionInfo::CvPromotionInfo() :
    [COLOR="#98fb98"]m_bNoBadGoodies(false),[/COLOR]
    m_iLayerAnimationPath(ANIMATIONPATH_NONE),
    
    I had you add this at the beginning to avoid explaining the commas at the end of the lines. But technically, you are doing all of this on a single line. Remember I told you that unlike python the whitespace doesn't mean much, the important thing is when you have a semi-colon. All of this being seperated by a comma is pretty important, but the REAL important thing is that the last item needs to NOT have a comma after it. So if you DO add to the end of the list, make sure that you have a comma before the first thing which you add (because there isn't one after the last thing in the list right now) and you do NOT have a comma after the last thing you add. Or just work from the front or middle of the list to avoid this completely ;)

    Then we need to add a function which allows us to find out what value is stored in this variable (it is always better to have a function access the variables rather than allowing direct access to the variables. Mainly so that we can catch all interaction in the debugger easily when things go wrong). We will go ahead and add this function right before the void CvPromotionInfo::read(FDataStreamBase* stream) function. But remember, as long as you don't put it inside of another function it isn't TOO important where you put it, as long as the name is correct we are good. But now we'll have:
    Code:
    bool CvPromotionInfo::getUnitCombat(int i) const
    {
    	FAssertMsg(i < GC.getNumUnitCombatInfos(), "Index out of bounds");
    	FAssertMsg(i > -1, "Index out of bounds");
    	return m_pbUnitCombat ? m_pbUnitCombat[i] : false;
    }
    
    [COLOR="#98fb98"]bool CvPromotionInfo::isNoBadGoodies() const
    {
    	return m_bNoBadGoodies;
    }
    [/COLOR]
    void CvPromotionInfo::read(FDataStreamBase* stream)
    {
    	CvHotkeyInfo::read(stream);
    
    	uint uiFlag=0;
    	stream->Read(&uiFlag);		// flag for expansion
    
    	stream->Read(&m_iLayerAnimationPath);
    	stream->Read(&m_iPrereqPromotion);
    	stream->Read(&m_iPrereqOrPromotion1);
    
    These next two steps it is absolutely vital that you remember the importance of keeping the same order for everything in both CvPromotionInfo::read & CvPromotionInfo::write. I cannot stress that nearly enough. If you mess that up, you won't notice until you try to load a savegame and it informs you "Failed to decompress Savedata" and refuses to load your save. If you don't check this yourself before releasing the mod you'll have some very upset players informing you that they lost 5 hours of playtime on a very enjoyable game because they can't load the save anymore. We will again go ahead and save the new information at the earliest point possible, though many modders do prefer to make their changes at the end of existing work instead of at the beginning of it (which can be important in some places, and like above when initializing your variable, can have different requirements sometimes too). One thing you cannot do though is add it before " stream->Read(&uiFlag); // flag for expansion", because that is a bit of a special item and should be the first thing listed.
    Code:
    void CvPromotionInfo::read(FDataStreamBase* stream)
    {
    	CvHotkeyInfo::read(stream);
    
    	uint uiFlag=0;
    	stream->Read(&uiFlag);		// flag for expansion
    
    [COLOR="#98fb98"]	stream->Read(&m_bNoBadGoodies);[/COLOR]
    	stream->Read(&m_iLayerAnimationPath);
    	stream->Read(&m_iPrereqPromotion);
    
    So now our new variable is listed. Remember that it has to match the variable name you initialized, and the variable name you will declare in the header file in a few moments. Also remember the order needs to match with this next part:
    Code:
    void CvPromotionInfo::write(FDataStreamBase* stream)
    {
    	CvHotkeyInfo::write(stream);
    
    	uint uiFlag = 0;
    	stream->Write(uiFlag);		// flag for expansion
    
    [COLOR="#98fb98"]	stream->Write(m_bNoBadGoodies);[/COLOR]
    	stream->Write(m_iLayerAnimationPath);
    	stream->Write(m_iPrereqPromotion);
    
    One last edit and we are done with CvInfos.cpp. We now need to write the interface to take the data out of the XML and into the game. So find the function bool CvPromotionInfo::read(CvXMLLoadUtility* pXML) and we'll go ahead and add our new function a couple of lines down just so you can be near other things which are loading in the same manner. So scroll down till you see it loading "bBlitz" and we'll add in after that one.
    Code:
    	pXML->GetChildXmlValByName(&m_bBlitz, "bBlitz");
    [COLOR="#98fb98"]	pXML->GetChildXmlValByName(&m_bNoBadGoodies, "bNoBadGoodies");[/COLOR]
    	pXML->GetChildXmlValByName(&m_bAmphib, "bAmphib");
    
    Remember, the &m_bNoBadGoodies has to match the variable name we created for our new field, and the "bNoBadGoodies" has to match the name we put in the Schema file earlier. With this done, we are finished with CvInfos.cpp. But if we try to compile now it'll complain about us using a variable and function without having told the program that we wanted to use them, so we are off to CvInfos.h to tell it such :)

    Open up CvInfos.h now, and search for class CvPromotionInfo : public CvHotkeyInfo, this marks the beginning of the Promotion Information here. The first thing (that isn't a comment) that it says is "public:" which means that those items listed after this phrase can be accessed by other files outside of CvInfos.cpp (but still inside the DLL). This is where our new function gets mentioned. Later it will have the keyword "protected:" which means that ONLY CvInfos.cpp can modify it (well, actually only those things which start with CvPromotionInfos:: will be able to, most of the time that is everything in the .cpp file, but CvInfos is an exception). This is where we will mention our variable so that it is kept nice and safe.
    So, first we declare our new function whcih we wrote. Again, order isn't terribly important, as long as it is listed under Public and not under Protected. We'll just toss it in right at the beginning to be done with it.
    Code:
    public:
    	DllExport CvPromotionInfo();
    	DllExport virtual ~CvPromotionInfo();
    
    [COLOR="#98fb98"]	bool isNoBadGoodies() const;[/COLOR]
    	DllExport int getLayerAnimationPath() const;
    	DllExport int getPrereqPromotion() const;				// Exposed to Python
    
    Remember that things have to match the first line of the function out in CvInfos, except without the CvPromotionInfos:: and now with a semi-colon on the end after const.

    Next we need to declare our new variable, so we search down to "protected:" and we'll add in there
    Code:
    protected:
    
    [COLOR="#98fb98"]	int m_bNoBadGoodies;[/COLOR]
    	int m_iLayerAnimationPath;
    	int m_iPrereqPromotion;
    
    
    Now everything we need in CvInfos and CvInfos.cpp is complete!
     
  3. xienwolf

    xienwolf Chieftain

    Joined:
    Oct 4, 2007
    Messages:
    10,589
    Location:
    Location! Location!


    As I mentioned, adding things to this file is done so that the Civilopedia will update itself to document things for you. It is one of the big things you can do in the DLL that you cannot do in python which pays off nicely.
    But figuring out precisely where to add new things can be a bit tricky. To make it a bit easier, you can collapse the functions to only show the top line. In VS2008+ you do this by right clicking anywhere in the main screen, selecting Outlining, then selecting Collapse to Definitions. In Codeblocks, right click anywhere in the main screen, select Folding and then select Fold All
    Now that you can see just the headlines, look at each function name and try to decide which one will accomplish what you want it to. When you are more familiar with C++ you will search for the function to see where else it is called from (highlight the function name, press CTRL+SHIFT+F and then hit enter, it'll search the entire project for you) But right now you might not be able to understand it, so we will just go off of intuition, then you load up the game and see if things appear where you want them to appear. If they didn't, you made a mistake, or you guessed wrong :p
    For our purposes, we are adding a new item to a Promotion, so the function we want to use is: void CvGameTextMgr::parsePromotionHelp(CvWStringBuffer &szBuffer, PromotionTypes ePromotion, const wchar* pcNewline). But if we want to see this information on the unit without having to check each and every promotion on them, we will also need to add something to void CvGameTextMgr::setUnitHelp(CvWStringBuffer &szString, const CvUnit* pUnit, bool bOneLine, bool bShort) (though for now, since we are duplicating a tag already available for a unit we don't have to worry about it quite as much, worst case we will have to move and re-target an already existing piece of code)
    Remember while trying to find the function that you want to use that the important bit is after the double colon ::, and before the starting parenthesis (. Everything inbetween the parenthesis indicates which variables are being sent to the function to use, so that can be of some assistance in deciding if there are multiple possibilities.

    So, we are seeking to add our new NoBadGoodies information to the promotion so that someone knows why they might want to promote to this. Since we are duplicating a tag, we can probably get away with stealing the work already done. So search for NoBadGoodies and you'll find:
    Code:
    	if (GC.getUnitInfo(eUnit).isNoBadGoodies())
    	{
    		szBuffer.append(NEWLINE);
    		szBuffer.append(gDLL->getText("TXT_KEY_UNIT_NO_BAD_GOODIES"));
    	}
    
    Which just happens to be in void CvGameTextMgr::setBasicUnitHelp(CvWStringBuffer &szBuffer, UnitTypes eUnit, bool bCivilopediaText) (Note, not the same function I just identified as possibly being where we want to add our information if it is going to be for a unit. The important difference is that the previously identified function is for units which already exist in the game, and thus have promotions on them. This function is for units which can be built, but haven't been yet. So mostly for Civilopedia displays. One important thing you would notice is that in setBasicUnitHelp they pass in "eUnit," which leads to CvUnitInfo (So just basic information straight from the XML), and in setUnitHelp they pass in "pUnit," which leads to CvUnit (so can have promotions attached to it and stuff). There is actually a second function called setUnitHelp which accepts eUnit instead of pUnit, this is used to display information in the city screen instead of the civilopedia, but calls the function used in the civilopedia as well during the course of the function.
    Anyway, what we are planning to do right now is just copy this information down into the promotion area, with the small modifications needed to make it check the promotion instead of the unit. And also add it to setUnitHelp so we can see what the unit is capable of without having to check each promotion

    So first, we move it down into void CvGameTextMgr::parsePromotionHelp(CvWStringBuffer &szBuffer, PromotionTypes ePromotion, const wchar* pcNewline). So find that function and then let's decide where to place the new information. Normally I would say that you should put things near the end of the list of what already exists, but for now, let's just cram it inbetween these two:
    Code:
    	if (GC.getPromotionInfo(ePromotion).isBlitz())
    	{
    		szBuffer.append(pcNewline);
    		szBuffer.append(gDLL->getText("TXT_KEY_PROMOTION_BLITZ_TEXT"));
    	}
    
    	if (GC.getPromotionInfo(ePromotion).isAmphib())
    	{
    		szBuffer.append(pcNewline);
    		szBuffer.append(gDLL->getText("TXT_KEY_PROMOTION_AMPHIB_TEXT"));
    	}
    As you see here, we are checking against the promotion information, through GC.getPromotionInfo(ePromotion). The bit of code we are copying had referenced UnitInfo, through GC.getUnitInfo(eUnit). So if we change that little tidbit, we are done!
    So now we'll have with our new items added:
    Code:
    	if (GC.getPromotionInfo(ePromotion).isBlitz())
    	{
    		szBuffer.append(pcNewline);
    		szBuffer.append(gDLL->getText("TXT_KEY_PROMOTION_BLITZ_TEXT"));
    	}
    
    [COLOR="PaleGreen"]	if (GC.getPromotionInfo(ePromotion).isNoBadGoodies())
    	{
    		szBuffer.append(pcNewline);
    		szBuffer.append(gDLL->getText("TXT_KEY_UNIT_NO_BAD_GOODIES"));
    	}
    [/COLOR]
    	if (GC.getPromotionInfo(ePromotion).isAmphib())
    	{
    		szBuffer.append(pcNewline);
    		szBuffer.append(gDLL->getText("TXT_KEY_PROMOTION_AMPHIB_TEXT"));
    	}
    You'll notice we also changed from NEWLINE to pcNewline. Small changes like this will crop up (even though this particular one wasn't absolutely required), so when you are abusing the power of Copy-Paste, always double check yourself against something similar that was already native (hence us sandwiching between two already existing booleans right now). It was also important that isNoBadGoodies() matched the function which we created in CvInfos.cpp during the last step.

    At this point, we stop again. Now that the information gets displayed automatically, we can check to see if it is working. We aren't actually done yet, because we need to also display the information on our unit. But right now we are curious if we have made any mistakes up till now, so we need to check things out. So compile, make sure you transfer the DLL into the right location, and load the game to see if the information shows up in the Civilopedia. If it doesn't, back up to the last step and make sure you did everything right in CvInfos. If that looks right, double check your stuff here in CvGameTextMgr and out in the XML (did you remember to set the new field to >1< instead of >0<?). Don't just load up and check the civilopedia though, one important thing we need to check is if we did the ::read and ::write functions correctly in CvInfos, so start a game, save and load (SHIFT+F5 then SHIFT + F8 is good enough) to ensure that things work nicely.

    If everything did work nicely, then we are going to add another data output so that information will be displayed when we do a mouseover of a unit on the game map who has this promotion. We couldn't add this earlier because it relies on making some changes in CvUnit.cpp (and CvUnit.h) and I wanted you to be able to compile things to check if your work in CvInfos was done properly. So now find void CvGameTextMgr::setUnitHelp(CvWStringBuffer &szString, const CvUnit* pUnit, bool bOneLine, bool bShort) and we'll toss in the same basic lines as we did before (and as already exist in the UnitHelp which the Civilopedia uses)

    First, let's look at the function definition. There are a couple of things passed in to this function (everything in parenthesis) which are somewhat important.
    CvWStringBuffer &szString: This is the text which is being displayed, it is what you write information to. So overall, we don't worry about it at all.
    const CvUnit* pUnit: This identifies the unit which we are trying to find out information about. The const prevents us from accidentally changing which unit we are talking about midway through the function.
    bool bOneLine: If this is true, then the game is trying to get a short definition of the unit. If you use the F5 menu in-game and have it display every unit, this is what you see.
    bool bShort: This is what shows up when viewing enemy units so that you don't get to see quite as much information, not so much to keep secrets, but so that you can fit the information of more total units on the screen at once.

    So with that sorted out, you should be able to understand where we want to place our code a little bit better. First part of this function is the information about the unit's name and basic stats, as well as which promotions he holds already. Then there is a little bit of stuff for debug purposes, so we'll just skip WAY down to an area where it won't display if either bOneLine or bShort is active (since this isn't VITAL information which you would care to see about enemy troops)
    Code:
    		if (!bShort)
    		{
    			if (pUnit->nukeRange() >= 0)
    			{
    				szString.append(NEWLINE);
    				szString.append(gDLL->getText("TXT_KEY_UNIT_CAN_NUKE"));
    			}
    
    This is really as decent of a place as any to put our information, so we'll slip it in
    Code:
    		if (!bShort)
    		{
    [COLOR="#98fb98"]			if (pUnit->isNoBadGoodies())
    			{
    				szString.append(NEWLINE);
    				szString.append(gDLL->getText("TXT_KEY_UNIT_NO_BAD_GOODIES"));
    			}
    [/COLOR]
    			if (pUnit->nukeRange() >= 0)
    			{
    				szString.append(NEWLINE);
    				szString.append(gDLL->getText("TXT_KEY_UNIT_CAN_NUKE"));
    			}
    
    You'll notice that now instead of doing a call to the promotion information, we are checking the unit itself (pUnit->), pUnit is what is referred to as a "pointer" if you care to look up information on that kind of thing online. For now you just need to know that it means the unit we are wondering about :) Also, we now are adding our lines of output to szString instead of szBuffer. That is just a choice of names though, which was set at the start of the function in the parenthesis. When you copy/paste from one location to another you need to be careful to make sure that you are writing the information to the proper string variable though :)

    One nifty thing which we have been able to dodge is adding a new text key to the XML. That will be required when we make our own unique field, but in this case we were copying something that already existed, so we were content to use what already existed for a label as well (TXT_KEY_UNIT_NO_BAD_GOODIES). It might be worthwhile to have a slightly different wording on the promotion itself, just for how it is phrased, so you would change the output for the PromotionInfo to be something like TXT_KEY_PROMOTION_NO_BAD_GOODIES and then go to XML/Text and add this text key to your text file so it can be used.

    Now we are done with CvGameTextMgr. Note that up to this point, NOTHING HAS CHANGED IN GAME. All that is different is that we now see a promotion CLAIMING to do something, but actually it doesn't do squat (well, unless you made it use more than just the new field that is). Also, with this last text key you normally cannot compile your source anymore, because it is trying to access a function in CvUnit.cpp which doesn't exist yet (but in our case we luck out and it DOES exist). So on to the functional bits.



    The nice thing about just moving an already existing XML field is that we get to make the functional bits pretty easily. All we have to do is link a function which already accomplishes something with our new place that modifies things. Sometimes this is messy, but most of the time it is pretty clear and easy. For us, the place where the existing function is actually used would be:
    In CvGameInterface:
    Code:
    									if (pHeadSelectedUnit->isNoBadGoodies())
    									{
    										if (pLoopPlot->isRevealedGoody(pHeadSelectedUnit->getTeam()))
    										{
    											gDLL->getEngineIFace()->addColoredPlot(pLoopPlot->getX_INLINE(), pLoopPlot->getY_INLINE(), GC.getColorInfo((ColorTypes)GC.getInfoTypeForString("COLOR_HIGHLIGHT_TEXT")).getColor(), PLOT_STYLE_CIRCLE, PLOT_LANDSCAPE_LAYER_RECOMMENDED_PLOTS);
    										}
    									}
    This makes the little blue ring appear on goody huts while you are selecting a scout. Nothing too important actually, and it already targets the CvUnit location, so our job is REALLY easy actually.
    In CvPlayer:
    Code:
    	if (GC.getGoodyInfo(eGoody).isBad())
    	{
    		if ((pUnit == NULL) || pUnit->isNoBadGoodies())
    		{
    			return false;
    		}
    	}
    This makes it impossible for the unit to get a bBad result from a goody hut. Thus this is where the actual important bits come into play.
    For the AI, there is also a check done in CvPlayerAI:
    Code:
    	case UNITAI_EXPLORE:
    		iValue += (iCombatValue / 2);
    		iValue += (GC.getUnitInfo(eUnit).getMoves() * 200);
    		if (GC.getUnitInfo(eUnit).isNoBadGoodies())
    		{
    			iValue += 100;
    		}
    		break;
    This happens in a function called AI_unitValue, which is used by the AI to decide what to build. Since it is thinking about units which don't exist yet, this isn't TOO important for us, but it would be a good idea to make this also check the starting promotions for the unit and see if by default it starts with a promotion that has our new field. If it does, then the AI should value it for exploration a bit more. But then we also would have to think about things like if we have a trait that hands out a promotion with this new field for free, then NO unit is more worthwhile than any other unit for exploration in this regard.... AI work can get complicated, but it IS important to do, and often makes you realize things you didn't think about before, so it can prove beneficial in the end. But for the moment, we are actually going to skip this particular section because it is a fairly difficult balance type of decision which depends on how you plan to use your promotion and how you want it to affect your Mod's AI in the end.

    So, both of the functional bits already target CvUnit, that means we just need to make the function they already target account for our new promotion being on the unit. That's going to make life nice and easy for us.

    Any time that a unit gains a promotion, the information from that promotion is loaded onto the unit through the function "void CvUnit::setHasPromotion(PromotionTypes eIndex, bool bNewValue)" If the unit gained the promotion, then bNewValue is true, if he lost it then bNewValue is false. eindex will point at the promotion which the unit just gained (normally it would be called ePromotion, not sure why they chose to use eIndex, they just did).
    We want to wait until after the line reading " if (isHasPromotion(eIndex) != bNewValue)" to add our changes. This is where you'll notice that all of the other changes happen and it just keeps you from double-applying (or removing) the promotion to a unit. Quite a few places in the code it will attempt to add or remove a promotion to a unit without checking if they already have it or not, counting on this check to prevent redundancy. So we will once again slip our new code in with some similar code bits:
    Code:
    		changeBlitzCount((GC.getPromotionInfo(eIndex).isBlitz()) ? iChange : 0);
    [COLOR="#98fb98"]		changeNoBadGoodiesCount((GC.getPromotionInfo(eIndex).isNoBadGoodies()) ? iChange : 0);[/COLOR]
    		changeAmphibCount((GC.getPromotionInfo(eIndex).isAmphib()) ? iChange : 0);
    Once again we have some new concepts popping in, so we'll dissect the new line we have added piece by piece.
    changeNoBadGoodiesCount: This is calling a function to load data onto the unit. This function doesn't exist yet, we have to write it in a moment here. The function we write will be void CvUnit::changeNoBadGoodiesCount(int iChange), so it needs to have an integer value sent to it. That is what the rest of this line is doing, deciding what value to send in.
    GC.getPromotionInfo(eIndex).isNoBadGoodies(): This is checking the promotion to see if it includes our nice new field. Remember that the function we wrote in CvInfos was bool CvPromotionInfos::isNoBadGoodies() const, the first part stated that if someone asked for this function we would provide them with a boolean (yes or no) response.
    ? iChange : 0: This is shorthand for an IF/THEN/ELSE statement. What it says is "IF <the promotion is NoBadGoody> THEN <use the value stored in iChange>, OTHERWISE <use 0>. iChange was set up at the start of the function to be a 1 if the promotion is being added to the unit, and a -1 if it is being removed from the unit. But this function we will write shortly will be called for EVERY promotion that the unit ever gains or loses, just most of the time it'll pass in a 0, meaning that nothing changes.

    So now information will get loaded onto our unit, but we need to write the functions and variables which will control this data. For that, we will look at the line just above our own, Blitz, and see how it handled things.
    Press CTRL+HOME to get to the very start of the file. Now CTRL+F to open a search window and type in "Blitz." The first result you should get taken to will be:
    Code:
    	m_iBlitzCount = 0;
    This is because one of the earliest things done in the file is to set initial values for all of our variables. This makes searching for functions pretty convenient, because while the word "Blitz" is used in this file a LOT, the word "m_iBlitzCount" is only used in the specific areas we want to see for adding our new field. Kinda handy ;) So now search for "m_bBlitzCount" and you'll get these results:
    Code:
    int CvUnit::getBlitzCount() const
    {
    	return m_iBlitzCount;
    }
    
    bool CvUnit::isBlitz() const
    {
    	return (getBlitzCount() > 0);
    }
    
    void CvUnit::changeBlitzCount(int iChange)
    {
    	m_iBlitzCount += iChange;
    	FAssert(getBlitzCount() >= 0);
    }
    
    These are our control and query functions. There is changeBlitzCount, which we started out seeing down in setHasPromotion, and there are the two query functions getBlitzCount and isBlitz. We could get away with just getBlitzCount, but since we are dealing with a boolean it will be more intuitive to ask for a boolean type response by using an is___ statement, hence 3 functions instead of 2. It is just being user-friendly (though we COULD get rid of the "getBlitzCount" function quite easily, and will not actually bother with making one like that ourselves for the new function). The last line in changeBlitzCount "FAssert(getBlitzCount() >= 0);" is an "Assert" statement. These exist COMPLETELY for the Debug DLL and can be very useful for finding where things went wrong. So if you have something you think should NEVER be able to happen (like a negative value for a counting of how many promotions affect you in this case) you can put in an Assert. This is completely useless though if you don't use a debug DLL. It is still good practice to include them however, because someday you might switch to using a debug DLL and then you'll be happy to have them.
    Code:
    	pStream->Read(&m_iBlitzCount);
    Code:
    	pStream->Write(m_iBlitzCount);
    These are our data saving and loading routines. Just like in CvInfos, the order has to match EXACTLY between these two functions (CvUnit::read and CvUnit::write by the way)

    So to use our new boolean tag, we just need to set up copies of these few functions. But remember that CvUnit::isNoBadGoodies() already exists for us, we'll search for that one now and see:
    Code:
    bool CvUnit::isNoBadGoodies() const
    {
    	return m_pUnitInfo->isNoBadGoodies();
    }
    So we can see that it is a "const" function (promises us that it doesn't change anything), and the value which it "returns" is just plucked straight from the UnitInfo for this unit. We will be changing that eventually to include our new variable, and adding another function which lets us control our new variable.

    Now to begin writing our new code. We will go ahead and write the function stuff first since we are right where we want to be for doing it. So make this change/addition:
    Code:
    bool CvUnit::isNoBadGoodies() const
    {
    	return (m_pUnitInfo->isNoBadGoodies() || m_bNoBadGoodies > 0);
    }
    
    [COLOR="#98fb98"]void CvUnit::changeNoBadGoodiesCount(int iChange)
    {
    	m_iNoBadGoodiesCount += iChange;
    	FAssert(m_iNoBadGoodiesCount >= 0);
    }[/COLOR]
    And to explain a bit of the new things in here:
    || - These two parallel lines mean "OR" in C++ language. So if either the UnitInfo claims that this guy is a NoBadGoody OR the unit has at least 1 promotion which makes him NoBadGoody, we will claim that he is indeed NoBadGoody.
    += - This means that we are going to add a value to the number already stored. It is the same as writing "m_iNoBadGoodies = m_iNoBadGoodies + iChange;" but it actually runs just a slight imperceptable bit faster to write it as +=

    So now our functions exist, we will have to remember to declare the new function we wrote in the header file, but first we'll go make that variable exist and ensure that it is saved and loaded with the rest of the game. We can piggy-back on Blitz again and just insert our new information right after those lines we already located earlier:
    Code:
    	m_iBlitzCount = 0;
    [COLOR="#98fb98"]	m_iNoBadGoodiesCount = 0;[/COLOR]
    	m_iAmphibCount = 0;
    Code:
    	pStream->Read(&m_iBlitzCount);
    [COLOR="#98fb98"]	pStream->Read(&m_iNoBadGoodiesCount);[/COLOR]
    	pStream->Read(&m_iAmphibCount);
    Code:
    	pStream->Write(m_iBlitzCount);
    [COLOR="#98fb98"]	pStream->Write(m_iNoBadGoodiesCount);[/COLOR]
    	pStream->Write(m_iAmphibCount);
    Again I remind you, make sure that you always keep things in the ::read and ::write functions listed in the EXACT same order, and never forget to add something to one of them when you add something to the other. But these changes finish up our work in CvUnit.cpp, all that is left now is 2 lines in CvUnit.h and our new tag is completely functional. So open CvUnit.h and let's get done!

    First, we want to declare our new function. We may as well write it next to the function we modified, so just search for "isNoBadGoodies" and you'll find
    Code:
    	bool isNoBadGoodies() const;																					// Exposed to Python
    Once again, the stuff after the semi-colon doesn't actually do anything, it just informs you that this function is available for python to query. We'll deal with python stuff in a moment. First, slip in our new function:
    Code:
    	bool isNoBadGoodies() const;																					// Exposed to Python
    [COLOR="#98fb98"]	void changeNoBadGoodiesCount();[/COLOR]
    	bool isOnlyDefensive() const;																					// Exposed to Python
    Remembering that this has to match exactly with the first line of our function in the .cpp, except with a semi-colon on the end and omitting the CvUnit::

    Now to add the variable, we will again piggy-back on Blitz, so search for "m_iBlitzCount" now and we'll add our variable after it:
    Code:
    	int m_iBlitzCount;
    [COLOR="#98fb98"]	int m_iNoBadGoodiesCount;[/COLOR]
    	int m_iAmphibCount;
    Now the code works and a human will be completely set. But just as we wrote a couple of lines in CvGameTextMgr.cpp to help out the Humans, we need to write a couple of lines in CvUnitAI.cpp to help out the computer. It doesn't read the pedia, so we have to inform it what this new function does. Well.. we can't tell it exactly what it does any better than we have, but we can tell it what it should THINK about what it does. That happens by changing the function int CvUnitAI::AI_promotionValue(PromotionTypes ePromotion) to include our new field.

    If you look through this bit of code, you'll notice that everything which a promotion CAN do has a bit of information in here which will adjust "iValue," sometimes it is based off the UNITAI_ type which this unit follows, sometimes it is just a general bonus. In our case, the new function is only worthwhile for a unit which follows UNITAI_EXPLORE, because no other unit will very likely be anywhere near a goody hut during its lifetime. So we will choose somewhere to add in this bit of code:
    Code:
    	if (GC.getPromotionInfo(ePromotion).isLeader())
    	{
    		// Don't consume the leader as a regular promotion
    		return 0;
    	}
    
    [COLOR="#98fb98"]	if (GC.getPromotionInfo(ePromotion).isNoBadGoodies() && !isNoBadGoodies())
    	{
    		if (AI_getUnitAIType() == UNITAI_EXPLORE)
    		{
    			iValue += 50;
    		}
    	}[/COLOR]
    
    	if (GC.getPromotionInfo(ePromotion).isBlitz())
    	{
    		if ((AI_getUnitAIType() == UNITAI_RESERVE  && baseMoves() > 1) ||
    			AI_getUnitAIType() == UNITAI_PARADROP)
    		{
    
    20 is a pretty incredibly high value. That means if somehow a unit does move onto UNITAI_EXPLORE without having NoBadGoodies capability already, they will REALLY want to take this promotion (the AI will always promote to the highest iValue of all available promotions, but at the end of this function is a random boost of 0 to 15 points in value, so if 2 promotions are within 15 points of each other, the AI might take the lower valued one from time to time. That means any value under 15 is saying that the promotion isn't THAT great, and any values OVER 15 say that this promotion is hands down better than another promotion which is exactly the same, except for missing this field)
    Of course, if a unit isn't already running UNITAI_EXPLORE, they will absolutely never take this promotion unless it also offers some other benefit. So you could write it as:
    Code:
    	if (GC.getPromotionInfo(ePromotion).isNoBadGoodies() && !isNoBadGoodies())
    	{
    		if (AI_getUnitAIType() == UNITAI_EXPLORE)
    		{
    			iValue += 50;
    		}
    		else
    		{
    			iValue += 5;
    		}
    	}
    
    And now other units would grudgingly admit that this promotion is better than nothing at all, and might take it at some point when they are out of other good promotion ideas. Also notice that we made this promotion field only be worth anything at all if the unit isn't already capable of dodging bad results from Goody Huts. This is quite a reasonable approach, but if you design this promotion to be easily lost then it might be worthwhile to allow the AI to seek more than 1 promotion which offers the enhancement.

    And Ba-Da-Boom, we are done making our new code work. You can compile now and work perfectly fine. But partially for practice, and mostly for completeness, we will expose our new function to python in the next section. The CvUnit portion is already done, so we just have to worry about the CvInfos bit.



    Remember that I said all files which start with Cy are for interacting with Python. The one we want right now to expose something from CvInfos will be CyInfoInterface1.cpp. Sometimes you'll need to intead use CyInfoInterface2 or CyInfoInterface3, it just depends on where the file you are modifying already exists at.

    Open up CyInfoInterface1.cpp and search for "CvPromotionInfo," you'll find:
    Code:
    	python::class_<CvPromotionInfo, python::bases<CvInfoBase> >("CvPromotionInfo")
    Everything you add to CvPromotionInfos can be added here to make it available for Python to access. Order doesn't matter at all, but we'll scroll down to where the other booleans are just so we can see how they were done and double check ourselves:
    Code:
    		.def("isLeader", &CvPromotionInfo::isLeader, "bool ()")
    [COLOR="#98fb98"]		.def("isNoBadGoodies", &CvPromotionInfo::isNoBadGoodies, "bool ()")[/COLOR]
    		.def("isBlitz", &CvPromotionInfo::isBlitz, "bool ()")
    The first part in quotations is what python will say when trying to call this function. You could use any phrase you really wish to, but for simplicity you usually just use the same function name as you wrote in CvInfos. The next part points the program at the function which you wrote in CvInfos so that the interface knows how to respond to the query from python. And the last quoted bit is actually just there for your own information to let you know what the function returns (bool) and what it expects to recieve (nothing, or (), in our case). This last bit is also used by automated programs to develop an API for python modders.
     
  4. xienwolf

    xienwolf Chieftain

    Joined:
    Oct 4, 2007
    Messages:
    10,589
    Location:
    Location! Location!
    And since CvUnit was already done for us, we are finished. I'll post what already existed in CyUnit files for you to look at though, and briefly mention what is happening.

    In CyUnit.cpp
    Code:
    bool CyUnit::isNoBadGoodies()
    {
    	return m_pUnit ? m_pUnit->isNoBadGoodies() : false;
    }
    
    Rather than directly going to CvUnit like we had done with the CvInfos case, we have a CyUnit.cpp file and corresponding CyUnit.h file. These are "wrappers" and help to protect the DLL from things being done improperly in python, mostly it protects us from python trying to ask for information from a CvUnit object which doesn't exist anymore (an invalid "pointer"). You'll notice the shorthand form of an IF/THEN/ELSE statment is being used to ensure that there is such a thing as "m_pUnit" which means that the pointer is valid and this particular unit actually exists. If it does exist, then we check CvUnit for the information, otherwise we just toss out a false response.
    Since there was a CyUnit.cpp function, we needed to declare it in CyUnit.h. Fortunately we didn't have any new variables, so it is just a single line change in there:
    Code:
    	bool isNoBadGoodies();
    Which just does as our previous experience in header files would indicate, copies the first line of the function exactly, without the CyUnit:: and with an added semi-colon at the end.

    There is also a CyUnitInterface.cpp file though, this is what tells python what command to use and links it to this CyUnit.cpp function, it works just like what we wrote for CyInfoInterface1.cpp a moment ago:
    Code:
    		.def("isNoBadGoodies", &CyUnit::isNoBadGoodies, "bool ()")
    Again, the first section is what python has to say, the second section is where we send python if they say that, and the last section is just there for user reference and API construction. If you really wanted to be a stinker you could write in there "Thank you for running an API on my mod, but I don't feel like sharing, so there! :p" and everything would still work perfectly fine for your code (or even just leave it blank), but there is no reason to do so, so it's nice to always fill it in appropriately.



    So I guess one of the biggest issues with adding to the XML is having a need, and realizing that it is best approached with a new tag. Sometimes this has more to do with maintenance and making your life easier in the future than with trying to do something ultra-fancy that people who play your mod will even realize is happening.

    This tag is in that spirit. We are going to modify CIV4UnitClassInfos.xml AND CIV4BuildingClassInfos.xml to both gain a new tag which we will call <bUnique>. This tag will make it impossible for any Civilizaiton to build the unit/building unless specifically granted permission through CIV4CivilizationInfos.xml

    In normal BtS and CIV4, this isn't such a big deal. Each Civilization has a single Unique Unit, and it replaces a normal unit. Many modders find this to be fairly boring though and seek to make the civilizations they add more unique. That can be done in the same manner as BtS/CIV4, by replacing existing units with unique versions who have different stats/names/appearances pretty easily. But you can also create a new UnitClass and make this be the only Civilization capable of building it.

    There are numerous ways in which to do this. We could add a <PrereqCivilization> tag to UnitInfos or even UnitClassInfos. We could go into CivilizationInfos and assign every OTHER Civilization a UniqueUnit replacement for this unitclass of NONE. We could also just make the DefaultUnit for this UnitClass be NONE.

    Adding PrereqCivilization would be a decent approach to this matter, and would be very flexible for future use as well. But this is actually harder than it would seem at first glance because of how the XML is loaded (something we will not talk about further until we discuss adding a String field to the XML), and as a result it doesn't work very nicely with Modular Mods which may be designed using your work as a base in the future. So this option is out.

    Making the default UnitClass be NONE seems very practical at first consideration. It only takes one quick edit, and it is something which you wrote for yourself anyway. But this makes the AI do very silly things, because they are designed to do some of their thinking based on what the default unit is for a given Unitclass. Fixing this would be a bit of a headache, so we just remember not to do it and move on.

    So the last option sitting around for us then is to give all of the other Civilizations a NONE replacement for this unit in CivilizationInfos. But in some mods, we are talking about a few hundred total civilizations. So we get smart and design something which automatically fills these in for us. Enter <bUnique>. (remember, start with a lower case "b" to indicate that we are requesting a boolean piece of data)

    The other nifty thing here is, since this tag is fairly simple, it can very easily be applied in another case which acts in much the same way. Thus we will add one tag to 2 different files (really we are adding 2 tags which never interact or share any code, but happen to have the same name and will require much of the same work from us). This also works nicely with the walkthrough in that we can be even more brief about the second time through as a review and highlight of the major important steps.



    Ok, we are adding 2 fields in 2 different places. For the entire walkthrough here we will do Units, then Buildings. Ideally it will always be pretty clear which one we are working on.

    So first we check out which Schema files we need. As before you can read the first functional line in the XML file you are adding to and it tells you which Schema file to use, but you can also just guess. You know that the Schema file has to be in the same folder as the other XML file, so looking in XML/Units we can see there are 2 schema file, FormationSchema, and UnitSchema. I'd guess that UnitSchema probably works for UnitClassInfos, and I'd be right. So open that one up and let's add our tag.

    Remember, we have to tell the computer what type of information to expect from this field by declaring the ElementType. It is VERY BAD if we try to do this for a field which already exists, so first we seach for "bUnique" (using the quotations in the search string so that we only get results for the EXACT same name). No results, so we will need to add:
    Code:
    	<ElementType name="bUnique" content="textOnly" dt:type="boolean"/>
    somewhere in the code. It doesn't completely matter WHERE we do it, just make sure that the lines before and after it are also declaring an ElementType and you are pretty safe.

    The next step is to tell the program when to expect this new field to appear. Lots of files are controlled by this Schema, and we need to make sure we are in our file's area. Fortunately that is as easy as searching for our filename since Firaxis was nice to us that way. So search for "UnitClassInfo" (again, use the quotes in the search string to get an exact match). There are 2 results, one saying what to expect as data inside "UnitClassInfo" and another saying when to expect "UnitClassInfo" to appear. We want the first one (which is declared as "eltOnly" and lists numerous elements after it). So now we add our own element to the list
    Code:
    	<ElementType name="UnitClassInfo" content="eltOnly">
    		<element type="Type"/>
    		<element type="Description"/>
    		<element type="iMaxGlobalInstances"/>
    		<element type="iMaxTeamInstances"/>
    		<element type="iMaxPlayerInstances"/>
    		<element type="iInstanceCostModifier"/>
    		<element type="DefaultUnit"/>
    [color="PaleGreen"]		<element type="bUnique" minOccurs="0"/>[/color]
    	</ElementType>
    
    I have chosen to add the new field at the very end of each XML entry, and to make it optional. That means I only need to include it in any of the entries where I desire to have the field be used (well, I could choose to make it the other way, but why complicate life?)

    Let's go a step further than in the last walkthrough since the XML file is so small this time. I'll pretend that we are making a new starting unit for some Civilization which we want them to have in addition to warriors and scouts and workers. We'll call it a Caveman. So we would need the new UnitClass, including our new XML tag, and it would look like this:
    Code:
    		<UnitClassInfo>
    			<Type>UNITCLASS_CAVEMAN</Type>
    			<Description>TXT_KEY_UNIT_CAVEMAN</Description>
    			<iMaxGlobalInstances>-1</iMaxGlobalInstances>
    			<iMaxTeamInstances>-1</iMaxTeamInstances>
    			<iMaxPlayerInstances>-1</iMaxPlayerInstances>
    			<iInstanceCostModifier>0</iInstanceCostModifier>
    			<DefaultUnit>UNIT_CAVEMAN</DefaultUnit>
    [color="PaleGreen"]			<bUnique>1</bUnique>[/color]
    		</UnitClassInfo>
    Now, we will go through adding to BuildingClassInfos a bit quicker.

    1) Add your new field to the Schema, being careful to make sure the field doesn't already exist, making the location of the tag be easy to remember, and making the tag optional so we don't have to include it when we don't want to use it (minOccurs="0"):

    a) Go to XML/Buildings, we see there is only one Schema file, so we know that is where we want to be.
    b) Search for "bUnique" (including the quote marks), since it is not found add:
    Code:
    	<ElementType name="bUnique" content="textOnly" dt:type="boolean"/>
    c) Search for "BuildingClassInfo" (including the quote marks), add our new field in an easily remembered spot (like at the end):
    Code:
    	<ElementType name="BuildingClassInfo" content="eltOnly">
    		<element type="Type"/>
    		<element type="Description"/>
    		<element type="iMaxGlobalInstances"/>
    		<element type="iMaxTeamInstances"/>
    		<element type="iMaxPlayerInstances"/>
    		<element type="iExtraPlayerInstances"/>
    		<element type="bNoLimit"/>
    		<element type="bMonument"/>
    		<element type="DefaultBuilding"/>
    		<element type="VictoryThresholds"/>
    [color="PaleGreen"]		<element type="bUnique" minOccurs="0"/>[/color]
    	</ElementType>

    2)Add your new field to the main XML file in the new item you wanted to create:
    Code:
    		<BuildingClassInfo>
    			<Type>BUILDINGCLASS_OUTHOUSE</Type>
    			<Description>TXT_KEY_BUILDING_OUTHOUSE</Description>
    			<iMaxGlobalInstances>-1</iMaxGlobalInstances>
    			<iMaxTeamInstances>-1</iMaxTeamInstances>
    			<iMaxPlayerInstances>-1</iMaxPlayerInstances>
    			<iExtraPlayerInstances>0</iExtraPlayerInstances>
    			<bNoLimit>0</bNoLimit>
    			<bMonument>0</bMonument>
    			<DefaultBuilding>BUILDING_OUTHOUSE</DefaultBuilding>
    			<VictoryThresholds/>
    [color="PaleGreen"]			<bUnique>1</bUnique>[/color]
    		</BuildingClassInfo>
    3) Save everything, then load the game and check that everything goes smoothly (no XML error messages while the game is loading, everything appears in the Civilopedia exactly how you would expect it to if your new tag didn't exist at all, which so far it doesn't really).



    Ok, now we are ready to make the DLL load this new tag up for us. Let's do things "right" this time and start in the header files. So open up CvInfos.h and search for
    CvUnitClassInfo (the name of the file you are adding to, without the "s" at the end, so technically it is actually the same as the name of the XML field you listed yourself as an "element" under in the Schema)

    We need a function for the rest of the DLL to use for checking if a UnitClass uses our new field. Since it is a boolean field, we tend to start with the keyword "is," then we just use the name we assigned to the new tag.
    Code:
    	DllExport void setDefaultUnitIndex(int i);
    [color="PaleGreen"]	bool isUnique() const;[/color]
    
    	DllExport bool read(CvXMLLoadUtility* pXML);
    
    Now scroll down to where the variables are declared and we will create a variable to hold the information which gets loaded from the XML
    Code:
    	int m_iDefaultUnitIndex;
    [color="PaleGreen"]	bool m_bUnique;[/color]
    
    
    };
    
    Remember that "m_" indicates that this is available from any function that starts with CvUnitClassInfo::, and the "b" indicates that we are dealing with a boolean. Both of these are totally optional, but make it easy for other programmers (including yourself when you have forgotten about adding this months down the road) to understand what is happeneing quickly.

    Now we will go into CvInfos.cpp and make the additions there which are required of us.

    Again, start your search by looking for CvUnitClassInfo.

    First thing we need to do is to set the initial value of our variable (traditionally always FALSE for boolean fields)
    Code:
    CvUnitClassInfo::CvUnitClassInfo() :
    m_iMaxGlobalInstances(0),
    m_iMaxTeamInstances(0),
    m_iMaxPlayerInstances(0),
    m_iInstanceCostModifier(0),
    [color="PaleGreen"]m_bUnique(false),[/color]
    m_iDefaultUnitIndex(NO_UNIT)
    {
    }
    Remember that the last item in this list is the only one which does NOT have a comma after it. Since we don't want to bother with adding anything to a line we didn't write ourselves, we just add this anywhere EXCEPT at the very end (we could add at the end if we wanted to, order doesn't matter).

    Now we need to create the function which other files will use to find out what value was in the XML for this field. This function can be created almost anywhere, just make sure it isn't inside another function (between an openning brace '{' and a closing brace '}')

    I will again choose to add the new function just before the ::read function:
    Code:
    void CvUnitClassInfo::setDefaultUnitIndex(int i)
    {
    	m_iDefaultUnitIndex = i;
    }
    
    [color="PaleGreen"]bool CvUnitClassInfo::isUnique() const
    {
    	return m_bUnique;
    }[/color]
    
    bool CvUnitClassInfo::read(CvXMLLoadUtility* pXML)
    {
    
    NOTE: As discussed during the last tag addition, for most files there is not a ::read(Stream) and ::write(Stream) function. I am reasonably certain these are used only for checking the Lock_Modified_Assets gameoption, and thus only exist for those files which have a significant impact on what appears in the game, like Units, Promotions, Buildings and the like. This particular file does NOT have these functions, so we have 2 fewer steps than normal.

    And finally we make the game read this data out of the XML by adding something to the ::read(XML) function.
    Code:
    	pXML->GetChildXmlValByName(&m_iInstanceCostModifier, "iInstanceCostModifier");
    [color="PaleGreen"]	pXML->GetChildXmlValByName(&m_bUnique, "bUnique");[/color]
    
    	CvString szTextVal;
    	pXML->GetChildXmlValByName(szTextVal, "DefaultUnit");
    	m_aszExtraXMLforPass3.push_back(szTextVal);
    
    And we are done. Let's move on to BuildingClassInfos now:

    REMEMBER: Order is not important for any of these steps (except in rare cases where it is part of what you are trying to do)

    1) Open CvInfos.h and search for the filename (without the last 's') which you are adding a new field to (in this case CvBuildingClassInfo).
    2) Declare a new Function in the "public:" section so that other files can use it.
    Code:
    	DllExport bool isMonument() const;				// Exposed to Python
    
    [color="PaleGreen"]	bool isUnique() const;[/color]
    	// Arrays
    
    	DllExport int getVictoryThreshold(int i) const;				// Exposed to Python
    
    3) Declare a new Variable in the "protected:" section so that no other files can use it directly (they have to go through the Function to find out what it is, and nothing is able to change it since we didn't write a function for them to change it with)
    Code:
    	bool m_bMonument;
    [color="PaleGreen"]	bool m_bUnique;[/color]
    
    	// Arrays
    
    	int* m_piVictoryThreshold;
    4) Open CvInfos.cpp and again search for the filename (without the last 's') which you are adding to (CvBuildingClassInfo for right now)
    5) Set an Initial Value in the "Default constructor"
    Code:
    m_bMonument(false),
    [color="PaleGreen"]m_bUnique(false),[/color]
    m_piVictoryThreshold(NULL)
    6) Create the function for other files to access the new value
    Code:
    bool CvBuildingClassInfo::isMonument() const
    {
    	return m_bMonument;
    }
    
    [color="PaleGreen"]bool CvBuildingClassInfo::isUnique() const
    {
    	return m_bUnique;
    }[/color]
    
    // Arrays
    
    int CvBuildingClassInfo::getVictoryThreshold(int i) const
    {
    7 & 8) Include your new field in the ::read(Stream) and ::write(Stream) functions. NOTE: Not always required! Only do this if there are already functions present for the task. In our case, this isn't required
    9) Load your data from the XML (using the ::read(XML) function)
    Code:
    	pXML->GetChildXmlValByName(&m_bMonument, "bMonument");
    [color="PaleGreen"]	pXML->GetChildXmlValByName(&m_bUnique, "bUnique");[/color]
    
    	pXML->SetVariableListTagPair(&m_piVictoryThreshold, "VictoryThresholds", sizeof(GC.getVictoryInfo((VictoryTypes)0)), GC.getNumVictoryInfos());
    
    And we are now done. Save everything, compile, make sure you are using your new DLL in your mod, and load the game to make sure that everything still works. If you had to do steps 7&8 you would want to remember to try saving and loading the game once to ensure you didn't mess up the order between those two functions and break the ability to load savegames.

    Remember during this test also that nothing should be any different than it was before adding your new tag right now. The DLL does have the information from your XML, but it doesn't know what to do with it, so nothing happens.



    For most tags is is pretty straightforward to write the text keys on them. You are just adding a bullet-point to a list of other bullet-points that auto-parses and tells you about the unit. But for this particular set of tags it is a bit trickier. You don't usually SEE any information about a UnitClass, except indirectly.

    The one place that it is most important is in the Civilopedia where it states that a unit is a UU for one Civ or another. This is pretty vital information though as it keeps people from wondering why they cannot build a particular unit. For example, the Quechua states in the Pedia "Unique Unit for the Incan Empire. Replaces Warrior" Which immediately tells everyone that if they want this unit, they need to play Incan and that they won't get to build Warriors. Well, this new tag is meant to allow us to say "If you want to use a Caveman, you have to play American. But you'll still be able to build everything else like usual." So we need the Civilopedia to make that clear for us. The other place that this information will be important is in the Dawn of Man screen and the Civilization section of the Civilopedia.

    One nice thing is that while we are digging through the code we can improve on how other things are done. Like right now Bears display in the Civilopedia, but they don't make it clear that you find them in the wild, rather than being able to build them yourself. For a new player this might not be completely clear to them, and with this tag we can make it very easy to understand (you add <bUnique>1</bUnique> to UNITCLASS_BEAR, then specify for the CIVILIZATION_BARBARIAN that they use UNIT_BEAR as a UU for UNITCLASS_BEAR).

    Alrighty then, adding in your code...

    We can go ahead and start from the top of the file since I already know everywhere I plan to add this. Normally it would be a fairly longer process of going where you can think of first, then checking things out in game and realizing you want it to be ever so slightly different, so coming back to tweak everything. We'll wind up with those kind of scenarios later on I imagine :) For now it is good to keep it somewhat simple.

    First spot we come to is adding to the Civilization Information. This will cover both the Dawn of Man screen at the start of a new game AND the Civilizations section of the Civilopedia, which naturally covers the PLAY NOW screen for us as well.

    Find (in CvGameTextMgr.cpp): "void CvGameTextMgr::parseCivInfos(CvWStringBuffer &szInfoText, CivilizationTypes eCivilization, bool bDawnOfMan, bool bLinks)"

    Scroll down to:
    Code:
    		// Free Units
    		szText = gDLL->getText("TXT_KEY_FREE_UNITS");
    		if (bDawnOfMan)
    		{
    			szTempString.Format(L"%s: ", szText.GetCString());
    		}
    		else
    		{
    			szTempString.Format(NEWLINE SETCOLR L"%s" ENDCOLR , TEXT_COLOR("COLOR_ALT_HIGHLIGHT_TEXT"), szText.GetCString());
    		}
    		szInfoText.append(szTempString);
    
    		bool bFound = false;
    		for (int iI = 0; iI < GC.getNumUnitClassInfos(); ++iI)
    		{
    			eDefaultUnit = ((UnitTypes)(GC.getCivilizationInfo(eCivilization).getCivilizationUnits(iI)));
    			eUniqueUnit = ((UnitTypes)(GC.getUnitClassInfo((UnitClassTypes) iI).getDefaultUnitIndex()));
    			if ((eDefaultUnit != NO_UNIT) && (eUniqueUnit != NO_UNIT))
    Starting with "bool bFound = false;" we are going to replace everything down till:
    Code:
    					szInfoText.append(szBuffer);
    					bFound = true;
    				}
    			}
    		}
    
    		if (!bFound)
    		{
    			szText = gDLL->getText("TXT_KEY_FREE_UNITS_NO");
    			if (bDawnOfMan)
    			{
    				szTempString.Format(L"%s", szText.GetCString());
    
    Stopping with everything after (and including) "if (!bFound)" being left unchanged.

    I am just going to directly post what I have, including the comment blocks, to show a "good housekeeping" approach to inserting your code. Especially when you replace large chunks of code like this it is VERY good practice, because it is easy to undo what you did.

    Code:
    		else
    		{
    			szTempString.Format(NEWLINE SETCOLR L"%s" ENDCOLR , TEXT_COLOR("COLOR_ALT_HIGHLIGHT_TEXT"), szText.GetCString());
    		}
    		szInfoText.append(szTempString);
    
    /*************************************************************************************************/
    /**	Xienwolf Tweak							10/01/08											**/
    /**																								**/
    /**				Allows display of units which are not a replacement, but Unique					**/
    /*************************************************************************************************/
    /**								---- Start Original Code ----									**
    		bool bFound = false;
    		for (int iI = 0; iI < GC.getNumUnitClassInfos(); ++iI)
    		{
    			eDefaultUnit = ((UnitTypes)(GC.getCivilizationInfo(eCivilization).getCivilizationUnits(iI)));
    			eUniqueUnit = ((UnitTypes)(GC.getUnitClassInfo((UnitClassTypes) iI).getDefaultUnitIndex()));
    			if ((eDefaultUnit != NO_UNIT) && (eUniqueUnit != NO_UNIT))
    			{
    				if (eDefaultUnit != eUniqueUnit)
    				{
    					// Add Unit
    					if (bDawnOfMan)
    					{
    						if (bFound)
    						{
    							szInfoText.append(L", ");
    						}
    						szBuffer.Format((bLinks ? L"<link=literal>%s</link> - (<link=literal>%s</link>)" : L"%s - (%s)"),
    							GC.getUnitInfo(eDefaultUnit).getDescription(),
    							GC.getUnitInfo(eUniqueUnit).getDescription());
    					}
    					else
    					{
    						szBuffer.Format(L"\n  %c%s - (%s)", gDLL->getSymbolID(BULLET_CHAR),
    							GC.getUnitInfo(eDefaultUnit).getDescription(),
    							GC.getUnitInfo(eUniqueUnit).getDescription());
    					}
    					szInfoText.append(szBuffer);
    					bFound = true;
    				}
    			}
    		}
    /**								----  End Original Code  ----									**/
    		bool bFound = false;
    		for (int iI = 0; iI < GC.getNumUnitClassInfos(); ++iI)
    		{
    			eUniqueUnit = ((UnitTypes)(GC.getCivilizationInfo(eCivilization).getCivilizationUnits(iI)));
    			eDefaultUnit = ((UnitTypes)(GC.getUnitClassInfo((UnitClassTypes) iI).getDefaultUnitIndex()));
    			if (eUniqueUnit != NO_UNIT)
    			{
    				if (eDefaultUnit == NO_UNIT || GC.getUnitClassInfo((UnitClassTypes) iI).isUnique() || eDefaultUnit != eUniqueUnit)
    				{
    					// Add Unit
    					if (bDawnOfMan)
    					{
    						if (bFound)
    						{
    							szInfoText.append(L", ");
    						}
    						if (GC.getUnitClassInfo((UnitClassTypes) iI).isUnique())
    						{
    							szBuffer.Format((bLinks ? L"<link=literal>%s</link>" : L"%s"), GC.getUnitInfo(eUniqueUnit).getDescription());
    						}
    						else
    						{
    							szBuffer.Format((bLinks ? L"<link=literal>%s</link> - (<link=literal>%s</link>)" : L"%s - (%s)"), GC.getUnitInfo(eUniqueUnit).getDescription(), GC.getUnitInfo(eDefaultUnit).getDescription());
    						}
    					}
    					else
    					{
    						if (GC.getUnitClassInfo((UnitClassTypes) iI).isUnique())
    						{
    							szBuffer.Format(L"\n  %c%s", gDLL->getSymbolID(BULLET_CHAR), GC.getUnitInfo(eUniqueUnit).getDescription());
    						}
    						else
    						{
    							szBuffer.Format(L"\n  %c%s - (%s)", gDLL->getSymbolID(BULLET_CHAR), GC.getUnitInfo(eUniqueUnit).getDescription(), GC.getUnitInfo(eDefaultUnit).getDescription());
    						}
    					}
    					szInfoText.append(szBuffer);
    					bFound = true;
    				}
    			}
    		}
    /*************************************************************************************************/
    /**	Tweak									END													**/
    /*************************************************************************************************/
    
    		if (!bFound)
    		{
    			szText = gDLL->getText("TXT_KEY_FREE_UNITS_NO");
    			if (bDawnOfMan)
    
    Remember, because I don't include a / at the end of the line saying "Start Original Code", everything down until "End Original Code" (where I do finally use the full command to end a comment */) is completely ignored by the compiler. This chunk is the original code, exact and unaltered. There is quite a bit which I did not change at all in the code which I DO include after that area, but my changes are scattered around in the function and it is easier to comment out a LOT of things with one big change than to make dozens of smaller comments scattered through the code section.

    The first thing which is different is that I fixed a small error in how the code was originally written (one that didn't actually matter in the end). Look at how they defined eDefaultUnit and eUniqueUnit. Each one looks up what should be stored as the other (UniqueUnit is the one which the Civ uses and possibly nobody else does. Default unit is the one which most people use, but this Civ might not). As I said though, the way things were handled after that point made it not really matter that this mistake was made.

    Changed:
    Code:
    			eDefaultUnit = ((UnitTypes)(GC.getCivilizationInfo(eCivilization).getCivilizationUnits(iI)));
    			eUniqueUnit = ((UnitTypes)(GC.getUnitClassInfo((UnitClassTypes) iI).getDefaultUnitIndex()));
    To:
    Code:
    			eUniqueUnit = ((UnitTypes)(GC.getCivilizationInfo(eCivilization).getCivilizationUnits(iI)));
    			eDefaultUnit = ((UnitTypes)(GC.getUnitClassInfo((UnitClassTypes) iI).getDefaultUnitIndex()));
    Next difference is that I do not worry about excluding the case where the default unit is NO_UNIT. Even though it is a bad thing to have this happen in general, there might be a day where someone forgets this fact, or fixes the reason we avoid it. So since it won't hurt my function to work with a non-existant Default Unit, I accept that possibility.

    Changed:
    Code:
    			if ((eDefaultUnit != NO_UNIT) && (eUniqueUnit != NO_UNIT))
    To:
    Code:
                if (eUniqueUnit != NO_UNIT)
    In the next line I have 3 ways of knowing that we are dealing with a unique unit. Either the default is to have no unit, but this civ DOES have a unit, or our new tag is active for this unitclass, or the normal method of noticing that this Civ uses a different unit than what most Civs use. Everything after that point is working to create our text output in a fairly compact, but readable and translatable format. This is often quite tricky to get working precisely how you want it to.
     
  5. xienwolf

    xienwolf Chieftain

    Joined:
    Oct 4, 2007
    Messages:
    10,589
    Location:
    Location! Location!
    Changed:
    Code:
    				if (eDefaultUnit != eUniqueUnit)
    To:
    Code:
    				if (eUniqueUnit != (UnitTypes)GC.getCivilizationInfo(eCivilization).getHero() && (eDefaultUnit == NO_UNIT || GC.getUnitClassInfo((UnitClassTypes) iI).isUnique() || eDefaultUnit != eUniqueUnit))
    You'll notice that bFound is checked in a couple of places. This is referred to as a "Flagging Variable" and we use it to track what has happened during chunks of our code where we might diverge a bit from our original intention. In this case, we were constructing a text output for a Civilization, but then we had to go and do a loop over all the UnitClasses. While the loop is happening we slowly build up a line of text, and at the end we need to know if we put anything in that line or not. bFound helps with both of those by just telling us that we have located at least 1 result that was considered a Unique Unit. So anything after that is seperated by a comma in the text string (to allow us to use a single line).

    Code:
    						if (bFound)
    						{
    							szInfoText.append(L", ");
    						}
    Also, if we did NOT find anything at all, then we know this Civilization has no Unique Units and we go ahead and generate some informative text so that the user doesn't wonder if we simply forgot to document the UU's for this Civ (many players don't think about how the text is made, and if asked would be willing to claim that you wrote everything by hand, until they thought about it a bit more critically at least).
    Code:
    		if (!bFound)
    		{
    			szText = gDLL->getText("TXT_KEY_FREE_UNITS_NO");
    			if (bDawnOfMan)
    			{
    				szTempString.Format(L"%s", szText.GetCString());
    			}
    			else
    			{
    				szTempString.Format(L"%s  %s", NEWLINE, szText.GetCString());
    			}
    			szInfoText.append(szTempString);
    			bFound = true;
    		}
    Breaking down the code so you can understand how to format your own Text Outputs a little bit better:

    Code:
    		bool bFound = false;
    First, we set the variable bFound to FALSE to act as a flagging variable (as just discussed, so we can add a comma or realize that we didn't find any Unique Units at all).

    Code:
    		for (int iI = 0; iI < GC.getNumUnitClassInfos(); ++iI)
    		{
    Now we start a loop over all of the unitclasses in the game so we can check for any unique replacements.

    Code:
    			eUniqueUnit = ((UnitTypes)(GC.getCivilizationInfo(eCivilization).getCivilizationUnits(iI)));
    			eDefaultUnit = ((UnitTypes)(GC.getUnitClassInfo((UnitClassTypes) iI).getDefaultUnitIndex()));
    Since we will be checking what the basic unit for this unitclass is, AND what unit the Civilization we are currently checking uses for this unitclass VERY often, we store them as variables. The two functions, getCivilizationUnits(int) and getDefaultUnitIndex() each return an integer value. But using the Casting Command of (UnitTypes) transforms the integer value into an index value of UnitTypes style. This can be thought of as a sort of "backup" to prevent you from accidentally trying to use these as just numbers sometime later, since it isn't the number which is important, but rather which XML dataset you are pointing at, and they just happen to be referred to in a numerical manner.

    Anyway, we store these as variables because each time you see a period in each of those lines it is forcing the code to run out and fetch a command somewhere. So to create these variables, the code runs 2 commands and one or two Variables Castings (UnitTypes for both of them, and also UnitClassTypes for the Default Unit). If we run the full command each time we want to check either of these values, that means we personally type a lot more code, and it means while the game is running the computer runs an extra 3 or 4 steps each time. It won't be a very noticeable increase in processing time by itself, but if you had thousands of unitclasses it might start to stack up enough to become a noticeable performance loss.

    Code:
    			if (eUniqueUnit != NO_UNIT)
    			{
    If the Civilization isn't capable of building anything in this particular UnitClass, then we don't need to bother checking if that nothing is unique.

    Code:
    				if (GC.getUnitClassInfo((UnitClassTypes) iI).isUnique() || eDefaultUnit != eUniqueUnit)
    				{
    Remember, the || symbol means OR. So there are 2 possible ways to get into this IF statement:
    1. If our new tag is being used to indicate that this IS a unique unit
    2. If the Default Unit and the Unique Units aren't the same thing (which covers the case of the Default Unit not existing at all, since we already confirmed that the Unique Unit DOES exist in the previous IF statement)

    Code:
    					if (bDawnOfMan)
    					{
    bDawnOfMan was one of the variables passed to this function when it was requested. It is meant to indicate that we are preparing the text for the Dawn of Man Screen, and so we change how the format of our text appears since we know it is meant to be more verbose.

    Code:
    						if (bFound)
    						{
    							szInfoText.append(L", ");
    						}
    If the code gets to this statement, it means that we ARE dealing with a Unique Unit. Since we haven't provided any method of changing bFound yet, the first time you get to this point it will be set to FALSE, so this is ignored. But as soon as we change the value of bFound (which we are about to), it will place a comma so seperate the list of multiple Unique Units. (.append(L",") command is basically typing a comma and a space into the text line which we are building)

    Code:
    						if (GC.getUnitClassInfo((UnitClassTypes) iI).isUnique())
    						{
    							szBuffer.Format((bLinks ? L"<link=literal>%s</link>" : L"%s"), GC.getUnitInfo(eUniqueUnit).getDescription());
    						}
    						else
    						{
    							szBuffer.Format((bLinks ? L"<link=literal>%s</link> - (<link=literal>%s</link>)" : L"%s - (%s)"), GC.getUnitInfo(eUniqueUnit).getDescription(), GC.getUnitInfo(eDefaultUnit).getDescription());
    						}
    
    Here we are placing the text which will announce the Unique Unit. For the case of a Swordsman replacing what is normally an Axeman you would see: Swordsman - (Axeman). We are using an IF statement to test for our new <bUnique> tag here, stating that if the unitclass was identified as UNIQUE, then it doesn't actually replace anything at all, other Civilizations simply do not have access to this kind of a unit at all. And so we make the text display be simpler: Swordsman

    bLinks is another variable which was sent to this function to dictate how our text should look. If this was set then we will attempt to provide clickable links which take the player to the appropriate entry in the Civilopedia. But since one place we use this text is in a mouse-over of the Civilization Information (somewhere that it is impossible to click on the data, because moving your mouse makes it disappear), we don't ALWAYS want to have links provided. However, that particular case is handled later, so mostly this will just appear during the PLAY NOW screen where is would be odd to allow the player to exit out to the pedia.

    <link=literal> - This command tells the text string to attempt to create a link in the Civilopedia by looking for things which end with exactly the same name as is typed inside of it (so if you said <link=literal>Warrior</link> then it would create a link to UNIT_WARRIOR in the Pedia. Of course, if you also had a promotion called PROMOTION_WARRIOR, then the game would choose whichever one of the two it happened to find first. That is why it is fairly important to use unique names for everything you design, at least in the code.

    %s - Using the % symbol within quotes while constructing a string tells the program that you want it to insert a variable here. %s means a string, %d means an integer, %f means a float, and %e means Scientific Notation numbers (so 125 would be displayed as "1.25e2" or something like that). %c means a special character, like the :strength: or :commerce: symbols. There are a few things you can do like saying %.4f to force the float to have 4 numbers after the decimal, but you should look those up online since they are relatively "special-case" for when you would want to use them. For now, just know that you can use this to insert variable items into your output, and understand that if you use more than 1 variable entry, they are used in the exact order that you list them after ending the quotation (seperated by commas). So: L"%s - (%s)", GC.getUnitInfo(eUniqueUnit).getDescription(), GC.getUnitInfo(eDefaultUnit).getDescription()) will list Swordsman - (Axeman), while L"%s - (%s)"), GC.getUnitInfo(eDefaultUnit).getDescription(), GC.getUnitInfo(eUniqueUnit).getDescription()) would list Axeman - (Swordsman).

    ? : - Remember that the ? and : symbols are shortcut commands for an IF THEN ELSE statement. So as used here, IF (bLinks) THEN (use the <link-literal> version) ELSE (use the normal, unlinked, version)

    Code:
    					else
    					{
    						if (GC.getUnitClassInfo((UnitClassTypes) iI).isUnique())
    						{
    							szBuffer.Format(L"\n  %c%s", gDLL->getSymbolID(BULLET_CHAR), GC.getUnitInfo(eUniqueUnit).getDescription());
    						}
    						else
    						{
    							szBuffer.Format(L"\n  %c%s - (%s)", gDLL->getSymbolID(BULLET_CHAR), GC.getUnitInfo(eUniqueUnit).getDescription(), GC.getUnitInfo(eDefaultUnit).getDescription());
    						}
    					}
    Remember that this ELSE statement links to the IF (bDawnOfMan) statement. So we are now dealing with all of the text outputs which are not found in the Dawn of Man location. Absolutely NONE of those display locations are appropriate to have links, so you don't see any checks for bLinks here. Again, we handle the case of our new isUnique check seperate from the normal version so that the unit which is replaced isn't displayed. You'll also notice that the %c command is used in forming this string.

    \n - This command in a string tells the computer to make a new line. We also follow it with a couple of spaces so that it appears our information has been indented in the final output.

    gDLL->getSymbolID(BULLET_CHAR) - This grabs an image from the gamefonts.tga file found in Assets/res/Fonts. In this case the one identified as BULLET_CHAR, which is the tiny grey dot. If you look for BULLET_CHAR in the file CvEnums.h you will see listed around it the names of the other items located on the same row in the gamefonts.tga file, listed in order. You can use absolutely any of these in the same manner, and add more of your own by properly editing the tga file (don't forget the Alpha Layer), CvEnums.h, and a few other locations (if you want to figure this out for yourself, search for any one of the names listed in the entire DLL project. I advise you choose something OTHER than BULLET_CHAR however, as that one is used in many places. Some of the others are not used at all, so will show you exactly where to add any new information to include an extra character of your own design).


    That ends what code we have changed here. But I would like to point out something you might not have noticed yet: We are dealing with 2 different output strings right now.

    The final item which will be displayed to the player in the game is identified as szInfoText. Right now we are formatting things and trying to get it all to look pretty, so we are dealing with smaller chunks little by little and piecing them together like a jigsaw puzzle. The main string we are editing here is szBuffer, that is where we create our display of Swordsman - (Axeman). But when we add our comma between multiple Unique Units, we add that to szInfoText! The reason for that is that right at the end of our edit we dump this single Unique Unit into szInfoText using the command szInfoText.append(szBuffer). At that point we also set our bFound flag to True, because we have now finally added a unit to our output display (or if we already added one, then we just re-affirm that bFound is indeed supposed to be True). Then, when it continues to check the other UnitClasses, if it does happen to find another Unique Unit, it will add the comma and space to the main output, szInfoText, and then create a completely new szBuffer. That is why when we constructed szBuffer we used szBuffer.Format instead of szBuffer.append.

    It is outside of our edit, but just for completeness, I'll cover this line as well:

    gDLL->getText("TXT_KEY_FREE_UNITS_NO") - This command is fetching text for us from the Assets/XML/Text folder. If anything in this folder uses <Type>TXT_KEY_FREE_UNITS_NO</Type>, then it will place here the text identified in our matching Language under that type. Otherwise it will just use exactly what is written here, and our users will report that we forgot to write a text key again :) Many of the same rules apply in creating a text key in XML as apply creating strings here in the DLL, but there are some minor differences. Those will come up in future walkthroughs when we need to create text keys with our new fields as an output though.


    I'll not cover the specifics again, but here is the code you would use just a little while later in the same section for displaying the extra Unique Buildings. It is almost exactly the same, except now we are dealing with BuildingClasses instead of UnitClasses really. You are making the changes inside of a loop over all buildingclasses, if this Civ can build any building for this class, we check if that building is unique (doesn't match the default, or uses our new tag). If it IS unique, then we do some fancy footwork to make it display in an appropriate format for each possible location that calls this function.

    Code:
    		bFound = false;
    		for (int iI = 0; iI < GC.getNumBuildingClassInfos(); ++iI)
    		{
    /*************************************************************************************************/
    /**	Xienwolf Tweak							10/01/08											**/
    /**																								**/
    /**				Allows display of buildings which are not a replacement, but Unique				**/
    /*************************************************************************************************/
    /**								---- Start Original Code ----									**
    			eDefaultBuilding = ((BuildingTypes)(GC.getCivilizationInfo(eCivilization).getCivilizationBuildings(iI)));
    			eUniqueBuilding = ((BuildingTypes)(GC.getBuildingClassInfo((BuildingClassTypes) iI).getDefaultBuildingIndex()));
    			if ((eDefaultBuilding != NO_BUILDING) && (eUniqueBuilding != NO_BUILDING))
    			{
    				if (eDefaultBuilding != eUniqueBuilding)
    				{
    					// Add Building
    					if (bDawnOfMan)
    					{
    						if (bFound)
    						{
    							szInfoText.append(L", ");
    						}
    						szBuffer.Format((bLinks ? L"<link=literal>%s</link> - (<link=literal>%s</link>)" : L"%s - (%s)"),
    							GC.getBuildingInfo(eDefaultBuilding).getDescription(),
    							GC.getBuildingInfo(eUniqueBuilding).getDescription());
    					}
    					else
    					{
    						szBuffer.Format(L"\n  %c%s - (%s)", gDLL->getSymbolID(BULLET_CHAR),
    							GC.getBuildingInfo(eDefaultBuilding).getDescription(),
    							GC.getBuildingInfo(eUniqueBuilding).getDescription());
    					}
    					szInfoText.append(szBuffer);
    					bFound = true;
    				}
    			}
    		}
    /**								----  End Original Code  ----									**/
    			eUniqueBuilding = ((BuildingTypes)(GC.getCivilizationInfo(eCivilization).getCivilizationBuildings(iI)));
    			eDefaultBuilding = ((BuildingTypes)(GC.getBuildingClassInfo((BuildingClassTypes) iI).getDefaultBuildingIndex()));
    			if (eUniqueBuilding != NO_BUILDING)
    			{
    				if (GC.getBuildingClassInfo((BuildingClassTypes)iI).isUnique() || eDefaultBuilding != eUniqueBuilding)
    				{
    					// Add Unit
    					if (bDawnOfMan)
    					{
    						if (bFound)
    						{
    							szInfoText.append(L", ");
    						}
    						if (GC.getBuildingClassInfo((BuildingClassTypes)iI).isUnique())
    						{
    							szBuffer.Format((bLinks ? L"<link=literal>%s</link>" : L"%s"), GC.getBuildingInfo(eUniqueBuilding).getDescription());
    						}
    						else
    						{
    							szBuffer.Format((bLinks ? L"<link=literal>%s</link> - (<link=literal>%s</link>)" : L"%s - (%s)"), GC.getBuildingInfo(eUniqueBuilding).getDescription(), GC.getBuildingInfo(eDefaultBuilding).getDescription());
    						}
    					}
    					else
    					{
    						if (GC.getBuildingClassInfo((BuildingClassTypes)iI).isUnique())
    						{
    							szBuffer.Format(L"\n  %c%s", gDLL->getSymbolID(BULLET_CHAR), GC.getBuildingInfo(eUniqueBuilding).getDescription());
    						}
    						else
    						{
    							szBuffer.Format(L"\n  %c%s - (%s)", gDLL->getSymbolID(BULLET_CHAR), GC.getBuildingInfo(eUniqueBuilding).getDescription(), GC.getBuildingInfo(eDefaultBuilding).getDescription());
    						}
    					}
    					szInfoText.append(szBuffer);
    					bFound = true;
    				}
    			}
    		}
    /*************************************************************************************************/
    /**	Tweak									END													**/
    /*************************************************************************************************/
    

    Ok, I am back to writing once again. Those of you reading this for the first time don't know how long of a break I took, but it was long enough that you will probably notice a rather severe change in tone for how I write the next few sections. I am seeking to avoid such a long break again any time soon, but just in case that should happen I am going to try and go a little bit quicker through the details from here on, unless someone asks for considerable clarification after having read some of this and I go back for a complete overhaul. But the main idea is to give you some examples you can place in the code yourself and to hope that you poke around at things while you work, and eventually that you challenge yourself to write the functions without reference to this guide until the point where something goes wrong :)


    So right now we have managed to modify the Dawn of Man Screen and the Civilization Information (mouseover text you get when you look in the Civilopedia, and the Play Now screen text). The remaining location where this field will have an impact is on the Unit/Building itself, to inform people browsing the Civilopedia that there are restrictions on being able to access these items.

    For the units, there is a slight complication we have to deal with during this process, and that is finding the right function to modify. As with most things in Civ code, the name is somewhat "guessable" you want to have helpful information about a unit, so you can search for unithelp and see what comes up. To our great relief, we get a response! void CvGameTextMgr::setUnitHelp(CvWStringBuffer &szString, const CvUnit* pUnit, bool bOneLine, bool bShort), but there is a problem here which you may not notice until you are getting comfortable in the code. Look at the second argument which is passed in to this function (the things in the parenthesis). It is const CvUnit* pUnit, which is a pointer to a CvUnit object, that means a unit which actually exists in the game and can be clicked on and given orders. Our information is important for units you are thinking about building someday, and only exist as information in the Civilopedia, that is a UnitTypes eUnit! Well, search for ::setUnitHelp one more time and you'll find that there is another function with the same name (but different Overload values... that is another way to refer to the things in the parenthesis) void CvGameTextMgr::setUnitHelp(CvWStringBuffer &szBuffer, UnitTypes eUnit, bool bCivilopediaText, bool bStrategyText, bool bTechChooserText, CvCity* pCity) This time the second value we see is the one we want, and then it even mentions the Civilopedia after that, how fortunate!

    So, make sure that you are in the proper location (the second function, which refers to a UnitTypes object) and we can look at where we want our text to display.

    For the first few dozen lines, the program is just setting up variables to use later on. First, we make sure that the unit we are being asked for information about really exists. If it doesn't, then we don't need information on it so we get out of the function as quickly as possible. Since another one of the variables we could have been given when this function was called is a pointer to a city, we see if that exists, and check who owns it if it does. Then, if we aren't in the Civilopedia Page for this unit where the name is displayed somewhere else in a special font, we want to start our information by informing the player what the name of this unit is. And after that function we hit upon a comment that pinpoints where we really should do our work:

    Code:
    	// test for unique unit
    	UnitClassTypes eUnitClass = (UnitClassTypes)GC.getUnitInfo(eUnit).getUnitClassType();
    	UnitTypes eDefaultUnit = (UnitTypes)GC.getUnitClassInfo(eUnitClass).getDefaultUnitIndex();
    

    First thing we needed to do was figure out what UnitClass this particular unit belongs to. The UnitClass is the link between Unique Units, so it is vital information. Then we check what the default unit is for that unitclass. Notice that this time they got the Default variable lined up correctly. Good job Firaxis! ;) First we'll take a look at how the information is currently displayed to the player, maybe we don't need to improve on it, that'd sure be nice!

    Code:
    	if (NO_UNIT != eDefaultUnit && eDefaultUnit != eUnit)
    	{
    		for (iI  = 0; iI < GC.getNumCivilizationInfos(); ++iI)
    		{
    			UnitTypes eUniqueUnit = (UnitTypes)GC.getCivilizationInfo((CivilizationTypes)iI).getCivilizationUnits((int)eUnitClass);
    			if (eUniqueUnit == eUnit)
    			{
    				szBuffer.append(NEWLINE);
    				szBuffer.append(gDLL->getText("TXT_KEY_UNIQUE_UNIT", GC.getCivilizationInfo((CivilizationTypes)iI).getTextKeyWide()));
    			}
    		}
    
    		szBuffer.append(NEWLINE);
    		szBuffer.append(gDLL->getText("TXT_KEY_REPLACES_UNIT", GC.getUnitInfo(eDefaultUnit).getTextKeyWide()));
    	}
    
    Ok, so if the default unit exists, and the unique unit is different than it, we actually process something. Well already we have something to change, because that whole "If the default unit exists" thing just isn't going to work so well for us. We honestly don't care if the default exists, as stated during the Dawn of Man adjustments. Really if we are in a situation where the default unit doesn't exist, then we already know that this most certainly IS a unique unit, as we already checked if this unit exists, so we know it doesn't match the default automatically.

    Next we enter into a Loop over all the different type of Civilizations. The only way to have a unique unit is through your civilization, so we are nicely covered here (if at some future point you make it possible to have Unique Units based on your current religion or Hairstyle, you would have to add loops over whichever of those you linked things to, but we'll worry about that when it matters, eh?)

    Then we check what the unique unit for each civilization type will be. Again, storing it in a variable so that we can use it multiple times nice and quickly, though in truth we only use it once anyway, so right now it is SLIGHTLY slower, but it is a good habit to store things you have to look up in local variables, because only using it once means you are so very barely slower that no human and few machines can notice, but if you didn't store it locally and used it even just 2 times, then the difference in speed can become large enough to be noticed by a profiling function (ie - by a machine, but not by a human)

    Now if the unique unit for the Civilization in question matches the unit we are working with, we know one person who has access to this particular unit, and we give them a line all to themself saying that they are able to build this unit. But we don't stop our loop over all civilizations, so if another Civ can ALSO build this unit, they will get a line for themselves as well.

    After that, the last section is outside of the loop, so this line would only appear once, and it will appear even if no Civilization is able to build this unit. We make a new line which states what the default unit had been. This is an area where it was important for us not to do anything if the default unit didn't exist, so if we allow ourselves to do the loop over all civilizations even if there wasn't a default unit, then we need to stop ourselves from doing THIS line in that case.

    Ok, and now for how we are going to re-write the function:

    Code:
    	if (GC.getUnitClassInfo(eUnitClass).isUnique() || eDefaultUnit != eUnit)
    	{
    		bool bFound = false;
    		for (iI  = 0; iI < GC.getNumCivilizationInfos(); ++iI)
    		{
    			UnitTypes eUniqueUnit = (UnitTypes)GC.getCivilizationInfo((CivilizationTypes)iI).getCivilizationUnits((int)eUnitClass);
    			if (eUniqueUnit == eUnit)
    			{
    				szBuffer.append(NEWLINE);
    				szBuffer.append(gDLL->getText("TXT_KEY_UNIQUE_UNIT", GC.getCivilizationInfo((CivilizationTypes)iI).getTextKeyWide()));
    				bFound = true;
    			}
    		}
    
    		if (bFound)
    		{
    			if (NO_UNIT != eDefaultUnit && eDefaultUnit != eUnit && !GC.getUnitClassInfo(eUnitClass).isUnique())
    			{
    				szBuffer.append(NEWLINE);
    				szBuffer.append(gDLL->getText("TXT_KEY_REPLACES_UNIT", GC.getUnitInfo(eDefaultUnit).getTextKeyWide()));
    			}
    		}
    		else
    		{
    			szBuffer.append(NEWLINE);
    			szBuffer.append(gDLL->getText("TXT_KEY_RESTRICTED_UNIT"));
    		}
    	}
    
    So, first thing we do, is instead of checking that the default unit exists, we only check if our new tag is being used (which automatically declares this unit to be unique and need special information for the user) or if it doesn't match the default unit. Note that the default unit could be NO_UNIT and that would just mean we know for certain that our current unit doesn't match so we DO perform further actions.

    Next we set up a flag variable for ourselves (a boolean which indicates if we have taken certain actions or not). We will be using this to format our text and make it somewhat more flexible. We could also use it to simplify the display, but I have chosen not to at this time for some reason, probably because I hadn't thought of it at the time... Anyway, all we will use it for is to say that nobody has access to this unit at all if we don't find anyone who seems to be allowed to build it, but we could also have used it to print all Civilizations who can use the unit on a single line, rather than giving each of them a line of their own.

    Next we wind up almost exactly like the original function, a loop over all civilizations, then a check to see if their unique unit matches this current unit who is under question. In fact, the only difference is that if we find someone who can build this unit we set our Flag to true.
     
  6. xienwolf

    xienwolf Chieftain

    Joined:
    Oct 4, 2007
    Messages:
    10,589
    Location:
    Location! Location!
    So, leaving the Civilization Loop, we look at what comes next: That would be us checking our flag to see if anyone can build this unit. If Anybody is able to, then we make the check I warned you about before and ensure that the default unit DOES exist before trying to find out what their name is. We also check to be certain that the default unit isn't the same as the current unit we are checking so we don't get something silly in our display like "Warrior replaces Warrior" and have to be all embarassed to breathe (the only way to have gotten to this point with those two matching would of course be if our new tag was being used, so this is actually a redundant check with what comes next). And of course we check for our new tag being used, because if we have claimed that the unit is unique, then technically it doesn't replace anything.

    In the case that these three situations have not happened, then it means that this unit replaces something else, so we do as before and let the player know what is being replaced. If one of those is true, then we simply won't print any further information at all.

    And finally, if we didn't discover that ANYONE was able to build this unit, then we slap out a line of text which just states "Nobody can built this unit" so that the player knows better than to try at all. Most likely that would happen for something which you have appear through a special event, or that you just want to taunt people with and make them be tempted to use Worldbuilder to play with it.



    And with that we are done with Units in the Text file. So we'll belt out Buildings in much the same fashion and then be ready to move on to the next steps (new files, where we make our field actually DO something).

    Ok, so buildings are a bit easier, we search for "BuildingHelp" and we only get the one function to appear, so no duplicity to sort out this time. We wind up in void CvGameTextMgr::setBuildingHelp(CvWStringBuffer &szBuffer, BuildingTypes eBuilding, bool bCivilopediaText, bool bStrategyText, bool bTechChooserText, CvCity* pCity) this time, and scroll down a little to find:

    Code:
    	// test for unique building
    	BuildingClassTypes eBuildingClass = (BuildingClassTypes)kBuilding.getBuildingClassType();
    	BuildingTypes eDefaultBuilding = (BuildingTypes)GC.getBuildingClassInfo(eBuildingClass).getDefaultBuildingIndex();
    
    Once again we have a handy comment telling us exactly where to go. How convenient.

    Original code is almost exactly the same as it was for units:
    Code:
    	if (NO_BUILDING != eDefaultBuilding && eDefaultBuilding != eBuilding)
    	{
    		for (iI  = 0; iI < GC.getNumCivilizationInfos(); ++iI)
    		{
    			BuildingTypes eUniqueBuilding = (BuildingTypes)GC.getCivilizationInfo((CivilizationTypes)iI).getCivilizationBuildings((int)eBuildingClass);
    			if (eUniqueBuilding == eBuilding)
    			{
    				szBuffer.append(NEWLINE);
    				szBuffer.append(gDLL->getText("TXT_KEY_UNIQUE_BUILDING", GC.getCivilizationInfo((CivilizationTypes)iI).getTextKeyWide()));
    			}
    		}
    
    		szBuffer.append(NEWLINE);
    		szBuffer.append(gDLL->getText("TXT_KEY_REPLACES_UNIT", GC.getBuildingInfo(eDefaultBuilding).getTextKeyWide()));
    	}
    
    So we replace it with almost exactly the same new code:

    Code:
    	bool bFound;
    	if (GC.getBuildingClassInfo(eBuildingClass).isUnique() || eDefaultBuilding != eBuilding)
    	{
    		bFound = false;
    		for (iI  = 0; iI < GC.getNumCivilizationInfos(); ++iI)
    		{
    			BuildingTypes eUniqueBuilding = (BuildingTypes)GC.getCivilizationInfo((CivilizationTypes)iI).getCivilizationBuildings((int)eBuildingClass);
    			if (eUniqueBuilding == eBuilding)
    			{
    				szBuffer.append(NEWLINE);
    				szBuffer.append(gDLL->getText("TXT_KEY_UNIQUE_BUILDING", GC.getCivilizationInfo((CivilizationTypes)iI).getTextKeyWide()));
    				bFound = true;
    			}
    		}
    
    		if (bFound)
    		{
    			if (NO_BUILDING != eDefaultBuilding && eDefaultBuilding != eBuilding && !GC.getBuildingClassInfo(eBuildingClass).isUnique())
    			{
    				szBuffer.append(NEWLINE);
    				szBuffer.append(gDLL->getText("TXT_KEY_REPLACES_UNIT", GC.getBuildingInfo(eDefaultBuilding).getTextKeyWide()));
    			}
    		}
    		else
    		{
    			szBuffer.append(NEWLINE);
    			szBuffer.append(gDLL->getText("TXT_KEY_RESTRICTED_UNIT"));
    		}
    	}
    
    Notice that my text keys are exactly the same as what I used for the units. So now in the middle of a function gathering data about buildings, I am suddenly talking about units! What poor taste! Well, in part that is from me not thinking ahead and giving my text keys suitably generic names, and in part it is because those particular text keys were written to be vague enough that they could be used for both situations. Neither actually says "Unit" anywhere in it, it just says that X replaces Y, and that nobody can build X. I suppose I should refer to the units as being "trained" and the buildings as being "built" and if I did that I would need seperate text keys, but I didn't, so I don't. :p

    Explaining this would really be a waste of time since it is EXACTLY what we did with units moments ago, so that brings the Game Text section to a close.

    As before we don't refer to any new functions in other files, so compile your work (should be quick since we didn't change any header files -- the ones ending with .h -- and so you can do a partial recompile), then load up the game and see if your new tag is causing the text to display how you think it should. Recall that nothing has changed except the text right now, but knowing that the text showed up proves to you that things are being loaded and understood by the program properly. But do note that if someone uses the default unit and you claim it is unique, you should not expect to see any information show up for them in the Pedia right now. Really you are looking to see that it still looks like it did before you did any work.



    Ok, for this field to work we have to go into a section of the code which you will RARELY ever mess with. And for relatively good reason. The file we must play with is CvXMLLoadUtilityInit.cpp, there are a few files starting with the CvXMLLoadUtility designation, and each of them have some pretty fundamental functions for the process of loading our XML into the DLL. These functions are there to make life SO easy on us if we jsut stay in CvInfos, but in our cases we want to change how one of them works, so we have to play with fire. Messing things up in this file can have a huge impact on a lot of things you didn't expect to impact since so many different functions rely on them. But luckily for us the two which we will modify are only ever used from one location

    With most of the tags we do I will encourage you to poke around in any new files we open up and see what looks like fun to fiddle with, maybe even adjust a few functions on the fly to see if they change what you expect them to change. But with this file it is written much more in "programmer speak" instead of "Modder Friendly Speak", so I don't expect you to find very much in here which you will be comfortable changing on a whim. As such, I will be considerably more brief than standard.

    We want to modify void CvXMLLoadUtility::InitUnitDefaults(int **ppiDefaults), this function is called when loading up the unique Units for a Civilization to pre-load the default units. We just want to change that behavior slightly.

    Code:
    void CvXMLLoadUtility::InitUnitDefaults(int **ppiDefaults)
    {
    	// SPEEDUP
    	PROFILE_FUNC();
    
    	int i;
    	int* piDefaults;
    
    	FAssertMsg(*ppiDefaults == NULL,"memory leak?");
    	// allocate memory based on the number of uniting classes
    	*ppiDefaults = new int[GC.getNumUnitClassInfos()];
    	// set the local pointer to the new memory
    	piDefaults = *ppiDefaults;
    
    	// loop through all the pointers and set their default values
    	for (i=0;i<GC.getNumUnitClassInfos();i++)
    	{
    		piDefaults[i] = GC.getUnitClassInfo((UnitClassTypes) i).getDefaultUnitIndex();
    	}
    }
    
    Ok, the double asterisk thing is complicated, so I won't go into it much. It is a pointer for an array, those keywords should allow you to look it up on your own. For practical purposes, we have had an array handed to this function and the function is allowed to change the array before it finishes. It doesn't RETURN the array, in fact it RETURNs nothing, but the Array data WILL change while this function runs. As I said, it is slightly complicated if you haven't ever programmed, and you won't deal with this kind of thing often, so just ignore it :)

    The first non-comment is "PROFILE_FUNC();", which you will see in many places through the code. This is calling a profiler program, which is a way to see how much time your code spends in various functions while it runs. If you compile a Final_Release DLL then all of these are ignored, so you can ignore it too.

    Then we set up some variables to use, and we see if the array we were handed actually exists, problems can happen if it doesn't. Then we do a pair of commands which assign some memory space to the array, and link it to the new one we just made. Again, I urge you to consider it all to be "Black Magic Computer Mumbo-Jumbo" and move along.

    Finally, we get to the actual important bit, a loop over all unitclasses, and a check for what the default unit of each one is. This is what we are going to intercept. You might be observant enough to realize that we are changing the array we created locally, not the one which was handed to us. Well, during the "Blank Magic Computer Mumbo-Jumbo" we made those two be the same thing, so changing this one changes the other as well. As I said, don't worry about it, or use the keywords to look it up and ask questions as you need to.

    Ok, so our modification is pretty simple here, we just change the check for the default unit:

    Code:
    	for (i=0;i<GC.getNumUnitClassInfos();i++)
    	{
    		if (GC.getUnitClassInfo((UnitClassTypes)i).isUnique())
    		{
    			piDefaults[i] = -1;
    		}
    		else
    		{
    			piDefaults[i] = GC.getUnitClassInfo((UnitClassTypes) i).getDefaultUnitIndex();
    		}
    	}
    
    Now when this function runs if we flagged the unitclass as <bUnique> then it claims that the default unit is NONE (but any other portion of the code asking what the default unit is will get the one actually entered in the XML, so we avoid those mystery complications I alluded to with the AI). If our new tag isn't being used, then it loads the default unit as usual.

    And that is all there is to the functional bits of this tag honestly. So we'll do the same thing for buildings:

    Code:
    void CvXMLLoadUtility::InitBuildingDefaults(int **ppiDefaults)
    {
    	// SPEEDUP
    	PROFILE_FUNC();
    
    	int i;
    	int* piDefaults;
    
    	FAssertMsg(*ppiDefaults == NULL,"memory leak?");
    	// allocate memory based on the number of building classes
    	*ppiDefaults = new int[GC.getNumBuildingClassInfos()];
    	// set the local pointer to the new memory
    	piDefaults = *ppiDefaults;
    
    	// loop through all the pointers and set their default values
    	for (i=0;i<GC.getNumBuildingClassInfos();i++)
    	{
    		piDefaults[i] = GC.getBuildingClassInfo((BuildingClassTypes) i).getDefaultBuildingIndex();
    	}
    
    }
    
    Changes to:

    Code:
    void CvXMLLoadUtility::InitBuildingDefaults(int **ppiDefaults)
    {
    	// SPEEDUP
    	PROFILE_FUNC();
    
    	int i;
    	int* piDefaults;
    
    	FAssertMsg(*ppiDefaults == NULL,"memory leak?");
    	// allocate memory based on the number of building classes
    	*ppiDefaults = new int[GC.getNumBuildingClassInfos()];
    	// set the local pointer to the new memory
    	piDefaults = *ppiDefaults;
    
    	// loop through all the pointers and set their default values
    	for (i=0;i<GC.getNumBuildingClassInfos();i++)
    	{
    		if (GC.getBuildingClassInfo((BuildingClassTypes)i).isUnique())
    		{
    			piDefaults[i] = -1;
    		}
    		else
    		{
    			piDefaults[i] = GC.getBuildingClassInfo((BuildingClassTypes) i).getDefaultBuildingIndex();
    		}
    	}
    
    }
    
    Again, we made a very small change in the loop at the end of the code here.




    Since our change only involved CvInfos, the python exposure is pathetically simple. And honestly it isn't too likely that you will ever need to access this information from python, but it is good practice to always expose everything so that you don't need DLL work in the future if you DO come up with a good reason to use python to access this information.

    All we need to do is figure out which CyInfoInterface file we need to change. So open up one of the 3 and search for CvUnitClass. Turns out that this one shows up in CyInfoInterface1.cpp

    Code:
    	python::class_<CvUnitClassInfo, python::bases<CvInfoBase> >("CvUnitClassInfo")
    		.def("getMaxGlobalInstances", &CvUnitClassInfo::getMaxGlobalInstances, "int ()")
    		.def("getMaxTeamInstances", &CvUnitClassInfo::getMaxTeamInstances, "int ()")
    		.def("getMaxPlayerInstances", &CvUnitClassInfo::getMaxPlayerInstances, "int ()")
    		.def("getInstanceCostModifier", &CvUnitClassInfo::getInstanceCostModifier, "int ()")
    		.def("getDefaultUnitIndex", &CvUnitClassInfo::getDefaultUnitIndex, "int ()")
    		;
    
    There are a lot of little ".def" things in here, and each one points toward CvInfos.cpp and allows python to read it directly. We just need to add our new functions in there, so choose a spot that looks pretty and slap in the new function

    Code:
    	python::class_<CvUnitClassInfo, python::bases<CvInfoBase> >("CvUnitClassInfo")
    		.def("getMaxGlobalInstances", &CvUnitClassInfo::getMaxGlobalInstances, "int ()")
    		.def("getMaxTeamInstances", &CvUnitClassInfo::getMaxTeamInstances, "int ()")
    		.def("getMaxPlayerInstances", &CvUnitClassInfo::getMaxPlayerInstances, "int ()")
    		.def("getInstanceCostModifier", &CvUnitClassInfo::getInstanceCostModifier, "int ()")
    		.def("getDefaultUnitIndex", &CvUnitClassInfo::getDefaultUnitIndex, "int ()")
    		.def("isUnique", &CvUnitClassInfo::isUnique, "bool ()")
    		;
    
    Order doesn't matter, as long as it is listed after the first line quoted and before the semi-colon on the last line quoted.

    Same thing for buildings, search for CvBuildingClass, turns out it isn't in CyInfoInterface1.cpp, so we check CyInfoInterface2.cpp and find it there right at the top.

    Code:
    	python::class_<CvBuildingClassInfo, python::bases<CvInfoBase> >("CvBuildingClassInfo")
    		.def("getMaxGlobalInstances", &CvBuildingClassInfo::getMaxGlobalInstances, "int ()")
    		.def("getMaxTeamInstances", &CvBuildingClassInfo::getMaxTeamInstances, "int ()")
    		.def("getMaxPlayerInstances", &CvBuildingClassInfo::getMaxPlayerInstances, "int ()")
    		.def("getExtraPlayerInstances", &CvBuildingClassInfo::getExtraPlayerInstances, "int ()")
    		.def("getDefaultBuildingIndex", &CvBuildingClassInfo::getDefaultBuildingIndex, "int ()")
    
    		.def("isNoLimit", &CvBuildingClassInfo::isNoLimit, "bool ()")
    		.def("isMonument", &CvBuildingClassInfo::isMonument, "bool ()")
    
    		// Arrays
    
    		.def("getVictoryThreshold", &CvBuildingClassInfo::getVictoryThreshold, "int (int i)")
    		;
    
    Again, slip in our new function anywhere you want to after the first line and before the last one.

    Code:
    	python::class_<CvBuildingClassInfo, python::bases<CvInfoBase> >("CvBuildingClassInfo")
    		.def("getMaxGlobalInstances", &CvBuildingClassInfo::getMaxGlobalInstances, "int ()")
    		.def("getMaxTeamInstances", &CvBuildingClassInfo::getMaxTeamInstances, "int ()")
    		.def("getMaxPlayerInstances", &CvBuildingClassInfo::getMaxPlayerInstances, "int ()")
    		.def("getExtraPlayerInstances", &CvBuildingClassInfo::getExtraPlayerInstances, "int ()")
    		.def("getDefaultBuildingIndex", &CvBuildingClassInfo::getDefaultBuildingIndex, "int ()")
    
    		.def("isNoLimit", &CvBuildingClassInfo::isNoLimit, "bool ()")
    		.def("isMonument", &CvBuildingClassInfo::isMonument, "bool ()")
    		.def("isUnique", &CvBuildingClassInfo::isUnique, "bool ()")
    
    		// Arrays
    
    		.def("getVictoryThreshold", &CvBuildingClassInfo::getVictoryThreshold, "int (int i)")
    		;
    
    And you are completely exposed to python and ready to recompile and see if your code works at all. After our work in CvXMLLoadUtility everything should be working nicely in game and having some sort of effect, if you are using the tag anywhere that is.
     
  7. xienwolf

    xienwolf Chieftain

    Joined:
    Oct 4, 2007
    Messages:
    10,589
    Location:
    Location! Location!
  8. xienwolf

    xienwolf Chieftain

    Joined:
    Oct 4, 2007
    Messages:
    10,589
    Location:
    Location! Location!
  9. xienwolf

    xienwolf Chieftain

    Joined:
    Oct 4, 2007
    Messages:
    10,589
    Location:
    Location! Location!
  10. xienwolf

    xienwolf Chieftain

    Joined:
    Oct 4, 2007
    Messages:
    10,589
    Location:
    Location! Location!
  11. xienwolf

    xienwolf Chieftain

    Joined:
    Oct 4, 2007
    Messages:
    10,589
    Location:
    Location! Location!
    Maybe I could just do a search for a good one instead... but that'll take longer than 30 seconds, and since I invited the Idiots to come read this, it means someone won't read the Table of Contents and won't realize I want to reserve a full 15 posts. I mean.. that bit on adding a pre-existing field too almost 60,000 characters alone. It nearly didn't fit in TWO POSTS of space!
     
  12. xienwolf

    xienwolf Chieftain

    Joined:
    Oct 4, 2007
    Messages:
    10,589
    Location:
    Location! Location!
    So yeah.. If you don't get the point, I am not spamming, I am just pretty sure that by the end of this project I will use AT LEAST 15 total posts worth of information and I want as much of that available on the front page as possible. I am only barely convincing myself not to reserve a full 20 posts right away to force all comments to page 2 and beyond.
     
  13. xienwolf

    xienwolf Chieftain

    Joined:
    Oct 4, 2007
    Messages:
    10,589
    Location:
    Location! Location!
  14. xienwolf

    xienwolf Chieftain

    Joined:
    Oct 4, 2007
    Messages:
    10,589
    Location:
    Location! Location!


    I suppose that the first thing I ought to do is say what an array actually is. Though one would hope that you know what they are if you know you want to use them. Though of course in the true spirit of modding for Civ, you are quite likely just snagging something you saw elsewhere and hoping that it works out for you ;)

    Anyway, an array is a list of values, all of which are the same basic type of data. Each one is an integer, or a boolean, or a string, or whatever type of data that they are. But you don't mix an integer with a boolean or anything like that.

    Arrays are usually written in braces, like this: [1, 2, 3, 4]. This would be an array of size 4. You count the elements of the array starting with 0 on the left. So the item in this array which would be identified as number 2 is the value of 3. There is no value in this array which is identified as a 4. In fact, trying to do anything with this array outside of the values 0 and 3 causes VERY bad things to happen. Messing up an array leads almost instantly to a crash. In the case it doesn't lead instantly to a crash, it leads to one eventually and it is nearly impossible to locate it.

    Ok, so this will be somewhat brief, if you really want to get some information on arrays, check out a tutorial on C++, or just Wiki Arrays, they are a generic data type which everyone uses in basically the same way.

    To make an array, you use an asterisk while declaring the variable in the normal way. So here I declare a boolean, then an integer, and then an array of booleans and an array of integers.
    Code:
    	bool m_bBoolean;
    	int m_iInteger;
    	bool* m_pbBooleanArray;
    	int* m_piIntegerArray;
    
    So, we have the asterisk which indicates that we are using an Array, and we have utilized a convention that Civ uses commonly of placing the "m_" at the start of the name to signify that this variable is defined for an entire class. Then we use a "p" to state that we are using an array, and as before, an "i" to indicate that we are using integers, or a "b" to indicate booleans.

    Now, when you are starting to use a variable, the first thing that you actually do, is save some space to have the variable be stored in your memory. This is done in the code listed above for the integers and booleans. At this point the value of the boolean and the integer are set to whatever happened to be in the memory at the location granted to them, in other words, something relatively random and unpredictable. That is why you would normally declare them with an initial value, like this:
    Code:
    	bool m_bBoolean = false;
    	int m_iInteger = 0;
    
    The way that the code is set up in Civilization though we don't really deal with values and reality in the header files (*.h), but the very first thing you do in what is known as the "Constructor" over in the *.cpp file is set an initial value. This is done in each of the files (go ahead and check if ya wanna... ;)). But for arrays it is a little bit different. An array is a LOT of values, and at the point in the code as we have it written above, we haven't said how MANY values yet, so the computer doesn't know how much memory to give to us for the array. To inform it how much space we need saved, we use the keyword "new" like this:
    Code:
    	bool* m_pbBooleanArray = new bool[5];
    	int* m_piIntegerArray = new int[27];
    
    Now we have reserved enough space to store 5 booleans and 27 integers. To store values in them, we would use something like this:

    Code:
    	m_pbBooleanArray[3] = true;
    	m_pbBooleanArray[0] = false;
    	m_pbBooleanArray[4] = true;
    	m_pbBooleanArray[2] = false;
    	m_pbBooleanArray[1] = false;
    	
    	for (int iI = 0; iI < 27; ++iI)
    	{
    		m_piIntegerArray[iI] = iI;
    	}
    
    This will have stored the arrays such that m_pbBooleanArray = [false, false, false, true, true] and m_piIntegerArray = [0,1,2,3,4...,24,25,26]. Notice that I refer to space "0" in each array, but never a space with the same number as their total size or greater, or any negative numbers. Doing either of those would break things horribly, so we must strive to avoid it. The main way you might make this mistake is if you are using a variable to choose which spot in the array you address, such as a loop over the wrong number of fields (more than the size you declared), or a "getInfoForType" which returns -1 for an invalid type. Or you just forgot to DECLARE your array (the whole "new" keyword) and so it doesn't have any memory space saved for itself yet and thus ANY value (even 0) is too big and breaks things.

    The final important thing about arrays is clearing that memory which they saved for themselves so that other variables can use it. If you declare an array locally (inside a function) it will generally clear itself when the function ends. But if you declare it globally (for an entire class) then you need to clear it when you are done with it by using SAFE_DELETE, which just clears the memory which was set aside if there WAS memory set aside for it (hence the "Safe" part of the delete command. Clearing memory which wasn't assigned to you in the first place is another of the things that can break programs horribly)

    Anyway, arrays are one of the more complicated methods which you will approach in modding the DLL until you get to the point that your creative stretches well beyond the bounds of most mods. As such, it is the pinnacle of this tutorial thread. I will of course be more than delighted to assist with the intricacies of modding beyond this level, but that will be done more directly as specific questions and answers, rather than seeking to provide an overview which might be presumed to properly model how you can do your own fields at any point in the future.

    One thing to note is that I am covering Arrays as a single topic, but that there are multiple approaches to them. As well as a nice choice of what you will keep inside the array. For example the Promotions on a unit are tracked by a Boolean array. Either you have that promotion or you don't. Yet the buildings in a city are tracked by an Integer array. You can be listed as having many of each building within a single city, but so far as the PLAYER sees things, he either has the building, or he doesn't. We need an integer though so we can track how many different ways you have a building in that city. Then if you build a wonder granting you a Granary in every city, and subsequently LOSE that wonder, you do not lose the 3 Granaries which you had built for real when you lose all of the granaries which had been granted for free.

    But the rabbit hole goes a tad bit deeper still. All three of these XML sections I am going to quote here are arrays, but they are quite different in important ways:

    A true Array, which we will be covering here in the Cloning:
    Code:
    			<YieldChanges>
    				<iYieldChange>0</iYieldChange>
    				<iYieldChange>1</iYieldChange>
    				<iYieldChange>0</iYieldChange>
    			</YieldChanges>
    
    A Linked List, which we shall cover when creating a completely new Array:
    Code:
    			<TerrainNatives>
    				<TerrainNative>
    					<TerrainType>TERRAIN_GRASS</TerrainType>
    					<bTerrainNative>1</bTerrainNative>
    				</TerrainNative>
    				<TerrainNative>
    					<TerrainType>TERRAIN_PLAINS</TerrainType>
    					<bTerrainNative>1</bTerrainNative>
    				</TerrainNative>
    				<TerrainNative>
    					<TerrainType>TERRAIN_DESERT</TerrainType>
    					<bTerrainNative>1</bTerrainNative>
    				</TerrainNative>
    			</TerrainNatives>
    
    And a Structure, which is one of those things that is far more complicated that most people will require in their modding career, and thus we shall not be covering directly:
    Code:
    			<UnitMeshGroups>
    				<iGroupSize>3</iGroupSize>
    				<fMaxSpeed>1.75</fMaxSpeed>
    				<fPadTime>1</fPadTime>
    				<iMeleeWaveSize>3</iMeleeWaveSize>
    				<iRangedWaveSize>0</iRangedWaveSize>
    				<UnitMeshGroup>
    					<iRequired>3</iRequired>
    					<EarlyArtDefineTag>ART_DEF_UNIT_LION</EarlyArtDefineTag>
    				</UnitMeshGroup>
    			</UnitMeshGroups>
    
    As you can see with a brief overview, the true Array just contains a series of numbers. One data type, and nothing else. That lends itself perfectly to storage within an array, which is just a semi-fancy method of storing a bunch of different values of the same type of data. As anyone who has modified these type of arrays in the XML knows, the order you list them in is very important. But we can actually avoid that complication if the numbers themselves mean something. No Firaxis lists approach things from this angle.

    The Linked List is an array as well, there are listed within "TerrainNatives" many copies of "TerrainNative", and so we have our Array criterion of a list of similar data types. But in this case that data type is a pair of items, both a string and an integer. The reason we call this a Linked List is because when we store it in the DLL we are going to throw away the string and only keep the integer. The String is used to determine the proper order in which to store our data. You'll see previsely how to use it in the next section of the tutorial though. The nice thing about this approach is that we are set free from the importance of proper order in listing our data. The strange thing about Firaxis on these is that so very often they make the number not actually MATTER (like in the FreePromotions listing on UnitInfos. They are storing a boolean, so the mere presence of the string should have covered us well enough and we could have used a true Array instead...

    The Structures are required when you need to maintain a large quantity of information bundled together, especially if they are of mixed types. You could just use a series of Linked Lists which happen to be listed in the same order and are always referred to at the same time, but the structure framework exists to make passing these between functions quicker and easier. The primary strength (and the main reason that modders will seldom require it) is the ability to carry multiple different types of data, like integers, floats, other arrays, and strings.

    There are many different ways to make good use of arrays, and onc eyou start to use them you will probably find yourself thinking of ways to re-write all your previous code to utilize them in some way or another. They are very handy and incredibly versatile, you just have to make it past the hurdle of figuring out how to load the data into the DLL from the XML.



    Ok, so we want to add some information to Technologies, so we see XML/Technologies and logically take it as our place to start. Conveniently we only find 2 files in here, a schema and an xml container. So we know exactly which files to edit right from the start. Since we are going to be importing a tag from another XML file we can copy their Schema entries to ensure that we get things properly written. Our choices are Civics and Traits, which are found in XML/GameInfo and XML/Civilizations respectively. So I'll pop open the CIV4CivilizationsSchema.xml file and snag out the bits I need, popping them in where I see fit within the technologies files.

    Code:
    	<ElementType name="TradeYieldModifiers" content="eltOnly">
    		<element type="iYield" minOccurs="0" maxOccurs="*"/>
    	</ElementType>
    
    This is the declaration of our ElementType, which says what the XML loading mechanism should expect to find as data in here. As you see it should hold another XML container which is called iYield, so we need to tell the XML what to expect in iYield as well.

    Code:
    	<ElementType name="iYield" content="textOnly" dt:type="int"/>
    So iYield should be an integer, and TradeYieldModifiers should hold some number of iYields

    Now of course we have to tell the Technologies where to expect to find information about the TradeYieldModifiers, so we slip that in at the bottom as usual

    Code:
    		<element type="SoundMP"/>
    		<element type="Button"/>
    [color="Red"]		<element type="TradeYieldModifiers" minOccurs="0"/>[/color]
    	</ElementType>
    [color="Red"]	<ElementType name="iYield" content="textOnly" dt:type="int"/>
    	<ElementType name="TradeYieldModifiers" content="eltOnly">
    		<element type="iYield" minOccurs="0" maxOccurs="*"/>
    	</ElementType>[/color]
    	<ElementType name="TechInfos" content="eltOnly">
    		<element type="TechInfo" maxOccurs="*"/>
    	</ElementType>
    
    Remember that caution must be observed when playing in the Schema files. If the name you have selected for your tag is already being used, then you must not declare it again in a new ElementType. If they had used the same kind of data as you are seeking to use now, just don't declare it and use it anyway, but if they are using a different type of data, or they are already using that keyword in the same file you wanted to add to, you need to choose a new name to use. In our case, we are nicely covered, iYield and TradeYieldModifiers didn't previously exist within the Technologies Schema file.

    And so we now know that Integers should be listed in <iYield>, iYield should be listed in <TradeYieldModifiers>, and TradeYieldModifiers should be listed as the last item under <TechInfo>. But you can list as many (or none) of the iYields as you want, and you are not required to list the TradeYieldModifiers at all.

    Just for the purposes of being able to test our code as wel finish each step in the DLL, we may as well list a TradeYieldModifier in the actual XML of some technology right away. I don't particularly care which one, so I'll just list under Mysticism since it is first in the XML.

    Code:
    			<Button>,Art/Interface/Buttons/TechTree/Mysticism.dds,Art/Interface/Buttons/TechTree_Atlas.dds,4,11</Button>
    			[COLOR="Red"]<TradeYieldModifiers>
    				<iYield>-100</iYield>
    				<iYield>300</iYield>
    				<iYield>1000</iYield>
    			</TradeYieldModifiers>[/COLOR]
    		</TechInfo>
    
    I have chosen to list a value in all 3 fields, and I have chosen values which will make it VERY clear if the fields are working when I test them. I also chose to list a negative value, which is always a good idea to test, even if you don't intend to allow negative values to be used. In that case you need to know they won't break things if attempted. But in this case I do want to allow negative values, though I haven't decided at this point if I want to allow a traderoute to have a negative final output. Most likely not as you cannot choose to remove a traderoute which is harming your economy, but it may prove amusing to allow for such a possibility and let the player's sort out how to deal with it for themselves. When I do test things, I will expect that I'll start a new game, place 2 cities in worldbuilder and connect them with a road, then give myself a building which grants 1 free Traderoute (Lighthouse does that I think...). That SHOULD get me a traderoute which yields 1 Commerce. So right now I need to test that I did the schema right anyway, so let's load a game and make sure we know how to force a traderoute to appear so we can test things later and see what has changed. A traderoute which gives 1 commerce right now would be expected to provide 0 commerce, 3 food and 10 production if I know Mysticism when we are finished with our DLL work.

    Now I want to be certain that I am not overlooking anything which might affect my tests, so I am going to go into Custom Game and choose my leader to be Alexander (since he is first on the list, no other reason), and then make sure I always choose him again when testing in the future. Ideally I would make a full worldbuilder save so that EVERYTHING is exactly the same, but I am reasonably confident that I don't care that much right now :p So I settle one city, then pop into worldbuilder and place another city and a road between them. I see that a Castle provides +1 Trade Route, so I pop one of those in each city. Exit worldbuilder, zoom in to the city and I see that both Athens and Sparta have a trade route leading to the other for a single commerce each. Now I know how to test my fields working properly at the end of everything.



    As we usually would do when importing a tag, save yourself some work and copy what was already written. I'll give you a bit of a hint in this case, ditch the pluralization when searching out a tag to copy (or just in general trace and decode). Doing so and searching in CvInfos.h shows us 5 results, 3 of them are functions, and 2 of them variables. The reason we get 5 results is because this is in 2 files already, and one of them has a somewhat interesting approach to display in the GameTextMgr. Anyway, we'll copy "getTradeYieldModifier(int i)" and "m_piTradeYieldModifier" from either of the two places they already exist over to TechInfos. Remember you can quickly find that by searching for "CvTech". So we'll do so now. Slip your function definition into the line with all of the other function definitions:

    Code:
    	bool isTerrainTrade(int i) const;			// Exposed to Python
    	[color="red"]int getTradeYieldModifier(int i) const;[/color]
    
    	void read(FDataStreamBase* );
    
    Now we have ditched the lovely little comment about being exposed to Python because they really do NOT matter. Just expose everything you ever write and you don't have to struggle to remember what was exposed. Easy as that. (Well, I should correct myself here, only expose the ability to FETCH everything you write, unless absolutely required, NEVER expose the ability to CHANGE anything which you write. Letting Python change things is a bad, filthy, horrible little habit ;)) I also haven't commented the new code, but that is because I need to use as few characters as I can because of stupid forum character count limitations. Remember to comment your own code, and preferrably in a way that is easily commented out when bughunting.

    At the point of writing this one, I have only written the booleans, so it might not be too clear just yet how unusual this function declaration is. But we are checking what a variable is, and normally that means we don't have to toss in any variables, so we would have empty parenthesis. Passing in an integer up to this point would only be used for setting or changing a value. But we are dealing with arrays now, so if you want to get out just a number, you have to state WHICH number you want to get.

    Code:
    	bool* m_pbCommerceFlexible;
    	bool* m_pbTerrainTrade;
    	[color="red"]int* m_piTradeYieldModifier;[/color]
    
    Ok, so now that we are allowed to have our variable exist (and hold the information after XML is loaded) and be read by other classes (through the "get" function), it is time for us to make them do something, so off to the CvInfos.cpp file, and search for CvTechInfo again

    As usual, first function we come across is the constructor, this is where you set initial values. Pop on down to the bottom of the value declarations and you'll notice that instead of 0 and false, suddenly it is declaring things as NULL. NULL is a keyword which is used for Arrays and other things which take up considerable size in memory which basically just says that while they exist as a variable, they don't exist yet as an object, ie - they have no memory reserved just yet. This whole setting an initial value thing is one of the main areas where people flub Arrays and cause themselves crashes or memory leaks, and unfortunately I probably won't wind up covering EVERY case in these two walkthroughs. So if there isn't another array for you to copy from, come over to the forums and ask someone if you are doing it right. Making mistakes here can cause you no end of headache when you go to hunt down bugs.

    Anyway, we are totally safe for now because we are copying a tag from somewhere else. By the way, I haven't yet discussed how to make the copy business easy on yourself. You are using Codeblocks or Visual Studio to modify the code, and if you are searching within that same program for the other instance of this variable you are wasting quite a bit of time by finding where Firaxis wrote it, then going back to where you are adding it, then going back to where Firaxis wrote it, then.... So do yourself a favor and open up a copy of the code in Notepad++ as well and do your searching for the code you are copying in there, then you can just ALT-TAB between the two windows and don't have to find your place over and over. Or if that is too much for you, then after you search for the Firaxis code, use CTRL-Z to undo the last thing you typed and it should bring you back to where you had been previously (just remember to CTRL-Y to redo it)

    Ok, so remembering to drop the pluralization from our XML tag so we can find it in the DLL, we do a quick search for "TradeYieldModifier" and come on the location where Firaxis initializes the value:

    Code:
    m_piTradeYieldModifier(NULL),
    
    Wee! Look at that, they also use a NULL, just like all the arrays already listed for Technologies when we peeked at that one earlier. So we slap this in to our CvTechInfos constructor as well:

    Code:
    m_pbCommerceFlexible(NULL), 
    [color="red"]m_piTradeYieldModifier(NULL),[/color]
    m_pbTerrainTrade(NULL)
    
    Remember that I didn't place this on the very last line because the last thing declared shouldn't have a comma after it and I find it annoying to have to remember that, so I just don't ever put my code on the last line and I don't have to bother remembering it.

    So we go back to our sample code and search one more time, expecting as normal to go to the function declaration next (so that other classes can ask what the value is), but instead we see this:

    Code:
    CvCivicInfo::~CvCivicInfo()
    {
    	int iI;
    
    	SAFE_DELETE_ARRAY(m_piYieldModifier);
    	SAFE_DELETE_ARRAY(m_piCapitalYieldModifier);
    	SAFE_DELETE_ARRAY(m_piTradeYieldModifier);
    
    I quoted a bit more than just the line we care about so I can have you looking at what function we are inside. Earlier we were setting an initial value in the Constructor so now we are on the other side of the fence, in the Destructor. The variables we have dealt with up till now (integers and booleans) have been single memory locations and free the memory as soon as they become unused. But Arrays set aside a reservation for a block of memory and refuse to allow anything else access to them. Computers run a lot faster by having this be an absolute rule and not using the space until specifically told that they are allowed to use it again (checking personally to see if the space is still in use would add hours to your computer startup time, let alone all the actions which take place when you USE your computer). Failing to properly clear the memory would be like locking your dog in the car to get some groceries, then getting a ride home from a friend and subsequently going on a tour around the world the next morning. Your dog isn't going to realize that you aren't coming back and take it upon himself to drive your car back home to park it in your garage and hire himself a dogsitter. He's just going to sit there trying his best not to piddle on your carseat, then eventually realizing that he is trapped and freaking out. That is roughly what your computer will do if you don't properly SAFE_DELETE your arrays, only the computer is quicker and doesn't mind piddling on your carseat so much.

    So, suffice it to say that this is an important piece of code, and we can still just lift it from where we are trying to copy out of. When we leave CvInfos we will have to deal with decisions about when and where to call the SAFE_DELETE, but while we are here in happy-land we always place it in the destructor.

    Code:
    CvTechInfo::~CvTechInfo()
    {
    	SAFE_DELETE_ARRAY(m_piDomainExtraMoves);
    	SAFE_DELETE_ARRAY(m_piFlavorValue);
    	SAFE_DELETE_ARRAY(m_piPrereqOrTechs);
    	SAFE_DELETE_ARRAY(m_piPrereqAndTechs);
    	SAFE_DELETE_ARRAY(m_pbCommerceFlexible);
    	SAFE_DELETE_ARRAY(m_pbTerrainTrade);
    [color="red"]	SAFE_DELETE_ARRAY(m_piTradeYieldModifier);[/color]
    }
    
    Again I remind you, COMMENT YOUR ADDITIONS. You'll thank yourself for it someday when hunting down a bug.

    Ok, search again and we discover:

    Code:
    int CvCivicInfo::getTradeYieldModifier(int i) const
    {
    	FAssertMsg(i < NUM_YIELD_TYPES, "Index out of bounds");
    	FAssertMsg(i > -1, "Index out of bounds");
    	return m_piTradeYieldModifier ? m_piTradeYieldModifier[i] : -1;
    }
    
    Our call function! Now we seem to be back in familiar waters, but again we are faced with things we have never seen up to this point. And again they are pretty important things to know about.

    We already talked about the need to pass an integer into this function, it is what allows us to know WHICH of the many variables stored in this array you want to get back. But these "FAssetMsg" thingies are pretty strange and new toys. Well, for you beginning modders, they are actually quite useless. But you want to use them anyway because someday you won't be a beginning modder and you won't feel like going through all of your old work and putting this back in.

    The FAssertMsg is an Assert message (notice how the word is right in there and all, handy isn't it?). These are a debugging tool, and when you see the "OPT_REF" warning at the end of compiling your code that is telling you that the code is deleting all of these little buggers from the code because it doesn't want to use them. That saves you about 3 Megs on the size of your DLL, so it is a good thing, even though it appears to you as a warning.

    An Assert statement is you writing in a conditional check which you want to be certain is true. The Assert Message carries a string of text to go along with the assert and be recorded when the assert fails, it helps you to remember WHY you asserted this condition. Firaxis kind of sucked on this end of setting things up for modders and doesn't often explain why they are making an assertion, so in many cases you don't know if it is an assert which is REQUIRED (things in the code break if violated) or ASSUMED (things in the code ought never to make this happen, but everything is fine if at some point in the future we allow these kind of values to exist). In fact, in some places instead of using "FAssertMsg" Firaxis uses "FAssert" which checks a condition and if it is false it sends up a red flag, but TELLS US NOTHING. This is about as useless as a toad with a blender and 5 shaved rabbits. Sometimes it is pretty obvious what went wrong, but most of the time you are left sucking your thumb and staring at the screen wondering why the Code Monkeys like to toy with your brain. It's because we get bored easily waiting on our software to compile.

    Anyway, inside the FAssertMsg you see 2 parts, seperated by a comma. The first part is a conditional check, and can be any conditional you want, just like an IF statement. Whenever this conditional is answered with a FALSE, the code will stop (when Asserts are enabled, like in a Debug DLL. Which is why you don't care about these as a novice, and cannot live without them as a more experienced programmer). The code will stop, and if there is a message included, it will tell you the message. It will NOT tell you why the result came out false, so it is sometimes nice to display the variables which you checked for the assert IN the statement. Then you can save yourself having to interrupt the code and debug things to see just how broken they are. If you are in a debug DLL, the Asserts allow you to jump straight into the code and look at the variables to see what broke and guess at how it happened though, so it isn't really REQUIRED that you incldue the variables, it just makes life more useful in a "Release" compile where you enabled Asserts, but not full debugging.
     
  15. xienwolf

    xienwolf Chieftain

    Joined:
    Oct 4, 2007
    Messages:
    10,589
    Location:
    Location! Location!
    So, what we are checking in the code with these two asserts is that the person asking for a value is asking for a LEGAL value. This array is going to be NUM_YIELD_TYPES long, so the number they ask for must be smaller than that, but positive. We have 2 Assert messages for the reason we discussed in the last paragraph, to let you know what is going on and which one broke your code. We could instead use something along the lines of:

    Code:
    	FAssertMsg((i < NUM_YIELD_TYPES) && (i > 0), CvString::format("Index (%d) out of bounds (0 -> %d)", i, NUM_YIELD_TYPES).c_str());
    
    This gets us done in a single line, and still checks both ends of the range, but it also required a bit more programming trickery to get the string formatted to be useful. By using the 2 lines of asserts we don't really lose much, and it is considerably easier to write (you don't have to know how to format a string to use variable inputs and still display as the proper data type)

    The last thing to explain is the final line, where we actually return our value. Remember that the question mark and colon are a shortcut for saying IF THEN ELSE. So we are checking first if the array exists at all (that means it isn't NULL, that memory space has been set aside for it, and hopefully has decent values stored now), if the Array doesn't exist yet (or anymore) for some reason, then we just say -1, since that sends up a nice red flag as a value for responding to this variable. Most likely it would lead to an Assert on the other end of the code which asked us for this number. If the Array does exist though, then we return the value they asked for "m_piTradeYieldModifier" which would be the variable stored in the "i'th" location of this array.

    A final note before we copy this function for ourselves, Assert messages do not by themselves stop your code. They only do anything at all if you have Asserts enabled, which generally means you are compiling in Release or Debug mode instead of the "normal" FInal Release mode. And even then, if you ignore them, the code carries on and assumes that you know what you are doing and the values which caused the conditional statement to fail don't matter. It is also a BAD practice to get in the habit of ignoring Assert messages when you have them on. Anything that triggers an assert is a BAD THING. And if it is not, you should remove or modify the Assert so that it IS. Letting yourself have a habit of skipping assert messages means you are sacrificing one of the greatest bug hunting tools there is for momentary convenience. Bad Future Code-Monkey!

    So, we copy this into our own code, remembering to remain outside of any Curly braces {} so that we are making a new function properly, but otherwise not really caring about WHERE we put it. I'll toss it before the ::read(stream) statement like I usually do.

    Code:
    bool CvTechInfo::isTerrainTrade(int i) const
    {
    	return m_pbTerrainTrade ? m_pbTerrainTrade[i] : false;
    }
    
    [color="red"]int [COLOR="Lime"]CvTechInfo[/COLOR]::getTradeYieldModifier(int i) const
    {
    	FAssertMsg(i < NUM_YIELD_TYPES, "Index out of bounds");
    	FAssertMsg(i > -1, "Index out of bounds");
    	return m_piTradeYieldModifier ? m_piTradeYieldModifier[i] : -1;
    }[/color]
    
    void CvTechInfo::read(FDataStreamBase* stream)
    
    Do note that you actually have to change something here. It is in Green in the middle of the red. It is NOT as simple as a direct Copy/Paste for this part.

    Ok, now you have probably already noticed that there are TWO functions here for checking the array, and that the one which comes after this (which I have been ignoring) returns the entire array instead of just a single value from it, as such it doesn't need an integer to be passed in to it. This is used in CvGameTextMgr for creating the text output in a slightly different way than we are going to do it, so we can ignore this second function and search past it. That will place us in the ::read(stream) function, followed by the ::read(write) function. Remember that the order inside these functions is absolutely vital. What the order is doesn't matter, just having the order match up between the two of them does. So we find these as our code pieces in each of the functions:

    Code:
    	SAFE_DELETE_ARRAY(m_piTradeYieldModifier);
    	m_piTradeYieldModifier = new int[NUM_YIELD_TYPES];
    	stream->Read(NUM_YIELD_TYPES, m_piTradeYieldModifier);
    
    Code:
    	stream->Write(NUM_YIELD_TYPES, m_piTradeYieldModifier);
    
    Writing sure does look a lot easier than reading! But that is mildly deceptive, because we are (as usual) just calling other functions already written by Firaxis that do the REAL programming work for us. They set things up very nicely so that we don't have to understand EXACTLY what is going on and can instead use nice keywords to handle all the grity details.

    Anyhow, look at the last line of the write statement and compare that to the only line of the write statement. Eerily similar aren't they? That part is the actual workhorse of the reading and writing function. You hand it the array you want data saved to/from, and tell it how much data there should be. Then other parts of the program sit down and write or read all of that information for us.

    The extra steps required when reading the data have to do with the fact that we already have some data loaded, and now we want to move to using the data which had been saved previously. So the first thing we need to do is get rid of our old array. Maybe the new data wants to be a different size than it was, so we need to start over. That's where SAFE_DELETE comes into the picture. Once it runs, we are back to NULL and don't have any space saved, but if we want to read some data, we need memory to store it in. That's where the second line comes into play. We reserve some memory to store our information in and make our array exist. Why don't we do those with the Write statement? Because we already have the data we want in this array, we just want to record that information in a savefile so we can get it back later. So why destroy the data we already have and then be required to pick it back up again?

    Ok, so now we slip in our own version of each of these statements in ::read(stream) and ::write(stream). Remember that since you are doing these two steps you will have to test saving and loading a game to verify that you haven't done things wrong. Doing things wrong here would mean making the order of the ::read(stream) and ::write(stream) functions different than each other. So we slip our code at the bottom as usual:

    Code:
    	stream->ReadString(m_szQuoteKey);
    	stream->ReadString(m_szSound);
    	stream->ReadString(m_szSoundMP);
    
    [color="red"]	SAFE_DELETE_ARRAY(m_piTradeYieldModifier);
    	m_piTradeYieldModifier = new int[NUM_YIELD_TYPES];
    	stream->Read(NUM_YIELD_TYPES, m_piTradeYieldModifier);[/color]
    }
    Code:
    	stream->WriteString(m_szQuoteKey);
    	stream->WriteString(m_szSound);
    	stream->WriteString(m_szSoundMP);
    
    [color="red"]	stream->Write(NUM_YIELD_TYPES, m_piTradeYieldModifier);[/color]
    }
    So both of them have been placed at the very end of the function just to make it easy for us to remember the "right" place to put the second one. You could have put them at the start if you really wanted. Oh yeah, and COMMENT YOUR CODE!

    So now we search one more time for TradeYieldModifiers to see what comes next, we find:

    Code:
    	if (gDLL->getXMLIFace()->SetToChildByTagName(pXML->GetXML(),"TradeYieldModifiers"))
    	{
    		pXML->SetYields(&m_piTradeYieldModifier);
    		gDLL->getXMLIFace()->SetToParent(pXML->GetXML());
    	}
    	else
    	{
    		pXML->InitList(&m_piTradeYieldModifier, NUM_YIELD_TYPES);
    	}
    This is all in the :;read(XML) function, and is where we load the data from XML to the DLL. Once more we have some new functions to learn about here.

    The first line, our IF statement, is using a key function for XML Loading "SetToChildByTagName" which searches the current level of the XML (indentation level, though it is based on <> and </> tags, not directly by indentation like silly python) for a specific tag name. The mildly interesting thing to note is that the actual order of your XML doesn't matter at all in most cases (we are about to see one where it does) for the DLL, it is XML itself that forces a set order on us unfortunately.

    Anyway, SetToChildByTagName will search for a tag of whatever is in the quotes, so in this case it checks for <TradeYieldModifiers> and will return TRUE if it finds such a tag, or FALSE if it does not.

    Should we find the tag, then the first part of that function is the key words which are important "SetToChild" which means that now the DLL can only see the XML that is between <TradeYieldModifiers> and </TradeYieldModifiers>, not any of the other Tech information like <iGridX> or <PrereqTech> or any of the other main fields. All it sees is 1 to 3 <iYield> tags.

    So now that we have the DLL looking inside our new tag, it has to read those <iYield> statements, that is what the "SetYields" function will do for us. We have to hand it the array which we want written in though. You'll notice the & symbol in front of our array name here. That is similar to the * symbol earlier which made us an array instead of just a single variable. In this case it is informing the other function that it is permitted to change the data in this variable while running code and doing what it does.

    The SetYields function is something you don't ever have to look at, but I will discuss a few of the quirks about it here anyway. First one which you might notice is that up to this point when we passed our array to another function to have something done to it we have stated how long the array should be. But this time we just passed our Array and said nothing of how long it should be. That is because the Yield types are hardcoded into the DLL and it already knows how long a yield array needs to be. It is also interesting to note that inside the function it doesn't care at all what the names of the variables in your <TradeYieldModifiers> tag are. Rather than using three <iYield> sets, you could use <iFood>, <iProduction> and <iCommerce>. This would certainly help you to remember what order they go in and more quickly read the XML to discover what bonuses a Tech actually provides. But it also might lead people to believe they can swap the order of the fields in the XML, or only list <iCommerce> and have it still work. This is VERY incorrect. Absolutely no matter what you name your fields in the XML, the first one will be loaded as Food, the second as Production, and the third as Commerce. If there is only 1 field present, it loads as food and the other 2 are set to 0. The only way to change this order is to change the order that the variables are listed in CvEnums.h.

    After we load the data into our Array, we need to undo the "SetToChild" action performed earlier so that we can resume loading the other XML data which is still needed. Of course in our case we will be adding the code to the end of the function so there isn't anything else needed, but eventually we will add something else to Technologies as well, and when we do so it will be important that we have our previous code working perfectly so we don't have to remember to undo previous mistakes. SetToParent moves the XML reading capability back up one indentation level just like SetToChild had moved us down one level (again, actual use of indentation isn't the important thing, the <> and </> pairs are the imporant bits)

    Finally, in the case that the code wasn't able to find any <TradeYieldModifiers> tag in the XML, it will run a function called "InitList" which will take our array and fill it with 0's for whatever size we tell it to. This is a general function which you can use on ANY array, so even though the program knows how many Yields there are, it doesn't know we are wanting to USE Yields, so we have to say that in this function to get an array of proper size.

    In our code we have:
    Code:
    	pXML->GetChildXmlValByName(szTextVal, "SoundMP");
    	setSoundMP(szTextVal);
    
    [color="red"]	if (gDLL->getXMLIFace()->SetToChildByTagName(pXML->GetXML(),"TradeYieldModifiers"))
    	{
    		pXML->SetYields(&m_piTradeYieldModifier);
    		gDLL->getXMLIFace()->SetToParent(pXML->GetXML());
    	}
    	else
    	{
    		pXML->InitList(&m_piTradeYieldModifier, NUM_YIELD_TYPES);
    	}[/color]
    
    	return true;
    }
    
    COMMENT YOUR CODE! Also, I'd like to point out something which you may not have been paying attention to so far: When I post code for you to look at while I discuss it, I just post the code we are looking at. But when I post code for you to insert and use, I include 2 or 3 lines before AND after the code. This helps to show you where to place the code, and is also a practice you should get into. When you post code on the forums, you need to use [code][/code] tags around it so that the indentation is maintained (vital for Python, helpful for C++), but including a few lines both before and after your code helps us to see if there is a problem with WHERE you placed the code (sometimes) because in rare instances it is important to know that kind of information.

    Ok, so now we have initialized our array, cleared it at the end, allowed people to query it, saved it, loaded it, and read it from the XML. Search one more time to see what comes next. NOTHING! There is nothing left at this point for CvInfos work. So now we need to compile our code and see if we broke anything. Remember that you modified a header file (*.h) so you must do a complete recompile. Codeblocks should force you to, but Visual Studio will let you try and get away without it. So be sure that you do a clean build and find something to do for 20 minutes or so. Though I would advise you stay near the screen until at least CvInfos has finished compiling because if there IS an error, it will be in the code you just changed, so once that clears the rest is just waiting.

    Also remember that when you test the game you shouldn't notice anything different. Yes, we placed some new lines of XML in Mysticism earlier, and now we are loading that data into the DLL, but that data doesn't DO anything yet, so we need to expect things to work like we haven't done anything at all up to this point. Also remember to test saving and loading a game to ensure your ::read(stream) and ::write(stream) functions were done properly.









     
  16. Jean Elcard

    Jean Elcard The Flavournator

    Joined:
    Feb 26, 2006
    Messages:
    1,008
    Location:
    Leipzig, Germany
    Noooooo! You are telling them all our secrets! ;)

    But seriously, great work Xienwolf! That will help many potential dll modders and will certainly be a great source of knowledge for modders already messing around with it.

    The m is just a C++ convention to make clear, that it is a so called member variable. What's the difference between a member variable, and a local variable? A member variable is a variable that belongs to an object/class (and is publicly accessible from everywhere in the class), whereas a local variable belongs to the current scope (a particular function for example) and is not accessible from anywhere else.
     
  17. The_Coyote

    The_Coyote Chieftain

    Joined:
    Mar 1, 2008
    Messages:
    1,367
    Location:
    Europe
    thanks for writing this awesome stuff :)

    so far i believe that i could follow your steps (and would have a much better understanding if i had my dll open in the same moment and not only read the steps, btw is there a good page with C++ commands and shorthands?)
     
  18. xienwolf

    xienwolf Chieftain

    Joined:
    Oct 4, 2007
    Messages:
    10,589
    Location:
    Location! Location!
    Usually if I have questions about C++ code I check this website. It has been useful so far, but the way that Civ uses C++ is considerably different than the way people normally use it, due to us interacting with an EXE and lots of pre-existing code, rather than attempting to generate direct output and managing all functions ourselves.
     
  19. deanej

    deanej Chieftain

    Joined:
    Apr 8, 2006
    Messages:
    4,859
    Location:
    New York State
    Hence why a tutorial has been desperately needed. ;) Great work!
     
  20. Iceciro

    Iceciro Special Ability: Decimate

    Joined:
    Jul 12, 2006
    Messages:
    1,944
    Location:
    in ur empire, takin ur cities
    I, for one, already find this incredibly useful as some of my ideas for Fall Flat are just pushing up against the limit of what I can do in Python/XML.

    I really appreciate the work you've put in - not just in the mod, but in the modding community itself, with things like this.
     

Share This Page