The Place to Start


Hallucination Gas Grenades 
Prefect
Hard


Hallucination Gas Grenades

 

This is the first tutorial in the series "TFC Style Grenades". I'm planning to create the following tutorials in the series:

 

I split up this tutorial into two chapters, because you might want the player to hallucinate without adding the grenades to your mod:

1. Creating the hallucination system
2. Hallucination gas grenades
 

Old code is in Red, new code is in Yellow. Changed code and reference info is Green. Note that code from a previous chapter will be listed as old code. Line numbers needn't be exact values, as I'm coding all my tutorials into one mod, which already has a lot of new code in it. You will see where you have to add new code by looking at the surrounding lines of old code.

 

 


1. Creating the hallucination system

 

First of all, we'll add a new damage type called DMG_HALLUC, which we'll use for the hallucination gas "blast". It's value has to be defined in cbase.h, around line 603:

#define DMG_MORTAR			(1 << 23)	// Hit by air raid (done to distinguish grenade from mortar)

// TFC Grenades #1
#define DMG_HALLUC			(1 << 31)	// hit by hallucination grenade
// TFC Grenades #1

// these are the damage types that are allowed to gib corpses
#define DMG_GIB_CORPSE		( DMG_CRUSH | DMG_FALL | DMG_BLAST | DMG_SONIC | DMG_CLUB )

 

Some words on how the damage system will work: We'll add a check to CBasePlayer::TakeDamage(), which increases the duration of the hallucinations based on the amount of hallucination damage taken. Then, in CBasePlayer::CheckTimeBasedDamage() (which checks time based damages every 2 seconds), we'll calculate the number of hallucinations to create during the next 2 seconds. Finally, the hallucinations will be create in CBasePlayer::Hallucinate(), which will be called by CBasePlayer::PreThink().

As the hallucination system is be based on time based damage, we have to define the following around line 650 (still in cbase.h):

#define	itbd_SlowFreeze		7
// TFC Grenades #1
#define itbd_Halluc		16
#define CDMG_TIMEBASED		17
// TFC Grenades #1

itbd_Halluc is the index into the time based damage array. It's 16, because the itbd_XXX-constants must match the DMG_XXX-constants. CDMG_TIMEBASED is the number of time based damage types (the types 8 to 15 are left out).

Some additional declarations go to line 636:

#define SLOWFREEZE_DAMAGE	1.0

// TFC Grenades #1
#define HALLUC_DURATION		4		// duration per 5health hit
#define HALLUC_MAXDURATION	30
// TFC Grenades #1

#define	itbd_Paralyze		0		

These are in 2 seconds units, because time based damages are checked every 2 seconds.

 

Next, some additional variables and functions are needed in CBasePlayer. Add the following to player.h, line 74:

class CBasePlayer : public CBaseMonster
{
public:
// TFC Grenades #1
	int		m_iHallucs;	// Hallucinations to produce during the next 2 secs
	float		m_flNextHalluc;
	BOOL		m_fHallucIcon;	// is the icon currently up`?

	void		Hallucinate( void );
	void		CreateRandomOrigin(float flMaxDist, Vector &vecSrc);
	void		CreateRandomDecalPos(Vector &vecSrc, TraceResult *tr);
	void		CreateRandomVelocity(Vector vecSrc, float flSpeed, Vector &vecV);
// TFC Grenades #1

The CreateXXX-functions are subroutines called by Hallucinat().

m_fHallucIcon will tell us if the hallucination icon on the HUD is currently switched on.

First of all, add the following to CBasePlayer::TakeDamage(), in player.cpp, line 415:

	float flBonus;
	float flHealthPrev = pev->health;

// TFC Grenades #1
	if (bitsDamageType & DMG_HALLUC) {
		float add = max((HALLUC_DURATION * flDamage) / 5, 1);

		m_rgbTimeBasedDamage[itbd_Halluc] += add;

		if (m_rgbTimeBasedDamage[itbd_Halluc] >= HALLUC_MAXDURATION)
			m_rgbTimeBasedDamage[itbd_Halluc] = HALLUC_MAXDURATION;
	}
// TFC Grenades #1

