Partie 44: Les Attaques Partie 2: Tuer les monstres avec des armes de mêlée
Téléchargements
Nettoyage des fonctions de rang
// 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];
}
// renvoie le rang d'un champion en fonction de la position du groupe de mosntres (0 = devant, 1 = derrière)
uint8_t CPlayer::getChampionRow(CVec2 monsterPos, 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 = getLocalFromPos(monsterPos);
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];
}
On devait aussi tester si les monstres de la première ligne d'un groupe étaient tous morts pour que la ligne
// retourne true si un monstre est en première ligne de son groupe, en prenant en compte les morts
bool CMonsterGroup2::isMonsterInFront(CVec2 mapPos, EMonsterPos monsterPos)
{
bool isFrontRowRowAlive = false;
for (int i = 0; i < 4; ++i)
{
if (mMonsters[i].pos != eMonsterPosNone &&
getMonsterRow(mapPos, mMonsters[i].pos) < 2)
{
isFrontRowRowAlive = true;
break;
}
}
uint8_t row = getMonsterRow(mapPos, monsterPos);
return (isFrontRowRowAlive == true && row < 2) ||
(isFrontRowRowAlive == false && row == 2);
}
// retourne true si un champion est en première ligne, en prenant en compte les morts.
bool CPlayer::isChampionInFront(CVec2 monsterPos, int championNum)
{
int rowToSearch;
// teste s'il y a au moins un champion vivant en première ligne
rowToSearch = 1;
for (int i = 0; i < 4; ++i)
{
if (interface.isChampionEmpty(i) == false &&
interface.isChampionDead(i) == false &&
player.getChampionRow(monsterPos, i) == 0)
{
rowToSearch = 0;
break;
}
}
return (player.getChampionRow(monsterPos, championNum) == rowToSearch);
}
Maintenant ça simplifie les fonctions qui utilisaient les rangs. Par exemple la fonction CMonster::chooseTarget():
// choisit le champion que ce monstre va attaquer
uint8_t CMonster::chooseTarget(CVec2 mapPos)
{
// 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 &&
player.isChampionInFront(mapPos, i) == true)
{
possibleTargets[nbTargets++] = i;
}
}
// choisit une cible
return possibleTargets[RANDOM(nbTargets)];
}
Et comme je l'ai dit, j'aurais dû utiliser ces fonctions quand l'équipe touche un mur, maintenant c'est fait:
void CPlayer::moveIfPossible(EWallSide side)
{
[...]
// mur
if (game.characters[0].portrait != -1)
{
for (int i = 0; i < 4; ++i)
{
if (player.isChampionInFront(newPos, i) == true)
game.characters[i].hitChampion(i, (RANDOM(4) == 0 ? 2 : 1));
}
sound->play(newPos, "sound/Party_Damaged.wav");
}
Les attaques de mêlée
<attacks>
<!-- 0: Rien -->
<attack>
<type>Melee</type>
[...]
</attack>
<!-- 1: FENDRE -->
<attack>
<type>Melee</type>
[...]
</attack>
[...]
Ce type peut prendre différentes valeurs:
Choisir une cible
void CInterface::updateWeapons()
{
[...]
if (frontTile != NULL)
{
// porte cassable
if (frontTile->getType() == eTileDoor &&
[...]
}
else if (attack.type == "Melee")
{
// attaque de melee: cherche un groupe de monstres en face de nous
CVec2 monsterPos = player.pos;
monsterPos.x += (player.dir == 3) - (player.dir == 1);
monsterPos.y += (player.dir == 2) - (player.dir == 0);
int groupIndex = map.findMonsterGroupIndex(monsterPos);
if (groupIndex != -1)
{
CMonsterGroup2& group = monsters.monsterGroups[groupIndex];
// seuls les champions de devant peuvent attaquer avec des armes de melee
if (player.isChampionInFront(monsterPos, currentWeapon) == true)
{
// choisit une cible
uint8_t target = chooseTarget(monsterPos);
[...]
}
else
{
// trop loin
weaponsAreaState = eWeaponsAreaWeapons;
}
}
else
{
// rien à frapper
weaponsAreaState = eWeaponsAreaWeapons;
}
}
La fonction chooseTarget() ressemble à celle qu'on a utilisée pour les monstres.
uint8_t CInterface::chooseTarget(CVec2 mapPos)
{
// liste les cibles possibles
int groupIndex = map.findMonsterGroupIndex(mapPos);
CMonsterGroup2& group = monsters.monsterGroups[groupIndex];
uint8_t possibleTargets[2];
int nbTargets = 0;
for (int i = 0; i < 4; ++i)
{
CMonster& monster = group.mMonsters[i];
if (monster.pos != eMonsterPosNone &&
group.isMonsterInFront(mapPos, monster.pos) == true)
{
possibleTargets[nbTargets++] = i;
}
}
// choisit une cible
return possibleTargets[RANDOM(nbTargets)];
}
Attaquer la cible
damages = game.characters[currentWeapon].getMeleeDamage(monsterPos, attack);
if (damages != 0)
{
// frappe
damagesDisplayTime.set(DAMAGES_DISPLAY_TIME, false);
weaponsAreaState = eWeaponsAreaDamage;
group.hitMonster(monsterPos, target, damages);
}
else
{
// manqué
weaponsAreaState = eWeaponsAreaWeapons;
}
La fonction getMeleeDamage() ne calcule pas seulement les dégâts, mais elle teste aussi si on touche une cible.
int CCharacter::getMeleeDamage(CVec2 monsterPos, CAttacks::SAttack& attack)
{
int groupIndex = map.findMonsterGroupIndex(monsterPos);
CMonsterGroup& mapGroup = map.mMonsterGroups[groupIndex];
int monsterType = mapGroup.getType();
CMonsters::SMonsterData& monsterData = monsters.monstersDatas[monsterType];
int weaponType = bodyObjects[eBodyPartRightHand].getType();
// est-ce qu'on touche ?
int monsterDexterity = monsterData.dexterity + game.levelMultiplier() * 2 + RANDOM(32) - 16;
int championDexterity = getDexterity();
if (championDexterity > monsterDexterity ||
((RANDOM(75) + 1) <= attack.hitProbability) ||
RANDOM(4) == 0)
{
Ensuite on calcule la valeur des dégâts subis par le monstre.
int damages = 0;
int monsterDefense = 0;
// calcule les dégâts
damages = getStrength(eBodyPartRightHand);
On pondère ces dégâts avec un coefficient pour l'attaque utilisée.
if (damages != 0)
{
damages += RANDOM(damages / 2 + 1);
damages = (damages * attack.damages) / 32;
monsterDefense = monsterData.defense + game.levelMultiplier() * 2 + RANDOM(32);
if (weaponType == OBJECT_TYPE_DIAMOND_EDGE)
{
monsterDefense -= monsterDefense / 4;
}
else if (weaponType == OBJECT_TYPE_HARDCLEAVE)
{
monsterDefense -= monsterDefense / 8;
}
damages = damages - monsterDefense - RANDOM(32);
}
Ensuite il y a une partie étrange du code qui ressemble a une tentative "d'affiner" les dégâts et d'augmenter un
// dernière chance ?
if (damages <= 1)
{
int damages2 = damages;
damages = RANDOM(4);
if (damages != 0)
{
damages++;
damages2 += RANDOM(16);
if (damages2 > 0 || RANDOM(2))
{
damages += RANDOM(4);
if (RANDOM(4) == 0)
damages += MAX(0, damages2 + RANDOM(16));
}
}
}
Enfin on "randomise" les dégâts de la même façon qu'on l'a fait pour les monstres, dans le but d'augmenter la
// randomize the damages
if (damages != 0)
{
damages /= 2;
damages += RANDOM(damages) + RANDOM(4);
damages += RANDOM(damages);
damages /= 4;
damages += RANDOM(4) + 1;
}
return damages;
}
return 0;
}
La "force" du champion
int CCharacter::getStrength(int objType)
{
int strength = stats[eStatStrength].value + RANDOM(16);
CObjects::CObjectInfo& objInfo = objects.mObjectInfos[objType];
Ensuite, on la module avec le poids de l'arme.
// poids de l'object
int loadLimit = stats[eStatLoad].maxValue / 16;
int loadLimit2 = loadLimit + (loadLimit - 12) / 2;
if (objInfo.weight <= loadLimit)
{
strength += objInfo.weight - 12;
}
else if (objInfo.weight <= loadLimit2)
{
strength += (objInfo.weight - loadLimit) / 2;
}
else
{
strength -= (objInfo.weight - loadLimit2) * 2;
}
Les armes ont chacune un coefficient de dégâts qui est ajouté à la force.
// dégâts de l'arme
strength += objInfo.damages;
Et finalement on divise la force par 2 et on limite cette valeur entre 0 et 100.
// limite la valeur
strength /= 2;
if (strength < 0)
strength = 0;
if (strength > 100)
strength = 100;
return strength;
}
Notez que ce n'est pas la formule exacte utilisée dans le jeu d'origine, car elle prenait aussi en compte
Vie et mort des monstres
void CMonsterGroup2::hitMonster(CVec2 groupPos, int monsterNum, int damages)
{
mMonsters[monsterNum].life -= damages;
// mort ?
if (mMonsters[monsterNum].life <= 0)
{
mMonsters[monsterNum].pos = eMonsterPosNone;
// si tous les monstres sont morts, on supprime le groupe
int i;
for (i = 0; i < 4; ++i)
{
if (mMonsters[i].pos != eMonsterPosNone)
break;
}
if (i == 4)
{
int groupIndex = map.findMonsterGroupIndex(groupPos);
map.removeMonsterGroup(groupPos);
monsters.monsterGroups.erase(monsters.monsterGroups.begin() + groupIndex);
}
}
}
Monstres solides
void CPlayer::moveIfPossible(EWallSide side)
{
[...]
int canMove = 0; // 0=OK, 1=mur, 2=monstre
if (tile->mWalls[side].getType() != 0)
{
canMove = 1;
}
else if (destTile == NULL)
{
canMove = 1;
}
else if (destTile->getType() == eTileDoor && doors.isOpened(destTile) == false)
{
canMove = 1;
}
else if (map.findMonsterGroupIndex(newPos) != -1)
{
canMove = 2;
}
if (canMove == 0)
{
// pas d'obstacle
pos = newPos;
[...]
}
else if (canMove == 1)
{
// mur
[...]
}
}