Part 50: Eating and drinking

Downloads

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

Although there is no change in the editor code in this part, I made an exe of it because the database files changed
a lot since the last time.

Champion's food and water

Each champion has an amount of food and water.

		class CCharacter
		{
		public:
			[...]
			int         food;
			int         water;
			[...]
		}
				
In the original game these values went from 2048 when the champion is "full" of food and water to -1024 when he is
thirsty and hungry.

		#define MIN_FOOD    -1024
		#define MAX_FOOD    2048
				
These values are initialized with 1500 + RANDOM(256) when we add the champion to our party.

		void CCharacter::fromDB(int num)
		{
			[...]
			// food, water
			food = 1500 + RANDOM(256);
			water = 1500 + RANDOM(256);
				
And obviously they are saved in the CCharacter::save() when we save the game, and loaded in CCharacter::load().

These values appear as bars in the character's sheet.


When the value is below 0 the bar appears yellow.


And when it is below -512 the bar appears red.


"Food" and "water" are one of the few texts of the game that are stored as images.
That's a bad habit if you want to translate your game in another language.

All these informations are displayed by the drawFood() function in interface:

		void    CInterface::drawFood(QImage* image)
		{
			CCharacter* c = &game.characters[currentChampion];

			// frame
			CRect   frame(CVec2(104, 85), CVec2(218, 157));
			graph2D.rect(image, frame, INVENTORY_SLOT_COLOR, false);

			// food text
			QImage  foodText = fileCache.getImage("gfx/interface/Food.png");
			graph2D.drawImage(image, CVec2(112, 93), foodText);

			int foodSize = ((c->food - MIN_FOOD) * (208 - 113)) / (MAX_FOOD - MIN_FOOD + 1);

			if (foodSize > 0)
			{
				// food bar shadow
				QColor foodColor;
				if (c->food < -512)
					foodColor = RED;
				else if (c->food < 0)
					foodColor = YELLOW;
				else
					foodColor = FOOD_COLOR;

				CRect   rectFoodShadow(CVec2(115, 104), CVec2(115 + foodSize - 1, 109));
				graph2D.rect(image, rectFoodShadow, BLACK, true);

				// food bar
				CRect   rectFoodBar(CVec2(113, 102), CVec2(113 + foodSize - 1, 107));
				graph2D.rect(image, rectFoodBar, foodColor, true);
			}

			// water text
			QImage  waterText = fileCache.getImage("gfx/interface/Water.png");
			graph2D.drawImage(image, CVec2(112, 116), waterText);

			int waterSize = ((c->water - MIN_FOOD) * (208 - 113)) / (MAX_FOOD - MIN_FOOD + 1);

			if (waterSize > 0)
			{
				// water bar shadow
				QColor waterColor;
				if (c->water < -512)
					waterColor = RED;
				else if (c->water < 0)
					waterColor = YELLOW;
				else
					waterColor = WATER_COLOR;

				CRect   rectWaterShadow(CVec2(115, 127), CVec2(115 + waterSize - 1, 132));
				graph2D.rect(image, rectWaterShadow, BLACK, true);

				// water bar
				CRect   rectWaterBar(CVec2(113, 125), CVec2(113 + waterSize - 1, 130));
				graph2D.rect(image, rectWaterBar, waterColor, true);
			}
		}
				

Food and water decreasing

The amount of food and water of each character decreases with time so I set up timers in the CCharracter class that
work just like the ones we had for the mana and health regeneration.

		class CCharacter
		{
			CTimer  foodTimer;
			CTimer  waterTimer;
				
These timers are initialized in fromDB()

		void CCharacter::fromDB(int num)
		{
			[...]
			// food, water
			food = 1500 + RANDOM(256);
			water = 1500 + RANDOM(256);
			foodTimer.set(FOOD_TIMER, true);
			waterTimer.set(WATER_TIMER, true);
				
We update them in CCharacter::update() and when they finish we decrement the food and water values.

		void CCharacter::update()
		{
			[...]
			// food, water
			if (foodTimer.update() == true)
			{
				food--;
				if (food < MIN_FOOD)
					food = MIN_FOOD;

				if (interface.isSleeping() == false)
					foodTimer.set(FOOD_TIMER, true);
				else
					foodTimer.set(FOOD_TIMER_SLEEP, true);
			}

			if (waterTimer.update() == true)
			{
				water--;
				if (water < MIN_FOOD)
					water = MIN_FOOD;

				if (interface.isSleeping() == false)
					waterTimer.set(WATER_TIMER, true);
				else
					waterTimer.set(WATER_TIMER_SLEEP, true);
			}
				
And as you can see they have different values when the party is sleeping or not.
So we restart them in enterSleep() and exitSleep() too:

		//------------------------------------------------------
		void CCharacter::enterSleep()
		{
			[...]
			foodTimer.set(FOOD_TIMER_SLEEP, true);
			waterTimer.set(WATER_TIMER_SLEEP, true);
		}

		//------------------------------------------------------
		void CCharacter::exitSleep()
		{
			[...]
			foodTimer.set(FOOD_TIMER, true);
			waterTimer.set(WATER_TIMER, true);
		}
				
In the original game the food and water decrease followed a complex rule based on the stamina level.
And in turns they had an effect on the stamina, because when they are in the red zone the maximum stamina of the
champion decreased. But we'll talk about that in another part.

Items food and water values

In items.xml, I added a food an a water value to the objects:

		<!-- WATER FLASK -->
		<item name="ITEM117">
			[...]
			<water>1600</water>
		</item>

		[...]

		<!-- APPLE -->
		<item name="ITEM120">
			[...]
			<food>500</food>
		</item>
				
We read them in readObjectsDB():

		void    CObjects::readObjectsDB()
		{
			[...]
			object.food = 0;
			object.water = 0;

			while(!itemInfo.isNull())
			{
				[...]
				else if (itemInfo.tagName() == "food")
				{
					object.food = itemInfo.text().toInt();
				}
				else if (itemInfo.tagName() == "water")
				{
					object.water = itemInfo.text().toInt();
				}
				

The mouth

The mouth icon in the character sheet has some peculiarities.
First, when the value of food or water is negative, we draw a red frame around it:



		void    CInterface::drawInventory(QImage* image)
		{
			[...]
			// red frame around the mouth
			if (c->food < 0 || c->water < 0)
			{
				QImage  redFrame = fileCache.getImage("gfx/interface/BorderRed.png");
				graph2D.drawImage(image, CVec2(55, 45), redFrame);
			}
				
Then we want to add a mouse area for it, as when we will click on it with food or water in our hand we will "eat"
or drink accordingly. So in drawInventory we can add:

		if (isResurrecting == false)
		{
			CRect   mouthPosRect(CVec2(56, 46), CVec2(71, 61));
			mouse.addArea(eMouseArea_Mouth, mouthPosRect, eCursor_Hand);
		}
				
Drinking is quite simple. If we either have a water flask in our hand, or a waterskin, we add it's value to the
water field of the character and play a sound.
After that the water flask turns to an empty flask.
The water skin has some charges like a torch. Because we can drink 3 times before it turns to an empty skin.
We'll talk about the initialization of these charges later.
So in CInterface::update() we will call a eat function when we click on the mouse.

		// mouth
		case eMouseArea_Mouth:
			eat();
		break;
				
And in this eat() function we will have code to drink:

		void   CInterface::eat()
		{
			if (mouse.mObjectInHand.getType() != 0)
			{
				int objectType = mouse.mObjectInHand.getType();
				CObjects::CObjectInfo  object = objects.mObjectInfos[objectType];
				CCharacter* c = &game.characters[currentChampion];

				if (object.food != 0)
				{
					[...]
				}
				else if (object.water != 0)
				{
					if (objectType == OBJECT_TYPE_WATERFLASK)
					{
						c->water += object.water;
						c->water = MIN(c->water, MAX_FOOD);
						sound->play(player.pos, "sound/Swallow.wav");
						mouse.mObjectInHand.setType(OBJECT_TYPE_EMPTYFLASK);
					}
					else if (objectType == OBJECT_TYPE_WATERSKIN)
					{
						c->water += object.water;
						c->water = MIN(c->water, MAX_FOOD);
						sound->play(player.pos, "sound/Swallow.wav");

						int charges = mouse.mObjectInHand.getIntParam("Charges");
						charges--;
						mouse.mObjectInHand.setIntParam("Charges", charges);

						if (charges == 0)
							mouse.mObjectInHand.setType(OBJECT_TYPE_WATERSKIN_EMPTY);
					}

					c->updateLoad();
				}
			}
		}
				
Eating is a little bit different, as when we click on the mouth we first launch a small animation, and we wait the
end of this animation before really eating the object.
This animation is basic. We simply switch between two mouth positions of the sprite sheet that contains the body
parts.


So when we click on the mouth, we will simply start a timer and store the food value we want to add:

		void   CInterface::eat()
		{
				[...]
				if (object.food != 0)
				{
					eatValue = object.food;
					eatTimer.set(EAT_ANIMATION_TIME, false);
					mouse.mObjectInHand.setType(0);
					c->updateLoad();
				}
				
Then in the drawInventory() function, we will update this timer, draw the appropriate image for the mouth and
finally increment the food field of the character.

		// mouth
		if (eatTimer.isRunning() == false)
		{
			if (isResurrecting == false)
			{
				CRect   mouthPosRect(CVec2(56, 46), CVec2(71, 61));
				mouse.addArea(eMouseArea_Mouth, mouthPosRect, eCursor_Hand);
			}
		}
		else
		{
			if (eatTimer.update() == false)
			{
				// mouth animation
				if ((eatTimer.get() / 8) % 2 == 1)
				{
					CRect   imageRect = getItemRect(14);
					graph2D.drawImageAtlas(image, CVec2(56, 46), bodyParts, imageRect);
				}
			}
			else
			{
				// eat at the end of anim
				CCharacter* c = &game.characters[currentChampion];
				c->food += eatValue;
				c->food = MIN(c->food, MAX_FOOD);
				sound->play(player.pos, "sound/Swallow.wav");
			}
		}
				

Waterskins

As we said waterskins have a number of charges, like torches.
So I added this parameter to items.xml, this way you can set it in the editor:

			<!-- WATERSKIN (FULL) -->
			<item name="ITEM144">
				[...]
				<water>800</water>
				<param type="int">Charges</param>
			</item>
				
Throughout the game, when you find a waterskin it is either completely empty or completely full.
So to make sure that this parameter is initialized even for objects that are generated, I set it in the
setDefaultParams() function:

		void CObjects::setDefaultParams(CObject* obj)
		{
			if (obj->getType() == OBJECT_TYPE_TORCH)
			{
				obj->setIntParam("Charges", TORCH_MAX_CHARGES);
			}
			else if (obj->getType() == OBJECT_TYPE_WATERSKIN)
			{
				obj->setIntParam("Charges", WATERSKIN_MAX_CHARGES);
			}
		}
				
Waterskins also have another peculiarity: their weight changes with their charge.
So I had to write a functions that returns the weight of an object according to this rule and use it everywhere
we used directly the "weight" variable of the CObjectInfo class:

		int CObjects::getWeight(CObject* obj)
		{
			int type = obj->getType();

			if (type == OBJECT_TYPE_WATERSKIN)
				return mObjectInfos[type].weight - 6 + obj->getIntParam("Charges") * 2;
			else
				return mObjectInfos[type].weight;
		}
				
Notice also that in the previous functions we called the updateLoad() function every time we ate or drank something
to reflect this action on the current load of the character.

Fountains

We already used a fountain for a lock, now we will create the real fountain object.
First we have to add it to walls.xml:

		<wall name="Fountain">
			<image>Fountain.png</image>
		</wall>
				
Then we draw it in CGame::drawWall():

		void CGame::drawWall(QImage* image, CVec2 mapPos, CVec2 tablePos, EWallSide side, bool flip)
		{
			[...]
			else if (wall->getType() == eWallFountain)
			{
				//------------------------------------------------------------------------------
				// fountain
				CRect   rect = walls.drawOrnate(image, tablePos, side, ORNATE_FOUNTAIN);

				if (tablePos == CVec2(2, 3) && side == eWallSideUp)
					mouse.addArea(eMouseAreaG_Fountain, rect, eCursor_Hand, (void*)mapPos.x, (void*)mapPos.y, (void*)playerSide);
			}
				
The mouse area is tested in CGame::update() and calls a fillOrDrink() function.
Note that we also call it for the "fountain lock" that we already had.

		else if (clickedArea->type == eMouseAreaG_Fountain)
		{
			fillOrDrink();
		}
				
This fillOrDrink() handles all the actions that happens when we click on this fountain:
We can either fill a water flask or a waterskin.
Or if our hand is empty, the selected champion drinks:

		void CGame::fillOrDrink()
		{
			int type = mouse.mObjectInHand.getType();

			if (type == OBJECT_TYPE_WATERSKIN || type == OBJECT_TYPE_WATERSKIN_EMPTY)
			{
				mouse.mObjectInHand.setType(OBJECT_TYPE_WATERSKIN);
				mouse.mObjectInHand.setIntParam("Charges", 3);
			}
			else if (type == OBJECT_TYPE_EMPTYFLASK)
			{
				mouse.mObjectInHand.setType(OBJECT_TYPE_WATERFLASK);
			}
			else if (type == 0)
			{
				characters[interface.selectedChampion].water = MAX_FOOD;
				sound->play(player.pos, "sound/Swallow.wav");
			}
		}
				
I added all the fountains of the second level in the map.


Bugs

While I was writing the getWeight function I found a bug in the calculation of the damages a champion deals with a
melee attack.
It uses the function getStrength() that needs the weight of the current weapon to compute the strength of the
attack. But we did not pass it the right object.
So combat difficulty should look more like the original game now.

I also fixed a bug that made the program crash when you clicked below the last attack of a weapon.