	flBonus = ARMOR_BONUS;
	flRatio = ARMOR_RATIO;

This updates the duration of the hallucinations if the player takes hallucination damage. I didn't like the idea of armor protecting against hallucinations, so I changed the following around line 456:

// TFC Grenades #1
	// armor won't protect against hallucination gas!
	if (pev->armorvalue && !(bitsDamageType & (DMG_FALL | DMG_DROWN | DMG_HALLUC)) )// armor doesn't protect against fall or drown damage!
// TFC Grenades #1

There's still something to add to make the code work correctly. Go to line 488 and change the code to:

	// reset damage time countdown for each type of time based damage player just sustained

	{
// TFC Grenades #1
		for (int i = 0; i < CDMG_TIMEBASED; i++) {
			// don't do this for hallucination! it's timedamage is handled specially
			if (i == itbd_Halluc)
				continue;

			if (bitsDamageType & (DMG_PARALYZE << i))
				m_rgbTimeBasedDamage[i] = 0;
		}
// TFC Grenades #1
	}

Now we have to add something to CBasePlayer::CheckTimeBasedDamage(), around 2466:

				bDuration = SLOWFREEZE_DURATION;
				break;
// TFC Grenades #1
			case itbd_Halluc:
				m_iHallucs = 1 + m_rgbTimeBasedDamage[i] / (HALLUC_MAXDURATION/2);

				// 33% chance that we get some extra hallucs
				if (RANDOM_LONG(0,2) == 0)
					m_iHallucs += 4;

				bDuration = 0;	// handled specially in TakeDamage()

				break;
// TFC Grenades #1
			default:
				bDuration = 0;
			}

This calculates the hallucinations to create during the next 2 seconds. Now we still have to create the hallucinations. Add the following code to CBasePlayer::PreThink(), around line 2219:

	if (pev->deadflag >= DEAD_DYING)
	{
		PlayerDeathThink();
		return;
	}

// TFC Grenades #1
	// create hallucinations, etc...
	if (m_iHallucs > 0 && m_flNextHalluc <= gpGlobals->time) {

		if (RANDOM_LONG(0, 100) > (80 / m_iHallucs)) {
			Hallucinate();
			m_iHallucs--;
		}

		m_flNextHalluc = gpGlobals->time + 0.2;
	}
// TFC Grenades #1

	// So the correct flags get sent to client asap.
	//

The precise time when hallucinations are to be created is randomized a bit in this code. Now it's time to add the code which actually creates the hallucinations by creating temporary entities only visible to one player. You can put it anywhere, but putting it into player.cpp is reasonable.

// TFC Grenades #1

// create a random vector that the player can see in vecSrc
void CBasePlayer::CreateRandomDecalPos(Vector &vecSrc, TraceResult *tr)
{
	Vector vecTrace;

	do {
		vecTrace.x = RANDOM_FLOAT(-1, 1);
		vecTrace.y = RANDOM_FLOAT(-1, 1);
		vecTrace.z = RANDOM_FLOAT(-0.5, 0.5);
	} while (vecTrace.Length() < 0.01);

	vecTrace = vecTrace * 4096;

	UTIL_TraceLine(pev->origin, pev->origin + vecTrace, ignore_monsters, ENT(pev), tr);

	vecSrc = tr->vecEndPos;
}

