Quake DeveLS - Sword Of The Highlander

Author: Patrick Wagstrom(Pridkett)
Difficulty: REALLY FRICKIN HARD

Pulling Quake 2 out of the box, I could hardly wait to play my favorite form of deathmatch with my friends, lumberjack! But alas, there was no melee weapon in Quake 2. So I got to thinking about making a cool mod for Quake 2 and decided well, Highlander is pretty cool. So while this isn't exactly highlander, because there ain't stuff like quickening and what not (thats in the works), this code lets you play lumberjack quake (without models or anything though).

On a side note, I had NO CLUE how hard this was going to be when I first started working on the sword. I thought (there's my problem, thinking) that it was going to be a relatively easy procedure to add in the sword, but I found out that I had to modify many a files in order to do it, and create some new ones...thus the really frickin hard rating.

Note:
Text in red = Quake II's code
Text in blue = Our code
Text in gray = Comments
//::Pridkett = Beginning of my code
//!Pridkett = End of my code

Well, first things first, we need to create the item for the sword in quake 2's item database so we open up the file g_item.c and begin editing. Find the spot where it has the list of all the items in the game. It should begin around line 990 or somewhere there abouts...then find this sections of code beneath it and insert the definition for the sword:


/*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,
		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"
	},


	//::Pridkett
	/* weapon_sword
	always owned, never in the world
	*/
	{
		"weapon_sword", 
		NULL,
		Use_Weapon,                             //How to use
		NULL,
		Weapon_Sword,                           //What the function is
		"misc/w_pkup.wav",
		NULL, 
		0,
		"models/weapons/v_blast/tris.md2",      //The models stuff
		"w_blaster",                                    //Icon to be used
		"Sword",                                        //Pickup name
		0,
		0,
		NULL,
		IT_WEAPON,
		NULL,
		0,
		 "weapons/blastf1a.wav misc/lasfly.wav" //The sound of the blaster
								//This is precached
	},
	
	//!Pridkett




In addition at the top of this file we need to make an additional definition now because we referred to a function that is unknown as of right now. So look and add in this code that is just a new function prototype:


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);

//::Pridkett
void Weapon_Sword (edict_t *ent);
//!Pridkett

Okay. So now the game knows that the weapon at least EXISTS. But now we have to do some handling so we can actually use the weapon in the game. The first thing we have to do is to give us a sword, because none of the levels have swords on them...yet. So lets open up p_client.c and do some editing on that. Look for the InitClientPersistant function and do some modifications as shown. This routine essentially tells it to find the item called a "Sword" and then give the person one of them.


void InitClientPersistant (gclient_t *client)
{
	gitem_t         *item;

	memset (&client->pers, 0, sizeof(client->pers));


	item = FindItem("Sword");
	client->pers.inventory[ITEM_INDEX(item)] = 1;

	item = FindItem("Blaster");
	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;
}

Now comes the REAL fun part. I suggest you make another file for this part because it gets to a be a lot of new stuff. Most of these functions are based off the blaster, because it is the easiest to understand as towards how it works. I called mine g_highln.h because eventually I plan to have more highlander-esque features involved in here.


/*
 * sword variable definitions, I have this here because the sword is most likely still unbalanced
 * I know this because I killed a super tank with it without taking ANY damaga
 */
#define SWORD_NORMAL_DAMAGE 100
#define SWORD_DEATHMATCH_DAMAGE 150
#define SWORD_KICK 500

These are a few constants that I needed to declare because I am not quite sure how to balance out the sword with the other weapons in the game. They should be pretty obvious as towards what they do. I think I need to decrease the kick and damage because you can pretty much kill a super tank with sword and have no resistance.

Each of the weapons in quake 2 has quite a few smaller functions with confusing names. In keeping with that spirit, I have named them along the same lines. Its quite obvious that Carmack never took any formal programming classes because his code is horrible to read... non-standard caps and function names....arghh...anyway, these are just other functions with small modifications


