Tutorial 1 for SDK 1.0
Text Messages Part 1
by Trevor Hogan

idStr (id Strings)

You'll need to know how to work with the idStr class if you want to manipulate strings in Doom 3. idStr is a comprehensive string class comparable to the STL's std :: string and is defined in idLib/Str.h. Check out the class definition for an idea of what functions are available. Here's a short introduction.

idStr string1;
idStr string2;

string1 = "hello";

// id has overloaded sprintf to work with idStr
// see Str.h:254 and Str.cpp:1503

int oranges = 5;

sprintf( string2, "I have %d oranges!", oranges );

// you can also initialize an idStr in its constructor

idStr string3( "goodbye" );

// idStr supports c_str( ) just like std :: string

const char *cstr = string1.c_str( );

If you've worked with std :: string before then you shouldn't have any problems with idStr. There is one major difference however - colours. Here's an excerpt from my copy of idLib/Str.h (your copy may be different if you have a newer version of the SDK).

// color escape character
const int C_COLOR_ESCAPE                  = '^';
const int C_COLOR_DEFAULT                 = '0';
const int C_COLOR_RED                     = '1';
const int C_COLOR_GREEN                   = '2';
const int C_COLOR_YELLOW                  = '3';
const int C_COLOR_BLUE                    = '4';
const int C_COLOR_CYAN                    = '5';
const int C_COLOR_MAGENTA                 = '6';
const int C_COLOR_WHITE                   = '7';
const int C_COLOR_GRAY                    = '8';
const int C_COLOR_BLACK                   = '9';

If a string contains "^x" where x is an integer from 0 to 9 then Doom 3 will interpret that character sequence as a colour change. All subsequent text will be printed in the specified colour as defined in the above code. Doom 3 respects colour escape characters both in the console and in the HUD. Here's an example (note that I'm running this line of code once every frame so it fills my console).

common->Printf( "Regular Text ^1Colour 1 ^2Colour 2 ^3This should be yellow!\n" );

image1

Console Messages

As you've already seen, printing messages to the console in Doom 3 is relatively straightforward. Console printing functionality is exposed through the idCommon class defined in framework/Common.h. Let's take a look.

// Prints message to the console, which may cause a screen update if com_refreshOnPrint is set.
virtual void                        Printf( const char *fmt, ... ) = 0;

// Same as Printf, with a more usable API - Printf pipes to this.
virtual void                        VPrintf( const char *fmt, va_list arg ) = 0;

// Prints message that only shows up if the "developer" cvar is set,
// and NEVER forces a screen update, which could cause reentrancy problems.
virtual void                        DPrintf( const char *fmt, ... ) = 0;

The Doom 3 SDK provides access to the idCommon class through a global pointer called common defined in game/Game_local.cpp and initialized in GetGameAPI. You'll probably use the Printf variant the most and thankfully it works just like the standard C printf (but watch out for that capital P). You may notice that id often calls gameLocal.Printf to print to the console but if you check the source code you'll find that gameLocal's Printf just wraps around common's Printf. I'm not entirely sure why they do this but either method works fine.

HUD Messages

If you've worked with Half-Life before you'll know that printing messages to the HUD can be extremely useful. Unfortunately it's not so easy in Doom 3 but it can be done. Doom 3 does not provide access to any printing or drawing functions in the source code so you'll have to rely on a GUI script to help you. If you're working on a singleplayer mod, open guis/hud.gui and look for the code shown below.

Correction (November 1, 2004): You can print messages to the HUD directly from the SDK. Please see Tutorial 4 for more information.

onNamedEvent Message {
      transition "message1a::forecolor" "0 0 0 1" "0 0 0 0" "5000" "1" "0" ;
      transition "message1::forecolor" "1 1 1 1" "1 1 1 0" "5000" "1" "0" ;
}

// ... snip ...

windowDef message1a {
      rect 21, 261, 638, 10
      forecolor 0, 0, 0, 0
      text "gui::message"
      textscale 0.25
      font "fonts/an"
}
windowDef message1 {
      rect 20, 260, 638, 10
      forecolor 1, 1, 1, 0
      text "gui::message"
      textscale 0.25
      font "fonts/an"
}

The first section of code tells the HUD to make message1a and message1 visible and start a 5 second nonlinear fadeout effect whenever the "Message" event is received. The other two blocks of code define the message1a and message1 objects and give them a default message of "gui::message". At this point you might think this is a dead end but it turns out that "gui::message" actually refers to a variable, not a static string. That's all you need to know about this particular GUI script because the SDK completes the puzzle by giving us an interface to the HUD. One important thing to note is that message1a and message1 are defined in the original singleplayer HUD GUI; you don't need to modify your HUD GUI to make this work. However, if you want to display a bigger message, a message in another location, or a second or third message at the same time then you'll need to modify your HUD GUI. Also, the SDK only seems to use these particular objects when saving a game but other GUI scripts might use them as well. It's probably safe to hijack them for your own purposes but you could always create another set of objects just to be safe.

Alright, it's time to find the HUD interface in the SDK. Open game/Player.h and find the code shown below.

idUserInterface *       hud;

The idUserInterface class is defined in ui/UserInterface.h and represents any GUI in the game. This class offers a wide range of GUI functions but we're interested in HandleNamedEvent and SetStateString in particular. If you can get ahold of the player's hud pointer you can change the "gui::message" string and fire the "Message" event like this.

if( hud )
{
      hud->SetStateString( "message", "your text here" );
      hud->HandleNamedEvent( "Message" );
}

So how do you get ahold of the player's hud pointer? One possible method is shown below.

idPlayer *player = gameLocal.GetLocalPlayer( );

if( player && player->hud )
{
      // go for it!
}

For a slightly more practical example, say you want to know what the ground normal is underneath the player for debugging purposes. You could always print it to the console but it's so much nicer to watch it change directly on the HUD. Open game/physics/Physics_Player.cpp and put this code in MovePlayer somewhere after the call to CheckGround.

if( groundPlane )
{
      idStr strGNorm;

      sprintf( strGNorm, "ground normal: ^1%f ^2%f ^4%f",
            groundTrace.c.normal.x,
            groundTrace.c.normal.y,
            groundTrace.c.normal.z );

      idPlayer *pPlayer = static_cast<idPlayer *>( self );

      if( pPlayer && pPlayer->hud )
      {
            pPlayer->hud->SetStateString( "message", strGNorm.c_str( ) );
            pPlayer->hud->HandleNamedEvent( "Message" );
      }
}

image2

At this point I'm still a bit confused about the seperation between client and server so I don't know if this will work in multiplayer games. You might be able to use the chat GUI or you might have to add your own element to the multiplayer HUD (guis/mphud.gui). Go and experiment!

October 29, 2004