hlsdk-xash3d/dlls/func_break.cpp

1047 lines
25 KiB
C++

/***
*
* 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.
*
****/
/*
===== bmodels.cpp ========================================================
spawn, think, and use functions for entities that use brush models
*/
#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "saverestore.h"
#include "func_break.h"
#include "decals.h"
#include "explode.h"
#include "game.h"
extern DLL_GLOBAL Vector g_vecAttackDir;
// =================== FUNC_Breakable ==============================================
// Just add more items to the bottom of this array and they will automagically be supported
// This is done instead of just a classname in the FGD so we can control which entities can
// be spawned, and still remain fairly flexible
const char *CBreakable::pSpawnObjects[] =
{
NULL, // 0
"item_battery", // 1
"item_healthkit", // 2
"weapon_9mmhandgun", // 3
"ammo_9mmclip", // 4
"weapon_9mmAR", // 5
"ammo_9mmAR", // 6
"ammo_ARgrenades", // 7
"weapon_shotgun", // 8
"ammo_buckshot", // 9
"weapon_crossbow", // 10
"ammo_crossbow", // 11
"weapon_357", // 12
"ammo_357", // 13
"weapon_rpg", // 14
"ammo_rpgclip", // 15
"ammo_gaussclip", // 16
"weapon_handgrenade", // 17
"weapon_tripmine", // 18
"weapon_satchel", // 19
"weapon_snark", // 20
"weapon_hornetgun", // 21
};
void CBreakable::KeyValue( KeyValueData* pkvd )
{
// UNDONE_WC: explicitly ignoring these fields, but they shouldn't be in the map file!
if( FStrEq( pkvd->szKeyName, "explosion" ) )
{
if( !stricmp( pkvd->szValue, "directed" ) )
m_Explosion = expDirected;
else if( !stricmp( pkvd->szValue, "random" ) )
m_Explosion = expRandom;
else
m_Explosion = expRandom;
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "material" ) )
{
int i = atoi( pkvd->szValue );
// 0:glass, 1:metal, 2:flesh, 3:wood
if( ( i < 0 ) || ( i >= matLastMaterial ) )
m_Material = matWood;
else
m_Material = (Materials)i;
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "deadmodel" ) )
{
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "shards" ) )
{
//m_iShards = atof( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "gibmodel" ) )
{
m_iszGibModel = ALLOC_STRING( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "spawnobject" ) )
{
int object = atoi( pkvd->szValue );
if( object > 0 && object < (int)ARRAYSIZE( pSpawnObjects ) )
m_iszSpawnObject = MAKE_STRING( pSpawnObjects[object] );
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "explodemagnitude" ) )
{
ExplosionSetMagnitude( atoi( pkvd->szValue ) );
pkvd->fHandled = TRUE;
}
else if( FStrEq( pkvd->szKeyName, "lip" ) )
pkvd->fHandled = TRUE;
else
CBaseDelay::KeyValue( pkvd );
}
//
// func_breakable - bmodel that breaks into pieces after taking damage
//
LINK_ENTITY_TO_CLASS( func_breakable, CBreakable )
TYPEDESCRIPTION CBreakable::m_SaveData[] =
{
DEFINE_FIELD( CBreakable, m_Material, FIELD_INTEGER ),
DEFINE_FIELD( CBreakable, m_Explosion, FIELD_INTEGER ),
// Don't need to save/restore these because we precache after restore
// DEFINE_FIELD( CBreakable, m_idShard, FIELD_INTEGER ),
DEFINE_FIELD( CBreakable, m_angle, FIELD_FLOAT ),
DEFINE_FIELD( CBreakable, m_iszGibModel, FIELD_STRING ),
DEFINE_FIELD( CBreakable, m_iszSpawnObject, FIELD_STRING ),
// Explosion magnitude is stored in pev->impulse
};
IMPLEMENT_SAVERESTORE( CBreakable, CBaseDelay )
void CBreakable::Spawn( void )
{
Precache();
if( FBitSet( pev->spawnflags, SF_BREAK_TRIGGER_ONLY ) )
pev->takedamage = DAMAGE_NO;
else
pev->takedamage = DAMAGE_YES;
pev->solid = SOLID_BSP;
pev->movetype = MOVETYPE_PUSH;
m_angle = pev->angles.y;
pev->angles.y = 0;
// HACK: matGlass can receive decals, we need the client to know about this
// so use class to store the material flag
if( m_Material == matGlass )
{
pev->playerclass = 1;
}
SET_MODEL( ENT( pev ), STRING( pev->model ) );//set size and link into world.
SetTouch( &CBreakable::BreakTouch );
if( FBitSet( pev->spawnflags, SF_BREAK_TRIGGER_ONLY ) ) // Only break on trigger
SetTouch( NULL );
// Flag unbreakable glass as "worldbrush" so it will block ALL tracelines
if( !IsBreakable() && pev->rendermode != kRenderNormal )
pev->flags |= FL_WORLDBRUSH;
}
const char *CBreakable::pSoundsWood[] =
{
"debris/wood1.wav",
"debris/wood2.wav",
"debris/wood3.wav",
};
const char *CBreakable::pSoundsFlesh[] =
{
"debris/flesh1.wav",
"debris/flesh2.wav",
"debris/flesh3.wav",
"debris/flesh5.wav",
"debris/flesh6.wav",
"debris/flesh7.wav",
};
const char *CBreakable::pSoundsMetal[] =
{
"debris/metal1.wav",
"debris/metal2.wav",
"debris/metal3.wav",
};
const char *CBreakable::pSoundsConcrete[] =
{
"debris/concrete1.wav",
"debris/concrete2.wav",
"debris/concrete3.wav",
};
const char *CBreakable::pSoundsGlass[] =
{
"debris/glass1.wav",
"debris/glass2.wav",
"debris/glass3.wav",
};
const char **CBreakable::MaterialSoundList( Materials precacheMaterial, int &soundCount )
{
const char **pSoundList = NULL;
switch( precacheMaterial )
{
case matWood:
pSoundList = pSoundsWood;
soundCount = ARRAYSIZE( pSoundsWood );
break;
case matFlesh:
pSoundList = pSoundsFlesh;
soundCount = ARRAYSIZE( pSoundsFlesh );
break;
case matComputer:
case matUnbreakableGlass:
case matGlass:
pSoundList = pSoundsGlass;
soundCount = ARRAYSIZE( pSoundsGlass );
break;
case matMetal:
pSoundList = pSoundsMetal;
soundCount = ARRAYSIZE( pSoundsMetal );
break;
case matCinderBlock:
case matRocks:
pSoundList = pSoundsConcrete;
soundCount = ARRAYSIZE( pSoundsConcrete );
break;
case matCeilingTile:
case matNone:
default:
soundCount = 0;
break;
}
return pSoundList;
}
void CBreakable::MaterialSoundPrecache( Materials precacheMaterial )
{
const char **pSoundList;
int i, soundCount = 0;
pSoundList = MaterialSoundList( precacheMaterial, soundCount );
for( i = 0; i < soundCount; i++ )
{
PRECACHE_SOUND( pSoundList[i] );
}
}
void CBreakable::MaterialSoundRandom( edict_t *pEdict, Materials soundMaterial, float volume )
{
const char **pSoundList;
int soundCount = 0;
pSoundList = MaterialSoundList( soundMaterial, soundCount );
if( soundCount )
EMIT_SOUND( pEdict, CHAN_BODY, pSoundList[RANDOM_LONG( 0, soundCount - 1 )], volume, 1.0f );
}
void CBreakable::Precache( void )
{
const char *pGibName = NULL;
switch( m_Material )
{
case matWood:
pGibName = "models/woodgibs.mdl";
PRECACHE_SOUND( "debris/bustcrate1.wav" );
PRECACHE_SOUND( "debris/bustcrate2.wav" );
break;
case matFlesh:
pGibName = "models/fleshgibs.mdl";
PRECACHE_SOUND( "debris/bustflesh1.wav" );
PRECACHE_SOUND( "debris/bustflesh2.wav" );
break;
case matComputer:
PRECACHE_SOUND( "buttons/spark5.wav" );
PRECACHE_SOUND( "buttons/spark6.wav" );
pGibName = "models/computergibs.mdl";
PRECACHE_SOUND( "debris/bustmetal1.wav" );
PRECACHE_SOUND( "debris/bustmetal2.wav" );
break;
case matUnbreakableGlass:
case matGlass:
pGibName = "models/glassgibs.mdl";
PRECACHE_SOUND( "debris/bustglass1.wav" );
PRECACHE_SOUND( "debris/bustglass2.wav" );
break;
case matMetal:
pGibName = "models/metalplategibs.mdl";
PRECACHE_SOUND( "debris/bustmetal1.wav" );
PRECACHE_SOUND( "debris/bustmetal2.wav" );
break;
case matCinderBlock:
pGibName = "models/cindergibs.mdl";
PRECACHE_SOUND( "debris/bustconcrete1.wav" );
PRECACHE_SOUND( "debris/bustconcrete2.wav" );
break;
case matRocks:
pGibName = "models/rockgibs.mdl";
PRECACHE_SOUND( "debris/bustconcrete1.wav" );
PRECACHE_SOUND( "debris/bustconcrete2.wav" );
break;
case matCeilingTile:
pGibName = "models/ceilinggibs.mdl";
PRECACHE_SOUND( "debris/bustceiling.wav" );
break;
case matNone:
case matLastMaterial:
break;
default:
break;
}
MaterialSoundPrecache( m_Material );
if( m_iszGibModel )
pGibName = STRING( m_iszGibModel );
m_idShard = PRECACHE_MODEL( pGibName );
// Precache the spawn item's data
if( m_iszSpawnObject )
UTIL_PrecacheOther( STRING( m_iszSpawnObject ) );
}
// play shard sound when func_breakable takes damage.
// the more damage, the louder the shard sound.
void CBreakable::DamageSound( void )
{
int pitch;
float fvol;
const char *rgpsz[6];
int i = 0;
int material = m_Material;
//if( RANDOM_LONG( 0, 1 ) )
// return;
if( RANDOM_LONG( 0, 2 ) )
pitch = PITCH_NORM;
else
pitch = 95 + RANDOM_LONG( 0, 34 );
fvol = RANDOM_FLOAT( 0.75, 1.0 );
if( material == matComputer && RANDOM_LONG( 0, 1 ) )
material = matMetal;
switch( material )
{
case matComputer:
case matGlass:
case matUnbreakableGlass:
rgpsz[0] = "debris/glass1.wav";
rgpsz[1] = "debris/glass2.wav";
rgpsz[2] = "debris/glass3.wav";
i = 3;
break;
case matWood:
rgpsz[0] = "debris/wood1.wav";
rgpsz[1] = "debris/wood2.wav";
rgpsz[2] = "debris/wood3.wav";
i = 3;
break;
case matMetal:
rgpsz[0] = "debris/metal1.wav";
rgpsz[1] = "debris/metal3.wav";
rgpsz[2] = "debris/metal2.wav";
i = 2;
break;
case matFlesh:
rgpsz[0] = "debris/flesh1.wav";
rgpsz[1] = "debris/flesh2.wav";
rgpsz[2] = "debris/flesh3.wav";
rgpsz[3] = "debris/flesh5.wav";
rgpsz[4] = "debris/flesh6.wav";
rgpsz[5] = "debris/flesh7.wav";
i = 6;
break;
case matRocks:
case matCinderBlock:
rgpsz[0] = "debris/concrete1.wav";
rgpsz[1] = "debris/concrete2.wav";
rgpsz[2] = "debris/concrete3.wav";
i = 3;
break;
case matCeilingTile:
// UNDONE: no ceiling tile shard sound yet
i = 0;
break;
}
if( i )
EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, rgpsz[ RANDOM_LONG( 0, i - 1 )], fvol, ATTN_NORM, 0, pitch );
}
void CBreakable::BreakTouch( CBaseEntity *pOther )
{
float flDamage;
entvars_t* pevToucher = pOther->pev;
// only players can break these right now
if( !pOther->IsPlayer() || !IsBreakable() )
{
return;
}
if( FBitSet( pev->spawnflags, SF_BREAK_TOUCH ) )
{
// can be broken when run into
flDamage = pevToucher->velocity.Length() * 0.01f;
if( flDamage >= pev->health )
{
SetTouch( NULL );
TakeDamage( pevToucher, pevToucher, flDamage, DMG_CRUSH );
// do a little damage to player if we broke glass or computer
pOther->TakeDamage( pev, pev, flDamage/4, DMG_SLASH );
}
}
if( FBitSet( pev->spawnflags, SF_BREAK_PRESSURE ) && pevToucher->absmin.z >= pev->maxs.z - 2 )
{
// can be broken when stood upon
// play creaking sound here.
DamageSound();
SetThink( &CBreakable::Die );
SetTouch( NULL );
if( m_flDelay == 0.0f )
{
// !!!BUGBUG - why doesn't zero delay work?
m_flDelay = 0.1f;
}
pev->nextthink = pev->ltime + m_flDelay;
}
}
//
// Smash the our breakable object
//
// Break when triggered
void CBreakable::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
if( IsBreakable() )
{
pev->angles.y = m_angle;
UTIL_MakeVectors( pev->angles );
g_vecAttackDir = gpGlobals->v_forward;
Die();
}
}
void CBreakable::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType )
{
// random spark if this is a 'computer' object
if( RANDOM_LONG( 0, 1 ) )
{
switch( m_Material )
{
case matComputer:
{
UTIL_Sparks( ptr->vecEndPos );
float flVolume = RANDOM_FLOAT( 0.7f, 1.0f );//random volume range
switch( RANDOM_LONG( 0, 1 ) )
{
case 0:
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "buttons/spark5.wav", flVolume, ATTN_NORM );
break;
case 1:
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "buttons/spark6.wav", flVolume, ATTN_NORM );
break;
}
}
break;
case matUnbreakableGlass:
UTIL_Ricochet( ptr->vecEndPos, RANDOM_FLOAT( 0.5f, 1.5f ) );
break;
default:
break;
}
}
CBaseDelay::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType );
}
//=========================================================
// Special takedamage for func_breakable. Allows us to make
// exceptions that are breakable-specific
// bitsDamageType indicates the type of damage sustained ie: DMG_CRUSH
//=========================================================
int CBreakable::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType )
{
Vector vecTemp;
// if Attacker == Inflictor, the attack was a melee or other instant-hit attack.
// (that is, no actual entity projectile was involved in the attack so use the shooter's origin).
if( pevAttacker == pevInflictor )
{
vecTemp = pevInflictor->origin - ( pev->absmin + ( pev->size * 0.5f ) );
// if a client hit the breakable with a crowbar, and breakable is crowbar-sensitive, break it now.
if( FBitSet ( pevAttacker->flags, FL_CLIENT ) &&
FBitSet ( pev->spawnflags, SF_BREAK_CROWBAR ) && ( bitsDamageType & DMG_CLUB ) )
flDamage = pev->health;
}
else
// an actual missile was involved.
{
vecTemp = pevInflictor->origin - ( pev->absmin + ( pev->size * 0.5f ) );
}
if( !IsBreakable() )
return 0;
// Breakables take double damage from the crowbar
if( bitsDamageType & DMG_CLUB )
flDamage *= 2.0f;
// Boxes / glass / etc. don't take much poison damage, just the impact of the dart - consider that 10%
if( bitsDamageType & DMG_POISON )
flDamage *= 0.1f;
// this global is still used for glass and other non-monster killables, along with decals.
g_vecAttackDir = vecTemp.Normalize();
// do the damage
pev->health -= flDamage;
if( pev->health <= 0 )
{
Killed( pevAttacker, GIB_NORMAL );
Die();
return 0;
}
// Make a shard noise each time func breakable is hit.
// Don't play shard noise if cbreakable actually died.
DamageSound();
return 1;
}
void CBreakable::Die( void )
{
Vector vecSpot;// shard origin
Vector vecVelocity;// shard velocity
char cFlag = 0;
int pitch;
float fvol;
pitch = 95 + RANDOM_LONG( 0, 29 );
if( pitch > 97 && pitch < 103 )
pitch = 100;
// The more negative pev->health, the louder
// the sound should be.
fvol = RANDOM_FLOAT( 0.85f, 1.0 ) + ( fabs( pev->health ) / 100.0f );
if( fvol > 1.0f )
fvol = 1.0f;
switch( m_Material )
{
case matGlass:
switch( RANDOM_LONG( 0, 1 ) )
{
case 0:
EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, "debris/bustglass1.wav", fvol, ATTN_NORM, 0, pitch );
break;
case 1:
EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, "debris/bustglass2.wav", fvol, ATTN_NORM, 0, pitch );
break;
}
cFlag = BREAK_GLASS;
break;
case matWood:
switch( RANDOM_LONG( 0, 1 ) )
{
case 0:
EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, "debris/bustcrate1.wav", fvol, ATTN_NORM, 0, pitch );
break;
case 1:
EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, "debris/bustcrate2.wav", fvol, ATTN_NORM, 0, pitch );
break;
}
cFlag = BREAK_WOOD;
break;
case matComputer:
case matMetal:
switch( RANDOM_LONG( 0, 1 ) )
{
case 0:
EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, "debris/bustmetal1.wav", fvol, ATTN_NORM, 0, pitch );
break;
case 1:
EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, "debris/bustmetal2.wav", fvol, ATTN_NORM, 0, pitch );
break;
}
cFlag = BREAK_METAL;
break;
case matFlesh:
switch( RANDOM_LONG( 0, 1 ) )
{
case 0:
EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, "debris/bustflesh1.wav", fvol, ATTN_NORM, 0, pitch );
break;
case 1:
EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, "debris/bustflesh2.wav", fvol, ATTN_NORM, 0, pitch );
break;
}
cFlag = BREAK_FLESH;
break;
case matRocks:
case matCinderBlock:
switch( RANDOM_LONG( 0, 1 ) )
{
case 0:
EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, "debris/bustconcrete1.wav", fvol, ATTN_NORM, 0, pitch );
break;
case 1:
EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, "debris/bustconcrete2.wav", fvol, ATTN_NORM, 0, pitch );
break;
}
cFlag = BREAK_CONCRETE;
break;
case matCeilingTile:
EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, "debris/bustceiling.wav", fvol, ATTN_NORM, 0, pitch );
break;
case matNone:
case matLastMaterial:
case matUnbreakableGlass:
break;
default:
break;
}
if( m_Explosion == expDirected )
vecVelocity = g_vecAttackDir * 200.0f;
else
{
vecVelocity.x = 0;
vecVelocity.y = 0;
vecVelocity.z = 0;
}
vecSpot = pev->origin + ( pev->mins + pev->maxs ) * 0.5f;
MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot );
WRITE_BYTE( TE_BREAKMODEL );
// position
WRITE_COORD( vecSpot.x );
WRITE_COORD( vecSpot.y );
WRITE_COORD( vecSpot.z );
// size
WRITE_COORD( pev->size.x );
WRITE_COORD( pev->size.y );
WRITE_COORD( pev->size.z );
// velocity
WRITE_COORD( vecVelocity.x );
WRITE_COORD( vecVelocity.y );
WRITE_COORD( vecVelocity.z );
// randomization
WRITE_BYTE( 10 );
// Model
WRITE_SHORT( m_idShard ); //model id#
// # of shards
WRITE_BYTE( 0 ); // let client decide
// duration
WRITE_BYTE( 25 );// 2.5 seconds
// flags
WRITE_BYTE( cFlag );
MESSAGE_END();
/*float size = pev->size.x;
if( size < pev->size.y )
size = pev->size.y;
if( size < pev->size.z )
size = pev->size.z;*/
// !!! HACK This should work!
// Build a box above the entity that looks like an 8 pixel high sheet
Vector mins = pev->absmin;
Vector maxs = pev->absmax;
mins.z = pev->absmax.z;
maxs.z += 8;
// BUGBUG -- can only find 256 entities on a breakable -- should be enough
CBaseEntity *pList[256];
int count = UTIL_EntitiesInBox( pList, 256, mins, maxs, FL_ONGROUND );
if( count )
{
for( int i = 0; i < count; i++ )
{
ClearBits( pList[i]->pev->flags, FL_ONGROUND );
pList[i]->pev->groundentity = NULL;
}
}
// Don't fire something that could fire myself
pev->targetname = 0;
pev->solid = SOLID_NOT;
// Fire targets on break
SUB_UseTargets( NULL, USE_TOGGLE, 0 );
SetThink( &CBaseEntity::SUB_Remove );
pev->nextthink = pev->ltime + 0.1f;
if( m_iszSpawnObject )
CBaseEntity::Create( STRING( m_iszSpawnObject ), VecBModelOrigin( pev ), pev->angles, edict() );
if( Explodable() )
{
ExplosionCreate( Center(), pev->angles, edict(), ExplosionMagnitude(), TRUE );
}
}
BOOL CBreakable::IsBreakable( void )
{
return m_Material != matUnbreakableGlass;
}
int CBreakable::DamageDecal( int bitsDamageType )
{
if( m_Material == matGlass )
return DECAL_GLASSBREAK1 + RANDOM_LONG( 0, 2 );
if( m_Material == matUnbreakableGlass )
return DECAL_BPROOF1;
return CBaseEntity::DamageDecal( bitsDamageType );
}
class CPushable : public CBreakable
{
public:
void Spawn ( void );
void Precache( void );
void Touch ( CBaseEntity *pOther );
void Move( CBaseEntity *pMover, int push );
void KeyValue( KeyValueData *pkvd );
void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
void EXPORT StopSound( void );
//virtual void SetActivator( CBaseEntity *pActivator ) { m_pPusher = pActivator; }
virtual int ObjectCaps( void ) { return ( CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION ) | FCAP_CONTINUOUS_USE; }
virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
inline float MaxSpeed( void ) { return m_maxSpeed; }
// breakables use an overridden takedamage
virtual int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType );
static TYPEDESCRIPTION m_SaveData[];
static const char *m_soundNames[3];
int m_lastSound; // no need to save/restore, just keeps the same sound from playing twice in a row
float m_maxSpeed;
float m_soundTime;
};
TYPEDESCRIPTION CPushable::m_SaveData[] =
{
DEFINE_FIELD( CPushable, m_maxSpeed, FIELD_FLOAT ),
DEFINE_FIELD( CPushable, m_soundTime, FIELD_TIME ),
};
IMPLEMENT_SAVERESTORE( CPushable, CBreakable )
LINK_ENTITY_TO_CLASS( func_pushable, CPushable )
const char *CPushable::m_soundNames[3] =
{
"debris/pushbox1.wav",
"debris/pushbox2.wav",
"debris/pushbox3.wav"
};
void CPushable::Spawn( void )
{
if( pev->spawnflags & SF_PUSH_BREAKABLE )
CBreakable::Spawn();
else
Precache();
pev->movetype = MOVETYPE_PUSHSTEP;
pev->solid = SOLID_BBOX;
SET_MODEL( ENT( pev ), STRING( pev->model ) );
if( pev->friction > 399 )
pev->friction = 399;
m_maxSpeed = 400 - pev->friction;
SetBits( pev->flags, FL_FLOAT );
pev->friction = 0;
pev->origin.z += 1; // Pick up off of the floor
UTIL_SetOrigin( pev, pev->origin );
// Multiply by area of the box's cross-section (assume 1000 units^3 standard volume)
pev->skin = (int)( ( pev->skin * ( pev->maxs.x - pev->mins.x ) * ( pev->maxs.y - pev->mins.y ) ) * 0.0005f );
m_soundTime = 0;
}
void CPushable::Precache( void )
{
for( int i = 0; i < 3; i++ )
PRECACHE_SOUND( m_soundNames[i] );
if( pev->spawnflags & SF_PUSH_BREAKABLE )
CBreakable::Precache();
}
void CPushable::KeyValue( KeyValueData *pkvd )
{
if( FStrEq( pkvd->szKeyName, "size" ) )
{
int bbox = atoi( pkvd->szValue );
pkvd->fHandled = TRUE;
switch( bbox )
{
case 0:
// Point
UTIL_SetSize( pev, Vector( -8.0f, -8.0f, -8.0f ), Vector( 8.0f, 8.0f, 8.0f ) );
break;
case 2:
// Big Hull!?!? !!!BUGBUG Figure out what this hull really is
UTIL_SetSize( pev, VEC_DUCK_HULL_MIN * 2.0f, VEC_DUCK_HULL_MAX * 2.0f );
break;
case 3:
// Player duck
UTIL_SetSize( pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX );
break;
default:
case 1:
// Player
UTIL_SetSize( pev, VEC_HULL_MIN, VEC_HULL_MAX );
break;
}
}
else if( FStrEq( pkvd->szKeyName, "buoyancy" ) )
{
pev->skin = atoi( pkvd->szValue );
pkvd->fHandled = TRUE;
}
else
CBreakable::KeyValue( pkvd );
}
// Pull the func_pushable
void CPushable::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
if( !pActivator || !pActivator->IsPlayer() )
{
if( pev->spawnflags & SF_PUSH_BREAKABLE )
this->CBreakable::Use( pActivator, pCaller, useType, value );
return;
}
if( pActivator->pev->velocity != g_vecZero )
Move( pActivator, 0 );
}
void CPushable::Touch( CBaseEntity *pOther )
{
if( FClassnameIs( pOther->pev, "worldspawn" ) )
return;
Move( pOther, 1 );
}
void CPushable::Move( CBaseEntity *pOther, int push )
{
entvars_t* pevToucher = pOther->pev;
int playerTouch = 0;
// Is entity standing on this pushable ?
if( FBitSet( pevToucher->flags,FL_ONGROUND ) && pevToucher->groundentity && VARS( pevToucher->groundentity ) == pev )
{
// Only push if floating
if( pev->waterlevel > 0 )
pev->velocity.z += pevToucher->velocity.z * 0.1f;
return;
}
if( pOther->IsPlayer() )
{
if( pushablemode.value == -1 )
{
// Don't push unless the player is pushing forward and NOT use (pull)
if( push && !( pevToucher->button & ( IN_FORWARD | IN_USE )))
return;
}
// g-cont. fix pushable acceleration bug (now implemented as cvar)
else if( pushablemode.value != 0 )
{
// Allow player push when moving right, left and back too
if( push && !( pevToucher->button & ( IN_FORWARD | IN_MOVERIGHT | IN_MOVELEFT | IN_BACK )))
return;
// Require player walking back when applying '+use' on pushable
if( !push && !( pevToucher->button & ( IN_BACK )))
return;
}
// Don't push when +use pressed
else if( push && ( pevToucher->button & ( IN_USE )))
return;
playerTouch = 1;
}
float factor;
if( playerTouch )
{
if( !( pevToucher->flags & FL_ONGROUND ) ) // Don't push away from jumping/falling players unless in water
{
if( pev->waterlevel < 1 )
return;
else
factor = 0.1f;
}
else
factor = 1.0f;
}
else
factor = 0.25f;
if( pushablemode.value != 0 )
{
pev->velocity.x += pevToucher->velocity.x * factor;
pev->velocity.y += pevToucher->velocity.y * factor;
}
else
{
if( push )
{
factor = 0.25f;
pev->velocity.x += pevToucher->velocity.x * factor;
pev->velocity.y += pevToucher->velocity.y * factor;
}
else
{
// fix for pushable acceleration
if( sv_pushable_fixed_tick_fudge.value >= 0 )
factor *= ( sv_pushable_fixed_tick_fudge.value * gpGlobals->frametime );
if( fabs( pev->velocity.x ) < fabs( pevToucher->velocity.x - pevToucher->velocity.x * factor ))
pev->velocity.x += pevToucher->velocity.x * factor;
if( fabs( pev->velocity.y ) < fabs( pevToucher->velocity.y - pevToucher->velocity.y * factor ))
pev->velocity.y += pevToucher->velocity.y * factor;
}
}
float length = sqrt( pev->velocity.x * pev->velocity.x + pev->velocity.y * pev->velocity.y );
if( ( push && pushablemode.value != 0 )
|| pushablemode.value == 0 )
{
if( length > MaxSpeed())
{
pev->velocity.x = ( pev->velocity.x * MaxSpeed() / length );
pev->velocity.y = ( pev->velocity.y * MaxSpeed() / length );
}
}
if( playerTouch )
{
if( push || pushablemode.value != 0 )
{
pevToucher->velocity.x = pev->velocity.x;
pevToucher->velocity.y = pev->velocity.y;
}
if( ( gpGlobals->time - m_soundTime ) > 0.7f )
{
m_soundTime = gpGlobals->time;
if( length > 0 && FBitSet( pev->flags, FL_ONGROUND ))
{
m_lastSound = RANDOM_LONG( 0, 2 );
EMIT_SOUND( ENT( pev ), CHAN_WEAPON, m_soundNames[m_lastSound], 0.5f, ATTN_NORM );
//SetThink( &StopSound );
//pev->nextthink = pev->ltime + 0.1f;
}
else
STOP_SOUND( ENT( pev ), CHAN_WEAPON, m_soundNames[m_lastSound] );
}
}
}
#if 0
void CPushable::StopSound( void )
{
Vector dist = pev->oldorigin - pev->origin;
if( dist.Length() <= 0 )
STOP_SOUND( ENT( pev ), CHAN_WEAPON, m_soundNames[m_lastSound] );
}
#endif
int CPushable::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType )
{
if( pev->spawnflags & SF_PUSH_BREAKABLE )
return CBreakable::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType );
return 1;
}