]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/eclass_doom3.cpp
Merge branch 'NateEag-master-patch-12920' into 'master'
[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) do { if (!( expression)) { printParseError(FILE_LINE "\nparse failed: " #expression "\n"); return false; } } while (0)
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                         if ( string_equal_n( key, "editor_", 7 ) ) {
506                                 globalErrorStream() << "unsupported editor key " << makeQuoted( key ) ;
507                         }
508                         EntityClassAttribute& attribute = EntityClass_insertAttribute( entityClass, key ).second;
509                         attribute.m_type = "string";
510                         const char* value;
511                         PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, value ) );
512                         if ( string_equal( value, "}" ) ) { // hack for quake4 powerups.def bug
513                                 globalErrorStream() << "entityDef " << makeQuoted( entityClass.m_name.c_str() ) << " key " << makeQuoted( tmp.c_str() ) << " has no value\n";
514                                 break;
515                         }
516                         else
517                         {
518                                 attribute.m_value = value;
519                         }
520                 }
521                 tokeniser.nextLine();
522         }
523
524         entityClass.m_comments = usage.c_str();
525
526         if ( string_equal( entityClass.m_name.c_str(), "light" ) ) {
527                 {
528                         EntityClassAttribute& attribute = EntityClass_insertAttribute( entityClass, "light_radius" ).second;
529                         attribute.m_type = "vector3";
530                         attribute.m_value = "300 300 300";
531                 }
532                 {
533                         EntityClassAttribute& attribute = EntityClass_insertAttribute( entityClass, "light_center" ).second;
534                         attribute.m_type = "vector3";
535                 }
536                 {
537                         EntityClassAttribute& attribute = EntityClass_insertAttribute( entityClass, "noshadows" ).second;
538                         attribute.m_type = "boolean";
539                         attribute.m_value = "0";
540                 }
541                 {
542                         EntityClassAttribute& attribute = EntityClass_insertAttribute( entityClass, "nospecular" ).second;
543                         attribute.m_type = "boolean";
544                         attribute.m_value = "0";
545                 }
546                 {
547                         EntityClassAttribute& attribute = EntityClass_insertAttribute( entityClass, "nodiffuse" ).second;
548                         attribute.m_type = "boolean";
549                         attribute.m_value = "0";
550                 }
551                 {
552                         EntityClassAttribute& attribute = EntityClass_insertAttribute( entityClass, "falloff" ).second;
553                         attribute.m_type = "real";
554                 }
555         }
556
557         return true;
558 }
559
560 bool EntityClassDoom3_parseEntityDef( Tokeniser& tokeniser ){
561         EntityClass* entityClass = Eclass_Alloc();
562         entityClass->free = &Eclass_Free;
563
564         if ( !EntityClass_parse( *entityClass, tokeniser ) ) {
565                 eclass_capture_state( entityClass ); // finish constructing the entity so that it can be destroyed cleanly.
566                 entityClass->free( entityClass );
567                 return false;
568         }
569
570         EntityClass* inserted = EntityClassDoom3_insertUnique( entityClass );
571         if ( inserted != entityClass ) {
572                 globalErrorStream() << "entityDef " << entityClass->name() << " is already defined, second definition ignored\n";
573                 eclass_capture_state( entityClass ); // finish constructing the entity so that it can be destroyed cleanly.
574                 entityClass->free( entityClass );
575         }
576         return true;
577 }
578
579 bool EntityClassDoom3_parseBlock( Tokeniser& tokeniser, const char* blockType ){
580         if ( string_equal( blockType, "entityDef" ) ) {
581                 return EntityClassDoom3_parseEntityDef( tokeniser );
582         }
583         else if ( string_equal( blockType, "model" ) ) {
584                 return EntityClassDoom3_parseModel( tokeniser );
585         }
586         else
587         {
588                 return EntityClassDoom3_parseUnknown( tokeniser );
589         }
590 }
591
592 bool EntityClassDoom3_parse( TextInputStream& inputStream, const char* filename ){
593         Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( inputStream );
594
595         tokeniser.nextLine();
596
597         for (;; )
598         {
599                 const char* blockType = tokeniser.getToken();
600                 if ( blockType == 0 ) {
601                         return true;
602                 }
603                 CopiedString tmp( blockType );
604                 if ( !EntityClassDoom3_parseBlock( tokeniser, tmp.c_str() ) ) {
605                         globalErrorStream() << GlobalFileSystem().findFile( filename ) << filename << ":" << (unsigned int)tokeniser.getLine() << ": " << tmp.c_str() << " parse failed, skipping rest of file\n";
606                         return false;
607                 }
608         }
609
610         tokeniser.release();
611 }
612
613
614 void EntityClassDoom3_loadFile( const char* filename ){
615         globalOutputStream() << "parsing entity classes from " << makeQuoted( filename ) << "\n";
616
617         StringOutputStream fullname( 256 );
618         fullname << "def/" << filename;
619
620         ArchiveTextFile* file = GlobalFileSystem().openTextFile( fullname.c_str() );
621         if ( file != 0 ) {
622                 EntityClassDoom3_parse( file->getInputStream(), fullname.c_str() );
623                 file->release();
624         }
625 }
626
627 EntityClass* EntityClassDoom3_findOrInsert( const char *name, bool has_brushes ){
628         ASSERT_NOTNULL( name );
629
630         if ( string_empty( name ) ) {
631                 return g_EntityClassDoom3_bad;
632         }
633
634         EntityClasses::iterator i = g_EntityClassDoom3_classes.find( name );
635         if ( i != g_EntityClassDoom3_classes.end()
636              //&& string_equal((*i).first, name)
637                  ) {
638                 return ( *i ).second;
639         }
640
641         EntityClass* e = EntityClass_Create_Default( name, has_brushes );
642         EntityClass* inserted = EntityClassDoom3_insertUnique( e );
643         ASSERT_MESSAGE( inserted == e, "" );
644         return inserted;
645 }
646
647 const ListAttributeType* EntityClassDoom3_findListType( const char* name ){
648         return 0;
649 }
650
651
652 void EntityClass_resolveInheritance( EntityClass* derivedClass ){
653         if ( derivedClass->inheritanceResolved == false ) {
654                 derivedClass->inheritanceResolved = true;
655                 EntityClasses::iterator i = g_EntityClassDoom3_classes.find( derivedClass->m_parent.front().c_str() );
656                 if ( i == g_EntityClassDoom3_classes.end() ) {
657                         globalErrorStream() << "failed to find entityDef " << makeQuoted( derivedClass->m_parent.front().c_str() ) << " inherited by "  << makeQuoted( derivedClass->m_name.c_str() ) << "\n";
658                 }
659                 else
660                 {
661                         EntityClass* parentClass = ( *i ).second;
662                         EntityClass_resolveInheritance( parentClass );
663                         if ( !derivedClass->colorSpecified ) {
664                                 derivedClass->colorSpecified = parentClass->colorSpecified;
665                                 derivedClass->color = parentClass->color;
666                         }
667                         if ( !derivedClass->sizeSpecified ) {
668                                 derivedClass->sizeSpecified = parentClass->sizeSpecified;
669                                 derivedClass->mins = parentClass->mins;
670                                 derivedClass->maxs = parentClass->maxs;
671                                 derivedClass->fixedsize = parentClass->fixedsize;
672                         }
673
674                         for ( EntityClassAttributes::iterator j = parentClass->m_attributes.begin(); j != parentClass->m_attributes.end(); ++j )
675                         {
676                                 EntityClass_insertAttribute( *derivedClass, ( *j ).first.c_str(), ( *j ).second );
677                         }
678                 }
679         }
680 }
681
682 class EntityClassDoom3 : public ModuleObserver
683 {
684 std::size_t m_unrealised;
685 ModuleObservers m_observers;
686 public:
687 EntityClassDoom3() : m_unrealised( 2 ){
688 }
689 void realise(){
690         if ( --m_unrealised == 0 ) {
691                 globalOutputStream() << "searching vfs directory " << makeQuoted( "def" ) << " for *.def\n";
692                 GlobalFileSystem().forEachFile( "def/", "def", makeCallbackF(EntityClassDoom3_loadFile) );
693
694                 {
695                         for ( Models::iterator i = g_models.begin(); i != g_models.end(); ++i )
696                         {
697                                 Model_resolveInheritance( ( *i ).first.c_str(), ( *i ).second );
698                         }
699                 }
700                 {
701                         for ( EntityClasses::iterator i = g_EntityClassDoom3_classes.begin(); i != g_EntityClassDoom3_classes.end(); ++i )
702                         {
703                                 EntityClass_resolveInheritance( ( *i ).second );
704                                 if ( !string_empty( ( *i ).second->m_modelpath.c_str() ) ) {
705                                         Models::iterator j = g_models.find( ( *i ).second->m_modelpath );
706                                         if ( j != g_models.end() ) {
707                                                 ( *i ).second->m_modelpath = ( *j ).second.m_mesh;
708                                                 ( *i ).second->m_skin = ( *j ).second.m_skin;
709                                         }
710                                 }
711                                 eclass_capture_state( ( *i ).second );
712
713                                 StringOutputStream usage( 256 );
714
715                                 usage << "-------- NOTES --------\n";
716
717                                 if ( !string_empty( ( *i ).second->m_comments.c_str() ) ) {
718                                         usage << ( *i ).second->m_comments.c_str() << "\n";
719                                 }
720
721                                 usage << "\n-------- KEYS --------\n";
722
723                                 for ( EntityClassAttributes::iterator j = ( *i ).second->m_attributes.begin(); j != ( *i ).second->m_attributes.end(); ++j )
724                                 {
725                                         const char* name = EntityClassAttributePair_getName( *j );
726                                         const char* description = EntityClassAttributePair_getDescription( *j );
727                                         if ( !string_equal( name, description ) ) {
728                                                 usage << EntityClassAttributePair_getName( *j ) << " : " << EntityClassAttributePair_getDescription( *j ) << "\n";
729                                         }
730                                 }
731
732                                 ( *i ).second->m_comments = usage.c_str();
733                         }
734                 }
735
736                 m_observers.realise();
737         }
738 }
739 void unrealise(){
740         if ( ++m_unrealised == 1 ) {
741                 m_observers.unrealise();
742                 EntityClassDoom3_clear();
743         }
744 }
745 void attach( ModuleObserver& observer ){
746         m_observers.attach( observer );
747 }
748 void detach( ModuleObserver& observer ){
749         m_observers.detach( observer );
750 }
751 };
752
753 EntityClassDoom3 g_EntityClassDoom3;
754
755 void EntityClassDoom3_attach( ModuleObserver& observer ){
756         g_EntityClassDoom3.attach( observer );
757 }
758 void EntityClassDoom3_detach( ModuleObserver& observer ){
759         g_EntityClassDoom3.detach( observer );
760 }
761
762 void EntityClassDoom3_realise(){
763         g_EntityClassDoom3.realise();
764 }
765 void EntityClassDoom3_unrealise(){
766         g_EntityClassDoom3.unrealise();
767 }
768
769 void EntityClassDoom3_construct(){
770         GlobalFileSystem().attach( g_EntityClassDoom3 );
771
772         // start by creating the default unknown eclass
773         g_EntityClassDoom3_bad = EClass_Create( "UNKNOWN_CLASS", Vector3( 0.0f, 0.5f, 0.0f ), "" );
774
775         EntityClassDoom3_realise();
776 }
777
778 void EntityClassDoom3_destroy(){
779         EntityClassDoom3_unrealise();
780
781         g_EntityClassDoom3_bad->free( g_EntityClassDoom3_bad );
782
783         GlobalFileSystem().detach( g_EntityClassDoom3 );
784 }
785
786 class EntityClassDoom3Dependencies : public GlobalFileSystemModuleRef, public GlobalShaderCacheModuleRef
787 {
788 };
789
790 class EntityClassDoom3API
791 {
792 EntityClassManager m_eclassmanager;
793 public:
794 typedef EntityClassManager Type;
795 STRING_CONSTANT( Name, "doom3" );
796
797 EntityClassDoom3API(){
798         EntityClassDoom3_construct();
799
800         m_eclassmanager.findOrInsert = &EntityClassDoom3_findOrInsert;
801         m_eclassmanager.findListType = &EntityClassDoom3_findListType;
802         m_eclassmanager.forEach = &EntityClassDoom3_forEach;
803         m_eclassmanager.attach = &EntityClassDoom3_attach;
804         m_eclassmanager.detach = &EntityClassDoom3_detach;
805         m_eclassmanager.realise = &EntityClassDoom3_realise;
806         m_eclassmanager.unrealise = &EntityClassDoom3_unrealise;
807 }
808 ~EntityClassDoom3API(){
809         EntityClassDoom3_destroy();
810 }
811 EntityClassManager* getTable(){
812         return &m_eclassmanager;
813 }
814 };
815
816 #include "modulesystem/singletonmodule.h"
817 #include "modulesystem/moduleregistry.h"
818
819 typedef SingletonModule<EntityClassDoom3API, EntityClassDoom3Dependencies> EntityClassDoom3Module;
820 typedef Static<EntityClassDoom3Module> StaticEntityClassDoom3Module;
821 StaticRegisterModule staticRegisterEntityClassDoom3( StaticEntityClassDoom3Module::instance() );