Created By: Frika-C
eMail: frika-c@earthling.net
Difficulty Scale: Easy/Medium


STEP 1

We'll be adding a new gun in this tutorial, so first we have to set up that. There are two 
ways to add a gun in the original Quake, one is to add a whole new weapon with it's own spawn
function, etc. or two, the method this tutorial uses, edit an existing weapon. We'll be 
replacing the nailgun, because I think it's the least useful. If you want to replace a 
different weapon, you'll have to do it on your own.

There are some custom models and sounds in this tutorial, so we'll have to precache them.


Open up weapons.qc and at the top in the function W_Precache add these three lines:

        precache_sound ("misc/basekey.wav");    // Laser Trip Bomb activate sound
        precache_sound ("weapons/deploy.wav");  // Laser Trip Bomb weapon sound
      precache_model ("progs/ltb.mdl");         // Laser Trip Bomb model

Your W_Precache function should now look something like this:

// called by worldspawn
void() W_Precache =

{
        precache_sound ("weapons/r_exp3.wav");  // new rocket explosion
        precache_sound ("weapons/rocket1i.wav");        // spike gun
        precache_sound ("weapons/sgun1.wav");
        precache_sound ("weapons/guncock.wav"); // player shotgun
        precache_sound ("weapons/ric1.wav");    // ricochet (used in c code)
        precache_sound ("weapons/ric2.wav");    // ricochet (used in c code)
        precache_sound ("weapons/ric3.wav");    // ricochet (used in c code)
        precache_sound ("weapons/spike2.wav");  // super spikes
        precache_sound ("weapons/tink1.wav");   // spikes tink (used in c code)
        precache_sound ("weapons/grenade.wav"); // grenade launcher
        precache_sound ("weapons/bounce.wav");          // grenade bounce
        precache_sound ("weapons/shotgn2.wav"); // super shotgun
        precache_sound ("misc/basekey.wav");    // Laser Trip Bomb
        precache_sound ("weapons/deploy.wav");  // Laser Trip Bomb
      precache_model ("progs/ltb.mdl");         // Laser Trip Bomb

};
To get the models and sounds, download this pak file and place it in the same directory as the patch.


STEP 2

Scroll down in weapons.qc and find the function called launch_spike. Cut and paste the 
following function right above it.

/*
================
W_FireLTB Laser Trip Bomb --(C) 1997 Frika C
================
*/
void() W_FireLTB =

{
        local   vector  source;         // Where the player is
        local entity    lasertrip;      // New entity for spawning

        
        makevectors (self.v_angle);     // Make a vector of the players facing angle
        source = self.origin + '0 0 16';        // Up a little
        traceline (source, source + v_forward*64, FALSE, self); // Trace a line straight out
        if (trace_fraction == 1.0)      // hit nothing
                return;
        if (trace_plane_normal_z != 0)  // Wall is not perfectly vertical
                return;                         // can't stick to floor or slope....
        if (trace_ent != world)         // hit a door, monster etc.
                return;
        self.currentammo = self.ammo_nails = self.ammo_nails - 10;      // Take away 10 nails  
        lasertrip = spawn();            // Spawn a new dynamic entity
        lasertrip.owner = world;        // If the player is the owner he can't trigger it!
        lasertrip.enemy = self;         // Keep track of the owner to give him credit though
        lasertrip.movetype = MOVETYPE_NONE;     // doesn't move
        lasertrip.solid = SOLID_BBOX;
        lasertrip.classname = "laser_trip_bomb";        // Name for it
        lasertrip.angles = vectoangles(trace_plane_normal);     // This aligns it perpendicular to the wall it struck
        lasertrip.weapon = FALSE;               // weapon is used to flag if the bomb has been armed or not

        lasertrip.takedamage = DAMAGE_YES;      // Can be destroyed
        lasertrip.health = 40;
        lasertrip.th_pain = LTB_Pain;           // This allows other explosions trigger it's explosion
        lasertrip.th_die = LTB_Detonate;

        lasertrip.think = LTB_Arm;              // Arm in one second
        lasertrip.nextthink = time + 1;
        lasertrip.attack_finished = time + 1;//Attack_finished keeps track of how long until it should re-draw the beam
        setmodel (lasertrip, "progs/ltb.mdl");  // set the model
        setsize (lasertrip, '0 0 0', '0 0 0');  // See below            

        setorigin (lasertrip, trace_endpos);
        sound (self, CHAN_WEAPON, "weapons/deploy.wav", 1, ATTN_NORM); // Play the deployment sound
};

