Welcome, Guest! Login | Register

CoD/Halo style weapon switching [Print this Article]
Posted by: [ICR]
Date posted: Dec 23 2003
User Rating: 4.7 out of 5.0
Number of views: 9789
Number of comments: 6
Description: How to implement call of Duty/Halo style weapon slots and switching.
This code was written for the mod I am coding for, http://www.hl-isolation.com. Basically it adds Call of Duty or Halo style weapon switching. For those of you who haven't played either of these games (and where have you been? Halo is one of the most popular games and Call of Duty is kick arse) basically the player has a certain number of slots, corresponding to weapon types, melee, primary weapon, secondary weapon etc. and they can only have a certain number of weapons in each slot. To pick up a weapon, you must walk over a weapon. If the slots are already full, you can press use to switch the weapon with the one in the slot.
This tutorial is limited to one weapon per slot; however, it can be extended to have several weapons. The tutorial will provide hopefully fairly detailed and useful comments, both inside the code and outside, to help you to understand, customize and expand the code.

Firstly we need to set up some definitions to store various values. We need an array to store pointers to the weapons in the slots. We could loop through the player's weapons, however this method is easier to maintain and makes the code smaller. So in the definition of CBasePlayer in player.h add:
 CODE (C++) 
CBasePlayerWeapon       *m_pSlots[4];   //[ICR] Stores what is in the player slots( 4 slots )p

However, in order to use CBasePlayerWeapon, we need to include the weapon.h with:
 CODE (C++) 
#include "weapons.h"  //[ICR] So we can use CBasePlayerWeapon for our slot code

Now we have these defined, we need to change the weapon pick up code. For this tutorial we will just be doing it for CBasePlayerWeapon. However, this could be extended to CBasePlayerItem and be used for all manner of items and pickups. So we need to find CBasePlayerWeapon::AddToPlayer in weapons.cpp and add to the beginning:
 CODE (C++) 
if (pPlayer->m_pSlots[iSlot()])  //[ICR] If we have a weapon in the slot
{
    if ( !(pPlayer->m_afButtonPressed & IN_USE) )  //[ICR] Make sure use is pressed before the weapon is picked up
        return FALSE;
}

Then edit the end of the function to look like:
 CODE (C++) 
if (bResult)
{
    pPlayer->m_pSlots[iSlot()] = (CBasePlayerWeapon *)this;  //[ICR] Store the weapon pointer in the slot array for future reference
    return AddWeapon( );
}
return FALSE;

This checks to see if the slot is occupied. If it is, then the player must hold down use in order to pick up the weapon. Then, at the end, the pointer to the weapon picked up is added into the array, ready for checking next time they walk over a weapon.
Inside the if (pPlayer->m_pSlots[iSlot()]) statement, after the existing code, add:
 CODE (C++) 
pPlayer->pev->weapons &= ~(1<<pPlayer->m_pSlots[iSlot()]->m_iId);  //[ICR] Remove the weapon from the HUD

This is the beginning of the code to remove the weapon from the player. This removes it from the HUD. Then inside the if (bResult) statement above the existing code add:
 CODE (C++) 
if (pPlayer->m_pSlots[iSlot()])  //[ICR] If there is a weapon in the slot
{
    pPlayer->m_pSlots[iSlot()]->DestroyItem();  //[ICR] Remove it
}

This checks if there is a weapon already in the slot, and if there is it destroys it. This effectively finishes off removing the weapon from the player.
The next thing to do is create the new weapon. Inside the if (pPlayer->m_pSlots[iSlot()]) statement, after the existing code, add:
 CODE (C++) 

CBasePlayerWeapon *DropPointer = (CBasePlayerWeapon *)CBaseEntity::Create( (char *)STRING(pPlayer->m_pSlots[iSlot()]->pev->classname), pPlayer->pev->origin, Vector(0, 0,0) );  //[ICR] Create the weapon
       