/*
=============
fire_sword

attacks with the beloved sword of the highlander
 edict_t *self - entity producing it, yourself
 vec3_t start - The place you are
 vec3_t aimdir - Where you are looking at in this case
 int damage - the damage the sword inflicts
 int kick - how much you want that bitch to be thrown back
=============
*/

void fire_sword ( edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick)
{
	//You may recognize a lot of this from the fire lead command, which
	//is the one that I understood best what the hell was going on

	trace_t tr;             //Not entirely sure what this is, I know that it is used
					//to trace out the route of the weapon being used...gotta limit it

	vec3_t          dir;            //Another point I am unclear about
	vec3_t          forward;        //maybe someday I will know a little bit
	vec3_t          right;          //better about what these are
	vec3_t          up;
	vec3_t          end;

	tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT);

	if (!(tr.fraction < 1.0))       //I can only assume this has something to do
								//with the progress of the trace
	{
		vectoangles(aimdir,dir);
		AngleVectors(dir,forward,right,up);             //possibly sets some of the angle vectors
												//as standards?
		
		VectorMA (start, 8192, forward, end);           //This does some extension of the vector...
													//note how short I have this attack going
	}

	//The fire_lead had an awful lot of stuff in here dealing with the effect of the shot
	//upon water and whatnot, but a sword doesn't make you worry about that sort of stuff
	//thats why highlanders are so damn cool.
	
	if (!((tr.surface) && (tr.surface->flags & SURF_SKY)))
	{
		if (tr.fraction < 1.0)
		{
			if (tr.ent->takedamage)
			{
				//This tells us to damage the thing that in our path...hehe
				T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0);
			}
			else
			{
				if (strncmp (tr.surface->name, "sky", 3) != 0)
				{
					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);
				}
			}
		}
	}
	return;
}

Now that we have the actual mechanism for use of the sword down, we need to make some routines for the sword to act as a weapon. These two routines are almost exact clones of how the blaster works. Minus the fact that there no spawned entity for the damage. The entity created dies almost immediately, so it does not travel, and thus needs to be in contact in order to damage something.


void sword_attack (edict_t *ent, vec3_t g_offset, int damage)
{
	vec3_t  forward, right;
	vec3_t  start;
	vec3_t  offset;

	if (is_quad)
		damage *= 4;
	AngleVectors (ent->client->v_angle, forward, right, NULL);
	VectorSet(offset, 24, 8, ent->viewheight-8);
	VectorAdd (offset, g_offset, offset);
	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);

	VectorScale (forward, -2, ent->client->kick_origin);
	ent->client->kick_angles[0] = -1;

	fire_sword (ent, start, forward, damage, SWORD_KICK );
}

void Weapon_Sword_Fire (edict_t *ent)
{
	int damage;
	if (deathmatch->value)
		damage = SWORD_DEATHMATCH_DAMAGE;
	else
		damage = SWORD_NORMAL_DAMAGE;
	sword_attack (ent, vec3_origin, damage);
	ent->client->ps.gunframe++;
}

Now as we saw when we created the sword weapon there is one function we need to define. That is the function that the item calls when it is used. Quake 2 uses a generic weapon handling routine to do everything and just passes it functions, so lets keep with the scheme of things. The frames listed at the bottom are those of the blaster. So you really can't tell the difference when you are using the blaster or sword right now, other than the fact that the blaster fires and the sword doesn't.


void Weapon_Sword (edict_t *ent)
{
	static int      pause_frames[]  = {19, 32, 0};
	static int      fire_frames[]   = {5, 0};

	Weapon_Generic (ent, 4, 8, 52, 55, pause_frames, fire_frames, Weapon_Sword_Fire);
}

//!Pridkett

My next tutorial will hopefully be one on how to make it so you have to attack people with the sword to kill them. Eventually I hope to have a total conversion (well at least rules wise) down for you to read.

Now to make this part of the code work, you need to include it in the program somewhere. Open up p_weapon.c and add this at the last line:


#include "g_highln.h"

To make things a little easier, I have included the entire g_highln.h file here for you. It should help in speeding some things up for you, but you still need to add the other stuff:


//::Pridkett

