Thanks to some tips I found at the SourceMod.net forums, I have devised out a simple way to print HUD messages in server plugins.
You certainly know how to print messages to console (using Msg()), and how to print hint messages and dialog boxes (thanks to the examples provided by Valve in the empty server plugin .cpp file), but printing real HUD messages using SayText network messages like in HL1 was problematic:
1. Because Valve doesn't provide server plugins with an interface for knowing about user message IDs and names, and
2. Because in order to send a user message you need to include a recipient filter class and there is no simple way to include the SDK one in a plugin.
To solve 1, the SourceMod guys (I think it was VanceLorgin) found out that the SayText user message ID is 3. Since the code in the SDK that deals with user messages is located in "game_shared", we can without many risk assume that this value will remain constant between most if not all game DLLs.
To solve 2, you'll have to write your own recipient filter class. It doesn't need to be very complicated. A "recipient filter" is a structure that tells the Source engine about the recipients of a particular network message. This is usually a list of entity indices (such as player indices).
Here's the one I made:
Code:
// helper class
class SimpleRecipientFilter: public IRecipientFilter
{
public:
SimpleRecipientFilter (void) { recipient_count = 0; };
~SimpleRecipientFilter (void) { };
virtual bool IsReliable (void) const { return (false); };
virtual bool IsInitMessage (void) const { return (false); };
virtual int GetRecipientCount (void) const { return (recipient_count); };
virtual int GetRecipientIndex (int slot) const { return (((slot < 0) || (slot >= recipient_count)) ? -1 : recipients[slot]); };
void Flush (void) { recipient_count = 0; };
void AddPlayer (int player_index)
{
if (recipient_count > 255)
return;
recipients[recipient_count] = player_index;
recipient_count++;
}
private:
int recipients[256]; // NOTE: this filter cannot be used for more than 256 recipients
int recipient_count;
};
You need to copy and paste it somewhere in your code, either in a .h file you create, or directly inside a .cpp file.
As you can see, this class is derived from IRecipientFilter. As a result, don't forget to
#include "IRecipientFilter.h" in your project, because that's where this parent class is defined.
Now, you also need to know how to send network messages. Contrarily to HL1, in Source the network message can be a stream of bits, and not bytes. This is obviously to save on bandwidth. All the data must pass through a serialization and deserialization interface when they are sent and received. This is why network messages have their own class for writing and reading bits and bytes: bf_read and bf_write. The WriteString(), WriteByte() etc. functions you know from the HL1 enginefuncs have been moved into the bf_write class, which is the one we are interested in here (since we want to WRITE a SayText message). To use them, you must
#include "bitbuf.h" in your project files.
We are ready to send our message now.
Code:
void HUD_printf (char *string, edict_t *pPlayerEdict)
{
// this function sends "string" over the network for display on pPlayerEdict's HUD
bf_write *netmsg; // our network message object
SimpleRecipientFilter recipient_filter; // the corresponding recipient filter
// BUG FIX: the Source engine "eats" the last character from HUD SayText messages :(
strcat (string, "\n"); // so add an arbitrary carriage return to the string :)
// pPlayerEdict is a pointer to the edict of a player for which you want this HUD message
// to be displayed. There can be several recipients in the filter, don't forget.
recipient_filter.AddPlayer (engine->IndexOfEdict (pPlayerEdict));
// HACK: Valve doesn't permit server plugins to know about net messages yet, we have
// to figure out ourselves the message numbers, which is not nice. Here "SayText" is 3.
// Hopefully this network message code being in game_shared, we can assume many game DLLs
// will keep the same message number. Until Valve gives us another interface...
netmsg = engine->UserMessageBegin (&recipient_filter, 3);
netmsg->WriteByte (0); // index of the player this message comes from. "0" means the server.
netmsg->WriteString (string); // the HUD message itself
netmsg->WriteByte (1); // I don't know yet the purpose of this byte, it can be 1 or 0
engine->MessageEnd ();
return; // et voilą
}
Here it is. The only thing really inelegant here, is the hardcoded network message number. But perhaps in the future, Valve will update the SDK so that we can load a true network message interface from the gameServerFactory. With such an interface we will then be able to query network message IDs from their names, like in a game DLL.
*edit* fixed typo, added to wiki