Source code
Executable for the map editor (Windows 32bits)
Executable for the game (Windows 32bits)
Before you try to compile the code go to the "Projects" tab on the left menu, select the "Run" settings for your kit,
and set the "Working directory" to the path of "editor\data" for the editor or "game\data" for the game.
Now we will add the portraits that allows us to choose our champions.
From the editor point of view, it will be a new type of walls with new parameters that should look like this:
There is an int value that gives the champion's portrait number. By convention I choosed to use the order as
they appear in the portrait sprite sheet that we saw in the last part.
The second parameter is a boolean that will be used internally in the game to tell if we already clicked on this
portrait and added this champion to our party.
As it is only used in the game, I could have hidden it in the editor - i.e. by using a naming convention like adding
a "_" at the beginning of it's name. But it may be useful to change the initial value of an internal variable for testing.
So, in "walls.xml", it looks like that:
<wall name="Portrait">
<image>Portrait.png</image>
<param type="int">Champion</param>
<param type="bool">isEmpty</param>
</wall>
As for the "ornate" type, we use 2 QML models, "ParamTextField.qml" and "ParamCheckBox.qml" for the appearance of the parameters
in the selection box.
The new parameters are defined in "map.h" like that:
enum EParamType
{
eParamOrnate = 0,
eParamInt,
eParamBool
};
[...]
class CParamInt : public CParam
{
public:
CParamInt();
int32_t mValue;
};
class CParamBool : public CParam
{
public:
CParamBool();
bool mValue;
};
Then it's more or less a copy/paste/modify job of all he functions where "eParamOrnate" or "CParamOrnate" appears,
with paying particular attention to the load and save functions.
Finally, I added a new tile to differenciate those walls from the simple ones. So our first level now looks like
that in the editor:
The ornate of the mirror is displayed like the other ornates we already saw in the previous parts.
The head of the champion is only drawn when the wall is right in front of us, and a mouse area is added at this moment.
Of course we also check the "isEmpty" bool to see if we already clicked on this portrait to add it to our party.
Let's see how all this is done in the drawWall() function in game.cpp:
void CGame::drawWall(QImage* image, CVec2 mapPos, CVec2 tablePos, EWallSide side, bool flip)
{
[...]
// draw the ornates
CWall* wall = &tile->mWalls[playerSide];
if (wall->getType() == eWallSimple)
{
//------------------------------------------------------------------------------
// simple wall
int ornate = wall->getOrnateParam("Ornate");
walls.drawOrnate(image, tablePos, side, ornate);
}
else if (wall->getType() == eWallPortrait)
{
//------------------------------------------------------------------------------
// champion mirror
walls.drawOrnate(image, tablePos, side, ORNATE_MIRROR);
if (tablePos == CVec2(2, 3) && side == eWallSideUp)
{
// if the mirror is right in front of us, we draw the champion's head
int champion = wall->getIntParam("Champion");
bool isEmpty = wall->getBoolParam("isEmpty");
if (isEmpty == false)
{
CRect rect = interface.getChampionPortraitRect(champion);
QImage portraits = fileCache.getImage("gfx/interface/ChampPortraits.png");
CVec2 pos(96, 68);
graph2D.drawImageAtlas(image, pos, portraits, rect);
int lastChampChosed;
for (lastChampChosed = 0; lastChampChosed < 4; ++lastChampChosed)
if (characters[lastChampChosed].portrait == -1)
break;
if (lastChampChosed != 4)
{
CRect mouseRect(pos, CVec2(pos.x + CHAMP_PORTRAIT_WIDTH - 1, pos.y + CHAMP_PORTRAIT_HEIGHT - 1));
mouse.addArea(eMouseAreaG_Champion, mouseRect, (void*)wall);
}
}
}
}
[...]
}
You can see that I added functions to get a parameter's value by it's name, as we read them in walls.xml.
This is mainly for readability.
In the original game the format of the walls parameters was hard-coded, as it is more efficient - no string
comparisons - and more safe as there is less information available to reverse-engineer the map format.
I made quite a lot of changes to the interface in this part.
First, when you click on a mirror, the inventory opens and a window appears to let you resurrect or reincarnate
the champion:
Note: I only did the resurrection int this version of the game. As we don't handle all the stats of the champion that
are modified by "re-incarnation". And I have to take a look at the original game sources to see exactly what is done.
In the original game most of the interactions were disabled in this screen.
- The spells, weapons and arrows were greyed-out.
- The save, sleep and close buttons were hidden.
- You could not exit this inventory by clicking on the bars near the champion's portrait.
- You could not move an object from the champion to the hands of another champion at the top of the screen
The only things that you could do were to click on the resurrect, re-incarnate or cancel button. And perhabs look at
an object or see the champions stats by clicking on the eye.
To see all the changes that implies in the code, search for the "isResurrecting" variable.
Now that we can resurrect a champion, it would be interesting to set the true values of it's statistics - at least the
visible ones are there is also many hidden stats and skills in the original game.
So I began a champion's database called "champions.xml":
<?xml version="1.0" encoding="ISO-8859-1"?>
<champions>
<champion firstName="CHAMP_FNAME00" lastName="CHAMP_LNAME00">
<portrait>0</portrait>
<health>60</health>
<stamina>58</stamina>
<mana>22</mana>
<strength>42</strength>
<dexterity>40</dexterity>
<wisdom>42</wisdom>
<vitality>36</vitality>
<anti_magic>53</anti_magic>
<anti_fire>40</anti_fire>
<load>44</load>
</champion>
[...]
</champions>
As before, I followed the order of the champions portraits as they appear in the sprite sheet.
Quite simply, this database, like the others, is loaded at the beginning of the game, and it's datas are copied the the
coresponding "characters" structure when a champion is resurrected.
You can see that the names of the champion doesn't appear here. Instead there is an identifier. We will see why right now.
All the texts of the game will appear in the "texts.xml" file:
<?xml version="1.0" encoding="ISO-8859-1"?>
<texts>
<text id="CHAMP_FNAME00">ELIJA</text>
<text id="CHAMP_LNAME00">LION OF YAITOPYA</text>
<text id="CHAMP_FNAME01">HALK</text>
<text id="CHAMP_LNAME01">THE BARBARIAN</text>
[...]
<text id="STATS00">HEALTH</text>
<text id="STATS01">STAMINA</text>
[...]
</texts>
This way it will be easier to translate the game to another language.
The only exceptions are a few texts that are drawn in the sprites - "Game Over", "Food", "Water"...
In games that are translated to many languages, the texts are generally handled in an Excel spreadsheet, then exported to
the game specific format.
As Dungeon Master was only translated in 3 languages in the latest versions, the texts were not handled so well, and they
were even split accross several files with various formats.
This datatbase is handled by 2 functions in "interface.cpp":
- readTextDB() to load the database.
- getText() to get a text string given it's id.
As you may have noticed, I already display the full name of a champion and it's health, stamina, mana and load stats in the inventory.
But I also did the information box that appears when you click on the eye:
Search for "isPressingEye" for more info.