[DLL/C++] Adding new primary tables to the database

whoward69

DLL Minion
Joined
May 30, 2011
Messages
8,699
Location
Near Portsmouth, UK
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
  1. 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.
  2. Knowledge of the Civ 5 database tables and how to update them either via XML or SQL
  3. Successfully built, deployed and loaded the unaltered source as a custom DLL mod
  4. 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>
    ...
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)
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;
}
This is the basic class "cookie cutter" for supporting primary database tables. We need to add our own specific entries as well.

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;
}
(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
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;
and
Code:
#if defined(MOD_API_PLOT_YIELDS)
    int getNumPlotInfos();
    std::vector<CvPlotInfo*>& getPlotInfo();
    CvPlotInfo* getPlotInfo(PlotTypes ePlotNum);
#endif

    int getNumTerrainInfos();
and
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");
and while we're here, we'll validate the table
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)
    }
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
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);
    }
and it will behave as expected.

[END]
 
Please use this thread in the SDK / Lua sub-forum for asking questions about this tutorial, I will then update/expand this thread as necessary.

This is NOT a tutorial on basic C/C++ programming, so please do not ask questions about the syntax, semantics or usage of the C++ language - there are many excellent resources for learning C++ available (most of which can be found in your local bookshop)

Also, please do NOT ask questions about specific issues for your own mods and/or ask for someone else to write your code for you on these threads - start a new thread either in the main C&C forum or the SDK / Lua sub-forum to ask your specific question/request, as that way you'll get specific answers ;) And finally, please note that I do NOT take requests for writing code for others' mods!
 
Top Bottom