whoward69
DLL Minion
The aim of this tutorial is to show you how to extend the existing database by creating a new primary table (one with ID and Type columns) and an associated secondary table (one with two or more XyzType columns that contain Xyzs(Type) references) and the required C++ code to load them from the database such that they can be accessed from the standard objects in a similar way to the other database tables. The tutorial code is provided as a starting point for the reader's own requirements, so doing something useful with the tables is left as an exercise for the reader.
This tutorial assumes you are familiar with the techniques presented in the "Replacing hard-coded constants with database values" tutorial, specifically adding your own custom header file and the pre-processor techniques. If you haven't already done so, please read that tutorial first.
This tutorial also assumes that you have
This tutorial builds on the "Adding new tables to the database" tutorial, if you haven't already done so, please read that tutorial first.
In that tutorial was the comment "Only uncomment the next line if you are also adding all the supporting code for the Plots table - which is beyond the scope of this tutorial". Adding the Plots table and it's supporting code is the scope of this tutorial.
Previously, we only added a "bare-bones" Plots table, now we are going to "flesh it out" a bit and add an associated table to create a relationship between the Plots and Yields tables.
Our Plots table has 6 extra columns, what they are used for isn't important for the purpose of this tutorial. We also have a Plot_Yields table, which we could use in the same way as the Terrain_Yields table.
We can now fill our tables (I'm using the text keys from the terrain table to keep the example short)
IMPORTANT NOTE: The order of the Types in the Plots table MUST be the same as the order in the PlotTypes enum.
That's it for the database updates, now for the C++ code. We need a new class to load the data into. The Firaxis naming convention for classes that back database tables is Cv{table_name}Info and we will stick with it. In the Solution Explorer window, under your own filter, add two new files - CvPlotInfo.h and CvPlotInfo.cpp
Our CvPlotInfo class needs to inherit from the core CvBaseInfo class and implement some standard methods.
Add the following to the CvPlotInfo.h file
This is the basic header "cookie cutter" for adding new classes to support primary database tables - for your own tables you can replace every occurance of "plot" with your own table name (preserving case of course!). In addition to the base code, we need to add our own specific entries as well. The CvBaseInfo class handles the ID, Type, Description and Civilopedia columns for us, the PortraitIndex and IconAtlas columns are only needed by the UI (Lua code) and not by C++, so we only need to add entries for the Water and Impassable boolean columns. We need two data members to store the values and associated accessor methods.
We now need the implementation for the class, add the following to the CvPlotInfo.cpp file
This is the basic class "cookie cutter" for supporting primary database tables. We need to add our own specific entries as well.
(Don't forget/omit the : in the constructor!)
As mentioned in the "Adding new columns to the database" tutorial, there are also GetInt(), GetFloat() and GetText() methods on the kResults object depending on what the column stores.
We now have our C++ class to back our database table, so we need to add code to load it when the other tables are loaded. But first we must add the header to the pch file so it's accessible to all other classes. In the CvGameCoreDLLPCH.h file add
To load the table we need to add a PrefetchCollection() call to CvDllDatabaseUtility.cpp, but that requires the backing class to be accessible as part of the GC global object, so we have some more cookie-cutting to do first.
In CvGlobals.h, make the class available and declare the required data members and accessors by adding the following
and
and
and in CvGlobals.h add the implementations
and the required tear-down code
We can (finally) add the line to load the database table into the C++ object, in CvDllDatabaseUtility.cpp add
and while we're here, we'll validate the table
We can now write code such as
and it will behave as expected.
Adding a secondary table to the class has been covered in the "Adding new tables to the database" tutorial, the complete CvPlotInfo.h/cpp files are given here for completeness.
CvPlotInfo.h
CvPlotInfo.cpp
We can now write code (in CvPlot.cpp) such as
and it will behave as expected.
[END]
This tutorial assumes you are familiar with the techniques presented in the "Replacing hard-coded constants with database values" tutorial, specifically adding your own custom header file and the pre-processor techniques. If you haven't already done so, please read that tutorial first.
This tutorial also assumes that you have
- More than basic knowledge of C++ programming and an intermediate understanding of object-orientated programming, specifically that you can already write C++ to create a new class.
- Knowledge of the Civ 5 database tables and how to update them either via XML or SQL
- Successfully built, deployed and loaded the unaltered source as a custom DLL mod
- Created your own custom header file and added it to the CvGameCoreDLLPCH.h file
This tutorial builds on the "Adding new tables to the database" tutorial, if you haven't already done so, please read that tutorial first.
In that tutorial was the comment "Only uncomment the next line if you are also adding all the supporting code for the Plots table - which is beyond the scope of this tutorial". Adding the Plots table and it's supporting code is the scope of this tutorial.
Previously, we only added a "bare-bones" Plots table, now we are going to "flesh it out" a bit and add an associated table to create a relationship between the Plots and Yields tables.
Code:
<?xml version="1.0" encoding="utf-8"?>
<GameData>
<Table name="Plots">
<Column name="ID" type="integer" primarykey="true" autoincrement="true"/>
<Column name="Type" type="text" notnull="true" unique="true"/>
<Column name="Description" type="text" reference="Language_en_US(Tag)" />
<Column name="Civilopedia" type="text" reference="Language_en_US(Tag)"/>
<Column name="Water" type="boolean" default="false"/>
<Column name="Impassable" type="boolean" default="false"/>
<Column name="PortraitIndex" type="integer" default="-1"/>
<Column name="IconAtlas" type="text" default="NULL" reference="IconTextureAtlases(Atlas)"/>
</Table>
<Table name="Plot_Yields">
<Column name="PlotType" type="text" reference="Plots(Type)"/>
<Column name="YieldType" type="text" reference="Yields(Type)"/>
<Column name="Yield" type="integer" default="0"/>
</Table>
...
We can now fill our tables (I'm using the text keys from the terrain table to keep the example short)
Code:
...
<Plots>
<Row>
<ID>0</ID>
<Type>PLOT_MOUNTAIN</Type>
<Description>TXT_KEY_TERRAIN_MOUNTAIN</Description>
<Civilopedia>TXT_KEY_CIV5_SPECIALTERRAIN_MOUNTAINS_TEXT</Civilopedia>
<Impassable>true</Impassable>
<PortraitIndex>9</PortraitIndex>
<IconAtlas>TERRAIN_ATLAS</IconAtlas>
</Row>
<Row>
<Type>PLOT_HILLS</Type>
<Description>TXT_KEY_TERRAIN_HILL</Description>
<Civilopedia>TXT_KEY_CIV5_SPECIALTERRAIN_HILLS_TEXT</Civilopedia>
<PortraitIndex>5</PortraitIndex>
<IconAtlas>TERRAIN_ATLAS</IconAtlas>
</Row>
<Row>
<Type>PLOT_LAND</Type>
<Description>TXT_KEY_TERRAIN_GRASS</Description>
<Civilopedia>TXT_KEY_CIV5_TERRAIN_GRASSLAND_TEXT</Civilopedia>
<PortraitIndex>4</PortraitIndex>
<IconAtlas>TERRAIN_ATLAS</IconAtlas>
</Row>
<Row>
<Type>PLOT_OCEAN</Type>
<Description>TXT_KEY_TERRAIN_OCEAN</Description>
<Civilopedia>TXT_KEY_CIV5_TERRAIN_OCEAN_TEXT</Civilopedia>
<Water>true</Water>
<PortraitIndex>11</PortraitIndex>
<IconAtlas>TERRAIN_ATLAS</IconAtlas>
</Row>
</Plots>
<Plot_Yields>
<Row>
<PlotType>PLOT_MOUNTAIN</PlotType>
<YieldType>YIELD_GOLD</YieldType>
<Yield>0</Yield>
</Row>
</Plot_Yields>
</GameData>
IMPORTANT NOTE: The order of the Types in the Plots table MUST be the same as the order in the PlotTypes enum.
That's it for the database updates, now for the C++ code. We need a new class to load the data into. The Firaxis naming convention for classes that back database tables is Cv{table_name}Info and we will stick with it. In the Solution Explorer window, under your own filter, add two new files - CvPlotInfo.h and CvPlotInfo.cpp
Our CvPlotInfo class needs to inherit from the core CvBaseInfo class and implement some standard methods.
Add the following to the CvPlotInfo.h file
Code:
#pragma once
#ifndef CV_PLOT_INFO_H
#define CV_PLOT_INFO_H
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// class : CvPlotInfo
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class CvPlotInfo : public CvBaseInfo
{
public:
CvPlotInfo();
virtual ~CvPlotInfo();
// Arrays
// Other
virtual bool CacheResults(Database::Results& kResults, CvDatabaseUtility& kUtility);
protected:
// Arrays
private:
CvPlotInfo(const CvPlotInfo&);
CvPlotInfo& operator=(const CvPlotInfo&);
};
#endif
This is the basic header "cookie cutter" for adding new classes to support primary database tables - for your own tables you can replace every occurance of "plot" with your own table name (preserving case of course!). In addition to the base code, we need to add our own specific entries as well. The CvBaseInfo class handles the ID, Type, Description and Civilopedia columns for us, the PortraitIndex and IconAtlas columns are only needed by the UI (Lua code) and not by C++, so we only need to add entries for the Water and Impassable boolean columns. We need two data members to store the values and associated accessor methods.
Code:
virtual ~CvPlotInfo();
[B]bool isWater() const;[/B]
[B]bool isImpassable() const;[/B]
// Arrays
...
protected:
[B]bool m_bWater;[/B]
[B]bool m_bImpassable;[/B]
// Arrays
We now need the implementation for the class, add the following to the CvPlotInfo.cpp file
Code:
#include "CvGameCoreDLLPCH.h"
#include "CvPlotInfo.h"
// must be included after all other headers
#include "LintFree.h"
CvPlotInfo::CvPlotInfo()
{
}
CvPlotInfo::~CvPlotInfo()
{
}
bool CvPlotInfo::CacheResults(Database::Results& kResults, CvDatabaseUtility& kUtility)
{
if(!CvBaseInfo::CacheResults(kResults, kUtility))
return false;
//Arrays
return true;
}
Code:
#include "CvGameCoreDLLPCH.h"
#include "CvPlotInfo.h"
// must be included after all other headers
#include "LintFree.h"
#if defined(MOD_API_PLOT_YIELDS)
CvPlotInfo::CvPlotInfo() [B]:
m_bWater(false),
m_bImpassable(false)[/B]
{
}
CvPlotInfo::~CvPlotInfo()
{
}
[B]bool CvPlotInfo::isWater() const
{
return m_bWater;
}[/B]
[B]bool CvPlotInfo::isImpassable() const
{
return m_bImpassable;
}[/B]
bool CvPlotInfo::CacheResults(Database::Results& kResults, CvDatabaseUtility& kUtility)
{
if(!CvBaseInfo::CacheResults(kResults, kUtility))
return false;
[B]m_bWater = kResults.GetBool("Water");[/B]
[B]m_bImpassable = kResults.GetBool("Impassable");[/B]
//Arrays
return true;
}
As mentioned in the "Adding new columns to the database" tutorial, there are also GetInt(), GetFloat() and GetText() methods on the kResults object depending on what the column stores.
We now have our C++ class to back our database table, so we need to add code to load it when the other tables are loaded. But first we must add the header to the pch file so it's accessible to all other classes. In the CvGameCoreDLLPCH.h file add
Code:
#if defined(MOD_API_PLOT_YIELDS)
#include "CvPlotInfo.h"
#endif
#include "CvInfos.h"
To load the table we need to add a PrefetchCollection() call to CvDllDatabaseUtility.cpp, but that requires the backing class to be accessible as part of the GC global object, so we have some more cookie-cutting to do first.
In CvGlobals.h, make the class available and declare the required data members and accessors by adding the following
Code:
#if defined(MOD_API_PLOT_YIELDS)
class CvPlotInfo;
#endif
class CvTerrainInfo;
Code:
#if defined(MOD_API_PLOT_YIELDS)
int getNumPlotInfos();
std::vector<CvPlotInfo*>& getPlotInfo();
CvPlotInfo* getPlotInfo(PlotTypes ePlotNum);
#endif
int getNumTerrainInfos();
Code:
#if defined(MOD_API_PLOT_YIELDS)
std::vector<CvPlotInfo*> m_paPlotInfo;
#endif
std::vector<CvTerrainInfo*> m_paTerrainInfo;
and in CvGlobals.h add the implementations
Code:
#if defined(MOD_API_PLOT_YIELDS)
int CvGlobals::getNumPlotInfos()
{
return (int)m_paPlotInfo.size();
}
std::vector<CvPlotInfo*>& CvGlobals::getPlotInfo()
{
return m_paPlotInfo;
}
CvPlotInfo* CvGlobals::getPlotInfo(PlotTypes ePlotNum)
{
CvAssert(ePlotNum > -1);
CvAssert(ePlotNum < GC.getNumPlotInfos());
if(ePlotNum > -1 && ePlotNum < (int)m_paPlotInfo.size())
return m_paPlotInfo[ePlotNum];
else
return NULL;
}
#endif
int CvGlobals::getNumTerrainInfos()
...
and the required tear-down code
Code:
#if defined(MOD_API_PLOT_YIELDS)
deleteInfoArray(m_paPlotInfo);
#endif
deleteInfoArray(m_paTerrainInfo);
We can (finally) add the line to load the database table into the C++ object, in CvDllDatabaseUtility.cpp add
Code:
#if defined(MOD_API_PLOT_YIELDS)
PrefetchCollection(GC.getPlotInfo(), "Plots");
#endif
PrefetchCollection(GC.getTerrainInfo(), "Terrains");
Code:
#if defined(MOD_API_PLOT_YIELDS)
ValidateVectorSize(getNumPlotInfos);
#endif
ValidateVectorSize(getNumTerrainInfos);
We can now write code such as
Code:
PlotTypes ePlot = (PlotTypes) GC.getInfoTypeForString("PLOT_HILLS", true);
CvAssertMsg(ePlot != PLOT_HILLS, "Plots are in the wrong order, getInfoTypeForString('PLOT_HILLS') is not equal to PlotTypes.PLOT_HILLS");
CvPlotInfo* pkPlotInfo = GC.getPlotInfo(ePlot);
if (pkPlotInfo && !pkPlotInfo->isImpassable())
{
// Plot is passable (ie not a mountain)
}
Adding a secondary table to the class has been covered in the "Adding new tables to the database" tutorial, the complete CvPlotInfo.h/cpp files are given here for completeness.
CvPlotInfo.h
Code:
#pragma once
#ifndef CV_PLOT_INFO_H
#define CV_PLOT_INFO_H
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// class : CvPlotInfo
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class CvPlotInfo : public CvBaseInfo
{
public:
CvPlotInfo();
virtual ~CvPlotInfo();
bool isWater() const;
bool isImpassable() const;
// Arrays
int getYield(int i) const;
// Other
virtual bool CacheResults(Database::Results& kResults, CvDatabaseUtility& kUtility);
protected:
bool m_bWater;
bool m_bImpassable;
// Arrays
int* m_piYields;
private:
CvPlotInfo(const CvPlotInfo&);
CvPlotInfo& operator=(const CvPlotInfo&);
};
#endif
CvPlotInfo.cpp
Code:
#include "CvGameCoreDLLPCH.h"
#include "CvPlotInfo.h"
// must be included after all other headers
#include "LintFree.h"
CvPlotInfo::CvPlotInfo() :
m_bWater(false),
m_bImpassable(false),
m_piYields(NULL)
{
}
CvPlotInfo::~CvPlotInfo()
{
SAFE_DELETE_ARRAY(m_piYields);
}
bool CvPlotInfo::isWater() const
{
return m_bWater;
}
bool CvPlotInfo::isImpassable() const
{
return m_bImpassable;
}
int CvPlotInfo::getYield(int i) const
{
CvAssertMsg(i < NUM_YIELD_TYPES, "Index out of bounds");
CvAssertMsg(i > -1, "Index out of bounds");
return m_piYields ? m_piYields[i] : -1;
}
bool CvPlotInfo::CacheResults(Database::Results& kResults, CvDatabaseUtility& kUtility)
{
if(!CvBaseInfo::CacheResults(kResults, kUtility))
return false;
m_bWater = kResults.GetBool("Water");
m_bImpassable = kResults.GetBool("Impassable");
//Arrays
const char* szPlotType = GetType();
kUtility.SetYields(m_piYields, "Plot_Yields", "PlotType", szPlotType);
return true;
}
We can now write code (in CvPlot.cpp) such as
Code:
CvPlotInfo* pkPlotInfo = GC.getPlotInfo(getPlotType());
if (pkPlotInfo)
{
int iExtraYield = pkPlotInfo->getYield(YIELD_GOLD);
}
[END]