hlsdk-xash3d/dlls/sound.cpp

1906 lines
48 KiB
C++
Raw Normal View History

2016-06-04 15:24:23 +02:00
/***
*
* 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
2016-07-31 15:48:50 +02:00
int lfotype; // 0) off 1) square 2) triangle 3) random
2016-06-04 15:24:23 +02:00
int lforate; // 0 - 1000, how fast lfo osciallates
2016-07-31 15:48:50 +02:00
2016-06-04 15:24:23 +02:00
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
2016-07-31 15:48:50 +02:00
int cspincount;
2016-06-04 15:24:23 +02:00
int pitch;
int spinupsav;
int spindownsav;
int pitchfrac;
int vol;
int fadeinsav;
int fadeoutsav;
int volfrac;
2016-07-31 15:48:50 +02:00
int lfofrac;
int lfomult;
2016-06-04 15:24:23 +02:00
} dynpitchvol_t;
#define CDPVPRESETMAX 27
// presets for runtime pitch and vol modulation of ambient sounds
dynpitchvol_t rgdpvpreset[CDPVPRESETMAX] =
2016-06-04 15:24:23 +02:00
{
// 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:
2016-07-31 15:48:50 +02:00
void KeyValue( KeyValueData* pkvd );
2016-06-04 15:24:23 +02:00
void Spawn( void );
void Precache( void );
2016-07-31 15:48:50 +02:00
void EXPORT ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
2016-06-04 15:24:23 +02:00
void EXPORT RampThink( void );
2016-07-31 15:48:50 +02:00
void InitModulationParms( void );
2016-06-04 15:24:23 +02:00
2016-07-31 15:48:50 +02:00
virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
static TYPEDESCRIPTION m_SaveData[];
virtual int ObjectCaps( void ) { return ( CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION ); }
2016-06-04 15:24:23 +02:00
float m_flAttenuation; // attenuation value
2016-07-31 15:48:50 +02:00
dynpitchvol_t m_dpv;
2016-06-04 15:24:23 +02:00
2016-07-31 15:48:50 +02:00
BOOL m_fActive; // only TRUE when the entity is playing a looping sound
BOOL m_fLooping; // TRUE when the sound played will loop
2016-06-04 15:24:23 +02:00
};
LINK_ENTITY_TO_CLASS( ambient_generic, CAmbientGeneric )
TYPEDESCRIPTION CAmbientGeneric::m_SaveData[] =
2016-06-04 15:24:23 +02:00
{
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 )
2016-06-04 15:24:23 +02:00
//
// ambient_generic - general-purpose user-defined static sound
//
2016-07-31 15:48:50 +02:00
void CAmbientGeneric::Spawn( void )
2016-06-04 15:24:23 +02:00
{
/*
-1 : "Default"
0 : "Everywhere"
200 : "Small Radius"
125 : "Medium Radius"
80 : "Large Radius"
*/
2016-07-31 15:48:50 +02:00
if( FBitSet( pev->spawnflags, AMBIENT_SOUND_EVERYWHERE ) )
2016-06-04 15:24:23 +02:00
{
m_flAttenuation = ATTN_NONE;
}
2016-07-31 15:48:50 +02:00
else if( FBitSet( pev->spawnflags, AMBIENT_SOUND_SMALLRADIUS ) )
2016-06-04 15:24:23 +02:00
{
m_flAttenuation = ATTN_IDLE;
}
2016-07-31 15:48:50 +02:00
else if( FBitSet( pev->spawnflags, AMBIENT_SOUND_MEDIUMRADIUS ) )
2016-06-04 15:24:23 +02:00
{
m_flAttenuation = ATTN_STATIC;
}
2016-07-31 15:48:50 +02:00
else if( FBitSet( pev->spawnflags, AMBIENT_SOUND_LARGERADIUS ) )
2016-06-04 15:24:23 +02:00
{
m_flAttenuation = ATTN_NORM;
}
else
{
// if the designer didn't set a sound attenuation, default to one.
2016-06-04 15:24:23 +02:00
m_flAttenuation = ATTN_STATIC;
}
2017-07-23 23:24:55 +02:00
const char *szSoundFile = STRING( pev->message );
2016-06-04 15:24:23 +02:00
2016-07-31 15:48:50 +02:00
if( FStringNull( pev->message ) || strlen( szSoundFile ) < 1 )
2016-06-04 15:24:23 +02:00
{
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;
}
2016-07-31 15:48:50 +02:00
pev->solid = SOLID_NOT;
pev->movetype = MOVETYPE_NONE;
2016-06-04 15:24:23 +02:00
// Set up think function for dynamic modification
// of ambient sound's pitch or volume. Don't
// start thinking yet.
2016-07-31 15:48:50 +02:00
SetThink( &CAmbientGeneric::RampThink );
2016-06-04 15:24:23 +02:00
pev->nextthink = 0;
// allow on/off switching via 'use' function.
SetUse( &CAmbientGeneric::ToggleUse );
m_fActive = FALSE;
2016-07-31 15:48:50 +02:00
if( FBitSet( pev->spawnflags, AMBIENT_SOUND_NOT_LOOPING ) )
2016-06-04 15:24:23 +02:00
m_fLooping = FALSE;
else
m_fLooping = TRUE;
2016-07-31 15:48:50 +02:00
Precache();
2016-06-04 15:24:23 +02:00
}
2016-07-31 15:48:50 +02:00
void CAmbientGeneric::Precache( void )
2016-06-04 15:24:23 +02:00
{
2017-07-23 23:24:55 +02:00
const char *szSoundFile = STRING( pev->message );
2016-06-04 15:24:23 +02:00
2016-07-31 15:48:50 +02:00
if( !FStringNull( pev->message ) && strlen( szSoundFile ) > 1 )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
if( *szSoundFile != '!' )
PRECACHE_SOUND( szSoundFile );
2016-06-04 15:24:23 +02:00
}
2016-06-04 15:24:23 +02:00
// init all dynamic modulation parms
InitModulationParms();
2016-07-31 15:48:50 +02:00
if( !FBitSet( pev->spawnflags, AMBIENT_SOUND_START_SILENT ) )
2016-06-04 15:24:23 +02:00
{
// start the sound ASAP
2016-07-31 15:48:50 +02:00
if( m_fLooping )
2016-06-04 15:24:23 +02:00
m_fActive = TRUE;
}
2016-07-31 15:48:50 +02:00
if( m_fActive )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
UTIL_EmitAmbientSound( ENT( pev ), pev->origin, szSoundFile,
( m_dpv.vol * 0.01 ), m_flAttenuation, SND_SPAWNING, m_dpv.pitch );
2016-06-04 15:24:23 +02:00
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.
2016-07-31 15:48:50 +02:00
void CAmbientGeneric::RampThink( void )
2016-06-04 15:24:23 +02:00
{
2017-07-23 23:24:55 +02:00
const char *szSoundFile = STRING( pev->message );
2016-06-04 15:24:23 +02:00
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
2016-07-31 15:48:50 +02:00
int prev;
2016-06-04 15:24:23 +02:00
2016-07-31 15:48:50 +02:00
if( !m_dpv.spinup && !m_dpv.spindown && !m_dpv.fadein && !m_dpv.fadeout && !m_dpv.lfotype )
2016-06-04 15:24:23 +02:00
return; // no ramps or lfo, stop thinking
// ==============
// pitch envelope
// ==============
2016-07-31 15:48:50 +02:00
if( m_dpv.spinup || m_dpv.spindown )
2016-06-04 15:24:23 +02:00
{
prev = m_dpv.pitchfrac >> 8;
2016-07-31 15:48:50 +02:00
if( m_dpv.spinup > 0 )
2016-06-04 15:24:23 +02:00
m_dpv.pitchfrac += m_dpv.spinup;
2016-07-31 15:48:50 +02:00
else if( m_dpv.spindown > 0 )
2016-06-04 15:24:23 +02:00
m_dpv.pitchfrac -= m_dpv.spindown;
pitch = m_dpv.pitchfrac >> 8;
2016-07-31 15:48:50 +02:00
if( pitch > m_dpv.pitchrun )
2016-06-04 15:24:23 +02:00
{
pitch = m_dpv.pitchrun;
m_dpv.spinup = 0; // done with ramp up
}
2016-07-31 15:48:50 +02:00
if( pitch < m_dpv.pitchstart )
2016-06-04 15:24:23 +02:00
{
pitch = m_dpv.pitchstart;
m_dpv.spindown = 0; // done with ramp down
// shut sound off
2016-07-31 15:48:50 +02:00
UTIL_EmitAmbientSound( ENT( pev ), pev->origin, szSoundFile,
0, 0, SND_STOP, 0 );
2016-06-04 15:24:23 +02:00
// return without setting nextthink
return;
}
2016-07-31 15:48:50 +02:00
if( pitch > 255 )
pitch = 255;
if( pitch < 1 )
pitch = 1;
2016-06-04 15:24:23 +02:00
m_dpv.pitch = pitch;
2016-07-31 15:48:50 +02:00
fChanged |= ( prev != pitch );
2016-06-04 15:24:23 +02:00
flags |= SND_CHANGE_PITCH;
}
// ==================
// amplitude envelope
// ==================
2016-07-31 15:48:50 +02:00
if( m_dpv.fadein || m_dpv.fadeout )
2016-06-04 15:24:23 +02:00
{
prev = m_dpv.volfrac >> 8;
2016-07-31 15:48:50 +02:00
if( m_dpv.fadein > 0 )
2016-06-04 15:24:23 +02:00
m_dpv.volfrac += m_dpv.fadein;
2016-07-31 15:48:50 +02:00
else if( m_dpv.fadeout > 0 )
2016-06-04 15:24:23 +02:00
m_dpv.volfrac -= m_dpv.fadeout;
vol = m_dpv.volfrac >> 8;
2016-07-31 15:48:50 +02:00
if( vol > m_dpv.volrun )
2016-06-04 15:24:23 +02:00
{
vol = m_dpv.volrun;
m_dpv.fadein = 0; // done with ramp up
}
2016-07-31 15:48:50 +02:00
if( vol < m_dpv.volstart )
2016-06-04 15:24:23 +02:00
{
vol = m_dpv.volstart;
m_dpv.fadeout = 0; // done with ramp down
2016-06-04 15:24:23 +02:00
// shut sound off
2016-07-31 15:48:50 +02:00
UTIL_EmitAmbientSound( ENT( pev ), pev->origin, szSoundFile,
0, 0, SND_STOP, 0 );
2016-06-04 15:24:23 +02:00
// return without setting nextthink
return;
}
2016-07-31 15:48:50 +02:00
if( vol > 100 )
vol = 100;
if( vol < 1 )
vol = 1;
2016-06-04 15:24:23 +02:00
m_dpv.vol = vol;
2016-07-31 15:48:50 +02:00
fChanged |= ( prev != vol );
2016-06-04 15:24:23 +02:00
flags |= SND_CHANGE_VOL;
}
// ===================
// pitch/amplitude LFO
// ===================
2016-07-31 15:48:50 +02:00
if( m_dpv.lfotype )
2016-06-04 15:24:23 +02:00
{
int pos;
2016-07-31 15:48:50 +02:00
if( m_dpv.lfofrac > 0x6fffffff )
2016-06-04 15:24:23 +02:00
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;
2016-07-31 15:48:50 +02:00
if( m_dpv.lfofrac < 0 )
2016-06-04 15:24:23 +02:00
{
m_dpv.lfofrac = 0;
2016-07-31 15:48:50 +02:00
m_dpv.lforate = abs( m_dpv.lforate );
2016-06-04 15:24:23 +02:00
pos = 0;
}
2016-07-31 15:48:50 +02:00
else if( pos > 255 )
2016-06-04 15:24:23 +02:00
{
pos = 255;
2016-07-31 15:48:50 +02:00
m_dpv.lfofrac = ( 255 << 8 );
m_dpv.lforate = -abs( m_dpv.lforate );
2016-06-04 15:24:23 +02:00
}
2016-07-31 15:48:50 +02:00
switch( m_dpv.lfotype )
2016-06-04 15:24:23 +02:00
{
case LFO_SQUARE:
2016-07-31 15:48:50 +02:00
if( pos < 128 )
2016-06-04 15:24:23 +02:00
m_dpv.lfomult = 255;
else
m_dpv.lfomult = 0;
break;
case LFO_RANDOM:
2016-07-31 15:48:50 +02:00
if( pos == 255 )
m_dpv.lfomult = RANDOM_LONG( 0, 255 );
2016-06-04 15:24:23 +02:00
break;
case LFO_TRIANGLE:
default:
m_dpv.lfomult = pos;
break;
}
2016-07-31 15:48:50 +02:00
if( m_dpv.lfomodpitch )
2016-06-04 15:24:23 +02:00
{
prev = pitch;
// pitch 0-255
2016-07-31 15:48:50 +02:00
pitch += ( ( m_dpv.lfomult - 128 ) * m_dpv.lfomodpitch ) / 100;
2016-06-04 15:24:23 +02:00
2016-07-31 15:48:50 +02:00
if( pitch > 255 )
pitch = 255;
if( pitch < 1 )
pitch = 1;
2016-06-04 15:24:23 +02:00
2016-07-31 15:48:50 +02:00
fChanged |= ( prev != pitch );
2016-06-04 15:24:23 +02:00
flags |= SND_CHANGE_PITCH;
}
2016-07-31 15:48:50 +02:00
if( m_dpv.lfomodvol )
2016-06-04 15:24:23 +02:00
{
// vol 0-100
prev = vol;
2016-07-31 15:48:50 +02:00
vol += ( ( m_dpv.lfomult - 128 ) * m_dpv.lfomodvol ) / 100;
2016-06-04 15:24:23 +02:00
2016-07-31 15:48:50 +02:00
if( vol > 100 )
vol = 100;
if( vol < 0 )
vol = 0;
2016-07-31 15:48:50 +02:00
fChanged |= ( prev != vol );
2016-06-04 15:24:23 +02:00
flags |= SND_CHANGE_VOL;
}
}
// Send update to playing sound only if we actually changed
// pitch or volume in this routine.
2016-07-31 15:48:50 +02:00
if( flags && fChanged )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
if( pitch == PITCH_NORM )
2016-06-04 15:24:23 +02:00
pitch = PITCH_NORM + 1; // don't send 'no pitch' !
2016-07-31 15:48:50 +02:00
UTIL_EmitAmbientSound( ENT( pev ), pev->origin, szSoundFile,
( vol * 0.01 ), m_flAttenuation, flags, pitch );
2016-06-04 15:24:23 +02:00
}
// update ramps at 5hz
pev->nextthink = gpGlobals->time + 0.2;
return;
}
// Init all ramp params in preparation to
// play a new sound
2016-07-31 15:48:50 +02:00
void CAmbientGeneric::InitModulationParms( void )
2016-06-04 15:24:23 +02:00
{
int pitchinc;
2017-06-29 15:56:03 +02:00
m_dpv.volrun = (int)( pev->health * 10 ); // 0 - 100
2016-07-31 15:48:50 +02:00
if( m_dpv.volrun > 100 )
m_dpv.volrun = 100;
if( m_dpv.volrun < 0 )
m_dpv.volrun = 0;
2016-06-04 15:24:23 +02:00
// get presets
2016-07-31 15:48:50 +02:00
if( m_dpv.preset != 0 && m_dpv.preset <= CDPVPRESETMAX )
2016-06-04 15:24:23 +02:00
{
// load preset values
m_dpv = rgdpvpreset[m_dpv.preset - 1];
// fixup preset values, just like
// fixups in KeyValue routine.
2016-07-31 15:48:50 +02:00
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;
2016-06-04 15:24:23 +02:00
m_dpv.volstart *= 10;
m_dpv.volrun *= 10;
2016-07-31 15:48:50 +02:00
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;
2016-06-04 15:24:23 +02:00
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;
2016-07-31 15:48:50 +02:00
if( m_dpv.fadein )
2016-06-04 15:24:23 +02:00
m_dpv.vol = m_dpv.volstart;
else
m_dpv.vol = m_dpv.volrun;
m_dpv.spinup = m_dpv.spinupsav;
m_dpv.spindown = 0;
2016-07-31 15:48:50 +02:00
if( m_dpv.spinup )
2016-06-04 15:24:23 +02:00
m_dpv.pitch = m_dpv.pitchstart;
else
m_dpv.pitch = m_dpv.pitchrun;
2016-07-31 15:48:50 +02:00
if( m_dpv.pitch == 0 )
2016-06-04 15:24:23 +02:00
m_dpv.pitch = PITCH_NORM;
m_dpv.pitchfrac = m_dpv.pitch << 8;
m_dpv.volfrac = m_dpv.vol << 8;
m_dpv.lfofrac = 0;
2016-07-31 15:48:50 +02:00
m_dpv.lforate = abs( m_dpv.lforate );
2016-06-04 15:24:23 +02:00
m_dpv.cspincount = 1;
2016-07-31 15:48:50 +02:00
if( m_dpv.cspinup )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
pitchinc = ( 255 - m_dpv.pitchstart ) / m_dpv.cspinup;
2016-06-04 15:24:23 +02:00
m_dpv.pitchrun = m_dpv.pitchstart + pitchinc;
2016-07-31 15:48:50 +02:00
if( m_dpv.pitchrun > 255 )
m_dpv.pitchrun = 255;
2016-06-04 15:24:23 +02:00
}
2016-07-31 15:48:50 +02:00
if( ( m_dpv.spinupsav || m_dpv.spindownsav || ( m_dpv.lfotype && m_dpv.lfomodpitch ) )
&& ( m_dpv.pitch == PITCH_NORM ) )
2016-06-04 15:24:23 +02:00
m_dpv.pitch = PITCH_NORM + 1; // must never send 'no pitch' as first pitch
// if we intend to pitch shift later!
2016-06-04 15:24:23 +02:00
}
//
// 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.
//
2016-07-31 15:48:50 +02:00
void CAmbientGeneric::ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
2016-06-04 15:24:23 +02:00
{
2017-07-23 23:24:55 +02:00
const char *szSoundFile = STRING( pev->message );
2016-06-04 15:24:23 +02:00
float fraction;
2016-07-31 15:48:50 +02:00
if( useType != USE_TOGGLE )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
if( ( m_fActive && useType == USE_ON ) || ( !m_fActive && useType == USE_OFF ) )
2016-06-04 15:24:23 +02:00
return;
}
// Directly change pitch if arg passed. Only works if sound is already playing.
2016-07-31 15:48:50 +02:00
if( useType == USE_SET && m_fActive ) // Momentary buttons will pass down a float in here
2016-06-04 15:24:23 +02:00
{
fraction = value;
2016-07-31 15:48:50 +02:00
if( fraction > 1.0 )
2016-06-04 15:24:23 +02:00
fraction = 1.0;
2016-07-31 15:48:50 +02:00
if( fraction < 0.0 )
2016-06-04 15:24:23 +02:00
fraction = 0.01;
2017-06-29 15:56:03 +02:00
m_dpv.pitch = (int)( fraction * 255 );
2016-06-04 15:24:23 +02:00
2016-07-31 15:48:50 +02:00
UTIL_EmitAmbientSound( ENT( pev ), pev->origin, szSoundFile, 0, 0, SND_CHANGE_PITCH, m_dpv.pitch );
2016-06-04 15:24:23 +02:00
return;
}
// Toggle
// m_fActive is TRUE only if a looping sound is playing.
2016-07-31 15:48:50 +02:00
if( m_fActive )
{
// turn sound off
2016-07-31 15:48:50 +02:00
if( m_dpv.cspinup )
2016-06-04 15:24:23 +02:00
{
// Don't actually shut off. Each toggle causes
// incremental spinup to max pitch
2016-07-31 15:48:50 +02:00
if( m_dpv.cspincount <= m_dpv.cspinup )
{
2016-06-04 15:24:23 +02:00
int pitchinc;
// start a new spinup
m_dpv.cspincount++;
2016-07-31 15:48:50 +02:00
pitchinc = ( 255 - m_dpv.pitchstart ) / m_dpv.cspinup;
2016-06-04 15:24:23 +02:00
m_dpv.spinup = m_dpv.spinupsav;
m_dpv.spindown = 0;
m_dpv.pitchrun = m_dpv.pitchstart + pitchinc * m_dpv.cspincount;
2016-07-31 15:48:50 +02:00
if( m_dpv.pitchrun > 255 )
m_dpv.pitchrun = 255;
2016-06-04 15:24:23 +02:00
pev->nextthink = gpGlobals->time + 0.1;
}
}
else
{
m_fActive = FALSE;
2016-06-04 15:24:23 +02:00
// HACKHACK - this makes the code in Precache() work properly after a save/restore
pev->spawnflags |= AMBIENT_SOUND_START_SILENT;
2016-07-31 15:48:50 +02:00
if( m_dpv.spindownsav || m_dpv.fadeoutsav )
2016-06-04 15:24:23 +02:00
{
// 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
2016-07-31 15:48:50 +02:00
UTIL_EmitAmbientSound( ENT( pev ), pev->origin, szSoundFile, 0, 0, SND_STOP, 0 );
2016-06-04 15:24:23 +02:00
}
}
else
{
// turn sound on
2016-06-04 15:24:23 +02:00
// 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.
2016-07-31 15:48:50 +02:00
if( m_fLooping )
2016-06-04 15:24:23 +02:00
m_fActive = TRUE;
else
// shut sound off now - may be interrupting a long non-looping sound
2016-07-31 15:48:50 +02:00
UTIL_EmitAmbientSound( ENT( pev ), pev->origin, szSoundFile, 0, 0, SND_STOP, 0 );
2016-06-04 15:24:23 +02:00
// init all ramp params for startup
2016-06-04 15:24:23 +02:00
InitModulationParms();
2016-07-31 15:48:50 +02:00
UTIL_EmitAmbientSound( ENT( pev ), pev->origin, szSoundFile, ( m_dpv.vol * 0.01 ), m_flAttenuation, 0, m_dpv.pitch );
2016-06-04 15:24:23 +02:00
pev->nextthink = gpGlobals->time + 0.1;
2016-06-04 15:24:23 +02:00
}
}
2016-06-04 15:24:23 +02:00
// KeyValue - load keyvalue pairs into member data of the
// ambient generic. NOTE: called BEFORE spawn!
2016-07-31 15:48:50 +02:00
void CAmbientGeneric::KeyValue( KeyValueData *pkvd )
2016-06-04 15:24:23 +02:00
{
// NOTE: changing any of the modifiers in this code
// NOTE: also requires changing InitModulationParms code.
// preset
2016-07-31 15:48:50 +02:00
if( FStrEq( pkvd->szKeyName, "preset" ) )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
m_dpv.preset = atoi( pkvd->szValue );
2016-06-04 15:24:23 +02:00
pkvd->fHandled = TRUE;
}
// pitchrun
2016-07-31 15:48:50 +02:00
else if( FStrEq( pkvd->szKeyName, "pitch" ) )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
m_dpv.pitchrun = atoi( pkvd->szValue );
2016-06-04 15:24:23 +02:00
pkvd->fHandled = TRUE;
2016-07-31 15:48:50 +02:00
if( m_dpv.pitchrun > 255 )
m_dpv.pitchrun = 255;
if( m_dpv.pitchrun < 0 )
m_dpv.pitchrun = 0;
}
2016-06-04 15:24:23 +02:00
// pitchstart
2016-07-31 15:48:50 +02:00
else if( FStrEq( pkvd->szKeyName, "pitchstart" ) )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
m_dpv.pitchstart = atoi( pkvd->szValue );
pkvd->fHandled = TRUE;
2016-06-04 15:24:23 +02:00
2016-07-31 15:48:50 +02:00
if( m_dpv.pitchstart > 255 )
m_dpv.pitchstart = 255;
if( m_dpv.pitchstart < 0 )
m_dpv.pitchstart = 0;
2016-06-04 15:24:23 +02:00
}
// spinup
2016-07-31 15:48:50 +02:00
else if( FStrEq( pkvd->szKeyName, "spinup" ) )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
m_dpv.spinup = atoi( pkvd->szValue );
2016-06-04 15:24:23 +02:00
2016-07-31 15:48:50 +02:00
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;
2016-06-04 15:24:23 +02:00
m_dpv.spinupsav = m_dpv.spinup;
pkvd->fHandled = TRUE;
}
2016-06-04 15:24:23 +02:00
// spindown
2016-07-31 15:48:50 +02:00
else if( FStrEq( pkvd->szKeyName, "spindown" ) )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
m_dpv.spindown = atoi( pkvd->szValue );
2016-06-04 15:24:23 +02:00
2016-07-31 15:48:50 +02:00
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;
2016-06-04 15:24:23 +02:00
m_dpv.spindownsav = m_dpv.spindown;
pkvd->fHandled = TRUE;
}
// volstart
2016-07-31 15:48:50 +02:00
else if( FStrEq( pkvd->szKeyName, "volstart" ) )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
m_dpv.volstart = atoi( pkvd->szValue );
2016-06-04 15:24:23 +02:00
2016-07-31 15:48:50 +02:00
if( m_dpv.volstart > 10 )
m_dpv.volstart = 10;
if( m_dpv.volstart < 0 )
m_dpv.volstart = 0;
2016-06-04 15:24:23 +02:00
m_dpv.volstart *= 10; // 0 - 100
pkvd->fHandled = TRUE;
}
// fadein
2016-07-31 15:48:50 +02:00
else if( FStrEq( pkvd->szKeyName, "fadein" ) )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
m_dpv.fadein = atoi( pkvd->szValue );
2016-07-31 15:48:50 +02:00
if( m_dpv.fadein > 100 )
m_dpv.fadein = 100;
if( m_dpv.fadein < 0 )
m_dpv.fadein = 0;
2016-06-04 15:24:23 +02:00
2016-07-31 15:48:50 +02:00
if( m_dpv.fadein > 0 )
m_dpv.fadein = ( 101 - m_dpv.fadein ) * 64;
2016-06-04 15:24:23 +02:00
m_dpv.fadeinsav = m_dpv.fadein;
pkvd->fHandled = TRUE;
}
// fadeout
2016-07-31 15:48:50 +02:00
else if( FStrEq( pkvd->szKeyName, "fadeout" ) )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
m_dpv.fadeout = atoi( pkvd->szValue );
2016-07-31 15:48:50 +02:00
if( m_dpv.fadeout > 100 )
m_dpv.fadeout = 100;
if( m_dpv.fadeout < 0 )
m_dpv.fadeout = 0;
2016-06-04 15:24:23 +02:00
2016-07-31 15:48:50 +02:00
if( m_dpv.fadeout > 0 )
m_dpv.fadeout = ( 101 - m_dpv.fadeout ) * 64;
2016-06-04 15:24:23 +02:00
m_dpv.fadeoutsav = m_dpv.fadeout;
pkvd->fHandled = TRUE;
}
// lfotype
2016-07-31 15:48:50 +02:00
else if( FStrEq( pkvd->szKeyName, "lfotype" ) )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
m_dpv.lfotype = atoi( pkvd->szValue );
if( m_dpv.lfotype > 4 )
m_dpv.lfotype = LFO_TRIANGLE;
2016-06-04 15:24:23 +02:00
pkvd->fHandled = TRUE;
}
// lforate
2016-07-31 15:48:50 +02:00
else if( FStrEq( pkvd->szKeyName, "lforate" ) )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
m_dpv.lforate = atoi( pkvd->szValue );
2016-07-31 15:48:50 +02:00
if( m_dpv.lforate > 1000 )
m_dpv.lforate = 1000;
if( m_dpv.lforate < 0 )
m_dpv.lforate = 0;
2016-06-04 15:24:23 +02:00
m_dpv.lforate *= 256;
pkvd->fHandled = TRUE;
}
// lfomodpitch
2016-07-31 15:48:50 +02:00
else if( FStrEq( pkvd->szKeyName, "lfomodpitch" ) )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
m_dpv.lfomodpitch = atoi( pkvd->szValue );
if( m_dpv.lfomodpitch > 100 )
m_dpv.lfomodpitch = 100;
if( m_dpv.lfomodpitch < 0 )
m_dpv.lfomodpitch = 0;
2016-06-04 15:24:23 +02:00
pkvd->fHandled = TRUE;
}
// lfomodvol
2016-07-31 15:48:50 +02:00
else if( FStrEq( pkvd->szKeyName, "lfomodvol" ) )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
m_dpv.lfomodvol = atoi( pkvd->szValue );
if( m_dpv.lfomodvol > 100 )
m_dpv.lfomodvol = 100;
if( m_dpv.lfomodvol < 0 )
m_dpv.lfomodvol = 0;
2016-06-04 15:24:23 +02:00
pkvd->fHandled = TRUE;
}
// cspinup
2016-07-31 15:48:50 +02:00
else if( FStrEq( pkvd->szKeyName, "cspinup" ) )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
m_dpv.cspinup = atoi( pkvd->szValue );
if( m_dpv.cspinup > 100 )
m_dpv.cspinup = 100;
if( m_dpv.cspinup < 0 )
m_dpv.cspinup = 0;
2016-06-04 15:24:23 +02:00
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 );
2016-07-31 15:48:50 +02:00
virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
static TYPEDESCRIPTION m_SaveData[];
2016-06-04 15:24:23 +02:00
float m_flRadius;
float m_flRoomtype;
};
LINK_ENTITY_TO_CLASS( env_sound, CEnvSound )
TYPEDESCRIPTION CEnvSound::m_SaveData[] =
2016-06-04 15:24:23 +02:00
{
DEFINE_FIELD( CEnvSound, m_flRadius, FIELD_FLOAT ),
DEFINE_FIELD( CEnvSound, m_flRoomtype, FIELD_FLOAT ),
};
IMPLEMENT_SAVERESTORE( CEnvSound, CBaseEntity )
2016-06-04 15:24:23 +02:00
2016-07-31 15:48:50 +02:00
void CEnvSound::KeyValue( KeyValueData *pkvd )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
if( FStrEq( pkvd->szKeyName, "radius" ) )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
m_flRadius = atof( pkvd->szValue );
2016-06-04 15:24:23 +02:00
pkvd->fHandled = TRUE;
}
2016-07-31 15:48:50 +02:00
if( FStrEq( pkvd->szKeyName, "roomtype" ) )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
m_flRoomtype = atof( pkvd->szValue );
2016-06-04 15:24:23 +02:00
pkvd->fHandled = TRUE;
}
}
// returns TRUE if the given sound entity (pev) is in range
// and can see the given player entity (pevTarget)
2016-07-31 15:48:50 +02:00
BOOL FEnvSoundInRange( entvars_t *pev, entvars_t *pevTarget, float *pflRange )
2016-06-04 15:24:23 +02:00
{
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;
2016-07-31 15:48:50 +02:00
UTIL_TraceLine( vecSpot1, vecSpot2, ignore_monsters, ENT( pev ), &tr );
2016-06-04 15:24:23 +02:00
// check if line of sight crosses water boundary, or is blocked
2016-07-31 15:48:50 +02:00
if( ( tr.fInOpen && tr.fInWater ) || tr.flFraction != 1 )
2016-06-04 15:24:23 +02:00
return FALSE;
// calc range from sound entity to player
vecRange = tr.vecEndPos - vecSpot1;
flRange = vecRange.Length();
2016-07-31 15:48:50 +02:00
if( pSound->m_flRadius < flRange )
2016-06-04 15:24:23 +02:00
return FALSE;
2016-07-31 15:48:50 +02:00
if( pflRange )
2016-06-04 15:24:23 +02:00
*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.
2016-07-31 15:48:50 +02:00
void CEnvSound::Think( void )
2016-06-04 15:24:23 +02:00
{
// get pointer to client if visible; FIND_CLIENT_IN_PVS will
// cycle through visible clients on consecutive calls.
2016-07-31 15:48:50 +02:00
edict_t *pentPlayer = FIND_CLIENT_IN_PVS( edict() );
2016-06-04 15:24:23 +02:00
CBasePlayer *pPlayer = NULL;
2016-07-31 15:48:50 +02:00
if( FNullEnt( pentPlayer ) )
2016-06-04 15:24:23 +02:00
goto env_sound_Think_slow; // no player in pvs of sound entity, slow it down
2016-07-31 15:48:50 +02:00
pPlayer = GetClassPtr( (CBasePlayer *)VARS( pentPlayer ) );
2016-06-04 15:24:23 +02:00
float flRange;
// check to see if this is the sound entity that is
// currently affecting this player
2016-07-31 15:48:50 +02:00
if( !FNullEnt( pPlayer->m_pentSndLast ) && ( pPlayer->m_pentSndLast == ENT( pev ) ) )
{
2016-06-04 15:24:23 +02:00
// this is the entity currently affecting player, check
// for validity
2016-07-31 15:48:50 +02:00
if( pPlayer->m_flSndRoomtype != 0 && pPlayer->m_flSndRange != 0 )
{
2016-06-04 15:24:23 +02:00
// we're looking at a valid sound entity affecting
// player, make sure it's still valid, update range
2016-07-31 15:48:50 +02:00
if( FEnvSoundInRange( pev, VARS( pentPlayer ), &flRange ) )
{
2016-06-04 15:24:23 +02:00
pPlayer->m_flSndRange = flRange;
goto env_sound_Think_fast;
}
else
{
2016-06-04 15:24:23 +02:00
// 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
{
2016-06-04 15:24:23 +02:00
// 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.
2016-07-31 15:48:50 +02:00
if( FEnvSoundInRange( pev, VARS(pentPlayer), &flRange ) )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
if( flRange < pPlayer->m_flSndRange || pPlayer->m_flSndRange == 0 )
2016-06-04 15:24:23 +02:00
{
// new entity is closer to player, so it wins.
2016-07-31 15:48:50 +02:00
pPlayer->m_pentSndLast = ENT( pev );
2016-06-04 15:24:23 +02:00
pPlayer->m_flSndRoomtype = m_flRoomtype;
pPlayer->m_flSndRange = flRange;
2016-06-04 15:24:23 +02:00
// send room_type command to player's server.
// this should be a rare event - once per change of room_type
// only!
2016-07-31 15:48:50 +02:00
//CLIENT_COMMAND( pentPlayer, "room_type %f", m_flRoomtype );
2016-06-04 15:24:23 +02:00
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.
//
//
2016-07-31 15:48:50 +02:00
void CEnvSound::Spawn()
2016-06-04 15:24:23 +02:00
{
// spread think times
2016-07-31 15:48:50 +02:00
pev->nextthink = gpGlobals->time + RANDOM_FLOAT( 0.0, 0.5 );
2016-06-04 15:24:23 +02:00
}
// ==================== 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
2016-07-31 15:48:50 +02:00
void USENTENCEG_InitLRU( unsigned char *plru, int count )
2016-06-04 15:24:23 +02:00
{
int i, j, k;
unsigned char temp;
2016-07-31 15:48:50 +02:00
if( !fSentencesInit )
2016-06-04 15:24:23 +02:00
return;
2016-07-31 15:48:50 +02:00
if( count > CSENTENCE_LRU_MAX )
2016-06-04 15:24:23 +02:00
count = CSENTENCE_LRU_MAX;
2016-07-31 15:48:50 +02:00
for( i = 0; i < count; i++ )
plru[i] = (unsigned char)i;
2016-06-04 15:24:23 +02:00
// randomize array
2016-07-31 15:48:50 +02:00
for( i = 0; i < ( count * 4 ); i++ )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
j = RANDOM_LONG( 0, count - 1 );
k = RANDOM_LONG( 0, count -1 );
2016-06-04 15:24:23 +02:00
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.
2016-07-31 15:48:50 +02:00
int USENTENCEG_PickSequential( int isentenceg, char *szfound, int ipick, int freset )
2016-06-04 15:24:23 +02:00
{
2019-07-07 17:30:20 +02:00
const char *szgroupname;
2016-06-04 15:24:23 +02:00
unsigned char count;
2016-07-31 15:48:50 +02:00
if( !fSentencesInit )
2016-06-04 15:24:23 +02:00
return -1;
2016-07-31 15:48:50 +02:00
if( isentenceg < 0 )
2016-06-04 15:24:23 +02:00
return -1;
szgroupname = rgsentenceg[isentenceg].szgroupname;
count = rgsentenceg[isentenceg].count;
2016-07-31 15:48:50 +02:00
if( count == 0 )
2016-06-04 15:24:23 +02:00
return -1;
2016-07-31 15:48:50 +02:00
if( ipick >= count )
ipick = count - 1;
2016-06-04 15:24:23 +02:00
2019-07-07 17:30:20 +02:00
sprintf( szfound, "!%s%d", szgroupname, ipick );
2016-07-31 15:48:50 +02:00
if( ipick >= count )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
if( freset )
2016-06-04 15:24:23 +02:00
// 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.
2016-07-31 15:48:50 +02:00
int USENTENCEG_Pick( int isentenceg, char *szfound )
2016-06-04 15:24:23 +02:00
{
2019-07-07 17:30:20 +02:00
const char *szgroupname;
2016-06-04 15:24:23 +02:00
unsigned char *plru;
unsigned char i;
unsigned char count;
unsigned char ipick;
int ffound = FALSE;
2016-07-31 15:48:50 +02:00
if( !fSentencesInit )
2016-06-04 15:24:23 +02:00
return -1;
2016-07-31 15:48:50 +02:00
if( isentenceg < 0 )
2016-06-04 15:24:23 +02:00
return -1;
szgroupname = rgsentenceg[isentenceg].szgroupname;
count = rgsentenceg[isentenceg].count;
plru = rgsentenceg[isentenceg].rgblru;
2016-07-31 15:48:50 +02:00
while( !ffound )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
for(i = 0; i < count; i++ )
if( plru[i] != 0xFF )
2016-06-04 15:24:23 +02:00
{
ipick = plru[i];
plru[i] = 0xFF;
ffound = TRUE;
break;
}
2016-07-31 15:48:50 +02:00
if( !ffound )
USENTENCEG_InitLRU( plru, count );
2016-06-04 15:24:23 +02:00
else
{
2019-07-07 17:30:20 +02:00
sprintf( szfound, "!%s%d", szgroupname, ipick );
2016-06-04 15:24:23 +02:00
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.
2016-07-31 15:48:50 +02:00
int SENTENCEG_GetIndex( const char *szgroupname )
2016-06-04 15:24:23 +02:00
{
int i;
2016-07-31 15:48:50 +02:00
if( !fSentencesInit || !szgroupname )
2016-06-04 15:24:23 +02:00
return -1;
// search rgsentenceg for match on szgroupname
i = 0;
2016-07-31 15:48:50 +02:00
while( rgsentenceg[i].count )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
if( !strcmp( szgroupname, rgsentenceg[i].szgroupname ) )
2016-06-04 15:24:23 +02:00
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).
2016-07-31 15:48:50 +02:00
int SENTENCEG_PlayRndI( edict_t *entity, int isentenceg, float volume, float attenuation, int flags, int pitch )
2016-06-04 15:24:23 +02:00
{
char name[64];
int ipick;
2016-07-31 15:48:50 +02:00
if( !fSentencesInit )
2016-06-04 15:24:23 +02:00
return -1;
name[0] = 0;
2016-07-31 15:48:50 +02:00
ipick = USENTENCEG_Pick( isentenceg, name );
2016-06-13 15:48:17 +02:00
if( ipick > 0 )
2016-07-31 15:48:50 +02:00
EMIT_SOUND_DYN( entity, CHAN_VOICE, name, volume, attenuation, flags, pitch );
2016-06-04 15:24:23 +02:00
return ipick;
}
// same as above, but takes sentence group name instead of index
2016-07-31 15:48:50 +02:00
int SENTENCEG_PlayRndSz( edict_t *entity, const char *szgroupname, float volume, float attenuation, int flags, int pitch )
2016-06-04 15:24:23 +02:00
{
char name[64];
int ipick;
int isentenceg;
2016-07-31 15:48:50 +02:00
if( !fSentencesInit )
2016-06-04 15:24:23 +02:00
return -1;
name[0] = 0;
2016-07-31 15:48:50 +02:00
isentenceg = SENTENCEG_GetIndex( szgroupname );
if( isentenceg < 0 )
2016-06-04 15:24:23 +02:00
{
ALERT( at_console, "No such sentence group %s\n", szgroupname );
return -1;
}
2016-07-31 15:48:50 +02:00
ipick = USENTENCEG_Pick( isentenceg, name );
if( ipick >= 0 && name[0] )
EMIT_SOUND_DYN( entity, CHAN_VOICE, name, volume, attenuation, flags, pitch );
2016-06-04 15:24:23 +02:00
return ipick;
}
// play sentences in sequential order from sentence group. Reset after last sentence.
2016-07-31 15:48:50 +02:00
int SENTENCEG_PlaySequentialSz( edict_t *entity, const char *szgroupname, float volume, float attenuation, int flags, int pitch, int ipick, int freset )
2016-06-04 15:24:23 +02:00
{
char name[64];
int ipicknext;
int isentenceg;
2016-07-31 15:48:50 +02:00
if( !fSentencesInit )
2016-06-04 15:24:23 +02:00
return -1;
name[0] = 0;
isentenceg = SENTENCEG_GetIndex(szgroupname);
2016-07-31 15:48:50 +02:00
if( isentenceg < 0 )
2016-06-04 15:24:23 +02:00
return -1;
2016-07-31 15:48:50 +02:00
ipicknext = USENTENCEG_PickSequential(isentenceg, name, ipick, freset );
if( ipicknext >= 0 && name[0] )
EMIT_SOUND_DYN( entity, CHAN_VOICE, name, volume, attenuation, flags, pitch );
2016-06-04 15:24:23 +02:00
return ipicknext;
}
// for this entity, for the given sentence within the sentence group, stop
// the sentence.
2016-07-31 15:48:50 +02:00
void SENTENCEG_Stop( edict_t *entity, int isentenceg, int ipick )
2016-06-04 15:24:23 +02:00
{
char buffer[64];
2016-07-31 15:48:50 +02:00
if( !fSentencesInit )
2016-06-04 15:24:23 +02:00
return;
2016-07-31 15:48:50 +02:00
if( isentenceg < 0 || ipick < 0 )
2016-06-04 15:24:23 +02:00
return;
2019-07-07 17:30:20 +02:00
sprintf( buffer, "!%s%d", rgsentenceg[isentenceg].szgroupname, ipick );
2016-06-04 15:24:23 +02:00
2016-07-31 15:48:50 +02:00
STOP_SOUND( entity, CHAN_VOICE, buffer );
2016-06-04 15:24:23 +02:00
}
// 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;
2016-07-31 15:48:50 +02:00
if( fSentencesInit )
2016-06-04 15:24:23 +02:00
return;
2016-07-31 15:48:50 +02:00
memset( gszallsentencenames, 0, CVOXFILESENTENCEMAX * CBSENTENCENAME_MAX );
2016-06-04 15:24:23 +02:00
gcallsentences = 0;
2016-07-31 15:48:50 +02:00
memset( rgsentenceg, 0, CSENTENCEG_MAX * sizeof(SENTENCEG) );
2016-06-04 15:24:23 +02:00
isentencegs = -1;
int filePos = 0, fileSize;
byte *pMemFile = g_engfuncs.pfnLoadFileForMe( "sound/sentences.txt", &fileSize );
2016-07-31 15:48:50 +02:00
if( !pMemFile )
2016-06-04 15:24:23 +02:00
return;
2016-08-02 13:59:22 +02:00
memset( buffer, 0, 512 );
2016-08-02 18:24:33 +02:00
memset( szgroup, 0, 64 );
2016-06-04 15:24:23 +02:00
// for each line in the file...
2016-07-31 15:48:50 +02:00
while( memfgets( pMemFile, fileSize, filePos, buffer, 511 ) != NULL )
2016-06-04 15:24:23 +02:00
{
// skip whitespace
i = 0;
2016-07-31 15:48:50 +02:00
while( buffer[i] && buffer[i] == ' ' )
2016-06-04 15:24:23 +02:00
i++;
2016-07-31 15:48:50 +02:00
if( !buffer[i] )
2016-06-04 15:24:23 +02:00
continue;
2016-07-31 15:48:50 +02:00
if( buffer[i] == '/' || !isalpha( buffer[i] ) )
2016-06-04 15:24:23 +02:00
continue;
// get sentence name
j = i;
2016-07-31 15:48:50 +02:00
while( buffer[j] && buffer[j] != ' ' )
2016-06-04 15:24:23 +02:00
j++;
2016-07-31 15:48:50 +02:00
if( !buffer[j] )
2016-06-04 15:24:23 +02:00
continue;
2016-07-31 15:48:50 +02:00
if( gcallsentences > CVOXFILESENTENCEMAX )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
ALERT( at_error, "Too many sentences in sentences.txt!\n" );
2016-06-04 15:24:23 +02:00
break;
}
// null-terminate name and save in sentences array
buffer[j] = 0;
const char *pString = buffer + i;
2016-07-31 15:48:50 +02:00
if( strlen( pString ) >= CBSENTENCENAME_MAX )
ALERT( at_warning, "Sentence %s longer than %d letters\n", pString, CBSENTENCENAME_MAX - 1 );
2016-06-04 15:24:23 +02:00
strcpy( gszallsentencenames[gcallsentences++], pString );
j--;
2016-07-31 15:48:50 +02:00
if( j <= i )
2016-06-04 15:24:23 +02:00
continue;
2016-07-31 15:48:50 +02:00
if( !isdigit( buffer[j] ) )
2016-06-04 15:24:23 +02:00
continue;
// cut out suffix numbers
2016-07-31 15:48:50 +02:00
while( j > i && isdigit( buffer[j] ) )
2016-06-04 15:24:23 +02:00
j--;
2016-07-31 15:48:50 +02:00
if( j <= i )
2016-06-04 15:24:23 +02:00
continue;
2016-07-31 15:48:50 +02:00
buffer[j + 1] = 0;
2016-06-04 15:24:23 +02:00
// if new name doesn't match previous group name,
// make a new group.
2016-07-31 15:48:50 +02:00
if( strcmp( szgroup, &( buffer[i] ) ) )
2016-06-04 15:24:23 +02:00
{
// name doesn't match with prev name,
// copy name into group, init count to 1
isentencegs++;
2016-07-31 15:48:50 +02:00
if( isentencegs >= CSENTENCEG_MAX )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
ALERT( at_error, "Too many sentence groups in sentences.txt!\n" );
2016-06-04 15:24:23 +02:00
break;
}
2016-07-31 15:48:50 +02:00
strcpy( rgsentenceg[isentencegs].szgroupname, &( buffer[i] ) );
2016-06-04 15:24:23 +02:00
rgsentenceg[isentencegs].count = 1;
2016-07-31 15:48:50 +02:00
strcpy( szgroup, &( buffer[i] ) );
2016-06-04 15:24:23 +02:00
continue;
}
else
{
//name matches with previous, increment group count
2016-07-31 15:48:50 +02:00
if( isentencegs >= 0 )
2016-06-04 15:24:23 +02:00
rgsentenceg[isentencegs].count++;
}
}
g_engfuncs.pfnFreeFile( pMemFile );
2016-06-04 15:24:23 +02:00
fSentencesInit = TRUE;
// init lru lists
i = 0;
2016-07-31 15:48:50 +02:00
while( rgsentenceg[i].count && i < CSENTENCEG_MAX )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
USENTENCEG_InitLRU( &( rgsentenceg[i].rgblru[0] ), rgsentenceg[i].count );
2016-06-04 15:24:23 +02:00
i++;
}
}
// convert sentence (sample) name to !sentencenum, return !sentencenum
2016-07-31 15:48:50 +02:00
int SENTENCEG_Lookup( const char *sample, char *sentencenum )
2016-06-04 15:24:23 +02:00
{
int i;
2019-07-07 17:30:20 +02:00
2016-06-04 15:24:23 +02:00
// this is a sentence name; lookup sentence number
// and give to engine as string.
2016-07-31 15:48:50 +02:00
for( i = 0; i < gcallsentences; i++ )
if( !stricmp( gszallsentencenames[i], sample + 1 ) )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
if( sentencenum )
2016-06-04 15:24:23 +02:00
{
2019-07-07 17:30:20 +02:00
sprintf(sentencenum, "!%d", i);
2016-06-04 15:24:23 +02:00
}
return i;
}
// sentence name not found!
return -1;
}
2016-07-31 15:48:50 +02:00
void EMIT_SOUND_DYN( edict_t *entity, int channel, const char *sample, float volume, float attenuation, int flags, int pitch )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
if( sample && *sample == '!' )
2016-06-04 15:24:23 +02:00
{
char name[32];
2016-07-31 15:48:50 +02:00
if( SENTENCEG_Lookup( sample, name ) >= 0 )
EMIT_SOUND_DYN2( entity, channel, name, volume, attenuation, flags, pitch );
2016-06-04 15:24:23 +02:00
else
ALERT( at_aiconsole, "Unable to find %s in sentences.txt\n", sample );
}
else
2016-07-31 15:48:50 +02:00
EMIT_SOUND_DYN2( entity, channel, sample, volume, attenuation, flags, pitch );
2016-06-04 15:24:23 +02:00
}
// play a specific sentence over the HEV suit speaker - just pass player entity, and !sentencename
2016-07-31 15:48:50 +02:00
void EMIT_SOUND_SUIT( edict_t *entity, const char *sample )
2016-06-04 15:24:23 +02:00
{
float fvol;
int pitch = PITCH_NORM;
2016-07-31 15:48:50 +02:00
fvol = CVAR_GET_FLOAT( "suitvolume" );
if( RANDOM_LONG( 0, 1 ) )
pitch = RANDOM_LONG( 0, 6 ) + 98;
2016-06-04 15:24:23 +02:00
2016-07-31 15:48:50 +02:00
if( fvol > 0.05 )
EMIT_SOUND_DYN( entity, CHAN_STATIC, sample, fvol, ATTN_NORM, 0, pitch );
2016-06-04 15:24:23 +02:00
}
// play a sentence, randomly selected from the passed in group id, over the HEV suit speaker
2016-07-31 15:48:50 +02:00
void EMIT_GROUPID_SUIT( edict_t *entity, int isentenceg )
2016-06-04 15:24:23 +02:00
{
float fvol;
int pitch = PITCH_NORM;
2016-07-31 15:48:50 +02:00
fvol = CVAR_GET_FLOAT( "suitvolume" );
if( RANDOM_LONG( 0, 1 ) )
pitch = RANDOM_LONG( 0, 6 ) + 98;
2016-06-04 15:24:23 +02:00
2016-07-31 15:48:50 +02:00
if( fvol > 0.05 )
SENTENCEG_PlayRndI( entity, isentenceg, fvol, ATTN_NORM, 0, pitch );
2016-06-04 15:24:23 +02:00
}
// play a sentence, randomly selected from the passed in groupname
2016-07-31 15:48:50 +02:00
void EMIT_GROUPNAME_SUIT( edict_t *entity, const char *groupname )
2016-06-04 15:24:23 +02:00
{
float fvol;
int pitch = PITCH_NORM;
2016-07-31 15:48:50 +02:00
fvol = CVAR_GET_FLOAT( "suitvolume" );
if( RANDOM_LONG( 0, 1 ) )
pitch = RANDOM_LONG( 0, 6 ) + 98;
2016-06-04 15:24:23 +02:00
2016-07-31 15:48:50 +02:00
if( fvol > 0.05 )
SENTENCEG_PlayRndSz( entity, groupname, fvol, ATTN_NORM, 0, pitch );
2016-06-04 15:24:23 +02:00
}
// ===================== 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.
// 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
2016-07-31 15:48:50 +02:00
if( !pMemFile || !pBuffer )
2016-06-04 15:24:23 +02:00
return NULL;
2016-07-31 15:48:50 +02:00
if( filePos >= fileSize )
2016-06-04 15:24:23 +02:00
return NULL;
int i = filePos;
int last = fileSize;
// fgets always NULL terminates, so only read bufferSize-1 characters
2016-07-31 15:48:50 +02:00
if( last - filePos > ( bufferSize - 1 ) )
last = filePos + ( bufferSize - 1 );
2016-06-04 15:24:23 +02:00
int stop = 0;
// Stop at the next newline (inclusive) or end of buffer
2016-07-31 15:48:50 +02:00
while( i < last && !stop )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
if( pMemFile[i] == '\n' )
2016-06-04 15:24:23 +02:00
stop = 1;
i++;
}
// If we actually advanced the pointer, copy it over
2016-07-31 15:48:50 +02:00
if( i != filePos )
2016-06-04 15:24:23 +02:00
{
// We read in size bytes
int size = i - filePos;
// copy it out
2016-07-31 15:48:50 +02:00
memcpy( pBuffer, pMemFile + filePos, sizeof(byte) * size );
2016-06-04 15:24:23 +02:00
// If the buffer isn't full, terminate (this is always true)
2016-07-31 15:48:50 +02:00
if( size < bufferSize )
2016-06-04 15:24:23 +02:00
pBuffer[size] = 0;
// Update file pointer
filePos = i;
return pBuffer;
}
// No data read, bail
return NULL;
}
// 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!
2018-04-08 18:59:11 +02:00
extern "C" char PM_FindTextureType( char *name );
2016-07-31 15:48:50 +02:00
char TEXTURETYPE_Find( char *name )
2016-06-04 15:24:23 +02:00
{
2018-04-08 18:59:11 +02:00
return PM_FindTextureType(name);
2016-06-04 15:24:23 +02:00
}
// 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
2016-07-31 15:48:50 +02:00
float TEXTURETYPE_PlaySound( TraceResult *ptr, Vector vecSrc, Vector vecEnd, int iBulletType )
2016-06-04 15:24:23 +02:00
{
// hit the world, try to play sound based on texture material type
2016-06-04 15:24:23 +02:00
char chTextureType;
float fvol;
float fvolbar;
char szbuffer[64];
const char *pTextureName;
float rgfl1[3];
float rgfl2[3];
2017-06-29 15:56:03 +02:00
const char *rgsz[4];
2016-06-04 15:24:23 +02:00
int cnt;
float fattn = ATTN_NORM;
2016-07-31 15:48:50 +02:00
if( !g_pGameRules->PlayTextureSounds() )
2016-06-04 15:24:23 +02:00
return 0.0;
2016-07-31 15:48:50 +02:00
CBaseEntity *pEntity = CBaseEntity::Instance( ptr->pHit );
2016-06-04 15:24:23 +02:00
chTextureType = 0;
2016-07-31 15:48:50 +02:00
if( pEntity && pEntity->Classify() != CLASS_NONE && pEntity->Classify() != CLASS_MACHINE )
2016-06-04 15:24:23 +02:00
// hit body
chTextureType = CHAR_TEX_FLESH;
else
{
// hit world
// find texture under strike, get material type
// copy trace vector into array for trace_texture
2016-07-31 15:48:50 +02:00
vecSrc.CopyToArray( rgfl1 );
vecEnd.CopyToArray( rgfl2 );
2016-06-04 15:24:23 +02:00
// get texture from entity or world (world is ent(0))
2016-07-31 15:48:50 +02:00
if( pEntity )
pTextureName = TRACE_TEXTURE( ENT( pEntity->pev ), rgfl1, rgfl2 );
2016-06-04 15:24:23 +02:00
else
2016-07-31 15:48:50 +02:00
pTextureName = TRACE_TEXTURE( ENT( 0 ), rgfl1, rgfl2 );
2016-07-31 15:48:50 +02:00
if( pTextureName )
2016-06-04 15:24:23 +02:00
{
// strip leading '-0' or '+0~' or '{' or '!'
2016-07-31 15:48:50 +02:00
if( *pTextureName == '-' || *pTextureName == '+' )
2016-06-04 15:24:23 +02:00
pTextureName += 2;
2016-07-31 15:48:50 +02:00
if( *pTextureName == '{' || *pTextureName == '!' || *pTextureName == '~' || *pTextureName == ' ' )
2016-06-04 15:24:23 +02:00
pTextureName++;
// '}}'
2016-07-31 15:48:50 +02:00
strcpy( szbuffer, pTextureName );
2016-06-04 15:24:23 +02:00
szbuffer[CBTEXTURENAMEMAX - 1] = 0;
2016-07-31 15:48:50 +02:00
// ALERT( at_console, "texture hit: %s\n", szbuffer );
2016-06-04 15:24:23 +02:00
// get texture type
2016-07-31 15:48:50 +02:00
chTextureType = TEXTURETYPE_Find( szbuffer );
2016-06-04 15:24:23 +02:00
}
}
2016-07-31 15:48:50 +02:00
switch( chTextureType )
2016-06-04 15:24:23 +02:00
{
default:
2016-07-31 15:48:50 +02:00
case CHAR_TEX_CONCRETE:
fvol = 0.9;
fvolbar = 0.6;
2016-06-04 15:24:23 +02:00
rgsz[0] = "player/pl_step1.wav";
rgsz[1] = "player/pl_step2.wav";
cnt = 2;
break;
2016-07-31 15:48:50 +02:00
case CHAR_TEX_METAL:
fvol = 0.9;
fvolbar = 0.3;
2016-06-04 15:24:23 +02:00
rgsz[0] = "player/pl_metal1.wav";
rgsz[1] = "player/pl_metal2.wav";
cnt = 2;
break;
2016-07-31 15:48:50 +02:00
case CHAR_TEX_DIRT:
fvol = 0.9;
fvolbar = 0.1;
2016-06-04 15:24:23 +02:00
rgsz[0] = "player/pl_dirt1.wav";
rgsz[1] = "player/pl_dirt2.wav";
rgsz[2] = "player/pl_dirt3.wav";
cnt = 3;
break;
2016-07-31 15:48:50 +02:00
case CHAR_TEX_VENT:
fvol = 0.5;
fvolbar = 0.3;
2016-06-04 15:24:23 +02:00
rgsz[0] = "player/pl_duct1.wav";
rgsz[1] = "player/pl_duct1.wav";
cnt = 2;
break;
2016-07-31 15:48:50 +02:00
case CHAR_TEX_GRATE:
fvol = 0.9;
fvolbar = 0.5;
2016-06-04 15:24:23 +02:00
rgsz[0] = "player/pl_grate1.wav";
rgsz[1] = "player/pl_grate4.wav";
cnt = 2;
break;
2016-07-31 15:48:50 +02:00
case CHAR_TEX_TILE:
fvol = 0.8;
fvolbar = 0.2;
2016-06-04 15:24:23 +02:00
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;
2016-07-31 15:48:50 +02:00
case CHAR_TEX_SLOSH:
fvol = 0.9;
fvolbar = 0.0;
2016-06-04 15:24:23 +02:00
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;
2016-07-31 15:48:50 +02:00
case CHAR_TEX_WOOD:
fvol = 0.9;
fvolbar = 0.2;
2016-06-04 15:24:23 +02:00
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:
2016-07-31 15:48:50 +02:00
fvol = 0.8;
fvolbar = 0.2;
2016-06-04 15:24:23 +02:00
rgsz[0] = "debris/glass1.wav";
rgsz[1] = "debris/glass2.wav";
rgsz[2] = "debris/glass3.wav";
cnt = 3;
break;
case CHAR_TEX_FLESH:
2016-07-31 15:48:50 +02:00
if( iBulletType == BULLET_PLAYER_CROWBAR )
2016-06-04 15:24:23 +02:00
return 0.0; // crowbar already makes this sound
2016-07-31 15:48:50 +02:00
fvol = 1.0;
fvolbar = 0.2;
2016-06-04 15:24:23 +02:00
rgsz[0] = "weapons/bullet_hit1.wav";
rgsz[1] = "weapons/bullet_hit2.wav";
fattn = 1.0;
cnt = 2;
break;
}
// did we hit a breakable?
2016-07-31 15:48:50 +02:00
if( pEntity && FClassnameIs( pEntity->pev, "func_breakable" ) )
2016-06-04 15:24:23 +02:00
{
// drop volumes, the object will already play a damaged sound
fvol /= 1.5;
2016-07-31 15:48:50 +02:00
fvolbar /= 2.0;
2016-06-04 15:24:23 +02:00
}
2016-07-31 15:48:50 +02:00
else if( chTextureType == CHAR_TEX_COMPUTER )
2016-06-04 15:24:23 +02:00
{
// play random spark if computer
2016-07-31 15:48:50 +02:00
if( ptr->flFraction != 1.0 && RANDOM_LONG( 0, 1 ) )
2016-06-04 15:24:23 +02:00
{
UTIL_Sparks( ptr->vecEndPos );
2016-07-31 15:48:50 +02:00
float flVolume = RANDOM_FLOAT( 0.7, 1.0 );//random volume range
switch( RANDOM_LONG( 0, 1 ) )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
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;*/
2016-06-04 15:24:23 +02:00
}
}
}
// play material hit sound
2016-07-31 15:48:50 +02:00
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 ) );
2016-06-04 15:24:23 +02:00
return fvolbar;
}
// ===================================================================================
//
// Speaker class. Used for announcements per level, for door lock/unlock spoken voice.
//
class CSpeaker : public CBaseEntity
{
public:
2016-07-31 15:48:50 +02:00
void KeyValue( KeyValueData *pkvd );
2016-06-04 15:24:23 +02:00
void Spawn( void );
void Precache( void );
2016-07-31 15:48:50 +02:00
void EXPORT ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
2016-06-04 15:24:23 +02:00
void EXPORT SpeakerThink( void );
2016-07-31 15:48:50 +02:00
virtual int Save( CSave &save );
virtual int Restore( CRestore &restore );
static TYPEDESCRIPTION m_SaveData[];
2016-06-04 15:24:23 +02:00
2016-07-31 15:48:50 +02:00
virtual int ObjectCaps( void ) { return ( CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION ); }
2016-07-31 15:48:50 +02:00
int m_preset; // preset number
2016-06-04 15:24:23 +02:00
};
LINK_ENTITY_TO_CLASS( speaker, CSpeaker )
2016-07-31 15:48:50 +02:00
TYPEDESCRIPTION CSpeaker::m_SaveData[] =
2016-06-04 15:24:23 +02:00
{
DEFINE_FIELD( CSpeaker, m_preset, FIELD_INTEGER ),
};
IMPLEMENT_SAVERESTORE( CSpeaker, CBaseEntity )
2016-06-04 15:24:23 +02:00
//
// ambient_generic - general-purpose user-defined static sound
//
2016-07-31 15:48:50 +02:00
void CSpeaker::Spawn( void )
2016-06-04 15:24:23 +02:00
{
2017-06-29 15:56:03 +02:00
const char *szSoundFile = STRING( pev->message );
2016-06-04 15:24:23 +02:00
2016-07-31 15:48:50 +02:00
if( !m_preset && ( FStringNull( pev->message ) || strlen( szSoundFile ) < 1 ) )
2016-06-04 15:24:23 +02:00
{
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;
}
2016-07-31 15:48:50 +02:00
pev->solid = SOLID_NOT;
pev->movetype = MOVETYPE_NONE;
2016-06-04 15:24:23 +02:00
2016-07-31 15:48:50 +02:00
SetThink( &CSpeaker::SpeakerThink );
2016-06-04 15:24:23 +02:00
pev->nextthink = 0.0;
// allow on/off switching via 'use' function.
SetUse( &CSpeaker::ToggleUse );
2016-07-31 15:48:50 +02:00
Precache();
2016-06-04 15:24:23 +02:00
}
#define ANNOUNCE_MINUTES_MIN 0.25
#define ANNOUNCE_MINUTES_MAX 2.25
2016-07-31 15:48:50 +02:00
void CSpeaker::Precache( void )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
if( !FBitSet( pev->spawnflags, SPEAKER_START_SILENT ) )
2016-06-04 15:24:23 +02:00
// set first announcement time for random n second
2016-07-31 15:48:50 +02:00
pev->nextthink = gpGlobals->time + RANDOM_FLOAT( 5.0, 15.0 );
2016-06-04 15:24:23 +02:00
}
2016-07-31 15:48:50 +02:00
void CSpeaker::SpeakerThink( void )
2016-06-04 15:24:23 +02:00
{
2017-06-29 15:56:03 +02:00
const char* szSoundFile = NULL;
2016-06-04 15:24:23 +02:00
float flvolume = pev->health * 0.1;
float flattenuation = 0.3;
int flags = 0;
int pitch = 100;
// Wait for the talkmonster to finish first.
2016-07-31 15:48:50 +02:00
if( gpGlobals->time <= CTalkMonster::g_talkWaitTime )
2016-06-04 15:24:23 +02:00
{
pev->nextthink = CTalkMonster::g_talkWaitTime + RANDOM_FLOAT( 5, 10 );
return;
}
2016-07-31 15:48:50 +02:00
if( m_preset )
2016-06-04 15:24:23 +02:00
{
// go lookup preset text, assign szSoundFile
2016-07-31 15:48:50 +02:00
switch( m_preset )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
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;
2016-06-04 15:24:23 +02:00
}
}
else
2017-06-29 15:56:03 +02:00
szSoundFile = STRING( pev->message );
2016-07-31 15:48:50 +02:00
if( szSoundFile[0] == '!' )
2016-06-04 15:24:23 +02:00
{
// play single sentence, one shot
2016-07-31 15:48:50 +02:00
UTIL_EmitAmbientSound( ENT( pev ), pev->origin, szSoundFile,
flvolume, flattenuation, flags, pitch );
2016-06-04 15:24:23 +02:00
// shut off and reset
pev->nextthink = 0.0;
}
else
{
// make random announcement from sentence group
2016-07-31 15:48:50 +02:00
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 );
2016-06-04 15:24:23 +02:00
// set next announcement time for random 5 to 10 minute delay
2016-07-31 15:48:50 +02:00
pev->nextthink = gpGlobals->time + RANDOM_FLOAT( ANNOUNCE_MINUTES_MIN * 60.0, ANNOUNCE_MINUTES_MAX * 60.0 );
2016-06-04 15:24:23 +02:00
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.
//
2016-07-31 15:48:50 +02:00
void CSpeaker::ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
int fActive = ( pev->nextthink > 0.0 );
2016-06-04 15:24:23 +02:00
// fActive is TRUE only if an announcement is pending
2016-07-31 15:48:50 +02:00
if( useType != USE_TOGGLE )
2016-06-04 15:24:23 +02:00
{
// ignore if we're just turning something on that's already on, or
// turning something off that's already off.
2016-07-31 15:48:50 +02:00
if( ( fActive && useType == USE_ON ) || ( !fActive && useType == USE_OFF ) )
2016-06-04 15:24:23 +02:00
return;
}
2016-07-31 15:48:50 +02:00
if( useType == USE_ON )
2016-06-04 15:24:23 +02:00
{
// turn on announcements
pev->nextthink = gpGlobals->time + 0.1;
return;
}
2016-07-31 15:48:50 +02:00
if( useType == USE_OFF )
2016-06-04 15:24:23 +02:00
{
// turn off announcements
pev->nextthink = 0.0;
return;
}
// Toggle announcements
2016-07-31 15:48:50 +02:00
if( fActive )
2016-06-04 15:24:23 +02:00
{
// turn off announcements
pev->nextthink = 0.0;
}
else
{
// turn on announcements
pev->nextthink = gpGlobals->time + 0.1;
}
2016-06-04 15:24:23 +02:00
}
// KeyValue - load keyvalue pairs into member data
// NOTE: called BEFORE spawn!
2016-07-31 15:48:50 +02:00
void CSpeaker::KeyValue( KeyValueData *pkvd )
2016-06-04 15:24:23 +02:00
{
// preset
2016-07-31 15:48:50 +02:00
if( FStrEq( pkvd->szKeyName, "preset" ) )
2016-06-04 15:24:23 +02:00
{
2016-07-31 15:48:50 +02:00
m_preset = atoi( pkvd->szValue );
2016-06-04 15:24:23 +02:00
pkvd->fHandled = TRUE;
}
else
CBaseEntity::KeyValue( pkvd );
2016-04-17 22:30:05 +02:00
}