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.

Introduction

Aujourd'hui, comment demander des petits services à la client.dll par le biais de la mp.dll !!! Mé ? comment qu'on va faire !? Et bien avec le SDK 2.0, est arrivé un nouveau machin bizarre qui comment par « Ev ».. et finit par « nt »... Allez je vous laisse chercher un peu... bon je vous aide, il manque une lettre... Meuh non pas Evant ! tss.. bon aller je vous le dis sinon on va encore y passer la nuit : il s'agit des Events ! Quoi c'était marqué dans le titre ? Ben vi que c'est dans le titre !! Quoi ? Vous êtes dégoûté ? arff... C'est trop tard maintenant.

Mé kézako ?

Les events ont été créés dans le but de diminuer la taille du flux de données network, et ainsi diminuer le lag, en intégrant des petits bouts de code dans la client.dll, appelés par la mp.dll, tel des animations, des effets sonores, application de décals d'impacts pour les armes, etc...

En fait, C'est en quelque sorte la mp.dll qui appelle une fonction de la client.dll. Évidemment, ces fonctions sont limitées. Vous ne pouvez pas par exemple décrémenter le nombre de vie du joueur, ou les munitions de son arme, et ces fonctions ne se coderont pas de la même manière que dans la mp.dll puisqu'on n'y retrouve pas les mêmes fonctions/classes, cependant certaines ont été copiées de la mp.dll et ressemble assez...

La question que vous devez vous poser maintenant, c'est comment on fait pour passer d'une dll à l'autre ? La réponse est simple, avec un fichier qui servira "d'intermédiaire". Ces fichiers, ce sont les .sc situés dans le répertoire « events » de votre mod (si vous ne l'avez pas, il est bien temps de le créer). Si vous prenez par exemple un des fichiers de valve, vous verrez par exemple à l'intérieur tout un script biscornu avec une sorte de fonction Fire_Glock() présente dans chaque fichier, et tout plein d'autres trucs qu'on voit pas trop d'où ça sort...

Le plus con dans tout ça c'est ce script ne sert strictement à rien (peut-être pour un prochain SDK ?), et donc vos .sc perso pour vos events perso, mettez simplement: « Salut client.dll ! ça va ? » pour créer un environnement de sympathie et une bonne ambiance entre les 2 dlls. Non je déconne :-) ne faites pas ça, vous auriez l'air trop con à la sortie de votre mod. Vous pourrez les laisser vide en fait, leur contenu n'a aucune importance.

Encore une remarque, chaque fichier .sc équivaut à UNE fonction event (UNE fonction de la dll client).

Appeler un event

On va commencer dans le mauvais sens, je sais, mais je pense que ce sera plus pratique comme ça. Nous sommes dans la mp.dll (ou hl.dll, ou votremod.dll, enfin c'est celle qu'est pas la dll client quoi)

En fait, c'est aussi facile que de créer une variable de type unsigned short, de lui assigner une valeur et de le faire passer comme paramètre à une fonction. Commençons par nous rendre dans la classe de notre objet, et d'y ajouter la variable membre :

private:
  unsigned short m_usMyEvent;

Puis maintenant, nous allons précacher le fichier qui nous servira de lien, dans votre fonction Precache() :

m_usMyEvent = PRECACHE_EVENT (1, "events/myevent.sc");

Tiens, PRECACHE_EVENT possède deux paramètres ! En fait, le premier paramètre doit toujours être 1, donc pas de soucis (seulement ne l'oubliez pas). N'oubliez pas non plus de créer myevent.sc.

Tous les events précachés sont envoyés à la client.dll, lors de la connexion. Si vous ne possédez pas le .sc que le serveur a besoin, il sera automatiquement téléchargé depuis le serveur (si le téléchargement est autorisé bien sur).

Maintenant, il nous reste une dernière chose à faire pour appeler l'event. Nous allons utiliser pour ça une macro définie dans enginecallback.h : PLAYBACK_EVENT_FULL qui vaut la même chose que (*g_engfuncs.pfnPlaybackEvent) à 12 paramètres, mais qui est plus compréhensible. La déclaration de fonction pfnPlaybackEvent se présente ainsi :

void pfnPlaybackEvent (int flags,
                       const edict_t *pInvoker,
                       unsigned short eventindex,
                       float delay,
                       float *origin,
                       float *angles,
                       float fparam1,
                       float fparam2,
                       int iparam1,
                       int iparam2,
                       int bparam1,
                       int bparam2);
Le 1er paramètre, c'est pour les flags. Vous trouverez la liste des flags valables dans common/event_flags.h (y'en a 7 et ils commencent par FEV).
Le 2ème c'est l'entité qui « appelle » l'event. C'est pour la plupart du temps, m_pPlayer->edict() ou edict() tout court qui sont utilisés ici.
Le 3ème c'est notre event ! ici on utilisera m_usMyEvent.
Le 4ème est un délai en seconde (je crois que c'est ça) avant que l'event ne soit exécuté côté client.
Les 5 et 6ème sont des vecteurs si c'est pour une entité spéciale... Si vous n'avez rien de spéciale à mettre, mettez (float *)&vec_Zero pour les 2.
Viens maintenant toute une liste de paramètres mis à disposition du codeur pour qu'il puise faire passer les valeurs qu'il veut à la dll client, comme la température extérieure, le pris du kilo de patate au marché, l'âge du capitaine, etc... Vous avez en libre service : 2 float, 2 int et 2 valeurs booléenne (d'après Valve, parce que moi je vois 4 int).

