Partie 42: Monstres partie 4: Probabilité de touche et dégats
Téléchargements
Paramètres des monstres
<monsters>
[...]
<!-- 01 Scorpion Géant -->
<monster name = "MONSTER01">
[...]
<defense>55</defense>
<health>150</health>
<attack>150</attack>
<dexterity>55</dexterity>
[...]
</monster>
<!-- 02 Démon Baveux -->
<monster name = "MONSTER02">
[...]
<defense>20</defense>
<health>110</health>
<attack>80</attack>
<dexterity>20</dexterity>
[...]
</monster>
int CMonsters::getStartingLife(int type)
{
int baseHealth = monstersDatas[type].baseHealth;
return baseHealth * game.levelMultiplier() + RANDOM((baseHealth / 4) + 1);
}
game.levelMultiplier() est une valeur qui sera utilisée dans divers calculs tout au long du jeu.
int CGame::levelMultiplier()
{
static const int multipliers[] =
{
0, 1, 1, 2, 2, 2, 3, 3, 3, 4, 5, 5, 6, 6
};
return multipliers[currentLevel - 1];
}
La ligne "avant" des monstres
// retourne le rang d'un monstre en fonction de la position du joueur (0 = avant, 1 = centre, 2 = arrière)
uint8_t CMonsterGroup2::getMonsterRow(CVec2 mapPos, EMonsterPos monsterPos)
{
static const uint8_t monsterRow[4][5] =
{
// NW NE SW SE C
{ 0, 0, 2, 2, 1}, // haut
{ 0, 2, 0, 2, 1}, // gauche
{ 2, 2, 0, 0, 1}, // bas
{ 2, 0, 2, 0, 1} // droite
};
int playerPosFromMonster;
if (player.pos.x < mapPos.x)
playerPosFromMonster = 1; // gauche
else if (player.pos.x > mapPos.x)
playerPosFromMonster = 3; // droite
else if (player.pos.y < mapPos.y)
playerPosFromMonster = 0; // haut
else
playerPosFromMonster = 2; // bas
return monsterRow[playerPosFromMonster][monsterPos - eMonsterPosNorthWest];
}
Ensuite, dans le groupe, avant d'appeler les updates de chaque monstre, on va utiliser ça pour tester si la
void CMonsterGroup2::update(CVec2 mapPos, uint8_t type)
{
// vérifie qu'il y a ait au moins un monstre vivant dans la ligne de front du groupe (ou au centre)
bool isFrontRowRowAlive = false;
for (int i = 0; i < 4; ++i)
{
if (monsters[i].pos != eMonsterPosNone &&
getMonsterRow(mapPos, monsters[i].pos) < 2)
{
isFrontRowRowAlive = true;
break;
}
}
Puis quand on update les monstes, on peut leur dire s'ils peuvent attaquer.
void CMonsterGroup2::update(CVec2 mapPos, uint8_t type)
{
[...]
// met à jour tous les monstres du groupe
for (int i = 0; i < 4; ++i)
if (monsters[i].pos != eMonsterPosNone)
{
uint8_t row = getMonsterRow(mapPos, monsters[i].pos);
bool isInFront = (isFrontRowRowAlive == true && row < 2) ||
(isFrontRowRowAlive == false && row == 2);
monsters[i].update(*this, mapPos, type, isInFront);
}
Choisir une cible
void CMonster::fight(CVec2 mapPos, uint8_t type, bool isInFront)
{
if (nextAttackTimer.update() == true)
{
// si le monstre est dans la ligne avant du groupe
if (isInFront == true)
{
// joue le son d'attaqued
[...]
// affiche l'animation du monstre
[...]
// choisit un champion cible
int target = chooseTarget(mapPos);
Ce champion devra être en première ligne du groupe du joueur.
// retourne le rang d'un champion en fonction de la position d'un groupe de monstres (0 = avant, 1 = arrière)
uint8_t CMonsterGroup2::getChampionRow(CVec2 mapPos, int championNum)
{
static const uint8_t championRow[4][4] =
{
// 1 2 3 4
{ 0, 0, 1, 1}, // haut
{ 0, 1, 0, 1}, // gauche
{ 1, 1, 0, 0}, // bas
{ 1, 0, 1, 0} // droite
};
CVec2 localMonsterPos = player.getLocalFromPos(mapPos);
int monsterPosFromPlayer;
if (localMonsterPos.x < 0)
monsterPosFromPlayer = 1; // gauche
else if (localMonsterPos.x > 0)
monsterPosFromPlayer = 3; // droite
else if (localMonsterPos.y < 0)
monsterPosFromPlayer = 0; // haut
else
monsterPosFromPlayer = 2; // bas
return championRow[monsterPosFromPlayer][championNum];
}
Ensuite, dans la fonction chooseTarget() on a aussi besoin de tester si la première ligne de champions est toujours
// choisit le champion que ce monstre va attaquer
uint8_t CMonster::chooseTarget(CVec2 mapPos)
{
int rowToSearch;
// vérifie qu'il y a au moins un champion vivant dans la ligne de front
rowToSearch = 1;
for (int i = 0; i < 4; ++i)
{
if (interface.isChampionEmpty(i) == false &&
interface.isChampionDead(i) == false &&
CMonsterGroup2::getChampionRow(mapPos, i) == 0)
{
rowToSearch = 0;
break;
}
}
// liste les cibles possibles
uint8_t possibleTargets[2];
int nbTargets = 0;
for (int i = 0; i < 4; ++i)
{
if (interface.isChampionEmpty(i) == false &&
interface.isChampionDead(i) == false &&
CMonsterGroup2::getChampionRow(mapPos, i) == rowToSearch)
{
possibleTargets[nbTargets++] = i;
}
}
// choisit une cible
return possibleTargets[RANDOM(nbTargets)];
}
Probabilité de toucher
#define RANDOM(_maxValue) (rand() % (_maxValue))
Maintenant qu'on a choisi une cible, il y a une chance qu'elle évite l'attaque.
// calcule les dextérités du monstre et du champion
int monsterDexterity = monsters.monstersDatas[type].dexterity + game.levelMultiplier() * 2 + RANDOM(32) - 16;
int championDexterity = game.characters[target].getDexterity();
La dextérité du champion est calculée par cette fonction.
int CCharacter::getDexterity()
{
int dexterity = stats[eStatDexterity].value + RANDOM(8);
// diminue la dextérité en fonction de la charge
dexterity -= (dexterity / 2) * stats[eStatLoad].value / stats[eStatLoad].maxValue;
// si on dort...
if (interface.isSleeping() == true)
dexterity /= 2;
// limite la valeur
int min = 1 + RANDOM(8);
int max = 100 - RANDOM(8);
if (dexterity < min)
dexterity = min;
if (dexterity > max)
dexterity = max;
return dexterity;
}
La dextérité de base du champion est diminuée par la charge qu'il porte et légèrement randomisée.
// teste si on a touché
if (interface.isSleeping() == true ||
monsterDexterity > championDexterity ||
RANDOM(4) == 0)
{
Quand l'équipe est en train de dormir, le monstre touche toujours.
Dégâts
// calcule les dégats (ici, on devrait avoir une chance de parer)
int damages = monsters.monstersDatas[type].attack + game.levelMultiplier() * 2 + RANDOM(16);
Comme le dit le commentaire, ici il devrait y avoir un test pour savoir si le champion pare l'attaque.
// randomise les dégats (d'une façon assez compliquée...)
damages /= 2;
damages += RANDOM(damages) + RANDOM(4);
damages += RANDOM(damages);
damages /= 4;
damages += RANDOM(4) + 1;
if (RANDOM(2))
damages -= RANDOM(damages / 2 + 1) - 1;
Alors pourquoi n'ont ils pas utilisé un simple random ?
// frappe le champion
static char soundName[256];
sprintf(soundName, "sound/Champion_Damaged_%d.wav", RANDOM(4) + 1);
sound->play(player.pos, soundName);
game.characters[target].hitChampion(damages);
A ce moment, dans le jeu d'origine, il y avait encore beaucoup de choses à prendre en compte:
void CCharacter::hitChampion(int damages)
{
displayedDamages = damages;
damagesTimer.set(90, false);
}
Ces valeurs seront ensuite utilisées dans l'interface pour afficher la bonne image et le nombre.