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();