Wayback Machine
FEB MAY Jun
Previous capture 5 Next capture
2005 2006 2007
22 captures
15 Mar 01 - 5 May 06
sparklines
Close Help
So, you want to customise half-life to suit your own needs? Well, you can start by downloading the source code. Go to Wavelength and get it at once!


NOTICE:All the tutorials found on this page are designed to be written and compiled using MS Visual C++ 5 or greater. The learning edition is available at around £40 for students and £70 otherwise. I would recommend you get hold of a copy if you want to edit half-life since Valve's project files were done using Visual C++.

All About messages in Half-Life
In this tutorial I want to build upon what you may have learnt in the 'Finding your way around' Tutorial about the client DLL. This tutorial will describe the basics of adding new HUD componants to the client dll. Please make sure you have followed the instructions in 'Your First Mod' to make sure that you have the correct compile settings for the client dll (if Half-Life crashed when the console first rolled up you need to disable function inlining).

Half-life works by sending 'messages' over the connection. each message contains a series of predefined data, both the client DLL and the server DLL need to know what each message contains and how to use it, this is why we have the code for both the client and the server (thank you valve!). So, the first thing you need to do is to modify the server DLL to declare your new messages. I'm going to do this in the player class because there is much more code there for you to look at and learn by (player.cpp defines the CBasePlayer class which is a player in half-life).

As the previous tutorial describes what you need to do to add your own HUD componant you won't find that described here, instead, I will assume that you have already made you HUD componant and now want to add messages to it. In order to add a message you need to do the following:

