]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/eclass_fgd.cpp
Merge branch 'optional_q3map2_type' into 'master'
[xonotic/netradiant.git] / radiant / eclass_fgd.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_fgd.h"
23
24 #include "debugging/debugging.h"
25
26 #include <map>
27
28 #include "ifilesystem.h"
29 #include "iscriplib.h"
30 #include "qerplugin.h"
31 #include "mainframe.h"
32
33 #include "string/string.h"
34 #include "eclasslib.h"
35 #include "os/path.h"
36 #include "os/dir.h"
37 #include "stream/stringstream.h"
38 #include "moduleobservers.h"
39 #include "stringio.h"
40 #include "stream/textfilestream.h"
41
42 namespace {
43     typedef std::map<const char *, EntityClass *, RawStringLessNoCase> EntityClasses;
44     EntityClasses g_EntityClassFGD_classes;
45     typedef std::map<const char *, EntityClass *, RawStringLess> BaseClasses;
46     BaseClasses g_EntityClassFGD_bases;
47     EntityClass *g_EntityClassFGD_bad = 0;
48     typedef std::map<CopiedString, ListAttributeType> ListAttributeTypes;
49     ListAttributeTypes g_listTypesFGD;
50 }
51
52
53 void EntityClassFGD_clear()
54 {
55     for (BaseClasses::iterator i = g_EntityClassFGD_bases.begin(); i != g_EntityClassFGD_bases.end(); ++i) {
56         (*i).second->free((*i).second);
57     }
58     g_EntityClassFGD_bases.clear();
59     g_listTypesFGD.clear();
60 }
61
62 EntityClass *EntityClassFGD_insertUniqueBase(EntityClass *entityClass)
63 {
64     std::pair<BaseClasses::iterator, bool> result = g_EntityClassFGD_bases.insert(
65             BaseClasses::value_type(entityClass->name(), entityClass));
66     if (!result.second) {
67         globalErrorStream() << "duplicate base class: " << makeQuoted(entityClass->name()) << "\n";
68         //eclass_capture_state(entityClass);
69         //entityClass->free(entityClass);
70     }
71     return (*result.first).second;
72 }
73
74 EntityClass *EntityClassFGD_insertUnique(EntityClass *entityClass)
75 {
76     EntityClassFGD_insertUniqueBase(entityClass);
77     std::pair<EntityClasses::iterator, bool> result = g_EntityClassFGD_classes.insert(
78             EntityClasses::value_type(entityClass->name(), entityClass));
79     if (!result.second) {
80         globalErrorStream() << "duplicate entity class: " << makeQuoted(entityClass->name()) << "\n";
81         eclass_capture_state(entityClass);
82         entityClass->free(entityClass);
83     }
84     return (*result.first).second;
85 }
86
87 void EntityClassFGD_forEach(EntityClassVisitor &visitor)
88 {
89     for (EntityClasses::iterator i = g_EntityClassFGD_classes.begin(); i != g_EntityClassFGD_classes.end(); ++i) {
90         visitor.visit((*i).second);
91     }
92 }
93
94 inline bool EntityClassFGD_parseToken(Tokeniser &tokeniser, const char *token)
95 {
96     return string_equal(tokeniser.getToken(), token);
97 }
98
99 const char *PARSE_ERROR = "error parsing entity class definition";
100
101 void EntityClassFGD_parseSplitString(Tokeniser &tokeniser, CopiedString &string)
102 {
103     StringOutputStream buffer(256);
104     for (;;) {
105         buffer << tokeniser.getToken();
106         if (!string_equal(tokeniser.getToken(), "+")) {
107             tokeniser.ungetToken();
108             string = buffer.c_str();
109             return;
110         }
111     }
112 }
113
114 void EntityClassFGD_parseClass(Tokeniser &tokeniser, bool fixedsize, bool isBase)
115 {
116     EntityClass *entityClass = Eclass_Alloc();
117     entityClass->free = &Eclass_Free;
118     entityClass->fixedsize = fixedsize;
119     entityClass->inheritanceResolved = false;
120     entityClass->mins = Vector3(-8, -8, -8);
121     entityClass->maxs = Vector3(8, 8, 8);
122
123     for (;;) {
124         const char *property = tokeniser.getToken();
125         if (string_equal(property, "=")) {
126             break;
127         } else if (string_equal(property, "base")) {
128             ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR);
129             for (;;) {
130                 const char *base = tokeniser.getToken();
131                 if (string_equal(base, ")")) {
132                     break;
133                 } else if (!string_equal(base, ",")) {
134                     entityClass->m_parent.push_back(base);
135                 }
136             }
137         } else if (string_equal(property, "size")) {
138             entityClass->sizeSpecified = true;
139             ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR);
140             Tokeniser_getFloat(tokeniser, entityClass->mins.x());
141             Tokeniser_getFloat(tokeniser, entityClass->mins.y());
142             Tokeniser_getFloat(tokeniser, entityClass->mins.z());
143             const char *token = tokeniser.getToken();
144             if (string_equal(token, ",")) {
145                 Tokeniser_getFloat(tokeniser, entityClass->maxs.x());
146                 Tokeniser_getFloat(tokeniser, entityClass->maxs.y());
147                 Tokeniser_getFloat(tokeniser, entityClass->maxs.z());
148                 ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR);
149             } else {
150                 entityClass->maxs = entityClass->mins;
151                 vector3_negate(entityClass->mins);
152                 ASSERT_MESSAGE(string_equal(token, ")"), "");
153             }
154         } else if (string_equal(property, "color")) {
155             entityClass->colorSpecified = true;
156             ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR);
157             Tokeniser_getFloat(tokeniser, entityClass->color.x());
158             entityClass->color.x() /= 256.0;
159             Tokeniser_getFloat(tokeniser, entityClass->color.y());
160             entityClass->color.y() /= 256.0;
161             Tokeniser_getFloat(tokeniser, entityClass->color.z());
162             entityClass->color.z() /= 256.0;
163             ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR);
164         } else if (string_equal(property, "iconsprite")) {
165             ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR);
166             StringOutputStream buffer(256);
167             buffer << PathCleaned(tokeniser.getToken());
168             entityClass->m_modelpath = buffer.c_str();
169             ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR);
170         } else if (string_equal(property, "sprite")
171                    || string_equal(property, "decal")
172                    // hl2 below
173                    || string_equal(property, "overlay")
174                    || string_equal(property, "light")
175                    || string_equal(property, "keyframe")
176                    || string_equal(property, "animator")
177                    || string_equal(property, "quadbounds")) {
178             ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR);
179             ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR);
180         }
181             // hl2 below
182         else if (string_equal(property, "sphere")
183                  || string_equal(property, "sweptplayerhull")
184                  || string_equal(property, "studio")
185                  || string_equal(property, "studioprop")
186                  || string_equal(property, "lightprop")
187                  || string_equal(property, "lightcone")
188                  || string_equal(property, "sidelist")) {
189             ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR);
190             if (string_equal(tokeniser.getToken(), ")")) {
191                 tokeniser.ungetToken();
192             }
193             ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR);
194         } else if (string_equal(property, "line")
195                    || string_equal(property, "cylinder")) {
196             ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR);
197             //const char* r =
198             tokeniser.getToken();
199             //const char* g =
200             tokeniser.getToken();
201             //const char* b =
202             tokeniser.getToken();
203             for (;;) {
204                 if (string_equal(tokeniser.getToken(), ")")) {
205                     tokeniser.ungetToken();
206                     break;
207                 }
208                 //const char* name =
209                 tokeniser.getToken();
210             }
211             ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR);
212         } else if (string_equal(property, "wirebox")) {
213             ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR);
214             //const char* mins =
215             tokeniser.getToken();
216             ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ","), PARSE_ERROR);
217             //const char* maxs =
218             tokeniser.getToken();
219             ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR);
220         } else if (string_equal(property, "halfgridsnap")) {
221         } else {
222             ERROR_MESSAGE(PARSE_ERROR);
223         }
224     }
225
226     entityClass->m_name = tokeniser.getToken();
227
228     if (!isBase) {
229         ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ":"), PARSE_ERROR);
230
231         EntityClassFGD_parseSplitString(tokeniser, entityClass->m_comments);
232     }
233
234     tokeniser.nextLine();
235
236     ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "["), PARSE_ERROR);
237
238     tokeniser.nextLine();
239
240     for (;;) {
241         CopiedString key = tokeniser.getToken();
242         if (string_equal(key.c_str(), "]")) {
243             tokeniser.nextLine();
244             break;
245         }
246
247         if (string_equal_nocase(key.c_str(), "input")
248             || string_equal_nocase(key.c_str(), "output")) {
249             const char *name = tokeniser.getToken();
250             if (!string_equal(name, "(")) {
251                 ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR);
252                 //const char* type =
253                 tokeniser.getToken();
254                 ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR);
255                 const char *descriptionSeparator = tokeniser.getToken();
256                 if (string_equal(descriptionSeparator, ":")) {
257                     CopiedString description;
258                     EntityClassFGD_parseSplitString(tokeniser, description);
259                 } else {
260                     tokeniser.ungetToken();
261                 }
262                 tokeniser.nextLine();
263                 continue;
264             }
265         }
266
267         ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR);
268         CopiedString type = tokeniser.getToken();
269         ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR);
270
271         if (string_equal_nocase(type.c_str(), "flags")) {
272             EntityClassAttribute attribute;
273
274             ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "="), PARSE_ERROR);
275             tokeniser.nextLine();
276             ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "["), PARSE_ERROR);
277             tokeniser.nextLine();
278             for (;;) {
279                 const char *flag = tokeniser.getToken();
280                 if (string_equal(flag, "]")) {
281                     tokeniser.nextLine();
282                     break;
283                 } else {
284                     ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ":"), PARSE_ERROR);
285                     //const char* name =
286                     tokeniser.getToken();
287                     {
288                         const char *defaultSeparator = tokeniser.getToken();
289                         if (string_equal(defaultSeparator, ":")) {
290                             tokeniser.getToken();
291                             {
292                                 const char *descriptionSeparator = tokeniser.getToken();
293                                 if (string_equal(descriptionSeparator, ":")) {
294                                     EntityClassFGD_parseSplitString(tokeniser, attribute.m_description);
295                                 } else {
296                                     tokeniser.ungetToken();
297                                 }
298                             }
299                         } else {
300                             tokeniser.ungetToken();
301                         }
302                     }
303                 }
304                 tokeniser.nextLine();
305             }
306             EntityClass_insertAttribute(*entityClass, key.c_str(), attribute);
307         } else if (string_equal_nocase(type.c_str(), "choices")) {
308             EntityClassAttribute attribute;
309
310             ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ":"), PARSE_ERROR);
311             attribute.m_name = tokeniser.getToken();
312             const char *valueSeparator = tokeniser.getToken();
313             if (string_equal(valueSeparator, ":")) {
314                 const char *value = tokeniser.getToken();
315                 if (!string_equal(value, ":")) {
316                     attribute.m_value = value;
317                 } else {
318                     tokeniser.ungetToken();
319                 }
320                 {
321                     const char *descriptionSeparator = tokeniser.getToken();
322                     if (string_equal(descriptionSeparator, ":")) {
323                         EntityClassFGD_parseSplitString(tokeniser, attribute.m_description);
324                     } else {
325                         tokeniser.ungetToken();
326                     }
327                 }
328             } else {
329                 tokeniser.ungetToken();
330             }
331             ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "="), PARSE_ERROR);
332             tokeniser.nextLine();
333             ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "["), PARSE_ERROR);
334             tokeniser.nextLine();
335
336             StringOutputStream listTypeName(64);
337             listTypeName << entityClass->m_name.c_str() << "_" << attribute.m_name.c_str();
338             attribute.m_type = listTypeName.c_str();
339
340             ListAttributeType &listType = g_listTypesFGD[listTypeName.c_str()];
341
342             for (;;) {
343                 const char *value = tokeniser.getToken();
344                 if (string_equal(value, "]")) {
345                     tokeniser.nextLine();
346                     break;
347                 } else {
348                     CopiedString tmp(value);
349                     ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ":"), PARSE_ERROR);
350                     const char *name = tokeniser.getToken();
351                     listType.push_back(name, tmp.c_str());
352                 }
353                 tokeniser.nextLine();
354             }
355
356             for (ListAttributeType::const_iterator i = listType.begin(); i != listType.end(); ++i) {
357                 if (string_equal(attribute.m_value.c_str(), (*i).first.c_str())) {
358                     attribute.m_value = (*i).second.c_str();
359                 }
360             }
361
362             EntityClass_insertAttribute(*entityClass, key.c_str(), attribute);
363         } else if (string_equal_nocase(type.c_str(), "decal")) {
364         } else if (string_equal_nocase(type.c_str(), "string")
365                    || string_equal_nocase(type.c_str(), "integer")
366                    || string_equal_nocase(type.c_str(), "studio")
367                    || string_equal_nocase(type.c_str(), "sprite")
368                    || string_equal_nocase(type.c_str(), "color255")
369                    || string_equal_nocase(type.c_str(), "target_source")
370                    || string_equal_nocase(type.c_str(), "target_destination")
371                    || string_equal_nocase(type.c_str(), "sound")
372                    // hl2 below
373                    || string_equal_nocase(type.c_str(), "angle")
374                    || string_equal_nocase(type.c_str(), "origin")
375                    || string_equal_nocase(type.c_str(), "float")
376                    || string_equal_nocase(type.c_str(), "node_dest")
377                    || string_equal_nocase(type.c_str(), "filterclass")
378                    || string_equal_nocase(type.c_str(), "vector")
379                    || string_equal_nocase(type.c_str(), "sidelist")
380                    || string_equal_nocase(type.c_str(), "material")
381                    || string_equal_nocase(type.c_str(), "vecline")
382                    || string_equal_nocase(type.c_str(), "axis")
383                    || string_equal_nocase(type.c_str(), "npcclass")
384                    || string_equal_nocase(type.c_str(), "target_name_or_class")
385                    || string_equal_nocase(type.c_str(), "pointentityclass")
386                    || string_equal_nocase(type.c_str(), "scene")) {
387             if (!string_equal(tokeniser.getToken(), "readonly")) {
388                 tokeniser.ungetToken();
389             }
390
391             ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ":"), PARSE_ERROR);
392             const char *attributeType = "string";
393             if (string_equal_nocase(type.c_str(), "studio")) {
394                 attributeType = "model";
395             }
396
397             EntityClassAttribute attribute;
398             attribute.m_type = attributeType;
399             attribute.m_name = tokeniser.getToken();
400
401             const char *defaultSeparator = tokeniser.getToken();
402             if (string_equal(defaultSeparator, ":")) {
403                 const char *value = tokeniser.getToken();
404                 if (!string_equal(value, ":")) {
405                     attribute.m_value = value;
406                 } else {
407                     tokeniser.ungetToken();
408                 }
409
410                 {
411                     const char *descriptionSeparator = tokeniser.getToken();
412                     if (string_equal(descriptionSeparator, ":")) {
413                         EntityClassFGD_parseSplitString(tokeniser, attribute.m_description);
414                     } else {
415                         tokeniser.ungetToken();
416                     }
417                 }
418             } else {
419                 tokeniser.ungetToken();
420             }
421             EntityClass_insertAttribute(*entityClass, key.c_str(), attribute);
422         } else {
423             ERROR_MESSAGE("unknown key type: " << makeQuoted(type.c_str()));
424         }
425         tokeniser.nextLine();
426     }
427
428     if (isBase) {
429         EntityClassFGD_insertUniqueBase(entityClass);
430     } else {
431         EntityClassFGD_insertUnique(entityClass);
432     }
433 }
434
435 void EntityClassFGD_loadFile(const char *filename);
436
437 void EntityClassFGD_parse(TextInputStream &inputStream, const char *path)
438 {
439     Tokeniser &tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser(inputStream);
440
441     tokeniser.nextLine();
442
443     for (;;) {
444         const char *blockType = tokeniser.getToken();
445         if (blockType == 0) {
446             break;
447         }
448         if (string_equal(blockType, "@SolidClass")) {
449             EntityClassFGD_parseClass(tokeniser, false, false);
450         } else if (string_equal(blockType, "@BaseClass")) {
451             EntityClassFGD_parseClass(tokeniser, false, true);
452         } else if (string_equal(blockType, "@PointClass")
453                    // hl2 below
454                    || string_equal(blockType, "@KeyFrameClass")
455                    || string_equal(blockType, "@MoveClass")
456                    || string_equal(blockType, "@FilterClass")
457                    || string_equal(blockType, "@NPCClass")) {
458             EntityClassFGD_parseClass(tokeniser, true, false);
459         }
460             // hl2 below
461         else if (string_equal(blockType, "@include")) {
462             StringOutputStream includePath(256);
463             includePath << StringRange(path, path_get_filename_start(path));
464             includePath << tokeniser.getToken();
465             EntityClassFGD_loadFile(includePath.c_str());
466         } else if (string_equal(blockType, "@mapsize")) {
467             ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, "("), PARSE_ERROR);
468             //const char* min =
469             tokeniser.getToken();
470             ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ","), PARSE_ERROR);
471             //const char* max =
472             tokeniser.getToken();
473             ASSERT_MESSAGE(EntityClassFGD_parseToken(tokeniser, ")"), PARSE_ERROR);
474         } else {
475             ERROR_MESSAGE("unknown block type: " << makeQuoted(blockType));
476         }
477     }
478
479     tokeniser.release();
480 }
481
482
483 void EntityClassFGD_loadFile(const char *filename)
484 {
485     TextFileInputStream file(filename);
486     if (!file.failed()) {
487         globalOutputStream() << "parsing entity classes from " << makeQuoted(filename) << "\n";
488
489         EntityClassFGD_parse(file, filename);
490     }
491 }
492
493 EntityClass *EntityClassFGD_findOrInsert(const char *name, bool has_brushes)
494 {
495     ASSERT_NOTNULL(name);
496
497     if (string_empty(name)) {
498         return g_EntityClassFGD_bad;
499     }
500
501     EntityClasses::iterator i = g_EntityClassFGD_classes.find(name);
502     if (i != g_EntityClassFGD_classes.end()
503         //&& string_equal((*i).first, name)
504             ) {
505         return (*i).second;
506     }
507
508     EntityClass *e = EntityClass_Create_Default(name, has_brushes);
509     return EntityClassFGD_insertUnique(e);
510 }
511
512 const ListAttributeType *EntityClassFGD_findListType(const char *name)
513 {
514     ListAttributeTypes::iterator i = g_listTypesFGD.find(name);
515     if (i != g_listTypesFGD.end()) {
516         return &(*i).second;
517     }
518     return 0;
519
520 }
521
522
523 void EntityClassFGD_resolveInheritance(EntityClass *derivedClass)
524 {
525     if (derivedClass->inheritanceResolved == false) {
526         derivedClass->inheritanceResolved = true;
527         for (StringList::iterator j = derivedClass->m_parent.begin(); j != derivedClass->m_parent.end(); ++j) {
528             BaseClasses::iterator i = g_EntityClassFGD_bases.find((*j).c_str());
529             if (i == g_EntityClassFGD_bases.end()) {
530                 globalErrorStream() << "failed to find entityDef " << makeQuoted((*j).c_str()) << " inherited by "
531                                     << makeQuoted(derivedClass->m_name.c_str()) << "\n";
532             } else {
533                 EntityClass *parentClass = (*i).second;
534                 EntityClassFGD_resolveInheritance(parentClass);
535                 if (!derivedClass->colorSpecified) {
536                     derivedClass->colorSpecified = parentClass->colorSpecified;
537                     derivedClass->color = parentClass->color;
538                 }
539                 if (!derivedClass->sizeSpecified) {
540                     derivedClass->sizeSpecified = parentClass->sizeSpecified;
541                     derivedClass->mins = parentClass->mins;
542                     derivedClass->maxs = parentClass->maxs;
543                 }
544
545                 for (EntityClassAttributes::iterator k = parentClass->m_attributes.begin();
546                      k != parentClass->m_attributes.end(); ++k) {
547                     EntityClass_insertAttribute(*derivedClass, (*k).first.c_str(), (*k).second);
548                 }
549             }
550         }
551     }
552 }
553
554 class EntityClassFGD : public ModuleObserver {
555     std::size_t m_unrealised;
556     ModuleObservers m_observers;
557 public:
558     EntityClassFGD() : m_unrealised(3)
559     {
560     }
561
562     void realise()
563     {
564         if (--m_unrealised == 0) {
565             StringOutputStream filename(256);
566             filename << GlobalRadiant().getGameToolsPath() << GlobalRadiant().getGameName() << "/halflife.fgd";
567             EntityClassFGD_loadFile(filename.c_str());
568
569             {
570                 for (EntityClasses::iterator i = g_EntityClassFGD_classes.begin();
571                      i != g_EntityClassFGD_classes.end(); ++i) {
572                     EntityClassFGD_resolveInheritance((*i).second);
573                     if ((*i).second->fixedsize && string_empty((*i).second->m_modelpath.c_str())) {
574                         if (!(*i).second->sizeSpecified) {
575                             globalErrorStream() << "size not specified for entity class: "
576                                                 << makeQuoted((*i).second->m_name.c_str()) << '\n';
577                         }
578                         if (!(*i).second->colorSpecified) {
579                             globalErrorStream() << "color not specified for entity class: "
580                                                 << makeQuoted((*i).second->m_name.c_str()) << '\n';
581                         }
582                     }
583                 }
584             }
585             {
586                 for (BaseClasses::iterator i = g_EntityClassFGD_bases.begin(); i != g_EntityClassFGD_bases.end(); ++i) {
587                     eclass_capture_state((*i).second);
588                 }
589             }
590
591             m_observers.realise();
592         }
593     }
594
595     void unrealise()
596     {
597         if (++m_unrealised == 1) {
598             m_observers.unrealise();
599             EntityClassFGD_clear();
600         }
601     }
602
603     void attach(ModuleObserver &observer)
604     {
605         m_observers.attach(observer);
606     }
607
608     void detach(ModuleObserver &observer)
609     {
610         m_observers.detach(observer);
611     }
612 };
613
614 EntityClassFGD g_EntityClassFGD;
615
616 void EntityClassFGD_attach(ModuleObserver &observer)
617 {
618     g_EntityClassFGD.attach(observer);
619 }
620
621 void EntityClassFGD_detach(ModuleObserver &observer)
622 {
623     g_EntityClassFGD.detach(observer);
624 }
625
626 void EntityClassFGD_realise()
627 {
628     g_EntityClassFGD.realise();
629 }
630
631 void EntityClassFGD_unrealise()
632 {
633     g_EntityClassFGD.unrealise();
634 }
635
636 void EntityClassFGD_construct()
637 {
638     // start by creating the default unknown eclass
639     g_EntityClassFGD_bad = EClass_Create("UNKNOWN_CLASS", Vector3(0.0f, 0.5f, 0.0f), "");
640
641     EntityClassFGD_realise();
642 }
643
644 void EntityClassFGD_destroy()
645 {
646     EntityClassFGD_unrealise();
647
648     g_EntityClassFGD_bad->free(g_EntityClassFGD_bad);
649 }
650
651 class EntityClassFGDDependencies
652         : public GlobalFileSystemModuleRef, public GlobalShaderCacheModuleRef, public GlobalRadiantModuleRef {
653 };
654
655 class EntityClassFGDAPI {
656     EntityClassManager m_eclassmanager;
657 public:
658     typedef EntityClassManager Type;
659
660     STRING_CONSTANT(Name, "halflife");
661
662     EntityClassFGDAPI()
663     {
664         EntityClassFGD_construct();
665
666         m_eclassmanager.findOrInsert = &EntityClassFGD_findOrInsert;
667         m_eclassmanager.findListType = &EntityClassFGD_findListType;
668         m_eclassmanager.forEach = &EntityClassFGD_forEach;
669         m_eclassmanager.attach = &EntityClassFGD_attach;
670         m_eclassmanager.detach = &EntityClassFGD_detach;
671         m_eclassmanager.realise = &EntityClassFGD_realise;
672         m_eclassmanager.unrealise = &EntityClassFGD_unrealise;
673
674         Radiant_attachGameToolsPathObserver(g_EntityClassFGD);
675         Radiant_attachGameNameObserver(g_EntityClassFGD);
676     }
677
678     ~EntityClassFGDAPI()
679     {
680         Radiant_detachGameNameObserver(g_EntityClassFGD);
681         Radiant_detachGameToolsPathObserver(g_EntityClassFGD);
682
683         EntityClassFGD_destroy();
684     }
685
686     EntityClassManager *getTable()
687     {
688         return &m_eclassmanager;
689     }
690 };
691
692 #include "modulesystem/singletonmodule.h"
693 #include "modulesystem/moduleregistry.h"
694
695 typedef SingletonModule<EntityClassFGDAPI, EntityClassFGDDependencies> EntityClassFGDModule;
696 typedef Static<EntityClassFGDModule> StaticEntityClassFGDModule;
697 StaticRegisterModule staticRegisterEntityClassFGD(StaticEntityClassFGDModule::instance());