if ( DropPointer )  //[ICR] If the weapon creation was successful
{
    DropPointer->pev->velocity = pPlayer->pev->velocity;  //[ICR] Inherit the player velocity  -  If you want to add a forward toss, add the vector here
    DropPointer->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 0, 100 ), 0 );  //[ICR] This adds a bit of spin to the weapon. For more spin, increase the 100 value
    DropPointer->SetThink( RetouchThink );  //[ICR] This makes it so you can't initial pick up the weapon up. This avoids the problems where the weapon is constantly switched and new ones create (as use is held down for about a second) resulting in a no free edicts error. It also means you can add a delay before re-picking up, helping prevent counter-strike style weapon walks
    DropPointer->SetTouch( NULL );  //[ICR] See above
    DropPointer->pev->nextthink = gpGlobals->time + 3.0;  //[ICR] This is the delay before the weapon can be picked up again in seconds. This must be at least 1.0 to prevent errors and crashing.
    DropPointer->pev->solid = SOLID_NOT;  //[ICR] This prevents the weapon getting stuck on the player and dropping very slowly.
    DropPointer->m_iDefaultAmmo = pPlayer->m_pSlots[iSlot()]->m_iClip;  //[ICR] This sets the ammunition for the weapon.
}
else
{
    ALERT ( at_debug, "Could not create dropped weapon\n" );  //[ICR] If the weapon was not created properly, alert the player
}

There is quite a lot of stuff in here, indeed it is the largest single part of the tutorial. It begins by creating the weapon, using the DropPointer we defined at the beginning. It then checks if this was created, since if it was not created successfully and then we attempted to set its properties, more errors would arise.
The velocity is inherited off the player, so if they are running it will look more convincing as it is dropped forward a little. If you want a counter-strike style throw, you can add the relevant vector here.
It then sets the touch function to NULL, meaning you cannot pick it up, and the think function to one we will define and write in a little while, which will reset it so you can pick it up. This delay is for two reasons. Firstly, if there was no delay, as the use key is held down for about a second the code will loop, dropping and picking up weapons. This will result in a not enough edicts error and crash the game. Secondly, it allows us to add a delay before the weapon can be picked up, and help prevent "weapon walking" where people continually switch weapons as they walk, allowing them to use two weapons at the same time.
The last line sets the amount of ammo the gun holds, so when it is picked up again any ammo that has been used will still remain the same.
Okay, the next thing to do is to add the RetouchThink function. Find the declaration of CBasePlayerItem and add:
 CODE (C++) 
void EXPORT RetouchThink ( void ); //[ICR] Declaration for the retouch think function

You may have noticed we use iSlot() a lot to get the slot of the weapon. However, it is not actually defined in the code, so it can be added now. Under the similar functions, add:
 CODE (C++) 
int         iSlot( void )       { return ItemInfoArray[ m_iId ].iSlot; }  //[ICR] Returns the slot number

This just provides an easy way to refer to the slot number. Now to add the actual RetouchThink function. Under AddToPlayer in weapons.cpp add:
 CODE (C++) 
void CBasePlayerItem::RetouchThink( void )
{
    pev->solid = SOLID_TRIGGER;  //[ICR] Makes it solid again
    UTIL_SetOrigin( this, pev->origin );  //[ICR] Links to the world. Probably not needed, but best to be safe
    SetTouch(&CBasePlayerItem::DefaultTouch); //[ICR] Set the touch to the normal touch function
    SetThink( NULL );  //[ICR] Set the think to null so this doesn't keep getting called
    SetNextThink( 0.0 );  //[ICR] Set the next think to 0, else it will screw up on subsequent thinks
}

This re-links it to the world, makes it solid for pickup again, and resets the touch function. This means it can be touched, and when it is it will call the AddToPlayer function again, ready for use to be pressed and the weapons switched again.
All that is left to do is to re-organize the weapons into the relevant slots. They should still all occupy unique positions within the slots, otherwise the sprites will screw up etc. and things become a lot more complicated. With the removal of the existing weapons in the slots, the slot positions are not obvious anyhow.

