Part 49: Stats and skills Part 2: Skills in melee
Downloads
Revisiting skills levels
int CCharacter::getSkillLevel(ESkillsNames skillName)
{
if (interface.isSleeping() == true)
return 1;
int exp = skills[skillName].experience;
// Hidden skill
if (skillName >= 4)
{
int mainSkill = (skillName - 4) / 4;
exp = (exp + skills[mainSkill].experience) / 2;
}
// compute level from experience
int level = 0;
while (exp >= 500)
{
exp /= 2;
level++;
}
return level;
}
Skills in attacks
<attacks>
<!-- 0: Nothing -->
<attack>
<type>Melee</type>
[...]
<skill>0</skill>
</attack>
<!-- 1: BLOCK -->
<attack>
<type>Melee</type>
[...]
<skill>7</skill>
</attack>
<!-- 2: CHOP -->
<attack>
<type>Melee</type>
[...]
<skill>6</skill>
</attack>
The skill's level you need for an attack is stored in the combos.
<combos>
<!-- Combo 0 (unused) -->
<combo>
<attack type="0">
<skill_level>0</skill_level>
</attack>
<attack type="0">
<skill_level>0</skill_level>
</attack>
<attack type="0">
<skill_level>0</skill_level>
</attack>
</combo>
<!-- Combo 1 Invoke/Fuse/Fluxcage -->
<combo>
<attack type="24">
<skill_level>0</skill_level>
</attack>
<attack type="40">
<skill_level>0</skill_level>
</attack>
<attack type="32">
<skill_level>0</skill_level>
</attack>
</combo>
These skills were used in the original game to add a "bonus" to the damages we do when we attack a monster.
int CCharacter::getMeleeDamage(CVec2 monsterPos, CAttacks::SAttack& attack)
{
[...]
// do we hit ?
[...]
if (championDexterity > monsterDexterity ||
((RANDOM(75) + 1) <= attack.hitProbability) ||
RANDOM(4) == 0)
{
[...]
// compute damages
[...]
// last chance ?
[...]
// randomize the damages
[...]
// skill bonus
if (getSkillLevel((ESkillsNames)attack.skill) >= RANDOM(64))
{
damages += 10;
}
return damages;
Attacks list
void CInterface::drawAttacks(QImage* image, int num)
{
CObject& weapon = getWeapon(num);
int weaponType = weapon.getType();
CObjects::CObjectInfo weaponInfo = objects.mObjectInfos[weaponType];
CAttacks::SCombo& combo = attacks.getCombo(weaponInfo.combo);
CCharacter* c = &game.characters[num];
// make a list of possible attacks
mAttacksList.clear();
for (int i = 0; i < 3; ++i)
{
if (combo.attacks[i].type != 0)
{
int attackType = combo.attacks[i].type;
CAttacks::SAttack& attack = attacks.getAttack(attackType);
int skillLevel = c->getSkillLevel((CCharacter::ESkillsNames)attack.skill) + 1;
if (skillLevel >= combo.attacks[i].skillLevel)
mAttacksList.push_back(attackType);
}
}
// draw the background image
QImage attacksBg = fileCache.getImage("gfx/interface/WeaponsActions.png");
graph2D.drawImage(image, CVec2(233, 77), attacksBg);
// hide unused slots
for (int i = 2; i >= mAttacksList.size(); i--)
{
CRect rect(CVec2(233, 86 + i * 12),
CVec2(319, 97 + i * 12));
graph2D.rect(image, rect, BLACK, true);
}
// draw character's name
drawText(image, CVec2(235, 79), eFontStandard, c->firstName.c_str(), BLACK);
// draw attacks' names
for (int i = 0; i < mAttacksList.size(); ++i)
{
static char attackName[16];
sprintf(attackName, "ATTACK%02d", mAttacksList[i]);
drawText(image, CVec2(241, 89 + i * 12), eFontStandard, getText(attackName).c_str(), MAIN_INTERFACE_COLOR);
if (attackHighlightTime.isRunning() == true && currentAttack == i)
{
CRect rect(CVec2(234, 86 + i * 12),
CVec2(318, 96 + i * 12));
graph2D.Xor(image, rect, MAIN_INTERFACE_COLOR);
}
}
// mouse areas
if (isWeaponGreyed(num) == false && attackHighlightTime.isRunning() == false)
{
for (int i = 0; i < 3; ++i)
{
if (combo.attacks[i].type != 0)
{
CRect rect(CVec2(234, 86 + i * 12),
CVec2(318, 96 + i * 12));
mouse.addArea(eMouseArea_Attack, rect, eCursor_Arrow, (void*)i);
}
}
CRect rect(CVec2(290, 77), CVec2(314, 83));
mouse.addArea(eMouseArea_CloseAttack, rect, eCursor_Arrow);
}
}
Weapons classes
<items>
<!-- BARE HAND -->
<item name="ITEM000">
[...]
<class>0</class>
</item>
<!-- ======================== Weapons ======================== -->
<!-- EYE OF TIME -->
<item name="ITEM001">
[...]
<class>130</class>
</item>
<!-- STORMRING -->
<item name="ITEM002">
[...]
<class>131</class>
</item>
As I said these values gives us the type of the weapons. They follow these tables:Values | Weapon type |
---|---|
0 | Swing weapon: swords, maces and clubs |
Values | Weapon type |
---|---|
1 | Throwing star |
2 | Daggers and axes |
3 to 9 | unused |
10 | Bows ammunitions - arrows |
11 | Slings ammunitions - rocks |
12 | Poison darts |
13 to 15 | unused |
Values | Weapon type |
---|---|
16 to 31 | Bows and crossbows |
32 to 47 | Slings |
48 to 111 | unused |
Values | Weapon type |
---|---|
112 to 255 | Magical weapons - rings, wands and staffs |
#define WEAPON_CLASS_SWING 0 /* Class 0: SWING weapons */
#define WEAPON_CLASS_DAGGER_AND_AXES 2 /* Class 1 to 15: THROW weapons */
#define WEAPON_CLASS_BOW_AMMUNITION 10
#define WEAPON_CLASS_SLING_AMMUNITION 11
#define WEAPON_CLASS_POISON_DART 12
#define WEAPON_CLASS_FIRST_BOW 16 /* Class 16 to 111: SHOOT weapons */
#define WEAPON_CLASS_LAST_BOW 31
#define WEAPON_CLASS_FIRST_SLING 32
#define WEAPON_CLASS_LAST_SLING 47
#define WEAPON_CLASS_FIRST_MAGIC 112 /* Class 112 to 255: Magic and special weapons */
Skills in the strength
int CCharacter::getStrength(int objType)
{
[...]
// object weight
[...]
// weapon's damages
strength += objInfo.damages;
if (objType != 0)
{
int skill = 0;
if (objInfo.weaponClass == WEAPON_CLASS_SWING || objInfo.weaponClass == WEAPON_CLASS_DAGGER_AND_AXES)
skill = getSkillLevel(eSkillSwing) + 1;
if (objInfo.weaponClass != WEAPON_CLASS_SWING && objInfo.weaponClass < WEAPON_CLASS_FIRST_BOW)
skill += getSkillLevel(eSkillThrow) + 1;
if (objInfo.weaponClass >= WEAPON_CLASS_FIRST_BOW && objInfo.weaponClass < WEAPON_CLASS_FIRST_MAGIC)
skill += getSkillLevel(eSkillShoot) + 1;
strength += skill * 2;
}
// bound the value
[...]
return strength;
}
Parrying
void CMonster::fight(CVec2 mapPos, uint8_t type, bool isInFront)
{
if (nextAttackTimer.update() == true)
{
initNextAttack(type);
// if the monster is in the front line of the group
if (isInFront == true)
{
// play attack sound
[...]
// show monster's animation
[...]
// choose a target champion
[...]
// compute monster's and champion's dexterities
[...]
// check if we hit
if (interface.isSleeping() == true ||
monsterDexterity > championDexterity ||
RANDOM(4) == 0)
{
// compute damages
int parry = (game.characters[target].getSkillLevel(CCharacter::eSkillParry) + 1) * 2;
int damages = monsters.monstersDatas[type].attack + game.levelMultiplier() * 2 + RANDOM(16) - parry;
if (damages <= 1)
{
// parry
if (RANDOM(2))
return;
damages = RANDOM(4) + 2;
}
// randomize damages (quite a complicated way...)
[...]
// hit the champion
[...]
}
// if we are sleeping, wake up
[...]
}
}
}
Pits' bug
if (fallTimer.update() == true)
player.fall();