
Version 3.20 source code
Sniper Rifle
Add in the blue code and remove any pink code.
First open up g_items.c.
This is where you should always start when adding a new weapon. Right at
the top, add in the indicated line:
void Weapon_Blaster (edict_t
*ent);
void Weapon_Shotgun (edict_t *ent);
void Weapon_SuperShotgun (edict_t *ent);
void Weapon_Machinegun (edict_t *ent);
void Weapon_Chaingun (edict_t *ent);
void Weapon_HyperBlaster (edict_t *ent);
void Weapon_RocketLauncher (edict_t *ent);
void Weapon_Grenade (edict_t *ent);
void Weapon_GrenadeLauncher (edict_t *ent);
void Weapon_Railgun (edict_t *ent);
void Weapon_BFG (edict_t *ent);
void
Weapon_SniperRifle (edict_t *ent);//Will
Ok, that just defines the actual
weapon function. You need to do that for every new weapon you add, as
well as adding in the next section near the end of the file:
/*QUAKED weapon_bfg (.3 .3 1)
(-16 -16 -16) (16 16 16)
*/
{
"weapon_bfg",
Pickup_Weapon,
Use_Weapon,
Drop_Weapon,
Weapon_BFG,
"misc/w_pkup.wav",
"models/weapons/g_bfg/tris.md2", EF_ROTATE,
"models/weapons/v_bfg/tris.md2",
/* icon */ "w_bfg",
/* pickup */ "BFG10K",
0,
50,
"Cells",
IT_WEAPON|IT_STAY_COOP,
NULL,
0,
/* precache */ "sprites/s_bfg1.sp2 sprites/s_bfg2.sp2 sprites/s_bfg3.sp2
weapons/bfg__f1y.wav weapons/bfg__l1a.wav weapons/bfg__x1b.wav
weapons/bfg_hum.wav"
},
/*QUAKED
weapon_sniper (.3 .3 1) (-16 -16 -16) (16 16 16)
*/
{
"weapon_sniper",//weapon function
Pickup_Weapon,
Use_Weapon,
Drop_Weapon,
Weapon_SniperRifle,//corresponds with the top of
g_items.c
"misc/w_pkup.wav",
"models/weapons/g_rail/tris.md2",
EF_ROTATE,//rotating
model
"models/weapons/v_rail/tris.md2",//model as seen in your hand
/* icon */ "w_railgun",
/* pickup */ "Sniper Rifle",
0,
1,//amount of ammo
"Slugs",//type of ammo
IT_WEAPON|IT_STAY_COOP,
WEAP_RAILGUN,//new
line in 3.20
NULL,
0,
/* precache */ "weapons/rg_hum.wav"
},
![]()
This part basically defines all
the functions that our sniper rifle will use, what ammo, the icon displayed,
the pickup name, which models to use, and how much ammo our new weapon will
look for before it actually fires. Notice that the above code you just
added is very similar to the railgun, only a few functions have changed.
Next open up p_weapon.c.
We need to add in a whole new weapon function for our sniper rifle, so go to
the bottom and add the code as shown:
void Weapon_BFG (edict_t *ent)
{
static int
pause_frames[] = {39, 45, 50, 55, 0};
static int fire_frames[]
= {9, 17, 0};
Weapon_Generic (ent, 8, 32, 55, 58, pause_frames,
fire_frames, weapon_bfg_fire);
}
/*
======================================================================
Sniper Rifle
======================================================================
*/
void weapon_sniper_fire (edict_t *ent)
{
vec3_t start;
vec3_t forward,
right;
vec3_t offset;
int
damage;
int
kick;
if (deathmatch->value)
{
damage = 100;
kick = 400;
}
else
{
damage = 100;
kick = 400;
}
if (is_quad)
{
damage *= 4;
kick *= 4;
}
AngleVectors
(ent->client->v_angle, forward, right, NULL);
VectorScale (forward, -3,
ent->client->kick_origin);
ent->client->kick_angles[0] =
-3;
VectorSet(offset, 0, 0,
ent->viewheight-0);
P_ProjectSource (ent->client,
ent->s.origin, offset, forward, right, start);
fire_sniper (ent, start, forward,
damage, kick);
// send muzzle flash
gi.WriteByte (svc_muzzleflash);
gi.WriteShort (ent-g_edicts);
gi.WriteByte (MZ_RAILGUN | is_silenced);
gi.multicast (ent->s.origin, MULTICAST_PVS);
ent->client->ps.gunframe++;
PlayerNoise(ent, start, PNOISE_WEAPON);
if ((! ( (int)dmflags->value & DF_INFINITE_AMMO ) )
&& (ent->client->pers.inventory[ent->client->ammo_index]
> 0))
ent->client->pers.inventory[ent->client->ammo_index]--;
}
void Weapon_SniperRifle (edict_t *ent)
{
static int
pause_frames[] = {56, 0};
static int fire_frames[]
= {4, 0};
Weapon_Generic (ent, 3, 16, 56, 57, pause_frames,
fire_frames, weapon_sniper_fire);
}
Ok, that is part of the weapon
firing function. At the top is where the chest damage is defined.
You'll see that we multiply or divide the damage later on for locational
damage, so set the damage in this function to whatever the chest damage will
be. Again, this code is similar to the railgun code, but I removed the
lines that shift the point of origin of the slug off center for right or left
handed, and removed the line that lowered the point of origin of the
slug. This makes the sniper rifle perfectly accurate no matter what
crosshair or hand you use (unless the crosshair was made incorrectly, the
standard crosshairs with Quake2 will work fine).
Next we need to move on to the
other very important file for adding new weapons; g_weapon.c. Again, go
down to the bottom and add in this function as shown:
void fire_bfg (edict_t *self,
vec3_t start, vec3_t dir, int damage, int speed, float damage_radius)
{
edict_t *bfg;
bfg = G_Spawn();
VectorCopy (start, bfg->s.origin);
VectorCopy (dir, bfg->movedir);
vectoangles (dir, bfg->s.angles);
VectorScale (dir, speed, bfg->velocity);
bfg->movetype = MOVETYPE_FLYMISSILE;
bfg->clipmask = MASK_SHOT;
bfg->solid = SOLID_BBOX;
bfg->s.effects |= EF_BFG | EF_ANIM_ALLFAST;
VectorClear (bfg->mins);
VectorClear (bfg->maxs);
bfg->s.modelindex = gi.modelindex
("sprites/s_bfg1.sp2");
bfg->owner = self;
bfg->touch = bfg_touch;
bfg->nextthink = level.time + 8000/speed;
bfg->think = G_FreeEdict;
bfg->radius_dmg = damage;
bfg->dmg_radius = damage_radius;
bfg->classname = "bfg blast";
bfg->s.sound = gi.soundindex
("weapons/bfg__l1a.wav");
bfg->think = bfg_think;
bfg->nextthink = level.time + FRAMETIME;
bfg->teammaster = bfg;
bfg->teamchain = NULL;
if (self->client)
check_dodge (self, bfg->s.origin,
dir, speed);
gi.linkentity (bfg);
}
/*
=================
fire_sniper
=================
*/
void fire_sniper (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int
kick)
{
vec3_t from;
vec3_t end;
trace_t tr;
edict_t *ignore;
int
mask;
qboolean water;
int bodypos;
int mod;
int n;
VectorMA (start, 8192, aimdir, end);
VectorCopy (start, from);
ignore = self;
water = false;
mask = MASK_SHOT|CONTENTS_SLIME|CONTENTS_LAVA;
while (ignore)
{
tr = gi.trace (from, NULL, NULL,
end, ignore, mask);
if (tr.contents &
(CONTENTS_SLIME|CONTENTS_LAVA))
{
mask &=
~(CONTENTS_SLIME|CONTENTS_LAVA);
water =
true;
}
else
{
if
((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client))
ignore = tr.ent;
else
ignore = NULL;
//beginning of locational damage
if (tr.ent->client)
{
if
(tr.endpos[2] < (tr.ent->s.origin[2] - 0))
{
bodypos = 1; // leg shot
mod = MOD_SNIPER_LEG;
damage = damage * .5;
}
else if
(tr.endpos[2] > ((tr.ent->s.origin[2] + 20)))
{
bodypos = 2;
mod = MOD_SNIPER_HEAD;
damage = damage * 2;
for (n= 0; n < 5; n++)
ThrowGib (tr.ent, "models/objects/gibs/sm_meat/tris.md2", damage,
GIB_ORGANIC);
}
else
{
bodypos = 0;
mod = MOD_SNIPER_CHEST;
}
//end of locational
damage
if ((tr.ent != self) && (tr.ent->takedamage))
T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick,
0, mod);
}
VectorCopy (tr.endpos, from);
}
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_GUNSHOT);
gi.WritePosition (tr.endpos);
gi.WriteDir (tr.plane.normal);
gi.multicast (tr.endpos, MULTICAST_PVS);
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_GUNSHOT);
gi.WritePosition (tr.endpos);
gi.WriteDir (tr.plane.normal);
gi.multicast (tr.endpos, MULTICAST_PVS);
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_GUNSHOT);
gi.WritePosition (tr.endpos);
gi.WriteDir (tr.plane.normal);
gi.multicast (tr.endpos, MULTICAST_PVS);
if (self->client)
PlayerNoise(self, tr.endpos,
PNOISE_IMPACT);
}
}
Ok, that was pretty big, but
I'll break it down. Again (!) this is alot like the railgun code in this
file, but it has a few important add-ons. The section that is commented
with "beginning of locational damage" is the special section just for
(you guessed it) locational damage! If you look at the if statements,
you'll see that all it does is check the height of the trace (that's the path
of your shot) on the player to determine where the shot hit. Remember
that a Quake2 player is 64 units tall. The first if statement looks for
anything below 0, the origin, which is at the middle of the player, or his/her
waist. If it's below that, then it multiplies the damage by .5,
effectively halfing it. If the shot is between 0 (remember, that's the
player's waist) and +20 (20 units above the origin or waist) then it does
normal damage, which you set in p_weapon.c. Lastly, if it's above +20,
than it's considered a headshot, and it multiplies the damage by 2, doubling
it. Also, there is a small line of code that begins with
"Throwgib" that makes some extra gibs fly if you happen to hit
someone in the head. The last section of the code has three structures
that are the same. They are the little bullet "splashes" seen
when your projectile hits the wall. I used three because a single splash
by itself looks wimpy, but the triple splash make it look like a larger
projectile hit the wall.
Next open up p_client.c and go
down to the initclientpersistent function. Since the sniper rifle doesn't
spawn on any maps, we'll have to have our player start with it when they spawn
into the game. Add in the code as shown:
/*
==============
InitClientPersistant
This is only called when the game first initializes in single player,
but is called after each death and level change in deathmatch
==============
*/
void InitClientPersistant (gclient_t *client)
{
gitem_t *item;
memset (&client->pers, 0, sizeof(client->pers));
item =
FindItem("Blaster");
client->pers.selected_item = ITEM_INDEX(item);
client->pers.inventory[client->pers.selected_item] =
1;
item = FindItem("Slugs");
client->pers.selected_item = ITEM_INDEX(item);
client->pers.inventory[client->pers.selected_item] =
10;
item = FindItem("Sniper Rifle");
client->pers.selected_item = ITEM_INDEX(item);
client->pers.inventory[client->pers.selected_item] =
1;
client->pers.weapon = item;
client->pers.health
= 100;
client->pers.max_health
= 100;
client->pers.max_bullets = 200;
client->pers.max_shells = 100;
client->pers.max_rockets = 50;
client->pers.max_grenades = 50;
client->pers.max_cells = 200;
client->pers.max_slugs = 50;
client->pers.connected = true;
}
All we did was add in a few
lines to give each player 10 slugs and a sniper rifle every respawn. This
code can be applied to any item in the Quake2 universe, just make sure you have
the name correct, other wise it will cause the game to crash when a player
enters the game. Also, the last item before the
"client->pers.weapon = item;" line is the item the player enters
the game with in their hands. NEVER have an itme there, only weapons!
Next we need to add in the new
Means Of Death statements in p_client.c. Go down to the clientobituary
function and add in all the indicated lines as shown:
if (attacker &&
attacker->client)
{
switch (mod)
{
//sniper rifle death
messages
case MOD_SNIPER_CHEST:
message = "was pierced by";
message2 = "'s sniper rifle";
break;
case
MOD_SNIPER_LEG:
message = "has lost a leg thanks to";
message2 = "'s sniper rifle";
break;
case
MOD_SNIPER_HEAD:
message = "recieved a lobotomy from";
message2 = "'s sniper rifle";
break;
//end sniper rifle death messages
case
MOD_BLASTER:
message = "was blasted by";
break;
case
MOD_SHOTGUN:
message = "was gunned down by";
break;
Why did I add THREE new death
messages? If you look back at the code you added to g_weapon.c each
location from our sniper rifle has one. This let's the world know how
good of a shot you are. Wouldn't you rather have the lobotomy message
show up instead of the legs? (I sure would!) Feel free to change the
death messages to whatever you like.
Lastly, we need to define the
death messages in g_local.h. Scroll down to the section with all the MODS
and add in the code as shown:
#define
MOD_TRIGGER_HURT 31
#define MOD_HIT
32
#define MOD_TARGET_BLASTER 33
#define MOD_SNIPER_CHEST
34
#define MOD_SNIPER_LEG 35
#define MOD_SNIPER_HEAD 36
#define MOD_FRIENDLY_FIRE 0x8000000 <---Do not EVER change this
line !!
Make sure the numbers increase
in order, since you may have already added new death messages. Also
remember to never change the friendly fire mod's number.
That's it! You have
successfully added a devastating new weapon to your Quake2 mod! Now get
out there and give those baddies lobotomies!
By Willie