Laser Grapple
Created By: Tux
eMail: tux@admdev.com
Difficulty Scale: Medium


Well, I actually did it, my beam hook tutorial, with the sound and models included.

Step 1.

First, prepare your mod directory. Create the following directory structure.

quake/moddir/sound/hook/grfire.wav
quake/moddir/sound/hook/grhit.wav
quake/moddir/sound/hook/grreset.wav
quake/moddir/progs/null.spr
quake/moddir/progs/bolt.mdl
Get these files from this zip tuthook.zip.

Step 2.

Add tuthook.qc to progs.src above weapons.qc

Step 3.

Next, go into weapons.qc and copy the following code into the precache function near the beginning of the file:

precache_sound("hook/grfire.wav");
precache_sound("hook/grhit.wav");
precache_sound("hook/grreset.wav");
precache_model("progs/null.spr");


Step 4.

Next go to client.qc and add this line to the end of the "PlayerPreThink" function:

CheckRope();


Step 5.

Then, add these lines to defs.qc:

.entity hook;
.float hooking;
Next go to weapons.qc and add the following lines to the Impulse check near the end of the file.

if (self.impulse == 24)
	FireHook ();
if (self.impulse == 25)
	BreakHook ();

Step 6.

Add all the below code to a new file called tuthook.qc (or use the tuthook.qc from the zip)

void () DrawBeam = 
{
   local vector org;
   org = (self.origin) + '0 0 16';
   WriteByte(MSG_BROADCAST, SVC_TEMPENTITY);
   WriteByte(MSG_BROADCAST, TE_LIGHTNING1);
   WriteEntity(MSG_BROADCAST, world);
   WriteCoord(MSG_BROADCAST, org_x);
   WriteCoord(MSG_BROADCAST, org_y);
   WriteCoord(MSG_BROADCAST, org_z);
   WriteCoord(MSG_BROADCAST, self.hook.origin_x);
   WriteCoord(MSG_BROADCAST, self.hook.origin_y);
   WriteCoord(MSG_BROADCAST, self.hook.origin_z);	
};

void() pull =
{
		if (self.owner.hooking == 1)
		{
		self.owner.velocity = normalize(self.origin - self.owner.origin);
		self.owner.velocity = self.owner.velocity * 500;
		self.think = pull;
		self.nextthink = time + 0.1;
		}
		else
		{
		self.owner.hooking = 0;
		remove(self);
		sound (self.owner, CHAN_WEAPON, "grapple/grreset.wav", 1, ATTN_NORM);
		}	
};

void() hookdamage =
{
if (self.enemy.health)
{
	self.owner.velocity = normalize(self.origin - self.owner.origin);
	self.owner.velocity = self.owner.velocity * 500;
	
	T_Damage(self.enemy, self, self.owner, 2);

	self.origin = self.enemy.origin;
	self.think = hookdamage;
	self.nextthink = time + 0.1;
}
else
{
	self.owner.hooking = 0;
	remove(self);
	sound (self.owner, CHAN_WEAPON, "grapple/grreset.wav", 1, ATTN_NORM);	
}
};

void() PullHook =
{
	if (other == self.owner)
		return;

	if (other == world) // Pull
	{
			self.solid = SOLID_NOT;
			self.movetype = MOVETYPE_NONE;
			self.think = pull;
			self.nextthink = time;
      }
	if (other.takedamage && other.health)
	{
			self.think = hookdamage;
			self.enemy = other;
			self.solid = SOLID_NOT;
			self.nextthink = time;
	}
	sound (self.owner, CHAN_WEAPON, "grapple/grhit.wav", 1, ATTN_NORM);	
	
};


void() BreakHook =
{
	if (self.hooking == 1)
	{
	remove(self.hook);
	self.hooking = 0;
	sound (self, CHAN_WEAPON, "grapple/grreset.wav", 1, ATTN_NORM);	
	}
};

void () FireHook =
{
	local entity hooky;
	hooky = spawn();
	setorigin(hooky, self.origin + self.view_ofs);
	setmodel(hooky, "progs/null.spr");
	setsize (hooky, '0 0 0', '0 0 0');
	hooky.solid = SOLID_BBOX;
	hooky.movetype = MOVETYPE_FLYMISSILE;
	makevectors(self.v_angle);
	self.aiment = hooky;
	hooky.velocity = v_forward * 1500;
	hooky.owner = self;
	self.hook = hooky;
	hooky.classname = "hookend";
	self.hooking = 1;
	hooky.touch = PullHook;
	//hooky.cansplash = 1; // If my splash tutorial is installed, uncomment this line
	sound (self, CHAN_WEAPON, "grapple/grfire.wav", 1, ATTN_NORM);	
};

void() CheckRope =
{
	if (self.hooking == 1 && self.hook.classname == "hookend")
	{
		DrawBeam ();
	}

};
The mod is now ready for compile. For the hook to work with a single button, make an autoexec.cfg file that contains these lines:

alias +hook "impulse 24"
alias -hook "impulse 25"
bind  +hook