Partie 38: Les portes cassables

Téléchargements

Code source
Exécutable de l'éditeur de niveaux (Windows 32bits)
Exécutable du jeu (Windows 32bits)

Avant d'essayer de compiler le code, allez dans l'onglet "Projets" dans le menu de gauche, séléctionnez l'onglet "Run" pour votre kit,
et mettez dans "Working directory" le chemin de "editor\data" pour l'éditeur ou "game\data" pour le jeu.

La porte 22

La porte 22 dans le 2ième niveau est piégeuse.


Au départ, elle est ouverte, mais quand on l'approche, un bouton invisible la ferme.
Ensuite, la seule facon de l'ouvrir est de la détruire avec nos armes.


Au départ je pensais utiliser le flag "estOuverte" pour afficher cet état, mais comme la porte commence par
descendre du plafond pour se fermer, il doit y avoir un état "estCassee" différent de celui "estOuverte".
Donc on doit ajouter ça à "tiles.cpp".

		<tile name="Porte">
			[...]
			<param type="bool">estCassee</param>
		</tile>
				
Maintenant on va devoir convertir la map au nouveau format.

Convertir les maps

J'ai déjà converti les maps plusieurs fois quand la version des fichiers changeait, mais je n'ai jamais
expliqué comment je faisais.
Dans l'éditeur, il y a un define appelé "CONVERT_MAP" qui est normalement commenté.

		//#define CONVERT_MAP 1
				
Si vous le cherchez dans le code, vous verrez qu'il est utilisé dans 2 fonctions.
La fonction load():

		void    CBridge::load(QString fileName)
		{
			// astuce pour convertir les anciens formats
		#ifdef CONVERT_MAP
			CMap::mTilesParams.clear();
			editor.readTilesDB("databases/tiles-old.xml");
		//    CMap::mWallsParams.clear();
		//    editor.readWallsDB("databases/walls-old.xml");
		//    CMap::mObjectsParams.clear();
		//    editor.readObjectsDB("databases/items-old.xml");
		#endif

			// [8] pour retirer "file:///"
			map.load(&fileName.toLocal8Bit().data()[8]);
			updateQMLImage();
		}
				
et la fonction save():

		void    CBridge::save(QString fileName)
		{
			// astuce pour convertir les anciens formats
		#ifdef CONVERT_MAP
			CMap::mTilesParams.clear();
			editor.readTilesDB("databases/tiles.xml");
		//    CMap::mWallsParams.clear();
		//    editor.readWallsDB("databases/walls.xml");
		//    CMap::mObjectsParams.clear();
		//    editor.readObjectsDB("databases/items.xml");
		#endif

			// [8] pour retirer "file:///"
			map.save(&fileName.toLocal8Bit().data()[8]);
		}
				
Comme vous pouvez le voir, la fonction load() ouvre un fichier avec un suffixe "-old". C'est le fichier qui était
dans la partie précédente, sans le nouveau paramètre.
La fonction save() va charger le fichier courant, avec le nouveau paramètre.

Alors, pour convertir une map pour qu'elle prenne en compte ce nouveau paramètre, vous allez avoir besoin des 2
versions de "tiles.xml" dans le répertoire database.
ensuite, dé-commentez le define "CONVERT_MAP", re-compilez l'éditeur et lancez-le.
Chargez la map que vous voulez convertir et re-sauvez la.
Pour vérifier que la conversion a fonctionné, re-compilez l'éditeur en commentant le define "CONVERT_MAP" et
essayez de recharger la map.

Comme la conversion teste seulement le nombre de paramètres d'un type de case donné, les nouveaux paramètres peuvent
seulement être ajouté à la fin des autres.
Ils seront remplis avec une valeur par défaut quand vous sauvegarderez la map (false pour les booleans, 0 pour les
integers, ...).

Dessiner la porte cassée

On a seulement une image qui représente le trou dans la porte.


On pourrait l'afficher comme on l'a fait pour les décors en le dessinant d'abord sur la porte, puis en "enlevant"
les pixels roses, mais les modes de composition de Qt nous permettent de rendre directement les pixels transparents
en utilisant cette image comme un masque.

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

			painter.setCompositionMode(QPainter::CompositionMode_DestinationOut);
			painter.drawImage(pos.x, pos.y, srcImage);
			painter.end();
		}

		void    CDoors::drawDoor(QImage* image, CVec2 tablePos, CTile* tile)
		{
			[...]
			// porte cassable
			if (tile->getBoolParam("estCassable") == true && tile->getBoolParam("estCassee") == true)
			{
				QImage  holeImage = fileCache.getImage(std::string("gfx/3DView/doors/Bashed_Door.png"));
				graph2D.drawImageTransparent(&doorImage, CVec2(), holeImage);
			}
				

Seules quelques attaques permettent de casse la porte.
Alors dans CInterface::updateWeapons(), quand on détecte qu'il y a une porte cassable en face de nous et que
l'attaque courante est d'un des types que l'on attend, on passe le flag "estCassee" à true.

		void CInterface::updateWeapons()
		{
			// surbrillance de l'attaque
			if (attackHighlightTime.update() == true)
			{
				[...]

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

				if (frontTile != NULL)
				{
					// porte cassable
					if (frontTile->getType() == eTileDoor &&
					    frontTile->getBoolParam("estCassable") == true &&
					    frontTile->getBoolParam("estCassee") == false &&
					    (attackType == ATTACK_CHOP ||
					     attackType == ATTACK_KICK ||
					     attackType == ATTACK_SWING ||
					     attackType == ATTACK_HACK ||
					     attackType == ATTACK_BERZERK ||
					     attackType == ATTACK_BASH)
					    )
					{
						frontTile->setBoolParam("estCassee", true);
						weaponCoolDown[currentWeapon].set(60, true);
						sound->play(frontPos, "sound/Wooden_Thud.wav");
					}
					else
					{
						// attaque normale
						[...]
					}
				}
				
Dans le jeu d'origine les portes avaient une valeur de "défense" et pouvaient seulement être détruites avec une
attaque assez forte, mais je n'ai pas inclus ça parce que la plupart du temps vous aurez la bonne arme pour casser
la porte à un niveau donné.
Quand j'ai regardé le code du jeu d'origine, j'ai aussi remarqué que certaines portes pouvaient être détruites avec
une boule de feu, mais je n'ai jamais vu personne faire ça.

Il faut aussi qu'on change la fonction isOpened() pour que les portes cassées soient considérées comme ouvertes et
qu'on puisse les traverser:

		bool CDoors::isOpened(CTile* tile)
		{
			bool isOpened = tile->getBoolParam("estOuverte") || tile->getBoolParam("estCassee");
			bool isMoving = tile->getBoolParam("estEnMouvement");

			if (isOpened == true && isMoving == false)
				return true;
			else
				return false;
		}
				
Finalement tous les mécanismes des portes sont codés pour le niveau 2.
Et on a aussi défini toutes les plaque de pression.