2001-07-23 20:52:47 +00:00
|
|
|
#include "defs.qh"
|
2001-07-17 05:58:10 +00:00
|
|
|
/*
|
|
|
|
TeamFortress 1.38 - 29/11/96
|
|
|
|
|
|
|
|
TeamFortress Software
|
|
|
|
Robin Walker, John Cook, Ian Caughley.
|
|
|
|
|
|
|
|
Original Code by "Mike" <amichael@asu.alasu.edu>
|
|
|
|
QuakeWorld version by Wedge
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
Modified for CustomTF 2.3 3/28/00
|
|
|
|
William Kerney
|
|
|
|
Removed the ability to hook onto or damage teammates
|
|
|
|
Stopped some crashing bugs with it
|
|
|
|
*/
|
|
|
|
|
|
|
|
// QuakeWorld version
|
|
|
|
|
|
|
|
/*
|
|
|
|
===========================================================================
|
|
|
|
Quakeworld-friendly grapple hook code by Wedge (Steve Bond)
|
|
|
|
visit Quake Command http://www.nuc.net/quake
|
|
|
|
|
|
|
|
|
|
|
|
Original 'Morning Star' (Grapple Hook) by "Mike" <amichael@asu.alasu.edu>
|
|
|
|
I took care to preserve the speed and damage values of the original
|
|
|
|
Morning Star. Depending on latency, performance should be near exact.
|
|
|
|
===========================================================================
|
|
|
|
*/
|
|
|
|
|
|
|
|
// prototypes for WEAPONS.QC functions
|
|
|
|
float() crandom;
|
2001-12-05 18:47:21 +00:00
|
|
|
void (entity base) Remove_Chain;
|
2001-07-17 05:58:10 +00:00
|
|
|
|
|
|
|
//
|
|
|
|
// Reset_Grapple - Removes the hook and resets its owner's state.
|
|
|
|
// expects a pointer to the hook
|
|
|
|
//
|
|
|
|
void (entity rhook) Reset_Grapple =
|
|
|
|
{
|
2001-07-23 20:52:47 +00:00
|
|
|
sound (rhook.owner, CHAN_WEAPON, "weapons/bounce.wav", 1, ATTN_NONE);
|
2001-12-05 18:47:21 +00:00
|
|
|
|
|
|
|
if (rhook.goalentity) {
|
|
|
|
Remove_Chain (rhook.goalentity);
|
|
|
|
}
|
|
|
|
|
2001-07-23 20:52:47 +00:00
|
|
|
rhook.owner.on_hook = FALSE;
|
|
|
|
rhook.owner.hook_out = FALSE;
|
|
|
|
rhook.owner.fire_held_down = FALSE;
|
2001-07-17 05:58:10 +00:00
|
|
|
rhook.owner.weaponframe = 0;
|
2001-10-14 00:57:50 +00:00
|
|
|
rhook.owner.gravity = 1; // FIXME: interferes with other gravity stuff
|
2001-12-05 18:47:21 +00:00
|
|
|
rhook.owner.hook = NIL;
|
2001-07-17 05:58:10 +00:00
|
|
|
|
|
|
|
rhook.think = SUB_Remove;
|
|
|
|
rhook.nextthink = time;
|
|
|
|
};
|
|
|
|
|
|
|
|
//
|
|
|
|
// Grapple_Track - Constantly updates the hook's position relative to
|
|
|
|
// what it's hooked to. Inflicts damage if attached to
|
|
|
|
// a player that is not on the same team as the hook's
|
|
|
|
// owner.
|
|
|
|
//
|
|
|
|
void () Grapple_Track =
|
|
|
|
{
|
2001-10-14 00:57:50 +00:00
|
|
|
// drop the hook if owner is dead or has released the button
|
|
|
|
if (!self.owner.on_hook || self.owner.health <= 0) {
|
2001-12-05 18:47:21 +00:00
|
|
|
Reset_Grapple (self.owner.hook);
|
2001-10-14 00:57:50 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (self.owner.health <= 0 || self.owner.has_disconnected) { //CH does a real check if dead
|
2001-12-05 18:47:21 +00:00
|
|
|
Reset_Grapple (self.owner.hook);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!self.enemy.solid) {
|
|
|
|
Reset_Grapple (self.owner.hook);
|
2001-10-14 00:57:50 +00:00
|
|
|
return;
|
|
|
|
}
|
2001-07-17 05:58:10 +00:00
|
|
|
|
|
|
|
//WK
|
2001-10-14 00:57:50 +00:00
|
|
|
if (self.enemy.classname == "player" && Teammate(self.enemy.team_no, self.owner.team_no)) {
|
2001-12-05 18:47:21 +00:00
|
|
|
Reset_Grapple (self.owner.hook);
|
2001-07-17 05:58:10 +00:00
|
|
|
return;
|
2001-10-14 00:57:50 +00:00
|
|
|
}
|
2001-07-17 05:58:10 +00:00
|
|
|
|
2001-10-14 00:57:50 +00:00
|
|
|
deathmsg = DMSG_HOOK;
|
|
|
|
if (self.enemy.classname == "player") {
|
|
|
|
T_Damage (self.enemy, self, self.owner, 2);
|
|
|
|
if (self.enemy.health <= 0) {
|
2001-12-05 18:47:21 +00:00
|
|
|
Reset_Grapple (self.owner.hook);
|
2001-07-17 05:58:10 +00:00
|
|
|
return;
|
2001-10-14 00:57:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the hook is not attached to the player, constantly
|
|
|
|
// copy the target's velocity. Velocity copying DOES NOT work properly
|
|
|
|
// for a hooked client.
|
|
|
|
if (self.enemy.classname != "player")
|
|
|
|
self.velocity = self.enemy.velocity;
|
|
|
|
|
|
|
|
self.nextthink = time + 0.1;
|
2001-07-17 05:58:10 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
//
|
|
|
|
// MakeLink - spawns the chain link entities
|
|
|
|
//
|
|
|
|
entity () MakeLink =
|
|
|
|
{
|
2001-12-05 18:47:21 +00:00
|
|
|
newmis = spawn ();
|
2001-07-17 05:58:10 +00:00
|
|
|
|
2001-12-05 18:47:21 +00:00
|
|
|
newmis.movetype = MOVETYPE_FLYMISSILE;
|
|
|
|
newmis.solid = SOLID_NOT;
|
|
|
|
newmis.owner = self;// SELF is the hook!
|
2001-07-17 05:58:10 +00:00
|
|
|
|
2001-12-05 18:47:21 +00:00
|
|
|
newmis.avelocity = '200 200 200';
|
2001-07-17 05:58:10 +00:00
|
|
|
|
2001-12-05 18:47:21 +00:00
|
|
|
setmodel (newmis, "progs/s_spike.mdl");
|
|
|
|
setorigin (newmis, self.origin);
|
|
|
|
setsize (newmis, '0 0 0' , '0 0 0');
|
2001-07-17 05:58:10 +00:00
|
|
|
|
2001-12-05 18:47:21 +00:00
|
|
|
return newmis;
|
2001-07-17 05:58:10 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
//
|
|
|
|
// Remove_Chain - Removes all chain link entities; this is a separate
|
|
|
|
// function because CLIENT also needs to be able
|
|
|
|
// to remove the chain. Only one function required to
|
|
|
|
// remove all links.
|
|
|
|
//
|
2001-12-05 18:47:21 +00:00
|
|
|
void (entity base) Remove_Chain =
|
2001-07-17 05:58:10 +00:00
|
|
|
{
|
2001-12-05 18:47:21 +00:00
|
|
|
base.think = SUB_Remove;
|
|
|
|
base.nextthink = time;
|
|
|
|
if (base.goalentity) {
|
|
|
|
base.goalentity.think = SUB_Remove;
|
|
|
|
base.goalentity.nextthink = time;
|
|
|
|
if (base.goalentity.goalentity) {
|
|
|
|
base.goalentity.goalentity.think = SUB_Remove;
|
|
|
|
base.goalentity.goalentity.nextthink = time;
|
|
|
|
}
|
2001-07-17 05:58:10 +00:00
|
|
|
}
|
2001-12-05 18:47:21 +00:00
|
|
|
|
|
|
|
base.owner.hook.goalentity = NIL;
|
2001-07-17 05:58:10 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
//
|
|
|
|
// Update_Chain - Repositions the chain links each frame. This single function
|
|
|
|
// maintains the positions of all of the links. Only one link
|
|
|
|
// is thinking every frame.
|
|
|
|
//
|
|
|
|
void () Update_Chain =
|
|
|
|
{
|
|
|
|
local vector temp;
|
|
|
|
|
|
|
|
if (!self.owner.hook_out)
|
|
|
|
{
|
2001-12-05 18:47:21 +00:00
|
|
|
// Remove_Chain (self);
|
2001-07-17 05:58:10 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//CH Using a show grapple loc thing, it was updating while dead.
|
|
|
|
if (self.owner.health <= 0 || self.owner.has_disconnected)
|
|
|
|
{
|
2001-12-05 18:47:21 +00:00
|
|
|
Reset_Grapple (self.owner.hook);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// reset if it gets insane
|
|
|
|
if (vlen (self.origin - self.owner.origin) >= 3000) {
|
|
|
|
Reset_Grapple (self.owner.hook);
|
|
|
|
return;
|
|
|
|
} else if (!self.owner.on_hook && vlen (self.owner.hook.velocity) < 10) {
|
|
|
|
Reset_Grapple (self.owner.hook);
|
2001-07-17 05:58:10 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
temp = (self.owner.hook.origin - self.owner.origin);
|
|
|
|
|
|
|
|
// These numbers are correct assuming 3 links.
|
|
|
|
// 4 links would be *20 *40 *60 and *80
|
|
|
|
setorigin (self, self.owner.origin + temp * 0.25);
|
|
|
|
setorigin (self.goalentity, self.owner.origin + temp * 0.5);
|
|
|
|
setorigin (self.goalentity.goalentity, self.owner.origin + temp * 0.75);
|
2001-12-05 18:47:21 +00:00
|
|
|
self.velocity = '0 0 0';
|
|
|
|
self.goalentity.velocity = '0 0 0';
|
|
|
|
self.goalentity.goalentity.velocity = '0 0 0';
|
2001-07-17 05:58:10 +00:00
|
|
|
|
|
|
|
self.nextthink = time + 0.1;
|
|
|
|
};
|
|
|
|
|
|
|
|
//
|
|
|
|
// Build_Chain - Builds the chain (linked list)
|
|
|
|
//
|
|
|
|
void () Build_Chain =
|
|
|
|
{
|
2001-12-05 18:47:21 +00:00
|
|
|
self.goalentity = MakeLink();
|
|
|
|
self.goalentity.think = Update_Chain;
|
|
|
|
self.goalentity.nextthink = time + 0.1;
|
|
|
|
self.goalentity.owner = self.owner;
|
|
|
|
|
|
|
|
self.goalentity.goalentity = MakeLink();
|
|
|
|
self.goalentity.goalentity.owner = self.owner;
|
2001-07-17 05:58:10 +00:00
|
|
|
|
2001-12-05 18:47:21 +00:00
|
|
|
self.goalentity.goalentity.goalentity = MakeLink();
|
|
|
|
self.goalentity.goalentity.goalentity.owner = self.owner;
|
2001-07-17 05:58:10 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
//
|
|
|
|
// Check_Overhead - Makes sure there is sufficient headroom above the player
|
|
|
|
// so that setorigin doesn't stick them into a wall. I tried
|
|
|
|
// to compare pointcontents, but that was too flaky.
|
|
|
|
//
|
|
|
|
float () Check_Overhead =
|
|
|
|
{
|
2001-12-05 18:47:21 +00:00
|
|
|
local vector src;
|
|
|
|
local vector end;
|
|
|
|
|
|
|
|
makevectors (self.owner.angles);
|
|
|
|
|
|
|
|
// The following comparisons could be optimized by doing away with
|
|
|
|
// SRC and END, and plugging the values directly into the traceline
|
|
|
|
// function calls. Using SRC and END made debugging easier. You
|
|
|
|
// decide if it's worth it.
|
|
|
|
|
|
|
|
// quick check right above head
|
|
|
|
src = self.owner.origin - '0 0 24';
|
|
|
|
end = self.owner.origin - '0 0 24';
|
|
|
|
traceline (src, end, FALSE, self.owner);
|
|
|
|
if (trace_fraction != 1.0)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
src = self.owner.origin - '0 0 24' - v_forward * 16;
|
|
|
|
end = self.owner.origin - '0 0 24' - v_forward * 16 + '0 0 58';
|
|
|
|
traceline (src, end, FALSE, self.owner);
|
|
|
|
if (trace_fraction != 1.0)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
src = self.owner.origin - '0 0 24' + v_forward * 16;
|
|
|
|
end = self.owner.origin - '0 0 24' + v_forward * 16 + '0 0 58';
|
|
|
|
traceline (src, end, FALSE, self.owner);
|
|
|
|
if (trace_fraction != 1.0)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
src = self.owner.origin - '0 0 24' - v_right * 16;
|
|
|
|
end = self.owner.origin - '0 0 24' - v_right * 16 + '0 0 58';
|
|
|
|
traceline (src, end, FALSE, self.owner);
|
|
|
|
if (trace_fraction != 1.0)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
src = self.owner.origin - '0 0 24' + v_right * 16;
|
|
|
|
end = self.owner.origin - '0 0 24' + v_right * 16 + '0 0 58';
|
|
|
|
traceline (src, end, FALSE, self.owner);
|
|
|
|
if (trace_fraction != 1.0)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
return TRUE;
|
2001-07-17 05:58:10 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// Anchor_Grapple - Tries to anchor the grapple to whatever it touches
|
|
|
|
//
|
|
|
|
void () Anchor_Grapple =
|
|
|
|
{
|
|
|
|
local float test;
|
|
|
|
|
|
|
|
if (other == self.owner)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// DO NOT allow the grapple to hook to any projectiles, no matter WHAT!
|
|
|
|
// if you create new types of projectiles, make sure you use one of the
|
|
|
|
// classnames below or write code to exclude your new classname so
|
|
|
|
// grapples will not stick to them.
|
2001-12-05 18:47:21 +00:00
|
|
|
if (other.classname == "missile"
|
|
|
|
|| other.classname == "grenade"
|
|
|
|
|| other.classname == "spike"
|
|
|
|
|| other.classname == "hook"
|
|
|
|
|| other.classname == "pipebomb"
|
|
|
|
|| other.classname == "force_field") //CH maybe fix crash?
|
2001-07-17 05:58:10 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
//if (isBuilding(other)) //WK don't hook onto buildings?
|
|
|
|
// return;
|
|
|
|
|
|
|
|
// Don't stick the the sky.
|
2001-07-31 17:08:59 +00:00
|
|
|
if (pointcontents(self.origin) == CONTENTS_SKY)
|
2001-07-17 05:58:10 +00:00
|
|
|
{
|
|
|
|
Reset_Grapple (self);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2001-07-23 20:52:47 +00:00
|
|
|
sound (self, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM);
|
2001-07-17 05:58:10 +00:00
|
|
|
//WK Only deal the initial damage to buttons so we can open stuff
|
|
|
|
if (other.takedamage)
|
|
|
|
if (other.classname == "func_button")
|
|
|
|
T_Damage (other, self, self.owner, 1);
|
|
|
|
|
|
|
|
self.velocity = '0 0 0';
|
|
|
|
self.avelocity = '0 0 0';
|
|
|
|
|
|
|
|
// conveniently clears the sound channel of the CHAIN1 sound,
|
|
|
|
// which is a looping sample and would continue to play. Tink1 is
|
|
|
|
// the least offensive choice, as NULL.WAV loops and clogs the
|
|
|
|
// channel with silence
|
2001-07-23 20:52:47 +00:00
|
|
|
sound (self.owner, CHAN_WEAPON, "weapons/tink1.wav", 1, ATTN_NORM);
|
2001-07-17 05:58:10 +00:00
|
|
|
|
|
|
|
if (!self.owner.button0)
|
|
|
|
{
|
|
|
|
Reset_Grapple (self);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// our last chance to avoid being picked up off of the ground.
|
|
|
|
// check over the client's head to make sure there is one unit
|
|
|
|
// clearance so we can lift him off of the ground.
|
|
|
|
test = Check_Overhead ();
|
|
|
|
if (!test)
|
|
|
|
{
|
|
|
|
Reset_Grapple (self);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2001-07-23 20:52:47 +00:00
|
|
|
self.owner.on_hook = TRUE;
|
2001-07-17 05:58:10 +00:00
|
|
|
|
2001-07-23 20:52:47 +00:00
|
|
|
if (self.owner.flags & FL_ONGROUND)
|
2001-07-17 05:58:10 +00:00
|
|
|
{
|
2001-07-23 20:52:47 +00:00
|
|
|
self.owner.flags = self.owner.flags - FL_ONGROUND;
|
2001-07-17 05:58:10 +00:00
|
|
|
setorigin(self.owner,self.owner.origin + '0 0 1');
|
|
|
|
}
|
|
|
|
|
|
|
|
// CHAIN2 is a looping sample. Use LEFTY as a flag so that client.qc
|
|
|
|
// will know to only play the tink sound ONCE to clear the weapons
|
|
|
|
// sound channel. (Lefty is a leftover from AI.QC, so I reused it to
|
|
|
|
// avoid adding a field)
|
2001-07-23 20:52:47 +00:00
|
|
|
self.owner.lefty = TRUE;
|
2001-07-17 05:58:10 +00:00
|
|
|
|
|
|
|
self.enemy = other;// remember this guy!
|
|
|
|
self.think = Grapple_Track;
|
|
|
|
self.nextthink = time;
|
2001-07-23 20:52:47 +00:00
|
|
|
self.solid = SOLID_NOT;
|
2001-10-17 07:48:11 +00:00
|
|
|
self.touch = NIL;
|
2001-07-17 05:58:10 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
// Throw_Grapple - called from WEAPONS.QC, 'fires' the grapple
|
|
|
|
//
|
|
|
|
void () Throw_Grapple =
|
|
|
|
{
|
2001-12-05 18:47:21 +00:00
|
|
|
if (self.hook_out)// reject subsequent calls from player.qc
|
|
|
|
return;
|
2001-07-17 05:58:10 +00:00
|
|
|
|
|
|
|
KickPlayer(-1, self);
|
|
|
|
|
2001-12-05 18:47:21 +00:00
|
|
|
// chain out sound (loops)
|
2001-07-17 05:58:10 +00:00
|
|
|
|
2001-12-05 18:47:21 +00:00
|
|
|
newmis = spawn();
|
|
|
|
newmis.movetype = MOVETYPE_FLYMISSILE;
|
|
|
|
newmis.solid = SOLID_BBOX;
|
|
|
|
newmis.owner = self;// newmis belongs to me
|
|
|
|
self.hook = newmis;// This is my newmis
|
|
|
|
newmis.classname = "hook";
|
2001-07-17 05:58:10 +00:00
|
|
|
|
2001-12-05 18:47:21 +00:00
|
|
|
makevectors (self.v_angle);
|
|
|
|
newmis.velocity = v_forward * 800;
|
|
|
|
// newmis.avelocity = '0 0 -500';
|
2001-07-17 05:58:10 +00:00
|
|
|
|
2001-12-05 18:47:21 +00:00
|
|
|
// set the facing of the grapple
|
|
|
|
newmis.angles = vectoangles(newmis.velocity);
|
2001-07-17 05:58:10 +00:00
|
|
|
|
2001-12-05 18:47:21 +00:00
|
|
|
newmis.touch = Anchor_Grapple;
|
|
|
|
newmis.think = Build_Chain;
|
|
|
|
newmis.nextthink = time + 0.1;// don't jam newmis and links into same packet
|
2001-07-17 05:58:10 +00:00
|
|
|
|
2001-12-05 18:47:21 +00:00
|
|
|
setmodel (newmis,"progs/hook.mdl");
|
|
|
|
setorigin (newmis, self.origin + v_forward * 16 + '0 0 16');
|
|
|
|
setsize(newmis, '0 0 0' , '0 0 0 ');
|
2001-07-17 05:58:10 +00:00
|
|
|
|
2001-12-05 18:47:21 +00:00
|
|
|
self.hook_out = TRUE;
|
|
|
|
self.fire_held_down = TRUE;
|
2001-07-17 05:58:10 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
//
|
|
|
|
// Service_Grapple - called each frame by CLIENT.QC if client is ON_HOOK
|
|
|
|
//
|
|
|
|
void () Service_Grapple =
|
|
|
|
{
|
2001-12-05 18:47:21 +00:00
|
|
|
local vector hook_dir;
|
|
|
|
|
|
|
|
self.gravity = 0;
|
|
|
|
|
|
|
|
// drop the hook if player lets go of button
|
|
|
|
if (!self.button0)
|
|
|
|
{
|
|
|
|
self.fire_held_down = FALSE;
|
|
|
|
|
|
|
|
if (self.current_weapon == WEAP_HOOK) {
|
|
|
|
Reset_Grapple (self.hook);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// If hooked to a player, track them directly!
|
|
|
|
if (self.hook.enemy.classname == "player")
|
|
|
|
hook_dir = (self.hook.enemy.origin - self.origin);
|
|
|
|
else // else, track to hook
|
|
|
|
hook_dir = (self.hook.origin - self.origin);
|
|
|
|
|
|
|
|
// set the facing of the grapple
|
|
|
|
// self.hook.angles = vectoangles(self.hook.velocity);
|
|
|
|
|
|
|
|
self.velocity = normalize(hook_dir) * self.maxspeed * 1.5;
|
|
|
|
|
|
|
|
if (vlen(hook_dir) <= 100 && self.lefty) // cancel chain sound
|
|
|
|
{
|
|
|
|
// If there is a chain, ditch it now. We're
|
|
|
|
// close enough. Having extra entities lying around
|
|
|
|
// is never a good idea.
|
|
|
|
if (self.hook.goalentity) {
|
|
|
|
Remove_Chain (self.hook.goalentity);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.lefty = FALSE; // we've reset the sound channel.
|
|
|
|
}
|
2001-07-17 05:58:10 +00:00
|
|
|
};
|