/* =========================================================================== Wolfenstein: Enemy Territory GPL Source Code Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. This file is part of the Wolfenstein: Enemy Territory GPL Source Code (“Wolf ET Source Code”). Wolf ET Source Code 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 3 of the License, or (at your option) any later version. Wolf ET Source Code 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 Wolf ET Source Code. If not, see . In addition, the Wolf: ET Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Wolf ET Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ // Ridah, cg_sound.c - parsing and use of sound script files #include "cg_local.h" // we have to define these static lists, since we can't alloc memory within the cgame #define FILE_HASH_SIZE 1024 static soundScript_t* hashTable[FILE_HASH_SIZE]; #define MAX_SOUND_SCRIPTS 4096 static soundScript_t soundScripts[MAX_SOUND_SCRIPTS]; int numSoundScripts = 0; #define MAX_SOUND_SCRIPT_SOUNDS 8192 static soundScriptSound_t soundScriptSounds[MAX_SOUND_SCRIPT_SOUNDS]; int numSoundScriptSounds = 0; /* ================ return a hash value for the filename ================ */ static long generateHashValue( const char *fname ) { int i; long hash; char letter; hash = 0; i = 0; while ( fname[i] != '\0' ) { letter = tolower( fname[i] ); if ( letter == '.' ) { break; // don't include extension } if ( letter == '\\' ) { letter = '/'; // damn path names } hash += (long)( letter ) * ( i + 119 ); i++; } hash &= ( FILE_HASH_SIZE - 1 ); return hash; } /* ============== CG_SoundScriptPrecache returns the index+1 of the script in the global list, for fast calling ============== */ int CG_SoundScriptPrecache( const char *name ) { soundScriptSound_t *scriptSound; long hash; char *s; soundScript_t *sound; // byte buf[1024]; int i; if ( !name || !name[0] ) { return 0; } hash = generateHashValue( name ); s = (char *)name; for ( sound = hashTable[hash]; sound; sound = sound->nextHash ) { if ( !Q_stricmp( s, sound->name ) ) { // found a match, precache these sounds scriptSound = sound->soundList; if ( !sound->streaming ) { for ( ; scriptSound; scriptSound = scriptSound->next ) { for ( i = 0; i < scriptSound->numsounds; i++ ) { scriptSound->sounds[i].sfxHandle = 0; // scriptSound->sounds[i].sfxHandle = trap_S_RegisterSound( scriptSound->sounds[i].filename, qfalse ); // FIXME: make compressed settable through the soundscript } } } else /*if (cg_buildScript.integer)*/ { // Enabled this permanently so that streaming sounds get touched within file system on startup for ( ; scriptSound; scriptSound = scriptSound->next ) { for ( i = 0; i < scriptSound->numsounds; i++ ) { // just open the file so it gets copied to the build dir /* fileHandle_t f; trap_FS_FOpenFile( scriptSound->sounds[i].filename, &f, FS_READ ); trap_FS_Read( buf, sizeof( buf ), f ); // read a few bytes so the operating system does a better job of caching it for us trap_FS_FCloseFile( f );*/ } } } return sound->index + 1; } } return 0; } /* ============== CG_SoundPickOldestRandomSound ============== */ int CG_SoundPickOldestRandomSound( soundScript_t *sound, vec3_t org, int entnum ) { int oldestTime = 0; // TTimo: init soundScriptSound_t *oldestSound; soundScriptSound_t *scriptSound; oldestSound = NULL; scriptSound = sound->soundList; while ( scriptSound ) { if ( !oldestSound || ( scriptSound->lastPlayed < oldestTime ) ) { oldestTime = scriptSound->lastPlayed; oldestSound = scriptSound; } scriptSound = scriptSound->next; } if ( oldestSound ) { int pos = rand() % oldestSound->numsounds; // play this sound if ( !sound->streaming ) { if ( !oldestSound->sounds[pos].sfxHandle ) { oldestSound->sounds[pos].sfxHandle = trap_S_RegisterSound( oldestSound->sounds[pos].filename, qfalse ); // FIXME: make compressed settable through the soundscript } trap_S_StartSound( org, entnum, sound->channel, oldestSound->sounds[pos].sfxHandle ); return trap_S_GetSoundLength( oldestSound->sounds[pos].sfxHandle ); } else { return trap_S_StartStreamingSound( oldestSound->sounds[pos].filename, sound->looping ? oldestSound->sounds[pos].filename : NULL, entnum, sound->channel, sound->attenuation ); } oldestSound->lastPlayed = cg.time; } else { CG_Error( "Unable to locate a valid sound for soundScript: %s\n", sound->name ); } return 0; } void CG_AddBufferedSoundScript( soundScript_t* sound ) { if ( cg.numbufferedSoundScripts >= MAX_BUFFERED_SOUNDSCRIPTS ) { return; } cg.bufferSoundScripts[cg.numbufferedSoundScripts++] = sound; if ( cg.numbufferedSoundScripts == 1 ) { cg.bufferedSoundScriptEndTime = cg.time + CG_SoundPickOldestRandomSound( cg.bufferSoundScripts[0], NULL, -1 ); } } void CG_UpdateBufferedSoundScripts( void ) { int i; if ( !cg.numbufferedSoundScripts ) { return; } if ( cg.time > cg.bufferedSoundScriptEndTime ) { for ( i = 1; i < MAX_BUFFERED_SOUNDSCRIPTS; i++ ) { cg.bufferSoundScripts[i - 1] = cg.bufferSoundScripts[i]; } cg.numbufferedSoundScripts--; if ( !cg.numbufferedSoundScripts ) { return; } cg.bufferedSoundScriptEndTime = cg.time + CG_SoundPickOldestRandomSound( cg.bufferSoundScripts[0], NULL, -1 ); } } /* ============== CG_SoundPlaySoundScript returns qtrue is a script is found ============== */ int CG_SoundPlaySoundScript( const char *name, vec3_t org, int entnum, qboolean buffer ) { long hash; char *s; soundScript_t *sound; if ( !name || !name[0] ) { return qfalse; } hash = generateHashValue( name ); s = (char *)name; sound = hashTable[hash]; while ( sound ) { if ( !Q_stricmp( s, sound->name ) ) { if ( buffer ) { CG_AddBufferedSoundScript( sound ); return 1; } else { // found a match, pick the oldest sound return CG_SoundPickOldestRandomSound( sound, org, entnum ); } } sound = sound->nextHash; } CG_Printf( S_COLOR_YELLOW "WARNING: CG_SoundPlaySoundScript: cannot find sound script '%s'\n", name ); return 0; } /* ============== CG_SoundPlayIndexedScript returns qtrue is a script is found ============== */ void CG_SoundPlayIndexedScript( int index, vec3_t org, int entnum ) { soundScript_t *sound; if ( !index ) { return; } if ( index > numSoundScripts ) { return; } sound = &soundScripts[index - 1]; // pick the oldest sound CG_SoundPickOldestRandomSound( sound, org, entnum ); } /* =============== CG_SoundParseSounds =============== */ static void CG_SoundParseSounds( char *filename, char *buffer ) { char *token, **text; int s; long hash; soundScript_t sound; // the current sound being read soundScriptSound_t *scriptSound = NULL; qboolean inSound, wantSoundName; s = 0; inSound = qfalse; wantSoundName = qtrue; text = &buffer; while ( 1 ) { token = COM_ParseExt( text, qtrue ); if ( !*token ) { if ( inSound ) { CG_Error( "no concluding '}' in sound %s, file %s\n", sound.name, filename ); } return; } if ( !Q_stricmp( token, "{" ) ) { if ( inSound ) { CG_Error( "no concluding '}' in sound %s, file %s\n", sound.name, filename ); } if ( wantSoundName ) { CG_Error( "'{' found but not expected, after %s, file %s\n", sound.name, filename ); } inSound = qtrue; // grab a free scriptSound scriptSound = &soundScriptSounds[numSoundScriptSounds++]; if ( numSoundScripts == MAX_SOUND_SCRIPT_SOUNDS ) { CG_Error( "MAX_SOUND_SCRIPT_SOUNDS exceeded.\nReduce number of sound scripts.\n" ); } scriptSound->lastPlayed = 0; scriptSound->next = sound.soundList; scriptSound->numsounds = 0; sound.soundList = scriptSound; continue; } if ( !Q_stricmp( token, "}" ) ) { if ( !inSound ) { CG_Error( "'}' unexpected after sound %s, file %s\n", sound.name, filename ); } // end of a sound, copy it to the global list and stick it in the hashTable hash = generateHashValue( sound.name ); sound.nextHash = hashTable[hash]; soundScripts[numSoundScripts] = sound; hashTable[hash] = &soundScripts[numSoundScripts++]; if ( numSoundScripts == MAX_SOUND_SCRIPTS ) { CG_Error( "MAX_SOUND_SCRIPTS exceeded.\nReduce number of sound scripts.\n" ); } inSound = qfalse; wantSoundName = qtrue; CG_SoundScriptPrecache( sound.name ); continue; } if ( !inSound ) { // this is the identifier for a new sound if ( !wantSoundName ) { CG_Error( "'%s' unexpected after sound %s, file %s\n", token, sound.name, filename ); } memset( &sound, 0, sizeof( sound ) ); Q_strncpyz( sound.name, token, sizeof( sound.name ) ); wantSoundName = qfalse; sound.index = numSoundScripts; // setup the new sound defaults sound.channel = CHAN_AUTO; sound.attenuation = 1; // default to fade away with distance (for streaming sounds) // continue; } // we are inside a sound script if ( !Q_stricmp( token, "channel" ) ) { // ignore this now, just look for the channel identifiers explicitly continue; } if ( !Q_stricmp( token, "local" ) ) { sound.channel = CHAN_LOCAL; continue; } else if ( !Q_stricmp( token, "announcer" ) ) { sound.channel = CHAN_ANNOUNCER; continue; } else if ( !Q_stricmp( token, "body" ) ) { sound.channel = CHAN_BODY; continue; } else if ( !Q_stricmp( token, "voice" ) ) { sound.channel = CHAN_VOICE; continue; } else if ( !Q_stricmp( token, "weapon" ) ) { sound.channel = CHAN_WEAPON; continue; } else if ( !Q_stricmp( token, "item" ) ) { sound.channel = CHAN_ITEM; continue; } else if ( !Q_stricmp( token, "auto" ) ) { sound.channel = CHAN_AUTO; continue; } if ( !Q_stricmp( token, "global" ) ) { sound.attenuation = 0; continue; } if ( !Q_stricmp( token, "streaming" ) ) { sound.streaming = qtrue; continue; } if ( !Q_stricmp( token, "looping" ) ) { sound.looping = qtrue; continue; } if ( !Q_stricmp( token, "sound" ) ) { if ( scriptSound->numsounds >= MAX_SOUNDSCRIPT_SOUNDS ) { CG_Error( "Too many sounds for soundscript %s\n" ); } token = COM_ParseExt( text, qtrue ); Q_strncpyz( scriptSound->sounds[scriptSound->numsounds].filename, token, sizeof( scriptSound->sounds[0].filename ) ); scriptSound->numsounds++; continue; } } } /* =============== CG_SoundLoadSoundFiles =============== */ extern char bigTextBuffer[100000]; // we got it anyway, might as well use it #define MAX_SOUND_FILES 128 static void CG_SoundLoadSoundFiles( void ) { char soundFiles[MAX_SOUND_FILES][MAX_QPATH]; char *text; char filename[MAX_QPATH]; fileHandle_t f; int numSounds; int i, len; char *token; // scan for sound files Com_sprintf( filename, MAX_QPATH, "sound/scripts/filelist.txt" ); len = trap_FS_FOpenFile( filename, &f, FS_READ ); if ( len <= 0 ) { CG_Printf( S_COLOR_RED "WARNING: no sound files found (filelist.txt not found in sound/scripts)\n" ); return; } if ( len > sizeof( bigTextBuffer ) ) { CG_Error( "%s is too big, make it smaller (max = %i bytes)\n", filename, sizeof( bigTextBuffer ) ); } // load the file into memory trap_FS_Read( bigTextBuffer, len, f ); bigTextBuffer[len] = 0; trap_FS_FCloseFile( f ); // parse the list text = bigTextBuffer; numSounds = 0; while ( 1 ) { token = COM_ParseExt( &text, qtrue ); if ( !token[0] ) { break; } Com_sprintf( soundFiles[numSounds++], MAX_QPATH, token ); } // add the map specific soundfile Com_sprintf( soundFiles[numSounds++], MAX_QPATH, "%s.sounds", cgs.rawmapname ); if ( !numSounds ) { CG_Printf( S_COLOR_RED "WARNING: no sound files found\n" ); return; } // load and parse sound files for ( i = 0; i < numSounds; i++ ) { Com_sprintf( filename, sizeof( filename ), "sound/scripts/%s", soundFiles[i] ); CG_Printf( "...loading '%s'\n", filename ); len = trap_FS_FOpenFile( filename, &f, FS_READ ); if ( len <= 0 ) { if ( i != ( numSounds - 1 ) ) { CG_Error( "Couldn't load %s", filename ); } continue; } if ( len > sizeof( bigTextBuffer ) ) { CG_Error( "%s is too big, make it smaller (max = %i bytes)\n", filename, sizeof( bigTextBuffer ) ); } memset( bigTextBuffer, 0, sizeof( bigTextBuffer ) ); trap_FS_Read( bigTextBuffer, len, f ); trap_FS_FCloseFile( f ); CG_SoundParseSounds( filename, bigTextBuffer ); } } /* ============== CG_SoundInit ============== */ void CG_SoundInit( void ) { if ( numSoundScripts ) { // keep all the information, just reset the vars int i, j; for ( i = 0; i < numSoundScriptSounds; i++ ) { soundScriptSounds[i].lastPlayed = 0; for ( j = 0; j < soundScriptSounds[i].numsounds; j++ ) { soundScriptSounds[i].sounds[j].sfxHandle = 0; } } } else { CG_Printf( "\n.........................\n" "Initializing Sound Scripts\n" ); CG_SoundLoadSoundFiles(); CG_Printf( "done.\n" ); } } // // Script Speakers // // Editing typedef struct editHandle_s { vec3_t origin; vec3_t oldOrigin; int activeAxis; } editHandle_t; static qhandle_t speakerShader = 0; static qhandle_t speakerShaderGrayScale = 0; static bg_speaker_t *editSpeaker = NULL; static bg_speaker_t undoSpeaker; static int undoSpeakerIndex; static qboolean editSpeakerActive = qfalse; static editHandle_t editSpeakerHandle; static int numSpeakersInPvs; static const char *s_lt_string[] = { "no", "on", "off" }; static const char *s_bt_string[] = { "no", "global", "nopvs" }; qboolean CG_SaveSpeakersToScript( void ) { int i; bg_speaker_t *speaker; fileHandle_t fh; char *s; if ( trap_FS_FOpenFile( va( "sound/maps/%s.sps", cgs.rawmapname ), &fh, FS_WRITE ) < 0 ) { CG_Printf( S_COLOR_RED "ERROR: failed to save speakers to 'sound/maps/%s.sps'\n", cgs.rawmapname ); return qfalse; } s = "speakerScript\n{"; trap_FS_Write( s, strlen( s ), fh ); for ( i = 0; i < BG_NumScriptSpeakers(); i++ ) { char filenameStr[96] = ""; char originStr[96]; char targetnameStr[56] = ""; char loopedStr[32]; char broadcastStr[32]; char waitStr[32] = ""; char randomStr[32] = ""; char volumeStr[32] = ""; char rangeStr[32] = ""; speaker = BG_GetScriptSpeaker( i ); if ( *speaker->filename ) { Com_sprintf( filenameStr, sizeof( filenameStr ), "\t\tnoise \"%s\"\n", speaker->filename ); } Com_sprintf( originStr, sizeof( originStr ), "\t\torigin %.2f %.2f %.2f\n", speaker->origin[0], speaker->origin[1], speaker->origin[2] ); if ( *speaker->targetname ) { Com_sprintf( targetnameStr, sizeof( targetnameStr ), "\t\ttargetname \"%s\"\n", speaker->targetname ); } Com_sprintf( loopedStr, sizeof( loopedStr ), "\t\tlooped \"%s\"\n", s_lt_string[speaker->loop] ); Com_sprintf( broadcastStr, sizeof( broadcastStr ), "\t\tbroadcast \"%s\"\n", s_bt_string[speaker->broadcast] ); if ( speaker->wait ) { Com_sprintf( waitStr, sizeof( waitStr ), "\t\twait %i\n", speaker->wait ); } if ( speaker->random ) { Com_sprintf( randomStr, sizeof( randomStr ), "\t\trandom %i\n", speaker->random ); } if ( speaker->volume ) { Com_sprintf( volumeStr, sizeof( volumeStr ), "\t\tvolume %i\n", speaker->volume ); } if ( speaker->range ) { Com_sprintf( rangeStr, sizeof( rangeStr ), "\t\trange %i\n", speaker->range ); } s = va( "\n\tspeakerDef {\n%s%s%s%s%s%s%s%s%s\t}\n", filenameStr, originStr, targetnameStr, loopedStr, broadcastStr, waitStr, randomStr, volumeStr, rangeStr ); trap_FS_Write( s, strlen( s ), fh ); } s = "}\n"; trap_FS_Write( s, strlen( s ), fh ); trap_FS_FCloseFile( fh ); CG_Printf( "Saved %i speakers to 'sound/maps/%s.sps'\n", BG_NumScriptSpeakers(), cgs.rawmapname ); return qtrue; } void CG_AddLineToScene( vec3_t start, vec3_t end, vec4_t colour ) { refEntity_t re; memset( &re, 0, sizeof( re ) ); re.reType = RT_RAIL_CORE; re.customShader = cgs.media.railCoreShader; VectorCopy( start, re.origin ); VectorCopy( end, re.oldorigin ); re.shaderRGBA[0] = colour[0] * 0xff; re.shaderRGBA[1] = colour[1] * 0xff; re.shaderRGBA[2] = colour[2] * 0xff; re.shaderRGBA[3] = colour[3] * 0xff; trap_R_AddRefEntityToScene( &re ); } void CG_SetViewanglesForSpeakerEditor( void ) { vec3_t vec; if ( !editSpeakerActive ) { return; } VectorSubtract( editSpeakerHandle.origin, cg.refdef_current->vieworg, vec ); vectoangles( vec, cg.refdefViewAngles ); } static void CG_RenderScriptSpeakers( void ) { int i, j, closest; float dist, minDist; vec3_t vec; refEntity_t re; bg_speaker_t *speaker; closest = -1; minDist = Square( 8.f ); /*{ float r, u; vec3_t axisOrg, dir; closest = -1; minDist = Square( 32.f ); r = -(cg.refdef_current->fov_x / 90.f) * (float)(cgs.cursorX - 320) / 320; u = -(cg.refdef_current->fov_y / 90.f) * (float)(cgs.cursorY - 240) / 240; for( i = 0; i < 3; i++ ) { dir[i] = cg.refdef_current->viewaxis[0][i] * 1.f + cg.refdef_current->viewaxis[1][i] * r + cg.refdef_current->viewaxis[2][i] * u; } VectorNormalizeFast( dir ); VectorMA( cg.refdef_current->vieworg, 512, dir, vec ); //VectorCopy( cg.refdef_current->vieworg, axisOrg ); //axisOrg[2]+=.1f; VectorMA( cg.refdef_current->vieworg, .1f, cg.refdef_current->viewaxis[0], axisOrg ); CG_AddLineToScene( vec, axisOrg, colorOrange ); }*/ numSpeakersInPvs = 0; for ( i = 0; i < BG_NumScriptSpeakers(); i++ ) { speaker = BG_GetScriptSpeaker( i ); if ( editSpeakerActive && editSpeaker == speaker ) { vec4_t colour; for ( j = 0; j < 3; j++ ) { VectorClear( colour ); colour[3] = 1.f; if ( editSpeakerHandle.activeAxis >= 0 ) { if ( editSpeakerHandle.activeAxis == j ) { colour[j] = 1.f; } else { colour[j] = .3f; } } else { colour[j] = 1.f; } VectorClear( vec ); vec[j] = 1.f; VectorMA( editSpeakerHandle.origin, 32, vec, vec ); CG_AddLineToScene( editSpeakerHandle.origin, vec, colour ); memset( &re, 0, sizeof( re ) ); re.reType = RT_SPRITE; VectorCopy( vec, re.origin ); VectorCopy( vec, re.oldorigin ); re.radius = 3; re.customShader = cgs.media.waterBubbleShader; re.shaderRGBA[0] = colour[0] * 0xff; re.shaderRGBA[1] = colour[1] * 0xff; re.shaderRGBA[2] = colour[2] * 0xff; re.shaderRGBA[3] = colour[3] * 0xff; trap_R_AddRefEntityToScene( &re ); } if ( trap_R_inPVS( cg.refdef_current->vieworg, speaker->origin ) ) { numSpeakersInPvs++; } } else if ( !trap_R_inPVS( cg.refdef_current->vieworg, speaker->origin ) ) { continue; } else { numSpeakersInPvs++; } memset( &re, 0, sizeof( re ) ); re.reType = RT_SPRITE; VectorCopy( speaker->origin, re.origin ); VectorCopy( speaker->origin, re.oldorigin ); re.radius = 8; re.customShader = speakerShader; if ( editSpeaker ) { re.customShader = speakerShaderGrayScale; if ( editSpeaker == speaker ) { re.shaderRGBA[0] = 0xff; re.shaderRGBA[1] = 0xaa; re.shaderRGBA[2] = 0xaa; re.shaderRGBA[3] = 0xff; } else { re.shaderRGBA[0] = 0x3f; re.shaderRGBA[1] = 0x3f; re.shaderRGBA[2] = 0x3f; re.shaderRGBA[3] = 0xff; } } else { re.customShader = speakerShader; re.shaderRGBA[0] = 0xff; re.shaderRGBA[1] = 0xff; re.shaderRGBA[2] = 0xff; re.shaderRGBA[3] = 0xff; } trap_R_AddRefEntityToScene( &re ); // see which one is closest to our cursor if ( !editSpeakerActive ) { VectorSubtract( speaker->origin, cg.refdef_current->vieworg, vec ); dist = DotProduct( vec, cg.refdef_current->viewaxis[0] ); VectorMA( cg.refdef_current->vieworg, dist, cg.refdef_current->viewaxis[0], vec ); VectorSubtract( speaker->origin, vec, vec ); dist = VectorLengthSquared( vec ); if ( dist <= minDist ) { minDist = dist; closest = i; } } } if ( !editSpeakerActive ) { if ( closest >= 0 ) { editSpeaker = BG_GetScriptSpeaker( closest ); } else { editSpeaker = NULL; } } } void CG_SpeakerInfo_Text( panel_button_t* button ) { char *s, *ptr, *strptr; float y, wMax, w, h; vec4_t colour; char originStr[96]; char filenameStr[96] = ""; char targetnameStr[56] = ""; char loopedStr[32]; char broadcastStr[32]; char waitStr[32] = ""; char randomStr[32] = ""; char volumeStr[32] = ""; char rangeStr[32] = ""; if ( !button->font ) { return; } Com_sprintf( originStr, sizeof( originStr ), "Speaker at %.2f %.2f %.2f\n", editSpeaker->origin[0], editSpeaker->origin[1], editSpeaker->origin[2] ); wMax = CG_Text_Width_Ext( originStr, button->font->scalex, 0, button->font->font ); h = 8.5f; if ( *editSpeaker->filename ) { Com_sprintf( filenameStr, sizeof( filenameStr ), "noise: %s\n", editSpeaker->filename ); w = CG_Text_Width_Ext( filenameStr, button->font->scalex, 0, button->font->font ); if ( w > wMax ) { wMax = w; } h += 8.5f; } if ( *editSpeaker->targetname ) { Com_sprintf( targetnameStr, sizeof( targetnameStr ), "targetname: %s\n", editSpeaker->targetname ); w = CG_Text_Width_Ext( targetnameStr, button->font->scalex, 0, button->font->font ); if ( w > wMax ) { wMax = w; } h += 8.5f; } Com_sprintf( loopedStr, sizeof( loopedStr ), "looped: %s\n", s_lt_string[editSpeaker->loop] ); w = CG_Text_Width_Ext( loopedStr, button->font->scalex, 0, button->font->font ); if ( w > wMax ) { wMax = w; } h += 8.5f; Com_sprintf( broadcastStr, sizeof( broadcastStr ), "broadcast: %s\n", s_bt_string[editSpeaker->broadcast] ); w = CG_Text_Width_Ext( broadcastStr, button->font->scalex, 0, button->font->font ); if ( w > wMax ) { wMax = w; } h += 8.5f; if ( editSpeaker->wait ) { Com_sprintf( waitStr, sizeof( waitStr ), "wait: %i\n", editSpeaker->wait ); w = CG_Text_Width_Ext( waitStr, button->font->scalex, 0, button->font->font ); if ( w > wMax ) { wMax = w; } h += 8.5f; } if ( editSpeaker->random ) { Com_sprintf( randomStr, sizeof( randomStr ), "random: %i\n", editSpeaker->random ); w = CG_Text_Width_Ext( randomStr, button->font->scalex, 0, button->font->font ); if ( w > wMax ) { wMax = w; } h += 8.5f; } if ( editSpeaker->volume ) { Com_sprintf( volumeStr, sizeof( volumeStr ), "volume: %i\n", editSpeaker->volume ); w = CG_Text_Width_Ext( volumeStr, button->font->scalex, 0, button->font->font ); if ( w > wMax ) { wMax = w; } h += 8.5f; } if ( editSpeaker->range ) { Com_sprintf( rangeStr, sizeof( rangeStr ), "range: %i\n", editSpeaker->range ); w = CG_Text_Width_Ext( rangeStr, button->font->scalex, 0, button->font->font ); if ( w > wMax ) { wMax = w; } h += 8.5f; } VectorCopy( colorMdBlue, colour ); colour[3] = .5f; CG_FillRect( button->rect.x - 2, button->rect.y - 2, wMax + 4, h + 4, colour ); VectorCopy( colorBlue, colour ); CG_DrawRect( button->rect.x - 2, button->rect.y - 2, wMax + 4, h + 4, 1.f, colour ); s = va( "%s%s%s%s%s%s%s%s%s", originStr, filenameStr, targetnameStr, loopedStr, broadcastStr, waitStr, randomStr, volumeStr, rangeStr ); y = button->rect.y + 8; for ( strptr = ptr = s; *ptr; ptr++ ) { if ( *ptr == '\n' ) { *ptr = '\0'; CG_Text_Paint_Ext( button->rect.x, y, button->font->scalex, button->font->scaley, button->font->colour, strptr, 0, 0, button->font->style, button->font->font ); y += 8; strptr = ptr + 1; } } } panel_button_text_t speakerEditorTxt = { 0.2f, 0.2f, { 1.0f, 1.0f, 1.0f, 0.5f }, ITEM_TEXTSTYLE_SHADOWED, 0, &cgs.media.limboFont2, }; panel_button_t speakerInfo = { NULL, NULL, { 344, 184, 272, 72 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, &speakerEditorTxt, /* font */ NULL, /* keyDown */ NULL, /* keyUp */ CG_SpeakerInfo_Text, NULL, }; static panel_button_t *speakerInfoButtons[] = { &speakerInfo, NULL }; void CG_SpeakerEditor_RenderEdit( panel_button_t* button ) { vec4_t colour; if ( button == BG_PanelButtons_GetFocusButton() ) { VectorCopy( colorYellow, colour ); colour[3] = .3f; } else { VectorCopy( colorWhite, colour ); colour[3] = .3f; } CG_FillRect( button->rect.x, button->rect.y, button->rect.w, button->rect.h, colour ); button->rect.x += 2.f; button->rect.h -= 3.f; BG_PanelButton_RenderEdit( button ); button->rect.x -= 2.f; button->rect.h += 3.f; } void CG_SpeakerEditor_RenderButton( panel_button_t* button ) { vec4_t colour; float oldX; if ( button == BG_PanelButtons_GetFocusButton() ) { VectorCopy( colorMdBlue, colour ); colour[3] = .5f; } else if ( BG_PanelButtons_GetFocusButton() == NULL && BG_CursorInRect( &button->rect ) ) { VectorCopy( colorWhite, colour ); colour[3] = .5f; } else { VectorCopy( colorWhite, colour ); colour[3] = .3f; } CG_FillRect( button->rect.x, button->rect.y, button->rect.w, button->rect.h, colour ); VectorCopy( colorBlue, colour ); CG_DrawRect( button->rect.x, button->rect.y, button->rect.w, button->rect.h, 1.f, colour ); oldX = button->rect.x; button->rect.x = button->rect.x + ( button->rect.w - CG_Text_Width_Ext( button->text, button->font->scalex, 0, button->font->font ) ) / 2.f; button->rect.y += 9.f; BG_PanelButtonsRender_Text( button ); button->rect.x = oldX; button->rect.y -= 9.f; } char *CG_GetStrFromStrArray( const char *in, const int index ) { char *ptr, *s; int i; s = ptr = (char *)in; i = 0; while ( 1 ) { if ( i == index ) { return s; } while ( *ptr ) { ptr++; } ptr++; s = ptr; i++; } } void CG_SpeakerEditor_RenderDropdown( panel_button_t* button ) { vec4_t colour; float textboxW; rectDef_t rect; int i; char *s; memcpy( &rect, &button->rect, sizeof( rect ) ); textboxW = button->rect.w - button->rect.h; rect.x += textboxW; rect.w = rect.h; if ( button == BG_PanelButtons_GetFocusButton() ) { VectorCopy( colorYellow, colour ); colour[3] = .3f; } else { VectorCopy( colorWhite, colour ); colour[3] = .3f; } CG_FillRect( button->rect.x, button->rect.y, textboxW, button->rect.h, colour ); VectorCopy( colorBlue, colour ); CG_DrawRect( button->rect.x, button->rect.y, textboxW, button->rect.h, 1.f, colour ); if ( button == BG_PanelButtons_GetFocusButton() ) { VectorCopy( colorYellow, colour ); colour[3] = .3f; } else if ( BG_PanelButtons_GetFocusButton() == NULL && BG_CursorInRect( &button->rect ) ) { VectorCopy( colorWhite, colour ); colour[3] = .5f; } else { VectorCopy( colorWhite, colour ); colour[3] = .3f; } CG_FillRect( rect.x, rect.y, rect.w, rect.h, colour ); VectorCopy( colorBlue, colour ); CG_DrawRect( rect.x, rect.y, rect.w, rect.h, 1.f, colour ); VectorCopy( button->font->colour, colour ); CG_Text_Paint_Ext( rect.x + ( rect.w - CG_Text_Width_Ext( "V", button->font->scalex, 0, button->font->font ) ) / 2.f, button->rect.y + 9.f, button->font->scalex, button->font->scaley, colour, "V", 0, 0, 0, button->font->font ); s = CG_GetStrFromStrArray( button->text, button->data[1] ); CG_Text_Paint_Ext( button->rect.x + ( textboxW - CG_Text_Width_Ext( s, button->font->scalex, 0, button->font->font ) ) / 2.f, button->rect.y + 9.f, button->font->scalex, button->font->scaley, button->font->colour, s, 0, 0, button->font->style, button->font->font ); if ( button == BG_PanelButtons_GetFocusButton() ) { memcpy( &rect, &button->rect, sizeof( rect ) ); for ( i = 0; i < button->data[0]; i++ ) { if ( i == button->data[1] ) { continue; } rect.y += 12.f; if ( BG_CursorInRect( &rect ) ) { VectorScale( colorYellow, .3f, colour ); colour[3] = 1.f; } else { VectorScale( colorWhite, .3f, colour ); colour[3] = 1.f; } CG_FillRect( rect.x, rect.y, rect.w, rect.h, colour ); s = CG_GetStrFromStrArray( button->text, i ); CG_Text_Paint_Ext( rect.x + ( textboxW - CG_Text_Width_Ext( s, button->font->scalex, 0, button->font->font ) ) / 2.f, rect.y + 9.f, button->font->scalex, button->font->scaley, button->font->colour, s, 0, 0, button->font->style, button->font->font ); } VectorCopy( colorBlue, colour ); colour[3] = .3f; CG_DrawRect( button->rect.x, button->rect.y + 12.f, button->rect.w, rect.y - button->rect.y, 1.f, colour ); } } void CG_SpeakerEditor_Back( panel_button_t* button ) { vec4_t colour; VectorCopy( colorMdBlue, colour ); colour[3] = .5f; CG_FillRect( button->rect.x - 2, button->rect.y - 2, button->rect.w + 4, button->rect.h + 4, colour ); VectorCopy( colorBlue, colour ); CG_DrawRect( button->rect.x - 2, button->rect.y - 2, button->rect.w + 4, button->rect.h + 4, 1.f, colour ); } void CG_SpeakerEditor_LocInfo( panel_button_t* button ) { CG_Text_Paint_Ext( button->rect.x, button->rect.y, button->font->scalex, button->font->scaley, button->font->colour, va( "Speaker at %.2f %.2f %.2f", editSpeaker->origin[0], editSpeaker->origin[1], editSpeaker->origin[2] ), 0, 0, button->font->style, button->font->font ); } static char noiseMatchString[MAX_QPATH]; static int noiseMatchCount; static int noiseMatchIndex; qboolean CG_SpeakerEditor_NoiseEdit_KeyDown( panel_button_t* button, int key ) { if ( button == BG_PanelButtons_GetFocusButton() ) { if ( key == K_TAB ) { char dirname[MAX_QPATH]; char filename[MAX_QPATH]; char match[MAX_QPATH]; int i, numfiles, filelen; char *fileptr; COM_StripFilename( (char *)button->text, dirname ); Q_strncpyz( filename, COM_SkipPath( (char *)button->text ), sizeof( filename ) ); if ( !Q_stricmp( button->text, dirname ) ) { return qtrue; } numfiles = trap_FS_GetFileList( dirname, "", bigTextBuffer, sizeof( bigTextBuffer ) ); // FIXME: autocomplete directories? fileptr = bigTextBuffer; if ( !*noiseMatchString || Q_stricmpn( noiseMatchString, filename, strlen( noiseMatchString ) ) ) { Q_strncpyz( noiseMatchString, filename, sizeof( noiseMatchString ) ); noiseMatchCount = 0; noiseMatchIndex = 0; for ( i = 0; i < numfiles; i++, fileptr += filelen + 1 ) { filelen = strlen( fileptr ); if ( Q_stricmpn( fileptr, filename, strlen( filename ) ) ) { continue; } noiseMatchCount++; if ( noiseMatchCount == 1 ) { Q_strncpyz( match, fileptr, sizeof( match ) ); continue; } /*if( strlen(fileptr) < strlen(match) ) { Q_strncpyz( match, fileptr, sizeof(match) ); noiseMatchIndex++; continue; }*/ } } else { if ( noiseMatchCount == 1 ) { return qtrue; } else { int findMatchIndex = 0; noiseMatchIndex++; if ( noiseMatchIndex == noiseMatchCount ) { noiseMatchIndex = 0; } for ( i = 0; i < numfiles; i++, fileptr += filelen + 1 ) { filelen = strlen( fileptr ); if ( Q_stricmpn( fileptr, noiseMatchString, strlen( noiseMatchString ) ) ) { continue; } if ( findMatchIndex == noiseMatchIndex ) { Q_strncpyz( match, fileptr, sizeof( match ) ); break; } findMatchIndex++; } } } if ( noiseMatchCount == 0 ) { noiseMatchString[0] = '\0'; return qtrue; } Com_sprintf( (char *)button->text, button->data[0], "%s%s", dirname, match ); return qtrue; } else { if ( key & K_CHAR_FLAG ) { int localkey = key; localkey &= ~K_CHAR_FLAG; if ( localkey == 'h' - 'a' + 1 || localkey >= 32 ) { noiseMatchString[0] = '\0'; } } } } return BG_PanelButton_EditClick( button, key ); } void CG_SpeakerEditor_NoiseEditFinish( panel_button_t* button ) { Q_strncpyz( editSpeaker->filename, button->text, sizeof( editSpeaker->filename ) ); if ( *editSpeaker->filename ) { editSpeaker->noise = trap_S_RegisterSound( editSpeaker->filename, qfalse ); } else { editSpeaker->noise = 0; } } void CG_SpeakerEditor_TargetnameEditFinish( panel_button_t* button ) { Q_strncpyz( editSpeaker->targetname, button->text, sizeof( editSpeaker->targetname ) ); } qboolean CG_SpeakerEditor_Dropdown_KeyDown( panel_button_t* button, int key ) { if ( key == K_MOUSE1 ) { BG_PanelButtons_SetFocusButton( button ); return qtrue; } return qfalse; } qboolean CG_SpeakerEditor_Looped_KeyUp( panel_button_t* button, int key ) { if ( key == K_MOUSE1 ) { if ( button == BG_PanelButtons_GetFocusButton() ) { rectDef_t rect; int i; memcpy( &rect, &button->rect, sizeof( rect ) ); for ( i = 0; i < 3; i++ ) { if ( i == editSpeaker->loop ) { continue; } rect.y += 12.f; if ( BG_CursorInRect( &rect ) ) { button->data[1] = editSpeaker->loop = i; break; } } if ( editSpeaker->loop == S_LT_LOOPED_ON ) { editSpeaker->activated = qtrue; } else { editSpeaker->activated = qfalse; } BG_PanelButtons_SetFocusButton( NULL ); return qtrue; } } return qfalse; } qboolean CG_SpeakerEditor_Broadcast_KeyUp( panel_button_t* button, int key ) { if ( key == K_MOUSE1 ) { if ( button == BG_PanelButtons_GetFocusButton() ) { rectDef_t rect; int i; memcpy( &rect, &button->rect, sizeof( rect ) ); for ( i = 0; i < 3; i++ ) { if ( i == editSpeaker->broadcast ) { continue; } rect.y += 12.f; if ( BG_CursorInRect( &rect ) ) { button->data[1] = editSpeaker->broadcast = i; break; } } BG_PanelButtons_SetFocusButton( NULL ); return qtrue; } } return qfalse; } void CG_SpeakerEditor_WaitEditFinish( panel_button_t* button ) { if ( *button->text ) { editSpeaker->wait = atoi( button->text ); if ( editSpeaker->wait < 0 ) { editSpeaker->wait = 0; Com_sprintf( (char*)button->text, sizeof( button->text ), "%i", editSpeaker->range ); } } else { editSpeaker->wait = 0; Com_sprintf( (char*)button->text, sizeof( button->text ), "%i", editSpeaker->wait ); } } void CG_SpeakerEditor_RandomEditFinish( panel_button_t* button ) { if ( *button->text ) { editSpeaker->random = atoi( button->text ); if ( editSpeaker->random < 0 ) { editSpeaker->random = 0; Com_sprintf( (char*)button->text, sizeof( button->text ), "%i", editSpeaker->random ); } } else { editSpeaker->random = 0; Com_sprintf( (char*)button->text, sizeof( button->text ), "%i", editSpeaker->random ); } } void CG_SpeakerEditor_VolumeEditFinish( panel_button_t* button ) { if ( *button->text ) { editSpeaker->volume = atoi( button->text ); if ( editSpeaker->volume < 0 ) { editSpeaker->volume = 0; Com_sprintf( (char*)button->text, sizeof( button->text ), "%i", editSpeaker->volume ); } else if ( editSpeaker->volume > 65535 ) { editSpeaker->volume = 65535; Com_sprintf( (char*)button->text, sizeof( button->text ), "%i", editSpeaker->volume ); } } else { editSpeaker->volume = 127; Com_sprintf( (char*)button->text, sizeof( button->text ), "%i", editSpeaker->volume ); } } void CG_SpeakerEditor_RangeEditFinish( panel_button_t* button ) { if ( *button->text ) { editSpeaker->range = atoi( button->text ); if ( editSpeaker->range < 0 ) { editSpeaker->range = 0; Com_sprintf( (char*)button->text, sizeof( button->text ), "%i", editSpeaker->range ); } } else { editSpeaker->range = 1250; Com_sprintf( (char*)button->text, sizeof( button->text ), "%i", editSpeaker->range ); } } qboolean CG_SpeakerEditor_Ok_KeyDown( panel_button_t* button, int key ) { if ( key == K_MOUSE1 ) { BG_PanelButtons_SetFocusButton( button ); return qtrue; } return qfalse; } qboolean CG_SpeakerEditor_Ok_KeyUp( panel_button_t* button, int key ) { if ( key == K_MOUSE1 ) { if ( button == BG_PanelButtons_GetFocusButton() ) { BG_PanelButtons_SetFocusButton( NULL ); if ( BG_CursorInRect( &button->rect ) ) { CG_SaveSpeakersToScript(); editSpeakerActive = qfalse; CG_EventHandling( -CGAME_EVENT_SPEAKEREDITOR, qtrue ); } return qtrue; } } return qfalse; } qboolean CG_SpeakerEditor_Cancel_KeyDown( panel_button_t* button, int key ) { if ( key == K_MOUSE1 ) { BG_PanelButtons_SetFocusButton( button ); return qtrue; } return qfalse; } qboolean CG_SpeakerEditor_Cancel_KeyUp( panel_button_t* button, int key ) { if ( key == K_MOUSE1 ) { if ( button == BG_PanelButtons_GetFocusButton() ) { BG_PanelButtons_SetFocusButton( NULL ); if ( BG_CursorInRect( &button->rect ) ) { memcpy( editSpeaker, &undoSpeaker, sizeof( *editSpeaker ) ); undoSpeakerIndex = -2; editSpeaker = NULL; editSpeakerActive = qfalse; CG_EventHandling( -CGAME_EVENT_SPEAKEREDITOR, qtrue ); } return qtrue; } } return qfalse; } qboolean CG_SpeakerEditor_Delete_KeyDown( panel_button_t* button, int key ) { if ( key == K_MOUSE1 ) { BG_PanelButtons_SetFocusButton( button ); return qtrue; } return qfalse; } qboolean CG_SpeakerEditor_Delete_KeyUp( panel_button_t* button, int key ) { if ( key == K_MOUSE1 ) { if ( button == BG_PanelButtons_GetFocusButton() ) { BG_PanelButtons_SetFocusButton( NULL ); if ( BG_CursorInRect( &button->rect ) ) { undoSpeakerIndex = -1; BG_SS_DeleteSpeaker( BG_GetIndexForSpeaker( editSpeaker ) ); CG_SaveSpeakersToScript(); editSpeaker = NULL; editSpeakerActive = qfalse; CG_EventHandling( -CGAME_EVENT_SPEAKEREDITOR, qtrue ); } return qtrue; } } return qfalse; } panel_button_t speakerEditorBack = { NULL, NULL, { 360, 330, 272, 142 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, NULL, /* font */ NULL, /* keyDown */ NULL, /* keyUp */ CG_SpeakerEditor_Back, NULL, }; panel_button_t speakerEditorLocInfo = { NULL, NULL, { 361, 330 + 9, 272, 10 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, &speakerEditorTxt, /* font */ NULL, /* keyDown */ NULL, /* keyUp */ CG_SpeakerEditor_LocInfo, NULL, }; panel_button_t speakerEditorNoiseLabel = { NULL, "Noise:", { 361, 344 + 9, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, &speakerEditorTxt, /* font */ NULL, /* keyDown */ NULL, /* keyUp */ BG_PanelButtonsRender_Text, NULL, }; char noiseEditBuffer[MAX_QPATH]; panel_button_t speakerEditorNoiseEdit = { NULL, noiseEditBuffer, { 430, 344, 200, 12 }, { MAX_QPATH, 0, 0, 0, 0, 0, 0, 0 }, &speakerEditorTxt, /* font */ CG_SpeakerEditor_NoiseEdit_KeyDown, /* keyDown */ NULL, /* keyUp */ CG_SpeakerEditor_RenderEdit, CG_SpeakerEditor_NoiseEditFinish, }; panel_button_t speakerEditorTargetnameLabel = { NULL, "Targetname:", { 361, 358 + 9, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, &speakerEditorTxt, /* font */ NULL, /* keyDown */ NULL, /* keyUp */ BG_PanelButtonsRender_Text, NULL, }; char targetnameEditBuffer[32]; panel_button_t speakerEditorTargetnameEdit = { NULL, targetnameEditBuffer, { 430, 358, 200, 12 }, { 32, 0, 0, 0, 0, 0, 0, 0 }, &speakerEditorTxt, /* font */ BG_PanelButton_EditClick, /* keyDown */ NULL, /* keyUp */ CG_SpeakerEditor_RenderEdit, CG_SpeakerEditor_TargetnameEditFinish, }; panel_button_t speakerEditorLoopedLabel = { NULL, "Looped:", { 361, 372 + 9, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, &speakerEditorTxt, /* font */ NULL, /* keyDown */ NULL, /* keyUp */ BG_PanelButtonsRender_Text, NULL, }; panel_button_t speakerEditorLoopedDropdown = { NULL, "no\0on\0off", { 430, 372, 60, 12 }, { 3, 0, 0, 0, 0, 0, 0, 0 }, &speakerEditorTxt, /* font */ CG_SpeakerEditor_Dropdown_KeyDown, /* keyDown */ CG_SpeakerEditor_Looped_KeyUp, /* keyUp */ CG_SpeakerEditor_RenderDropdown, NULL, }; panel_button_t speakerEditorBroadcastLabel = { NULL, "Broadcast:", { 361, 386 + 9, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, &speakerEditorTxt, /* font */ NULL, /* keyDown */ NULL, /* keyUp */ BG_PanelButtonsRender_Text, NULL, }; panel_button_t speakerEditorBroadcastDropdown = { NULL, "no\0global\0nopvs", { 430, 386, 60, 12 }, { 3, 0, 0, 0, 0, 0, 0, 0 }, &speakerEditorTxt, /* font */ CG_SpeakerEditor_Dropdown_KeyDown, /* keyDown */ CG_SpeakerEditor_Broadcast_KeyUp, /* keyUp */ CG_SpeakerEditor_RenderDropdown, NULL, }; panel_button_t speakerEditorWaitLabel = { NULL, "Wait:", { 361, 400 + 9, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, &speakerEditorTxt, /* font */ NULL, /* keyDown */ NULL, /* keyUp */ BG_PanelButtonsRender_Text, NULL, }; char waitEditBuffer[12]; panel_button_t speakerEditorWaitEdit = { NULL, waitEditBuffer, { 430, 400, 200, 12 }, { 12, 2, 0, 0, 0, 0, 0, 0 }, &speakerEditorTxt, /* font */ BG_PanelButton_EditClick, /* keyDown */ NULL, /* keyUp */ CG_SpeakerEditor_RenderEdit, CG_SpeakerEditor_WaitEditFinish, }; panel_button_t speakerEditorRandomLabel = { NULL, "Random:", { 361, 414 + 9, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, &speakerEditorTxt, /* font */ NULL, /* keyDown */ NULL, /* keyUp */ BG_PanelButtonsRender_Text, NULL, }; char randomEditBuffer[12]; panel_button_t speakerEditorRandomEdit = { NULL, randomEditBuffer, { 430, 414, 200, 12 }, { 12, 2, 0, 0, 0, 0, 0, 0 }, &speakerEditorTxt, /* font */ BG_PanelButton_EditClick, /* keyDown */ NULL, /* keyUp */ CG_SpeakerEditor_RenderEdit, CG_SpeakerEditor_RandomEditFinish, }; panel_button_t speakerEditorVolumeLabel = { NULL, "Volume:", { 361, 428 + 9, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, &speakerEditorTxt, /* font */ NULL, /* keyDown */ NULL, /* keyUp */ BG_PanelButtonsRender_Text, NULL, }; char volumeEditBuffer[12]; panel_button_t speakerEditorVolumeEdit = { NULL, volumeEditBuffer, { 430, 428, 200, 12 }, { 12, 2, 0, 0, 0, 0, 0, 0 }, &speakerEditorTxt, /* font */ BG_PanelButton_EditClick, /* keyDown */ NULL, /* keyUp */ CG_SpeakerEditor_RenderEdit, CG_SpeakerEditor_VolumeEditFinish, }; panel_button_t speakerEditorRangeLabel = { NULL, "Range:", { 361, 442 + 9, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, &speakerEditorTxt, /* font */ NULL, /* keyDown */ NULL, /* keyUp */ BG_PanelButtonsRender_Text, NULL, }; char rangeEditBuffer[12]; panel_button_t speakerEditorRangeEdit = { NULL, rangeEditBuffer, { 430, 442, 200, 12 }, { 12, 2, 0, 0, 0, 0, 0, 0 }, &speakerEditorTxt, /* font */ BG_PanelButton_EditClick, /* keyDown */ NULL, /* keyUp */ CG_SpeakerEditor_RenderEdit, CG_SpeakerEditor_RangeEditFinish, }; panel_button_t speakerEditorOkButton = { NULL, "Ok", { 376, 458, 70, 12 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, &speakerEditorTxt, /* font */ CG_SpeakerEditor_Ok_KeyDown, /* keyDown */ CG_SpeakerEditor_Ok_KeyUp, /* keyUp */ CG_SpeakerEditor_RenderButton, NULL, }; panel_button_t speakerEditorCancelButton = { NULL, "Cancel", { 461, 458, 70, 12 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, &speakerEditorTxt, /* font */ CG_SpeakerEditor_Cancel_KeyDown, /* keyDown */ CG_SpeakerEditor_Cancel_KeyUp, /* keyUp */ CG_SpeakerEditor_RenderButton, NULL, }; panel_button_t speakerEditorDeleteButton = { NULL, "Delete", { 546, 458, 70, 12 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, &speakerEditorTxt, /* font */ CG_SpeakerEditor_Delete_KeyDown, /* keyDown */ CG_SpeakerEditor_Delete_KeyUp, /* keyUp */ CG_SpeakerEditor_RenderButton, NULL, }; static panel_button_t *speakerEditorButtons[] = { &speakerEditorBack, &speakerEditorLocInfo, &speakerEditorNoiseLabel, &speakerEditorNoiseEdit, &speakerEditorTargetnameLabel, &speakerEditorTargetnameEdit, &speakerEditorLoopedLabel, &speakerEditorBroadcastLabel, &speakerEditorWaitLabel, &speakerEditorWaitEdit, &speakerEditorRandomLabel, &speakerEditorRandomEdit, &speakerEditorVolumeLabel, &speakerEditorVolumeEdit, &speakerEditorRangeLabel, &speakerEditorRangeEdit, &speakerEditorOkButton, &speakerEditorCancelButton, &speakerEditorDeleteButton, // Below here all components that should draw on top &speakerEditorBroadcastDropdown, &speakerEditorLoopedDropdown, NULL }; void CG_SpeakerEditorDraw( void ) { if ( !cg.editingSpeakers ) { return; } if ( !editSpeakerActive ) { int bindingkey[2]; char binding[2][32]; vec4_t colour; float x, y, w, h; VectorCopy( colorWhite, colour ); colour[3] = .8f; if ( undoSpeakerIndex == -2 ) { y = 452; } else { y = 442; } CG_Text_Paint_Ext( 8, y, .2f, .2f, colour, va( "Current amount of speakers in map: %i (inpvs: %i max in map: %i)", BG_NumScriptSpeakers(), numSpeakersInPvs, 256 ), 0, 0, ITEM_TEXTSTYLE_SHADOWED, &cgs.media.limboFont2 ); trap_Key_KeysForBinding( "dumpspeaker", &bindingkey[0], &bindingkey[1] ); trap_Key_KeynumToStringBuf( bindingkey[0], binding[0], sizeof( binding[0] ) ); trap_Key_KeynumToStringBuf( bindingkey[1], binding[1], sizeof( binding[1] ) ); Q_strupr( binding[0] ); Q_strupr( binding[1] ); CG_Text_Paint_Ext( 8, y + 10, .2f, .2f, colour, va( "Create new speaker: %s%s", bindingkey[0] != -1 ? binding[0] : "???", bindingkey[1] != -1 ? va( " or %s", binding[1] ) : "" ), 0, 0, ITEM_TEXTSTYLE_SHADOWED, &cgs.media.limboFont2 ); trap_Key_KeysForBinding( "modifyspeaker", &bindingkey[0], &bindingkey[1] ); trap_Key_KeynumToStringBuf( bindingkey[0], binding[0], sizeof( binding[0] ) ); trap_Key_KeynumToStringBuf( bindingkey[1], binding[1], sizeof( binding[1] ) ); Q_strupr( binding[0] ); Q_strupr( binding[1] ); CG_Text_Paint_Ext( 8, y + 20, .2f, .2f, colour, va( "Modify target speaker: %s%s", bindingkey[0] != -1 ? binding[0] : "???", bindingkey[1] != -1 ? va( " or %s", binding[1] ) : "" ), 0, 0, ITEM_TEXTSTYLE_SHADOWED, &cgs.media.limboFont2 ); if ( undoSpeakerIndex != -2 ) { trap_Key_KeysForBinding( "undospeaker", &bindingkey[0], &bindingkey[1] ); trap_Key_KeynumToStringBuf( bindingkey[0], binding[0], sizeof( binding[0] ) ); trap_Key_KeynumToStringBuf( bindingkey[1], binding[1], sizeof( binding[1] ) ); Q_strupr( binding[0] ); Q_strupr( binding[1] ); CG_Text_Paint_Ext( 8, y + 30, .2f, .2f, colour, va( "Undo %s speaker: %s%s", undoSpeakerIndex == -1 ? "remove" : "modify", bindingkey[0] != -1 ? binding[0] : "???", bindingkey[1] != -1 ? va( " or %s", binding[1] ) : "" ), 0, 0, ITEM_TEXTSTYLE_SHADOWED, &cgs.media.limboFont2 ); } // render crosshair x = cg_crosshairX.integer; y = cg_crosshairY.integer; w = h = cg_crosshairSize.value; CG_AdjustFrom640( &x, &y, &w, &h ); trap_R_DrawStretchPic( x + 0.5 * ( cg.refdef_current->width - w ), y + 0.5 * ( cg.refdef_current->height - h ), w, h, 0, 0, 1, 1, cgs.media.crosshairShader[ cg_drawCrosshair.integer % NUM_CROSSHAIRS ] ); if ( cg.crosshairShaderAlt[ cg_drawCrosshair.integer % NUM_CROSSHAIRS ] ) { trap_R_DrawStretchPic( x + 0.5 * ( cg.refdef_current->width - w ), y + 0.5 * ( cg.refdef_current->height - h ), w, h, 0, 0, 1, 1, cg.crosshairShaderAlt[ cg_drawCrosshair.integer % NUM_CROSSHAIRS ] ); } if ( editSpeaker ) { // render interface BG_PanelButtonsRender( speakerInfoButtons ); } } else { // render interface BG_PanelButtonsRender( speakerEditorButtons ); // render cursor trap_R_SetColor( NULL ); CG_DrawPic( cgDC.cursorx, cgDC.cursory, 32, 32, cgs.media.cursorIcon ); } } void CG_SpeakerEditor_KeyHandling( int key, qboolean down ) { if ( !BG_PanelButtonsKeyEvent( key, down, speakerEditorButtons ) ) { switch ( key ) { case K_MOUSE1: if ( !down ) { editSpeakerHandle.activeAxis = -1; break; } else if ( editSpeakerHandle.activeAxis == -1 ) { int i, closest; float dist, minDist, r, u; vec3_t vec, axisOrg, dir; closest = -1; minDist = Square( 16.f ); r = -( cg.refdef_current->fov_x / 90.f ) * (float)( cgs.cursorX - 320 ) / 320; u = -( cg.refdef_current->fov_y / 90.f ) * (float)( cgs.cursorY - 240 ) / 240; for ( i = 0; i < 3; i++ ) { dir[i] = cg.refdef_current->viewaxis[0][i] * 1.f + cg.refdef_current->viewaxis[1][i] * r + cg.refdef_current->viewaxis[2][i] * u; } VectorNormalizeFast( dir ); for ( i = 0; i < 3; i++ ) { VectorClear( vec ); vec[i] = 1.f; VectorMA( editSpeakerHandle.origin, 32, vec, axisOrg ); // see which one is closest to our cursor VectorSubtract( axisOrg, cg.refdef_current->vieworg, vec ); dist = DotProduct( vec, dir ); VectorMA( cg.refdef_current->vieworg, dist, dir, vec ); dist = DistanceSquared( axisOrg, vec ); if ( dist <= minDist ) { minDist = dist; closest = i; } } editSpeakerHandle.activeAxis = closest; if ( editSpeakerHandle.activeAxis >= 0 ) { VectorCopy( editSpeakerHandle.origin, editSpeakerHandle.oldOrigin ); } ; } break; case K_ESCAPE: BG_PanelButtons_SetFocusButton( NULL ); CG_SaveSpeakersToScript(); editSpeakerActive = qfalse; CG_EventHandling( -CGAME_EVENT_SPEAKEREDITOR, qtrue ); break; } } } void CG_SpeakerEditorMouseMove_Handling( int x, int y ) { if ( !cg.editingSpeakers ) { return; } if ( editSpeakerActive ) { if ( editSpeakerHandle.activeAxis >= 0 ) { if ( editSpeakerHandle.activeAxis == 0 ) { // this one and the next one are quite nasty, so do it the hacky way if ( cgs.cursorX - x < 320 ) { editSpeaker->origin[0] -= x; } else { editSpeaker->origin[0] += x; } } else if ( editSpeakerHandle.activeAxis == 1 ) { if ( cgs.cursorX - x < 320 ) { editSpeaker->origin[1] -= x; } else { editSpeaker->origin[1] += x; } } else if ( editSpeakerHandle.activeAxis == 2 ) { // but this one is easy editSpeaker->origin[2] -= y; } cgs.cursorX -= x; cgs.cursorY -= y; VectorCopy( editSpeakerHandle.origin, editSpeakerHandle.oldOrigin ); VectorCopy( editSpeaker->origin, editSpeakerHandle.origin ); } } } void CG_ActivateEditSoundMode( void ) { CG_Printf( "Activating Speaker Edit mode.\n" ); cg.editingSpeakers = qtrue; editSpeaker = NULL; editSpeakerActive = qfalse; editSpeakerHandle.activeAxis = -1; undoSpeakerIndex = -2; if ( !speakerShader ) { speakerShader = trap_R_RegisterShader( "gfx/misc/speaker" ); speakerShaderGrayScale = trap_R_RegisterShader( "gfx/misc/speaker_gs" ); BG_PanelButtonsSetup( speakerInfoButtons ); BG_PanelButtonsSetup( speakerEditorButtons ); } } void CG_DeActivateEditSoundMode( void ) { CG_Printf( "De-activating Speaker Edit mode.\n" ); cg.editingSpeakers = qfalse; if ( editSpeakerActive ) { CG_EventHandling( -CGAME_EVENT_SPEAKEREDITOR, qtrue ); } editSpeaker = NULL; editSpeakerActive = qfalse; editSpeakerHandle.activeAxis = -1; undoSpeakerIndex = -2; } void CG_ModifyEditSpeaker( void ) { if ( !editSpeaker || editSpeakerActive ) { return; } CG_EventHandling( CGAME_EVENT_SPEAKEREDITOR, qfalse ); editSpeakerActive = qtrue; memcpy( &undoSpeaker, editSpeaker, sizeof( undoSpeaker ) ); undoSpeakerIndex = BG_GetIndexForSpeaker( editSpeaker ); VectorCopy( editSpeaker->origin, editSpeakerHandle.origin ); VectorCopy( editSpeaker->origin, editSpeakerHandle.oldOrigin ); Q_strncpyz( noiseEditBuffer, editSpeaker->filename, sizeof( noiseEditBuffer ) ); Q_strncpyz( targetnameEditBuffer, editSpeaker->targetname, sizeof( targetnameEditBuffer ) ); speakerEditorLoopedDropdown.data[1] = editSpeaker->loop; speakerEditorBroadcastDropdown.data[1] = editSpeaker->broadcast; Com_sprintf( waitEditBuffer, sizeof( waitEditBuffer ), "%i", editSpeaker->wait ); Com_sprintf( randomEditBuffer, sizeof( randomEditBuffer ), "%i", editSpeaker->random ); Com_sprintf( volumeEditBuffer, sizeof( volumeEditBuffer ), "%i", editSpeaker->volume ); Com_sprintf( rangeEditBuffer, sizeof( rangeEditBuffer ), "%i", editSpeaker->range ); } void CG_UndoEditSpeaker( void ) { if ( undoSpeakerIndex == -2 ) { return; } if ( undoSpeakerIndex == -1 ) { if ( !BG_SS_StoreSpeaker( &undoSpeaker ) ) { CG_Printf( S_COLOR_YELLOW "UNDO: restoring deleted speaker failed, no storage memory for speaker\n" ); } else { CG_Printf( "UNDO: restored deleted speaker at %.2f %.2f %.2f.\n", undoSpeaker.origin[0], undoSpeaker.origin[1], undoSpeaker.origin[2] ); } } else { bg_speaker_t *speaker = BG_GetScriptSpeaker( undoSpeakerIndex ); memcpy( speaker, &undoSpeaker, sizeof( *speaker ) ); CG_Printf( "UNDO: restoring modified settings of speaker at %.2f %.2f %.2f.\n", undoSpeaker.origin[0], undoSpeaker.origin[1], undoSpeaker.origin[2] ); } CG_SaveSpeakersToScript(); undoSpeakerIndex = -2; } // Normal Use void CG_ToggleActiveOnScriptSpeaker( int index ) { bg_speaker_t *speaker = BG_GetScriptSpeaker( index ); if ( speaker ) { speaker->activated = !speaker->activated; } } void CG_UnsetActiveOnScriptSpeaker( int index ) { bg_speaker_t *speaker = BG_GetScriptSpeaker( index ); if ( speaker ) { speaker->activated = qfalse; } } void CG_SetActiveOnScriptSpeaker( int index ) { bg_speaker_t *speaker = BG_GetScriptSpeaker( index ); if ( speaker ) { speaker->activated = qtrue; } } static void CG_PlayScriptSpeaker( bg_speaker_t *speaker, qboolean global ) { switch ( speaker->loop ) { case S_LT_NOT_LOOPED: if ( global ) { trap_S_StartLocalSound( speaker->noise, CHAN_ITEM ); } else { trap_S_StartSoundVControl( speaker->origin, -1, CHAN_ITEM, speaker->noise, speaker->volume ); } break; case S_LT_LOOPED_ON: case S_LT_LOOPED_OFF: if ( speaker->soundTime == 0 ) { speaker->soundTime = trap_S_GetCurrentSoundTime(); } trap_S_AddRealLoopingSound( speaker->origin, vec3_origin, speaker->noise, speaker->range, speaker->volume, speaker->soundTime ); break; } } void CG_AddScriptSpeakers( void ) { int i; bg_speaker_t *speaker; if ( cg.editingSpeakers ) { CG_RenderScriptSpeakers(); } for ( i = 0; i < BG_NumScriptSpeakers(); i++ ) { speaker = BG_GetScriptSpeaker( i ); // don't bother playing missing sounds if ( !speaker->noise ) { continue; } // activate if needed if ( speaker->loop == S_LT_NOT_LOOPED ) { if ( cg.time >= speaker->nextActivateTime && ( speaker->wait || speaker->random ) ) { speaker->activated = qtrue; speaker->nextActivateTime = cg.time + speaker->wait + speaker->random * crandom(); } } if ( !speaker->activated ) { speaker->soundTime = 0; continue; } switch ( speaker->broadcast ) { case S_BT_LOCAL: if ( trap_R_inPVS( cg.refdef_current->vieworg, speaker->origin ) ) { CG_PlayScriptSpeaker( speaker, qfalse ); } break; case S_BT_GLOBAL: CG_PlayScriptSpeaker( speaker, qtrue ); break; case S_BT_NOPVS: CG_PlayScriptSpeaker( speaker, qfalse ); break; } if ( speaker->loop == S_LT_NOT_LOOPED ) { speaker->activated = qfalse; } } }