Portable Half-Life SDK. GoldSource and Xash3D. Crossplatform.
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.

725 lines
17 KiB

/***
*
* Copyright (c) 1996-2001, 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.
*
****/
#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "monsters.h"
#include "weapons.h"
#include "nodes.h"
#include "player.h"
#include "gamerules.h"
#ifndef CLIENT_DLL
#include "grapple_tonguetip.h"
#endif
LINK_ENTITY_TO_CLASS(weapon_grapple, CGrapple);
enum bgrap_e {
BGRAP_BREATHE = 0,
BGRAP_LONGIDLE,
BGRAP_SHORTIDLE,
BGRAP_COUGH,
BGRAP_DOWN,
BGRAP_UP,
BGRAP_FIRE,
BGRAP_FIREWAITING,
BGRAP_FIREREACHED,
BGRAP_FIRETRAVEL,
BGRAP_FIRERELEASE,
};
//=========================================================
// Purpose:
//=========================================================
void CGrapple::Spawn()
{
Precache();
m_iId = WEAPON_GRAPPLE;
SET_MODEL(ENT(pev), "models/w_bgrap.mdl");
m_iClip = -1;
m_pBeam = NULL;
m_pTongueTip = NULL;
m_fTipHit = FALSE;
m_fPlayPullSound = FALSE;
m_iHitFlags = 0;
m_iFirestate = FIRESTATE_NONE;
m_flNextPullSoundTime = 0.0f;
FallInit();// get ready to fall down.
}
//=========================================================
// Purpose:
//=========================================================
void CGrapple::Precache(void)
{
PRECACHE_MODEL("models/v_bgrap.mdl");
PRECACHE_MODEL("models/v_bgrap_tonguetip.mdl");
PRECACHE_MODEL("models/w_bgrap.mdl");
PRECACHE_MODEL("models/p_bgrap.mdl");
PRECACHE_SOUND("weapons/alienweap_draw.wav");
PRECACHE_SOUND("weapons/bgrapple_cough.wav");
PRECACHE_SOUND("weapons/bgrapple_fire.wav");
PRECACHE_SOUND("weapons/bgrapple_impact.wav");
PRECACHE_SOUND("weapons/bgrapple_pull.wav");
PRECACHE_SOUND("weapons/bgrapple_release.wav");
PRECACHE_SOUND("weapons/bgrapple_wait.wav");
PRECACHE_MODEL("sprites/smoke_blk.spr");
}
//=========================================================
// Purpose:
//=========================================================
int CGrapple::GetItemInfo(ItemInfo *p)
{
p->pszName = STRING(pev->classname);
p->pszAmmo1 = NULL;
p->iMaxAmmo1 = -1;
p->pszAmmo2 = NULL;
p->iMaxAmmo2 = -1;
p->iMaxClip = WEAPON_GRAPPLE;
p->iSlot = 0;
p->iPosition = 3;
p->iId = WEAPON_GRAPPLE;
p->iWeight = GRAPPLE_WEIGHT;
return 1;
}
//=========================================================
// Purpose:
//=========================================================
BOOL CGrapple::Deploy()
{
m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.9f;
m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.9f;
return DefaultDeploy("models/v_bgrap.mdl", "models/p_bgrap.mdl", BGRAP_UP, "bgrap");
}
//=========================================================
// Purpose:
//=========================================================
void CGrapple::Holster(int skiplocal /* = 0 */)
{
FireRelease();
m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5;
SendWeaponAnim(BGRAP_DOWN);
}
//=========================================================
// Purpose:
//=========================================================
void CGrapple::PrimaryAttack(void)
{
if (m_iFirestate != FIRESTATE_NONE)
return;
m_iFirestate = FIRESTATE_FIRE;
m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + RANDOM_FLOAT(10, 15);
m_flTimeWeaponIdle = UTIL_WeaponTimeBase();
}
//=========================================================
// Purpose: ``
//=========================================================
void CGrapple::ItemPostFrame(void)
{
if (!(m_pPlayer->pev->button & IN_ATTACK))
{
m_flLastFireTime = 0.0f;
}
if (m_iFirestate != FIRESTATE_NONE)
{
if (m_fTipHit)
{
Pull();
}
// Check if fire is still eligible.
CheckFireEligibility();
// Check if the current tip is attached to a monster or a wall.
// If the tongue tip move type is MOVETYPE_FOLLOW, then it
// implies that we are targetting a monster, so it will kill
// the monster when close enough.
CheckTargetProximity();
// Update the tip velocity and position.
UpdateTongueTip();
// Update the tongue beam.
UpdateBeam();
// Update the pull sound.
UpdatePullSound();
}
if ((m_pPlayer->pev->button & IN_ATTACK) && CanAttack(m_flNextPrimaryAttack, gpGlobals->time, UseDecrement()))
{
if ((m_iClip == 0 && pszAmmo1()) || (iMaxClip() == -1 && !m_pPlayer->m_rgAmmo[PrimaryAmmoIndex()]))
{
m_fFireOnEmpty = TRUE;
}
#ifndef CLIENT_DLL
m_pPlayer->TabulateAmmo();
#endif
PrimaryAttack();
m_pPlayer->pev->button &= ~IN_ATTACK;
}
else if (!(m_pPlayer->pev->button & (IN_ATTACK | IN_ATTACK2)))
{
if (m_iFirestate != FIRESTATE_NONE)
{
FireRelease();
}
#ifndef CLIENT_DLL
// no fire buttons down
m_fFireOnEmpty = FALSE;
if (!IsUseable() && m_flNextPrimaryAttack < (UseDecrement() ? 0.0 : gpGlobals->time))
{
// weapon isn't useable, switch.
if (!(iFlags() & ITEM_FLAG_NOAUTOSWITCHEMPTY) && g_pGameRules->GetNextBestWeapon(m_pPlayer, this))
{
m_flNextPrimaryAttack = (UseDecrement() ? 0.0 : gpGlobals->time) + 0.3;
return;
}
}
#endif
WeaponIdle();
return;
}
// catch all
if (ShouldWeaponIdle())
{
WeaponIdle();
}
}
//=========================================================
// Purpose:
//=========================================================
void CGrapple::WeaponIdle(void)
{
ResetEmptySound();
if (m_flTimeWeaponIdle > UTIL_WeaponTimeBase())
return;
if (m_iFirestate != FIRESTATE_NONE)
{
switch (m_iFirestate)
{
case FIRESTATE_FIRE:
Fire();
break;
case FIRESTATE_FIRE2:
Fire2();
break;
case FIRESTATE_WAIT:
FireWait();
break;
case FIRESTATE_REACH:
FireReach();
break;
case FIRESTATE_TRAVEL:
FireTravel();
break;
case FIRESTATE_RELEASE:
FireRelease();
break;
default:
break;
}
return;
}
int iAnim;
float flRand = UTIL_SharedRandomFloat(m_pPlayer->random_seed, 0, 1);
if (flRand <= 0.8f)
{
iAnim = BGRAP_BREATHE;
m_flTimeWeaponIdle = 2.6f;
}
else if (flRand <= 0.7f)
{
iAnim = BGRAP_LONGIDLE;
m_flTimeWeaponIdle = 10.0f;
}
else if (flRand <= 0.95)
{
iAnim = BGRAP_SHORTIDLE;
m_flTimeWeaponIdle = 1.3f;
}
else
{
iAnim = BGRAP_COUGH;
m_flTimeWeaponIdle = 4.6f;
EMIT_SOUND(ENT(pev), CHAN_BODY, "weapons/bgrapple_cough.wav", 1, ATTN_NORM);
}
SendWeaponAnim(iAnim, UseDecrement());
}
//=========================================================
// Purpose:
//=========================================================
void CGrapple::Fire(void)
{
EMIT_SOUND(ENT(pev), CHAN_BODY, "weapons/bgrapple_fire.wav", 1, ATTN_NORM);
SendWeaponAnim(BGRAP_FIRE, UseDecrement());
m_iFirestate = FIRESTATE_FIRE2;
m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + RANDOM_FLOAT(15, 20);
m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.45f;
}
//=========================================================
// Purpose:
//=========================================================
void CGrapple::Fire2(void)
{
// Create the tongue tip.
CreateTongueTip();
// Start the pull sound.
StartPullSound();
m_iFirestate = FIRESTATE_WAIT;
m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + RANDOM_FLOAT(15, 20);
m_flTimeWeaponIdle = UTIL_WeaponTimeBase();
}
//=========================================================
// Purpose:
//=========================================================
void CGrapple::FireWait(void)
{
SendWeaponAnim(BGRAP_FIREWAITING, UseDecrement());
m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + RANDOM_FLOAT(15, 20);
m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.5f;
}
//=========================================================
// Purpose:
//=========================================================
void CGrapple::FireReach(void)
{
SendWeaponAnim(BGRAP_FIREREACHED, UseDecrement());
m_iFirestate = FIRESTATE_TRAVEL;
// Start pulling the owner toward tongue tip.
StartPull();
m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + RANDOM_FLOAT(15, 20);
//m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 4.7;
m_flTimeWeaponIdle = UTIL_WeaponTimeBase();
}
//=========================================================
// Purpose:
//=========================================================
void CGrapple::FireTravel(void)
{
SendWeaponAnim(BGRAP_FIRETRAVEL, UseDecrement());
m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + RANDOM_FLOAT(15, 20);
m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.6f;
}
//=========================================================
// Purpose:
//=========================================================
void CGrapple::FireRelease(void)
{
// Stop pull sound.
// STOP_SOUND(ENT(pev), CHAN_VOICE, "weapons/bgrapple_pull.wav");
// Play release sound.
EMIT_SOUND(ENT(pev), CHAN_BODY, "weapons/bgrapple_release.wav", 1, ATTN_NORM);
SendWeaponAnim(BGRAP_FIRERELEASE, UseDecrement());
m_iFirestate = FIRESTATE_NONE;
// Stop the pulling.
StopPull();
// Reset pull sound.
ResetPullSound();
// Destroy the tongue tip.
DestroyTongueTip();
m_fTipHit = FALSE;
m_iHitFlags = 0;
m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.75f;
m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.1;
}
//=========================================================
// Purpose:
//=========================================================
void CGrapple::OnTongueTipHitSurface(const Vector& vecTarget)
{
#ifndef CLIENT_DLL
ALERT(at_console, "Hit surface at: (%f,%f,%f).\n", vecTarget.x, vecTarget.y, vecTarget.z);
#endif
FireReach();
}
//=========================================================
// Purpose:
//=========================================================
void CGrapple::OnTongueTipHitEntity(CBaseEntity* pEntity)
{
#ifndef CLIENT_DLL
ALERT(at_console, "Hit entity with classname %s.\n", (char*)STRING(pEntity->pev->classname));
#endif
FireReach();
}
//=========================================================
// Purpose:
//=========================================================
void CGrapple::StartPull(void)
{
#ifndef CLIENT_DLL
Vector vecOrigin = m_pPlayer->pev->origin;
vecOrigin.z++;
UTIL_SetOrigin(m_pPlayer->pev, vecOrigin);
m_pPlayer->pev->movetype = MOVETYPE_FLY;
m_pPlayer->pev->gravity = 0.0f;
m_pPlayer->pev->flags &= ~FL_ONGROUND;
m_pPlayer->m_afPhysicsFlags |= PFLAG_LATCHING;
#endif
}
//=========================================================
// Purpose:
//=========================================================
void CGrapple::StopPull(void)
{
#ifndef CLIENT_DLL
m_pPlayer->pev->movetype = MOVETYPE_WALK;
m_pPlayer->pev->gravity = 1.0f;
m_pPlayer->m_afPhysicsFlags &= ~PFLAG_LATCHING;
#endif
}
//=========================================================
// Purpose:
//=========================================================
void CGrapple::Pull( void )
{
#ifndef CLIENT_DLL
Vector vecOwnerPos, vecTipPos, vecDirToTip;
vecOwnerPos = m_pPlayer->pev->origin;
vecTipPos = m_pTongueTip->pev->origin;
vecDirToTip = (vecTipPos - vecOwnerPos).Normalize();
m_pPlayer->pev->velocity = vecDirToTip * 300;
m_pPlayer->m_afPhysicsFlags |= PFLAG_LATCHING;
#endif
}
//=========================================================
// Purpose:
//=========================================================
void CGrapple::CreateTongueTip( void )
{
#ifndef CLIENT_DLL
Vector vecSrc, vecAiming, vecVel;
UTIL_MakeVectors( m_pPlayer->pev->v_angle );
vecSrc = m_pPlayer->GetGunPosition();
vecSrc = vecSrc + gpGlobals->v_forward * 8;
vecSrc = vecSrc + gpGlobals->v_right * 8;
vecSrc = vecSrc + gpGlobals->v_up;
vecVel = gpGlobals->v_forward * 300;
//GetAttachment(0, vecSrc, vecAiming);
m_pTongueTip = CGrappleTonguetip::CreateTip(pev, vecSrc, vecVel);
CreateBeam(m_pTongueTip);
#endif
}
//=========================================================
// Purpose:
//=========================================================
void CGrapple::DestroyTongueTip(void)
{
#ifndef CLIENT_DLL
DestroyBeam();
if (m_pTongueTip)
{
UTIL_Remove(m_pTongueTip);
m_pTongueTip = NULL;
}
#endif
}
//=========================================================
// Purpose:
//=========================================================
void CGrapple::UpdateTongueTip(void)
{
#ifndef CLIENT_DLL
if (m_pTongueTip)
{
Vector tipVel = m_pTongueTip->pev->velocity;
Vector ownerVel = VARS(pev->owner)->velocity;
Vector newVel;
newVel.x = tipVel.x + (ownerVel.x * 0.02f);
newVel.y = tipVel.y + (ownerVel.y * 0.05f);
newVel.z = tipVel.z + (ownerVel.z * 0.01f);
m_pTongueTip->pev->velocity = newVel;
}
#endif
}
//=========================================================
// Purpose:
//=========================================================
void CGrapple::CreateBeam( CBaseEntity* pTongueTip )
{
#ifndef CLIENT_DLL
m_pBeam = CBeam::BeamCreate("sprites/smoke_blk.spr", 16);
if (m_pBeam)
{
m_pBeam->PointsInit(pTongueTip->pev->origin, pev->origin);
m_pBeam->SetColor(255, 255, 255);
m_pBeam->SetBrightness(255);
m_pBeam->SetNoise(0);
m_pBeam->SetFrame(0);
m_pBeam->pev->rendermode = kRenderNormal;
m_pBeam->pev->renderamt = 255;
}
#endif
}
//=========================================================
// Purpose:
//=========================================================
void CGrapple::DestroyBeam(void)
{
#ifndef CLIENT_DLL
if (m_pBeam)
{
UTIL_Remove(m_pBeam);
m_pBeam = NULL;
}
#endif
}
//=========================================================
// Purpose:
//=========================================================
void CGrapple::UpdateBeam(void)
{
#ifndef CLIENT_DLL
if (m_pBeam)
{
Vector vecSrc;
UTIL_MakeVectors(m_pPlayer->pev->v_angle);
vecSrc = m_pPlayer->GetGunPosition();
vecSrc = vecSrc + gpGlobals->v_forward * 16;
vecSrc = vecSrc + gpGlobals->v_right * 6;
vecSrc = vecSrc + gpGlobals->v_up * -8;
m_pBeam->SetStartPos(vecSrc);
m_pBeam->SetEndPos(m_pTongueTip->pev->origin);
m_pBeam->RelinkBeam();
}
#endif
}
//=========================================================
// Purpose:
//=========================================================
BOOL CGrapple::CanAttack(float attack_time, float curtime, BOOL isPredicted)
{
#if defined( CLIENT_WEAPONS )
if (!isPredicted)
#else
if (1)
#endif
{
return (attack_time <= curtime) ? TRUE : FALSE;
}
else
{
return (attack_time <= 0.0) ? TRUE : FALSE;
}
}
void CGrapple::StartPullSound(void)
{
m_flNextPullSoundTime = UTIL_WeaponTimeBase();
m_fPlayPullSound = TRUE;
}
void CGrapple::UpdatePullSound(void)
{
if (!m_fPlayPullSound)
return;
if (m_flNextPullSoundTime <= UTIL_WeaponTimeBase())
{
EMIT_SOUND(ENT(pev), CHAN_BODY, "weapons/bgrapple_pull.wav", 1, ATTN_NORM);
m_flNextPullSoundTime = UTIL_WeaponTimeBase() + 0.6f;
}
}
void CGrapple::ResetPullSound(void)
{
STOP_SOUND(ENT(pev), CHAN_BODY, "weapons/bgrapple_pull.wav");
m_flNextPullSoundTime = 0.0f;
m_fPlayPullSound = FALSE;
}
BOOL CGrapple::IsTongueColliding(const Vector& vecShootOrigin, const Vector& vecTipPos)
{
#ifndef CLIENT_DLL
TraceResult tr;
UTIL_TraceLine(vecShootOrigin, vecTipPos, dont_ignore_monsters, ENT(m_pPlayer->pev), &tr);
if (tr.flFraction != 1.0)
{
if (m_pTongueTip->pev->movetype == MOVETYPE_FOLLOW)
{
if (tr.pHit && m_pTongueTip->pev->aiment && m_pTongueTip->pev->aiment == tr.pHit)
return FALSE;
}
return TRUE;
}
return FALSE;
#else
return TRUE;
#endif
}
void CGrapple::CheckFireEligibility(void)
{
#ifndef CLIENT_DLL
// Do not check for this if the tongue is not valid.
if (!m_pTongueTip)
return;
// Check if the tongue is through walls or if other things
// such as entities are between the owner and the tip.
if (IsTongueColliding(m_pPlayer->GetGunPosition(), m_pTongueTip->pev->origin))
{
// Stop firing!
FireRelease();
}
#endif
}
BOOL CGrapple::CheckTargetProximity(void)
{
#ifndef CLIENT_DLL
// Do not check for this if the tongue is not valid.
if (!m_pTongueTip)
return FALSE;
if (m_pTongueTip->pev->movetype == MOVETYPE_FOLLOW)
{
// Trace a hull around to see if we hit something within 20 units.
TraceResult tr;
UTIL_TraceHull(
pev->origin,
pev->origin + VARS(pev->owner)->velocity.Normalize() * 20,
dont_ignore_monsters,
head_hull,
edict(),
&tr);
// Check to see if we are close enough to our target.
// In the case o a monster, attempt to get a pointer to
// the entity, gib it, release grappler fire.
edict_t* pHit = ENT(tr.pHit);
if ( pHit &&
(VARS(pHit)->flags & FL_MONSTER) &&
(m_pTongueTip->pev->aiment == tr.pHit))
{
// Get a pointer to the entity.
CBaseEntity* pEnt = GetClassPtr((CBaseEntity*)VARS(tr.pHit));
if (pEnt)
{
// Since FL_MONSTER is set, it can only be a monster entity.
ASSERT(pEnt->MyMonsterPointer() != NULL);
// Gib monster.
((CBaseMonster*)pEnt)->GibMonster();
}
else
{
ALERT(at_console, "ERROR: %s attempted to reference an unlisted monster.\n", STRING(m_pTongueTip->pev->classname));
}
// Release fire.
FireRelease();
}
}
#endif
return FALSE;
}