Quantcast
Channel: Civilization Fanatics' Forums
Viewing all articles
Browse latest Browse all 12856

Modder's Guide to Utilizing C2C Game Option Edits

$
0
0
Modder's Guide to Utilizing C2C Game Option Edits



This is a project I've been working on for a bit here in the background. After an initial attempt to implement the Combat Mod was met with a blend of extreme approval and disapproval in equal amounts, it became clear that to legitimately implement the Combat Mod, I'd need to unlock the ability to enable Game Options to actually change the values of existing game objects.

By Game Object, I mean any entry in a given class. For example: a particular Unit, Promotion, Civic, Tech, Trait etc...

Up until now, our only ability to conditionally edit an existing game object has been via modules. This in and of itself has been a very powerful tool for the mod team. But it tends to confuse new players to the mod who wish to turn off or turn on specific modules. The MLF methods employed there require forethought to edit before beginning a game, and can be intimidating to do so for those who have not learned any modding skills. And its just kinda a pain so if a modder really wants his edits to game options to be optional but for that option to be easily toggled for players, this is not the BEST answer.

Unfortunately, this modular method has been our ONLY answer... until now.

What the functionality I call Game Option Edits accomplishes is the ability to write, directly into the core info files a given game object, an entry that will completely replace another indicated entry in the SAME file IF an indicated Game Option is in use.

Basically, this means that if we wish to have a different definition for a Trait under a Complex Traits game option, we can. Or perhaps we want a different Scout unit definition to be in use if Raging Barbarians is on... we could do that too. Or perhaps under a new religious option, the religion's Temples should be redefined to include the use of a tag they don't normally use - we can do that too.

Although at the moment this new functionality in our dll only applies to Traits, it should eventually be added to all the most core game classes over the course of the next version or two.


Now, this does not just assist me and my goals to implement portions of the Combat Mod in various Game Options so that nobody is forced to play any particular new way, it also helps us all with other matters that have long been at hand.

For example, an upcoming project that utilizes this method could be changing the Alternative Timeline projects into a Game Option or a set of Game Options. This would make it much nicer for those players who can't understand why we've currently made those on by default while maintaining the approval of those who are quite happy to play with Alternative Timelines. As a game option, they will be very easily determined on or off when setting up the game.

At this point, all the rest of the team will need to know is how to use this system and some of you have expressed a desire to know how it was setup. I may be able to get some feedback here too on little ways to improve the structure as well.

But I'm pretty sure we'll all find that the only processing time impact this method will have is at loadup the game will simply have more content to process in, the usual price to pay for additional content - and it pays it faster this way than it does in a modular format! In game, there would be NO additional processing time added from this method, and those who will understand the coding section here will see why.

Memory requirements may increase some through this method. @Koshling/AIAndy... shouldn't be too bad there should it?


Anyhow, without further introduction, let's discuss:

HOW to utilize Game Option Edits (for the XML modder)

