![]() |
|
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! |
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:
In the client dll source code:
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
int CHudScoreboard :: MsgFunc_ScoreInfo( const char *pszName, int iSize, void *pbuf )
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