game-source/ctf/qwsrc/grapple.qc

386 lines
11 KiB
C++
Raw Normal View History

/*
2003-03-03 19:17:04 +00:00
grapple.qc
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to:
Free Software Foundation, Inc.
59 Temple Place - Suite 330
Boston, MA 02111-1307, USA
$Id$
*/
/*
===========================================================================
grapple.qc 12/29/96
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
2003-03-03 19:17:04 +00:00
float () crandom;
void (vector org, float damage) SpawnBlood;
// Reset_Grapple - Removes the hook and resets its owner's state.
// expects a pointer to the hook
2003-03-03 19:17:04 +00:00
void (entity rhook)
Reset_Grapple =
{
2003-03-03 19:17:04 +00:00
if (rhook.owner == world)
return;
sound (rhook.owner, CHAN_NO_PHS_ADD + CHAN_WEAPON, "weapons/bounce2.wav",
1, ATTN_NORM);
rhook.owner.on_hook = FALSE;
rhook.owner.hook_out = FALSE;
rhook.owner.weaponframe = 0;
rhook.owner.attack_finished = time + 0.25;
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.
2003-03-03 19:17:04 +00:00
void ()
Grapple_Track =
{
2003-03-03 19:17:04 +00:00
// Release dead targets
if (self.enemy.classname == "player" && self.enemy.health <= 0) {
self.owner.on_hook = FALSE;
self.owner.attack_finished = time +0.75;
}
// drop the hook if owner is dead or has released the button
if (!self.owner.on_hook || self.owner.health <= 0) {
Reset_Grapple (self);
return;
}
// bring the pAiN!
if (self.enemy.classname == "player") {
// 4.1, if we can't see our enemy, unlock
if (!CanDamage(self.enemy, self.owner)) {
Reset_Grapple(self);
return;
}
// move the hook along with the player. It's invisible, but
// we need this to make the sound come from the right spot
setorigin (self, self.enemy.origin);
2003-03-03 19:17:04 +00:00
sound (self, CHAN_WEAPON, "blob/land1.wav", 1, ATTN_NORM);
T_Damage (self.enemy, self, self.owner, 1);
makevectors (self.v_angle);
SpawnBlood (self.enemy.origin, 1);
}
// 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;
};
// MakeLink - spawns the chain link entities
2003-03-03 19:17:04 +00:00
entity ()
MakeLink =
{
2003-03-03 19:17:04 +00:00
newmis = spawn ();
2003-03-03 19:17:04 +00:00
newmis.movetype = MOVETYPE_FLYMISSILE;
newmis.solid = SOLID_NOT;
newmis.owner = self; // SELF is the hook!
2003-03-03 19:17:04 +00:00
newmis.avelocity = '200 200 200';
2003-03-03 19:17:04 +00:00
setmodel (newmis, "progs/bit.mdl");
setorigin (newmis, self.origin);
setsize (newmis, '0 0 0' , '0 0 0');
2003-03-03 19:17:04 +00:00
return newmis;
};
// 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.
2003-03-03 19:17:04 +00:00
void ()
Remove_Chain =
{
2003-03-03 19:17:04 +00:00
self.think = SUB_Remove;
self.nextthink = time;
if (self.goalentity) {
self.goalentity.think = SUB_Remove;
self.goalentity.nextthink = time;
if (self.goalentity.goalentity) {
self.goalentity.goalentity.think = SUB_Remove;
self.goalentity.goalentity.nextthink = time;
}
}
};
// 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.
2003-03-03 19:17:04 +00:00
void ()
Update_Chain =
{
2003-03-03 19:17:04 +00:00
local vector temp;
2003-03-03 19:17:04 +00:00
if (!self.owner.hook_out) {
self.think = Remove_Chain;
self.nextthink = time;
return;
}
2003-03-03 19:17:04 +00:00
temp = (self.owner.hook.origin - self.owner.origin);
2003-03-03 19:17:04 +00:00
// 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);
2003-03-03 19:17:04 +00:00
self.nextthink = time + 0.1;
};
// Build_Chain - Builds the chain (linked list)
2003-03-03 19:17:04 +00:00
void ()
Build_Chain =
{
2003-03-03 19:17:04 +00:00
self.goalentity = MakeLink();
self.goalentity.think = Update_Chain;
self.goalentity.nextthink = time + 0.1;
self.goalentity.owner = self.owner;
2003-03-03 19:17:04 +00:00
self.goalentity.goalentity = MakeLink ();
self.goalentity.goalentity.goalentity = MakeLink ();
};
// 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.
2003-03-03 19:17:04 +00:00
float ()
Check_Overhead =
{
2003-03-03 19:17:04 +00:00
makevectors (self.owner.angles);
// quick check right above head
traceline (self.owner.origin - '0 0 24', self.owner.origin - '0 0 24',
FALSE, self.owner);
if (trace_fraction != 1.0)
return FALSE;
traceline (self.owner.origin - '0 0 24' - v_forward * 16,
self.owner.origin - '0 0 24' - v_forward * 16 + '0 0 58',
FALSE, self.owner);
if (trace_fraction != 1.0)
return FALSE;
traceline (self.owner.origin - '0 0 24' + v_forward * 16,
self.owner.origin - '0 0 24' + v_forward * 16 + '0 0 58',
FALSE, self.owner);
if (trace_fraction != 1.0)
return FALSE;
traceline (self.owner.origin - '0 0 24' - v_right * 16,
self.owner.origin - '0 0 24' - v_right * 16 + '0 0 58',
FALSE, self.owner);
if (trace_fraction != 1.0)
return FALSE;
traceline (self.owner.origin - '0 0 24' + v_right * 16,
self.owner.origin - '0 0 24' + v_right * 16 + '0 0 58',
FALSE, self.owner);
if (trace_fraction != 1.0)
return FALSE;
return TRUE;
};
// Anchor_Grapple - Tries to anchor the grapple to whatever it touches
2003-03-03 19:17:04 +00:00
void ()
Anchor_Grapple =
{
2003-03-03 19:17:04 +00:00
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.
if (other.classname == "missile" || other.classname == "grenade"
|| other.classname == "spike" || other.classname == "hook")
return;
// Don't stick the the sky.
if (pointcontents (self.origin) == CONTENT_SKY) {
Reset_Grapple (self);
return;
}
if (other.classname == "player") {
// glance off of teammates
if (other.steam == self.owner.steam)
return;
2003-03-03 19:17:04 +00:00
sound (self, CHAN_WEAPON, "player/axhit1.wav", 1, ATTN_NORM);
T_Damage (other, self, self.owner, 10);
// make hook invisible since we will be pulling directly towards the
// player the hook hit. Quakeworld makes it too quirky to try to match
// hook's velocity with that of the client that it hit.
setmodel (self, "");
} else {
sound (self, CHAN_WEAPON, "player/axhit2.wav", 1, ATTN_NORM);
// One point of damage inflicted upon impact. Subsequent damage will
// only be done to PLAYERS... this way secret doors and triggers will
// only be damaged once.
if (other.takedamage)
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, ass NULL.WAV loops and clogs the channel with silence
sound (self.owner, CHAN_NO_PHS_ADD + CHAN_WEAPON,
"weapons/tink1.wav", 1, ATTN_NORM);
if (!self.owner.button0) {
Reset_Grapple (self);
return;
}
/*
2003-03-03 19:17:04 +00:00
// 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;
}
*/
2003-03-03 19:17:04 +00:00
if (self.owner.flags & FL_ONGROUND) {
self.owner.flags &= ~FL_ONGROUND;
// setorigin (self.owner,self.owner.origin + '0 0 1');
}
2003-03-03 19:17:04 +00:00
self.owner.on_hook = TRUE;
2003-03-03 19:17:04 +00:00
sound (self.owner, CHAN_WEAPON, "weapons/chain2.wav", 1, ATTN_NORM);
2003-03-03 19:17:04 +00:00
// 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)
self.owner.lefty = TRUE;
2003-03-03 19:17:04 +00:00
self.enemy = other; // remember this guy!
self.think = Grapple_Track;
self.nextthink = time;
self.solid = SOLID_NOT;
self.touch = SUB_Null;
};
// Throw_Grapple - called from WEAPONS.QC, 'fires' the grapple
2003-03-03 19:17:04 +00:00
void ()
Throw_Grapple =
{
2003-03-03 19:17:04 +00:00
if (self.hook_out) // reject subsequent calls from player.qc
return;
2003-03-03 19:17:04 +00:00
msg_entity = self;
WriteByte (MSG_ONE, SVC_SMALLKICK);
2003-03-03 19:17:04 +00:00
// chain out sound (loops)
sound (self, CHAN_WEAPON, "weapons/chain1.wav", 1, ATTN_NORM);
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";
2003-03-03 19:17:04 +00:00
makevectors (self.v_angle);
newmis.velocity = v_forward * 800;
newmis.avelocity = '0 0 -500';
2003-03-03 19:17:04 +00:00
newmis.touch = Anchor_Grapple;
newmis.think = Build_Chain;
2003-03-03 19:17:04 +00:00
// don't jam newmis & links into same packet
newmis.nextthink = time + 0.1;
2003-03-03 19:17:04 +00:00
setmodel (newmis,"progs/star.mdl");
setorigin (newmis, self.origin + v_forward * 16 + '0 0 16');
setsize(newmis, '0 0 0' , '0 0 0 ');
2003-03-03 19:17:04 +00:00
self.hook_out = TRUE;
};
// Service_Grapple - called each frame by CLIENT.QC if client is ON_HOOK
2003-03-03 19:17:04 +00:00
void ()
Service_Grapple =
{
2003-03-03 19:17:04 +00:00
local vector hook_dir = '0 0 0';
2003-03-03 19:17:04 +00:00
// drop the hook if player lets go of button
if (!self.button0) {
if (self.weapon == IT_GRAPPLE) {
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 if (self.hook.enemy.classname != "player") // track to hook
hook_dir = (self.hook.origin - self.origin);
self.velocity = normalize (hook_dir) * 750;
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) {
self.hook.goalentity.think = Remove_Chain;
self.hook.goalentity.nextthink = time;
}
sound (self, CHAN_NO_PHS_ADD + CHAN_WEAPON, "weapons/chain3.wav", 1,
ATTN_NORM);
self.lefty = FALSE; // we've reset the sound channel.
}
};