Part 37: Attacks Part 1
Downloads
Attacks
<text id="ATTACK01">BLOCK</text>
<text id="ATTACK02">CHOP</text>
<text id="ATTACK03">BLOW HORN</text>
<text id="ATTACK04">FLIP</text>
<text id="ATTACK05">PUNCH</text>
<text id="ATTACK06">KICK</text>
<text id="ATTACK07">WAR CRY</text>
<text id="ATTACK08">STAB</text>
<text id="ATTACK09">CLIMB DOWN</text>
<text id="ATTACK10">FREEZE LIFE</text>
<text id="ATTACK11">HIT</text>
<text id="ATTACK12">SWING</text>
<text id="ATTACK13">THRUST</text>
<text id="ATTACK14">JAB</text>
<text id="ATTACK15">PARRY</text>
<text id="ATTACK16">HACK</text>
<text id="ATTACK17">BERZERK</text>
<text id="ATTACK18">FIREBALL</text>
<text id="ATTACK19">DISPELL</text>
<text id="ATTACK20">CONFUSE</text>
<text id="ATTACK21">LIGHTNING</text>
<text id="ATTACK22">DISRUPT</text>
<text id="ATTACK23">MELEE</text>
<text id="ATTACK24">INVOKE</text>
<text id="ATTACK25">SLASH</text>
<text id="ATTACK26">CLEAVE</text>
<text id="ATTACK27">BASH</text>
<text id="ATTACK28">STUN</text>
<text id="ATTACK29">SHOOT</text>
<text id="ATTACK30">SPELLSHIELD</text>
<text id="ATTACK31">FIRESHIELD</text>
<text id="ATTACK32">FLUXCAGE</text>
<text id="ATTACK33">HEAL</text>
<text id="ATTACK34">CALM</text>
<text id="ATTACK35">LIGHT</text>
<text id="ATTACK36">WINDOW</text>
<text id="ATTACK37">SPIT</text>
<text id="ATTACK38">BRANDISH</text>
<text id="ATTACK39">THROW</text>
<text id="ATTACK40">FUSE</text>
I created a new database "attacks.xml" that contains some parameters for each of them, but we will talk about
Combos
<?xml version="1.0" encoding="utf-8"?>
<combos>
<!-- Combo 0 (unused) -->
<combo>
<attack type="0">
</attack>
<attack type="0">
</attack>
<attack type="0">
</attack>
</combo>
<!-- Combo 1 Invoke/Fuse/Fluxcage -->
<combo>
<attack type="24">
</attack>
<attack type="40">
</attack>
<attack type="32">
</attack>
</combo>
<!-- Combo 2 Punch/Kick/War Cry -->
<combo>
<attack type="5">
</attack>
<attack type="6">
</attack>
<attack type="7">
</attack>
</combo>
[...]
</combos>
Each combo contains 3 attacks with only a type. In the future, these attacks will contain more parameters.
class CAttacks
{
public:
[...]
struct SComboAttack
{
uint8_t type;
};
struct SCombo
{
SComboAttack attacks[3];
};
[...]
void readCombosDB();
[...]
SCombo& getCombo(int num);
private:
[...]
std::vector<SCombo> mCombos;
};
Linking the combos to the weapons
<?xml version="1.0" encoding="utf-8"?>
<items>
<!-- BARE HAND -->
<item name="ITEM000">
[...]
<combo>2</combo>
</item>
<!-- ======================== Weapons ======================== -->
<!-- EYE OF TIME -->
<item name="ITEM001">
[...]
<combo>43</combo>
</item>
<!-- STORMRING -->
<item name="ITEM002">
[...]
<combo>7</combo>
</item>
You can see that I added a "bare hand" object because when there is no weapon in the champion's hand, it is linked
CObjects::CObjectInfo& infos = objects.mObjectInfos[objType - 1];
This was confusing and could lead to bugs. So now we don't need the "-1".
void CInterface::drawWeapon(QImage* image, int num)
{
if (isWeaponEmpty(num) == false)
{
// draw the background rectangle
CRect rect(CVec2(233 + 22 * num, 86),
CVec2(252 + 22 * num, 120));
graph2D.rect(image, rect, MAIN_INTERFACE_COLOR, true);
// draw the weapon
CVec2 pos = CVec2(235 + 22 * num, 96);
CObject& weapon = getWeapon(num);
int weaponType = weapon.getType();
CObjects::CObjectInfo weaponInfo = objects.mObjectInfos[weaponType];
if (weaponInfo.combo != 0)
{
std::string imageFile;
int imageNum;
CObjects::CObjectInfo object = objects.mObjectInfos[weaponType];
imageFile = object.imageFile.toLocal8Bit().constData();
imageNum = object.imageNum;
QImage objImage = fileCache.getImage(imageFile);
CRect objRect = getItemRect(imageNum);
graph2D.drawImageAtlas(image, pos, objImage, objRect, BLACK);
}
// grey out or mouse area
if (isWeaponGreyed(num) == true)
graph2D.patternRectangle(image, rect);
else if (weaponInfo.combo != 0)
mouse.addArea(eMouseArea_Weapon, rect, eCursor_Arrow, (void*)num);
}
}
Of course we also use the combo datas to find how many attacks a weapon has and display their names in
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);
// draw the background image
QImage attacksBg = fileCache.getImage("gfx/interface/WeaponsActions.png");
CVec2 pos(233, 77);
graph2D.drawImage(image, pos, attacksBg);
// hide unused slots
for (int i = 0; i < 3; ++i)
{
if (combo.attacks[i].type == 0)
{
CRect rect(CVec2(233, 86 + i * 12),
CVec2(319, 97 + i * 12));
graph2D.rect(image, rect, BLACK, true);
}
}
// draw character's name
CCharacter* c = &game.characters[num];
drawText(image, CVec2(235, 79), eFontStandard, c->firstName.c_str(), BLACK);
// draw attacks' names
for (int i = 0; i < 3; ++i)
{
if (combo.attacks[i].type != 0)
{
static char attackName[16];
sprintf(attackName, "ATTACK%02d", combo.attacks[i].type);
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);
}
}
All the attacks of a weapon won't be available at the beginning. Some of them will require that the character has a
The attacks database
<?xml version="1.0" encoding="utf-8"?>
<attacks>
<!-- Nothing -->
<attack>
<cooldown>0</cooldown>
<hitprob>0</hitprob>
</attack>
<!-- BLOCK -->
<attack>
<cooldown>60</cooldown>
<hitprob>22</hitprob>
</attack>
<!-- CHOP -->
<attack>
<cooldown>80</cooldown>
<hitprob>48</hitprob>
</attack>
At the moment it only holds the cooldown time in frames and the probability to hit with this weapon that goes
void CInterface::updateWeapons()
{
// attack highlight
if (attackHighlightTime.update() == true)
{
CObject& weapon = getWeapon(currentWeapon);
int weaponType = weapon.getType();
CObjects::CObjectInfo weaponInfo = objects.mObjectInfos[weaponType];
CAttacks::SCombo& combo = attacks.getCombo(weaponInfo.combo);
int attackType = combo.attacks[currentAttack].type;
CAttacks::SAttack& attack = attacks.getAttack(attackType);
weaponCoolDown[currentWeapon].set(attack.cooldown, true);
if (((rand() % 75) + 1) <= attack.hitProbability)
{
damages = rand() % 200 + 1;
damagesDisplayTime.set(DAMAGES_DISPLAY_TIME, false);
weaponsAreaState = eWeaponsAreaDamage;
}
else
{
weaponsAreaState = eWeaponsAreaWeapons;
}
}
// damages timer
if (damagesDisplayTime.update() == true)
weaponsAreaState = eWeaponsAreaWeapons;
// weapons cooldowns
for (int i = 0; i < 4; ++i)
weaponCoolDown[i].update();
}
This is not the exact formula used in the original game, as the hit probability will vary with the champion's