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