Part 25: Objects - Part 5: Weight and informations

Downloads

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

Selected champion

One of the 4 champions is different from the others.
At each moment there is always one "selected" champion that you can recognize by the color of its name.


It is this champion who carries the object you have on your cursor.
When you are holding an object with your mouse, its weight is added to the load of this champion.
You can change the selected champion by clicking on the name of another one.

I replaced all the characters' names we displayed with a new function:

		void CInterface::drawChampionName(QImage* image, int num, CVec2 pos, bool isFullName)
		{
			CCharacter* c = &game.characters[num];
			std::string name = c->firstName;

			if (isFullName == true)
				name += " " + c->lastName;

			QColor  color = (num == selectedChampion ? HIGHLIGHT_TEXT_COLOR : DEFAULT_TEXT_COLOR);
			drawText(image, pos, eFontStandard, name.c_str(), color);
		}
				
It draws the champion's first or full name with the right color.
Most of the colors used for the interface are now defined "in interface.h":

		#define MAIN_INTERFACE_COLOR    QColor(0, 204, 204)
		#define DEFAULT_TEXT_COLOR      QColor(173, 173, 173)
		#define HIGHLIGHT_TEXT_COLOR    QColor(255, 170, 0)
		#define INVENTORY_BG_COLOR      QColor(68, 68, 68)
		#define INVENTORY_SLOT_COLOR    QColor(102, 102, 102)
		#define OVERLOAD1_COLOR         QColor(255, 255, 0)
		#define OVERLOAD2_COLOR         QColor(255, 0, 0)
				
To change the selected champion, we need to define mouse areas around the champions' names in CInterface::drawChampion():

		CRect nameRect(basePos, basePos + CVec2(42, 6));
		mouse.addArea(eMouseArea_SelectChamp, nameRect, eCursor_Arrow, (void*)num);
				
