Loading and saving maps

Downloads

Source code
Executable for the map editor (Windows 32bits)
Executable for the game - exactly the same as in part 4 (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.

What are we missing ?

Now that we have a map editor where we can draw walls, that we have a project for the game, and that we have graphics
for the walls, can we begin to draw a simple map in 3D ?
We are only missing one stage before that: a way to save the map from the editor and to load it in the game.
Additionnaly it will be useful for the editor to load maps that it has previously saved too.

We will put those two functions in "Map.cpp" that is in the "common" folder, so that the two projects can access them.
Even if the game itself will not use the save function it is a good idea to keep them together because they are symmetrical.

When you write a new file format it is usually better to start with an ASCII version that can be easily read in a text editor.
But the format of the maps is simple enough at this stage that we can write it in binary right now.

In this part we will focus on the loading and saving in the editor. Then loading the map in the game will reduce to a simple call to
the load function...

In the QML

In "main.qml", I added two FileDialog objects - one for loading and one for saving - that looks like this:

		FileDialog {
			id: saveDialog
			visible: false
			title: "Please choose a file"
			folder: "file:///maps"
			nameFilters: [ "Map files (*.map)", "All files (*)" ]
			selectExisting: false;
			onAccepted: {
				bridge.save(fileUrl);
				visible=false;
			}

			onRejected: visible=false;
		}
				
The only difference between the two - apart from the function called in onAccepted - is the property "selectExisting" that is set to "true"
for the loading dialog.

When "visible" is set to true, those dialogs display a standard file selector:


A little bit under that, in main.qml, I modified the "file" menu:

		Menu {
			title: qsTr("File")
			MenuItem {
				text: qsTr("Load")
				onTriggered: loadDialog.visible = true;
			}
			MenuItem {
				text: qsTr("Save")
				onTriggered: saveDialog.visible = true;
			}
			MenuItem {
				text: qsTr("Exit")
				onTriggered: Qt.quit();
			}
		}
				
So now it looks like that:


In bridge.cpp

In "bridge.cpp" there are 2 new functions:

		void    CBridge::save(QString fileName)
		{
			// [8] to remove "file:///"
			map.save(&fileName.toLocal8Bit().data()[8]);
		}

		void    CBridge::load(QString fileName)
		{
			// [8] to remove "file:///"
			map.load(&fileName.toLocal8Bit().data()[8]);
			updateQMLImage();
		}
				
As the comments say there is a little trick here, because the qml dialogs don't send us the path but an url beginning with "file:///".
In example we would get: "file:///E:/prg/DungeonMaster/editor/data/maps/test.map"
That's why you see a "&" character before "fileName" and "[8]" at the end because we skip 8 characters.

In Map.cpp

In "Map" I added 2 functions "load()" and "save()".
Here is the one that saves the map:

		char    gFileHeader[] = "DMMP"; // Dungeon Master MaP
		#define CURRENT_VERSION     1
		#define CURRENT_REVISION    0

		void CMap::save(char* fileName)
		{
			FILE*       handle = fopen(fileName, "wb");
			uint16_t    ver = CURRENT_VERSION;
			uint16_t    rev = CURRENT_REVISION;

			if (handle != NULL)
			{
				// header, version, revision
				fwrite(gFileHeader, 1, 4, handle);
				fwrite(&ver, 2, 1, handle);
				fwrite(&rev, 2, 1, handle);

				// map size
				fwrite(&mSize.x, 4, 1, handle);
				fwrite(&mSize.y, 4, 1, handle);

				// map data
				for (int y = 0; y < mSize.y; ++y)
					for (int x = 0; x < mSize.x; ++x)
					{
						CTile*  t = getTile(CVec2(x, y));
						fwrite(&t->mType, 1, 1, handle);

						for (int side = 0; side < eWallSideMax; ++side)
							fwrite(&t->mWalls[side].mType, 1, 1, handle);
					}

				fclose(handle);
			}
		}
				
So the format look like this:

First we write a 4 characters string "DMMP" so that it will be easy to see if this file contains map datas.

Then two 16bits values that contains a version and a revision number.
As we will probably make a lot of modifications to our file format in the future, it is important to know which version of the format
this file uses to avoid reading datas that don't exist or interpret them in a wrong way.

Then two 32bits values that tells the size of the map in tiles.

And finally, for each tile, we save it's type and the types of it's 4 walls, each on 1 byte.

The load() function is very similar, except that we check the values of the header and we allocate memory to read the map data.

In "editor/data/maps" I have included a very simple test map saved by this function.