Notice the use of trace_plane_normal. This seldom used but very powerful angle is a line 
perpendicular to the plane of the world brush traceline struck.  Catch that? No? Oh well, 
it's not important.  What is important is the setsize is all set to zeros. This turns off 
all hit detection with the trip bomb. 


STEP 3

Next function we have to write is LBT_Arm. This function will arm the trip bomb once your out 
of the way then keep track of when someone gets in from of the beam. Copy this function and 
paste it immediately *above* the previous one.


void() LTB_Arm =

{
        local vector org;                               // Origin of the LTB
        local vector targ;                      // Target "       "
        
        makevectors(self.angles);               // Load the current looking position into v_forward
        org = self.origin + self.view_ofs;      // Set the origin;
        targ = org + v_forward * 2000;  // 2000 is the maximum distance of the bomb
        traceline(org, targ, FALSE, self);      // Traceline outward
        if (trace_fraction == 1.0)              // If hit nothing in 2000 units
        {
                LTB_Explode(); // Explode if wall is too far away
                return;
        }
        if ((trace_ent == self.enemy) && (self.weapon == FALSE)) //Weapon indicates if the LTB is armed yet
        {                                                                          //And enemy is the owner
                self.nextthink = time + 1; // Don't Arm until player is out of the way
                self.think = LTB_Arm;           // Wait yet another second (move it buster!)
                return;                 
        }
        if (self.weapon == FALSE)               // If not armed...
                self.weapon = TRUE; // arm....watch out!
        if ((trace_ent.velocity != '0 0 0') || (trace_ent.flags & FL_MONSTER)) // If object is moving or monster
        {// The above FL monster check was put in because some monsters could walk through
        // The beam without being detected (they don't have a velocity, they're using movetogoal())                     
                LTB_Explode();  // Kaboom!
                return;
        }
        if (self.attack_finished < time)        // Attack_finished keeps track of when the beam should be refeshed
        {
        WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
        WriteByte (MSG_BROADCAST, TE_LIGHTNING2);               // Fire lightning two.
        WriteEntity (MSG_BROADCAST, self);
        WriteCoord (MSG_BROADCAST, org_x);                      //This is a lighting brodcast.
        WriteCoord (MSG_BROADCAST, org_y);
        WriteCoord (MSG_BROADCAST, org_z);
        WriteCoord (MSG_BROADCAST, trace_endpos_x);     // it happens every .2 secs
        WriteCoord (MSG_BROADCAST, trace_endpos_y);
        WriteCoord (MSG_BROADCAST, trace_endpos_z);     // but the think function occurs evey .07
        self.attack_finished = time + 0.2;                      // this allows fast reaction on the beam
        }                                                               // without crowding the network with broadcasts

        self.think = LTB_Arm;
        self.nextthink = time + 0.07;                   // 0.07 secs before checking again
};

One of the replacement models in the .pak is bolt2.mdl. If overrides Quake's standard bolt2 
model which is used whenever the client recieves a TE_LIGHTNING2 message. The new bolt2.mdl 
is a red laser, it it looks kind of strange using the lightning gun and a red laser comes out 
of the end. So we'll fix that next.


STEP 4

Still in weapons.qc, scroll up to the function W_FireLightning and find this bit of code:

        
        traceline (org, org + v_forward*600, TRUE, self);

        WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
        WriteByte (MSG_BROADCAST, TE_LIGHTNING2);
        WriteEntity (MSG_BROADCAST, self);
        WriteCoord (MSG_BROADCAST, org_x);
        WriteCoord (MSG_BROADCAST, org_y);
        WriteCoord (MSG_BROADCAST, org_z);
        WriteCoord (MSG_BROADCAST, trace_endpos_x);
        WriteCoord (MSG_BROADCAST, trace_endpos_y);
        WriteCoord (MSG_BROADCAST, trace_endpos_z);

        LightningDamage (self.origin, trace_endpos + v_forward*4, self, 30);
};

