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