And when we test it, we simply have to change the value of selectedChampion.

		void   CInterface::update(SMouseArea* clickedArea)
		{
			[...]

			if (clickedArea != NULL)
			{
				if (mouse.mButtonPressing == true)
				{
					switch (clickedArea->type)
					{
						[...]

						// click on a champion's name
						case eMouseArea_SelectChamp:
							selectedChampion = (int)clickedArea->param1;
						break;
				

Weight

I added weights to all the objects in "items.xml".
Note that the weights are multiplied by 10 because it's easier to work with integers.

		<?xml version="1.0" encoding="utf-8"?>
		<items>
			<!-- ======================== Weapons ======================== -->
			<!-- EYE OF TIME -->
			<item name="ITEM001">
				[...]
				<weight>1</weight>
			</item>

			<!-- STORMRING -->
			<item name="ITEM002">
				[...]
				<weight>1</weight>
			</item>

			<!-- TORCH -->
			<item name="ITEM003">
				[...]
				<weight>11</weight>
			</item>

			[...]
		</items>
				
I also wrote a function to compute the load of a character.

		void CCharacter::updateLoad()
		{
			int type;
			stats[eStatLoad].value = 0;

			for (int i = 0; i < eBodyPartCount; ++i)
			{
				type = bodyObjects[i].getType();

				if (type != 0)
					stats[eStatLoad].value += objects.mObjectInfos[type - 1].weight;
			}

			if (&game.characters[interface.selectedChampion] == this)
			{
				type = mouse.mObjectInHand.getType();

				if (type != 0)
					stats[eStatLoad].value += objects.mObjectInfos[type - 1].weight;
			}
		}
				
We simply sum up the weights of all the objects carried by the character.
Additionally, if it's the selected character, we add the weight of the object on the mouse.

We don't need to call this function 60 times per second, we only call it in the character's initialization, when we
pick up or drop an object on the ground...

		void CGame::update(SMouseArea* clickedArea)
		{
			[...]

			if (clickedArea != NULL)
			{
				if (mouse.mButtonPressing == true)
				{
					if (clickedArea->type == eMouseAreaG_Champion)
					{
						[...]
					}
					else if (clickedArea->type == eMouseAreaG_DoorButton)
					{
						[...]
					}
					else if (clickedArea->type == eMouseAreaG_PickObject)
					{
						[...]
						characters[interface.selectedChampion].updateLoad();
					}
					else if (clickedArea->type == eMouseAreaG_DropObject)
					{
						[...]
						characters[interface.selectedChampion].updateLoad();
					}
				}
			}
		}
				
...and when we take or put an object in a character's sheet.

		void   CInterface::update(SMouseArea* clickedArea)
		{
			[...]

			if (clickedArea != NULL)
			{
				if (mouse.mButtonPressing == true)
				{
					switch (clickedArea->type)
					{
						[...]

						// object slot in inventory
						case eMouseArea_InvObject:
						{
							[...]

							// update the loads of the characters
							c->updateLoad();
							c = &game.characters[selectedChampion];
							c->updateLoad();
						}
						break;
				

Overload

There are 3 differents level of overloading in this game.
When the load is low, it is displayed in grey.


When the load is higher than 62.5% of the maximum load, it is displayed in yellow.


When the load is higher than the maximum, it is displayed in red.


So I wrote a simple function to return the overload level of a character:

		int CCharacter::overload()
		{
			int value = stats[eStatLoad].value;
			int max = stats[eStatLoad].maxValue;

			if (value > max)
				return 2;
			else if (value > (625 * max) / 1000)
				return 1;
			else
				return 0;
		}
				
And I used it to choose the color when we draw the load:

		void    CInterface::drawInventory(QImage* image)
		{
			[...]

			// display load
			CCharacter* c = &game.characters[currentChampion];
			QColor  loadColor;
			if (c->overload() == 0)
				loadColor = DEFAULT_TEXT_COLOR;
			else if (c->overload() == 1)
				loadColor = OVERLOAD1_COLOR;
			else
				loadColor = OVERLOAD2_COLOR;

			drawText(image, CVec2(104, 161), eFontStandard, getText("STATS09").c_str(), loadColor);
			static char temp[256];
			CCharacter::SStat stat = getStat(currentChampion, CCharacter::eStatLoad);
			sprintf(temp, "%5.1f/%3d KG", (float)stat.value / 10.0f, stat.maxValue / 10);

			drawText(image, CVec2(148, 161), eFontStandard, temp, loadColor);

			[...]
		}
				
But that was the easy part. Apart from the color of the text, the overload level also have an
effect on various parameters. Mostly on the stamina level - as we will see in a future part it is
quite buggy in the original game - but on the movement speed too.

When the overload is at the first level for all the champions, the speed is the same as we saw since
the beginning of this project. But when a character is more loaded, the movement is slowed down.
That's to say there is a timer that avoids you to move immediately when you press a key or click
on an arrow.
So in CInterface::updateArrows(), instead of moving directly, we will call a setDelayedArrow() function:

		if (isClickingOnArrow(clickedArea, eMouseArea_ArrowForward) == true || keyboard.keys[CKeyboard::eForward] == true)
		{
			arrowHighlightTime = ARROWS_HIGHLIGHT_TIME;
			currentArrow = eArrowForward;
			setDelayedArrow(eArrowForward);
		}
		else if (isClickingOnArrow(clickedArea, eMouseArea_ArrowBackward) == true || keyboard.keys[CKeyboard::eBackward] == true)
		{
			arrowHighlightTime = ARROWS_HIGHLIGHT_TIME;
			currentArrow = eArrowBackward;
			setDelayedArrow(eArrowBackward);
		}
		else if (isClickingOnArrow(clickedArea, eMouseArea_ArrowLeft) == true || keyboard.keys[CKeyboard::eLeft] == true)
		{
			arrowHighlightTime = ARROWS_HIGHLIGHT_TIME;
			currentArrow = eArrowLeft;
			setDelayedArrow(eArrowLeft);
		}
		else if (isClickingOnArrow(clickedArea, eMouseArea_ArrowRight) == true || keyboard.keys[CKeyboard::eRight] == true)
		{
			arrowHighlightTime = ARROWS_HIGHLIGHT_TIME;
			currentArrow = eArrowRight;
			setDelayedArrow(eArrowRight);
		}
		else if (isClickingOnArrow(clickedArea, eMouseArea_ArrowTurnLeft) == true || keyboard.keys[CKeyboard::eTurnLeft] == true)
		{
			arrowHighlightTime = ARROWS_HIGHLIGHT_TIME;
			currentArrow = eArrowTurnLeft;
			player.turnLeft();
		}
		else if (isClickingOnArrow(clickedArea, eMouseArea_ArrowTurnRight) == true || keyboard.keys[CKeyboard::eTurnRight] == true)
		{
			arrowHighlightTime = ARROWS_HIGHLIGHT_TIME;
			currentArrow = eArrowTurnRight;
			player.turnRight();
		}
				
This function only sets 2 variables. delayedArrow is the id of the arrow we clicked and delayedArrowTime
is a counter like the one we used to time the highlighting of the arrows.

		void    CInterface::setDelayedArrow(EArrows arrow)
		{
			delayedArrow = arrow;

			int maxOverload = 0;
			for (int i = 0; i < 4; ++i)
			{
				int overload = game.characters[i].overload();

				if (overload > maxOverload)
					maxOverload = overload;
			}

			if (maxOverload == 0)
				delayedArrowTime = OVERLOAD0_DELAY;
			else if (maxOverload == 1)
				delayedArrowTime = OVERLOAD1_DELAY;
			else
				delayedArrowTime = OVERLOAD2_DELAY;
		}
				
So in updateArrow(), if delayedArrowTime is not equal to 0, we won't test the keyboard and arrows, and at the end
of this function, we will decrement delayedArrowTime and really execute the movement if we get to 0.

		void   CInterface::updateArrows(SMouseArea* clickedArea)
		{
			if (delayedArrowTime == 0)
			{
				if (isClickingOnArrow...
				[...]
			}

			// delayed movements
			if (delayedArrowTime != 0)
			{
				delayedArrowTime--;

				if (delayedArrowTime == 0)
				{
					switch(delayedArrow)
					{
						case eArrowForward:
							player.moveForward();
							break;

						case eArrowBackward:
							player.moveBackward();
							break;

						case eArrowLeft:
							player.moveLeft();
							break;

						case eArrowRight:
							player.moveRight();
							break;

						default:
							break;
					}
				}
			}
		}
				
In the map you will see that I added a bunch of objects so that you can test the overloading.

Objects infos

I also coded the info window that appears when you put an object on the eye.


The code is simply a matter of drawing images and texts, so I won't explain it in detail.
Just note that some objects have 2 lines of texts - the waterskin and consumable objects - but most
of them only have their weight displayed.

I put this info window and the info window for the character's stats in a new function called drawInfos:

		void    CInterface::drawInfos(QImage* image)
		{
			// draw info image
			QImage  infoBg = fileCache.getImage("gfx/interface/EmptyInfo.png");
			graph2D.drawImage(image, CVec2(80, 85), infoBg);

			int objType = mouse.mObjectInHand.getType();

			if (objType == 0)
			{
				// charater's infos
				[...]
			}
			else
			{
				// object's infos
				QImage  eye = fileCache.getImage("gfx/interface/Eye.png");
				graph2D.drawImage(image, CVec2(83, 90), eye);

				QImage  circle = fileCache.getImage("gfx/interface/Circle.png");
				graph2D.drawImage(image, CVec2(106, 87), circle);

				CObjects::CObjectInfo&  infos = objects.mObjectInfos[objType - 1];
				QImage  objSheet = fileCache.getImage(infos.imageFile.toLocal8Bit().constData());
				CRect   objRect = interface.getItemRect(infos.imageNum);
				graph2D.drawImageAtlas(image, CVec2(111, 92), objSheet, objRect);

				// object name
				drawText(image, CVec2(134, 98), eFontStandard, infos.name.c_str(), DEFAULT_TEXT_COLOR);

				int y = 116;

				if (objType == 144)
				{
					// waterskin full
					drawText(image, CVec2(108, y), eFontStandard, getText("OBJ_INFO00").c_str(), DEFAULT_TEXT_COLOR);
					y += 7;
				}
				else if (objType == 145)
				{
					// waterskin empty
					drawText(image, CVec2(108, y), eFontStandard, getText("OBJ_INFO03").c_str(), DEFAULT_TEXT_COLOR);
					y += 7;
				}
				else if (infos.positions.contains("c"))
				{
					// consumable
					drawText(image, CVec2(108, y), eFontStandard, getText("OBJ_INFO04").c_str(), DEFAULT_TEXT_COLOR);
					y += 7;
				}

				// weight
				static char temp[256];
				sprintf(temp, "%s %.1f KG.", getText("OBJ_INFO05").c_str(), (float)infos.weight / 10.0f);
				drawText(image, CVec2(108, y), eFontStandard, temp, DEFAULT_TEXT_COLOR);
			}
		}