1 /* -------------------------------------------------------------------------------
3 Copyright (C) 1999-2007 id Software, Inc. and contributors.
4 For a list of contributors, see the accompanying CONTRIBUTORS file.
6 This file is part of GtkRadiant.
8 GtkRadiant is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or
11 (at your option) any later version.
13 GtkRadiant is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with GtkRadiant; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22 ----------------------------------------------------------------------------------
24 This code has been altered significantly from its original form, to support
25 several games based on the Quake III Arena engine, in the form of "Q3Map2."
27 ------------------------------------------------------------------------------- */
32 #define BSPFILE_ABSTRACT_C
42 /* -------------------------------------------------------------------------------
44 this file was copied out of the common directory in order to not break
45 compatibility with the q3map 1.x tree. it was moved out in order to support
46 the raven bsp format (RBSP) used in soldier of fortune 2 and jedi knight 2.
48 since each game has its own set of particular features, the data structures
49 below no longer directly correspond to the binary format of a particular game.
51 the translation will be done at bsp load/save time to keep any sort of
52 special-case code messiness out of the rest of the program.
54 ------------------------------------------------------------------------------- */
58 /* FIXME: remove the functions below that handle memory management of bsp file chunks */
60 int numBSPDrawVertsBuffer = 0;
64 if ( bspDrawVerts == 0 ) {
65 numBSPDrawVertsBuffer = 1024;
67 bspDrawVerts = safe_malloc_info( sizeof( bspDrawVert_t ) * numBSPDrawVertsBuffer, "IncDrawVerts" );
70 else if ( numBSPDrawVerts > numBSPDrawVertsBuffer ) {
71 numBSPDrawVertsBuffer *= 3; // multiply by 1.5
72 numBSPDrawVertsBuffer /= 2;
74 bspDrawVerts = realloc( bspDrawVerts, sizeof( bspDrawVert_t ) * numBSPDrawVertsBuffer );
76 if ( !bspDrawVerts ) {
77 Error( "realloc() failed (IncDrawVerts)" );
81 memset( bspDrawVerts + ( numBSPDrawVerts - 1 ), 0, sizeof( bspDrawVert_t ) );
84 void SetDrawVerts( int n ){
85 if ( bspDrawVerts != 0 ) {
90 numBSPDrawVertsBuffer = numBSPDrawVerts;
92 bspDrawVerts = safe_malloc0_info( sizeof( bspDrawVert_t ) * numBSPDrawVertsBuffer, "IncDrawVerts" );
95 int numBSPDrawSurfacesBuffer = 0;
96 void SetDrawSurfacesBuffer(){
97 if ( bspDrawSurfaces != 0 ) {
98 free( bspDrawSurfaces );
101 numBSPDrawSurfacesBuffer = MAX_MAP_DRAW_SURFS;
103 bspDrawSurfaces = safe_malloc0_info( sizeof( bspDrawSurface_t ) * numBSPDrawSurfacesBuffer, "IncDrawSurfaces" );
106 void SetDrawSurfaces( int n ){
107 if ( bspDrawSurfaces != 0 ) {
108 free( bspDrawSurfaces );
111 numBSPDrawSurfaces = n;
112 numBSPDrawSurfacesBuffer = numBSPDrawSurfaces;
114 bspDrawSurfaces = safe_malloc0_info( sizeof( bspDrawSurface_t ) * numBSPDrawSurfacesBuffer, "IncDrawSurfaces" );
117 void BSPFilesCleanup(){
118 if ( bspDrawVerts != 0 ) {
119 free( bspDrawVerts );
121 if ( bspDrawSurfaces != 0 ) {
122 free( bspDrawSurfaces );
124 if ( bspLightBytes != 0 ) {
125 free( bspLightBytes );
127 if ( bspGridPoints != 0 ) {
128 free( bspGridPoints );
139 if all values are 32 bits, this can be used to swap everything
142 void SwapBlock( int *block, int size ){
147 if ( block == NULL ) {
153 for ( i = 0; i < size; i++ )
154 block[ i ] = LittleLong( block[ i ] );
161 byte swaps all data in the abstract bsp
164 void SwapBSPFile( void ){
169 SwapBlock( (int*) bspModels, numBSPModels * sizeof( bspModels[ 0 ] ) );
171 /* shaders (don't swap the name) */
172 for ( i = 0; i < numBSPShaders ; i++ )
175 si = ShaderInfoForShader( bspShaders[ i ].shader );
176 if ( si->remapShader && si->remapShader[ 0 ] ) {
177 strcpy( bspShaders[ i ].shader, si->remapShader );
180 bspShaders[ i ].contentFlags = LittleLong( bspShaders[ i ].contentFlags );
181 bspShaders[ i ].surfaceFlags = LittleLong( bspShaders[ i ].surfaceFlags );
185 SwapBlock( (int*) bspPlanes, numBSPPlanes * sizeof( bspPlanes[ 0 ] ) );
188 SwapBlock( (int*) bspNodes, numBSPNodes * sizeof( bspNodes[ 0 ] ) );
191 SwapBlock( (int*) bspLeafs, numBSPLeafs * sizeof( bspLeafs[ 0 ] ) );
194 SwapBlock( (int*) bspLeafSurfaces, numBSPLeafSurfaces * sizeof( bspLeafSurfaces[ 0 ] ) );
197 SwapBlock( (int*) bspLeafBrushes, numBSPLeafBrushes * sizeof( bspLeafBrushes[ 0 ] ) );
200 SwapBlock( (int*) bspBrushes, numBSPBrushes * sizeof( bspBrushes[ 0 ] ) );
203 SwapBlock( (int*) bspBrushSides, numBSPBrushSides * sizeof( bspBrushSides[ 0 ] ) );
206 ( (int*) &bspVisBytes )[ 0 ] = LittleLong( ( (int*) &bspVisBytes )[ 0 ] );
207 ( (int*) &bspVisBytes )[ 1 ] = LittleLong( ( (int*) &bspVisBytes )[ 1 ] );
209 /* drawverts (don't swap colors) */
210 for ( i = 0; i < numBSPDrawVerts; i++ )
212 bspDrawVerts[ i ].xyz[ 0 ] = LittleFloat( bspDrawVerts[ i ].xyz[ 0 ] );
213 bspDrawVerts[ i ].xyz[ 1 ] = LittleFloat( bspDrawVerts[ i ].xyz[ 1 ] );
214 bspDrawVerts[ i ].xyz[ 2 ] = LittleFloat( bspDrawVerts[ i ].xyz[ 2 ] );
215 bspDrawVerts[ i ].normal[ 0 ] = LittleFloat( bspDrawVerts[ i ].normal[ 0 ] );
216 bspDrawVerts[ i ].normal[ 1 ] = LittleFloat( bspDrawVerts[ i ].normal[ 1 ] );
217 bspDrawVerts[ i ].normal[ 2 ] = LittleFloat( bspDrawVerts[ i ].normal[ 2 ] );
218 bspDrawVerts[ i ].st[ 0 ] = LittleFloat( bspDrawVerts[ i ].st[ 0 ] );
219 bspDrawVerts[ i ].st[ 1 ] = LittleFloat( bspDrawVerts[ i ].st[ 1 ] );
220 for ( j = 0; j < MAX_LIGHTMAPS; j++ )
222 bspDrawVerts[ i ].lightmap[ j ][ 0 ] = LittleFloat( bspDrawVerts[ i ].lightmap[ j ][ 0 ] );
223 bspDrawVerts[ i ].lightmap[ j ][ 1 ] = LittleFloat( bspDrawVerts[ i ].lightmap[ j ][ 1 ] );
228 SwapBlock( (int*) bspDrawIndexes, numBSPDrawIndexes * sizeof( bspDrawIndexes[0] ) );
231 /* note: rbsp files (and hence q3map2 abstract bsp) have byte lightstyles index arrays, this follows sof2map convention */
232 SwapBlock( (int*) bspDrawSurfaces, numBSPDrawSurfaces * sizeof( bspDrawSurfaces[ 0 ] ) );
235 for ( i = 0; i < numBSPFogs; i++ )
237 bspFogs[ i ].brushNum = LittleLong( bspFogs[ i ].brushNum );
238 bspFogs[ i ].visibleSide = LittleLong( bspFogs[ i ].visibleSide );
242 for ( i = 0; i < numBSPAds; i++ )
244 bspAds[ i ].cellId = LittleLong( bspAds[ i ].cellId );
245 bspAds[ i ].normal[ 0 ] = LittleFloat( bspAds[ i ].normal[ 0 ] );
246 bspAds[ i ].normal[ 1 ] = LittleFloat( bspAds[ i ].normal[ 1 ] );
247 bspAds[ i ].normal[ 2 ] = LittleFloat( bspAds[ i ].normal[ 2 ] );
249 for ( j = 0; j < 4; j++ )
251 bspAds[ i ].rect[j][ 0 ] = LittleFloat( bspAds[ i ].rect[j][ 0 ] );
252 bspAds[ i ].rect[j][ 1 ] = LittleFloat( bspAds[ i ].rect[j][ 1 ] );
253 bspAds[ i ].rect[j][ 2 ] = LittleFloat( bspAds[ i ].rect[j][ 2 ] );
256 //bspAds[ i ].model[ MAX_QPATH ];
262 gets the number of elements in a bsp lump
265 int GetLumpElements( bspHeader_t *header, int lump, int size ){
266 /* check for odd size */
267 if ( header->lumps[ lump ].length % size ) {
269 Sys_FPrintf( SYS_WRN, "WARNING: GetLumpElements: odd lump size (%d) in lump %d\n", header->lumps[ lump ].length, lump );
273 Error( "GetLumpElements: odd lump size (%d) in lump %d", header->lumps[ lump ].length, lump );
277 /* return element count */
278 return header->lumps[ lump ].length / size;
285 returns a pointer to the specified lump
288 void *GetLump( bspHeader_t *header, int lump ){
289 return (void*)( (byte*) header + header->lumps[ lump ].offset );
296 copies a bsp file lump into a destination buffer
299 int CopyLump( bspHeader_t *header, int lump, void *dest, int size ){
303 /* get lump length and offset */
304 length = header->lumps[ lump ].length;
305 offset = header->lumps[ lump ].offset;
307 /* handle erroneous cases */
311 if ( length % size ) {
313 Sys_FPrintf( SYS_WRN, "WARNING: CopyLump: odd lump size (%d) in lump %d\n", length, lump );
317 Error( "CopyLump: odd lump size (%d) in lump %d", length, lump );
321 /* copy block of memory and return */
322 memcpy( dest, (byte*) header + offset, length );
323 return length / size;
326 int CopyLump_Allocate( bspHeader_t *header, int lump, void **dest, int size, int *allocationVariable ){
327 /* get lump length and offset */
328 *allocationVariable = header->lumps[ lump ].length / size;
329 *dest = realloc( *dest, size * *allocationVariable );
330 return CopyLump( header, lump, *dest, size );
336 adds a lump to an outgoing bsp file
339 void AddLump( FILE *file, bspHeader_t *header, int lumpNum, const void *data, int length ){
342 /* add lump to bsp file header */
343 lump = &header->lumps[ lumpNum ];
344 lump->offset = LittleLong( ftell( file ) );
345 lump->length = LittleLong( length );
347 /* write lump to file */
348 SafeWrite( file, data, length );
350 /* write padding zeros */
351 SafeWrite( file, (const byte[3]){ 0, 0, 0 }, ( ( length + 3 ) & ~3 ) - length );
358 loads a bsp file into memory
361 void LoadBSPFile( const char *filename ){
363 if ( game == NULL || game->load == NULL ) {
364 Error( "LoadBSPFile: unsupported BSP file format" );
367 /* load it, then byte swap the in-memory version */
368 game->load( filename );
374 partially loads a bsp file into memory
378 void PartialLoadBSPFile( const char *filename ){
380 if ( game == NULL || game->load == NULL ) {
381 Error( "LoadBSPFile: unsupported BSP file format" );
384 /* load it, then byte swap the in-memory version */
385 //game->load( filename );
386 PartialLoadIBSPFile( filename );
388 /* PartialSwapBSPFile() */
392 /* shaders (don't swap the name) */
393 for ( i = 0; i < numBSPShaders ; i++ )
395 bspShaders[ i ].contentFlags = LittleLong( bspShaders[ i ].contentFlags );
396 bspShaders[ i ].surfaceFlags = LittleLong( bspShaders[ i ].surfaceFlags );
400 /* note: rbsp files (and hence q3map2 abstract bsp) have byte lightstyles index arrays, this follows sof2map convention */
401 SwapBlock( (int*) bspDrawSurfaces, numBSPDrawSurfaces * sizeof( bspDrawSurfaces[ 0 ] ) );
409 void WriteBSPFile( const char *filename ){
410 char tempname[ 1024 ];
415 if ( game == NULL || game->write == NULL ) {
416 Error( "WriteBSPFile: unsupported BSP file format" );
419 /* make fake temp name so existing bsp file isn't damaged in case write process fails */
421 sprintf( tempname, "%s.%08X", filename, (int) tm );
423 /* byteswap, write the bsp, then swap back so it can be manipulated further */
425 game->write( tempname );
428 /* replace existing bsp file */
430 rename( tempname, filename );
437 dumps info about current file
440 void PrintBSPFileSizes( void ){
441 /* parse entities first */
442 if ( numEntities <= 0 ) {
446 /* note that this is abstracted */
447 Sys_Printf( "Abstracted BSP file components (*actual sizes may differ)\n" );
449 /* print various and sundry bits */
450 Sys_Printf( "%9d models %9d\n",
451 numBSPModels, (int) ( numBSPModels * sizeof( bspModel_t ) ) );
452 Sys_Printf( "%9d shaders %9d\n",
453 numBSPShaders, (int) ( numBSPShaders * sizeof( bspShader_t ) ) );
454 Sys_Printf( "%9d brushes %9d\n",
455 numBSPBrushes, (int) ( numBSPBrushes * sizeof( bspBrush_t ) ) );
456 Sys_Printf( "%9d brushsides %9d *\n",
457 numBSPBrushSides, (int) ( numBSPBrushSides * sizeof( bspBrushSide_t ) ) );
458 Sys_Printf( "%9d fogs %9d\n",
459 numBSPFogs, (int) ( numBSPFogs * sizeof( bspFog_t ) ) );
460 Sys_Printf( "%9d planes %9d\n",
461 numBSPPlanes, (int) ( numBSPPlanes * sizeof( bspPlane_t ) ) );
462 Sys_Printf( "%9d entdata %9d\n",
463 numEntities, bspEntDataSize );
466 Sys_Printf( "%9d nodes %9d\n",
467 numBSPNodes, (int) ( numBSPNodes * sizeof( bspNode_t ) ) );
468 Sys_Printf( "%9d leafs %9d\n",
469 numBSPLeafs, (int) ( numBSPLeafs * sizeof( bspLeaf_t ) ) );
470 Sys_Printf( "%9d leafsurfaces %9d\n",
471 numBSPLeafSurfaces, (int) ( numBSPLeafSurfaces * sizeof( *bspLeafSurfaces ) ) );
472 Sys_Printf( "%9d leafbrushes %9d\n",
473 numBSPLeafBrushes, (int) ( numBSPLeafBrushes * sizeof( *bspLeafBrushes ) ) );
476 Sys_Printf( "%9d drawsurfaces %9d *\n",
477 numBSPDrawSurfaces, (int) ( numBSPDrawSurfaces * sizeof( *bspDrawSurfaces ) ) );
478 Sys_Printf( "%9d drawverts %9d *\n",
479 numBSPDrawVerts, (int) ( numBSPDrawVerts * sizeof( *bspDrawVerts ) ) );
480 Sys_Printf( "%9d drawindexes %9d\n",
481 numBSPDrawIndexes, (int) ( numBSPDrawIndexes * sizeof( *bspDrawIndexes ) ) );
484 Sys_Printf( "%9d lightmaps %9d\n",
485 numBSPLightBytes / ( game->lightmapSize * game->lightmapSize * 3 ), numBSPLightBytes );
486 Sys_Printf( "%9d lightgrid %9d *\n",
487 numBSPGridPoints, (int) ( numBSPGridPoints * sizeof( *bspGridPoints ) ) );
488 Sys_Printf( " visibility %9d\n",
494 /* -------------------------------------------------------------------------------
498 ------------------------------------------------------------------------------- */
503 strips low byte chars off the end of a string
506 void StripTrailing( char *e ){
510 s = e + strlen( e ) - 1;
511 while ( s >= e && *s <= 32 )
522 parses a single quoted "key" "value" pair into an epair struct
525 epair_t *ParseEPair( void ){
529 /* allocate and clear new epair */
530 e = safe_malloc0( sizeof( epair_t ) );
533 if ( strlen( token ) >= ( MAX_KEY - 1 ) ) {
534 Error( "ParseEPair: token too long" );
537 e->key = copystring( token );
541 if ( strlen( token ) >= MAX_VALUE - 1 ) {
542 Error( "ParseEpar: token too long" );
544 e->value = copystring( token );
546 /* strip trailing spaces that sometimes get accidentally added in the editor */
547 StripTrailing( e->key );
548 StripTrailing( e->value );
558 parses an entity's epairs
561 qboolean ParseEntity( void ){
566 if ( !GetToken( qtrue ) ) {
569 if ( strcmp( token, "{" ) ) {
570 Error( "ParseEntity: { not found" );
572 AUTOEXPAND_BY_REALLOC( entities, numEntities, allocatedEntities, 32 );
574 /* create new entity */
575 mapEnt = &entities[ numEntities ];
577 memset( mapEnt, 0, sizeof( *mapEnt ) );
582 if ( !GetToken( qtrue ) ) {
583 Error( "ParseEntity: EOF without closing brace" );
585 if ( !EPAIR_STRCMP( token, "}" ) ) {
589 e->next = mapEnt->epairs;
593 /* return to sender */
601 parses the bsp entity data string into entities
604 void ParseEntities( void ){
606 ParseFromMemory( bspEntData, bspEntDataSize );
607 while ( ParseEntity() ) ;
609 /* ydnar: set number of bsp entities in case a map is loaded on top */
610 numBSPEntities = numEntities;
616 * must be called before UnparseEntities
618 void InjectCommandLine( char **argv, int beginArgs, int endArgs ){
619 const char *previousCommandLine;
620 char newCommandLine[1024];
622 char *outpos = newCommandLine;
623 char *sentinel = newCommandLine + sizeof( newCommandLine ) - 1;
630 previousCommandLine = ValueForKey( &entities[0], "_q3map2_cmdline" );
631 if ( previousCommandLine && *previousCommandLine ) {
632 inpos = previousCommandLine;
633 while ( outpos != sentinel && *inpos )
634 *outpos++ = *inpos++;
635 if ( outpos != sentinel ) {
638 if ( outpos != sentinel ) {
643 for ( i = beginArgs; i < endArgs; ++i )
645 if ( argv[i] == NULL ) {
648 if ( outpos != sentinel && i != beginArgs ) {
652 while ( outpos != sentinel && *inpos )
653 if ( *inpos != '\\' && *inpos != '"' && *inpos != ';' && (unsigned char) *inpos >= ' ' ) {
654 *outpos++ = *inpos++;
659 SetKeyValue( &entities[0], "_q3map2_cmdline", newCommandLine );
660 SetKeyValue( &entities[0], "_q3map2_version", Q3MAP_VERSION );
667 generates the dentdata string from all the entities.
668 this allows the utilities to add or remove key/value
669 pairs to the data created by the map editor
672 void UnparseEntities( void ){
677 char key[ 1024 ], value[ 1024 ];
682 AUTOEXPAND_BY_REALLOC( bspEntData, 0, allocatedBSPEntData, 1024 );
688 /* run through entity list */
689 for ( i = 0; i < numBSPEntities && i < numEntities; i++ )
693 AUTOEXPAND_BY_REALLOC( bspEntData, sz + 65536, allocatedBSPEntData, 1024 );
699 ep = entities[ i ].epairs;
701 continue; /* ent got removed */
704 /* ydnar: certain entities get stripped from bsp file */
705 value2 = ValueForKey( &entities[ i ], "classname" );
706 if ( !Q_stricmp( value2, "misc_model" ) ||
707 !Q_stricmp( value2, "_decal" ) ||
708 !Q_stricmp( value2, "_skybox" ) ) {
712 /* add beginning brace */
713 strcat( end, "{\n" );
716 /* walk epair list */
717 for ( ep = entities[ i ].epairs; ep != NULL; ep = ep->next )
720 strcpy( key, ep->key );
721 StripTrailing( key );
722 strcpy( value, ep->value );
723 StripTrailing( value );
726 sprintf( line, "\"%s\" \"%s\"\n", key, value );
728 end += strlen( line );
731 /* add trailing brace */
735 /* check for overflow */
736 if ( end > buf + allocatedBSPEntData ) {
737 Error( "Entity text too long" );
742 bspEntDataSize = end - buf + 1;
749 prints an entity's epairs to the console
752 void PrintEntity( const entity_t *ent ){
756 Sys_Printf( "------- entity %p -------\n", ent );
757 for ( ep = ent->epairs; ep != NULL; ep = ep->next )
758 Sys_Printf( "%s = %s\n", ep->key, ep->value );
766 sets an epair in an entity
769 void SetKeyValue( entity_t *ent, const char *key, const char *value ){
773 /* check for existing epair */
774 for ( ep = ent->epairs; ep != NULL; ep = ep->next )
776 if ( !EPAIR_STRCMP( ep->key, key ) ) {
778 ep->value = copystring( value );
783 /* create new epair */
784 ep = safe_malloc( sizeof( *ep ) );
785 ep->next = ent->epairs;
787 ep->key = copystring( key );
788 ep->value = copystring( value );
795 returns true if entity has this key
798 qboolean KeyExists( const entity_t *ent, const char *key ){
801 /* walk epair list */
802 for ( ep = ent->epairs; ep != NULL; ep = ep->next )
804 if ( !EPAIR_STRCMP( ep->key, key ) ) {
817 gets the value for an entity key
820 const char *ValueForKey( const entity_t *ent, const char *key ){
829 /* walk epair list */
830 for ( ep = ent->epairs; ep != NULL; ep = ep->next )
832 if ( !EPAIR_STRCMP( ep->key, key ) ) {
837 /* if no match, return empty string */
845 gets the integer point value for an entity key
848 int IntForKey( const entity_t *ent, const char *key ){
852 k = ValueForKey( ent, key );
860 gets the floating point value for an entity key
863 vec_t FloatForKey( const entity_t *ent, const char *key ){
867 k = ValueForKey( ent, key );
875 gets a 3-element vector value for an entity key
878 qboolean GetVectorForKey( const entity_t *ent, const char *key, vec3_t vec ){
884 k = ValueForKey( ent, key );
886 /* scanf into doubles, then assign, so it is vec_t size independent */
888 sscanf( k, "%lf %lf %lf", &v1, &v2, &v3 );
893 /* true if the key is found, false otherwise */
901 finds an entity target
904 entity_t *FindTargetEntity( const char *target ){
909 /* walk entity list */
910 for ( i = 0; i < numEntities; i++ )
912 n = ValueForKey( &entities[ i ], "targetname" );
913 if ( !strcmp( n, target ) ) {
914 return &entities[ i ];
925 GetEntityShadowFlags() - ydnar
926 gets an entity's shadow flags
927 note: does not set them to defaults if the keys are not found!
930 void GetEntityShadowFlags( const entity_t *ent, const entity_t *ent2, int *castShadows, int *recvShadows ){
933 /* get cast shadows */
934 if ( castShadows != NULL ) {
935 value = ValueForKey( ent, "_castShadows" );
936 if ( value[ 0 ] == '\0' ) {
937 value = ValueForKey( ent, "_cs" );
939 if ( value[ 0 ] == '\0' ) {
940 value = ValueForKey( ent2, "_castShadows" );
942 if ( value[ 0 ] == '\0' ) {
943 value = ValueForKey( ent2, "_cs" );
945 if ( value[ 0 ] != '\0' ) {
946 *castShadows = atoi( value );
951 if ( recvShadows != NULL ) {
952 value = ValueForKey( ent, "_receiveShadows" );
953 if ( value[ 0 ] == '\0' ) {
954 value = ValueForKey( ent, "_rs" );
956 if ( value[ 0 ] == '\0' ) {
957 value = ValueForKey( ent2, "_receiveShadows" );
959 if ( value[ 0 ] == '\0' ) {
960 value = ValueForKey( ent2, "_rs" );
962 if ( value[ 0 ] != '\0' ) {
963 *recvShadows = atoi( value );
967 /* vortex: game-specific default entity keys */
968 value = ValueForKey( ent, "classname" );
969 if ( !Q_stricmp( game->magic, "dq" ) || !Q_stricmp( game->magic, "prophecy" ) ) {
970 /* vortex: deluxe quake default shadow flags */
971 if ( !Q_stricmp( value, "func_wall" ) ) {
972 if ( recvShadows != NULL ) {
975 if ( castShadows != NULL ) {