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://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! |