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.
1997 lines
50 KiB
1997 lines
50 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. |
|
* |
|
****/ |
|
//========================================================= |
|
// sound.cpp |
|
//========================================================= |
|
|
|
#include "extdll.h" |
|
#include "util.h" |
|
#include "cbase.h" |
|
#include "weapons.h" |
|
#include "player.h" |
|
#include "talkmonster.h" |
|
#include "gamerules.h" |
|
|
|
static char *memfgets( byte *pMemFile, int fileSize, int &filePos, char *pBuffer, int bufferSize ); |
|
|
|
// ==================== GENERIC AMBIENT SOUND ====================================== |
|
|
|
// runtime pitch shift and volume fadein/out structure |
|
|
|
// NOTE: IF YOU CHANGE THIS STRUCT YOU MUST CHANGE THE SAVE/RESTORE VERSION NUMBER |
|
// SEE BELOW (in the typedescription for the class) |
|
typedef struct dynpitchvol |
|
{ |
|
// NOTE: do not change the order of these parameters |
|
// NOTE: unless you also change order of rgdpvpreset array elements! |
|
int preset; |
|
|
|
int pitchrun; // pitch shift % when sound is running 0 - 255 |
|
int pitchstart; // pitch shift % when sound stops or starts 0 - 255 |
|
int spinup; // spinup time 0 - 100 |
|
int spindown; // spindown time 0 - 100 |
|
|
|
int volrun; // volume change % when sound is running 0 - 10 |
|
int volstart; // volume change % when sound stops or starts 0 - 10 |
|
int fadein; // volume fade in time 0 - 100 |
|
int fadeout; // volume fade out time 0 - 100 |
|
// Low Frequency Oscillator |
|
int lfotype; // 0) off 1) square 2) triangle 3) random |
|
int lforate; // 0 - 1000, how fast lfo osciallates |
|
|
|
int lfomodpitch; // 0-100 mod of current pitch. 0 is off. |
|
int lfomodvol; // 0-100 mod of current volume. 0 is off. |
|
|
|
int cspinup; // each trigger hit increments counter and spinup pitch |
|
|
|
int cspincount; |
|
|
|
int pitch; |
|
int spinupsav; |
|
int spindownsav; |
|
int pitchfrac; |
|
|
|
int vol; |
|
int fadeinsav; |
|
int fadeoutsav; |
|
int volfrac; |
|
|
|
int lfofrac; |
|
int lfomult; |
|
} dynpitchvol_t; |
|
|
|
#define CDPVPRESETMAX 27 |
|
|
|
// presets for runtime pitch and vol modulation of ambient sounds |
|
|
|
dynpitchvol_t rgdpvpreset[CDPVPRESETMAX] = |
|
{ |
|
// pitch pstart spinup spindwn volrun volstrt fadein fadeout lfotype lforate modptch modvol cspnup |
|
{1, 255, 75, 95, 95, 10, 1, 50, 95, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, |
|
{2, 255, 85, 70, 88, 10, 1, 20, 88, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, |
|
{3, 255, 100, 50, 75, 10, 1, 10, 75, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, |
|
{4, 100, 100, 0, 0, 10, 1, 90, 90, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, |
|
{5, 100, 100, 0, 0, 10, 1, 80, 80, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, |
|
{6, 100, 100, 0, 0, 10, 1, 50, 70, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, |
|
{7, 100, 100, 0, 0, 5, 1, 40, 50, 1, 50, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, |
|
{8, 100, 100, 0, 0, 5, 1, 40, 50, 1, 150, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, |
|
{9, 100, 100, 0, 0, 5, 1, 40, 50, 1, 750, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, |
|
{10,128, 100, 50, 75, 10, 1, 30, 40, 2, 8, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, |
|
{11,128, 100, 50, 75, 10, 1, 30, 40, 2, 25, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, |
|
{12,128, 100, 50, 75, 10, 1, 30, 40, 2, 70, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, |
|
{13,50, 50, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, |
|
{14,70, 70, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, |
|
{15,90, 90, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, |
|
{16,120, 120, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, |
|
{17,180, 180, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, |
|
{18,255, 255, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, |
|
{19,200, 75, 90, 90, 10, 1, 50, 90, 2, 100, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, |
|
{20,255, 75, 97, 90, 10, 1, 50, 90, 1, 40, 50, 0, 0, 0,0,0,0,0,0,0,0,0,0}, |
|
{21,100, 100, 0, 0, 10, 1, 30, 50, 3, 15, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, |
|
{22,160, 160, 0, 0, 10, 1, 50, 50, 3, 500, 25, 0, 0, 0,0,0,0,0,0,0,0,0,0}, |
|
{23,255, 75, 88, 0, 10, 1, 40, 0, 0, 0, 0, 0, 5, 0,0,0,0,0,0,0,0,0,0}, |
|
{24,200, 20, 95, 70, 10, 1, 70, 70, 3, 20, 50, 0, 0, 0,0,0,0,0,0,0,0,0,0}, |
|
{25,180, 100, 50, 60, 10, 1, 40, 60, 2, 90, 100, 100, 0, 0,0,0,0,0,0,0,0,0,0}, |
|
{26,60, 60, 0, 0, 10, 1, 40, 70, 3, 80, 20, 50, 0, 0,0,0,0,0,0,0,0,0,0}, |
|
{27,128, 90, 10, 10, 10, 1, 20, 40, 1, 5, 10, 20, 0, 0,0,0,0,0,0,0,0,0,0} |
|
}; |
|
|
|
class CAmbientGeneric : public CBaseEntity |
|
{ |
|
public: |
|
void KeyValue( KeyValueData* pkvd ); |
|
void Spawn( void ); |
|
void Precache( void ); |
|
void EXPORT ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); |
|
void EXPORT RampThink( void ); |
|
void InitModulationParms( void ); |
|
|
|
virtual int Save( CSave &save ); |
|
virtual int Restore( CRestore &restore ); |
|
static TYPEDESCRIPTION m_SaveData[]; |
|
virtual int ObjectCaps( void ) { return ( CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION ); } |
|
|
|
float m_flAttenuation; // attenuation value |
|
dynpitchvol_t m_dpv; |
|
|
|
BOOL m_fActive; // only TRUE when the entity is playing a looping sound |
|
BOOL m_fLooping; // TRUE when the sound played will loop |
|
}; |
|
|
|
LINK_ENTITY_TO_CLASS( ambient_generic, CAmbientGeneric ) |
|
|
|
TYPEDESCRIPTION CAmbientGeneric::m_SaveData[] = |
|
{ |
|
DEFINE_FIELD( CAmbientGeneric, m_flAttenuation, FIELD_FLOAT ), |
|
DEFINE_FIELD( CAmbientGeneric, m_fActive, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( CAmbientGeneric, m_fLooping, FIELD_BOOLEAN ), |
|
|
|
// HACKHACK - This is not really in the spirit of the save/restore design, but save this |
|
// out as a binary data block. If the dynpitchvol_t is changed, old saved games will NOT |
|
// load these correctly, so bump the save/restore version if you change the size of the struct |
|
// The right way to do this is to split the input parms (read in keyvalue) into members and re-init this |
|
// struct in Precache(), but it's unlikely that the struct will change, so it's not worth the time right now. |
|
DEFINE_ARRAY( CAmbientGeneric, m_dpv, FIELD_CHARACTER, sizeof(dynpitchvol_t) ), |
|
}; |
|
|
|
IMPLEMENT_SAVERESTORE( CAmbientGeneric, CBaseEntity ) |
|
|
|
// |
|
// ambient_generic - general-purpose user-defined static sound |
|
// |
|
void CAmbientGeneric::Spawn( void ) |
|
{ |
|
/* |
|
-1 : "Default" |
|
0 : "Everywhere" |
|
200 : "Small Radius" |
|
125 : "Medium Radius" |
|
80 : "Large Radius" |
|
*/ |
|
if( FBitSet( pev->spawnflags, AMBIENT_SOUND_EVERYWHERE ) ) |
|
{ |
|
m_flAttenuation = ATTN_NONE; |
|
} |
|
else if( FBitSet( pev->spawnflags, AMBIENT_SOUND_SMALLRADIUS ) ) |
|
{ |
|
m_flAttenuation = ATTN_IDLE; |
|
} |
|
else if( FBitSet( pev->spawnflags, AMBIENT_SOUND_MEDIUMRADIUS ) ) |
|
{ |
|
m_flAttenuation = ATTN_STATIC; |
|
} |
|
else if( FBitSet( pev->spawnflags, AMBIENT_SOUND_LARGERADIUS ) ) |
|
{ |
|
m_flAttenuation = ATTN_NORM; |
|
} |
|
else |
|
{ |
|
// if the designer didn't set a sound attenuation, default to one. |
|
m_flAttenuation = ATTN_STATIC; |
|
} |
|
|
|
const char *szSoundFile = STRING( pev->message ); |
|
|
|
if( FStringNull( pev->message ) || strlen( szSoundFile ) < 1 ) |
|
{ |
|
ALERT( at_error, "EMPTY AMBIENT AT: %f, %f, %f\n", pev->origin.x, pev->origin.y, pev->origin.z ); |
|
pev->nextthink = gpGlobals->time + 0.1; |
|
SetThink( &CBaseEntity::SUB_Remove ); |
|
return; |
|
} |
|
pev->solid = SOLID_NOT; |
|
pev->movetype = MOVETYPE_NONE; |
|
|
|
// Set up think function for dynamic modification |
|
// of ambient sound's pitch or volume. Don't |
|
// start thinking yet. |
|
|
|
SetThink( &CAmbientGeneric::RampThink ); |
|
pev->nextthink = 0; |
|
|
|
// allow on/off switching via 'use' function. |
|
|
|
SetUse( &CAmbientGeneric::ToggleUse ); |
|
|
|
m_fActive = FALSE; |
|
|
|
if( FBitSet( pev->spawnflags, AMBIENT_SOUND_NOT_LOOPING ) ) |
|
m_fLooping = FALSE; |
|
else |
|
m_fLooping = TRUE; |
|
Precache(); |
|
} |
|
|
|
void CAmbientGeneric::Precache( void ) |
|
{ |
|
const char *szSoundFile = STRING( pev->message ); |
|
|
|
if( !FStringNull( pev->message ) && strlen( szSoundFile ) > 1 ) |
|
{ |
|
if( *szSoundFile != '!' ) |
|
PRECACHE_SOUND( szSoundFile ); |
|
} |
|
|
|
// init all dynamic modulation parms |
|
InitModulationParms(); |
|
|
|
if( !FBitSet( pev->spawnflags, AMBIENT_SOUND_START_SILENT ) ) |
|
{ |
|
// start the sound ASAP |
|
if( m_fLooping ) |
|
m_fActive = TRUE; |
|
} |
|
if( m_fActive ) |
|
{ |
|
UTIL_EmitAmbientSound( ENT( pev ), pev->origin, szSoundFile, |
|
( m_dpv.vol * 0.01 ), m_flAttenuation, SND_SPAWNING, m_dpv.pitch ); |
|
|
|
pev->nextthink = gpGlobals->time + 0.1; |
|
} |
|
} |
|
|
|
// RampThink - Think at 5hz if we are dynamically modifying |
|
// pitch or volume of the playing sound. This function will |
|
// ramp pitch and/or volume up or down, modify pitch/volume |
|
// with lfo if active. |
|
void CAmbientGeneric::RampThink( void ) |
|
{ |
|
const char *szSoundFile = STRING( pev->message ); |
|
int pitch = m_dpv.pitch; |
|
int vol = m_dpv.vol; |
|
int flags = 0; |
|
int fChanged = 0; // FALSE if pitch and vol remain unchanged this round |
|
int prev; |
|
|
|
if( !m_dpv.spinup && !m_dpv.spindown && !m_dpv.fadein && !m_dpv.fadeout && !m_dpv.lfotype ) |
|
return; // no ramps or lfo, stop thinking |
|
|
|
// ============== |
|
// pitch envelope |
|
// ============== |
|
if( m_dpv.spinup || m_dpv.spindown ) |
|
{ |
|
prev = m_dpv.pitchfrac >> 8; |
|
|
|
if( m_dpv.spinup > 0 ) |
|
m_dpv.pitchfrac += m_dpv.spinup; |
|
else if( m_dpv.spindown > 0 ) |
|
m_dpv.pitchfrac -= m_dpv.spindown; |
|
|
|
pitch = m_dpv.pitchfrac >> 8; |
|
|
|
if( pitch > m_dpv.pitchrun ) |
|
{ |
|
pitch = m_dpv.pitchrun; |
|
m_dpv.spinup = 0; // done with ramp up |
|
} |
|
|
|
if( pitch < m_dpv.pitchstart ) |
|
{ |
|
pitch = m_dpv.pitchstart; |
|
m_dpv.spindown = 0; // done with ramp down |
|
|
|
// shut sound off |
|
UTIL_EmitAmbientSound( ENT( pev ), pev->origin, szSoundFile, |
|
0, 0, SND_STOP, 0 ); |
|
|
|
// return without setting nextthink |
|
return; |
|
} |
|
|
|
if( pitch > 255 ) |
|
pitch = 255; |
|
if( pitch < 1 ) |
|
pitch = 1; |
|
|
|
m_dpv.pitch = pitch; |
|
|
|
fChanged |= ( prev != pitch ); |
|
flags |= SND_CHANGE_PITCH; |
|
} |
|
|
|
// ================== |
|
// amplitude envelope |
|
// ================== |
|
if( m_dpv.fadein || m_dpv.fadeout ) |
|
{ |
|
prev = m_dpv.volfrac >> 8; |
|
|
|
if( m_dpv.fadein > 0 ) |
|
m_dpv.volfrac += m_dpv.fadein; |
|
else if( m_dpv.fadeout > 0 ) |
|
m_dpv.volfrac -= m_dpv.fadeout; |
|
|
|
vol = m_dpv.volfrac >> 8; |
|
|
|
if( vol > m_dpv.volrun ) |
|
{ |
|
vol = m_dpv.volrun; |
|
m_dpv.fadein = 0; // done with ramp up |
|
} |
|
|
|
if( vol < m_dpv.volstart ) |
|
{ |
|
vol = m_dpv.volstart; |
|
m_dpv.fadeout = 0; // done with ramp down |
|
|
|
// shut sound off |
|
UTIL_EmitAmbientSound( ENT( pev ), pev->origin, szSoundFile, |
|
0, 0, SND_STOP, 0 ); |
|
|
|
// return without setting nextthink |
|
return; |
|
} |
|
|
|
if( vol > 100 ) |
|
vol = 100; |
|
if( vol < 1 ) |
|
vol = 1; |
|
|
|
m_dpv.vol = vol; |
|
|
|
fChanged |= ( prev != vol ); |
|
flags |= SND_CHANGE_VOL; |
|
} |
|
|
|
// =================== |
|
// pitch/amplitude LFO |
|
// =================== |
|
if( m_dpv.lfotype ) |
|
{ |
|
int pos; |
|
|
|
if( m_dpv.lfofrac > 0x6fffffff ) |
|
m_dpv.lfofrac = 0; |
|
|
|
// update lfo, lfofrac/255 makes a triangle wave 0-255 |
|
m_dpv.lfofrac += m_dpv.lforate; |
|
pos = m_dpv.lfofrac >> 8; |
|
|
|
if( m_dpv.lfofrac < 0 ) |
|
{ |
|
m_dpv.lfofrac = 0; |
|
m_dpv.lforate = abs( m_dpv.lforate ); |
|
pos = 0; |
|
} |
|
else if( pos > 255 ) |
|
{ |
|
pos = 255; |
|
m_dpv.lfofrac = ( 255 << 8 ); |
|
m_dpv.lforate = -abs( m_dpv.lforate ); |
|
} |
|
|
|
switch( m_dpv.lfotype ) |
|
{ |
|
case LFO_SQUARE: |
|
if( pos < 128 ) |
|
m_dpv.lfomult = 255; |
|
else |
|
m_dpv.lfomult = 0; |
|
break; |
|
case LFO_RANDOM: |
|
if( pos == 255 ) |
|
m_dpv.lfomult = RANDOM_LONG( 0, 255 ); |
|
break; |
|
case LFO_TRIANGLE: |
|
default: |
|
m_dpv.lfomult = pos; |
|
break; |
|
} |
|
|
|
if( m_dpv.lfomodpitch ) |
|
{ |
|
prev = pitch; |
|
|
|
// pitch 0-255 |
|
pitch += ( ( m_dpv.lfomult - 128 ) * m_dpv.lfomodpitch ) / 100; |
|
|
|
if( pitch > 255 ) |
|
pitch = 255; |
|
if( pitch < 1 ) |
|
pitch = 1; |
|
|
|
fChanged |= ( prev != pitch ); |
|
flags |= SND_CHANGE_PITCH; |
|
} |
|
|
|
if( m_dpv.lfomodvol ) |
|
{ |
|
// vol 0-100 |
|
prev = vol; |
|
|
|
vol += ( ( m_dpv.lfomult - 128 ) * m_dpv.lfomodvol ) / 100; |
|
|
|
if( vol > 100 ) |
|
vol = 100; |
|
if( vol < 0 ) |
|
vol = 0; |
|
|
|
fChanged |= ( prev != vol ); |
|
flags |= SND_CHANGE_VOL; |
|
} |
|
} |
|
|
|
// Send update to playing sound only if we actually changed |
|
// pitch or volume in this routine. |
|
|
|
if( flags && fChanged ) |
|
{ |
|
if( pitch == PITCH_NORM ) |
|
pitch = PITCH_NORM + 1; // don't send 'no pitch' ! |
|
|
|
UTIL_EmitAmbientSound( ENT( pev ), pev->origin, szSoundFile, |
|
( vol * 0.01 ), m_flAttenuation, flags, pitch ); |
|
} |
|
|
|
// update ramps at 5hz |
|
pev->nextthink = gpGlobals->time + 0.2; |
|
return; |
|
} |
|
|
|
// Init all ramp params in preparation to |
|
// play a new sound |
|
|
|
void CAmbientGeneric::InitModulationParms( void ) |
|
{ |
|
int pitchinc; |
|
|
|
m_dpv.volrun = (int)( pev->health * 10 ); // 0 - 100 |
|
if( m_dpv.volrun > 100 ) |
|
m_dpv.volrun = 100; |
|
if( m_dpv.volrun < 0 ) |
|
m_dpv.volrun = 0; |
|
|
|
// get presets |
|
if( m_dpv.preset != 0 && m_dpv.preset <= CDPVPRESETMAX ) |
|
{ |
|
// load preset values |
|
m_dpv = rgdpvpreset[m_dpv.preset - 1]; |
|
|
|
// fixup preset values, just like |
|
// fixups in KeyValue routine. |
|
if( m_dpv.spindown > 0 ) |
|
m_dpv.spindown = ( 101 - m_dpv.spindown ) * 64; |
|
if( m_dpv.spinup > 0 ) |
|
m_dpv.spinup = ( 101 - m_dpv.spinup ) * 64; |
|
|
|
m_dpv.volstart *= 10; |
|
m_dpv.volrun *= 10; |
|
|
|
if( m_dpv.fadein > 0 ) |
|
m_dpv.fadein = ( 101 - m_dpv.fadein ) * 64; |
|
if( m_dpv.fadeout > 0 ) |
|
m_dpv.fadeout = ( 101 - m_dpv.fadeout ) * 64; |
|
|
|
m_dpv.lforate *= 256; |
|
|
|
m_dpv.fadeinsav = m_dpv.fadein; |
|
m_dpv.fadeoutsav = m_dpv.fadeout; |
|
m_dpv.spinupsav = m_dpv.spinup; |
|
m_dpv.spindownsav = m_dpv.spindown; |
|
} |
|
|
|
m_dpv.fadein = m_dpv.fadeinsav; |
|
m_dpv.fadeout = 0; |
|
|
|
if( m_dpv.fadein ) |
|
m_dpv.vol = m_dpv.volstart; |
|
else |
|
m_dpv.vol = m_dpv.volrun; |
|
|
|
m_dpv.spinup = m_dpv.spinupsav; |
|
m_dpv.spindown = 0; |
|
|
|
if( m_dpv.spinup ) |
|
m_dpv.pitch = m_dpv.pitchstart; |
|
else |
|
m_dpv.pitch = m_dpv.pitchrun; |
|
|
|
if( m_dpv.pitch == 0 ) |
|
m_dpv.pitch = PITCH_NORM; |
|
|
|
m_dpv.pitchfrac = m_dpv.pitch << 8; |
|
m_dpv.volfrac = m_dpv.vol << 8; |
|
|
|
m_dpv.lfofrac = 0; |
|
m_dpv.lforate = abs( m_dpv.lforate ); |
|
|
|
m_dpv.cspincount = 1; |
|
|
|
if( m_dpv.cspinup ) |
|
{ |
|
pitchinc = ( 255 - m_dpv.pitchstart ) / m_dpv.cspinup; |
|
|
|
m_dpv.pitchrun = m_dpv.pitchstart + pitchinc; |
|
if( m_dpv.pitchrun > 255 ) |
|
m_dpv.pitchrun = 255; |
|
} |
|
|
|
if( ( m_dpv.spinupsav || m_dpv.spindownsav || ( m_dpv.lfotype && m_dpv.lfomodpitch ) ) |
|
&& ( m_dpv.pitch == PITCH_NORM ) ) |
|
m_dpv.pitch = PITCH_NORM + 1; // must never send 'no pitch' as first pitch |
|
// if we intend to pitch shift later! |
|
} |
|
|
|
// |
|
// ToggleUse - turns an ambient sound on or off. If the |
|
// ambient is a looping sound, mark sound as active (m_fActive) |
|
// if it's playing, innactive if not. If the sound is not |
|
// a looping sound, never mark it as active. |
|
// |
|
void CAmbientGeneric::ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) |
|
{ |
|
const char *szSoundFile = STRING( pev->message ); |
|
float fraction; |
|
|
|
if( useType != USE_TOGGLE ) |
|
{ |
|
if( ( m_fActive && useType == USE_ON ) || ( !m_fActive && useType == USE_OFF ) ) |
|
return; |
|
} |
|
|
|
// Directly change pitch if arg passed. Only works if sound is already playing. |
|
if( useType == USE_SET && m_fActive ) // Momentary buttons will pass down a float in here |
|
{ |
|
|
|
fraction = value; |
|
|
|
if( fraction > 1.0 ) |
|
fraction = 1.0; |
|
if( fraction < 0.0 ) |
|
fraction = 0.01; |
|
|
|
m_dpv.pitch = (int)( fraction * 255 ); |
|
|
|
UTIL_EmitAmbientSound( ENT( pev ), pev->origin, szSoundFile, 0, 0, SND_CHANGE_PITCH, m_dpv.pitch ); |
|
return; |
|
} |
|
|
|
// Toggle |
|
// m_fActive is TRUE only if a looping sound is playing. |
|
if( m_fActive ) |
|
{ |
|
// turn sound off |
|
if( m_dpv.cspinup ) |
|
{ |
|
// Don't actually shut off. Each toggle causes |
|
// incremental spinup to max pitch |
|
if( m_dpv.cspincount <= m_dpv.cspinup ) |
|
{ |
|
int pitchinc; |
|
|
|
// start a new spinup |
|
m_dpv.cspincount++; |
|
|
|
pitchinc = ( 255 - m_dpv.pitchstart ) / m_dpv.cspinup; |
|
|
|
m_dpv.spinup = m_dpv.spinupsav; |
|
m_dpv.spindown = 0; |
|
|
|
m_dpv.pitchrun = m_dpv.pitchstart + pitchinc * m_dpv.cspincount; |
|
if( m_dpv.pitchrun > 255 ) |
|
m_dpv.pitchrun = 255; |
|
|
|
pev->nextthink = gpGlobals->time + 0.1; |
|
} |
|
} |
|
else |
|
{ |
|
m_fActive = FALSE; |
|
|
|
// HACKHACK - this makes the code in Precache() work properly after a save/restore |
|
pev->spawnflags |= AMBIENT_SOUND_START_SILENT; |
|
|
|
if( m_dpv.spindownsav || m_dpv.fadeoutsav ) |
|
{ |
|
// spin it down (or fade it) before shutoff if spindown is set |
|
m_dpv.spindown = m_dpv.spindownsav; |
|
m_dpv.spinup = 0; |
|
|
|
m_dpv.fadeout = m_dpv.fadeoutsav; |
|
m_dpv.fadein = 0; |
|
pev->nextthink = gpGlobals->time + 0.1; |
|
} |
|
else |
|
UTIL_EmitAmbientSound( ENT( pev ), pev->origin, szSoundFile, 0, 0, SND_STOP, 0 ); |
|
} |
|
} |
|
else |
|
{ |
|
// turn sound on |
|
|
|
// only toggle if this is a looping sound. If not looping, each |
|
// trigger will cause the sound to play. If the sound is still |
|
// playing from a previous trigger press, it will be shut off |
|
// and then restarted. |
|
if( m_fLooping ) |
|
m_fActive = TRUE; |
|
else |
|
// shut sound off now - may be interrupting a long non-looping sound |
|
UTIL_EmitAmbientSound( ENT( pev ), pev->origin, szSoundFile, 0, 0, SND_STOP, 0 ); |
|
|
|
// init all ramp params for startup |
|
InitModulationParms(); |
|
|
|
UTIL_EmitAmbientSound( ENT( pev ), pev->origin, szSoundFile, ( m_dpv.vol * 0.01 ), m_flAttenuation, 0, m_dpv.pitch ); |
|
|
|
pev->nextthink = gpGlobals->time + 0.1; |
|
} |
|
} |
|
|
|
// KeyValue - load keyvalue pairs into member data of the |
|
// ambient generic. NOTE: called BEFORE spawn! |
|
void CAmbientGeneric::KeyValue( KeyValueData *pkvd ) |
|
{ |
|
// NOTE: changing any of the modifiers in this code |
|
// NOTE: also requires changing InitModulationParms code. |
|
|
|
// preset |
|
if( FStrEq( pkvd->szKeyName, "preset" ) ) |
|
{ |
|
m_dpv.preset = atoi( pkvd->szValue ); |
|
pkvd->fHandled = TRUE; |
|
} |
|
// pitchrun |
|
else if( FStrEq( pkvd->szKeyName, "pitch" ) ) |
|
{ |
|
m_dpv.pitchrun = atoi( pkvd->szValue ); |
|
pkvd->fHandled = TRUE; |
|
|
|
if( m_dpv.pitchrun > 255 ) |
|
m_dpv.pitchrun = 255; |
|
if( m_dpv.pitchrun < 0 ) |
|
m_dpv.pitchrun = 0; |
|
} |
|
// pitchstart |
|
else if( FStrEq( pkvd->szKeyName, "pitchstart" ) ) |
|
{ |
|
m_dpv.pitchstart = atoi( pkvd->szValue ); |
|
pkvd->fHandled = TRUE; |
|
|
|
if( m_dpv.pitchstart > 255 ) |
|
m_dpv.pitchstart = 255; |
|
if( m_dpv.pitchstart < 0 ) |
|
m_dpv.pitchstart = 0; |
|
} |
|
// spinup |
|
else if( FStrEq( pkvd->szKeyName, "spinup" ) ) |
|
{ |
|
m_dpv.spinup = atoi( pkvd->szValue ); |
|
|
|
if( m_dpv.spinup > 100 ) |
|
m_dpv.spinup = 100; |
|
if( m_dpv.spinup < 0 ) |
|
m_dpv.spinup = 0; |
|
|
|
if( m_dpv.spinup > 0 ) |
|
m_dpv.spinup = ( 101 - m_dpv.spinup ) * 64; |
|
m_dpv.spinupsav = m_dpv.spinup; |
|
pkvd->fHandled = TRUE; |
|
} |
|
// spindown |
|
else if( FStrEq( pkvd->szKeyName, "spindown" ) ) |
|
{ |
|
m_dpv.spindown = atoi( pkvd->szValue ); |
|
|
|
if( m_dpv.spindown > 100 ) |
|
m_dpv.spindown = 100; |
|
if( m_dpv.spindown < 0 ) |
|
m_dpv.spindown = 0; |
|
|
|
if( m_dpv.spindown > 0 ) |
|
m_dpv.spindown = ( 101 - m_dpv.spindown ) * 64; |
|
m_dpv.spindownsav = m_dpv.spindown; |
|
pkvd->fHandled = TRUE; |
|
} |
|
// volstart |
|
else if( FStrEq( pkvd->szKeyName, "volstart" ) ) |
|
{ |
|
m_dpv.volstart = atoi( pkvd->szValue ); |
|
|
|
if( m_dpv.volstart > 10 ) |
|
m_dpv.volstart = 10; |
|
if( m_dpv.volstart < 0 ) |
|
m_dpv.volstart = 0; |
|
|
|
m_dpv.volstart *= 10; // 0 - 100 |
|
|
|
pkvd->fHandled = TRUE; |
|
} |
|
// fadein |
|
else if( FStrEq( pkvd->szKeyName, "fadein" ) ) |
|
{ |
|
m_dpv.fadein = atoi( pkvd->szValue ); |
|
|
|
if( m_dpv.fadein > 100 ) |
|
m_dpv.fadein = 100; |
|
if( m_dpv.fadein < 0 ) |
|
m_dpv.fadein = 0; |
|
|
|
if( m_dpv.fadein > 0 ) |
|
m_dpv.fadein = ( 101 - m_dpv.fadein ) * 64; |
|
m_dpv.fadeinsav = m_dpv.fadein; |
|
pkvd->fHandled = TRUE; |
|
} |
|
// fadeout |
|
else if( FStrEq( pkvd->szKeyName, "fadeout" ) ) |
|
{ |
|
m_dpv.fadeout = atoi( pkvd->szValue ); |
|
|
|
if( m_dpv.fadeout > 100 ) |
|
m_dpv.fadeout = 100; |
|
if( m_dpv.fadeout < 0 ) |
|
m_dpv.fadeout = 0; |
|
|
|
if( m_dpv.fadeout > 0 ) |
|
m_dpv.fadeout = ( 101 - m_dpv.fadeout ) * 64; |
|
m_dpv.fadeoutsav = m_dpv.fadeout; |
|
pkvd->fHandled = TRUE; |
|
} |
|
// lfotype |
|
else if( FStrEq( pkvd->szKeyName, "lfotype" ) ) |
|
{ |
|
m_dpv.lfotype = atoi( pkvd->szValue ); |
|
if( m_dpv.lfotype > 4 ) |
|
m_dpv.lfotype = LFO_TRIANGLE; |
|
pkvd->fHandled = TRUE; |
|
} |
|
// lforate |
|
else if( FStrEq( pkvd->szKeyName, "lforate" ) ) |
|
{ |
|
m_dpv.lforate = atoi( pkvd->szValue ); |
|
|
|
if( m_dpv.lforate > 1000 ) |
|
m_dpv.lforate = 1000; |
|
if( m_dpv.lforate < 0 ) |
|
m_dpv.lforate = 0; |
|
|
|
m_dpv.lforate *= 256; |
|
|
|
pkvd->fHandled = TRUE; |
|
} |
|
// lfomodpitch |
|
else if( FStrEq( pkvd->szKeyName, "lfomodpitch" ) ) |
|
{ |
|
m_dpv.lfomodpitch = atoi( pkvd->szValue ); |
|
if( m_dpv.lfomodpitch > 100 ) |
|
m_dpv.lfomodpitch = 100; |
|
if( m_dpv.lfomodpitch < 0 ) |
|
m_dpv.lfomodpitch = 0; |
|
|
|
pkvd->fHandled = TRUE; |
|
} |
|
// lfomodvol |
|
else if( FStrEq( pkvd->szKeyName, "lfomodvol" ) ) |
|
{ |
|
m_dpv.lfomodvol = atoi( pkvd->szValue ); |
|
if( m_dpv.lfomodvol > 100 ) |
|
m_dpv.lfomodvol = 100; |
|
if( m_dpv.lfomodvol < 0 ) |
|
m_dpv.lfomodvol = 0; |
|
|
|
pkvd->fHandled = TRUE; |
|
} |
|
// cspinup |
|
else if( FStrEq( pkvd->szKeyName, "cspinup" ) ) |
|
{ |
|
m_dpv.cspinup = atoi( pkvd->szValue ); |
|
if( m_dpv.cspinup > 100 ) |
|
m_dpv.cspinup = 100; |
|
if( m_dpv.cspinup < 0 ) |
|
m_dpv.cspinup = 0; |
|
|
|
pkvd->fHandled = TRUE; |
|
} |
|
else |
|
CBaseEntity::KeyValue( pkvd ); |
|
} |
|
|
|
// =================== ROOM SOUND FX ========================================== |
|
|
|
class CEnvSound : public CPointEntity |
|
{ |
|
public: |
|
void KeyValue( KeyValueData* pkvd); |
|
void Spawn( void ); |
|
|
|
void Think( void ); |
|
|
|
virtual int Save( CSave &save ); |
|
virtual int Restore( CRestore &restore ); |
|
static TYPEDESCRIPTION m_SaveData[]; |
|
|
|
float m_flRadius; |
|
float m_flRoomtype; |
|
}; |
|
|
|
LINK_ENTITY_TO_CLASS( env_sound, CEnvSound ) |
|
|
|
TYPEDESCRIPTION CEnvSound::m_SaveData[] = |
|
{ |
|
DEFINE_FIELD( CEnvSound, m_flRadius, FIELD_FLOAT ), |
|
DEFINE_FIELD( CEnvSound, m_flRoomtype, FIELD_FLOAT ), |
|
}; |
|
|
|
IMPLEMENT_SAVERESTORE( CEnvSound, CBaseEntity ) |
|
|
|
void CEnvSound::KeyValue( KeyValueData *pkvd ) |
|
{ |
|
if( FStrEq( pkvd->szKeyName, "radius" ) ) |
|
{ |
|
m_flRadius = atof( pkvd->szValue ); |
|
pkvd->fHandled = TRUE; |
|
} |
|
if( FStrEq( pkvd->szKeyName, "roomtype" ) ) |
|
{ |
|
m_flRoomtype = atof( pkvd->szValue ); |
|
pkvd->fHandled = TRUE; |
|
} |
|
} |
|
|
|
// returns TRUE if the given sound entity (pev) is in range |
|
// and can see the given player entity (pevTarget) |
|
|
|
BOOL FEnvSoundInRange( entvars_t *pev, entvars_t *pevTarget, float *pflRange ) |
|
{ |
|
CEnvSound *pSound = GetClassPtr( (CEnvSound *)pev ); |
|
Vector vecSpot1 = pev->origin + pev->view_ofs; |
|
Vector vecSpot2 = pevTarget->origin + pevTarget->view_ofs; |
|
Vector vecRange; |
|
float flRange; |
|
TraceResult tr; |
|
|
|
UTIL_TraceLine( vecSpot1, vecSpot2, ignore_monsters, ENT( pev ), &tr ); |
|
|
|
// check if line of sight crosses water boundary, or is blocked |
|
if( ( tr.fInOpen && tr.fInWater ) || tr.flFraction != 1 ) |
|
return FALSE; |
|
|
|
// calc range from sound entity to player |
|
vecRange = tr.vecEndPos - vecSpot1; |
|
flRange = vecRange.Length(); |
|
|
|
if( pSound->m_flRadius < flRange ) |
|
return FALSE; |
|
|
|
if( pflRange ) |
|
*pflRange = flRange; |
|
|
|
return TRUE; |
|
} |
|
|
|
// |
|
// A client that is visible and in range of a sound entity will |
|
// have its room_type set by that sound entity. If two or more |
|
// sound entities are contending for a client, then the nearest |
|
// sound entity to the client will set the client's room_type. |
|
// A client's room_type will remain set to its prior value until |
|
// a new in-range, visible sound entity resets a new room_type. |
|
// |
|
|
|
// CONSIDER: if player in water state, autoset roomtype to 14,15 or 16. |
|
|
|
void CEnvSound::Think( void ) |
|
{ |
|
// get pointer to client if visible; FIND_CLIENT_IN_PVS will |
|
// cycle through visible clients on consecutive calls. |
|
edict_t *pentPlayer = FIND_CLIENT_IN_PVS( edict() ); |
|
CBasePlayer *pPlayer = NULL; |
|
|
|
if( FNullEnt( pentPlayer ) ) |
|
goto env_sound_Think_slow; // no player in pvs of sound entity, slow it down |
|
|
|
pPlayer = GetClassPtr( (CBasePlayer *)VARS( pentPlayer ) ); |
|
float flRange; |
|
|
|
// check to see if this is the sound entity that is |
|
// currently affecting this player |
|
if( !FNullEnt( pPlayer->m_pentSndLast ) && ( pPlayer->m_pentSndLast == ENT( pev ) ) ) |
|
{ |
|
// this is the entity currently affecting player, check |
|
// for validity |
|
if( pPlayer->m_flSndRoomtype != 0 && pPlayer->m_flSndRange != 0 ) |
|
{ |
|
// we're looking at a valid sound entity affecting |
|
// player, make sure it's still valid, update range |
|
if( FEnvSoundInRange( pev, VARS( pentPlayer ), &flRange ) ) |
|
{ |
|
pPlayer->m_flSndRange = flRange; |
|
goto env_sound_Think_fast; |
|
} |
|
else |
|
{ |
|
// current sound entity affecting player is no longer valid, |
|
// flag this state by clearing room_type and range. |
|
// NOTE: we do not actually change the player's room_type |
|
// NOTE: until we have a new valid room_type to change it to. |
|
pPlayer->m_flSndRange = 0; |
|
pPlayer->m_flSndRoomtype = 0; |
|
goto env_sound_Think_slow; |
|
} |
|
} |
|
else |
|
{ |
|
// entity is affecting player but is out of range, |
|
// wait passively for another entity to usurp it... |
|
goto env_sound_Think_slow; |
|
} |
|
} |
|
|
|
// if we got this far, we're looking at an entity that is contending |
|
// for current player sound. the closest entity to player wins. |
|
if( FEnvSoundInRange( pev, VARS(pentPlayer), &flRange ) ) |
|
{ |
|
if( flRange < pPlayer->m_flSndRange || pPlayer->m_flSndRange == 0 ) |
|
{ |
|
// new entity is closer to player, so it wins. |
|
pPlayer->m_pentSndLast = ENT( pev ); |
|
pPlayer->m_flSndRoomtype = m_flRoomtype; |
|
pPlayer->m_flSndRange = flRange; |
|
|
|
// send room_type command to player's server. |
|
// this should be a rare event - once per change of room_type |
|
// only! |
|
|
|
//CLIENT_COMMAND( pentPlayer, "room_type %f", m_flRoomtype ); |
|
|
|
MESSAGE_BEGIN( MSG_ONE, SVC_ROOMTYPE, NULL, pentPlayer ); // use the magic #1 for "one client" |
|
WRITE_SHORT( (short)m_flRoomtype ); // sequence number |
|
MESSAGE_END(); |
|
|
|
// crank up nextthink rate for new active sound entity |
|
// by falling through to think_fast... |
|
} |
|
// player is not closer to the contending sound entity, |
|
// just fall through to think_fast. this effectively |
|
// cranks up the think_rate of entities near the player. |
|
} |
|
// player is in pvs of sound entity, but either not visible or |
|
// not in range. do nothing, fall through to think_fast... |
|
|
|
env_sound_Think_fast: |
|
pev->nextthink = gpGlobals->time + 0.25; |
|
return; |
|
env_sound_Think_slow: |
|
pev->nextthink = gpGlobals->time + 0.75; |
|
return; |
|
} |
|
|
|
// |
|
// env_sound - spawn a sound entity that will set player roomtype |
|
// when player moves in range and sight. |
|
// |
|
// |
|
void CEnvSound::Spawn() |
|
{ |
|
// spread think times |
|
pev->nextthink = gpGlobals->time + RANDOM_FLOAT( 0.0, 0.5 ); |
|
} |
|
|
|
// ==================== SENTENCE GROUPS, UTILITY FUNCTIONS ====================================== |
|
|
|
#define CSENTENCE_LRU_MAX 32 // max number of elements per sentence group |
|
|
|
// group of related sentences |
|
|
|
typedef struct sentenceg |
|
{ |
|
char szgroupname[CBSENTENCENAME_MAX]; |
|
int count; |
|
unsigned char rgblru[CSENTENCE_LRU_MAX]; |
|
} SENTENCEG; |
|
|
|
#define CSENTENCEG_MAX 200 // max number of sentence groups |
|
// globals |
|
|
|
SENTENCEG rgsentenceg[CSENTENCEG_MAX]; |
|
int fSentencesInit = FALSE; |
|
|
|
char gszallsentencenames[CVOXFILESENTENCEMAX][CBSENTENCENAME_MAX]; |
|
int gcallsentences = 0; |
|
|
|
// randomize list of sentence name indices |
|
|
|
void USENTENCEG_InitLRU( unsigned char *plru, int count ) |
|
{ |
|
int i, j, k; |
|
unsigned char temp; |
|
|
|
if( !fSentencesInit ) |
|
return; |
|
|
|
if( count > CSENTENCE_LRU_MAX ) |
|
count = CSENTENCE_LRU_MAX; |
|
|
|
for( i = 0; i < count; i++ ) |
|
plru[i] = (unsigned char)i; |
|
|
|
// randomize array |
|
for( i = 0; i < ( count * 4 ); i++ ) |
|
{ |
|
j = RANDOM_LONG( 0, count - 1 ); |
|
k = RANDOM_LONG( 0, count -1 ); |
|
temp = plru[j]; |
|
plru[j] = plru[k]; |
|
plru[k] = temp; |
|
} |
|
} |
|
|
|
// ignore lru. pick next sentence from sentence group. Go in order until we hit the last sentence, |
|
// then repeat list if freset is true. If freset is false, then repeat last sentence. |
|
// ipick is passed in as the requested sentence ordinal. |
|
// ipick 'next' is returned. |
|
// return of -1 indicates an error. |
|
|
|
int USENTENCEG_PickSequential( int isentenceg, char *szfound, int ipick, int freset ) |
|
{ |
|
char *szgroupname; |
|
unsigned char count; |
|
char sznum[8]; |
|
|
|
if( !fSentencesInit ) |
|
return -1; |
|
|
|
if( isentenceg < 0 ) |
|
return -1; |
|
|
|
szgroupname = rgsentenceg[isentenceg].szgroupname; |
|
count = rgsentenceg[isentenceg].count; |
|
|
|
if( count == 0 ) |
|
return -1; |
|
|
|
if( ipick >= count ) |
|
ipick = count - 1; |
|
|
|
strcpy( szfound, "!" ); |
|
strcat( szfound, szgroupname ); |
|
sprintf( sznum, "%d", ipick ); |
|
strcat( szfound, sznum ); |
|
|
|
if( ipick >= count ) |
|
{ |
|
if( freset ) |
|
// reset at end of list |
|
return 0; |
|
else |
|
return count; |
|
} |
|
|
|
return ipick + 1; |
|
} |
|
|
|
// pick a random sentence from rootname0 to rootnameX. |
|
// picks from the rgsentenceg[isentenceg] least |
|
// recently used, modifies lru array. returns the sentencename. |
|
// note, lru must be seeded with 0-n randomized sentence numbers, with the |
|
// rest of the lru filled with -1. The first integer in the lru is |
|
// actually the size of the list. Returns ipick, the ordinal |
|
// of the picked sentence within the group. |
|
|
|
int USENTENCEG_Pick( int isentenceg, char *szfound ) |
|
{ |
|
char *szgroupname; |
|
unsigned char *plru; |
|
unsigned char i; |
|
unsigned char count; |
|
char sznum[8]; |
|
unsigned char ipick; |
|
int ffound = FALSE; |
|
|
|
if( !fSentencesInit ) |
|
return -1; |
|
|
|
if( isentenceg < 0 ) |
|
return -1; |
|
|
|
szgroupname = rgsentenceg[isentenceg].szgroupname; |
|
count = rgsentenceg[isentenceg].count; |
|
plru = rgsentenceg[isentenceg].rgblru; |
|
|
|
while( !ffound ) |
|
{ |
|
for(i = 0; i < count; i++ ) |
|
if( plru[i] != 0xFF ) |
|
{ |
|
ipick = plru[i]; |
|
plru[i] = 0xFF; |
|
ffound = TRUE; |
|
break; |
|
} |
|
|
|
if( !ffound ) |
|
USENTENCEG_InitLRU( plru, count ); |
|
else |
|
{ |
|
strcpy( szfound, "!" ); |
|
strcat( szfound, szgroupname ); |
|
sprintf( sznum, "%d", ipick ); |
|
strcat( szfound, sznum ); |
|
return ipick; |
|
} |
|
} |
|
return -1; |
|
} |
|
|
|
// ===================== SENTENCE GROUPS, MAIN ROUTINES ======================== |
|
|
|
// Given sentence group rootname (name without number suffix), |
|
// get sentence group index (isentenceg). Returns -1 if no such name. |
|
|
|
int SENTENCEG_GetIndex( const char *szgroupname ) |
|
{ |
|
int i; |
|
|
|
if( !fSentencesInit || !szgroupname ) |
|
return -1; |
|
|
|
// search rgsentenceg for match on szgroupname |
|
i = 0; |
|
while( rgsentenceg[i].count ) |
|
{ |
|
if( !strcmp( szgroupname, rgsentenceg[i].szgroupname ) ) |
|
return i; |
|
i++; |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
// given sentence group index, play random sentence for given entity. |
|
// returns ipick - which sentence was picked to |
|
// play from the group. Ipick is only needed if you plan on stopping |
|
// the sound before playback is done (see SENTENCEG_Stop). |
|
|
|
int SENTENCEG_PlayRndI( edict_t *entity, int isentenceg, float volume, float attenuation, int flags, int pitch ) |
|
{ |
|
char name[64]; |
|
int ipick; |
|
|
|
if( !fSentencesInit ) |
|
return -1; |
|
|
|
name[0] = 0; |
|
|
|
ipick = USENTENCEG_Pick( isentenceg, name ); |
|
if( ipick > 0 ) |
|
EMIT_SOUND_DYN( entity, CHAN_VOICE, name, volume, attenuation, flags, pitch ); |
|
return ipick; |
|
} |
|
|
|
// same as above, but takes sentence group name instead of index |
|
|
|
int SENTENCEG_PlayRndSz( edict_t *entity, const char *szgroupname, float volume, float attenuation, int flags, int pitch ) |
|
{ |
|
char name[64]; |
|
int ipick; |
|
int isentenceg; |
|
|
|
if( !fSentencesInit ) |
|
return -1; |
|
|
|
name[0] = 0; |
|
|
|
isentenceg = SENTENCEG_GetIndex( szgroupname ); |
|
if( isentenceg < 0 ) |
|
{ |
|
ALERT( at_console, "No such sentence group %s\n", szgroupname ); |
|
return -1; |
|
} |
|
|
|
ipick = USENTENCEG_Pick( isentenceg, name ); |
|
if( ipick >= 0 && name[0] ) |
|
EMIT_SOUND_DYN( entity, CHAN_VOICE, name, volume, attenuation, flags, pitch ); |
|
|
|
return ipick; |
|
} |
|
|
|
// play sentences in sequential order from sentence group. Reset after last sentence. |
|
|
|
int SENTENCEG_PlaySequentialSz( edict_t *entity, const char *szgroupname, float volume, float attenuation, int flags, int pitch, int ipick, int freset ) |
|
{ |
|
char name[64]; |
|
int ipicknext; |
|
int isentenceg; |
|
|
|
if( !fSentencesInit ) |
|
return -1; |
|
|
|
name[0] = 0; |
|
|
|
isentenceg = SENTENCEG_GetIndex(szgroupname); |
|
if( isentenceg < 0 ) |
|
return -1; |
|
|
|
ipicknext = USENTENCEG_PickSequential(isentenceg, name, ipick, freset ); |
|
if( ipicknext >= 0 && name[0] ) |
|
EMIT_SOUND_DYN( entity, CHAN_VOICE, name, volume, attenuation, flags, pitch ); |
|
return ipicknext; |
|
} |
|
|
|
// for this entity, for the given sentence within the sentence group, stop |
|
// the sentence. |
|
|
|
void SENTENCEG_Stop( edict_t *entity, int isentenceg, int ipick ) |
|
{ |
|
char buffer[64]; |
|
char sznum[8]; |
|
|
|
if( !fSentencesInit ) |
|
return; |
|
|
|
if( isentenceg < 0 || ipick < 0 ) |
|
return; |
|
|
|
strcpy( buffer, "!" ); |
|
strcat( buffer, rgsentenceg[isentenceg].szgroupname ); |
|
sprintf( sznum, "%d", ipick ); |
|
strcat( buffer, sznum ); |
|
|
|
STOP_SOUND( entity, CHAN_VOICE, buffer ); |
|
} |
|
|
|
// open sentences.txt, scan for groups, build rgsentenceg |
|
// Should be called from world spawn, only works on the |
|
// first call and is ignored subsequently. |
|
|
|
void SENTENCEG_Init() |
|
{ |
|
char buffer[512]; |
|
char szgroup[64]; |
|
int i, j; |
|
int isentencegs; |
|
|
|
if( fSentencesInit ) |
|
return; |
|
|
|
memset( gszallsentencenames, 0, CVOXFILESENTENCEMAX * CBSENTENCENAME_MAX ); |
|
gcallsentences = 0; |
|
|
|
memset( rgsentenceg, 0, CSENTENCEG_MAX * sizeof(SENTENCEG) ); |
|
isentencegs = -1; |
|
|
|
int filePos = 0, fileSize; |
|
byte *pMemFile = g_engfuncs.pfnLoadFileForMe( "sound/sentences.txt", &fileSize ); |
|
if( !pMemFile ) |
|
return; |
|
|
|
memset( buffer, 0, 512 ); |
|
memset( szgroup, 0, 64 ); |
|
// for each line in the file... |
|
while( memfgets( pMemFile, fileSize, filePos, buffer, 511 ) != NULL ) |
|
{ |
|
// skip whitespace |
|
i = 0; |
|
while( buffer[i] && buffer[i] == ' ' ) |
|
i++; |
|
|
|
if( !buffer[i] ) |
|
continue; |
|
|
|
if( buffer[i] == '/' || !isalpha( buffer[i] ) ) |
|
continue; |
|
|
|
// get sentence name |
|
j = i; |
|
while( buffer[j] && buffer[j] != ' ' ) |
|
j++; |
|
|
|
if( !buffer[j] ) |
|
continue; |
|
|
|
if( gcallsentences > CVOXFILESENTENCEMAX ) |
|
{ |
|
ALERT( at_error, "Too many sentences in sentences.txt!\n" ); |
|
break; |
|
} |
|
|
|
// null-terminate name and save in sentences array |
|
buffer[j] = 0; |
|
const char *pString = buffer + i; |
|
|
|
if( strlen( pString ) >= CBSENTENCENAME_MAX ) |
|
ALERT( at_warning, "Sentence %s longer than %d letters\n", pString, CBSENTENCENAME_MAX - 1 ); |
|
|
|
strcpy( gszallsentencenames[gcallsentences++], pString ); |
|
|
|
j--; |
|
if( j <= i ) |
|
continue; |
|
if( !isdigit( buffer[j] ) ) |
|
continue; |
|
|
|
// cut out suffix numbers |
|
while( j > i && isdigit( buffer[j] ) ) |
|
j--; |
|
|
|
if( j <= i ) |
|
continue; |
|
|
|
buffer[j + 1] = 0; |
|
|
|
// if new name doesn't match previous group name, |
|
// make a new group. |
|
if( strcmp( szgroup, &( buffer[i] ) ) ) |
|
{ |
|
// name doesn't match with prev name, |
|
// copy name into group, init count to 1 |
|
isentencegs++; |
|
if( isentencegs >= CSENTENCEG_MAX ) |
|
{ |
|
ALERT( at_error, "Too many sentence groups in sentences.txt!\n" ); |
|
break; |
|
} |
|
|
|
strcpy( rgsentenceg[isentencegs].szgroupname, &( buffer[i] ) ); |
|
rgsentenceg[isentencegs].count = 1; |
|
|
|
strcpy( szgroup, &( buffer[i] ) ); |
|
|
|
continue; |
|
} |
|
else |
|
{ |
|
//name matches with previous, increment group count |
|
if( isentencegs >= 0 ) |
|
rgsentenceg[isentencegs].count++; |
|
} |
|
} |
|
|
|
g_engfuncs.pfnFreeFile( pMemFile ); |
|
|
|
fSentencesInit = TRUE; |
|
|
|
// init lru lists |
|
|
|
i = 0; |
|
|
|
while( rgsentenceg[i].count && i < CSENTENCEG_MAX ) |
|
{ |
|
USENTENCEG_InitLRU( &( rgsentenceg[i].rgblru[0] ), rgsentenceg[i].count ); |
|
i++; |
|
} |
|
} |
|
|
|
// convert sentence (sample) name to !sentencenum, return !sentencenum |
|
|
|
int SENTENCEG_Lookup( const char *sample, char *sentencenum ) |
|
{ |
|
char sznum[8]; |
|
|
|
int i; |
|
// this is a sentence name; lookup sentence number |
|
// and give to engine as string. |
|
for( i = 0; i < gcallsentences; i++ ) |
|
if( !stricmp( gszallsentencenames[i], sample + 1 ) ) |
|
{ |
|
if( sentencenum ) |
|
{ |
|
strcpy( sentencenum, "!" ); |
|
sprintf( sznum, "%d", i ); |
|
strcat( sentencenum, sznum ); |
|
} |
|
return i; |
|
} |
|
// sentence name not found! |
|
return -1; |
|
} |
|
|
|
void EMIT_SOUND_DYN( edict_t *entity, int channel, const char *sample, float volume, float attenuation, int flags, int pitch ) |
|
{ |
|
if( sample && *sample == '!' ) |
|
{ |
|
char name[32]; |
|
if( SENTENCEG_Lookup( sample, name ) >= 0 ) |
|
EMIT_SOUND_DYN2( entity, channel, name, volume, attenuation, flags, pitch ); |
|
else |
|
ALERT( at_aiconsole, "Unable to find %s in sentences.txt\n", sample ); |
|
} |
|
else |
|
EMIT_SOUND_DYN2( entity, channel, sample, volume, attenuation, flags, pitch ); |
|
} |
|
|
|
// play a specific sentence over the HEV suit speaker - just pass player entity, and !sentencename |
|
|
|
void EMIT_SOUND_SUIT( edict_t *entity, const char *sample ) |
|
{ |
|
float fvol; |
|
int pitch = PITCH_NORM; |
|
|
|
fvol = CVAR_GET_FLOAT( "suitvolume" ); |
|
if( RANDOM_LONG( 0, 1 ) ) |
|
pitch = RANDOM_LONG( 0, 6 ) + 98; |
|
|
|
if( fvol > 0.05 ) |
|
EMIT_SOUND_DYN( entity, CHAN_STATIC, sample, fvol, ATTN_NORM, 0, pitch ); |
|
} |
|
|
|
// play a sentence, randomly selected from the passed in group id, over the HEV suit speaker |
|
|
|
void EMIT_GROUPID_SUIT( edict_t *entity, int isentenceg ) |
|
{ |
|
float fvol; |
|
int pitch = PITCH_NORM; |
|
|
|
fvol = CVAR_GET_FLOAT( "suitvolume" ); |
|
if( RANDOM_LONG( 0, 1 ) ) |
|
pitch = RANDOM_LONG( 0, 6 ) + 98; |
|
|
|
if( fvol > 0.05 ) |
|
SENTENCEG_PlayRndI( entity, isentenceg, fvol, ATTN_NORM, 0, pitch ); |
|
} |
|
|
|
// play a sentence, randomly selected from the passed in groupname |
|
|
|
void EMIT_GROUPNAME_SUIT( edict_t *entity, const char *groupname ) |
|
{ |
|
float fvol; |
|
int pitch = PITCH_NORM; |
|
|
|
fvol = CVAR_GET_FLOAT( "suitvolume" ); |
|
if( RANDOM_LONG( 0, 1 ) ) |
|
pitch = RANDOM_LONG( 0, 6 ) + 98; |
|
|
|
if( fvol > 0.05 ) |
|
SENTENCEG_PlayRndSz( entity, groupname, fvol, ATTN_NORM, 0, pitch ); |
|
} |
|
|
|
// ===================== MATERIAL TYPE DETECTION, MAIN ROUTINES ======================== |
|
// |
|
// Used to detect the texture the player is standing on, map the |
|
// texture name to a material type. Play footstep sound based |
|
// on material type. |
|
|
|
int fTextureTypeInit = FALSE; |
|
|
|
#define CTEXTURESMAX 512 // max number of textures loaded |
|
|
|
int gcTextures = 0; |
|
char grgszTextureName[CTEXTURESMAX][CBTEXTURENAMEMAX]; // texture names |
|
char grgchTextureType[CTEXTURESMAX]; // parallel array of texture types |
|
|
|
// open materials.txt, get size, alloc space, |
|
// save in array. Only works first time called, |
|
// ignored on subsequent calls. |
|
|
|
static char *memfgets( byte *pMemFile, int fileSize, int &filePos, char *pBuffer, int bufferSize ) |
|
{ |
|
// Bullet-proofing |
|
if( !pMemFile || !pBuffer ) |
|
return NULL; |
|
|
|
if( filePos >= fileSize ) |
|
return NULL; |
|
|
|
int i = filePos; |
|
int last = fileSize; |
|
|
|
// fgets always NULL terminates, so only read bufferSize-1 characters |
|
if( last - filePos > ( bufferSize - 1 ) ) |
|
last = filePos + ( bufferSize - 1 ); |
|
|
|
int stop = 0; |
|
|
|
// Stop at the next newline (inclusive) or end of buffer |
|
while( i < last && !stop ) |
|
{ |
|
if( pMemFile[i] == '\n' ) |
|
stop = 1; |
|
i++; |
|
} |
|
|
|
// If we actually advanced the pointer, copy it over |
|
if( i != filePos ) |
|
{ |
|
// We read in size bytes |
|
int size = i - filePos; |
|
// copy it out |
|
memcpy( pBuffer, pMemFile + filePos, sizeof(byte) * size ); |
|
|
|
// If the buffer isn't full, terminate (this is always true) |
|
if( size < bufferSize ) |
|
pBuffer[size] = 0; |
|
|
|
// Update file pointer |
|
filePos = i; |
|
return pBuffer; |
|
} |
|
|
|
// No data read, bail |
|
return NULL; |
|
} |
|
|
|
void TEXTURETYPE_Init() |
|
{ |
|
char buffer[512]; |
|
int i, j; |
|
byte *pMemFile; |
|
int fileSize, filePos = 0; |
|
|
|
if( fTextureTypeInit ) |
|
return; |
|
|
|
memset( &( grgszTextureName[0][0] ), 0, CTEXTURESMAX * CBTEXTURENAMEMAX ); |
|
memset( grgchTextureType, 0, CTEXTURESMAX ); |
|
|
|
gcTextures = 0; |
|
|
|
pMemFile = g_engfuncs.pfnLoadFileForMe( "sound/materials.txt", &fileSize ); |
|
if( !pMemFile ) |
|
return; |
|
|
|
memset( buffer, 0, 512 ); |
|
// for each line in the file... |
|
while( memfgets( pMemFile, fileSize, filePos, buffer, 511 ) != NULL && ( gcTextures < CTEXTURESMAX) ) |
|
{ |
|
// skip whitespace |
|
i = 0; |
|
while( buffer[i] && isspace( buffer[i] ) ) |
|
i++; |
|
|
|
if( !buffer[i] ) |
|
continue; |
|
|
|
// skip comment lines |
|
if( buffer[i] == '/' || !isalpha( buffer[i] ) ) |
|
continue; |
|
|
|
// get texture type |
|
grgchTextureType[gcTextures] = toupper( buffer[i++] ); |
|
|
|
// skip whitespace |
|
while( buffer[i] && isspace( buffer[i] ) ) |
|
i++; |
|
|
|
if( !buffer[i] ) |
|
continue; |
|
|
|
// get sentence name |
|
j = i; |
|
while( buffer[j] && !isspace( buffer[j] ) ) |
|
j++; |
|
|
|
if( !buffer[j] ) |
|
continue; |
|
|
|
// null-terminate name and save in sentences array |
|
j = Q_min( j, CBTEXTURENAMEMAX - 1 + i ); |
|
buffer[j] = 0; |
|
strcpy( &( grgszTextureName[gcTextures++][0] ), &( buffer[i] ) ); |
|
} |
|
|
|
g_engfuncs.pfnFreeFile( pMemFile ); |
|
|
|
fTextureTypeInit = TRUE; |
|
} |
|
|
|
// given texture name, find texture type |
|
// if not found, return type 'concrete' |
|
|
|
// NOTE: this routine should ONLY be called if the |
|
// current texture under the player changes! |
|
|
|
char TEXTURETYPE_Find( char *name ) |
|
{ |
|
// CONSIDER: pre-sort texture names and perform faster binary search here |
|
|
|
for( int i = 0; i < gcTextures; i++ ) |
|
{ |
|
if( !strnicmp( name, &( grgszTextureName[i][0] ), CBTEXTURENAMEMAX - 1 ) ) |
|
return grgchTextureType[i]; |
|
} |
|
|
|
return CHAR_TEX_CONCRETE; |
|
} |
|
|
|
// play a strike sound based on the texture that was hit by the attack traceline. VecSrc/VecEnd are the |
|
// original traceline endpoints used by the attacker, iBulletType is the type of bullet that hit the texture. |
|
// returns volume of strike instrument (crowbar) to play |
|
|
|
float TEXTURETYPE_PlaySound( TraceResult *ptr, Vector vecSrc, Vector vecEnd, int iBulletType ) |
|
{ |
|
// hit the world, try to play sound based on texture material type |
|
char chTextureType; |
|
float fvol; |
|
float fvolbar; |
|
char szbuffer[64]; |
|
const char *pTextureName; |
|
float rgfl1[3]; |
|
float rgfl2[3]; |
|
const char *rgsz[4]; |
|
int cnt; |
|
float fattn = ATTN_NORM; |
|
|
|
if( !g_pGameRules->PlayTextureSounds() ) |
|
return 0.0; |
|
|
|
CBaseEntity *pEntity = CBaseEntity::Instance( ptr->pHit ); |
|
|
|
chTextureType = 0; |
|
|
|
if( pEntity && pEntity->Classify() != CLASS_NONE && pEntity->Classify() != CLASS_MACHINE ) |
|
// hit body |
|
chTextureType = CHAR_TEX_FLESH; |
|
else |
|
{ |
|
// hit world |
|
|
|
// find texture under strike, get material type |
|
|
|
// copy trace vector into array for trace_texture |
|
|
|
vecSrc.CopyToArray( rgfl1 ); |
|
vecEnd.CopyToArray( rgfl2 ); |
|
|
|
// get texture from entity or world (world is ent(0)) |
|
if( pEntity ) |
|
pTextureName = TRACE_TEXTURE( ENT( pEntity->pev ), rgfl1, rgfl2 ); |
|
else |
|
pTextureName = TRACE_TEXTURE( ENT( 0 ), rgfl1, rgfl2 ); |
|
|
|
if( pTextureName ) |
|
{ |
|
// strip leading '-0' or '+0~' or '{' or '!' |
|
if( *pTextureName == '-' || *pTextureName == '+' ) |
|
pTextureName += 2; |
|
|
|
if( *pTextureName == '{' || *pTextureName == '!' || *pTextureName == '~' || *pTextureName == ' ' ) |
|
pTextureName++; |
|
// '}}' |
|
strcpy( szbuffer, pTextureName ); |
|
szbuffer[CBTEXTURENAMEMAX - 1] = 0; |
|
|
|
// ALERT( at_console, "texture hit: %s\n", szbuffer ); |
|
|
|
// get texture type |
|
chTextureType = TEXTURETYPE_Find( szbuffer ); |
|
} |
|
} |
|
|
|
switch( chTextureType ) |
|
{ |
|
default: |
|
case CHAR_TEX_CONCRETE: |
|
fvol = 0.9; |
|
fvolbar = 0.6; |
|
rgsz[0] = "player/pl_step1.wav"; |
|
rgsz[1] = "player/pl_step2.wav"; |
|
cnt = 2; |
|
break; |
|
case CHAR_TEX_METAL: |
|
fvol = 0.9; |
|
fvolbar = 0.3; |
|
rgsz[0] = "player/pl_metal1.wav"; |
|
rgsz[1] = "player/pl_metal2.wav"; |
|
cnt = 2; |
|
break; |
|
case CHAR_TEX_DIRT: |
|
fvol = 0.9; |
|
fvolbar = 0.1; |
|
rgsz[0] = "player/pl_dirt1.wav"; |
|
rgsz[1] = "player/pl_dirt2.wav"; |
|
rgsz[2] = "player/pl_dirt3.wav"; |
|
cnt = 3; |
|
break; |
|
case CHAR_TEX_VENT: |
|
fvol = 0.5; |
|
fvolbar = 0.3; |
|
rgsz[0] = "player/pl_duct1.wav"; |
|
rgsz[1] = "player/pl_duct1.wav"; |
|
cnt = 2; |
|
break; |
|
case CHAR_TEX_GRATE: |
|
fvol = 0.9; |
|
fvolbar = 0.5; |
|
rgsz[0] = "player/pl_grate1.wav"; |
|
rgsz[1] = "player/pl_grate4.wav"; |
|
cnt = 2; |
|
break; |
|
case CHAR_TEX_TILE: |
|
fvol = 0.8; |
|
fvolbar = 0.2; |
|
rgsz[0] = "player/pl_tile1.wav"; |
|
rgsz[1] = "player/pl_tile3.wav"; |
|
rgsz[2] = "player/pl_tile2.wav"; |
|
rgsz[3] = "player/pl_tile4.wav"; |
|
cnt = 4; |
|
break; |
|
case CHAR_TEX_SLOSH: |
|
fvol = 0.9; |
|
fvolbar = 0.0; |
|
rgsz[0] = "player/pl_slosh1.wav"; |
|
rgsz[1] = "player/pl_slosh3.wav"; |
|
rgsz[2] = "player/pl_slosh2.wav"; |
|
rgsz[3] = "player/pl_slosh4.wav"; |
|
cnt = 4; |
|
break; |
|
case CHAR_TEX_WOOD: |
|
fvol = 0.9; |
|
fvolbar = 0.2; |
|
rgsz[0] = "debris/wood1.wav"; |
|
rgsz[1] = "debris/wood2.wav"; |
|
rgsz[2] = "debris/wood3.wav"; |
|
cnt = 3; |
|
break; |
|
case CHAR_TEX_GLASS: |
|
case CHAR_TEX_COMPUTER: |
|
fvol = 0.8; |
|
fvolbar = 0.2; |
|
rgsz[0] = "debris/glass1.wav"; |
|
rgsz[1] = "debris/glass2.wav"; |
|
rgsz[2] = "debris/glass3.wav"; |
|
cnt = 3; |
|
break; |
|
case CHAR_TEX_FLESH: |
|
if( iBulletType == BULLET_PLAYER_CROWBAR ) |
|
return 0.0; // crowbar already makes this sound |
|
fvol = 1.0; |
|
fvolbar = 0.2; |
|
rgsz[0] = "weapons/bullet_hit1.wav"; |
|
rgsz[1] = "weapons/bullet_hit2.wav"; |
|
fattn = 1.0; |
|
cnt = 2; |
|
break; |
|
} |
|
|
|
// did we hit a breakable? |
|
if( pEntity && FClassnameIs( pEntity->pev, "func_breakable" ) ) |
|
{ |
|
// drop volumes, the object will already play a damaged sound |
|
fvol /= 1.5; |
|
fvolbar /= 2.0; |
|
} |
|
else if( chTextureType == CHAR_TEX_COMPUTER ) |
|
{ |
|
// play random spark if computer |
|
if( ptr->flFraction != 1.0 && RANDOM_LONG( 0, 1 ) ) |
|
{ |
|
UTIL_Sparks( ptr->vecEndPos ); |
|
|
|
float flVolume = RANDOM_FLOAT( 0.7, 1.0 );//random volume range |
|
switch( RANDOM_LONG( 0, 1 ) ) |
|
{ |
|
case 0: |
|
UTIL_EmitAmbientSound( ENT( 0 ), ptr->vecEndPos, "buttons/spark5.wav", flVolume, ATTN_NORM, 0, 100 ); |
|
break; |
|
case 1: |
|
UTIL_EmitAmbientSound( ENT( 0 ), ptr->vecEndPos, "buttons/spark6.wav", flVolume, ATTN_NORM, 0, 100 ); |
|
break; |
|
/*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;*/ |
|
} |
|
} |
|
} |
|
|
|
// play material hit sound |
|
UTIL_EmitAmbientSound( ENT( 0 ), ptr->vecEndPos, rgsz[RANDOM_LONG( 0, cnt - 1 )], fvol, fattn, 0, 96 + RANDOM_LONG( 0, 0xf ) ); |
|
//EMIT_SOUND_DYN( ENT( m_pPlayer->pev ), CHAN_WEAPON, rgsz[RANDOM_LONG( 0, cnt - 1 )], fvol, ATTN_NORM, 0, 96 + RANDOM_LONG( 0, 0xf ) ); |
|
|
|
return fvolbar; |
|
} |
|
|
|
// =================================================================================== |
|
// |
|
// Speaker class. Used for announcements per level, for door lock/unlock spoken voice. |
|
// |
|
class CSpeaker : public CBaseEntity |
|
{ |
|
public: |
|
void KeyValue( KeyValueData *pkvd ); |
|
void Spawn( void ); |
|
void Precache( void ); |
|
void EXPORT ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); |
|
void EXPORT SpeakerThink( void ); |
|
|
|
virtual int Save( CSave &save ); |
|
virtual int Restore( CRestore &restore ); |
|
static TYPEDESCRIPTION m_SaveData[]; |
|
|
|
virtual int ObjectCaps( void ) { return ( CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION ); } |
|
|
|
int m_preset; // preset number |
|
}; |
|
|
|
LINK_ENTITY_TO_CLASS( speaker, CSpeaker ) |
|
|
|
TYPEDESCRIPTION CSpeaker::m_SaveData[] = |
|
{ |
|
DEFINE_FIELD( CSpeaker, m_preset, FIELD_INTEGER ), |
|
}; |
|
|
|
IMPLEMENT_SAVERESTORE( CSpeaker, CBaseEntity ) |
|
|
|
// |
|
// ambient_generic - general-purpose user-defined static sound |
|
// |
|
void CSpeaker::Spawn( void ) |
|
{ |
|
const char *szSoundFile = STRING( pev->message ); |
|
|
|
if( !m_preset && ( FStringNull( pev->message ) || strlen( szSoundFile ) < 1 ) ) |
|
{ |
|
ALERT( at_error, "SPEAKER with no Level/Sentence! at: %f, %f, %f\n", pev->origin.x, pev->origin.y, pev->origin.z ); |
|
pev->nextthink = gpGlobals->time + 0.1; |
|
SetThink( &CBaseEntity::SUB_Remove ); |
|
return; |
|
} |
|
pev->solid = SOLID_NOT; |
|
pev->movetype = MOVETYPE_NONE; |
|
|
|
SetThink( &CSpeaker::SpeakerThink ); |
|
pev->nextthink = 0.0; |
|
|
|
// allow on/off switching via 'use' function. |
|
SetUse( &CSpeaker::ToggleUse ); |
|
|
|
Precache(); |
|
} |
|
|
|
#define ANNOUNCE_MINUTES_MIN 0.25 |
|
#define ANNOUNCE_MINUTES_MAX 2.25 |
|
|
|
void CSpeaker::Precache( void ) |
|
{ |
|
if( !FBitSet( pev->spawnflags, SPEAKER_START_SILENT ) ) |
|
// set first announcement time for random n second |
|
pev->nextthink = gpGlobals->time + RANDOM_FLOAT( 5.0, 15.0 ); |
|
} |
|
void CSpeaker::SpeakerThink( void ) |
|
{ |
|
const char* szSoundFile = NULL; |
|
float flvolume = pev->health * 0.1; |
|
float flattenuation = 0.3; |
|
int flags = 0; |
|
int pitch = 100; |
|
|
|
// Wait for the talkmonster to finish first. |
|
if( gpGlobals->time <= CTalkMonster::g_talkWaitTime ) |
|
{ |
|
pev->nextthink = CTalkMonster::g_talkWaitTime + RANDOM_FLOAT( 5, 10 ); |
|
return; |
|
} |
|
|
|
if( m_preset ) |
|
{ |
|
// go lookup preset text, assign szSoundFile |
|
switch( m_preset ) |
|
{ |
|
case 1: |
|
szSoundFile = "C1A0_"; |
|
break; |
|
case 2: |
|
szSoundFile = "C1A1_"; |
|
break; |
|
case 3: |
|
szSoundFile = "C1A2_"; |
|
break; |
|
case 4: |
|
szSoundFile = "C1A3_"; |
|
break; |
|
case 5: |
|
szSoundFile = "C1A4_"; |
|
break; |
|
case 6: |
|
szSoundFile = "C2A1_"; |
|
break; |
|
case 7: |
|
szSoundFile = "C2A2_"; |
|
break; |
|
case 8: |
|
szSoundFile = "C2A3_"; |
|
break; |
|
case 9: |
|
szSoundFile = "C2A4_"; |
|
break; |
|
case 10: |
|
szSoundFile = "C2A5_"; |
|
break; |
|
case 11: |
|
szSoundFile = "C3A1_"; |
|
break; |
|
case 12: |
|
szSoundFile = "C3A2_"; |
|
break; |
|
} |
|
} |
|
else |
|
szSoundFile = STRING( pev->message ); |
|
|
|
if( szSoundFile[0] == '!' ) |
|
{ |
|
// play single sentence, one shot |
|
UTIL_EmitAmbientSound( ENT( pev ), pev->origin, szSoundFile, |
|
flvolume, flattenuation, flags, pitch ); |
|
|
|
// shut off and reset |
|
pev->nextthink = 0.0; |
|
} |
|
else |
|
{ |
|
// make random announcement from sentence group |
|
if( SENTENCEG_PlayRndSz( ENT( pev ), szSoundFile, flvolume, flattenuation, flags, pitch ) < 0 ) |
|
ALERT( at_console, "Level Design Error!\nSPEAKER has bad sentence group name: %s\n",szSoundFile ); |
|
|
|
// set next announcement time for random 5 to 10 minute delay |
|
pev->nextthink = gpGlobals->time + RANDOM_FLOAT( ANNOUNCE_MINUTES_MIN * 60.0, ANNOUNCE_MINUTES_MAX * 60.0 ); |
|
|
|
CTalkMonster::g_talkWaitTime = gpGlobals->time + 5; // time delay until it's ok to speak: used so that two NPCs don't talk at once |
|
} |
|
|
|
return; |
|
} |
|
|
|
// |
|
// ToggleUse - if an announcement is pending, cancel it. If no announcement is pending, start one. |
|
// |
|
void CSpeaker::ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) |
|
{ |
|
int fActive = ( pev->nextthink > 0.0 ); |
|
|
|
// fActive is TRUE only if an announcement is pending |
|
|
|
if( useType != USE_TOGGLE ) |
|
{ |
|
// ignore if we're just turning something on that's already on, or |
|
// turning something off that's already off. |
|
if( ( fActive && useType == USE_ON ) || ( !fActive && useType == USE_OFF ) ) |
|
return; |
|
} |
|
|
|
if( useType == USE_ON ) |
|
{ |
|
// turn on announcements |
|
pev->nextthink = gpGlobals->time + 0.1; |
|
return; |
|
} |
|
|
|
if( useType == USE_OFF ) |
|
{ |
|
// turn off announcements |
|
pev->nextthink = 0.0; |
|
return; |
|
} |
|
|
|
// Toggle announcements |
|
if( fActive ) |
|
{ |
|
// turn off announcements |
|
pev->nextthink = 0.0; |
|
} |
|
else |
|
{ |
|
// turn on announcements |
|
pev->nextthink = gpGlobals->time + 0.1; |
|
} |
|
} |
|
|
|
// KeyValue - load keyvalue pairs into member data |
|
// NOTE: called BEFORE spawn! |
|
|
|
void CSpeaker::KeyValue( KeyValueData *pkvd ) |
|
{ |
|
// preset |
|
if( FStrEq( pkvd->szKeyName, "preset" ) ) |
|
{ |
|
m_preset = atoi( pkvd->szValue ); |
|
pkvd->fHandled = TRUE; |
|
} |
|
else |
|
CBaseEntity::KeyValue( pkvd ); |
|
}
|
|
|