Part 43: Champions' life and death
Downloads
Damages and death
		void CCharacter::hitChampion(int num, int damages)
		{
			// TODO: wound, damage types
			// already dead or empty ?
			if (portrait == -1 || isDead() == true)
				return;
			displayedDamages = damages;
			damagesTimer.set(90, false);
			stats[eStatHealth].value -= damages;
			static CVec2    championPos[4][4] =
			{
				{{0, 0}, {1, 0}, {0, 1}, {1, 1}},   // up
				{{0, 1}, {0, 0}, {1, 1}, {1, 0}},   // left
				{{1, 1}, {0, 1}, {1, 0}, {0, 0}},   // down
				{{1, 0}, {1, 1}, {0, 0}, {0, 1}}    // right
			};
			// dead ?
			if (stats[eStatHealth].value <= 0)
			{
				stats[eStatHealth].value = 0;
				// drop all objects on the ground
				CVec2   stackPos = player.pos * 2 + championPos[player.dir][num];
				CObjectStack*   stack = map.addObjectsStack(stackPos, eWallSideMax);
				for (int i = 0; i < eBodyPartCount; ++i)
				{
					CObject&    obj = bodyObjects[i];
					if (obj.getType() != 0)
					{
						stack->addObject(obj);
						obj.setType(0);
					}
				}
				// drop the champion's bones
				CObject bones;
				bones.setType(OBJECT_TYPE_BONES);
				bones.setIntParam("Champion", num);
				stack->addObject(bones);
			}
		}
				
				We subtract the damages from the champion's health.
				
		<!-- # BONES -->
		<item name="ITEM149">
			[...]
			<param type="int">Champion</param>
		</item>
				
				Because we need to know the name of the champion to display the bones' name when we pick them up.
				
		void CInterface::drawObjectName(QImage* image)
		{
			int type = mouse.mObjectInHand.getType();
			if (type != 0)
			{
				std::string name;
				if (type == OBJECT_TYPE_BONES)
					name = setChampionNameInString(mouse.mObjectInHand.getIntParam("Champion"), "ITEM149");
				else
					name = objects.mObjectInfos[type].name;
				drawText(image, CVec2(233, 33), eFontStandard, name.c_str(), MAIN_INTERFACE_COLOR);
			}
		}
				
			
			
Hitting the walls
				
		void CPlayer::moveIfPossible(EWallSide side)
		{
			[...]
			if (canMove == true)
			{
				[...]
			}
			else
			{
				if (game.characters[0].portrait != -1)
				{
					static const int championsHit[eWallSideMax][2] =
					{
						{0, 1}, // eWallSideUp
						{2, 3}, // eWallSideDown
						{0, 2}, // eWallSideLeft
						{1, 3}  // eWallSideRight
					};
					for (int i = 0; i < 2; ++i)
					{
						int champNum = championsHit[invWallSide(side)][i];
						game.characters[champNum].hitChampion(champNum, (RANDOM(4) == 0 ? 2 : 1));
					}
					sound->play(newPos, "sound/Party_Damaged.wav");
				}
			}
		}
				
				I should probably have used the getChampionRow() from the last part here and take into account the case when the
Falling into a pit
				
		void CPlayer::fall()
		{
			[...]
			hitAllChampions(20);
		}
		//-----------------------------------------------------------------------------------------
		void CPlayer::hitAllChampions(int damages)
		{
			int rnd = damages / 8 + 1;
			sound->play(pos, "sound/Party_Damaged.wav");
			for (int i = 0; i < 4; ++i)
			{
				int dmg = damages - rnd + RANDOM(rnd * 2);
				dmg = MAX(1, dmg);
				game.characters[i].hitChampion(i, dmg);
			}
		}
				
			
			
