9a2a4388a3621aefa7c0a0fc48891ff326837d3d
[xonotic/netradiant.git] / radiant / eclass.cpp
1 /*
2 Copyright (C) 1999-2006 Id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
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.h"
23
24 #include "debugging/debugging.h"
25
26 #include <map>
27
28 #include "ifilesystem.h"
29
30 #include "string/string.h"
31 #include "eclasslib.h"
32 #include "os/path.h"
33 #include "os/dir.h"
34 #include "stream/stringstream.h"
35 #include "moduleobservers.h"
36
37 #include "cmdlib.h"
38
39 #include "preferences.h"
40 #include "mainframe.h"
41
42
43 namespace
44 {
45   typedef std::map<const char*, EntityClass*, RawStringLessNoCase> EntityClasses;
46   EntityClasses g_entityClasses;
47   EntityClass   *eclass_bad = 0;
48   char          eclass_directory[1024];
49   typedef std::map<CopiedString, ListAttributeType> ListAttributeTypes;
50   ListAttributeTypes g_listTypes;
51 }
52
53 /*!
54 implementation of the EClass manager API
55 */
56
57 void CleanEntityList(EntityClasses& entityClasses)
58 {
59   for(EntityClasses::iterator i = entityClasses.begin(); i != entityClasses.end(); ++i)
60   {
61     (*i).second->free((*i).second);
62   }
63   entityClasses.clear();
64 }
65
66 void Eclass_Clear()
67 {
68   CleanEntityList(g_entityClasses);
69   g_listTypes.clear();
70 }
71
72 EntityClass* EClass_InsertSortedList(EntityClasses& entityClasses, EntityClass *entityClass)
73 {
74   std::pair<EntityClasses::iterator, bool> result = entityClasses.insert(EntityClasses::value_type(entityClass->name(), entityClass));
75   if(!result.second)
76   {
77     entityClass->free(entityClass);
78   }
79   return (*result.first).second;
80 }
81
82 EntityClass* Eclass_InsertAlphabetized (EntityClass *e)
83 {
84   return EClass_InsertSortedList(g_entityClasses, e);
85 }
86
87 void Eclass_forEach(EntityClassVisitor& visitor)
88 {
89   for(EntityClasses::iterator i = g_entityClasses.begin(); i != g_entityClasses.end(); ++i)
90   {
91     visitor.visit((*i).second);
92   }
93 }
94
95
96 class RadiantEclassCollector : public EntityClassCollector
97 {
98 public:
99   void insert(EntityClass* eclass)
100   {
101     Eclass_InsertAlphabetized(eclass);
102   }
103   void insert(const char* name, const ListAttributeType& list)
104   {
105     g_listTypes.insert(ListAttributeTypes::value_type(name, list));
106   }
107 };
108
109 RadiantEclassCollector g_collector;
110
111 const ListAttributeType* EntityClass_findListType(const char* name)
112 {
113   ListAttributeTypes::iterator i = g_listTypes.find(name);
114   if(i != g_listTypes.end())
115   {
116     return &(*i).second;
117   }
118   return 0;
119 }
120
121
122 class EntityClassFilterMode
123 {
124 public:
125   bool filter_mp_sp;
126   const char* mp_ignore_prefix;
127   const char* sp_ignore_prefix;
128
129   EntityClassFilterMode() :
130     filter_mp_sp(!string_empty(g_pGameDescription->getKeyValue("eclass_filter_gamemode"))),
131     mp_ignore_prefix(g_pGameDescription->getKeyValue("eclass_sp_prefix")),
132     sp_ignore_prefix(g_pGameDescription->getKeyValue("eclass_mp_prefix"))
133   {
134     if(string_empty(mp_ignore_prefix))
135     {
136       mp_ignore_prefix = "sp_";
137     }
138     if(string_empty(sp_ignore_prefix))
139     {
140       sp_ignore_prefix = "mp_";
141     }
142   }
143 };
144
145 class EntityClassesLoadFile
146 {
147   const char* m_directory;
148 public:
149   EntityClassesLoadFile(const char* directory) : m_directory(directory)
150   {
151   }
152   void operator()(const char* name) const
153   {
154     EntityClassFilterMode filterMode;
155
156     if(filterMode.filter_mp_sp)
157     {
158       if(string_empty(GlobalRadiant().getGameMode()) || string_equal(GlobalRadiant().getGameMode(), "sp"))
159       {
160         if(string_equal_n(name, filterMode.sp_ignore_prefix, strlen(filterMode.sp_ignore_prefix)))
161         {
162           globalOutputStream() << "Ignoring '" << name << "'\n";
163           return;
164         }
165       }
166       else
167       {
168         if(string_equal_n(name, filterMode.mp_ignore_prefix, strlen(filterMode.mp_ignore_prefix)))
169         {
170           globalOutputStream() << "Ignoring '" << name << "'\n";
171           return;
172         }
173       }
174     }
175   
176     // for a given name, we grab the first .def in the vfs
177     // this allows to override baseq3/scripts/entities.def for instance
178     StringOutputStream relPath(256);
179     relPath << m_directory << name;
180
181     GlobalEClassLoader().scanFile(g_collector, relPath.c_str());
182   }
183 };
184
185 struct PathLess
186 {
187   bool operator()(const CopiedString& path, const CopiedString& other) const
188   {
189     return path_less(path.c_str(), other.c_str());
190   }
191 };
192
193 typedef std::map<CopiedString, const char*, PathLess> Paths;
194
195 class PathsInsert
196 {
197   Paths& m_paths;
198   const char* m_directory;
199 public:
200   PathsInsert(Paths& paths, const char* directory) : m_paths(paths), m_directory(directory)
201   {
202   }
203   void operator()(const char* name) const
204   {
205     m_paths.insert(Paths::value_type(name, m_directory));
206   }
207 };
208
209 #if 0
210 void EntityClassQuake3_constructDirectory(const char* directory, const char* extension)
211 {
212   globalOutputStream() << "EntityClass: searching " << makeQuoted(directory) << " for *." << extension << '\n'; 
213   Directory_forEach(directory, matchFileExtension(extension, EntityClassesLoadFile(directory)));
214 }
215 #else
216 void EntityClassQuake3_constructDirectory(const char* directory, const char* extension, Paths& paths)
217 {
218   globalOutputStream() << "EntityClass: searching " << makeQuoted(directory) << " for *." << extension << '\n'; 
219   Directory_forEach(directory, matchFileExtension(extension, PathsInsert(paths, directory)));
220 }
221 #endif
222
223 void EntityClassQuake3_Construct()
224 {
225 #if 1
226   StringOutputStream baseDirectory(256);
227   StringOutputStream gameDirectory(256);
228   const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue("basegame");
229   const char* gamename = GlobalRadiant().getGameName();
230   baseDirectory << GlobalRadiant().getGameToolsPath() << basegame << '/';
231   gameDirectory << GlobalRadiant().getGameToolsPath() << gamename << '/';
232
233   Paths paths;
234   EntityClassQuake3_constructDirectory(baseDirectory.c_str(), GlobalEClassLoader().getExtension(), paths);
235   if(!string_equal(basegame, gamename))
236   {
237     EntityClassQuake3_constructDirectory(gameDirectory.c_str(), GlobalEClassLoader().getExtension(), paths);
238   }
239
240   for(Paths::iterator i = paths.begin(); i != paths.end(); ++i)
241   {
242     EntityClassesLoadFile((*i).second)((*i).first.c_str());
243   }
244 #else
245   StringOutputStream directory(256);
246   directory << GlobalRadiant().getGameToolsPath() << GlobalRadiant().getGameName() << '/';
247   EntityClassQuake3_constructDirectory(directory.c_str(), GlobalEClassLoader().getExtension());
248 #endif
249 }
250
251 EntityClass *Eclass_ForName(const char *name, bool has_brushes)
252 {
253         ASSERT_NOTNULL(name);
254
255   if(string_empty(name))
256   {
257     return eclass_bad;
258   }
259
260   EntityClasses::iterator i = g_entityClasses.find(name);
261   if(i != g_entityClasses.end() && string_equal((*i).first, name))
262   {
263     return (*i).second;
264   }
265
266         EntityClass* e = EntityClass_Create_Default(name, has_brushes);
267         return Eclass_InsertAlphabetized(e);
268 }
269
270 class EntityClassQuake3 : public ModuleObserver
271 {
272   std::size_t m_unrealised;
273   ModuleObservers m_observers;
274 public:
275   EntityClassQuake3() : m_unrealised(4)
276   {
277   }
278   void realise()
279   {
280     if(--m_unrealised == 0)
281     {
282       //globalOutputStream() << "Entity Classes: realise\n";
283       EntityClassQuake3_Construct();
284       m_observers.realise();
285     }
286   }
287   void unrealise()
288   {
289     if(++m_unrealised == 1)
290     {
291       m_observers.unrealise();
292       //globalOutputStream() << "Entity Classes: unrealise\n";
293       Eclass_Clear();
294     }
295   }
296   void attach(ModuleObserver& observer)
297   {
298     m_observers.attach(observer);
299   }
300   void detach(ModuleObserver& observer)
301   {
302     m_observers.detach(observer);
303   }
304 };
305
306 EntityClassQuake3 g_EntityClassQuake3;
307
308 void EntityClass_attach(ModuleObserver& observer)
309 {
310   g_EntityClassQuake3.attach(observer);
311 }
312 void EntityClass_detach(ModuleObserver& observer)
313 {
314   g_EntityClassQuake3.detach(observer);
315 }
316
317 void EntityClass_realise()
318 {
319   g_EntityClassQuake3.realise();
320 }
321 void EntityClass_unrealise()
322 {
323   g_EntityClassQuake3.unrealise();
324 }
325
326 void EntityClassQuake3_construct()
327 {
328   // start by creating the default unknown eclass
329   eclass_bad = EClass_Create("UNKNOWN_CLASS", Vector3(0.0f, 0.5f, 0.0f), "");
330
331   EntityClass_realise();
332 }
333
334 void EntityClassQuake3_destroy()
335 {
336   EntityClass_unrealise();
337
338   eclass_bad->free(eclass_bad);
339 }
340
341 class EntityClassQuake3Dependencies :
342   public GlobalRadiantModuleRef,
343   public GlobalFileSystemModuleRef,
344   public GlobalShaderCacheModuleRef,
345   public GlobalEClassModuleRef
346 {
347 public:
348   EntityClassQuake3Dependencies() :
349     GlobalEClassModuleRef(GlobalRadiant().getRequiredGameDescriptionKeyValue("entityclasstype"))
350   {
351   }
352 };
353
354 class EclassManagerAPI
355 {
356   EntityClassManager m_eclassmanager;
357 public:
358   typedef EntityClassManager Type;
359   STRING_CONSTANT(Name, "quake3");
360
361   EclassManagerAPI()
362   {
363     EntityClassQuake3_construct();
364
365     m_eclassmanager.findOrInsert = &Eclass_ForName;
366     m_eclassmanager.findListType = &EntityClass_findListType;
367     m_eclassmanager.forEach = &Eclass_forEach;
368     m_eclassmanager.attach = &EntityClass_attach;
369     m_eclassmanager.detach = &EntityClass_detach;
370     m_eclassmanager.realise = &EntityClass_realise;
371     m_eclassmanager.unrealise = &EntityClass_unrealise;
372
373     GlobalRadiant().attachGameToolsPathObserver(g_EntityClassQuake3);
374     GlobalRadiant().attachGameModeObserver(g_EntityClassQuake3);
375     GlobalRadiant().attachGameNameObserver(g_EntityClassQuake3);
376   }
377   ~EclassManagerAPI()
378   {
379     GlobalRadiant().detachGameNameObserver(g_EntityClassQuake3);
380     GlobalRadiant().detachGameModeObserver(g_EntityClassQuake3);
381     GlobalRadiant().detachGameToolsPathObserver(g_EntityClassQuake3);
382
383     EntityClassQuake3_destroy();
384   }
385   EntityClassManager* getTable()
386   {
387     return &m_eclassmanager;
388   }
389 };
390
391 #include "modulesystem/singletonmodule.h"
392 #include "modulesystem/moduleregistry.h"
393
394 typedef SingletonModule<EclassManagerAPI, EntityClassQuake3Dependencies> EclassManagerModule;
395 typedef Static<EclassManagerModule> StaticEclassManagerModule;
396 StaticRegisterModule staticRegisterEclassManager(StaticEclassManagerModule::instance());
397
398