Part 36: Pits


Source code
Executable for the map editor - exactly the same as in part 35 (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.


Pits are very simples. They will use these parameters:

		<tile name="Pit">
			<param type="int">Level</param>
			<param type="int">X</param>
			<param type="int">Y</param>
			<param type="bool">isClosed</param>
Level, X and Y defines the position where we land after falling in the pit.
And isClosed tells if it is opened or closed.


The graphics are similar to the floor ornates. We have images for the facing and left pits.
The right ones are simply the left images flipped.

The drawing function is very simple too. Quite similar to the ornates, except that the graphics and their
coordinates are hardcoded in a table.

		struct SPitData
			char        file[32];
			int16_t     x, y;

		static const SPitData   gPits[WALL_TABLE_HEIGHT][(WALL_TABLE_WIDTH + 1) / 2] =
			{{"Pit03.png", 0, 96}, {"Pit13.png",  6,  98}, {"Pit23.png", 80,  98}}, // Row 3
			{{"", 0, 0},           {"Pit12.png", -1, 109}, {"Pit22.png", 66, 110}}, // Row 2
			{{"", 0, 0},           {"Pit11.png", -3, 127}, {"Pit21.png", 43, 127}}, // Row 1
			{{"", 0, 0},           {"Pit10.png",  0, 159}, {"Pit20.png", 27, 160}}  // Row 0

		void CTiles::drawPit(QImage* image, CVec2 tablePos)
			const SPitData*  data;

			// get the table data
			bool    flip = (tablePos.x >= (WALL_TABLE_WIDTH + 1) / 2);

			if (flip == false)
				data = &gPits[tablePos.y][tablePos.x];
				data = &gPits[tablePos.y][WALL_TABLE_WIDTH - tablePos.x - 1];

			// display
			if (data->file[0] != 0)
				std::string fileName = std::string("gfx/3DView/pits/") + std::string(data->file);
				QImage  pitImage = fileCache.getImage(fileName);
				CVec2   pos = CVec2(data->x, data->y);

				if (flip == true)
					pos.x = MAINVIEW_WIDTH - pos.x - pitImage.width();

				QRect   clip(0, 33, MAINVIEW_WIDTH, MAINVIEW_WIDTH);
				graph2D.drawImage(image, pos, pitImage, 0, flip, clip);
There are no graphics for the closed pits. Although in the original game's graphics there was outline images that
that were not used in the game.

There are graphics for the pits in the ceilings that you can see from the level below after falling, but I did not
have time to include them in this part.


As for the stairs and the teleporters, when we walk on an opened pit tile, we set a flag that will be handled in
the mainLoop() function:

        if (destTile->getType() == eTilePit && destTile->getBoolParam("isClosed") == false)
            sound->play(pos, "sound/Scream.wav");
            shouldFall = true;
The only special thing is that in mainLoop() we wait for the end of the sound before calling the function to fall.

		if (player.shouldFall && sound->isFinished(player.pos, "sound/Scream.wav") == true)
This fall() function is a simplified version of the teleport() one.

		void CPlayer::fall()
			CTile*  tile = map.getTile(pos);
			int     level = tile->getIntParam("Level");
			CVec2   newPos = CVec2(tile->getIntParam("X"), tile->getIntParam("Y"));

			game.loadLevel(level, newPos, dir);
			shouldFall = false;

Dropping objects in pits

Dropping objects in pits is also a simplified case of dropping them on a teleporter tile.
When we drop an objet on one of theses tiles, we call a function that moves all the objects stacks on this tile:

		void CGame::fallStacks(CVec2 mapPos)
			CTile*  tile = map.getTile(mapPos);
			std::vector<CObjectStack>   copyStacks;

			// copies the stacks
			for (int y = 0; y < 2; ++y)
				for (int x = 0; x < 2; ++x)
					CVec2 objPos = mapPos * 2 + CVec2(x, y);
					CObjectStack*    stack = map.findObjectsStack(objPos, eWallSideMax);

					if (stack != NULL)
						CObjectStack    copy = *stack;
						copy.mPos = CVec2(x, y);
						map.removeObjectsStack(objPos, eWallSideMax);

			int     lastLevel = currentLevel;
			CVec2   lastPos = player.pos;
			uint8_t lastDir = player.dir;

			int     level = tile->getIntParam("Level");
			CVec2   newPos = CVec2(tile->getIntParam("X"), tile->getIntParam("Y"));

			// load destination level
			loadLevel(level, CVec2(), 0);

			// set the stacks at the new pos
			for (size_t i = 0; i < copyStacks.size(); ++i)
				CVec2 objPos = newPos * 2 + copyStacks[i].mPos;
				CObjectStack*   stack = map.addObjectsStack(objPos, eWallSideMax);

				for (size_t j = 0; j < copyStacks[i].getSize(); ++j)

			// reload the last level
			loadLevel(lastLevel, lastPos, lastDir);

Level 3 and scripts

To test the pits I had to create a minimal level 3.
You can find it in the "data/maps" folders of the sources.
There are only a few tiles and teleporters to bring you back to level 2.
But at least the game does not crash anymore when you take the stairs at the end of level 2.

I also wrote a few scripts to open/close the pits according to the puzzles.
They are as simples as the ones to open/close the doors because we only have to change a boolean value.