Part 38: Breakable doors


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.

Door 22

Door 22 in the second level is a tricky one.

At the beginning it is opened, but when we approach it, an invisible switch closes it. Then, the only way to open it is to smash it with our weapons.

At first I thought to use the "isOpened" flag to display this state, but as the door first slides down from the
ceiling to close itself, there must be a "isBroken" state separate from the "isOpened" one. So we must add this to "tiles.cpp".

		<tile name="Door">
			<param type="bool">isBroken</param>
Now we will have to convert the map to the new format.

Converting maps

I already converted the maps a few times when the file version changed but I never explained howI did.
In the editor there is a define called "CONVERT_MAP" that is normally commented.

		//#define CONVERT_MAP 1
If you search for it in the code, you will find that it is used in 2 functions.
The load() function:

		void    CBridge::load(QString fileName)
			//trick to convert old map formats
		#ifdef CONVERT_MAP
		//    CMap::mWallsParams.clear();
		//    editor.readWallsDB("databases/walls-old.xml");
		//    CMap::mObjectsParams.clear();
		//    editor.readObjectsDB("databases/items-old.xml");

			// [8] to remove "file:///"
and the save() function:

		void    CBridge::save(QString fileName)
			//trick to convert old map formats
		#ifdef CONVERT_MAP
		//    CMap::mWallsParams.clear();
		//    editor.readWallsDB("databases/walls.xml");
		//    CMap::mObjectsParams.clear();
		//    editor.readObjectsDB("databases/items.xml");

			// [8] to remove "file:///"[8]);
As you can see, the load() function opens a file with an "-old" suffix. That is the file as it was in the previous
part, without our new parameter.
The save() function will load the current file, with the new parameter.

So to convert a map to take into account this new parameter, you will need to have two the versons of "tiles.xml"
in the database directory.
Then un-comment the "CONVERT_MAP" define, re-compile the editor and launch it.
Load the map you want to convert and save it again.
To check if the conversion worked, re-compile the editor with the "CONVERT_MAP" define commented out and try to
reload the map.

As the conversion only checks the number of parameters of a given tile type, the new parameters can only be added
at the end of the existing ones.
They will get a default value when you save the map (false for booleans, 0 for integers, ...).

Drawing the broken door

We have only one image that pictures the hole in the door.

We could draw it the same way as we did for the ornates by first drawing it over the door image and then "removing"
the pink pixels, but the composition modes of Qt allows us to turn the pixels transparent directly, using this
image as a mask.

		void    CGraphics2D::drawImageTransparent(QImage* destImage, const CVec2& pos, const QImage& srcImage)
			QPainter painter(destImage);

			painter.drawImage(pos.x, pos.y, srcImage);

		void    CDoors::drawDoor(QImage* image, CVec2 tablePos, CTile* tile)

				// breakable door
				if (tile->getBoolParam("isBreakable") == true && tile->getBoolParam("isBroken") == true)
					QImage  holeImage = fileCache.getImage(std::string("gfx/3DView/doors/Bashed_Door.png"));
					graph2D.drawImageTransparent(&doorImage, CVec2(), holeImage);

Only a few attacks can break the door.
So in CInterface::updateWeapons(), when we detect that there is a breakable door in front of us and when the current
attack is one of the wanted types, we set the "isBroken" flag to true.

		void CInterface::updateWeapons()
			// attack highlight
			if (attackHighlightTime.update() == true)

				CVec2   frontPos = player.getPosFromLocal(CVec2(0, -1));
				CTile*  frontTile = map.getTile(frontPos);

				if (frontTile != NULL)
					// breakble door
					if (frontTile->getType() == eTileDoor &&
					    frontTile->getBoolParam("isBreakable") == true &&
					    frontTile->getBoolParam("isBroken") == false &&
					    (attackType == ATTACK_CHOP ||
					     attackType == ATTACK_KICK ||
					     attackType == ATTACK_SWING ||
					     attackType == ATTACK_HACK ||
					     attackType == ATTACK_BERZERK ||
					     attackType == ATTACK_BASH)
						frontTile->setBoolParam("isBroken", true);
						weaponCoolDown[currentWeapon].set(60, true);
						sound->play(frontPos, "sound/Wooden_Thud.wav");
						// normal hit
In the original game, the doors had a "defense" value and could only be destroyed with an attack strong enough, but
I didn't include this because most of the time you will have the right weapon to break the door at a given level.
When I looked at the code of the original game, I noticed that some doors could be broken with a fireball, but I
never saw anybody do this.

We also have to change the isOpened() function so that broken doors are considered as opened and we can walk
through them:

		bool CDoors::isOpened(CTile* tile)
			bool isOpened = tile->getBoolParam("isOpened") || tile->getBoolParam("isBroken");
			bool isMoving = tile->getBoolParam("isMoving");

			if (isOpened == true && isMoving == false)
				return true;
				return false;
Finally all the doors mechanics are coded for level 2.
And we defined all the pressure plates too.