Wavelength Logo
tl.jpg (2351 bytes) blank.gif (837 bytes) tr.jpg (2446 bytes)
blank.gif (837 bytes)
Cluster Grenades by RR2DO2
blank.gif (837 bytes)
Yeah, I know it isn't a very original weapon to create, but it's rather easy to code and shows off some of the main things that are involved with creating extra weapons in Half-Life.

Ok, let's start this thing! I assume you have a compiler and are able to compile a server-dll for Half-Life (check out "tutorial0"). In my example code I mention line numbers, but they can be a few off. Also I give some of the lines that are before and after my added or changed code to make it more clear where the code should be insterted.

Open the server-dll project and add this file: "clustergrenade.cpp". Then open "handgrenade.cpp" and copy all contents into the "clustergrenade.cpp". Continue with renaming all "hand" occurences in the clustergrenade file to "cluster". (For example: "HANDGRENADE_PRIMARY_VOLUME" must be changed to "CLUSTERGRENADE_PRIMARY_VOLUME")

Next go to the CClusterGrenade::GetItemIfno routine and change it to the following code:

int CClusterGrenade::GetItemInfo(ItemInfo *p)
{
p->pszName = STRING(pev->classname); //The name of the grenade
p->pszAmmo1 = "Cluster Grenade"; //The name of the ammo (?)
p->iMaxAmmo1 = CLUSTERGRENADE_MAX_CARRY; //How much can you carry?
p->pszAmmo2 = NULL; //No secondary ammo (?)
p->iMaxAmmo2 = -1; //Thus some sort of NULL value (?)
p->iMaxClip = WEAPON_NOCLIP; //This weapon doesn't use clips
p->iSlot = 0; //Weapon-slot index 0
p->iPosition = 1; //Weapon-slot position 1
p->iId = m_iId = WEAPON_CLUSTERGRENADE; //Identifier (what it is)
p->iWeight = CLUSTERGRENADE_WEIGHT; //How heavy is it
p->iFlags = ITEM_FLAG_LIMITINWORLD | ITEM_FLAG_EXHAUSTIBLE; //Not sure, maybe it is limited to a certain ammount of instances (?)

return 1;
}

Some explanation on the weapon-slots. A weapon slot is the place on the hud where a player can select weapons. The most left slot (number 1, with the crowbar) has index 0. The crowbar thus has p->iSlot=0. I wanted to put the cluster grenade selection under the crowbar, so i gave it slot index 0. The crowbar has a p->iPosition of 0 (it's the upper most weapon in this slot) so I gave the cluster grenade an p->iPosition of 1. More on weapon selection later.

Next go to the public section of "class CClusterGrenade : public CBasePlayerWeapon". Change the code to the following:

void Precache( void );
int iItemSlot( void ) { return 1; } //It's in slot index 0, with menu number 1 (you have to enter the "slot1" command to access this slot)
int GetItemInfo(ItemInfo *p);

Continue by going to the "void CClusterGrenade::WeaponIdle( void )" routine. In this code you will find this line:

CGrenade::ShootTimed( m_pPlayer->pev, vecSrc, vecThrow, time );

This will launch a default timed grenade when the player is shooting. "m_pPlayer->pev" is the owner, "vecSrc" the origing, vecThrow the vector which is used to determine the way the grenade is launched and "time" is the time in seconds (at least, I think it is in seconds, it seems to be correct) before the launched grenade detonates. Anyway, we're going to change this line into:

CGrenade::ShootTimedCluster( m_pPlayer->pev, vecSrc, vecThrow, time );

Because we don't want a normal timed grenade, but a timed cluster grenade! This is all there has to be changed in this file, so save it.

If you would compile now, the compiler would go mad because we first have to add some declarations in other files. The first one is in "func_break.cpp". Open it and go to the "CBreakable::pSpawnObjects" routine. Then enter this code:

"weapon_hornetgun", // 21
"weapon_clustergrenade", //22 For clustergrenade
};

Save this file and open "game.cpp" and enter this code around line 359:

// Tripmine
cvar_t sk_plr_tripmine1 = {"sk_plr_tripmine1","0"};
cvar_t sk_plr_tripmine2 = {"sk_plr_tripmine2","0"};
cvar_t sk_plr_tripmine3 = {"sk_plr_tripmine3","0"};