Voilà pour la description des paramètres. Maintenant nous allons pouvoir poursuivre notre exemple, placez vous à l'endroit où y'aura besoin d'appeler l'event, et mettez par exemple pour une arme :

PLAYBACK_EVENT_FULL (0, m_pPlayer->edict(), m_usMyEvent, 
                     0, (float *)&vec_Zero, (float *)&vec_Zero, 
                     0.0, 0.0, 0, 0, 0, 0);

Vous allez me dire, « mais put1 ça fait des kms d'inutile !!! » et moi je vais vous répondre « ben ouai » et la vous allez me dire « et ya rien pour faire plus simple? » alors moi je vais vous répondre « si » et là, vous allez me demander c'est quoi et moi je vais vous expliquer ce que c'est :

Il existe des versions simplifiées de PLAYBACK_EVENT_FULL, qui sont PLAYBACK_EVENT à 3 paramètres et PLAYBACK_EVENT_DELAY à 4 paramètres définies dans util.h. Dans les 2 cas, on utilise les 3 premiers paramètres de PLAYBACK_EVENT_FULL, c'est à dire les flags, « l'appelant » et l'index (qu'on récupère en précachant) du fichier qui sert de transition entre les deux dlls. Pour PLAYBACK_EVENT_DELAY, le 4ème paramètre, je vous laisse deviner... c'est pas dur, c'est même les 5 derniers caractères du nom de la macro...

On peut donc dans notre cas, mettre simplement :

PLAYBACK_EVENT (0, m_pPlayer->edict (), m_usMyEvent);

Notez que je ne fais pas passer de flags. Vous pouvez rajouter le delay en 4ème paramètre et changer PLAYBACK_EVENT en PLAYBACK_EVENT_DELAY. Si vous voulez mettre votre code en open source, et que vous êtes un salop, vous pouvez mettre (*g_engfuncs.pfnPlaybackEvent)( /* blablabla... avec tous les paramètres même les inutiles */ ) mais rien que pour vous, je vous recommande d'utiliser plutôt les macros.

Voilà, reste plus qu'à voir ce qui se passe côté client.

Créer un event

Nous sommes maintenant dans la client.dll, et nous allons créer la fonction qui devra être exécutée à l'appelle de l'event.

Vous avez un petit sous dossier nommé « hl » dans votre workspace, ouvrez-le et allez dans hl_events.cpp. Dans la partie extern "C", rajoutez votre nouvelle fonction à la suite des autres :

extern "C"
  {
    // HLDM
    void EV_FireGlock1 (struct event_args_s *args);
    void EV_FireGlock2 (struct event_args_s *args);
    void EV_FireShotGunSingle (struct event_args_s *args);
    void EV_FireShotGunDouble (struct event_args_s *args);
    void EV_FireMP5 (struct event_args_s *args);
    void EV_FireMP52 (struct event_args_s *args);
    void EV_FirePython (struct event_args_s *args);
    void EV_FireGauss (struct event_args_s *args);
    void EV_SpinGauss (struct event_args_s *args);
    void EV_Crowbar (struct event_args_s *args);
    void EV_FireCrossbow (struct event_args_s *args);
    void EV_FireCrossbow2 (struct event_args_s *args);
    void EV_FireRpg (struct event_args_s *args);
    void EV_EgonFire (struct event_args_s *args);
    void EV_EgonStop (struct event_args_s *args);
    void EV_HornetGunFire (struct event_args_s *args);
    void EV_TripmineFire (struct event_args_s *args);
    void EV_SnarkFire (struct event_args_s *args);

    void EV_TrainPitchAdjust (struct event_args_s *args);

    void EV_MyEvent (struct event_args_s *args);
}

