b172a36a7dd18a93414d17640c1b026d83afbbd9
[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       usage << "\n";
456       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, usage));
457       currentString = &usage;
458     }
459     else if(string_equal(key, "editor_rotatable")
460       || string_equal(key, "editor_showangle")
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     else
560     {
561       CopiedString tmp(key);
562       ASSERT_MESSAGE(!string_equal_n(key, "editor_", 7), "unsupported editor key: " << makeQuoted(key));
563       EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, key).second;
564       attribute.m_type = "string";
565       const char* value;
566       PARSE_RETURN_FALSE_IF_FAIL(EntityClassDoom3_parseString(tokeniser, value));
567       if(string_equal(value, "}")) // hack for quake4 powerups.def bug
568       {
569         globalErrorStream() << "entityDef " << makeQuoted(entityClass.m_name.c_str()) << " key " << makeQuoted(tmp.c_str()) << " has no value\n";
570         break;
571       }
572       else
573       {
574         attribute.m_value = value;
575       }
576     }
577     tokeniser.nextLine();
578   }
579
580   entityClass.m_comments = usage.c_str();
581
582   if(string_equal(entityClass.m_name.c_str(), "light"))
583   {
584     {
585       EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, "light_radius").second;
586       attribute.m_type = "vector3";
587       attribute.m_value = "300 300 300";
588     }
589     {
590       EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, "light_center").second;
591       attribute.m_type = "vector3";
592     }
593     {
594       EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, "noshadows").second;
595       attribute.m_type = "boolean";
596       attribute.m_value = "0";
597     }
598     {
599       EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, "nospecular").second;
600       attribute.m_type = "boolean";
601       attribute.m_value = "0";
602     }
603     {
604       EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, "nodiffuse").second;
605       attribute.m_type = "boolean";
606       attribute.m_value = "0";
607     }
608     {
609       EntityClassAttribute& attribute = EntityClass_insertAttribute(entityClass, "falloff").second;
610       attribute.m_type = "real";
611     }
612   }
613
614   return true;
615 }
616
617 bool EntityClassDoom3_parseEntityDef(Tokeniser& tokeniser)
618 {
619   EntityClass* entityClass = Eclass_Alloc();
620   entityClass->free = &Eclass_Free;
621
622   if(!EntityClass_parse(*entityClass, tokeniser))
623   {
624     eclass_capture_state(entityClass); // finish constructing the entity so that it can be destroyed cleanly.
625     entityClass->free(entityClass);
626     return false;
627   }
628
629   EntityClass* inserted = EntityClassDoom3_insertUnique(entityClass);
630   if(inserted != entityClass)
631   {
632     globalErrorStream() << "entityDef " << entityClass->name() << " is already defined, second definition ignored\n";
633     eclass_capture_state(entityClass); // finish constructing the entity so that it can be destroyed cleanly.
634     entityClass->free(entityClass);
635   }
636   return true;
637 }
638
639 bool EntityClassDoom3_parseBlock(Tokeniser& tokeniser, const char* blockType)
640 {
641   if(string_equal(blockType, "entityDef"))
642   {
643     return EntityClassDoom3_parseEntityDef(tokeniser);
644   }
645   else if(string_equal(blockType, "model"))
646   {
647     return EntityClassDoom3_parseModel(tokeniser);
648   }
649   else
650   {
651     return EntityClassDoom3_parseUnknown(tokeniser);
652   }
653 }
654
655 bool EntityClassDoom3_parse(TextInputStream& inputStream, const char* filename)
656 {
657   Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser(inputStream);
658
659   tokeniser.nextLine();
660
661   for(;;)
662   {
663     const char* blockType = tokeniser.getToken();
664     if(blockType == 0)
665     {
666       return true;
667     }
668     CopiedString tmp(blockType);
669     if(!EntityClassDoom3_parseBlock(tokeniser, tmp.c_str()))
670     {
671       globalErrorStream() << GlobalFileSystem().findFile(filename) << filename << ":" << (unsigned int)tokeniser.getLine() << ": " << tmp.c_str() << " parse failed, skipping rest of file\n";
672       return false;
673     }
674   }
675
676   tokeniser.release();
677 }
678
679
680 void EntityClassDoom3_loadFile(const char* filename)
681 {
682   globalOutputStream() << "parsing entity classes from " << makeQuoted(filename) << "\n";
683
684   StringOutputStream fullname(256);
685   fullname << "def/" << filename;
686
687         ArchiveTextFile* file = GlobalFileSystem().openTextFile(fullname.c_str());
688   if(file != 0)
689   {
690     EntityClassDoom3_parse(file->getInputStream(), fullname.c_str());
691     file->release();
692   }
693 }
694
695 EntityClass* EntityClassDoom3_findOrInsert(const char *name, bool has_brushes)
696 {
697         ASSERT_NOTNULL(name);
698
699   if(string_empty(name))
700   {
701     return g_EntityClassDoom3_bad;
702   }
703
704   EntityClasses::iterator i = g_EntityClassDoom3_classes.find(name);
705   if(i != g_EntityClassDoom3_classes.end() 
706     //&& string_equal((*i).first, name)
707     )
708   {
709     return (*i).second;
710   }
711
712         EntityClass* e = EntityClass_Create_Default(name, has_brushes);
713         EntityClass* inserted = EntityClassDoom3_insertUnique(e);
714   ASSERT_MESSAGE(inserted == e, "");
715   return inserted;
716 }
717
718 const ListAttributeType* EntityClassDoom3_findListType(const char* name)
719 {
720   return 0;
721 }
722
723
724 void EntityClass_resolveInheritance(EntityClass* derivedClass)
725 {
726   if(derivedClass->inheritanceResolved == false)
727   {
728     derivedClass->inheritanceResolved = true;
729     EntityClasses::iterator i = g_EntityClassDoom3_classes.find(derivedClass->m_parent.front().c_str());
730     if(i == g_EntityClassDoom3_classes.end())
731     {
732       globalErrorStream() << "failed to find entityDef " << makeQuoted(derivedClass->m_parent.front().c_str()) << " inherited by "  << makeQuoted(derivedClass->m_name.c_str()) << "\n";
733     }
734     else
735     {
736       EntityClass* parentClass = (*i).second;
737       EntityClass_resolveInheritance(parentClass);
738       if(!derivedClass->colorSpecified)
739       {
740         derivedClass->colorSpecified = parentClass->colorSpecified;
741         derivedClass->color = parentClass->color;
742       }
743       if(!derivedClass->sizeSpecified)
744       {
745         derivedClass->sizeSpecified = parentClass->sizeSpecified;
746         derivedClass->mins = parentClass->mins;
747         derivedClass->maxs = parentClass->maxs;
748         derivedClass->fixedsize = parentClass->fixedsize;
749       }
750
751       for(EntityClassAttributes::iterator j = parentClass->m_attributes.begin(); j != parentClass->m_attributes.end(); ++j)
752       {
753         EntityClass_insertAttribute(*derivedClass, (*j).first.c_str(), (*j).second);
754       }
755     }
756   }
757 }
758
759 class EntityClassDoom3 : public ModuleObserver
760 {
761   std::size_t m_unrealised;
762   ModuleObservers m_observers;
763 public:
764   EntityClassDoom3() : m_unrealised(2)
765   {
766   }
767   void realise()
768   {
769     if(--m_unrealised == 0)
770     {
771       globalOutputStream() << "searching vfs directory " << makeQuoted("def") << " for *.def\n";
772       GlobalFileSystem().forEachFile("def/", "def", FreeCaller1<const char*, EntityClassDoom3_loadFile>());
773
774       {
775         for(Models::iterator i = g_models.begin(); i != g_models.end(); ++i)
776         {
777           Model_resolveInheritance((*i).first.c_str(), (*i).second);
778         }
779       }
780       {
781         for(EntityClasses::iterator i = g_EntityClassDoom3_classes.begin(); i != g_EntityClassDoom3_classes.end(); ++i)
782         {
783           EntityClass_resolveInheritance((*i).second);
784           if(!string_empty((*i).second->m_modelpath.c_str()))
785           {
786             Models::iterator j = g_models.find((*i).second->m_modelpath);
787             if(j != g_models.end())
788             {
789               (*i).second->m_modelpath = (*j).second.m_mesh;
790               (*i).second->m_skin = (*j).second.m_skin;
791             }
792           }
793           eclass_capture_state((*i).second);
794
795           StringOutputStream usage(256);
796
797           usage << "-------- KEYS --------\n";
798
799           for(EntityClassAttributes::iterator j = (*i).second->m_attributes.begin(); j != (*i).second->m_attributes.end(); ++j)
800           {
801             const char* name = EntityClassAttributePair_getName(*j);
802             const char* description = EntityClassAttributePair_getDescription(*j);
803             if(!string_equal(name, description))
804             {
805               usage << EntityClassAttributePair_getName(*j) << " : " << EntityClassAttributePair_getDescription(*j) << "\n";
806             }
807           }
808
809           (*i).second->m_comments = usage.c_str();
810         }
811       }
812
813       m_observers.realise();
814     }
815   }
816   void unrealise()
817   {
818     if(++m_unrealised == 1)
819     {
820       m_observers.unrealise();
821       EntityClassDoom3_clear();
822     }
823   }
824   void attach(ModuleObserver& observer)
825   {
826     m_observers.attach(observer);
827   }
828   void detach(ModuleObserver& observer)
829   {
830     m_observers.detach(observer);
831   }
832 };
833
834 EntityClassDoom3 g_EntityClassDoom3;
835
836 void EntityClassDoom3_attach(ModuleObserver& observer)
837 {
838   g_EntityClassDoom3.attach(observer);
839 }
840 void EntityClassDoom3_detach(ModuleObserver& observer)
841 {
842   g_EntityClassDoom3.detach(observer);
843 }
844
845 void EntityClassDoom3_realise()
846 {
847   g_EntityClassDoom3.realise();
848 }
849 void EntityClassDoom3_unrealise()
850 {
851   g_EntityClassDoom3.unrealise();
852 }
853
854 void EntityClassDoom3_construct()
855 {
856   GlobalFileSystem().attach(g_EntityClassDoom3);
857
858   // start by creating the default unknown eclass
859   g_EntityClassDoom3_bad = EClass_Create("UNKNOWN_CLASS", Vector3(0.0f, 0.5f, 0.0f), "");
860
861   EntityClassDoom3_realise();
862 }
863
864 void EntityClassDoom3_destroy()
865 {
866   EntityClassDoom3_unrealise();
867
868   g_EntityClassDoom3_bad->free(g_EntityClassDoom3_bad);
869
870   GlobalFileSystem().detach(g_EntityClassDoom3);
871 }
872
873 class EntityClassDoom3Dependencies : public GlobalFileSystemModuleRef, public GlobalShaderCacheModuleRef
874 {
875 };
876
877 class EntityClassDoom3API
878 {
879   EntityClassManager m_eclassmanager;
880 public:
881   typedef EntityClassManager Type;
882   STRING_CONSTANT(Name, "doom3");
883
884   EntityClassDoom3API()
885   {
886     EntityClassDoom3_construct();
887
888     m_eclassmanager.findOrInsert = &EntityClassDoom3_findOrInsert;
889     m_eclassmanager.findListType = &EntityClassDoom3_findListType;
890     m_eclassmanager.forEach = &EntityClassDoom3_forEach;
891     m_eclassmanager.attach = &EntityClassDoom3_attach;
892     m_eclassmanager.detach = &EntityClassDoom3_detach;
893     m_eclassmanager.realise = &EntityClassDoom3_realise;
894     m_eclassmanager.unrealise = &EntityClassDoom3_unrealise;
895   }
896   ~EntityClassDoom3API()
897   {
898     EntityClassDoom3_destroy();
899   }
900   EntityClassManager* getTable()
901   {
902     return &m_eclassmanager;
903   }
904 };
905
906 #include "modulesystem/singletonmodule.h"
907 #include "modulesystem/moduleregistry.h"
908
909 typedef SingletonModule<EntityClassDoom3API, EntityClassDoom3Dependencies> EntityClassDoom3Module;
910 typedef Static<EntityClassDoom3Module> StaticEntityClassDoom3Module;
911 StaticRegisterModule staticRegisterEntityClassDoom3(StaticEntityClassDoom3Module::instance());