/* 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; iwidth; i++) { GetToken(true); //( for(j=0; jheight; 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; iGetAt(i); brushes = (CPtrArray*)e->pData; num_brushes = brushes->GetSize(); for(j=0; jGetAt(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); }