apply misc fixes from Markus Fischer and Rambetter
[xonotic/netradiant.git] / radiant / eclass.cpp
1 /*
2 Copyright (C) 1999-2007 id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
4
5 This file is part of GtkRadiant.
6
7 GtkRadiant is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 GtkRadiant is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with GtkRadiant; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 */
21
22 #include "stdafx.h"
23 #include <sys/stat.h>
24 #if defined (__linux__) || defined (__APPLE__)
25 #include <dirent.h>
26 #endif
27 #include "assert.h"
28
29 eclass_t        *eclass = NULL;
30 eclass_t        *eclass_bad = NULL;
31 const vec3_t smallbox[2] = {{-8,-8,-8},{8,8,8}};
32 char            eclass_directory[1024];
33
34 qboolean parsing_single = false;
35 eclass_t *eclass_e;
36
37 /*!
38 implementation of the EClass manager API
39 */
40 eclass_t** Get_EClass_E()
41 {
42   return &eclass_e;
43 }
44
45 void Set_Eclass_Found(qboolean b)
46 {
47   eclass_found = b;
48 }
49
50 qboolean Get_Parsing_Single()
51 {
52   return parsing_single;
53 }
54
55
56 // md3 cache for misc_models
57 //eclass_t *g_md3Cache = NULL;
58
59 /*
60
61 the classname, color triple, and bounding box are parsed out of comments
62 A ? size means take the exact brush size.
63
64 / *QUAKED <classname> (0 0 0) ?
65 / *QUAKED <classname> (0 0 0) (-8 -8 -8) (8 8 8)
66
67 Flag names can follow the size description:
68
69 / *QUAKED func_door (0 .5 .8) ? START_OPEN STONE_SOUND DOOR_DONT_LINK GOLD_KEY SILVER_KEY
70
71 */
72
73 void CleanEntityList(eclass_t *&pList)
74 {
75   while (pList)
76   {
77     eclass_t* pTemp = pList->next;
78
79     entitymodel *model = pList->model;
80     while (model != NULL)
81     {
82       delete []model->pTriList;
83       if (model->strSkin)
84         g_string_free( (GString *)model->strSkin, TRUE );
85       model->strSkin = NULL;
86       model = model->pNext;
87     }
88
89     if (pList->modelpath) {
90       free(pList->modelpath);
91       pList->modelpath = NULL;
92     }
93     if (pList->skinpath) {
94       free(pList->skinpath);
95       pList->skinpath = NULL;
96     }
97
98     free(pList->name);
99     free(pList->comments);
100     free(pList);
101     pList = pTemp;
102   }
103
104   pList = NULL;
105
106 }
107
108
109 void CleanUpEntities()
110 {
111   // NOTE: maybe some leak checks needed .. older versions of Radiant looked like they were freezing more stuff
112   CleanEntityList(eclass);
113   //CleanEntityList(g_md3Cache);
114   if (eclass_bad)
115   {
116     free(eclass_bad->name);
117     free(eclass_bad->comments);
118     free(eclass_bad);
119     eclass_bad = NULL;
120   }
121 }
122
123 void EClass_InsertSortedList(eclass_t *&pList, eclass_t *e)
124 {
125         eclass_t        *s;
126
127         if (!pList)
128         {
129                 pList = e;
130                 return;
131         }
132
133
134         s = pList;
135         if (stricmp (e->name, s->name) < 0)
136         {
137                 e->next = s;
138                 pList = e;
139                 return;
140         }
141
142         do
143         {
144                 if (!s->next || stricmp (e->name, s->next->name) < 0)
145                 {
146                         e->next = s->next;
147                         s->next = e;
148                         return;
149                 }
150                 s=s->next;
151         } while (1);
152 }
153
154 /*
155 =================
156 Eclass_InsertAlphabetized
157 =================
158 */
159 void Eclass_InsertAlphabetized (eclass_t *e)
160 {
161 #if 1
162   EClass_InsertSortedList(eclass, e);
163 #else
164         eclass_t        *s;
165
166         if (!eclass)
167         {
168                 eclass = e;
169                 return;
170         }
171
172
173         s = eclass;
174         if (stricmp (e->name, s->name) < 0)
175         {
176                 e->next = s;
177                 eclass = e;
178                 return;
179         }
180
181         do
182         {
183                 if (!s->next || stricmp (e->name, s->next->name) < 0)
184                 {
185                         e->next = s->next;
186                         s->next = e;
187                         return;
188                 }
189                 s=s->next;
190         } while (1);
191 #endif
192 }
193
194 /*!
195 This looks at each eclass_t, if it has a "modelpath" set then it leaves it alone
196 if it's not set it checks to see if a file called "sprites/<eclassname>.*" exists, and
197 if it does exist then it sets the "modelpath" to "sprites/<eclassname>.spr"
198 */
199 void Eclass_CreateSpriteModelPaths()
200 {
201   int Counts[4] = { 0, 0, 0, 0 };
202   char filename[512]; // should be big enough, ExtractFileBase doesn't take a buffer size...
203   eclass_t *e;
204
205   // get a list of all sprite/*>* files in all sprite/ directories
206   Sys_Printf("Searching VFS for files in sprites/*.* that match entity names...\n");
207   GSList *pFiles = vfsGetFileList("sprites", NULL);
208   GSList *pFile;
209
210   if (pFiles)
211   {
212
213     // find an eclass without a modelpath.
214     for (e=eclass ; e ; e=e->next)
215     {
216       Counts[0]++;
217       if (e->modelpath)
218       {
219 #ifdef _DEBUG
220         Sys_Printf("Ignoring sprite for entity %s (modelpath: \"%s\")\n",e->name,e->modelpath);
221 #endif
222         Counts[1]++;
223         continue;  // ignore this eclass, it's already got a model
224       }
225
226       // TODO: remove this check when we can have sprites for non-fixed size entities.
227       if (!e->fixedsize)
228       {
229 #ifdef _DEBUG
230         Sys_Printf("Ignoring sprite for non-fixed-size entity %s\n",e->name);
231 #endif
232         Counts[2]++;
233         continue;  // can't have sprites for non-fixed size entities (yet!)
234       }
235
236
237       Sys_Printf("Searching for sprite for fixed-size entity %s...",e->name);
238
239       pFile = pFiles; // point to start of list
240
241       // look for a file that has the same name, with any extension.
242       bool Found = FALSE;
243       while (pFile)
244       {
245
246         // strip the path/ and the .extension.
247         ExtractFileBase((char *)pFile->data,filename);
248
249         // does the eclass name match the filename?
250         if (stricmp(e->name,filename) == 0)
251         {
252           // yes, so generate a sprite filename using the all-encompasing .spr extension
253           // so that the model wrapper knows the sprite model plugin will be the model
254           // plugin used to render it.
255           CString strSpriteName;
256           strSpriteName.Format("sprites/%s.spr",e->name);
257           e->modelpath = strdup(strSpriteName.GetBuffer());
258           Sys_Printf("Found! (\"%s\")\n",(char *)pFile->data);
259           Counts[3]++;
260           Found = TRUE;
261         }
262         pFile = pFile->next;
263       }
264
265       if (!Found)
266         Sys_Printf("not found\n");
267
268     }
269
270     vfsClearFileDirList(&pFiles);
271   }
272   Sys_Printf("%d entities were scanned\n"
273              "%d entities that already had models/sprites were ignored\n"
274              "%d non-fixed-size entities were ignored\n"
275              "%d entities did not have matching sprite files\n"
276              "%d entities had sprite files and have been attached\n",
277              Counts[0],Counts[1],Counts[2],Counts[0]-Counts[3],Counts[3]);
278
279 }
280
281 void EClass_InitForFileList(GSList *pFiles, _EClassTable *pTable)
282 {
283   GSList *pFile = pFiles;
284   while (pFile)
285   {
286     // for a given name, we grab the first .def in the vfs
287     // this allows to override baseq3/scripts/entities.def for instance
288     char relPath[PATH_MAX];
289     strcpy(relPath, "scripts/");
290     strcat(relPath, (char*)pFile->data);
291     if (!vfsGetFullPath(relPath, 0, 0))
292     {
293       Sys_FPrintf(SYS_ERR, "Failed to find the full path for '%s' in the VFS\n", relPath);
294     }
295     else
296       pTable->m_pfnScanFile(vfsGetFullPath(relPath, 0, 0));
297     pFile = pFile->next;
298   }
299 }
300
301 /*!
302 Manually create an eclass_t, for when no modules exist.
303 this replaces and centralizes the eclass_t allocation
304 */
305 eclass_t * EClass_Create( const char *name, float col1, float col2, float col3, const vec3_t *mins, const vec3_t *maxs, const char *comments )
306 {
307   eclass_t *e;
308         char    color[128];
309
310   e = (eclass_t*)malloc(sizeof(*e));
311         memset (e, 0, sizeof(*e));
312
313   e->name = strdup(name);
314
315   // grab the color, reformat as texture name
316         e->color[0] = col1;
317   e->color[1] = col2;
318   e->color[2] = col3;
319         sprintf (color, "(%f %f %f)", e->color[0], e->color[1], e->color[2]);
320         e->texdef.SetName(color);
321
322   // supplied size ?
323   if (mins && maxs)
324   {
325     // Hydra:
326     // If we set worldspawn to be a fixed-size all the textures are
327     // displayed as flat-shaded.  This is a KLUDGE now that we have
328     // multiple game support as the worldspawn entity is game specific.
329     // Note that this is only ever fixed for the user if a definition
330     // for the worldspawn entity was not loaded, this can happen for
331     // several reasons:
332     // a) no entity definition plugin exists
333     // b) no entity definition files were found
334     // c) no entity definition file contained an entry for worldspawn.
335
336     if (stricmp(name,"worldspawn") != 0) e->fixedsize = true;
337
338     // copy the sizes..
339     memcpy(e->mins,mins,sizeof(vec3_t));
340     memcpy(e->maxs,maxs,sizeof(vec3_t));
341   }
342
343   if (comments)
344     e->comments = strdup(comments);
345   else
346   {
347     e->comments = (char*)malloc(1);
348     e->comments[0] = '\0';
349   }
350
351   return e;
352 }
353
354 void Eclass_Init ()
355 {
356   GSList *pFiles;
357
358   // start by creating the default unknown eclass
359   eclass_bad = EClass_Create("UNKNOWN_CLASS" , 0, 0.5, 0,NULL,NULL,NULL);
360
361   // now scan the definitions
362   _EClassTable *pTable = &g_EClassDefTable;
363   while (pTable)
364   {
365     // read in all scripts/*.<extension>
366     pFiles = vfsGetFileList("scripts", pTable->m_pfnGetExtension());
367     if (pFiles)
368     {
369       GSList *pFile = pFiles;
370       while (pFile)
371       {
372         /*!
373         \todo the MP/SP filtering rules need to be CLEANED UP and SANITIZED
374         */
375         // HACK
376         // JKII SP/MP mapping mode
377         if (g_pGameDescription->mGameFile == "jk2.game" || g_pGameDescription->mGameFile == "ja.game")
378         {
379           if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"), "sp"))
380           {
381             // SP mapping, ignore mp_*.def
382             char *name = (char *)pFile->data;
383             if (name[0]=='m' && name[1]=='p' && name[2]=='_')
384             {
385               Sys_Printf("Single Player mapping mode. Ignoring '%s'\n", name);
386               pFile = pFile->next;
387               continue;
388             }
389           }
390           else
391           {
392             // MP mapping, ignore sp_*.def
393             char *name = (char *)pFile->data;
394             if (name[0]=='s' && name[1]=='p' && name[2]=='_')
395             {
396               Sys_Printf("Multiplayer mapping mode. Ignoring '%s'\n", name);
397               pFile = pFile->next;
398               continue;
399             }
400           }
401         }
402         // RIANT
403         // STVEF SP/MP mapping mode
404         else if (g_pGameDescription->mGameFile == "stvef.game")
405         {
406           if (!strcmp(ValueForKey(g_qeglobals.d_project_entity, "gamemode"), "sp"))
407           {
408             // SP mapping, ignore mp_*.def
409             char *name = (char *)pFile->data;
410             if (name[0]=='m' && name[1]=='p' && name[2]=='_')
411             {
412               Sys_Printf("Single Player mapping mode. Ignoring '%s'\n", name);
413               pFile = pFile->next;
414               continue;
415             }
416           }
417           else
418           {
419             // HM mapping, ignore sp_*.def
420             char *name = (char *)pFile->data;
421             if (name[0]=='h' && name[1]=='m' && name[2]=='_')
422             {
423               Sys_Printf("HoloMatch mapping mode. Ignoring '%s'\n", name);
424               pFile = pFile->next;
425               continue;
426             }
427           }
428         }
429         // for a given name, we grab the first .def in the vfs
430         // this allows to override baseq3/scripts/entities.def for instance
431         char relPath[PATH_MAX];
432         strcpy(relPath, "scripts/");
433         strcat(relPath, (char*)pFile->data);
434         char *fullpath = vfsGetFullPath(relPath, 0, 0);
435         if (!fullpath)
436         {
437           Sys_FPrintf(SYS_ERR, "Failed to find the full path for \"%s\" in the VFS\n", relPath);
438         }
439         else
440           pTable->m_pfnScanFile(fullpath);
441         if (g_pGameDescription->mEClassSingleLoad)
442           break;
443         pFile = pFile->next;
444       }
445       vfsClearFileDirList(&pFiles);
446       pFiles = NULL;
447     }
448     else
449       Sys_FPrintf(SYS_ERR, "Didn't find any scripts/*.%s files to load EClass information\n", pTable->m_pfnGetExtension());
450
451     // we deal with two formats max, if the other table exists, loop again
452     if (g_bHaveEClassExt && pTable == &g_EClassDefTable)
453       pTable = &g_EClassExtTable;
454     else
455       pTable = NULL; // done, exit
456   }
457   Eclass_CreateSpriteModelPaths();
458 }
459
460 eclass_t *Eclass_ForName (const char *name, qboolean has_brushes)
461 {
462         eclass_t        *e;
463
464   if (!name || *name == '\0')
465     return eclass_bad;
466
467 #ifdef _DEBUG
468   // grouping stuff, not an eclass
469   if (strcmp(name, "group_info")==0)
470     Sys_Printf("WARNING: unexpected group_info entity in Eclass_ForName\n");
471 #endif
472
473         if (!name)
474                 return eclass_bad;
475
476         for (e=eclass ; e ; e=e->next)
477                 if (!strcmp (name, e->name))
478                         return e;
479
480         // create a new class for it
481         if (has_brushes)
482         {
483     e = EClass_Create(name , 0, 0.5, 0,NULL,NULL,"Not found in source.");
484         }
485         else
486         {
487     e = EClass_Create(name , 0, 0.5, 0,&smallbox[0],&smallbox[1],"Not found in source.");
488         }
489
490         Eclass_InsertAlphabetized (e);
491
492         return e;
493 }