VERC: How To Enable HLTV In Your Mod Last edited 3 years ago2019-04-17 09:00:38 UTC

HL SDK 2.2

This article was originally made available as part of version 2.2 of the Half-Life SDK. As such, all limitations, restrictions, license agreements, copyrights and trademarks from the original document apply here.

Changes in the game DLL

The proxy connects to the game server as a normal client. During the connection process the engine calls ClientPutInServer(edict_t *pEntity). A connecting proxy has set the new FL_PROXY flag in pEntitiy->v.flags. Make sure to keep this flag while spawning the new player. The proxy sends a client command "spectate" after it is fully connected. Then the proxy should be assigned to some kind of spectator team (as used in TFC or CS). In SetupVisibility() for all clients with FL_PROXY flag set, a NULL PVS & PAS is returned. That way all entities and events are transmitted to spectator proxies.

The proxy buffers the game stream for at least 10 seconds. During this time a director module tries to analyse the game and find interesting camera positions. Since the proxy doesn't know the logic of each MOD, the game DLL can send special hints about important game events. These director events contain at least one entity number of the primary involved entity. If a second entity number is given, the director knows that this event is an interaction between these two entities. For example killer & victim or backpack & player who picks it up. Because lots of events may happen at the same time, they should have priorities. A priority is a value between 15 and 1, where 15 is the most important event. The director module sorts the events based on time and priority, thus always the most interesting scene is shown. If no information about events is given at all, the director module uses players' positions and directions to choose a good camera position.

A typical event message in CHalfLifeMultiplay::DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pevInflictor )
     WRITE_SHORT( ENTINDEX(pVictim->edict()) );
MSG_SPEC ensures that only proxies get these messages. SVC_SPECTATOR is a new server command used for all proxy-related functions. All director event messages start with the DRC_EVENT byte. The first short Integer is the entity number of the primary object. The second short Integer is the number of another involved entity, but it could also be null, in which case the director module chooses the closest player as the secondary camera target, if possible. The last long Integer contains the priority (7 in this example) and some additional information about the type of the event. In this case it's a dramatic event, so the camera position and angle is chosen lower and nearer to emphasize this.

Changes in the client DLL

The client DLL contains many more changes than the game DLL. Here all of the overview drawing and the inset window is handled. Most of the changes are just "fancy features" and not really necessary for standard spectating. First of all, the engine makes some new functions available to the client DLL. IsSpectateOnly() returns true if the client is connected to a HLTV proxy or retrieves data from a multicast stream. LoadMapSprite(char *filename) loads a TGA file (or 256 color BMP in software mode), cuts it into small tiles of 128x128 pixel, and stores it in the frames of a sprite structure. This way you can load a large map image and draw it within HUD_DrawNormalTriangles().

Most of the proxy spectator-related code is in the new class CHudSpectator. Some of the code may look unusual because the logic in multicast mode is a bit different. The client must do everything on its own, especially handling keyboard events in HudSpectator::HandleButtons(), since there is no real game DLL that the client is connected to. There is also a callback function, HUD_DirectorEvent(), which is executed by the engine if a SVC_DIRECTOR message is received. Each time a client enters a level as spectator, the DRC_ACTIVE event is fired so the client can reset all settings, parse the overview description file for the level, and load the overview map image. During the game, the proxy sends a DRC_CAMERA event at certain time intervals that tells the client which targets it should follow in "Directed Mode."

The old engine asked the client DLL each frame with V_CalcRefdef() about the current viewpoint and view direction. Then it drew the world, and the client could add some extra objects in HUD_DrawNormalTriangles(). Now, you can draw more than one view per frame. All you have to do is set pparams->nextFrame to something other than null. Also, each time you can change the size and position of the next viewport by filling the pparams->viewport structure. Finally, if you set pparams->onlyClientDraw, the engine won't draw anything except objects in HUD_DrawNormalTriangles() or HUD_DrawTransparentTriangles(). If you leave all these new fields untouched, the engine will behave exactly as it used to. All these new features are used in V_CalcSpectatorRefdef(), but they could also be used for normal clients (for example, you could do a rearview mirror for players ). Note that all of these features are only available in OpenGL/DirectX Mode, not Software Mode (except pparams->onlyClientDraw).

