diff options
Diffstat (limited to 'src/game')
-rw-r--r-- | src/game/bg_public.h | 56 | ||||
-rw-r--r-- | src/game/bg_voice.c | 652 | ||||
-rw-r--r-- | src/game/g_active.c | 6 | ||||
-rw-r--r-- | src/game/g_client.c | 8 | ||||
-rw-r--r-- | src/game/g_cmds.c | 123 | ||||
-rw-r--r-- | src/game/g_local.h | 9 | ||||
-rw-r--r-- | src/game/g_main.c | 8 |
7 files changed, 860 insertions, 2 deletions
diff --git a/src/game/bg_public.h b/src/game/bg_public.h index f9527d37..1e1c98ac 100644 --- a/src/game/bg_public.h +++ b/src/game/bg_public.h @@ -1225,6 +1225,7 @@ qboolean BG_WeaponIsAllowed( weapon_t weapon ); qboolean BG_UpgradeIsAllowed( upgrade_t upgrade ); qboolean BG_ClassIsAllowed( class_t class ); qboolean BG_BuildableIsAllowed( buildable_t buildable ); +weapon_t BG_PrimaryWeapon( int stats[ ] ); typedef struct { @@ -1242,3 +1243,58 @@ void BG_ClientListParse( clientList_t *list, const char *s ); #define FFF_ALIENS 2 #define FFF_BUILDABLES 4 +// bg_voice.c +#define MAX_VOICES 8 +#define MAX_VOICE_NAME_LEN 16 +#define MAX_VOICE_CMD_LEN 16 +#define VOICE_ENTHUSIASM_DECAY 0.5f // enthusiasm lost per second + +typedef enum +{ + VOICE_CHAN_ALL, + VOICE_CHAN_TEAM , + VOICE_CHAN_LOCAL, + + VOICE_CHAN_NUM_CHANS +} voiceChannel_t; + +typedef struct voiceTrack_s +{ +#ifdef CGAME + sfxHandle_t track; + int duration; +#endif + char *text; + int enthusiasm; + int team; + int class; + int weapon; + struct voiceTrack_s *next; +} voiceTrack_t; + + +typedef struct voiceCmd_s +{ + char cmd[ MAX_VOICE_CMD_LEN ]; + voiceTrack_t *tracks; + struct voiceCmd_s *next; +} voiceCmd_t; + +typedef struct voice_s +{ + char name[ MAX_VOICE_NAME_LEN ]; + voiceCmd_t *cmds; + struct voice_s *next; +} voice_t; + +voice_t *BG_VoiceInit( void ); +void BG_PrintVoices( voice_t *voices, int debugLevel ); + +voice_t *BG_VoiceByName( voice_t *head, char *name ); +voiceCmd_t *BG_VoiceCmdFind( voiceCmd_t *head, char *name, int *cmdNum ); +voiceCmd_t *BG_VoiceCmdByNum( voiceCmd_t *head, int num); +voiceTrack_t *BG_VoiceTrackByNum( voiceTrack_t *head, int num ); +voiceTrack_t *BG_VoiceTrackFind( voiceTrack_t *head, team_t team, + class_t class, weapon_t weapon, + int enthusiasm, int *trackNum ); + diff --git a/src/game/bg_voice.c b/src/game/bg_voice.c new file mode 100644 index 00000000..91f65617 --- /dev/null +++ b/src/game/bg_voice.c @@ -0,0 +1,652 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. +Copyright (C) 2000-2006 Tim Angus +Copyright (C) 2008 Tony J. White + +This file is part of Tremulous. + +Tremulous is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Tremulous is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// bg_voice.c -- both games voice functions +#include "../qcommon/q_shared.h" +#include "bg_public.h" +#include "bg_local.h" + +int trap_FS_FOpenFile( const char *qpath, fileHandle_t *f, fsMode_t mode ); +int trap_FS_GetFileList( const char *path, const char *extension, char *listbuf, int bufsize ); +int trap_Parse_LoadSource( const char *filename ); +int trap_Parse_FreeSource( int handle ); +int trap_Parse_ReadToken( int handle, pc_token_t *pc_token ); +int trap_Parse_SourceFileAndLine( int handle, char *filename, int *line ); + +#ifdef CGAME +sfxHandle_t trap_S_RegisterSound( const char *sample, qboolean compressed ); +int trap_S_SoundDuration( sfxHandle_t handle ); +#endif + + +/* +============ +BG_VoiceParseError +============ +*/ +static void BG_VoiceParseError( fileHandle_t handle, char *err ) +{ + int line; + char filename[ MAX_QPATH ]; + + trap_Parse_SourceFileAndLine( handle, filename, &line ); + trap_Parse_FreeSource( handle ); + Com_Error( ERR_FATAL, "%s on line %d of %s\n", err, line, filename ); +} + +/* +============ +BG_VoiceList +============ +*/ +static voice_t *BG_VoiceList( void ) +{ + char fileList[ MAX_VOICES * ( MAX_VOICE_NAME_LEN + 8 ) ] = {""}; + int numFiles, i, fileLen = 0; + int count = 0; + char *filePtr; + voice_t *voices = NULL; + voice_t *top = NULL; + + numFiles = trap_FS_GetFileList( "voice", ".voice", fileList, + sizeof( fileList ) ); + + if( numFiles < 1 ) + return NULL; + + // special case for default.voice. this file is REQUIRED and will + // always be loaded first in the event of overflow of voice definitions + if( !trap_FS_FOpenFile( "voice/default.voice", NULL, FS_READ ) ) + { + Com_Printf( "voice/default.voice missing, voice system disabled." ); + return NULL; + } + + voices = (voice_t*)BG_Alloc( sizeof( voice_t ) ); + Q_strncpyz( voices->name, "default", sizeof( voices->name ) ); + voices->cmds = NULL; + voices->next = NULL; + count = 1; + + top = voices; + + filePtr = fileList; + for( i = 0; i < numFiles; i++, filePtr += fileLen + 1 ) + { + fileLen = strlen( filePtr ); + + // accounted for above + if( !Q_stricmp( filePtr, "default.voice" ) ) + continue; + + if( fileLen > MAX_VOICE_NAME_LEN + 8 ) { + Com_Printf( S_COLOR_YELLOW "WARNING: MAX_VOICE_NAME_LEN is %d. " + "skipping \"%s\", filename too long", MAX_VOICE_NAME_LEN, filePtr ); + continue; + } + + // trap_FS_GetFileList() buffer has overflowed + if( !trap_FS_FOpenFile( va( "voice/%s", filePtr ), NULL, FS_READ ) ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: BG_VoiceList(): detected " + " an invalid .voice file \"%s\" in directory listing. You have" + "probably named one or more .voice files with outrageously long " + "names. gjbs", filePtr ); + break; + } + + if( count >= MAX_VOICES ) + { + Com_Printf( S_COLOR_YELLOW "WARNING: .voice file overflow. " + "%d of %d .voice files loaded. MAX_VOICES is %d", + count, numFiles, MAX_VOICES ); + break; + } + + voices->next = (voice_t*)BG_Alloc( sizeof( voice_t ) ); + voices = voices->next; + + Q_strncpyz( voices->name, filePtr, sizeof( voices->name ) ); + // strip extension + voices->name[ fileLen - 6 ] = '\0'; + voices->cmds = NULL; + voices->next = NULL; + count++; + } + return top; +} + +/* +============ +BG_VoiceParseTrack +============ +*/ +static qboolean BG_VoiceParseTrack( int handle, voiceTrack_t *voiceTrack ) +{ + pc_token_t token; + qboolean found = qfalse; + qboolean foundText = qfalse; + qboolean foundToken = qfalse; + + foundToken = trap_Parse_ReadToken( handle, &token ); + while( foundToken ) + { + if( token.string[ 0 ] == '}' ) + { + if( foundText ) + return qtrue; + else + { + BG_VoiceParseError( handle, "BG_VoiceParseTrack(): " + "missing text attribute for track" ); + } + } + else if( !Q_stricmp( token.string, "team" ) ) + { + foundToken = trap_Parse_ReadToken( handle, &token ); + found = qfalse; + while( foundToken && token.type == TT_NUMBER ) + { + found = qtrue; + if( voiceTrack->team < 0 ) + voiceTrack->team = 0; + voiceTrack->team |= ( 1 << token.intvalue ); + foundToken = trap_Parse_ReadToken( handle, &token ); + } + if( !found ) + { + BG_VoiceParseError( handle, + "BG_VoiceParseTrack(): missing \"team\" value" ); + } + continue; + } + else if( !Q_stricmp( token.string, "class" ) ) + { + foundToken = trap_Parse_ReadToken( handle, &token ); + found = qfalse; + while( foundToken && token.type == TT_NUMBER ) + { + found = qtrue; + if( voiceTrack->class < 0 ) + voiceTrack->class = 0; + voiceTrack->class |= ( 1 << token.intvalue ); + foundToken = trap_Parse_ReadToken( handle, &token ); + } + if( !found ) + { + BG_VoiceParseError( handle, + "BG_VoiceParseTrack(): missing \"class\" value" ); + } + continue; + } + else if( !Q_stricmp( token.string, "weapon" ) ) + { + foundToken = trap_Parse_ReadToken( handle, &token ); + found = qfalse; + while( foundToken && token.type == TT_NUMBER ) + { + found = qtrue; + if( voiceTrack->weapon < 0 ) + voiceTrack->weapon = 0; + voiceTrack->weapon |= ( 1 << token.intvalue ); + foundToken = trap_Parse_ReadToken( handle, &token ); + } + if( !found ) + { + BG_VoiceParseError( handle, + "BG_VoiceParseTrack(): missing \"weapon\" value"); + } + continue; + } + else if( !Q_stricmp( token.string, "text" ) ) + { + if( foundText ) + { + BG_VoiceParseError( handle, "BG_VoiceParseTrack(): " + "duplicate \"text\" definition for track" ); + } + foundToken = trap_Parse_ReadToken( handle, &token ); + if( !foundToken ) + { + BG_VoiceParseError( handle, "BG_VoiceParseTrack(): " + "missing \"text\" value" ); + } + foundText = qtrue; + if( strlen( token.string ) >= MAX_SAY_TEXT ) + { + BG_VoiceParseError( handle, va( "BG_VoiceParseTrack(): " + "\"text\" value " "\"%s\" exceeds MAX_SAY_TEXT length", + token.string ) ); + } + + voiceTrack->text = (char *)BG_Alloc( strlen( token.string ) + 1 ); + Q_strncpyz( voiceTrack->text, token.string, strlen( token.string ) + 1 ); + foundToken = trap_Parse_ReadToken( handle, &token ); + continue; + } + else if( !Q_stricmp( token.string, "enthusiasm" ) ) + { + foundToken = trap_Parse_ReadToken( handle, &token ); + if( token.type == TT_NUMBER ) + { + voiceTrack->enthusiasm = token.intvalue; + } + else + { + BG_VoiceParseError( handle, "BG_VoiceParseTrack(): " + "missing \"enthusiasm\" value" ); + } + foundToken = trap_Parse_ReadToken( handle, &token ); + continue; + } + else + { + BG_VoiceParseError( handle, va( "BG_VoiceParseTrack():" + " unknown token \"%s\"", token.string ) ); + } + } + return qfalse; +} + +/* +============ +BG_VoiceParseCommand +============ +*/ +static voiceTrack_t *BG_VoiceParseCommand( int handle ) +{ + pc_token_t token; + qboolean parsingTrack = qfalse; + voiceTrack_t *voiceTracks = NULL; + voiceTrack_t *top = NULL; + + while( trap_Parse_ReadToken( handle, &token ) ) + { + if( !parsingTrack && token.string[ 0 ] == '}' ) + return top; + + if( parsingTrack ) + { + if( token.string[ 0 ] == '{' ) + { + BG_VoiceParseTrack( handle, voiceTracks ); + parsingTrack = qfalse; + continue; + + } + else + { + BG_VoiceParseError( handle, va( "BG_VoiceParseCommand(): " + "parse error at \"%s\"", token.string ) ); + } + } + + + if( top == NULL ) + { + voiceTracks = BG_Alloc( sizeof( voiceTrack_t ) ); + top = voiceTracks; + } + else + { + voiceTracks->next = BG_Alloc( sizeof( voiceCmd_t ) ); + voiceTracks = voiceTracks->next; + } + + if( !trap_FS_FOpenFile( va( "%s", token.string ), NULL, FS_READ ) ) + { + int line; + char filename[ MAX_QPATH ]; + + trap_Parse_SourceFileAndLine( handle, filename, &line ); + Com_Printf( S_COLOR_YELLOW "WARNING: BG_VoiceParseCommand(): " + "track \"%s\" referenced on line %d of %s does not exist\n", + token.string, line, filename ); + } + else + { +#ifdef CGAME + voiceTracks->track = trap_S_RegisterSound( token.string, qfalse ); + voiceTracks->duration = trap_S_SoundDuration( voiceTracks->track ); +#endif + } + + voiceTracks->team = -1; + voiceTracks->class = -1; + voiceTracks->weapon = -1; + voiceTracks->enthusiasm = 0; + voiceTracks->text = NULL; + voiceTracks->next = NULL; + parsingTrack = qtrue; + + } + return NULL; +} + +/* +============ +BG_VoiceParse +============ +*/ +static voiceCmd_t *BG_VoiceParse( char *name ) +{ + voiceCmd_t *voiceCmds = NULL; + voiceCmd_t *top = NULL; + pc_token_t token; + qboolean parsingCmd = qfalse; + int handle; + + handle = trap_Parse_LoadSource( va( "voice/%s.voice", name ) ); + if( !handle ) + return NULL; + + while( trap_Parse_ReadToken( handle, &token ) ) + { + if( parsingCmd ) + { + if( token.string[ 0 ] == '{' ) + { + voiceCmds->tracks = BG_VoiceParseCommand( handle ); + parsingCmd = qfalse; + continue; + } + else + { + int line; + char filename[ MAX_QPATH ]; + + trap_Parse_SourceFileAndLine( handle, filename, &line ); + Com_Error( ERR_FATAL, "BG_VoiceParse(): " + "parse error on line %d of %s\n", line, filename ); + } + } + + if( strlen( token.string ) >= MAX_VOICE_CMD_LEN ) + { + int line; + char filename[ MAX_QPATH ]; + + trap_Parse_SourceFileAndLine( handle, filename, &line ); + Com_Error( ERR_FATAL, "BG_VoiceParse(): " + "command \"%s\" exceeds MAX_VOICE_CMD_LEN (%d) on line %d of %s\n", + token.string, MAX_VOICE_CMD_LEN, line, filename ); + } + + if( top == NULL ) + { + voiceCmds = BG_Alloc( sizeof( voiceCmd_t ) ); + top = voiceCmds; + } + else + { + voiceCmds->next = BG_Alloc( sizeof( voiceCmd_t ) ); + voiceCmds = voiceCmds->next; + } + + Q_strncpyz( voiceCmds->cmd, token.string, sizeof( voiceCmds->cmd ) ); + voiceCmds->next = NULL; + parsingCmd = qtrue; + + } + + trap_Parse_FreeSource( handle ); + + return top; +} + +/* +============ +BG_VoiceInit +============ +*/ +voice_t *BG_VoiceInit( void ) +{ + voice_t *voices; + voice_t *voice; + + voices = BG_VoiceList(); + + voice = voices; + while( voice ) + { + voice->cmds = BG_VoiceParse( voice->name ); + voice = voice->next; + } + + return voices; +} + + +/* +============ +BG_PrintVoices +============ +*/ +void BG_PrintVoices( voice_t *voices, int debugLevel ) +{ + voice_t *voice = voices; + voiceCmd_t *voiceCmd; + voiceTrack_t *voiceTrack; + + int cmdCount; + int trackCount; + + if( voice == NULL ) + { + Com_Printf( "voice list is empty\n" ); + return; + } + + while( voice != NULL ) + { + if( debugLevel > 0 ) + Com_Printf( "voice \"%s\"\n", voice->name ); + voiceCmd = voice->cmds; + cmdCount = 0; + trackCount = 0; + while( voiceCmd != NULL ) + { + if( debugLevel > 0 ) + Com_Printf( " %s\n", voiceCmd->cmd ); + voiceTrack = voiceCmd->tracks; + cmdCount++; + while ( voiceTrack != NULL ) + { + if( debugLevel > 1 ) + Com_Printf( " text -> %s\n", voiceTrack->text ); + if( debugLevel > 2 ) + { + Com_Printf( " team -> %d\n", voiceTrack->team ); + Com_Printf( " class -> %d\n", voiceTrack->class ); + Com_Printf( " weapon -> %d\n", voiceTrack->weapon ); + Com_Printf( " enthusiasm -> %d\n", voiceTrack->enthusiasm ); +#ifdef CGAME + Com_Printf( " duration -> %d\n", voiceTrack->duration ); +#endif + } + if( debugLevel > 1 ) + Com_Printf( "\n" ); + trackCount++; + voiceTrack = voiceTrack->next; + } + voiceCmd = voiceCmd->next; + } + + if( !debugLevel ) + { + Com_Printf( "voice \"%s\": %d commands, %d tracks\n", + voice->name, cmdCount, trackCount ); + } + voice = voice->next; + } +} + +/* +============ +BG_VoiceByName +============ +*/ +voice_t *BG_VoiceByName( voice_t *head, char *name ) +{ + voice_t *v = head; + + while( v ) + { + if( !Q_stricmp( v->name, name ) ) + return v; + v = v->next; + } + return NULL; +} + +/* +============ +BG_VoiceCmdFind +============ +*/ +voiceCmd_t *BG_VoiceCmdFind( voiceCmd_t *head, char *name, int *cmdNum ) +{ + voiceCmd_t *vc = head; + int i = 0; + + while( vc ) + { + i++; + if( !Q_stricmp( vc->cmd, name ) ) + { + *cmdNum = i; + return vc; + } + vc = vc->next; + } + return NULL; +} + +/* +============ +BG_VoiceCmdByNum +============ +*/ +voiceCmd_t *BG_VoiceCmdByNum( voiceCmd_t *head, int num ) +{ + voiceCmd_t *vc = head; + int i = 0; + + while( vc ) + { + i++; + if( i == num ) + return vc; + vc = vc->next; + } + return NULL; +} + +/* +============ +BG_VoiceTrackByNum +============ +*/ +voiceTrack_t *BG_VoiceTrackByNum( voiceTrack_t *head, int num ) +{ + voiceTrack_t *vt = head; + int i = 0; + + while( vt ) + { + i++; + if( i == num ) + return vt; + vt = vt->next; + } + return NULL; +} + +/* +============ +BG_VoiceTrackFind +============ +*/ +voiceTrack_t *BG_VoiceTrackFind( voiceTrack_t *head, team_t team, + class_t class, weapon_t weapon, + int enthusiasm, int *trackNum ) +{ + voiceTrack_t *vt = head; + int highestMatch = 0; + int matchCount = 0; + int selectedMatch = 0; + int i = 0; + int j = 0; + + // find highest enthusiasm without going over + while( vt ) + { + if( ( vt->team >= 0 && !( vt->team & ( 1 << team ) ) ) || + ( vt->class >= 0 && !( vt->class & ( 1 << class ) ) ) || + ( vt->weapon >= 0 && !( vt->weapon & ( 1 << weapon ) ) ) || + vt->enthusiasm > enthusiasm ) + { + vt = vt->next; + continue; + } + + if( vt->enthusiasm > highestMatch ) + { + matchCount = 0; + highestMatch = vt->enthusiasm; + } + if( vt->enthusiasm == highestMatch ) + matchCount++; + vt = vt->next; + } + + if( !matchCount ) + return NULL; + + // return randomly selected match + selectedMatch = rand() % matchCount; + vt = head; + i = 0; + j = 0; + while( vt ) + { + j++; + if( ( vt->team >= 0 && !( vt->team & ( 1 << team ) ) ) || + ( vt->class >= 0 && !( vt->class & ( 1 << class ) ) ) || + ( vt->weapon >= 0 && !( vt->weapon & ( 1 << weapon ) ) ) || + vt->enthusiasm != highestMatch ) + { + vt = vt->next; + continue; + } + if( i == selectedMatch ) + { + *trackNum = j; + return vt; + } + i++; + vt = vt->next; + } + return NULL; +} diff --git a/src/game/g_active.c b/src/game/g_active.c index 7b03ce0f..cbc9aa5c 100644 --- a/src/game/g_active.c +++ b/src/game/g_active.c @@ -812,6 +812,12 @@ void ClientTimerActions( gentity_t *ent, int msec ) { G_Damage( ent, NULL, NULL, NULL, NULL, 5, DAMAGE_NO_ARMOR, MOD_SUICIDE ); } + + // lose some voice enthusiasm + if( client->voiceEnthusiasm > 0.0f ) + client->voiceEnthusiasm -= VOICE_ENTHUSIASM_DECAY; + else + client->voiceEnthusiasm = 0.0f; } // Regenerate Adv. Dragoon barbs diff --git a/src/game/g_client.c b/src/game/g_client.c index 01f5dbb2..578fc176 100644 --- a/src/game/g_client.c +++ b/src/game/g_client.c @@ -1091,14 +1091,18 @@ void ClientUserinfoChanged( int clientNum ) team = client->pers.teamSelection; + Q_strncpyz( client->pers.voice, Info_ValueForKey( userinfo, "voice" ), + sizeof( client->pers.voice ) ); + // send over a subset of the userinfo keys so other clients can // print scoreboards, display models, and play custom sounds Com_sprintf( userinfo, sizeof( userinfo ), "n\\%s\\t\\%i\\model\\%s\\c1\\%s\\c2\\%s\\" - "hc\\%i\\ig\\%16s", + "hc\\%i\\ig\\%16s\\v\\%s", client->pers.netname, team, model, c1, c2, - client->pers.maxHealth, BG_ClientListString( &client->sess.ignoreList ) ); + client->pers.maxHealth, BG_ClientListString( &client->sess.ignoreList ), + client->pers.voice ); trap_SetConfigstring( CS_PLAYERS + clientNum, userinfo ); diff --git a/src/game/g_cmds.c b/src/game/g_cmds.c index 88baf39c..13205415 100644 --- a/src/game/g_cmds.c +++ b/src/game/g_cmds.c @@ -931,6 +931,126 @@ static void Cmd_Tell_f( gentity_t *ent ) /* ================== +Cmd_VSay_f +================== +*/ +void Cmd_VSay_f( gentity_t *ent ) +{ + char arg[MAX_TOKEN_CHARS]; + voiceChannel_t vchan; + voice_t *voice; + voiceCmd_t *cmd; + voiceTrack_t *track; + int cmdNum = 0; + int trackNum = 0; + char voiceName[ MAX_VOICE_NAME_LEN ] = {"default"}; + char voiceCmd[ MAX_VOICE_CMD_LEN ] = {""}; + char vsay[ 12 ] = {""}; + weapon_t weapon; + + if( !ent || !ent->client ) + Com_Error( ERR_FATAL, "Cmd_VSay_f() called by non-client entity\n" ); + + trap_Argv( 0, arg, sizeof( arg ) ); + if( trap_Argc( ) < 2 ) + { + trap_SendServerCommand( ent-g_entities, va( + "print \"usage: %s command [text] \n\"", arg ) ); + return; + } + if( !level.voices ) + { + trap_SendServerCommand( ent-g_entities, va( + "print \"%s: voice system is not installed on this server\n\"", arg ) ); + return; + } + if( !g_voiceChats.integer ) + { + trap_SendServerCommand( ent-g_entities, va( + "print \"%s: voice system administratively disabled on this server\n\"", + arg ) ); + return; + } + if( !Q_stricmp( arg, "vsay" ) ) + vchan = VOICE_CHAN_ALL; + else if( !Q_stricmp( arg, "vsay_team" ) ) + vchan = VOICE_CHAN_TEAM; + else if( !Q_stricmp( arg, "vsay_local" ) ) + vchan = VOICE_CHAN_LOCAL; + else + return; + Q_strncpyz( vsay, arg, sizeof( vsay ) ); + + if( ent->client->pers.voice[ 0 ] ) + Q_strncpyz( voiceName, ent->client->pers.voice, sizeof( voiceName ) ); + voice = BG_VoiceByName( level.voices, voiceName ); + if( !voice ) + { + trap_SendServerCommand( ent-g_entities, va( + "print \"%s: voice '%s' not found\n\"", vsay, voiceName ) ); + return; + } + + trap_Argv( 1, voiceCmd, sizeof( voiceCmd ) ) ; + cmd = BG_VoiceCmdFind( voice->cmds, voiceCmd, &cmdNum ); + if( !cmd ) + { + trap_SendServerCommand( ent-g_entities, va( + "print \"%s: command '%s' not found in voice '%s'\n\"", + vsay, voiceCmd, voiceName ) ); + return; + } + + // filter non-spec humans by their primary weapon as well + weapon = WP_NONE; + if( ent->client->sess.spectatorState == SPECTATOR_NOT ) + { + weapon = BG_PrimaryWeapon( ent->client->ps.stats ); + } + + track = BG_VoiceTrackFind( cmd->tracks, ent->client->pers.teamSelection, + ent->client->pers.classSelection, weapon, (int)ent->client->voiceEnthusiasm, + &trackNum ); + if( !track ) + { + trap_SendServerCommand( ent-g_entities, va( + "print \"%s: no available track for command '%s', team %d, " + "class %d, weapon %d, and enthusiasm %d in voice '%s'\n\"", + vsay, voiceCmd, ent->client->pers.teamSelection, + ent->client->pers.classSelection, weapon, + (int)ent->client->voiceEnthusiasm, voiceName ) ); + return; + } + + if( !Q_stricmp( ent->client->lastVoiceCmd, cmd->cmd ) ) + ent->client->voiceEnthusiasm++; + + Q_strncpyz( ent->client->lastVoiceCmd, cmd->cmd, + sizeof( ent->client->lastVoiceCmd ) ); + + // optional user supplied text + trap_Argv( 2, arg, sizeof( arg ) ); + + switch( vchan ) + { + case VOICE_CHAN_ALL: + case VOICE_CHAN_LOCAL: + trap_SendServerCommand( -1, va( + "voice %d %d %d %d \"%s\"\n", + ent-g_entities, vchan, cmdNum, trackNum, arg ) ); + break; + case VOICE_CHAN_TEAM: + G_TeamCommand( ent->client->pers.teamSelection, va( + "voice %d %d %d %d \"%s\"\n", + ent-g_entities, vchan, cmdNum, trackNum, arg ) ); + break; + default: + break; + } +} + +/* +================== Cmd_Where_f ================== */ @@ -2890,6 +3010,9 @@ commands_t cmds[ ] = { // can be used even during intermission { "say", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f }, { "say_team", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f }, + { "vsay", CMD_MESSAGE|CMD_INTERMISSION, Cmd_VSay_f }, + { "vsay_team", CMD_MESSAGE|CMD_INTERMISSION, Cmd_VSay_f }, + { "vsay_local", CMD_MESSAGE|CMD_INTERMISSION, Cmd_VSay_f }, { "m", CMD_MESSAGE|CMD_INTERMISSION, G_PrivateMessage }, { "mt", CMD_MESSAGE|CMD_INTERMISSION, G_PrivateMessage }, diff --git a/src/game/g_local.h b/src/game/g_local.h index d4e5b61a..8f5a19a9 100644 --- a/src/game/g_local.h +++ b/src/game/g_local.h @@ -356,6 +356,7 @@ typedef struct qboolean muted; qboolean denyBuild; int adminLevel; + char voice[ MAX_VOICE_NAME_LEN ]; } clientPersistant_t; #define MAX_UNLAGGED_MARKERS 10 @@ -452,6 +453,9 @@ struct gclient_s unlagged_t unlaggedBackup; unlagged_t unlaggedCalc; int unlaggedTime; + + float voiceEnthusiasm; + char lastVoiceCmd[ MAX_VOICE_CMD_LEN ]; int lcannonStartTime; @@ -638,6 +642,8 @@ typedef struct char layout[ MAX_QPATH ]; team_t surrenderTeam; + + voice_t *voices; } level_locals_t; #define CMD_CHEAT 0x01 @@ -1151,6 +1157,9 @@ extern vmCvar_t g_currentMap; extern vmCvar_t g_initialMapRotation; extern vmCvar_t g_chatTeamPrefix; +extern vmCvar_t g_debugVoices; +extern vmCvar_t g_voiceChats; + extern vmCvar_t g_shove; extern vmCvar_t g_mapConfigs; diff --git a/src/game/g_main.c b/src/game/g_main.c index 6643b52e..eda9a91a 100644 --- a/src/game/g_main.c +++ b/src/game/g_main.c @@ -113,6 +113,9 @@ vmCvar_t g_currentMapRotation; vmCvar_t g_currentMap; vmCvar_t g_initialMapRotation; +vmCvar_t g_debugVoices; +vmCvar_t g_voiceChats; + vmCvar_t g_shove; vmCvar_t g_mapConfigs; @@ -233,6 +236,8 @@ static cvarTable_t gameCvarTable[ ] = { &g_currentMapRotation, "g_currentMapRotation", "-1", 0, 0, qfalse }, // -1 = NOT_ROTATING { &g_currentMap, "g_currentMap", "0", 0, 0, qfalse }, { &g_initialMapRotation, "g_initialMapRotation", "", CVAR_ARCHIVE, 0, qfalse }, + { &g_debugVoices, "g_debugVoices", "0", 0, 0, qfalse }, + { &g_voiceChats, "g_voiceChats", "1", CVAR_ARCHIVE, 0, qfalse }, { &g_shove, "g_shove", "0.0", CVAR_ARCHIVE, 0, qfalse }, { &g_mapConfigs, "g_mapConfigs", "", CVAR_ARCHIVE, 0, qfalse }, { NULL, "g_mapConfigsLoaded", "0", CVAR_ROM, 0, qfalse }, @@ -625,6 +630,9 @@ void G_InitGame( int levelTime, int randomSeed, int restart ) if( g_debugMapRotation.integer ) G_PrintRotations( ); + level.voices = BG_VoiceInit( ); + BG_PrintVoices( level.voices, g_debugVoices.integer ); + //reset stages trap_Cvar_Set( "g_alienStage", va( "%d", S1 ) ); trap_Cvar_Set( "g_humanStage", va( "%d", S1 ) ); |