void CBasePlayer::CreateRandomOrigin(float flMaxDist, Vector &vecSrc)
{
	Vector vecTrace;
	TraceResult tr;

	do {
		vecTrace.x = RANDOM_FLOAT(-1, 1);
		vecTrace.y = RANDOM_FLOAT(-1, 1);
		vecTrace.z = RANDOM_FLOAT(-0.5, 0.5);
	} while (vecTrace.Length() < 0.01);

	vecTrace = vecTrace * flMaxDist;

	UTIL_TraceLine(pev->origin, pev->origin + vecTrace, ignore_monsters, ENT(pev), &tr);

	// move out of the wall
	tr.flFraction -= 4 / flMaxDist;
	if (tr.flFraction <= 0)
		tr.flFraction = 4 / flMaxDist;

	vecSrc = pev->origin + vecTrace * RANDOM_FLOAT(0, tr.flFraction);
}

// create random velocity for hallucinated grenades, for example
void CBasePlayer::CreateRandomVelocity(Vector vecSrc, float flSpeed, Vector &vecV)
{
	vecV = pev->origin - vecSrc;

	vecV = vecV.Normalize();
	vecV.x += RANDOM_FLOAT(-0.4, 0.4);
	vecV.y += RANDOM_FLOAT(-0.4, 0.4);
	vecV.z += RANDOM_FLOAT(-0.3, 0.3);

	vecV = vecV * RANDOM_FLOAT(0.7, 1.3) * flSpeed;
}

// this will create the hallucination effect by sending
// temp entities only to myself
void CBasePlayer::Hallucinate( void )
{
	// create a random hallucination effect
	Vector vecSrc;	// vector the "damage" comes from
	BOOL fShowDamage = FALSE;	// should the damage compass flash?

	int effect = RANDOM_LONG(0, 100);

	if (effect < 30) 
	{
		// create a fake explosion
		CreateRandomOrigin(512, vecSrc);

		int iContents = UTIL_PointContents ( vecSrc );

		MESSAGE_BEGIN( MSG_ONE, SVC_TEMPENTITY, vecSrc, pev );
			WRITE_BYTE( TE_EXPLOSION );		// This makes a dynamic light and the explosion sprites/sound
			WRITE_COORD( vecSrc.x );
			WRITE_COORD( vecSrc.y );
			WRITE_COORD( vecSrc.z );
			if (iContents != CONTENTS_WATER)
			{
				WRITE_SHORT( g_sModelIndexFireball );
			}
			else
			{
				WRITE_SHORT( g_sModelIndexWExplosion );
			}
			WRITE_BYTE( 30  ); // scale * 10
			WRITE_BYTE( 15  ); // framerate
			WRITE_BYTE( TE_EXPLFLAG_NONE );
		MESSAGE_END();

		fShowDamage = TRUE;
	}
	else if (effect < 40)
	{
		// create a grenade model
		Vector vecVel;

		CreateRandomOrigin(512, vecSrc);
		CreateRandomVelocity(vecSrc, 300, vecVel);

		MESSAGE_BEGIN( MSG_ONE, SVC_TEMPENTITY, vecSrc, pev );
			WRITE_BYTE( TE_MODEL );	// create a grenade model

			WRITE_COORD( vecSrc.x );	// origin
			WRITE_COORD( vecSrc.y );
			WRITE_COORD( vecSrc.z );

			WRITE_COORD( vecVel.x );	// initial velocity
			WRITE_COORD( vecVel.y );
			WRITE_COORD( vecVel.z );

			WRITE_ANGLE( 0 );			// initial yaw
			WRITE_SHORT( PRECACHE_MODEL("models/w_grenade.mdl") );	// model index
			WRITE_BYTE( BOUNCE_SHRAP );
			WRITE_BYTE( 30 );			// life in 0.1s
		MESSAGE_END();
	}
	else
	{
		// create a gunshot decal
		TraceResult tr;

		CreateRandomDecalPos(vecSrc, &tr);

		if (tr.flFraction == 1.0) {
			return;	// can't put decals into the air
		}

		MESSAGE_BEGIN( MSG_ONE, SVC_TEMPENTITY, vecSrc, pev );
			WRITE_BYTE( TE_GUNSHOT );	// create the particle effect

			WRITE_COORD( vecSrc.x );	// origin
			WRITE_COORD( vecSrc.y );
			WRITE_COORD( vecSrc.z );
		MESSAGE_END();

		// Decal the wall with a gunshot
		CBaseEntity *pEntity = NULL;
		if ( !FNullEnt(tr.pHit) )
			pEntity = CBaseEntity::Instance(tr.pHit);
		int index = gDecals[ DamageDecal(DMG_BULLET) ].index;
		if ( index < 0 )
			return;

		MESSAGE_BEGIN( MSG_ONE, SVC_TEMPENTITY, vecSrc, pev );
			WRITE_BYTE( TE_GUNSHOTDECAL );	// create the decal
			WRITE_COORD( vecSrc.x );
			WRITE_COORD( vecSrc.y );
			WRITE_COORD( vecSrc.z );
			WRITE_SHORT( (short)ENTINDEX(tr.pHit) );
			WRITE_BYTE( index );
		MESSAGE_END();
	}

	// let the damage compass flash if necessary
	if (fShowDamage) {
		MESSAGE_BEGIN( MSG_ONE, gmsgDamage, NULL, pev );
			WRITE_BYTE( pev->dmg_save );
			WRITE_BYTE( pev->dmg_take+10 );
			WRITE_LONG( m_bitsDamageType & DMG_SHOWNHUD );
			WRITE_COORD( vecSrc.x );
			WRITE_COORD( vecSrc.y );
			WRITE_COORD( vecSrc.z );
		MESSAGE_END();
	}
}

