/*** * * 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; }