// Cluster Grendade
cvar_t sk_plr_cluster_grenade1 = {"sk_plr_cluster_grenade1","0"};
cvar_t sk_plr_cluster_grenade2 = {"sk_plr_cluster_grenade2","0"};
cvar_t sk_plr_cluster_grenade3 = {"sk_plr_cluster_grenade3","0"};

// WORLD WEAPONS
cvar_t sk_12mm_bullet1 = {"sk_12mm_bullet1","0"};
cvar_t sk_12mm_bullet2 = {"sk_12mm_bullet2","0"};
cvar_t sk_12mm_bullet3 = {"sk_12mm_bullet3","0"};

This part of the code is to enable the user to change cluster grenade settings through the skill.cfg file. But we're not finished with it yet, go to line 788 and change the part of the code there to this:

// Tripmine
CVAR_REGISTER ( &sk_plr_tripmine1 );// {"sk_plr_tripmine1","0"};
CVAR_REGISTER ( &sk_plr_tripmine2 );// {"sk_plr_tripmine2","0"};
CVAR_REGISTER ( &sk_plr_tripmine3 );// {"sk_plr_tripmine3","0"};

// Cluster Grendade
CVAR_REGISTER ( &sk_plr_cluster_grenade1 );// {"sk_plr_cluster_grenade1","0"};
CVAR_REGISTER ( &sk_plr_cluster_grenade2 );// {"sk_plr_cluster_grenade2","0"};
CVAR_REGISTER ( &sk_plr_cluster_grenade3 );// {"sk_plr_cluster_grenade3","0"};

// WORLD WEAPONS
CVAR_REGISTER ( &sk_12mm_bullet1 );// {"sk_12mm_bullet1","0"};
CVAR_REGISTER ( &sk_12mm_bullet2 );// {"sk_12mm_bullet2","0"};
CVAR_REGISTER ( &sk_12mm_bullet3 );// {"sk_12mm_bullet3","0"};

That's all for this file. Save it and open "gamerules.cpp". Go to line 261 and change it to this:

// Tripmine
gSkillData.plrDmgTripmine = GetSkillCvar( "sk_plr_tripmine");

// Cluster Grendade
gSkillData.plrDmgClusterGrenade = GetSkillCvar( "sk_plr_cluster_grenade");

// MONSTER WEAPONS
gSkillData.monDmg12MM = GetSkillCvar( "sk_12mm_bullet");
gSkillData.monDmgMP5 = GetSkillCvar ("sk_9mmAR_bullet" );
gSkillData.monDmg9MM = GetSkillCvar( "sk_9mm_bullet");

This is also for the "skill.cfg" config. The last part for this config is found in "skill.h". Open it and go to line 111 and add this code:

float plrDmgTripmine;
float plrDmgClusterGrenade;

// weapons shared by monsters
float monDmg9MM;

Save the file and open "multiplay_gamerulse.cpp" and go to line 138. Enter this:

// hornet
gSkillData.plrDmgHornet = 10;

// Cluster Grendade
gSkillData.plrDmgHandGrenade = 50;
}

This is the damage declaration for multiplayer games. Of course we want cheating to keep working right, so open "player.cpp" and go to line 3784. Enter the following code:

GiveNamedItem( "weapon_tripmine" );
GiveNamedItem( "weapon_clustergrenade" );
#ifndef OEM_BUILD
GiveNamedItem( "weapon_357" );

This will add the "weapon_clustergrenade" to the players inventory when the "impulse 101" cheat is used. To make sure the cluster grenade will be precached, open "weapons.cpp" and go to line 387. Add this code:

UTIL_PrecacheOtherWeapon( "weapon_hornetgun" );
#endif

//Cluster grenade only works with the full HL version
#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD )
// cluster grenade
UTIL_PrecacheOtherWeapon("weapon_clustergrenade");
#endif

#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD )

Next we save it and open "weapons.h". Go to line 79 and add this:

#define WEAPON_SNARK 15
#define WEAPON_CLUSTERGRENADE 16

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

I think this us used to give a weapon a id code as an alternative for the name. Continue by going to line 105 and add this:

#define TRIPMINE_WEIGHT -10
#define CLUSTERGRENADE_WEIGHT 8