// TFC Grenades #1

CreateRandomDecalPos() creates a random point we can decal at by tracing in a random direction. It returns the end point of the traceline.

CreateRandomOrigin() creates a random point in the room around the player by tracing in a random direction, as CreateRandomDecalPos() does it. It differs only in that it returns a random point along the traceline.

CreateRandomVelocity() creates a random vector which points more or less from the source vector to the player.

Hallucinate() creates a random hallucination. Currently explosions, fake grenades and bullet holes are implemented. You're free to implement your own, mod-dependent effects. Just take the temporary entity that is used for the effect, and change the MESSAGE_BEGIN() so that it looks like in the examples (use MSG_ONE, and pass pev off as the last parameter).

 

To display the hallucination icon (as you know it from TFC), we'll add a server-to-client message which is already defined in the Valve client dll, but which is not registered in the default entity dll yet. Go to line 190 in player.cpp and add:

int gmsgShowMenu = 0;

// TFC Grenades #1
int gmsgStatusIcon = 0;
// TFC Grenades #1

To register the message, add the following to line 3346 (in CBasePlayer::Precache()):

gmsgFade = REG_USER_MSG("ScreenFade", sizeof(ScreenFade));
gmsgAmmoX = REG_USER_MSG("AmmoX", 2);

// TFC Grenades #1
gmsgStatusIcon = REG_USER_MSG( "StatusIcon", -1 );
// TFC Grenades #1

The message is variable in length, so the second parameter to REG_USER_MSG() must be -1. Now add the following to CBasePlayer::UpdateClientData(), around line 4761:

		m_bitsDamageType &= DMG_TIMEBASED;
	}

// TFC Grenades #1
	BOOL fNewHallucIcon = m_bitsDamageType & DMG_HALLUC;

	if (fNewHallucIcon != m_fHallucIcon) {
		MESSAGE_BEGIN( MSG_ONE, gmsgStatusIcon, NULL, pev );
			if (fNewHallucIcon)
				WRITE_BYTE( TRUE );
			else
				WRITE_BYTE( FALSE );
			WRITE_STRING( "dmg_haluc" );
			if (fNewHallucIcon) {
				WRITE_BYTE(255);	// rgb color values
				WRITE_BYTE(160);
				WRITE_BYTE(0);
			}
		MESSAGE_END();

		m_fHallucIcon = fNewHallucIcon;
	}
