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