Everything should now work. Do not hesitate to modify, expand and change the code, and if you make any significant changes post them up somewhere. However, please do not rip this tutorial off, only post if there are significant changes to the way the code works, or new additions added. Also, do not hesitate to point out errors within the code, or ask for more help or explanation.

Rate This Article
This article is currently rated: 4.7 out of 5.0 (3 Votes)

You have to register to rate this article.
User Comments Showing comments 1-6

Posted By: Gnr.SnotBall on Dec 25 2003 at 21:53:11
ICR this one is great! :D But I've found a little bug; if you stand ontop of a weapon and press use while facing in any direction - even upwards, it let's you switch weapons. It would be nice if it was possible only to do so when aiming at the weapon.
Maybe it could be done somewhat like the thing with buttons, where you have to aim at them.. Not sure though - you should try it.

Posted By: XENO on Dec 27 2003 at 02:01:56
Umm... You could make it so that the player's USE key was pressed, it does a traceline to find the entity infront of the player, and if it is a weapon, it picks it up... You could make it draw TRIAPI corners around it's bounding box, to achieve a similar effect to the one in Deus Ex...Edited by Bulk on Dec 27 2003, 22:29:59

Posted By: [ICR] on Dec 27 2003 at 12:33:46
Yes, that's something I didn't think of. Here is the code ripped from the Spirit trigger_onsight entity. If you add this function:
// by the criteria we're using, can the Looker see the Seen entity?
BOOL CBasePlayerWeapon :: CanSee(CBaseEntity *pLooker, CBaseEntity *pSeen)
{
// out of range?
if (pev->frags && (pLooker->pev->origin - pSeen->pev->origin).Length() > pev->frags)
return FALSE;

// check FOV if appropriate
if (pev->max_health < 360)
{
// copied from CBaseMonster's FInViewCone function
Vector2D vec2LOS;
float flDot;
float flComp = pev->health;
UTIL_MakeVectors ( pLooker->pev->angles );
vec2LOS = ( pSeen->pev->origin - pLooker->pev->origin ).Make2D();
vec2LOS = vec2LOS.Normalize();
flDot = DotProduct (vec2LOS , gpGlobals->v_forward.Make2D() );

// ALERT(at_debug, "flDot is %f\n", flDot);

if ( pev->max_health == -1 )
{
CBaseMonster *pMonst = pLooker->MyMonsterPointer();
if (pMonst)
flComp = pMonst->m_flFieldOfView;
else
return FALSE; // not a monster, can't use M-M-M-MonsterVision
}

// outside field of view
if (flDot <= flComp)
return FALSE;
}

// check LOS if appropriate
if (!FBitSet(pev->spawnflags, SF_ONSIGHT_NOLOS))
{
TraceResult tr;
if (SF_ONSIGHT_NOGLASS)
UTIL_TraceLine( pLooker->EyePosition(), pSeen->pev->origin, ignore_monsters, ignore_glass, pLooker->edict(), &tr );
else
UTIL_TraceLine( pLooker->EyePosition(), pSeen->pev->origin, ignore_monsters, dont_ignore_glass, pLooker->edict(), &tr );
if (tr.flFraction < 1.0 && tr.pHit != pSeen->edict())
return FALSE;
}

return TRUE;
}
Then change the if ( !(pPlayer->m_afButtonPressed & IN_USE) ) line to:
if ( !(pPlayer->m_afButtonPressed & IN_USE) || !CanSee( (CBaseEntity *)pPlayer, this) )
Should work I think. I don't think thats the right way to change the pPlayer, I'm busy and hungover atm. Perhaps someone else can post it.

As for Tri-API boxes and stuff, that's not really part of the tutorials objective, but it is a good idea. For my mod I have a Tri-API box (just the corners) drawn round it and the lighting value is changed to make it brighter when you look at it. This basicaly uses a similar method to the one Xeno said. However, I decided not to demonstrate the pickup only when you can see it method with this, as it is more complex, and if you aren't adding the boxes or lighting stuff, it is simpler and better.

Posted By: DOOManiac on Jul 21 2004 at 06:11:25
Great tutorial. One problem though which can cause HL to crash!

