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