Les events

Écrit le 03/07/2003 par DukeNukem
Dernière mise à jour : 30/01/2006

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 decals 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 en 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...

Pour le moment, tout ce bordel n'est pas utilisé (peut-être pour un prochain SDK ?), et donc vos .sc perso pour vos events perso, mettez simplement: « Salut client.dll ! ça va ? et tes enfants ils vont bien aussi ? Comment qu'elle va la famille ? » pour créer un environnement de sympathie et une bonne ambiance entre les 2 dll. Nan 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 client dll)

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 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 2 paramètres ! En fait, le premier paramètre doit toujours être 1, donc pas de soucis (seulement ne l'oubliez pas). Oubliez pas non plus de créer myevent.sc.

Tous les events précachés sont envoyés à client.dll, lors de la connexion. Si vous ne possédez pas le *.sc que le serveur à 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 );

Voilà pour la description des paramètres :-) Maintenant nous allons pouvoir poursuivre notre exemple, placez-vous à l'endroit où y'aura besoin d'appeler le 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 ouais » et la vous allez me dire « et y'a 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 version 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 2 dll. 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 délai en 4ème paramètre et changer PLAYBACK_EVENT en PLAYBACK_EVENT_DELAY. Si vous voulez mettre votre code en opensource, et que vous êtes un salaud, 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( void )
{
    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 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 :/. Ca fait un peu lourd en effet, de devoir recopier la même chose 2 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 :