Introduce Property<T> to simplify preferences system
[xonotic/netradiant.git] / plugins / entity / entity.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 "entity.h"
23
24 #include "ifilter.h"
25 #include "selectable.h"
26 #include "namespace.h"
27
28 #include "scenelib.h"
29 #include "entitylib.h"
30 #include "eclasslib.h"
31 #include "pivot.h"
32
33 #include "targetable.h"
34 #include "uniquenames.h"
35 #include "namekeys.h"
36 #include "stream/stringstream.h"
37 #include "filters.h"
38
39
40 #include "miscmodel.h"
41 #include "light.h"
42 #include "group.h"
43 #include "eclassmodel.h"
44 #include "generic.h"
45 #include "doom3group.h"
46
47
48
49 EGameType g_gameType;
50
51 inline scene::Node& entity_for_eclass( EntityClass* eclass ){
52         if ( ( string_compare_nocase_n( eclass->name(), "misc_", 5 ) == 0 && string_equal_nocase( eclass->name() + string_length( eclass->name() ) - 5, "model" ) ) // misc_*model (also misc_model) // TODO make classname_* wrapper functions for this
53                  || classname_equal( eclass->name(), "model_static" ) ) {
54                 return New_MiscModel( eclass );
55         }
56         else if ( classname_equal( eclass->name(), "light" )
57                           || classname_equal( eclass->name(), "lightJunior" ) ) {
58                 return New_Light( eclass );
59         }
60         if ( !eclass->fixedsize ) {
61                 if ( g_gameType == eGameTypeDoom3 ) {
62                         return New_Doom3Group( eclass );
63                 }
64                 else
65                 {
66                         return New_Group( eclass );
67                 }
68         }
69         else if ( !string_empty( eclass->modelpath() ) ) {
70                 return New_EclassModel( eclass );
71         }
72         else
73         {
74                 return New_GenericEntity( eclass );
75         }
76 }
77
78 void Entity_setName( Entity& entity, const char* name ){
79         entity.setKeyValue( "name", name );
80 }
81 typedef ReferenceCaller<Entity, void(const char*), Entity_setName> EntitySetNameCaller;
82
83 inline Namespaced* Node_getNamespaced( scene::Node& node ){
84         return NodeTypeCast<Namespaced>::cast( node );
85 }
86
87 inline scene::Node& node_for_eclass( EntityClass* eclass ){
88         scene::Node& node = entity_for_eclass( eclass );
89         Node_getEntity( node )->setKeyValue( "classname", eclass->name() );
90
91         if ( g_gameType == eGameTypeDoom3
92                  && string_not_empty( eclass->name() )
93                  && !string_equal( eclass->name(), "worldspawn" )
94                  && !string_equal( eclass->name(), "UNKNOWN_CLASS" ) ) {
95                 char buffer[1024];
96                 strcpy( buffer, eclass->name() );
97                 strcat( buffer, "_1" );
98                 GlobalNamespace().makeUnique( buffer, EntitySetNameCaller( *Node_getEntity( node ) ) );
99         }
100
101         Namespaced* namespaced = Node_getNamespaced( node );
102         if ( namespaced != 0 ) {
103                 namespaced->setNamespace( GlobalNamespace() );
104         }
105
106         return node;
107 }
108
109 EntityCreator::KeyValueChangedFunc EntityKeyValues::m_entityKeyValueChanged = 0;
110 EntityCreator::KeyValueChangedFunc KeyValue::m_entityKeyValueChanged = 0;
111 Counter* EntityKeyValues::m_counter = 0;
112
113 bool g_showNames = true;
114 bool g_showAngles = true;
115 bool g_newLightDraw = true;
116 bool g_lightRadii = false;
117
118 class ConnectEntities
119 {
120 public:
121 Entity* m_e1;
122 Entity* m_e2;
123 int m_index;
124 ConnectEntities( Entity* e1, Entity* e2, int index ) : m_e1( e1 ), m_e2( e2 ), m_index( index ){
125 }
126 const char *keyname(){
127         StringOutputStream key( 16 );
128         if ( m_index <= 0 ) {
129                 return "target";
130         }
131         if ( m_index == 1 ) {
132                 return "killtarget";
133         }
134         key << "target" << m_index;
135         return key.c_str();
136 }
137 void connect( const char* name ){
138         m_e1->setKeyValue( keyname(), name );
139         m_e2->setKeyValue( "targetname", name );
140 }
141 typedef MemberCaller<ConnectEntities, void(const char*), &ConnectEntities::connect> ConnectCaller;
142 };
143
144 inline Entity* ScenePath_getEntity( const scene::Path& path ){
145         Entity* entity = Node_getEntity( path.top() );
146         if ( entity == 0 ) {
147                 entity = Node_getEntity( path.parent() );
148         }
149         return entity;
150 }
151
152 class Quake3EntityCreator : public EntityCreator
153 {
154 public:
155 scene::Node& createEntity( EntityClass* eclass ){
156         return node_for_eclass( eclass );
157 }
158 void setKeyValueChangedFunc( KeyValueChangedFunc func ){
159         EntityKeyValues::setKeyValueChangedFunc( func );
160 }
161 void setCounter( Counter* counter ){
162         EntityKeyValues::setCounter( counter );
163 }
164 void connectEntities( const scene::Path& path, const scene::Path& targetPath, int index ){
165         Entity* e1 = ScenePath_getEntity( path );
166         Entity* e2 = ScenePath_getEntity( targetPath );
167
168         if ( e1 == 0 || e2 == 0 ) {
169                 globalErrorStream() << "entityConnectSelected: both of the selected instances must be an entity\n";
170                 return;
171         }
172
173         if ( e1 == e2 ) {
174                 globalErrorStream() << "entityConnectSelected: the selected instances must not both be from the same entity\n";
175                 return;
176         }
177
178
179         UndoableCommand undo( "entityConnectSelected" );
180
181         if ( g_gameType == eGameTypeDoom3 ) {
182                 StringOutputStream key( 16 );
183                 if ( index >= 0 ) {
184                         key << "target";
185                         if ( index != 0 ) {
186                                 key << index;
187                         }
188                         e1->setKeyValue( key.c_str(), e2->getKeyValue( "name" ) );
189                         key.clear();
190                 }
191                 else
192                 {
193                         for ( unsigned int i = 0; ; ++i )
194                         {
195                                 key << "target";
196                                 if ( i != 0 ) {
197                                         key << i;
198                                 }
199                                 const char* value = e1->getKeyValue( key.c_str() );
200                                 if ( string_empty( value ) ) {
201                                         e1->setKeyValue( key.c_str(), e2->getKeyValue( "name" ) );
202                                         break;
203                                 }
204                                 key.clear();
205                         }
206                 }
207         }
208         else
209         {
210                 ConnectEntities connector( e1, e2, index );
211                 const char* value = e2->getKeyValue( "targetname" );
212                 if ( !string_empty( value ) ) {
213                         connector.connect( value );
214                 }
215                 else
216                 {
217                         const char* type = e2->getKeyValue( "classname" );
218                         if ( string_empty( type ) ) {
219                                 type = "t";
220                         }
221                         StringOutputStream key( 64 );
222                         key << type << "1";
223                         GlobalNamespace().makeUnique( key.c_str(), ConnectEntities::ConnectCaller( connector ) );
224                 }
225         }
226
227         SceneChangeNotify();
228 }
229 void setLightRadii( bool lightRadii ){
230         g_lightRadii = lightRadii;
231 }
232 bool getLightRadii() const {
233         return g_lightRadii;
234 }
235 void setShowNames( bool showNames ){
236         g_showNames = showNames;
237 }
238 bool getShowNames(){
239         return g_showNames;
240 }
241 void setShowAngles( bool showAngles ){
242         g_showAngles = showAngles;
243 }
244 bool getShowAngles(){
245         return g_showAngles;
246 }
247
248 void printStatistics() const {
249         StringPool_analyse( EntityKeyValues::getPool() );
250 }
251 };
252
253 Quake3EntityCreator g_Quake3EntityCreator;
254
255 EntityCreator& GetEntityCreator(){
256         return g_Quake3EntityCreator;
257 }
258
259
260
261 class filter_entity_classname : public EntityFilter
262 {
263 const char* m_classname;
264 public:
265 filter_entity_classname( const char* classname ) : m_classname( classname ){
266 }
267 bool filter( const Entity& entity ) const {
268         return string_equal( entity.getKeyValue( "classname" ), m_classname );
269 }
270 };
271
272 class filter_entity_classgroup : public EntityFilter
273 {
274 const char* m_classgroup;
275 std::size_t m_length;
276 public:
277 filter_entity_classgroup( const char* classgroup ) : m_classgroup( classgroup ), m_length( string_length( m_classgroup ) ){
278 }
279 bool filter( const Entity& entity ) const {
280         return string_equal_n( entity.getKeyValue( "classname" ), m_classgroup, m_length );
281 }
282 };
283
284 filter_entity_classname g_filter_entity_world( "worldspawn" );
285 filter_entity_classname g_filter_entity_func_group( "func_group" );
286 filter_entity_classname g_filter_entity_light( "light" );
287 filter_entity_classname g_filter_entity_misc_model( "misc_model" );
288 filter_entity_classname g_filter_entity_misc_gamemodel( "misc_gamemodel" );
289 filter_entity_classgroup g_filter_entity_trigger( "trigger_" );
290 filter_entity_classgroup g_filter_entity_path( "path_" );
291
292 class filter_entity_doom3model : public EntityFilter
293 {
294 public:
295 bool filter( const Entity& entity ) const {
296         return string_equal( entity.getKeyValue( "classname" ), "func_static" )
297                    && !string_equal( entity.getKeyValue( "model" ), entity.getKeyValue( "name" ) );
298 }
299 };
300
301 filter_entity_doom3model g_filter_entity_doom3model;
302
303
304 void Entity_InitFilters(){
305         add_entity_filter( g_filter_entity_world, EXCLUDE_WORLD );
306         add_entity_filter( g_filter_entity_func_group, EXCLUDE_WORLD );
307         add_entity_filter( g_filter_entity_world, EXCLUDE_ENT, true );
308         add_entity_filter( g_filter_entity_trigger, EXCLUDE_TRIGGERS );
309         add_entity_filter( g_filter_entity_misc_model, EXCLUDE_MODELS );
310         add_entity_filter( g_filter_entity_misc_gamemodel, EXCLUDE_MODELS );
311         add_entity_filter( g_filter_entity_doom3model, EXCLUDE_MODELS );
312         add_entity_filter( g_filter_entity_light, EXCLUDE_LIGHTS );
313         add_entity_filter( g_filter_entity_path, EXCLUDE_PATHS );
314 }
315
316
317 #include "preferencesystem.h"
318
319 void Entity_Construct( EGameType gameType ){
320         g_gameType = gameType;
321         if ( g_gameType == eGameTypeDoom3 ) {
322                 g_targetable_nameKey = "name";
323
324                 Static<KeyIsName>::instance().m_keyIsName = keyIsNameDoom3;
325                 Static<KeyIsName>::instance().m_nameKey = "name";
326         }
327         else
328         {
329                 Static<KeyIsName>::instance().m_keyIsName = keyIsNameQuake3;
330                 Static<KeyIsName>::instance().m_nameKey = "targetname";
331         }
332
333         GlobalPreferenceSystem().registerPreference( "SI_ShowNames", make_property_string( g_showNames ) );
334         GlobalPreferenceSystem().registerPreference( "SI_ShowAngles", make_property_string( g_showAngles ) );
335         GlobalPreferenceSystem().registerPreference( "NewLightStyle", make_property_string( g_newLightDraw ) );
336         GlobalPreferenceSystem().registerPreference( "LightRadiuses", make_property_string( g_lightRadii ) );
337
338         Entity_InitFilters();
339         LightType lightType = LIGHTTYPE_DEFAULT;
340         if ( g_gameType == eGameTypeRTCW ) {
341                 lightType = LIGHTTYPE_RTCW;
342         }
343         else if ( g_gameType == eGameTypeDoom3 ) {
344                 lightType = LIGHTTYPE_DOOM3;
345         }
346         Light_Construct( lightType );
347         MiscModel_construct();
348         Doom3Group_construct();
349
350         RenderablePivot::StaticShader::instance() = GlobalShaderCache().capture( "$PIVOT" );
351
352         GlobalShaderCache().attachRenderable( StaticRenderableConnectionLines::instance() );
353 }
354
355 void Entity_Destroy(){
356         GlobalShaderCache().detachRenderable( StaticRenderableConnectionLines::instance() );
357
358         GlobalShaderCache().release( "$PIVOT" );
359
360         Doom3Group_destroy();
361         MiscModel_destroy();
362         Light_Destroy();
363 }