// TFC Grenades #1

	// Update Flashlight
	if ((m_flFlashLightTime) && (m_flFlashLightTime <= gpGlobals->time))

Okay, the code is ready. But we need to define the hud icon "dmg_haluc" somewhere. You could use the TFC icon for testing purposes. Put the files tfchud05.spr and tfchud06.spr from the TFC pak file into your mod's sprite directory. Then you have to change the file sprite\hud.txt. If you don't have this file in your mod yet, copy it from the valve\sprites directory.

First of all, you add two sprites, so change the number in the first line of the file from 123 to 125. Then you have to add a sprite reference for both low (<640x480) and high (>=640x480) resolutions:

dmg_shock		320 320hud4	224	0	32	32

// TFC Grenades #1
dmg_haluc		320 tfchud06	176	120	32	32
// TFC Grenades #1

number_0		320 320hud2	0	0	12  16

Then somewhere below:

dmg_shock		640 640hud8	192	0	64	64

// TFC Grenades #1
dmg_haluc		640 tfchud05	192	0	64	64
// TFC Grenades #1

number_0		640 640hud7	0	0	20	24

 

That was the hallucination system. Read the next chapter on how to implement the actual hallucination grenade.

 

 


2. Hallucination gas grenades

 

To implement the new weapon, I will create two new classes:

CHallucGrenade is derived from CGrenade. It's the actual grenade flying around.

CWHallucGrenade is derived from CHandGrenade. It's the player's weapon.

 

Some things need to be prepared in the base classes. Go to weapons.h and change the following around line 40:

// TFC Grenades #1
	virtual void Spawn( void );
// TFC Grenades #1

	typedef enum { SATCHEL_DETONATE = 0, SATCHEL_RELEASE } SATCHELCODE;

	static CGrenade *ShootTimed( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time );
	static CGrenade *ShootContact( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity );
	static CGrenade *ShootSatchelCharge( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity );
	static void UseSatchelCharges( entvars_t *pevOwner, SATCHELCODE code );

// TFC Grenades #1
	virtual void Explode( Vector vecSrc, Vector vecAim );
	virtual void Explode( TraceResult *pTrace, int bitsDamageType );
// TFC Grenades #1

The functions that are overriden need to be virtual. Next, the definition of CHandGrenade has to be in an include-file, because we'll derive a class from it. Go to line 388 and add:

// TFC Grenades #1
// inherited by CHallucGrenade, so I moved it here
class CHandGrenade : public CBasePlayerWeapon
{
public:
	virtual void Spawn( void );
	void Precache( void );
	int iItemSlot( void ) { return 5; }
	virtual int GetItemInfo(ItemInfo *p);

	void PrimaryAttack( void );
	BOOL Deploy( void );
	BOOL CanHolster( void );
	void Holster( void );
	void WeaponIdle( void );

	virtual void ShootGrenade( entvars_t *pevAttacker, Vector vecSrc, Vector vecThrow, float flTime );
	float m_flStartThrow;
	float m_flReleaseThrow;
};
// TFC Grenades #1

class CBasePlayerAmmo : public CBaseEntity
{

This is basically the original declaration of CHandGrenade, only I made Spawn() and GetItemInfo() virtual functions (we'll override them) and I added the ShootGrenade() function, which makes inheritance easier. As a result of this, something has to be changed in handgrenade.cpp. First of all, remove the class-declaration of CHandGrenade, around line 40. Don't remove the LINK_ENTITY_TO_CLASS-statement. Now go to line 160, and change the code to:

		if (time < 0)
			time = 0;

// TFC Grenades #1
		ShootGrenade( m_pPlayer->pev, vecSrc, vecThrow, time );
// TFC Grenades #1

		if (flVel < 500)

Instead of directly creating the grenade, it calls a subfunction. The advantage of this is that we needn't recode all of the grenade launching stuff for the hallucination grenade. It will be as simple as overwriting ShootGrenade(), which is only 4 lines long and has to be added on line 232:

		SendWeaponAnim( iAnim );
	}
}