In the game dll source code:

  • Declare a global integer to hold the id of your message provided by the half-life engine - many of these can be found around line 190 of player.cpp, they all begin with 'gmsg'

  • Register your message with the Half-Life engine, you will find this around line 3260 of player.cpp where REG_USER_MSG is used

  • Send your message from somewhere within the source code, look for MESSAGE_BEGIN and MESSAGE_END macros
  • In the client dll source code:

  • Declare a member function in your class to handle the message

  • Declare the message to the engine

  • Hook the message from the engine
  • I will cover each of these six points in order.

    Game DLL: Declaring a global integer
    The half-life messaging system uses integeres to determine what type of information each message sent contains, in order for it to work correctly the Engine needs to assign each message a unique id. Because of this you need a global variable to hold this unique id to be used whenever you want to send your message.

    Game DLL: Registering your message
    Now that you have declared you global id, you need to get Half-Life to assign an id to your message, you do this by using the return value of the REG_USER_MSG function:

    gmsgMyNewMsg = REG_USER_MSG("MyNewMsg", 1);
    The REG_USER_MSG function takes two parameters, the text name of your message (I think these are sent along with the id's to clients when they connect) and the size in bytes of your message. If you don't know the size of your message until you send it (for example if you are using strings) then you need to set the size to -1.

    Game DLL: Sending your message
    Of course now that you have declared your message yo actually want to send it at the appropriate time. This is done through the use of the MESSAGE_BEGIN, WRITE_x and MESSAGE_END macros. First call MESSAGE_BEGIN with parameters describing your message type and who you want to send it to, then call the WRITE_BYTE, WRITE_SHORT, WRITE_STRING, WRITE_COORD, WRITE_ANGLE, WRITE_CHAR, WRITE_ENTITY and WRITE_LONG macros to write your data to the message. Finally call MESSAGE_END to send your message. For example, the following code is used to send updated score information for the CBasePlayer plr to the CBasePlayer pl (i is a variable used to identify the current player number):

    MESSAGE_BEGIN( MSG_ONE, gmsgScoreInfo, NULL, pl->edict() );
    	WRITE_BYTE( i );	// client number
    	WRITE_SHORT( plr->pev->frags );
    	WRITE_SHORT( plr->m_iDeaths );
    MESSAGE_END();
    

    There are several ways of called MESSAGE_BEGIN, a brief description of each follows:
    MESSAGE_BEGIN( MSG_ALL, Message ID ), MSG_ALL sends the message to every player in the game, it requires only one parameter which is the unique message identifier returned from REG_USER_MSG.
    MESSAGE_BEGIN( MSG_ONE, Message ID, NULL, Player pev), MSG_ONE sends the message to one player only, it takes three further parameters, the message id, a NULL value, and the edict or pev member of a CBasePlayer class you will see both ->pev and ->edict() used in the Half-Life code.
    MESSAGE_BEGIN( MSG_BROADCAST, Message ID ). This is used to send non-critical messages to all clients, these messages are not guaranteed to arrive at the client. Takes the Message ID
    MESSAGE_BEGIN( MSG_PVS, Message ID, Origin) Used to send the message to all players who can see the point at 'origin', useful for only sending spark visuals to people who can actually see the sparks.
    MESSAGE_BEGIN( MSG_PAS, Message ID, Origin) Used to send the message to all players who can hear a sound played at 'origin', useful for only sending spark effects to people who can actually hear the sparks.

    Okay, so now you know how to call MESSAGE_BEGIN, I'd better describe what all the WRITE functions do:
    WRITE_BYTE used to write a single byte to the message (a char is one byte) Unsigned range 0-255, takes the byte itself as a parameter
    WRITE_SHORT used to write a short (two bytes) to the message. Takes the number itself as a parameter.
    WRITE_LONG used to write a long or int (four bytes) to the message. Takes the number as a parameter.
    WRITE_CHAR used to write a single character to the message (the same as WRITE_BYTE).
    WRITE_STRING is used to write a string (of any length) to the message, your message length needs to be -1 for this to work correctly. This takes a pointer to the NULL terminated string.
    WRITE_COORD used to write an individual co-ordinate of an object (32 bit/4 byte float). You need to call this for each x, y and z componant to write a full vector.
    WRITE_ANGLE writes a 32 bit floating point number to the message.
    WRITE_ENTITY. This function is not used, I assume it can be used to write an entity structure, but would advise against it's use.

    Having read all that you should now know how to set-up and send messages. I hope to cover the built in system messages in a future tutorial, but for now your going to have to put up with using your own or working out how to use them by yourself! Now we are going to learn how to recieve messages...

    Client DLL: Declaring the message handler
    First you need to create a function in your new HUD componant's class to handle the message. The name of this function is very important, it must start with 'MsgFunc_' and end in the text name of your message as you declared it in the game DLL, for example the following message is used by CHudScoreboard to handle the ScroeInfo message:

    MsgFunc_ScoreInfo

    Your message must return an int and take three parameters, a char * to the name of the message, an int specifying the size of the data and a pointer to the buffer holding the data. The following would be the declaration of the ScoreInfo message above:
    int CHudScoreboard :: MsgFunc_ScoreInfo( const char *pszName, int iSize, void *pbuf )

    Other than these limitations your function can be implemented as any other member function. I will describe how you extract data from the message later in this article.

    Client DLL: Telling the engine what to do with the message
    Now you need to tell the engine how to call you message handler, this is done using the DECLARE_MESSAGE macro. DECALRE_MESSAGE takes two parameters, the first is the name of your class' member variable in the CHud class and the second is the text name of your function to handle the message. Simply insert the call at the beginning of your class' implementation file, again, using the scoreboard example the code would look like this

    DECLARE_MESSAGE( m_Scoreboard, ScoreInfo );

    Client DLL: Hooking the message
    Finally you need to hook your message handler to the message, this is done through the HOOK_MESSAGE macro which is called in your class' Init function. It takes one parameter which is the text name of your message. For the ScoreInfo message the code would look like this:

    HOOK_MESSAGE( ScoreInfo );

    Now you have successfully declared, defined and hooked your message all that is left to do is to get some data out of it. This is done using the BEGIN_READ, READ_BYTE, READ_SHORT, READ_LONG, READ_CHAR, READ_STRING, READ_COORD, READ_FLOAT, READ_ANGLE, READ_HIRESANGLE and READ_WORD macros. Simply call BEGIN_READ with a pointer to your buffer as the first parameter and the size of the data in the buffer as the second then use the appropriate READ macros above to read back the data in the same order as it was written. Each macro returns a value of the appropriate type, so READ_SHORT returns a short and READ_STRING returns a char*. You should note that the buffer is temporary so any data you may want to access in th future will need to be copied out of the buffer (this mainly applies to strings).

    I think that concludes this tutorial on Messaging. It is worth noting that you can create your own custom client side commands (such as +showscores and -showscores) using a similar method with the HOOK_COMMAND and DECLARE_COMMAND macros. Take a look at the code already there for specific examples.

    Once again if anyone thinks I've made a mistake or have left anything out mail me with the info and I'll try and fix it.

    Fade


    COPYRIGHT NOTICE: Please do not just 'copy' information from these pages, I would prefer it if you just linked to this page rather than stealing my hard work and effort.