Let me simply show you upfront how a Game Option Edit entry would look in the XML:
Spoiler:
Code:

                <TraitInfo>
                        <Type>TRAIT_OPTIONEDIT_COMPLEX_TRAITS_PHILOSOPHICAL</Type>
                        <Description>TXT_KEY_TRAIT_PHILOSOPHICAL</Description>
                        <ShortDescription>TXT_KEY_TRAIT_PHILOSOPHICAL_SHORT</ShortDescription>
                        <Button>ART/Buttons/Traits/TraitPhilosophical.dds</Button>
                        <ForGameOption>GAMEOPTION_COMPLEX_TRAITS</ForGameOption>
                        <EditedTrait>TRAIT_PHILOSOPHICAL</EditedTrait>
                        <OnGameOption>GAMEOPTION_COMPLEX_TRAITS</OnGameOption>
                        <NotOnGameOption/>
                        <PromotionLine>PROMOTIONLINE_PHILOSOPHICAL</PromotionLine>
                        <iLinePriority>0</iLinePriority>
                        <bNegativeTrait>0</bNegativeTrait>
                        <bImpurePromotions>0</bImpurePromotions>
                        <bImpurePropertyManipulators>0</bImpurePropertyManipulators>
                        <Flavors>
                                <Flavor>
                                        <FlavorType>FLAVOR_PRODUCTION</FlavorType>
                                        <iFlavor>1</iFlavor>
                                </Flavor>
                                <Flavor>
                                        <FlavorType>FLAVOR_GOLD</FlavorType>
                                        <iFlavor>2</iFlavor>
                                </Flavor>
                                <Flavor>
                                        <FlavorType>FLAVOR_RELIGION</FlavorType>
                                        <iFlavor>1</iFlavor>
                                </Flavor>
                                <Flavor>
                                        <FlavorType>FLAVOR_SCIENCE</FlavorType>
                                        <iFlavor>3</iFlavor>
                                </Flavor>
                                <Flavor>
                                        <FlavorType>FLAVOR_CULTURE</FlavorType>
                                        <iFlavor>4</iFlavor>
                                </Flavor>
                                <Flavor>
                                        <FlavorType>FLAVOR_ESPIONAGE</FlavorType>
                                        <iFlavor>1</iFlavor>
                                </Flavor>
                                <Flavor>
                                        <FlavorType>FLAVOR_MILITARY</FlavorType>
                                        <iFlavor>-2</iFlavor>
                                </Flavor>
                        </Flavors>
                        <iHealth>10</iHealth>
                        <iHappiness>0</iHappiness>
                        <iMaxAnarchy>-1</iMaxAnarchy>
                        <iUpkeepModifier>0</iUpkeepModifier>
                        <iLevelExperienceModifier>0</iLevelExperienceModifier>
                        <iGreatPeopleRateModifier>0</iGreatPeopleRateModifier>
                        <iGreatGeneralRateModifier>0</iGreatGeneralRateModifier>
                        <iDomesticGreatGeneralRateModifier>0</iDomesticGreatGeneralRateModifier>
                        <iMaxGlobalBuildingProductionModifier>0</iMaxGlobalBuildingProductionModifier>
                        <iMaxTeamBuildingProductionModifier>0</iMaxTeamBuildingProductionModifier>
                        <iMaxPlayerBuildingProductionModifier>0</iMaxPlayerBuildingProductionModifier>
                        <!-- Revolution Trait Effects Begin -->
                        <iRevIdxLocal>0</iRevIdxLocal>
                        <iRevIdxNational>0</iRevIdxNational>
                        <iRevIdxDistanceModifier>0</iRevIdxDistanceModifier>
                        <iRevIdxHolyCityGood>0</iRevIdxHolyCityGood>
                        <iRevIdxHolyCityBad>0</iRevIdxHolyCityBad>
                        <fRevIdxNationalityMod>0</fRevIdxNationalityMod>
                        <fRevIdxBadReligionMod>0.0</fRevIdxBadReligionMod>
                        <fRevIdxGoodReligionMod>0.0</fRevIdxGoodReligionMod>
                        <bNonStateReligionCommerce>0</bNonStateReligionCommerce>
                        <bUpgradeAnywhere>0</bUpgradeAnywhere>
                        <!-- Revolution Trait Effects End -->
                        <ExtraYieldThresholds/>
                        <BonusHappinessChanges>
                                <BonusHappinessChange>
                                        <BonusType>BONUS_FUR</BonusType>
                                        <iHappinessChange>-1</iHappinessChange>
                                </BonusHappinessChange>
                        </BonusHappinessChanges>
                        <TradeYieldModifiers/>
                        <CommerceChanges/>
                        <CommerceModifiers/>
                        <SpecialistCommerceChanges>
                                <SpecialistCommerceChange>
                                        <SpecialistType>SPECIALIST_SCIENTIST</SpecialistType>
                                        <CommerceChanges>
                                                <iCommerce>0</iCommerce>
                                                <iCommerce>0</iCommerce>
                                                <iCommerce>1</iCommerce>
                                        </CommerceChanges>
                                </SpecialistCommerceChange>       
                        </SpecialistCommerceChanges>
                        <iWarWearinessAccumulationModifier>20</iWarWearinessAccumulationModifier>
                        <iCivicAnarchyTimeModifier>20</iCivicAnarchyTimeModifier>
                </TraitInfo>


