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