You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
351 lines
9.7 KiB
351 lines
9.7 KiB
/*** |
|
* |
|
* Copyright (c) 1996-2002, Valve LLC. All rights reserved. |
|
* |
|
* This product contains software technology licensed from Id |
|
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. |
|
* All Rights Reserved. |
|
* |
|
* Use, distribution, and modification of this source code and/or resulting |
|
* object code is restricted to non-commercial enhancements to products from |
|
* Valve LLC. All other use, distribution, or modification is prohibited |
|
* without written permission from Valve LLC. |
|
* |
|
****/ |
|
/*********************************************** |
|
************************************************ |
|
GRAPPLE |
|
************************************************ |
|
***********************************************/ |
|
|
|
#include "extdll.h" |
|
#include "util.h" |
|
#include "cbase.h" |
|
#include "player.h" |
|
#include "weapons.h" |
|
#include "grapple.h" |
|
|
|
unsigned short g_usHook; |
|
unsigned short g_usCable; |
|
|
|
void CGrapple::Reset_Grapple() |
|
{ |
|
CBasePlayer *pOwner = (CBasePlayer *)CBaseEntity::Instance( pev->owner ); |
|
|
|
pOwner->m_bOn_Hook = FALSE; |
|
pOwner->m_bHook_Out = FALSE; |
|
|
|
PLAYBACK_EVENT_FULL( FEV_GLOBAL | FEV_RELIABLE, |
|
pOwner->edict(), g_usCable, 0, g_vecZero, g_vecZero, |
|
0.0, 0.0, entindex(), pev->team, 1, 0 ); |
|
|
|
STOP_SOUND( edict(), CHAN_WEAPON, "weapons/grhang.wav" ); |
|
STOP_SOUND( pOwner->edict(), CHAN_WEAPON, "weapons/grfire.wav" ); |
|
STOP_SOUND( pOwner->edict(), CHAN_WEAPON, "weapons/grpull.wav" ); |
|
|
|
pOwner->m_ppHook = NULL; |
|
pev->enemy = NULL; |
|
|
|
UTIL_Remove( this ); |
|
} |
|
|
|
void CGrapple::GrappleTouch( CBaseEntity *pOther ) |
|
{ |
|
CBasePlayer *pOwner = (CBasePlayer *)CBaseEntity::Instance( pev->owner ); |
|
|
|
if( pOther == pOwner ) |
|
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( pOther->Classify() == CLASS_PROJECTILE ) |
|
return; |
|
|
|
if( pOther->IsPlayer() ) |
|
{ |
|
// glance off of teammates |
|
if( pOther->pev->team == pOwner->pev->team ) |
|
return; |
|
|
|
// sound( self, CHAN_WEAPON, "player/axhit1.wav", 1, ATTN_NORM ); |
|
// TakeDamage( pOther->pev, pOwner->pev, 10, DMG_GENERIC ); |
|
|
|
// 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, ""); |
|
pev->velocity = g_vecZero; |
|
UTIL_SetOrigin( pev, pOther->pev->origin ); |
|
} |
|
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( pOther->pev->takedamage ) |
|
TakeDamage( pOther->pev, pOwner->pev, 1, DMG_GENERIC ); |
|
|
|
pev->velocity = g_vecZero; |
|
EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "weapons/grhit.wav", 1, ATTN_NORM ); |
|
|
|
// No sparks underwater |
|
if( pev->waterlevel == 0 ) |
|
UTIL_Sparks( pev->origin ); |
|
} |
|
|
|
// 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( !( pOwner->pev->button & IN_ATTACK ) ) |
|
{ |
|
if( pOwner->m_bOn_Hook ) |
|
{ |
|
Reset_Grapple(); |
|
return; |
|
} |
|
} |
|
|
|
if( pOwner->pev->flags & FL_ONGROUND ) |
|
{ |
|
pOwner->pev->flags &= ~FL_ONGROUND; |
|
// setorigin(self.owner,self.owner.origin + '0 0 1'); |
|
} |
|
|
|
pOwner->m_bOn_Hook = TRUE; |
|
|
|
// sound( self.owner, CHAN_WEAPON, "weapons/chain2.wav", 1, ATTN_NORM ); |
|
|
|
// 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; |
|
|
|
STOP_SOUND( pOwner->edict(), CHAN_WEAPON, "weapons/grfire.wav" ); |
|
|
|
pev->enemy = pOther->edict();// remember this guy! |
|
SetThink( &CGrapple::Grapple_Track ); |
|
pev->nextthink = gpGlobals->time; |
|
m_flNextIdleTime = gpGlobals->time + 0.1f; |
|
pev->solid = SOLID_NOT; |
|
SetTouch( NULL ); |
|
} |
|
|
|
bool CanSee( CBaseEntity *pEnemy, CBaseEntity *pOwner ) |
|
{ |
|
TraceResult tr; |
|
|
|
UTIL_TraceLine( pOwner->pev->origin, pEnemy->pev->origin, ignore_monsters, ENT( pOwner->pev ), &tr ); |
|
if( tr.flFraction == 1 ) |
|
return TRUE; |
|
|
|
UTIL_TraceLine( pOwner->pev->origin, pEnemy->pev->origin + Vector( 15, 15, 0 ), ignore_monsters, ENT( pOwner->pev ), &tr ); |
|
if( tr.flFraction == 1 ) |
|
return TRUE; |
|
|
|
UTIL_TraceLine( pOwner->pev->origin, pEnemy->pev->origin + Vector( -15, -15, 0 ), ignore_monsters, ENT( pOwner->pev ), &tr ); |
|
if( tr.flFraction == 1 ) |
|
return TRUE; |
|
|
|
UTIL_TraceLine( pOwner->pev->origin, pEnemy->pev->origin + Vector( -15, 15, 0 ), ignore_monsters, ENT( pOwner->pev ), &tr ); |
|
if( tr.flFraction == 1 ) |
|
return TRUE; |
|
|
|
UTIL_TraceLine( pOwner->pev->origin, pEnemy->pev->origin + Vector( 15, -15, 0 ), ignore_monsters, ENT( pOwner->pev ), &tr ); |
|
if( tr.flFraction == 1 ) |
|
return TRUE; |
|
|
|
return FALSE; |
|
} |
|
|
|
void CGrapple::Grapple_Track() |
|
{ |
|
CBasePlayer *pOwner = (CBasePlayer*)CBaseEntity::Instance( pev->owner ); |
|
CBaseEntity *pEnemy = CBaseEntity::Instance( pev->enemy ); |
|
|
|
// Release dead targets |
|
if( pEnemy->IsPlayer() && pEnemy->pev->health <= 0 ) |
|
Reset_Grapple(); |
|
|
|
// drop the hook if owner is dead or has released the button |
|
if( !pOwner->m_bOn_Hook || pOwner->pev->health <= 0 ) |
|
{ |
|
Reset_Grapple(); |
|
return; |
|
} |
|
|
|
if( !( pOwner->pev->button & IN_ATTACK ) ) |
|
{ |
|
if( pOwner->m_iQuakeWeapon == IT_EXTRA_WEAPON ) |
|
{ |
|
Reset_Grapple(); |
|
return; |
|
} |
|
} |
|
|
|
// bring the pAiN! |
|
if( pEnemy->IsPlayer() ) |
|
{ |
|
if( !CanSee( pEnemy, pOwner ) ) |
|
{ |
|
Reset_Grapple(); |
|
return; |
|
} |
|
|
|
// move the hook along with the player. It's invisible, but |
|
// we need this to make the sound come from the right spot |
|
UTIL_SetOrigin( pev, pEnemy->pev->origin ); |
|
|
|
// sound( self, CHAN_WEAPON, "blob/land1.wav", 1, ATTN_NORM ); |
|
|
|
SpawnBlood( pEnemy->pev->origin, BLOOD_COLOR_RED, 1 ); |
|
( (CBasePlayer *)pEnemy )->TakeDamage( pev, pOwner->pev, 1, DMG_GENERIC ); |
|
} |
|
|
|
// If the hook is not attached to the player, constantly copy |
|
// copy the target's velocity. Velocity copying DOES NOT work properly |
|
// for a hooked client. |
|
else |
|
pev->velocity = pEnemy->pev->velocity; |
|
|
|
pev->nextthink = gpGlobals->time + 0.1f; |
|
} |
|
|
|
void CBasePlayer::Service_Grapple() |
|
{ |
|
Vector hook_dir; |
|
CBaseEntity *pEnemy = CBaseEntity::Instance( pev->enemy ); |
|
|
|
// drop the hook if player lets go of button |
|
if( !( pev->button & IN_ATTACK ) ) |
|
{ |
|
if( m_iQuakeWeapon == IT_EXTRA_WEAPON ) |
|
{ |
|
m_ppHook->Reset_Grapple(); |
|
return; |
|
} |
|
} |
|
|
|
if( m_ppHook->pev->enemy != NULL ) |
|
{ |
|
// If hooked to a player, track them directly! |
|
if( pEnemy->IsPlayer() ) |
|
{ |
|
pEnemy = CBaseEntity::Instance( pev->enemy ); |
|
hook_dir = ( pEnemy->pev->origin - pev->origin ); |
|
} |
|
// else, track to hook |
|
else |
|
hook_dir = ( m_ppHook->pev->origin - pev->origin ); |
|
|
|
pev->speed = 750; |
|
pev->velocity = ( hook_dir.Normalize() * pev->speed ); |
|
|
|
if( m_ppHook->m_flNextIdleTime <= gpGlobals->time && hook_dir.Length() <= 50 ) |
|
{ |
|
//No sparks underwater |
|
if( m_ppHook->pev->waterlevel == 0 ) |
|
UTIL_Sparks( m_ppHook->pev->origin ); |
|
|
|
STOP_SOUND( edict(), CHAN_WEAPON, "weapons/grpull.wav" ); |
|
EMIT_SOUND( ENT( m_ppHook->pev ), CHAN_WEAPON, "weapons/grhang.wav", 1, ATTN_NORM ); |
|
|
|
m_ppHook->m_flNextIdleTime = gpGlobals->time + RANDOM_LONG( 1, 3 ); |
|
|
|
PLAYBACK_EVENT_FULL( FEV_GLOBAL | FEV_RELIABLE, |
|
edict(), g_usCable, 0, g_vecZero, g_vecZero, |
|
0.0, 0.0, m_ppHook->entindex(), pev->team, 1, 0 ); |
|
} |
|
else if( m_ppHook->m_flNextIdleTime <= gpGlobals->time ) |
|
{ |
|
// No sparks underwater |
|
if( m_ppHook->pev->waterlevel == 0 ) |
|
UTIL_Sparks( m_ppHook->pev->origin ); |
|
|
|
STOP_SOUND( edict(), CHAN_WEAPON, "weapons/grfire.wav" ); |
|
EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "weapons/grpull.wav", 1, ATTN_NORM ); |
|
m_ppHook->m_flNextIdleTime = gpGlobals->time + RANDOM_LONG( 1, 3 ); |
|
} |
|
} |
|
} |
|
|
|
void CGrapple::OnAirThink() |
|
{ |
|
TraceResult tr; |
|
|
|
CBaseEntity *pOwner = CBaseEntity::Instance( pev->owner ); |
|
|
|
if( !( pOwner->pev->button & IN_ATTACK ) ) |
|
{ |
|
Reset_Grapple(); |
|
return; |
|
} |
|
|
|
UTIL_TraceLine( pev->origin, pOwner->pev->origin, ignore_monsters, ENT( pev ), &tr ); |
|
|
|
if( tr.flFraction < 1.0f ) |
|
{ |
|
Reset_Grapple(); |
|
return; |
|
} |
|
|
|
pev->nextthink = gpGlobals->time + 0.5f; |
|
} |
|
|
|
void CGrapple::Spawn() |
|
{ |
|
pev->movetype = MOVETYPE_FLYMISSILE; |
|
pev->solid = SOLID_BBOX; |
|
|
|
SET_MODEL( ENT( pev ),"models/hook.mdl" ); |
|
|
|
SetTouch( &CGrapple::GrappleTouch ); |
|
SetThink( &CGrapple::OnAirThink ); |
|
|
|
pev->nextthink = gpGlobals->time + 0.1f; |
|
} |
|
|
|
LINK_ENTITY_TO_CLASS( hook, CGrapple ); |
|
|
|
void CBasePlayer::Throw_Grapple() |
|
{ |
|
if( m_bHook_Out ) |
|
return; |
|
|
|
CGrapple *pHookCBEnt = NULL; |
|
pHookCBEnt = (CGrapple*)CBaseEntity::Create( "hook", pev->origin, pev->angles, NULL ); |
|
|
|
if( pHookCBEnt ) |
|
{ |
|
m_ppHook = pHookCBEnt; |
|
|
|
m_ppHook->pev->owner = edict(); |
|
|
|
UTIL_MakeVectors( pev->v_angle ); |
|
|
|
UTIL_SetOrigin( m_ppHook->pev, pev->origin + gpGlobals->v_forward * 16 + Vector( 0, 0, 16 ) ); |
|
UTIL_SetSize( m_ppHook->pev, g_vecZero, g_vecZero ); |
|
|
|
EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "weapons/grfire.wav", 1, ATTN_NORM ); |
|
|
|
// Make if fly forward |
|
m_ppHook->pev->velocity = gpGlobals->v_forward * 1000; |
|
// And make the hook face forward too! |
|
m_ppHook->pev->angles = UTIL_VecToAngles( gpGlobals->v_forward ); |
|
m_ppHook->pev->fixangle = TRUE; |
|
|
|
PLAYBACK_EVENT_FULL( FEV_GLOBAL | FEV_RELIABLE, |
|
edict(), g_usCable, 0, g_vecZero, g_vecZero, |
|
0.0, 0.0, m_ppHook->entindex(), pev->team, 0, 0 ); |
|
|
|
m_bHook_Out = TRUE; |
|
} |
|
}
|
|
|