Part 50: Eating and drinking
Downloads
Champion's 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
#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().
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
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.
//------------------------------------------------------
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.
Items food and water values
<!-- 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
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"
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
// 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
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
// 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
<!-- 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.
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.
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
Fountains
<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.
else if (clickedArea->type == eMouseAreaG_Fountain)
{
fillOrDrink();
}
This fillOrDrink() handles all the actions that happens when we click on this fountain:
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