// weapon clip/carry ammo capacities
#define URANIUM_MAX_CARRY 100

This weight is used by gravity code and by the weapon changing code to see how the weapon reacts on gravity and how fast it swaps on weapon changing. Now go to line 120 and do the same we're already doing the whole time:

#define M203_GRENADE_MAX_CARRY 10
#define CLUSTERGRENADE_MAX_CARRY 6

// the maximum amount of ammo each weapon's clip can hold
#define WEAPON_NOCLIP -1

This defines how many cluster grenades a player can carry. Add this at line 139:

#define SNARK_MAX_CLIP WEAPON_NOCLIP
#define CLUSTERGRENADE_MAX_CLIP WEAPON_NOCLIP

// the default amount of ammo that comes with each gun when it spawns
#define GLOCK_DEFAULT_GIVE 17

A cluster grenade doesn't use clips, so define "WEAPON_NOCLIP" to say to the engine it doesn't has to look for them. At line 159 add this:

#define HIVEHAND_DEFAULT_GIVE 8
#define CLUSTERGRENADE_DEFAULT_GIVE 3

// The amount of ammo given to a player by an ammo item.
#define AMMO_URANIUMBOX_GIVE 20

This is how much ammo a player gets when he picks up a "weapon_clustergrenade" in a map. (So in this case he gets three grenades.) So much for now in this file.

Next we need to add the new cluster grenade firing code. Start this by opening the "ggrenade.cpp file". Go to the end of this file and add the following code just above the command line with "//====end grenade" on it:

//Cluster grenade code

//This is almost unchanged ShootTimed except for the fact it calls an ClusterTumbleThink
CGrenade * CGrenade:: ShootTimedCluster( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time )
{
CGrenade *pGrenade = GetClassPtr( (CGrenade *)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->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( ClusterTumbleThink );
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/w_grenade.mdl");
pGrenade->pev->dmg = 100;

return pGrenade;
}

//Now calls the ClusterDetonate routine instead of the Detonate one
void CGrenade :: ClusterTumbleThink( void )
{
if (!IsInWorld())
{
UTIL_Remove( this );
return;
}

StudioFrameAdvance( );
pev->nextthink = gpGlobals->time + 0.1;

if (pev->dmgtime - 1 < gpGlobals->time)
{
CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin + pev->velocity * (pev->dmgtime - gpGlobals->time), 400, 0.1 );
}

if (pev->dmgtime <= gpGlobals->time)
{
SetThink( ClusterDetonate );
}
if (pev->waterlevel != 0)
{
pev->velocity = pev->velocity * 0.5;
pev->framerate = 0.2;
}
}

//This one launches the cluster fragments
void CGrenade::ClusterDetonate( void )
{
TraceResult tr;
Vector vecSpot;// trace starts here!

vecSpot = pev->origin + Vector ( 0 , 0 , 8 );
UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -40 ), ignore_monsters, ENT(pev), & tr);

Explode( &tr, DMG_BLAST );

//Launch 6 grenades at random angles
CGrenade::ShootTimed( pev, pev->origin, Vector(RANDOM_LONG( -200, 200), RANDOM_LONG( -200, 200), RANDOM_LONG( -200, 200))*RANDOM_LONG( 5, 10), RANDOM_FLOAT( 1.00, 5.00));
CGrenade::ShootTimed( pev, pev->origin, Vector(RANDOM_LONG( -200, 200), RANDOM_LONG( -200, 200), RANDOM_LONG( -200, 200))*RANDOM_LONG( 5, 10), RANDOM_FLOAT( 1.00, 5.00));
CGrenade::ShootTimed( pev, pev->origin, Vector(RANDOM_LONG( -200, 200), RANDOM_LONG( -200, 200), RANDOM_LONG( -200, 200))*RANDOM_LONG( 5, 10), RANDOM_FLOAT( 1.00, 5.00));
CGrenade::ShootTimed( pev, pev->origin, Vector(RANDOM_LONG( -200, 200), RANDOM_LONG( -200, 200), RANDOM_LONG( -200, 200))*RANDOM_LONG( 5, 10), RANDOM_FLOAT( 1.00, 5.00));
CGrenade::ShootTimed( pev, pev->origin, Vector(RANDOM_LONG( -200, 200), RANDOM_LONG( -200, 200), RANDOM_LONG( -200, 200))*RANDOM_LONG( 5, 10), RANDOM_FLOAT( 1.00, 5.00));
CGrenade::ShootTimed( pev, pev->origin, Vector(RANDOM_LONG( -200, 200), RANDOM_LONG( -200, 200), RANDOM_LONG( -200, 200))*RANDOM_LONG( 5, 10), RANDOM_FLOAT( 1.00, 5.00));
}

