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