// TFC Grenades #1
void CHandGrenade::ShootGrenade( entvars_t *pevAttacker, Vector vecSrc, Vector vecThrow, float flTime )
{
	CGrenade::ShootTimed( pevAttacker, vecSrc, vecThrow, flTime );
}
// TFC Grenades #1

There are still some preparations to be done in weapons.h. Around line 84:

#define	WEAPON_SNARK			15
// TFC Grenades #1
#define WEAPON_HALLUCGRENADE	16
// TFC Grenades #1

#define WEAPON_ALLWEAPONS		(~(1<<WEAPON_SUIT))

This is the weapon's unique id. If you already added a weapon, you'd set it to 17 or more. Then around line 113:

#define TRIPMINE_WEIGHT		-10
// TFC Grenades #1
#define HALLUCGRENADE_WEIGHT	3
// TFC Grenades #1

The weapon code will choose the weapon with the heighest weight if your current weapon runs out of ammo. Now around line 132:

#define HORNET_MAX_CARRY		8
// TFC Grenades #1
#define HALLUCGRENADE_MAX_CARRY	10
// TFC Grenades #1

Pretty self-explanatory. Around line 172:

#define HIVEHAND_DEFAULT_GIVE		8
// TFC Grenades #1
#define HALLUCGRENADE_DEFAULT_GIVE	5
// TFC Grenades #1

Now we'll add the actual code. Add a new source file - I named it hallucgren.cpp, but that's up to you. We'll add the code to it now:

#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "weapons.h"
#include "soundent.h"

class CHallucGrenade : public CGrenade {
public:
	BOOL m_fDetonated;
	int m_iGasCycle;
	float m_flGasTimeout;

	entvars_t *pevOwner;

	virtual void Spawn( void );
	static CHallucGrenade *ShootHalluc( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time );

	virtual void Explode( TraceResult *pTrace, int bitsDamageType );
	void EXPORT DamageThink( void );
};
LINK_ENTITY_TO_CLASS( hallucgrenade, CHallucGrenade );

First of all, there are some necessary #includes. Then CHallucGrenade is declared, and the classname linked to the C++ class.

void CHallucGrenade::Spawn( void )
{
	pev->movetype = MOVETYPE_BOUNCE;
	pev->classname = MAKE_STRING( "hallucgrenade" );
	
	pev->solid = SOLID_BBOX;

	SET_MODEL(ENT(pev), "models/grenade.mdl");
	UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0));

	pev->dmg = 5;
	m_fRegisteredSound = FALSE;

	m_fDetonated = FALSE;
}

// more or less the same as ShootTimed()
CHallucGrenade *CHallucGrenade::ShootHalluc( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time )
{
	CHallucGrenade *pGrenade = GetClassPtr( (CHallucGrenade *)NULL );
	pGrenade->Spawn();
	UTIL_SetOrigin( pGrenade->pev, vecStart );
	pGrenade->pev->velocity = vecVelocity;
	pGrenade->pev->angles = UTIL_VecToAngles(pGrenade->pev->velocity);
	pGrenade->pev->owner = ENT(pevOwner);
	pGrenade->pevOwner = pevOwner;
	
	pGrenade->SetTouch( BounceTouch );	// Bounce if touched
	
	// Take one second off of the desired detonation time and set the think to PreDetonate. PreDetonate
	// will insert a DANGER sound into the world sound list and delay detonation for one second so that 
	// the grenade explodes after the exact amount of time specified in the call to ShootTimed(). 

	pGrenade->pev->dmgtime = gpGlobals->time + time;
	pGrenade->SetThink( TumbleThink );
	pGrenade->pev->nextthink = gpGlobals->time + 0.1;
	if (time < 0.1)
	{
		pGrenade->pev->nextthink = gpGlobals->time;
		pGrenade->pev->velocity = Vector( 0, 0, 0 );
	}
		
	pGrenade->pev->sequence = RANDOM_LONG( 3, 6 );
	pGrenade->pev->framerate = 1.0;

	// Tumble through the air
	// pGrenade->pev->avelocity.x = -400;

	pGrenade->pev->gravity = 0.5;
	pGrenade->pev->friction = 0.8;

	SET_MODEL(ENT(pGrenade->pev), "models/spy_grenade.mdl");

	// Change this to fit your needs
	pGrenade->pev->dmg = 5;

	return pGrenade;
}

