View Full Version : An Idiots Guide to Editing the DLL
xienwolf Mar 15, 2009, 10:30 PM http://verydemotivational.com/wp-content/uploads/2010/02/129097851616363086.jpg
==================================================
================ Table of Contents ===================
==================================================
When to Mod the DLL Setting up the SDK What's up with all the files? (introduction to the function of various source files) A word to the wise...
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) Schema & XML CvInfos CvGameTextMgr Functional Parts Exposing to Python
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) Schema & XML CvInfos CvGameTextMgr Functional Parts Exposing to Python
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
Creating a completely new Integer XML tag Schema & XML CvInfos CvGameTextMgr Functional Parts Exposing to Python
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
Creating a completely new String XML tag Schema & XML CvInfos CvGameTextMgr Functional Parts Exposing to Python
Cloning an already existing Array XML tag (Moving <TradeYieldModifiers> from TraitInfos & CivicInfos to TechInfos) Schema & XML CvInfos CvGameTextMgr Functional Parts Exposing to Python
Creating a completely new Array XML tag Schema & XML CvInfos CvGameTextMgr Functional Parts Exposing to Python
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
(return to Table of Contents)
==================================================
============== When to Mod the DLL ===================
==================================================
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.
(return to Table of Contents)
==================================================
================ Setting up the SDK ===================
==================================================
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 (http://forums.civfanatics.com/showthread.php?t=166933). 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 (http://forums.civfanatics.com/downloads.php?do=file&id=10018). 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 (http://modiki.civfanatics.com/index.php/How_to_Install_the_SDK) for better updated information, primarily the 3.19 version of the Makefile for Visual Studio
(return to Table of Contents)
==================================================
============= What's up with all the files? ===============
==================================================
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!
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
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 ;)
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)
CvPlayer.cpp, CvPlayerAI.cpp & CvPlayer.hOk, 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
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.
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
(return to Table of Contents)
==================================================
================ A word to the Wise ==================
==================================================
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):
/************************************************** ***********************************************/
/** 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:
/************************************************** ***********************************************/
/** 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 ---- **/
m_iHideUnits = 0;
m_iSeeInvisible = 0;
/************************************************** ***********************************************/
/** Tweak END **/
/************************************************** ***********************************************/
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)
/************************************************** ***********************************************/
/** 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 ---- **
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:
/************************************************** ***********************************************/
/** New Tag Defs (VictoryInfos) 01/16/09 Xienwolf **/
/** **/
/** Initial Values **/
************************************************** ***********************************************/
/************************************************** ***********************************************/
/** New Tag Defs END **/
/************************************************** ***********************************************/
xienwolf Mar 15, 2009, 10:32 PM 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):
/************************************************** ***********************************************/
/** 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:
/************************************************** ***********************************************/
/** 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)
(return to Table of Contents)
==================================================
====== Cloning an already existing Boolean XML tag ===========
==================================================
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
(return to Table of Contents)
==================================================
================= Schema & 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:
<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:
<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:
<ElementType name="_________" content="textOnly" dt:type="int"/>
or to indicate that the field uses a boolean:
<ElementType name="_________" content="textOnly" dt:type="boolean"/>
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):
<ElementType name="_________" content="eltOnly">
{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:
<element type="_______" minOccurs="0"/>
Or you can make it have a limit on how often it can be used by adding:
<element type="_______" maxOccurs="3"/>
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:
<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: <element type="iOrderPriority" minOccurs="0"/>
<element type="bNoBadGoodies" minOccurs="0"/>
</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 (http://forums.civfanatics.com/showthread.php?t=213706) (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.
(return to Table of Contents)
==================================================
==================== CvInfos =======================
==================================================
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.
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.
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.
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).
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).
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.
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.
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)
CvPromotionInfo::CvPromotionInfo() :
m_bNoBadGoodies(false),
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:
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 : false;
}
bool CvPromotionInfo::isNoBadGoodies() const
{
return m_bNoBadGoodies;
}
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.
void CvPromotionInfo::read(FDataStreamBase* stream)
{
CvHotkeyInfo::read(stream);
uint uiFlag=0;
stream->Read(&uiFlag); // flag for expansion
stream->Read(&m_bNoBadGoodies);
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:
void CvPromotionInfo::write(FDataStreamBase* stream)
{
CvHotkeyInfo::write(stream);
uint uiFlag = 0;
stream->Write(uiFlag); // flag for expansion
stream->Write(m_bNoBadGoodies);
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 [i]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.
pXML->GetChildXmlValByName(&m_bBlitz, "bBlitz");
pXML->GetChildXmlValByName(&m_bNoBadGoodies, "bNoBadGoodies");
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.
public:
DllExport CvPromotionInfo();
DllExport virtual ~CvPromotionInfo();
bool isNoBadGoodies() const;
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
protected:
int m_bNoBadGoodies;
int m_iLayerAnimationPath;
int m_iPrereqPromotion;
Now everything we need in CvInfos and CvInfos.cpp is complete!
xienwolf Mar 15, 2009, 10:34 PM (return to Table of Contents)
==================================================
=============== CvGameTextMgr ======================
==================================================
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:
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:
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:
if (GC.getPromotionInfo(ePromotion).isBlitz())
{
szBuffer.append(pcNewline);
szBuffer.append(gDLL->getText("TXT_KEY_PROMOTION_BLITZ_TEXT"));
}
if (GC.getPromotionInfo(ePromotion).isNoBadGoodies())
{
szBuffer.append(pcNewline);
szBuffer.append(gDLL->getText("TXT_KEY_UNIT_NO_BAD_GOODIES"));
}
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)
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
if (!bShort)
{
if (pUnit->isNoBadGoodies())
{
szString.append(NEWLINE);
szString.append(gDLL->getText("TXT_KEY_UNIT_NO_BAD_GOODIES"));
}
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.
(return to Table of Contents)
==================================================
================ Functional Parts =====================
==================================================
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: if (pHeadSelectedUnit->isNoBadGoodies())
{
if (pLoopPlot->isRevealedGoody(pHeadSelectedUnit->getTeam()))
{
gDLL->getEngineIFace()->addColoredPlot(pLoopPlot->getX_INLINE(), pLoopPlot->getY_INLINE(), GC.getColorInfo((ColorTypes)GC.getInfoTypeForStrin g("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: 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: 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:
changeBlitzCount((GC.getPromotionInfo(eIndex).isBl itz()) ? iChange : 0);
changeNoBadGoodiesCount((GC.getPromotionInfo(eInde x).isNoBadGoodies()) ? iChange : 0);
changeAmphibCount((GC.getPromotionInfo(eIndex).isA mphib()) ? 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:
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:
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.
pStream->Read(&m_iBlitzCount);
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:
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:
bool CvUnit::isNoBadGoodies() const
{
return (m_pUnitInfo->isNoBadGoodies() || m_bNoBadGoodies > 0);
}
void CvUnit::changeNoBadGoodiesCount(int iChange)
{
m_iNoBadGoodiesCount += iChange;
FAssert(m_iNoBadGoodiesCount >= 0);
}
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:
m_iBlitzCount = 0;
m_iNoBadGoodiesCount = 0;
m_iAmphibCount = 0;
pStream->Read(&m_iBlitzCount);
pStream->Read(&m_iNoBadGoodiesCount);
pStream->Read(&m_iAmphibCount);
pStream->Write(m_iBlitzCount);
pStream->Write(m_iNoBadGoodiesCount);
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
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:
bool isNoBadGoodies() const; // Exposed to Python
void changeNoBadGoodiesCount();
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:
int m_iBlitzCount;
int m_iNoBadGoodiesCount;
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:
if (GC.getPromotionInfo(ePromotion).isLeader())
{
// Don't consume the leader as a regular promotion
return 0;
}
if (GC.getPromotionInfo(ePromotion).isNoBadGoodies() && !isNoBadGoodies())
{
if (AI_getUnitAIType() == UNITAI_EXPLORE)
{
iValue += 50;
}
}
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:
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.
(return to Table of Contents)
==================================================
=============== Exposing to Python ====================
==================================================
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:
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:
.def("isLeader", &CvPromotionInfo::isLeader, "bool ()")
.def("isNoBadGoodies", &CvPromotionInfo::isNoBadGoodies, "bool ()")
.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.
xienwolf Mar 15, 2009, 10:34 PM 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
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:
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:
.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.
(return to Table of Contents)
==================================================
====== Creating a completely new Boolean XML tag ===========
==================================================
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.
(return to Table of Contents)
==================================================
================= Schema & XML =====================
==================================================
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:
<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
<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"/>
<element type="bUnique" minOccurs="0"/>
</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:
<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>
<bUnique>1</bUnique>
</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:
<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):
<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"/>
<element type="bUnique" minOccurs="0"/>
</ElementType>
2)Add your new field to the main XML file in the new item you wanted to create:
<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/>
<bUnique>1</bUnique>
</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).
(return to Table of Contents)
==================================================
==================== CvInfos =======================
==================================================
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.
DllExport void setDefaultUnitIndex(int i);
bool isUnique() const;
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
int m_iDefaultUnitIndex;
bool m_bUnique;
};
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)
CvUnitClassInfo::CvUnitClassInfo() :
m_iMaxGlobalInstances(0),
m_iMaxTeamInstances(0),
m_iMaxPlayerInstances(0),
m_iInstanceCostModifier(0),
m_bUnique(false),
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:
void CvUnitClassInfo::setDefaultUnitIndex(int i)
{
m_iDefaultUnitIndex = i;
}
bool CvUnitClassInfo::isUnique() const
{
return m_bUnique;
}
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.
pXML->GetChildXmlValByName(&m_iInstanceCostModifier, "iInstanceCostModifier");
pXML->GetChildXmlValByName(&m_bUnique, "bUnique");
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.
DllExport bool isMonument() const; // Exposed to Python
bool isUnique() const;
// 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)
bool m_bMonument;
bool m_bUnique;
// 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"
m_bMonument(false),
m_bUnique(false),
m_piVictoryThreshold(NULL)
6) Create the function for other files to access the new value
bool CvBuildingClassInfo::isMonument() const
{
return m_bMonument;
}
bool CvBuildingClassInfo::isUnique() const
{
return m_bUnique;
}
// 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)
pXML->GetChildXmlValByName(&m_bMonument, "bMonument");
pXML->GetChildXmlValByName(&m_bUnique, "bUnique");
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.
(return to Table of Contents)
==================================================
=============== CvGameTextMgr ======================
==================================================
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:
// 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:
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.
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:
eDefaultUnit = ((UnitTypes)(GC.getCivilizationInfo(eCivilization) .getCivilizationUnits(iI)));
eUniqueUnit = ((UnitTypes)(GC.getUnitClassInfo((UnitClassTypes) iI).getDefaultUnitIndex()));
To:
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:
if ((eDefaultUnit != NO_UNIT) && (eUniqueUnit != NO_UNIT))
To:
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.
xienwolf Mar 15, 2009, 10:35 PM Changed:
if (eDefaultUnit != eUniqueUnit)
To:
if (eUniqueUnit != (UnitTypes)GC.getCivilizationInfo(eCivilization).g etHero() && (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).
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).
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:
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).
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.
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.
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.
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: If our new tag is being used to indicate that this IS a unique unit 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)
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.
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)
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)
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.
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(eCivilizat ion).getCivilizationBuildings(iI)));
eUniqueBuilding = ((BuildingTypes)(GC.getBuildingClassInfo((Building ClassTypes) 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).getDescriptio n(),
GC.getBuildingInfo(eUniqueBuilding).getDescription ());
}
else
{
szBuffer.Format(L"\n %c%s - (%s)", gDLL->getSymbolID(BULLET_CHAR),
GC.getBuildingInfo(eDefaultBuilding).getDescriptio n(),
GC.getBuildingInfo(eUniqueBuilding).getDescription ());
}
szInfoText.append(szBuffer);
bFound = true;
}
}
}
/** ---- End Original Code ---- **/
eUniqueBuilding = ((BuildingTypes)(GC.getCivilizationInfo(eCivilizat ion).getCivilizationBuildings(iI)));
eDefaultBuilding = ((BuildingTypes)(GC.getBuildingClassInfo((Building ClassTypes) iI).getDefaultBuildingIndex()));
if (eUniqueBuilding != NO_BUILDING)
{
if (GC.getBuildingClassInfo((BuildingClassTypes)iI).i sUnique() || eDefaultBuilding != eUniqueBuilding)
{
// Add Unit
if (bDawnOfMan)
{
if (bFound)
{
szInfoText.append(L", ");
}
if (GC.getBuildingClassInfo((BuildingClassTypes)iI).i sUnique())
{
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).getDescriptio n());
}
}
else
{
if (GC.getBuildingClassInfo((BuildingClassTypes)iI).i sUnique())
{
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).getDescriptio n());
}
}
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:
// test for unique unit
UnitClassTypes eUnitClass = (UnitClassTypes)GC.getUnitInfo(eUnit).getUnitClass Type();
UnitTypes eDefaultUnit = (UnitTypes)GC.getUnitClassInfo(eUnitClass).getDefa ultUnitIndex();
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!
if (NO_UNIT != eDefaultUnit && eDefaultUnit != eUnit)
{
for (iI = 0; iI < GC.getNumCivilizationInfos(); ++iI)
{
UnitTypes eUniqueUnit = (UnitTypes)GC.getCivilizationInfo((CivilizationTyp es)iI).getCivilizationUnits((int)eUnitClass);
if (eUniqueUnit == eUnit)
{
szBuffer.append(NEWLINE);
szBuffer.append(gDLL->getText("TXT_KEY_UNIQUE_UNIT", GC.getCivilizationInfo((CivilizationTypes)iI).getT extKeyWide()));
}
}
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:
if (GC.getUnitClassInfo(eUnitClass).isUnique() || eDefaultUnit != eUnit)
{
bool bFound = false;
for (iI = 0; iI < GC.getNumCivilizationInfos(); ++iI)
{
UnitTypes eUniqueUnit = (UnitTypes)GC.getCivilizationInfo((CivilizationTyp es)iI).getCivilizationUnits((int)eUnitClass);
if (eUniqueUnit == eUnit)
{
szBuffer.append(NEWLINE);
szBuffer.append(gDLL->getText("TXT_KEY_UNIQUE_UNIT", GC.getCivilizationInfo((CivilizationTypes)iI).getT extKeyWide()));
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.
xienwolf Mar 15, 2009, 10:36 PM 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:
// test for unique building
BuildingClassTypes eBuildingClass = (BuildingClassTypes)kBuilding.getBuildingClassType ();
BuildingTypes eDefaultBuilding = (BuildingTypes)GC.getBuildingClassInfo(eBuildingCl ass).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:
if (NO_BUILDING != eDefaultBuilding && eDefaultBuilding != eBuilding)
{
for (iI = 0; iI < GC.getNumCivilizationInfos(); ++iI)
{
BuildingTypes eUniqueBuilding = (BuildingTypes)GC.getCivilizationInfo((Civilizatio nTypes)iI).getCivilizationBuildings((int)eBuilding Class);
if (eUniqueBuilding == eBuilding)
{
szBuffer.append(NEWLINE);
szBuffer.append(gDLL->getText("TXT_KEY_UNIQUE_BUILDING", GC.getCivilizationInfo((CivilizationTypes)iI).getT extKeyWide()));
}
}
szBuffer.append(NEWLINE);
szBuffer.append(gDLL->getText("TXT_KEY_REPLACES_UNIT", GC.getBuildingInfo(eDefaultBuilding).getTextKeyWid e()));
}
So we replace it with almost exactly the same new code:
bool bFound;
if (GC.getBuildingClassInfo(eBuildingClass).isUnique( ) || eDefaultBuilding != eBuilding)
{
bFound = false;
for (iI = 0; iI < GC.getNumCivilizationInfos(); ++iI)
{
BuildingTypes eUniqueBuilding = (BuildingTypes)GC.getCivilizationInfo((Civilizatio nTypes)iI).getCivilizationBuildings((int)eBuilding Class);
if (eUniqueBuilding == eBuilding)
{
szBuffer.append(NEWLINE);
szBuffer.append(gDLL->getText("TXT_KEY_UNIQUE_BUILDING", GC.getCivilizationInfo((CivilizationTypes)iI).getT extKeyWide()));
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).getTextKeyWid e()));
}
}
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.
(return to Table of Contents)
==================================================
================ Functional Parts =====================
==================================================
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.
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 = 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:
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:
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:
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).is Unique())
{
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.
(return to Table of Contents)
==================================================
=============== Exposing to Python ====================
==================================================
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 [i]CyInfoInterface1.cpp
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
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.
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.
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.
xienwolf Mar 15, 2009, 10:36 PM Coming Someday!
xienwolf Mar 15, 2009, 10:37 PM Eh, it'll be here when it gets here!
xienwolf Mar 15, 2009, 10:37 PM Gee, I should totally do a Burma-Shave add so these are at least mildly amusing till filled in...
xienwolf Mar 15, 2009, 10:38 PM Of course... that would rely on a good sense of humor. Crud-Monkeys!
xienwolf Mar 15, 2009, 10:39 PM 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!
xienwolf Mar 15, 2009, 10:40 PM 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.
xienwolf Mar 15, 2009, 10:41 PM Yipee! I am almost there. No more room for a Burma-Shave joke now though.
xienwolf Mar 15, 2009, 10:41 PM (return to Table of Contents)
==================================================
====== Cloning an already existing Array XML tag ===========
==================================================
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.
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:
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:
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:
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:
<YieldChanges>
<iYieldChange>0</iYieldChange>
<iYieldChange>1</iYieldChange>
<iYieldChange>0</iYieldChange>
</YieldChanges>
A Linked List, which we shall cover when creating a completely new Array:
<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:
<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.
(return to Table of Contents)
==================================================
================= Schema & 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.
<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.
<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
<element type="SoundMP"/>
<element type="Button"/>
<element type="TradeYieldModifiers" minOccurs="0"/>
</ElementType>
<ElementType name="iYield" content="textOnly" dt:type="int"/>
<ElementType name="TradeYieldModifiers" content="eltOnly">
<element type="iYield" minOccurs="0" maxOccurs="*"/>
</ElementType>
<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.
<Button>,Art/Interface/Buttons/TechTree/Mysticism.dds,Art/Interface/Buttons/TechTree_Atlas.dds,4,11</Button>
<TradeYieldModifiers>
<iYield>-100</iYield>
<iYield>300</iYield>
<iYield>1000</iYield>
</TradeYieldModifiers>
</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.
(return to Table of Contents)
==================================================
==================== CvInfos =======================
==================================================
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:
bool isTerrainTrade(int i) const; // Exposed to Python
int getTradeYieldModifier(int i) const;
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.
bool* m_pbCommerceFlexible;
bool* m_pbTerrainTrade;
int* m_piTradeYieldModifier;
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:
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:
m_pbCommerceFlexible(NULL),
m_piTradeYieldModifier(NULL),
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:
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.
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);
SAFE_DELETE_ARRAY(m_piTradeYieldModifier);
}
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:
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.
xienwolf Mar 15, 2009, 10:42 PM 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:
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[i]" 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.
bool CvTechInfo::isTerrainTrade(int i) const
{
return m_pbTerrainTrade ? m_pbTerrainTrade[i] : false;
}
int CvTechInfo::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;
}
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:
SAFE_DELETE_ARRAY(m_piTradeYieldModifier);
m_piTradeYieldModifier = new int[NUM_YIELD_TYPES];
stream->Read(NUM_YIELD_TYPES, m_piTradeYieldModifier);
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:
stream->ReadString(m_szQuoteKey);
stream->ReadString(m_szSound);
stream->ReadString(m_szSoundMP);
SAFE_DELETE_ARRAY(m_piTradeYieldModifier);
m_piTradeYieldModifier = new int[NUM_YIELD_TYPES];
stream->Read(NUM_YIELD_TYPES, m_piTradeYieldModifier);
}
stream->WriteString(m_szQuoteKey);
stream->WriteString(m_szSound);
stream->WriteString(m_szSoundMP);
stream->Write(NUM_YIELD_TYPES, m_piTradeYieldModifier);
}
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:
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:
pXML->GetChildXmlValByName(szTextVal, "SoundMP");
setSoundMP(szTextVal);
if (gDLL->getXMLIFace()->SetToChildByTagName(pXML->GetXML(),"TradeYieldModifiers"))
{
pXML->SetYields(&m_piTradeYieldModifier);
gDLL->getXMLIFace()->SetToParent(pXML->GetXML());
}
else
{
pXML->InitList(&m_piTradeYieldModifier, NUM_YIELD_TYPES);
}
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 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.
(return to Table of Contents)
==================================================
=============== CvGameTextMgr ======================
==================================================
(return to Table of Contents)
==================================================
================ Functional Parts =====================
==================================================
(return to Table of Contents)
==================================================
=============== Exposing to Python ====================
==================================================
Jean Elcard Mar 16, 2009, 05:06 AM 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.
m_bNoBadGoodies;: This is whatever variable name we made up. I am not sure if it is a normal convention in all C++ programming, but starting with the m_ indicates that you are dealing with a variable which exists for the entire function, rather than one you created locally.
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.
The_Coyote Mar 16, 2009, 03:57 PM 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?)
xienwolf Mar 16, 2009, 04:13 PM Usually if I have questions about C++ code I check this website (http://www.cplusplus.com/doc/tutorial/). 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.
deanej Mar 16, 2009, 07:44 PM Usually if I have questions about C++ code I check this website (http://www.cplusplus.com/doc/tutorial/). 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.
Hence why a tutorial has been desperately needed. ;) Great work!
Iceciro Mar 16, 2009, 10:25 PM 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.
renegadechicken Mar 16, 2009, 11:30 PM Thank you so much for doing this! This tutorial is desperately needed, because C++ is used in such a different way for Civ IV (as you yourself noted!). I look forward to your next lesson! :)
xienwolf Mar 17, 2009, 12:14 AM By the way, I will write all of these on-the-fly. So if anybody has some specific requests for what I should write the tutorials on which are still upcoming, feel free to request them. Most likely what you request will lead to far more interesting "Oh yeah! I have to do this too..." moments than the things which I grab, and potentially be useful somewhere in a mod (not sure how many people were just DYING for a promotion to get better results from goody huts with...)
EDIT: Added a new section to post #1 about commenting your code. I realized that while I advised the prospective modder to do so, I didn't exactly tell them HOW to do so, except for once in passing when I said that "//" starts a line comment.
renegadechicken Mar 17, 2009, 12:22 AM Well, here's a request, but I don't know how hard this would be to code, so I'll understand if this doesn't get mentioned in your tutorial... :)
I'm working on a mod for Final Frontier, and I need a Promotion that is basically an "Ion Pulse." The promotion is a once-per-turn "spell" (like in FFH2) that disables a single unit for one (or two) turn(s). The tricky part is where stacks come into play; I don't want it to hit the whole stack! :) My preference would be for the player who fired the pulse to be able to choose which unit to hit. If that's truly impossible, I'd settle for the strongest unit.
Would this be a useful example of how to use SDK/C++? I really don't know-- I'll leave that up to you. :)
Jean Elcard Mar 17, 2009, 03:20 AM I think you should use a darker green than "lime" for comments and stuff. It's incredibly hard to read on my screen (using standard forum colors).
Iceciro Mar 17, 2009, 07:58 AM You could show us where BTS' Great General creation is hid. I'm sure a lot of people would like to tweak those numbers, if nothing else. There's also a lot of stuff about the specialists in general that's not a part of xml/python.
And I want to re-enable generals through combat. :P
xienwolf Mar 17, 2009, 05:31 PM I'll move over to a more base-line color and check out the default scheme for readability too in the future. Don't understand why people stick with the default layout here :p
Not sure if I completely understand how you want the Ion Pulse to function, but it would most likely not be something you would implement as a new XML Field. To allow the ability to be targetted you would want to utilize python, so it might be a good example for the "Creating a Python Callback" function. Though honestly if this is the only promotion which will ever do such a thing you are better off sticking to pure python for it. Not too easy of a function to generalize, and you don't want to hard-code in the DLL since it is a mild PITA to adjust things.
From what I see in the FfH base code, you can re-enable Great Generals from combat by just creating a unit (any unit) which offers a LeaderPromotion or LeaderExperience. In Fall Further I actually stopped this function from running at all, so THERE you would have to remove a block in the DLL (void CvPlayer::setCombatExperience(int iExperience)).
zenspiderz Mar 17, 2009, 06:03 PM Wow this great. I have been praying for this info for ages. At the moment tho' i am busy making unit graphics but soon as i get tired of that i will be back to editing my mod and it is fast getting to the point where i will need to monkey about with the sdk to achieve my vision. Nice work!
renegadechicken Mar 17, 2009, 06:11 PM Not sure if I completely understand how you want the Ion Pulse to function, but it would most likely not be something you would implement as a new XML Field. To allow the ability to be targetted you would want to utilize python, so it might be a good example for the "Creating a Python Callback" function. Though honestly if this is the only promotion which will ever do such a thing you are better off sticking to pure python for it. Not too easy of a function to generalize, and you don't want to hard-code in the DLL since it is a mild PITA to adjust things.
Well, what I'm going for is almost exactly like the spell "Blinding Light" from FFH2 with some important changes: the "spell" wouldn't hit all units in all adjacent plots, but would be targeted at one plot (similar to bombarding), and could only hit a single unit (chosen by the "casting" player). Man, now that I describe, it really sounds complex...
But does that make it a bit clearer what I'm after? If it's all in Python, then that's cool, but it would not be my only Promotion (I plan on adding quite a few others that might be similar to other FFH2 spells). Once again, though, I understand if you don't include this in your tutorial...but I can hope! :)
xienwolf Mar 17, 2009, 09:39 PM I think I mostly follow what you are seeking with it. And due to being targetted/triggered type of effects, the spells are almost all best as spells.
If you want them to act on each turn, then they gain some validity as candidates for an XML field in PromotionInfos, but even then you really want it to be something that you use on a lot of different promotions (can't think of many good examples), something INCREDIBLY simple/passive (like bBlitz), or something which will have numerical differences between different promotion (like iCombatBonus). Something which is a "do this thing each turn" hits a bit of a grey area (it could be a fairly simple thing, like March promotions "always heal"). However, the moment you talk about targetting and strategic choices, then it starts to sound more appropriate for python to handle.
snipperrabbit!! Mar 19, 2009, 05:59 AM This thread wants to be stickied. Please, moderator, sticky it.
renegadechicken Mar 19, 2009, 10:56 AM This thread wants to be stickied. Please, moderator, sticky it.
Yes, definitely! :)
JEELEN Mar 19, 2009, 04:23 PM Better actually PM a Mod or they might not see (to) it.;)
Ajidica Mar 19, 2009, 05:54 PM Very nice job. I only read the fist post and basics of the dll, but very well writted and useful once I get into the dll after learning python. We needed an idiots guide.
snipperrabbit!! Mar 20, 2009, 08:51 AM Better actually PM a Mod or they might not see (to) it.;)
OMG ! Can I really ask for moderator status when I'll be blind ? (sthg that will happen eventually :cool:)
More seriously, I prefer to be confident and think moderators are reading the threads by themselves from time to time and not only upon user's report. I think that sticky thread doesn't have to be requested, petitioned or lobbyied. IMO polite suggestion is all about it.
JEELEN Mar 21, 2009, 01:03 AM Well, if you think lobbying will do no good... (I merely mentioned it, because there's countless threads out there, which can't possibly all be read by moderators - they'd have a full-time job doing that.)
LoneTraveller Mar 21, 2009, 05:12 PM I need basic info about python and SDK. I'm learning the xml part of modding and getting to a point of working the files okay by myself (through the help of many ppl in these forums).
I'm wondering when would I need python or SDK ? What is the modding point when someone has to migrate to python or SDK ? An example for each would be nice.
Thank you
xienwolf Mar 21, 2009, 07:08 PM I changed out the "Intro" section for a "When to Mod the DLL" section. Let me know if that discussion is sufficient to answer your questions.
LoneTraveller Mar 22, 2009, 03:22 AM I changed out the "Intro" section for a "When to Mod the DLL" section. Let me know if that discussion is sufficient to answer your questions.
Its helpful indeed.
Thank you.
Kalimakhus Mar 23, 2009, 11:31 AM @xienwolf
I just wanted to say that this is really a wonderful and most needed guide. I've read the intro and skimmed through some of the other posts and I really like the way you explain things so as to make it really easy for beginners who possibly have never wrote C++ code before. On the other hand the guide is so helpful even for those who know C++ very well. There is a difference between knowing the language and knowing how to get things done with a specific API.
I hope a moderator would notice the thread and sticky it but I doubt it will happen unless someone (maybe you) PMs one of them. I can judge from the fact that the thread for setting up the SDK with Visual Studio has been around for years without becoming a sticky up till now. Moderators have limited time and they would spend it in the most trafficked sub-forums (this is not really one of them of course).
Thunder_Gr Mar 27, 2009, 10:49 AM Amazing guide Xienwolf! Thank you! :goodjob:
Ploeperpengel Apr 06, 2009, 07:04 AM This makes my fingers itch to start coding again. Certainly less blind fooling around with this not only informative but also very entertaining writeup. Thank you and don't stop!:)
Ninja2 Apr 06, 2009, 03:10 PM Brilliant stuff, Xienwolf! :goodjob: You're bringing back my motivation to get into modding again. I haven't read all of your writings yet, except for all the filler posts at the bottom. ;)
xienwolf Apr 06, 2009, 06:54 PM Ah yes, those are the most critical of all the posts. So much information in such a compact notation! :D
Gooblah Apr 12, 2009, 01:25 PM I LOVE YOU!!!!!!
Oh man, thios is what I have been waiting for! Holy crap!!!! :goodjob:
Just one suggestions
could you possibly add 'common functions' that are called a lot? For example, functions that deal with how many Civs there are, or the status of a Civ as a vassal or a master...etc
Kalimakhus Apr 12, 2009, 02:02 PM I would suggest some info like "Where can you find What?". While it seems obvious that CvUnit.cpp is where you can deal with units it is not always the case. Also how a CvXXXXAI.cpp will differ from a CvXXXX.cpp. Some general situations like when should expose my custom code to python and when I shouldn't bother. How should I deal with a selection set and what I should watch out for when doing so (a major point of infinite loops and CTDs in many mods).
I won't ask for a whole map for the SDK files and what each does. This would be too much and I am sure your too busy to even start thinking of such an endeavor. Also I don't expect you to have already played with every single file there because simply no modders has ever needed to do so. Just a simple road map to commonly and frequently treaded paths through files for the clueless.
xienwolf Apr 12, 2009, 03:41 PM Gooblah: It would be a bit tricky to decide precistly what are commonly called functions. Those which you can safely say are common are easily found and copy/pasted when needed. The rest... Well the main goal is that by the end of these tutorials someone is comfortable enough in the DLL that they understand how to find such things (go to the header file and search for the data type you are seeking to work with usually)
Kalimakhus: Kinda the same response as Gooblah got in the end: The hope is that by the time I am done writing all of these, someone is familiar enough that they can find whatever they need. There are numerous exceptions and unexpected twists which could take up far more space than I reserved for myself to cover completely, and will in many cases be so specialized in scope that the one piece you need is buried among a thousand pieces you don't need and so you'll not find it. I do intend to choose challenging examples which force me to spread throughout the code so that I can point out as much as possible, but in a context which makes it relevant.
As for when to expose to python: Make a callback when you MUST do so, but everything else you should ALWAYS expose, it doesn't cost you any processor time, barely affects the size of the DLL, and saves you a headache in the future when you decide you need to access some variable from python.
Selectionset... not sure what you are talking about there actually.
You are right about the not much time though :) Unfortunately it may be another month until I can finish writing the boolean I am in the middle of, let alone start up on the INTs.
Gooblah Apr 13, 2009, 08:01 AM Gooblah: It would be a bit tricky to decide precistly what are commonly called functions. Those which you can safely say are common are easily found and copy/pasted when needed. The rest... Well the main goal is that by the end of these tutorials someone is comfortable enough in the DLL that they understand how to find such things (go to the header file and search for the data type you are seeking to work with usually)
Gotcha, that makes sense. My main trouble right now is attempting to right for loops; when you refer to a Civ, does that mean the Civ whose turn it is RIGHT NOW, or is it the human? A specific number? :confused:
xienwolf Apr 13, 2009, 09:56 AM You can't actually tell whose turn it is right now very easily. You can loop over all the players and check if their turn is active, but most likely you are talking about getActivePlayer, which always means the person behind the keyboard. That is most certainly something which I should mention somewhere early on, and then repeatedly after.
Otherwise, in the SDK you generally don't refer to a specific Civ (well, except for the Barbarians), that's hardcoding :) You would refer to a player or a team which have certain attributes, and it will just so happen that in your mod, only one specific Civ has those attributes.
Kalimakhus Apr 13, 2009, 01:23 PM When I talked about "Where to find What" I meant things that worth mentioning and are found somewhat in an unexpected place. Anyway, I think your plan will end up covering most if not all the points a beginner modder will need. By the time a modder is ready to go into unexplored areas of code he will be familiar enough with the DLL that he can find his way on his own.
Selection Set.. I meant the times when you need to loop through the units on some plot and do something to all, or some of them. In many mods this is a place where problems tend to arise. They are usually hard to discover because the code seems quite innocent. What usually happens is that while looping through the (selection set) some of the units attributes may change or some units may actually become unavailable (moved, killed, or replaced).. This how many infinite loops and null pointers access violations come from. What I am asking you to do is to explain through examples some of the common mistakes in this area. My guess is that FfF and FF work extensively with such situations where the existence of unit X may give units of type y the z promotion if illegible or casting some spell will affect some units but not others based on some attributes or the fact that the units have moved for this turn or not etc.
Anyway, it may not be the time for asking for specific things. It is better to let you go on with your plan then if a point that is worth asking for is need one may ask you about it.
xienwolf Apr 13, 2009, 02:57 PM I think I see now what type of issues you mean with the selection set. Actually not something I have done much with so far for precisely that reason. Usually the best way to avoid the issues is to come at things from another angle in my experience.
NeverMind Apr 15, 2009, 06:47 AM Exellent tutorial, xienwolf! :goodjob: Keep it up!
PPQ_Purple Apr 15, 2009, 01:25 PM I am using the newest FF core DLL.
Could anyone point me to what dll handles terain improvement.
Esentialy, I want to check if unit A is standing on improvement B. For example:
If(Mage is on Farm)
Where can I find a function that returns the improvement on a certain location?
xienwolf Apr 15, 2009, 02:54 PM plot()->getImprovement()
Ploeperpengel Apr 15, 2009, 03:27 PM I guess the idiot's guide is a better place for me than the modder's guides to FF. I just noticed any changes I do to the sourcecode(FF) breaks the ability to build a dll. Commenting out a line and undoing it again results in failure to build.
This is just mad:crazyeye: any idea what I might do wrong? The strange thing is I compiled a lot of dlls in the past without problems altering quite a bit of code...
Edit: nevermind I don't experience problems using FFH code just FF gives me all sorts of problems, guess I'll just wait until you provide the full sources
xienwolf Apr 15, 2009, 07:03 PM Actually either thread would work, I just happened to check this one first that time around. My answer would be rather different in the other thread than it would be here though :) Almost didn't realize that it was the same question from the same person actually :)
Ploeperpengel Apr 15, 2009, 07:20 PM Actually either thread would work, I just happened to check this one first that time around. My answer would be rather different in the other thread than it would be here though :) Almost didn't realize that it was the same question from the same person actually :)
It's the same person but actually not the same question:p
But nevermind I'm happily editing FFH sourcecode now. Just FF resisted every attempt to alter it therefore I'll grab what I need from there later.;)
xienwolf Apr 15, 2009, 09:08 PM I just realized I still have the full source from before last patch uploaded on the first patch of the modder's guide, so if you downloaded that it should all work and have almost everything that is current anyway.
Ploeperpengel Apr 15, 2009, 09:48 PM I just realized I still have the full source from before last patch uploaded on the first patch of the modder's guide, so if you downloaded that it should all work and have almost everything that is current anyway.
If the patchL download contains the full source - well that's where all the trouble started. I will redownload it later and try again just to make sure.
xienwolf Apr 15, 2009, 11:38 PM Ah... well I had packed that one up from a working copy, might have been in the middle of finding out I had a bug to solve when I did it :(
The_J Apr 19, 2009, 03:28 PM :wow: what work :goodjob:.
I have a question, because i can't find it somewhere, and the pro is here :D:
Is it possible, to add a new function to the CvEventManager.py?
I've tried to add
virtual void setTerrainType(CvPlot *pPlot, TerrainTypes eNewValue, bool bRecalculate, bool bRebuildGraphics) = 0;
to CvDLLEventReporterIFaceBase.h (in the last line), and
gDLL->getEventReporterIFace()->setTerrainType(this, eNewValue, bRecalculate, bRebuildGraphics);
in CvPlot.cpp in the void CvPlot::setTerrainType(TerrainTypes eNewValue, bool bRecalculate, bool bRebuildGraphics) function.
I've also modified the CvEventManager.py.
The dll is compiled, but the game crashes when initialising the map.
Can it be done, or is it not possible?
Or am i just stupid?
xienwolf Apr 19, 2009, 10:31 PM I haven't personally used the event reporter yet. But what you have for the DLL side does seem to work. I would wonder if you have everything proper in the python in that case.
You defined the function to call in self.EventHandlerMap? Then created your def to match?
The_J Apr 20, 2009, 01:31 PM Yes, i did.
'setTerrainType' : self.onSetTerrainType,
and
def onSetTerrainType(self, argsList):
eNewValue = argsList[0]
bRecalculate = argsList[1]
bRebuildGraphics = argsList[2]
I'm not sure, if there's no typo, but would a misspelling lead to a crash without an error?
xienwolf Apr 20, 2009, 02:38 PM First element from the argslist is pPlot, not eNewValue. Would possibly cause errors if you use them wrong, and when the map is made this will fire a LOT of times, so pile enough small errors and it might crash. But if you don't have any actual code yet (instead just have the definitions posted so far), then just declaring the variables won't cause a crash
The_J Apr 21, 2009, 03:22 PM First element from the argslist is pPlot, not eNewValue.
:blush:
Would possibly cause errors if you use them wrong, and when the map is made this will fire a LOT of times, so pile enough small errors and it might crash.
I think atm, that i've placed the...event-report (?) at the false place in the function, so that it fires every time, when the function is called, also at the beginning, when there is no plot.
Maybe this is the mistake.
And the performance when creating the map: Sure you're right, but it doesn't matter, i need the function.
You say it is right, so it is right :), it gives me a bit self-confidence.
Thanks for the help :).
I'll try it again tomorrow, let's see, what happens.
But if you don't have any actual code yet (instead just have the definitions posted so far), then just declaring the variables won't cause a crash
I've only written the print-statement for all variables after the code, and i could have interpreted the python errors, so that's not the problem.
ZachScape Apr 25, 2009, 11:28 AM Where can I extract Notepad++? Once I download it, I don't know what to do with it.
Gooblah Apr 28, 2009, 05:31 PM Quick tip/pointer:
CvTeamAI: 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. :goodjob:
xienwolf Apr 28, 2009, 06:05 PM Copy/Pasted that into the description for CvTeam file introduction :) Could well prove useful to quite a few modders :)
phungus420 Apr 30, 2009, 11:37 PM I've tried the instructions for adding a boolean tag, but it crashes on loading the first step in the adding tags process. Only edited in the information to the XML schema (works fine), and CvInfos (.h & .cpp). I can't figure out what I'm doing wrong here, have tried it about 7 times now, and while it compiles fine, it crashes when initializing the XML. Here is the post in SDK/Python forums:
http://forums.civfanatics.com/showpost.php?p=8040290&postcount=3
I don't know why, but it wanted me to compile a whole fresh new gamecore... so frustrating that takes like an hour and a half...
xienwolf May 01, 2009, 07:04 AM Any time you change anything in the header files (*.h) you MUST do a full recompile.
phungus420 May 01, 2009, 07:45 AM Any time you change anything in the header files (*.h) you MUST do a full recompile.
:sad:
That makes things take alot longer...
Thanks though for that clarification. That might be the cause of my error at the moment...
xienwolf May 01, 2009, 11:32 AM It does get annoying. Once you get accustomed to the DLL though you can usually predict what header file changes you need in advance and write all of them on the first go through. Then you sit down to work on the main working code and have quick compile times for the rest of things.
phungus420 May 02, 2009, 06:59 AM Xienwolf any chance you could write your exposing to python tutorial next? I've added a couple new XML tags, and would like to expose their generated effects to Python (not just the tag itself, but the get and such functions that go along with them). It's not essential at this point in time, but I'd consider it cleaner, and would help with the overall utility of the code.
xienwolf May 02, 2009, 01:49 PM The python part will be for python callbacks, allowing Python to override a function or add input in the middle of it. The other parts, checking data on a new building or on a player/unit, are going to be included as I place data on them, it is everything which happens in the Cy____ files, with the Cy___Interface being the most important piece of the puzzle.
About 2 weeks now till I can return to full modding mode and get some more material written both here and in my personal modding endeavors :)
xienwolf May 02, 2009, 01:49 PM Bah, silly forum burping double-posts...
Anyway, if you meant something other than what is in the end of the first boolean tutorial (http://forums.civfanatics.com/showthread.php?t=314201#BOOL_CLONE_PYTHON), let me know and I'll make certain to cover it in some manner soonish.
phungus420 May 22, 2009, 02:28 AM Well, actually I have added a new integer iUpgradePriceModifier to buildinginfos, basically took Tstentom's Leonardo's Workshop and moved it over the SDK from Python (also as a side note I just followed the Boolean tag example, pretty similar with integers, just referenced other integer functions in the SDK to see what needed changing, but it's pretty much the same). It's cleaner this way, and I also wanted to make another change. Also this allows for easier modibility for any mod moders, if anyone ever starts modding Legends of Revolution. I was more wondering how to expose the integer that is set in the XML to python, so that other moders, or myself could reference it in Python code if it is convient. It just seems like one of those tags which should be exposed.
xienwolf May 22, 2009, 06:16 AM Referencing a loaded XML value happens through the Cy____ files, so it was covered at the end of the Boolean example. For BuildingInfos changes that would only involve CyInfoInterface(1, 2 or 3).cpp (single line addition). For that particular type of a field, probably you also want to expose a new field in CvPlayer, which would be through CyPlayerInterface.cpp, CyPlayer.cpp and CyPlayer.h (single line in ___interface, single function in the other 2).
Really the exposing to python is such a small and simple step for new XML fields and tracker values that it is easy to overlook and/or mess up by simply forgetting about it :) Just 6 lines total, 2 of which are just { or }.
phungus420 Jun 30, 2009, 06:47 PM OK, here is another question. I'll just go by example here as it'll explain exactly what I'm asking, I don't know how to ask this more simply.
In RevolutionDCM, the Revolutions Component adds an XML tag to the Civilizations section, it is found in CivicInfos. Now I want to clone this tag into LeaderheadInfos. I know I'll need to edit the CivilizationsSchema file, but I'm wondering since the tag is already loaded in CvInfos through the Civilizations section, if I'll need to also modify the dll, or if it'll just load the cloned tag. If it does load the cloned tag, will it add it (ie the Civilization is running a civic that provides a penalty to Revolutions, but their leadertrait adds a bonus will it add these together), or will it just overwrite the previous value with whatever it reads last? If it does overwrite, is there anyway to get it to add the value of the cloned tag, or do I need to create an entirly new variable and tag, even though I want it to do the exact same thing?
xienwolf Jun 30, 2009, 10:02 PM Yes, you MUST modify the DLL any time you add a new tag. Having the tag already exist means that the functional portion is simpler (places in the code it does something) because you just need to read from an additional location (though sometimes even that is complicated enough). It is actually in the Schema where you have the most work saved, in that you don't have to define what goes in the tag again (if you stay in the same Schema at least), but just say where to place things.
I finally seem to have things sorted out in my lab, so soon I should be doing dataruns, which means a couple of minutes where I have little else to focus on. Ideally I will be able to write some then, and I might jump straight to the Integer and Array fields since I have a few to write myself. I am attempting to stick to things which use the base BtS code so that someone who reads this can follow every step and compile along the way successfully, but unfortunately I am far more familiar with my own code and there are SO many tricks available in it which aren't there in BtS, so it takes me a while to "dumb down" my approach to handling things so that it is possible with native Firaxis code. But I will endeavor to have some more written semi-soonish.
phungus420 Jun 30, 2009, 11:38 PM So I will need to name it something else then I'm assuming? I can't use the exact same variable name, that just doesn't seem right :hmm:
Or rather in the XML I can use the same thing, but when I load these into CvInfos, I should split the two into something like m_RevDistanceModTraits and m_RevDistanceModCivics so that I don't run into any conflicts later in the code.
xienwolf Jul 01, 2009, 08:34 AM You can use the same variable name actually. The computer can tell the difference because they "live" in different places. It's like having 2 friends by the name of Jones. If you call Jones Sampson's house and ask for Jones, his mother isn't going to tell you that regretfully Jones Henry isn't available, but she can maybe send her son out to check the local park for him. She'll naturally hand the phone to her own son.
The "Full name" of your field will be something like:
CvLeaderHeadInfos::m_RevDisanceModCivics
as opposed to his
CvCivicInfos::m_RevDistanceModCivics
Of course, in this case having CIVICS in the name of something attached to a LEADERHEAD can get confusing for people who use your code (to include yourself later on down the road), so it might be worth changing the name. Though I personally would just drop the last word out so it is simply m_RevDistanceMod
phungus420 Jul 01, 2009, 03:23 PM Thanks xienwolf. Once I get the install script squared away, I will move onto this. Have alot of Dll stuff I need to do, most of it relateing to CvGameTextManager, which i should be able to figure out, but given the sheer amount of stuff I plan on working on, I'm sure a non programmer like myself will have a few questions.
xienwolf Jul 01, 2009, 07:33 PM No problem, the more you ask the more I know to include when I do a write up :)
deanej Jul 01, 2009, 08:40 PM Well, I wouldn't mind seeing the bUnique tag finished up sometime in the nearish future (doesn't have to be now, but within a 2-3 months would be nice) as I'm planning on using it in future versions of Star Trek. But feel free to add other stuff in the meantime.
cyther Jul 04, 2009, 09:18 AM I'd like to say thanks, Xienwolf. This guide was really helpful in teaching me how to mod the DLL.
Opera Jul 10, 2009, 02:41 AM Very useful thread :) I don't remember posting here even though I read it many times already...
One thing I'm looking forward to is array tag creation. I'm learning everyday how to mod the DLL by reading it and copying and etc. but I fear to try to create an array :lol:
xienwolf Jul 10, 2009, 11:27 AM I held off on those for quite a while myself when I was learning the ropes. In the end I still don't really use them, since I wound up teaching myself vectors and lists first and vastly prefer using them to arrays :)
Hopefully this weekend I'll be able to set aside some time to write up a bit more, at the least finish up the bUnique tag which I apparently stopped mid-write on.
Opera Jul 11, 2009, 01:26 AM I don't know how vectors work either. Are PrereqBuildingORs vectors? Lists?
Anyway, I hope you find time. It's somewhat selfish to hope that but I'd like to see your thread resumed. There's a lot to learn from you :)
xienwolf Jul 11, 2009, 10:03 AM The only Vector you really encounter would be See Invisible Types in UnitInfos (well, and SeeInvisible Types in my code). They are quite handy, but very easily overlooked by XML modders, so I try to avoid them.
xienwolf Jul 14, 2009, 05:03 PM bUnique tag walkthrough is now complete. Any requests for the next field I work on (specific field or even just which type of field, new or import, string, int or array...)?
Not sure when I'll get the time for it, but I plan to be somewhat brief and write it on the fly when I do get the time.
Opera Jul 14, 2009, 06:27 PM Well, you know it already: I'd like to know more about arrays, like the one used in BuildingInfos for RiverPlotYieldChange... but for, say, Hills or even a specific terrain.
deanej Jul 14, 2009, 06:54 PM Arrays sound good, especially since there aren't any existing tutorials with them (at least that I know of).
phungus420 Jul 14, 2009, 07:45 PM One of the Yield Arrays I was interested in working on was an effect from a tech or civic where Trade Route Yields would start producing :hammers: and :food: as well as the standard :commerce:. not sure if I would really use it though, but it seems like an interesting bit of code to set up, and the basic principle could be useful for other things.
deanej Jul 14, 2009, 07:55 PM Thankfully the traits already have that, so you would just have to clone it into the tech/civic area.
phungus420 Jul 14, 2009, 07:59 PM Hmm, I didn't think <TradeYieldModifiers/> would work because in the default game trade routes only yield :commerce: and multiplying a percent of 0 will still always return zero. Anyway, that's not really important, I'm sure there are plenty of good ideas for arrays.
xienwolf Jul 14, 2009, 08:16 PM Well, you know it already: I'd like to know more about arrays, like the one used in BuildingInfos for RiverPlotYieldChange... but for, say, Hills or even a specific terrain.
You are actually talking about Structures with the specifics of what you said, but mostly that is from not understanding what you are asking for just yet :) But basically it is a no-no to mix types of items into a single field unless you want to get REALLY complicated (like my CityBonuses in Promotions, that required a Structure, and was a PITA) Well, unless you actually MEANT to ask about doing two seperate arrays, then it is still just an array.
I'll probably do Yields for the Copy an existing function setup, since it is halfway done for you, then I'll make up something for the create your own which requires some serious loading work-arounds.
Hmm, I didn't think <TradeYieldModifiers/> would work because in the default game trade routes only yield :commerce: and multiplying a percent of 0 will still always return zero. Anyway, that's not really important, I'm sure there are plenty of good ideas for arrays.
Actually it will work perfectly. Your trade yields are a raw number, and are multiplied by what is listed in your TradeYield framework. By default you get 100 in Commerce, so all your yields are commerce. But with a default BtS civic you can add Food and Production to Yields from Traderoutes by just giving them values in that array.
I don't know if the TradeYieldModifiers exists in Technologies already, maybe I'll port it from Civics to Techs for the Array Copy.
Gooblah Jul 31, 2009, 02:26 PM Xienwolf, any thoughts about the functions of CvPlot/CvMap (i.e what they control, etc)?
xienwolf Aug 01, 2009, 04:20 PM Lotta functions there, so lots of thoughts on them :) It's a pretty broad question that you ask.
As a general overview of what each file controls, CvPlot holds data about the route/terrain/feature/bonus/improvement on a tile, and links to data on what units are present. CvMap is mostly just used by the main engine to control things like the fog of war, cloud overlay, and information layers, but it also has some search functions built into it.
I would say that if you are thinking of making modifications to how land/water work, you'll find yourself mostly working in CvPlot. If you are doing a graphical overhaul of the base engine, then you'd be mostly in CvMap.
phungus420 Aug 03, 2009, 11:19 PM Xienwolf, Cybah just made an interesting report:
Python Callbacks cause significant slowdown (http://forums.civfanatics.com/showthread.php?t=330458)
Now for Legends of Revolution, it uses the RevDCM core which only has one python callback function it uses, specifically cannot train for the inquistor. I'd like to set up a tag for required civics that will eliminate the need to use this callback. It seems to me the best way to do this would be to use a setup like the SeeInvisible tag which I believe is a vector. Unfortunately you haven't covered this yet. Basically it would be best is to have it work so that if nothing is specified the unit could be trained under any civic condition, but if something is specified then the unit may only be trained while running those civics (which would require the ability to list mulitiple civics, prefferably in a way similar to how you can define mulitple SeeInvisible types). Any chance you could explain vectors, and give direction on how to start on such a task?
xienwolf Aug 04, 2009, 12:33 AM Yes, the entire reason that the Callback file was created was Kael having discovered the immense processor toll taken by some callbacks which were running to BLANK python (but still cost a LOT, just to find out they were blank). So the Callback XML was generated so that BtS could remove this major lag source, but modders who were afraid of the DLL could easily bring back the few which they couldn't live without. This is one of the MAJOR reasons why I insist that DLL modding is superior to python modding. These areas of the code which run frequently run almost instantly in the DLL, and drag out in python.
I hadn't planned on covering vectors at all in the initial write-up of this tutorial. Most things you would think to use a vector for are best done as an array, or a list to save memory space. Vectors tend to confuse people who use your source because it isn't always obvious they are allowed to use it in that way (look at how often we get posts about the amazing new thing someone learned about SeeInvisible, or how few people know what we are talking about at all and will be looking it up shortly).
To make the processing go quicker, you'll actually be wanting to toss in a boolean (not read from XML, but set while reading the XML) with the array, to signify the need to check civics at all.
Unfortunately I have a HUGE test I am preparing for on the 17th and 19th, so I shouldn't devote the time I would need to writing up good information for you right now. But I will say that you should ignore the vectors and instead look at how m_piPrereqAndTechs or m_piPrereqOrBonuses are set up. Unfortunately the section in ::read(XML) will probably confuse you to no end, that is the kind of thing I was planning to walk through slowly in the "Create your own array" when I got to it, but I got swamped midway through the Copy Array writeup. Though what I have on that so far might be of help to you (I wasn't going to announce I had done any work on it till I was done, but I am mostly complete already with it).
Here is the section I anticipate you having problems with:
if (gDLL->getXMLIFace()->SetToChildByTagName(pXML->GetXML(),"PrereqBonuses"))
{
if (pXML->SkipToNextVal())
{
iNumSibs = gDLL->getXMLIFace()->GetNumChildren(pXML->GetXML());
FAssertMsg((0 < GC.getNUM_UNIT_PREREQ_OR_BONUSES()),"Allocating zero or less memory in SetGlobalUnitInfo");
pXML->InitList(&m_piPrereqOrBonuses, GC.getNUM_UNIT_PREREQ_OR_BONUSES(), -1);
if (0 < iNumSibs)
{
if (pXML->GetChildXmlVal(szTextVal))
{
FAssertMsg((iNumSibs <= GC.getNUM_UNIT_PREREQ_OR_BONUSES()) , "There are more siblings than memory allocated for them in SetGlobalUnitInfo");
for (j=0;j<iNumSibs;j++)
{
m_piPrereqOrBonuses[j] = pXML->FindInInfoClass(szTextVal);
if (!pXML->GetNextXmlVal(szTextVal))
{
break;
}
}
gDLL->getXMLIFace()->SetToParent(pXML->GetXML());
}
}
}
gDLL->getXMLIFace()->SetToParent(pXML->GetXML());
}
I briefly mention the SetToChild deal in the copy Array walkthrough, same thing here, you are "moving inside" the deeper layer of XML, so have to remember to move back out at the end with SetToParent. SkipToNextVal checks if there is anything else written within the current level of XML. GetNumChildren checks how many total things are written at this level. Ignore the Assert, it only matters when you do a debug DLL much later, InitList stores 0 values in the entire array so we can have meaningful data to start with. Next, the check for iNumSibs > 0 is redundant since we couldn't have 0 siblings and still have passed the NextVal check earlier. Then we load the value of the first child in szTextVal (notice that it doesn't matter at all what the child is called in the XML), again ignore the Assert message. Now a loop starts which will check what szTextVal matches (in this case a bonus, in your case a civic) and flags a boolean in our array as true which matches this civic. We then check if there is another XML value (there should be, unless one of those siblings was a comment) and prepare to load it next. It is a little confusing because the pXML->GetNextXmlVal(szTextVal) is actually changing the value of szTextVal while it runs, and then returns a boolean value as well.
Anyway, you could use this exact routine copied and change the variables for the array to something properly civic-named, and the name of the piece in quotes at the top to what you use in the XML for your new field. Compare the Tech with the Bonus to see precisely what to change and where.
I had mentioned also setting up a boolean value during this array. It would just be a method of speeding up the code even more than normally done, at the minor cost of a bit more savegame size (a VERY minor cost). But I haven't really time to get into how to do that.
I am not sure if there are any good examples of a loop over all civic types in the code for you to build the actual functional pieces from. If there aren't and you don't hear from me in a semi-speedy manner when asking for more help, I do some things fairly similar to this in Fall Further, so you could grab our sourcecode and look at it for examples. I don't do this EXACT thing though, and am not sure if I have set up any arrays for civics, but I am reasonably certain that I did do some Civic arrays in PromotionInfos which you can check out. Actually, I am almost 100% certain that I have done so. But I used lists instead of arrays to save some memory in the savefile, and don't use the boolean trick I am alluding to on occasion because I hadn't thought of it yet and haven't had the time to retro-fit all of my code to use it for the performance boost.
rockinroger Aug 04, 2009, 08:37 PM Excellent work. As someone trying to move from simple xml to more complex c++ and python, its greatly appreciated. Now maybe i can figure out what is causing this crash
int CvHotkeyInfo::getOrderPriority() const
{
return m_iOrderPriority;
}
xienwolf Aug 04, 2009, 08:46 PM What kind of crash are you getting with that function? Pretty basic (ie - fundamental, not simple) function there which impacts a LOT of the code. But also looks absolutely unchanged to me.
TC01 Aug 05, 2009, 07:01 AM Where in the SDK would I go if I wanted to change a python callback on the SDK side of things?
For instance, if I wanted to control whether I could or could not build an improvement on a plot (canBuild/cannotBuild in CvGameUtils.py), there's a canBuild in both CvPlayer.cpp and CvPlot.cpp, one seems to call the other. Which one would I edit? (I want to check if other units are building the same improvement, and if they are, stop the improvement- which I created a boolean tag for- from being built). It seems to work the same way for things like canConstruct, or canFound (except here there's one in CvUnit.cpp and CvPlayer.cpp).
xienwolf Aug 05, 2009, 03:31 PM It really depends on what information you need for your prereq condition. Typically the information available to you in each of the function calls is rather different (the canConstruct example would be that in CvUnit you know if the unit is a certain level or unit type, and in CvPlayer you only know what technologies the player has so far). Usually one of the two functions is fairly simple and is called at the start of the other function to make things faster by looking for obvious problems (like not having the right technology for the action).
In your specific case, while checking the Plot it is just seeing if the terrain and features and elevation allow the action, and while checking the player it is seeing if the technologies are known. So whichever one has the info you need (you said based on improvements which are already there? So that would be in CvPlot) is the one you write for.
Cybah Aug 27, 2009, 06:35 AM Do you know if it's "easy" to clone the
<ObsoleteTech>NONE</ObsoleteTech>
into Civ4UnitsInfos.xml ?
phungus420 Aug 27, 2009, 06:57 AM That should be relatively easy. It's a string value, which isn't much differnent then a boolean or integer, pretty much the same thing really, just need to take that into account when defining the stuff in CvInfos, but pretty much you can just follow the cloning a boolean tag example. The hard part would be getting the functionality to work, but that shouldn't be too hard either. I was able to clone the MaxEraStart string from BuildingInfos to UnitInfos pretty easily, and the only problem I experienced that I needed help with was getting the information to display properly in the civilopedia, and those CvGameTextMgr issues weren't too difficult, but I did need to ask a couple questions on it in the Python/SDK forums, got the functionality to work fine on my own though. Overall the ObsoleteTech function should be pretty similar and it's pretty much a straight cloning of the code, just need to take account you're dealing with pUnit instead of pBuilding and such.
Cybah Aug 27, 2009, 07:42 AM Can you explain how you have added the information to the civilopedia?
phungus420 Aug 27, 2009, 08:04 AM http://forums.civfanatics.com/showpost.php?p=8285411&postcount=2
Look at the code in the Unit Era Limit spoiler, it'll show you everything I did to clone the MaxStartEra tag into UnitInfos. Since you're cloning a different tag, it wol't be exactly the same, but it'll be very similar.
xienwolf Aug 27, 2009, 04:34 PM One thing you will have to decide is precisely what it means for a unit to be obsolete though. For buildings, they stop working completely, and cannot be built. If my cities are all defended by nothing but warriors and suddenly they go obsolete and disappear off the map, I may be a tad upset. So cities cannot build the unit anymore is an easy thing to decide, but all the unit already existing, do they get upgraded for free? Which of the multiple possible upgrades do they become for units who can go down 2 paths? Does the upgrade still cost gold, but you don't get a choice about it? If so, how do you handle setting the player to a negative gold value in the treasury? Or do the units simply exist on the map with the knowledge that replacements cannot be trained (and that they probably suck and need updated manually)?
But yes, it will be a "simple" import for you. Just that unlike most imports, none of the functional pieces will be "prebuilt" for you, you simply have a nice example to reference at almost every step along the way.
Cybah Aug 27, 2009, 05:35 PM O_o good point. The units shall still stay in the game but they should not be buildable anymore. Maybe I really should use maxerastart instead?
phungus420 Aug 27, 2009, 08:49 PM O_o good point. The units shall still stay in the game but they should not be buildable anymore. Maybe I really should use maxerastart instead?
MaxStartEra and ObsoleteTech are two different concepts. MaxStartEra means that the unit can't be built on the defined era and later starts, ObsoleteTech should mean (based on how it works for buildings) that the unit can't be trained if you have that tech.
You can use the MaxStartEra example as a guide. But you'll need to use common sense to account for the differences. such as all the NO_ERA references need to be NO_TECH, and you'd need to rename MaxStartEra to ObsoleteTech etc. Like I said the biggest issue is the actual functionality but even that isn't too different. Instead of this code for MaxStartEra:
bool CvPlayer::canTrain(UnitTypes eUnit, bool bContinue, bool bTestVisible, bool bIgnoreCost) const
{
PROFILE_FUNC();
...
if (GC.getUnitInfo(eUnit).getMaxStartEra() != NO_ERA)
{
if (GC.getGameINLINE().getStartEra() > GC.getUnitInfo(eUnit).getMaxStartEra())
{
return false;
}
}
You'd use this:
bool CvPlayer::canTrain(UnitTypes eUnit, bool bContinue, bool bTestVisible, bool bIgnoreCost) const
{
PROFILE_FUNC();
...
if (GC.getUnitInfo(eUnit).getObsoleteTech() != NO_TECH)
{
if ((GET_TEAM(getTeam()).isHasTech((TechTypes)(GC.get UnitInfo(eUnit).getObsoleteTech()))))
{
return false;
}
}
You would need to do similar for CvGameTextManager as well.
Cybah Aug 27, 2009, 09:08 PM with your suggestion - existing - obsolete units won't disappear off the map?
phungus420 Aug 27, 2009, 09:55 PM bool CvPlayer::canTrain(UnitTypes eUnit, bool bContinue, bool bTestVisible, bool bIgnoreCost) const
This is just a check in CvPlayer to see if the unit is trainable. The code I show two posts above checks the players techs and makes the unit unable to be trained if the player has the tech, nothing more. I don't even know how to make a unit disapear off the map even if I wanted too.
When in doubt read the code and see if you can figure out what it means. Most of the example code from MaxStartEra and the suggested code above should make sense intuitively, so you should be able to understand it for the most part.
xienwolf Aug 28, 2009, 01:14 AM To remove units mid-game by force you would have to ask CvPlayer for a list of all units it owns, then loop over that list and run a kill command on each one you deem worthy of removal.
Cybah, remember that even though the names mean something to US and we can see a connection between this function and the other one and ask these kinds of questions, to the computer, the names are meaningless garbage, and it doesn't know that anything else exists, let alone that this is similar to it. So the computer will only ever do what you tell it to do. The way you run into issues with the computer is when you find out that you told it to do something at the wrong time (ie - it asked you a question you weren't expecting, so you had the wrong answer prepared).
Opera Aug 28, 2009, 01:35 AM What is the point of PROFILE_FUNC()?
Cybah Aug 28, 2009, 05:20 AM damn... one thing just came in my mind:
for the maceman/grenadier/rifleman problem we would need TWO obselete techs: military_science and rifling.
phungus420 Aug 28, 2009, 06:38 AM Xienwolf in the canTrain function:
bool CvPlayer::canTrain(UnitTypes eUnit, bool bContinue, bool bTestVisible, bool bIgnoreCost) const
What is the point of these identifiers?:
bool bContinue, bool bTestVisible, bool bIgnoreCost
Why can't we just say:
bool CvPlayer::canTrain(UnitTypes eUnit) const
?
xienwolf Aug 28, 2009, 09:58 AM What is the point of PROFILE_FUNC()?
That allows the Profiler to track the time spent in this function and the number of calls to it when you have the profiler enabled. VERY useful functions which were used to decide what to cache to make CAR Mod.
damn... one thing just came in my mind:
for the maceman/grenadier/rifleman problem we would need TWO obselete techs: military_science and rifling.
You have two choices: Create an array field, like <TechTypes>, where having any listed tech obsoletes you, or choose just one of the two techs to obsolete with (Or a tech which requires both of them, and just accept a brief additional period of relevance)
Xienwolf in the canTrain function:
bool CvPlayer::canTrain(UnitTypes eUnit, bool bContinue, bool bTestVisible, bool bIgnoreCost) const
What is the point of these identifiers?:
bool bContinue, bool bTestVisible, bool bIgnoreCostWhy can't we just say:
bool CvPlayer::canTrain(UnitTypes eUnit) const?
bContinue will ignore the number you are ALREADY making, but not yet done with. Thus if only 4 of the unit can ever exist and you own 3, you can only start building one more. Each turn the city checks if you are allowed to keep working on whatever the city is making (ie - someone else finished a wonder before you). So even though your unit count is 3 alive + 1 in process, making all OTHER cities unable to train this unit, in the city making him we do a bContinue check so that we are still allowed to make him and thus finish.
bTestVisible is used for "greyed out" units in the build order. Like when you have the tech, but not a required building. a mouseover tells you WHY you cannot build it yet (If the modder responsible for the limiting field updated CvGameTextMgr properly)
bIgnoreCost is there because we also use ::canTrain to find out if we can upgrade existing units. a unit with <iCost>-1</iCost> cannot be built in a city. But by ignoring the cost check, other units can upgrade to the unit.
TheLadiesOgre Sep 02, 2009, 04:28 PM I know that you have effectively stated that vectors should be avoided, but just out of curiousity-- How hard would it be it clone the <Invisible> vector from UnitInfos to PromotionInfos?
xienwolf Sep 02, 2009, 04:40 PM SeeInvisible is the only vector in BtS code that I am aware of. Invisible is left as just a single type. Cloning SeeInvisible to Promotions wasn't too difficult, but to make Invisible into a vector and add it to PromotionInfos takes a bit more work since you have to reconfigure things to allow a unit to have more than 1 type of invisibility at a time, and decide what that means for finding the unit (my approach had been that if you have 2 types of invis, the opponent must see through both types simultaneously. But you may decide to create a "ranking" for Invis types where only the highest ranking invis matters, and all SeeInvis types above that rank can see you)
TheLadiesOgre Sep 02, 2009, 05:33 PM So long as it could be cloned, I doubt, I'd need the invis to be a vector, I'm really just tossing around ideas that I have in my head, another one, I think I can hijack ::resolveCombat with a pair of bools bVictor and bDefeated, if I can get that done, how hard do you think it would be to add a Commerce Yield like is done with Python in Industry Espionage, Marauder and Sneak? (just in case you aren't familiar they each award the winner of combat with percentage of the losers owners store of :science:, :commerce: and :espionage: respectively)
deanej Sep 02, 2009, 06:23 PM Conqueror's Delight had both Invisible and SeeInvisible with the promotions (as well as a bunch of other stuff); unfortunately it does not have a 3.19 version, but if you want to port those over you can have a look at the SDK source for my Star Trek mod (I recommend using a file comparison utility like Beyond Compare or WinMerge because it makes finding the changes much easier).
xienwolf Sep 02, 2009, 11:58 PM Commerces and Yields from combat are available in a Modcomp already out there, but I cannot remember if he had included them in only UnitInfos or also in PromotionInfos. I wound up expanding his code quite a bit when I imported it a long time ago, so you can see them in the Fall Further code, along with SeeInvis and Invis. Just remember that importing is always bad if it is a straight copy/paste. Never allow a single line of code into your mod which you do not understand and couldn't write for yourself (except in RARE cases where it is very useful, far beyond your abilities, certainly stable, and you know the limits of what it affects so that you don't blame every bug that ever comes up in the future on it. Otherwise that is precisely what you will find yourself doing. The imported code becomes your scapegoat/gremlin)
Dom Pedro II's Conqueror's Delight was a fantastic source of code, as well as History of the Three Kingdoms. Both are quite worth checking the source of for inspiration and samples while you write functions of your own.
Anyway, for the vectors the main trick which you have to do right, but can just clone line for line from SeeInvisible, is the parsing of the string based on tokens. The keyword token is utilized in the code so that you won't easily miss when it happens. All it does is take the one long string which exists in the XML and look for any commas. When one is found, the comma is removed and everything before it is used in a lookup to decide which invis type was just added. Repeat till end of string.
And the Yields/Commerces from kills was Food From Animals Modcomp as I recall it. Also by Dom Pedro II, so probably a part of Conqueror's Delight eventually.
TheLadiesOgre Sep 03, 2009, 08:25 AM Conqueror's Delight had both Invisible and SeeInvisible with the promotions (as well as a bunch of other stuff); unfortunately it does not have a 3.19 version, but if you want to port those over you can have a look at the SDK source for my Star Trek mod (I recommend using a file comparison utility like Beyond Compare or WinMerge because it makes finding the changes much easier).
Thank you sir, always (of recent at least) interested in looking at mod sources
Commerces and Yields from combat are available in a Modcomp already out there, but I cannot remember if he had included them in only UnitInfos or also in PromotionInfos. I wound up expanding his code quite a bit when I imported it a long time ago, so you can see them in the Fall Further code, along with SeeInvis and Invis. Just remember that importing is always bad if it is a straight copy/paste. Never allow a single line of code into your mod which you do not understand and couldn't write for yourself (except in RARE cases where it is very useful, far beyond your abilities, certainly stable, and you know the limits of what it affects so that you don't blame every bug that ever comes up in the future on it. Otherwise that is precisely what you will find yourself doing. The imported code becomes your scapegoat/gremlin)
Dom Pedro II's Conqueror's Delight was a fantastic source of code, as well as History of the Three Kingdoms. Both are quite worth checking the source of for inspiration and samples while you write functions of your own.
Anyway, for the vectors the main trick which you have to do right, but can just clone line for line from SeeInvisible, is the parsing of the string based on tokens. The keyword token is utilized in the code so that you won't easily miss when it happens. All it does is take the one long string which exists in the XML and look for any commas. When one is found, the comma is removed and everything before it is used in a lookup to decide which invis type was just added. Repeat till end of string.
And the Yields/Commerces from kills was Food From Animals Modcomp as I recall it. Also by Dom Pedro II, so probably a part of Conqueror's Delight eventually.
First off, I noticed I screwed up in my last post, I meant :gold: not :commerce:, sorry. I've downloaded DPII's Conqueror's Delight, will download HoTK once I finish this post. Thank you for the tip on vectors. I am aware of Food From Animals, nice modcomp, though not really what I am looking for right now (I think it would be a less extensive change to add it to the promotion itself instead of every unit). The Fall Further sources :drool:, I will be pillaging them back to the stone age probably, several of the ideas that I had to include, you've already done, make no mistake, I will properly credit you where ever I use your code. Thank you, gentlemen.
xienwolf Sep 03, 2009, 09:22 PM I look forward to seeing the uproar/activity if you ever make CityBonuses or AutoAcquire/MustMaintain available to general BtS. Or the Commander/Minion system. Each led to lots of activity among the FfH based mods which decided to use them (but I'm a bit too busy to export to BtS and share for the "glory" of the thing. Especially since that means updating all of the code each patch)
TheLadiesOgre Sep 04, 2009, 08:00 AM What file must I tinker in to have a promotion give a notification similar to the collateral damage notification (you know, the "your [blank] has caused collateral damage (X units)")? Oh, and I will definitely be adding the ones you mentioned (I really like the Commander/Minion idea) at some point in the near future (along with iSlaveGenerationChance and CivicPrereqs both essential for a slaver promotion)
xienwolf Sep 04, 2009, 08:00 PM For that you would make the announcement happen when you perform the action. So you would be triggering gDLL->getInterfaceIFace()->addMessage. The specific place where Collateral is called would be
if (iDamageCount > 0)
{
szBuffer = gDLL->getText("TXT_KEY_MISC_YOU_SUFFER_COL_DMG", iDamageCount);
gDLL->getInterfaceIFace()->addMessage(pSkipUnit->getOwnerINLINE(), (pSkipUnit->getDomainType() != DOMAIN_AIR), GC.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_COLLATERAL", MESSAGE_TYPE_INFO, getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_RED"), pSkipUnit->getX_INLINE(), pSkipUnit->getY_INLINE(), true, true);
szBuffer = gDLL->getText("TXT_KEY_MISC_YOU_INFLICT_COL_DMG", getNameKey(), iDamageCount);
gDLL->getInterfaceIFace()->addMessage(getOwnerINLINE(), true, GC.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_COLLATERAL", MESSAGE_TYPE_INFO, getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_GREEN"), pSkipUnit->getX_INLINE(), pSkipUnit->getY_INLINE());
}
In CvUnit::collateralCombat.
You send the player number who gets the message, a boolean to say if you want to force the view with the message (ie - zoom the interface to the active tile specified later), the length of time for which the message should display before the next item in the queue shows (remember you only see up to... 4 messages?... at a time), the message to use, a sound to play when the message displays, what type of message it is (if it will be included in Replay save, if it will get added to the Combat Log, if it will get added to event log, if it will just display and not be available if missed...), an image icon to display, what color to have the message display in, a tile to highlight (X and Y Co-ords), boolean about showing an arrow pointing toward the tile if the tile is offscreen, a boolean about showing an arrow pointing toward the tile if the tile is onscreen.
TheLadiesOgre Sep 05, 2009, 05:23 PM Thank you sir, if you wouldn't mind, I am having a little trouble with this particular bit of code (I can't seem to get it to work for the life of me). This is from CvUnit.cpp ::resolveCombat this is right before the last break;
/************************************************** ****************************************/
/* Author: TheLadiesOgre (Original Concept By: Tsentom1) */
/* Date: 03.09.2009 */
/* ModComp: Promotions DLL */
/* Intended to: Add functionality for iVictory________Heals */
/************************************************** ****************************************/
if ((getVictoryAdjacentTileHeal()) >= GC.getGameINLINE().getSorenRandNum(100, "Field Hospital Die Roll"))
{
int iI;
for (iI = 0; iI < NUM_DIRECTION_TYPES; iI++)
{
CvPlot* pLoopPlot = plotDirection(pPlot->getX_INLINE(), pPlot->getY_INLINE(), ((DirectionTypes)iI));
if (pLoopPlot != NULL)
{
if (pLoopPlot->area() == pPlot->area())
{
CLLNode<IDInfo>* pUnitNode = pLoopPlot->headUnitNode();
while (pUnitNode != NULL)
{
CvUnit* pLoopUnit = ::getUnit(pUnitNode->m_data);
pUnitNode = pLoopPlot->nextUnitNode(pUnitNode);
if (pLoopUnit->getTeam() == getTeam())
{
pLoopUnit->doHeal();
}
}
}
}
}
}
if ((getVictorySameTileHeal()) >= GC.getGameINLINE().getSorenRandNum(100, "Field Surgeon Die Roll"))
{
CLLNode<IDInfo>* pUnitNode = pPlot->headUnitNode();
while (pUnitNode != NULL)
{
CvUnit* pLoopUnit = ::getUnit(pUnitNode->m_data);
pUnitNode = pPlot->nextUnitNode(pUnitNode);
if (pLoopUnit->getTeam() == getTeam())
{
pLoopUnit->doHeal();
}
}
}
if ((getVictoryHeal()) >= GC.getGameINLINE().getSorenRandNum(100, "Field Medic Die Roll"))
{
doHeal();
}
if (getVictoryMoveCount() > 0)
{
setMoves(movesLeft() + 1);
}
/************************************************** ****************************************/
/* Promotions DLL End; TheLadiesOgre; 03.09.2009 */
/************************************************** ****************************************/
I've bolded the part I'm having trouble with, it is most of the way down. The victory heals are custom, they seem to be working (they are what I wanted to know how to notifiy the player about in the last post). I've followed the guide, entered it everywhere I needed to, but it just refuses to work.
xienwolf Sep 06, 2009, 01:16 AM At the very end of CvUnit::doTurn() you will find:
setMoves(0);
So I am inclined to believe that you want the unit to have the ability to move MORE after winning the battle, and thus want to SUBTRACT 1 from the current number of moves. You could also just use changeMoves(-1) to save some typing.
TheLadiesOgre Sep 06, 2009, 11:11 PM btw, that fix worked like a charm, now it works and it is not quite so powerful (blitz/skirmisher was a ridiculous combo) as it only fires once per turn, thanks again. I will be finishing adding the promotions I want to before releasing v0.3 (upload 0.2 a little while ago)
TheLadiesOgre Sep 08, 2009, 12:57 PM CivicPrereq is giving me a headache, I decided to clone StateReligionPrereq into CivicPrereq (as well as adding bAutoAcquire and bMustMaintain), compiled fine, no errors that I couldn't take care of easily (#$^#$%&%^#@ Punctuation Marks!!!!). I've tried testing it out but everytime I load the mod, the XML complains about Tag: CIVIC_THEOCRACY in Info class is incorrect. I've also tried THEOCRACY, CIVIC_VASSALAGE, VASSALAGE-- nothing is working. Any inklings as to what I am doing wrong?
xienwolf Sep 08, 2009, 01:10 PM You need a readpass3, I would guess you tried to do it in the normal ::read(XML) and thus get the errors because no civics exist yet at that point.
TheLadiesOgre Sep 08, 2009, 01:14 PM Thank you sir
TheLadiesOgre Sep 08, 2009, 04:34 PM 1) Will I have to add the readPass3 (for PromotionInfos) to CvXMLLoadUtilitySet? I am assuming that is a yes, but it doesn't hurt to ask.
2) Just to double check, I was figuring that I'd add bAutoAcquire under ::setHasPromotion along with any Prereqs I wanted to check, sound about right?
if (GC.getPromotionInfo(ePromotion).isAutoAcquire())
{
if (GC.getPromotionInfo(ePromotion).getPrereqPromotio n() != NO_PROMOTION)
{
if (!isHasPromotion((PromotionTypes)(GC.getPromotionI nfo(ePromotion).getPrereqPromotion())))
{
return false;
}
}
if (GC.getPromotionInfo(ePromotion).getPrereqOrPromot ion1() != NO_PROMOTION)
{
if (!isHasPromotion((PromotionTypes)(GC.getPromotionI nfo(ePromotion).getPrereqOrPromotion1())))
{
if ((GC.getPromotionInfo(ePromotion).getPrereqOrPromo tion2() == NO_PROMOTION) || !isHasPromotion((PromotionTypes)(GC.getPromotionIn fo(ePromotion).getPrereqOrPromotion2())))
{
return false;
}
}
}
if (GC.getPromotionInfo(ePromotion).getTechPrereq() != NO_TECH)
{
if (!(GET_TEAM(getTeam()).isHasTech((TechTypes)(GC.ge tPromotionInfo(ePromotion).getTechPrereq()))))
{
return false;
}
}
if (GC.getPromotionInfo(ePromotion).getStateReligionP rereq() != NO_RELIGION)
{
if (GET_PLAYER(getOwnerINLINE()).getStateReligion() != GC.getPromotionInfo(ePromotion).getStateReligionPr ereq())
{
return false;
}
}
if (GC.getPromotionInfo(ePromotion).getCivicPrereq() != NO_CIVIC)
{
for (int iI = 0; iI < GC.getNumCivicInfos(); ++iI)
{
CivicTypes eCivic = GET_PLAYER(getOwnerINLINE()).getCivics((CivicOptio nTypes)iI);
if (GC.getPromotionInfo(ePromotion).getCivicPrereq() != eCivic)
{
return false;
}
}
}
}
I was figuring that I'd slide that into the list where I put bAutoAcquire in the schema or should I save it for last? In the same vein, I would add bMustMaintain to ::isPromotionValid using similar code as is in the spoiler only replacing bAutoAcquire with bMustMaintain, do you think this would work like this?
xienwolf Sep 08, 2009, 05:21 PM 1) Yes. And make sure you put it somewhere after Civics, I don't see any harm at putting it after EVERYTHING personally, not sure why any readpasses are placed earlier in the order.
2) What I had done with AutoAcquire and MustMaintain was to place checks against isPromotionValid in various locations (::doTurn, ::setXY, ::setHasPromotion...) where you can have your prereqs suddenly no longer be maintained. isPromotionValid had to be modified though to ignore the claim that a promotion you already have is automatically invalid when doing a MM check, if I got a false, I forced a ::setHasPromotion(eProm, false). And for AutoAcquire, I tested (in much the same locations) against ::canPromote, if I got a true, I forced a ::promote to run. This allowed me to force a unit to spend the XP gained for a level if desired, though I only use AA/MM on bNoXP promotions thus far so that the user doesn't have their level selection forced upon them.
The way you describe your way doesn't sound like it would quite work, as you cannot remove a promotion from a unit during ::isPromotionValid since it is a CONST function and cannot alter data. And doing the checks in ::setHasPromotion would only check against a promotion they are already in the process of gaining/losing. So maybe I am reading what you wrote wrong.
TheLadiesOgre Sep 09, 2009, 08:31 AM 1) Yes. And make sure you put it somewhere after Civics, I don't see any harm at putting it after EVERYTHING personally, not sure why any readpasses are placed earlier in the order.
Ok, I'm assuming I attach it to PromotionInfos and reference CivicTypes in the "for" statement. Should I comment out the preexisting PromotionInfos entry?
The way you describe your way doesn't sound like it would quite work, as you cannot remove a promotion from a unit during ::isPromotionValid since it is a CONST function and cannot alter data. And doing the checks in ::setHasPromotion would only check against a promotion they are already in the process of gaining/losing. So maybe I am reading what you wrote wrong.
You're right, it doesn't. You weren't reading it wrong, I am just not nearly as familiar with CivIV's guts as you are (though I am trying to gain familiarity by doing this). Part of me is half-tempted to just port it directly over (as it is too good of an idea not to add). I wasn't planning on adding bNoXP as I was only planning on using bAutoAcquire for one promotion (Frekk's Enslaver, make the Prereqs CIVIC_SLAVERY and Combat I, though force the XP expenditure in an effort to balance it somewhat), however, looking at it from the user friendly POV, I probably should include it and just leave it set to "0". Which brings to mind another question: Is the Commander/Minion System closer to Beginner or Advanced? (I have an idea concerning things like slave armies and such :D)
xienwolf Sep 09, 2009, 02:44 PM I don't understand your first question. What you need to do is create a third readpass for promotioninfos, if one doesn't already exist, and ensure that the calls for it come after Civics are loaded.
This means in CvXMLLoadUtilitySet you want a loop over getNumPromotionInfos which executes readPass3 on each one. (kPromotion.readPass3())
I would call both Commander system and AA/MM intermediate/advanced systems. You need to understand most all of CvUnit for each
TheLadiesOgre Sep 09, 2009, 04:58 PM This means in CvXMLLoadUtilitySet you want a loop over getNumPromotionInfos which executes readPass3 on each one. (kPromotion.readPass3())
Like this?:
LoadGlobalClassInfo(GC.getCivicInfo(), "CIV4CivicInfos", "GameInfo", "Civ4CivicInfos/CivicInfos/CivicInfo", false, &CvDLLUtilityIFaceBase::createCivicInfoCacheObject) ;
for (int i=0; i < GC.getNumVoteSourceInfos(); ++i)
{
GC.getVoteSourceInfo((VoteSourceTypes)i).readPass3 ();
}
/************************************************** ****************************************/
/* Author: TheLadiesOgre (Original Concept By: Xienwolf) */
/* Date: 09.09.2009 */
/* ModComp: Promotions DLL */
/* Intended to: Add readPass3 for PromotionInfos to enable CivicPrereqs */
/************************************************** ****************************************/
for (int i=0; i < GC.getNumPromotionInfos(); ++i)
{
GC.getPromotionInfo((PromotionTypes)i).readPass3() ;
}
/************************************************** ****************************************/
/* Promotions DLL End; TheLadiesOgre; 09.09.2009 */
/************************************************** ****************************************/
I would call both Commander system and AA/MM intermediate/advanced systems. You need to understand most all of CvUnit for each
So are you recommending that I wait to implement such things as Slave Armies and Title Promotions (like: you are Sir Somebody, you get a Squire)?
Actually, while I am thinking about slaves, any suggestions for implementing a random chance you die after a build? In Frekk's Enslaver Promotion the slave dies after doing one thing, I would like to change that though. I would like to make it so that a slave has a small chance per build to die (ideally it would be cumulative so that the slave you take could die after their first build or could take 15 builds to be "worked to death") but unless gifted to another civ or released back to their home civ they will die sooner or later. Eventually, I'd want to also add two negative diplo hits one for "You've enslaved our people" and the second for "You've worked our people to death" which would both stack with Afforess' CivicAttitudeModifier (adding up to you being powerful but despised by the world for your civic choices).
xienwolf Sep 09, 2009, 06:02 PM Yes, exactly like that. As long as you had to create CvPromotionInfo::readPass3() yourself from scratch. If it already existed, just make sure it is listed after civics and use the one which already exists.
I wouldn't say you have to wait on them, but if you want to be able to do them from scratch then it is advisable that you get some more experience, or that you flowchart everything before you begin and request feedback to ensure you are predicting things properly. You could use them as an exercise to push yourself though by attempting to write them yourself, then comparing against my code and ensuring you understand why everything is where it is. Since things are not clearly labeled as being JUST for this system or that one, you would have to do some serious digging or know roughly where to look (or at least what to look for) anyway or you would miss some vital pieces. So even just a straight import, as long as you comment on each section of code precisely what it does, and ask questions when unsure, could be a learning experience.
You could implement a chance for death fairly easy on slaves. I assume you mean a chance to die each time they finish building an improvement in the normal method. In that case you would look for the lines of code which check work progress on a tile against the build cost of the improvement and run setHasImprovement on the tile if complete. In there (while still linked to the unit) you would check the unit for an integer value which denotes the chance to die per build and kill them if they fail. Or you could invent a hidden (or not) vitality system and make each improvement (or contribution of work TOWARD an improvement) cost the unit a certain amount of their vitality, and once all is spent, they die. You could even link such a vitality system to the number of units displayed for the slave on the map so that the player can guess roughly when a slave is about to die, and/or provide a method of regenerating (resting) slaves.
TheLadiesOgre Sep 10, 2009, 06:02 PM You could implement a chance for death fairly easy on slaves. I assume you mean a chance to die each time they finish building an improvement in the normal method. In that case you would look for the lines of code which check work progress on a tile against the build cost of the improvement and run setHasImprovement on the tile if complete. In there (while still linked to the unit) you would check the unit for an integer value which denotes the chance to die per build and kill them if they fail. Or you could invent a hidden (or not) vitality system and make each improvement (or contribution of work TOWARD an improvement) cost the unit a certain amount of their vitality, and once all is spent, they die. You could even link such a vitality system to the number of units displayed for the slave on the map so that the player can guess roughly when a slave is about to die, and/or provide a method of regenerating (resting) slaves.
I am really liking the idea of a hidden vitality system which is slightly drained each turn the slave works on an improvement, in addition to a small chance that they die outright. Which brings me to yet more ideas I have regarding slaves
1) Is it possible to have the unit not be killed but be made into a slave while still retaining the information concerning their prior incarnation? For example, I take a Chinese Swordsman as a slave, it gets logged that he was a Chinese Swordsman, remembering his strength (though not displaying it) so that this then becomes the slaves hidden vitality rating? If I later chose to "liberate" him back to the Chinese (or if he was captured by a Chinese combat unit), he would once again become a Swordsman (regaining his promotions and having the same number of hit points left that the slave did in vitality), however, if I gift him to the French, he remains a slave. I also like the idea of displaying the slaves vitality through the number of units displayed on the map, I also feel that the method for healing slaves should be as typical though at a rate about 20% of normal (after all they're slaves, even if they aren't slaving away building my improvements, they're slaving away serving my populace doing the jobs that are beneath my people).
2) How hard would it be to have the same Chinese Swordsman from the above example retain his asian UnitArtStyleType even though he is slaving over my greco-roman improvements?
xienwolf Sep 11, 2009, 02:19 PM You could use ::convert so that you capture the exact same unit, promotions and all. Then in a new variable, mark who his original owner was (could do this for all units on creation and have it carry with conversion, but change on gifting).
Then you set bypasses in various locations to force the stats of the unit to be those of a slave when the original owner != current owner. To get the ability to build improvements you would want to override the unittype, which may not be easy.
That would do most of what you want. All original unit stats would still be available for checking, but the unit would seem to be a slave.
To maintain artstyle, link art checks to CvUnit instead of unitinfos.
TheLadiesOgre Sep 12, 2009, 04:17 PM Alright, I finished up the PillageCommerce stuff and when I went to compile, it complained about an illegal else without an if, but there is an if. This is the code (watch out its a lot, I've bolded the if and the matching else):
if (isDead())
{
int iExperience = defenseXPValue();
iExperience = ((iExperience * iAttackerStrength) / iDefenderStrength);
iExperience = range(iExperience, GC.getDefineINT("MIN_EXPERIENCE_PER_COMBAT"), GC.getDefineINT("MAX_EXPERIENCE_PER_COMBAT"));
pDefender->changeExperience(iExperience, maxXPValue(), true, pPlot->getOwnerINLINE() == pDefender->getOwnerINLINE(), !isBarbarian());
/************************************************** ****************************************/
/* Author: TheLadiesOgre (Original Concept By: Tsentom1) */
/* Date: 03.09.2009 */
/* ModComp: Promotions DLL */
/* Intended to: Add functionality for iVictory________Heals */
/************************************************** ****************************************/
int iUnitsHealed = 0;
if ((pDefender->getVictoryAdjacentTileHeal()) >= GC.getGameINLINE().getSorenRandNum(100, "Field Hospital Die Roll"))
{
int iI;
for (iI = 0; iI < NUM_DIRECTION_TYPES; iI++)
{
CvPlot* pLoopPlot = plotDirection(pPlot->getX_INLINE(), pPlot->getY_INLINE(), ((DirectionTypes)iI));
if (pLoopPlot != NULL)
{
if (pLoopPlot->area() == pPlot->area())
{
CLLNode<IDInfo>* pUnitNode = pLoopPlot->headUnitNode();
while (pUnitNode != NULL)
{
CvUnit* pLoopUnit = ::getUnit(pUnitNode->m_data);
pUnitNode = pLoopPlot->nextUnitNode(pUnitNode);
if (pLoopUnit->getTeam() == getTeam())
{
iUnitsHealed++;
pLoopUnit->doHeal();
}
}
}
}
}
}
bool bDefenderHealed = false;
if ((pDefender->getVictorySameTileHeal()) >= GC.getGameINLINE().getSorenRandNum(100, "Field Surgeon Die Roll"))
{
bDefenderHealed = true;
CLLNode<IDInfo>* pUnitNode = pPlot->headUnitNode();
while (pUnitNode != NULL)
{
CvUnit* pLoopUnit = ::getUnit(pUnitNode->m_data);
pUnitNode = pPlot->nextUnitNode(pUnitNode);
if (pLoopUnit->getTeam() == getTeam())
{
iUnitsHealed++;
pLoopUnit->doHeal();
}
}
}
if (!bDefenderHealed && (pDefender->getVictoryHeal()) >= GC.getGameINLINE().getSorenRandNum(100, "Field Medic Die Roll"))
{
pDefender->doHeal();
}
if (iUnitsHealed > 0)
{
CvWString szBuffer = gDLL->getText("TXT_KEY_MISC_ENEMY_FIELD_MEDIC_DEFENDERS", GET_PLAYER(pDefender->getOwnerINLINE()).getCivilizationAdjective());
gDLL->getInterfaceIFace()->addMessage(getOwnerINLINE(), true, GC.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_COMBAT", MESSAGE_TYPE_INFO, getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_RED"), pDefender->getX_INLINE(), pDefender->getY_INLINE());
szBuffer = gDLL->getText("TXT_KEY_MISC_FIELD_MEDIC_DEFENDERS", getNameKey(), iUnitsHealed);
gDLL->getInterfaceIFace()->addMessage(pDefender->getOwnerINLINE(), true, GC.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_POSITIVE_DINK", MESSAGE_TYPE_INFO, getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_GREEN"), pDefender->getX_INLINE(), pDefender->getY_INLINE());
}
if (pDefender->isPillageOnVictory())
{
// Use python to determine pillage amounts...
lPillageGold = 0;
CyPlot* pyPlot = new CyPlot(pPlot);
CyUnit* pyUnit = new CyUnit(this);
CyArgsList argsList;
argsList.add(gDLL->getPythonIFace()->makePythonObject(pyPlot)); // pass in plot class
argsList.add(gDLL->getPythonIFace()->makePythonObject(pyUnit)); // pass in unit class
gDLL->getPythonIFace()->callFunction(PYGameModule, "doPillageGold", argsList.makeFunctionArgs(),&lPillageGold);
delete pyPlot; // python fxn must not hold on to this pointer
delete pyUnit; // python fxn must not hold on to this pointer
iPillageGold = (int)lPillageGold;
iPillageGold += (iPillageGold * getPillageChange()) / 100;
int iI = 0;
if ((iPillageGold > 0) && (!pDefender->isPillageCommerceChange((CommerceTypes)iI)))
{
GET_PLAYER(pDefender->getOwnerINLINE()).changeGold(iPillageGold);
szBuffer = gDLL->getText("TXT_KEY_MISC_PLUNDERED_GOLD_ON_VICTORY", iPillageGold, pDefender->getNameKey(), getVisualCivAdjective(getTeam()));
gDLL->getInterfaceIFace()->addMessage(pDefender->getOwnerINLINE(), true, GC.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_PILLAGE", MESSAGE_TYPE_INFO, getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_GREEN"), pPlot->getX_INLINE(), pPlot->getY_INLINE());
szBuffer = gDLL->getText("TXT_KEY_MISC_DEAD_LOOTED", getNameKey(), getVisualCivAdjective(pDefender->getTeam()));
gDLL->getInterfaceIFace()->addMessage(getOwnerINLINE(), false, GC.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_PILLAGED", MESSAGE_TYPE_INFO, getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_RED"), pPlot->getX_INLINE(), pPlot->getY_INLINE(), true, true);
}
}
if ((iPillageGold > 0) && (pDefender->isPillageCommerceChange(COMMERCE_CULTURE) == true))
{
float fInfluenceRatio = 0.0f;
fInfluenceRatio = doVictoryInfluence(this, false, false);
GET_PLAYER(pDefender->getOwnerINLINE()).changeGold(iPillageGold);
szBuffer = gDLL->getText("TXT_KEY_MISC_PLUNDERED_CULTURE_GOLD_ON_VICTORY", iPillageGold, pDefender->getNameKey(), getVisualCivAdjective(getTeam()));
if (fInfluenceRatio > 0.0f)
{
CvWString szInfluence;
szInfluence.Format(L" Tile influence: +%.1f%%", fInfluenceRatio);
szBuffer += szInfluence;
}
gDLL->getInterfaceIFace()->addMessage(pDefender->getOwnerINLINE(), true, GC.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_PILLAGE", MESSAGE_TYPE_INFO, getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_GREEN"), pPlot->getX_INLINE(), pPlot->getY_INLINE());
szBuffer = gDLL->getText("TXT_KEY_MISC_LOOTED_DEAD_SLANDERED", getNameKey(), getVisualCivAdjective(pDefender->getTeam()));
CvWString szInfluence;
szInfluence.Format(L" Tile influence: -%.1f%%", fInfluenceRatio);
szBuffer += szInfluence;
gDLL->getInterfaceIFace()->addMessage(getOwnerINLINE(), false, GC.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_PILLAGED", MESSAGE_TYPE_INFO, getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_RED"), pPlot->getX_INLINE(), pPlot->getY_INLINE(), true, true);
}
}
if ((iPillageGold > 0) && (pDefender->isPillageCommerceChange(COMMERCE_ESPIONAGE) == true))
{
GET_PLAYER(pDefender->getOwnerINLINE()).changeGold(iPillageGold);
GET_TEAM(getTeam()).changeEspionagePointsAgainstTe am(GET_PLAYER(pDefender->getOwnerINLINE()).getTeam(), iPillageGold);
szBuffer = gDLL->getText("TXT_KEY_MISC_PLUNDERED_ESPIONAGE_GOLD_ON_VICTORY", iPillageGold, getVisualCivAdjective(getTeam()));
gDLL->getInterfaceIFace()->addMessage(pDefender->getOwnerINLINE(), true, GC.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_PILLAGE", MESSAGE_TYPE_INFO, getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_GREEN"), pPlot->getX_INLINE(), pPlot->getY_INLINE());
szBuffer = gDLL->getText("TXT_KEY_MISC_INTERROGATED_BEFORE_EXECUTION", getNameKey(), getVisualCivAdjective(pDefender->getTeam()));
gDLL->getInterfaceIFace()->addMessage(getOwnerINLINE(), false, GC.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_PILLAGED", MESSAGE_TYPE_INFO, getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_RED"), pPlot->getX_INLINE(), pPlot->getY_INLINE(), true, true);
}
if ((iPillageGold > 0) && (pDefender->isPillageCommerceChange(COMMERCE_GOLD) == true))
{
GET_PLAYER(pDefender->getOwnerINLINE()).changeGold(iPillageGold * 2);
szBuffer = gDLL->getText("TXT_KEY_MISC_PLUNDERED_GOLD_ON_VICTORY", iPillageGold);
gDLL->getInterfaceIFace()->addMessage(pDefender->getOwnerINLINE(), true, GC.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_PILLAGE", MESSAGE_TYPE_INFO, getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_GREEN"), pPlot->getX_INLINE(), pPlot->getY_INLINE());
szBuffer = gDLL->getText("TXT_KEY_MISC_BEGGED_FOR_MERCY", getNameKey(), getVisualCivAdjective(pPlot->getTeam()));
gDLL->getInterfaceIFace()->addMessage(getOwnerINLINE(), false, GC.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_PILLAGED", MESSAGE_TYPE_INFO, getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_RED"), pPlot->getX_INLINE(), pPlot->getY_INLINE(), true, true);
}
if ((iPillageGold > 0) && (pDefender->isPillageCommerceChange(COMMERCE_RESEARCH) == true))
{
GET_PLAYER(pDefender->getOwnerINLINE()).changeGold(iPillageGold);
TechTypes eCurrentTech = GET_PLAYER(pDefender->getOwnerINLINE()).getCurrentResearch();
GET_TEAM(getTeam()).changeResearchProgress(eCurren tTech, iPillageGold, pDefender->getOwnerINLINE());
szBuffer = gDLL->getText("TXT_KEY_MISC_PLUNDERED_RESEARCH_GOLD_ON_VICTORY", iPillageGold);
gDLL->getInterfaceIFace()->addMessage(pDefender->getOwnerINLINE(), true, GC.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_PILLAGE", MESSAGE_TYPE_INFO, getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_GREEN"), pPlot->getX_INLINE(), pPlot->getY_INLINE());
szBuffer = gDLL->getText("TXT_KEY_MISC_TECH_COMPROMISED", getNameKey(), getVisualCivAdjective(pPlot->getTeam()));
gDLL->getInterfaceIFace()->addMessage(getOwnerINLINE(), false, GC.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_PILLAGED", MESSAGE_TYPE_INFO, getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_RED"), pPlot->getX_INLINE(), pPlot->getY_INLINE(), true, true);
}
}
/************************************************** ****************************************/
/* Promotions DLL End; TheLadiesOgre; 03.09.2009 */
/************************************************** ****************************************/
}
else
It looks perfectly fine to me, but it refuses to work, what is it that I am missing? I know that it has to be something idiotically simple
Edit: The bold if and the bold else line up in VC++
Opera Sep 12, 2009, 04:24 PM Isn't there one } too many here? if ((iPillageGold > 0) && (!pDefender->isPillageCommerceChange((CommerceTypes)iI)))
{
GET_PLAYER(pDefender->getOwnerINLINE()).changeGold(iPillageGold);
szBuffer = gDLL->getText("TXT_KEY_MISC_PLUNDERED_GOLD_ON_VICTORY", iPillageGold, pDefender->getNameKey(), getVisualCivAdjective(getTeam()));
gDLL->getInterfaceIFace()->addMessage(pDefender->getOwnerINLINE(), true, GC.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_PILLAGE", MESSAGE_TYPE_INFO, getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_GREEN"), pPlot->getX_INLINE(), pPlot->getY_INLINE());
szBuffer = gDLL->getText("TXT_KEY_MISC_DEAD_LOOTED", getNameKey(), getVisualCivAdjective(pDefender->getTeam()));
gDLL->getInterfaceIFace()->addMessage(getOwnerINLINE(), false, GC.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_PILLAGED", MESSAGE_TYPE_INFO, getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_RED"), pPlot->getX_INLINE(), pPlot->getY_INLINE(), true, true);
}
}
TheLadiesOgre Sep 12, 2009, 04:37 PM I feel stupid, I was right though, it was something simple I was overlooking due to my lack of experience. thank you again
Opera Sep 12, 2009, 04:43 PM Bah, it happens sometime ;)
Like when you forgot to type CvWhateverClassYouModIn:: when creating a new function :p
TheLadiesOgre Sep 12, 2009, 04:48 PM Where did I forget that?
Opera Sep 12, 2009, 04:53 PM Ha, nowhere, I did once, that's all ;)
TheLadiesOgre Sep 13, 2009, 11:35 AM I've compiled one, now how exactly do I get the best use out of it? The pillage promotions are causing CTD's (at least I think it is them), I was able to use one twice before a CTD and every other time as soon as I clicked the button, straight back out to the desktop. Really, really sucks but at least I'm pretty sure it has something to do with the level one promotions (I was trying to play through getting them instead of WBing them in).
xienwolf Sep 13, 2009, 02:58 PM Having the Debug DLL instead of a normal one automatically gains you Assert statements, which do some limited good. As long as you have written Assert checks in the right locations. The main use though is that once you launch your mod, you go to DEBUG->ATTACH TO PROCESS in VS (with your project open and preferably unaltered since the compile you are using). From the window that pops up, choose the process which is identified as Civ 4 Beyond the Sword.
You can also automatically connect the debugger (http://forums.civfanatics.com/showpost.php?p=8446232&postcount=7) to watch the XML loading happen as well. Slows you down, but sometimes reveals more bugs.
Opera Sep 13, 2009, 05:19 PM I looked to your files but I didn't find anything glaringly wrong. I'll look more into them and will keep you updated.
Headlock Sep 21, 2009, 07:04 PM Easily, the best tutorial I have ever read, on any subject, period.
Very nice work.So easy to read, so understandable, superb editing and formatting.
*APPLAUSE*
hdk
xienwolf Sep 21, 2009, 09:28 PM Thanks :) While questions and challenges are enjoyable, gratuitous positive feedback is divine ;)
phungus420 Sep 27, 2009, 06:00 PM Finally decided to add in the civic functionality for Units, so RevDCM could remove the Python Callback. But I can't even get past the schema file, I haven't even started working on the actual code yet. I took a look at fall further's schema code, and cloned what I thought was needed, but the game wol't even start up, due to the XML being broke. Here is what I've done:
From Fall Further UnitSchema
<ElementType name="PrereqCivics" content="eltOnly">
<element type="PrereqCivic" minOccurs="0"/>
</ElementType>
...
<element type="PrereqCivics" minOccurs="0"/>
Added it to LoR's UnitSchema file:
<ElementType name="PrereqBonuses" content="eltOnly">
<element type="BonusType" minOccurs="0" maxOccurs="*"/>
</ElementType>
<ElementType name="PrereqCivics" content="eltOnly">
<element type="PrereqCivic" minOccurs="0"/>
</ElementType>
<ElementType name="ProductionTraitType" content="textOnly"/>
<ElementType name="iProductionTrait" content="textOnly" dt:type="int"/>
<ElementType name="ProductionTrait" content="eltOnly">
<element type="ProductionTraitType"/>
<element type="iProductionTrait"/>
</ElementType>
<ElementType name="ProductionTraits" content="eltOnly">
<element type="ProductionTrait" minOccurs="0" maxOccurs="*"/>
</ElementType>
...
<element type="BonusType" minOccurs="0"/>
<element type="PrereqBonuses" minOccurs="0"/>
<element type="PrereqCivics" minOccurs="0"/>
<element type="ProductionTraits" minOccurs="0"/>
I haven't even tried adding the tag into any file, just testing to see if the schema worked, and it fails...
I have not had a problem adding stuff to the schema for integers and booleans, so what am I doing wrong here?
xienwolf Sep 27, 2009, 06:26 PM Is there a:
<ElementType name="PrereqCivic" content="textOnly"/>
Anywhere in your code? You also need that line, along with the:
<ElementType name="PrereqCivics" content="eltOnly">
<element type="PrereqCivic" minOccurs="0"/>
</ElementType>
TheLadiesOgre Oct 01, 2009, 12:45 PM Hello, I'm still having trouble with CivicPrereqs, they are just refusing to work, I've added everything I needed to add (at least I think I did), hell, I even added a new function in CvGameCoreUtils called isCivicRequiredForPromotion() (I modeled it after isTechRequiredForProject()). I am nearing my wits end, I really want to get started on the Slaver Promotion, but not without working CivicPrereq. Everything we've already discussed about them is in, but I know I have to be missing something, I just don't know what. I really want to try to get this without doing a direct import, but I'm starting to doubt that I can.
xienwolf Oct 01, 2009, 04:00 PM Ok, so things needed for a civic prereq field: Assuming you want to authorize multiple Civics on a single promotion, and assuming that it will be an OR type of mix:
You need to add either an boolean array of size CivicInfos or an INT list of arbitrary size to CvInfos. You need to load the civics from the XML into the array or list to indicate which civics will authorize the promotion.
Next you need to modify gametext to loop over civics (for the array array option, the size of the list for the list option) during parsePromotionHelp function and state the need of the civics. Then you'll check out the pedia in game to ensure that things are loading proper.
If the civics you want listed are being listed, the next step would be to go into UnitInfos and add a loop over civics (or list length) in ::isPromotionValid. Start with a boolean bValid set as false, and if a civic listed for the promotion is found to be active for the owner, flip bValid to true and break the loop, authorizing the promotion for this aspect of the prereqs.
phungus420 Oct 01, 2009, 04:25 PM Since you don't have a Creating a completely new Array XML tag tutorial written yet, and a civics check in the UnitsSchema seems to be in high demand, any chance you could write up such a tutorial? I've tried to get a civic check working using the FF code for promotions, and can't figure it out. A properly done array that can be used in UnitInfos would get alot of use, as it would instantly get implemented in RevolutionDCM, and by extension all mods that use a RevDCM core (RoM, LoR, Quote Capita, and Dune Wars to name a few).
xienwolf Oct 01, 2009, 04:54 PM So you want a prereqCivic for Units? Could put that up on the blocks to code easily enough
phungus420 Oct 01, 2009, 05:42 PM So you want a prereqCivic for Units? Could put that up on the blocks to code easily enough
Yeah, a RequiredCivics array (or setup) to train units in UnitInfos would be perfect. A required RequiredCivics array to select a promotion and have it work would also be benificial, but I'd have to convert this for my uses. You already have this in FF, but I can't make it work when cloning it to UnitInfos.
TheLadiesOgre Oct 02, 2009, 08:00 AM Cool, an Array Tutorial would be exceedingly helpful, given the troubles I've been having. Like I've said before there are several things I'd like to add, but IMHO they all hinge on having a CivicPrereq. Thank you again, I'll be looking forward to it.
LoneTraveller Oct 04, 2009, 08:23 PM Hi there,
I have a few problems and I'm not sure how to go about them.
- I wanna know how to hook up a SDK function to a button in python (like an action button). I asked on the thread by TC01 but he says that he doesn't know how to do it.
- I'd like to understand how the game manages the resources of the same civilization on the different land mass IF it can't trade over water. Sounds contradictory I know so here is the situation. I'm working on a ww2 mod and I want to force the players who have key resources to send them overseas by "tanker" ships to their other cities who are requiring them to build units, buildings, etc. So far I have managed (with alot of help) too shut down cross ocean trading, I made a tanker ship unit and an action button exists for it. The action would be to increment the number of turns of production requiring the carried resource to the city where it is presently located.
Here is where things get difficult.
I'd like for every territory zone (for the same civ) to have an integer counting the amount of turns of production left with the resource in question. I'm not sure if CvArea.cpp controls the territory or the whole civ.
Thanks for your time
xienwolf Oct 04, 2009, 11:58 PM You want to create a Widget. That identifies something placed in python which will be handled by the DLL.
There would be quite a few ways to go about your tanker idea. I'll let you play with it to get a feel for which is most intuitive for you (and thus easier to eventually write).
An Area is one continent, or one Ocean. If a worker or workboat could move between two tiles (on a mapwith no cities or improvements), those tiles are in the same area.
LoneTraveller Oct 11, 2009, 08:30 PM I can't figure out where the city access to resources is located. I can't find it in cvCity or CvArea. would you have any idea about this ?
xienwolf Oct 11, 2009, 11:34 PM Do you mean where the data is stored which you see on the top right side of the city detail screen? (number of each resource conntected to the city)?
CvCity::getNumBonuses
Stored in the array m_paiNumBonuses (and those granted by a corporation are from m_paiNumCorpProducedBonuses array).
Use CvCity::changeNumBonuses if you want to change how many bonuses are available in a city.
LoneTraveller Oct 12, 2009, 07:42 PM Not exactly. I consider those functions to be "accessors" but what I want is the "above layer" of programming who uses these functions to manage the resources for each city. I "think" this should exist.
xienwolf Oct 12, 2009, 08:28 PM Not following what you are asking for at this point. Do you mean "How does the game tell City A that it has Iron & Horses, while City B has Copper and Silk?"
Ninja2 Oct 13, 2009, 01:49 AM Use CvCity::changeNumBonuses if you want to change how many bonuses are available in a city.
I'm also messing around with resources... I have some functions that make additional bonus resources available to cities. I'm also using changeNumBonuses to change bonuses. It works, insofar that the additional bonus is available, but only in a single city (where the building which makes the resource available is built). How can I make this bonus available to the entire trade network, and also available for AI trade?
LoneTraveller Oct 13, 2009, 07:12 AM Not following what you are asking for at this point. Do you mean "How does the game tell City A that it has Iron & Horses, while City B has Copper and Silk?"
That's exactly what I mean
xienwolf Oct 13, 2009, 12:24 PM Ok, both of your questions are answered by the same function fortunately. You want to mess with a "plotgroup" not a city. Plotgroups carry the tradeable/shared bonuses.
For adding a bonus into a plotgroup, you can do it THROUGH a city with CvCity::changeFreeBonus. But for all the full details of a plotgroup, they have their own file, CvPlotGroup.cpp. I assume that file answers the original question?
Ninja2 Oct 14, 2009, 02:49 AM Ok, please confirm if I understood this correctly... by using changeNumBonuses I can make available a bonus resource in a single city, that is what my code does now. If I instead of changeNumBonuses use changeFreeBonus, I make this resource available in all cities?
LoneTraveller Oct 14, 2009, 07:54 AM Ok, both of your questions are answered by the same function fortunately. You want to mess with a "plotgroup" not a city. Plotgroups carry the tradeable/shared bonuses.
For adding a bonus into a plotgroup, you can do it THROUGH a city with CvCity::changeFreeBonus. But for all the full details of a plotgroup, they have their own file, CvPlotGroup.cpp. I assume that file answers the original question?
Yes it does thank you.
But I have another question...I'm following along the lines of this thread http://forums.civfanatics.com/showthread.php?t=334289&highlight=widget and have modified the specified files but I don't understand what gives data to the widget (inside it's struct) cause I need it for further treatment.
xienwolf Oct 14, 2009, 02:04 PM Ninja2: Correct.
LoneTraveller: The 2 items of data passed to the widget are the two numbers listed in python right after saying WidgetType.WIDGET_WHATEVER. Frequently they are both a -1 due to the widget not needing any data.
LoneTraveller Oct 14, 2009, 06:38 PM Ninja2: Correct.
LoneTraveller: The 2 items of data passed to the widget are the two numbers listed in python right after saying WidgetType.WIDGET_WHATEVER. Frequently they are both a -1 due to the widget not needing any data.
Thanks.
With your knowledge I bet you could write a sweet API with quite usefull comments for this SDK.
Now I have another problem. I'm looking for a function that would gimme a pointer to a unit by calling it's "m_iID" from the CvUnit.cpp. I figured that it's in the GC functions but all I saw were about getUnitInfo, getUnitCombat, getUnitArt, etc. I saw nothing requiring an ID integer.
Where should I look ?
xienwolf Oct 15, 2009, 01:03 AM I could, until I hit the pieces of the API which aren't even in the DLL. Then it would rely on those which I have been forced to muddle through, or some deliberate testing. The lack of info in the damnable API is the main reason I despise being forced to use python for the interface pieces (and avoid it for everything else like the plague)
So you already have the m_iID data, and couldn't have grabbed anything else, or that is what you are using in your widget, since you want to refer to a specific unit and only have integers available? Probably the latter, context clues and whatnot.
Anyway, to use the ID number to find a unit, you also need to know who owns the unit, so I hope you weren't already using the second data item for something else.
From CvGameCoreUtils:
CvUnit* getUnit(IDInfo unit)
{
if ((unit.eOwner >= 0) && unit.eOwner < MAX_PLAYERS)
{
return (GET_PLAYER((PlayerTypes)unit.eOwner).getUnit(unit .iID));
}
return NULL;
}
And the IDInfo object you need to send is built like this:
IDInfo CvUnit::getIDInfo() const
{
IDInfo unit(getOwnerINLINE(), getID());
return unit;
}
So for you it would look something like:
IDInfo unit(widgetDataStruct.m_iData1, widgetDataStruct.m_iData2);
CvUnit* pUnit = getUnit(unit)
Then use pUnit as you desire. Oh, for this setup the Player number for the owner was data1, and the unit's ID was data2
LoneTraveller Oct 23, 2009, 05:47 PM the UnitID thing worked out great.
Thanks Xienwolf !
Now...naturally I have another problem...I need to redefine the characteristics of "open borders" diplomatic status. What I mean is to make it so that my internal trade of resources would stop between each plotGroup going through other nation's territory but I would still be able to navigate ships in that foreign territory.
Looking at the list of .cpp files I thought that CvDiploParameters.cpp would be the thing but it strikes me that it relates more to interactions with another civ.
I also looked around for 'open_border' and the only thing that showed up was in AI_doDiplo function of the file I originally looked into.
I'm not sure what to do now...
xienwolf Oct 23, 2009, 07:46 PM So you want to make it so that having roads through territory not belonging to yourself fail to connect cities for resources?
That would be a bit trickier, as I think the checks are done by simply looking for a valid route between two cities, so the only way to block trade connections is to block unit movement.
deanej Nov 01, 2009, 03:18 PM I'm trying to clone bOnlyDefensive into Civ4PromotionInfos.xml. Unfortunately, I don't understand how to display the info on the unit itself, expose it to python, or (most importantly) make the modifications to CvUnit.cpp in order to make it work. Can anyone help?
xienwolf Nov 01, 2009, 11:37 PM To display on the unit, you would want to modify CvGameTextMgr::setUnitHelp, or some fairly similar name to that. I do remember that there are a couple such functions, so you want to look at the first argument for the function to make sure it is a CvUnit* pUnit, and not a UnitInfos eUnit. pUnit version is for an actual unit out on the field. If defensiveOnly status is already displayed on units who cannot attack by UnitInfo tag, then you can just search for it and place yours as a simple OR statement appended to the already existing code.
Exposure to python will be a matter of going to CyUnit.cpp and making a function that points at CvUnit.cpp functions, just like all the other functions around it will. And then of course CyUnit.h needs to declare those functions, and finally CyUnitInterface needs to have a single line which acts as the bridge between python and the CyUnit.cpp file, again it will mimic what is all around it for simplicity.
In CvUnit.cpp you would make a function to track the DefensiveOnly status of your unit, and modify ::setHasPromotion to increment a tracking integer when such a promotion is gained. Then you find all locations where isDefensiveOnly() is asked, and point them at CvUnit instead of UnitInfos.
phungus420 Nov 02, 2009, 02:23 AM Working on what I thought would be a pretty simple tag addition. Adding a tag to UnitInfos so that a Unit will either require, or cannot be built with certain game options. But I've ran into an issue while compiling.
I have the schema set up fine, adding two new tags: ReqGameOption (required game option to build), and NotGameOption (cannot build with this game option selected). Here is my code:
CvInfos.h
public:
CvUnitInfo();
virtual ~CvUnitInfo();
...
//phungus Gameoption CanTrain
int getReqGameOption() const; // Exposed to Python
int getNotGameOption() const; // Exposed to Python
//phungus -end
...
protected:
...
//phungus GameOption CanTrain
int m_iReqGameOption;
int m_iNotGameOption;
//phungus -end
CvInfos.cpp
CvUnitInfo::CvUnitInfo() :
...
//phungus GameOption CanTrain
m_iReqGameOption(NO_GAMEOPTION),
m_iNotGameOption(NO_GAMEOPTION),
//phungus -end
...
//phungus GameOption CanTrain
int CvUnitInfo::getReqGameOption() const
{
return m_iReqGameOption;
}
int CvUnitInfo::getNotGameOption() const
{
return m_iNotGameOption;
}
//phungus -end
...
void CvUnitInfo::read(FDataStreamBase* stream)
{
CvHotkeyInfo::read(stream);
uint uiFlag=0;
stream->Read(&uiFlag); // flags for expansion
...
//phungus GameOption CanTrain
stream->Read(&m_iReqGameOption);
stream->Read(&m_iNotGameOption);
//phungus -end
...
void CvUnitInfo::write(FDataStreamBase* stream)
{
CvHotkeyInfo::write(stream);
uint uiFlag=0;
stream->Write(uiFlag); // flag for expansion
...
//phungus GameOption CanTrain
stream->Write(m_iReqGameOption);
stream->Write(m_iNotGameOption);
//phungus -end
...
bool CvUnitInfo::read(CvXMLLoadUtility* pXML)
{
CvString szTextVal;
if (!CvHotkeyInfo::read(pXML))
{
return false;
}
...
//phungus GameOption canTrain
pXML->GetChildXmlValByName(szTextVal, "ReqGameOption");
m_iReqGameOption = pXML->FindInInfoClass(szTextVal);
pXML->GetChildXmlValByName(szTextVal, "NotGameOption");
m_iNotGameOption = pXML->FindInInfoClass(szTextVal);
//phungus -end
...
void CvUnitInfo::copyNonDefaults(CvUnitInfo* pClassInfo)
{
CvHotkeyInfo::copyNonDefaults(pClassInfo);
bool bDefault = false;
int iDefault = 0;
int iTextDefault = -1;
CvString cDefault = CvString::format("").GetCString();
...
//phungus GameOption canTrain
if ( m_iReqGameOption == iTextDefault ) m_iReqGameOption = pClassInfo->getReqGameOption();
if ( m_iNotGameOption == iTextDefault ) m_iNotGameOption = pClassInfo->getNotGameOption();
//phungus -end
And finally CvPlayer.cpp (not going to bother showing the CyInterfaceInfo code)
bool CvPlayer::canTrain(UnitTypes eUnit, bool bContinue, bool bTestVisible, bool bIgnoreCost) const
{
PROFILE_FUNC();
...
//phungus GameOption canTrain
if (GC.getUnitInfo(eUnit).getReqGameOption() != NO_GAMEOPTION)
{
if (!(GC.getGameINLINE().isOption(GC.getUnitInfo(eUni t).getReqGameOption())))
{
return false;
}
}
if (GC.getUnitInfo(eUnit).getNotGameOption() != NO_GAMEOPTION)
{
if (GC.getGameINLINE().isOption(GC.getUnitInfo(eUnit) .getReqGameOption()))
{
return false;
}
}
//phungus End
Here is the error I'm getting:
1>CvPlayer.cpp(6596) : error C2664: 'CvGame::isOption' : cannot convert parameter 1 from 'int' to 'GameOptionTypes'
1> Conversion to enumeration type requires an explicit cast (static_cast, C-style cast or function-style cast)
1>CvPlayer.cpp(6604) : error C2664: 'CvGame::isOption' : cannot convert parameter 1 from 'int' to 'GameOptionTypes'
1> Conversion to enumeration type requires an explicit cast (static_cast, C-style cast or function-style cast)
Now my impression is that the problem is a string vs int definition. But all the stings in CvInfos are declared as ints... So I'm a bit confused. Any idea what's wrong here, and more importantly how to fix?
xienwolf Nov 02, 2009, 01:40 PM First issue is your CopyNonDefaults, the compiler didn't catch it, but you are comparing an integer against the text default, you want to compare it against -1 instead, as that is the TRUE default (it is no longer a string at that point).
The actual error, is that you need to say:
(GameOptionTypes)gx.getUnitInfo(eUnit).getReqGameO ption()
Having the (GameOptionTypes) changes it from 3 to GAMEOPTION_NO_WARRIORS, and thus allows checking it.
phungus420 Nov 02, 2009, 03:13 PM First issue is your CopyNonDefaults, the compiler didn't catch it, but you are comparing an integer against the text default, you want to compare it against -1 instead, as that is the TRUE default (it is no longer a string at that point).
Isn't that what the m_iReqGameOption(NO_GAMEOPTION), does, doesn't this make it so that RegGameOption returns NO_GAMEOPTION if nothing else is specified? I guess not, but what is being stored in the variable then?
The actual error, is that you need to say:
(GameOptionTypes)gx.getUnitInfo(eUnit).getReqGameO ption()
Having the (GameOptionTypes) changes it from 3 to GAMEOPTION_NO_WARRIORS, and thus allows checking it.
I'm not understanding, what's the proper syntax of these lines supposed to be (also the first line didn't return an error, so I'm confused, if it's not returning a string, why was the first line OK)?
if (GC.getUnitInfo(eUnit).getNotGameOption() != NO_GAMEOPTION)
if (GC.getGameINLINE().isOption(GC.getUnitInfo(eUnit) .getReqGameOption()))
xienwolf Nov 02, 2009, 03:59 PM NO_GAMEOPTION is not an integer, it is an enumerator. As such, you CAN get away with using it as an integer, since there is a sequential order assigned, but you are FORBIDDEN to use an integer in place of an enumerator. You must cast it, which is what the (GameOptionTypes)iNum does.
So the game will read:
m_iReqGameOption(NO_GAMEOPTION)
as
m_iReqGameOption(-1)
Even though it prefers you don't do things that way. In this particular case, there is no harm done. But what is stored, is a -1.
The proper format for your IF statement is:
if (GC.getGameINLINE().isOption((GameOptionTypes)GC.g etUnitInfo(eUnit) .getReqGameOption())).
But you want to make sure you do not make this check if getReqGameOption returns NO_GAMEOPTION, because you don't know if that will return true or false, as it doesn't exist.
So you want to use both cases really. I suspect that:
if (GC.getUnitInfo(eUnit).getNotGameOption() != NO_GAMEOPTION)
will work fine, it will treat NO_GAMEOPTION like a -1 in this case and be able to compare it with an integer. The reason that the other one doesn't work is because .isOption() requires that you pass it a GameOptionTypes variable, so will yell at you for trying to give it an integer.
phungus420 Nov 02, 2009, 04:13 PM Thanks Xienwolf.
Just a quick clarification, this looks wrong to me:
(GameOptionTypes)GC.getUnitInfo(eUnit) .getReqGameOption(), why is there no period . between GameOptionTypes and GC.getUnitInfo(eUnit)?
xienwolf Nov 02, 2009, 05:46 PM The period means that you are talking about a property of an object. getUnitInfo(eUnit) has many function inside of it, one of which is getReqGameOption(), so you use a period to "slip inside of" the UnitInfo object and get a piece of it.
But the (GameOptionTypes) is what is known as a TypeCast, it doesn't have any item inside of it, so you can't "slip inside" with a period. What it does instead is convert an item which comes after it, in this case an integer which is plucked by the code GC.getUnitInfo(eUnit).getReqGameOption(), into an enumerator for GameOptionTypes.
So GC.getUnitInfo(eUnit).getReqGameOption() is used to get something like 6
Then (GameOptionTypes)6 is used to get something like GAMEOPTION_ALLOW_ARCHERS.
phungus420 Nov 02, 2009, 05:57 PM OK, this question may seem apparent, but I'm not seeing it. How does CvInfos know to "type cast" the string passed to it from the UnitInfos XML, with the correct GameOption integer? I never told CvInfos to reference the GameOption index when I have it load the tag ReqGameOption, so how does the program know to set a string to the correct index integer value when storing it's reference variable (and why doesn't it just store it as a string in the first place)?
xienwolf Nov 02, 2009, 05:59 PM It looks for the exact string which you loaded from the XML, which was GAMEOPTION_BLAH_BLAH. If you happened to create a unit with the exact same type name, then it would cause some SERIOUS issues with this part of the code (with all getInfoTypeFor pieces). That is why we have the standard format of starting all unit names with UNIT_ and all promotion names with PROMOTION_ and so on. So the game didn't know, it actually looked at every single enumeration that exists up to that point.
phungus420 Nov 02, 2009, 06:01 PM OK, so, the preceding part of a string UNIT_, PROMOTION_, etc the dll knows to set these up in their own separate lists?
xienwolf Nov 02, 2009, 06:10 PM Not really. I edited the last post as soon as I popped it up, but I guess you had already read it. The game actually looks at absolutely every enumeration it already has created by that point. It might be smart enough to do a quick Alpha filter instead of a flat loop over everything though, so as to not waste a ton of time. I haven't looked closely at the precise code setup for that part. But it doesn't have a CLUE that you want a GameOption, so if you list UNIT_WARRIOR in this field, it will find the integer which matches up with a UNIT_WARRIOR and load that integer, even though it is probably a number FAR higher than the total number of gameoptions in the game, and it'll cause a confusing bug for you later on (many people actually run into this issue with Units and Buildings when they use a CLASS in place of a TYPE, or the other way around)
phungus420 Nov 02, 2009, 07:15 PM I'm still not getting it. Whenever I reference an object and get an index value, it's specific to that class. NO_GAMEOPTION returns -1, the first gameoption in the XML is going to be 0, etc. Same for units, or any other object list. What's confusing me is how does CvInfos know to set the integer value based on GameOptionInfos.xml?
deanej Nov 02, 2009, 08:04 PM In CvUnit.cpp you would make a function to track the DefensiveOnly status of your unit, and modify ::setHasPromotion to increment a tracking integer when such a promotion is gained. Then you find all locations where isDefensiveOnly() is asked, and point them at CvUnit instead of UnitInfos.
I've made some changes now to CvUnit.cpp and CvUnit.h, modeled on the no bad goodies example, and it compiles, but the changes do nothing. Units with the promotion can still attack. isOnlyDefensive appears to already be pointed at CvUnit - the function itself looks like this:
bool CvUnit::isOnlyDefensive() const
{
return m_pUnitInfo->isOnlyDefensive();
}
That's the only time I see the function referenced to a class. How do I make the change?
xienwolf Nov 02, 2009, 08:47 PM Phungus, the DLL doesn't know to use GameOptionInfos.xml at all. It just has a tracker which says that once upon a time it loaded something with the Type identifier of "GAMEOPTION_BLAH" and it assigned a 12 to that particular item. So when you do a getInfoTypeFor("GAMEOPTION_BLAH") it looks through everything it has ever loaded. All units, all Promotion, all Terrains, all gameoptions, EVERYTHING, until it finds something which it registered as having once loaded as a match for that string. Then it hands you the integer which had been assigned to that item. Thus, if you list <PrereqGameOption>UNIT_WARRIOR</PrereqGameOption>, then the code will not give you an error immediately, it doesn't know that UNIT_WARRIOR is NOT a GameOption, it just knows that UNIT_WARRIOR had been stored once upon a time as a 43, so it gives you a 43 to store in this value. That will lead to errors later in life for you. And the precise reason it can happen is because this function has NO IDEA where to look, it just looks EVERYWHERE.
deanej: You would add an integer to CvUnit.cpp/CvUnit.h which tracks how many promotions the unit currently has with the <bOnlyDefensive> tag. Then you include that integer with this function like so:
bool CvUnit::isOnlyDefensive() const
{
return m_pUnitInfo->isOnlyDefensive() || m_iOnlyDefensive > 0;
}
void CvUnit::changeOnlyDefensive(int iChange)
{
m_iOnlyDefensive += iChange;
}
And in CvUnit::setHasPromotion you will add a line in there along the gist of:
changeOnlyDefensive(GC.getPromotionInfo(ePromotion ).isDefensiveOnly() ? iChange : 0);
I don't remember the PRECISE format, but that should work fairly well, or you can just copy the format of the other boolean expressions already in the function.
deanej Nov 02, 2009, 09:08 PM I had the latter two changes, just not the first one. Thanks, it works now!
phungus420 Nov 02, 2009, 09:26 PM Well, things just got alot more complicated. It acts as though whatever I enter in the tags is garbage, and gives asserts, and the default XML error message saying "GAMEOPTION_NO_ESPIONAGE" doesn't exist. Specifically, the assert is:
Assert Failed
File: CvGlobals.cpp
Line: 3921
Expression: strcmp(szType, "NONE")==0 || strcmp(szType, "")==0
Message: info type GAMEOPTION_NO_ESPIONAGE not found, Current XML file is: xml\Buildings/CIV4BuildingInfos.xml
Which makes me think that GameOptionInfos isn't loaded yet. Any ideas as to how to get around this?
xienwolf Nov 02, 2009, 09:51 PM Gameoptions are hardcoded in the DLL, so they are ALWAYS loaded at ALL times. The catch is that since they ARE hardcoded, they may not exist at all in the lookup tables used by getInfoTypeFor... In fact, thinking about it in that light, I am almost certain that they are NOT included there.
But, since the XML file for GameOptions needs to link the description to the Option, there must be a way to figure out the number properly. I'll actually open the code to check out that link for you instead of guessing and probably leading you astray.
Well craps... the load method is the same as normal. In that case, the descriptions ARE loaded after units, so you can TRY to use Readpass3, or move the loading of gameoptions to the top of:
bool CvXMLLoadUtility::LoadPreMenuGlobals()
Normally I wouldn't advise moving something in that function, but GameOptions don't depend on anything else, so I don't see any reason NOT to load them first, so it should be safe in this particular case, and might be all you need to fix things.
phungus420 Nov 02, 2009, 10:09 PM Thanks Xienwolf, that works. I am as well a bit apprehensious about changing the loading order of the XML, but I don't see how it can hurt in this instance; as far as I can see GameOptionInfos references absolutely nothing. It's kind of weird that since these have to exist in the dll in CvEnums, that they are loaded so late in the XML anyway, I don't see any reason for it.
phungus420 Nov 03, 2009, 09:25 AM Is there a:
<ElementType name="PrereqCivic" content="textOnly"/>
Anywhere in your code? You also need that line, along with the:
<ElementType name="PrereqCivics" content="eltOnly">
<element type="PrereqCivic" minOccurs="0"/>
</ElementType>
I'm trying once again to do this, but I cannot even get the schema set up. Unfortunately I could not follow the tutorial on cloning an existing array and make it work (which sucks I was able to dive right in following the boolean tutorial, and use it to create new integers); I'm currently failing at the very first step with setting up the schema. Was hoping it was the missing textOnly line, but that has not helped. Here is my current schema:
Schema
<ElementType name="NotGameOption" content="textOnly"/>
<ElementType name="ReqCivicOrs" content="textOnly"/>
<ElementType name="ReqCivicOrs" content="eltOnly">
<element type="CivicOption" minOccurs="0" maxOccurs="*"/>
</ElementType>
...
<element type="NotGameOption" minOccurs="0"/>
<element type="ReqCivicOrs" minOccurs="0"/>
UnitInfos:
<NotGameOption>NONE</NotGameOption>
<ReqCivicOrs>
<CivicOption>CIVIC_THEOCRACY</CivicOption>
<CivicOption>NONE</CivicOption>
<CivicOption>NONE</CivicOption>
</ReqCivicOrs>
The NotGameOption tag is one of the ones I just added with your help described a couple posts above. It works fine, just referencing it here to show the placement is the same. I have also posted in Afforess's trouble with arrays thread (http://forums.civfanatics.com/showpost.php?p=8606892&postcount=155), as I stand a good chance of running into more issues after the schema is fixed, and the array thread in the Python/SDK forum seems a good spot for such a post to be.
Sephi Nov 03, 2009, 09:53 AM in your schema you need to replace the first reqCivicOrs with CivicOption (and make sure CivicOption isn't listed twice in the schema file)
xienwolf Nov 04, 2009, 09:38 PM Mildly amusing, but congratulations phungus. :) I decided to spend a little time tonight on writing up the next leg of the tutorial, remembered that I had a pending request for a "create your own array" item, and since the idea for the tag is the hard part I would have been able to just belt it out.
Alas, I found that the request was for precisely what you just finished, a CivicPrereq Array for units :)
So again, grats on getting it done for yourself :)
phungus420 Nov 04, 2009, 10:11 PM So again, grats on getting it done for yourself :)
With the help of about a half dozen others, but thanks.
deanej Nov 13, 2009, 09:29 PM I'm having issues again. I'm trying to add a bNoCloaking tag to Civ4UnitInfos that if set to 1 will block a unit from being able to cloak. It's not supposed to do anything in the SDK, just act as a hook for the python code and display text for the civilopedia and build list. I've added in all the code to CvInfos, CvGameTextMgr, and CyInfoInterface, but the text does not display, and I get python exceptions when the tech for cloaking is researched.
CvInfos.cpp
m_bNoRevealMap(false),
//Star Trek - cloaking
m_bNoCloaking(false),
m_fUnitMaxSpeed(0.0f),
bool CvUnitInfo::isNoRevealMap() const
{
return m_bNoRevealMap;
}
//Star Trek - cloaking
bool CvUnitInfo::isNoCloaking() const
{
return m_bNoCloaking;
}
float CvUnitInfo::getUnitMaxSpeed() const
{
return m_fUnitMaxSpeed;
}
stream->Read(&m_bNoRevealMap);
//Star Trek - cloaking
stream->Read(&m_bNoCloaking);
stream->Read(&m_fUnitMaxSpeed);
stream->Write(m_bNoRevealMap);
//Star Trek - cloaking
stream->Write(m_bNoCloaking);
stream->Write(m_fUnitMaxSpeed);
pXML->GetChildXmlValByName(&m_bNoRevealMap,"bNoRevealMap",false);
//Star Trek - cloaking
pXML->GetChildXmlValByName(&m_bNoCloaking,"bNoCloaking",false);
pXML->SetVariableListTagPair(&m_pbUpgradeUnitClass, "UnitClassUpgrades", sizeof(GC.getUnitClassInfo((UnitClassTypes)0)), GC.getNumUnitClassInfos());
CvInfos.h
bool isNoRevealMap() const; // Exposed to Python
//Star Trek - cloaking
bool isNoCloaking() const;
float getUnitMaxSpeed() const; // Exposed to Python
bool m_bNoRevealMap;
//Star Trek - cloaking
bool m_bNoCloaking;
int m_iLeaderPromotion;
CvGameTextMgr.cpp, in setUnitHelp
if (pUnit->getUnitInfo().isNoRevealMap())
{
szString.append(NEWLINE);
szString.append(gDLL->getText("TXT_KEY_UNIT_VISIBILITY_MOVE_RANGE"));
}
//Star Trek - cloaking
if (pUnit->getUnitInfo().isNoCloaking())
{
szString.append(NEWLINE);
szString.append(gDLL->getText("TXT_KEY_UNIT_CANNOT_CLOAK"));
}
if (!CvWString(pUnit->getUnitInfo().getHelp()).empty())
{
szString.append(NEWLINE);
szString.append(pUnit->getUnitInfo().getHelp());
}
CyInfoInterface1.cpp
.def("isAlwaysHostile", &CvUnitInfo::isAlwaysHostile, "bool ()")
//Star Trek - cloaking
.def("isNoCloaking", &CvUnitInfo::isNoCloaking, "bool ()")
.def("getUnitMaxSpeed", &CvUnitInfo::getUnitMaxSpeed, "float ()")
CvMainInterface.py
pUnit = g_pSelectedUnit
iUnitType = pUnit.getUnitType()
pUnitOwner = gc.getPlayer( pUnit.getOwner( ))
pUnitTeam = gc.getTeam(pUnit.getOwner())
if pUnitOwner.isTurnActive( ):
if (pUnitTeam.isHasTech(gc.getInfoTypeForString('TECH _CLOAKING_DEVICE')) and not (pUnit.isNoCloaking() or pUnit.isHasPromotion(gc.getInfoTypeForString('PROM OTION_CLOAK')) or pUnit.isHasPromotion(gc.getInfoTypeForString('PROM OTION_CLOAK_FIRE')))):
screen.appendMultiListButton( "BottomButtonContainer", gc.getPromotionInfo(gc.getInfoTypeForString('PROMO TION_CLOAK')).getButton(), 0, WidgetTypes.WIDGET_GENERAL, 660, 660, False )
screen.show( "BottomButtonContainer" )
iCount = iCount + 1
Traceback (most recent call last):
File "CvScreensInterface", line 713, in forceScreenRedraw
File "CvMainInterface", line 811, in redraw
File "CvMainInterface", line 1599, in updateSelectionButtons
AttributeError: 'CyUnit' object has no attribute 'isNoCloaking'
ERR: Python function forceScreenRedraw failed, module CvScreensInterface
All I know is, civ's code and I do NOT get along. Ever.
xienwolf Nov 13, 2009, 10:05 PM You asked python to display:
pUnit.isNoCloacking()
You need to ask instead:
cg.getUnitInfo(pUnit.getUnitType()).isNoCloacking( )
Also, your line in CvGameTextMgr will show the inability to cloak on a unit on the map, but will not show that information in the Civilopedia. You need to add that to the OTHER UnitHelp function in CvGameTextMgr (in the one which the pedia displays you won't have a pUnit to work with)
deanej Nov 14, 2009, 12:58 PM Thanks, that works! I guess it wouldn't be civ if everything was consistant and easy.
peteandbill Dec 13, 2009, 11:09 AM I can already tell this will be a great guide to modding the sdk once i get it working, only one problem
i can't get the sdk set up
i've tried downloading the sdk but as soon as i go to install it it starts up, shows a splash screen, then it says:
Error: Setup was started in a non-native or WoW environment.
Please run the setup package that is appropriate for your operating system installation
i'm running vista x64 on an HP laptop
i can't find any other place to download the sdk
any ideas?
xienwolf Dec 13, 2009, 11:15 AM There are two guides linked in the first post for setting up the SDK. One for Codeblocks, the other for Visual Studio. Both do a fantastic job of walking you through step-by-step. If you are trying to use a compiler which you already have on your computer and normally use, the issue is most probably that you do not have the proper base libraries, as they are all quite heavily outdated now (from 2003)
deanej Dec 13, 2009, 11:34 AM I also recommend looking at this guide: http://modiki.civfanatics.com/index.php/How_to_Install_the_SDK
Since Refar's guide was written for 3.17 this has all the modifications necessary to make it work (not many, but it removes the digging necessary to get the 3.19 makefile).
peteandbill Dec 13, 2009, 11:55 AM There are two guides linked in the first post for setting up the SDK. One for Codeblocks, the other for Visual Studio. Both do a fantastic job of walking you through step-by-step. If you are trying to use a compiler which you already have on your computer and normally use, the issue is most probably that you do not have the proper base libraries, as they are all quite heavily outdated now (from 2003)
so is there no way to set it up for another compiler?
i'm using the netbeans ide which i've already gotten used to, do i have to switch to codeblocks for this to work?
xienwolf Dec 13, 2009, 04:40 PM You have to use the rules of the 2003 VS compiler. As for which program you use, that is just a pretty GUI for doing your edits to the pre-compiled code, as long as you know how to define the new/old ruleset for compiling (probably a Makefile is pretty standard) in your IDE, you can use any of them. The catch is being familiar enough with the program to know how to force it to use other libraries and rulesets for the compilation process. I am sure you can find guides that are general for the program itself, and adapt them as needed to fit Civ's specific needs. If you do go through the effort to discover all of that, please write up a guide so that others can benefit from your work and possibly use a compiler better suited to their own tastes :)
peteandbill Dec 13, 2009, 05:11 PM ok this probably isn't the best forum to ask this but i think this is the root of all my problems:
is the sdk i'm supposed to be setting up the windows sdk so i can use c++, or is it something just for civ4? I'm already set up to program in C++ and without downloading anything i found a folder called CvGameCoreDLL which has all the files listed in the first post. So can i just get the makefile and then edit those files (backing them up first of course)?
xienwolf Dec 13, 2009, 05:15 PM It is C++, but it uses libraries from 2003 to compile, so you have to download those libraries, which is what the Makefile will force the compiler to use.
deanej Dec 22, 2009, 04:26 PM How do traits work? I want to make it so that if a civ has a certain trait, it won't get resistance in captured cities. I've changed code from RFC to the following:
//Star Trek - Alliance civ trait
if(!hasTrait(GC.getInfoTypeForString("TRAIT_ALLIANCE")))
{
pNewCity->changeOccupationTimer(((GC.getDefineINT("BASE_OCCUPATION_TURNS") + ((pNewCity->getPopulation() * GC.getDefineINT("OCCUPATION_TURNS_POPULATION_PERCENT")) / 100)) * (100 - iTeamCulturePercent)) / 100);
}
else
{
pNewCity->changeOccupationTimer(0);
}
I'd rather not hardcode this, though. I'd like to create a new XML value, but I'm not sure how to reference it here.
xienwolf Dec 22, 2009, 04:41 PM You would add a Boolean tag to TraitInfos <bNoResistance> or something. Then in CvPlayer during ::setHasTrait (FfH code and a few others) or ::init (base BtS code) you would mark a Boolean on the player to indicate the ability. Query that Boolean here.
Alternatively, you could just loop over all traits here and check of the player has the trait and of so, check of the trait has this Boolean.
deanej Dec 22, 2009, 08:07 PM Thanks, I've gotten that working.
phungus420 Jan 11, 2010, 11:50 AM For reference, this is what I'm trying to do:
Softcoding Inquisition civics (http://forums.civfanatics.com/showthread.php?t=349487)
The reason I'm trying to do this is that RevDCM is the core of many mods, and it makes sense to softcode the inquisition civics so that modders can easily change things in the XML.
How do I expose an array to Python? I've added a PrereqOrCivic array to UnitInfos and it works. However I'd like to now reference this array in CyUnit, and can't figure out how to expose it there. Also this tag is never referenced in CvUnit (it's a canTrain function, so like the resource and tech arrays, it's only referenced in CvPlayer). I'm assuming I need to add it to CvUnit in order to expose it to CyUnit, so how do I do this?
This is the code in CvInfos:
CvInfos.h
//RevolutionDCM canTrain
bool getPrereqOrCivics(int iCivic) const; // Exposed to Python
//RevolutionDCM end
...
//RevolutionDCM canTrain
bool* m_pbPrereqOrCivics;
//RevolutionDCM end
CvInfos.cpp
//RevolutionDCM canTrain
m_pbPrereqOrCivics(NULL),
//RevolutionDCM end
...
//RevolutionDCM canTrain
SAFE_DELETE_ARRAY(m_pbPrereqOrCivics);
//RevolutionDCM end
...
//RevolutionDCM canTrain
SAFE_DELETE_ARRAY(m_pbPrereqOrCivics);
m_pbPrereqOrCivics = new bool[GC.getNumCivicInfos()];
stream->Read(GC.getNumCivicInfos(), m_pbPrereqOrCivics);
//RevolutionDCM end
...
//RevolutionDCM canTrain
stream->Write(GC.getNumCivicInfos(), m_pbPrereqOrCivics);
//RevolutionDCM end
...
pXML->SetVariableListTagPair(&m_pbPrereqOrCivics, "PrereqOrCivics", sizeof(GC.getCivicInfo((CivicTypes)0)), GC.getNumCivicInfos());
//RevolutionDCM end
...
//RevolutionDCM canTrain
for ( int iCivic = 0; i < GC.getNumCivicInfos(); iCivic++)
{
if ( getPrereqOrCivics(iCivic) == bDefault )
{
m_pbPrereqOrCivics[iCivic] = pClassInfo->getPrereqOrCivics(iCivic);
}
}
//RevolutionDCM end
...
And here is what the array looks like in UnitInfos
<PrereqOrCivics>
<PrereqCivic>
<CivicOption>CIVIC_THEOCRACY</CivicOption>
<bPrereqCivic>1</bPrereqCivic>
</PrereqCivic>
<PrereqCivic>
<CivicOption>CIVIC_ORGANIZED_RELIGION</CivicOption>
<bPrereqCivic>1</bPrereqCivic>
</PrereqCivic>
</PrereqOrCivics>
Also will exposing this array to CyUnit break save game compatibility?
xienwolf Jan 11, 2010, 03:52 PM You would want to leave it in CvPlayer, and thus CyPlayer, just for clarity I believe. Then in python you just reference the player identified with pUnit.getOwner() and check the canTrain against the player as normal. Reason being that most cases where you want to use canTrain you are checking about building a unit, it just HAPPENS to also be used for upgrading units. I guess that is the primary use for it in your case?
Anyway, there are plenty of examples for exposing arrays to python, if you just want the array itself (a UnitInfos object) then you can do it with a single line in the appropriate CyInfoInterface(1/2/3).cpp file (you just define a getPrereqOrCivics(int i) which points to the CvInfos function you wrote with this tag). The canTrain portion in CvPlayer is likely already exposed to python, so as long as you have included the check in there you'll be set in that regard.
Exposing things never harms savegame compat status to my knowledge, as you aren't saving/loading any new data.
phungus420 Jan 11, 2010, 04:56 PM The problem with using the existing python exposure is that it checks if the unit can be trained. But I specifically only want to check if the player is running one of the civics defined in the units array and nothing else.
xienwolf Jan 11, 2010, 07:37 PM Sounds like what you want is to just use the UnitInfo exposure then. I assume this is for checking when they move OUT of a civic to see if the unit should be lost? You could do that check in the DLL as well quite easily.
Anyway, to a getUnitInfo(pUnit.getUnitType()).getPrereqOrCivics (i) check, you just need the single line entry in CyInfoInterface(1/2/3), whichever holds all the other unitinfo exposure.
phungus420 Jan 11, 2010, 10:00 PM Don't I need to also put the variable in CvUnit and CyUnit if I want it to be able to call the civics from python? At least the call isNukeImmune in CvUnit isn't referenced in the SDK, and seems to have it's functional aspects work in python, yet if you follow it, it is set in CvUnit and CyUnit respectively, as well as CyInfoInterface.
Looks like I'm going to need to google loops again. I can't figure out how to really code this without using a loop, and I can't remember how to code a loop that would work for this :crazyeye:
xienwolf Jan 11, 2010, 11:06 PM No, this will be something which doesn't need to be loaded onto the unit (pUnit), just the UnitInfo (eUnit). It exists only in CvInfos, specifically in the CvUnitInfo section of it (where XML is loaded from).
The only time you would need to move to having this as a property of the CvUnit object would be if you allowed promotions or something else to modify it. But since any unit which is UNIT_WARRIOR in the XML (for example) will ALWAYS have a certain PrereqCivic requirement, you can leave the data in CvInfos (same place that the name "Warrior" exists).
You are loading your data into an array of getNumCivicTypes() size, right? So any civic which is required just has a TRUE stored in that spot of the array. So you would do a loop over getNumCivicTypes(), check if the Unit gives you a TRUE for requiring that civic, then check if the player who owns the unit is following that civic. If not, unit needs killing.
phungus420 Jan 24, 2010, 05:56 PM Hey Xienwolf, using one of your current example new tags you've created in the tutorial, could you add a tut on how to do that and make the new tag save game compatible with a previous version that didn't have the new tag?
xienwolf Jan 24, 2010, 11:05 PM I could look at doing that. Probably best not to have it too early in the series so I don't confuse new codemonkies, but also best not to have it on the arrays so it isn't terribly complicated. Then again, most cases don't get complicated when adding new material, it is when you alter things that it goes a bit crazy.
Opera Jan 25, 2010, 03:22 AM I added a whole new class to the dll and I was wondering whether or not I need to create pStream->Read and ->Write parts? For now, I only have the read function that read the xml, not the saves functions, not sure I need them.
xienwolf Jan 25, 2010, 08:43 AM I'd say don't bother. Still not 100% certain what they are used for, but relatively certain it is just for checking a savegame which uses lock modified assets to ensure nothing was changed.
phungus420 Feb 11, 2010, 11:12 PM Repeating the same request for a tutorial on how to use the save game flags so that if we add a new XML tag, we can keep things save game compatible. I've added 4 new tags to the RevDCM core, and want to incorporate these in LoR without breaking save game compatibility. Interestingly enough 3 of the tags apparently don't break save game (only the array does), but davidlallen stated that the data is probably screwed up because of the new tags even if the save will load; and this seems like a good simple and useful tutorial to add.
xienwolf Feb 12, 2010, 12:02 AM Yeah, sorry. Meant to write it up a couple times over the last few days, but I am stuck in a fairly endless loop of looking up citations of citations trying to track down some specifics on a fabrication technique for my lab. After banging my head against the wall for a few hours on that my ability to type coherent guidelines for simple processes is a bit limited (and my sense of humor is pretty harsh), so I wind up preventing myself from attempting to write anything out.
The big tricky bit of trying to maintain your savegame compatibility is to have a backup method of acquiring the information which wasn't available previously. This means picking it up out of the Cv___Infos functions (unfortunately you cannot be PERFECTLY savegame compatible if you added things to the XML and they had Lock Modified Assets enabled. But it is their fault for using that strange gameoption and wanting to update midgame)
Basically how it works is like this:
You added a new tag to the XML, so in CvInfos you would look at your ::read(stream) function if you have one and you would find this line early on:
uint uiFlag=0;
stream->Read(&uiFlag); // flags for expansion
And of course in ::write(Stream) you'll find the matching element:
uint uiFlag=0;
stream->Write(uiFlag); // flag for expansion
When the DLL was first fashioned, every ::read and ::write in the entire DLL contained these lines at their opening. Wherever the value is no longer 0, that is where Firaxis has manipulated it to maintain some savegame compatibility. The only ones I see right now is in CvGame they have uiFlag=1 and in CvUnit they have uiFlag=2. Though honestly the CvUnit one could be
In CvGame, they used it in ::read as follows:
if (uiFlag < 1)
{
int iEndTurnMessagesSent;
pStream->Read(&iEndTurnMessagesSent);
}
.
.
.
.
if (uiFlag < 1)
{
std::vector<int> aiEndTurnMessagesReceived(MAX_PLAYERS);
pStream->Read(MAX_PLAYERS, &aiEndTurnMessagesReceived[0]);
}
This tells us that before the flag was set to 1 (I am pretty sure that this is the one they did in 3.19 which let me catch on to how you do this) there was an m_iEndTurnMessagesSent variable in the code. After 3.19, this variable didn't exist, so it wasn't required to be written/read. But if your savegame was from before that patch, your stream has this spare INT. Thus if the flag was a 0 (meaning a pre-3.19 save) it would create a dummy variable to read the information into. As soon as the IF statement finishes, the variable is discarded since it was a local. Thus we read that spare INT and did nothing with it. The second chunk is the same thing, but with a vector instead of a single INT.
This is the EASY problem to fix, they removed something. So you read it in a dummy variable just to "jump ahead" in the stream and get back to what really exists.
In CvUnit, it is the same deal
if (uiFlag < 2)
{
int iCombatDamage;
pStream->Read(&iCombatDamage);
}
So apparently we no longer track the combat damage specifically. Instead we just track their health as I recall it.
Further down, they have a sample where something was added though.
if (uiFlag > 0)
{
pStream->Read(&m_bAirCombat);
}
Far as I can tell, this flag is just used for inteception of air units, so apparently that wasn't a possibility in the initial launch of Civ 4, or it just wasn't handled as efficiently. Not entirely sure. In this case, if you come before this patch (probably this one had moved them to uiFlag 1, while the removal of CombatDamage moved them to 2) then you LACK a boolean variable, so you need to skip reading this.
Now, this time we ADDED something, so we have to ensure that the default values are acceptable, or that proper values are loaded. This particular flag is only ever set DURING combat, and since you cannot save mid-combat, it seems that it would be false for all units at the time of a save anyway. But even lacking that, it flags an ability which hadn't existed prior, so a false on all units is a safe bet.
I would post where/how I used this, but looking at it now, I am pretty sure that I got things backwards and wrote the ::read in the manner I needed for the ::write and vice-versa.
Anyway, where things get tricky are where you are adding information to the game for which a default is NOT going to work. Like if you added a new array to PromotionInfos, and used that array for some information on a pre-existing promotion, like Combat I. If you had only used it on a completely NEW promotion, then no unit would have that promotion yet, so the default of NO INFORMATION LOADED in CvUnit, would be perfect. But if it is on a promotion that some unit may already have, then you have to load the new data on to the unit as well (this would be true of course for any change to a pre-existing promotion, sure you might SEEM savegame compat, but when you play for a while you'll notice problems where unit stats don't match up with what they should be based on promotions held, because they lack the additional stats of the promotion they gained pre-patch, or even have those stats REMOVED from themselves if they have gained the promotion pre-patch, and lost it post-patch).
In cases like that, within your IF check to verify that you are loading a pre-change savegame, you have to set things up to find the proper data and modify your gamestate to match. In this example case of modification to Combat I, you would check if the unit had Combat I (well, any promotion which utilizes the new fields honestly) and if they do have such a promotion, load up the new information just like as if you were running ::setHasPromotion for just the new tags. Obviously this has to be done AFTER loading promotions onto the unit, as well as setting any other state information for the unit which you may be changing (otherwise you just write over your careful update).
Yeah.... not so easy to read/understand possibly. But it might help to get you a bit further along for some more pointed questions, or till I have the time to figure out a nice way to write it up as part of the main tutorials. Though my current example code is all fairly simple to make savegame compat, so wouldn't show off the more difficult possibilities too well.
EDIT: Also, nobody has noticed the change to first post? Awww :(
phungus420 Feb 12, 2010, 12:45 AM Could you show an example? Ie, could you take the tutorial you did for adding an array, then expand it showing how to make that new Array XML tag be save game compatible? This is definitely one of those things I need to see done, trying to follow the above post leaves me still lost as to where I am supposed to start.
xienwolf Feb 12, 2010, 09:46 AM For the parts in CvInfos: There is nothing needed/possible. These read/writes are only utilized for Lock Modified Assets, which any patch WILL break savegames for. So that portion of the tutorial remains unchanged. And since it seems I only half finished the array tutorial, that means I have nothing to modify and show with that particular example.
Maybe a non-code illustration could help?
Let's pretend that on initial release, you had a count of even numbers.
::write(stream)
{
int uiFlag=0;
->write(uiFlag);
->write(m_eDataType);
->write(2);
->write(4);
->write(6);
->write(8);
->write(10);
->write(12);
->write(14);
}
::read(stream)
{
int uiFlag=0;
->read(uiFlag);
->read(m_eDataType);
->read(2);
->read(4);
->read(6);
->read(8);
->read(10);
->read(12);
->read(14);
}
Now, if you wanted to add odd numbers in a future patch, you would change it as follows:
::write(stream)
{
int uiFlag=1;
->write(uiFlag);
->write(m_eDataType);
->write(1);
->write(2);
->write(3);
->write(4);
->write(5);
->write(6);
->write(7);
->write(8);
->write(9);
->write(10);
->write(11);
->write(12);
->write(13);
->write(14);
}
::read(stream)
{
int uiFlag=0;
->read(uiFlag);
->read(m_eDataType);
if (uiFlag>0)
{
->read(1);
}
->read(2);
if (uiFlag>0)
{
->read(3);
}
->read(4);
if (uiFlag>0)
{
->read(5);
}
->read(6);
if (uiFlag>0)
{
->read(7);
}
->read(8);
if (uiFlag>0)
{
->read(9);
}
->read(10);
if (uiFlag>0)
{
->read(11);
}
->read(12);
if (uiFlag>0)
{
->read(13);
}
->read(14);
}
This is assuming that I have default values for the odd numbers in my code during initialization. If I did not have such, or those were insufficient, I would have to set them up here.
::read(stream)
{
int uiFlag=0;
->read(uiFlag);
->read(m_eDataType);
if (uiFlag>0)
{
->read(1);
}
else
{
1 = m_eDataType.get1();
}
->read(2);
if (uiFlag>0)
{
->read(3);
else
{
3 = m_eDataType.get3();
}
->read(4);
if (uiFlag>0)
{
->read(5);
else
{
5 = m_eDataType.get5();
}
->read(6);
if (uiFlag>0)
{
->read(7);
else
{
7 = m_eDataType.get7();
}
->read(8);
if (uiFlag>0)
{
->read(9);
else
{
9 = m_eDataType.get9();
}
->read(10);
if (uiFlag>0)
{
->read(11);
else
{
11 = m_eDataType.get11();
}
->read(12);
if (uiFlag>0)
{
->read(13);
else
{
13 = m_eDataType.get13();
}
->read(14);
}
If I were able to just say 1=1; 3=3... for the values, that would have easily been set up through my defaults in initialization, which would have allowed me to use the first version. This one instead accesses the CvDataInfos:: functions over in CvInfo to pick up what is required for this particular object to be accurate (maybe in one of my CvData types stores raw numbers as numbers, but another stores letters as numbers, so 1=a; 3=c... If that were the case then strict defaults wouldn't work as it isn't always the same. But accessing the CvDataInfo to pick up what it SHOULD have been does work)
And then in a patch after that, you decide to remove multiples of 3:
::write(stream)
{
int uiFlag=2;
->write(uiFlag);
->write(m_eDataType);
->write(1);
->write(2);
->write(4);
->write(5);
->write(7);
->write(8);
->write(10);
->write(11);
->write(13);
->write(14);
}
::read(stream)
{
int uiFlag=0;
->read(uiFlag);
->read(m_eDataType);
if (uiFlag>0)
{
->read(1);
}
->read(2);
if (uiFlag>0)
{
if (uiFlag<2)
{
int trash=0;
->read(trash);
}
}
->read(4);
if (uiFlag>0)
{
->read(5);
}
if (uiFlag<2)
{
int trash=0;
->read(trash);
}
if (uiFlag>0)
{
->read(7);
}
->read(8);
if (uiFlag>0)
{
if (uiFlag<2)
{
int trash=0;
->read(trash);
}
}
->read(10);
if (uiFlag>0)
{
->read(11);
}
if (uiFlag<2)
{
int trash=0;
->read(trash);
}
if (uiFlag>0)
{
->read(13);
}
->read(14);
}
Here we loaded the extra information in older save versions into a trash dummy variable just to get it out of our way since the data isn't required any longer.
phungus420 Feb 12, 2010, 07:01 PM So for added tags that only have a read/write stream in CvInfos I don't need to do this? I only need to take into account tags that have a read/write stream portions in other files? This seems odd, because most tags that are added only have a read/write stream in CvInfos.
Shiggs713 Feb 12, 2010, 08:53 PM well this is quite a gem! thanks for posting xienwolf
xienwolf Feb 13, 2010, 07:39 AM Mostly, it is just that for tags which read/write in CvInfos you CANNOT do this. There is no other way to get the information which is needed.
You can do the modification for when you REMOVE a tag, that is easy enough. But if you ADD a tag, then doing this means that for a pre-patch save you simply don't wind up having any data at all for those variables. You COULD set it up this way still, but you would have default values for all of those variables until you save under the new patch and then load the game once again.
But it shouldn't matter in CvInfos. I am 90% certain that those ::read and ::write functions are ONLY used when you have Lock Modified Assets enabled so that it can test that your game data hasn't been altered.
phungus420 May 03, 2010, 08:40 AM I'm a bit stumped by a recent code change I would like to implement. I have a boolean array created by afforess in BuildingInfos: PrereqBuildingClasses. I pass in a value (corresponding to a buildingclass), it tells me true or false. Works great. I'd like to add in a second paramater: TechOverride, where if the player has researched this tech it overrides the requirement. How is the best way to do this? I've added it into the schema, but am stuck on how to load it in CvInfos.
This is the function currently in UnitInfos:
bool getPrereqBuildingClass(int i)
I suppose I could add in a second argument, the tech, but then I run into two problems. First, I want it to override the "true" value, if it's there, but not if nothing is defined (or if the wrong tech is passed in), but I also wouldn't want it to override a false. I'm not exactly sure how to set this up. The second problem is a bigger concern, and that's that this is for RevDCM, which is used as a modding base for multiple mods. If I add in a second argument other mods are going to suddenly have their code break because the'll get a "function takes two arguments, one given" error.
My other issue I'm not really seeing is how I can get the tech that overrides, I'm not sure how to create a function to ask for an int value inside a boolean array.
I've been staring at the screen for a couple hours now, trying to wrap my head around these issues, but haven't made any progress, so figured I'd post in here for help.
Edit:
I'm not sure if this is the right way, but I followed YieldTechChanges as a template in ImprovementInfos. Following this setup I left well enough alone with the original getPrereqBuildingClass function and created a new one, along with a multidimensional array, where the m_ppbPrereqBuildingAndTechs was the number of buildinclasses in the [i] part and as big as TechInfos in m_ppbPrereqBuildingClass[i][j]; or in other words:
I_size = GC.getNumBuildingInfos();
J_size = GC.getNumTechInfos();
bool m_ppbPrereqBuildingAndTechs[I_size][J_size];
Of course since one cannot set up array sizes as non constants, I'm using what i believe is the proper method, as I've followed the TechYieldChanges tag in ImprovementsInfos as a template. Now everything was going smoothly, and I figured I'd have a nice multidimesional array to call weather a unit has a PrereqBuildingClass with a corresponding override tech or not, but then I got to where CvInfos actually reads things from the XML. The problem is the tag I am following has an extra level of depth, as it looks inside the YieldTypes array, in the XML it looks like this:
<TechYieldChanges>
<TechYieldChange>
<PrereqTech>TECH_AQUACULTURE</PrereqTech>
<TechYields>
<iYield>0</iYield>
<iYield>0</iYield>
<iYield>1</iYield>
</TechYields>
</TechYieldChange>
</TechYieldChanges>
What I've got, or rather what makes the most sense to me is this:
<PrereqBuildingClasses>
<PrereqBuildingClass>
<BuildingClassType>NONE</BuildingClassType>
<bPrereq>1</bPrereq>
<TechOverride>NONE</TechOverride>
</PrereqBuildingClass>
</PrereqBuildingClasses>
It's different, and the method CvInfos reads TechYieldChanges doesn't correspond correctly. Here is the code it uses:
// initialize the boolean list to the correct size and all the booleans to false
FAssertMsg((GC.getNumTechInfos() > 0) && (NUM_YIELD_TYPES) > 0,"either the number of tech infos is zero or less or the number of yield types is zero or less");
pXML->Init2DIntList(&m_ppiTechYieldChanges, GC.getNumTechInfos(), NUM_YIELD_TYPES);
if (gDLL->getXMLIFace()->SetToChildByTagName(pXML->GetXML(),"TechYieldChanges"))
{
if (pXML->SkipToNextVal())
{
iNumSibs = gDLL->getXMLIFace()->GetNumChildren(pXML->GetXML());
if (gDLL->getXMLIFace()->SetToChild(pXML->GetXML()))
{
if (0 < iNumSibs)
{
for (j=0;j<iNumSibs;j++)
{
pXML->GetChildXmlValByName(szTextVal, "PrereqTech");
iIndex = pXML->FindInInfoClass(szTextVal);
if (iIndex > -1)
{
// delete the array since it will be reallocated
SAFE_DELETE_ARRAY(m_ppiTechYieldChanges[iIndex]);
// if we can set the current xml node to it's next sibling
if (gDLL->getXMLIFace()->SetToChildByTagName(pXML->GetXML(),"TechYields"))
{
// call the function that sets the yield change variable
pXML->SetYields(&m_ppiTechYieldChanges[iIndex]);
gDLL->getXMLIFace()->SetToParent(pXML->GetXML());
}
else
{
pXML->InitList(&m_ppiTechYieldChanges[iIndex], NUM_YIELD_TYPES);
}
}
if (!gDLL->getXMLIFace()->NextSibling(pXML->GetXML()))
{
break;
}
}
}
gDLL->getXMLIFace()->SetToParent(pXML->GetXML());
}
}
gDLL->getXMLIFace()->SetToParent(pXML->GetXML());
}
I am not familiar with how the XML is read by, and I have always simply copied this stuff before. Right now though I cannot see how to get the XML properly read. Hoping for some help or direction here.
phungus420 May 03, 2010, 05:06 PM Well it apears things are getting sorted out in the python/SDK forums. I aparently was not on the right track going with a multidmensional array.
xienwolf May 03, 2010, 11:15 PM Cool. I get email updates from this thread, though I don't check my email too often either, so if you need help with it post here again with a link to the other thread and I'll try to take a look. Been lax on the forum checking lately. A tad busy with other activities :)
phungus420 May 04, 2010, 06:59 AM The thread in the SDK/Python forums is here:
http://forums.civfanatics.com/showthread.php?t=337981
I still do not know how to get the XML read properly.
Magma_Dragoon Feb 13, 2011, 03:37 PM :goodjob:Thanks for the guide on what all the .cpp files are for. I've decided to mod civ4 and get a few more games worth of fun out of it since civ 5 seems kinda "feh".
|
|