Vi altar
				
		void CGame::update(SMouseArea* clickedArea)
		{
			[...]
			else if (clickedArea->type == eMouseAreaG_DropObject)
			{
				// get the pos and side
				[...]
				// drop object
				CObjectStack*   stack = map.addObjectsStack(pos, side);
				stack->addObject(mouse.mObjectInHand);
				// test if we resurrect a champion
				if (mouse.mObjectInHand.getType() == OBJECT_TYPE_BONES &&
				    side != eWallSideMax)
				{
					CWall&  wall = map.getTile(pos)->mWalls[side];
					if (wall.getType() == eWallAlcove &&
					    wall.getEnumParam("Type") == 2)
					{
						// resurrect
						sound->play(pos, "sound/Spell.wav");
						walls.resurrectWallPos = pos;
						walls.resurrectWallSide = side;
						walls.resurrectTimer.set(120, true);
					}
				}
				
				The timer is used to play a lightning animation during 2 seconds.
		CWalls::COrnateData  alcoveLightning =
		{
			"",
			"gfx/3DView/missiles/Lightning_side.png",
			"",
			{82, 95},
			{0, 0}
		};
		//---------------------------------------------------------------------------------------------
		CRect CWalls::drawOrnate(QImage* image, CVec2 tablePos, EWallSide side, int ornate, int textLines)
		{
			if (ornate != 0)
			{
				int tableSide = walls.getTableSide(side, false);
				if (gWallsInfos[tablePos.y][tablePos.x][tableSide].size.x != 0)
				{
					COrnateData* ornateData = NULL;
					if (ornate == ORNATE_ALCOVE_LIGHTNING)
						ornateData = &alcoveLightning;
					else
						ornateData = &walls.ornatesDatas[ornate];
					[...]
					// draw the ornate on screen.
					bool    flipX = false;
					bool    flipY = false;
					if (ornate == ORNATE_ALCOVE_LIGHTNING)
					{
						int image = walls.resurrectTimer.get() / 30;
						flipX = image & 1;
						flipY = (image >> 1) & 1;
					}
					graph2D.drawImage(image, ornatePos, tempImage, 0, flipX, QRect(0, 33, MAINVIEW_WIDTH, MAINVIEW_HEIGHT), flipY);
				
			
			
Explosion
				
		void CExplosions::add(EExplosions type, CVec2 mapPos, int duration)
		{
			SExplosion  explo;
			explo.type = type;
			explo.mapPos = mapPos;
			explo.timer.set(duration, true);
			mExploList.push_back(explo);
			if (type == eExplo_Resurrect)
				sound->play(mapPos, "sound/Explosion.wav");
		}
				
				An update() function handles the timers of all the explosions and removes them from the list when they are finished.
		void CExplosions::update()
		{
			std::vector<SExplosion>::iterator   it = mExploList.begin();
			while(it != mExploList.end())
			{
				if (it->timer.update() == true)
					it = mExploList.erase(it);
				else
					it++;
			}
		}
				
				The draw() function is a simplified version of CWalls::drawOrnate()
				
		void CExplosions::draw(QImage* image, CVec2 mapPos, CVec2 tablePos)
		{
			for (size_t i = 0; i < mExploList.size(); ++i)
			{
				SExplosion& explo = mExploList[i];
				if (explo.mapPos == mapPos)
				{
					QImage  exploGfx;
					CVec2   pos;
					float   scale = 1.0;
					if (explo.type == eExplo_Resurrect)
					{
						exploGfx = fileCache.getImage("gfx/3DView/explosions/Fireball.png");
						pos = CVec2(57, 66);
						scale = 110.0 / 145.0;
					}
					const SWallInfos*   tabData = &gWallsInfos[tablePos.y][tablePos.x][eWallSideUp];
					if (tabData->size.x != 0)
					{
						const SWallInfos*   refWall = NULL;
						refWall = &gWallsInfos[3][2][eWallSideUp];
						// calculate the position and scale factor and draw the explosion in a temporary image
						scale *= (float)tabData->size.y / 111.0f;
						QSize   scaledSize = exploGfx.size() * scale;
						pos = pos - refWall->pos;
						pos.x = pos.x * tabData->size.x / refWall->size.x;
						pos.y = pos.y * tabData->size.y / refWall->size.y;
						pos += tabData->pos;
						QImage  tempImage(scaledSize, QImage::Format_ARGB32);
						tempImage.fill(TRANSPARENT);
						graph2D.drawImageScaled(&tempImage, CVec2(), exploGfx, scale);
						// darken the explosion based on it's distance
						static const float shadowLevels[] = {0.0f, 0.2f, 0.4f};
						float shadow = shadowLevels[WALL_TABLE_HEIGHT - 1 - tablePos.y];
						graph2D.darken(&tempImage, shadow);
						// draw the explosion on the screen.
						graph2D.drawImage(image, pos, tempImage, 0, false, QRect(0, 33, MAINVIEW_WIDTH, MAINVIEW_HEIGHT));
					}
				}
			}
		}
				
				When we create this explosion, the champion is also resurrected and gets back half o its starting life.
				
		void CWalls::update()
		{
			// vi altar animation
			if (resurrectTimer.update() == true)
			{
				// retrieve champion's number
				CObjectStack*   stack = map.findObjectsStack(resurrectWallPos, resurrectWallSide);
				int         last = stack->getSize() - 1;
				CObject&    bones = stack->getObject(last);
				int         champNum = bones.getIntParam("Champion");
				// delete object
				stack->removeObject(last);
				resurrectWallSide = eWallSideMax;
				// resurrect champion
				CCharacter::SStat&  health = game.characters[champNum].stats[CCharacter::eStatHealth];
				health.value = health.startValue / 2;
				// launch explosion
				explosions.add(eExplo_Resurrect, player.pos, 20);
			}
		}
				
				I didn't look at the code of the original game to see if other stats of the champion are changed.
			
			
Health regeneration
		#define HEALTH_REGEN_TIMER        (30 * 60)
		#define HEALTH_REGEN_TIMER_SLEEP  90