and just change TE_LIGHTNING2 to TE_LIGHTNING1. The code then should look like this:

        traceline (org, org + v_forward*600, TRUE, self);

        WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
        WriteByte (MSG_BROADCAST, TE_LIGHTNING1);       // Only change
        WriteEntity (MSG_BROADCAST, self);
        WriteCoord (MSG_BROADCAST, org_x);
        WriteCoord (MSG_BROADCAST, org_y);
        WriteCoord (MSG_BROADCAST, org_z);
        WriteCoord (MSG_BROADCAST, trace_endpos_x);
        WriteCoord (MSG_BROADCAST, trace_endpos_y);
        WriteCoord (MSG_BROADCAST, trace_endpos_z);

        LightningDamage (self.origin, trace_endpos + v_forward*4, self, 30);
};

Now when you fire the lightning gun instead of shooting out a bolt2.mdl it shoots out bolt.mdl
(No real noticeable difference between the two.)


STEP 5

That takes care of the deploying and arming of the weapon, let's move on to the actual 
explosion. Place the next three function *above* the function LTB_Arm we wrote in step 3.

void() LTB_Detonate =

{
        self.th_pain = SUB_Null;
        self.th_die = SUB_Null;         // switched to null to avoid chain reaction when exploding (repeatedly kills itself!)
        self.owner = self.enemy;        // Return Property rights so the player gets credit

        T_RadiusDamage (self, self.owner, 200, world); // Do some damage

        WriteByte (MSG_BROADCAST, SVC_TEMPENTITY);
        WriteByte (MSG_BROADCAST, TE_EXPLOSION);
        WriteCoord (MSG_BROADCAST, self.origin_x); // Standard Textbook explosion code
        WriteCoord (MSG_BROADCAST, self.origin_y);
        WriteCoord (MSG_BROADCAST, self.origin_z);

        BecomeExplosion ();
};
void() LTB_Explode =

{
        sound (self, CHAN_AUTO, "misc/basekey.wav", 1, ATTN_NORM);
        self.nextthink = time + 0.2;
        self.think = LTB_Detonate;      // A quick little function to play the trigger wav before detonation
};



void() LTB_Pain =

{
                self.health = 40;       //didn't kill it.....probably a distant explosion
};


The function LTB_Explode is called when someone or something crosses the beam . The little 
delay before the explosion allows them to think "Oh @#$*!" before they get blown sky high. 
LTB_Detonate is the actual explosion. LTB_Pain keeps the trip bomb from weakening. 


STEP 6

That's it for all the logic and stuff. Now all we have to do is hook this up to the nailgun.

Scroll down further in weapons.qc and find W_Attack
Look for this little bit of code:

        else if (self.weapon == IT_SUPER_SHOTGUN)
        {
                player_shot1 ();
                W_FireSuperShotgun ();
                self.attack_finished = time + 0.7;
        }
        else if (self.weapon == IT_NAILGUN)
        {
                player_nail1 ();
        }
        else if (self.weapon == IT_SUPER_NAILGUN)
        {
                player_rocket1();
                W_FireSuperSpikes();
                self.attack_finished = time + 0.8;
        }

And replace it with this:

        else if (self.weapon == IT_SUPER_SHOTGUN)
        {
                player_shot1 ();
                W_FireSuperShotgun ();
                self.attack_finished = time + 0.7;
        }
        else if (self.weapon == IT_NAILGUN) // Now Laser Trib Bomb
        {
                player_rocket1(); // Rocket animation
                W_FireLTB();    // Our deploy function
                self.attack_finished = time + 0.8; // Wait a little
        }
        else if (self.weapon == IT_SUPER_NAILGUN)
        {
                player_rocket1();
                W_FireSuperSpikes();
                self.attack_finished = time + 0.8;
        }
 

Compile the QC as usual, switch to the nailgun and fire it at a wall. Viola! Instant security 
system!


Tutorial htmlcoding by legion.