Partie 31: Temps, lumière et mana
Téléchargements
Repenser le temps
if (counter != 0)
{
counter--;
if (counter == 0)
{
[...]
}
}
On va les remplacer par une nouvelle classe appelée 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;
}
La seule différence ici, c'est qu'on a utilisé une fonction getGameTimeFactor() qui nous renvoie la vitesse à
int CTimer::getGameTimeFactor()
{
if (interface.isGamePaused() == true)
return 0;
else if (interface.isSleeping() == true)
return 4;
return 1;
}
J'ai aussi utilisé cette fonction pour la vitesse des portes, comme elle n'utilisent pas de timer.
Lumière
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();
}
On va l'utiliser directement sur l'écran.
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));
[...]
// lumière
if (currentLevel != 1)
{
float level = getLightLevel();
if (level != MAX_LIGHT_LEVEL)
graph2D.darkenRect(image, 1.0f - level, mainRect);
}
}
}
Il y a 2 source principales de lumière dans le jeu: les torches et les sorts (ah oui, j'ai oublié l'illumulette...).
Les torches
<!-- TORCHE -->
<item name="ITEM003">
[...]
<param type="int">Charges</param>
</item>
Ca implique de petites modifications dans l'éditeur de niveau comme il ne gérait pas les paramètres int dans les
void CObjects::setDefaultParams(CObject* obj)
{
if (obj->getType() == OBJECT_TYPE_TORCH)
{
obj->setIntParam("Charges", TORCH_MAX_CHARGES);
}
}
Pour mettre à jour leurs charges j'ai du les convertir en CTimer pour utiliser le contrôle du temps dont on a parlé
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());
}
[...]
}
Les torches ont aussi 4 images différentes quand vous les tenez dans la main, qui dépendent de leur niveau, alors
void CInterface::drawBodyPart(QImage* image, CVec2 pos, int championNum, CCharacter::EBodyParts part, bool enableArea)
{
[...]
if (objType != 0)
{
[...]
// torche
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);
}
[...]
}
Maintenant pour calculer la puissance de la lumière, le jeu d'origine prenait les 5 torches les plus puissantes
float CGame::getLightLevel()
{
int power = 0;
// gère la puissance des torches dans les amins des champions
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");
}
// trie les 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;
}
// prend les 5 torches les plus puissantes (comme dans le jeu d'origine)
for (int i = 0; i < MIN(numTorches, 5); ++i)
power += (torches[i] >> i) / 545;
[...]
}
Sorts de lumière
enum ESpells
{
eSpell_MagicTorch,
eSpell_Light,
eSpell_Darkness,
eSpell_Count
};
class CSpell
{
public:
int getPower();
[...]
ESpells spell;
int power;
CTimer time;
int decreaseTime;
};
Les noms des variables parlent d'eux-même:
int CSpell::getPower()
{
if (time.get() >= decreaseTime)
return power;
else
return (power * time.get()) / decreaseTime;
}
On va stocker une liste de tous les sorts actifs dans une nouvelle classe CSpells:
class CSpells
{
public:
CSpells();
void cast(int champion);
void update();
[...]
std::vector<CSpell> mActiveSpells;
};
extern CSpells spells;
Evidemment, la fonction update() décrémente les timers de chaque sort et les retire de la liste quand ils arrivent
void CSpells::cast(int champion)
{
CCharacter* c = &game.characters[champion];
char* spell = c->spell;
int level = spell[0] - 'a';
// torche magique
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;
}
// lumière
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;
}
// ténèbres
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);
}
Les sorts ne rateront jamais (pour le moment on ne teste pas le niveau de compétence du lanceur).
// sorts
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();
}
}
Calcul final de la lumière
// calcule le niveau global de lumière
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;
Remarquez que je n'ai pas mis MIN_LIGHT_LEVEL à 0 pour éviter d'avoir un écran complètement noir.
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;
}
}
Et maintenant qu'on peut perdre du mana, on doit aussi le régénérer.
void CCharacter::update()
{
[...]
// régénération de mana
if (manaRegenTimer.update() == true)
{
if (portrait != -1)
if (stats[eStatMana].value < stats[eStatMana].maxValue)
stats[eStatMana].value++;
[...]
manaRegenTimer.set(MANA_REGEN_TIMER, true);
[...]
}
}
Mais même si le temps s'écoule 4 fois plus vite quand les champions dorment, ça ne a suffisait pas.
void CCharacter::enterSleep()
{
manaRegenTimer.set(MANA_REGEN_TIMER_SLEEP, true);
}
void CCharacter::exitSleep()
{
manaRegenTimer.set(MANA_REGEN_TIMER, true);
}
Sauvegarde et choses diverses