Part 31: Time, light and mana
Downloads
Rethinking the time
if (counter != 0)
{
counter--;
if (counter == 0)
{
[...]
}
}
We will replace them with a new class called CTimer.
bool CTimer::update()
{
int speed = 1;
if (mTime != 0)
{
if (mFollowGameTime)
speed = getGameTimeFactor();
mTime -= speed;
if (mTime <= 0)
{
mTime = 0;
return true;
}
}
return false;
}
The only difference here is that we use a getGameTimeFactor function that returns us the speed at which the time
int CTimer::getGameTimeFactor()
{
if (interface.isGamePaused() == true)
return 0;
else if (interface.isSleeping() == true)
return 4;
return 1;
}
I also used this function for the speed of the doors, as they don't use timers.
Light
void CGraphics2D::darkenRect(QImage* image, float opacity, CRect destRect)
{
QImage shadowImage(image->width(), image->height(), QImage::Format_ARGB32);
shadowImage.fill(QColor(0, 0, 0, 0));
rect(&shadowImage, destRect, QColor(0, 0, 0), true);
QPainter painter(image);
painter.setOpacity(opacity);
painter.setCompositionMode(QPainter::CompositionMode_SourceAtop);
painter.drawImage(QPoint(0, 0), shadowImage);
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
painter.setOpacity(1.0f);
painter.end();
}
We will use it directly on the screen image.
void CGame::displayMainView(QImage* image)
{
if (interface.isInInventory() == false &&
interface.isGamePaused() == false &&
interface.isSleeping() == false)
{
CRect mainRect(CVec2(0, 33), CVec2(MAINVIEW_WIDTH - 1, 33 + MAINVIEW_HEIGHT - 1));
[...]
// light
if (currentLevel != 1)
{
float level = getLightLevel();
if (level != MAX_LIGHT_LEVEL)
graph2D.darkenRect(image, 1.0f - level, mainRect);
}
}
}
There are 2 main sources of light in the game: torches and spells - and yes, I forgot the illumulet...
Torches
<!-- TORCH -->
<item name="ITEM003">
[...]
<param type="int">Charges</param>
</item>
This implies small modifications to the map editor as it didn't handle int parameters in objects.
void CObjects::setDefaultParams(CObject* obj)
{
if (obj->getType() == OBJECT_TYPE_TORCH)
{
obj->setIntParam("Charges", TORCH_MAX_CHARGES);
}
}
To update their charges I had to convert them to a CTimer to use the time control that we talked about.
void CCharacter::update()
{
if (bodyObjects[eBodyPartLeftHand].getType() == OBJECT_TYPE_TORCH)
{
CTimer t;
t.set(bodyObjects[eBodyPartLeftHand].getIntParam("Charges"), true);
t.update();
bodyObjects[eBodyPartLeftHand].setIntParam("Charges", t.get());
}
if (bodyObjects[eBodyPartRightHand].getType() == OBJECT_TYPE_TORCH)
{
CTimer t;
t.set(bodyObjects[eBodyPartRightHand].getIntParam("Charges"), true);
t.update();
bodyObjects[eBodyPartRightHand].setIntParam("Charges", t.get());
}
[...]
}
Torches also have 4 different images when you hold them in your hand depending on their level, so I had to
void CInterface::drawBodyPart(QImage* image, CVec2 pos, int championNum, CCharacter::EBodyParts part, bool enableArea)
{
[...]
if (objType != 0)
{
[...]
// torch
if (part == CCharacter::eBodyPartRightHand ||
part == CCharacter::eBodyPartLeftHand)
{
if (objType == OBJECT_TYPE_TORCH)
{
int charges = obj->getIntParam("Charges");
if (charges > 0)
imageNum = 5 + ((charges - 1) * 3) / TORCH_MAX_CHARGES;
}
}
CRect rect = getItemRect(imageNum);
graph2D.drawImageAtlas(image, pos, objImage, rect);
}
[...]
}
Now, to calculate the light power, the original game took the 5 most powerful torches in the champions' hands.
float CGame::getLightLevel()
{
int power = 0;
// get the powers of the torches in che champions' hands
int torches[8];
int numTorches = 0;
CObject* obj;
for (int i = 0; i < 4; ++i)
{
CCharacter* c = &game.characters[i];
obj = &c->bodyObjects[CCharacter::eBodyPartLeftHand];
if (obj->getType() == OBJECT_TYPE_TORCH)
torches[numTorches++] = obj->getIntParam("Charges");
obj = &c->bodyObjects[CCharacter::eBodyPartRightHand];
if (obj->getType() == OBJECT_TYPE_TORCH)
torches[numTorches++] = obj->getIntParam("Charges");
}
// sort the torches
for (int i = 0; i < numTorches - 1; ++i)
for (int j = i + 1; j < numTorches; ++j)
if (torches[i] < torches[j])
{
int temp = torches[i];
torches[i] = torches[j];
torches[j] = temp;
}
// get the 5 most powerful torches (as in the original game)
for (int i = 0; i < MIN(numTorches, 5); ++i)
power += (torches[i] >> i) / 545;
[...]
}
Light spells
enum ESpells
{
eSpell_MagicTorch,
eSpell_Light,
eSpell_Darkness,
eSpell_Count
};
class CSpell
{
public:
int getPower();
[...]
ESpells spell;
int power;
CTimer time;
int decreaseTime;
};
The variables name are self explantory:
int CSpell::getPower()
{
if (time.get() >= decreaseTime)
return power;
else
return (power * time.get()) / decreaseTime;
}
We will keep a list of all the active spells in a new class CSpells:
class CSpells
{
public:
CSpells();
void cast(int champion);
void update();
[...]
std::vector<CSpell> mActiveSpells;
};
extern CSpells spells;
The update() function obviously decreases the timer of each spell and removes it from the list when it
void CSpells::cast(int champion)
{
CCharacter* c = &game.characters[champion];
char* spell = c->spell;
int level = spell[0] - 'a';
// magic torch
if (strcmp(&spell[1], "j") == 0)
{
CSpell s;
s.spell = eSpell_MagicTorch;
s.power = (level + 4) * 16;
s.decreaseTime = (level + 4) * 60;
s.time.set(41250 + level * 8000 + s.decreaseTime, true);
mActiveSpells.push_back(s);
return;
}
// light
else if (strcmp(&spell[1], "ipw") == 0)
{
CSpell s;
s.spell = eSpell_MagicTorch;
s.power = (level * 2 + 4) * 16;
s.decreaseTime = (level * 2 + 4) * 60;
s.time.set(156250 + level * 32000 + s.decreaseTime, true);
mActiveSpells.push_back(s);
return;
}
// darkness
else if (strcmp(&spell[1], "kpx") == 0)
{
CSpell s;
s.spell = eSpell_MagicTorch;
s.power = -(level + 3) * 16;
s.decreaseTime = (level + 3) * 60;
s.time.set(1531 + s.decreaseTime, true);
mActiveSpells.push_back(s);
return;
}
std::string message = interface.setChampionNameInString(champion, "MESSAGE01");
interface.addMessage(message, 4);
}
The spells will never fail - at the moment we don't check the skill level of the caster.
// magical spells
for (size_t i = 0; i < spells.mActiveSpells.size(); ++i)
{
CSpell* s = &spells.mActiveSpells[i];
if (s->spell == eSpell_MagicTorch ||
s->spell == eSpell_Light ||
s->spell == eSpell_Darkness)
{
power += s->getPower();
}
}
Final light calculation
// compute the global light level
float level = ((float)power / 256.0f) * (MAX_LIGHT_LEVEL - MIN_LIGHT_LEVEL) + MIN_LIGHT_LEVEL;
if (level > MAX_LIGHT_LEVEL)
level = MAX_LIGHT_LEVEL;
return level;
Note that I didn't set the MIN_LIGHT_LEVEL to 0 to avoid a completely black screen.
Mana
int CSpells::getMana(int champion, char symbol)
{
CCharacter* c = &game.characters[champion];
char* spell = c->spell;
if (spell[0] == 0)
{
return symbol - 'a' + 1;
}
else
{
int level = spell[0] - 'a';
static const int costs[] =
{
2, // g = Ya
3, // h = Vi
4, // i = Oh
5, // j = Ful
6, // k = Des
7, // l = Zo
4, // m = Ven
5, // n = Ew
6, // o = Kath
7, // p = Ir
7, // q = Bro
9, // r = Gor
2, // s = Ku
2, // t = Ros
3, // u = Dain
4, // v = Neta
6, // w = Ra
7 // x = Sar
};
return (costs[symbol - 'g'] * (level + 2)) / 2;
}
}
And now that we can lose mana, we also need to regenerate it.
void CCharacter::update()
{
[...]
// mana regeneration
if (manaRegenTimer.update() == true)
{
if (portrait != -1)
if (stats[eStatMana].value < stats[eStatMana].maxValue)
stats[eStatMana].value++;
[...]
manaRegenTimer.set(MANA_REGEN_TIMER, true);
[...]
}
}
But even if the time runs 4 times faster when the champions sleep, that was not enough.
void CCharacter::enterSleep()
{
manaRegenTimer.set(MANA_REGEN_TIMER_SLEEP, true);
}
void CCharacter::exitSleep()
{
manaRegenTimer.set(MANA_REGEN_TIMER, true);
}
Save and other things