Program the Tag into the Game Stream
At this point we are ready to start working our tag into the flow of the game itself. Here's where there are a lot of steps that will vary a great deal from one tag to another. Therefore this is the step where we go swimming out into the big ocean of code and need to have some idea of the larger picture of the DLL structure overall. But it's not as confusing as it might seem and there are some standard steps you will likely take here.
Almost always, you'll have to work the tag into being applied to the object IN the game that it's supposed to apply to.
For example, if you have a building tag, you'll need to set it up so that the values of that tag will add to the city when the building using that tag is constructed - making it's value removed when the building is lost somehow is usually part of that step too. This step is called the processX step. In such an example, processBuilding is actually the name of the function where this takes place.
Even before you do this, you've probably gotta set up the game object with the ability to keep track of the value that your new tag brings to that object. That step is a matter of setting up a variable for the object type in question, then programming functions to add or subtract values of that variable, and retrieve those values when the game needs them. This whole process is much like the previous step except that it applies to the object in the game, not just the type of base object it derives its initial values from.
For example, we just setup a UnitInfos function, getReligiousCombatModifier, but that is to be called when a unit needs to know what amount that variable is defined as on its base unit TYPE, whereas now we need to make it possible to add in promotion and unitcombat change amounts to a final total for each individual unit and make that total something the game can reference.
We also need to setup the save and load sequence for those new variables.
Then we can move into applying the sum total of your tag variables where it actually counts. In the case of my ReligiousCombatModifier, I'll setup where it applies as a modifier in combat and the filters that determine if the unit gets the benefit (or penalty) of this modifier when and where combat modifiers are being compiled for application in establishing combat odds and during actual combat. Similar things would take place for just about any tag, plugging the final compiled variable for that game object into where it will apply to make its effect happen in the game.
That's also the part I can't explain everything about. It takes some searching through the code to figure out where to apply those tag values. You get more familiar over time but there are some ways to look through the function lists and some naming conventions on functions that make this a little easier to figure out how to research into fast enough to make it possible to keep from losing your way - and your mind.
So let's start here by listing the above sub-steps and we'll work through them for my example.
- Setup the variable and functions for compiling the variable on the applicable game object.
- Initialize the default data on your variable
- Include the variable in the read/write wrapper
- processObject - insert your info.cpp variable into the process stage so that it adds/subtracts when the object, such as trait, building, promotion, unitcombat etc... is added or lost.
- Apply your total to where it interacts with the game process itself
Establish the Variable and Functions for calculating and returning that value
In our example, I'm adding a set of tag effects to units. Therefore, I'm going to now go to CvUnit.h and CvUnit.cpp to compile and track my values on the actual units in the game. Note the difference between CvUnitInfos.cpp and CvUnit.cpp. The first is for accounting for the base unit type but the second is for accounting for (and working the game processing functions on) the actual unit in the game. Thus one Spearman unit can differ in abilities to another Spearman unit based on the specific level and promotions it has taken, whether it's been damaged, etc... CvUnit.cpp tracks that specific in-game unit where all it's variables are uniquely compiled.
This portion of the process, however, is much like the Infos setup. We must declare the variable and declare the functions that track, return and manipulate that variable all in the header file (.h). Then we program those functions in the .cpp file. If you were doing this for a building, you'd do this all in CvCity.cpp/.h. If it was for a trait or civic you're adding things to the player level so CvPlayer.cpp/h. For a Feature tag or a Terrain tag or an Improvement or Bonus (that affects the plot) you'd do all this in CvPlot.cpp. Usually, if you don't know where to go, research the flow of tags that work in a similar manner to the one you want to use to determine where you need to compile the tag in these files.
So I start in CvUnit.h, and find a similar variable to the one I want to insert. This will remain a simple integer, and the naming convention for these integers is to, as in the Infos files, start them with m_i. So I can simply search for m_i and find a similar variable I want to follow the steps this variable takes. A simple conditional combat modifier would work very well to model this after so I'll use m_iExtraVSBarbs as my model to work from. This is a combat modifier tag that applies in combat only against non-animal barbarians and neanderthals (collectively known as Hominids in the code).
You will notice the 'Extra' term being added at this step. This is because the base value is going to be directly derived from the UnitInfo and for the actual unit, here we only need to track the EXTRA amounts that the unit's unitcombats and promotions are adding(or subtracting) to the unit so that we can provide a sum totaling function for retrieving the total amount the unit has. This is also one helpful way of distinguishing between the various steps we're working on is to vary the naming conventions a little. You might notice quite a few little naming convention tricks being employed here and I'll try to point out those we encounter as we go.
In the case of a promotion-only tag, you may not want to use the Extra moniker. You'll still need to create a variable for the unit to store the total amounts of that value that promotions have given to the unit - remember that multiple promotions can still add up this value.
There are SOME cases where you might want the value to be the highest or lowest of all the promotion sources the unit has processed into it instead of being a tally, and with some searching you can find some examples of tags that work like this as well.
Either way, the only difference here when you are not using the 'triad' of unit modifiers, is that you aren't going to have a base unit value or combat class source to tally into this variable total. Thus, a promotion tag alone is nearly going to mean the same amount of data reserved per unit as a unit/promotion/unitcombat set does, which is why I usually am liberal about doing all three if I can even imagine ever wanting the tag in use on the other types.
So I have found in the CvUnit.h file where the m_iExtraVSBarbs is declared and I add my variable declaration right afterwards so now it looks like this:
Code:
int m_iExtraPursuit;
int m_iExtraEarlyWithdraw;
int m_iExtraVSBarbs;
int m_iExtraReligiousCombatModifier;
You'll notice this section where the variables are being declared is also located beneath a protected: designation, just as it is in the Infos file, and the functions are declared under a public: designation.
So my next step is to declare the necessary functions for compiling, calculating and reporting this variable. So I look for 'ExtraVSBarbs for where the set of functions for this variable are declared and I'll just copy those declarations and replace the terminology of VSBarbs with ReligiousCombatModifier.
So the first line I find is:
Code:
int vsBarbsModifier() const;
This is the function that provides the grand total of this modifier for this unit when it is called, the one that really gets applied into the code stream where you want the effect to be included. I know because it uses the naming convention of lower case letters to start it off and doesn't utilize 'get' or 'is' or 'has' or anything like that. Often it will also end with the word Total but this was a tag I did in earlier days of modding and hadn't yet decided to add that to make it more clear. This function is also the one we call when we're building the display for this value on the in-game unit help tool / mouse hover information.
Anyhow, I'll need to duplicate this declaration and rename it for my new tag. I'm going to add the Total term for the sake of clarity as well.
int religiousCombatModifierTotal() const;
Again, const is for retrieving values and means the variables in these functions cannot be manipulated within the function (though localized variables can be which is sometimes useful for these totaling functions.)
Then I continue my search. In newer tag sets you'll probably find this totaling function is right next to the rest of the functions regarding the tag but earlier examples have the total functions separated out. This could all do for some re-organizing of the .h/.cpp but it really doesn't ultimately matter what order anything appears in so it's hard to see the value in reorganizing this.
The next place I find VSBarbs applied is here:
Code:
int getExtraVSBarbs (bool bIgnoreCommanders = false) const;
void changeExtraVSBarbs (int iChange);
These are the functions that adjust and return the total of our variable. The first is used in the totalling function above where it is added to the base value from the unit and the total is returned.
The parameter, bool bIgnoreCommanders = false, is completely optional and you only need to include it if you want this tag to be something a field commander would pass along to those within his command rather than adds to himself. You may already be aware that a Field Commander unit cannot have a tag for himself AND pass that value along to those under his command.
The inclusion of ' = false ' in this parameter is an established default for when this function is called so that the function can be called without having to specify this parameter at all if it's calling it in the 'usual' manner.
The actual programming of this function will be altered a bit in a commander applicable tag vs one that isn't. It should also be noted that I did not realize that the use of commander application does cause a little processing delay that can add up unnecessarily so at some point we may want to audit the necessity of applying this option and try to eliminate some of them, but it cuts down on our options for commander promos when we do really and I honestly feel that we haven't even begun to tap the potential for cool commander promos yet.
The second is where the processPromotion or processUnitCombat function will add or subtract the values from the promotion or unitcombat when those objects are processed onto the unit. That second function is thus declared a void function, as it is a call to manipulate the variable rather than return its value, (it's an action command rather than a call for information), and it has a parameter of iChange, where the amount to manipulate the variable by is supplied in the process stage where it calls this function.
So I duplicate these function declarations for my example:
Code:
int getExtraReligiousCombatModifier(bool bIgnoreCommanders = false) const;
void changeExtraReligiousCombatModifier(int iChange);
Then I find I loop back around to the declaration of the VS Barbs variable so I know I'm done with my work in the CvUnit.h file. I move on to CvUnit.cpp and continue, from the top down, my search for VSBarbs and I'll end up hitting all other steps I gave above, just maybe not in the order I assigned them, which is not entirely necessary to program them in that order - I gave that order above for the sake of following in the order the system would process the data flow rather than exactly the order in which I address these steps exactly.
Initialize the default data on your variable
So from here I let the search guide my path. The first instance of VSBarbs I find in CvUnit.cpp is:
This appears in a long string of similar value assignments. In C++ you must give your variables a default value when you initialize them into the game. Therefore, here is where, when a unit is born, it's initial default value is applied to all it's tags. Actually, this first instance of this is under void CvUnit::reset(yada yada) so it's not only called during unit manifestation but also during resets such as what happens during a recreation of the unit that takes place when the unit is upgraded.
So I add a similar line to establish the default value for the tag here.
Code:
m_iExtraReligiousCombatModifier = 0;
Sometimes you'll find more than one function where this needs to take place in the class you're working in, which is why I am teaching to follow previous examples. Where recalculations take place there is a similar function that resets the variables to 0 usually. Units don't get recalculated by the recalc feature so don't have that second spot for this.
Note:
As you continue to search, sometimes you'll find some results that contain the term you're searching for but have nothing to do with the actual tag you're looking at. Watch out for those and ignore them. For example, I just found:
Code:
if (isDead())
{
if (isNPC())
{
GET_PLAYER(pDefender->getOwnerINLINE()).changeWinsVsBarbs(1);
}
becuase 'changeWinsVsBarbs' has VSBarbs in it but this has little to do with the value manipulations we need to address right now.
Apply your total to where it interacts with the game process (part I)
Rather out of order here to logical coding sequence I find:
Code:
if (isHominid())
{
//TB Combat Mods Begin
iExtraModifier2 = -pAttacker->vsBarbsModifier();
within the
int CvUnit::maxCombatStr(const CvPlot* pPlot, const CvUnit* pAttacker, CombatDetails* pCombatDetails, bool bSurroundedModifier) const
function. This is where the combat modifier total is being conditionally added and I might as well just take the step to add my total combat modifier conditionally here. THIS is not as simple as copying my example because I need to build my filter for when this modifier applies here and that filter isn't a terribly simple one. This example also isn't being applied quite where this new one needs to go. There's a lot of little intricacies to cover in this function - it's really kind of messy with tons of different ways the function can be called. But suffice it to say, the modifiers are tallied here and reported into the combat help hover and even into the EXE in a list of specific variables you cannot add to. So I want this to be a conditional normal combat modifier so I want to find where the actual usual generic combat modifier is being added and add to that total with this value WHEN it should apply.
So I'll look for that moment here in this function. I find it's being applied up above in a completely non-conditional placement where we don't have to know what the opponent unit is. So I do have to add it to that variable but I have to add it below, where we are only adding the modifier on the basis of knowledge of what unit it is we're fighting and variables on that unit or its owner.
What is clear from finding where the normal combat modifier is applied is the variable we have to add our total to, pCombatDetails->iExtraCombatPercent
So now, armed with that knowledge, I have to now look further down to find where we start adding values based on the knowledge we can have of our opponent.
All modifiers are applied to the defender so under this line:
Code:
// only compute comparisons if we are the defender with a known attacker
if (!bAttackingUnknownDefender)
{
is where we'll find where we can add our variable.
Here's a good example of the format to apply things:
Code:
iExtraModifier = unitClassDefenseModifier(pAttacker->getUnitClassType());
iTempModifier += iExtraModifier;
if (pCombatDetails != NULL)
{
pCombatDetails->iClassDefenseModifier = iExtraModifier;
}
iExtraModifier = -pAttacker->unitClassAttackModifier(getUnitClassType());
iTempModifier += iExtraModifier;
if (pCombatDetails != NULL)
{
pCombatDetails->iClassAttackModifier = iExtraModifier;
}
The first section there is applying any applicable modifier vs UnitCombat from the defender based on the attacker and the second is applying any applicable modifier vs UnitCombat from the attacker based on the defender.
This filter for religious combat modifier will be similar in complexity. I'm going to want to build the filter into the totaling function for this value to determine when it should apply. This means I'm going to go back and add a parameter to send the determined 'religion' of the opponent to the function so that the total is only returned IF the opponent's religion is contradictory to our unit's religion. (This is where my specific example gets a bit more complicated.) So I'll have to go back to the .h file and change the declaration there for the totaling function to:
int religiousCombatModifierTotal(ReligionTypes eReligion) const;
I'm also going to need a whole new function to draw upon for units to 'getReligion()' for that unit specifically so I can define there what kind of religion the unit is and be able to place that function in the new parameter I just added in the totaling function.
So for that I go all the way to the bottom of CvUnit.cpp and go to the declaration of the last new function and add beneath that the declaration for my new function:
Code:
ReligionTypes getReligion() const;
I'll program this as part of the last step since it will be added to the functions at the end of the .cpp.
So my application here in the combat modifier tally in int CvUnit::maxCombatStr is as follows:
Code:
iExtraModifier = religiousCombatModifierTotal(pAttacker->getReligion());
iTempModifier += iExtraModifier;
if (pCombatDetails != NULL)
{
pCombatDetails->iExtraCombatPercent+= iExtraModifier;
}
iExtraModifier = -pAttacker->religiousCombatModifierTotal(getReligion());
iTempModifier += iExtraModifier;
if (pCombatDetails != NULL)
{
pCombatDetails->iExtraCombatPercent+= iExtraModifier;
}
iExtraModifier is a variable that is a temporary platform upon which we are placing the modifier variable before applying it to the end combat modifier total (pCombatDetails->iExtraCombatPercent).
Sometimes you will see iExtraModifier set to 0 before a new section because it should always be completely reset before we use it in another segment here, but for this, we just set it directly to the amount our totaling function returns when we refer to our unit and qualify the value by supplying the religion of the opponent (and in that function it will ask us our own and compare our religion vs theirs to see if the modifier applies.) We do that because there's no need to set to 0 since the function return will fill it with 0 if our function returns no value.
iTempModifier is taking a running tally of all applicable combat modifiers through this section of the code and actually adds this end total to the actual combat system itself. Therefore, if we find an applicable combat modifier, it gets added in here. Being able to add the iExtraModifier derived here AND to the combat details tally (for the help display later) is why we use the iExtraModifier platform to place our results onto, thus it can be called on twice without calculating everything with function calls both times.
You will notice that the value assigned to iExtraModifier is negative in the second step of this process where we are deriving the value for the attacker's religious combat modifier. That's because this running total for iTempModifier is all going to apply in the end to the defender only - yep that's how this crazy combat system works and it can be a little disorienting to work with for a while until it gets comfortable.
To explain another way, positive combat modifiers for an attacker are reflected as negative total combat modifiers for the defender in the underlying combat engine.
You might not need to know that for the tag you're working on but there are probably going to be some specific quirks like these to cover for each unique project. I can't prepare someone for every unique facet of the core game design, unfortunately. So if you hit a barrier, which you likely will when you reach this stage of application, I'm more than happy to try to guide you through the specifics.
You can see (if you're following me in the code and I've already committed it) that after my added section for the religious combat modifier, moving into the processing of the unitcombat combat modifiers, the platform variable of iExtraModifier is immediately reset to 0.
At this point, I'm going to take a break from the application portion, as I've done what I need to do to apply the religious combat modifier into the game engine. I won't even have to do some more complex work with the combat help because that pCombatDetails bit feeds directly into that section - this will look like a basic combat modifier when it applies. I'll wrap things up when I program the getReligion function and I'll do that when I find the totaling function and insert where I need to call for that information on our unit in question.
I next find vsBarbs being applied in Air Combat modifier totals and I think I'm going to keep the religious combat modifier out of the realm of air combat because of the impersonal nature of air strikes. So I'll ignore that second application.
Part II
My next search result finds the vsBarbsModifier totalling function. So it looks like I'll be keeping with the application portion of our segment here.
Here's the function for our example tag:
Code:
int CvUnit::vsBarbsModifier() const
{
return (m_pUnitInfo->getVSBarbs() + getExtraVSBarbs());
}
So when this function is called, it's taking the getVSBarbs() function value from CvUnitInfo.cpp getVSBarbs() function for this unit, thus within CvUnit.cpp, m_pUnitInfo-> means get the tag data pointed to by the arrow from the base unit definition for this particular unit. This is part of why the tag for the unit, from the very beginning, is usually named without the 'Change' denomination.
THIS is where the base is pulled in and added to all extra sources, promos and combat classes, that have been processed onto the unit, which is what getExtraVSBarbs() returns.
By not including a pointer setup before the function call, getExtraVSBarbs() obviously is a function that exists within CvUnit.cpp. Within the AI files you can also call directly to these functions without a pointer setup as well. This works here and in the CvUnitAI.cpp so long as you're getting the information for the unit you have in focus when this portion of the code is processing. Otherwise, if you're looking for information on another unit, such as one we're in battle with, then you need to ask for the function return for that unit, and you thus may call something like pAttacker->getUnitClassType() as we did in the above example. pAttacker was a variable that was setup to refer to that specific unit that was the attacker in that case.
Ok, that's probably a bunch of stuff you didn't need to know or already do.
Anyhow, my example will be a little more involved here than the normal totaling function like this one because it's going to include filtering it's response in a manner that depends on what religion the opposition is.
One thing I forgot to do earlier when I changed the declaration of our new totaling function to take a Religion input in the parameters was to give that parameter a default value. If I do this, then I don't HAVE to specify the religion. I mentioned how to establish a default for a parameter earlier as well if you recall. So now my function declaration should look like this:
Code:
int religiousCombatModifierTotal(ReligionTypes eReligion = NO_RELIGION) const;
The term NO_RELIGION is pretty much used in any kind of indexed Type call as a default - just change the name after NO_ to match the object. These are macro defined definitions elsewhere in the code that mean a flat type declaration with a no type definition. It would be otherwise expressed as: (ReligionTypes)-1.
I'm also going to add a boolean parameter here that enables me to call this function and always get an answer as to what the total amount is without having to provide a religion to do it. I'm going to call that bDisplay.
Wherever you start declaring defaults on parameters, every parameter listed afterwards must also declare a default. I want one for this anyhow, false, so that the assumption is that we're looking for the value in relation to an opponent, as we've already programmed that portion and this boolean is here more specifically for display reasons so the help hovers can show the unit's accumulated Religious Combat ability when its not in the context of a potential battle.
So now our declaration looks like this:
Code:
int religiousCombatModifierTotal(ReligionTypes eReligion = NO_RELIGION, bool bDisplay = false) const;
Code:
int CvUnit::religiousCombatModifierTotal(ReligionTypes eReligion, bool bDisplay) const
{
if (bDisplay || (getReligion() != NO_RELIGION))
{
if (bDisplay || getReligion() != eReligion)
{
return (m_pUnitInfo->getReligiousCombatModifier() + getExtraReligiousCombatModifier());
}
else if (getReligion() == eReligion)
{
return -(m_pUnitInfo->getReligiousCombatModifier() + getExtraReligiousCombatModifier());
}
}
return 0;
}
Note that when you are setting up the function programming, you don't put the defaults as declared in the .h with =NO_RELIGION and =false. Just leave them out in this first line of the function programming. You can always see what the defaults are by right clicking on the function address line or on the function wherever it is being called in the code and selecting 'go to declaration'.
So you can see that I'm only returning the total if this unit's religion differs from the religion that its opponent, projected or real, has, and to make this tag really interesting, if, instead, they have the same religion, the combat bonus becomes a penalty instead (reluctant to hurt those who share the same beliefs.)
If the function is being called for display purpose only, then it will quickly return the base value for the unit plus all extra sources.
If your unit has no religion then this combat modifier is useless and will always return 0.
If your unit has a religion but your enemy does not, we just see that as a disagreement between religious views just as if the enemy had a religion, and we apply the totals.
Note that this is a const and nowhere in here are we actually changing any variables. We could get away with being able to manipulate a variable that's totally local to this function within a const, but we CANNOT assign a change to any variable declared outside this function directly, such as m_iReligiousCombatModifier. This is JUST for returning the value that exists, dependant on the filter conditions we established here. When you run afoul of const and have a hard time with the compiling of the code as a result, it helps to understand this. Also, you can only call for a const value within a const function like this. Therefore, if m_pUnitInfo->getReligiousCombatModifier() was not also declared as a const function, you would error out during compile. It's all to keep the program on protected tracks really.
Also keep in mind that keepReligion() has been declared at this point but I still need to program it. I'll again do that at the end here since we're searching through the code for VSBarbs to guide our efforts still.
Part III
Here's the next spot to address that our example of VSBarbs provides.
Code:
int CvUnit::getExtraVSBarbs (bool bIgnoreCommanders) const
{
if (!bIgnoreCommanders && !isCommander()) //this is not a commander
{
CvUnit* pCommander = getCommander();
if (pCommander != NULL)
{
return m_iExtraVSBarbs + pCommander->getExtraVSBarbs();
}
}
return m_iExtraVSBarbs;
}
void CvUnit::changeExtraVSBarbs(int iChange)
{
m_iExtraVSBarbs +=iChange;
FAssert(getExtraVSBarbs() >= 0);
}
To do this super simple, just copy and paste this example and switch out every instance of VSBarbs with RelgiousCombatModifier. Ultimately that's all we're doing here. But what do these functions mean? What are they saying?
Again, the first one is an integer call to the total of all extra sources of VSBarbs that have been assigned to this unit. Usually, it's only ever called in the totaling function we just did.
The parameter of bIgnoreCommanders is declared with a = false default so it's going to assume we should check for a commander that is in command of this unit giving it some value to add to here from his own amount tallied up in this function for him. getCommander() searches for an applicable local commander that has enough command points and influence range to contribute his abilities to this unit this round and is heavily cached for optimum performance so is not a simple function call.
Our new tag will allow commanders to become holy inspired leaders with this tag in use on commander promotions so we'll keep all that in place for our corresponding function here. Ignoring the commander is setup for calls to this function where that may be a desireable thing to bypass, not that this is usually used in any kind of display text anywhere.
This commander stuff will get a little trickier with domain-specific commanders being a project to address at some point. (For admirals, generals, and aviators to only command units of their own domain type.)
The second function receives the amount to change our unit's storage variable by and adjusts it (from a default we established above as 0) whenever we hit the point of processing a promotion or unitcombat that gives or manipulates this ability on a unit.
The Fassert is saying throw an assert when the debug dll is running if this total comes up as less than 0 because really, that shouldn't be a valid value in the XML so the XML should be reviewed to see how it got that way and what the thinking was and perhaps we should allow it but enforce that the return on the total extra amount instead be limited to a minimum of 0 even if the variable comes up as less than that. If you make this change, by having the first 'get' function here return
std::min(0,m_iExtraVSBarbs) , then remove the FAssert here.
So our corresponding functions now look like:
Code:
int CvUnit::getExtraReligiousCombatModifier(bool bIgnoreCommanders) const
{
if (!bIgnoreCommanders && !isCommander()) //this is not a commander
{
CvUnit* pCommander = getCommander();
if (pCommander != NULL)
{
return m_iExtraReligiousCombatModifier + pCommander->getExtraReligiousCombatMoidifier();
}
}
return m_iExtraReligiousCombatModifier;
}
void CvUnit::changeExtraReligiousCombatModifier(int iChange)
{
m_iExtraReligiousCombatModifier += iChange;
FAssert(getExtraReligiousCombatModifier() >= 0);
}
I added them in right below the VSBarbs functions and that's perfectly ok to do, to add your tag processing functions right after the best example you find to base your work on.
You could also do them all in a group at the end of the function and you'll see I've done that numerous times when I wasn't working with a template example but was just programming it out. Took me a bit to get comfortable enough to be able to do this.
Following previous examples are particularly important when you start interacting with mapped variables that Koshling put in CvUnit, which he did to give us tons more data room over the way it was in Firaxis's original work, where they were all stored in data costly arrays. The syntax for interacting with these is tricky for me still. They come up when the tag also declares a specific type that it applies to, like a combat modifier vs a particular unitcombat type for example. Had I made this ReligiousCombatModifier capable of specifying specific religions you'd get a modifier against, I'd have pushed us into this realm of complexity. At some point, you may wish to 'go there' in some way, and again, the best advice is to find an example and make your processing analogous to it, particularly until it really starts making sense how it works.
Even now knowing how to usually go about things without an example, I still often follow them just to make my task faster, easier and less error-prone. Thus my most common error is failing to swap out a term in copied and pasted code for the new tag's terminology. Thankfully the compiler often catches this. I also am sure to reread everything I do numerous times before moving on.
Moving on...
PromotionValidity checks you may need to look at:
I find VSBarbs here in this section which is a bit unique for promotion tags:
Code:
if (isSpy())
{
if (promotionInfo.getAttackCombatModifierChange() != false ||
promotionInfo.getDefenseCombatModifierChange() != false ||
promotionInfo.getPursuitChange() != 0 ||
promotionInfo.getEarlyWithdrawChange() != 0 ||
promotionInfo.getVSBarbsChange() != 0 ||
This list is found in
bool CvUnit::isPromotionValid(PromotionTypes ePromotion, bool bKeepCheck) const
and it indicates that promotions with this value are not valid for spy units, which do not take normal promotion tags in the normal manner - usually they mean something completely different for spies. Spies are tricksy little dudes and a whole subject of study in and of themselves. If you aren't programming a tag specifically to be used for spy promos, include your promotion's get function call here on a new line somewhere in the middle here. Thus, right after the line I stopped at above, I'm adding:
Code:
promotionInfo.getReligiousCombatModifierChange() != 0 ||
I'm also adding a similar line under the same basic syntax I found before under another validity check designed to turn off promotions that use certain tags for all units that don't have any strength score. OBVIOUSLY a combat modifier of any kind won't apply to 0 strength units.
Code:
if (getDomainType() != DOMAIN_AIR)
{
if (baseCombatStr() < 1 && m_iBaseCombat < 1 && !isCommander())
{
if (promotionInfo.getInterceptChange() != 0 ||
promotionInfo.getEvasionChange() != 0 ||
promotionInfo.getWithdrawalChange() != 0 ||
starts off this list and it's in the same validity function.
processUnitCombat and processPromotion
Next on the search I find the line that processes in our values from the unitcombat tag applications as we're adding or removing unitcombats that use this tag:
Code:
changeExtraVSBarbs(kUnitCombat.getVSBarbsChange() * iChange);//no merge/split
In void CvUnit:

rocessUnitCombat(UnitCombatTypes eIndex, bool bAdding, bool bByPromo)
At the beginning of this function we are setting up whether we are adding or subtracting the value and that will determine whether iChange is 1 or -1. When iChange is 1, the tag is being added if there is any value in that tag on the unitcombat being added. If the unitcombat is being removed from the unit, iChange will be -1 and thus the call to changeExtraVSBarbs will include a negative parameter that will remove the amount this unitcombat added to the unit when it was originally assigned.
So our new tag needs a line for this since we've included the full triad of unit tags and have made it possible for unitcombats to influence Religious Combat Modifiers.
Code:
changeExtraReligiousCombatModifier(kUnitCombat.getReligiousCombatModifierChange() * iChange);//no merge/split
Remember we just established the changeExtraReligiousCombatModifier function above.
If you do not have this tag in use for unit combats, you don't include this step. Again this gets a bit trickier for more involved data types so follow examples given and you should be able to work through it properly.
The //no merge/split comment was added to help identify those tags that won't have this data calculated in another after-stage if the unit is merged or split. Combat Modifiers don't adjust, for example, on a split or merge in size matters, but the actual unit strength very much does. The work involved on those tags that would be influenced by a merge or split gets much more intense. Again, follow examples (even I have to with this go off of what took many hours of headaches to perfect.) It's nice to explicitly state here that this tag is NOT needing these extra efforts.
It should come as NO surprise that you're going to find the same exact sort of line in processPromotion next and need to take an almost identical step there if you are working with a promotion tag.
Code:
changeExtraVSBarbs(kPromotion.getVSBarbsChange() * iChange);
changeExtraReligiousCombatModifier(kPromotion.getReligiousCombatModifierChange() * iChange);
You'll notice the only difference here (aside from not having put the merge/split comment) is that we're calling for kPromotion rather than kUnitCombat. The promotion or unitcombat that is being processed in is referred to this way to setup the call to that promo or unitcombat's information on this particular tag.
Read/Write Wrappers
Continuing our search through for our example of VSBarbs, we find:
Code:
WRAPPER_READ(wrapper, "CvUnit", &m_iExtraVSBarbs);
Here's where we need to be a little careful.
This is the read wrapper. What it does is load in the unit's saved value from a game save. This IS the read sequence for each unit. When values are stored for a unit in the save file, they are saved in something similar to a comma delineated sequence. So this is the #1 most important thing to know here:
THE ORDER MATTERS! THE ORDER IS EVERYTHING! DO NOT DISTURB THE EXISTING ORDER!
Always add to the end of the read list. So take this example of code and paste it to the end of the WRAPPER_READ list and replace with your tag instead.
BUT, how do you know where the end of the list is? It seems to start and stop a LOT. It sorta does and then again all it's really doing is pausing to process a few things needed after loading a tag's data on occasion.
THE end of the list is where you find this statement:
Code:
WRAPPER_READ_OBJECT_END(wrapper);
So place your tag's variable into the read stream at the very end, just before this. Always at the end. No matter how crazy complex your data model requires, always the end. Ok, there ARE a few exceptions but for now let's not get into that and confuse things.
So now at the end of the read sequence, I've added:
Code:
WRAPPER_READ(wrapper, "CvUnit", &m_iExtraReligiousCombatModifier);
And I'm not getting into HOW all this works. I've allowed it to largely remain mysterious for me and procedural and I'll let a more advanced programmer answer more advanced questions about these streams. But I WILL say that if you ever mess this up you will create the most headache-inducing bugs I have ever had to work on. Audit the order of reads vs the order of writes once more at the end of your process before compiling. It's just so important to get it right.
The next thing you'll find is going to be the write wrapper sequence and where our example is placed there. AGAIN, the sequence is everything and you need to put your tag's write line(s) at the END of the write stream. The write stream is usually cleaner.
So now the end of my write stream looks like:
Code:
WRAPPER_WRITE(wrapper, "CvUnit", m_iExtraGatherHerdCount);
WRAPPER_WRITE(wrapper, "CvUnit", m_iExtraReligiousCombatModifier);
WRAPPER_WRITE_OBJECT_END(wrapper);
}
There is one Cv type that this end is a little harder to find and all the read and write lines are in a conditional nesting within an if{} statement. Be sure to include yours within that nesting as well. I think it was CvPlayer.cpp. Just a warning to stay sharp in passing - every warning has a story behind it
Another Misc application issue for our example tag
vsBarbs shows up in a few applications for checking whether to apply anti-barbarian handicaps or not, which has nothing to do with our example tag nor our tags.
But it does also show up in the section for battlefield promotions, where if the promotion has some NEGATIVE value against barbarians, victorious units (both attackers and defenders are specified in different sections here) shouldn't be able to get this promotion automatically from a battlefield promotion event (free promo when winning a tough fight).
In this section if you want to make it conditionally possible to have a promotion with this tag be potentially handed to the victor, you can specify the tag and the condition of the battle. I'm going to keep this Religious Combat tag completely out of this space but not ban a promo for having it from being awardable in this context either. Thus, while promos with getVSBarbsChange at less than 0 are set to be impossible to earn via battlefield promos, I won't be adding my new tag anywhere in this function's many filters.
Here is the section where it's setting up the positive value of anti-barbarian combat modifiers to be capable of making their promotion applicable to a victorious attacker:
Code:
//TB Combat Mods Begin * anti-barbarian combat mod
if (kPromotion.getVSBarbsChange()>0 &&(pDefender->isHominid()))
{
aAttackerAvailablePromotions.push_back((PromotionTypes)iI);
}
It's saying if our unit attacked a Hominid (non-animal barbarian or neanderthal) and the promotion has some positive anti-barbarian value, then add it to the list of considered possible promotions to be added if we're earning a battlefield promotion here.
Finale
At this point, we've reached the end of the CvUnit.cpp file where all of the instances of our example (VSBarbs) have been found and mimicked where appropriate to do so. Thus, we've completed this... wait no we haven't! I personally still need to program the getReligion() function for the unit which plays into how this tag works!
Ok, so I scroll to the bottom of CvUnitInfo and add my function - though when I go to do so I realize that this is going to take some caching so that the unit only has to infrequently figure out what religion it is - and that takes setting up a few new variables and another function entirely for that. I won't get too in detail as to what I did here - I'll let you ask if you wish to understand deeper
Code:
void CvUnit::defineReligion()
{//call this when a unitcombat that has a religion is processed in and for all units when the state religion is changed.
//Check for dedicated faith by unit type, assign it and let it not be changeable unless the unit type changes
if (!m_bIsReligionLocked)//purely meaning the unit has an overriding religious unitcombat in its base definition (like a missionary, crusader or hellsmouth dog would)
{
if (m_eReligionType == NO_RELIGION)
{
for (int iI = 0; iI < GC.getNumUnitCombatInfos(); iI++)
{
if (m_pUnitInfo->hasUnitCombat((UnitCombatTypes)iI))
{
ReligionTypes eOriginalCombatReligion = GC.getUnitCombatInfo((UnitCombatTypes)iI).getReligion();
if (eOriginalCombatReligion != NO_RELIGION)
{
m_eReligionType = eOriginalCombatReligion;
m_bIsReligionLocked = true;
break;
}
}
}
}
//if not locked by innate type, after changes in unitcombat process function we'll call this function IF the unitcombat has a religion.
//This function is also called if the state religion changes so if we find a unit combat has defined m_eReligionType then we'll not bother with switching to the state religion so check here first
bool bFound = false;
for (int iI = 0; iI < GC.getNumUnitCombatInfos(); iI++)
{
if (isHasUnitCombat((UnitCombatTypes)iI))
{
ReligionTypes eUnitCombatReligion = GC.getUnitCombatInfo((UnitCombatTypes)iI).getReligion();
if (eUnitCombatReligion != NO_RELIGION)
{
m_eReligionType = eUnitCombatReligion; //Let's assume there's only going to be one of these on a unit ever - it only ever comes up if the unit isn't locked with a pre-defined one anyhow
//and unitcombats that assign a religion should be rare to assign unless we are more advanced into the Ideas project where the city will assign its religion type to all units that it produces.
//There could be promos that assign overriding religious types but we'll cross that bridge when we get there.
bFound = true;
break;//thus we stop at the first one we find
}
}
}
if (!bFound)
{
m_eReligionType = GET_PLAYER(getOwner()).getStateReligion();//NO_RELIGION is a perfectly satisfactory answer here.
}
}
//else do nothing - if the religion is locked we're done here.
}
ReligionTypes CvUnit::getReligion() const
{
return m_eReligionType;
}
At this point, you definitely want to compile the finalrelease code again to make sure you haven't pooed the scrooch. You don't have to run the mod, just look for compiler errors so they don't get overwhelming later. You're doing pretty dang good if you haven't messed up any syntax anywhere!