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