The map image is drawn in CHudSpectator::DrawOverviewLayer(). By default, a map image is divided into 8*6 tiles, which are stored in the frames of one sprite. If no map image is present, a green grid will be drawn instead. The entity icons are stored in a list m_OverviewEntities[]. This list is updated each frame in HUD_AddEntity() for every entity by calling CHudSpectator::AddOverviewEntity(). AddOverviewEntity() determines for each entity if an icon should be drawn for that entity and which icon to draw. Entities that should be drawn make it into the list and get a duration, how long they should be drawn (at least 1 frame). The entities are drawn by DrawOverviewEntities(), and old ones are removed from this list in CheckOverviewEntities().

The z-axis position of entity icons and the map image is scaled depending on the spectator's viewangle. This is done to provide an orthogonal view if you are looking directly from above, and a smooth change to perspective projection when looking from the side.

How to create new map images?

Map images are normal TGA files (1024x768x24) made as screenshots from the level in HL. So, to create new map images, choose OpenGL 1024x768x32 as video mode for best results. The color green (0,255,0) is used as the transparent color.

The engine has a special cvar "dev_overview" to create new map images. If set to "1", the engine draws the level from above, looking down, but using orthographic projection instead of the normal perspective projection. This is done to avoid perspective distortions in overview mode. The engine will write the current parameters to the console (make sure to set developer 1 also) like so:

Zoom 1.63, Map Origin (1903, -1283, -200), Z Min 500, Z Max -1088, Rotated 0
User posted image
You can move the map around with keys FORWARD,BACKWARD, RIGHT & LEFT. Maps will be automatically rotated to fit optimally onto the screen. If they are rotated, FORWARD and RIGHT are swapped, etc. Use the keys ATTACK and ATTACK2 to change the zoom. With zmin and zmax you can choose which parts of the map should be drawn. Only polygons between these two values are shown. This can be used to cut off ceilings or other unwanted objects. Origin is your viewpoint projected into the map. The map will be rotated around this point if you move the mouse in overview mode. Zmin can be changed with UP & DOWN and zmax with JUMP & DUCK. If you think you've found the correct values for the new map, simply make a screenshot and remember all of the values, which are needed for the overview description file (to avoid console text on the screenshot set developer 0).

The screenshot can be edited with any paint program to erase areas or make dark rooms brighter. The final map image could also be the result of several merged screenshots (each with different zmin/zmax values, but some origin and zoom) to get an optimal map representation.

Next, you have to create an overview description file that will be parsed by the client DLL to get the map image filename and the correct view parameters. This file must be in the overviews directory and must have the same name as the current BSP file, but with TXT as its extension. Here is an example (overviewsde_vegas.txt):
// overview description file for de_vegas.bsp

    ZOOM        1.63
    ORIGIN    1903     -1283    -1088
    ROTATED    0

    IMAGE    "overviews/de_vegas.tga"
    HEIGHT    -1088
The global section describes zoom, the origin point the map is rotated around, and a boolean to indicate if the map image is rotated or not. The layer section describes the map image filename and at which height (z-axis) the map should be shown. The overview mode uses the some coordinate space as the game world and all icons are drawn at the same position as their corresponding entities (you could even combine map and world mode. For example real players and items moving over a map blueprint). In software mode, map images should be BMP files with 512x384x8 resolution.

Remember, the complete description file parsing, inset window, map projection and drawing code is all within the client DLL and may be changed as desired. It should just give an idea of what could be done with the new multiple views per frame and the LoadMapSprite() functions. It's not really needed for HLTV support.

If you have any questions about HLTV support in your MOD, send e-mail to (
This article was originally published on the Valve Editing Resource Collective (VERC).
TWHL only archives articles from defunct websites. For more information on TWHL's archiving efforts, please visit the TWHL Archiving Project page.


You must log in to post a comment. You can login or register a new account.