916 lines
23 KiB
C++
916 lines
23 KiB
C++
/***
|
|
*
|
|
* Copyright (c) 1996-2001, 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 "cmbase.h"
|
|
#include "cmbasemonster.h"
|
|
#include "weapons.h"
|
|
#include "cmtalkmonster.h"
|
|
#include "pm_materials.h"
|
|
|
|
|
|
static char *memfgets( byte *pMemFile, int fileSize, int &filePos, char *pBuffer, int bufferSize );
|
|
|
|
|
|
// ==================== GENERIC AMBIENT SOUND ======================================
|
|
|
|
// runtime pitch shift and volume fadein/out structure
|
|
|
|
// NOTE: IF YOU CHANGE THIS STRUCT YOU MUST CHANGE THE SAVE/RESTORE VERSION NUMBER
|
|
// SEE BELOW (in the typedescription for the class)
|
|
typedef struct dynpitchvol
|
|
{
|
|
// NOTE: do not change the order of these parameters
|
|
// NOTE: unless you also change order of rgdpvpreset array elements!
|
|
int preset;
|
|
|
|
int pitchrun; // pitch shift % when sound is running 0 - 255
|
|
int pitchstart; // pitch shift % when sound stops or starts 0 - 255
|
|
int spinup; // spinup time 0 - 100
|
|
int spindown; // spindown time 0 - 100
|
|
|
|
int volrun; // volume change % when sound is running 0 - 10
|
|
int volstart; // volume change % when sound stops or starts 0 - 10
|
|
int fadein; // volume fade in time 0 - 100
|
|
int fadeout; // volume fade out time 0 - 100
|
|
|
|
// Low Frequency Oscillator
|
|
int lfotype; // 0) off 1) square 2) triangle 3) random
|
|
int lforate; // 0 - 1000, how fast lfo osciallates
|
|
|
|
int lfomodpitch; // 0-100 mod of current pitch. 0 is off.
|
|
int lfomodvol; // 0-100 mod of current volume. 0 is off.
|
|
|
|
int cspinup; // each trigger hit increments counter and spinup pitch
|
|
|
|
|
|
int cspincount;
|
|
|
|
int pitch;
|
|
int spinupsav;
|
|
int spindownsav;
|
|
int pitchfrac;
|
|
|
|
int vol;
|
|
int fadeinsav;
|
|
int fadeoutsav;
|
|
int volfrac;
|
|
|
|
int lfofrac;
|
|
int lfomult;
|
|
|
|
|
|
} dynpitchvol_t;
|
|
|
|
#define CDPVPRESETMAX 27
|
|
|
|
// presets for runtime pitch and vol modulation of ambient sounds
|
|
|
|
/*jlb
|
|
dynpitchvol_t rgdpvpreset[CDPVPRESETMAX] =
|
|
{
|
|
// pitch pstart spinup spindwn volrun volstrt fadein fadeout lfotype lforate modptch modvol cspnup
|
|
{1, 255, 75, 95, 95, 10, 1, 50, 95, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0},
|
|
{2, 255, 85, 70, 88, 10, 1, 20, 88, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0},
|
|
{3, 255, 100, 50, 75, 10, 1, 10, 75, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0},
|
|
{4, 100, 100, 0, 0, 10, 1, 90, 90, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0},
|
|
{5, 100, 100, 0, 0, 10, 1, 80, 80, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0},
|
|
{6, 100, 100, 0, 0, 10, 1, 50, 70, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0},
|
|
{7, 100, 100, 0, 0, 5, 1, 40, 50, 1, 50, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0},
|
|
{8, 100, 100, 0, 0, 5, 1, 40, 50, 1, 150, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0},
|
|
{9, 100, 100, 0, 0, 5, 1, 40, 50, 1, 750, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0},
|
|
{10,128, 100, 50, 75, 10, 1, 30, 40, 2, 8, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0},
|
|
{11,128, 100, 50, 75, 10, 1, 30, 40, 2, 25, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0},
|
|
{12,128, 100, 50, 75, 10, 1, 30, 40, 2, 70, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0},
|
|
{13,50, 50, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0},
|
|
{14,70, 70, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0},
|
|
{15,90, 90, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0},
|
|
{16,120, 120, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0},
|
|
{17,180, 180, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0},
|
|
{18,255, 255, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0},
|
|
{19,200, 75, 90, 90, 10, 1, 50, 90, 2, 100, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0},
|
|
{20,255, 75, 97, 90, 10, 1, 50, 90, 1, 40, 50, 0, 0, 0,0,0,0,0,0,0,0,0,0},
|
|
{21,100, 100, 0, 0, 10, 1, 30, 50, 3, 15, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0},
|
|
{22,160, 160, 0, 0, 10, 1, 50, 50, 3, 500, 25, 0, 0, 0,0,0,0,0,0,0,0,0,0},
|
|
{23,255, 75, 88, 0, 10, 1, 40, 0, 0, 0, 0, 0, 5, 0,0,0,0,0,0,0,0,0,0},
|
|
{24,200, 20, 95, 70, 10, 1, 70, 70, 3, 20, 50, 0, 0, 0,0,0,0,0,0,0,0,0,0},
|
|
{25,180, 100, 50, 60, 10, 1, 40, 60, 2, 90, 100, 100, 0, 0,0,0,0,0,0,0,0,0,0},
|
|
{26,60, 60, 0, 0, 10, 1, 40, 70, 3, 80, 20, 50, 0, 0,0,0,0,0,0,0,0,0,0},
|
|
{27,128, 90, 10, 10, 10, 1, 20, 40, 1, 5, 10, 20, 0, 0,0,0,0,0,0,0,0,0,0}
|
|
};
|
|
jlb*/
|
|
|
|
|
|
// ==================== SENTENCE GROUPS, UTILITY FUNCTIONS ======================================
|
|
|
|
#define CSENTENCE_LRU_MAX 32 // max number of elements per sentence group
|
|
|
|
// group of related sentences
|
|
|
|
typedef struct sentenceg
|
|
{
|
|
char szgroupname[CBSENTENCENAME_MAX];
|
|
int count;
|
|
unsigned char rgblru[CSENTENCE_LRU_MAX];
|
|
|
|
} SENTENCEG;
|
|
|
|
#define CSENTENCEG_MAX 200 // max number of sentence groups
|
|
// globals
|
|
|
|
SENTENCEG rgsentenceg[CSENTENCEG_MAX];
|
|
int fSentencesInit = FALSE;
|
|
|
|
char gszallsentencenames[CVOXFILESENTENCEMAX][CBSENTENCENAME_MAX];
|
|
int gcallsentences = 0;
|
|
|
|
// randomize list of sentence name indices
|
|
|
|
void USENTENCEG_InitLRU(unsigned char *plru, int count)
|
|
{
|
|
int i, j, k;
|
|
unsigned char temp;
|
|
|
|
if (!fSentencesInit)
|
|
return;
|
|
|
|
if (count > CSENTENCE_LRU_MAX)
|
|
count = CSENTENCE_LRU_MAX;
|
|
|
|
for (i = 0; i < count; i++)
|
|
plru[i] = (unsigned char) i;
|
|
|
|
// randomize array
|
|
for (i = 0; i < (count * 4); i++)
|
|
{
|
|
j = RANDOM_LONG(0,count-1);
|
|
k = RANDOM_LONG(0,count-1);
|
|
temp = plru[j];
|
|
plru[j] = plru[k];
|
|
plru[k] = temp;
|
|
}
|
|
}
|
|
|
|
// ignore lru. pick next sentence from sentence group. Go in order until we hit the last sentence,
|
|
// then repeat list if freset is true. If freset is false, then repeat last sentence.
|
|
// ipick is passed in as the requested sentence ordinal.
|
|
// ipick 'next' is returned.
|
|
// return of -1 indicates an error.
|
|
|
|
int USENTENCEG_PickSequential(int isentenceg, char *szfound, int ipick, int freset)
|
|
{
|
|
char *szgroupname;
|
|
unsigned char count;
|
|
char sznum[8];
|
|
|
|
if (!fSentencesInit)
|
|
return -1;
|
|
|
|
if (isentenceg < 0)
|
|
return -1;
|
|
|
|
szgroupname = rgsentenceg[isentenceg].szgroupname;
|
|
count = rgsentenceg[isentenceg].count;
|
|
|
|
if (count == 0)
|
|
return -1;
|
|
|
|
if (ipick >= count)
|
|
ipick = count-1;
|
|
|
|
strcpy(szfound, "!");
|
|
strcat(szfound, szgroupname);
|
|
sprintf(sznum, "%d", ipick);
|
|
strcat(szfound, sznum);
|
|
|
|
if (ipick >= count)
|
|
{
|
|
if (freset)
|
|
// reset at end of list
|
|
return 0;
|
|
else
|
|
return count;
|
|
}
|
|
|
|
return ipick + 1;
|
|
}
|
|
|
|
|
|
|
|
// pick a random sentence from rootname0 to rootnameX.
|
|
// picks from the rgsentenceg[isentenceg] least
|
|
// recently used, modifies lru array. returns the sentencename.
|
|
// note, lru must be seeded with 0-n randomized sentence numbers, with the
|
|
// rest of the lru filled with -1. The first integer in the lru is
|
|
// actually the size of the list. Returns ipick, the ordinal
|
|
// of the picked sentence within the group.
|
|
|
|
int USENTENCEG_Pick(int isentenceg, char *szfound)
|
|
{
|
|
char *szgroupname;
|
|
unsigned char *plru;
|
|
unsigned char i;
|
|
unsigned char count;
|
|
char sznum[8];
|
|
unsigned char ipick;
|
|
int ffound = FALSE;
|
|
|
|
if (!fSentencesInit)
|
|
return -1;
|
|
|
|
if (isentenceg < 0)
|
|
return -1;
|
|
|
|
szgroupname = rgsentenceg[isentenceg].szgroupname;
|
|
count = rgsentenceg[isentenceg].count;
|
|
plru = rgsentenceg[isentenceg].rgblru;
|
|
|
|
while (!ffound)
|
|
{
|
|
for (i = 0; i < count; i++)
|
|
if (plru[i] != 0xFF)
|
|
{
|
|
ipick = plru[i];
|
|
plru[i] = 0xFF;
|
|
ffound = TRUE;
|
|
break;
|
|
}
|
|
|
|
if (!ffound)
|
|
USENTENCEG_InitLRU(plru, count);
|
|
else
|
|
{
|
|
strcpy(szfound, "!");
|
|
strcat(szfound, szgroupname);
|
|
sprintf(sznum, "%d", ipick);
|
|
strcat(szfound, sznum);
|
|
return ipick;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// ===================== SENTENCE GROUPS, MAIN ROUTINES ========================
|
|
|
|
// Given sentence group rootname (name without number suffix),
|
|
// get sentence group index (isentenceg). Returns -1 if no such name.
|
|
|
|
int SENTENCEG_GetIndex(const char *szgroupname)
|
|
{
|
|
int i;
|
|
|
|
if (!fSentencesInit || !szgroupname)
|
|
return -1;
|
|
|
|
// search rgsentenceg for match on szgroupname
|
|
|
|
i = 0;
|
|
while (rgsentenceg[i].count)
|
|
{
|
|
if (!strcmp(szgroupname, rgsentenceg[i].szgroupname))
|
|
return i;
|
|
i++;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
// given sentence group index, play random sentence for given entity.
|
|
// returns ipick - which sentence was picked to
|
|
// play from the group. Ipick is only needed if you plan on stopping
|
|
// the sound before playback is done (see SENTENCEG_Stop).
|
|
|
|
int SENTENCEG_PlayRndI(edict_t *entity, int isentenceg,
|
|
float volume, float attenuation, int flags, int pitch)
|
|
{
|
|
char name[64];
|
|
int ipick;
|
|
|
|
if (!fSentencesInit)
|
|
return -1;
|
|
|
|
name[0] = 0;
|
|
|
|
ipick = USENTENCEG_Pick(isentenceg, name);
|
|
if (ipick > 0 && name)
|
|
EMIT_SOUND_DYN(entity, CHAN_VOICE, name, volume, attenuation, flags, pitch);
|
|
return ipick;
|
|
}
|
|
|
|
// same as above, but takes sentence group name instead of index
|
|
|
|
int SENTENCEG_PlayRndSz(edict_t *entity, const char *szgroupname,
|
|
float volume, float attenuation, int flags, int pitch)
|
|
{
|
|
char name[64];
|
|
int ipick;
|
|
int isentenceg;
|
|
|
|
if (!fSentencesInit)
|
|
return -1;
|
|
|
|
name[0] = 0;
|
|
|
|
isentenceg = SENTENCEG_GetIndex(szgroupname);
|
|
if (isentenceg < 0)
|
|
{
|
|
ALERT( at_console, "No such sentence group %s\n", szgroupname );
|
|
return -1;
|
|
}
|
|
|
|
ipick = USENTENCEG_Pick(isentenceg, name);
|
|
if (ipick >= 0 && name[0])
|
|
EMIT_SOUND_DYN(entity, CHAN_VOICE, name, volume, attenuation, flags, pitch);
|
|
|
|
return ipick;
|
|
}
|
|
|
|
// play sentences in sequential order from sentence group. Reset after last sentence.
|
|
|
|
int SENTENCEG_PlaySequentialSz(edict_t *entity, const char *szgroupname,
|
|
float volume, float attenuation, int flags, int pitch, int ipick, int freset)
|
|
{
|
|
char name[64];
|
|
int ipicknext;
|
|
int isentenceg;
|
|
|
|
if (!fSentencesInit)
|
|
return -1;
|
|
|
|
name[0] = 0;
|
|
|
|
isentenceg = SENTENCEG_GetIndex(szgroupname);
|
|
if (isentenceg < 0)
|
|
return -1;
|
|
|
|
ipicknext = USENTENCEG_PickSequential(isentenceg, name, ipick, freset);
|
|
if (ipicknext >= 0 && name[0])
|
|
EMIT_SOUND_DYN(entity, CHAN_VOICE, name, volume, attenuation, flags, pitch);
|
|
return ipicknext;
|
|
}
|
|
|
|
|
|
// for this entity, for the given sentence within the sentence group, stop
|
|
// the sentence.
|
|
|
|
void SENTENCEG_Stop(edict_t *entity, int isentenceg, int ipick)
|
|
{
|
|
char buffer[64];
|
|
char sznum[8];
|
|
|
|
if (!fSentencesInit)
|
|
return;
|
|
|
|
if (isentenceg < 0 || ipick < 0)
|
|
return;
|
|
|
|
strcpy(buffer, "!");
|
|
strcat(buffer, rgsentenceg[isentenceg].szgroupname);
|
|
sprintf(sznum, "%d", ipick);
|
|
strcat(buffer, sznum);
|
|
|
|
STOP_SOUND(entity, CHAN_VOICE, buffer);
|
|
}
|
|
|
|
// open sentences.txt, scan for groups, build rgsentenceg
|
|
// Should be called from world spawn, only works on the
|
|
// first call and is ignored subsequently.
|
|
|
|
void SENTENCEG_Init()
|
|
{
|
|
char buffer[512];
|
|
char szgroup[64];
|
|
int i, j;
|
|
int isentencegs;
|
|
|
|
if (fSentencesInit)
|
|
return;
|
|
|
|
memset(gszallsentencenames, 0, CVOXFILESENTENCEMAX * CBSENTENCENAME_MAX);
|
|
gcallsentences = 0;
|
|
|
|
memset(rgsentenceg, 0, CSENTENCEG_MAX * sizeof(SENTENCEG));
|
|
memset(buffer, 0, 512);
|
|
memset(szgroup, 0, 64);
|
|
isentencegs = -1;
|
|
|
|
|
|
int filePos = 0, fileSize;
|
|
byte *pMemFile = g_engfuncs.pfnLoadFileForMe( "sound/sentences.txt", &fileSize );
|
|
if ( !pMemFile )
|
|
return;
|
|
|
|
// for each line in the file...
|
|
while ( memfgets(pMemFile, fileSize, filePos, buffer, 511) != NULL )
|
|
{
|
|
// skip whitespace
|
|
i = 0;
|
|
while(buffer[i] && buffer[i] == ' ')
|
|
i++;
|
|
|
|
if (!buffer[i])
|
|
continue;
|
|
|
|
if (buffer[i] == '/' || !isalpha(buffer[i]))
|
|
continue;
|
|
|
|
// get sentence name
|
|
j = i;
|
|
while (buffer[j] && buffer[j] != ' ')
|
|
j++;
|
|
|
|
if (!buffer[j])
|
|
continue;
|
|
|
|
if (gcallsentences > CVOXFILESENTENCEMAX)
|
|
{
|
|
ALERT (at_error, "Too many sentences in sentences.txt!\n");
|
|
break;
|
|
}
|
|
|
|
// null-terminate name and save in sentences array
|
|
buffer[j] = 0;
|
|
const char *pString = buffer + i;
|
|
|
|
if ( strlen( pString ) >= CBSENTENCENAME_MAX )
|
|
ALERT( at_warning, "Sentence %s longer than %d letters\n", pString, CBSENTENCENAME_MAX-1 );
|
|
|
|
strcpy( gszallsentencenames[gcallsentences++], pString );
|
|
|
|
j--;
|
|
if (j <= i)
|
|
continue;
|
|
if (!isdigit(buffer[j]))
|
|
continue;
|
|
|
|
// cut out suffix numbers
|
|
while (j > i && isdigit(buffer[j]))
|
|
j--;
|
|
|
|
if (j <= i)
|
|
continue;
|
|
|
|
buffer[j+1] = 0;
|
|
|
|
// if new name doesn't match previous group name,
|
|
// make a new group.
|
|
|
|
if (strcmp(szgroup, &(buffer[i])))
|
|
{
|
|
// name doesn't match with prev name,
|
|
// copy name into group, init count to 1
|
|
isentencegs++;
|
|
if (isentencegs >= CSENTENCEG_MAX)
|
|
{
|
|
ALERT (at_error, "Too many sentence groups in sentences.txt!\n");
|
|
break;
|
|
}
|
|
|
|
strcpy(rgsentenceg[isentencegs].szgroupname, &(buffer[i]));
|
|
rgsentenceg[isentencegs].count = 1;
|
|
|
|
strcpy(szgroup, &(buffer[i]));
|
|
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
//name matches with previous, increment group count
|
|
if (isentencegs >= 0)
|
|
rgsentenceg[isentencegs].count++;
|
|
}
|
|
}
|
|
|
|
g_engfuncs.pfnFreeFile( pMemFile );
|
|
|
|
fSentencesInit = TRUE;
|
|
|
|
// init lru lists
|
|
|
|
i = 0;
|
|
|
|
while (rgsentenceg[i].count && i < CSENTENCEG_MAX)
|
|
{
|
|
USENTENCEG_InitLRU(&(rgsentenceg[i].rgblru[0]), rgsentenceg[i].count);
|
|
i++;
|
|
}
|
|
|
|
}
|
|
|
|
// convert sentence (sample) name to !sentencenum, return !sentencenum
|
|
|
|
int SENTENCEG_Lookup(const char *sample, char *sentencenum)
|
|
{
|
|
char sznum[8];
|
|
|
|
int i;
|
|
// this is a sentence name; lookup sentence number
|
|
// and give to engine as string.
|
|
for (i = 0; i < gcallsentences; i++)
|
|
if (!stricmp(gszallsentencenames[i], sample+1))
|
|
{
|
|
if (sentencenum)
|
|
{
|
|
strcpy(sentencenum, "!");
|
|
sprintf(sznum, "%d", i);
|
|
strcat(sentencenum, sznum);
|
|
}
|
|
return i;
|
|
}
|
|
// sentence name not found!
|
|
return -1;
|
|
}
|
|
|
|
void EMIT_SOUND_DYN(edict_t *entity, int channel, const char *sample, float volume, float attenuation,
|
|
int flags, int pitch)
|
|
{
|
|
if (sample && *sample == '!')
|
|
{
|
|
char name[32];
|
|
if (SENTENCEG_Lookup(sample, name) >= 0)
|
|
EMIT_SOUND_DYN2(entity, channel, name, volume, attenuation, flags, pitch);
|
|
else
|
|
ALERT( at_aiconsole, "Unable to find %s in sentences.txt\n", sample );
|
|
}
|
|
else
|
|
EMIT_SOUND_DYN2(entity, channel, sample, volume, attenuation, flags, pitch);
|
|
}
|
|
|
|
// play a specific sentence over the HEV suit speaker - just pass player entity, and !sentencename
|
|
|
|
void EMIT_SOUND_SUIT(edict_t *entity, const char *sample)
|
|
{
|
|
float fvol;
|
|
int pitch = PITCH_NORM;
|
|
|
|
fvol = CVAR_GET_FLOAT("suitvolume");
|
|
if (RANDOM_LONG(0,1))
|
|
pitch = RANDOM_LONG(0,6) + 98;
|
|
|
|
if (fvol > 0.05)
|
|
EMIT_SOUND_DYN(entity, CHAN_STATIC, sample, fvol, ATTN_NORM, 0, pitch);
|
|
}
|
|
|
|
// play a sentence, randomly selected from the passed in group id, over the HEV suit speaker
|
|
|
|
void EMIT_GROUPID_SUIT(edict_t *entity, int isentenceg)
|
|
{
|
|
float fvol;
|
|
int pitch = PITCH_NORM;
|
|
|
|
fvol = CVAR_GET_FLOAT("suitvolume");
|
|
if (RANDOM_LONG(0,1))
|
|
pitch = RANDOM_LONG(0,6) + 98;
|
|
|
|
if (fvol > 0.05)
|
|
SENTENCEG_PlayRndI(entity, isentenceg, fvol, ATTN_NORM, 0, pitch);
|
|
}
|
|
|
|
// play a sentence, randomly selected from the passed in groupname
|
|
|
|
void EMIT_GROUPNAME_SUIT(edict_t *entity, const char *groupname)
|
|
{
|
|
float fvol;
|
|
int pitch = PITCH_NORM;
|
|
|
|
fvol = CVAR_GET_FLOAT("suitvolume");
|
|
if (RANDOM_LONG(0,1))
|
|
pitch = RANDOM_LONG(0,6) + 98;
|
|
|
|
if (fvol > 0.05)
|
|
SENTENCEG_PlayRndSz(entity, groupname, fvol, ATTN_NORM, 0, pitch);
|
|
}
|
|
|
|
// ===================== MATERIAL TYPE DETECTION, MAIN ROUTINES ========================
|
|
//
|
|
// Used to detect the texture the player is standing on, map the
|
|
// texture name to a material type. Play footstep sound based
|
|
// on material type.
|
|
|
|
int fTextureTypeInit = FALSE;
|
|
|
|
#define CTEXTURESMAX 512 // max number of textures loaded
|
|
|
|
//jlbint gcTextures = 0;
|
|
//jlbchar grgszTextureName[CTEXTURESMAX][CBTEXTURENAMEMAX]; // texture names
|
|
//jlbchar grgchTextureType[CTEXTURESMAX]; // parallel array of texture types
|
|
|
|
// open materials.txt, get size, alloc space,
|
|
// save in array. Only works first time called,
|
|
// ignored on subsequent calls.
|
|
|
|
static char *memfgets( byte *pMemFile, int fileSize, int &filePos, char *pBuffer, int bufferSize )
|
|
{
|
|
// Bullet-proofing
|
|
if ( !pMemFile || !pBuffer )
|
|
return NULL;
|
|
|
|
if ( filePos >= fileSize )
|
|
return NULL;
|
|
|
|
int i = filePos;
|
|
int last = fileSize;
|
|
|
|
// fgets always NULL terminates, so only read bufferSize-1 characters
|
|
if ( last - filePos > (bufferSize-1) )
|
|
last = filePos + (bufferSize-1);
|
|
|
|
int stop = 0;
|
|
|
|
// Stop at the next newline (inclusive) or end of buffer
|
|
while ( i < last && !stop )
|
|
{
|
|
if ( pMemFile[i] == '\n' )
|
|
stop = 1;
|
|
i++;
|
|
}
|
|
|
|
|
|
// If we actually advanced the pointer, copy it over
|
|
if ( i != filePos )
|
|
{
|
|
// We read in size bytes
|
|
int size = i - filePos;
|
|
// copy it out
|
|
memcpy( pBuffer, pMemFile + filePos, sizeof(byte)*size );
|
|
|
|
// If the buffer isn't full, terminate (this is always true)
|
|
if ( size < bufferSize )
|
|
pBuffer[size] = 0;
|
|
|
|
// Update file pointer
|
|
filePos = i;
|
|
return pBuffer;
|
|
}
|
|
|
|
// No data read, bail
|
|
return NULL;
|
|
}
|
|
|
|
|
|
void TEXTURETYPE_Init()
|
|
{
|
|
/*jlb
|
|
char buffer[512];
|
|
int i, j;
|
|
byte *pMemFile;
|
|
int fileSize, filePos;
|
|
|
|
if (fTextureTypeInit)
|
|
return;
|
|
|
|
memset(&(grgszTextureName[0][0]), 0, CTEXTURESMAX * CBTEXTURENAMEMAX);
|
|
memset(grgchTextureType, 0, CTEXTURESMAX);
|
|
|
|
gcTextures = 0;
|
|
memset(buffer, 0, 512);
|
|
|
|
pMemFile = g_engfuncs.pfnLoadFileForMe( "sound/materials.txt", &fileSize );
|
|
if ( !pMemFile )
|
|
return;
|
|
|
|
// for each line in the file...
|
|
while (memfgets(pMemFile, fileSize, filePos, buffer, 511) != NULL && (gcTextures < CTEXTURESMAX))
|
|
{
|
|
// skip whitespace
|
|
i = 0;
|
|
while(buffer[i] && isspace(buffer[i]))
|
|
i++;
|
|
|
|
if (!buffer[i])
|
|
continue;
|
|
|
|
// skip comment lines
|
|
if (buffer[i] == '/' || !isalpha(buffer[i]))
|
|
continue;
|
|
|
|
// get texture type
|
|
grgchTextureType[gcTextures] = toupper(buffer[i++]);
|
|
|
|
// skip whitespace
|
|
while(buffer[i] && isspace(buffer[i]))
|
|
i++;
|
|
|
|
if (!buffer[i])
|
|
continue;
|
|
|
|
// get sentence name
|
|
j = i;
|
|
while (buffer[j] && !isspace(buffer[j]))
|
|
j++;
|
|
|
|
if (!buffer[j])
|
|
continue;
|
|
|
|
// null-terminate name and save in sentences array
|
|
j = min (j, CBTEXTURENAMEMAX-1+i);
|
|
buffer[j] = 0;
|
|
strcpy(&(grgszTextureName[gcTextures++][0]), &(buffer[i]));
|
|
}
|
|
|
|
g_engfuncs.pfnFreeFile( pMemFile );
|
|
|
|
fTextureTypeInit = TRUE;
|
|
jlb*/
|
|
}
|
|
|
|
// 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
|
|
/*jlb
|
|
for (int i = 0; i < gcTextures; i++)
|
|
{
|
|
if (!strnicmp(name, &(grgszTextureName[i][0]), CBTEXTURENAMEMAX-1))
|
|
return (grgchTextureType[i]);
|
|
}
|
|
jlb*/
|
|
return CHAR_TEX_CONCRETE;
|
|
}
|
|
|
|
// play a strike sound based on the texture that was hit by the attack traceline. VecSrc/VecEnd are the
|
|
// original traceline endpoints used by the attacker, iBulletType is the type of bullet that hit the texture.
|
|
// returns volume of strike instrument (crowbar) to play
|
|
|
|
float TEXTURETYPE_PlaySound(TraceResult *ptr, Vector vecSrc, Vector vecEnd, int iBulletType)
|
|
{
|
|
// hit the world, try to play sound based on texture material type
|
|
|
|
char chTextureType;
|
|
float fvol;
|
|
float fvolbar;
|
|
char szbuffer[64];
|
|
const char *pTextureName;
|
|
float rgfl1[3];
|
|
float rgfl2[3];
|
|
char *rgsz[4];
|
|
int cnt;
|
|
float fattn = ATTN_NORM;
|
|
|
|
CMBaseEntity *pEntity = CMBaseEntity::Instance(ptr->pHit);
|
|
|
|
chTextureType = 0;
|
|
|
|
if (pEntity && pEntity->Classify() != CLASS_NONE && pEntity->Classify() != CLASS_MACHINE)
|
|
// hit body
|
|
chTextureType = CHAR_TEX_FLESH;
|
|
else
|
|
{
|
|
// hit world
|
|
|
|
// find texture under strike, get material type
|
|
|
|
// copy trace vector into array for trace_texture
|
|
|
|
vecSrc.CopyToArray(rgfl1);
|
|
vecEnd.CopyToArray(rgfl2);
|
|
|
|
// get texture from entity or world (world is ent(0))
|
|
if (pEntity)
|
|
pTextureName = TRACE_TEXTURE( ENT(pEntity->pev), rgfl1, rgfl2 );
|
|
else
|
|
pTextureName = TRACE_TEXTURE( ENT(0), rgfl1, rgfl2 );
|
|
|
|
if ( pTextureName )
|
|
{
|
|
// strip leading '-0' or '+0~' or '{' or '!'
|
|
if (*pTextureName == '-' || *pTextureName == '+')
|
|
pTextureName += 2;
|
|
|
|
if (*pTextureName == '{' || *pTextureName == '!' || *pTextureName == '~' || *pTextureName == ' ')
|
|
pTextureName++;
|
|
// '}}'
|
|
strcpy(szbuffer, pTextureName);
|
|
szbuffer[CBTEXTURENAMEMAX - 1] = 0;
|
|
|
|
// ALERT ( at_console, "texture hit: %s\n", szbuffer);
|
|
|
|
// get texture type
|
|
chTextureType = TEXTURETYPE_Find(szbuffer);
|
|
}
|
|
}
|
|
|
|
switch (chTextureType)
|
|
{
|
|
default:
|
|
case CHAR_TEX_CONCRETE: fvol = 0.9; fvolbar = 0.6;
|
|
rgsz[0] = "player/pl_step1.wav";
|
|
rgsz[1] = "player/pl_step2.wav";
|
|
cnt = 2;
|
|
break;
|
|
case CHAR_TEX_METAL: fvol = 0.9; fvolbar = 0.3;
|
|
rgsz[0] = "player/pl_metal1.wav";
|
|
rgsz[1] = "player/pl_metal2.wav";
|
|
cnt = 2;
|
|
break;
|
|
case CHAR_TEX_DIRT: fvol = 0.9; fvolbar = 0.1;
|
|
rgsz[0] = "player/pl_dirt1.wav";
|
|
rgsz[1] = "player/pl_dirt2.wav";
|
|
rgsz[2] = "player/pl_dirt3.wav";
|
|
cnt = 3;
|
|
break;
|
|
case CHAR_TEX_VENT: fvol = 0.5; fvolbar = 0.3;
|
|
rgsz[0] = "player/pl_duct1.wav";
|
|
rgsz[1] = "player/pl_duct1.wav";
|
|
cnt = 2;
|
|
break;
|
|
case CHAR_TEX_GRATE: fvol = 0.9; fvolbar = 0.5;
|
|
rgsz[0] = "player/pl_grate1.wav";
|
|
rgsz[1] = "player/pl_grate4.wav";
|
|
cnt = 2;
|
|
break;
|
|
case CHAR_TEX_TILE: fvol = 0.8; fvolbar = 0.2;
|
|
rgsz[0] = "player/pl_tile1.wav";
|
|
rgsz[1] = "player/pl_tile3.wav";
|
|
rgsz[2] = "player/pl_tile2.wav";
|
|
rgsz[3] = "player/pl_tile4.wav";
|
|
cnt = 4;
|
|
break;
|
|
case CHAR_TEX_SLOSH: fvol = 0.9; fvolbar = 0.0;
|
|
rgsz[0] = "player/pl_slosh1.wav";
|
|
rgsz[1] = "player/pl_slosh3.wav";
|
|
rgsz[2] = "player/pl_slosh2.wav";
|
|
rgsz[3] = "player/pl_slosh4.wav";
|
|
cnt = 4;
|
|
break;
|
|
case CHAR_TEX_WOOD: fvol = 0.9; fvolbar = 0.2;
|
|
rgsz[0] = "debris/wood1.wav";
|
|
rgsz[1] = "debris/wood2.wav";
|
|
rgsz[2] = "debris/wood3.wav";
|
|
cnt = 3;
|
|
break;
|
|
case CHAR_TEX_GLASS:
|
|
case CHAR_TEX_COMPUTER:
|
|
fvol = 0.8; fvolbar = 0.2;
|
|
rgsz[0] = "debris/glass1.wav";
|
|
rgsz[1] = "debris/glass2.wav";
|
|
rgsz[2] = "debris/glass3.wav";
|
|
cnt = 3;
|
|
break;
|
|
case CHAR_TEX_FLESH:
|
|
if (iBulletType == BULLET_PLAYER_CROWBAR)
|
|
return 0.0; // crowbar already makes this sound
|
|
fvol = 1.0; fvolbar = 0.2;
|
|
rgsz[0] = "weapons/bullet_hit1.wav";
|
|
rgsz[1] = "weapons/bullet_hit2.wav";
|
|
fattn = 1.0;
|
|
cnt = 2;
|
|
break;
|
|
}
|
|
|
|
// did we hit a breakable?
|
|
|
|
if (pEntity && FClassnameIs(pEntity->pev, "func_breakable"))
|
|
{
|
|
// drop volumes, the object will already play a damaged sound
|
|
fvol /= 1.5;
|
|
fvolbar /= 2.0;
|
|
}
|
|
else if (chTextureType == CHAR_TEX_COMPUTER)
|
|
{
|
|
// play random spark if computer
|
|
|
|
if ( ptr->flFraction != 1.0 && RANDOM_LONG(0,1))
|
|
{
|
|
UTIL_Sparks( ptr->vecEndPos );
|
|
|
|
float flVolume = RANDOM_FLOAT ( 0.7 , 1.0 );//random volume range
|
|
switch ( RANDOM_LONG(0,1) )
|
|
{
|
|
case 0: UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, "buttons/spark5.wav", flVolume, ATTN_NORM, 0, 100); break;
|
|
case 1: UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, "buttons/spark6.wav", flVolume, ATTN_NORM, 0, 100); break;
|
|
// case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark5.wav", flVolume, ATTN_NORM); break;
|
|
// case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark6.wav", flVolume, ATTN_NORM); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// play material hit sound
|
|
UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, rgsz[RANDOM_LONG(0,cnt-1)], fvol, fattn, 0, 96 + RANDOM_LONG(0,0xf));
|
|
//EMIT_SOUND_DYN( ENT(m_pPlayer->pev), CHAN_WEAPON, rgsz[RANDOM_LONG(0,cnt-1)], fvol, ATTN_NORM, 0, 96 + RANDOM_LONG(0,0xf));
|
|
|
|
return fvolbar;
|
|
}
|
|
|