Post tutorial Report RSS 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 on - 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!

Part 2: Marking players and keeping track of them(server side):

First we're going to actually implement the marking, removing, cycling and more of targets.
First up add the following somewhere in shareddefs.h:
#define MAX_TARGETS 3

Change the 3 to whatever amount of maximum possible targets you want to hold in the array(note that this shouldn't be more then MAX possible players) if you want you could use the existing MAX_PLAYERS constant if you want the maximum amount of possible targets.
Also keep in mind this will increase or decrease the array size, also everything that may cause it to go out of bounds uses MAX_TARGETS so if you change this everything should still work fine.

Next a very miniscule client side part in c_hl2mp_player.h under m_iPlayerSoundType add:
int m_iTeleportTargetIndex;

This is the entindex of the currently selected target. It's what we set in the message receiving function in our HUD Element.

Next in the public: section add the functions:

cpp code:
int      GetTeleportTargetIndex(void) { return m_iTeleportTargetIndex; }
void      SetTeleportTargetIndex(int index) { m_iTeleportTargetIndex = index; }

Note that we're putting the bodies of the functions directly in the header as well, this might mean it will become inlined making calls to the function faster. Though it doesn't really matter in our case, if you want to be sure it won't be inlined feel free to add the body in the .cpp file instead.

To finish things up in the client side of the player add the following in the constructor:
m_iTeleportTargetIndex = -1;

Makes sure our integer gets initialized to -1(means nothing selected) afterwards the integer will only be updated through the message receiving function in our HUD element.

Next in hl2mp_player.h we'll add 2 variables and a integer array, but first let me discuss them:

  • m_iLastMarkedIndex: This is the index of our array of player indexes, not a direct player index, assuming max targets is 3 this will be a value of -1 to 2, -1 meaning clean last index and 0 to 2 the array index. We use this index to add players to the array 1 by 1 rather then randomly adding onto the array.
  • m_iCurSelectedPlayer: Similar to the LastMarkedIndex and is also an index for our array, but this one is important so our current selected target won't get switched whenever we mark a new player. It's also used to cycle through targets when the player does want to select a new target.
  • m_iMarkedPlayers[MAX_TARGETS]: The array holding the player indexes, values of -1 will mean that there are no player indexes at that part of the array.

Ok now let's add these variables and the array underneath m_iModelType(in hl2mp_player.h):

cpp code:
int m_iLastMarkedIndex;
int m_iCurSelectedPlayer;

int m_iMarkedPlayers[MAX_TARGETS];

Next in the public: section add the following functions:

cpp code:
void AddPlayerMarkedForTeleport(int sender_index);
void RemovePlayerMarkedForTeleport(int sender_index);
void CyclePlayerMarkedForTeleport();

int   GetTeleportTargetIndex(void) { return m_iMarkedPlayers[m_iCurSelectedPlayer]; }

The first three are for adding, removing or cycling through marked players the last one is a getter for the player index of our current target and is pretty much the brother of the client side version under the same name.

Let's start in the hl2mp_player.cpp file in the constructor where we initialize our variables to -1:

cpp code:
m_iLastMarkedIndex = -1;
m_iCurSelectedPlayer = -1;

for (int i = 0; i < MAX_TARGETS; i++)
   m_iMarkedPlayers[i]= -1;

Now we'll take a look at the first function we declared which is for adding new marked players:

cpp code:
void CHL2MP_Player::AddPlayerMarkedForTeleport(int sender_index)
{
   // It can be quite likely that the "new" selected target won't change, in which case there's no need to send a HUD update later on
   int old_selection_index = m_iMarkedPlayers[m_iCurSelectedPlayer];

   // If the player is already marked nothing needs to be done
   for (int i = 0; i < MAX_TARGETS; i++)
      if( sender_index == m_iMarkedPlayers[i])
         return;

   // If we reached the end of the maximum allowed targets(and this isn't a earlier marked target),
   // then change index in order to overwrite the first marked target.
   if( ++m_iLastMarkedIndex == MAX_TARGETS )
      m_iLastMarkedIndex = 0;

   // assign the sender index to the array of marked players
   m_iMarkedPlayers[m_iLastMarkedIndex] = sender_index;

   // if nothing selected yet or it's the same as the last marked index then update selected player and selected player handle
   if( m_iCurSelectedPlayer == -1 || m_iCurSelectedPlayer == m_iLastMarkedIndex )
      m_iCurSelectedPlayer = m_iLastMarkedIndex;

   // Selection didn't change, don't update HUD
   if( m_iMarkedPlayers[m_iCurSelectedPlayer] == old_selection_index )
      return;

   // Update this player's HUD
   CSingleUserRecipientFilter singlefilter( this );
   singlefilter.MakeReliable();
   UserMessageBegin( singlefilter, "updateSelectedTeleportTarget" );
      WRITE_BYTE(m_iMarkedPlayers[m_iCurSelectedPlayer]);
   MessageEnd();
}

The comments should help you understand it, if not feel free to ask me about it.
The last part of the function will send a usermessage with the entindex of the player to show the tracker icon on to our HUD Element.

Next up removing marked players:

cpp code:
void CHL2MP_Player::RemovePlayerMarkedForTeleport(int sender_index)
{
   // It can be quite likely that the "new" selected target won't change, in which case there's no need to send a HUD update later on
   int old_selection_index = m_iMarkedPlayers[m_iCurSelectedPlayer];

   // only delete them if this player is marked
   for (int i = 0; i < MAX_TARGETS; i++)
   {
      if( sender_index != m_iMarkedPlayers[i])
         continue;

      // this player was marked and has to be deleted from the list by overwriting with -1
      m_iMarkedPlayers[i]= -1;

      // if the player that has to be deleted was the currently selected player find a new player to select
      if( m_iCurSelectedPlayer == i )
      {
         // start looking at the start
         m_iCurSelectedPlayer = 0;

         // becomes false if a marked player is found, stays true if there's nobody to select
         bool bInvalidIndex = true;
         for(; m_iCurSelectedPlayer < MAX_TARGETS; m_iCurSelectedPlayer++)
         {
            if( m_iMarkedPlayers[m_iCurSelectedPlayer] != -1 )
            {
               bInvalidIndex = false;
               break;
            }
         }
         
         // if invalid set the selection to -1 and the handle to NULL, else update the handle to point to the new selected player
         if( bInvalidIndex )
            m_iCurSelectedPlayer = -1;
      }
      // Last marked index is set the same as the selection, not much gain from checking the array for -1 again...
      m_iLastMarkedIndex = m_iCurSelectedPlayer;
   }

   // Selection didn't change, don't update HUD
   if( m_iMarkedPlayers[m_iCurSelectedPlayer] == old_selection_index )
      return;

   // Update this player's HUD
   CSingleUserRecipientFilter singlefilter( this );
   singlefilter.MakeReliable();
   UserMessageBegin( singlefilter, "updateSelectedTeleportTarget" );
      WRITE_BYTE(m_iMarkedPlayers[m_iCurSelectedPlayer]);
   MessageEnd();
}

Again comments should help you here, moving on with cycling through targets:

cpp code:
void CHL2MP_Player::CyclePlayerMarkedForTeleport()
{
   // It can be quite likely that the "new" selected target won't change, in which case there's no need to send a HUD update later on
   int old_selection_index = m_iMarkedPlayers[m_iCurSelectedPlayer];

   // this will only be -1 if there's nothing to select
   if( m_iCurSelectedPlayer == -1 )
      return;

   // If no valid targets we'll have looped back to starting selection, if there is one found break off the loop to keep selection
   for (int i = 0; i < MAX_TARGETS; i++)
   {
      if( ++m_iCurSelectedPlayer == MAX_TARGETS )
         m_iCurSelectedPlayer = 0;

      if( m_iMarkedPlayers[m_iCurSelectedPlayer] != -1 )
         break;
   }

   // Selection didn't change, don't update HUD
   if( m_iMarkedPlayers[m_iCurSelectedPlayer] == old_selection_index )
      return;

   // Update this player's HUD
   CSingleUserRecipientFilter singlefilter( this );
   singlefilter.MakeReliable();
   UserMessageBegin( singlefilter, "updateSelectedTeleportTarget" );
      WRITE_BYTE(m_iMarkedPlayers[m_iCurSelectedPlayer]);
   MessageEnd();
}

These should be all the functions needed to add, remove and cycle through marked players, now we'll need to call these functions or else there ain't much use for them.

Let's start with marking players, in my situation my grenade(grenade_stopwatch) explodes and does "damage" like a normal grenade, BUT then the OnTakeDamage function get's called where I'll be cancelling damage and make the call to mark the player(s) that got hit by the grenade:

cpp code:
   // stopwatch - marks players for teleport swap
   if ( inputInfo.GetGrenadeEffectType() &amp; G_EFFECT_STOPWATCH )
   {
      CHL2MP_Player *pAttacker = ToHL2MPPlayer(inputInfo.GetAttacker());
      if( pAttacker &amp;&amp; pAttacker != this &amp;&amp; IsAlive() &amp;&amp; pAttacker->IsAlive() )
         pAttacker->AddPlayerMarkedForTeleport(entindex());

      return 0;
   }

Please note that GetGrenadeEffectType is NOT a Source function, nor are the flags it uses part of the default code, I've added this myself, if you want to do the same thing in a simple way, you're best off overriding or editting a constructor of the class CTakeDamageInfo, create a private member variable in there which is set through the constructor and create getters and setters like I have, then in the Explode function of your grenade(should be in basegrenade.cpp) you then call your editted constructor or overloaded constructor instead(there should be a CTakeDamageInfo info object in the Explode function)

If my grenade type is hitting the player we'll create a pointer to the attacker(the player who threw it, this IS default part of CTakeDamageInfo) note that it returns a Object of CBaseEntity so you will need to type cast or use ToBasePlayer and then perform checking whether the pointer is correct, whether the player isn't attacking himself, the player being hit is alive(this function does seem to get called shortly after death so hence this check) and whether the attacker is still alive(he could have died in the time it took for the grenade to explode) now if those are all ok we'll call AddPlayerMarkedForTeleport on the ATTACKER(if you forget to use the pointer the player will add himself to the target list as it will be the same as calling this->Function() then) as an argument we pass the entindex of the player being attacked.

Lastly regardless of whether we're adding a new target or not we return 0; so no further code will be executed as part of the OnTakeDamage function chain, which will result in the part that actually does the damage never being called, note that inputInfo.SetDamage(0); should also work without having to return.

Next we'll take a look at removing marked players from our target list, we do this in the Event_Killed function, add the following code above BaseClass::Event_Killed( subinfo ):

cpp code:
   // Reset teleport mark data
   m_iLastMarkedIndex = -1;
   m_iCurSelectedPlayer = -1;

   for (int i = 0; i < MAX_TARGETS; i++)
      m_iMarkedPlayers[i]= -1;

   for( int i = 0; i < MAX_PLAYERS; i++ )
   {
      CHL2MP_Player *pPlayer = ToHL2MPPlayer(UTIL_PlayerByIndex( i ));

      if( !pPlayer || pPlayer == this )
         continue;

      pPlayer->RemovePlayerMarkedForTeleport(entindex());
   }

   // Update this player's HUD
   CSingleUserRecipientFilter singlefilter( this );
   singlefilter.MakeReliable();
   UserMessageBegin( singlefilter, "updateSelectedTeleportTarget" );
      WRITE_BYTE(-1);
   MessageEnd();

We'll set our variables to -1, using a loop we're also looping through all players in the server(we skip the same player that died as there's no point removing yourself from the target list...) lastly we'll send an update to our HUD Element that we no longer have anyone selected.

Note that if your mod has a round based system you will also need to do the above in your Reset function of the player, excluding the loop through all the players in the servers though since when Reset is called everyone's target array will be cleared!


Part 3: Cycling targets and teleporting(shared code):
In my case this is done when the player is holding the grenade that will teleport the owner and the target to eachother's locations and you can push a key to cycle through targets.

This weapon is for me weapon_stopwatch.cpp
First off we'll need a private member variable:
bool m_bActivatedTeleport;

Note that if you have a appropiate animation and events for it that you could do this through events, in which case you probably won't need this boolean.

However in my case there's no animation ready yet and no special animation either, thus I'm sharing my custom event for pulling the pin of the grenade instead, where I use the boolean to prevent starting the timer that will happen when that event launches, like so:

cpp code:
case EVENT_WEAPON_PULLPIN:
   if( m_bActivatedTeleport )
      break;

   SetTimer( GRENADE_TIMER, 1.4f );
   m_flNextBlipTime = gpGlobals->curtime;
   break;

If I were to have a seperate event I could do things more orderly then I do now. But alas have to wait for my lazy animator :lol: (just kidding Nauz, you rock!)

Ok next we'll initialize the boolean in the Deploy function:

cpp code:
bool CWeaponStopwatch::Deploy( void )
{
   m_bRedraw = false;
   m_fDrawbackFinished = false;
   m_bActivatedTeleport = false;

   return BaseClass::Deploy();
}

Next in the SecondaryAttack function somewhere after the owner/player pointer initialization's I will add:

cpp code:
   CHL2MP_Player *pOwner = ToHL2MPPlayer( pPlayer );

   if( pOwner )
   {
      if( pOwner->GetTeleportTargetIndex() == -1 )
         WeaponSound( SPECIAL1 );
      else
         WeaponSound( SPECIAL2 );
   }

   m_bActivatedTeleport = true;
   m_AttackPaused = GRENADE_PAUSED_SECONDARY;
   SendWeaponAnim( ACT_VM_PULLBACK_HIGH );

   // Disables calls to secondary attack for a while so all of this only gets done once
   m_flTimeWeaponIdle = gpGlobals->curtime + SequenceDuration();
   m_flNextSecondaryAttack   = gpGlobals->curtime + SequenceDuration();

Again, using animations this could be much better, but as a substitute right now, I'm using the pOwner pointer to check if we have a target(note that this is both for the CLIENT and the SERVER sides of the players, if we have a target I play a sound that will be that of success, if not it will fail.

Next in the ItemPostFrame function we'll change the start of the function to:

cpp code:
   CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
   
   if( pOwner )
   {
#ifndef CLIENT_DLL
      if( pOwner->m_afButtonReleased &amp; IN_CYCLE_TARGET )
         ToHL2MPPlayer(pOwner)->CyclePlayerMarkedForTeleport();
#endif
      if( m_fDrawbackFinished )
      {
         switch( m_AttackPaused )
         {
         case GRENADE_PAUSED_PRIMARY:
            if( !(pOwner->m_nButtons &amp; IN_ATTACK) )
            {
               SendWeaponAnim( ACT_VM_THROW );
               m_fDrawbackFinished = false;
            }
            break;

         case GRENADE_PAUSED_SECONDARY:
            InitiateTeleport(pOwner);

            m_fDrawbackFinished = m_bActivatedTeleport = false;
            m_bThrewGrenade = true;
            break;
// rest of the function
 

We start with creating a pointer to the owner, if it's a valid pointer in the SERVER side code we'll check if the owner released a certain key recently, if so we call the Cycle function in the server side player class, for the owner, as you have guessed this will cycle through targets, note that IN_CYCLE_TARGET is a custom made key, you will have to make your own or use an existing IN_ key.

Next if the drawback is finished(grenade being readied) and the case is GRENADE_PAUSED_SECONDARY then we call the function that will initiate teleport. We also adjust some variables.

Let's take a look at the function for the teleport:

cpp code:
void CWeaponStopwatch::InitiateTeleport(CBasePlayer *pOwner)
{
   CBasePlayer *pTarget = UTIL_PlayerByIndex( ToHL2MPPlayer(pOwner)->GetTeleportTargetIndex() );

   if( !pTarget || !pTarget->IsAlive() )
      return;

#ifndef CLIENT_DLL
      Vector vecTargetAbsOrigin = pTarget->GetAbsOrigin(), vecTargetAbsVelocity = pTarget->GetAbsVelocity();
      QAngle angTargetAbsAngles = pTarget->GetAbsAngles();

      pTarget->Teleport( &amp;pOwner->GetAbsOrigin(), &amp;pOwner->GetAbsAngles(), &amp;pOwner->GetAbsVelocity() );
      pOwner->Teleport( &amp;vecTargetAbsOrigin, &amp;angTargetAbsAngles, &amp;vecTargetAbsVelocity );

      color32 white = { 255,255,255,255 };
      UTIL_ScreenFade(pTarget, white, 0.75f, 0.5f, FFADE_IN);
      UTIL_ScreenFade(pOwner, white, 0.75f, 0.5f, FFADE_IN);

      pOwner->RemovePlayerMarkedForTeleport(pTarget->entindex());
#endif

   pOwner->RemoveAmmo( 1, m_iPrimaryAmmoType );
}

First we create a pointer to the target by retrieving the target player index, next we check if the pointer is valid and if the target is still alive, if so we continue onward. to the server side section, which handles the teleport, after the teleport we use a screenfade to create a minor flash of white effect and we remove the target player from the owners target array(can't teleport with the same player twice without marking him again)

Lastly back in shared code again we remove some ammo from the owner.

That should be all, if you have any questions please ask them, any suggestions/constructive critiscm are welcome as well.

Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: