]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - plugins/map/parse.cpp
transfer from internal tree r5311 branches/1.4-gpl
[xonotic/netradiant.git] / plugins / map / parse.cpp
1 /*\r
2 Copyright (C) 1999-2007 id Software, Inc. and contributors.\r
3 For a list of contributors, see the accompanying CONTRIBUTORS file.\r
4 \r
5 This file is part of GtkRadiant.\r
6 \r
7 GtkRadiant is free software; you can redistribute it and/or modify\r
8 it under the terms of the GNU General Public License as published by\r
9 the Free Software Foundation; either version 2 of the License, or\r
10 (at your option) any later version.\r
11 \r
12 GtkRadiant is distributed in the hope that it will be useful,\r
13 but WITHOUT ANY WARRANTY; without even the implied warranty of\r
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
15 GNU General Public License for more details.\r
16 \r
17 You should have received a copy of the GNU General Public License\r
18 along with GtkRadiant; if not, write to the Free Software\r
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\r
20 */\r
21 \r
22 // Hydra - FIXME : TTimo, We need to know what game + engine we're using for\r
23 // the halflife (not Q2) specific stuff\r
24 // we need an API for modules to get this info!\r
25 \r
26 //\r
27 // parses quake3 map format into internal objects\r
28 //\r
29 \r
30 #include "plugin.h"\r
31 \r
32 // cmdlib\r
33 extern void ExtractFileName (const char *path, char *dest);\r
34 \r
35 extern int g_MapVersion;\r
36 int abortcode; // see imap.h for values.\r
37 \r
38 // Start of half-life specific stuff\r
39 \r
40 GSList *g_WadList; // halflife specific.\r
41 GSList *g_TextureNameCache; // halflife specific.\r
42 \r
43 // NOTE TTimo: yuck..\r
44 void FreeGSList( GSList *l )\r
45 {\r
46   while (l)\r
47   {\r
48     free (l->data);\r
49     l = g_slist_remove (l, l->data);\r
50   }\r
51 }\r
52 \r
53 // NOTE TTimo: ideally, this would be using Str functions instead\r
54 void trim( char *str)\r
55 {\r
56   int len;\r
57   len = strlen(str);\r
58   while(str[--len] == ' ')\r
59     str[len] = 0;\r
60 }\r
61 \r
62 void BuildWadList( char *wadstr )\r
63 {\r
64   char wads[2048]; // change to CString usage ?\r
65   wads[0] = 0;\r
66   char *p1,*p2;\r
67   char cleanwadname[QER_MAX_NAMELEN];\r
68 \r
69   g_WadList = NULL;\r
70 \r
71   strcpy(wads,wadstr);\r
72   QE_ConvertDOSToUnixName(wads,wads);\r
73 \r
74   // ok, we got the list of ; delimited wads, now split it into a GSList that contains\r
75   // just the wad names themselves.\r
76 \r
77   p1 = wads;\r
78 \r
79   do\r
80   {\r
81     p2 = strchr(p1,';');\r
82     if (p2)\r
83       *p2 = 0; // swap the ; with a null terminator\r
84 \r
85     if (strchr(p1,'/') || strchr(p1,'\\'))\r
86     {\r
87       ExtractFileName(p1,cleanwadname);\r
88 \r
89       trim(cleanwadname);\r
90 \r
91       if (*cleanwadname)\r
92       {\r
93         g_WadList = g_slist_append (g_WadList, strdup(cleanwadname));\r
94         Sys_Printf("wad: %s\n",cleanwadname);\r
95       }\r
96     }\r
97     else\r
98     {\r
99       trim(p1);\r
100       if (*p1)\r
101       {\r
102         g_WadList = g_slist_append (g_WadList, strdup(p1));\r
103         Sys_Printf("wad: %s\n",p1);\r
104       }\r
105     }\r
106     if (p2)\r
107       p1 = p2+1; // point back to the remainder of the string\r
108     else\r
109       p1 = NULL; // make it so we exit the loop.\r
110 \r
111   } while (p1);\r
112 \r
113   // strip the ".wad" extensions.\r
114   for (GSList *l = g_WadList; l != NULL ; l = l->next)\r
115   {\r
116     p1 = (char *)l->data;\r
117 \r
118     if (p1[strlen(p1)-4] == '.')\r
119       p1[strlen(p1)-4] = 0;\r
120   }\r
121 }\r
122 \r
123 // FIXME: usefulness of this cache sounds very discutable\r
124 char *CheckCacheForTextureName( const char *cleantexturename )\r
125 {\r
126   char *str;\r
127   int len;\r
128   GSList *l;\r
129 \r
130   // search our little cache first to speed things up.\r
131   // cache strings are stored as "<cleanname>;<actualnameshader>"\r
132   len = strlen(cleantexturename);\r
133   for (l = g_TextureNameCache; l != NULL ; l = l->next)\r
134   {\r
135     str = (char *)l->data;\r
136     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.\r
137     {\r
138       return (str + len + 1); // skip the delimiter ;\r
139     }\r
140   }\r
141   return NULL;\r
142 }\r
143 \r
144 char *AddToCache(const char *cleantexturename, const char *actualname)\r
145 {\r
146   char *cachestr;\r
147   cachestr = (char *)malloc(strlen(cleantexturename)+1+strlen(actualname)+1); // free()'d when g_TextureNameCache is freed\r
148   sprintf(cachestr,"%s;%s",cleantexturename,actualname);\r
149   g_TextureNameCache = g_slist_append (g_TextureNameCache, cachestr);\r
150   return cachestr;\r
151 }\r
152 \r
153 char *SearchWadsForTextureName( const char *cleantexturename )\r
154 {\r
155   char *str;\r
156   char *wadname;\r
157   char *actualtexturename = NULL;\r
158   GSList *l;\r
159   int count;\r
160 \r
161   actualtexturename = CheckCacheForTextureName(cleantexturename);\r
162   if (actualtexturename)\r
163     return actualtexturename;\r
164 \r
165   // still here ?  guess it's not in the cache then!\r
166 \r
167   // search the wads listed in the worldspawn "wad" key\r
168   for (l = g_WadList; l != NULL && actualtexturename == NULL ; l = l->next)\r
169   {\r
170     wadname = (char *)l->data;\r
171 \r
172     str = new char[strlen(wadname)+strlen(cleantexturename)+9+1+4+1];\r
173 \r
174     // hlw here is ok as we never have anything other than hlw files in a wad.\r
175     sprintf(str,"textures/%s/%s.hlw",wadname,cleantexturename);\r
176     count = vfsGetFileCount(str, VFS_SEARCH_PAK); // only search pack files\r
177     // LordHavoc: hacked in .mip loading here\r
178     if (!count)\r
179     {\r
180       sprintf(str,"textures/%s/%s.mip",wadname,cleantexturename);\r
181       count = vfsGetFileCount(str, VFS_SEARCH_PAK); // only search pack files\r
182     }\r
183 \r
184     if (count > 0)\r
185     {\r
186       // strip the extension, build the cache string and add the the cache\r
187       str[strlen(str)-4] = 0;\r
188 \r
189       actualtexturename = AddToCache(cleantexturename,str);\r
190 \r
191       //point the return value to the actual name, not what we add to the cache\r
192       actualtexturename += 1+strlen(cleantexturename);\r
193     }\r
194     delete [] str;\r
195   }\r
196   return actualtexturename;\r
197 }\r
198 // End of half-life specific stuff\r
199 \r
200 void Patch_Parse(patchMesh_t *pPatch)\r
201 {\r
202   int i, j;\r
203   char *str;\r
204 \r
205   char *token = Token();\r
206 \r
207   GetToken(true); //{\r
208 \r
209   // parse shader name\r
210   GetToken(true);\r
211   str = new char[strlen(token)+10];\r
212   strcpy(str, "textures/");\r
213   strcpy(str+9, token);\r
214   pPatch->pShader = QERApp_Shader_ForName(str);\r
215   pPatch->d_texture = pPatch->pShader->getTexture();\r
216   delete [] str;\r
217 \r
218   GetToken(true); //(\r
219 \r
220   // parse matrix dimensions\r
221   GetToken(false);\r
222   pPatch->width = atoi(token);\r
223   GetToken(false);\r
224   pPatch->height = atoi(token);\r
225 \r
226   // ignore contents/flags/value\r
227   GetToken(false);\r
228   GetToken(false);\r
229   GetToken(false);\r
230 \r
231   GetToken(false); //)\r
232 \r
233   // parse matrix\r
234   GetToken(true); //(\r
235   for(i=0; i<pPatch->width; i++)\r
236   {\r
237     GetToken(true); //(\r
238     for(j=0; j<pPatch->height; j++)\r
239     {\r
240       GetToken(false); //(\r
241 \r
242       GetToken(false);\r
243       pPatch->ctrl[i][j].xyz[0] = atof(token);\r
244       GetToken(false);\r
245       pPatch->ctrl[i][j].xyz[1] = atof(token);\r
246       GetToken(false);\r
247       pPatch->ctrl[i][j].xyz[2] = atof(token);\r
248       GetToken(false);\r
249       pPatch->ctrl[i][j].st[0] = atof(token);\r
250       GetToken(false);\r
251       pPatch->ctrl[i][j].st[1] = atof(token);\r
252 \r
253       GetToken(false); //)\r
254     }\r
255     GetToken(false); //)\r
256   }\r
257   GetToken(true); //)\r
258 \r
259   GetToken(true); //}\r
260 }\r
261 \r
262 void Face_Parse (face_t *face, bool bAlternateTexdef = false)\r
263 {\r
264   int i, j;\r
265   char *str;\r
266         bool bworldcraft = false;\r
267 \r
268   char *token = Token();\r
269 \r
270   // parse planepts\r
271   str = NULL;\r
272   for(i=0; i<3; i++)\r
273   {\r
274     GetToken(true); //(\r
275     for(j=0; j<3; j++)\r
276     {\r
277       GetToken(false);\r
278       face->planepts[i][j] = atof(token);\r
279     }\r
280     GetToken(false); //)\r
281   }\r
282 \r
283   if(bAlternateTexdef)\r
284   {\r
285     // parse alternate texdef\r
286                 GetToken (false); // (\r
287                 GetToken (false); // (\r
288                 for (i=0;i<3;i++)\r
289                 {\r
290                         GetToken(false);\r
291                         face->brushprimit_texdef.coords[0][i]=atof(token);\r
292                 }\r
293                 GetToken (false); // )\r
294                 GetToken (false); // (\r
295                 for (i=0;i<3;i++)\r
296                 {\r
297                         GetToken(false);\r
298                         face->brushprimit_texdef.coords[1][i]=atof(token);\r
299                 }\r
300                 GetToken (false); // )\r
301                 GetToken (false); // )\r
302   }\r
303 \r
304 \r
305   // parse shader name\r
306   GetToken(false); // shader\r
307 \r
308   // if we're loading a halflife map then we don't have a relative texture name\r
309   // we just get <texturename>.  So we need to convert this to a relative name\r
310   // like this: "textures/<wadname>/shader", so we use vfsFileFile to get the filename.\r
311 \r
312   // *** IMPORTANT ***\r
313   // For Halflife we need to see if the texture is in wads listed in the\r
314   // map's worldspawn "wad" e-pair.  If we don't then the image used will be the\r
315   // first image with this texture name that is found in any of the wads on the\r
316   // user's system.  this is not a huge problem, because the map compiler obeys\r
317   // the "wad" epair when compiling the map, but the user might end up looking at\r
318   // the wrong texture in the editor. (more of a problem if the texture we use\r
319   // here has a different size from the one in the wad the map compiler uses...)\r
320 \r
321   // Hydra: - TTimo: I looked all over for other places to put this, but really it\r
322   // is an issue with map loading (because of a limitation of halflife/q2 map format)\r
323   // so it's gone in here, it also stops incorrect shader/texdef names getting used\r
324   // in the radiant core and it's modules which we'd only have to change later on.\r
325   // (either in map_importentities() or the shader module).  so it's actually cleaner\r
326   // in the long run, even if a little odd.  And it keeps more game specific stuff\r
327   // OUT of the core, which is a good thing.\r
328 \r
329   if (g_MapVersion == MAPVERSION_HL)\r
330   {\r
331     qboolean done = false;\r
332 \r
333     // FIXME: This bit is halflife specific.\r
334     // look in the list of wads supplied in the worldspawn "wad" key/pair for the\r
335     // texture first, if it's not in any then we carry on searching the vfs for it\r
336     // as usual.\r
337 \r
338     // each time we find a texture, we add it to the a cache\r
339     // so we don't have to hunt the vfs for it each time.\r
340     // See SearchWadsForTextureName() and AddToCache() above for cache stuff\r
341 \r
342     char *wadname;\r
343     wadname = SearchWadsForTextureName(token);\r
344 \r
345     if (wadname)\r
346     {\r
347       face->texdef.SetName(wadname);\r
348       done = true;\r
349     }\r
350     else\r
351     {\r
352       // using the cache below means that this message is only ever printed out once!\r
353       Sys_Printf("WARNING: could not find \"%s\" in any listed wad files, searching all wad files instead!\n",token);\r
354     }\r
355     // end of half-life specific bit.\r
356 \r
357     // check the cache!\r
358     if (!done)\r
359     {\r
360       str = CheckCacheForTextureName(token);\r
361       if (str)\r
362       {\r
363         face->texdef.SetName(str);\r
364         done = true;\r
365       }\r
366     }\r
367 \r
368     if (!done)\r
369     {\r
370       char *fullpath;\r
371 \r
372       str = new char[strlen(token)+4+1];\r
373 \r
374       // FIXME: halflife specific file extension, we'll have to support Q2/Q1 formats\r
375       // and maybe tga texture format for HL here too..\r
376       sprintf(str,"%s.hlw",token);\r
377       fullpath = vfsGetFullPath(str,0,VFS_SEARCH_PAK | VFS_SEARCH_DIR);\r
378 \r
379       // MIP support for quake\r
380       if (!fullpath)\r
381       {\r
382         sprintf(str,"%s.mip",token);\r
383         fullpath = vfsGetFullPath( str, 0, 0 );\r
384       }\r
385 \r
386       // TGA support in halflife ?\r
387       /*\r
388       if (!fullpath)\r
389       {\r
390         sprintf(str,"%s.tga",token);\r
391         fullpath = vfsGetFullPath(str);\r
392       }\r
393       */\r
394       delete [] str;\r
395 \r
396       if (fullpath)\r
397       {\r
398         // strip the extension.\r
399         int len = strlen(fullpath);\r
400         if (fullpath[len-4] == '.')\r
401           fullpath[len-4] = '\0';\r
402 \r
403         // and set the correct name!\r
404         face->texdef.SetName(fullpath);\r
405         AddToCache(token,fullpath);\r
406       }\r
407       else\r
408       {\r
409         Sys_Printf("WARNING: could not find \"%s\" in the vfs search path\n",token);\r
410         str = new char[strlen(token)+10];\r
411         strcpy(str, "textures/");\r
412         strcpy(str+9, token);\r
413         face->texdef.SetName(str);\r
414         AddToCache(token,str);\r
415         delete [] str;\r
416       }\r
417     }\r
418   }\r
419   else // !MAPVERSION_HL\r
420   {\r
421     str = new char[strlen(token)+10];\r
422     strcpy(str, "textures/");\r
423     strcpy(str+9, token);\r
424     face->texdef.SetName(str);\r
425     delete [] str;\r
426   }\r
427 \r
428   if(!bAlternateTexdef)\r
429   {\r
430     if (g_MapVersion == MAPVERSION_HL) // Q1 as well ?\r
431     {\r
432       GetToken(false);\r
433       if (token[0] == '[' && token[1] == '\0')\r
434       {\r
435         bworldcraft = true;\r
436 \r
437         GetToken(false); // UAxis[0]\r
438         GetToken(false); // UAxis[1]\r
439         GetToken(false); // UAxis[2]\r
440 \r
441         GetToken(false); // shift\r
442         face->texdef.shift[0] = atof(token);\r
443 \r
444         GetToken(false); // ]\r
445 \r
446         GetToken(false); // [\r
447         GetToken(false); // VAxis[0]\r
448         GetToken(false); // VAxis[1]\r
449         GetToken(false); // VAxis[2]\r
450 \r
451         GetToken(false); // shift\r
452         face->texdef.shift[1] = atof(token);\r
453 \r
454         GetToken(false); // ]\r
455 \r
456         // rotation is derived from the U and V axes.\r
457         // ZHLT ignores this setting even if present in a .map file.\r
458         GetToken(false);\r
459         face->texdef.rotate = atof(token);\r
460 \r
461         // Scales\r
462         GetToken(false);\r
463         face->texdef.scale[0] = atof(token);\r
464         GetToken(false);\r
465         face->texdef.scale[1] = atof(token);\r
466       }\r
467       else\r
468       {\r
469         UnGetToken();\r
470       }\r
471     }\r
472 \r
473     if (!bworldcraft) // !MAPVERSION_HL\r
474     {\r
475       // parse texdef\r
476       GetToken(false);\r
477       face->texdef.shift[0] = atof(token);\r
478       GetToken(false);\r
479       face->texdef.shift[1] = atof(token);\r
480       GetToken(false);\r
481       face->texdef.rotate = atof(token);\r
482       GetToken(false);\r
483       face->texdef.scale[0] = atof(token);\r
484       GetToken(false);\r
485       face->texdef.scale[1] = atof(token);\r
486     }\r
487   }\r
488   // parse the optional contents/flags/value\r
489   if (!bworldcraft && TokenAvailable())\r
490   {\r
491   GetToken(true);\r
492   if (isdigit(token[0]))\r
493   {\r
494     face->texdef.contents = atoi(token);\r
495     GetToken(false);\r
496     face->texdef.flags = atoi(token);\r
497     GetToken(false);\r
498     face->texdef.value = atoi(token);\r
499   }\r
500   else\r
501   {\r
502     UnGetToken();\r
503   }\r
504   }\r
505 }\r
506 \r
507 bool Primitive_Parse(brush_t *pBrush)\r
508 {\r
509   char *token = Token();\r
510 \r
511   GetToken(true);\r
512   if (!strcmp(token, "patchDef2"))\r
513   {\r
514     pBrush->patchBrush = true;\r
515     pBrush->pPatch = Patch_Alloc();\r
516     pBrush->pPatch->pSymbiot = pBrush;\r
517     Patch_Parse(pBrush->pPatch);\r
518     GetToken(true); //}\r
519 \r
520     // A patchdef should never be loaded from a quake2 map file\r
521     // so we just return false and the brush+patch gets freed\r
522     // and the user gets told.\r
523     if (g_MapVersion != MAPVERSION_Q3)\r
524     {\r
525       // 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.\r
526       Syn_Printf("ERROR: patchDef2's are not supported in Quake%d format .map files!\n",g_MapVersion);\r
527       abortcode = MAP_WRONGVERSION;\r
528       return false;\r
529     }\r
530   }\r
531   else if (!strcmp(token, "brushDef"))\r
532   {\r
533     pBrush->bBrushDef = true;\r
534     GetToken(true); // {\r
535     while(1)\r
536     {\r
537       face_t    *f = pBrush->brush_faces;\r
538       pBrush->brush_faces = Face_Alloc();\r
539       Face_Parse(pBrush->brush_faces, true);\r
540       pBrush->brush_faces->next = f;\r
541       // check for end of brush\r
542       GetToken(true);\r
543       if(strcmp(token,"}") == 0)\r
544         break;\r
545       UnGetToken();\r
546     }\r
547     GetToken(true); // }\r
548   }\r
549   else\r
550   {\r
551     UnGetToken();\r
552     while(1)\r
553     {\r
554       face_t    *f = pBrush->brush_faces;\r
555       pBrush->brush_faces = Face_Alloc();\r
556       Face_Parse(pBrush->brush_faces);\r
557       pBrush->brush_faces->next = f;\r
558 \r
559       // check for end of brush\r
560       GetToken(true);\r
561       if(strcmp(token,"}") == 0)\r
562         break;\r
563       UnGetToken();\r
564     }\r
565   }\r
566   return true;\r
567 }\r
568 \r
569 void Entity_Parse(entity_t *pEntity)\r
570 {\r
571   brush_t *pBrush;\r
572   CPtrArray *brushes = NULL;\r
573   char temptoken[1024];\r
574 \r
575   char *token = Token();\r
576 \r
577   while(1)\r
578   {\r
579     GetToken(true); // { or } or epair\r
580     if (!strcmp(token, "}")) {\r
581       break;\r
582     } else if(!strcmp(token, "{")) {\r
583 \r
584       pBrush = Brush_Alloc();\r
585       if (Primitive_Parse(pBrush)) {\r
586         ((CPtrArray*)pEntity->pData)->Add(pBrush);\r
587       } else {\r
588         Brush_Free( pBrush, true );\r
589       }\r
590 \r
591     } else {\r
592 \r
593       strcpy(temptoken, token);\r
594       GetToken(false);\r
595 \r
596       SetKeyValue(pEntity, temptoken, token);\r
597 \r
598       if (g_MapVersion == MAPVERSION_HL) {\r
599         // if we've not god a "wads" key/pair already, then break it into a list.\r
600         if (!g_WadList && (stricmp(temptoken,"wad") == 0)) {\r
601           BuildWadList(token);\r
602         }\r
603       }\r
604 \r
605     }\r
606   }\r
607 }\r
608 \r
609 void Map_Read (IDataStream *in, CPtrArray *map)\r
610 {\r
611   entity_t *pEntity;\r
612   char *buf;\r
613 \r
614   unsigned long len = in->GetLength();\r
615   buf = new char[len+1];\r
616   in->Read(buf, len);\r
617   buf[len] = '\0';\r
618   StartTokenParsing(buf);\r
619   abortcode = MAP_NOERROR;\r
620 \r
621   while(abortcode == MAP_NOERROR)\r
622   {\r
623     if (!GetToken (true)) // { or NULL\r
624                   break;\r
625     pEntity = Entity_Alloc();\r
626     pEntity->pData = new CPtrArray;\r
627     Entity_Parse(pEntity);\r
628     map->Add(pEntity);\r
629   }\r
630 \r
631   delete [] buf;\r
632 \r
633   if (abortcode != MAP_NOERROR)\r
634   {\r
635     int num_ents, num_brushes,i,j;\r
636     entity_t *e;\r
637     CPtrArray *brushes;\r
638 \r
639     num_ents = map->GetSize();\r
640     for(i=0; i<num_ents; i++)\r
641     {\r
642       e = (entity_t*)map->GetAt(i);\r
643       brushes = (CPtrArray*)e->pData;\r
644       num_brushes = brushes->GetSize();\r
645       for(j=0; j<num_brushes; j++)\r
646       {\r
647         Brush_Free( (brush_t *)brushes->GetAt(j), true );\r
648       }\r
649       brushes->RemoveAll();\r
650       delete brushes;\r
651       Entity_Free(e);\r
652     }\r
653     map->RemoveAll();\r
654   }\r
655 }\r
656 \r
657 void Map_ReadQ3 (IDataStream *in, CPtrArray *map)\r
658 {\r
659   g_MapVersion = MAPVERSION_Q3;\r
660   Map_Read(in,map);\r
661 }\r
662 \r
663 void Map_ReadHL (IDataStream *in, CPtrArray *map)\r
664 {\r
665   g_WadList = NULL;\r
666   g_TextureNameCache = NULL;\r
667 \r
668   g_MapVersion = MAPVERSION_HL;\r
669   Map_Read(in,map);\r
670 \r
671   FreeGSList(g_TextureNameCache);\r
672   FreeGSList(g_WadList);\r
673 }\r
674 \r
675 void Map_ReadQ2 (IDataStream *in, CPtrArray *map)\r
676 {\r
677   g_MapVersion = MAPVERSION_Q2;\r
678   Map_Read(in,map);\r
679 }\r