mirror of
https://github.com/s1lentq/ReGameDLL_CS.git
synced 2024-12-27 07:05:38 +03:00
19714af6e6
minor refactor
1972 lines
49 KiB
C++
1972 lines
49 KiB
C++
#include "precompiled.h"
|
|
|
|
// presets for runtime pitch and vol modulation of ambient sounds
|
|
dynpitchvol_t rgdpvpreset[MAX_SENTENCE_DPV_RESET] =
|
|
{
|
|
// pitch prun pstart spinup spindwn volrun volstrt fadein fadeout lfotype lforate lfomodp modvol cspnup cspnct pitch spupsv spdwnsv pfrac vol fdinsv fdotsv volfrac lfofrac lfomult
|
|
{ 1, 255, 75, 95, 95, 10, 1, 50, 95, LFO_OFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 2, 255, 85, 70, 88, 10, 1, 20, 88, LFO_OFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 3, 255, 100, 50, 75, 10, 1, 10, 75, LFO_OFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 4, 100, 100, 0, 0, 10, 1, 90, 90, LFO_OFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 5, 100, 100, 0, 0, 10, 1, 80, 80, LFO_OFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 6, 100, 100, 0, 0, 10, 1, 50, 70, LFO_OFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 7, 100, 100, 0, 0, 5, 1, 40, 50, LFO_SQUARE, 50, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 8, 100, 100, 0, 0, 5, 1, 40, 50, LFO_SQUARE, 150, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 9, 100, 100, 0, 0, 5, 1, 40, 50, LFO_SQUARE, 750, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 10, 128, 100, 50, 75, 10, 1, 30, 40, LFO_TRIANGLE, 8, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 11, 128, 100, 50, 75, 10, 1, 30, 40, LFO_TRIANGLE, 25, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 12, 128, 100, 50, 75, 10, 1, 30, 40, LFO_TRIANGLE, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 13, 50, 50, 0, 0, 10, 1, 20, 50, LFO_OFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 14, 70, 70, 0, 0, 10, 1, 20, 50, LFO_OFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 15, 90, 90, 0, 0, 10, 1, 20, 50, LFO_OFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 16, 120, 120, 0, 0, 10, 1, 20, 50, LFO_OFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 17, 180, 180, 0, 0, 10, 1, 20, 50, LFO_OFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 18, 255, 255, 0, 0, 10, 1, 20, 50, LFO_OFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 19, 200, 75, 90, 90, 10, 1, 50, 90, LFO_TRIANGLE, 100, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 20, 255, 75, 97, 90, 10, 1, 50, 90, LFO_SQUARE, 40, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 21, 100, 100, 0, 0, 10, 1, 30, 50, LFO_RANDOM, 15, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 22, 160, 160, 0, 0, 10, 1, 50, 50, LFO_RANDOM, 500, 25, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 23, 255, 75, 88, 0, 10, 1, 40, 0, LFO_OFF, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 24, 200, 20, 95, 70, 10, 1, 70, 70, LFO_RANDOM, 20, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 25, 180, 100, 50, 60, 10, 1, 40, 60, LFO_TRIANGLE, 90, 100, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 26, 60, 60, 0, 0, 10, 1, 40, 70, LFO_RANDOM, 80, 20, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 27, 128, 90, 10, 10, 10, 1, 20, 40, LFO_SQUARE, 5, 10, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
};
|
|
|
|
int gcallsentences = 0;
|
|
BOOL fSentencesInit = FALSE;
|
|
|
|
int gcTextures = 0;
|
|
BOOL fTextureTypeInit = FALSE;
|
|
|
|
// time delay until it's ok to speak: used so that two NPCs don't talk at once
|
|
float CTalkMonster::g_talkWaitTime = 0;
|
|
|
|
char gszallsentencenames[MAX_SENTENCE_VOXFILE][MAX_SENTENCE_NAME];
|
|
sentenceg rgsentenceg[MAX_SENTENCE_GROUPS];
|
|
|
|
// 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.
|
|
char grgszTextureName[MAX_TEXTURES][MAX_TEXTURENAME_LENGHT];
|
|
char grgchTextureType[MAX_TEXTURES];
|
|
|
|
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)),
|
|
};
|
|
|
|
LINK_ENTITY_TO_CLASS(ambient_generic, CAmbientGeneric, CCSAmbientGeneric)
|
|
IMPLEMENT_SAVERESTORE(CAmbientGeneric, CBaseEntity)
|
|
|
|
// -1 : "Default"
|
|
// 0 : "Everywhere"
|
|
// 200 : "Small Radius"
|
|
// 125 : "Medium Radius"
|
|
// 80 : "Large Radius"
|
|
void CAmbientGeneric::Spawn()
|
|
{
|
|
if (pev->spawnflags & SF_AMBIENT_SOUND_EVERYWHERE)
|
|
{
|
|
m_flAttenuation = ATTN_NONE;
|
|
}
|
|
else if (pev->spawnflags & SF_AMBIENT_SOUND_SMALLRADIUS)
|
|
{
|
|
m_flAttenuation = ATTN_IDLE;
|
|
}
|
|
else if (pev->spawnflags & SF_AMBIENT_SOUND_MEDIUMRADIUS)
|
|
{
|
|
m_flAttenuation = ATTN_STATIC;
|
|
}
|
|
else if (pev->spawnflags & SF_AMBIENT_SOUND_LARGERADIUS)
|
|
{
|
|
m_flAttenuation = ATTN_NORM;
|
|
}
|
|
else
|
|
{
|
|
// if the designer didn't set a sound attenuation, default to one.
|
|
m_flAttenuation = ATTN_STATIC;
|
|
}
|
|
|
|
char *szSoundFile = (char *)STRING(pev->message);
|
|
|
|
if (FStringNull(pev->message) || Q_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.1f;
|
|
SetThink(&CAmbientGeneric::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 (pev->spawnflags & SF_AMBIENT_SOUND_NOT_LOOPING)
|
|
m_fLooping = FALSE;
|
|
else
|
|
m_fLooping = TRUE;
|
|
|
|
Precache();
|
|
}
|
|
|
|
void CAmbientGeneric::Restart()
|
|
{
|
|
if (pev->spawnflags & SF_AMBIENT_SOUND_EVERYWHERE)
|
|
{
|
|
m_flAttenuation = ATTN_NONE;
|
|
}
|
|
else if (pev->spawnflags & SF_AMBIENT_SOUND_SMALLRADIUS)
|
|
{
|
|
m_flAttenuation = ATTN_IDLE;
|
|
}
|
|
else if (pev->spawnflags & SF_AMBIENT_SOUND_MEDIUMRADIUS)
|
|
{
|
|
m_flAttenuation = ATTN_STATIC;
|
|
}
|
|
else if (pev->spawnflags & SF_AMBIENT_SOUND_LARGERADIUS)
|
|
{
|
|
m_flAttenuation = ATTN_NORM;
|
|
}
|
|
else
|
|
{
|
|
// if the designer didn't set a sound attenuation, default to one.
|
|
m_flAttenuation = ATTN_STATIC;
|
|
}
|
|
|
|
char *szSoundFile = (char *)STRING(pev->message);
|
|
|
|
if (FStringNull(pev->message) || Q_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.1f;
|
|
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;
|
|
|
|
UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, 0, 0, SND_STOP, 0);
|
|
InitModulationParms();
|
|
pev->nextthink = gpGlobals->time + 0.1f;
|
|
|
|
if (!(pev->spawnflags & SF_AMBIENT_SOUND_NOT_LOOPING))
|
|
{
|
|
m_fLooping = TRUE;
|
|
m_fActive = TRUE;
|
|
}
|
|
else
|
|
m_fLooping = FALSE;
|
|
|
|
if (m_fActive)
|
|
{
|
|
UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, (m_dpv.vol * 0.01f), m_flAttenuation, 0, m_dpv.pitch);
|
|
}
|
|
}
|
|
|
|
void CAmbientGeneric::Precache()
|
|
{
|
|
char *szSoundFile = (char *)STRING(pev->message);
|
|
|
|
if (!FStringNull(pev->message) && Q_strlen(szSoundFile) > 1)
|
|
{
|
|
if (*szSoundFile != '!')
|
|
{
|
|
PRECACHE_SOUND(szSoundFile);
|
|
}
|
|
}
|
|
|
|
// init all dynamic modulation parms
|
|
InitModulationParms();
|
|
|
|
if (!(pev->spawnflags & SF_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.1f;
|
|
}
|
|
}
|
|
|
|
// 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()
|
|
{
|
|
char *szSoundFile = (char *)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)
|
|
{
|
|
// no ramps or lfo, stop thinking
|
|
return;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// pitch clamp
|
|
if (pitch > 255)
|
|
pitch = 255;
|
|
if (pitch < 1)
|
|
pitch = 1;
|
|
|
|
//pitch = Q_max(1, Q_min(255, pitch));
|
|
|
|
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;
|
|
}
|
|
|
|
// volume clamp
|
|
if (vol > 100)
|
|
vol = 100;
|
|
if (vol < 1)
|
|
vol = 1;
|
|
|
|
//vol = Q_max(1, Q_min(100, vol));
|
|
|
|
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 = Q_abs(m_dpv.lforate);
|
|
pos = 0;
|
|
}
|
|
else if (pos > 255)
|
|
{
|
|
pos = 255;
|
|
m_dpv.lfofrac = (255 << 8);
|
|
m_dpv.lforate = -Q_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;
|
|
|
|
// pitch clamp
|
|
if (pitch > 255)
|
|
pitch = 255;
|
|
if (pitch < 1)
|
|
pitch = 1;
|
|
|
|
//pitch = Q_max(1, Q_min(255, pitch));
|
|
|
|
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;
|
|
|
|
// volume clamp
|
|
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)
|
|
{
|
|
// don't send 'no pitch' !
|
|
pitch = PITCH_NORM + 1;
|
|
}
|
|
|
|
UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, (vol * 0.01), m_flAttenuation, flags, pitch);
|
|
}
|
|
|
|
// update ramps at 5hz
|
|
pev->nextthink = gpGlobals->time + 0.2f;
|
|
return;
|
|
}
|
|
|
|
// Init all ramp params in preparation to
|
|
// play a new sound
|
|
void CAmbientGeneric::InitModulationParms()
|
|
{
|
|
int pitchinc;
|
|
|
|
// 0 - 100
|
|
m_dpv.volrun = pev->health * 10;
|
|
|
|
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 <= MAX_SENTENCE_DPV_RESET)
|
|
{
|
|
// 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 = Q_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)
|
|
{
|
|
// must never send 'no pitch' as first pitch
|
|
// if we intend to pitch shift later!
|
|
m_dpv.pitch = PITCH_NORM + 1;
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
char *szSoundFile = (char *)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.
|
|
// Momentary buttons will pass down a float in here
|
|
if (useType == USE_SET && m_fActive)
|
|
{
|
|
fraction = value;
|
|
|
|
if (fraction > 1.0f)
|
|
fraction = 1.0f;
|
|
|
|
if (fraction < 0.0f)
|
|
fraction = 0.01f;
|
|
|
|
m_dpv.pitch = fraction * 255;
|
|
UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, 0, 0, SND_CHANGE_PITCH, m_dpv.pitch);
|
|
return;
|
|
}
|
|
|
|
// m_fActive is TRUE only if a looping sound is playing.
|
|
// turn sound off
|
|
if (m_fActive)
|
|
{
|
|
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.1f;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_fActive = FALSE;
|
|
|
|
// HACKHACK - this makes the code in Precache() work properly after a save/restore
|
|
pev->spawnflags |= SF_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.1f;
|
|
}
|
|
else
|
|
UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, 0, 0, SND_STOP, 0);
|
|
}
|
|
}
|
|
// turn sound on
|
|
else
|
|
{
|
|
// 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.1f;
|
|
}
|
|
}
|
|
|
|
// 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 = Q_atoi(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
|
|
// pitchrun
|
|
else if (FStrEq(pkvd->szKeyName, "pitch"))
|
|
{
|
|
m_dpv.pitchrun = Q_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 = Q_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 = Q_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 = Q_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 = Q_atoi(pkvd->szValue);
|
|
|
|
if (m_dpv.volstart > 10)
|
|
m_dpv.volstart = 10;
|
|
|
|
if (m_dpv.volstart < 0)
|
|
m_dpv.volstart = 0;
|
|
|
|
// 0 - 100
|
|
m_dpv.volstart *= 10;
|
|
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
|
|
// fadein
|
|
else if (FStrEq(pkvd->szKeyName, "fadein"))
|
|
{
|
|
m_dpv.fadein = Q_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 = Q_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 = Q_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 = Q_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 = Q_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 = Q_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 = Q_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);
|
|
}
|
|
}
|
|
|
|
TYPEDESCRIPTION CEnvSound::m_SaveData[] =
|
|
{
|
|
DEFINE_FIELD(CEnvSound, m_flRadius, FIELD_FLOAT),
|
|
DEFINE_FIELD(CEnvSound, m_flRoomtype, FIELD_FLOAT),
|
|
};
|
|
|
|
LINK_ENTITY_TO_CLASS(env_sound, CEnvSound, CCSEnvSound)
|
|
IMPLEMENT_SAVERESTORE(CEnvSound, CBaseEntity)
|
|
|
|
void CEnvSound::KeyValue(KeyValueData *pkvd)
|
|
{
|
|
if (FStrEq(pkvd->szKeyName, "radius"))
|
|
{
|
|
m_flRadius = Q_atof(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
if (FStrEq(pkvd->szKeyName, "roomtype"))
|
|
{
|
|
m_flRoomtype = Q_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<CCSEnvSound>((CEnvSound *)pev);
|
|
Vector vecSpot1 = pev->origin + pev->view_ofs;
|
|
Vector vecSpot2 = pevTarget->origin + pevTarget->view_ofs;
|
|
Vector vecRange;
|
|
real_t 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.0f)
|
|
{
|
|
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()
|
|
{
|
|
// 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());
|
|
|
|
if (FNullEnt(pentPlayer))
|
|
{
|
|
// no player in pvs of sound entity, slow it down
|
|
goto env_sound_Think_slow;
|
|
}
|
|
|
|
{
|
|
CBasePlayer *pPlayer = GetClassPtr<CCSPlayer>((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, nullptr, 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.25f;
|
|
return;
|
|
|
|
env_sound_Think_slow:
|
|
pev->nextthink = gpGlobals->time + 0.75f;
|
|
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);
|
|
}
|
|
|
|
// 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 > MAX_SENTENCE_LRU)
|
|
count = MAX_SENTENCE_LRU;
|
|
|
|
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)[64], int ipick, int freset)
|
|
{
|
|
char *szgroupname;
|
|
unsigned char count;
|
|
|
|
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;
|
|
|
|
Q_snprintf(szfound, sizeof(szfound), "!%s%d", szgroupname, ipick);
|
|
|
|
if (ipick >= count)
|
|
{
|
|
if (freset)
|
|
return 0; // reset at end of list
|
|
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)[64])
|
|
{
|
|
char *szgroupname;
|
|
unsigned char *plru;
|
|
unsigned char i;
|
|
unsigned char count;
|
|
unsigned char ipick = 0xFF;
|
|
BOOL 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)
|
|
{
|
|
Q_snprintf(szfound, sizeof(szfound), "!%s%d", szgroupname, ipick);
|
|
return ipick;
|
|
}
|
|
else
|
|
USENTENCEG_InitLRU(plru, count);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
// 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 != 0)
|
|
{
|
|
if (!Q_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;
|
|
|
|
ipick = USENTENCEG_Pick(isentenceg, name);
|
|
|
|
#ifndef REGAMEDLL_FIXES
|
|
if (ipick > 0 && name)
|
|
#else
|
|
if (ipick > 0 /*&& name[0] != '\0'*/)
|
|
#endif
|
|
{
|
|
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;
|
|
|
|
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] != '\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;
|
|
|
|
isentenceg = SENTENCEG_GetIndex(szgroupname);
|
|
if (isentenceg < 0)
|
|
return -1;
|
|
|
|
ipicknext = USENTENCEG_PickSequential(isentenceg, name, ipick, freset);
|
|
if (ipicknext >= 0 && name[0] != '\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.
|
|
NOXREF void SENTENCEG_Stop(edict_t *entity, int isentenceg, int ipick)
|
|
{
|
|
char buffer[64];
|
|
char sznum[12];
|
|
|
|
if (!fSentencesInit)
|
|
return;
|
|
|
|
if (isentenceg < 0 || ipick < 0)
|
|
return;
|
|
|
|
Q_strlcpy(buffer, "!");
|
|
Q_strlcat(buffer, rgsentenceg[isentenceg].szgroupname);
|
|
Q_snprintf(sznum, sizeof(sznum), "%d", ipick);
|
|
Q_strlcat(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;
|
|
|
|
Q_memset(gszallsentencenames, 0, sizeof(gszallsentencenames));
|
|
gcallsentences = 0;
|
|
|
|
Q_memset(rgsentenceg, 0, MAX_SENTENCE_GROUPS * sizeof(sentenceg));
|
|
Q_memset(buffer, 0, sizeof(buffer));
|
|
Q_memset(szgroup, 0, sizeof(szgroup));
|
|
|
|
isentencegs = -1;
|
|
|
|
int filePos = 0, fileSize;
|
|
byte *pMemFile = LOAD_FILE_FOR_ME("sound/sentences.txt", &fileSize);
|
|
if (!pMemFile)
|
|
return;
|
|
|
|
// for each line in the file...
|
|
while (memfgets(pMemFile, fileSize, filePos, buffer, sizeof(buffer) - 1))
|
|
{
|
|
// 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 > MAX_SENTENCE_VOXFILE)
|
|
{
|
|
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 (Q_strlen(pString) >= MAX_SENTENCE_NAME)
|
|
{
|
|
ALERT(at_warning, "Sentence %s longer than %d letters\n", pString, MAX_SENTENCE_NAME - 1);
|
|
}
|
|
|
|
Q_strlcpy(gszallsentencenames[gcallsentences++], pString);
|
|
|
|
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 (Q_strcmp(szgroup, &(buffer[i])) != 0)
|
|
{
|
|
// name doesn't match with prev name,
|
|
// copy name into group, init count to 1
|
|
isentencegs++;
|
|
if (isentencegs >= MAX_SENTENCE_GROUPS)
|
|
{
|
|
ALERT(at_error, "Too many sentence groups in sentences.txt!\n");
|
|
break;
|
|
}
|
|
|
|
Q_strlcpy(rgsentenceg[isentencegs].szgroupname, &(buffer[i]));
|
|
rgsentenceg[isentencegs].count = 1;
|
|
|
|
Q_strlcpy(szgroup, &(buffer[i]));
|
|
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
//name matches with previous, increment group count
|
|
if (isentencegs >= 0)
|
|
rgsentenceg[isentencegs].count++;
|
|
}
|
|
}
|
|
|
|
FREE_FILE(pMemFile);
|
|
|
|
fSentencesInit = TRUE;
|
|
|
|
// init lru lists
|
|
|
|
i = 0;
|
|
|
|
while (rgsentenceg[i].count && i < MAX_SENTENCE_GROUPS)
|
|
{
|
|
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)[32])
|
|
{
|
|
int i;
|
|
|
|
// this is a sentence name; lookup sentence number
|
|
// and give to engine as string.
|
|
for (i = 0; i < gcallsentences; i++)
|
|
{
|
|
if (!Q_stricmp(gszallsentencenames[i], sample + 1))
|
|
{
|
|
if (sentencenum)
|
|
{
|
|
Q_snprintf(sentencenum, sizeof(sentencenum), "!%d", i);
|
|
}
|
|
|
|
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.05f)
|
|
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.05f)
|
|
{
|
|
SENTENCEG_PlayRndI(entity, isentenceg, fvol, ATTN_NORM, 0, pitch);
|
|
}
|
|
}
|
|
|
|
// play a sentence, randomly selected from the passed in groupname
|
|
NOXREF 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.05f)
|
|
{
|
|
SENTENCEG_PlayRndSz(entity, groupname, fvol, ATTN_NORM, 0, pitch);
|
|
}
|
|
}
|
|
|
|
// open materials.txt, get size, alloc space,
|
|
// save in array. Only works first time called,
|
|
// ignored on subsequent calls.
|
|
char *memfgets(byte *pMemFile, int fileSize, int &filePos, char *pBuffer, int bufferSize)
|
|
{
|
|
// Bullet-proofing
|
|
if (!pMemFile || !pBuffer)
|
|
return nullptr;
|
|
|
|
if (filePos >= fileSize)
|
|
return nullptr;
|
|
|
|
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);
|
|
|
|
bool bStop = false;
|
|
|
|
// Stop at the next newline (inclusive) or end of buffer
|
|
while (i < last && !bStop)
|
|
{
|
|
if (pMemFile[i] == '\n')
|
|
bStop = true;
|
|
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
|
|
Q_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 nullptr;
|
|
}
|
|
|
|
void TEXTURETYPE_Init()
|
|
{
|
|
char buffer[512];
|
|
int i, j;
|
|
byte *pMemFile;
|
|
int fileSize, filePos = 0;
|
|
|
|
if (fTextureTypeInit)
|
|
return;
|
|
|
|
Q_memset(&(grgszTextureName[0][0]), 0, sizeof(grgszTextureName));
|
|
Q_memset(grgchTextureType, 0, sizeof(grgchTextureType));
|
|
|
|
gcTextures = 0;
|
|
Q_memset(buffer, 0, sizeof(buffer));
|
|
|
|
pMemFile = LOAD_FILE_FOR_ME("sound/materials.txt", &fileSize);
|
|
|
|
if (!pMemFile)
|
|
return;
|
|
|
|
// for each line in the file...
|
|
while (memfgets(pMemFile, fileSize, filePos, buffer, sizeof(buffer) - 1) && (gcTextures < MAX_TEXTURES))
|
|
{
|
|
// 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, MAX_TEXTURENAME_LENGHT - 1 + i);
|
|
buffer[j] = '\0';
|
|
|
|
Q_strlcpy(grgszTextureName[gcTextures++], &(buffer[i]));
|
|
}
|
|
|
|
FREE_FILE(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 (!Q_strnicmp(name, &(grgszTextureName[i][0]), MAX_TEXTURENAME_LENGHT - 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[MAX_TEXTURENAME_LENGHT];
|
|
const char *pTextureName;
|
|
float rgfl1[3];
|
|
float rgfl2[3];
|
|
char *rgsz[4];
|
|
int cnt;
|
|
float fattn = ATTN_NORM;
|
|
|
|
if (!g_pGameRules->PlayTextureSounds())
|
|
return 0.0;
|
|
|
|
CBaseEntity *pEntity = CBaseEntity::Instance(ptr->pHit);
|
|
|
|
chTextureType = '\0';
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
if (pEntity && pEntity->Classify() != CLASS_NONE && pEntity->Classify() != CLASS_MACHINE && pEntity->Classify() != CLASS_VEHICLE)
|
|
#else
|
|
if (pEntity && pEntity->Classify() != CLASS_NONE && pEntity->Classify() != CLASS_MACHINE)
|
|
#endif
|
|
{
|
|
// 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++;
|
|
|
|
// '}}'
|
|
Q_strlcpy(szBuffer, pTextureName);
|
|
|
|
// get texture type
|
|
chTextureType = TEXTURETYPE_Find(szBuffer);
|
|
}
|
|
}
|
|
|
|
switch (chTextureType)
|
|
{
|
|
default:
|
|
case CHAR_TEX_CONCRETE:
|
|
fvol = 0.9f;
|
|
fvolbar = 0.6f;
|
|
|
|
rgsz[0] = "player/pl_step1.wav";
|
|
rgsz[1] = "player/pl_step2.wav";
|
|
cnt = 2;
|
|
break;
|
|
case CHAR_TEX_METAL:
|
|
fvol = 0.9f;
|
|
fvolbar = 0.3f;
|
|
|
|
rgsz[0] = "player/pl_metal1.wav";
|
|
rgsz[1] = "player/pl_metal2.wav";
|
|
cnt = 2;
|
|
break;
|
|
case CHAR_TEX_DIRT:
|
|
fvol = 0.9f;
|
|
fvolbar = 0.1f;
|
|
|
|
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.5f;
|
|
fvolbar = 0.3f;
|
|
|
|
rgsz[0] = "player/pl_duct1.wav";
|
|
rgsz[1] = "player/pl_duct1.wav";
|
|
cnt = 2;
|
|
break;
|
|
case CHAR_TEX_GRATE:
|
|
fvol = 0.9f;
|
|
fvolbar = 0.5f;
|
|
|
|
rgsz[0] = "player/pl_grate1.wav";
|
|
rgsz[1] = "player/pl_grate4.wav";
|
|
cnt = 2;
|
|
break;
|
|
case CHAR_TEX_TILE:
|
|
fvol = 0.8f;
|
|
fvolbar = 0.2f;
|
|
|
|
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.9f;
|
|
fvolbar = 0.0f;
|
|
|
|
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_SNOW:
|
|
fvol = 0.7f;
|
|
fvolbar = 0.4f;
|
|
|
|
rgsz[0] = "player/pl_snow1.wav";
|
|
rgsz[1] = "player/pl_snow2.wav";
|
|
rgsz[2] = "player/pl_snow3.wav";
|
|
rgsz[3] = "player/pl_snow4.wav";
|
|
cnt = 4;
|
|
break;
|
|
case CHAR_TEX_WOOD:
|
|
fvol = 0.9f;
|
|
fvolbar = 0.2f;
|
|
|
|
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.8f;
|
|
fvolbar = 0.2f;
|
|
|
|
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.0f; // crowbar already makes this sound
|
|
|
|
fvol = 1.0f;
|
|
fvolbar = 0.2f;
|
|
|
|
rgsz[0] = "weapons/bullet_hit1.wav";
|
|
rgsz[1] = "weapons/bullet_hit2.wav";
|
|
fattn = 1.0f;
|
|
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.5f;
|
|
fvolbar /= 2.0f;
|
|
}
|
|
else if (chTextureType == CHAR_TEX_COMPUTER)
|
|
{
|
|
// play random spark if computer
|
|
if (ptr->flFraction != 1.0f && RANDOM_LONG(0, 1))
|
|
{
|
|
UTIL_Sparks(ptr->vecEndPos);
|
|
|
|
// random volume range
|
|
float flVolume = RANDOM_FLOAT(0.7 , 1.0);
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// play material hit sound
|
|
UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, rgsz[RANDOM_LONG(0, cnt - 1)], fvol, fattn, 0, 96 + RANDOM_LONG(0, 0xf));
|
|
return fvolbar;
|
|
}
|
|
|
|
TYPEDESCRIPTION CSpeaker::m_SaveData[] =
|
|
{
|
|
DEFINE_FIELD(CSpeaker, m_preset, FIELD_INTEGER),
|
|
};
|
|
|
|
LINK_ENTITY_TO_CLASS(speaker, CSpeaker, CCSSpeaker)
|
|
IMPLEMENT_SAVERESTORE(CSpeaker, CBaseEntity)
|
|
|
|
// ambient_generic - general-purpose user-defined static sound
|
|
void CSpeaker::Spawn()
|
|
{
|
|
char *szSoundFile = (char *)STRING(pev->message);
|
|
|
|
if (!m_preset && (FStringNull(pev->message) || Q_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.1f;
|
|
SetThink(&CSpeaker::SUB_Remove);
|
|
return;
|
|
}
|
|
|
|
pev->solid = SOLID_NOT;
|
|
pev->movetype = MOVETYPE_NONE;
|
|
|
|
SetThink(&CSpeaker::SpeakerThink);
|
|
pev->nextthink = 0.0f;
|
|
|
|
// allow on/off switching via 'use' function.
|
|
SetUse(&CSpeaker::ToggleUse);
|
|
|
|
Precache();
|
|
}
|
|
|
|
void CSpeaker::Precache()
|
|
{
|
|
if (!(pev->spawnflags & SF_SPEAKER_START_SILENT))
|
|
{
|
|
// set first announcement time for random n second
|
|
pev->nextthink = gpGlobals->time + RANDOM_FLOAT(5, 15);
|
|
}
|
|
}
|
|
|
|
void CSpeaker::SpeakerThink()
|
|
{
|
|
char *szSoundFile = nullptr;
|
|
float flvolume = pev->health * 0.1f;
|
|
float flattenuation = 0.3f;
|
|
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 = (char *)STRING(pev->message);
|
|
|
|
#ifdef REGAMEDLL_FIXES
|
|
if (szSoundFile == nullptr)
|
|
{
|
|
// if is null - return;
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
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.0f;
|
|
}
|
|
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(MIN_ANNOUNCE_MINS * 60.0f, MAX_ANNOUNCE_MINS * 60.0f);
|
|
|
|
// time delay until it's ok to speak: used so that two NPCs don't talk at once
|
|
CTalkMonster::g_talkWaitTime = gpGlobals->time + 5.0f;
|
|
}
|
|
|
|
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)
|
|
{
|
|
bool bActive = (pev->nextthink > 0.0f);
|
|
|
|
// bActive 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 ((bActive && useType == USE_ON) || (!bActive && useType == USE_OFF))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (useType == USE_ON)
|
|
{
|
|
// turn on announcements
|
|
pev->nextthink = gpGlobals->time + 0.1f;
|
|
return;
|
|
}
|
|
|
|
if (useType == USE_OFF)
|
|
{
|
|
// turn off announcements
|
|
pev->nextthink = 0.0f;
|
|
return;
|
|
|
|
}
|
|
|
|
// Toggle announcements
|
|
if (bActive)
|
|
{
|
|
// turn off announcements
|
|
pev->nextthink = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
// turn on announcements
|
|
pev->nextthink = gpGlobals->time + 0.1f;
|
|
}
|
|
}
|
|
|
|
// KeyValue - load keyvalue pairs into member data
|
|
// NOTE: called BEFORE spawn!
|
|
void CSpeaker::KeyValue(KeyValueData *pkvd)
|
|
{
|
|
// preset
|
|
if (FStrEq(pkvd->szKeyName, "preset"))
|
|
{
|
|
m_preset = Q_atoi(pkvd->szValue);
|
|
pkvd->fHandled = TRUE;
|
|
}
|
|
else
|
|
{
|
|
CBaseEntity::KeyValue(pkvd);
|
|
}
|
|
}
|