Notre fonction s'appellera donc EV_MyEvent et elle aura un paramètre, c'est pour ensuite pouvoir récupérer les variables que l'on a fait passer en paramètre à pfnPlaybackEvent() dans la mp.dll. Ensuite, descendez un petit peu dans ce fichier, et dans la fonction Game_HookEvents(), rajoutez le vôtre sur le modèle des autres :

void
Game_HookEvents ()
{
  gEngfuncs.pfnHookEvent ("events/glock1.sc",     EV_FireGlock1);
  gEngfuncs.pfnHookEvent ("events/glock2.sc",     EV_FireGlock2);
  gEngfuncs.pfnHookEvent ("events/shotgun1.sc",   EV_FireShotGunSingle);
  gEngfuncs.pfnHookEvent ("events/shotgun2.sc",   EV_FireShotGunDouble);
  gEngfuncs.pfnHookEvent ("events/mp5.sc",        EV_FireMP5);
  gEngfuncs.pfnHookEvent ("events/mp52.sc",       EV_FireMP52);
  gEngfuncs.pfnHookEvent ("events/python.sc",     EV_FirePython);
  gEngfuncs.pfnHookEvent ("events/gauss.sc",      EV_FireGauss);
  gEngfuncs.pfnHookEvent ("events/gaussspin.sc",  EV_SpinGauss);
  gEngfuncs.pfnHookEvent ("events/train.sc",      EV_TrainPitchAdjust);
  gEngfuncs.pfnHookEvent ("events/crowbar.sc",    EV_Crowbar);
  gEngfuncs.pfnHookEvent ("events/crossbow1.sc",  EV_FireCrossbow);
  gEngfuncs.pfnHookEvent ("events/crossbow2.sc",  EV_FireCrossbow2);
  gEngfuncs.pfnHookEvent ("events/rpg.sc",        EV_FireRpg);
  gEngfuncs.pfnHookEvent ("events/egon_fire.sc",  EV_EgonFire);
  gEngfuncs.pfnHookEvent ("events/egon_stop.sc",  EV_EgonStop);
  gEngfuncs.pfnHookEvent ("events/firehornet.sc", EV_HornetGunFire);
  gEngfuncs.pfnHookEvent ("events/tripfire.sc",   EV_TripmineFire);
  gEngfuncs.pfnHookEvent ("events/snarkfire.sc",  EV_SnarkFire);

  gEngFuncs.pfnHookEvent ("events/myevent.sc",    EV_MyEvent);
}

C'est ici qu'on dit à la dll client que le fichier « intermédiaire » entre la mp.dll et notre fonction event EV_MyEvent, c'est myevent.sc.

Allez maintenant dans ev_hldm.cpp et là, rebelotte : on va devoir refaire la même chose avec le extern "C" qu'il y a au début de ce fichier :/. Ça fait un peu lourd en effet, de devoir recopier la même chose deux fois... Perso moi ce que j'ai fait, c'est que j'ai déplacer cet extern "C" dans event.h et d'ajouter un include dans hl_events.cpp pour n'avoir qu'un seul extern "C" sur ces fonctions (attention par contre lors de ce genre de manip', je ne vous dis pas de le faire ici, et je ne prends aucune responsabilité en cas de pb...).

Puis rendez-vous tout à la fin de ev_hldm.cpp pour y ajouter notre fonction :

void
EV_MyEvent (event_args_t *args)
{
  // PLACEZ VOTRE CODE --> ICI <--
}

Voilà, vous avez votre fonction event ! Maintenant, quelques petits trucs quand même avant la fin de l'émission :

Si vous codez un event pour une arme, et que vous voulez faire jouer une anim' à votre arme, n'oubliez pas dans event.h d'ajouter l'enum de vos anims comme dans la mp.dll !
Vous avez déjà un certain nombre de fonctions de base pour vous permettre de coder votre event (EV_HLDM_FireBullets(), EV_EjectBrass(), EV_MuzzleFlash(), ...), principalement dans ev_hldm.cpp.
Pour récupérer les arguments que vous avez envoyé depuis la mp.dll, c'est votre objet args de type event_args_s qui vous sera utile. event_args_s est une structure définie dans event_args.h ; les noms des 6 paramètres sont les mêmes que dans la mp.dll donc pour les réutiliser : args->fparam1, args->iparams2, etc...
Encore une fois, n'oubliez pas de créer le fichier event .sc.
Creative Commons Logo Contrat Creative Commons

Cet article est mis à disposition sous un contrat Creative Commons (licence CC-BY-ND).