
Grappling Hook
This grappling hook is a modified version of the Expert
Quake2 grappling hook. It uses the push function in Quake2 to prevent
jerkiness and bouncing, even when you have moderate lag. I personally
believe that it's the best grappling hook around. It is offhand, so you
don't have to switch weapons to fire it.
Remember to add in the text that is blue,
taking out any text that is pink.
First open up g_local.h, we're going to define some variables in the
gclient_s structure:
qboolean grenade_blew_up;
float
grenade_time;
int
silencer_shots;
int
weapon_sound;
float
pickup_msg_time;
float
respawn_time; // can respawn when
time > this
//Grapple Variables
edict_t *hook;
edict_t
*hook_touch;
qboolean
on_hook;
int
hook_frame;
Next we need to change a function in p_weapon.c, so open it up and remove
the "static" part of the P_projectsource like this:
static void P_ProjectSource (gclient_t
*client, vec3_t point, vec3_t distance, vec3_t forward, vec3_t right, vec3_t
result)
{
vec3_t _distance;
VectorCopy (distance, _distance);
if (client->pers.hand == LEFT_HANDED)
_distance[1] *= -1;
else if (client->pers.hand == CENTER_HANDED)
_distance[1] = 0;
G_ProjectSource (point, _distance, forward, right, result);
}
Next we're going to make two new files: grapple.c and grapple.h. Make
sure you add grapple.c to your project!
In grapple.c, add the following section:
#include "g_local.h"
#include "grapple.h"
#define HOOK_TIME
5000
#define HOOK_SPEED
1200
#define THINK_TIME 0.3
#define HOOK_DAMAGE 5
#define GRAPPLE_REFIRE 2
#define PULL_SPEED 500
void P_ProjectSource (gclient_t *client, vec3_t point, vec3_t distance, vec3_t
forward, vec3_t right, vec3_t result);
qboolean Ended_Grappling (gclient_t *client)
{
return (!(client->buttons & BUTTON_USE) &&
client->oldbuttons & BUTTON_USE);
}
qboolean Is_Grappling (gclient_t *client)
{
return (client->hook == NULL) ? false : true;
}
void Grapple_Touch(edict_t *hook, edict_t *other, cplane_t *plane, csurface_t
*surf)
{
// Release if hitting its owner
if (other == hook->owner)
return;
if (!Is_Grappling(hook->owner->client) &&
hook->health == 0) {
return;
}
hook->health = 0;
if (surf && surf->flags & SURF_SKY)
{
Release_Grapple(hook);
return;
}
if (other != g_edicts && other->clipmask ==
MASK_SHOT)
return;
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_BLASTER);
gi.WritePosition (hook->s.origin);
gi.WriteDir (plane->normal);
gi.multicast (hook->s.origin, MULTICAST_PVS);
//gi.sound(hook, CHAN_ITEM,
gi.soundindex("hook/hit.wav"), 1, ATTN_NORM, 0);//uncomment this line to make it play a sound
//when your hook hits a wall
if (other != NULL) {
T_Damage(other, hook,
hook->owner, hook->velocity, hook->s.origin, plane->normal,
HOOK_DAMAGE, 0, 0, MOD_SUICIDE);
}
if (other != g_edicts && other->health &&
other->solid == SOLID_BBOX) {
Release_Grapple(hook);
return;
}
if (other != g_edicts && other->inuse &&
(other->movetype == MOVETYPE_PUSH
|| other->movetype == MOVETYPE_STOP))
{
other->mynoise2 = hook;
hook->owner->client->hook_touch = other;
hook->enemy = other;
hook->groundentity = NULL;
hook->flags |= FL_TEAMSLAVE;
}
VectorClear(hook->velocity);
VectorClear(hook->avelocity);
hook->solid = SOLID_NOT;
hook->touch = NULL;
hook->movetype = MOVETYPE_NONE;
hook->delay = level.time + HOOK_TIME;
hook->owner->client->on_hook = true;
hook->owner->groundentity = NULL;
Pull_Grapple(hook->owner);
}
void Think_Grapple(edict_t *hook)
{
if (level.time > hook->delay)
hook->prethink = Release_Grapple;
else
{
if
(hook->owner->client->hook_touch) {
edict_t *obj
= hook->owner->client->hook_touch;
if (obj ==
g_edicts)
{
Release_Grapple(hook);
return;
}
if
(obj->inuse == false) {
Release_Grapple(hook);
return;
}
if
(obj->deadflag == DEAD_DEAD)
{
Release_Grapple(hook);
return;
}
// Movement
code is handled with the MOVETYPE_PUSH stuff in g_phys.c
T_Damage(obj, hook, hook->owner, hook->velocity, hook->s.origin,
vec3_origin, HOOK_DAMAGE, 0, 0, MOD_SUICIDE);
}
hook->nextthink += THINK_TIME;
}
}
static void DrawBeam (edict_t *ent)
{
gi.WriteByte (svc_temp_entity);
gi.WriteByte (TE_BFG_LASER);
gi.WritePosition (ent->owner->s.origin);
gi.WritePosition (ent->s.origin);
gi.multicast (ent->s.origin, MULTICAST_PHS);
}
void Make_Hook(edict_t *ent)
{
edict_t *hook;
vec3_t forward, right, start, offset;
hook = G_Spawn();
AngleVectors(ent->client->v_angle, forward, right,
NULL);
VectorScale(forward, -2, ent->client->kick_origin);
ent->client->kick_angles[0] = -1;
VectorSet(offset, 8, 0, ent->viewheight-8);
P_ProjectSource (ent->client, ent->s.origin, offset,
forward, right, start);
VectorCopy(start, hook->s.origin);
VectorCopy(forward, hook->movedir);
vectoangles(forward, hook->s.angles);
VectorScale(forward, HOOK_SPEED, hook->velocity);
VectorSet(hook->avelocity, 0, 0, 500);
hook->classname = "hook";
hook->movetype = MOVETYPE_FLYMISSILE;
hook->clipmask = MASK_SHOT;
hook->solid = SOLID_BBOX;
hook->svflags |= SVF_DEADMONSTER;
hook->s.renderfx = RF_FULLBRIGHT;
VectorClear (hook->mins);
VectorClear (hook->maxs);
hook->s.effects |= EF_COLOR_SHELL;
hook->s.renderfx |= RF_SHELL_GREEN;
hook->s.modelindex = gi.modelindex ("models/objects/flash/tris.md2");
hook->owner = ent;
hook->touch = Grapple_Touch;
hook->delay = level.time + HOOK_TIME;
hook->nextthink = level.time;
hook->prethink = DrawBeam;
hook->think = Think_Grapple;
hook->health = 100;
hook->svflags = SVF_MONSTER;
ent->client->hook = hook;
gi.linkentity(hook);
}
void Throw_Grapple (edict_t *player)
{
if (player->client->hook) {
return;
}
gi.sound(player, CHAN_ITEM,
gi.soundindex("medic/medatck2.wav"), 0.5, ATTN_NORM, 0);
player->client->hook_touch = NULL;
Make_Hook(player);
}
void Release_Grapple (edict_t *hook)
{
edict_t *owner = hook->owner;
gclient_t *client = hook->owner->client;
edict_t *link = hook->teamchain;
client->on_hook = false;
client->hook_touch = NULL;
if (client->hook != NULL) {
client->hook = NULL;
VectorClear(client->oldvelocity);
hook->think = NULL;
if (hook->enemy) {
hook->enemy->mynoise2 = NULL;
}
G_FreeEdict(hook);
}
}
void Pull_Grapple (edict_t *player)
{
vec3_t hookDir;
vec_t length;
VectorSubtract(player->client->hook->s.origin,
player->s.origin, hookDir);
length = VectorNormalize(hookDir);
VectorScale(hookDir, /*player->scale * */ PULL_SPEED,
player->velocity);
VectorCopy(hookDir, player->movedir);
//To move the player off the ground just a bit so he doesn't stay stuck
(version 3.17 bug)
if (player->velocity[2] > 0) {
vec3_t traceTo;
trace_t trace;
// find the point immediately above
the player's origin
VectorCopy(player->s.origin,
traceTo);
traceTo[2] += 1;
// trace to it
trace = gi.trace(traceTo,
player->mins, player->maxs, traceTo, player, MASK_PLAYERSOLID);
// if there isn't a solid
immediately above the player
if (!trace.startsolid) {
player->s.origin[2] += 1; // make sure player off ground
}
}
}
These are all the functions used to create, fire, and use the grappling
hook. Next add this code to grapple.h:
qboolean Started_Grappling(gclient_t *client);
qboolean Ended_Grappling(gclient_t *client);
qboolean Is_Grappling(gclient_t *client);
void Throw_Grapple(edict_t *ent);
void Release_Grapple(edict_t *ent);
void Think_Grapple(edict_t *ent);
void Pull_Grapple(edict_t *ent);
Next go to p_client.c, and in the player_die function add these lines like
this:
/*
==================
player_die
==================
*/
void player_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int
damage, vec3_t point)
{
int n;
if (self->client->hook)
Release_Grapple(self->client->hook);
VectorClear (self->avelocity);
self->takedamage = DAMAGE_YES;
self->movetype = MOVETYPE_TOSS;
This just removes the hook if the player dies. Next go down to the
clientthink function and add in these few lines like this:
if (level.intermissiontime)
{
client->ps.pmove.pm_type =
PM_FREEZE;
// can exit intermission after five
seconds
if (level.time >
level.intermissiontime + 5.0
&&
(ucmd->buttons & BUTTON_ANY) )
level.exitintermission = true;
return;
}
if (client->on_hook == true)
{
Pull_Grapple(ent);
client->ps.pmove.gravity = 0;
}
else
{
client->ps.pmove.gravity =
sv_gravity->value;
}
pm_passent = ent;
if (ent->client->chase_target) {
client->resp.cmd_angles[0] =
SHORT2ANGLE(ucmd->angles[0]);
client->resp.cmd_angles[1] =
SHORT2ANGLE(ucmd->angles[1]);
client->resp.cmd_angles[2] =
SHORT2ANGLE(ucmd->angles[2]);
} else {
// set up for pmove
memset (&pm, 0, sizeof(pm));
if (ent->movetype ==
MOVETYPE_NOCLIP)
client->ps.pmove.pm_type = PM_SPECTATOR;
else if (ent->s.modelindex !=
255)
client->ps.pmove.pm_type = PM_GIB;
else if (ent->deadflag)
client->ps.pmove.pm_type = PM_DEAD;
else
client->ps.pmove.pm_type = PM_NORMAL;
client->ps.pmove.gravity =
sv_gravity->value;//REMOVE THIS LINE!
pm.s = client->ps.pmove;
This checks to see if the grappling hook has lodged in a wall and starts the
pulling function which would draw you to the hook. It also turns off the
gravity while your are on the hook. Next go down to the bottom of
clientthink and add in these few lines:
// fire weapon from final position if needed
if (client->latched_buttons & BUTTON_ATTACK)
{
if (!client->weapon_thunk)
{
client->weapon_thunk = true;
Think_Weapon
(ent);
}
}
// Check to see if player pressing the
"use" key
if (ent->client->buttons & BUTTON_USE &&
!ent->deadflag && client->hook_frame <= level.framenum)
{
Throw_Grapple (ent);
}
if (Ended_Grappling (client) &&
!ent->deadflag && client->hook)
{
Release_Grapple (client->hook);
}
}
This was an extremely important part, it set the actual grapple function to
the use command, so using the command +use will
fire the grapple! For some unknown reason, Id Software left that command
open for use. Next go back to the top, we need to #include grapple.h
because it has the declarations for all the functions:
#include "g_local.h"
#include "m_player.h"
#include
"grapple.h"
void ClientUserinfoChanged (edict_t *ent, char *userinfo);
void SP_misc_teleporter_dest (edict_t *ent);
Next we need to modify a small section in g_misc.c to set the hook time for
setting the refire delay. Add in the lines shown:
void teleporter_touch (edict_t *self, edict_t *other, cplane_t *plane,
csurface_t *surf)
{
edict_t *dest;
int
i;
if (!other->client)
return;
dest = G_Find (NULL, FOFS(targetname), self->target);
if (!dest)
{
gi.dprintf ("Couldn't find
destination\n");
return;
}
// unlink to make sure it can't possibly interfere with
KillBox
gi.unlinkentity (other);
VectorCopy (dest->s.origin, other->s.origin);
VectorCopy (dest->s.origin, other->s.old_origin);
other->s.origin[2] += 10;
// clear the velocity and hold them in place briefly
VectorClear (other->velocity);
other->client->ps.pmove.pm_time = 160>>3;
// hold time
other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT;
other->client->hook_frame =
level.framenum + 1;
// draw the teleport splash at source and on the player
self->owner->s.event = EV_PLAYER_TELEPORT;
other->s.event = EV_PLAYER_TELEPORT;
// set angles
for (i=0 ; i<3 ; i++)
other->client->ps.pmove.delta_angles[i] =
ANGLE2SHORT(dest->s.angles[i] - other->client->resp.cmd_angles[i]);
VectorClear (other->s.angles);
VectorClear (other->client->ps.viewangles);
VectorClear (other->client->v_angle);
// kill anything at the destination
KillBox (other);
if (Is_Grappling(other->client)) {
Release_Grapple(other->client->hook);
}
gi.linkentity (other);
}
Now go back to the top and do another #include like so:
// g_misc.c
#include "g_local.h"
#include "grapple.h"
/*QUAKED func_group (0 0 0) ?
Used to group brushes together just for editor convenience.
*/
Next open up g_phys.c and add the #include to the top like this:
// g_phys.c
#include "g_local.h"
#include "grapple.h"
And now go down to the sv_physics_pusher function and add in the indicated
lines like so:
void SV_Physics_Pusher (edict_t *ent)
{
vec3_t move,
amove;
edict_t *part,
*mv;
// if not a team captain, so movement will be handled
elsewhere
if ( ent->flags & FL_TEAMSLAVE)
return;
// make sure all team slaves can move before commiting
// any moves or calling any think functions
// if the move is blocked, all moved objects will be backed
out
//retry:
pushed_p = pushed;
for (part = ent ; part ; part=part->teamchain)
{
qboolean
blocked = false;
if (part->mynoise2 &&
part->mynoise2->think == Think_Grapple)
{
edict_t
*hook = part->mynoise2;
vec3_t org,
org2, forward, right, up, move2;
if
(hook->enemy == NULL || hook->inuse == false)
continue;
VectorScale
(part->velocity, FRAMETIME, move);
VectorScale
(part->avelocity, FRAMETIME, amove);
VectorAdd(hook->s.origin,
move, hook->s.origin);
VectorAdd(hook->s.angles, amove, hook->s.angles);
// figure
movement due to the pusher's amove
VectorSubtract (vec3_origin, amove, org);
AngleVectors
(org, forward, right, up);
VectorSubtract (hook->s.origin, part->s.origin, org);
org2[0] =
DotProduct (org, forward);
org2[1] =
-DotProduct (org, right);
org2[2] =
DotProduct (org, up);
VectorSubtract (org2, org, move2);
VectorAdd
(hook->s.origin, move2, hook->s.origin);
}
if (part->velocity[0] ||
part->velocity[1] || part->velocity[2] ||
part->avelocity[0] || part->avelocity[1] || part->avelocity[2]
)
{ // object is
moving
VectorScale
(part->velocity, FRAMETIME, move);
VectorScale
(part->avelocity, FRAMETIME, amove);
if (!SV_Push
(part, move, amove))
break; // move was blocked
}
}
All this does is give the hook the same velocity as the entity it's stuck
in. That's it! You now have an offhand grappling hook that is used
with the command "+use". If you
want, you can alias this command to +hook by using the stuffcmd() tutorial also
on the tutorial page. It would look like this:
stuffcmd(ent, "alias +hook \"+use\"\n");
stuffcmd(ent, "alias -hook \"-use\"\n");
I hope this helps! If there are any problems with this grapple, please
let me know, I understand how coders hate it when a tutorial doesn't work.
Tutorial by Willi