This is more or less the same as for the normal grenade. Some changes are done, e.g. a different model (the spy_grenade.mdl from TFC), a different classname and damage.

void CHallucGrenade::DamageThink( void )
{
	if (gpGlobals->time > m_flGasTimeout) {
		UTIL_Remove(this);
		return;
	}

	// don't produce the gas effect to often, to reduce lag
	if (!m_iGasCycle) {
		MESSAGE_BEGIN(MSG_PVS, SVC_TEMPENTITY, pev->origin);
			WRITE_BYTE( TE_PARTICLEBURST );
			WRITE_COORD( pev->origin.x );
			WRITE_COORD( pev->origin.y );
			WRITE_COORD( pev->origin.z );
			WRITE_SHORT( 128 );	// radius
			WRITE_BYTE( 80 );	// color
			WRITE_BYTE( 30 );	// life
		MESSAGE_END( );
	}

	m_iGasCycle++;
	// you can increase this, to create less particle and reduce lag
	if (m_iGasCycle >= 2)
		m_iGasCycle = 0;

	// do the damage; we use the global function, as CBaseMonster's RadiusDamage
	// doesn't have a radius parameter
	// NOTE: the direct damage is quite low (currently 5 per half second),
	//       most of the effect will be the hallucination
	::RadiusDamage(pev->origin, pev, pevOwner, pev->dmg, 128, CLASS_NONE, DMG_HALLUC );

	pev->nextthink = gpGlobals->time  + 0.5;
}

Once the grenade has exploded, this function is called every 0.5 seconds to create the particle effect and to do damage.

void CHallucGrenade::Explode( TraceResult *pTrace, int bitsDamageType )
{
	if (!m_fDetonated)
		m_fDetonated = TRUE;
	else
		return;	// don't explode twice

	pev->solid = SOLID_NOT;// intangible
	pev->takedamage = DAMAGE_NO;

	// should we really do this? what if the grenade is still flying around?
	pev->velocity = g_vecZero;

	// don't explode if under water
	// you can take this out if you want
	int iContents = UTIL_PointContents ( pev->origin );
	if (iContents == CONTENTS_WATER) {
		UTIL_Remove(this);
		return;
	}
	
	// the DamageThink will actually do the damage
	SetThink(DamageThink);
	pev->nextthink = gpGlobals->time + 0.1;
	m_flGasTimeout = gpGlobals->time + 7.5;

	// Pull out of the wall a bit
	if ( pTrace->flFraction != 1.0 )
	{
		pev->origin = pTrace->vecEndPos + (pTrace->vecPlaneNormal * 5);
	}

	CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, NORMAL_EXPLOSION_VOLUME, 3.0 );

	// oh well...
	// Valve comments say that TraceLine won't hit pev->owner; let's believe 'em ;-)
	if ( pev->owner )
		pevOwner = VARS( pev->owner );
	else
		pevOwner = NULL;
	pev->owner = NULL; // can't traceline attack owner if this is set

	// change the wavs below to something better
	switch ( RANDOM_LONG( 0, 2 ) )
	{
		case 0:	EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris1.wav", 0.55, ATTN_NORM);	break;
		case 1:	EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris2.wav", 0.55, ATTN_NORM);	break;
		case 2:	EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris3.wav", 0.55, ATTN_NORM);	break;
	}
}