/*
 * Highlander Quake 2
 * Main Header File
 * Author(s): Pridkett ([email protected])
 *
 * Last Revision:   1/1/98 - Wow...the sword actually works now
 *                  12/29/97 - Initial Code
 */


/*
 * sword variable definitions, I have this here because the sword is most likely still unbalanced
 * I know this because I killed a super tank with it without taking ANY damaga
 */
#define SWORD_NORMAL_DAMAGE 100
#define SWORD_DEATHMATCH_DAMAGE 150
#define SWORD_KICK 500

/*
=============
fire_sword

attacks with the beloved sword of the highlander
 edict_t *self - entity producing it, yourself
 vec3_t start - The place you are
 vec3_t aimdir - Where you are looking at in this case
 int damage - the damage the sword inflicts
 int kick - how much you want that bitch to be thrown back
=============
*/

void fire_sword ( edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick)
{
	//You may recognize a lot of this from the fire lead command, which
	//is the one that I understood best what the hell was going on

	trace_t tr;             //Not entirely sure what this is, I know that it is used
					//to trace out the route of the weapon being used...gotta limit it

	vec3_t          dir;            //Another point I am unclear about
	vec3_t          forward;        //maybe someday I will know a little bit
	vec3_t          right;          //better about what these are
	vec3_t          up;
	vec3_t          end;

	tr = gi.trace (self->s.origin, NULL, NULL, start, self, MASK_SHOT);

	if (!(tr.fraction < 1.0))       //I can only assume this has something to do
								//with the progress of the trace
	{
		vectoangles(aimdir,dir);
		AngleVectors(dir,forward,right,up);             //possibly sets some of the angle vectors
												//as standards?
		
		VectorMA (start, 8192, forward, end);           //This does some extension of the vector...
													//note how short I have this attack going
	}

	//The fire_lead had an awful lot of stuff in here dealing with the effect of the shot
	//upon water and whatnot, but a sword doesn't make you worry about that sort of stuff
	//thats why highlanders are so damn cool.
	
	if (!((tr.surface) && (tr.surface->flags & SURF_SKY)))
	{
		if (tr.fraction < 1.0)
		{
			if (tr.ent->takedamage)
			{
				//This tells us to damage the thing that in our path...hehe
				T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0);
			}
			else
			{
				if (strncmp (tr.surface->name, "sky", 3) != 0)
				{
					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);
				}
			}
		}
	}
	return;
}

void sword_attack (edict_t *ent, vec3_t g_offset, int damage)
{
	vec3_t  forward, right;
	vec3_t  start;
	vec3_t  offset;

	if (is_quad)
		damage *= 4;
	AngleVectors (ent->client->v_angle, forward, right, NULL);
	VectorSet(offset, 24, 8, ent->viewheight-8);
	VectorAdd (offset, g_offset, offset);
	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);

	VectorScale (forward, -2, ent->client->kick_origin);
	ent->client->kick_angles[0] = -1;

	fire_sword (ent, start, forward, damage, SWORD_KICK );
}

void Weapon_Sword_Fire (edict_t *ent)
{
	int damage;
	if (deathmatch->value)
		damage = SWORD_DEATHMATCH_DAMAGE;
	else
		damage = SWORD_NORMAL_DAMAGE;
	sword_attack (ent, vec3_origin, damage);
	ent->client->ps.gunframe++;
}

void Weapon_Sword (edict_t *ent)
{
	static int      pause_frames[]  = {19, 32, 0};
	static int      fire_frames[]   = {5, 0};

	Weapon_Generic (ent, 4, 8, 52, 55, pause_frames, fire_frames, Weapon_Sword_Fire);
}

//
//!Pridkett

This site, and all content and graphics displayed on it,
are ©opyrighted to the Quake DeveLS team. All rights received.
Got a suggestion? Comment? Question? Hate mail? Send it to us!
Oh yeah, this site is best viewed in 16 Bit or higher, with the resolution on 800*600.
Thanks to Planet Quake for there great help and support with hosting.
Best viewed with Netscape 4