[DLL Tutorial] Adding new XML Tags/Tables to work through the DLL

Putmalk

Deity
Joined
Sep 26, 2010
Messages
2,652
Location
New York
Note: DLL modding requires a working knowledge of C++.

Follow the steps here to download, install, and get a working project of the DLL up.

This tutorial is an expansion of the XML to Lua tutorial by Pazyryk.

This tutorial is going to create a new Column to the Units table called "Mana".

Create a new column using SQL, like follows:

Code:
ALTER TABLE Units ADD COLUMN 'Mana' integer default 0;

Now we've added the Mana tag to the Units table, meaning we can apply it anywhere. For example:

Code:
<Units>
   <Update>
       <Where Type="UNIT_WARRIOR"/>
       <Set Mana="100"/>
   </Update>
   <Update>
       <Where Type="UNIT_ARCHER"/>
       <Set Mana="47"/>
   </Update>
</Units>

Nothing new yet, right? Well, now, it's time to open up the source code and get your tag recognized by the game core.

In order to do this step you must identify which header file contains the data stored for your source. A quick list from my experiences in the DLL:

Code:
Units - CvUnitClasses.h
Buildings - CvBuildingClasses.h
Policies - CvPolicyClasses.h
Beliefs - CvBeliefClasses.h
Religion - CvReligionClasses.h
Resources - CvInfos.h
Eras - CvInfos.h

Obviously you may have to do some digging to find where you need to go!

Either way, let's open up CvUnitClasses.h because that's where the game handles loading our database into the game.

Inside CvUnitClasses.h, navigate to the CvUnitEntry class. You will see a great amount of accessor functions (GetRange(), for example) and defined variables (m_iRange, for example). Let's create a new variable, so it looks like follows:

Spoiler :

qLw49QU.png


Please note the variable is named "m_iMana" because m_ stands for "member" and i stands for "integer", which helps you quickly identify the variable's purpose just by its name.
note: the variable type will depend on what column you're loading in. If it's an integer, you'll declare an int, if it's a boolean, you'll declare a bool.

Also create a new member accessor function.

Spoiler :

nGPVpxU.png


note: Your function return type must match the integer type. If it's an integer, you'll return an int, if it's a boolean, you'll return a bool.

Warning: Please remember to create a comment differentiating your code from the Firaxis code! This stay may save hours of confusion in the future!

After that, we are done in the header and can now work on the UnitClasses.cpp file. Locate the CvUnitEntry section of the file (Visual Studio allows for quick navigation via the dropdowns below the file names). Navigate to the constructor, which shares the name of your class, so it looks like follows:
Spoiler :

jFGTisw.png


Next we must load our variable a value from the XML tables. Head to the CacheResults() function and create a new entry into the function, keeping in mind that you must match the name inside the parentheses with the name of your column in the XML.
Spoiler :

TLtlclV.png



Finally, we must define our function that we created in the header file, GetMana(). The code to this is follows, and the format will be the same no matter what variable you choose:

Code:
/// Unit's Mana
int CvUnitEntry::GetMana() const
{
	return m_iMana;
}

We are now done with UnitClasses.h. Now, for most other things, you would be done around here. Now you would be able to access your column's data from anywhere in the source code, for you to use at will. But, Units are special, and it's why I chose to use them. UnitClasses.h handles all the unit properties: movement, range, etc. but it doesn't handle their behavior. This is handled in the CvUnit object. Thus, open up CvUnit.h, we have to make some additions.

I assume you understand the basics of navigating from function to function from now on, so I won't be using anymore images.

In the CvUnit.h file, navigate to the CvUnit class declaration, and create a new member function
Code:
int GetMana() const;
The function will link back into the object's class association and pull their data.

That's it, now we enter the CvUnit.cpp file and define our function:
Code:
//	--------------------------------------------------------------------------------
int CvUnit::GetMana() const
{
	VALIDATE_OBJECT
	return (m_pUnitInfo->GetMana());
}

Whoa, whoa? What's going on here? What's this VALIDATE_OBJECT and m_pUnitInfo nonsense? Well, VALIDATE_OBJECT, for the purposes of your needs, will be useless. It's tied to asserts, which is useful for debugging, but not something you need to concern yourself with. That being said, for safety include it. m_pUnitInfo, however, is much more important. It's a pointer to your unit info, or, the information defined in the CvUnitEntry class (what we modified in CvUnitClasses). Since UnitEntry stores all of the data about our unit (from the XML tables), we use it to help us out. This function thus just returns the value of our mana. Once we have this function, anywhere in our CvUnit.cpp we can call GetMana() and it will give us the correct value.

That's it for loading a column into the C++ database! Probably a lot more complicated than the Lua, but much more robust! The possibilities with the C++ are endless!

For testing purposes, I will create a hook into Lua to call our new function. Open up CvLuaUnit.h and CvLuaUnit.cpp.

In the CvLuaUnit.h header file, create a new function:
Code:
static int lGetMana(lua_State* L);
And in the CvLuaUnit.cpp file, you must define that new function:
Code:
//------------------------------------------------------------------------------
//int GetMana();
int CvLuaUnit::lGetMana(lua_State* L)
{
	CvUnit* pkUnit = GetInstance(L);
	const int iResult = pkUnit->GetMana();

	lua_pushinteger(L, iResult);
	return 1;
}
If you were returning a boolean function, the main lua function lGetMana would still return an integer, but the functions inside of it would change (int iResult would become bool bResult and lua_pushinteger would become lua_pushboolean).

And "push" it into Lua, via the PushMethods() function in CvLuaUnit.cpp
Code:
Method(GetMana);	// putmalk: tutorial

Aaannnndd we're done! Hopefully that wasn't so hard. ;)

=== Testing In-Game ===
Because we hooked our function into Lua, we can test it in game.

Load your mod and remember to set VFS to True for your DLL. Open up the FireTuner and load the game up. Create an archer, and select your Warrior. Enter the following commands into the tuner:

Code:
unit = UI.GetHeadSelectedUnit();
unit:GetMana();

The game should print out "100".

Select your archer.
Code:
unit = UI.GetHeadSelectedUnit();
unit:GetMana();

The game should print out "47".

Spoiler :

HOcqQ9h.jpg



Congratulations! You've now loaded your new column into the game via the DLL! Now mess around! The possibilities are endless!
 
Part 2: Creating a new table and loading

Reserved. Very difficult!
 
Great work :D!

I knew this stuff from Civ4 but wanted to check my knowledge was still applicable. How does loading and saving work? in the CvUnitInfos in Civ4 you had to save the data, does that no apply to Civ5, so just to CvUnit?

any ETA on the second part? Might come in handy!
 
Back
Top Bottom