This is actually IN the TraitInfo file NOW. If you want to prove it works, go ahead and edit the GameOptionsInfos.xml file to make it possible to use the Complex Traits option I added for eventual use (I currently have it invisible and off by default) and make sure that option is on when you start the game. Take a look at the Philosophical trait... it will be according to the definitions given in that Game Option Edit entry rather than the definitions given in the original Philosophical Trait entry.

So the first thing to really be clear about is: Yes, this is an entry IN the TraitsInfo.xml file.

You'll notice that the <Type> tag is not named the same as the game object we want to edit under the Complex Traits option. Instead, its tagline is given a name under a naming convention: TRAIT_OPTIONEDIT_COMPLEX_TRAITS_PHILOSOPHICAL.

As with any Trait, they <Type> tag begins with TRAIT_ and very well should as sometimes in python or code, this might be an assumed aspect of this label to indicate its class type. Then we add OPTIONEDIT_ to indicate what we're making this entry for. Then we add COMPLEX_TRAITS_ to indicate the Option this will function under (this is not what controls it... just a naming convention to help all of us quickly 'get the point'). Then we indicate PHILOSOPHICAL to denote the trait being edited under the aforementioned option.


Now, although Description through Button tags are present, they are the only tags that do NOT function to replace the edited trait (yet... I'm hoping I can yet figure out how to get that to work so we can redefine the Description text(s) and the Button if needbe.)


Then pretty much any other tag that CAN exist on the original is up for redefinition here. Just keep in mind this redefinition entry must be absolutely complete. Anything you leave out is equivalently left out of the redefined trait stats. Thus, if I were to want to take an original iHealth value of 1 and turn it into 0, I could either define it here as 0 or even just leave it off.

This whole Trait entry completely replaces the Trait that's indicated when the option is ON and leaves the original definition in use whenever the indicated option is OFF.


There are a few new tags to use for setting this up though. You'll notice these tags just under the Button tag:
Spoiler:
Code:

                        <ForGameOption>GAMEOPTION_COMPLEX_TRAITS</ForGameOption>
                        <EditedTrait>TRAIT_PHILOSOPHICAL</EditedTrait>
                        <OnGameOption>GAMEOPTION_COMPLEX_TRAITS</OnGameOption>
                        <NotOnGameOption/>


<ForGameOption> sets the option that will cause this trait to replace whatever trait is indicated in <EditedTrait> whenever that option is ON.

<OnGameOption> is critical to use here as well to make sure this trait doesn't show up independently as a valid trait of its own whenever the option isn't in use. It's also used on its own to restrict any additional trait definitions that aren't intended for use whenever the option isn't on BUT is also not an edit of a previous trait.

Thus if I want to create EXTRA traits for a given option, I'd define them as normal and utilize the OnGameOption tag to make sure those extra traits are only on for that Game Option.

But FOR game option is only part of an Option Edit setup. As is <EditedTrait> (which won't work unless a ForGameOption is specified.)

One can also use the new <NotOnGameOption> tag to indicate that a trait that would normally appear simply does NOT appear if the indicated option is used. Don't set both OnGameOption and NotOnGameOption to the same thing... results would be somewhat unpredictable as it would be completely illogical to do so.


And that's it! It's really that simple. Might not seem like it in a written out explanation but hopefully the example helps! All you need is an option to work with and you can edit any other Trait's details under that option's use. (And soon... so much more!)


How it works in the SDK code
Since there's only a few of us here who even want to see this, I'll put this in a whole spoiler block. The rest may ignore ;)
Spoiler:

The trick to this method has been to manipulate the return on each tag in the traits class. But that required a loop check SO it ALSO caches this result whenever a GAME (not THE game but A game) is initiated or loaded so that the time spent to process out what the modified return should be is spent only once on starting or loading up a game.

Here's a basic example. The rest you can find in CvInfo.cpp under CvTrait:
Code:

int CvTraitInfo::getHealth() const                                                                       
{
        if (isOptionEditsIndexCached() && getOptionEditsIndex() != -1)
        {
                return GC.getTraitInfo((TraitTypes)getOptionEditsIndex()).getHealth();
        }

        if (GC.getGameINLINE().isOption(GAMEOPTION_PURE_TRAITS))
        {
                if (isNegativeTrait() && m_iHealth > 0)
                {
                                return 0;
                }
                else if (!isNegativeTrait() && m_iHealth < 0)
                {
                        return 0;
                }
        }
        return m_iHealth;
}

There's a few things going on here that I should mention. The traits mechanism also has the Pure Traits option to work with here so not all of the tweaks you see here from what the original was:
Code:

int CvTraitInfo::getHealth() const                                                                       
{
        return m_iHealth;
}

applies specifically to this Option Edit method.

Really, its all encapsulated in this simple statement:
Code:

        if (isOptionEditsIndexCached() && getOptionEditsIndex() != -1)
        {
                return GC.getTraitInfo((TraitTypes)getOptionEditsIndex()).getHealth();
        }

See... what happens is at game load you have the following new functions called(in CvGame):
Code:

void CvGame::setOptionEdits()
{
        int iI;

        //Traits
        for (iI = 0; iI < GC.getNumTraitInfos(); iI++)
        {
                GC.getTraitInfo((TraitTypes)iI).setOptionEditsIndex();
        }
}

void CvGame::resetOptionEdits()
{
        int iI;

        //Traits
        for (iI = 0; iI < GC.getNumTraitInfos(); iI++)
        {
                GC.getTraitInfo((TraitTypes)iI).setOptionEditsIndexUncached();
        }
}

You can find them to see if these are being called everywhere they should be but testing has shown it to be completely successful from starting a game, or loading a game, or even loading in a game without the same options as the game currently being played.

It cycles through and sends every trait entry through .setOptionEditsIndex(). This reads (from CvInfos.cpp):
Code:

void CvTraitInfo::setOptionEditsIndex()
{
        int iI;
        for (iI = 0; iI < GC.getNumTraitInfos(); iI++)
        {
                TraitTypes eTOE = ((TraitTypes)iI);
                if ((TraitTypes)GC.getTraitInfo(eTOE).getEditedTrait() == (TraitTypes)GC.getInfoTypeForString(getType()))
                {
                        if (GC.getGameINLINE().isOption(GC.getTraitInfo(eTOE).getForGameOption()))
                        {
                                m_iOptionEditsIndex = iI;
                                m_bOptionEditsIndexCached = true;
                                break;
                        }
                }
        }
}

Honestly, "== (TraitTypes)GC.getInfoTypeForString(getType())" is the part I think could be done better here. I wasn't sure about the best way to refer to 'self' and had struggled with it a bit (though this works I think there's a faster processing method, isn't there?)

Anyhow, this goes through and establishes that for this trait, we've identified an existing qualified Option Edit entry(or have identified that there are none), stored the first one found and ignored any others that may be applicable to that trait(so as to keep from there being any further conflicts).

It also stores, in boolean form, that this trait has a defined option edit and that option edit's info will now be used to replace the values in any given tag call for this trait.


All modders should, however, take note that it is possible to string edits if needbe. Thus I can option edit Philosophical under the Complex Traits game option, then option edit the option edit of Philosophical under the Complex Traits game option under another option entirely. Thus if BOTH options are selected, we get a whole THIRD type of definition that overrides both previous ones! Hopefully we won't find too many need for that but it I figured I should point out that its very possible to do!


Anyhow, I'll leave this lengthy explanation there for now. Any further questions? I'm excited to get working on adding this fairly simply programmed method to other Game Object Classes! But I need the review of the team before I'd feel right moving past this basic prototype. So... comments anyone?

Viewing all articles
Browse latest Browse all 12856

Trending Articles