- Fix for bug 1106 - .wad files don't get listed in the textures menu (Shaderman)
[xonotic/netradiant.git] / radiant / eclass_doom3.cpp
1 /*
2 Copyright (C) 2001-2006, William Joseph.
3 All Rights Reserved.
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 "eclass_doom3.h"
23
24 #include "debugging/debugging.h"
25
26 #include <map>
27
28 #include "ifilesystem.h"
29 #include "iscriplib.h"
30 #include "iarchive.h"
31 #include "qerplugin.h"
32
33 #include "generic/callback.h"
34 #include "string/string.h"
35 #include "eclasslib.h"
36 #include "os/path.h"
37 #include "os/dir.h"
38 #include "stream/stringstream.h"
39 #include "moduleobservers.h"
40 #include "stringio.h"
41
42 class RawString
43 {
44   const char* m_value;
45 public:
46   RawString(const char* value) : m_value(value)
47   {
48   }
49   const char* c_str() const
50   {
51     return m_value;
52   }
53 };
54
55 inline bool operator<(const RawString& self, const RawString& other)
56 {
57   return string_less_nocase(self.c_str(), other.c_str());
58 }
59
60 typedef std::map<RawString, EntityClass*> EntityClasses;
61 EntityClasses g_EntityClassDoom3_classes;
62 EntityClass     *g_EntityClassDoom3_bad = 0;
63
64
65 void EntityClassDoom3_clear()
66 {
67   for(EntityClasses::iterator i = g_EntityClassDoom3_classes.begin(); i != g_EntityClassDoom3_classes.end(); ++i)
68   {
69     (*i).second->free((*i).second);
70   }
71   g_EntityClassDoom3_classes.clear();
72 }
73
74 // entityClass will be inserted only if another of the same name does not already exist.
75 // if entityClass was inserted, the same object is returned, otherwise the already-existing object is returned.
76 EntityClass* EntityClassDoom3_insertUnique(EntityClass* entityClass)
77 {
78   return (*g_EntityClassDoom3_classes.insert(EntityClasses::value_type(entityClass->name(), entityClass)).first).second;
79 }
80
81 void EntityClassDoom3_forEach(EntityClassVisitor& visitor)
82 {
83   for(EntityClasses::iterator i = g_EntityClassDoom3_classes.begin(); i != g_EntityClassDoom3_classes.end(); ++i)
84   {
85     visitor.visit((*i).second);
86   }
87 }
88
89 inline void printParseError(const char* message)
90 {
91   globalErrorStream() << message;
92 }
93
94 #define PARSE_RETURN_FALSE_IF_FAIL(expression) if(!(expression)) { printParseError(FILE_LINE "\nparse failed: " #expression "\n"); return false; } else
95
96 bool EntityClassDoom3_parseToken(Tokeniser& tokeniser)
97 {
98   const char* token = tokeniser.getToken();
99   PARSE_RETURN_FALSE_IF_FAIL(token != 0);
100   return true;
101 }
102
103 bool EntityClassDoom3_parseToken(Tokeniser& tokeniser, const char* string)
104 {
105   const char* token = tokeniser.getToken();
106   PARSE_RETURN_FALSE_IF_FAIL(token != 0);
107   return string_equal(token, string);
108 }
109
110 bool EntityClassDoom3_parseString(Tokeniser& tokeniser, const char*& s)
111 {
112   const char* token = tokeniser.getToken();
113   PARSE_RETURN_FALSE_IF_FAIL(token != 0);
114   s = token;
115   return true;
116 }
117
118 bool EntityClassDoom3_parseString(Tokeniser& tokeniser, CopiedString& s)
119 {
120   const char* token = tokeniser.getToken();
121   PARSE_RETURN_FALSE_IF_FAIL(token != 0);
122   s = token;
123   return true;
124 }
125
126 bool EntityClassDoom3_parseString(Tokeniser& tokeniser, StringOutputStream& s)
127 {
128   const char* token = tokeniser.getToken();
129   PARSE_RETURN_FALSE_IF_FAIL(token != 0);
130   s << token;
131   return true;
132 }
133
134 bool EntityClassDoom3_parseUnknown(Tokeniser& tokeniser)
135 {
136   //const char* name = 
137   PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser));
138
139   //globalOutputStream() << "parsing unknown block " << makeQuoted(name) << "\n";
140
141   PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser, "{"));
142   tokeniser.nextLine();
143
144   std::size_t depth = 1;
145   for(;;)
146   {
147     const char* token;
148     PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, token));
149     if(string_equal(token, "}"))
150     {
151       if(--depth == 0)
152       {
153         tokeniser.nextLine();
154         break;
155       }
156     }
157     else if(string_equal(token, "{"))
158     {
159       ++depth;
160     }
161     tokeniser.nextLine();
162   }
163   return true;
164 }
165
166
167 class Model
168 {
169 public:
170   bool m_resolved;
171   CopiedString m_mesh;
172   CopiedString m_skin;
173   CopiedString m_parent;
174   typedef std::map<CopiedString, CopiedString> Anims;
175   Anims m_anims;
176   Model() : m_resolved(false)
177   {
178   }
179 };
180
181 typedef std::map<CopiedString, Model> Models;
182
183 Models g_models;
184
185 void Model_resolveInheritance(const char* name, Model& model)
186 {
187   if(model.m_resolved == false)
188   {
189     model.m_resolved = true;
190
191     if(!string_empty(model.m_parent.c_str()))
192     {
193       Models::iterator i = g_models.find(model.m_parent);
194       if(i == g_models.end())
195       {
196         globalErrorStream() << "model " << name << " inherits unknown model " << model.m_parent.c_str() << "\n";
197       }
198       else
199       {
200         Model_resolveInheritance((*i).first.c_str(), (*i).second);
201         model.m_mesh = (*i).second.m_mesh;
202         model.m_skin = (*i).second.m_skin;
203       }
204     }
205   }
206 }
207
208 bool EntityClassDoom3_parseModel(Tokeniser& tokeniser)
209 {
210   const char* name;
211   PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, name));
212
213   Model& model = g_models[name];
214
215   PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser, "{"));
216   tokeniser.nextLine();
217
218   for(;;)
219   {
220     const char* parameter;
221     PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, parameter));
222     if(string_equal(parameter, "}"))
223     {
224       tokeniser.nextLine();
225       break;
226     }
227     else if(string_equal(parameter, "inherit"))
228     {
229       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, model.m_parent));
230       tokeniser.nextLine();
231     }
232     else if(string_equal(parameter, "remove"))
233     {
234       //const char* remove =
235       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser));
236       tokeniser.nextLine();
237     }
238     else if(string_equal(parameter, "mesh"))
239     {
240       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, model.m_mesh));
241       tokeniser.nextLine();
242     }
243     else if(string_equal(parameter, "skin"))
244     {
245       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, model.m_skin));
246       tokeniser.nextLine();
247     }
248     else if(string_equal(parameter, "offset"))
249     {
250       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser, "("));
251       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser));
252       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser));
253       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser));
254       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser, ")"));
255       tokeniser.nextLine();
256     }
257     else if(string_equal(parameter, "channel"))
258     {
259       //const char* channelName =
260       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser));
261       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser, "("));
262       for(;;)
263       {
264         const char* end;
265         PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, end));
266         if(string_equal(end, ")"))
267         {
268           tokeniser.nextLine();
269           break;
270         }
271       }
272     }
273     else if(string_equal(parameter, "anim"))
274     {
275       CopiedString animName;
276       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, animName));
277       const char* animFile;
278       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, animFile));
279       model.m_anims.insert(Model::Anims::value_type(animName, animFile));
280
281       const char* token;
282       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, token));
283
284       while(string_equal(token, ","))
285       {
286         PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, animFile));
287         PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, token));
288       }
289
290       if(string_equal(token, "{"))
291       {
292         for(;;)
293         {
294           const char* end;
295           PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, end));
296           if(string_equal(end, "}"))
297           {
298             tokeniser.nextLine();
299             break;
300           }
301           tokeniser.nextLine();
302         }
303       } 
304       else
305       {
306         tokeniser.ungetToken();
307       }
308     }
309     else
310     {
311       globalErrorStream() << "unknown model parameter: " << makeQuoted(parameter) << "\n";
312       return false;
313     }
314     tokeniser.nextLine();
315   }
316   return true;
317 }
318
319 inline bool char_isSpaceOrTab(char c)
320 {
321   return c == ' ' || c == '\t';
322 }
323
324 inline bool char_isNotSpaceOrTab(char c)
325 {
326   return !char_isSpaceOrTab(c);
327 }
328
329 template<typename Predicate>
330 inline const char* string_find_if(const char* string, Predicate predicate)
331 {
332   for(; *string != 0; ++string)
333   {
334     if(predicate(*string))
335     {
336       return string;
337     }
338   }
339   return string;
340 }
341
342 inline const char* string_findFirstSpaceOrTab(const char* string)
343 {
344   return string_find_if(string, char_isSpaceOrTab);
345 }
346
347 inline const char* string_findFirstNonSpaceOrTab(const char* string)
348 {
349   return string_find_if(string, char_isNotSpaceOrTab);
350 }
351
352
353 static bool EntityClass_parse(EntityClass& entityClass, Tokeniser& tokeniser)
354 {
355   PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, entityClass.m_name));
356
357   PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser, "{"));
358   tokeniser.nextLine();
359
360   StringOutputStream usage(256);
361   StringOutputStream description(256);
362   CopiedString* currentDescription = 0;
363   StringOutputStream* currentString = 0;
364
365   for(;;)
366   {
367     const char* key;
368     PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, key));
369     
370     const char* last = string_findFirstSpaceOrTab(key);
371     CopiedString first(StringRange(key, last));
372
373     if(!string_empty(last))
374     {
375       last = string_findFirstNonSpaceOrTab(last);
376     }
377
378     if(currentString != 0 && string_equal(key, "\\"))
379     {
380       tokeniser.nextLine();
381       *currentString << " ";
382       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, *currentString));
383       continue;
384     }
385
386     if(currentDescription != 0)
387     {
388       *currentDescription = description.c_str();
389       description.clear();
390       currentDescription = 0;
391     }
392     currentString = 0;
393
394     if(string_equal(key, "}"))
395     {
396       tokeniser.nextLine();
397       break;
398     }
399     else if(string_equal(key, "model"))
400     {
401       const char* token;
402       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, token));
403       entityClass.fixedsize = true;
404       StringOutputStream buffer(256);
405       buffer << PathCleaned(token);
406       entityClass.m_modelpath = buffer.c_str();
407     }
408     else if(string_equal(key, "editor_color"))
409     {
410       const char* value;
411       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, value));
412       if(!string_empty(value))
413       {
414         entityClass.colorSpecified = true;
415         bool success = string_parse_vector3(value, entityClass.color);
416         ASSERT_MESSAGE(success, "editor_color: parse error");
417       }
418     }
419     else if(string_equal(key, "editor_ragdoll"))
420     {
421       //bool ragdoll = atoi(tokeniser.getToken()) != 0;
422       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser));
423     }
424     else if(string_equal(key, "editor_mins"))
425     {
426       entityClass.sizeSpecified = true;
427       const char* value;
428       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, value));
429       if(!string_empty(value) && !string_equal(value, "?"))
430       {
431         entityClass.fixedsize = true;
432         bool success = string_parse_vector3(value, entityClass.mins);
433         ASSERT_MESSAGE(success, "editor_mins: parse error");
434       }
435     }
436     else if(string_equal(key, "editor_maxs"))
437     {
438       entityClass.sizeSpecified = true;
439       const char* value;
440       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, value));
441       if(!string_empty(value) && !string_equal(value, "?"))
442       {
443         entityClass.fixedsize = true;
444         bool success = string_parse_vector3(value, entityClass.maxs);
445         ASSERT_MESSAGE(success, "editor_maxs: parse error");
446       }
447     }
448     else if(string_equal(key, "editor_usage"))
449     {
450       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, usage));
451       currentString = &usage;
452     }
453     else if(string_equal_n(key, "editor_usage", 12))
454     {
455       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, usage));
456       currentString = &usage;
457     }
458     else if(string_equal(key, "editor_rotatable")
459       || string_equal(key, "editor_showangle")
460           || string_equal(key, "editor_showangles") // typo? in prey movables.def
461       || string_equal(key, "editor_mover")
462       || string_equal(key, "editor_model")
463       || string_equal(key, "editor_material")
464       || string_equal(key, "editor_combatnode")
465       || (!string_empty(last) && string_equal(first.c_str(), "editor_gui"))
466       || string_equal_n(key, "editor_copy", 11))
467     {
468       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser));
469     }
470     else if(!string_empty(last) && (string_equal(first.c_str(), "editor_var") || string_equal(first.c_str(), "editor_string")))
471     {
472       EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, last).second;
473       attribute.m_type = "string";
474       currentDescription = &attribute.m_description;
475       currentString = &description;
476       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description));
477     }
478     else if(!string_empty(last) && string_equal(first.c_str(), "editor_float"))
479     {
480       EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, last).second;
481       attribute.m_type = "string";
482       currentDescription = &attribute.m_description;
483       currentString = &description;
484       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description));
485     }
486     else if(!string_empty(last) && string_equal(first.c_str(), "editor_snd"))
487     {
488       EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, last).second;
489       attribute.m_type = "sound";
490       currentDescription = &attribute.m_description;
491       currentString = &description;
492       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description));
493     }
494     else if(!string_empty(last) && string_equal(first.c_str(), "editor_bool"))
495     {
496       EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, last).second;
497       attribute.m_type = "boolean";
498       currentDescription = &attribute.m_description;
499       currentString = &description;
500       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description));
501     }
502     else if(!string_empty(last) && string_equal(first.c_str(), "editor_int"))
503     {
504       EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, last).second;
505       attribute.m_type = "integer";
506       currentDescription = &attribute.m_description;
507       currentString = &description;
508       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description));
509     }
510     else if(!string_empty(last) && string_equal(first.c_str(), "editor_model"))
511     {
512       EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, last).second;
513       attribute.m_type = "model";
514       currentDescription = &attribute.m_description;
515       currentString = &description;
516       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description));
517     }
518     else if(!string_empty(last) && string_equal(first.c_str(), "editor_color"))
519     {
520       EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, last).second;
521       attribute.m_type = "color";
522       currentDescription = &attribute.m_description;
523       currentString = &description;
524       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description));
525     }
526     else if(!string_empty(last) && (string_equal(first.c_str(), "editor_material") || string_equal(first.c_str(), "editor_mat")))
527     {
528       EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, last).second;
529       attribute.m_type = "shader";
530       currentDescription = &attribute.m_description;
531       currentString = &description;
532       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, description));
533     }
534     else if(string_equal(key, "inherit"))
535     {
536       entityClass.inheritanceResolved = false;
537       ASSERT_MESSAGE(entityClass.m_parent.empty(), "only one 'inherit' supported per entityDef");
538       const char* token;
539       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, token));
540       entityClass.m_parent.push_back(token);
541     }
542     // begin quake4-specific keys
543     else if(string_equal(key, "editor_targetonsel"))
544     {
545       //const char* value =
546       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser));
547     }
548     else if(string_equal(key, "editor_menu"))
549     {
550       //const char* value =
551       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser));
552     }
553     else if(string_equal(key, "editor_ignore"))
554     {
555       //const char* value =
556       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser));
557     }
558     // end quake4-specific keys
559         // begin ignore prey (unknown/unused?) entity keys
560     else if(string_equal(key, "editor_light")
561           || string_equal(key, "editor_def def_debrisspawner")
562           || string_equal(key, "editor_def def_drop")
563           || string_equal(key, "editor_def def_guihand")
564           || string_equal(key, "editor_def def_mine"))
565     {
566       //const char* value =
567       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseToken(tokeniser));
568     }
569         // end ignore prey entity keys
570     else
571     {
572       CopiedString tmp(key);
573       ASSERT_MESSAGE(!string_equal_n(key, "editor_", 7), "unsupported editor key: " << makeQuoted(key));
574       EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, key).second;
575       attribute.m_type = "string";
576       const char* value;
577       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, value));
578       if(string_equal(value, "}")) // hack for quake4 powerups.def bug
579       {
580         globalErrorStream() << "entityDef " << makeQuoted(entityClass.m_name.c_str()) << " key " << makeQuoted(tmp.c_str()) << " has no value\n";
581         break;
582       }
583       else
584       {
585         attribute.m_value = value;
586       }
587     }
588     tokeniser.nextLine();
589   }
590
591   entityClass.m_comments = usage.c_str();
592
593   if(string_equal(entityClass.m_name.c_str(), "light"))
594   {
595     {
596       EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, "light_radius").second;
597       attribute.m_type = "vector3";
598       attribute.m_value = "300 300 300";
599     }
600     {
601       EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, "light_center").second;
602       attribute.m_type = "vector3";
603     }
604     {
605       EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, "noshadows").second;
606       attribute.m_type = "boolean";
607       attribute.m_value = "0";
608     }
609     {
610       EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, "nospecular").second;
611       attribute.m_type = "boolean";
612       attribute.m_value = "0";
613     }
614     {
615       EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, "nodiffuse").second;
616       attribute.m_type = "boolean";
617       attribute.m_value = "0";
618     }
619     {
620       EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, "falloff").second;
621       attribute.m_type = "real";
622     }
623   }
624
625   return true;
626 }
627
628 bool EntityClassDoom3_parseEntityDef(Tokeniser& tokeniser)
629 {
630   EntityClass* entityClass = Eclass_Alloc();
631   entityClass->free = &Eclass_Free;
632
633   if(!EntityClass_parse(*entityClass, tokeniser))
634   {
635     eclass_capture_state(entityClass); // finish constructing the entity so that it can be destroyed cleanly.
636     entityClass->free(entityClass);
637     return false;
638   }
639
640   EntityClass* inserted = EntityClassDoom3_insertUnique(entityClass);
641   if(inserted != entityClass)
642   {
643     globalErrorStream() << "entityDef " << entityClass->name() << " is already defined, second definition ignored\n";
644     eclass_capture_state(entityClass); // finish constructing the entity so that it can be destroyed cleanly.
645     entityClass->free(entityClass);
646   }
647   return true;
648 }
649
650 bool EntityClassDoom3_parseBlock(Tokeniser& tokeniser, const char* blockType)
651 {
652   if(string_equal(blockType, "entityDef"))
653   {
654     return EntityClassDoom3_parseEntityDef(tokeniser);
655   }
656   else if(string_equal(blockType, "model"))
657   {
658     return EntityClassDoom3_parseModel(tokeniser);
659   }
660   else
661   {
662     return EntityClassDoom3_parseUnknown(tokeniser);
663   }
664 }
665
666 bool EntityClassDoom3_parse(TextInputStream& inputStream, const char* filename)
667 {
668   Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser(inputStream);
669
670   tokeniser.nextLine();
671
672   for(;;)
673   {
674     const char* blockType = tokeniser.getToken();
675     if(blockType == 0)
676     {
677       return true;
678     }
679     CopiedString tmp(blockType);
680     if(!EntityClassDoom3_parseBlock(tokeniser, tmp.c_str()))
681     {
682       globalErrorStream() << GlobalFileSystem().findFile(filename) << filename << ":" << (unsigned int)tokeniser.getLine() << ": " << tmp.c_str() << " parse failed, skipping rest of file\n";
683       return false;
684     }
685   }
686
687   tokeniser.release();
688 }
689
690
691 void EntityClassDoom3_loadFile(const char* filename)
692 {
693   globalOutputStream() << "parsing entity classes from " << makeQuoted(filename) << "\n";
694
695   StringOutputStream fullname(256);
696   fullname << "def/" << filename;
697
698         ArchiveTextFile* file = GlobalFileSystem().openTextFile(fullname.c_str());
699   if(file != 0)
700   {
701     EntityClassDoom3_parse(file->getInputStream(), fullname.c_str());
702     file->release();
703   }
704 }
705
706 EntityClass* EntityClassDoom3_findOrInsert(const char *name, bool has_brushes)
707 {
708         ASSERT_NOTNULL(name);
709
710   if(string_empty(name))
711   {
712     return g_EntityClassDoom3_bad;
713   }
714
715   EntityClasses::iterator i = g_EntityClassDoom3_classes.find(name);
716   if(i != g_EntityClassDoom3_classes.end() 
717     //&& string_equal((*i).first, name)
718     )
719   {
720     return (*i).second;
721   }
722
723         EntityClass* e = EntityClass_Create_Default(name, has_brushes);
724         EntityClass* inserted = EntityClassDoom3_insertUnique(e);
725   ASSERT_MESSAGE(inserted == e, "");
726   return inserted;
727 }
728
729 const ListAttributeType* EntityClassDoom3_findListType(const char* name)
730 {
731   return 0;
732 }
733
734
735 void EntityClass_resolveInheritance(EntityClass* derivedClass)
736 {
737   if(derivedClass->inheritanceResolved == false)
738   {
739     derivedClass->inheritanceResolved = true;
740     EntityClasses::iterator i = g_EntityClassDoom3_classes.find(derivedClass->m_parent.front().c_str());
741     if(i == g_EntityClassDoom3_classes.end())
742     {
743       globalErrorStream() << "failed to find entityDef " << makeQuoted(derivedClass->m_parent.front().c_str()) << " inherited by "  << makeQuoted(derivedClass->m_name.c_str()) << "\n";
744     }
745     else
746     {
747       EntityClass* parentClass = (*i).second;
748       EntityClass_resolveInheritance(parentClass);
749       if(!derivedClass->colorSpecified)
750       {
751         derivedClass->colorSpecified = parentClass->colorSpecified;
752         derivedClass->color = parentClass->color;
753       }
754       if(!derivedClass->sizeSpecified)
755       {
756         derivedClass->sizeSpecified = parentClass->sizeSpecified;
757         derivedClass->mins = parentClass->mins;
758         derivedClass->maxs = parentClass->maxs;
759         derivedClass->fixedsize = parentClass->fixedsize;
760       }
761
762       for(EntityClassAttributes::iterator j = parentClass->m_attributes.begin(); j != parentClass->m_attributes.end(); ++j)
763       {
764         EntityClass_insertAttribute(*derivedClass, (*j).first.c_str(), (*j).second);
765       }
766     }
767   }
768 }
769
770 class EntityClassDoom3 : public ModuleObserver
771 {
772   std::size_t m_unrealised;
773   ModuleObservers m_observers;
774 public:
775   EntityClassDoom3() : m_unrealised(2)
776   {
777   }
778   void realise()
779   {
780     if(--m_unrealised == 0)
781     {
782       globalOutputStream() << "searching vfs directory " << makeQuoted("def") << " for *.def\n";
783       GlobalFileSystem().forEachFile("def/", "def", FreeCaller1<const char*, EntityClassDoom3_loadFile>());
784
785       {
786         for(Models::iterator i = g_models.begin(); i != g_models.end(); ++i)
787         {
788           Model_resolveInheritance((*i).first.c_str(), (*i).second);
789         }
790       }
791       {
792         for(EntityClasses::iterator i = g_EntityClassDoom3_classes.begin(); i != g_EntityClassDoom3_classes.end(); ++i)
793         {
794           EntityClass_resolveInheritance((*i).second);
795           if(!string_empty((*i).second->m_modelpath.c_str()))
796           {
797             Models::iterator j = g_models.find((*i).second->m_modelpath);
798             if(j != g_models.end())
799             {
800               (*i).second->m_modelpath = (*j).second.m_mesh;
801               (*i).second->m_skin = (*j).second.m_skin;
802             }
803           }
804           eclass_capture_state((*i).second);
805
806           StringOutputStream usage(256);
807
808           usage << "-------- NOTES --------\n";
809
810           if(!string_empty((*i).second->m_comments.c_str()))
811           {
812             usage << (*i).second->m_comments.c_str() << "\n";
813           }
814
815           usage << "\n-------- KEYS --------\n";
816
817           for(EntityClassAttributes::iterator j = (*i).second->m_attributes.begin(); j != (*i).second->m_attributes.end(); ++j)
818           {
819             const char* name = EntityClassAttributePair_getName(*j);
820             const char* description = EntityClassAttributePair_getDescription(*j);
821             if(!string_equal(name, description))
822             {
823               usage << EntityClassAttributePair_getName(*j) << " : " << EntityClassAttributePair_getDescription(*j) << "\n";
824             }
825           }
826
827           (*i).second->m_comments = usage.c_str();
828         }
829       }
830
831       m_observers.realise();
832     }
833   }
834   void unrealise()
835   {
836     if(++m_unrealised == 1)
837     {
838       m_observers.unrealise();
839       EntityClassDoom3_clear();
840     }
841   }
842   void attach(ModuleObserver& observer)
843   {
844     m_observers.attach(observer);
845   }
846   void detach(ModuleObserver& observer)
847   {
848     m_observers.detach(observer);
849   }
850 };
851
852 EntityClassDoom3 g_EntityClassDoom3;
853
854 void EntityClassDoom3_attach(ModuleObserver& observer)
855 {
856   g_EntityClassDoom3.attach(observer);
857 }
858 void EntityClassDoom3_detach(ModuleObserver& observer)
859 {
860   g_EntityClassDoom3.detach(observer);
861 }
862
863 void EntityClassDoom3_realise()
864 {
865   g_EntityClassDoom3.realise();
866 }
867 void EntityClassDoom3_unrealise()
868 {
869   g_EntityClassDoom3.unrealise();
870 }
871
872 void EntityClassDoom3_construct()
873 {
874   GlobalFileSystem().attach(g_EntityClassDoom3);
875
876   // start by creating the default unknown eclass
877   g_EntityClassDoom3_bad = EClass_Create("UNKNOWN_CLASS", Vector3(0.0f, 0.5f, 0.0f), "");
878
879   EntityClassDoom3_realise();
880 }
881
882 void EntityClassDoom3_destroy()
883 {
884   EntityClassDoom3_unrealise();
885
886   g_EntityClassDoom3_bad->free(g_EntityClassDoom3_bad);
887
888   GlobalFileSystem().detach(g_EntityClassDoom3);
889 }
890
891 class EntityClassDoom3Dependencies : public GlobalFileSystemModuleRef, public GlobalShaderCacheModuleRef
892 {
893 };
894
895 class EntityClassDoom3API
896 {
897   EntityClassManager m_eclassmanager;
898 public:
899   typedef EntityClassManager Type;
900   STRING_CONSTANT(Name, "doom3");
901
902   EntityClassDoom3API()
903   {
904     EntityClassDoom3_construct();
905
906     m_eclassmanager.findOrInsert = &EntityClassDoom3_findOrInsert;
907     m_eclassmanager.findListType = &EntityClassDoom3_findListType;
908     m_eclassmanager.forEach = &EntityClassDoom3_forEach;
909     m_eclassmanager.attach = &EntityClassDoom3_attach;
910     m_eclassmanager.detach = &EntityClassDoom3_detach;
911     m_eclassmanager.realise = &EntityClassDoom3_realise;
912     m_eclassmanager.unrealise = &EntityClassDoom3_unrealise;
913   }
914   ~EntityClassDoom3API()
915   {
916     EntityClassDoom3_destroy();
917   }
918   EntityClassManager* getTable()
919   {
920     return &m_eclassmanager;
921   }
922 };
923
924 #include "modulesystem/singletonmodule.h"
925 #include "modulesystem/moduleregistry.h"
926
927 typedef SingletonModule<EntityClassDoom3API, EntityClassDoom3Dependencies> EntityClassDoom3Module;
928 typedef Static<EntityClassDoom3Module> StaticEntityClassDoom3Module;
929 StaticRegisterModule staticRegisterEntityClassDoom3(StaticEntityClassDoom3Module::instance());