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.
1670 lines
46 KiB
1670 lines
46 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. |
|
* |
|
****/ |
|
/* |
|
|
|
===== weapons.cpp ======================================================== |
|
|
|
functions governing the selection/use of weapons for players |
|
|
|
*/ |
|
|
|
#include "extdll.h" |
|
#include "util.h" |
|
#include "cbase.h" |
|
#include "player.h" |
|
#include "monsters.h" |
|
#include "weapons.h" |
|
#include "nodes.h" |
|
#include "soundent.h" |
|
#include "decals.h" |
|
#include "gamerules.h" |
|
#include "gearbox_weapons.h" |
|
|
|
extern CGraph WorldGraph; |
|
extern int gEvilImpulse101; |
|
|
|
#define NOT_USED 255 |
|
|
|
DLL_GLOBAL short g_sModelIndexLaser;// holds the index for the laser beam |
|
DLL_GLOBAL const char *g_pModelNameLaser = "sprites/laserbeam.spr"; |
|
DLL_GLOBAL short g_sModelIndexLaserDot;// holds the index for the laser beam dot |
|
DLL_GLOBAL short g_sModelIndexFireball;// holds the index for the fireball |
|
DLL_GLOBAL short g_sModelIndexSmoke;// holds the index for the smoke cloud |
|
DLL_GLOBAL short g_sModelIndexWExplosion;// holds the index for the underwater explosion |
|
DLL_GLOBAL short g_sModelIndexBubbles;// holds the index for the bubbles model |
|
DLL_GLOBAL short g_sModelIndexBloodDrop;// holds the sprite index for the initial blood |
|
DLL_GLOBAL short g_sModelIndexBloodSpray;// holds the sprite index for splattered blood |
|
|
|
DLL_GLOBAL short g_sModelIndexSpore1; // holds the index for the spore explosion 1 |
|
DLL_GLOBAL short g_sModelIndexSpore2; // holds the index for the spore explosion 2 |
|
DLL_GLOBAL short g_sModelIndexSpore3; // holds the index for the spore explosion 3 |
|
|
|
DLL_GLOBAL short g_sModelIndexBigSpit; // holds the index for the bullsquid big spit. |
|
DLL_GLOBAL short g_sModelIndexTinySpit; // holds the index for the bullsquid tiny spit. |
|
|
|
ItemInfo CBasePlayerItem::ItemInfoArray[MAX_WEAPONS]; |
|
AmmoInfo CBasePlayerItem::AmmoInfoArray[MAX_AMMO_SLOTS]; |
|
|
|
extern int gmsgCurWeapon; |
|
|
|
MULTIDAMAGE gMultiDamage; |
|
|
|
#define TRACER_FREQ 4 // Tracers fire every fourth bullet |
|
|
|
//========================================================= |
|
// MaxAmmoCarry - pass in a name and this function will tell |
|
// you the maximum amount of that type of ammunition that a |
|
// player can carry. |
|
//========================================================= |
|
int MaxAmmoCarry( int iszName ) |
|
{ |
|
for( int i = 0; i < MAX_WEAPONS; i++ ) |
|
{ |
|
if( CBasePlayerItem::ItemInfoArray[i].pszAmmo1 && !strcmp( STRING( iszName ), CBasePlayerItem::ItemInfoArray[i].pszAmmo1 ) ) |
|
return CBasePlayerItem::ItemInfoArray[i].iMaxAmmo1; |
|
if( CBasePlayerItem::ItemInfoArray[i].pszAmmo2 && !strcmp( STRING( iszName ), CBasePlayerItem::ItemInfoArray[i].pszAmmo2 ) ) |
|
return CBasePlayerItem::ItemInfoArray[i].iMaxAmmo2; |
|
} |
|
|
|
ALERT( at_console, "MaxAmmoCarry() doesn't recognize '%s'!\n", STRING( iszName ) ); |
|
return -1; |
|
} |
|
|
|
/* |
|
============================================================================== |
|
|
|
MULTI-DAMAGE |
|
|
|
Collects multiple small damages into a single damage |
|
|
|
============================================================================== |
|
*/ |
|
|
|
// |
|
// ClearMultiDamage - resets the global multi damage accumulator |
|
// |
|
void ClearMultiDamage( void ) |
|
{ |
|
gMultiDamage.pEntity = NULL; |
|
gMultiDamage.amount = 0; |
|
gMultiDamage.type = 0; |
|
} |
|
|
|
// |
|
// ApplyMultiDamage - inflicts contents of global multi damage register on gMultiDamage.pEntity |
|
// |
|
// GLOBALS USED: |
|
// gMultiDamage |
|
void ApplyMultiDamage( entvars_t *pevInflictor, entvars_t *pevAttacker ) |
|
{ |
|
Vector vecSpot1;//where blood comes from |
|
Vector vecDir;//direction blood should go |
|
TraceResult tr; |
|
|
|
if( !gMultiDamage.pEntity ) |
|
return; |
|
|
|
gMultiDamage.pEntity->TakeDamage( pevInflictor, pevAttacker, gMultiDamage.amount, gMultiDamage.type ); |
|
} |
|
|
|
// GLOBALS USED: |
|
// gMultiDamage |
|
void AddMultiDamage( entvars_t *pevInflictor, CBaseEntity *pEntity, float flDamage, int bitsDamageType ) |
|
{ |
|
if( !pEntity ) |
|
return; |
|
|
|
gMultiDamage.type |= bitsDamageType; |
|
|
|
if( pEntity != gMultiDamage.pEntity ) |
|
{ |
|
ApplyMultiDamage( pevInflictor,pevInflictor ); // UNDONE: wrong attacker! |
|
gMultiDamage.pEntity = pEntity; |
|
gMultiDamage.amount = 0; |
|
} |
|
|
|
gMultiDamage.amount += flDamage; |
|
} |
|
|
|
/* |
|
================ |
|
SpawnBlood |
|
================ |
|
*/ |
|
void SpawnBlood( Vector vecSpot, int bloodColor, float flDamage ) |
|
{ |
|
UTIL_BloodDrips( vecSpot, g_vecAttackDir, bloodColor, (int)flDamage ); |
|
} |
|
|
|
int DamageDecal( CBaseEntity *pEntity, int bitsDamageType ) |
|
{ |
|
if( !pEntity ) |
|
return ( DECAL_GUNSHOT1 + RANDOM_LONG( 0, 4 ) ); |
|
|
|
return pEntity->DamageDecal( bitsDamageType ); |
|
} |
|
|
|
void DecalGunshot( TraceResult *pTrace, int iBulletType ) |
|
{ |
|
// Is the entity valid |
|
if( !UTIL_IsValidEntity( pTrace->pHit ) ) |
|
return; |
|
|
|
if( VARS( pTrace->pHit )->solid == SOLID_BSP || VARS( pTrace->pHit )->movetype == MOVETYPE_PUSHSTEP ) |
|
{ |
|
CBaseEntity *pEntity = NULL; |
|
// Decal the wall with a gunshot |
|
if( !FNullEnt( pTrace->pHit ) ) |
|
pEntity = CBaseEntity::Instance( pTrace->pHit ); |
|
|
|
switch( iBulletType ) |
|
{ |
|
case BULLET_PLAYER_9MM: |
|
case BULLET_MONSTER_9MM: |
|
case BULLET_PLAYER_MP5: |
|
case BULLET_MONSTER_MP5: |
|
case BULLET_PLAYER_BUCKSHOT: |
|
case BULLET_PLAYER_357: |
|
case BULLET_MONSTER_357: |
|
case BULLET_PLAYER_556: |
|
case BULLET_MONSTER_556: |
|
case BULLET_PLAYER_762: |
|
case BULLET_MONSTER_762: |
|
default: |
|
// smoke and decal |
|
UTIL_GunshotDecalTrace( pTrace, DamageDecal( pEntity, DMG_BULLET ) ); |
|
break; |
|
case BULLET_MONSTER_12MM: |
|
// smoke and decal |
|
UTIL_GunshotDecalTrace( pTrace, DamageDecal( pEntity, DMG_BULLET ) ); |
|
break; |
|
case BULLET_PLAYER_CROWBAR: |
|
// wall decal |
|
UTIL_DecalTrace( pTrace, DamageDecal( pEntity, DMG_CLUB ) ); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
// |
|
// EjectBrass - tosses a brass shell from passed origin at passed velocity |
|
// |
|
void EjectBrass( const Vector &vecOrigin, const Vector &vecVelocity, float rotation, int model, int soundtype ) |
|
{ |
|
// FIX: when the player shoots, their gun isn't in the same position as it is on the model other players see. |
|
MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecOrigin ); |
|
WRITE_BYTE( TE_MODEL ); |
|
WRITE_COORD( vecOrigin.x ); |
|
WRITE_COORD( vecOrigin.y ); |
|
WRITE_COORD( vecOrigin.z ); |
|
WRITE_COORD( vecVelocity.x ); |
|
WRITE_COORD( vecVelocity.y ); |
|
WRITE_COORD( vecVelocity.z ); |
|
WRITE_ANGLE( rotation ); |
|
WRITE_SHORT( model ); |
|
WRITE_BYTE( soundtype ); |
|
WRITE_BYTE( 25 );// 2.5 seconds |
|
MESSAGE_END(); |
|
} |
|
|
|
#if 0 |
|
// UNDONE: This is no longer used? |
|
void ExplodeModel( const Vector &vecOrigin, float speed, int model, int count ) |
|
{ |
|
MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecOrigin ); |
|
WRITE_BYTE( TE_EXPLODEMODEL ); |
|
WRITE_COORD( vecOrigin.x ); |
|
WRITE_COORD( vecOrigin.y ); |
|
WRITE_COORD( vecOrigin.z ); |
|
WRITE_COORD( speed ); |
|
WRITE_SHORT( model ); |
|
WRITE_SHORT( count ); |
|
WRITE_BYTE( 15 );// 1.5 seconds |
|
MESSAGE_END(); |
|
} |
|
#endif |
|
|
|
int giAmmoIndex = 0; |
|
|
|
// Precaches the ammo and queues the ammo info for sending to clients |
|
void AddAmmoNameToAmmoRegistry( const char *szAmmoname ) |
|
{ |
|
// make sure it's not already in the registry |
|
for( int i = 0; i < MAX_AMMO_SLOTS; i++ ) |
|
{ |
|
if( !CBasePlayerItem::AmmoInfoArray[i].pszName) |
|
continue; |
|
|
|
if( stricmp( CBasePlayerItem::AmmoInfoArray[i].pszName, szAmmoname ) == 0 ) |
|
return; // ammo already in registry, just quite |
|
} |
|
|
|
giAmmoIndex++; |
|
ASSERT( giAmmoIndex < MAX_AMMO_SLOTS ); |
|
if( giAmmoIndex >= MAX_AMMO_SLOTS ) |
|
giAmmoIndex = 0; |
|
|
|
CBasePlayerItem::AmmoInfoArray[giAmmoIndex].pszName = szAmmoname; |
|
CBasePlayerItem::AmmoInfoArray[giAmmoIndex].iId = giAmmoIndex; // yes, this info is redundant |
|
} |
|
|
|
// Precaches the weapon and queues the weapon info for sending to clients |
|
void UTIL_PrecacheOtherWeapon( const char *szClassname ) |
|
{ |
|
edict_t *pent; |
|
|
|
pent = CREATE_NAMED_ENTITY( MAKE_STRING( szClassname ) ); |
|
if( FNullEnt( pent ) ) |
|
{ |
|
ALERT( at_console, "NULL Ent in UTIL_PrecacheOtherWeapon\n" ); |
|
return; |
|
} |
|
|
|
CBaseEntity *pEntity = CBaseEntity::Instance( VARS( pent ) ); |
|
|
|
if( pEntity ) |
|
{ |
|
ItemInfo II = {0}; |
|
pEntity->Precache(); |
|
if( ( (CBasePlayerItem*)pEntity )->GetItemInfo( &II ) ) |
|
{ |
|
CBasePlayerItem::ItemInfoArray[II.iId] = II; |
|
|
|
if( II.pszAmmo1 && *II.pszAmmo1 ) |
|
{ |
|
AddAmmoNameToAmmoRegistry( II.pszAmmo1 ); |
|
} |
|
|
|
if( II.pszAmmo2 && *II.pszAmmo2 ) |
|
{ |
|
AddAmmoNameToAmmoRegistry( II.pszAmmo2 ); |
|
} |
|
} |
|
} |
|
|
|
REMOVE_ENTITY( pent ); |
|
} |
|
|
|
// called by worldspawn |
|
void W_Precache( void ) |
|
{ |
|
memset( CBasePlayerItem::ItemInfoArray, 0, sizeof(CBasePlayerItem::ItemInfoArray) ); |
|
memset( CBasePlayerItem::AmmoInfoArray, 0, sizeof(CBasePlayerItem::AmmoInfoArray) ); |
|
giAmmoIndex = 0; |
|
|
|
// custom items... |
|
|
|
// common world objects |
|
UTIL_PrecacheOther( "item_suit" ); |
|
UTIL_PrecacheOther( "item_battery" ); |
|
UTIL_PrecacheOther( "item_antidote" ); |
|
UTIL_PrecacheOther( "item_security" ); |
|
UTIL_PrecacheOther( "item_longjump" ); |
|
|
|
// shotgun |
|
UTIL_PrecacheOtherWeapon( "weapon_shotgun" ); |
|
UTIL_PrecacheOther( "ammo_buckshot" ); |
|
|
|
// crowbar |
|
UTIL_PrecacheOtherWeapon( "weapon_crowbar" ); |
|
|
|
// glock |
|
UTIL_PrecacheOtherWeapon( "weapon_9mmhandgun" ); |
|
UTIL_PrecacheOther( "ammo_9mmclip" ); |
|
|
|
// mp5 |
|
UTIL_PrecacheOtherWeapon( "weapon_9mmAR" ); |
|
UTIL_PrecacheOther( "ammo_9mmAR" ); |
|
UTIL_PrecacheOther( "ammo_ARgrenades" ); |
|
|
|
#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) |
|
// python |
|
UTIL_PrecacheOtherWeapon( "weapon_357" ); |
|
UTIL_PrecacheOther( "ammo_357" ); |
|
|
|
// gauss |
|
UTIL_PrecacheOtherWeapon( "weapon_gauss" ); |
|
UTIL_PrecacheOther( "ammo_gaussclip" ); |
|
|
|
// rpg |
|
UTIL_PrecacheOtherWeapon( "weapon_rpg" ); |
|
UTIL_PrecacheOther( "ammo_rpgclip" ); |
|
|
|
// crossbow |
|
UTIL_PrecacheOtherWeapon( "weapon_crossbow" ); |
|
UTIL_PrecacheOther( "ammo_crossbow" ); |
|
|
|
// egon |
|
UTIL_PrecacheOtherWeapon( "weapon_egon" ); |
|
#endif |
|
// tripmine |
|
UTIL_PrecacheOtherWeapon( "weapon_tripmine" ); |
|
#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) |
|
// satchel charge |
|
UTIL_PrecacheOtherWeapon( "weapon_satchel" ); |
|
#endif |
|
// hand grenade |
|
UTIL_PrecacheOtherWeapon("weapon_handgrenade"); |
|
#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) |
|
// squeak grenade |
|
UTIL_PrecacheOtherWeapon( "weapon_snark" ); |
|
|
|
// hornetgun |
|
UTIL_PrecacheOtherWeapon( "weapon_hornetgun" ); |
|
|
|
UTIL_PrecacheOtherWeapon( "weapon_displacer" ); |
|
UTIL_PrecacheOtherWeapon( "weapon_eagle" ); |
|
UTIL_PrecacheOtherWeapon( "weapon_grapple" ); |
|
UTIL_PrecacheOther( "grapple_tonguetip" ); |
|
UTIL_PrecacheOtherWeapon( "weapon_knife" ); |
|
UTIL_PrecacheOtherWeapon( "weapon_m249" ); |
|
UTIL_PrecacheOther( "ammo_556" ); |
|
UTIL_PrecacheOtherWeapon( "weapon_penguin" ); |
|
UTIL_PrecacheOtherWeapon( "weapon_pipewrench" ); |
|
UTIL_PrecacheOtherWeapon( "weapon_shockrifle" ); |
|
UTIL_PrecacheOtherWeapon( "weapon_sniperrifle" ); |
|
UTIL_PrecacheOther( "ammo_762" ); |
|
UTIL_PrecacheOtherWeapon( "weapon_sporelauncher" ); |
|
UTIL_PrecacheOther( "ammo_spore" ); |
|
|
|
if( g_pGameRules->IsDeathmatch() ) |
|
{ |
|
UTIL_PrecacheOther( "weaponbox" );// container for dropped deathmatch weapons |
|
} |
|
#endif |
|
g_sModelIndexFireball = PRECACHE_MODEL( "sprites/zerogxplode.spr" );// fireball |
|
g_sModelIndexWExplosion = PRECACHE_MODEL( "sprites/WXplo1.spr" );// underwater fireball |
|
g_sModelIndexSmoke = PRECACHE_MODEL( "sprites/steam1.spr" );// smoke |
|
g_sModelIndexBubbles = PRECACHE_MODEL( "sprites/bubble.spr" );//bubbles |
|
g_sModelIndexBloodSpray = PRECACHE_MODEL( "sprites/bloodspray.spr" ); // initial blood |
|
g_sModelIndexBloodDrop = PRECACHE_MODEL( "sprites/blood.spr" ); // splattered blood |
|
|
|
g_sModelIndexLaser = PRECACHE_MODEL( (char *)g_pModelNameLaser ); |
|
g_sModelIndexLaserDot = PRECACHE_MODEL( "sprites/laserdot.spr" ); |
|
|
|
// used by explosions |
|
PRECACHE_MODEL( "models/grenade.mdl" ); |
|
PRECACHE_MODEL( "sprites/explode1.spr" ); |
|
|
|
PRECACHE_SOUND( "weapons/debris1.wav" );// explosion aftermaths |
|
PRECACHE_SOUND( "weapons/debris2.wav" );// explosion aftermaths |
|
PRECACHE_SOUND( "weapons/debris3.wav" );// explosion aftermaths |
|
|
|
PRECACHE_SOUND( "weapons/grenade_hit1.wav" );//grenade |
|
PRECACHE_SOUND( "weapons/grenade_hit2.wav" );//grenade |
|
PRECACHE_SOUND( "weapons/grenade_hit3.wav" );//grenade |
|
|
|
PRECACHE_SOUND( "weapons/bullet_hit1.wav" ); // hit by bullet |
|
PRECACHE_SOUND( "weapons/bullet_hit2.wav" ); // hit by bullet |
|
|
|
PRECACHE_SOUND( "items/weapondrop1.wav" );// weapon falls to the ground |
|
|
|
// Used by spore grenades. |
|
PRECACHE_MODEL( "models/spore.mdl" ); |
|
g_sModelIndexSpore1 = PRECACHE_MODEL( "sprites/spore_exp_01.spr" ); |
|
g_sModelIndexSpore2 = PRECACHE_MODEL( "sprites/spore_exp_b_01.spr" ); |
|
g_sModelIndexSpore3 = PRECACHE_MODEL( "sprites/spore_exp_c_01.spr" ); |
|
|
|
g_sModelIndexBigSpit = PRECACHE_MODEL( "sprites/bigspit.spr" ); |
|
g_sModelIndexTinySpit = PRECACHE_MODEL( "sprites/tinyspit.spr" ); |
|
|
|
PRECACHE_SOUND( "weapons/splauncher_impact.wav" );//explosion aftermaths |
|
|
|
PRECACHE_SOUND( "weapons/spore_hit1.wav" );//sporegrenade |
|
PRECACHE_SOUND( "weapons/spore_hit2.wav" );//sporegrenade |
|
PRECACHE_SOUND( "weapons/spore_hit3.wav" );//sporegrenade |
|
} |
|
|
|
TYPEDESCRIPTION CBasePlayerItem::m_SaveData[] = |
|
{ |
|
DEFINE_FIELD( CBasePlayerItem, m_pPlayer, FIELD_CLASSPTR ), |
|
DEFINE_FIELD( CBasePlayerItem, m_pNext, FIELD_CLASSPTR ), |
|
//DEFINE_FIELD( CBasePlayerItem, m_fKnown, FIELD_INTEGER ),Reset to zero on load |
|
DEFINE_FIELD( CBasePlayerItem, m_iId, FIELD_INTEGER ), |
|
// DEFINE_FIELD( CBasePlayerItem, m_iIdPrimary, FIELD_INTEGER ), |
|
// DEFINE_FIELD( CBasePlayerItem, m_iIdSecondary, FIELD_INTEGER ), |
|
}; |
|
|
|
IMPLEMENT_SAVERESTORE( CBasePlayerItem, CBaseAnimating ) |
|
|
|
TYPEDESCRIPTION CBasePlayerWeapon::m_SaveData[] = |
|
{ |
|
#if defined( CLIENT_WEAPONS ) |
|
DEFINE_FIELD( CBasePlayerWeapon, m_flNextPrimaryAttack, FIELD_FLOAT ), |
|
DEFINE_FIELD( CBasePlayerWeapon, m_flNextSecondaryAttack, FIELD_FLOAT ), |
|
DEFINE_FIELD( CBasePlayerWeapon, m_flTimeWeaponIdle, FIELD_FLOAT ), |
|
#else // CLIENT_WEAPONS |
|
DEFINE_FIELD( CBasePlayerWeapon, m_flNextPrimaryAttack, FIELD_TIME ), |
|
DEFINE_FIELD( CBasePlayerWeapon, m_flNextSecondaryAttack, FIELD_TIME ), |
|
DEFINE_FIELD( CBasePlayerWeapon, m_flTimeWeaponIdle, FIELD_TIME ), |
|
#endif // CLIENT_WEAPONS |
|
DEFINE_FIELD( CBasePlayerWeapon, m_iPrimaryAmmoType, FIELD_INTEGER ), |
|
DEFINE_FIELD( CBasePlayerWeapon, m_iSecondaryAmmoType, FIELD_INTEGER ), |
|
DEFINE_FIELD( CBasePlayerWeapon, m_iClip, FIELD_INTEGER ), |
|
DEFINE_FIELD( CBasePlayerWeapon, m_iDefaultAmmo, FIELD_INTEGER ), |
|
//DEFINE_FIELD( CBasePlayerWeapon, m_iClientClip, FIELD_INTEGER ), reset to zero on load so hud gets updated correctly |
|
//DEFINE_FIELD( CBasePlayerWeapon, m_iClientWeaponState, FIELD_INTEGER ), reset to zero on load so hud gets updated correctly |
|
}; |
|
|
|
IMPLEMENT_SAVERESTORE( CBasePlayerWeapon, CBasePlayerItem ) |
|
|
|
void CBasePlayerItem::SetObjectCollisionBox( void ) |
|
{ |
|
pev->absmin = pev->origin + Vector( -24, -24, 0 ); |
|
pev->absmax = pev->origin + Vector( 24, 24, 16 ); |
|
} |
|
|
|
//========================================================= |
|
// Sets up movetype, size, solidtype for a new weapon. |
|
//========================================================= |
|
void CBasePlayerItem::FallInit( void ) |
|
{ |
|
pev->movetype = MOVETYPE_TOSS; |
|
pev->solid = SOLID_BBOX; |
|
|
|
UTIL_SetOrigin( pev, pev->origin ); |
|
UTIL_SetSize( pev, Vector( 0, 0, 0 ), Vector( 0, 0, 0 ) );//pointsize until it lands on the ground. |
|
|
|
SetTouch( &CBasePlayerItem::DefaultTouch ); |
|
SetThink( &CBasePlayerItem::FallThink ); |
|
|
|
pev->nextthink = gpGlobals->time + 0.1; |
|
} |
|
|
|
//========================================================= |
|
// FallThink - Items that have just spawned run this think |
|
// to catch them when they hit the ground. Once we're sure |
|
// that the object is grounded, we change its solid type |
|
// to trigger and set it in a large box that helps the |
|
// player get it. |
|
//========================================================= |
|
void CBasePlayerItem::FallThink( void ) |
|
{ |
|
pev->nextthink = gpGlobals->time + 0.1; |
|
|
|
if( pev->flags & FL_ONGROUND ) |
|
{ |
|
// clatter if we have an owner (i.e., dropped by someone) |
|
// don't clatter if the gun is waiting to respawn (if it's waiting, it is invisible!) |
|
if( !FNullEnt( pev->owner ) ) |
|
{ |
|
int pitch = 95 + RANDOM_LONG( 0, 29 ); |
|
EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, "items/weapondrop1.wav", 1, ATTN_NORM, 0, pitch ); |
|
} |
|
|
|
// lie flat |
|
pev->angles.x = 0; |
|
pev->angles.z = 0; |
|
|
|
Materialize(); |
|
} |
|
} |
|
|
|
//========================================================= |
|
// Materialize - make a CBasePlayerItem visible and tangible |
|
//========================================================= |
|
void CBasePlayerItem::Materialize( void ) |
|
{ |
|
if( pev->effects & EF_NODRAW ) |
|
{ |
|
// changing from invisible state to visible. |
|
EMIT_SOUND_DYN( ENT( pev ), CHAN_WEAPON, "items/suitchargeok1.wav", 1, ATTN_NORM, 0, 150 ); |
|
pev->effects &= ~EF_NODRAW; |
|
pev->effects |= EF_MUZZLEFLASH; |
|
} |
|
|
|
pev->solid = SOLID_TRIGGER; |
|
|
|
UTIL_SetOrigin( pev, pev->origin );// link into world. |
|
SetTouch( &CBasePlayerItem::DefaultTouch); |
|
SetThink( NULL ); |
|
} |
|
|
|
//========================================================= |
|
// AttemptToMaterialize - the item is trying to rematerialize, |
|
// should it do so now or wait longer? |
|
//========================================================= |
|
void CBasePlayerItem::AttemptToMaterialize( void ) |
|
{ |
|
float time = g_pGameRules->FlWeaponTryRespawn( this ); |
|
|
|
if( time == 0 ) |
|
{ |
|
Materialize(); |
|
return; |
|
} |
|
|
|
pev->nextthink = gpGlobals->time + time; |
|
} |
|
|
|
//========================================================= |
|
// CheckRespawn - a player is taking this weapon, should |
|
// it respawn? |
|
//========================================================= |
|
void CBasePlayerItem::CheckRespawn( void ) |
|
{ |
|
switch( g_pGameRules->WeaponShouldRespawn( this ) ) |
|
{ |
|
case GR_WEAPON_RESPAWN_YES: |
|
Respawn(); |
|
break; |
|
case GR_WEAPON_RESPAWN_NO: |
|
return; |
|
break; |
|
} |
|
} |
|
|
|
//========================================================= |
|
// Respawn- this item is already in the world, but it is |
|
// invisible and intangible. Make it visible and tangible. |
|
//========================================================= |
|
CBaseEntity* CBasePlayerItem::Respawn( void ) |
|
{ |
|
// make a copy of this weapon that is invisible and inaccessible to players (no touch function). The weapon spawn/respawn code |
|
// will decide when to make the weapon visible and touchable. |
|
CBaseEntity *pNewWeapon = CBaseEntity::Create( (char *)STRING( pev->classname ), g_pGameRules->VecWeaponRespawnSpot( this ), pev->angles, pev->owner ); |
|
|
|
if( pNewWeapon ) |
|
{ |
|
pNewWeapon->pev->effects |= EF_NODRAW;// invisible for now |
|
pNewWeapon->SetTouch( NULL );// no touch |
|
pNewWeapon->SetThink( &CBasePlayerItem::AttemptToMaterialize ); |
|
|
|
DROP_TO_FLOOR( ENT( pev ) ); |
|
|
|
// not a typo! We want to know when the weapon the player just picked up should respawn! This new entity we created is the replacement, |
|
// but when it should respawn is based on conditions belonging to the weapon that was taken. |
|
pNewWeapon->pev->nextthink = g_pGameRules->FlWeaponRespawnTime( this ); |
|
} |
|
else |
|
{ |
|
ALERT( at_console, "Respawn failed to create %s!\n", STRING( pev->classname ) ); |
|
} |
|
|
|
return pNewWeapon; |
|
} |
|
|
|
void CBasePlayerItem::DefaultTouch( CBaseEntity *pOther ) |
|
{ |
|
// if it's not a player, ignore |
|
if( !pOther->IsPlayer() ) |
|
return; |
|
|
|
CBasePlayer *pPlayer = (CBasePlayer *)pOther; |
|
|
|
// can I have this? |
|
if( !g_pGameRules->CanHavePlayerItem( pPlayer, this ) ) |
|
{ |
|
if( gEvilImpulse101 ) |
|
{ |
|
UTIL_Remove( this ); |
|
} |
|
return; |
|
} |
|
|
|
if( pOther->AddPlayerItem( this ) ) |
|
{ |
|
AttachToPlayer( pPlayer ); |
|
EMIT_SOUND( ENT( pPlayer->pev ), CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM ); |
|
} |
|
|
|
SUB_UseTargets( pOther, USE_TOGGLE, 0 ); // UNDONE: when should this happen? |
|
} |
|
|
|
BOOL 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 CBasePlayerWeapon::ItemPostFrame( void ) |
|
{ |
|
if( ( m_fInReload ) && ( m_pPlayer->m_flNextAttack <= UTIL_WeaponTimeBase() ) ) |
|
{ |
|
// complete the reload. |
|
int j = min( iMaxClip() - m_iClip, m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]); |
|
|
|
// Add them to the clip |
|
m_iClip += j; |
|
m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= j; |
|
|
|
m_pPlayer->TabulateAmmo(); |
|
|
|
m_fInReload = FALSE; |
|
} |
|
|
|
if( !( m_pPlayer->pev->button & IN_ATTACK ) ) |
|
{ |
|
m_flLastFireTime = 0.0f; |
|
} |
|
|
|
if( ( m_pPlayer->pev->button & IN_ATTACK2 ) && CanAttack( m_flNextSecondaryAttack, gpGlobals->time, UseDecrement() ) ) |
|
{ |
|
if( pszAmmo2() && !m_pPlayer->m_rgAmmo[SecondaryAmmoIndex()] ) |
|
{ |
|
m_fFireOnEmpty = TRUE; |
|
} |
|
|
|
m_pPlayer->TabulateAmmo(); |
|
SecondaryAttack(); |
|
m_pPlayer->pev->button &= ~IN_ATTACK2; |
|
} |
|
else 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; |
|
} |
|
|
|
m_pPlayer->TabulateAmmo(); |
|
PrimaryAttack(); |
|
} |
|
else if( m_pPlayer->pev->button & IN_RELOAD && iMaxClip() != WEAPON_NOCLIP && !m_fInReload ) |
|
{ |
|
// reload when reload is pressed, or if no buttons are down and weapon is empty. |
|
Reload(); |
|
} |
|
else if( !( m_pPlayer->pev->button & ( IN_ATTACK | IN_ATTACK2 ) ) ) |
|
{ |
|
// 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; |
|
} |
|
} |
|
else |
|
{ |
|
// weapon is useable. Reload if empty and weapon has waited as long as it has to after firing |
|
if( m_iClip == 0 && !(iFlags() & ITEM_FLAG_NOAUTORELOAD ) && m_flNextPrimaryAttack < ( UseDecrement() ? 0.0 : gpGlobals->time ) ) |
|
{ |
|
Reload(); |
|
return; |
|
} |
|
} |
|
|
|
WeaponIdle(); |
|
return; |
|
} |
|
|
|
// catch all |
|
if( ShouldWeaponIdle() ) |
|
{ |
|
WeaponIdle(); |
|
} |
|
} |
|
|
|
void CBasePlayerItem::DestroyItem( void ) |
|
{ |
|
if( m_pPlayer ) |
|
{ |
|
// if attached to a player, remove. |
|
m_pPlayer->RemovePlayerItem( this ); |
|
} |
|
|
|
Kill(); |
|
} |
|
|
|
int CBasePlayerItem::AddToPlayer( CBasePlayer *pPlayer ) |
|
{ |
|
m_pPlayer = pPlayer; |
|
|
|
return TRUE; |
|
} |
|
|
|
void CBasePlayerItem::Drop( void ) |
|
{ |
|
SetTouch( NULL ); |
|
SetThink( &CBaseEntity::SUB_Remove ); |
|
pev->nextthink = gpGlobals->time + .1; |
|
} |
|
|
|
void CBasePlayerItem::Kill( void ) |
|
{ |
|
SetTouch( NULL ); |
|
SetThink( &CBaseEntity::SUB_Remove ); |
|
pev->nextthink = gpGlobals->time + .1; |
|
} |
|
|
|
void CBasePlayerItem::Holster( int skiplocal /* = 0 */ ) |
|
{ |
|
m_pPlayer->pev->viewmodel = 0; |
|
m_pPlayer->pev->weaponmodel = 0; |
|
} |
|
|
|
void CBasePlayerItem::AttachToPlayer( CBasePlayer *pPlayer ) |
|
{ |
|
pev->movetype = MOVETYPE_FOLLOW; |
|
pev->solid = SOLID_NOT; |
|
pev->aiment = pPlayer->edict(); |
|
pev->effects = EF_NODRAW; // ?? |
|
pev->modelindex = 0;// server won't send down to clients if modelindex == 0 |
|
pev->model = iStringNull; |
|
pev->owner = pPlayer->edict(); |
|
pev->nextthink = gpGlobals->time + .1; |
|
SetTouch( NULL ); |
|
SetThink( NULL ); |
|
} |
|
|
|
// CALLED THROUGH the newly-touched weapon's instance. The existing player weapon is pOriginal |
|
int CBasePlayerWeapon::AddDuplicate( CBasePlayerItem *pOriginal ) |
|
{ |
|
if( m_iDefaultAmmo ) |
|
{ |
|
return ExtractAmmo( (CBasePlayerWeapon *)pOriginal ); |
|
} |
|
else |
|
{ |
|
// a dead player dropped this. |
|
return ExtractClipAmmo( (CBasePlayerWeapon *)pOriginal ); |
|
} |
|
} |
|
|
|
int CBasePlayerWeapon::AddToPlayer( CBasePlayer *pPlayer ) |
|
{ |
|
int bResult = CBasePlayerItem::AddToPlayer( pPlayer ); |
|
|
|
pPlayer->pev->weapons |= ( 1 << m_iId ); |
|
|
|
if( !m_iPrimaryAmmoType ) |
|
{ |
|
m_iPrimaryAmmoType = pPlayer->GetAmmoIndex( pszAmmo1() ); |
|
m_iSecondaryAmmoType = pPlayer->GetAmmoIndex( pszAmmo2() ); |
|
} |
|
|
|
if( bResult ) |
|
return AddWeapon(); |
|
return FALSE; |
|
} |
|
|
|
int CBasePlayerWeapon::UpdateClientData( CBasePlayer *pPlayer ) |
|
{ |
|
BOOL bSend = FALSE; |
|
int state = 0; |
|
if( pPlayer->m_pActiveItem == this ) |
|
{ |
|
if( pPlayer->m_fOnTarget ) |
|
state = WEAPON_IS_ONTARGET; |
|
else |
|
state = 1; |
|
} |
|
|
|
// Forcing send of all data! |
|
if( !pPlayer->m_fWeapon ) |
|
{ |
|
bSend = TRUE; |
|
} |
|
|
|
// This is the current or last weapon, so the state will need to be updated |
|
if( this == pPlayer->m_pActiveItem || this == pPlayer->m_pClientActiveItem ) |
|
{ |
|
if( pPlayer->m_pActiveItem != pPlayer->m_pClientActiveItem ) |
|
{ |
|
bSend = TRUE; |
|
} |
|
} |
|
|
|
// If the ammo, state, or fov has changed, update the weapon |
|
if( m_iClip != m_iClientClip || state != m_iClientWeaponState || pPlayer->m_iFOV != pPlayer->m_iClientFOV ) |
|
{ |
|
bSend = TRUE; |
|
} |
|
|
|
if( bSend ) |
|
{ |
|
MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pPlayer->pev ); |
|
WRITE_BYTE( state ); |
|
WRITE_BYTE( m_iId ); |
|
WRITE_BYTE( m_iClip ); |
|
MESSAGE_END(); |
|
|
|
m_iClientClip = m_iClip; |
|
m_iClientWeaponState = state; |
|
pPlayer->m_fWeapon = TRUE; |
|
} |
|
|
|
if( m_pNext ) |
|
m_pNext->UpdateClientData( pPlayer ); |
|
|
|
return 1; |
|
} |
|
|
|
void CBasePlayerWeapon::SendWeaponAnim( int iAnim, int skiplocal, int body ) |
|
{ |
|
if( UseDecrement() ) |
|
skiplocal = 1; |
|
else |
|
skiplocal = 0; |
|
|
|
m_pPlayer->pev->weaponanim = iAnim; |
|
|
|
#if defined( CLIENT_WEAPONS ) |
|
if( skiplocal && ENGINE_CANSKIP( m_pPlayer->edict() ) ) |
|
return; |
|
#endif |
|
MESSAGE_BEGIN( MSG_ONE, SVC_WEAPONANIM, NULL, m_pPlayer->pev ); |
|
WRITE_BYTE( iAnim ); // sequence number |
|
WRITE_BYTE( pev->body ); // weaponmodel bodygroup. |
|
MESSAGE_END(); |
|
} |
|
|
|
BOOL CBasePlayerWeapon::AddPrimaryAmmo( int iCount, char *szName, int iMaxClip, int iMaxCarry ) |
|
{ |
|
int iIdAmmo; |
|
|
|
if( iMaxClip < 1 ) |
|
{ |
|
m_iClip = -1; |
|
iIdAmmo = m_pPlayer->GiveAmmo( iCount, szName, iMaxCarry ); |
|
} |
|
else if( m_iClip == 0 ) |
|
{ |
|
int i; |
|
i = min( m_iClip + iCount, iMaxClip ) - m_iClip; |
|
m_iClip += i; |
|
iIdAmmo = m_pPlayer->GiveAmmo( iCount - i, szName, iMaxCarry ); |
|
} |
|
else |
|
{ |
|
iIdAmmo = m_pPlayer->GiveAmmo( iCount, szName, iMaxCarry ); |
|
} |
|
|
|
// m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] = iMaxCarry; // hack for testing |
|
|
|
if( iIdAmmo > 0 ) |
|
{ |
|
m_iPrimaryAmmoType = iIdAmmo; |
|
if( m_pPlayer->HasPlayerItem( this ) ) |
|
{ |
|
// play the "got ammo" sound only if we gave some ammo to a player that already had this gun. |
|
// if the player is just getting this gun for the first time, DefaultTouch will play the "picked up gun" sound for us. |
|
EMIT_SOUND( ENT( pev ), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM ); |
|
} |
|
} |
|
|
|
return iIdAmmo > 0 ? TRUE : FALSE; |
|
} |
|
|
|
BOOL CBasePlayerWeapon::AddSecondaryAmmo( int iCount, char *szName, int iMax ) |
|
{ |
|
int iIdAmmo; |
|
|
|
iIdAmmo = m_pPlayer->GiveAmmo( iCount, szName, iMax ); |
|
|
|
//m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] = iMax; // hack for testing |
|
|
|
if( iIdAmmo > 0 ) |
|
{ |
|
m_iSecondaryAmmoType = iIdAmmo; |
|
EMIT_SOUND( ENT( pev ), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM ); |
|
} |
|
return iIdAmmo > 0 ? TRUE : FALSE; |
|
} |
|
|
|
//========================================================= |
|
// IsUseable - this function determines whether or not a |
|
// weapon is useable by the player in its current state. |
|
// (does it have ammo loaded? do I have any ammo for the |
|
// weapon?, etc) |
|
//========================================================= |
|
BOOL CBasePlayerWeapon::IsUseable( void ) |
|
{ |
|
if( m_iClip <= 0 ) |
|
{ |
|
if( m_pPlayer->m_rgAmmo[PrimaryAmmoIndex()] <= 0 && iMaxAmmo1() != -1 ) |
|
{ |
|
// clip is empty (or nonexistant) and the player has no more ammo of this type. |
|
return FALSE; |
|
} |
|
} |
|
|
|
return TRUE; |
|
} |
|
|
|
BOOL CBasePlayerWeapon::CanDeploy( void ) |
|
{ |
|
BOOL bHasAmmo = 0; |
|
|
|
if( !pszAmmo1() ) |
|
{ |
|
// this weapon doesn't use ammo, can always deploy. |
|
return TRUE; |
|
} |
|
|
|
if( pszAmmo1() ) |
|
{ |
|
bHasAmmo |= ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] != 0 ); |
|
} |
|
if( pszAmmo2() ) |
|
{ |
|
bHasAmmo |= ( m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] != 0 ); |
|
} |
|
if( m_iClip > 0 ) |
|
{ |
|
bHasAmmo |= 1; |
|
} |
|
if( !bHasAmmo ) |
|
{ |
|
return FALSE; |
|
} |
|
|
|
return TRUE; |
|
} |
|
|
|
BOOL CBasePlayerWeapon::DefaultDeploy( char *szViewModel, char *szWeaponModel, int iAnim, char *szAnimExt, int skiplocal /* = 0 */, int body ) |
|
{ |
|
if( !CanDeploy() ) |
|
return FALSE; |
|
|
|
m_pPlayer->TabulateAmmo(); |
|
m_pPlayer->pev->viewmodel = MAKE_STRING( szViewModel ); |
|
m_pPlayer->pev->weaponmodel = MAKE_STRING( szWeaponModel ); |
|
strcpy( m_pPlayer->m_szAnimExtention, szAnimExt ); |
|
SendWeaponAnim( iAnim, skiplocal, body ); |
|
|
|
m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; |
|
m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.0; |
|
m_flLastFireTime = 0.0f; |
|
|
|
return TRUE; |
|
} |
|
|
|
BOOL CBasePlayerWeapon::DefaultReload( int iClipSize, int iAnim, float fDelay, int body ) |
|
{ |
|
if( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0 ) |
|
return FALSE; |
|
|
|
int j = min( iClipSize - m_iClip, m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] ); |
|
|
|
if( j == 0 ) |
|
return FALSE; |
|
|
|
m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + fDelay; |
|
|
|
//!!UNDONE -- reload sound goes here !!! |
|
SendWeaponAnim( iAnim, UseDecrement() ? 1 : 0 ); |
|
|
|
m_fInReload = TRUE; |
|
|
|
m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 3; |
|
return TRUE; |
|
} |
|
|
|
BOOL CBasePlayerWeapon::PlayEmptySound( void ) |
|
{ |
|
if( m_iPlayEmptySound ) |
|
{ |
|
EMIT_SOUND( ENT( m_pPlayer->pev ), CHAN_WEAPON, "weapons/357_cock1.wav", 0.8, ATTN_NORM ); |
|
m_iPlayEmptySound = 0; |
|
return 0; |
|
} |
|
return 0; |
|
} |
|
|
|
void CBasePlayerWeapon::ResetEmptySound( void ) |
|
{ |
|
m_iPlayEmptySound = 1; |
|
} |
|
|
|
//========================================================= |
|
//========================================================= |
|
int CBasePlayerWeapon::PrimaryAmmoIndex( void ) |
|
{ |
|
return m_iPrimaryAmmoType; |
|
} |
|
|
|
//========================================================= |
|
//========================================================= |
|
int CBasePlayerWeapon::SecondaryAmmoIndex( void ) |
|
{ |
|
return -1; |
|
} |
|
|
|
void CBasePlayerWeapon::Holster( int skiplocal /* = 0 */ ) |
|
{ |
|
m_fInReload = FALSE; // cancel any reload in progress. |
|
m_pPlayer->pev->viewmodel = 0; |
|
m_pPlayer->pev->weaponmodel = 0; |
|
} |
|
|
|
void CBasePlayerAmmo::Spawn( void ) |
|
{ |
|
pev->movetype = MOVETYPE_TOSS; |
|
pev->solid = SOLID_TRIGGER; |
|
UTIL_SetSize( pev, Vector( -16, -16, 0 ), Vector( 16, 16, 16 ) ); |
|
UTIL_SetOrigin( pev, pev->origin ); |
|
|
|
SetTouch( &CBasePlayerAmmo::DefaultTouch ); |
|
} |
|
|
|
CBaseEntity* CBasePlayerAmmo::Respawn( void ) |
|
{ |
|
pev->effects |= EF_NODRAW; |
|
SetTouch( NULL ); |
|
|
|
UTIL_SetOrigin( pev, g_pGameRules->VecAmmoRespawnSpot( this ) );// move to wherever I'm supposed to repawn. |
|
|
|
SetThink( &CBasePlayerAmmo::Materialize ); |
|
pev->nextthink = g_pGameRules->FlAmmoRespawnTime( this ); |
|
|
|
return this; |
|
} |
|
|
|
void CBasePlayerAmmo::Materialize( void ) |
|
{ |
|
if( pev->effects & EF_NODRAW ) |
|
{ |
|
// changing from invisible state to visible. |
|
EMIT_SOUND_DYN( ENT( pev ), CHAN_WEAPON, "items/suitchargeok1.wav", 1, ATTN_NORM, 0, 150 ); |
|
pev->effects &= ~EF_NODRAW; |
|
pev->effects |= EF_MUZZLEFLASH; |
|
} |
|
|
|
SetTouch( &CBasePlayerAmmo::DefaultTouch ); |
|
} |
|
|
|
void CBasePlayerAmmo::DefaultTouch( CBaseEntity *pOther ) |
|
{ |
|
if( !pOther->IsPlayer() ) |
|
{ |
|
return; |
|
} |
|
|
|
if( AddAmmo( pOther ) ) |
|
{ |
|
if( g_pGameRules->AmmoShouldRespawn( this ) == GR_AMMO_RESPAWN_YES ) |
|
{ |
|
Respawn(); |
|
} |
|
else |
|
{ |
|
SetTouch( NULL ); |
|
SetThink( &CBaseEntity::SUB_Remove ); |
|
pev->nextthink = gpGlobals->time + .1; |
|
} |
|
} |
|
else if( gEvilImpulse101 ) |
|
{ |
|
// evil impulse 101 hack, kill always |
|
SetTouch( NULL ); |
|
SetThink( &CBaseEntity::SUB_Remove ); |
|
pev->nextthink = gpGlobals->time + .1; |
|
} |
|
} |
|
|
|
//========================================================= |
|
// called by the new item with the existing item as parameter |
|
// |
|
// if we call ExtractAmmo(), it's because the player is picking up this type of weapon for |
|
// the first time. If it is spawned by the world, m_iDefaultAmmo will have a default ammo amount in it. |
|
// if this is a weapon dropped by a dying player, has 0 m_iDefaultAmmo, which means only the ammo in |
|
// the weapon clip comes along. |
|
//========================================================= |
|
int CBasePlayerWeapon::ExtractAmmo( CBasePlayerWeapon *pWeapon ) |
|
{ |
|
int iReturn = 0; |
|
|
|
if( pszAmmo1() != NULL ) |
|
{ |
|
// blindly call with m_iDefaultAmmo. It's either going to be a value or zero. If it is zero, |
|
// we only get the ammo in the weapon's clip, which is what we want. |
|
iReturn = pWeapon->AddPrimaryAmmo( m_iDefaultAmmo, (char *)pszAmmo1(), iMaxClip(), iMaxAmmo1() ); |
|
m_iDefaultAmmo = 0; |
|
} |
|
|
|
if( pszAmmo2() != NULL ) |
|
{ |
|
iReturn = pWeapon->AddSecondaryAmmo( 0, (char *)pszAmmo2(), iMaxAmmo2() ); |
|
} |
|
|
|
return iReturn; |
|
} |
|
|
|
//========================================================= |
|
// called by the new item's class with the existing item as parameter |
|
//========================================================= |
|
int CBasePlayerWeapon::ExtractClipAmmo( CBasePlayerWeapon *pWeapon ) |
|
{ |
|
int iAmmo; |
|
|
|
if( m_iClip == WEAPON_NOCLIP ) |
|
{ |
|
iAmmo = 0;// guns with no clips always come empty if they are second-hand |
|
} |
|
else |
|
{ |
|
iAmmo = m_iClip; |
|
} |
|
|
|
return pWeapon->m_pPlayer->GiveAmmo( iAmmo, (char *)pszAmmo1(), iMaxAmmo1() ); // , &m_iPrimaryAmmoType |
|
} |
|
|
|
//========================================================= |
|
// RetireWeapon - no more ammo for this gun, put it away. |
|
//========================================================= |
|
void CBasePlayerWeapon::RetireWeapon( void ) |
|
{ |
|
// first, no viewmodel at all. |
|
m_pPlayer->pev->viewmodel = iStringNull; |
|
m_pPlayer->pev->weaponmodel = iStringNull; |
|
//m_pPlayer->pev->viewmodelindex = NULL; |
|
|
|
g_pGameRules->GetNextBestWeapon( m_pPlayer, this ); |
|
} |
|
|
|
//========================================================================= |
|
// GetNextAttackDelay - An accurate way of calcualting the next attack time. |
|
//========================================================================= |
|
float CBasePlayerWeapon::GetNextAttackDelay( float delay ) |
|
{ |
|
if( m_flLastFireTime == 0 || m_flNextPrimaryAttack == -1 ) |
|
{ |
|
// At this point, we are assuming that the client has stopped firing |
|
// and we are going to reset our book keeping variables. |
|
m_flLastFireTime = gpGlobals->time; |
|
m_flPrevPrimaryAttack = delay; |
|
} |
|
|
|
// calculate the time between this shot and the previous |
|
float flTimeBetweenFires = gpGlobals->time - m_flLastFireTime; |
|
float flCreep = 0.0f; |
|
if( flTimeBetweenFires > 0 ) |
|
flCreep = flTimeBetweenFires - m_flPrevPrimaryAttack; // postive or negative |
|
|
|
// save the last fire time |
|
m_flLastFireTime = gpGlobals->time; |
|
|
|
float flNextAttack = UTIL_WeaponTimeBase() + delay - flCreep; |
|
// we need to remember what the m_flNextPrimaryAttack time is set to for each shot, |
|
// store it as m_flPrevPrimaryAttack. |
|
m_flPrevPrimaryAttack = flNextAttack - UTIL_WeaponTimeBase(); |
|
//char szMsg[256]; |
|
//_snprintf( szMsg, sizeof(szMsg), "next attack time: %0.4f\n", gpGlobals->time + flNextAttack ); |
|
//OutputDebugString( szMsg ); |
|
return flNextAttack; |
|
} |
|
//********************************************************* |
|
// weaponbox code: |
|
//********************************************************* |
|
|
|
LINK_ENTITY_TO_CLASS( weaponbox, CWeaponBox ) |
|
|
|
TYPEDESCRIPTION CWeaponBox::m_SaveData[] = |
|
{ |
|
DEFINE_ARRAY( CWeaponBox, m_rgAmmo, FIELD_INTEGER, MAX_AMMO_SLOTS ), |
|
DEFINE_ARRAY( CWeaponBox, m_rgiszAmmo, FIELD_STRING, MAX_AMMO_SLOTS ), |
|
DEFINE_ARRAY( CWeaponBox, m_rgpPlayerItems, FIELD_CLASSPTR, MAX_ITEM_TYPES ), |
|
DEFINE_FIELD( CWeaponBox, m_cAmmoTypes, FIELD_INTEGER ), |
|
}; |
|
|
|
IMPLEMENT_SAVERESTORE( CWeaponBox, CBaseEntity ) |
|
|
|
//========================================================= |
|
// |
|
//========================================================= |
|
void CWeaponBox::Precache( void ) |
|
{ |
|
PRECACHE_MODEL( "models/w_weaponbox.mdl" ); |
|
} |
|
|
|
//========================================================= |
|
//========================================================= |
|
void CWeaponBox::KeyValue( KeyValueData *pkvd ) |
|
{ |
|
if( m_cAmmoTypes < MAX_AMMO_SLOTS ) |
|
{ |
|
PackAmmo( ALLOC_STRING( pkvd->szKeyName ), atoi( pkvd->szValue ) ); |
|
m_cAmmoTypes++;// count this new ammo type. |
|
|
|
pkvd->fHandled = TRUE; |
|
} |
|
else |
|
{ |
|
ALERT( at_console, "WeaponBox too full! only %d ammotypes allowed\n", MAX_AMMO_SLOTS ); |
|
} |
|
} |
|
|
|
//========================================================= |
|
// CWeaponBox - Spawn |
|
//========================================================= |
|
void CWeaponBox::Spawn( void ) |
|
{ |
|
Precache(); |
|
|
|
pev->movetype = MOVETYPE_TOSS; |
|
pev->solid = SOLID_TRIGGER; |
|
|
|
UTIL_SetSize( pev, g_vecZero, g_vecZero ); |
|
|
|
SET_MODEL( ENT( pev ), "models/w_weaponbox.mdl" ); |
|
} |
|
|
|
//========================================================= |
|
// CWeaponBox - Kill - the think function that removes the |
|
// box from the world. |
|
//========================================================= |
|
void CWeaponBox::Kill( void ) |
|
{ |
|
CBasePlayerItem *pWeapon; |
|
int i; |
|
|
|
// destroy the weapons |
|
for( i = 0; i < MAX_ITEM_TYPES; i++ ) |
|
{ |
|
pWeapon = m_rgpPlayerItems[i]; |
|
|
|
while( pWeapon ) |
|
{ |
|
pWeapon->SetThink( &CBaseEntity::SUB_Remove ); |
|
pWeapon->pev->nextthink = gpGlobals->time + 0.1; |
|
pWeapon = pWeapon->m_pNext; |
|
} |
|
} |
|
|
|
// remove the box |
|
UTIL_Remove( this ); |
|
} |
|
|
|
//========================================================= |
|
// CWeaponBox - Touch: try to add my contents to the toucher |
|
// if the toucher is a player. |
|
//========================================================= |
|
void CWeaponBox::Touch( CBaseEntity *pOther ) |
|
{ |
|
if( !( pev->flags & FL_ONGROUND ) ) |
|
{ |
|
return; |
|
} |
|
|
|
if( !pOther->IsPlayer() ) |
|
{ |
|
// only players may touch a weaponbox. |
|
return; |
|
} |
|
|
|
if( !pOther->IsAlive() ) |
|
{ |
|
// no dead guys. |
|
return; |
|
} |
|
|
|
CBasePlayer *pPlayer = (CBasePlayer *)pOther; |
|
int i; |
|
|
|
// dole out ammo |
|
for( i = 0; i < MAX_AMMO_SLOTS; i++ ) |
|
{ |
|
if( !FStringNull( m_rgiszAmmo[i] ) ) |
|
{ |
|
// there's some ammo of this type. |
|
pPlayer->GiveAmmo( m_rgAmmo[i], (char *)STRING( m_rgiszAmmo[i] ), MaxAmmoCarry( m_rgiszAmmo[i] ) ); |
|
|
|
//ALERT( at_console, "Gave %d rounds of %s\n", m_rgAmmo[i], STRING( m_rgiszAmmo[i] ) ); |
|
|
|
// now empty the ammo from the weaponbox since we just gave it to the player |
|
m_rgiszAmmo[i] = iStringNull; |
|
m_rgAmmo[i] = 0; |
|
} |
|
} |
|
|
|
// go through my weapons and try to give the usable ones to the player. |
|
// it's important the the player be given ammo first, so the weapons code doesn't refuse |
|
// to deploy a better weapon that the player may pick up because he has no ammo for it. |
|
for( i = 0; i < MAX_ITEM_TYPES; i++ ) |
|
{ |
|
if( m_rgpPlayerItems[i] ) |
|
{ |
|
CBasePlayerItem *pItem; |
|
|
|
// have at least one weapon in this slot |
|
while( m_rgpPlayerItems[i] ) |
|
{ |
|
//ALERT( at_console, "trying to give %s\n", STRING( m_rgpPlayerItems[i]->pev->classname ) ); |
|
|
|
pItem = m_rgpPlayerItems[i]; |
|
m_rgpPlayerItems[i] = m_rgpPlayerItems[i]->m_pNext;// unlink this weapon from the box |
|
|
|
if( pPlayer->AddPlayerItem( pItem ) ) |
|
{ |
|
pItem->AttachToPlayer( pPlayer ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
EMIT_SOUND( pOther->edict(), CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM ); |
|
SetTouch( NULL ); |
|
UTIL_Remove(this); |
|
} |
|
|
|
//========================================================= |
|
// CWeaponBox - PackWeapon: Add this weapon to the box |
|
//========================================================= |
|
BOOL CWeaponBox::PackWeapon( CBasePlayerItem *pWeapon ) |
|
{ |
|
// is one of these weapons already packed in this box? |
|
if( HasWeapon( pWeapon ) ) |
|
{ |
|
return FALSE;// box can only hold one of each weapon type |
|
} |
|
|
|
if( pWeapon->m_pPlayer ) |
|
{ |
|
if( !pWeapon->m_pPlayer->RemovePlayerItem( pWeapon ) ) |
|
{ |
|
// failed to unhook the weapon from the player! |
|
return FALSE; |
|
} |
|
} |
|
|
|
int iWeaponSlot = pWeapon->iItemSlot(); |
|
|
|
if( m_rgpPlayerItems[iWeaponSlot] ) |
|
{ |
|
// there's already one weapon in this slot, so link this into the slot's column |
|
pWeapon->m_pNext = m_rgpPlayerItems[iWeaponSlot]; |
|
m_rgpPlayerItems[iWeaponSlot] = pWeapon; |
|
} |
|
else |
|
{ |
|
// first weapon we have for this slot |
|
m_rgpPlayerItems[iWeaponSlot] = pWeapon; |
|
pWeapon->m_pNext = NULL; |
|
} |
|
|
|
pWeapon->pev->spawnflags |= SF_NORESPAWN;// never respawn |
|
pWeapon->pev->movetype = MOVETYPE_NONE; |
|
pWeapon->pev->solid = SOLID_NOT; |
|
pWeapon->pev->effects = EF_NODRAW; |
|
pWeapon->pev->modelindex = 0; |
|
pWeapon->pev->model = iStringNull; |
|
pWeapon->pev->owner = edict(); |
|
pWeapon->SetThink( NULL );// crowbar may be trying to swing again, etc. |
|
pWeapon->SetTouch( NULL ); |
|
pWeapon->m_pPlayer = NULL; |
|
|
|
//ALERT( at_console, "packed %s\n", STRING( pWeapon->pev->classname ) ); |
|
|
|
return TRUE; |
|
} |
|
|
|
//========================================================= |
|
// CWeaponBox - PackAmmo |
|
//========================================================= |
|
BOOL CWeaponBox::PackAmmo( int iszName, int iCount ) |
|
{ |
|
int iMaxCarry; |
|
|
|
if( FStringNull( iszName ) ) |
|
{ |
|
// error here |
|
ALERT( at_console, "NULL String in PackAmmo!\n" ); |
|
return FALSE; |
|
} |
|
|
|
iMaxCarry = MaxAmmoCarry( iszName ); |
|
|
|
if( iMaxCarry != -1 && iCount > 0 ) |
|
{ |
|
//ALERT( at_console, "Packed %d rounds of %s\n", iCount, STRING( iszName ) ); |
|
GiveAmmo( iCount, (char *)STRING( iszName ), iMaxCarry ); |
|
return TRUE; |
|
} |
|
|
|
return FALSE; |
|
} |
|
|
|
//========================================================= |
|
// CWeaponBox - GiveAmmo |
|
//========================================================= |
|
int CWeaponBox::GiveAmmo( int iCount, char *szName, int iMax, int *pIndex/* = NULL*/ ) |
|
{ |
|
int i; |
|
|
|
for( i = 1; i < MAX_AMMO_SLOTS && !FStringNull( m_rgiszAmmo[i] ); i++ ) |
|
{ |
|
if( stricmp( szName, STRING( m_rgiszAmmo[i] ) ) == 0 ) |
|
{ |
|
if( pIndex ) |
|
*pIndex = i; |
|
|
|
int iAdd = min( iCount, iMax - m_rgAmmo[i] ); |
|
if( iCount == 0 || iAdd > 0 ) |
|
{ |
|
m_rgAmmo[i] += iAdd; |
|
|
|
return i; |
|
} |
|
return -1; |
|
} |
|
} |
|
if( i < MAX_AMMO_SLOTS ) |
|
{ |
|
if( pIndex ) |
|
*pIndex = i; |
|
|
|
m_rgiszAmmo[i] = MAKE_STRING( szName ); |
|
m_rgAmmo[i] = iCount; |
|
|
|
return i; |
|
} |
|
ALERT( at_console, "out of named ammo slots\n" ); |
|
return i; |
|
} |
|
|
|
//========================================================= |
|
// CWeaponBox::HasWeapon - is a weapon of this type already |
|
// packed in this box? |
|
//========================================================= |
|
BOOL CWeaponBox::HasWeapon( CBasePlayerItem *pCheckItem ) |
|
{ |
|
CBasePlayerItem *pItem = m_rgpPlayerItems[pCheckItem->iItemSlot()]; |
|
|
|
while( pItem ) |
|
{ |
|
if( FClassnameIs( pItem->pev, STRING( pCheckItem->pev->classname ) ) ) |
|
{ |
|
return TRUE; |
|
} |
|
pItem = pItem->m_pNext; |
|
} |
|
|
|
return FALSE; |
|
} |
|
|
|
//========================================================= |
|
// CWeaponBox::IsEmpty - is there anything in this box? |
|
//========================================================= |
|
BOOL CWeaponBox::IsEmpty( void ) |
|
{ |
|
int i; |
|
|
|
for( i = 0; i < MAX_ITEM_TYPES; i++ ) |
|
{ |
|
if( m_rgpPlayerItems[i] ) |
|
{ |
|
return FALSE; |
|
} |
|
} |
|
|
|
for( i = 0; i < MAX_AMMO_SLOTS; i++ ) |
|
{ |
|
if( !FStringNull( m_rgiszAmmo[i] ) ) |
|
{ |
|
// still have a bit of this type of ammo |
|
return FALSE; |
|
} |
|
} |
|
|
|
return TRUE; |
|
} |
|
|
|
//========================================================= |
|
//========================================================= |
|
void CWeaponBox::SetObjectCollisionBox( void ) |
|
{ |
|
pev->absmin = pev->origin + Vector( -16, -16, 0 ); |
|
pev->absmax = pev->origin + Vector( 16, 16, 16 ); |
|
} |
|
|
|
void CBasePlayerWeapon::PrintState( void ) |
|
{ |
|
ALERT( at_console, "primary: %f\n", m_flNextPrimaryAttack ); |
|
ALERT( at_console, "idle : %f\n", m_flTimeWeaponIdle ); |
|
|
|
//ALERT( at_console, "nextrl : %f\n", m_flNextReload ); |
|
//ALERT( at_console, "nextpum: %f\n", m_flPumpTime ); |
|
|
|
//ALERT( at_console, "m_frt : %f\n", m_fReloadTime ); |
|
ALERT( at_console, "m_finre: %i\n", m_fInReload ); |
|
//ALERT( at_console, "m_finsr: %i\n", m_fInSpecialReload ); |
|
|
|
ALERT( at_console, "m_iclip: %i\n", m_iClip ); |
|
} |
|
|
|
TYPEDESCRIPTION CRpg::m_SaveData[] = |
|
{ |
|
DEFINE_FIELD( CRpg, m_fSpotActive, FIELD_INTEGER ), |
|
DEFINE_FIELD( CRpg, m_cActiveRockets, FIELD_INTEGER ), |
|
}; |
|
|
|
IMPLEMENT_SAVERESTORE( CRpg, CBasePlayerWeapon ) |
|
|
|
TYPEDESCRIPTION CRpgRocket::m_SaveData[] = |
|
{ |
|
DEFINE_FIELD( CRpgRocket, m_flIgniteTime, FIELD_TIME ), |
|
DEFINE_FIELD( CRpgRocket, m_pLauncher, FIELD_CLASSPTR ), |
|
}; |
|
|
|
IMPLEMENT_SAVERESTORE( CRpgRocket, CGrenade ) |
|
|
|
TYPEDESCRIPTION CShotgun::m_SaveData[] = |
|
{ |
|
DEFINE_FIELD( CShotgun, m_flNextReload, FIELD_TIME ), |
|
DEFINE_FIELD( CShotgun, m_fInSpecialReload, FIELD_INTEGER ), |
|
DEFINE_FIELD( CShotgun, m_flNextReload, FIELD_TIME ), |
|
// DEFINE_FIELD( CShotgun, m_iShell, FIELD_INTEGER ), |
|
DEFINE_FIELD( CShotgun, m_flPumpTime, FIELD_TIME ), |
|
}; |
|
|
|
IMPLEMENT_SAVERESTORE( CShotgun, CBasePlayerWeapon ) |
|
|
|
TYPEDESCRIPTION CGauss::m_SaveData[] = |
|
{ |
|
DEFINE_FIELD( CGauss, m_fInAttack, FIELD_INTEGER ), |
|
//DEFINE_FIELD( CGauss, m_flStartCharge, FIELD_TIME ), |
|
//DEFINE_FIELD( CGauss, m_flPlayAftershock, FIELD_TIME ), |
|
//DEFINE_FIELD( CGauss, m_flNextAmmoBurn, FIELD_TIME ), |
|
DEFINE_FIELD( CGauss, m_fPrimaryFire, FIELD_BOOLEAN ), |
|
}; |
|
|
|
IMPLEMENT_SAVERESTORE( CGauss, CBasePlayerWeapon ) |
|
|
|
TYPEDESCRIPTION CEgon::m_SaveData[] = |
|
{ |
|
//DEFINE_FIELD( CEgon, m_pBeam, FIELD_CLASSPTR ), |
|
//DEFINE_FIELD( CEgon, m_pNoise, FIELD_CLASSPTR ), |
|
//DEFINE_FIELD( CEgon, m_pSprite, FIELD_CLASSPTR ), |
|
DEFINE_FIELD( CEgon, m_shootTime, FIELD_TIME ), |
|
DEFINE_FIELD( CEgon, m_fireState, FIELD_INTEGER ), |
|
DEFINE_FIELD( CEgon, m_fireMode, FIELD_INTEGER ), |
|
DEFINE_FIELD( CEgon, m_shakeTime, FIELD_TIME ), |
|
DEFINE_FIELD( CEgon, m_flAmmoUseTime, FIELD_TIME ), |
|
}; |
|
|
|
IMPLEMENT_SAVERESTORE( CEgon, CBasePlayerWeapon ) |
|
|
|
TYPEDESCRIPTION CSatchel::m_SaveData[] = |
|
{ |
|
DEFINE_FIELD( CSatchel, m_chargeReady, FIELD_INTEGER ), |
|
}; |
|
|
|
IMPLEMENT_SAVERESTORE( CSatchel, CBasePlayerWeapon ) |
|
|
|
TYPEDESCRIPTION CDisplacer::m_SaveData[] = |
|
{ |
|
DEFINE_FIELD( CDisplacer, m_iFireState, FIELD_INTEGER ), |
|
DEFINE_FIELD( CDisplacer, m_iFireMode, FIELD_INTEGER ), |
|
DEFINE_FIELD( CDisplacer, m_hTargetEarth, FIELD_CLASSPTR ), |
|
DEFINE_FIELD( CDisplacer, m_hTargetXen, FIELD_CLASSPTR ), |
|
}; |
|
IMPLEMENT_SAVERESTORE( CDisplacer, CBasePlayerWeapon ) |
|
|
|
TYPEDESCRIPTION CEagle::m_SaveData[] = |
|
{ |
|
DEFINE_FIELD( CEagle, m_fEagleLaserActive, FIELD_INTEGER ), |
|
}; |
|
|
|
IMPLEMENT_SAVERESTORE( CEagle, CBasePlayerWeapon ) |
|
|
|
TYPEDESCRIPTION CGrapple::m_SaveData[] = |
|
{ |
|
DEFINE_FIELD( CGrapple, m_iFirestate, FIELD_INTEGER ), |
|
DEFINE_FIELD( CGrapple, m_iHitFlags, FIELD_INTEGER ), |
|
DEFINE_FIELD( CGrapple, m_fTipHit, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( CGrapple, m_pTongueTip, FIELD_CLASSPTR ), |
|
DEFINE_FIELD( CGrapple, m_pBeam, FIELD_CLASSPTR ), |
|
DEFINE_FIELD( CGrapple, m_flNextPullSoundTime, FIELD_TIME ), |
|
DEFINE_FIELD( CGrapple, m_fPlayPullSound, FIELD_BOOLEAN ), |
|
}; |
|
IMPLEMENT_SAVERESTORE( CGrapple, CBasePlayerWeapon ) |
|
|
|
TYPEDESCRIPTION CM249::m_SaveData[] = |
|
{ |
|
DEFINE_FIELD( CM249, m_iReloadState, FIELD_INTEGER ), |
|
}; |
|
IMPLEMENT_SAVERESTORE( CM249, CBasePlayerWeapon ) |
|
|
|
TYPEDESCRIPTION CPipeWrench::m_SaveData[] = |
|
{ |
|
DEFINE_FIELD( CPipeWrench, m_iFirestate, FIELD_INTEGER ), |
|
DEFINE_FIELD( CPipeWrench, m_flHoldStartTime, FIELD_TIME ), |
|
}; |
|
IMPLEMENT_SAVERESTORE( CPipeWrench, CBasePlayerWeapon ) |
|
|
|
TYPEDESCRIPTION CShockrifle::m_SaveData[] = |
|
{ |
|
DEFINE_FIELD( CShockrifle, m_fShouldUpdateEffects, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( CShockrifle, m_flBeamLifeTime, FIELD_TIME ), |
|
}; |
|
IMPLEMENT_SAVERESTORE( CShockrifle, CHgun ) |
|
|
|
TYPEDESCRIPTION CSniperrifle::m_SaveData[] = |
|
{ |
|
DEFINE_FIELD( CSniperrifle, m_fNeedAjustBolt, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( CSniperrifle, m_iBoltState, FIELD_INTEGER ), |
|
}; |
|
IMPLEMENT_SAVERESTORE( CSniperrifle, CBasePlayerWeapon ) |
|
|
|
TYPEDESCRIPTION CSporelauncher::m_SaveData[] = |
|
{ |
|
DEFINE_FIELD( CSporelauncher, m_iSquidSpitSprite, FIELD_INTEGER ), |
|
}; |
|
IMPLEMENT_SAVERESTORE( CSporelauncher, CShotgun )
|
|
|