When a player dies, their m_pSlots[] are not reset, so it causes the server to think a player has a weapon when they really don't, but even worse, when they hit Use to pick up a new weapon it crashes when creating the old one (that isn't there but the server doesn't know because the pointer is still valid)

This is an easy fix.

In CBasePlayer::PlayerDeathThink(void) in the if(HasWeapons()) code block add this code before PackDeadPlayerItems():

// Crash fix by DOOM
// We got to reset m_pSlots[] or we end up crashing shit
for (int i=0; i < 4; i++) // If you increased the slot size in the above tutorial, change it here too
m_pSlots[i] = NULL;

Posted By: DOOManiac on Jul 21 2004 at 06:24:57
Another small point, you might want to add:

DropPointer->pev->spawnflags |= SF_NORESPAWN;// never respawn

when you are setting all of the other DropPointer properties, otherwise it will respawn after someone picks it up again

Posted By: ShCiPwA on Oct 23 2004 at 07:20:19
2 things, i am new to this so im not sure but with the new sdk i think you need to change:

UTIL_SetOrigin( this, pev->origin );
to
UTIL_SetOrigin(pev, pev->origin );
AND
SetNextThink( 0.0 );
to
pev->nextthink = 0.0;

because i got compile errors from the other code, not sure if that makes logic errors but it compiles

next thing, in relation to the m_pSlots not being set to null. with weapons like grenades and trip mines the weapon entity is destroyed when the ammo is empty, so when you get another one the slot seems full still.
to fix this i added under
CBasePlayerItem::DestroyItem
inside the
if ( m_pPlayer )
add the line:

m_pPlayer->m_pSlots[iSlot()] = NULL;

Please correct me if i am wrong as this is the first time i have seen the source.


You must register to post a comment. If you have already registered, you must login.

Latest Articles
3rd person View in Multiplayer
Half-Life 2 | Coding | Client Side Tutorials
How to enable it in HL2DM

By: cct | Nov 13 2006

Making a Camera
Half-Life 2 | Level Design
This camera is good for when you join a map, it gives you a view of the map before you join a team

By: slackiller | Mar 05 2006

Making a camera , Part 2
Half-Life 2 | Level Design
these cameras are working monitors that turn on when a button is pushed.

By: slackiller | Mar 04 2006

Storing weapons on ladder
Half-Life 2 | Coding | Snippets
like Raven Sheild or BF2

By: British_Bomber | Dec 24 2005

Implementation of a string lookup table
Half-Life 2 | Coding | Snippets
A string lookup table is a set of functions that is used to convert strings to pre-defined values

By: deathz0rz | Nov 13 2005


Latest Comments
knock knock
General | News
By: MIFUNE | Dec 31 2017
 
knock knock
General | News
By: omega | Dec 22 2016
 
knock knock
General | News
By: MIFUNE | Oct 10 2015
 
New HL HUD Message System
Half-Life | Coding | Shared Tutorials
By: chbrules | Dec 31 2011
 
knock knock
General | News
By: Whistler | Nov 05 2011
 
Particle Engine tutorial part 4
Half-Life | Coding | Client Side Tutorials
By: darkPhoenix | Feb 18 2010
 
Particle Engine tutorial part 2
Half-Life | Coding | Client Side Tutorials
By: darkPhoenix | Feb 11 2010
 
Particle Engine tutorial part 3
Half-Life | Coding | Client Side Tutorials
By: darkPhoenix | Feb 11 2010
 
Game Movement Series #2: Analog Jumping and Floating
Half-Life 2 | Coding | Shared Tutorials
By: mars3554 | Oct 26 2009
 
Particle Engine tutorial part 5
Half-Life | Coding | Client Side Tutorials
By: Deadpool | Aug 02 2009
 

Site Info
297 Approved Articless
8 Pending Articles
3940 Registered Members
0 People Online (7 guests)
About - Credits - Contact Us

Wavelength version: 3.0.0.9
Valid XHTML 1.0! Valid CSS!