/* Copyright (C) 1999-2007 id Software, Inc. and contributors. For a list of contributors, see the accompanying CONTRIBUTORS file. This file is part of GtkRadiant. GtkRadiant 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. GtkRadiant 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 GtkRadiant; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ // Hydra - FIXME : TTimo, We need to know what game + engine we're using for // the halflife (not Q2) specific stuff // we need an API for modules to get this info! // // parses quake3 map format into internal objects // #include "plugin.h" // cmdlib extern void ExtractFileName( const char *path, char *dest ); extern int g_MapVersion; int abortcode; // see imap.h for values. // Start of half-life specific stuff GSList *g_WadList; // halflife specific. GSList *g_TextureNameCache; // halflife specific. // NOTE TTimo: yuck.. void FreeGSList( GSList *l ){ while ( l ) { free( l->data ); l = g_slist_remove( l, l->data ); } } // NOTE TTimo: ideally, this would be using Str functions instead void trim( char *str ){ int len; len = strlen( str ); while ( str[--len] == ' ' ) str[len] = 0; } void BuildWadList( char *wadstr ){ char wads[2048]; // change to CString usage ? wads[0] = 0; char *p1,*p2; char cleanwadname[QER_MAX_NAMELEN]; g_WadList = NULL; strcpy( wads,wadstr ); QE_ConvertDOSToUnixName( wads,wads ); // ok, we got the list of ; delimited wads, now split it into a GSList that contains // just the wad names themselves. p1 = wads; do { p2 = strchr( p1,';' ); if ( p2 ) { *p2 = 0; // swap the ; with a null terminator } if ( strchr( p1,'/' ) || strchr( p1,'\\' ) ) { ExtractFileName( p1,cleanwadname ); trim( cleanwadname ); if ( *cleanwadname ) { g_WadList = g_slist_append( g_WadList, strdup( cleanwadname ) ); Sys_Printf( "wad: %s\n",cleanwadname ); } } else { trim( p1 ); if ( *p1 ) { g_WadList = g_slist_append( g_WadList, strdup( p1 ) ); Sys_Printf( "wad: %s\n",p1 ); } } if ( p2 ) { p1 = p2 + 1; // point back to the remainder of the string } else{ p1 = NULL; // make it so we exit the loop. } } while ( p1 ); // strip the ".wad" extensions. for ( GSList *l = g_WadList; l != NULL ; l = l->next ) { p1 = (char *)l->data; if ( p1[strlen( p1 ) - 4] == '.' ) { p1[strlen( p1 ) - 4] = 0; } } } // FIXME: usefulness of this cache sounds very discutable char *CheckCacheForTextureName( const char *cleantexturename ){ char *str; int len; GSList *l; // search our little cache first to speed things up. // cache strings are stored as ";" len = strlen( cleantexturename ); for ( l = g_TextureNameCache; l != NULL ; l = l->next ) { str = (char *)l->data; if ( ( strnicmp( cleantexturename,str,len ) == 0 ) && ( str[len] == ';' ) ) { // must do in this order or we'll get an access violation, even though it's slower. return ( str + len + 1 ); // skip the delimiter ; } } return NULL; } char *AddToCache( const char *cleantexturename, const char *actualname ){ char *cachestr; cachestr = (char *)malloc( strlen( cleantexturename ) + 1 + strlen( actualname ) + 1 ); // free()'d when g_TextureNameCache is freed sprintf( cachestr,"%s;%s",cleantexturename,actualname ); g_TextureNameCache = g_slist_append( g_TextureNameCache, cachestr ); return cachestr; } char *SearchWadsForTextureName( const char *cleantexturename ){ char *str; char *wadname; char *actualtexturename = NULL; GSList *l; int count; actualtexturename = CheckCacheForTextureName( cleantexturename ); if ( actualtexturename ) { return actualtexturename; } // still here ? guess it's not in the cache then! // search the wads listed in the worldspawn "wad" key for ( l = g_WadList; l != NULL && actualtexturename == NULL ; l = l->next ) { wadname = (char *)l->data; str = new char[strlen( wadname ) + strlen( cleantexturename ) + 9 + 1 + 4 + 1]; // hlw here is ok as we never have anything other than hlw files in a wad. sprintf( str,"textures/%s/%s.hlw",wadname,cleantexturename ); count = vfsGetFileCount( str, VFS_SEARCH_PAK ); // only search pack files // LordHavoc: hacked in .mip loading here if ( !count ) { sprintf( str,"textures/%s/%s.mip",wadname,cleantexturename ); count = vfsGetFileCount( str, VFS_SEARCH_PAK ); // only search pack files } if ( count > 0 ) { // strip the extension, build the cache string and add the the cache str[strlen( str ) - 4] = 0; actualtexturename = AddToCache( cleantexturename,str ); //point the return value to the actual name, not what we add to the cache actualtexturename += 1 + strlen( cleantexturename ); } delete [] str; } return actualtexturename; } // End of half-life specific stuff void Patch_Parse( patchMesh_t *pPatch ){ int i, j; char *str; char *token = Token(); GetToken( true ); //{ // parse shader name GetToken( true ); str = new char[strlen( token ) + 10]; strcpy( str, "textures/" ); strcpy( str + 9, token ); pPatch->pShader = QERApp_Shader_ForName( str ); pPatch->d_texture = pPatch->pShader->getTexture(); delete [] str; GetToken( true ); //( // parse matrix dimensions GetToken( false ); pPatch->width = atoi( token ); GetToken( false ); pPatch->height = atoi( token ); // ignore contents/flags/value GetToken( false ); GetToken( false ); GetToken( false ); GetToken( false ); //) // parse matrix GetToken( true ); //( for ( i = 0; i < pPatch->width; i++ ) { GetToken( true ); //( for ( j = 0; j < pPatch->height; j++ ) { GetToken( false ); //( GetToken( false ); pPatch->ctrl[i][j].xyz[0] = atof( token ); GetToken( false ); pPatch->ctrl[i][j].xyz[1] = atof( token ); GetToken( false ); pPatch->ctrl[i][j].xyz[2] = atof( token ); GetToken( false ); pPatch->ctrl[i][j].st[0] = atof( token ); GetToken( false ); pPatch->ctrl[i][j].st[1] = atof( token ); GetToken( false ); //) } GetToken( false ); //) } GetToken( true ); //) GetToken( true ); //} } void Face_Parse( face_t *face, bool bAlternateTexdef = false ){ int i, j; char *str; bool bworldcraft = false; char *token = Token(); // parse planepts str = NULL; for ( i = 0; i < 3; i++ ) { GetToken( true ); //( for ( j = 0; j < 3; j++ ) { GetToken( false ); face->planepts[i][j] = atof( token ); } GetToken( false ); //) } if ( bAlternateTexdef ) { // parse alternate texdef GetToken( false ); // ( GetToken( false ); // ( for ( i = 0; i < 3; i++ ) { GetToken( false ); face->brushprimit_texdef.coords[0][i] = atof( token ); } GetToken( false ); // ) GetToken( false ); // ( for ( i = 0; i < 3; i++ ) { GetToken( false ); face->brushprimit_texdef.coords[1][i] = atof( token ); } GetToken( false ); // ) GetToken( false ); // ) } // parse shader name GetToken( false ); // shader // if we're loading a halflife map then we don't have a relative texture name // we just get . So we need to convert this to a relative name // like this: "textures//shader", so we use vfsFileFile to get the filename. // *** IMPORTANT *** // For Halflife we need to see if the texture is in wads listed in the // map's worldspawn "wad" e-pair. If we don't then the image used will be the // first image with this texture name that is found in any of the wads on the // user's system. this is not a huge problem, because the map compiler obeys // the "wad" epair when compiling the map, but the user might end up looking at // the wrong texture in the editor. (more of a problem if the texture we use // here has a different size from the one in the wad the map compiler uses...) // Hydra: - TTimo: I looked all over for other places to put this, but really it // is an issue with map loading (because of a limitation of halflife/q2 map format) // so it's gone in here, it also stops incorrect shader/texdef names getting used // in the radiant core and it's modules which we'd only have to change later on. // (either in map_importentities() or the shader module). so it's actually cleaner // in the long run, even if a little odd. And it keeps more game specific stuff // OUT of the core, which is a good thing. if ( g_MapVersion == MAPVERSION_HL ) { qboolean done = false; // FIXME: This bit is halflife specific. // look in the list of wads supplied in the worldspawn "wad" key/pair for the // texture first, if it's not in any then we carry on searching the vfs for it // as usual. // each time we find a texture, we add it to the a cache // so we don't have to hunt the vfs for it each time. // See SearchWadsForTextureName() and AddToCache() above for cache stuff char *wadname; wadname = SearchWadsForTextureName( token ); if ( wadname ) { face->texdef.SetName( wadname ); done = true; } else { // using the cache below means that this message is only ever printed out once! Sys_Printf( "WARNING: could not find \"%s\" in any listed wad files, searching all wad files instead!\n",token ); } // end of half-life specific bit. // check the cache! if ( !done ) { str = CheckCacheForTextureName( token ); if ( str ) { face->texdef.SetName( str ); done = true; } } if ( !done ) { char *fullpath; str = new char[strlen( token ) + 4 + 1]; // FIXME: halflife specific file extension, we'll have to support Q2/Q1 formats // and maybe tga texture format for HL here too.. sprintf( str,"%s.hlw",token ); fullpath = vfsGetFullPath( str,0,VFS_SEARCH_PAK | VFS_SEARCH_DIR ); // MIP support for quake if ( !fullpath ) { sprintf( str,"%s.mip",token ); fullpath = vfsGetFullPath( str, 0, 0 ); } // TGA support in halflife ? /* if (!fullpath) { sprintf(str,"%s.tga",token); fullpath = vfsGetFullPath(str); } */ delete [] str; if ( fullpath ) { // strip the extension. int len = strlen( fullpath ); if ( fullpath[len - 4] == '.' ) { fullpath[len - 4] = '\0'; } // and set the correct name! face->texdef.SetName( fullpath ); AddToCache( token,fullpath ); } else { Sys_Printf( "WARNING: could not find \"%s\" in the vfs search path\n",token ); str = new char[strlen( token ) + 10]; strcpy( str, "textures/" ); strcpy( str + 9, token ); face->texdef.SetName( str ); AddToCache( token,str ); delete [] str; } } } else // !MAPVERSION_HL { str = new char[strlen( token ) + 10]; strcpy( str, "textures/" ); strcpy( str + 9, token ); face->texdef.SetName( str ); delete [] str; } if ( !bAlternateTexdef ) { if ( g_MapVersion == MAPVERSION_HL ) { // Q1 as well ? GetToken( false ); if ( token[0] == '[' && token[1] == '\0' ) { bworldcraft = true; GetToken( false ); // UAxis[0] GetToken( false ); // UAxis[1] GetToken( false ); // UAxis[2] GetToken( false ); // shift face->texdef.shift[0] = atof( token ); GetToken( false ); // ] GetToken( false ); // [ GetToken( false ); // VAxis[0] GetToken( false ); // VAxis[1] GetToken( false ); // VAxis[2] GetToken( false ); // shift face->texdef.shift[1] = atof( token ); GetToken( false ); // ] // rotation is derived from the U and V axes. // ZHLT ignores this setting even if present in a .map file. GetToken( false ); face->texdef.rotate = atof( token ); // Scales GetToken( false ); face->texdef.scale[0] = atof( token ); GetToken( false ); face->texdef.scale[1] = atof( token ); } else { UnGetToken(); } } if ( !bworldcraft ) { // !MAPVERSION_HL // parse texdef GetToken( false ); face->texdef.shift[0] = atof( token ); GetToken( false ); face->texdef.shift[1] = atof( token ); GetToken( false ); face->texdef.rotate = atof( token ); GetToken( false ); face->texdef.scale[0] = atof( token ); GetToken( false ); face->texdef.scale[1] = atof( token ); } } // parse the optional contents/flags/value if ( !bworldcraft && TokenAvailable() ) { GetToken( true ); if ( isdigit( token[0] ) ) { face->texdef.contents = atoi( token ); GetToken( false ); face->texdef.flags = atoi( token ); GetToken( false ); face->texdef.value = atoi( token ); } else { UnGetToken(); } } } bool Primitive_Parse( brush_t *pBrush ){ char *token = Token(); GetToken( true ); if ( !strcmp( token, "patchDef2" ) ) { pBrush->patchBrush = true; pBrush->pPatch = Patch_Alloc(); pBrush->pPatch->pSymbiot = pBrush; Patch_Parse( pBrush->pPatch ); GetToken( true ); //} // A patchdef should never be loaded from a quake2 map file // so we just return false and the brush+patch gets freed // and the user gets told. if ( g_MapVersion != MAPVERSION_Q3 ) { // FIXME: Hydra - I wanted to write out a line number here, but I can't because there's no API to access the core's "scriptline" variable. Syn_Printf( "ERROR: patchDef2's are not supported in Quake%d format .map files!\n",g_MapVersion ); abortcode = MAP_WRONGVERSION; return false; } } else if ( !strcmp( token, "brushDef" ) ) { pBrush->bBrushDef = true; GetToken( true ); // { while ( 1 ) { face_t *f = pBrush->brush_faces; pBrush->brush_faces = Face_Alloc(); Face_Parse( pBrush->brush_faces, true ); pBrush->brush_faces->next = f; // check for end of brush GetToken( true ); if ( strcmp( token,"}" ) == 0 ) { break; } UnGetToken(); } GetToken( true ); // } } else { UnGetToken(); while ( 1 ) { face_t *f = pBrush->brush_faces; pBrush->brush_faces = Face_Alloc(); Face_Parse( pBrush->brush_faces ); pBrush->brush_faces->next = f; // check for end of brush GetToken( true ); if ( strcmp( token,"}" ) == 0 ) { break; } UnGetToken(); } } return true; } void Entity_Parse( entity_t *pEntity ){ brush_t *pBrush; // CPtrArray *brushes = NULL; char temptoken[1024]; char *token = Token(); while ( 1 ) { GetToken( true ); // { or } or epair if ( !strcmp( token, "}" ) ) { break; } else if ( !strcmp( token, "{" ) ) { pBrush = Brush_Alloc(); if ( Primitive_Parse( pBrush ) ) { ( (CPtrArray*)pEntity->pData )->Add( pBrush ); } else { Brush_Free( pBrush, true ); } } else { strcpy( temptoken, token ); GetToken( false ); SetKeyValue( pEntity, temptoken, token ); if ( g_MapVersion == MAPVERSION_HL ) { // if we've not god a "wads" key/pair already, then break it into a list. if ( !g_WadList && ( stricmp( temptoken,"wad" ) == 0 ) ) { BuildWadList( token ); } } } } } void Map_Read( IDataStream *in, CPtrArray *map ){ entity_t *pEntity; char *buf; unsigned long len = in->GetLength(); buf = new char[len + 1]; in->Read( buf, len ); buf[len] = '\0'; StartTokenParsing( buf ); abortcode = MAP_NOERROR; while ( abortcode == MAP_NOERROR ) { if ( !GetToken( true ) ) { // { or NULL break; } pEntity = Entity_Alloc(); pEntity->pData = new CPtrArray; Entity_Parse( pEntity ); map->Add( pEntity ); } delete [] buf; if ( abortcode != MAP_NOERROR ) { int num_ents, num_brushes,i,j; entity_t *e; CPtrArray *brushes; num_ents = map->GetSize(); for ( i = 0; i < num_ents; i++ ) { e = (entity_t*)map->GetAt( i ); brushes = (CPtrArray*)e->pData; num_brushes = brushes->GetSize(); for ( j = 0; j < num_brushes; j++ ) { Brush_Free( (brush_t *)brushes->GetAt( j ), true ); } brushes->RemoveAll(); delete brushes; Entity_Free( e ); } map->RemoveAll(); } } void Map_ReadQ3( IDataStream *in, CPtrArray *map ){ g_MapVersion = MAPVERSION_Q3; Map_Read( in,map ); } void Map_ReadHL( IDataStream *in, CPtrArray *map ){ g_WadList = NULL; g_TextureNameCache = NULL; g_MapVersion = MAPVERSION_HL; Map_Read( in,map ); FreeGSList( g_TextureNameCache ); FreeGSList( g_WadList ); } void Map_ReadQ2( IDataStream *in, CPtrArray *map ){ g_MapVersion = MAPVERSION_Q2; Map_Read( in,map ); }