Step 2: SDK changes
Now the tricky part.
2.1) CvEnums.h
I add a new enum in order to have internal names for the options. Othewise we would have to cope with numbers which would not be very useful.
Code:
//Mod Chooser Start
enum DllExport ModOptionTypes // Exposed to Python
{
NO_MOD_OPTION = -1,
MODOPTION_A,
MODOPTION_B,
MODOPTION_C,
NUM_MODOPTION_TYPES
};
//Mod Chooser End
Recognize 3 things:
1) The enum is exposed to python. We will need this later.
2) The elements have the same names as in the XML files.
3) NUM_MODOPTION_TYPES has the number of options. This is useful for loops and arrays.
2.2) CvGame.h
We need a variable which holds the state of our new options.
In "class CvGame" in the section "protected" I added an array m_abModOptions:
Code:
...
int m_aiTeamScore[MAX_TEAMS]; // Ordered by team ID...
//Mod Chooser Start
bool m_abModOptions[NUM_MODOPTION_TYPES];
//Mod Chooser End
int* m_paiUnitCreatedCount;
...
Here we see the first use of NUM_MODOPTION_TYPES. The array is declared with a size referring to the number of our options.
Also, in CvGame.h in the public section I declare one method to get the state of a specific option and a method to set the state.
Code:
...
DllExport void setOption(GameOptionTypes eIndex, bool bEnabled);
//Mod Chooser Start
DllExport bool isModOption(ModOptionTypes eIndex) const; // Exposed to Python
DllExport void setModOption(ModOptionTypes eIndex, bool bEnabled); // Exposed to Python
//Mod Chooser End
DllExport bool isMPOption(MultiplayerOptionTypes eIndex) const; // Exposed to Python
...
Both methods are exposed to python. We will need this later.
The methods are defined in CvGame.cpp. I will explain this file later.
2.3) CvXMLLoadUtilitySet.cpp
Bevor we can fill our array we have to read the XML files. We do this in CvXMLLoadUtility::LoadPostMenuGlobals()
Code:
...
// load the new FXml variable with the Civ4TutorialInfos.xml file
bLoaded = LoadCivXml(m_pFXml, "Misc/Civ4TutorialInfos.xml");
if (!bLoaded)
{
char szMessage[1024];
sprintf( szMessage, "LoadXML call failed for Misc/Civ4TutorialInfos.xml. \n Current XML file is: %s", GC.getCurrentXMLFile().GetCString());
gDLL->MessageBox(szMessage, "XML Load Error");
}
if (bLoaded && Validate())
{
SetGlobalClassInfo(&GC.getTutorialInfo(), "Civ4TutorialInfos/TutorialInfo", &GC.getNumTutorialInfos());
}
//Mod Chooser Start
bLoaded = LoadCivXml(m_pFXml, "GameInfo/CIV4ModOptionInfos.xml");
if (!bLoaded)
{
char szMessage[1024];
sprintf( szMessage, "LoadXML call failed for GameInfo/CIV4ModOptionInfos.xml. \n Current XML file is: %s", GC.getCurrentXMLFile().GetCString());
gDLL->MessageBox(szMessage, "XML Load Error");
}
if (bLoaded && Validate())
{
int iEnumVal = NUM_MODOPTION_TYPES;
SetGlobalClassInfo(&GC.getModOptionInfo(), "Civ4ModOptionInfos/ModOptionInfos/ModOptionInfo", &iEnumVal, true);
}
//Mod Chooser End
SAFE_DELETE_ARRAY(pszDefaultUnits);
...
Now we have our values in the memory, but we need to have a function to get them.
2.4) CvInfos.h
Let's make a new class for the options. Each instance of the class holds the set of all values of one option. In our exmaple it's just the default value.
Code:
...
//Mod Chooser Start
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//
// class : CvModOptionInfo
//
//
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class CvModOptionInfo :
public CvInfoBase
{
public:
DllExport CvModOptionInfo();
DllExport virtual ~CvModOptionInfo();
DllExport bool getDefault() const;
DllExport bool read(CvXMLLoadUtility* pXML);
private:
bool m_bDefault;
};
...
This class is derived from CvInfoBase.
The definition of the class can be found in CvInfos.cpp.
2.5) CvInfos.cpp
The declared methods have to be defined.
Code:
...
//Mod Chooser Start
//////////////////////////////////////////////////////////////////////////
//
// CvModOptionInfo
// Mod options and their default values
//
//
CvModOptionInfo::CvModOptionInfo() :
m_bDefault(false)
{
}
CvModOptionInfo::~CvModOptionInfo()
{
}
bool CvModOptionInfo::getDefault() const
{
return m_bDefault;
}
bool CvModOptionInfo::read(CvXMLLoadUtility* pXML)
{
if (!CvInfoBase::read(pXML))
{
return false;
}
pXML->GetChildXmlValByName(&m_bDefault, "bDefault");
return true;
}
//Mod Chooser End
...
The method "read" gets the default value from the XML file and the method getDefault() returns this value.
2.6) CvGlobals.h
After defining the class CvModOptionInfo we need an instance of this class. I do that in CvGlobals.h
First, we need to make our class known to the file:
Code:
...
class CvGameOptionInfo;
//Mod Chooser Start
class CvModOptionInfo;
//Mod Chooser End
class CvMPOptionInfo;
This is beacuse the file containing the class hasn't been read yet. We just say: there is a class called CvModOptionInfo.
And now, we declare the instance of CvModOptionInfo
Code:
... int m_iNumGameOptionInfos;
//Mod Chooser Start
CvModOptionInfo* m_paModOptionInfos;
int m_iNumModOptionInfos;
//Mod Chooser End
CvMPOptionInfo* m_paMPOptionInfos;
...
I do this in the protected section of CvGlobals.
Beside the instance of CvModOptionInfo I also declare a variable for the number of options.
Now, we have to expose those variables in the public section of CvGlobals to the world.
Code:
...
DllExport CvGameOptionInfo& getGameOptionInfo(GameOptionTypes eGameOptionNum);
//Mod Chooser Start
DllExport int& getNumModOptionInfos();
CvModOptionInfo*& getModOptionInfo();
DllExport CvModOptionInfo& getModOptionInfo(ModOptionTypes eModOptionNum);
//Mod Chooser End
DllExport int& getNumMPOptionInfos();
...
2.7) CvGlobals.cpp
We have to define the new methods of section 2.6
Code:
...
int& CvGlobals::getNumGameOptionInfos()
{
m_iNumGameOptionInfos = NUM_GAMEOPTION_TYPES;
return m_iNumGameOptionInfos;
}
//Mod Chooser Start
int& CvGlobals::getNumModOptionInfos()
{
m_iNumModOptionInfos = NUM_MODOPTION_TYPES;
return m_iNumModOptionInfos;
}
CvModOptionInfo*& CvGlobals::getModOptionInfo()
{
return m_paModOptionInfos;
}
CvModOptionInfo& CvGlobals::getModOptionInfo(ModOptionTypes eModOptionNum)
{
FAssert(eModOptionNum >= 0);
FAssert(eModOptionNum < GC.getNumModOptionInfos());
return m_paModOptionInfos[eModOptionNum];
}
//Mod Chooser End
CvGameOptionInfo*& CvGlobals::getGameOptionInfo()
{
return m_paGameOptionInfos;
}
...
getNumModOptionInfos() just returns NUM_MODOPTION_TYPES from our enum.
getModOptionInfo() returns the whole array m_paModOptionInfos with all CvModOptionInfo objects.
getModOptionInfo(ModOptionTypes eModOptionNum) returns one specific object of the type CvModOptionInfo.
2.8) CvGame.cpp
Now we can use all our new functions.
In CvGame::init(HandicapTypes eHandicap) we get the default values from the XML file to initialize the array m_abModOptions
Code:
...
if (isOption(GAMEOPTION_LOCK_MODS))
{
if (isGameMultiPlayer())
{
setOption(GAMEOPTION_LOCK_MODS, false);
}
else
{
static const int iPasswordSize = 8;
char szRandomPassword[iPasswordSize];
for (int i = 0; i < iPasswordSize-1; i++)
{
szRandomPassword[i] = getSorenRandNum(128, NULL);
}
szRandomPassword[iPasswordSize-1] = 0;
GC.getInitCore().setAdminPassword(szRandomPassword);
}
}
//Mod Chooser Start
for (int i = 0; i < NUM_MODOPTION_TYPES; ++i)
{
m_abModOptions[(ModOptionTypes)i] = GC.getModOptionInfo((ModOptionTypes)i).getDefault();
}
//Mod Chooser End
...
The command GC.getModOptionInfo((ModOptionTypes)i).getDefault() calls the method getModOptionInfo in CvGlobals.cpp which returns the
CvModOptionInfo object. On this object the method getDefault() is called which returns the value from the XML file.
Finally, we now have filled the array declared in CvGame.h in section 2.2
In CvGame::reset(HandicapTypes eHandicap, bool bConstructorCall) I reset all values to false:
Code:
...
for (iI = 0; iI < MAX_TEAMS; iI++)
{
m_aiRankTeam[iI] = 0;
m_aiTeamRank[iI] = 0;
m_aiTeamScore[iI] = 0;
}
//Mod Chooser Start
for (iI = 0; iI < NUM_MODOPTION_TYPES; ++iI)
{
m_abModOptions[iI] = false;
}
//Mod Chooser End
...
It's just for resetting the game.
There are two more methods in CvGame to be declared:
Code:
...
void CvGame::setOption(GameOptionTypes eIndex, bool bEnabled)
{
GC.getInitCore().setOption(eIndex, bEnabled);
}
//Mod Chooser Start
bool CvGame::isModOption(ModOptionTypes eIndex) const
{
FASSERT_BOUNDS(0, NUM_MODOPTION_TYPES, eIndex, "CvGame::isModOption");
return m_abModOptions[eIndex];
}
void CvGame::setModOption(ModOptionTypes eIndex, bool bEnabled)
{
FASSERT_BOUNDS(0, NUM_MODOPTION_TYPES, eIndex, "CvGame::setModOption");
m_abModOptions[eIndex] = bEnabled;
}
//Mod Chooser End
...
These methods are what we will use later to determine wether an option is true or false and to set an option.
Two more changes to deal with savegames:
In CvGame::read(FDataStreamBase* pStream):
Code:
...
if (isOption(GAMEOPTION_NEW_RANDOM_SEED))
{
if (!isNetworkMultiPlayer())
{
m_sorenRand.reseed(timeGetTime());
}
}
//Mod Chooser Start
pStream->Read(NUM_MODOPTION_TYPES, m_abModOptions);
//Mod Chooser End
}
...
This reads the variable m_abModOptions from savegames.
And in CvGame::write(FDataStreamBase* pStream):
Code:
...
pStream->Write(m_iNumSessions);
//Mod Chooser Start
pStream->Write(NUM_MODOPTION_TYPES, m_abModOptions);
//Mod Chooser End
}
...
This writes the variable to savegames.
2.9) CvXMLLoadUtilityInit.cpp
One more change here in CvXMLLoadUtility::CleanUpGlobalVariables()
Code:
...
SAFE_DELETE_ARRAY(GC.getGameOptionInfo());
//Mod Chooser Start
SAFE_DELETE_ARRAY(GC.getModOptionInfo());
//Mod Chooser End
SAFE_DELETE_ARRAY(GC.getMPOptionInfo());
...
The next sections show how to expose all we have done to python.
2.10) CyEnumsInterface.cpp
First, I want to have my names of the enum to be exposed to python. I do that in CyEnumsPythonInterface()
Code:
...
//Mod Chooser Start
python::enum_<ModOptionTypes>("ModOptionTypes")
.value("NO_MOD_OPTION", NO_MOD_OPTION)
.value("MODOPTION_A", MODOPTION_A)
.value("MODOPTION_B", MODOPTION_B)
.value("MODOPTION_C", MODOPTION_C)
.value("NUM_MODOPTION_TYPES", NUM_MODOPTION_TYPES)
;
//Mod Chooser Start
}
...
Now we can use ModOptionTypes.MODOPTION_X in python.
2.11) CyGame.h
I define two method to read and write the options. I do this in the public section of CyGame.
Code:
...
bool isOption(int /*GameOptionTypes*/ eIndex);
//Mod Chooser Start
bool isModOption(int /*ModOptionTypes*/ eIndex);
void setModOption(int /*ModOptionTypes*/ eIndex, bool bEnabled);
//Mod Chooser End
bool isMPOption(int /*MultiplayerOptionTypes*/ eIndex);
...
2.12) CyGame.cpp
The declared methods have to be defined.
Code:
...
bool CyGame::isOption(int /*GameOptionTypes*/ eIndex)
{
return m_pGame ? m_pGame->isOption((GameOptionTypes)eIndex) : -1;
}
//Mod Chooser Start
bool CyGame::isModOption(int /*ModOptionTypes*/ eIndex)
{
return m_pGame ? m_pGame->isModOption((ModOptionTypes)eIndex) : -1;
}
void CyGame::setModOption(int /*ModOptionTypes*/ eIndex, bool bEnabled)
{
m_pGame->setModOption((ModOptionTypes)eIndex, bEnabled);
}
//Mod Chooser End
bool CyGame::isMPOption(int /*MultiplayerOptionTypes*/ eIndex)
{
return m_pGame ? m_pGame->isMPOption((MultiplayerOptionTypes)eIndex) : -1;
}
...
isModOption(int /*ModOptionTypes*/ eIndex) calls the method isModOption of CvGame and returns the result.
setModOption(int /*ModOptionTypes*/ eIndex, bool bEnabled) calls setModOption of CvGame and sets the specific option to the value given by bEnabled.
2.13) CyGameInterface.cpp
The two methods of the former section have to be exposed to python. In CyGamePythonInterface() I added:
Code:
...
.def("isOption", &CyGame::isOption, "bool (eIndex) - returns whether Game Option is valid")
//Mod Chooser Start
.def("isModOption", &CyGame::isModOption, "bool (eIndex) - returns whether Mod Option is valid")
.def("setModOption", &CyGame::setModOption, "(eIndex, bEnabled) - sets Mod Option")
//Mod Chooser End
.def("isMPOption", &CyGame::isMPOption, "bool (eIndex) - returns whether MP Option is valid")
...
Now, the methods are usable in python.
2.14) CyGlobalContext.h
Beside the state of an option we also need the name and the description from the XML file. In the public section ov CyGlobalContext I added:
Code:
...
CvInfoBase* getGameOptionInfo(int i) const;
//Mod Chooser Start
CvInfoBase* getModOptionInfo(int i) const;
//Mod Chooser End
CvInfoBase* getMPOptionInfo(int i) const;
...
And also for the number of options:
Code:
...
int getNumGameOptionInfos() const { return GC.getNumGameOptionInfos(); }
//Mod Chooser Start
int getNumModOptionInfos() const { return GC.getNumModOptionInfos(); }
//Mod Chooser End
int getNumMPOptionInfos() const { return GC.getNumMPOptionInfos(); }
...
2.15) CyGlobalContext.cpp
The method getModOptionInfo(int i) has to be defined:
Code:
...
CvInfoBase* CyGlobalContext::getGameOptionInfo(int i) const
{
return (i>=0 && i<GC.getNumGameOptionInfos()) ? &GC.getGameOptionInfo((GameOptionTypes)i) : NULL;
}
//Mod Chooser Start
CvInfoBase* CyGlobalContext::getModOptionInfo(int i) const
{
return (i>=0 && i<GC.getNumModOptionInfos()) ? &GC.getModOptionInfo((ModOptionTypes)i) : NULL;
}
//Mod Chooser End
CvInfoBase* CyGlobalContext::getMPOptionInfo(int i) const
{
return (i>=0 && i<GC.getNumMPOptionInfos()) ? &GC.getMPOptionInfo((MultiplayerOptionTypes)i) : NULL;
}
...
2.16) CyGlobalContextInterface3.cpp
Now, the two methods also have to be exposed to python.
Code:
...
.def("getGameOptionInfo", &CyGlobalContext::getGameOptionInfo, python::return_value_policy<python::reference_existing_object>(), "GameOptionInfo () - Returns Info object")
//Mod Chooser Start
.def("getNumModOptionInfos", &CyGlobalContext::getNumModOptionInfos, "int () - Returns NumModOptionInfos")
.def("getModOptionInfo", &CyGlobalContext::getModOptionInfo, python::return_value_policy<python::reference_existing_object>(), "ModOptionInfo () - Returns Info object")
//Mod Chooser End
.def("getNumMPOptionInfos", &CyGlobalContext::getNumMPOptionInfos, "int () - Returns NumMPOptionInfos")
...
The methods are now usable in python.
2.17) CyInfoInterface3.cpp
One more method to expose to python. I want the default value of an option to be present in python.
Code:
...
python::class_<CvGameOptionInfo, python::bases<CvInfoBase> >("CvGameOptionInfo")
.def("getDefault", &CvGameOptionInfo::getDefault, "bool ()")
;
//Mod Chooser Start
python::class_<CvModOptionInfo, python::bases<CvInfoBase> >("CvModOptionInfo")
.def("getDefault", &CvModOptionInfo::getDefault, "bool ()")
;
//Mod Chooser End
python::class_<CvMPOptionInfo, python::bases<CvInfoBase> >("CvMPOptionInfo")
.def("getDefault", &CvMPOptionInfo::getDefault, "bool ()")
;
...
Well, I have made some additional changes to Replays used in the Hall of Fame. Section 2.18-2.22 explains that.
2.18) CvReplayInfo.h
In the public section:
Code:
...
DllExport bool isGameOption(GameOptionTypes eOption) const;
//Mod Chooser Start
DllExport bool isModOption(ModOptionTypes eOption) const;
//Mod Chooser End
DllExport bool isVictoryCondition(VictoryTypes eVictory) const;
...
This returns the state of our options.
Code:
...
std::vector<GameOptionTypes> m_listGameOptions;
//Mod Chooser Start
std::vector<ModOptionTypes> m_listModOptions;
//Mod Chooser End
std::vector<VictoryTypes> m_listVictoryTypes;
...
This vector holds the options.
2.19) CvReplayInfo.cpp
In CvReplayInfo::createInfo(PlayerTypes ePlayer):
Code:
...
m_listGameOptions.clear();
for (int i = 0; i < NUM_GAMEOPTION_TYPES; i++)
{
GameOptionTypes eOption = (GameOptionTypes)i;
if (game.isOption(eOption))
{
m_listGameOptions.push_back(eOption);
}
}
//Mod Chooser Start
m_listModOptions.clear();
for (int i = 0; i < NUM_MODOPTION_TYPES; i++)
{
ModOptionTypes eOption = (ModOptionTypes)i;
if (game.isModOption(eOption))
{
m_listModOptions.push_back(eOption);
}
}
//Mod Chooser End
m_listVictoryTypes.clear();
...
This reads the options from the game and stores them in m_listModOptions.
Code:
...
bool CvReplayInfo::isGameOption(GameOptionTypes eOption) const
{
for (uint i = 0; i < m_listGameOptions.size(); i++)
{
if (m_listGameOptions[i] == eOption)
{
return true;
}
}
return false;
}
//Mod Chooser Start
bool CvReplayInfo::isModOption(ModOptionTypes eOption) const
{
for (uint i = 0; i < m_listModOptions.size(); i++)
{
if (m_listModOptions[i] == eOption)
{
return true;
}
}
return false;
}
//Mod Chooser End
...
Definition isModOption. Returns the state of the option given by eOption.
In CvReplayInfo::read(FDataStreamBase& stream):
Code:
...
for (int i = 0; i < iNumTypes; i++)
{
stream.Read(&iType);
m_listGameOptions.push_back((GameOptionTypes)iType);
}
//Mod Chooser Start
stream.Read(&iNumTypes);
for (int i = 0; i < iNumTypes; i++)
{
stream.Read(&iType);
m_listModOptions.push_back((ModOptionTypes)iType);
}
//Mod Chooser End
...
Reads the set of options from the replay file.
In CvReplayInfo::write(FDataStreamBase& stream):
Code:
...
for (uint i = 0; i < m_listGameOptions.size(); i++)
{
stream.Write((int)m_listGameOptions[i]);
}
//Mod Chooser Start
stream.Write((int)m_listModOptions.size());
for (uint i = 0; i < m_listModOptions.size(); i++)
{
stream.Write((int)m_listModOptions[i]);
}
//Mod Chooser End
...
Stores the set of options in the replay file.
2.20) CyReplayInfo.h
We need to expose the method defined above to python.
So, I added to the public section:
Code:
...
bool isGameOption(int iOption) const;
//Mod Chooser Start
bool isModOption(int iOption) const;
//Mod Chooser End
bool isVictoryCondition(int iVictory) const;
...
2.21) CyReplayInfo.cpp
Definition of the above method:
Code:
...
bool CyReplayInfo::isGameOption(int iOption) const
{
if (m_pHoF)
{
return m_pHoF->isGameOption((GameOptionTypes)iOption);
}
return false;
}
//Mod Chooser Start
bool CyReplayInfo::isModOption(int iOption) const
{
if (m_pHoF)
{
return m_pHoF->isModOption((ModOptionTypes)iOption);
}
return false;
}
//Mod Chooser End
...
2.22) CyHallOfFameInterface.cpp
Finally, we have to expose that method to python. In python::class_<CyReplayInfo>("CyReplayInfo"):
Code:
...
.def("isGameOption", &CyReplayInfo::isGameOption, "bool (int iOption)")
//Mod Chooser Start
.def("isModOption", &CyReplayInfo::isModOption, "bool (int iOption)")
//Mod Chooser End
.def("isVictoryCondition", &CyReplayInfo::isVictoryCondition, "bool (int iVictory)")
...
Well, that's all with the SDK.
That's all? No, not really. You have to USE the options. A simple example:
Imagine you want to use
Borders over water.
RogerBacon changed CvCity::doPlotCulture(bool bUpdate).
Original:
Code:
...
if pLoopPlot->isPotentialCityWorkForArea(area()))
{
pLoopPlot->changeCulture(getOwnerINLINE(), (((getCultureLevel() - iCultureRange) * 20) + getCommerceRate(COMMERCE_CULTURE) + 1), (bUpdate || !(pLoopPlot->isOwned())));
}
Change:
Code:
...
if (true)
{
pLoopPlot->changeCulture(getOwnerINLINE(), (((getCultureLevel() - iCultureRange) * 20) + getCommerceRate(COMMERCE_CULTURE) + 1), (bUpdate || !(pLoopPlot->isOwned())));
}
...
To make this switchable, you have to modify the IF-clause:
Code:
...
if (pLoopPlot->isPotentialCityWorkForArea(area()) || GC.getGameINLINE().isModOption(MODOPTION_BORDERS_OVER_WATER))
{
pLoopPlot->changeCulture(getOwnerINLINE(), (((getCultureLevel() - iCultureRange) * 20) + getCommerceRate(COMMERCE_CULTURE) + 1), (bUpdate || !(pLoopPlot->isOwned())));
}
...
Assuming there is a mod option called "MODOPTION_BORDERS_OVER_WATER" defined in CvEnums.h and, off cause, in the XML file.
You can use GC.getGameINLINE().isModOption(MODOPTION_X) wherever you want to determine wether an option is true or false referring to the default value in the XML file.
This is also possible in python. The call is:
gc.getGame().isModOption(ModOptionTypes.MODOPTION_X)
For getting the name of the mod defined in the XML file:
gc.getModOptionInfo().getDescription(ModOptionTypes.MODOPTION_X)
And for the tool tip:
gc.getModOptionInfo(ModOptionTypes.MODOPTION_X).getHelp()
Matze