FragOut! is a Half Life 2 multiplayer mod, in which you only get different kinds of grenades to fight with, instead of the usual weapons. Forcing players to adapt at using only throwing weapons new tactics are sure to arise!

Report article RSS Feed Marking and tracking players

This is a tutorial I wrote up over at the Jailbreak forums on how to mark a player and then show a icon on top of a selected player to track him that's visible even through walls.

Posted by DuckSauce on Jul 2nd, 2009 Page 1 of 3    
Intermediate Server Side Coding.

Info and credits:
This tutorial was created with help from Dogmeat(Base Location indicator code) and a little help from the people over at the Steam forums Source Coding section.

The goal in my mod was to have a grenade which when it explodes could "mark" people and that the player who marked them can cycle through his targets at will(you could adapt this to track every target that's marked without too much effort), the player will have to be able to actualy cycle through his targets as in my mod he can push a button to teleport swap with the target(they'll both be teleported to each other's locations).

But since he can hit more then one player in the explosion of the grenade he will need to cycle through targets to get the desired player and as a bonus an icon will be displayed over their head even through walls making it even more usefull for timing the teleport or simply ambushing that player.

Example: You could wait behind a wall, on the other side is a base filled with enemies, you wait for the target to run towards a health pickup in the back of the base, then when you see the icon is far away push the teleport button and voila you will be right behind the enemies forces and can rip them a new one!

Part 1: The HUD Element(client side):
We'll start in the hud element that's gonna show our tracking icon.

In my case it's in a file I called: hud_teleport_marker.cpp in the client side project in the HL2 DLL filter/folder, but you can name it anything you want.

Let's start with the includes which have to be in the top of the file:

cpp code:
#include "cbase.h"
#include "hud.h"
#include "hudelement.h"
#include "hud_macros.h"
#include "vgui_EntityPanel.h"
#include "iclientmode.h"
#include "c_hl2mp_player.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

My actual class will be called CHudTeleportMarker, but again that can be anything you want it to be.

Now let's look at the header, which can be simply in the .cpp file as long as we won't need to include it anywhere.

First off, important is: m_nTeleportMarkerTextureID which is for the texture of the icon we'll show on the hud. If you don't have a icon you can use for testing I'm willing to send my icon so you can make sure it works.

Even more important is the handle:
CHandle m_hSelectedMarkedPlayer;

A handle is pretty much like a pointer but with some special properties, you can treat it like you would any pointer to a object of a class.
This handle will point to the currently selected player out of our marked players and will be used to fetch this players origin which is used to show the icon on the HUD.

Now the header looks like this:

cpp code:
class CHudTeleportMarker : public CHudElement, public vgui::Panel
{
   DECLARE_CLASS_SIMPLE( CHudTeleportMarker, vgui::Panel );

public:
   CHudTeleportMarker(const char *pElementName);

   void ApplySchemeSettings( vgui::IScheme *scheme );
   void Init(void);
   
   virtual void Paint(void);

   void MsgFunc_updateSelectedTeleportTarget( bf_read &msg );
   
private:
  bool InView(C_BasePlayer *pLocalPlayer, Vector v);

  int m_nTeleportMarkerTextureID;

  CHandle<c_baseplayer> m_hSelectedMarkedPlayer;
};

Next we'll need to use some macro's to set up some stuff:

cpp code:
DECLARE_HUDELEMENT(CHudTeleportMarker);
DECLARE_HUD_MESSAGE( CHudTeleportMarker, updateSelectedTeleportTarget );

The second is very important here as this will declare the message that will be hooked to this hud(we'll have to register this message later as well).

Next we'll create the constructor for this class:

cpp code:
CHudTeleportMarker::CHudTeleportMarker(const char *pElementName) : CHudElement(pElementName), BaseClass(NULL, "HudTeleportMarker")
{
  HOOK_HUD_MESSAGE( CHudTeleportMarker, updateSelectedTeleportTarget );

  vgui::Panel *pParent = g_pClientMode->GetViewport();
  SetParent( pParent );

  SetVisible(true);
  SetEnabled(true);

  m_hSelectedMarkedPlayer = NULL;

  m_nTeleportMarkerTextureID = vgui::surface()->CreateNewTextureID();
  vgui::surface()->DrawSetTextureFile( m_nTeleportMarkerTextureID, "hud/markers/teleport_marker" , true, false);

  SetHiddenBits( HIDEHUD_HEALTH | HIDEHUD_NEEDSUIT );
}

The next function will update the marked teleport target(the player who we are tracking), it's a function that will receive a networked usermessage, the usermessage sends a byte(1/4th the size of a normal integer) that contains the entity index of the currently selected player.

cpp code:
void CHudTeleportMarker::MsgFunc_updateSelectedTeleportTarget( bf_read &amp;msg )
{
  // player that sent the message
  int sender_index = msg.ReadByte();

  // if -1 then nobody is selected, otherwise use the index to retrieve a pointer to the player
  if( sender_index == -1 )
    m_hSelectedMarkedPlayer = NULL;
  else
    m_hSelectedMarkedPlayer = UTIL_PlayerByIndex( sender_index );

  C_HL2MP_Player *pLocalPlayer = C_HL2MP_Player::GetLocalHL2MPPlayer();

  // update client side player's target index
  if ( pLocalPlayer )
    pLocalPlayer->SetTeleportTargetIndex(sender_index);
}

Be sure to read data sent in the same order you send it! In this case there's only one byte of data being sent, so there's no order to keep in mind, but if you're gonna send larger message you must read in the same order as you send data or else you'll put the wrong values into your variables.

Next up follow two small functions:

cpp code:
void CHudTeleportMarker::ApplySchemeSettings( vgui::IScheme *scheme )
{
   BaseClass::ApplySchemeSettings( scheme );
   SetPaintBackgroundEnabled( false );
}

// my guess is this overrides the baseclass so it won't do anything funny...
void CHudTeleportMarker::Init( void )
{
}

And now the function that's actually gonna paint/draw the tracker image on the screen:

cpp code:
void CHudTeleportMarker::Paint()
{
  // If we have nobody selected or bad pointer then return
  if( !m_hSelectedMarkedPlayer )
    return;

  C_BasePlayer *pLocalPlayer = C_BasePlayer::GetLocalPlayer();

  if ( !pLocalPlayer )
    return;

  // x and y screen co√∂rdinates, the length(distance) to the target and the alpha(transparency) of our tracker imagee
  int x, y, length, alpha;

// Use the pointer we initialized in our message receiver to grab the origin locally

  Vector vecTarget = m_hSelectedMarkedPlayer->GetAbsOrigin();

// Player origin is at feet height, heighten z position by 75 to put the icon above the target's head

  vecTarget.z+= 75;

// If the icon isn't too far away or behind the local player then draw it

  if(InView(pLocalPlayer, vecTarget))
  {
  // Length of the direction from the local players origin to the target.

    length = (vecTarget - pLocalPlayer->GetAbsOrigin()).Length();

  // alpha(transparency) won't change if we are this far away

    if(length <= 675)
      alpha = 255;
    else
    {
    // We're getting farther away now, decrease visibilty of the icon

      alpha = (675 + 255) - length;

    // cap the lowest transparency(lower it to 0 for fully invisble)

      if(alpha < 25)
        alpha = 25;
    }

  // This Source function will translate the target player origin to screen co√∂rdinates
s
    GetVectorInScreenSpace(vecTarget, x, y, NULL);

//set the indicator texture and draw
w
    vgui::surface()->DrawSetTexture( m_nTeleportMarkerTextureID );
    vgui::surface()->DrawSetColor(255, 255, 255, alpha);
    vgui::surface()->DrawTexturedRect(x - 15, y - 15, x + 15, y + 15 );
  }

Note that the target pointer gets created in the message receiving function like this(don't insert this code as it's already there):
m_hSelectedMarkedPlayer = UTIL_PlayerByIndex( sender_index );

It will create a C_BasePlayer pointer by fetching the player object by the index we are going to send with WRITE_BYTE which will be stored in an array part of the server side player class.

What's so handy about this system is that you only need to send a players index when the selection changed and that's only sent as a single byte(8 bits), which is then used to create a pointer which can then fetch the origin without sending the Origin vector over the network constantly to keep it updated but rather doing it locally, lowering network traffic.

There may be still be some form of networking going on behind the scenes(how else does the object being pointed to stay updated so that player origin stays correct) but that means we aren't sending it twice over the network so my point stands... lower amount of network traffic.

Next up the last function of the hud element:

cpp code:
bool CHudTeleportMarker::InView(C_BasePlayer *pLocalPlayer, Vector vecTarget)
{
    // Clip image that is far away
    if((pLocalPlayer->GetAbsOrigin() - vecTarget).LengthSqr() > 90000000)
    return false;

    // Clip image that is behind the client
    Vector clientForward;
    pLocalPlayer->EyeVectors( &amp;clientForward );

    Vector toImage = vecTarget - pLocalPlayer->GetAbsOrigin();

    float  dotPr = DotProduct(clientForward,toImage);

    if (dotPr < 0)
    return false;

    return true;
}

This is the code DogMeat used for the base locations, to prevent showing the image when distance is too great or the image is behind the player.

We'll also need to register the usermessage, this is fairly simply, just open up hl2_usermessages.cpp and add:
usermessages->Register( "updateSelectedTeleportTarget", 1 );

Note that according to the Valve Developer Community this is the size in bytes not bits we'll only be sending a byte holding the entindex of the target so our size of the message will be 1 byte, for values that may differ insize(such as chat string) or if you don't know the size you can use -1 to have it determine the size on it's own.

Lastly to show the hud element we'll need a entry in HudLayouts.res
Which looks like this:

text code:
HudTeleportMarker
   {
      "fieldName"      "HudTeleportMarker"
      "xpos"   "0"
      "ypos"   "0"
      "wide"   "640"
      "tall"  "480"
      "visible" "1"
      "enabled" "1"

      "PaintBackgroundType"   "0"
   }

This finishes up the HUD side of the tracking, the next page will continue with selecting and marking players, cycling through selections and interacting with the targeted players(in my case I'm gonna teleport the marking player to the location of the marked player and the marked player to the location of the marking player) when a player activates his secondary weapon function for my custom weapon_stopwatch.

So keep on reading!

Post comment Comments
dafatcat
dafatcat Jul 19 2009, 2:52pm says:

I am officially confuzzled

+3 votes     reply to comment
Holymac
Holymac Mar 18 2010, 11:08am replied:

Maybe because you are a ******* idiot!

-1 votes     reply to comment
Post a Comment
click to sign in

You are not logged in, your comment will be anonymous unless you join the community today (totally free - or sign in with your social account on the right) which we encourage all contributors to do.

2000 characters limit; HTML formatting and smileys are not supported - text only

Icon
Half-Life 2 Icon
Platform
Windows
Developer
Team FragOut!
Contact
Send Message
Release Date
TBD
Mod Watch
Track this mod
Tutorial
Browse
Tutorials
Report Abuse
Report article
Related Mods
FragOut! (Half-Life 2)
FragOut! Half-Life 2 - Multiplayer First Person Shooter
Related Games
Half-Life 2
Half-Life 2 Single & Multiplayer First Person Shooter
Related Groups
Team FragOut!
Team FragOut! Developer & Publisher with 6 members