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.
575 lines
14 KiB
575 lines
14 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. |
|
* |
|
****/ |
|
#if !OEM_BUILD |
|
|
|
#include "extdll.h" |
|
#include "util.h" |
|
#include "cbase.h" |
|
#include "monsters.h" |
|
#include "weapons.h" |
|
#include "nodes.h" |
|
#include "player.h" |
|
#include "gamerules.h" |
|
|
|
enum rpg_e |
|
{ |
|
RPG_IDLE = 0, |
|
RPG_DRAW1, |
|
RPG_AIMED, |
|
RPG_LAUNCH, |
|
RPG_DOWN_TO_UP, |
|
RPG_UP_TO_DOWN, |
|
RPG_RELOAD_AIMED, |
|
RPG_RELOAD_IDLE |
|
}; |
|
|
|
LINK_ENTITY_TO_CLASS( weapon_rpg, CRpg ) |
|
|
|
#if !CLIENT_DLL |
|
|
|
LINK_ENTITY_TO_CLASS( laser_spot, CLaserSpot ) |
|
|
|
//========================================================= |
|
//========================================================= |
|
CLaserSpot *CLaserSpot::CreateSpot( void ) |
|
{ |
|
CLaserSpot *pSpot = GetClassPtr( (CLaserSpot *)NULL ); |
|
pSpot->Spawn(); |
|
|
|
pSpot->pev->classname = MAKE_STRING( "laser_spot" ); |
|
|
|
return pSpot; |
|
} |
|
|
|
//========================================================= |
|
//========================================================= |
|
void CLaserSpot::Spawn( void ) |
|
{ |
|
Precache(); |
|
pev->movetype = MOVETYPE_NONE; |
|
pev->solid = SOLID_NOT; |
|
|
|
pev->rendermode = kRenderGlow; |
|
pev->renderfx = kRenderFxNoDissipation; |
|
pev->renderamt = 255; |
|
|
|
SET_MODEL( ENT( pev ), "sprites/laserdot.spr" ); |
|
UTIL_SetOrigin( pev, pev->origin ); |
|
} |
|
|
|
//========================================================= |
|
// Suspend- make the laser sight invisible. |
|
//========================================================= |
|
void CLaserSpot::Suspend( float flSuspendTime ) |
|
{ |
|
pev->effects |= EF_NODRAW; |
|
|
|
SetThink( &CLaserSpot::Revive ); |
|
pev->nextthink = gpGlobals->time + flSuspendTime; |
|
} |
|
|
|
//========================================================= |
|
// Revive - bring a suspended laser sight back. |
|
//========================================================= |
|
void CLaserSpot::Revive( void ) |
|
{ |
|
pev->effects &= ~EF_NODRAW; |
|
|
|
SetThink( NULL ); |
|
} |
|
|
|
void CLaserSpot::Precache( void ) |
|
{ |
|
PRECACHE_MODEL( "sprites/laserdot.spr" ); |
|
} |
|
|
|
LINK_ENTITY_TO_CLASS( rpg_rocket, CRpgRocket ) |
|
|
|
//========================================================= |
|
//========================================================= |
|
CRpgRocket *CRpgRocket::CreateRpgRocket( Vector vecOrigin, Vector vecAngles, CBaseEntity *pOwner, CRpg *pLauncher ) |
|
{ |
|
CRpgRocket *pRocket = GetClassPtr( (CRpgRocket *)NULL ); |
|
|
|
UTIL_SetOrigin( pRocket->pev, vecOrigin ); |
|
pRocket->pev->angles = vecAngles; |
|
pRocket->Spawn(); |
|
pRocket->SetTouch( &CRpgRocket::RocketTouch ); |
|
pRocket->m_hLauncher = pLauncher;// remember what RPG fired me. |
|
pLauncher->m_cActiveRockets++;// register this missile as active for the launcher |
|
pRocket->pev->owner = pOwner->edict(); |
|
|
|
return pRocket; |
|
} |
|
|
|
//========================================================= |
|
//========================================================= |
|
void CRpgRocket::Spawn( void ) |
|
{ |
|
Precache(); |
|
// motor |
|
pev->movetype = MOVETYPE_BOUNCE; |
|
pev->solid = SOLID_BBOX; |
|
|
|
SET_MODEL( ENT( pev ), "models/rpgrocket.mdl" ); |
|
UTIL_SetSize( pev, Vector( 0, 0, 0 ), Vector( 0, 0, 0 ) ); |
|
UTIL_SetOrigin( pev, pev->origin ); |
|
|
|
pev->classname = MAKE_STRING( "rpg_rocket" ); |
|
|
|
SetThink( &CRpgRocket::IgniteThink ); |
|
SetTouch( &CRpgRocket::ExplodeTouch ); |
|
|
|
pev->angles.x -= 1; |
|
UTIL_MakeVectors( pev->angles ); |
|
pev->angles.x = -( pev->angles.x + 1 ); |
|
|
|
pev->velocity = gpGlobals->v_forward * 250.0f; |
|
pev->gravity = 0.0f; |
|
|
|
pev->nextthink = gpGlobals->time + 0.05f; |
|
|
|
pev->dmg = gSkillData.plrDmgRPG; |
|
} |
|
|
|
//========================================================= |
|
//========================================================= |
|
void CRpgRocket::RocketTouch( CBaseEntity *pOther ) |
|
{ |
|
if( CRpg* pLauncher = (CRpg*)( (CBaseEntity*)( m_hLauncher ) ) ) |
|
{ |
|
// my launcher is still around, tell it I'm dead. |
|
pLauncher->m_cActiveRockets--; |
|
} |
|
|
|
STOP_SOUND( edict(), CHAN_VOICE, "weapons/rocket1.wav" ); |
|
ExplodeTouch( pOther ); |
|
} |
|
|
|
//========================================================= |
|
//========================================================= |
|
void CRpgRocket::Precache( void ) |
|
{ |
|
PRECACHE_MODEL( "models/rpgrocket.mdl" ); |
|
m_iTrail = PRECACHE_MODEL( "sprites/smoke.spr" ); |
|
PRECACHE_SOUND( "weapons/rocket1.wav" ); |
|
} |
|
|
|
void CRpgRocket::IgniteThink( void ) |
|
{ |
|
// pev->movetype = MOVETYPE_TOSS; |
|
|
|
pev->movetype = MOVETYPE_FLY; |
|
pev->effects |= EF_LIGHT; |
|
|
|
// make rocket sound |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "weapons/rocket1.wav", 1, 0.5f ); |
|
|
|
// rocket trail |
|
MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); |
|
WRITE_BYTE( TE_BEAMFOLLOW ); |
|
WRITE_SHORT( entindex() ); // entity |
|
WRITE_SHORT( m_iTrail ); // model |
|
WRITE_BYTE( 40 ); // life |
|
WRITE_BYTE( 5 ); // width |
|
WRITE_BYTE( 224 ); // r, g, b |
|
WRITE_BYTE( 224 ); // r, g, b |
|
WRITE_BYTE( 255 ); // r, g, b |
|
WRITE_BYTE( 255 ); // brightness |
|
MESSAGE_END(); // move PHS/PVS data sending into here (SEND_ALL, SEND_PVS, SEND_PHS) |
|
|
|
m_flIgniteTime = gpGlobals->time; |
|
|
|
// Move in forward direction. Ignore guiding. |
|
SetThink( &CRpgRocket::FlyThink ); |
|
pev->nextthink = gpGlobals->time + 0.1f; |
|
} |
|
|
|
void CRpgRocket::FlyThink( void ) |
|
{ |
|
Vector vecTarget; |
|
|
|
UTIL_MakeAimVectors( pev->angles ); |
|
|
|
vecTarget = gpGlobals->v_forward; |
|
|
|
pev->angles = UTIL_VecToAngles( vecTarget ); |
|
|
|
// this acceleration and turning math is totally wrong, but it seems to respond well so don't change it. |
|
float flSpeed = pev->velocity.Length(); |
|
if( gpGlobals->time - m_flIgniteTime < 1.0f ) |
|
{ |
|
pev->velocity = pev->velocity * 0.2f + vecTarget * ( flSpeed * 0.8f + 400.0f ); |
|
if( pev->waterlevel == 3 ) |
|
{ |
|
// go slow underwater |
|
if( pev->velocity.Length() > 300 ) |
|
{ |
|
pev->velocity = pev->velocity.Normalize() * 300; |
|
} |
|
UTIL_BubbleTrail( pev->origin - pev->velocity * 0.1f, pev->origin, 4 ); |
|
} |
|
else |
|
{ |
|
if( pev->velocity.Length() > 2000 ) |
|
{ |
|
pev->velocity = pev->velocity.Normalize() * 2000; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
if( pev->effects & EF_LIGHT ) |
|
{ |
|
pev->effects = 0; |
|
STOP_SOUND( ENT( pev ), CHAN_VOICE, "weapons/rocket1.wav" ); |
|
} |
|
pev->velocity = pev->velocity * 0.2f + vecTarget * flSpeed * 0.798f; |
|
if( pev->waterlevel == 0 && pev->velocity.Length() < 1500 ) |
|
{ |
|
Detonate(); |
|
} |
|
} |
|
// ALERT( at_console, "%.0f\n", flSpeed ); |
|
|
|
pev->nextthink = gpGlobals->time + 0.1f; |
|
} |
|
|
|
void CRpgRocket::FollowThink( void ) |
|
{ |
|
CBaseEntity *pOther = NULL; |
|
Vector vecTarget; |
|
Vector vecDir; |
|
float flDist, flMax, flDot; |
|
TraceResult tr; |
|
|
|
UTIL_MakeAimVectors( pev->angles ); |
|
|
|
vecTarget = gpGlobals->v_forward; |
|
flMax = 4096; |
|
|
|
// Examine all entities within a reasonable radius |
|
while( ( pOther = UTIL_FindEntityByClassname( pOther, "laser_spot" ) ) != NULL ) |
|
{ |
|
UTIL_TraceLine( pev->origin, pOther->pev->origin, dont_ignore_monsters, ENT( pev ), &tr ); |
|
// ALERT( at_console, "%f\n", tr.flFraction ); |
|
if( tr.flFraction >= 0.9f ) |
|
{ |
|
vecDir = pOther->pev->origin - pev->origin; |
|
flDist = vecDir.Length(); |
|
vecDir = vecDir.Normalize(); |
|
flDot = DotProduct( gpGlobals->v_forward, vecDir ); |
|
if( ( flDot > 0 ) && ( flDist * ( 1 - flDot ) < flMax ) ) |
|
{ |
|
flMax = flDist * ( 1 - flDot ); |
|
vecTarget = vecDir; |
|
} |
|
} |
|
} |
|
|
|
pev->angles = UTIL_VecToAngles( vecTarget ); |
|
|
|
// this acceleration and turning math is totally wrong, but it seems to respond well so don't change it. |
|
float flSpeed = pev->velocity.Length(); |
|
if( gpGlobals->time - m_flIgniteTime < 1.0f ) |
|
{ |
|
pev->velocity = pev->velocity * 0.2f + vecTarget * ( flSpeed * 0.8f + 400.0f ); |
|
if( pev->waterlevel == 3 ) |
|
{ |
|
// go slow underwater |
|
if( pev->velocity.Length() > 300.0f ) |
|
{ |
|
pev->velocity = pev->velocity.Normalize() * 300.0f; |
|
} |
|
UTIL_BubbleTrail( pev->origin - pev->velocity * 0.1f, pev->origin, 4 ); |
|
} |
|
else |
|
{ |
|
if( pev->velocity.Length() > 2000.0f ) |
|
{ |
|
pev->velocity = pev->velocity.Normalize() * 2000.0f; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
if( pev->effects & EF_LIGHT ) |
|
{ |
|
pev->effects = 0; |
|
STOP_SOUND( ENT( pev ), CHAN_VOICE, "weapons/rocket1.wav" ); |
|
} |
|
pev->velocity = pev->velocity * 0.2f + vecTarget * flSpeed * 0.798f; |
|
if( pev->waterlevel == 0 && pev->velocity.Length() < 1500.0f ) |
|
{ |
|
if( CRpg *pLauncher = (CRpg*)( (CBaseEntity*)( m_hLauncher ) ) ) |
|
{ |
|
// my launcher is still around, tell it I'm dead. |
|
pLauncher->m_cActiveRockets--; |
|
} |
|
Detonate(); |
|
} |
|
} |
|
// ALERT( at_console, "%.0f\n", flSpeed ); |
|
|
|
pev->nextthink = gpGlobals->time + 0.1f; |
|
} |
|
#endif |
|
|
|
void CRpg::Reload( void ) |
|
{ |
|
int iResult = 0; |
|
|
|
// don't bother with any of this if don't need to reload. |
|
if( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0 || m_iClip == RPG_MAX_CLIP ) |
|
return; |
|
|
|
// because the RPG waits to autoreload when no missiles are active while the LTD is on, the |
|
// weapons code is constantly calling into this function, but is often denied because |
|
// a) missiles are in flight, but the LTD is on |
|
// or |
|
// b) player is totally out of ammo and has nothing to switch to, and should be allowed to |
|
// shine the designator around |
|
// |
|
// Set the next attack time into the future so that WeaponIdle will get called more often |
|
// than reload, allowing the RPG LTD to be updated |
|
|
|
m_flNextPrimaryAttack = GetNextAttackDelay( 0.5f ); |
|
|
|
if( m_iClip == 0 ) |
|
iResult = DefaultReload( RPG_MAX_CLIP, RPG_RELOAD_IDLE, 3.5 ); |
|
|
|
if( iResult ) |
|
m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); |
|
} |
|
|
|
void CRpg::Spawn() |
|
{ |
|
Precache(); |
|
m_iId = WEAPON_RPG; |
|
|
|
SET_MODEL( ENT( pev ), "models/w_rpg.mdl" ); |
|
m_fSpotActive = 0; |
|
|
|
#if CLIENT_DLL |
|
if( bIsMultiplayer() ) |
|
#else |
|
if( g_pGameRules->IsMultiplayer() ) |
|
#endif |
|
{ |
|
// more default ammo in multiplay. |
|
m_iDefaultAmmo = RPG_DEFAULT_GIVE * 2; |
|
} |
|
else |
|
{ |
|
m_iDefaultAmmo = RPG_DEFAULT_GIVE; |
|
} |
|
|
|
FallInit();// get ready to fall down. |
|
} |
|
|
|
void CRpg::Precache( void ) |
|
{ |
|
PRECACHE_MODEL( "models/w_rpg.mdl" ); |
|
PRECACHE_MODEL( "models/v_rpg.mdl" ); |
|
PRECACHE_MODEL( "models/p_rpg.mdl" ); |
|
|
|
PRECACHE_SOUND( "items/9mmclip1.wav" ); |
|
|
|
UTIL_PrecacheOther( "laser_spot" ); |
|
UTIL_PrecacheOther( "rpg_rocket" ); |
|
|
|
PRECACHE_SOUND( "weapons/rocketfire1.wav" ); |
|
PRECACHE_SOUND( "weapons/glauncher.wav" ); // alternative fire sound |
|
|
|
m_usRpg = PRECACHE_EVENT( 1, "events/rpg.sc" ); |
|
} |
|
|
|
int CRpg::GetItemInfo( ItemInfo *p ) |
|
{ |
|
p->pszName = STRING( pev->classname ); |
|
p->pszAmmo1 = "rockets"; |
|
p->iMaxAmmo1 = ROCKET_MAX_CARRY; |
|
p->pszAmmo2 = NULL; |
|
p->iMaxAmmo2 = -1; |
|
p->iMaxClip = RPG_MAX_CLIP; |
|
p->iSlot = 3; |
|
p->iPosition = 0; |
|
p->iId = m_iId = WEAPON_RPG; |
|
p->iFlags = 0; |
|
p->iWeight = RPG_WEIGHT; |
|
|
|
return 1; |
|
} |
|
|
|
int CRpg::AddToPlayer( CBasePlayer *pPlayer ) |
|
{ |
|
if( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) |
|
{ |
|
MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); |
|
WRITE_BYTE( m_iId ); |
|
MESSAGE_END(); |
|
return TRUE; |
|
} |
|
return FALSE; |
|
} |
|
|
|
BOOL CRpg::Deploy() |
|
{ |
|
return DefaultDeploy( "models/v_rpg.mdl", "models/p_rpg.mdl", RPG_DRAW1, "rpg" ); |
|
} |
|
|
|
BOOL CRpg::CanHolster( void ) |
|
{ |
|
return TRUE; |
|
} |
|
|
|
void CRpg::Holster( int skiplocal /* = 0 */ ) |
|
{ |
|
m_fInReload = FALSE;// cancel any reload in progress. |
|
|
|
m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5f; |
|
} |
|
|
|
void CRpg::PrimaryAttack() |
|
{ |
|
if( m_iClip ) |
|
{ |
|
m_pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; |
|
m_pPlayer->m_iWeaponFlash = BRIGHT_GUN_FLASH; |
|
|
|
#if !CLIENT_DLL |
|
// player "shoot" animation |
|
m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); |
|
|
|
UTIL_MakeVectors( m_pPlayer->pev->v_angle ); |
|
Vector vecSrc = m_pPlayer->GetGunPosition() + gpGlobals->v_forward * 16.0f + gpGlobals->v_right * 8.0f + gpGlobals->v_up * -8.0f; |
|
|
|
CRpgRocket *pRocket = CRpgRocket::CreateRpgRocket( vecSrc, m_pPlayer->pev->v_angle, m_pPlayer, this ); |
|
|
|
UTIL_MakeVectors( m_pPlayer->pev->v_angle );// RpgRocket::Create stomps on globals, so remake. |
|
pRocket->pev->velocity = pRocket->pev->velocity + gpGlobals->v_forward * DotProduct( m_pPlayer->pev->velocity, gpGlobals->v_forward ); |
|
#endif |
|
|
|
// firing RPG no longer turns on the designator. ALT fire is a toggle switch for the LTD. |
|
// Ken signed up for this as a global change (sjb) |
|
|
|
int flags; |
|
#if CLIENT_WEAPONS |
|
flags = FEV_NOTHOST; |
|
#else |
|
flags = 0; |
|
#endif |
|
PLAYBACK_EVENT( flags, m_pPlayer->edict(), m_usRpg ); |
|
|
|
m_iClip--; |
|
|
|
m_flNextPrimaryAttack = GetNextAttackDelay( 1.5f ); |
|
m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.5f; |
|
|
|
ResetEmptySound(); |
|
} |
|
else |
|
{ |
|
PlayEmptySound(); |
|
m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.2f; |
|
} |
|
} |
|
|
|
void CRpg::SecondaryAttack() |
|
{ |
|
} |
|
|
|
void CRpg::WeaponIdle( void ) |
|
{ |
|
if( m_flTimeWeaponIdle > UTIL_WeaponTimeBase() ) |
|
return; |
|
|
|
if( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] ) |
|
{ |
|
m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 2.0f / 20.0f; |
|
SendWeaponAnim( RPG_IDLE ); |
|
} |
|
else |
|
{ |
|
m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.0f; |
|
} |
|
} |
|
|
|
void CRpg::UpdateSpot( void ) |
|
{ |
|
#if !CLIENT_DLL |
|
if( m_fSpotActive ) |
|
{ |
|
if (m_pPlayer->pev->viewmodel == 0) |
|
return; |
|
|
|
if( !m_pSpot ) |
|
{ |
|
m_pSpot = CLaserSpot::CreateSpot(); |
|
} |
|
|
|
UTIL_MakeVectors( m_pPlayer->pev->v_angle ); |
|
Vector vecSrc = m_pPlayer->GetGunPosition(); |
|
Vector vecAiming = gpGlobals->v_forward; |
|
|
|
TraceResult tr; |
|
UTIL_TraceLine( vecSrc, vecSrc + vecAiming * 8192.0f, dont_ignore_monsters, ENT( m_pPlayer->pev ), &tr ); |
|
|
|
UTIL_SetOrigin( m_pSpot->pev, tr.vecEndPos ); |
|
} |
|
#endif |
|
} |
|
|
|
class CRpgAmmo : public CBasePlayerAmmo |
|
{ |
|
void Spawn( void ) |
|
{ |
|
Precache(); |
|
SET_MODEL( ENT( pev ), "models/w_rpgammo.mdl" ); |
|
CBasePlayerAmmo::Spawn(); |
|
} |
|
void Precache( void ) |
|
{ |
|
PRECACHE_MODEL( "models/w_rpgammo.mdl" ); |
|
PRECACHE_SOUND( "items/9mmclip1.wav" ); |
|
} |
|
BOOL AddAmmo( CBaseEntity *pOther ) |
|
{ |
|
int iGive; |
|
#if CLIENT_DLL |
|
if( bIsMultiplayer() ) |
|
#else |
|
if( g_pGameRules->IsMultiplayer() ) |
|
#endif |
|
{ |
|
// hand out more ammo per rocket in multiplayer. |
|
iGive = AMMO_RPGCLIP_GIVE * 2; |
|
} |
|
else |
|
{ |
|
iGive = AMMO_RPGCLIP_GIVE; |
|
} |
|
|
|
if( pOther->GiveAmmo( iGive, "rockets", ROCKET_MAX_CARRY ) != -1 ) |
|
{ |
|
EMIT_SOUND( ENT( pev ), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM ); |
|
return TRUE; |
|
} |
|
return FALSE; |
|
} |
|
}; |
|
|
|
LINK_ENTITY_TO_CLASS( ammo_rpgclip, CRpgAmmo ) |
|
#endif
|
|
|