Écrit par David Henry, le 9 juin 2002
Attention : cet article a été écrit pour le SDK 2.2 d'Half-Life 1 ! Il se peut qu'il ne soit pas entièrement compatible avec des versions antérieures ou supérieures du Kit.
Bon tout d'abord je tiens à préciser que je n'ai pas envie d'expliquer les fondements du C++ dans ce tutorial déjà bien assez long, donc si vous avez des lacunes sur le langage ou si vous ne le connaissez pas du tout, courrez à la FNAC ou aller faire un tour sur Amazon.fr pour vous payer une petite bible. Je présume également que vous connaissez un minimum le SDK d'Half-Life et que vous savez ce que sont les DLL mp et client. Voilà, les présentations sont faites, on peut commencer sérieusement :-)
Ce tutorial a pour but de vous apprendre un peu commencer qu'on crée une arme avec le SDK 2.2. Pour le cas général on appellera l'arme monarme.
Pour commencer, vous aurez besoin de plusieurs fichiers :
Voilà pour les fichiers. Maintenant où va-t-on coder ? dans la mp.dll et client.dll (ouais c'est une nouveauté du SDK 2.2 et pour être franc, je préférais comme avant...). Allez, c'est partiii !!!
Lancez Visual C++ et direction la DLL serveur !
On va commencer par créer une classe pour notre nouvelle arme. Rendez-vous donc
dans weapon.h là ou vous voulez mais après la définition CBasePlayerWeapon
,
car notre arme en héritera, et placez le code suivant :
///////////////////////////////////////////////////////////////////////////// // // CMonArme - classe pour le ma super arme. // ///////////////////////////////////////////////////////////////////////////// #ifndef CLIENT_DLL class CMonArme : public CBasePlayerWeapon { public: // fonctions virtual BOOL UseDecrement (); int SecondaryAmmoIndex (); int iItemSlot (); void Spawn (); void Precache (); int GetItemInfo (ItemInfo *p); int AddToPlayer (CBasePlayer *pPlayer); void SendWeaponAnim (int iAnim, int skiplocal = 1, int body = 0); void PrimaryAttack (); void SecondaryAttack (); BOOL Deploy (); void Holster (int skiplocal = 0); void Reload (); void WeaponIdle (); public: // variables membres int m_iShell; // douille }; #endif // CLIENT_DLL
À noter qu'autrefois on déclarait la classe dans le fichier source de l'arme
(ici vous pouvez encore le faire) car nous n'en avions pas besoin côté client.
C'est pour cela aussi que j'ai désactivé la compilation de ce bout de code avec
#ifndef CLIENT_DLL
.
En gros, comment ça marche une arme ? Bien elle est composée de différentes fonctions appelées régulièrement pour différentes actions :
PrimaryAttack()
;SecondaryAttack()
;Reload()
;WeaponIdle()
;Deploy()
;Holster()
.Les fonctions Spawn()
, Precache()
, GetItemInfo()
et
AddToPlayer()
servent respectivement pour faire apparaître l'arme sur la map,
précacher les ressources necessaires (modèles, sons, ...) et ajouter l'arme à l'inventaire
du joueur.
Nous aurons ensuite besoin de quelques définitions, à déclarer toujours dans ce fichier :
#define WEAPON_MONARME 16 // ID de l'arme #define MONARME_WEIGHT 15 // priorité dans la séléction automatique #define MONARME_MAX_CARRY 35 // nombre maximum de munitions portables #define MONARME_MAX_CLIP 7 // capacité maximal d'un chargeur #define MONARME_DEFAULT_GIVE 7 // munitions données par l'arme #define AMMO_MONARME_GIVE 14 // munitions données par un chargeur
Le Mieux serait de les définir avec les autres du même genre des autres armes.
Regardez en haut de weapon.h après la déclaration de CGrenade
,
les définitions sont rangées par catégories pour toutes les armes.
Voilà pour les déclarations ! Maintenant créez un nouveau fichier source pour votre arme et ajoutez-le au projet. Il contiendra les définitions des fonctions membres de la classe. Commencez par ajouter les #includes nécessaires :
// // monarme.cpp // #include "extdll.h" #include "util.h" #include "cbase.h" #include "weapons.h" #include "player.h"
Pour pouvoir utiliser votre arme dans Half-life, il faut dire au moteur que c'est une entité. Il suffit donc simplement d'utiliser la macro suivante :
LINK_ENTITY_TO_CLASS (weapon_monarme, CMonArme);
On va avoir besoin d'une fonction globale déclarée et définie dans weapons.cpp :
extern void EjectBrass (const Vector &vecOrigin, const Vector &vecVelocity, float rotation, int model, int soundtype);
Maintenant on va déclarer la liste des animations de l'arme, pour pouvoir dire ensuite au moteur quelle animation jouer au moment voulu. Ce sont les seules choses des modèles que l'on aura besoin pour coder notre arme, peu importe son squelette, son nombre de triangles, de texture, de submodels ou autre. Pour les avoir, demandez à votre modeleur la liste ou prenez hlmviewer par exemple et regardez votre w_monarme.mdl. Le nom des anims n'est pas important non plus, seul l'odre compte.
// liste des animations enum monarme_e { MA_IDLE1 = 0, MA_IDLE2, MA_IDLE3, MA_SHOOT, MA_SHOOT_EMPTY, MA_RELOAD, MA_DRAW, MA_HOLSTER, };
On va imaginer ici que le modèle est composé de trois animations idle, deux shoot dont une lorsque le chargeur est à cours de munitions, une reload lorsqu'on recharge et une lorsqu'on sort l'arme (draw) et une lorsqu'on la range (holster). « MA » signifie ici « Mon Arme ».
Ceci fait, on peut commencer à coder les fonctions. Débarrassons-nous d'abord des petites fonctions :
// -------------------------------------------------------------------------- // CMonArme::UseDecrement // -------------------------------------------------------------------------- BOOL CMonArme::UseDecrement () { #if defined (CLIENT_WEAPONS) return TRUE; #else return FALSE; #endif } // -------------------------------------------------------------------------- // CMonArme::iItemSlot // Retourne l'index du slot de l'arme dans le HUD. // -------------------------------------------------------------------------- int CMonArme::iItemSlot () { return 2; } // -------------------------------------------------------------------------- // CMonArme::SecondaryAmmoIndex // // Facultatif si on utilise pas de second mode de tir. // -------------------------------------------------------------------------- int CMonArme::SecondaryAmmoIndex () { return m_iSecondaryAmmoType; } // -------------------------------------------------------------------------- // CMonArme::SendWeaponAnim // // Joue l'animation iAnim de du modèle de l'arme. // -------------------------------------------------------------------------- void CMonArme::SendWeaponAnim (int iAnim, int skiplocal, int body) { MESSAGE_BEGIN (MSG_ONE, SVC_WEAPONANIM, NULL, m_pPlayer->pev); WRITE_BYTE (iAnim); WRITE_BYTE (body); MESSAGE_END (); }
Vite fait : UseDecrement()
est un truc assez récent pour les
armes côté client. Notre arme ne sera codé que côté serveur mais les autres ne le
sont pas donc elle retournera TRUE quand même. iItemSlot()
retourne le numéro du slot où se trouve l'arme dans le HUD. SecondaryAmmoIndex()
retourne un index pour obtenir les bonnes munitions utiliées par le second mode de
tir (utiliser ainsi : m_pPlayer->m_rgAmmo[ SecondaryAmmoIndex() ]
).
Cette dernière n'est pas du tout obligatoire si vous n'avez pas de second mode de
tir ou si y vous utiliser les mêmes munitions. Pour SendWeaponAnim()
,
c'est une fonction qu'on est obligé de surcharger depuis le SDK 2.2 pour éviter des
problèmes dus à UseDecrement()
. Elle sert à jouer une animation du
modèle « view » de l'arme. Le paramètre skiplocal
ne nous
est inutile mais on est obligé de le garder à cause de la surcharge de fonction.
Pasons au sérieux :
// -------------------------------------------------------------------------- // CMonArme::Spawn // // Apparition de l'arme sur la map. // -------------------------------------------------------------------------- void CMonArme::Spawn () { pev->classname = MAKE_STRING ("weapon_monarme"); // nom de l'entité m_iId = WEAPON_MONARME; // ID de l'arme m_iDefaultAmmo = MONARME_DEFAULT_GIVE; // munitions que donne l'arme quand on la ramasse Precache (); // Précache des ressources necessaires SET_MODEL (ENT (pev), "models/w_monarme.mdl"); // modèle "world" de l'arme FallInit (); // prête à tomber au sol }
La fonction Spawn()
est appelée lorsque le modèle apparaît dans le
monde. Elle initialise certaiens de ses variables membres puis charge son modèle à
l'appel de SET_MODEL()
. FallInit()
prépare l'entité à être
« libérée » dans le monde au cas ou le joueur se ferait tuer par exemple.
Enfin, la fonction Precache()
est obligatoire pour que le moteur charge
les fichiers ressources nécessaires avant leur rendu. Si vous oubliez de précacher
les ressources, vous n'aurez pas de modèles, sons, events ou sprites. La fonction
se présente ainsi :
// -------------------------------------------------------------------------- // CMonArme::Precache // // Précache toutes les ressources necessaires. // -------------------------------------------------------------------------- void CMonArme::Precache () { // modèles de l'arme PRECACHE_MODEL ("models/v_monarme.mdl"); PRECACHE_MODEL ("models/w_monarme.mdl"); PRECACHE_MODEL ("models/p_monarme.mdl"); // modèle douille m_iShell = PRECACHE_MODEL ("models/shell.mdl"); // sons PRECACHE_SOUND ("weapons/monarme_reload.wav"); PRECACHE_SOUND ("weapons/monarme_fire.wav"); PRECACHE_SOUND ("weapons/357_cock1.wav"); }
Je pense que vous l'avez compris : PRECACHE_MODEL()
sert à
précacher les *.mdl, PRECACHE_SOUND()
les sons et
PRECACHE_EVENT()
les fichiers *.sc des events. Si vous
avez des sprites, c'est PRECACHE_MODEL()
aussi. J'ai précaché
357_cock1.wav car c'est le son utilisé par toutes les armes en général
lorsque le chargeur est vide.
Passons à la fonction suivante, GetItemInfo()
, qui renvoie des
infos sur l'arme :
// -------------------------------------------------------------------------- // CMonArme::GetItemInfo // // Récupère les infos de l'arme. // -------------------------------------------------------------------------- int CMonArme::GetItemInfo (ItemInfo *p) { p->pszName = STRING (pev->classname); // nom de l'entité p->pszAmmo1 = "mes munitions"; // type de munitions pour le premier mode de tire p->iMaxAmmo1 = MONARME_MAX_CARRY; // nombre maximum de munition type #1 p->pszAmmo2 = NULL; // type de munitions pour le second mode de tire p->iMaxAmmo2 = -1; // nombre maximum de munition type #2 p->iMaxClip = MONARME_MAX_CLIP; // capacité maximale du chargeur p->iSlot = 1; // slot dans le HUD p->iPosition = 2; // position dans le slot p->iFlags = 0; // drapeau d'état p->iId = m_iId = WEAPON_MONARME; // ID de l'arme p->iWeight = MONARME_WEIGHT; // priorité dans le choix automatique return 1; // tout s'est bien passé, on retourne 1 }
Pour les types de munitions, ils sont identifiés par des chaînes de caractère.
Ici, on a qu'un seul type donc pour le second on passe NULL
au type et
-1
à la capacité maximale. On verra un peu plus tard comment créer un
nouveau type de munitions. Pour les slots du HUD, rappelez-vous qu'ils commencent
à 0 donc il faut soustraire 1 à la vraie valeur ! De même pour la position dans
le slot.
On va maintenant passer à la fonction chargée d'ajouter l'arme à l'inventaire du joueur lorsqu'il la ramasse :
// -------------------------------------------------------------------------- // CMonArme::AddToPlayer // -------------------------------------------------------------------------- int CMonArme::AddToPlayer (CBasePlayer *pPlayer) { if (CBasePlayerWeapon::AddToPlayer (pPlayer)) { MESSAGE_BEGIN (MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev); WRITE_BYTE (m_iId); MESSAGE_END (); return TRUE; } return FALSE; }
C'est tout le temps la même : elle envoie un message au client après avoir appelé
CBasePlayerWeapon::AddToPlayer()
.
Les deux fonctions suivantes sont appelées lorsque le joueur sort l'arme et lorsqu'il la range (quand il en sélectionne une autre quoi) :
// -------------------------------------------------------------------------- // CMonArme::Deploy // // Déploiement de l'arme. // -------------------------------------------------------------------------- BOOL CMonArme::Deploy () { return DefaultDeploy ("models/v_monarme.mdl", "models/p_monarme.mdl", MA_DRAW, "onehanded"); } // -------------------------------------------------------------------------- // CMonArme::Holster // -------------------------------------------------------------------------- void CMonArme::Holster (int skiplocal /* = 0 */) { SendWeaponAnim (MA_HOLSTER); }
Dans la fonction Deploy()
on appelle DefaultDeploy()
qui va
être chargée d'initialiser les deux modèles « view » et « player ». Le
troisième paramètre est l'animation à jouer lorsqu'on sort l'arme (donc à prendre dans
l'enum du début) et le quatrième paramètre est une chaîne de caractère pour
identifier le type d'animation que le modèle du joueur doit jouer. Vous pouvez en utiliser
d'autres comme « python » (357), « mp5 » (mp5) ou « trip »
(tripmine) ou en créer vous même (voir ça avec votre modeleur ;-)).
Il y'a quatre animations par « type » d'arme :
La fonction Holster()
, elle, est appelée théoriquement lorsqu'on sélectionne
une autre arme. Elle sert à « dé-initialiser » l'arme si vous voulez. Par exemple,
si vous aviez un mode zoom, vous pouvez le désactiver ici si ce n'est pas fait par le joueur
avant. Vous pouvez également faire jouer une animation à votre modèle d'arme comme ici m'enfin
on a pas souvent le temps de la voir.
Voyons maintenant une des fonctions les plus importantes : PrimaryAttack()
!
Dans cette fonction vous y mettez ce que vous voulez que votre arme fasse au moment de l'attaque.
Ici, je vais vous montrer un exemple d'une simple arme à feu :
// -------------------------------------------------------------------------- // CMonArme::PrimaryAttack // // Premier mode de tir. // -------------------------------------------------------------------------- void CMonArme::PrimaryAttack () { if (m_pPlayer->pev->waterlevel == 3) { // on empèche de tirer si l'on est sous l'eau PlayEmptySound (); m_flNextPrimaryAttack = UTIL_WeaponTimeBase () + 0.15; return; } if (m_iClip <= 0) { // on empèche de tirer si l'on est à court de munitions PlayEmptySound (); m_flNextPrimaryAttack = UTIL_WeaponTimeBase () + 0.15; return; }
On vérifie d'abord si le joueur est sous l'eau ou s'il n'a plus de munitions. Dans ce cas,
impossible de tirer. PlayEmptySound()
joue le son weapons/357_cock1.wav
que l'on avait vu dans la fonction Precache()
. La variable
m_flNextPrimaryAttack
contient le temps d'attente entre deux tirs. On utilise
UTIL_WeaponTimeBase()
pour obtenir le temps actuel auquel on ajoute le temps
en secondes.
// volume du son/puissance du flash m_pPlayer->m_iWeaponVolume = NORMAL_GUN_VOLUME; m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; // on décrémente le compteur de munitions m_iClip--; // muzzleflash m_pPlayer->pev->effects |= EF_MUZZLEFLASH; // on force le modèle du joueur à jouer l'animation "shoot" m_pPlayer->SetAnimation (PLAYER_ATTACK1);
Bon ici y'a pas grand chose à dire. On ajuste quelques paramètres pour les « effets spéciaux », on décrémente le nombre de munitions du chargeur et on force le modèle du joueur à jouer l'animation « attack ».
On va aborder maintenant la partie la plus intéressante :
/////////////////////////////////////////////////////////////////////////////// // variables locales TraceResult tr; float flDistance = 8192; int rnd_seed = m_pPlayer->random_seed; Vector vecSrc = m_pPlayer->GetGunPosition (); Vector vecDir = m_pPlayer->GetAutoaimVector (AUTOAIM_5DEGREES); Vector vecEnd = vecSrc + vecDir * flDistance; Vector vecShellVelocity = m_pPlayer->pev->velocity + gpGlobals->v_right * UTIL_SharedRandomFloat (rnd_seed, 50, 70) + gpGlobals->v_up * UTIL_SharedRandomFloat (rnd_seed, 100, 150) + gpGlobals->v_forward * 25; Vector vecShellOrigin = pev->origin + m_pPlayer->pev->view_ofs + gpGlobals->v_up * -12 + gpGlobals->v_forward * 32 + gpGlobals->v_right * 6; // on applique les dommages à la cible m_pPlayer->FireBulletsPlayer (1, vecSrc, vecDir, VECTOR_CONE_3DEGREES, flDistance, BULLET_PLAYER_MONARME, 0, 0, m_pPlayer->pev, rnd_seed); // on joue l'animation "shoot" SendWeaponAnim (MA_SHOOT); // joue un son de tir EMIT_SOUND (ENT (m_pPlayer->pev), CHAN_WEAPON, "weapons/monarme_fire.wav", 0.8, ATTN_NORM); // on ejecte une douille EjectBrass (vecShellOrigin, vecShellVelocity, pev->angles.y, m_iShell, TE_BOUNCE_SHELL); // on dessine un decal d'impact sur le mur UTIL_TraceLine (vecSrc, vecEnd, dont_ignore_monsters, ENT (pev), &tr); DecalGunshot (&tr, BULLET_PLAYER_MONARME); ///////////////////////////////////////////////////////////////////////////////
J'ai mis toute cette partie du code entre des slashs car elle est susceptible de changer selon si vous voulez coder votre arme côté serveur seulement, avec des events ou côté serveurs et client.
Bon on a d'abord un première partie avec plein de nouvelles variables locales.
flDistance
est la distance maximale que les projectiles de l'arme peuvent
traverser (8192 est la taille d'une map). vecSrc
est la position de l'arme
dans l'espace (la source du tir), vecDir
est la direction pointée par le
canon. vecShellVelocity
est le vecteur vitesse initial de la douille (on
utilise des variables aléatoires pour plus de réalisme) et vecShellOrigin
son point d'origine.
On arrive aux appels de fonctions. FireBulletsPlayer()
est responsable
des dégâts causés par le tir. Voici sa déclaration (ne recopiez pas ce code
hein !) :
Vector CBaseEntity::FireBulletsPlayer (ULONG cShots, // nombre de projectiles tirés Vector vecSrc, // source du tir Vector vecDirShooting, // vecteur direction Vector vecSpread, // dispersion des projectiles float flDistance, // distance maximale int iBulletType, // type de projectile int iTracerFreq, // (inutilisé) int iDamage, // domages causés entvars_t *pevAttacker, // attaquant int shared_rand) // partage variables aléatoires avec client dll
L'argument vecSpread
sert pour la dispersion des balles, une sorte d'offset
d'inexactitude de l'arme. Différents vecteurs sont prédéfini pour, tous du style
VECTOR_CONE_#DEGREES
où # prend une valeur de 1 à 10, plus 15 et 20. Pour les
curieux, ils sont définis dans weapons.h. iBulletType
est le type de
projectile envoyé. Dans le code que je vous ai balancé j'ai mis BULLET_PLAYER_MONARME
mais il va falloir le déclarer (on verra plus tard coment). Je voudrais également
m'arrêter sur iDamage
. Si ce paramètre est différent de 0, les
skill cvar ne seront pas utilisées c'est pour cela que je l'ai
mis à 0, et il n'y a pas besoin ensuite d'appeler DecalGunshot()
. Il nous faudra
alors modifier la fonction FireBulletsPlayer()
pour qu'elle accepte
BULLET_PLAYER_MONARME et qu'elle cause quand même des dégâts à la cible. On fait
ensuite un simple appel à SendWeaponAnim()
pour jouer une animation de tir puis
EMIT_SOUND()
pour jouer un son. Pour ejecter une douille, on utilisera
EjectBrass()
qui prend en paramètres les vecteurs que nous avons calculés
plus haut, la variable membre m_iShell
contenant l'ID du modèle précaché
de la douille et le type de son lorsque la douille tombe au sol. Enfin,
DecalGunshot()
va nous dessiner un impact de balle contre le mur.
Reste à finir la fonction :
// on ajuste le temps avant de pouvoir tirer un nouveau coup m_flNextPrimaryAttack += 0.25; if (m_flNextPrimaryAttack < UTIL_WeaponTimeBase ()) m_flNextPrimaryAttack = UTIL_WeaponTimeBase () + 0.25; // on aléatoirement l'animation "idle" m_flTimeWeaponIdle = UTIL_WeaponTimeBase () + UTIL_SharedRandomFloat (m_pPlayer->random_seed, 10, 15); }
Pour m_flNextPrimaryAttack
, on a vu plus haut et pour
m_flTimeWeaponIdle
, ça marche pareil sauf que c'est le temps d'attente
avant d'executer la fonction WeaponIdle()
.
SecondaryAttack()
fonctionne exactement de la même manière sauf
qu'on utilise m_flNextSecondaryAttack
à la place de
m_flNextPrimaryAttack
. Ici nous n'avons pas de second mode de tir,
donc on laisse la fonction vide :
// -------------------------------------------------------------------------- // CMonArme::SecondaryAttack // // Second mode de tir. Non implémenté. // -------------------------------------------------------------------------- void CMonArme::SecondaryAttack () { }
Il nous reste deux fonctions. D'abord Reload()
:
// -------------------------------------------------------------------------- // CMonArme::Reload // // Recharge l'arme. // -------------------------------------------------------------------------- void CMonArme::Reload () { if (m_pPlayer->ammo_monarme <= 0) return; // on recharge m_iClip if (DefaultReload (MONARME_MAX_CLIP, MA_RELOAD, 1.5)) { // on joue un son EMIT_SOUND (ENT (m_pPlayer->pev), CHAN_WEAPON, "weapons/monarme_reload.wav", 0.8, ATTN_NORM); } }
Elle est très simple. Si on n'a plus de munitions, on sort de la fonction.
Sinon DefaultReload()
se charge de remplir le chargeur et de faire
jouer l'animation au modèle de l'arme. Son troisième paramètre est le temps en
seconde avant de pouvoir tirer de nouveau. Si le modèle de votre arme ne joue
pas de son (voir les events dans le .qc) vous pouvez le faire ici avec
EMIT_SOUND()
.
Et enfin la dernière fonction :
// -------------------------------------------------------------------------- // CMonArme::WeaponIdle // -------------------------------------------------------------------------- void CMonArme::WeaponIdle () { ResetEmptySound (); m_pPlayer->GetAutoaimVector (AUTOAIM_5DEGREES); if (m_flTimeWeaponIdle > UTIL_WeaponTimeBase ()) return; switch (RANDOM_LONG (0, 2)) { // on joue une animation "idle" au hasard default: case 0: SendWeaponAnim (MA_IDLE1); break; case 1: SendWeaponAnim (MA_IDLE2); break; case 2: SendWeaponAnim (MA_IDLE3); break; } // temps aléatoire avant de rappler cette fonction m_flTimeWeaponIdle = UTIL_WeaponTimeBase () + UTIL_SharedRandomFloat (m_pPlayer->random_seed, 10, 15); }
Pas grand chose à dire dessus. Elle est appelée lorsqu'on ne fait rien. Si vous avez plusieurs animations "idle", faites les jouer aléatoirement pour plus de réalisme.
Voilà, c'est fini pour la classe de notre arme.
Pour les dégâts on va utiliser les skill cvar. Commencez par aller dans skill.h
et ajoutez une variable membre à la structure skilldata_t
:
float plrDmgMonArme;
Cette variable contiendra les points de dommages de notre arme (enfin de ses projectiles plus exactement). Maintenant allez dans game.cpp et ajoutez le code suivant :
// Mon Arme cvar_t sk_plr_monarme_bullet1 = { "sk_plr_monarme_bullet1", "0" }; cvar_t sk_plr_monarme_bullet2 = { "sk_plr_monarme_bullet2", "0" }; cvar_t sk_plr_monarme_bullet3 = { "sk_plr_monarme_bullet3", "0" };
On crée en fait trois objets cvar_t
qu'on initialise immédiatement.
Maintenant dans la fonction GameDLLInit()
(toujours game.cpp)
on va les enregistrer :
// Mon Arme CVAR_REGISTER (&sk_plr_monarme_bullet1); // { "sk_plr_monarme_bullet1", "0" }; CVAR_REGISTER (&sk_plr_monarme_bullet2); // { "sk_plr_monarme_bullet2", "0" }; CVAR_REGISTER (&sk_plr_monarme_bullet3); // { "sk_plr_monarme_bullet3", "0" };
On va à présent définir la valeur de plrDmgMonArme
. Dans gamerules.cpp,
placez dans la fonction RefreshSkillData()
l'instruction suivante :
// Mon Arme
gSkillData.plrDmgMonArme = GetSkillCvar ("sk_plr_monarme_bullet");
Pour éviter la triche en multijoueur, et vu qu'il n'y a pas de niveaux de difficultés,
on va définir plrDmgMonArme
directement dans le code de notre DLL.
Déplacez-vous dans multiplay_gamerules.cpp, dans la même fonction
(RefreshSkillData()
) et placez le code suivant :
// Mon Arme gSkillData.plrDmgMonArme = 30;
Vous pouvez bien évidemment changer la valeur. Voilà c'est terminé pour les dégats.
Bon voilà, notre fling il est là mais p'têtre bien qu'il tire quelque chose !
Alors on va coder des balles. On a vu lorsqu'on a codé PrimrayAttack()
qu'on utilisait BULLET_PLAYER_MONARME
pour définir le type de projectile.
On va donc maintenant le déclarer. Dans weapon.h vers la ligne 190~200
normalement (si vous avez pas trop transformé le fichier), dans l'enum de
Bullet
, ajoutez BULLET_PLAYER_MONARME
:
// bullet types typedef enum { BULLET_NONE = 0, BULLET_PLAYER_9MM, // glock BULLET_PLAYER_MP5, // mp5 BULLET_PLAYER_357, // python BULLET_PLAYER_BUCKSHOT, // shotgun BULLET_PLAYER_CROWBAR, // crowbar swipe BULLET_PLAYER_MONARME, // mon arme BULLET_MONSTER_9MM, BULLET_MONSTER_MP5, BULLET_MONSTER_12MM, } Bullet;
Allez maintenant dans weapon.cpp et dans la fonction DecalGunshot()
,
ajoutez une ligne dans le switch de iBulletType
:
switch (iBulletType) { case BULLET_PLAYER_9MM: // ... case BULLET_PLAYER_357: case BULLET_PLAYER_MONARME: default: // smoke and decal UTIL_GunshotDecalTrace (pTrace, DamageDecal (pEntity, DMG_BULLET)); break;
Ça c'était pour les décals d'impact. Dans combat.cpp on va modifier la
fonction FireBulletsPlayer()
. Ici aussi trouvez le switch
de iBulletType
et ajoutez le bloc de code suivant :
case BULLET_PLAYER_MONARME: pEntity->TraceAttack (pevAttacker, gSkillData.plrDmgMonArme, vecDir, &tr, DMG_BULLET); break;
Ainsi lorsque PrimrayAttack()
appellera FireBulletsPlayer()
avec BULLET_PLAYER_MONARME
, les degats de gSkillData.plrDmgMonArme
seront attribués à la cible.
Quand je dis munitions je parle de chargeur. Commencez par aller dans cbase.h
et à la fin de la classe CBaseEntity
, ajoutez une variable membre :
int ammo_monarme;
Cette variable contiendra le nombre de munitions disponibles pour notre arme. Dans
TabulateAmmo()
(player.cpp) ajoutez cette instruction :
ammo_monarme = AmmoInventory (GetAmmoIndex ("mes munitions"));
mes munitions souvenez-vous, est la chaîne identifiant le type de munitions que
notre arme a besoin. Tout au début de stats.cpp, dans AmmoDamage()
,
ajoutez cette condition avant le return :
if (0 == strcmp (pName, "mes munitions")) return gSkillData.plrDmgMonArme;
Et Maintenant, on va créer une nouvelle classe pour un nouveau type de munitions.
Retournez dans le fichier de votre arme (monarme.cpp) et tout à la fin, après la
dernière fonction de CMonArme
(WeaponIdle()
normalement) ajoutez
le code du chargeur :
///////////////////////////////////////////////////////////////////////////// // // CMonArmeAmmoClip - classe chargeur pour mon arme. // ///////////////////////////////////////////////////////////////////////////// class CMonArmeAmmoClip : public CBasePlayerAmmo { void Spawn () { Precache (); SET_MODEL (ENT (pev), "models/w_mesmunitions.mdl"); CBasePlayerAmmo::Spawn (); } void Precache () { PRECACHE_MODEL ("models/w_mesmunitions.mdl"); PRECACHE_SOUND ("items/9mmclip1.wav"); } BOOL AddAmmo (CBaseEntity *pOther) { int bResult = (pOther->GiveAmmo (AMMO_MONARME_GIVE, "mes munitions", MONARME_MAX_CARRY) != -1); if (bResult) EMIT_SOUND (ENT (pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); return bResult; } }; LINK_ENTITY_TO_CLASS (ammo_monarme, CMonArmeAmmoClip);
Bon je passe les fonctions Spawn()
et Precache()
car elles
fonctionnent comme pour notre arme. Pour AddAmmo()
, elle n'est pas compliquée
non plus. La fonction principale est GiveAmmo()
qui prend comme paramètre
le nombre de munitions à donner, la chaîne de caractère identifiant le type de munitions
Eh voilà on arrive à la fin ! Il reste cependant quelques petits trucs à ajouter.
Dans la fonction W_Precache()
de weapon.cpp vous devez précacher
les armes et munitions sinon elles apparaissent pas ou alors c'est le crash :
UTIL_PrecacheOtherWeapon ("weapon_monarme"); UTIL_PrecacheOther ("ammo_monarme");
Et dans player.cpp, rajoutez ceci au cas 101 du switch de
iImpulse
(dans la fonction CheatImpulseCommands()
) pour avoir
votre arme en tapant dans la console « impulse 101 » :
GiveNamedItem ("weapon_monarme"); GiveNamedItem ("ammo_monarme");
Maintenant vous avez enfin terminé, vous pouvez compiler mp.dll !
Il faut aussi modifier le fichier *.fgd pour pouvoir ajouter votre nouvelle arme et votre nouveau type de munitions dans vos maps. Éditez le fichier avec un éditeur de texte (bloc note ou worpad) et ajoutez ceci :
@PointClass base(Weapon, Targetx) = ammo_monarme : "Mon Arme Ammo" [] @PointClass base(Weapon, Targetx) = weapon_monarme : "Mon Arme" []
Reste enfin hud.txt dans le dossier sprites. Ouvrez-le, commencez par remplacer le 123 par 125 à la toute première ligne (c'est le nombre de sprites HUD déclarés dans ce fichier) puis rajoutez :
d_monarme 320 320hud1 0 224 32 16 d_monarme 640 640hud1 192 16 32 16
Et n'oubliez pas de créer un weapon_monarme.txt toujours dans le dossier sprites pour le HUD (regardez ceux des armes déjà existance pour prendre exemple).
Voilà ! Bon j'espère que c'était pas trop long et que vous en avez appris quelque chose (à faire des armes tiens !). Je ne prends aucune responsabilité en cas de dommages, pertes, vols, explosions du PC, chien écrasé ou météore qui s'écrase sur votre maison. Si j'ai dis des trucs faux dans ce tutorial merci de me le signaler par mail (tfc.duke (CHEZ) gmail (POINT) com) ou autre ; si vous avez des questions, préférez les forums que le mail, d'autres pourrons aussi vous aider.
Cet article est mis à disposition sous un contrat Creative Commons (licence CC-BY-ND).