This is the new Explode function. The main difference from the original is that it won't create an explosion and radius damage. Instead, it causes DamageThink() to be called, which does that job every 0.5, until the timeout has been reached (after 7.5 seconds).

class CWHallucGrenade : public CHandGrenade {
public:
	virtual void Spawn( void );
	virtual int GetItemInfo(ItemInfo *p);

	void ShootGrenade( entvars_t *pevAttacker, Vector vecSrc, Vector vecThrow, float flTime );
};
LINK_ENTITY_TO_CLASS( weapon_hallucgrenade, CWHallucGrenade );

void CWHallucGrenade::Spawn( void )
{
	Precache( );

	m_iId = WEAPON_HALLUCGRENADE;
	SET_MODEL(ENT(pev), "models/w_grenade.mdl");

	m_iDefaultAmmo = HALLUCGRENADE_DEFAULT_GIVE;

	FallInit();// get ready to fall down.
}

int CWHallucGrenade::GetItemInfo(ItemInfo *p)
{
	p->pszName = STRING(pev->classname);
	p->pszAmmo1 = "Hallucination Grenade";
	p->iMaxAmmo1 = HALLUCGRENADE_MAX_CARRY;
	p->pszAmmo2 = NULL;
	p->iMaxAmmo2 = -1;
	p->iMaxClip = WEAPON_NOCLIP;
// secondary clip
	p->iMaxClip2 = WEAPON_NOCLIP;
// secondary clip
	p->iSlot = 4;
	p->iPosition = 4;
	p->iId = m_iId = WEAPON_HALLUCGRENADE;
	p->iWeight = HALLUCGRENADE_WEIGHT;
	p->iFlags = ITEM_FLAG_LIMITINWORLD | ITEM_FLAG_EXHAUSTIBLE;

	return 1;
}

void CWHallucGrenade::ShootGrenade( entvars_t *pevAttacker, Vector vecSrc, Vector vecThrow, float flTime )
{
	CHallucGrenade::ShootHalluc( pevAttacker, vecSrc, vecThrow, flTime );
}

This is the code for the actual weapon. It overwrites Spawn() and GetItemInfo() to fit the new weapon's need, and finally makes us shoot a hallucination gas grenade instead of the plain old handgrenade.

Now you need to precache some things. Go to line 380 of weapons.cpp and add:

	// hand grenade
	UTIL_PrecacheOtherWeapon("weapon_handgrenade");
// TFC Grenades #1
	UTIL_PrecacheOtherWeapon("weapon_hallucgrenade");
// TFC Grenades #1

Then add the following to line 413:

// TFC Grenades #1
	PRECACHE_MODEL ("models/spy_grenade.mdl");
// TFC Grenades #1

	// used by explosions
	PRECACHE_MODEL ("models/grenade.mdl");

Make sure that a model with this name is in your mod directory. You could extract the model from the TFC pakfile if you don't have your own.

And you want cheating to work, don't you? So add the following to line 4260 in player.cpp:

		GiveNamedItem( "weapon_handgrenade" );
// TFC Grenades #1
		GiveNamedItem( "weapon_hallucgrenade" );
// TFC Grenades #1
		GiveNamedItem( "weapon_tripmine" );

Finally, you need a file called weapon_hallucgrenade.txt in your mod's sprite directory. Otherwise, the weapon won't show up correctly on the hud. I simply copied and renamed weapon_handgrenade.txt for this.

 

 


If you have any questions, suggestions, bugfixes, creative critizism or you want to give me donations for this great tutorial, mail me or post on the Wavelength forums. Go to Wavelength, click on Forums and then on Coding. You have to register yourself to post there, but visiting it regularly is good anyway. There is a forum on HL Programming Planet that I regularly visit, too, but it has less traffic and less people visit it.


You may use this tutorial for any non-commercial mod development, as long as you give me some credit for it.

(c) 2000 by Nicolai Haehnle aka Prefect