Location Main > Metamod-P Extension Notes > Hooking UserMsgs/GameEvents


Login

Hooking UserMsgs/GameEvents


User Messages are messages which are sent asynchronously between server and client to affect different aspects of GoldSrc.

Game Events are sent as UserMsgs with a header that consists of a specific user message and then usually follow-up data that contains information to carry out the event. 
https://wiki.alliedmods.net/Half-life_1_game_events

Some game events are simple and only consist of the entity edict and a byte, such as the 'health' message which is sent if the user's health changes.

By simple logging, you can log if a user has died by a few different user messages: health, setfov, startsound, etc.. and react based on the information given.


void hMessageBegin(int msg_dest, int msg_type, const float *pOrigin, edict_t *ed)
{
    if (msg_type == GET_USER_MSG_ID(PLID, "SetFOV", NULL) && ed) //if SetFOV & ed
    {
        for(int i = 1; i<32;i++)       
        if (ed->v.health <= 0.0f && g_Players[i].pEdict == ed)
        {
            g_Players[i].State = DEAD;
            LOG_CONSOLE(PLID, "(%s) index=%i died.\n", g_Pl[i].Name, g_Pl[i].index);
        }

    }

    RETURN_META(MRES_IGNORED);
}


On the client, one can simply use gEngfuncs.pfnHookUserMsg to intercept events and messages that came from the server.

On the server, it can take a while before you realize that even the functions which appear relevant for hooking messages or events are not.



1) Hooking

In my implementation I simply created a linked list to manage the positions of decals/sprays and display the client who sprayed the data.

The functions you want to hook to modify or simply catch user messages are all of the pfnMessageBegin...pfnMessageEnd, and pfnWriteByte, pfnWriteShort, pfnWriteCoords, etc..




Example of hooking the relevant export functions:
C_DLLEXPORT    int    hGetEngineFunctions(enginefuncs_t *pEngfuncs, int *) {
    pEngfuncs->pfnMessageBegin = hMessageBegin;
    pEngfuncs->pfnWriteByte = hWriteByte;
    pEngfuncs->pfnWriteString = hWriteString;
    ...


2) UserMsgs

Each message is sent asynchronously from the server to the players, but it is still done very fast.


This is one of the usage of pfnUserRegMsg as you can find the index of something as it is registered and store it for later; but it is depreciated by GET_USER_MSG_ID.



Fully functional example of supressing the 'left game' message which is part of the engine, but uses the SayText user-message:

bool g_bHookText = false;
void hMessageBegin(int msg_dest, int msg_type, const float *pOrigin, edict_t *ed)
{
    if (msg_type == GET_USER_MSG_ID(PLID, "SayText", NULL))
    g_bHookText = true; // this message is a Saytext type
   
    RETURN_META(MRES_IGNORED);
}
...
void hWriteString(const char *sz)
{
    if (g_bHookText && strstr(sz, "has left the game"))
    {
        memset((void*)sz, 0, strlen(sz)); // nullify the message, now it won't be sent
        RETURN_META(MRES_SUPERCEDE);
    }
   
    RETURN_META(MRES_IGNORED);
}
...
void hMessageEnd()
{
    g_bHookText = false; // the current sequence ended, just set this to false again

    RETURN_META(MRES_IGNORED);
}


3) "Game Events"

One of the more useful events is SVC_TEMPENTITY, which allows you to do more advanced modding and deal with entity data.

Let's try to implement a way to find when a user sprays a decal or fires a weapon:

 
the header for TE_PLAYERDECAL in the SDK (const.h):
#define TE_PLAYERDECAL 112
// write_byte(TE_PLAYERDECAL)
// write_byte(playerindex)
// write_coord(position.x)
// write_coord(position.y)
// write_coord(position.z)
// write_short(entity???)
// write_byte(decal number)
// [optional] write_short(model index)

The problem with this is that it wasn't explained that TE_ stands for 'Temp Entity' and that all of these TE_ events begin with the usermessage SVC_TEMPENTITY (23).

I will just post an example and then explain it:


BYTE g_bHookSpray = 0;
void hMessageBegin(int msg_dest, int msg_type, const float *pOrigin, edict_t *ed)
{
    if (msg_type == SVC_TEMPENTITY) g_bHookSpray = 1; // SVC_TEMPENTITY message is being sent

    RETURN_META(MRES_IGNORED);
}

void hWriteByte(int iValue)
{
    if (g_bHookSpray== 1 && iValue == TE_PLAYERDECAL) // check if it's a spray
    {
        g_bHookSpray = 2;
    }
    else if (g_bHookSpray == 2)
    {
        iValue; // iValue here is player index
        g_bHookSpray = 3;
    }
    else if(g_bHookSpray>6)
    {
        iValue; // iValue here is the decal's entity number if needed
    }
}

void hWriteCoords(float fValue)
{
    if (g_bHookSprayMessage == 3)
    {
      fValue; // fValue here = x coordinate of spray
      g_bHookSprayMessage++;
    }
    else if (g_bHookSprayMessage == 4)
    {
      fValue; // fValue here = y coordinate of spray
      g_bHookSprayMessage++;
    }
    else if (g_bHookSprayMessage == 5)
    {
      fValue; // fValue here = z coordinate of spray
      g_bHookSprayMessage = 6;
    }

    RETURN_META(MRES_IGNORED);
}

void MessageEnd()
{
    g_bHookSpray = 0; // sequence is over, so reset the counter
    RETURN_META(MRES_IGNORED);
}


Brief explanation:
Because the data is sent asynchronously, and there are no multi-threaded or other complexity factors, you can determine that each element will be sent in order and thus the counter is not exactly needed except for explanation purposes.

To get the coordinates, you must grab each float value in a hooked pfnWriteCoord by applying the same method as above. Note a 3d vector is just X,Y, Z, 3 float values.

Once you do this, you can devise a linked list to store the position of each spray, the client who sprayed it, and if another client looks at that region it will display that information to them until the decal is erased (which also needs to be handled).