In this tutorial we will be reconstructing a basic mod that makes use of many DarkPlaces features and/or extensions.
Note that due to the nature of this tutorial, you must wait until you finish it before you compile and run.
I also recommend you create a new mod first to play around with the new DP effects before you add them to any serious
work you are doing, or at least create a backup of your source.
To start, please download darkplacestut.zip here. Inside it are three files needed for this lesson to work - effects.pk3,
game.cfg and playermovement.qc. The other two, credits.txt and dpextensions.qc are (mostly) unneeded but I strongly suggest you read them anyway.
Place playermovement.qc in your mod's source dir, add it one line below misc.qc in progs.src.
Place effects.pk3 and game.cfg in your mod dir.
Now we are going to add the definitions from dpextensions.qc that we will be using in this tut.
(Although normally you would stick dpextensions.qc in your source dir and add it to
progs.src, for this tut we will be doing it through defs.qc)
Paste the following at the bottom of defs.qc
float EF_NODRAW = 16; //Ent isn't rendered at all, can also use on a light source
float EF_ADDITIVE = 32; //Ent is rendered with additive blending
float EF_BLUE = 64; //Ent emits blue light - similar to the QuakeWorld (Ugh) effect
float EF_RED = 128; //Ent emits red light - see above
float EF_FLAME = 1024; //Ent emits smoke and fire particles
.float alpha; //Alpha value of an ent, default is 1 - 100% visible. To make an ent completely
// invisible, set its alpha to -1
.float glow_color; //This is the 8bit color of the glow
.float glow_size; //Size of the glow in units
.float glow_trail; //0 = no trail, 1 = trail
.float button3; //We're only going to use one extra input button for this tut
.float laser_active; //This is needed by something interesting :P
.float scale; //Render scale of an ent, the bounding box/clip hull is not affected by this.
// note that the default for this is 1, 100%. Maximum is somewhere around 15, 1500%
.entity viewmodelforclient; //Defines this ent as a viewmodel for another ent
.entity exteriormodeltoclient; //Use this for 3rd person muzzleflashes and such
vector(vector org) getlight = #92; //Returns a vector with the RGB value of the light on a 0-255 scale
void(vector org, vector velocity, float howmany) te_blood = #405; //A nice blood effect
void(vector org, vector color) te_explosionrgb = #407; //Like TE_EXPLOSIONQUAD, but you define
// the flash color - scale is RGB, 0-255
// converted to 0-1
void(vector org, vector vel, float howmany) te_spark = #411; //A nice spark effect
void(vector org) te_gunshotquad = #412; //Like the normal effect but it also produces a nice blue flash
void(vector org) te_spikequad = #413;
void(vector org) te_superspikequad = #414;
void(vector org) te_explosionquad = #415;
void(vector org) te_gunshot = #418; //Just like the old effect, but now called with a single line
void(vector org) te_spike = #419;
void(vector org) te_superspike = #420;
void(vector org) te_explosion = #421;
void(vector org) te_tarexplosion = #422;
void(vector org) te_wizspike = #423;
void(vector org) te_knightspike = #424;
void(vector org) te_lavasplash = #425;
void(vector org) te_teleport = #426;
void(entity own, vector start, vector end) te_lightning1 = #428;
void(entity own, vector start, vector end) te_lightning2 = #429;
void(entity own, vector start, vector end) te_lightning3 = #430;
void(vector org) te_plasmaburn = #433; //Produces a light flash and marks walls
float(string name, string value) registercvar = #93; //Creates a new cvar, ain't that handy?
//Begin value definitions (This is a dummy, only needed because world.qc comes after weapons.qc)
float player_jump_velocity;
float weapon_slot1_damage;
float weapon_slot1_alt_rof;
float weapon_slot1_alt_damage;
float weapon_slot1_rof;
float weapon_slot2_damage;
float weapon_slot2_alt_rof;
float weapon_slot2_alt_damage;
float weapon_slot3_rof;
float weapon_slot3_shots;
float weapon_slot3_alt_rof;
float weapon_slot3_alt_shots;
float weapon_slot4_damage;
float weapon_slot5_damage;
float weapon_slot5_alt_rof;
float weapon_slot5_alt_damage;
float weapon_slot6_rof;
float weapon_slot6_damage;
float weapon_slot7_rof;
float weapon_slot7_damage;
float weapon_slot8_range;
float weapon_slot8_damage;
//End value definitions
//All of this is needed for our slightly modified playermovement.qc - if you are going to
// start a new project using custom phsyics, read the notes at the bottom of this tutorial
.float ladder_time;
.vector punchvector;
.vector movement;
float(float a, float b) min = #94;
float(float a, float b, float c) min3 = #94;
float(float a, float b, float c, float d) min4 = #94;
float(float a, float b, float c, float d, float e) min5 = #94;
float(float a, float b, float c, float d, float e, float f) min6 = #94;
float(float a, float b, float c, float d, float e, float f, float g) min7 = #94;
float(float a, float b, float c, float d, float e, float f, float g, float h) min8 = #94;
float(float a, float b) max = #95;
float(float a, float b, float c) max3 = #95;
float(float a, float b, float c, float d) max4 = #95;
float(float a, float b, float c, float d, float e) max5 = #95;
float(float a, float b, float c, float d, float e, float f) max6 = #95;
float(float a, float b, float c, float d, float e, float f, float g) max7 = #95;
float(float a, float b, float c, float d, float e, float f, float g, float h) max8 = #95;
float(float minimum, float val, float maximum) bound = #96;
//And the rest of this is needed to invoke FRIK_FILE - comments below this line by LordHavoc
float(string s) stof = #81; // get numerical value from a string
float(string filename, float mode) fopen = #110; // opens a file inside quake/gamedir/data/ (mode is FILE_READ,
// FILE_APPEND, or FILE_WRITE), returns fhandle >= 0 if successful,
// or fhandle < 0 if unable to open file for any reason
void(float fhandle) fclose = #111; // closes a file
string(float fhandle) fgets = #112; // reads a line of text from the file and returns as a tempstring
void(float fhandle, string s) fputs = #113; // writes a line of text to the end of the file
float(string s) strlen = #114; // returns how many characters are in a string
string(string s1, string s2) strcat = #115; // concatenates two strings (for example "abc", "def" would return "abcdef")
// and returns as a tempstring
string(string s, float start, float length) substring = #116; // returns a section of a string as a tempstring
vector(string s) stov = #117; // returns vector value from a string
string(string s) strzone = #118; // makes a copy of a string into the string zone and returns it, this is often used to
// keep around a tempstring for longer periods of time (tempstrings are replaced often)
void(string s) strunzone = #119; // removes a copy of a string from the string zone (you can not use that string again
// or it may crash!!!)
float FILE_READ = 0;
float FILE_APPEND = 1;
float FILE_WRITE = 2;
Close defs.qc. Open world.qc.
Go to the very beginning of worldspawn() and paste this:
registercvar("player_jump_velocity", "270");
registercvar("weapon_slot1_rof", "0.5");
registercvar("weapon_slot1_damage", "50");
registercvar("weapon_slot1_alt_rof", "1");
registercvar("weapon_slot1_alt_damage", "100");
registercvar("weapon_slot2_rof", "0.1");
registercvar("weapon_slot2_damage", "10");
registercvar("weapon_slot2_alt_rof", "1");
registercvar("weapon_slot2_alt_damage", "120");
registercvar("weapon_slot3_rof", "0.5");
registercvar("weapon_slot3_shots", "7");
registercvar("weapon_slot3_alt_rof", "1");
registercvar("weapon_slot3_alt_shots", "14");
registercvar("weapon_slot4_rof", "0.4");
registercvar("weapon_slot4_damage", "10");
registercvar("weapon_slot5_rof", "0.1");
registercvar("weapon_slot5_damage", "20");
registercvar("weapon_slot5_alt_rof", "0.5");
registercvar("weapon_slot5_alt_damage", "60");
registercvar("weapon_slot6_rof", "0.7");
registercvar("weapon_slot6_damage", "120");
registercvar("weapon_slot7_rof", "0.8");
registercvar("weapon_slot7_damage", "100");
registercvar("weapon_slot8_range", "600");
registercvar("weapon_slot8_damage", "30");
Note that each time you call registercvar, you must provide two things:
A string to name the cvar and a float for the default value of the cvar.
Also note that Quake will not recognize the cvar until worldspawn is called, meaning
that if you are creating a server configuration cvar you are going to want to call a config
file somewhere in worldspawn. Since we are actually doing this, paste this after the call to InitBodyQue()
localcmd("exec game.cfg\n");
//Begin value redefinitions
player_jump_velocity = cvar("player_jump_velocity");
weapon_slot1_rof = cvar("weapon_slot1_rof");
weapon_slot1_damage = cvar("weapon_slot1_damage");
weapon_slot1_alt_rof = cvar("weapon_slot1_alt_rof");
weapon_slot1_alt_damage = cvar("weapon_slot1_alt_damage");
weapon_slot2_damage = cvar("weapon_slot2_damage");
weapon_slot2_alt_rof = cvar("weapon_slot2_alt_rof");
weapon_slot2_alt_damage = cvar("weapon_slot2_alt_damage");
weapon_slot3_rof = cvar("weapon_slot3_rof");
weapon_slot3_shots = cvar("weapon_slot3_shots");
weapon_slot3_alt_rof = cvar("weapon_slot3_alt_rof");
weapon_slot3_alt_shots = cvar("weapon_slot3_alt_shots");
weapon_slot4_damage = cvar("weapon_slot4_damage");
weapon_slot5_damage = cvar("weapon_slot5_damage");
weapon_slot5_alt_rof = cvar("weapon_slot5_alt_rof");
weapon_slot5_alt_damage = cvar("weapon_slot5_alt_damage");
weapon_slot6_rof = cvar("weapon_slot6_rof");
weapon_slot6_damage = cvar("weapon_slot6_damage");
weapon_slot7_rof = cvar("weapon_slot7_rof");
weapon_slot7_damage = cvar("weapon_slot7_damage");
weapon_slot8_range = cvar("weapon_slot8_range");
weapon_slot8_damage = cvar("weapon_slot8_damage");
//End value redefinitions
Note that we need localcmd to do this because the client hasn't spawned yet - so stuffcmd is
useless at this point and wouldn't work for *SERVER* configs anyway. Also double check your
mod dir to make sure you extracted darkplacestut.zip into it. There should be three files:
game.cfg, credits.txt and effects.pk3 (Yes, thats right - DP can load .pk3 files in place
of .paks. Not only is this more efficent but you can also name .pk3's anything you want
and DP will load them correctly. In case you have no clue on how to create pk3 files, its
just a normal .zip renamed to .pk3)
While we still have world.qc open we're going to do a little something to save time.
Paste the the following after the localcmd we just added.
Init_Log();
Guess what that does? It starts the basic logging system we're going to implement.
Paste this above worldspawn(), now.
void (string foo) Log_It =
{
local float file;
file = fopen ("log.txt", FILE_APPEND);
fputs(file, foo);
fclose(file);
};
void () Init_Log =
{
local string h;
local float file;
file = fopen ("log.txt", FILE_READ);
if (!file < 0)
{
file = fopen ("log.txt", FILE_APPEND);
fputs(file, "//Map: ");
h = world.model;
fputs(file, h);
fputs(file, "\n");
}
else
{
file = fopen ("log.txt", FILE_WRITE);
fputs(file, "//Log start\n");
fputs(file, "//Map: ");
h = world.model;
fputs(file, h);
fputs(file, "\n");
}
fclose(file);
};
While we are going to add logging functions to client.qc, keep in mind that you can call Log_It("Text")
pretty much anywhere and have it add to the log. Close world.qc.
Open misc.qc. Add this to the top:
void() FireAmbient;
void() flamespawn =
{
newmis = spawn();
setorigin(newmis, self.origin + self.dest + '0 0 -5');
setmodel(newmis, "progs/torchflamebase.spr32");
newmis.effects = EF_ADDITIVE;
newmis.scale = self.scale;
if (newmis.scale == 1)
makestatic(newmis);
newmis = spawn();
setorigin(newmis, self.origin + self.dest);
setmodel(newmis, "progs/torchflametop.spr32");
newmis.effects = EF_ADDITIVE;
newmis.scale = self.scale;
if (newmis.scale == 1)
makestatic(newmis);
if (self.model)
makestatic (self);
else
remove(self);
};
void() torchflame =
{
FireAmbient();
self.think = flamespawn;
self.nextthink = time + 0.1;
};
Find light_torch_small_walltorch(), light_flame_large_yellow(), light_flame_small_yellow() and light_flame_small_white(). Replace them all with this:
void() light_torch_small_walltorch =
{
self.dest = '0 0 16';
torchflame();
precache_model ("progs/flame.mdl");
setmodel (self, "progs/flame.mdl");
};
void() light_flame_large_yellow =
{
self.scale = 2;
self.dest = '0 0 16';
torchflame();
};
void() light_flame_small_yellow =
{
self.dest = '0 0 0';
torchflame();
};
void() light_flame_small_white =
{
self.dest = '0 0 0';
torchflame();
precache_model ("progs/flame2.mdl");
setmodel (self, "progs/flame2.mdl");
};
Open up weapons.qc. Add this to W_Precache().
precache_sound ("weapons/pulserifle.wav"); //Some random sound you don't really need
precache_model ("progs/x_explod.spr"); //New explosion sprite, from Half Life (eeep..)
precache_model ("progs/smokepuff.spr"); //Smoke puff sprite, used in a few things
precache_model ("progs/xhair.spr"); //Multi-frame sprite that has all our crosshairs
precache_model ("progs/muzzleflash.spr"); //First person muzzle flash
precache_model ("progs/muzzleflash2.spr"); //Third person muzzle flash
precache_model ("progs/v_pulse.mdl"); //A pulse rifle model
precache_model ("progs/laserdot.spr"); //Little laser dot suitable for many things
precache_model ("progs/hud_armor.spr"); //Part of our new hud used in client.qc
precache_model ("progs/hud_face.spr"); //
precache_model ("progs/hud_ammo.spr"); //
precache_model ("progs/torchflametop.spr32"); //New torch sprite provided by LordHavoc
precache_model ("progs/torchflamebase.spr32"); //
Now add this between W_Precache() and crandom().
void() x_explode1 = [0, x_explode2] {};
void() x_explode2 = [1, x_explode3] {};
void() x_explode3 = [2, x_explode4] {};
void() x_explode4 = [3, x_explode5] {};
void() x_explode5 = [4, x_explode6] {};
void() x_explode6 = [5, x_explode7] {};
void() x_explode7 = [6, x_explode8] {};
void() x_explode8 = [7, x_explode9] {};
void() x_explode9 = [8, x_explode10] {};
void() x_explode10 = [9, x_explode11] {};
void() x_explode11 = [10, x_explode12] {};
void() x_explode12 = [11, x_explode13] {};
void() x_explode13 = [12, x_explode14] {};
void() x_explode14 = [13, x_explode15] {};
void() x_explode15 = [14, SUB_Remove] {};
void() smokepuff1 = [0, smokepuff2] {};
void() smokepuff2 = [1, smokepuff3] {};
void() smokepuff3 = [2, smokepuff4] {};
void() smokepuff4 = [3, smokepuff5] {};
void() smokepuff5 = [4, smokepuff6] {};
void() smokepuff6 = [5, smokepuff7] {};
void() smokepuff7 = [6, smokepuff8] {};
void() smokepuff8 = [7, smokepuff9] {};
void() smokepuff9 = [8, smokepuff10] {};
void() smokepuff10 = [9, smokepuff11] {};
void() smokepuff11 = [10, smokepuff12] {};
void() smokepuff12 = [11, smokepuff13] {};
void() smokepuff13 = [12, smokepuff14] {};
void() smokepuff14 = [13, smokepuff15] {};
void() smokepuff15 = [14, smokepuff16] {};
void() smokepuff16 = [15, smokepuff17] {};
void() smokepuff17 = [16, smokepuff18] {};
void() smokepuff18 = [17, smokepuff19] {};
void() smokepuff19 = [18, SUB_Remove] {};
void(vector org) SpawnSmoke = //Spawns a puff of smoke
{
newmis = spawn();
newmis.movetype = MOVETYPE_NONE;
newmis.classname = "smoke";
newmis.touch = SUB_Null;
newmis.velocity = '0 0 0'; //Note that you could mess with the movetype and give it a z velocity for rising smoke..
newmis.effects = newmis.effects | EF_ADDITIVE;
setmodel(newmis, "progs/smokepuff.spr");
newmis.solid = SOLID_NOT;
setsize(newmis, '0 0 0', '0 0 0');
setorigin(newmis, org);
newmis.think = smokepuff1;
newmis.nextthink = time + 0.1;
};
void () LaserThink = //Updates the laserdot position - this should be done with velocity
{ // instead of changing the origin, but I'm tired..
local vector org;
makevectors(self.owner.v_angle);
org = self.owner.origin + v_up * 18;
traceline (org, org + v_forward * 4096, FALSE, self); //Should be higher
if (self.owner.weapon != IT_ROCKET_LAUNCHER) //Laser only works with our l'cher
{
self.owner.laser_active = 0; //Tells Quake our laser is off
remove(self);
}
if (trace_fraction == 1.0)
{
// Move sight inside player if something strange happens
setorigin(self, self.owner.origin );
return;
}
self.angles = vectoangles(v_forward); //Make sure the sprite looks right
setorigin(self, trace_endpos - v_forward*4); //Also make sure it doesn't end up in a wall
self.nextthink = time + 0.05;
};
void () SpawnLaser = //Spawns a rocket guidance laser for a player
{
local entity foo;
self.laser_active = 1; //Laser is active
foo = spawn ();
foo.owner = self;
foo.movetype = MOVETYPE_NOCLIP;
foo.solid = SOLID_NOT;
setmodel (foo, "progs/laserdot.spr");
foo.classname = "sight";
foo.effects = foo.effects | EF_ADDITIVE;
setorigin( foo, self.origin );
foo.think = LaserThink;
foo.nextthink = time + 0.05; //Change this as you please
};
Basicly from top to bottom you just added:
* Two sprite animations
* A function to spawn a puff of smoke somewhere - you must provide a vector
* Two more functions for our laser guided rocket
Go down to W_FireAxe() and replace it with this.
void() W_FireAxe =
{
local vector source;
local vector org;
makevectors (self.v_angle);
source = self.origin + '0 0 16';
traceline (source, source + v_forward*64, FALSE, self);
if (trace_fraction == 1.0)
return;
org = trace_endpos - v_forward*4;
if (trace_ent.takedamage)
{
local float axedmg;
axedmg = weapon_slot1_damage; //Server defined damage
trace_ent.axhitme = 1;
if (!trace_ent.solid == SOLID_BSP) //Fixes a minor issue where doors would 'bleed'
te_blood(org, '0 0 0', axedmg/2);
if (trace_ent.solid == SOLID_BSP && self.super_damage_finished)
te_gunshotquad(org);
else te_gunshot(org);
T_Damage (trace_ent, self, self, axedmg);
}
else
{ // hit wall
sound (self, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM);
if (self.super_damage_finished)
te_gunshotquad(org);
else te_gunshot(org);
}
};
void() W_AltFireAxe = //Alternate fire routine for the axe
{
local vector source;
local vector org;
makevectors (self.v_angle);
source = self.origin + '0 0 16';
traceline (source, source + v_forward*128, FALSE, self); //Twice the range
if (trace_fraction == 1.0)
return;
org = trace_endpos - v_forward*4;
if (trace_ent.takedamage)
{
local float axedmg;
axedmg = weapon_slot1_alt_damage;
trace_ent.axhitme = 1;
if (!trace_ent.solid == SOLID_BSP)
te_blood(org, '0 0 0', axedmg/2);
if (trace_ent.solid == SOLID_BSP && self.super_damage_finished)
te_gunshotquad(org);
else te_gunshot(org);
T_Damage (trace_ent, self, self, axedmg);
}
else
{ // hit wall
sound (self, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM);
if (self.super_damage_finished)
te_gunshotquad(org);
else te_gunshot(org);
}
};
Hopefully you should understand what we just added. If you don't
understand it, don't panic! We just messed around with the axe firing
function to make use of some new stuff we defined in defs.qc and also
added a alternate firing function for later on.
Go down to spawn_touchblood(). Find this line:
SpawnBlood (self.origin + vel*0.01, vel, damage);
Replace it with this:
te_blood(self.origin + vel*0.01, vel, damage);
Go down to TraceAttack(). Replace it with this:
void(float damage, vector dir) TraceAttack =
{
local vector vel, org;
vel = normalize(dir + v_up*crandom() + v_right*crandom());
vel = vel + 2*trace_plane_normal;
vel = vel * 200;
org = trace_endpos - dir*4;
if (trace_ent.takedamage)
{
if (!trace_ent.solid == SOLID_BSP)
te_blood(org, vel*0.2, damage);
if (trace_ent.solid == SOLID_BSP && self.super_damage_finished)
te_gunshotquad(org);
else te_gunshot(org);
AddMultiDamage (trace_ent, damage);
}
else
{
if (self.super_damage_finished)
te_gunshotquad(org);
else te_gunshot(org);
}
};
Go down to FireBullets(). Replace! Replace! REPLACE!
void(float shotcount, vector dir, vector spread) FireBullets =
{
local vector direction, src;
local float damg; //Addition
makevectors(self.v_angle);
src = self.origin + v_forward*10;
src_z = self.absmin_z + self.size_z * 0.7;
damg = weapon_slot2_damage; //Addition
ClearMultiDamage ();
while (shotcount > 0)
{
direction = dir + crandom() * spread_x * v_right + crandom() * spread_y * v_up;
traceline (src, src + direction * 2048, FALSE, self);
if (trace_fraction != 1.0)
TraceAttack (damg, direction);
shotcount = shotcount - 1;
}
ApplyMultiDamage ();
};
Go down to W_FireShotgun(). Replace it with this:
void() SpawnMuzzleFlash = //Spawns a first person muzzleflash
{
newmis = spawn();
newmis.movetype = MOVETYPE_NONE;
newmis.solid = SOLID_NOT;
newmis.viewmodelforclient = self;
newmis.owner = self;
newmis.classname = "flash";
newmis.effects = newmis.effects + EF_ADDITIVE;
setmodel(newmis, "progs/muzzleflash.spr");
setsize(newmis, '0 0 0', '0 0 0');
setorigin(newmis, '64 -16 -28'); //Don't worry about this showing up in weird places on higher resolutions -
// all viewmodels are scaled to fit the res perfectly
newmis.think = SUB_Remove;
newmis.nextthink = time + 0.07; //Adjust this as you wish
};
void(vector org) SpawnMuzzleFlash2 =
{
newmis = spawn();
newmis.movetype = MOVETYPE_NONE;
newmis.classname = "flash";
newmis.touch = SUB_Null;
newmis.effects = newmis.effects + EF_ADDITIVE;
setmodel(newmis, "progs/muzzleflash2.spr");
newmis.solid = SOLID_NOT;
setsize(newmis, '0 0 0', '0 0 0');
setorigin(newmis, org);
newmis.exteriormodeltoclient = self;
newmis.think = SUB_Remove;
newmis.angles = self.angles;
newmis.nextthink = time + 0.07;
};
void() W_FireShotgun =
{
local vector dir;
if (self.ammo_shells < 1)
{
self.weapon = W_BestWeapon ();
W_SetCurrentAmmo ();
return;
}
sound (self, CHAN_WEAPON, "weapons/pulserifle.wav", 1, ATTN_NORM);
self.punchangle_x = -2;
self.currentammo = self.ammo_shells = self.ammo_shells - 1;
dir = aim (self, 100000);
FireBullets (3, dir, '0.1 0.1 0');
SpawnMuzzleFlash();
makevectors (self.angles); //Hack, needed for 3rd person muzzleflashes to look right
SpawnMuzzleFlash2(self.origin + v_forward*16 + v_right * 8 + '0 0 16');
};
void() AltGrenadeExplode =
{
local float damg;
if (pointcontents(self.origin) == CONTENT_SKY)
{
remove(self);
return;
}
damg = weapon_slot2_alt_damage;
T_RadiusDamage (self, self.owner, damg, world);
te_spark(self.origin, '0 0 0', 150); //For bonus points, make the amount of sparks spawned random and give
// them a random velocity
te_plasmaburn(self.origin);
self.movetype = MOVETYPE_NONE;
sound (self, CHAN_WEAPON, "weapons/r_exp3.wav", 1, ATTN_NORM); //TE_EXPLOSION automagickly plays this sound
self.velocity = '0 0 0';
self.touch = SUB_Null;
self.effects = self.effects - (self.effects & EF_FLAME);
self.effects = self.effects + EF_ADDITIVE;
setmodel (self, "progs/x_explod.spr");
self.solid = SOLID_NOT;
x_explode1 ();
};
void() W_AltFireShotgun =
{
local entity missile;
self.effects = self.effects | EF_MUZZLEFLASH;
self.ammo_rockets = self.ammo_rockets - 1;
sound (self, CHAN_WEAPON, "weapons/grenade.wav", 1, ATTN_NORM);
self.punchangle_x = -2;
missile = spawn ();
missile.owner = self;
missile.movetype = MOVETYPE_BOUNCE;
missile.solid = SOLID_BBOX;
missile.classname = "grenade";
makevectors (self.v_angle);
if (self.v_angle_x)
missile.velocity = v_forward*1200 + v_up * 50 + crandom()*v_right*10 + crandom()*v_up*10;
else
{
missile.velocity = aim(self, 10000);
missile.velocity = missile.velocity * 1200;
missile.velocity_z = 50;
}
missile.avelocity = '0 0 0';
missile.angles = vectoangles(missile.velocity);
missile.touch = AltGrenadeExplode;
missile.effects = missile.effects + EF_FLAME;
missile.nextthink = time + 5;
missile.think = AltGrenadeExplode;
setmodel (missile, "progs/grenade.mdl");
setsize (missile, '0 0 0', '0 0 0');
setorigin (missile, self.origin + v_forward * 8 + v_right * 4 + '0 0 12');
};
Go down to W_FireSuperShotgun(). You know the drill.
void() W_FireSuperShotgun =
{
local vector dir;
local float shots;
shots = weapon_slot3_shots;
sound (self ,CHAN_WEAPON, "weapons/guncock.wav", 1, ATTN_NORM);
self.punchangle_x = -2;
self.currentammo = self.ammo_shells = self.ammo_shells - 1;
dir = aim (self, 100000);
FireBullets (shots, dir, '0.14 0.08 0');
SpawnSmoke(self.origin + v_forward * 16 + '0 0 16');
};
void() W_AltFireSuperShotgun =
{
local vector dir;
local float shots;
if (self.currentammo == 1)
{
W_FireSuperShotgun ();
return;
}
shots = weapon_slot3_alt_shots;
sound (self ,CHAN_WEAPON, "weapons/shotgn2.wav", 1, ATTN_NORM);
self.punchangle_x = -6;
self.currentammo = self.ammo_shells = self.ammo_shells - 2;
dir = aim (self, 100000);
FireBullets (shots, dir, '0.14 0.08 0');
SpawnSmoke(self.origin + v_forward * 16 + v_right * 4 + '0 0 16');
SpawnSmoke(self.origin + v_forward * 16 + v_right * -4 + '0 0 16'); //I'm so clever
};
Find BecomeExplosion(), T_MissileTouch() and W_FireRocket().
Replace all three functions with this.
void() BecomeExplosion =
{
te_spark(self.origin, '0 0 0', 150);
te_plasmaburn(self.origin);
self.movetype = MOVETYPE_NONE;
sound (self, CHAN_WEAPON, "weapons/r_exp3.wav", 1, ATTN_NORM);
self.velocity = '0 0 0';
self.touch = SUB_Null;
self.effects = self.effects + EF_ADDITIVE;
setmodel (self, "progs/x_explod.spr");
self.solid = SOLID_NOT;
x_explode1 ();
};
void() T_MissileTouch =
{
local float damg;
if (other == self.owner)
return; // don't explode on owner, because that would suck
if (pointcontents(self.origin) == CONTENT_SKY)
{
remove(self);
return;
}
self.glow_size = 0; //Else our fairly long sprite animation would look silly
damg = weapon_slot7_damage;
if (other.health)
{
if (other.classname == "monster_shambler")
damg = damg * 2; // I'm such a radical
T_Damage (other, self, self.owner, damg );
}
T_RadiusDamage (self, self.owner, damg, other);
self.origin = self.origin - 8 * normalize(self.velocity);
BecomeExplosion ();
};
entity() FindDot =
{
local entity e, selected;
if (self.owner.laser_active == 1)
{
e = find( world, classname, "sight");
while (e)
{
if (e.classname == "sight" && e.owner == self.owner)
{
selected = e;
return e;
}
e = nextent(e);
}
}
return selected;
};
void() MissileThink =
{
local vector dir, olddir;
local float turnrate;
turnrate = 0.1; //Change as you wish
if ( !(self.enemy) || (self.enemy == world) )
self.enemy = FindDot();
if (self.enemy != world)
{
olddir = normalize(self.velocity);
dir = normalize(self.enemy.origin - self.origin);
if (olddir_x - dir_x > turnrate)
dir_x = olddir_x - turnrate;
if (olddir_x - dir_x < -1 * turnrate)
dir_x = olddir_x + turnrate;
if (olddir_y - dir_y > turnrate)
dir_y = olddir_y - turnrate;
if (olddir_y - dir_y < -1 * turnrate)
dir_y = olddir_y + turnrate;
if (olddir_z - dir_z > turnrate)
dir_z = olddir_z - turnrate;
if (olddir_z - dir_z < -1 * turnrate)
dir_z = olddir_z + turnrate;
self.velocity = dir * 1500;
self.angles = vectoangles(self.velocity);
}
self.nextthink = time + 0.1;
self.think = MissileThink;
};
void() RocketFly =
{
self.glow_size = 100;
self.glow_color = 254;
self.glow_trail = 1;
self.velocity = self.velocity * 5;
self.nextthink = time + 0.05;
self.think = MissileThink;
};
void() W_FireRocket =
{
local entity missile, mpuff;
self.currentammo = self.ammo_rockets = self.ammo_rockets - 1;
sound (self, CHAN_WEAPON, "weapons/sgun1.wav", 1, ATTN_NORM);
self.punchangle_x = -2;
missile = spawn ();
missile.owner = self;
missile.movetype = MOVETYPE_FLYMISSILE;
missile.solid = SOLID_BBOX;
missile.classname = "missile";
makevectors (self.v_angle);
missile.velocity = aim(self, 1000);
missile.velocity = missile.velocity * 300;
missile.angles = vectoangles(missile.velocity);
missile.touch = T_MissileTouch;
missile.nextthink = time + 0.5;
missile.think = RocketFly;
setmodel (missile, "progs/missile.mdl");
setsize (missile, '0 0 0', '0 0 0');
setorigin (missile, self.origin + v_forward*8 + '0 0 16');
};
Go down to LightningDamage() and W_FireLightning(). Replace them with this:
void(vector p1, vector p2, entity from, float damage) LightningDamage =
{
local entity e1, e2;
local vector f;
f = p2 - p1;
normalize (f);
f_x = 0 - f_y;
f_y = f_x;
f_z = 0;
f = f*16;
e1 = e2 = world;
traceline (p1, p2, FALSE, self);
if (trace_ent.takedamage)
{
te_blood(trace_endpos, '0 0 100', damage*4);
T_Damage (trace_ent, from, from, damage);
if (self.classname == "player")
{
if (other.classname == "player")
trace_ent.velocity_z = trace_ent.velocity_z + 400;
}
}
e1 = trace_ent;
traceline (p1 + f, p2 + f, FALSE, self);
if (trace_ent != e1 && trace_ent.takedamage)
{
te_blood(trace_endpos, '0 0 100', damage*4);
T_Damage (trace_ent, from, from, damage);
}
e2 = trace_ent;
traceline (p1 - f, p2 - f, FALSE, self);
if (trace_ent != e1 && trace_ent != e2 && trace_ent.takedamage)
{
te_blood(trace_endpos, '0 0 100', damage*4);
T_Damage (trace_ent, from, from, damage);
}
};
void() W_FireLightning =
{
local vector org;
local float cells;
if (self.ammo_cells < 1)
{
self.weapon = W_BestWeapon ();
W_SetCurrentAmmo ();
return;
}
if (self.waterlevel > 1)
{
cells = self.ammo_cells;
self.ammo_cells = 0;
W_SetCurrentAmmo ();
T_RadiusDamage (self, self, (weapon_slot8_damage +5)*cells, world);
return;
}
if (self.t_width < time)
{
sound (self, CHAN_WEAPON, "weapons/lhit.wav", 1, ATTN_NORM);
self.t_width = time + 0.6;
}
self.punchangle_x = -2;
self.currentammo = self.ammo_cells = self.ammo_cells - 1;
org = self.origin + '0 0 16';
traceline (org, org + v_forward*weapon_slot8_range, TRUE, self);
te_lightning2 (self, org, trace_endpos);
LightningDamage (self.origin, trace_endpos + v_forward*4, self, weapon_slot8_damage);
};
Now find GrenadeExplode(), GrenadeTouch() and W_FireGrenade() and replace it all with this:
void() GrenadeExplode =
{
local float damg;
if (pointcontents(self.origin) == CONTENT_SKY)
{
remove(self);
return;
}
damg = weapon_slot6_damage;
T_RadiusDamage (self, self.owner, damg, world);
te_spark(self.origin, '0 0 0', 150);
te_plasmaburn(self.origin);
self.movetype = MOVETYPE_NONE;
sound (self, CHAN_WEAPON, "weapons/r_exp3.wav", 1, ATTN_NORM);
self.velocity = '0 0 0';
self.touch = SUB_Null;
self.effects = self.effects - (self.effects & EF_FLAME);
self.effects = self.effects + EF_ADDITIVE;
setmodel (self, "progs/x_explod.spr");
self.solid = SOLID_NOT;
x_explode1 ();
};
void() GrenadeTouch =
{
if (other == self.owner)
return; // don't explode on owner
if (other.takedamage == DAMAGE_AIM)
{
GrenadeExplode();
return;
}
sound (self, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NORM); // bounce sound
if (self.velocity == '0 0 0')
self.avelocity = '0 0 0';
};
void() W_FireGrenade =
{
local entity missile, mpuff;
self.currentammo = self.ammo_rockets = self.ammo_rockets - 1;
sound (self, CHAN_WEAPON, "weapons/grenade.wav", 1, ATTN_NORM);
self.punchangle_x = -2;
missile = spawn ();
missile.owner = self;
missile.movetype = MOVETYPE_BOUNCE;
missile.solid = SOLID_BBOX;
missile.classname = "grenade";
makevectors (self.v_angle);
if (self.v_angle_x)
missile.velocity = v_forward * 600 + v_up * 200 + crandom() * v_right * 10 + crandom() * v_up * 10;
else
{
missile.velocity = aim(self, 10000);
missile.velocity = missile.velocity * 600;
missile.velocity_z = 200;
}
missile.avelocity = '300 300 300';
missile.angles = vectoangles(missile.velocity);
missile.touch = GrenadeTouch;
missile.nextthink = time + 2.5;
missile.think = GrenadeExplode;
missile.effects = missile.effects | EF_FLAME;
setmodel (missile, "progs/grenade.mdl");
setsize (missile, '0 0 0', '0 0 0');
setorigin (missile, self.origin);
SpawnSmoke(self.origin + v_forward * 16 + '0 0 16');
};
void() W_AltFireGrenade =
{
local entity missile, mpuff;
self.currentammo = self.ammo_rockets = self.ammo_rockets - 1;
sound (self, CHAN_WEAPON, "weapons/grenade.wav", 1, ATTN_NORM);
self.punchangle_x = -2;
missile = spawn ();
missile.owner = self;
missile.movetype = MOVETYPE_BOUNCE;
missile.solid = SOLID_BBOX;
missile.classname = "grenade";
makevectors (self.v_angle);
if (self.v_angle_x)
missile.velocity = v_forward * 600 + v_up * 200 + crandom() * v_right * 10 + crandom() * v_up * 10;
else
{
missile.velocity = aim(self, 10000);
missile.velocity = missile.velocity * 600;
missile.velocity_z = 200;
}
missile.avelocity = '300 300 300';
missile.angles = vectoangles(missile.velocity);
missile.touch = GrenadeExplode;
missile.nextthink = time + 2.5;
missile.think = GrenadeExplode;
missile.effects = missile.effects | EF_FLAME;
setmodel (missile, "progs/grenade.mdl");
setsize (missile, '0 0 0', '0 0 0');
setorigin (missile, self.origin);
SpawnSmoke(self.origin + v_forward * 16 + '0 0 16');
};
Find W_FireSuperSpikes(), W_FireSpikes(), spike_touch() and superspike_touch() - replace that ENTIRE block of code with this:
void() W_FireSuperSpikes =
{
local vector dir;
local entity old;
sound (self, CHAN_WEAPON, "weapons/spike2.wav", 1, ATTN_NORM);
self.attack_finished = time + 0.2;
self.currentammo = self.ammo_nails = self.ammo_nails - 1;
dir = aim (self, 1000);
launch_spike (self.origin + '0 0 16', dir);
newmis.touch = superspike_touch;
setmodel (newmis, "progs/s_spike.mdl");
setsize (newmis, VEC_ORIGIN, VEC_ORIGIN);
newmis.velocity = dir * 2000;
newmis.glow_color = 208;
newmis.glow_size = 60;
};
void(float ox) W_FireSpikes =
{
local vector dir, foo;
local float foo2;
makevectors (self.v_angle);
if (self.ammo_nails > 0 && self.weapon == IT_SUPER_NAILGUN)
{
W_FireSuperSpikes ();
return;
}
if (self.ammo_nails < 1)
{
self.weapon = W_BestWeapon ();
W_SetCurrentAmmo ();
return;
}
foo = getlight(self.origin); //getlight returns the RGB value of light at a point as a vector - 'R G B'
foo2 = (foo_x + foo_y + foo_z) * 0.333;
sound (self, CHAN_WEAPON, "weapons/rocket1i.wav", 1, ATTN_NORM);
self.attack_finished = time + 0.2;
self.currentammo = self.ammo_nails = self.ammo_nails - 1;
dir = aim (self, 1000);
launch_spike (self.origin + '0 0 16' + v_right * ox, dir);
self.punchangle_x = -2;
newmis.think = SUB_Remove;
newmis.nextthink = time + 5;
if (foo2 > 50) //Mess with this
newmis.velocity = dir * 2000;
else if (foo2 > 100)
newmis.velocity = dir * 1500 + v_up * (crandom() * 25) + v_right * (crandom() * 25);
else if (foo2 > 150)
newmis.velocity = dir * 1000 + v_up * (crandom() * 50) + v_right * (crandom() * 50);
else newmis.velocity = dir * 500 + v_up * (crandom() * 100) + v_right * (crandom() * 100);
};
void() plasma_explode =
{
local float damg;
local vector org;
if (other == self.owner)
return;
if (pointcontents(self.origin) == CONTENT_SKY)
{
remove(self);
return;
}
damg = weapon_slot5_alt_damage;
if (other.health)
{
T_Damage (other, self, self.owner, damg );
}
T_RadiusDamage (self, self.owner, damg, other);
te_explosionrgb (self.origin, '0 0 0.9'); //RGB on a 0-255 scale converted to a 0-1 scale
remove(self);
};
void() W_AltFireSuperSpikes =
{
local vector dir;
local entity old;
sound (self, CHAN_WEAPON, "weapons/lstart.wav", 1, ATTN_NORM);
self.effects = self.effects | EF_MUZZLEFLASH;
self.ammo_cells = self.ammo_cells - 1;
dir = aim (self, 1000);
launch_spike (self.origin + '0 0 16', dir);
newmis.touch = plasma_explode;
setmodel (newmis, "progs/s_spike.mdl");
setsize (newmis, VEC_ORIGIN, VEC_ORIGIN);
newmis.velocity = dir * 1500;
newmis.glow_color = 208;
newmis.glow_size = 240;
};
.float hit_z; //I'm the one writing this tut and I STILL don't know what this does..
void() spike_touch =
{
local float rand, damg;
if (other == self.owner)
return;
if (other.solid == SOLID_TRIGGER)
return; // trigger field, do nothing
if (pointcontents(self.origin) == CONTENT_SKY)
{
remove(self);
return;
}
// hit something that bleeds
if (other.takedamage)
{
if (!trace_ent.solid == SOLID_BSP)
spawn_touchblood (damg);
if (trace_ent.solid == SOLID_BSP && self.super_damage_finished)
te_gunshotquad(self.origin);
else te_gunshot(self.origin);
T_Damage (other, self, self.owner, weapon_slot4_damage);
}
else
{
if (self.owner.super_damage_finished)
te_spikequad(self.origin);
else te_spike(self.origin);
}
remove(self);
};
void() superspike_touch =
{
local float rand, damg;
if (other == self.owner)
return;
if (other.solid == SOLID_TRIGGER)
return; // trigger field, do nothing
if (pointcontents(self.origin) == CONTENT_SKY)
{
remove(self);
return;
}
// hit something that bleeds
if (other.health)
T_Damage (other, self, self.owner, weapon_slot5_damage);
T_RadiusDamage (self, self.owner, weapon_slot5_damage, other);
te_plasmaburn(self.origin);
remove(self);
};
Quit groaning, we're almost done with weapons.qc.
Go down to W_SetCurrentAmmo(). Find this line of code:
self.weaponmodel = "progs/v_shot.mdl";
And replace it with this:
self.weaponmodel = "progs/v_pulse.mdl";
Add these lines above W_Attack():
void() player_altplasma1;
void() player_aaxe1;
void() player_aaxeb1;
void() player_aaxec1;
void() player_aaxed1;
void() player_pulse1;
void() player_pulsegren1;
And replace W_Attack() with this:
void() W_Attack =
{
local float r;
if (!W_CheckNoAmmo ())
return;
makevectors (self.v_angle);
self.show_hostile = time + 1;
if (self.weapon == IT_AXE)
{
sound (self, CHAN_WEAPON, "weapons/ax1.wav", 1, ATTN_NORM);
r = random();
if (r < 0.25)
player_axe1 ();
else if (r<0.5)
player_axeb1 ();
else if (r<0.75)
player_axec1 ();
else
player_axed1 ();
self.attack_finished = time + weapon_slot1_rof;
}
else if (self.weapon == IT_SHOTGUN)
{
player_pulse1 ();
}
else if (self.weapon == IT_SUPER_SHOTGUN)
{
player_shot1 ();
W_FireSuperShotgun ();
self.attack_finished = time + weapon_slot3_rof;
}
else if (self.weapon == IT_NAILGUN)
{
player_nail1 ();
}
else if (self.weapon == IT_SUPER_NAILGUN)
{
player_nail1 ();
}
else if (self.weapon == IT_GRENADE_LAUNCHER)
{
player_rocket1();
W_FireGrenade();
self.attack_finished = time + weapon_slot6_rof;
}
else if (self.weapon == IT_ROCKET_LAUNCHER)
{
player_rocket1();
W_FireRocket();
self.attack_finished = time + weapon_slot7_rof;
}
else if (self.weapon == IT_LIGHTNING)
{
player_light1();
self.attack_finished = time + 0.1;
sound (self, CHAN_AUTO, "weapons/lstart.wav", 1, ATTN_NORM);
}
};
void() W_AltAttack =
{
local float r;
if (!W_CheckNoAmmo ())
return;
makevectors (self.v_angle);
self.show_hostile = time + 1;
if (self.weapon == IT_AXE)
{
sound (self, CHAN_WEAPON, "weapons/ax1.wav", 1, ATTN_NORM);
r = random();
if (r < 0.25)
player_aaxe1 ();
else if (r<0.5)
player_aaxeb1 ();
else if (r<0.75)
player_aaxec1 ();
else
player_aaxed1 ();
self.attack_finished = time + weapon_slot1_alt_rof;
}
else if (self.weapon == IT_SHOTGUN && self.ammo_rockets > 0)
{
player_pulsegren1();
W_AltFireShotgun();
self.attack_finished = time + weapon_slot1_alt_rof;
}
else if (self.weapon == IT_SUPER_SHOTGUN)
{
player_shot1 ();
W_AltFireSuperShotgun ();
self.attack_finished = time + weapon_slot3_alt_rof;
}
else if (self.weapon == IT_NAILGUN)
{
player_nail1 ();
}
else if (self.weapon == IT_SUPER_NAILGUN && self.ammo_cells > 0)
{
player_altplasma1();
W_AltFireSuperSpikes();
self.attack_finished = time + weapon_slot5_alt_rof;
}
else if (self.weapon == IT_GRENADE_LAUNCHER)
{
player_rocket1();
W_AltFireGrenade();
self.attack_finished = time + weapon_slot6_rof;
}
else if (self.weapon == IT_ROCKET_LAUNCHER)
{
self.attack_finished = time + weapon_slot7_rof; //Don't ask
if (self.laser_active != 1)
{
SpawnLaser();
}
else
{
self.laser_active = 0;
// Find the sight entity and remove it
local entity e;
e = find( world, classname, "sight");
while (e)
{
if (e.classname == "sight" && e.owner == self)
{
remove(e);
return;
}
e = nextent(e);
}
}
}
else if (self.weapon == IT_LIGHTNING)
{
player_light1();
self.attack_finished = time + 0.1;
sound (self, CHAN_AUTO, "weapons/lstart.wav", 1, ATTN_NORM);
}
};
Go down to W_WeaponFrame(). Replace it with this:
void() W_WeaponFrame =
{
if (time < self.attack_finished)
return;
if (self.impulse) //Fixing id's bugs!
ImpulseCommands ();
// check for attack
if (self.button0)
{
SuperDamageSound ();
W_Attack ();
}
if (self.button3)
{
SuperDamageSound ();
W_AltAttack ();
}
};
Relax now, we're done with weapons.qc. Open player.qc.
What we're going to do here:
* Add animations
* Add some other stuff
Go down to player_nail1 & 2 and player_light1 & 2. Find the line that reads:
if (!self.button0)
And replace it with this:
if (!self.button0 && !self.button3)
Do me a favor, add these animation sequences to the bottom of player.qc now.
void() player_pulse1 =[$nailatt1, player_pulse2 ]
{
self.effects = self.effects | EF_MUZZLEFLASH;
if (!self.button0)
{player_run ();return;}
self.weaponframe = self.weaponframe + 1;
if (self.weaponframe == 10)
self.weaponframe = 1;
SuperDamageSound();
W_FireShotgun ();
self.attack_finished = time + 0.2;
};
void() player_pulse2 =[$nailatt2, player_pulse1 ]
{
self.effects = self.effects | EF_MUZZLEFLASH;
if (!self.button0)
{player_run ();return;}
self.weaponframe = self.weaponframe + 1;
if (self.weaponframe == 10)
self.weaponframe = 1;
SuperDamageSound();
W_FireShotgun ();
self.attack_finished = time + 0.2;
};
void() player_aaxe1 = [$axatt1, player_aaxe2 ] {self.weaponframe=1;};
void() player_aaxe2 = [$axatt2, player_aaxe3 ] {self.weaponframe=2;};
void() player_aaxe3 = [$axatt3, player_aaxe4 ] {self.weaponframe=3;W_AltFireAxe();};
void() player_aaxe4 = [$axatt4, player_run ] {self.weaponframe=4;};
void() player_aaxeb1 = [$axattb1, player_aaxeb2] {self.weaponframe=5;};
void() player_aaxeb2 = [$axattb2, player_aaxeb3] {self.weaponframe=6;};
void() player_aaxeb3 = [$axattb3, player_aaxeb4] {self.weaponframe=7;W_AltFireAxe();};
void() player_aaxeb4 = [$axattb4, player_run ] {self.weaponframe=8;};
void() player_aaxec1 = [$axattc1, player_aaxec2] {self.weaponframe=1;};
void() player_aaxec2 = [$axattc2, player_aaxec3] {self.weaponframe=2;};
void() player_aaxec3 = [$axattc3, player_aaxec4] {self.weaponframe=3;W_AltFireAxe();};
void() player_aaxec4 = [$axattc4, player_run ] {self.weaponframe=4;};
void() player_aaxed1 = [$axattd1, player_aaxed2] {self.weaponframe=5;};
void() player_aaxed2 = [$axattd2, player_aaxed3] {self.weaponframe=6;};
void() player_aaxed3 = [$axattd3, player_aaxed4] {self.weaponframe=7;W_AltFireAxe();};
void() player_aaxed4 = [$axattd4, player_run ] {self.weaponframe=8;};
void() player_pulsegren1 =[$rockatt1, player_pulsegren2 ] {self.weaponframe=11;};
void() player_pulsegren2 =[$rockatt2, player_pulsegren3 ] {self.weaponframe=12;};
void() player_pulsegren3 =[$rockatt3, player_pulsegren4 ] {self.weaponframe=13;};
void() player_pulsegren4 =[$rockatt4, player_pulsegren5 ] {self.weaponframe=14;};
void() player_pulsegren5 =[$rockatt5, player_pulsegren6 ] {self.weaponframe=0;};
void() player_pulsegren6 =[$rockatt6, player_run ] {self.weaponframe=0;};
void() player_altplasma1 =[$rockatt1, player_altplasma2 ] {self.weaponframe=9;};
void() player_altplasma2 =[$rockatt2, player_altplasma3 ] {self.weaponframe=10;};
void() player_altplasma3 =[$rockatt3, player_altplasma4 ] {self.weaponframe=11;};
void() player_altplasma4 =[$rockatt4, player_altplasma5 ] {self.weaponframe=0;};
void() player_altplasma5 =[$rockatt5, player_altplasma6 ] {self.weaponframe=0;};
void() player_altplasma6 =[$rockatt6, player_run ] {self.weaponframe=0;};
Close player.qc. Open client.qc. Add this above PutClientInServer():
void() xhair_think =
{
if (self.owner.health <= 0)
remove(self);
if (self.owner.weapon == IT_AXE)
self.frame = 0;
if (self.owner.weapon == IT_SHOTGUN)
self.frame = 1;
if (self.owner.weapon == IT_SUPER_SHOTGUN)
self.frame = 2;
if (self.owner.weapon == IT_NAILGUN)
self.frame = 2;
if (self.owner.weapon == IT_SUPER_NAILGUN)
self.frame = 3;
if (self.owner.weapon == IT_GRENADE_LAUNCHER)
self.frame = 1;
if (self.owner.weapon == IT_ROCKET_LAUNCHER)
self.frame = 2;
if (self.owner.weapon == IT_LIGHTNING)
self.frame = 2;
self.nextthink = 0.1;
};
void() hud_face_think =
{
if (self.owner.health <= 0)
remove(self);
if (self.owner.health > 79)
self.frame = 0;
else if (self.owner.health > 59)
self.frame = 1;
else if (self.owner.health > 39)
self.frame = 2;
else if (self.owner.health > 19)
self.frame = 3;
else self.frame = 4;
self.nextthink = 0.1;
};
void() hud_armor_think =
{
if (self.owner.health <= 0)
remove(self);
if (self.owner.armortype == 0) //None
self.frame = 3;
else if (self.owner.armortype == 0.3) //Green
self.frame = 0;
else if (self.owner.armortype == 0.6) //Yellow
self.frame = 1;
else if (self.owner.armortype == 0.8) //Red
self.frame = 2;
self.nextthink = 0.1;
};
void() hud_ammo_think =
{
if (self.owner.health <= 0)
remove(self);
if (self.owner.weapon == IT_SHOTGUN)
{
if (self.owner.currentammo < 25 && self.owner.currentammo != 0)
self.frame = 4;
else if (self.owner.currentammo == 0)
self.frame = 9;
else self.frame = 0;
}
else if (self.owner.weapon == IT_SUPER_SHOTGUN)
{
if (self.owner.currentammo < 10 && self.owner.currentammo != 0)
self.frame = 4;
else if (self.owner.currentammo == 0)
self.frame = 9;
else self.frame = 0;
}
else if (self.owner.weapon == IT_NAILGUN)
{
if (self.owner.currentammo < 40 && self.owner.currentammo != 0)
self.frame = 5;
else if (self.owner.currentammo == 0)
self.frame = 10;
else self.frame = 1;
}
else if (self.owner.weapon == IT_SUPER_NAILGUN)
{
if (self.owner.currentammo < 40 && self.owner.currentammo != 0)
self.frame = 5;
else if (self.owner.currentammo == 0)
self.frame = 10;
else self.frame = 1;
}
else if (self.owner.weapon == IT_GRENADE_LAUNCHER)
{
if (self.owner.currentammo < 20 && self.owner.currentammo != 0)
self.frame = 6;
else if (self.owner.currentammo == 0)
self.frame = 11;
else self.frame = 2;
}
else if (self.owner.weapon == IT_ROCKET_LAUNCHER)
{
if (self.owner.currentammo < 5 && self.owner.currentammo != 0)
self.frame = 6;
else if (self.owner.currentammo == 0)
self.frame = 11;
else self.frame = 2;
}
else if (self.owner.weapon == IT_LIGHTNING)
{
if (self.owner.currentammo < 12 && self.owner.currentammo != 0)
self.frame = 7;
else if (self.owner.currentammo == 0)
self.frame = 12;
else self.frame = 3;
}
else self.frame = 8; //Axe
self.nextthink = 0.1;
};
void() hud_altammo_think =
{
if (self.owner.health <= 0)
remove(self);
if (self.owner.weapon == IT_SHOTGUN)
{
if (self.owner.ammo_rockets < 5 && self.owner.ammo_rockets != 0)
self.frame = 6;
else if (self.owner.ammo_rockets == 0)
self.frame = 11;
else self.frame = 2;
}
else if (self.owner.weapon == IT_SUPER_NAILGUN)
{
if (self.owner.ammo_cells < 10 && self.owner.ammo_cells != 0)
self.frame = 7;
else if (self.owner.ammo_cells == 0)
self.frame = 12;
else self.frame = 3;
}
else self.frame = 8; //Alt uses same ammo
self.nextthink = 0.1;
};
void(entity foo) SpawnHUD_Face =
{
newmis = spawn();
newmis.owner = foo;
newmis.movetype = MOVETYPE_NONE;
newmis.solid = SOLID_NOT;
newmis.classname = "hud";
setmodel(newmis, "progs/hud_face.spr");
newmis.viewmodelforclient = foo;
newmis.think = hud_face_think;
newmis.nextthink = 0.1;
setorigin(newmis, '64 60 -28');
};
void(entity foo) SpawnHUD_Armor =
{
newmis = spawn();
newmis.owner = foo;
newmis.movetype = MOVETYPE_NONE;
newmis.solid = SOLID_NOT;
newmis.classname = "hud";
setmodel(newmis, "progs/hud_armor.spr");
newmis.viewmodelforclient = foo;
newmis.think = hud_armor_think;
newmis.nextthink = 0.1;
setorigin(newmis, '64 60 -20');
};
void(entity foo) SpawnHUD_Ammo =
{
newmis = spawn();
newmis.owner = foo;
newmis.movetype = MOVETYPE_NONE;
newmis.solid = SOLID_NOT;
newmis.classname = "hud";
setmodel(newmis, "progs/hud_ammo.spr");
newmis.viewmodelforclient = foo;
newmis.think = hud_ammo_think;
newmis.nextthink = 0.1;
setorigin(newmis, '64 60 -36');
};
void(entity foo) SpawnHUD_AltAmmo =
{
newmis = spawn();
newmis.owner = foo;
newmis.movetype = MOVETYPE_NONE;
newmis.solid = SOLID_NOT;
newmis.classname = "hud";
setmodel(newmis, "progs/hud_ammo.spr");
newmis.viewmodelforclient = foo;
newmis.think = hud_altammo_think;
newmis.nextthink = 0.1;
setorigin(newmis, '64 60 -44');
};
void(entity foo) SpawnCrosshair =
{
newmis = spawn();
newmis.owner = foo;
newmis.movetype = MOVETYPE_NONE;
newmis.solid = SOLID_NOT;
newmis.classname = "hud";
newmis.effects = newmis.effects | EF_ADDITIVE;
setmodel(newmis, "progs/xhair.spr");
newmis.viewmodelforclient = foo;
newmis.think = xhair_think;
newmis.nextthink = 0.1;
setorigin(newmis, '128 0 0');
};
Add this to the bottom of PutClientInServer():
SpawnCrosshair(self);
SpawnHUD_Face(self);
SpawnHUD_Armor(self);
SpawnHUD_Ammo(self);
SpawnHUD_AltAmmo(self);
Go down to PlayerDeathThink(). Change the line that reads:
if (!self.button2 && !self.button1 && !self.button0)
To
if (!self.button3 && !self.button2 && !self.button1 && !self.button0)
Find these lines:
self.button2 = 0;
respawn();
In between them, add a new line stating self.button3 = 0; so it should look like this:
self.button2 = 0;
self.button3 = 0;
respawn();
Go down to PlayerJump(). Change the line that reads:
self.velocity_z = self.velocity_z + 270;
To
self.velocity_z = self.velocity_z + player_jump_velocity;
Add this to PlayerPreThink() after the call for WaterMove().
if (cvar("crosshair")) //Remove this line if doing a multiplayer mod
stuffcmd (self, "crosshair 0\n");
if (cvar("r_lerpsprite")) //Remove this line if doing a multiplayer mod
stuffcmd (self, "r_lerpsprite 0\n");
if (cvar("viewsize") != 120) //Remove this line if doing a multiplayer mod
stuffcmd (self, "viewsize 120\n");
And finally, replace ClientObituary() with this:
void(entity targ, entity attacker) ClientObituary =
{
local float rnum;
local string deathstring, deathstring2;
rnum = random();
if ((targ.flags & FL_MONSTER) && attacker.classname == "player")
{
Log_It (attacker.netname);
if (targ.classname == "monster_army")
Log_It (" retired a Grunt\n");
if (targ.classname == "monster_demon1")
Log_It (" exorcised a Fiend\n");
if (targ.classname == "monster_dog")
Log_It (" taught a Rottweiler to play dead\n");
if (targ.classname == "monster_enforcer")
Log_It (" terminated an Enforcer\n");
if (targ.classname == "monster_fish")
Log_It (" made Rotfish sushi\n");
if (targ.classname == "monster_hell_knight")
Log_It (" slain a Death Knight\n");
if (targ.classname == "monster_knight")
Log_It (" fragged a Knight\n");
if (targ.classname == "monster_ogre")
Log_It (" destroyed an Ogre\n");
if (targ.classname == "monster_shalrath")
Log_It (" exorcised a Vore\n");
if (targ.classname == "monster_shambler")
Log_It (" smashed a Shambler\n");
if (targ.classname == "monster_tarbaby")
Log_It (" splattered a Spawn\n");
if (targ.classname == "monster_wizard")
Log_It (" shot down a Scrag\n");
if (targ.classname == "monster_zombie")
Log_It (" is one step closer to being a full-time Zombie hunter\n");
}
if (targ.classname == "player")
{
if (attacker.classname == "teledeath")
{
bprint (targ.netname);
bprint (" was telefragged by ");
bprint (attacker.owner.netname);
bprint ("\n");
Log_It (targ.netname);
Log_It (" was telefragged by ");
Log_It (attacker.owner.netname);
Log_It ("\n");
attacker.owner.frags = attacker.owner.frags + 1;
return;
}
if (attacker.classname == "teledeath2")
{
bprint ("Satan's power deflects ");
bprint (targ.netname);
bprint ("'s telefrag\n");
Log_It ("Satan's power deflects ");
Log_It (targ.netname);
Log_It ("'s telefrag\n");
targ.frags = targ.frags - 1;
return;
}
if (attacker.classname == "player")
{
if (targ == attacker)
{
// killed self
attacker.frags = attacker.frags - 1;
bprint (targ.netname);
Log_It (targ.netname);
if (targ.weapon == 64 && targ.waterlevel > 1)
{
bprint (" discharges into the water.\n");
Log_It (" discharges into the water.\n");
return;
}
if (targ.weapon == IT_GRENADE_LAUNCHER)
{
bprint (" tries to put the pin back in\n");
Log_It (" tries to put the pin back in\n");
}
else
{
bprint (" becomes bored with life\n");
Log_It (" becomes bored with life\n");
}
return;
}
else if ( (teamplay == 2) && (targ.team > 0)&&(targ.team == attacker.team) )
{
if (rnum < 0.25)
deathstring = " mows down a teammate\n";
else if (rnum < 0.50)
deathstring = " checks his glasses\n";
else if (rnum < 0.75)
deathstring = " gets a frag for the other team\n";
else
deathstring = " loses another friend\n";
bprint (attacker.netname);
bprint (deathstring);
Log_It (attacker.netname);
Log_It (deathstring);
attacker.frags = attacker.frags - 1;
return;
}
else
{
attacker.frags = attacker.frags + 1;
rnum = attacker.weapon;
if (rnum == IT_AXE)
{
deathstring = " was ax-murdered by ";
deathstring2 = "\n";
}
if (rnum == IT_SHOTGUN)
{
deathstring = " chewed on ";
deathstring2 = "'s boomstick\n";
}
if (rnum == IT_SUPER_SHOTGUN)
{
deathstring = " ate 2 loads of ";
deathstring2 = "'s buckshot\n";
}
if (rnum == IT_NAILGUN)
{
deathstring = " was nailed by ";
deathstring2 = "\n";
}
if (rnum == IT_SUPER_NAILGUN)
{
deathstring = " was punctured by ";
deathstring2 = "\n";
}
if (rnum == IT_GRENADE_LAUNCHER)
{
deathstring = " eats ";
deathstring2 = "'s pineapple\n";
if (targ.health < -40)
{
deathstring = " was gibbed by ";
deathstring2 = "'s grenade\n";
}
}
if (rnum == IT_ROCKET_LAUNCHER)
{
deathstring = " rides ";
deathstring2 = "'s rocket\n";
if (targ.health < -40)
{
deathstring = " was gibbed by ";
deathstring2 = "'s rocket\n" ;
}
}
if (rnum == IT_LIGHTNING)
{
deathstring = " accepts ";
if (attacker.waterlevel > 1)
deathstring2 = "'s discharge\n";
else
deathstring2 = "'s shaft\n";
}
bprint (targ.netname);
bprint (deathstring);
bprint (attacker.netname);
bprint (deathstring2);
Log_It (targ.netname);
Log_It (deathstring);
Log_It (attacker.netname);
Log_It (deathstring2);
}
return;
}
else
{
targ.frags = targ.frags - 1;
bprint (targ.netname);
Log_It (targ.netname);
// killed by a montser?
if (attacker.flags & FL_MONSTER)
{
if (attacker.classname == "monster_army")
{
bprint (" was shot by a Grunt\n");
Log_It (" was shot by a Grunt\n");
}
if (attacker.classname == "monster_demon1")
{
bprint (" was eviscerated by a Fiend\n");
Log_It (" was eviscerated by a Fiend\n");
}
if (attacker.classname == "monster_dog")
{
bprint (" was mauled by a Rottweiler\n");
Log_It (" was mauled by a Rottweiler\n");
}
if (attacker.classname == "monster_enforcer")
{
bprint (" was blasted by an Enforcer\n");
Log_It (" was blasted by an Enforcer\n");
}
if (attacker.classname == "monster_fish")
{
bprint (" was fed to the Rotfish\n");
Log_It (" was fed to the Rotfish\n");
}
if (attacker.classname == "monster_hell_knight")
{
bprint (" was slain by a Death Knight\n");
Log_It (" was slain by a Death Knight\n");
}
if (attacker.classname == "monster_knight")
{
bprint (" was slashed by a Knight\n");
Log_It (" was slashed by a Knight\n");
}
if (attacker.classname == "monster_ogre")
{
bprint (" was destroyed by an Ogre\n");
Log_It (" was destroyed by an Ogre\n");
}
if (attacker.classname == "monster_oldone")
{
bprint (" became one with Shub-Niggurath\n");
Log_It (" became one with Shub-Niggurath\n");
}
if (attacker.classname == "monster_shalrath")
{
bprint (" was exploded by a Vore\n");
Log_It (" was exploded by a Vore\n");
}
if (attacker.classname == "monster_shambler")
{
bprint (" was smashed by a Shambler\n");
Log_It (" was smashed by a Shambler\n");
}
if (attacker.classname == "monster_tarbaby")
{
bprint (" was slimed by a Spawn\n");
Log_It (" was slimed by a Spawn\n");
}
if (attacker.classname == "monster_wizard")
{
bprint (" was scragged by a Scrag\n");
Log_It (" was scragged by a Scrag\n");
}
if (attacker.classname == "monster_zombie")
{
bprint (" joins the Zombies\n");
Log_It (" joins the Zombies\n");
}
return;
}
// tricks and traps
if (attacker.classname == "explo_box")
{
bprint (" blew up\n");
Log_It (" blew up\n");
return;
}
if (attacker.solid == SOLID_BSP && attacker != world)
{
bprint (" was squished\n");
Log_It (" was squished\n");
return;
}
if (attacker.classname == "trap_shooter" || attacker.classname == "trap_spikeshooter")
{
bprint (" was spiked\n");
Log_It (" was spiked\n");
return;
}
if (attacker.classname == "fireball")
{
bprint (" ate a lavaball\n");
Log_It (" ate a lavaball\n");
return;
}
if (attacker.classname == "trigger_changelevel")
{
bprint (" tried to leave\n");
Log_It (" tried to leave\n");
return;
}
// in-water deaths
rnum = targ.watertype;
if (rnum == -3)
{
if (random() < 0.5)
{
bprint (" sleeps with the fishes\n");
Log_It (" sleeps with the fishes\n");
}
else
{
bprint (" sucks it down\n");
Log_It (" sucks it down\n");
}
return;
}
else if (rnum == -4)
{
if (random() < 0.5)
{
bprint (" gulped a load of slime\n");
Log_It (" gulped a load of slime\n");
}
else
{
bprint (" can't exist on slime alone\n");
Log_It (" can't exist on slime alone\n");
}
return;
}
else if (rnum == -5)
{
if (targ.health < -15)
{
bprint (" burst into flames\n");
Log_It (" burst into flames\n");
return;
}
if (random() < 0.5)
{
bprint (" turned into hot slag\n");
Log_It (" turned into hot slag\n");
}
else
{
bprint (" visits the Volcano God\n");
Log_It (" visits the Volcano God\n");
}
return;
}
// fell to their death?
if (targ.deathtype == "falling")
{
targ.deathtype = "";
bprint (" fell to his death\n");
Log_It (" fell to his death\n");
return;
}
// He's dead, Jim.
bprint (" died\n");
Log_It (" died\n");
}
}
};
Close client.qc. Thats it for this tutorial, compile and run.
Notes: If you're still scratching your head, bind +button3 to a key to use alternate fire.
Please note that there is currently an issue with .viewmodeltoclient - .viewmodels can currently
clip against the world, so please take this into account when you use it.
I do know a lot of stuff in this tutorial could be done better, such as the RPG laser.
If you're feeling bored you could improve these yourself (Such as instead of changing the RPG laser's
origin you could give it a point to move to and move it with velocity..) but keep in mind the focus of
this tutorial is to experiment with DarkPlaces. On that note, PLEASE READ DPEXTENSIONS.QC AS MANY TIMES
AS YOUR MIND CAN HANDLE! You will learn so much more about DarkPlaces features if you experiment with
them yourself, trust me.
Please note that by the time this tut was finished, a newer version of playermovement.qc was released.
Rather then get into an endless cycle of updating the tut to fit every new version I have decided to
simply release it as is. Please, if you are going to use playermovement.qc get the latest version of
DarkPlaces (Which includes dpextensions.qc, which is needed to use playermovement.qc without changes)
and DPMod (Which contains the latest playermovement.qc in the source) at LordHavoc's site