Let me explain some things. First, when a cluster grenade is thrown it loops through the "ShootTimedCluster" routine. In that routing the think function of the cluster grenade will be set to "ClusterTumbleThink". This routine will be run every server frame, and it will check or change some things such as the velocity of the grenade, or if it is time to explode at that moment. If yes, the "ClusterDetonate" routine is called. In this routine (which will cause the cluster grenade to be removed from the world and will spawn a explosion) I've added six times a call to the "ShootTimed". This causes six "ShootTimed" grenades to be launched. The "ShootTimed" fuction is used in this way: "ShootTimed( owner, origin, movementvector, timebeforedetonation)".
I've used "pev" as owner. One of the nice things of the Half-Life engine is that the player who casted the original cluster grenade the owner is over that cluster grenade AND over all cluster fragments that this grenade launches. Or, in other words, the pev has a "masterowner" and passes this one through to it's own "clients". This way there won't be troubles with the scoring of frags. The origin is placed at the origin of "pev", because the fragments have to spawn at the place where the cluster grenade explodes. The movement vector is created like this: Vector(x, y, z)*speed. I use the "RANDOM_LONG(lowestpossiblevalue, highestpossiblevalue)" function to get a random value for each of these parameters in the movement vector calculation. Next I calculate a random detonation time using the "RANDOM_FLOAT(lowestpossiblevalue, highestpossiblevalue)" function. This one returns a random float value.

Now we need to declerate the cluster grenade routines. This is also done in "weapons.h". Open it and go to line 36. Add this:

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

void Explode( Vector vecSrc, Vector vecAim );

This is just a simple declaration, just as the next one at line 49:

void EXPORT TumbleThink( void );
void EXPORT ClusterTumbleThink( void );
void EXPORT ClusterDetonate( void );

virtual void BounceSound( void );

This is everything there had to be done in the code. So you can save everything and hit the compile button! When the dll has been compiled and you start Half-Life you will see that some things are missing. The sprites that represent the weapon info on the hud. This needs to be done in a few other files in the "[yourmoddir]/sprites" directory. For the cluster grenade I'll use the sprites of the normal handgrenade. Create a "weapon_clustergrenade.txt" file in the sprites dir and change the contents to this:

6
weapon 320 320hud1 160 0 80 20
weapon_s 320 320hud1 160 20 80 20
ammo 320 320hud2 36 34 18 18
weapon 640 640hud3 0 0 170 45
weapon_s 640 640hud6 0 0 170 45
ammo 640 640hud7 48 96 24 24

Some comments on this. The "6" in the first line is needed for the engine so it knows that all the data is on the following six lines. The first three lines are the sprite information for low-res, the last three are for high-res. A line is build up like this: "item name - resolution info - filename where the sprite is in - upperleft x coord of part of image that has to be drawn - upperleft y coord - lowerright x coord - lowerright y coord". I'm almost sure "weapon" is the big weapon icon, "weapon_s" the small one and "ammo" the ammo-sprite that represents the weapons ammo.

Well, that's all! Now you've sucessfully created a complete new weapon in Half-Life. Some other things you could try to change are:
-Add smoke trails to the cluster fragments
-Change the model of the cluster fragments, for example to the "touchgrenade" model (the MP5 grenade)
-Whatever crazy thing you can come up with... (Bouncy cluster fragments? ;)

Send any suggestions, comments or additions to rr2do2@gmx.net and check my site out at http://web.archive.org/web/20030508004732/http://surf.to/rr2do2 (sorry if it isn't up to date at he moment, it's just some sort of gallery of my work).

Go go gib 'm!

 

blank.gif (837 bytes)
bl.jpg (2471 bytes) blank.gif (837 bytes) br.jpg (2132 bytes)