]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/map.cpp
Merge commit '6134b25a1b87ad94c5a082915064a33ea66b07d7' into master-merge
[xonotic/netradiant.git] / radiant / map.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 "map.h"
23
24 #include <gtk/gtk.h>
25
26 #include "debugging/debugging.h"
27
28 #include "imap.h"
29
30 MapModules& ReferenceAPI_getMapModules();
31
32 #include "iselection.h"
33 #include "iundo.h"
34 #include "ibrush.h"
35 #include "ifilter.h"
36 #include "ireference.h"
37 #include "ifiletypes.h"
38 #include "ieclass.h"
39 #include "irender.h"
40 #include "ientity.h"
41 #include "editable.h"
42 #include "iarchive.h"
43 #include "ifilesystem.h"
44 #include "namespace.h"
45 #include "moduleobserver.h"
46
47 #include <set>
48
49 #include <gdk/gdkkeysyms.h>
50 #include "uilib/uilib.h"
51
52 #include "scenelib.h"
53 #include "transformlib.h"
54 #include "selectionlib.h"
55 #include "instancelib.h"
56 #include "traverselib.h"
57 #include "maplib.h"
58 #include "eclasslib.h"
59 #include "cmdlib.h"
60 #include "stream/textfilestream.h"
61 #include "os/path.h"
62 #include "os/file.h"
63 #include "uniquenames.h"
64 #include "modulesystem/singletonmodule.h"
65 #include "modulesystem/moduleregistry.h"
66 #include "stream/stringstream.h"
67 #include "signal/signal.h"
68
69 #include "gtkutil/filechooser.h"
70 #include "timer.h"
71 #include "select.h"
72 #include "plugin.h"
73 #include "filetypes.h"
74 #include "gtkdlgs.h"
75 #include "entityinspector.h"
76 #include "points.h"
77 #include "qe3.h"
78 #include "camwindow.h"
79 #include "xywindow.h"
80 #include "mainframe.h"
81 #include "preferences.h"
82 #include "preferencesystem.h"
83 #include "referencecache.h"
84 #include "mru.h"
85 #include "commands.h"
86 #include "autosave.h"
87 #include "brushmodule.h"
88 #include "brush.h"
89 #include "patch.h"
90
91 bool g_writeMapComments = true;
92
93 class NameObserver
94 {
95 UniqueNames& m_names;
96 CopiedString m_name;
97
98 void construct(){
99         if ( !empty() ) {
100                 //globalOutputStream() << "construct " << makeQuoted(c_str()) << "\n";
101                 m_names.insert( name_read( c_str() ) );
102         }
103 }
104
105 void destroy(){
106         if ( !empty() ) {
107                 //globalOutputStream() << "destroy " << makeQuoted(c_str()) << "\n";
108                 m_names.erase( name_read( c_str() ) );
109         }
110 }
111
112 NameObserver& operator=( const NameObserver& other );
113
114 public:
115 NameObserver( UniqueNames& names ) : m_names( names ){
116         construct();
117 }
118 NameObserver( const NameObserver& other ) : m_names( other.m_names ), m_name( other.m_name ){
119         construct();
120 }
121
122 ~NameObserver(){
123         destroy();
124 }
125
126 bool empty() const {
127         return string_empty( c_str() );
128 }
129
130 const char* c_str() const {
131         return m_name.c_str();
132 }
133
134 void nameChanged( const char* name ){
135         destroy();
136         m_name = name;
137         construct();
138 }
139
140 typedef MemberCaller<NameObserver, void(const char*), &NameObserver::nameChanged> NameChangedCaller;
141 };
142
143 class BasicNamespace : public Namespace
144 {
145 typedef std::map<NameCallback, NameObserver> Names;
146 Names m_names;
147 UniqueNames m_uniqueNames;
148 public:
149 ~BasicNamespace(){
150         ASSERT_MESSAGE( m_names.empty(), "namespace: names still registered at shutdown" );
151 }
152
153 void attach( const NameCallback& setName, const NameCallbackCallback& attachObserver ){
154         std::pair<Names::iterator, bool> result = m_names.insert( Names::value_type( setName, m_uniqueNames ) );
155         ASSERT_MESSAGE( result.second, "cannot attach name" );
156         attachObserver( NameObserver::NameChangedCaller( ( *result.first ).second ) );
157         //globalOutputStream() << "attach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
158 }
159
160 void detach( const NameCallback& setName, const NameCallbackCallback& detachObserver ){
161         Names::iterator i = m_names.find( setName );
162         ASSERT_MESSAGE( i != m_names.end(), "cannot detach name" );
163         //globalOutputStream() << "detach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
164         detachObserver( NameObserver::NameChangedCaller( ( *i ).second ) );
165         m_names.erase( i );
166 }
167
168 void makeUnique( const char* name, const NameCallback& setName ) const {
169         char buffer[1024];
170         name_write( buffer, m_uniqueNames.make_unique( name_read( name ) ) );
171         setName( buffer );
172 }
173
174 void mergeNames( const BasicNamespace& other ) const {
175         typedef std::list<NameCallback> SetNameCallbacks;
176         typedef std::map<CopiedString, SetNameCallbacks> NameGroups;
177         NameGroups groups;
178
179         UniqueNames uniqueNames( other.m_uniqueNames );
180
181         for ( Names::const_iterator i = m_names.begin(); i != m_names.end(); ++i )
182         {
183                 groups[( *i ).second.c_str()].push_back( ( *i ).first );
184         }
185
186         for ( NameGroups::iterator i = groups.begin(); i != groups.end(); ++i )
187         {
188                 name_t uniqueName( uniqueNames.make_unique( name_read( ( *i ).first.c_str() ) ) );
189                 uniqueNames.insert( uniqueName );
190
191                 char buffer[1024];
192                 name_write( buffer, uniqueName );
193
194                 //globalOutputStream() << "renaming " << makeQuoted((*i).first.c_str()) << " to " << makeQuoted(buffer) << "\n";
195
196                 SetNameCallbacks& setNameCallbacks = ( *i ).second;
197
198                 for ( SetNameCallbacks::const_iterator j = setNameCallbacks.begin(); j != setNameCallbacks.end(); ++j )
199                 {
200                         ( *j )( buffer );
201                 }
202         }
203 }
204 };
205
206 BasicNamespace g_defaultNamespace;
207 BasicNamespace g_cloneNamespace;
208
209 class NamespaceAPI
210 {
211 Namespace* m_namespace;
212 public:
213 typedef Namespace Type;
214
215 STRING_CONSTANT( Name, "*" );
216
217 NamespaceAPI(){
218         m_namespace = &g_defaultNamespace;
219 }
220
221 Namespace* getTable(){
222         return m_namespace;
223 }
224 };
225
226 typedef SingletonModule<NamespaceAPI> NamespaceModule;
227 typedef Static<NamespaceModule> StaticNamespaceModule;
228 StaticRegisterModule staticRegisterDefaultNamespace( StaticNamespaceModule::instance() );
229
230
231 std::list<Namespaced*> g_cloned;
232
233 inline Namespaced* Node_getNamespaced( scene::Node& node ){
234         return NodeTypeCast<Namespaced>::cast( node );
235 }
236
237 void Node_gatherNamespaced( scene::Node& node ){
238         Namespaced* namespaced = Node_getNamespaced( node );
239         if ( namespaced != 0 ) {
240                 g_cloned.push_back( namespaced );
241         }
242 }
243
244 class GatherNamespaced : public scene::Traversable::Walker
245 {
246 public:
247 bool pre( scene::Node& node ) const {
248         Node_gatherNamespaced( node );
249         return true;
250 }
251 };
252
253 void Map_gatherNamespaced( scene::Node& root ){
254         Node_traverseSubgraph( root, GatherNamespaced() );
255 }
256
257 void Map_mergeClonedNames(){
258         for ( std::list<Namespaced*>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i )
259         {
260                 ( *i )->setNamespace( g_cloneNamespace );
261         }
262         g_cloneNamespace.mergeNames( g_defaultNamespace );
263         for ( std::list<Namespaced*>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i )
264         {
265                 ( *i )->setNamespace( g_defaultNamespace );
266         }
267
268         g_cloned.clear();
269 }
270
271 class WorldNode
272 {
273 scene::Node* m_node;
274 public:
275 WorldNode()
276         : m_node( 0 ){
277 }
278
279 void set( scene::Node* node ){
280         if ( m_node != 0 ) {
281                 m_node->DecRef();
282         }
283         m_node = node;
284         if ( m_node != 0 ) {
285                 m_node->IncRef();
286         }
287 }
288
289 scene::Node* get() const {
290         return m_node;
291 }
292 };
293
294 class Map;
295 void Map_SetValid( Map& map, bool valid );
296
297 void Map_UpdateTitle( const Map& map );
298
299 void Map_SetWorldspawn( Map& map, scene::Node* node );
300
301
302 class Map : public ModuleObserver
303 {
304 public:
305 CopiedString m_name;
306 Resource* m_resource;
307 bool m_valid;
308
309 bool m_modified;
310
311 void ( *m_modified_changed )( const Map& );
312
313 Signal0 m_mapValidCallbacks;
314
315 WorldNode m_world_node;   // "classname" "worldspawn" !
316
317 Map() : m_resource( 0 ), m_valid( false ), m_modified_changed( Map_UpdateTitle ){
318 }
319
320 void realise(){
321         if ( m_resource != 0 ) {
322                 if ( Map_Unnamed( *this ) ) {
323                         g_map.m_resource->setNode( NewMapRoot( "" ).get_pointer() );
324                         MapFile* map = Node_getMapFile( *g_map.m_resource->getNode() );
325                         if ( map != 0 ) {
326                                 map->save();
327                         }
328                 }
329                 else
330                 {
331                         m_resource->load();
332                 }
333
334                 GlobalSceneGraph().insert_root( *m_resource->getNode() );
335
336                 AutoSave_clear();
337
338                 Map_SetValid( g_map, true );
339         }
340 }
341
342 void unrealise(){
343         if ( m_resource != 0 ) {
344                 Map_SetValid( g_map, false );
345                 Map_SetWorldspawn( g_map, 0 );
346
347
348                 GlobalUndoSystem().clear();
349
350                 GlobalSceneGraph().erase_root();
351         }
352 }
353 };
354
355 Map g_map;
356 Map* g_currentMap = 0;
357
358 void Map_addValidCallback( Map& map, const SignalHandler& handler ){
359         map.m_mapValidCallbacks.connectLast( handler );
360 }
361
362 bool Map_Valid( const Map& map ){
363         return map.m_valid;
364 }
365
366 void Map_SetValid( Map& map, bool valid ){
367         map.m_valid = valid;
368         map.m_mapValidCallbacks();
369 }
370
371
372 const char* Map_Name( const Map& map ){
373         return map.m_name.c_str();
374 }
375
376 bool Map_Unnamed( const Map& map ){
377         return string_equal( Map_Name( map ), "unnamed.map" );
378 }
379
380 inline const MapFormat& MapFormat_forFile( const char* filename ){
381         const char* moduleName = findModuleName( GetFileTypeRegistry(), MapFormat::Name(), path_get_extension( filename ) );
382         MapFormat* format = Radiant_getMapModules().findModule( moduleName );
383         ASSERT_MESSAGE( format != 0, "map format not found for file " << makeQuoted( filename ) );
384         return *format;
385 }
386
387 const MapFormat& Map_getFormat( const Map& map ){
388         return MapFormat_forFile( Map_Name( map ) );
389 }
390
391
392 bool Map_Modified( const Map& map ){
393         return map.m_modified;
394 }
395
396 void Map_SetModified( Map& map, bool modified ){
397         if ( map.m_modified ^ modified ) {
398                 map.m_modified = modified;
399
400                 map.m_modified_changed( map );
401         }
402 }
403
404 void Map_UpdateTitle( const Map& map ){
405         Sys_SetTitle( map.m_name.c_str(), Map_Modified( map ) );
406 }
407
408
409 scene::Node* Map_GetWorldspawn( const Map& map ){
410         return map.m_world_node.get();
411 }
412
413 void Map_SetWorldspawn( Map& map, scene::Node* node ){
414         map.m_world_node.set( node );
415 }
416
417
418 // TTimo
419 // need that in a variable, will have to tweak depending on the game
420 float g_MaxWorldCoord = 64 * 1024;
421 float g_MinWorldCoord = -64 * 1024;
422
423 void AddRegionBrushes( void );
424
425 void RemoveRegionBrushes( void );
426
427
428 /*
429    ================
430    Map_Free
431    free all map elements, reinitialize the structures that depend on them
432    ================
433  */
434 void Map_Free(){
435         Pointfile_Clear();
436
437         g_map.m_resource->detach( g_map );
438         GlobalReferenceCache().release( g_map.m_name.c_str() );
439         g_map.m_resource = 0;
440
441         FlushReferences();
442
443         g_currentMap = 0;
444         Brush_unlatchPreferences();
445 }
446
447 class EntityFindByClassname : public scene::Graph::Walker
448 {
449 const char* m_name;
450 Entity*& m_entity;
451 public:
452 EntityFindByClassname( const char* name, Entity*& entity ) : m_name( name ), m_entity( entity ){
453         m_entity = 0;
454 }
455
456 bool pre( const scene::Path& path, scene::Instance& instance ) const {
457         if ( m_entity == 0 ) {
458                 Entity* entity = Node_getEntity( path.top() );
459                 if ( entity != 0
460                          && string_equal( m_name, entity->getKeyValue( "classname" ) ) ) {
461                         m_entity = entity;
462                 }
463         }
464         return true;
465 }
466 };
467
468 Entity* Scene_FindEntityByClass( const char* name ){
469         Entity* entity;
470         GlobalSceneGraph().traverse( EntityFindByClassname( name, entity ) );
471         return entity;
472 }
473
474 Entity *Scene_FindPlayerStart(){
475         typedef const char* StaticString;
476         StaticString strings[] = {
477                 "info_player_start",
478                 "info_player_deathmatch",
479                 "team_CTF_redplayer",
480                 "team_CTF_blueplayer",
481                 "team_CTF_redspawn",
482                 "team_CTF_bluespawn",
483         };
484         typedef const StaticString* StaticStringIterator;
485         for ( StaticStringIterator i = strings, end = strings + ( sizeof( strings ) / sizeof( StaticString ) ); i != end; ++i )
486         {
487                 Entity* entity = Scene_FindEntityByClass( *i );
488                 if ( entity != 0 ) {
489                         return entity;
490                 }
491         }
492         return 0;
493 }
494
495 //
496 // move the view to a start position
497 //
498
499
500 void FocusViews( const Vector3& point, float angle ){
501         CamWnd& camwnd = *g_pParentWnd->GetCamWnd();
502         Camera_setOrigin( camwnd, point );
503         Vector3 angles( Camera_getAngles( camwnd ) );
504         angles[CAMERA_PITCH] = 0;
505         angles[CAMERA_YAW] = angle;
506         Camera_setAngles( camwnd, angles );
507
508         XYWnd* xywnd = g_pParentWnd->GetXYWnd();
509         xywnd->SetOrigin( point );
510 }
511
512 #include "stringio.h"
513
514 void Map_StartPosition(){
515         Entity* entity = Scene_FindPlayerStart();
516
517         if ( entity ) {
518                 Vector3 origin;
519                 string_parse_vector3( entity->getKeyValue( "origin" ), origin );
520                 FocusViews( origin, string_read_float( entity->getKeyValue( "angle" ) ) );
521         }
522         else
523         {
524                 FocusViews( g_vector3_identity, 0 );
525         }
526 }
527
528
529 inline bool node_is_worldspawn( scene::Node& node ){
530         Entity* entity = Node_getEntity( node );
531         return entity != 0 && string_equal( entity->getKeyValue( "classname" ), "worldspawn" );
532 }
533
534
535 // use first worldspawn
536 class entity_updateworldspawn : public scene::Traversable::Walker
537 {
538 public:
539 bool pre( scene::Node& node ) const {
540         if ( node_is_worldspawn( node ) ) {
541                 if ( Map_GetWorldspawn( g_map ) == 0 ) {
542                         Map_SetWorldspawn( g_map, &node );
543                 }
544         }
545         return false;
546 }
547 };
548
549 scene::Node* Map_FindWorldspawn( Map& map ){
550         Map_SetWorldspawn( map, 0 );
551
552         Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() );
553
554         return Map_GetWorldspawn( map );
555 }
556
557
558 class CollectAllWalker : public scene::Traversable::Walker
559 {
560 scene::Node& m_root;
561 UnsortedNodeSet& m_nodes;
562 public:
563 CollectAllWalker( scene::Node& root, UnsortedNodeSet& nodes ) : m_root( root ), m_nodes( nodes ){
564 }
565
566 bool pre( scene::Node& node ) const {
567         m_nodes.insert( NodeSmartReference( node ) );
568         Node_getTraversable( m_root )->erase( node );
569         return false;
570 }
571 };
572
573 void Node_insertChildFirst( scene::Node& parent, scene::Node& child ){
574         UnsortedNodeSet nodes;
575         Node_getTraversable( parent )->traverse( CollectAllWalker( parent, nodes ) );
576         Node_getTraversable( parent )->insert( child );
577
578         for ( UnsortedNodeSet::iterator i = nodes.begin(); i != nodes.end(); ++i )
579         {
580                 Node_getTraversable( parent )->insert( ( *i ) );
581         }
582 }
583
584 scene::Node& createWorldspawn(){
585         NodeSmartReference worldspawn( GlobalEntityCreator().createEntity( GlobalEntityClassManager().findOrInsert( "worldspawn", true ) ) );
586         Node_insertChildFirst( GlobalSceneGraph().root(), worldspawn );
587         return worldspawn;
588 }
589
590 void Map_UpdateWorldspawn( Map& map ){
591         if ( Map_FindWorldspawn( map ) == 0 ) {
592                 Map_SetWorldspawn( map, &createWorldspawn() );
593         }
594 }
595
596 scene::Node& Map_FindOrInsertWorldspawn( Map& map ){
597         Map_UpdateWorldspawn( map );
598         return *Map_GetWorldspawn( map );
599 }
600
601
602 class MapMergeAll : public scene::Traversable::Walker
603 {
604 mutable scene::Path m_path;
605 public:
606 MapMergeAll( const scene::Path& root )
607         : m_path( root ){
608 }
609
610 bool pre( scene::Node& node ) const {
611         Node_getTraversable( m_path.top() )->insert( node );
612         m_path.push( makeReference( node ) );
613         selectPath( m_path, true );
614         return false;
615 }
616
617 void post( scene::Node& node ) const {
618         m_path.pop();
619 }
620 };
621
622 class MapMergeEntities : public scene::Traversable::Walker
623 {
624 mutable scene::Path m_path;
625 public:
626 MapMergeEntities( const scene::Path& root )
627         : m_path( root ){
628 }
629
630 bool pre( scene::Node& node ) const {
631         if ( node_is_worldspawn( node ) ) {
632                 scene::Node* world_node = Map_FindWorldspawn( g_map );
633                 if ( world_node == 0 ) {
634                         Map_SetWorldspawn( g_map, &node );
635                         Node_getTraversable( m_path.top().get() )->insert( node );
636                         m_path.push( makeReference( node ) );
637                         Node_getTraversable( node )->traverse( SelectChildren( m_path ) );
638                 }
639                 else
640                 {
641                         m_path.push( makeReference( *world_node ) );
642                         Node_getTraversable( node )->traverse( MapMergeAll( m_path ) );
643                 }
644         }
645         else
646         {
647                 Node_getTraversable( m_path.top() )->insert( node );
648                 m_path.push( makeReference( node ) );
649                 if ( node_is_group( node ) ) {
650                         Node_getTraversable( node )->traverse( SelectChildren( m_path ) );
651                 }
652                 else
653                 {
654                         selectPath( m_path, true );
655                 }
656         }
657         return false;
658 }
659
660 void post( scene::Node& node ) const {
661         m_path.pop();
662 }
663 };
664
665 class BasicContainer : public scene::Node::Symbiot
666 {
667 class TypeCasts
668 {
669 NodeTypeCastTable m_casts;
670 public:
671 TypeCasts(){
672         NodeContainedCast<BasicContainer, scene::Traversable>::install( m_casts );
673 }
674
675 NodeTypeCastTable& get(){
676         return m_casts;
677 }
678 };
679
680 scene::Node m_node;
681 TraversableNodeSet m_traverse;
682 public:
683
684 typedef LazyStatic<TypeCasts> StaticTypeCasts;
685
686 scene::Traversable& get( NullType<scene::Traversable>){
687         return m_traverse;
688 }
689
690 BasicContainer() : m_node( this, this, StaticTypeCasts::instance().get() ){
691 }
692
693 void release(){
694         delete this;
695 }
696
697 scene::Node& node(){
698         return m_node;
699 }
700 };
701
702 /// Merges the map graph rooted at \p node into the global scene-graph.
703 void MergeMap( scene::Node& node ){
704         Node_getTraversable( node )->traverse( MapMergeEntities( scene::Path( makeReference( GlobalSceneGraph().root() ) ) ) );
705 }
706
707 void Map_ImportSelected( TextInputStream& in, const MapFormat& format ){
708         NodeSmartReference node( ( new BasicContainer )->node() );
709         format.readGraph( node, in, GlobalEntityCreator() );
710         Map_gatherNamespaced( node );
711         Map_mergeClonedNames();
712         MergeMap( node );
713 }
714
715 inline scene::Cloneable* Node_getCloneable( scene::Node& node ){
716         return NodeTypeCast<scene::Cloneable>::cast( node );
717 }
718
719 inline scene::Node& node_clone( scene::Node& node ){
720         scene::Cloneable* cloneable = Node_getCloneable( node );
721         if ( cloneable != 0 ) {
722                 return cloneable->clone();
723         }
724
725         return ( new scene::NullNode )->node();
726 }
727
728 class CloneAll : public scene::Traversable::Walker
729 {
730 mutable scene::Path m_path;
731 public:
732 CloneAll( scene::Node& root )
733         : m_path( makeReference( root ) ){
734 }
735
736 bool pre( scene::Node& node ) const {
737         if ( node.isRoot() ) {
738                 return false;
739         }
740
741         m_path.push( makeReference( node_clone( node ) ) );
742         m_path.top().get().IncRef();
743
744         return true;
745 }
746
747 void post( scene::Node& node ) const {
748         if ( node.isRoot() ) {
749                 return;
750         }
751
752         Node_getTraversable( m_path.parent() )->insert( m_path.top() );
753
754         m_path.top().get().DecRef();
755         m_path.pop();
756 }
757 };
758
759 scene::Node& Node_Clone( scene::Node& node ){
760         scene::Node& clone = node_clone( node );
761         scene::Traversable* traversable = Node_getTraversable( node );
762         if ( traversable != 0 ) {
763                 traversable->traverse( CloneAll( clone ) );
764         }
765         return clone;
766 }
767
768
769 typedef std::map<CopiedString, std::size_t> EntityBreakdown;
770
771 class EntityBreakdownWalker : public scene::Graph::Walker
772 {
773 EntityBreakdown& m_entitymap;
774 public:
775 EntityBreakdownWalker( EntityBreakdown& entitymap )
776         : m_entitymap( entitymap ){
777 }
778
779 bool pre( const scene::Path& path, scene::Instance& instance ) const {
780         Entity* entity = Node_getEntity( path.top() );
781         if ( entity != 0 ) {
782                 const EntityClass& eclass = entity->getEntityClass();
783                 if ( m_entitymap.find( eclass.name() ) == m_entitymap.end() ) {
784                         m_entitymap[eclass.name()] = 1;
785                 } else
786                 {
787                         ++m_entitymap[eclass.name()];
788                 }
789         }
790         return true;
791 }
792 };
793
794 void Scene_EntityBreakdown( EntityBreakdown& entitymap ){
795         GlobalSceneGraph().traverse( EntityBreakdownWalker( entitymap ) );
796 }
797
798 class CountStuffWalker : public scene::Graph::Walker
799 {
800 int& m_patches;
801 int& m_ents_ingame;
802 int& m_groupents;
803 int& m_groupents_ingame;
804 public:
805 CountStuffWalker( int& patches, int& ents_ingame, int& groupents, int& groupents_ingame )
806         : m_patches( patches ), m_ents_ingame( ents_ingame ), m_groupents( groupents ), m_groupents_ingame( groupents_ingame ){
807 }
808 bool pre( const scene::Path& path, scene::Instance& instance ) const {
809         Patch* patch = Node_getPatch( path.top() );
810         if( patch != 0 ){
811                 ++m_patches;
812         }
813         Entity* entity = Node_getEntity( path.top() );
814         if ( entity != 0 ){
815                 if( entity->isContainer() ){
816                         ++m_groupents;
817                         if( !string_equal_nocase( "func_group", entity->getKeyValue( "classname" ) ) &&
818                                 !string_equal_nocase( "_decal", entity->getKeyValue( "classname" ) ) ){
819                                 ++m_groupents_ingame;
820                                 ++m_ents_ingame;
821                         }
822                         return true;
823                 }
824                 if( !string_equal_nocase_n( "light", entity->getKeyValue( "classname" ), 5 ) &&
825                         !string_equal_nocase( "misc_model", entity->getKeyValue( "classname" ) ) ){
826                         ++m_ents_ingame;
827                 }
828         }
829         return true;
830 }
831 };
832
833 void Scene_CountStuff( int& patches, int& ents_ingame, int& groupents, int& groupents_ingame ){
834         GlobalSceneGraph().traverse( CountStuffWalker( patches, ents_ingame, groupents, groupents_ingame ) );
835 }
836
837 WindowPosition g_posMapInfoWnd( c_default_window_pos );
838
839 void DoMapInfo(){
840         ModalDialog dialog;
841         ui::Widget w_brushes{ui::null};
842         ui::Widget w_patches{ui::null};
843         ui::Widget w_ents{ui::null};
844         ui::Widget w_ents_ingame{ui::null};
845         ui::Widget w_groupents{ui::null};
846         ui::Widget w_groupents_ingame{ui::null};
847
848         ui::ListStore EntityBreakdownWalker{ui::null};
849
850         ui::Window window = MainFrame_getWindow().create_dialog_window("Map Info", G_CALLBACK(dialog_delete_callback ), &dialog );
851
852         window_set_position( window, g_posMapInfoWnd );
853
854         {
855                 auto vbox = create_dialog_vbox( 4, 4 );
856                 window.add(vbox);
857
858                 {
859                         auto hbox = create_dialog_hbox( 4 );
860                         vbox.pack_start( hbox, FALSE, FALSE, 0 );
861
862                         {
863                                 auto table = create_dialog_table( 3, 4, 4, 4 );
864                                 hbox.pack_start( table, TRUE, TRUE, 0 );
865
866                                 {
867                                         auto label = ui::Label( "Total Brushes:" );
868                                         label.show();
869                     table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
870                                         gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
871                                 }
872                                 {
873                                         auto label = ui::Label( "" );
874                                         label.show();
875                     table.attach(label, {1, 2, 0, 1}, {GTK_FILL | GTK_EXPAND, 0}, {3, 0});
876                                         w_brushes = label;
877                                 }
878                                 {
879                                         auto label = ui::Label( "Total Patches" );
880                                         label.show();
881                     table.attach(label, {2, 3, 0, 1}, {GTK_FILL, 0});
882                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
883                                 }
884                                 {
885                                         auto label = ui::Label( "" );
886                                         label.show();
887                     table.attach(label, {3, 4, 0, 1}, {GTK_FILL, 0}, {3, 0});
888                                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
889                                         w_patches = label;
890                                 }
891                                 {
892                                         auto label = ui::Label( "Total Entities:" );
893                                         label.show();
894                                         table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
895                                         gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
896                                 }
897                                 {
898                                         auto label = ui::Label( "" );
899                                         label.show();
900                                         table.attach(label, {1, 2, 1, 2}, {GTK_FILL | GTK_EXPAND, 0}, {3, 0});
901                                         gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
902                                         w_ents = label;
903                                 }
904                                 {
905                                         auto label = ui::Label( "Ingame Entities:" );
906                                         label.show();
907                                         table.attach(label, {2, 3, 1, 2}, {GTK_FILL, 0});
908                                         gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
909                                 }
910                                 {
911                                         auto label = ui::Label( "" );
912                                         label.show();
913                                         table.attach(label, {3, 4, 1, 2}, {GTK_FILL | GTK_EXPAND, 0 }, {3, 0});
914                                         gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
915                                         w_ents_ingame = label;
916                                 }
917                                 {
918                                         auto label = ui::Label( "Group Entities:" );
919                                         label.show();
920                                         table.attach(label, {0, 1, 2, 3}, {GTK_FILL, 0});
921                                         gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
922                                 }
923                                 {
924                                         auto label = ui::Label( "" );
925                                         label.show();
926                                         table.attach(label, {1, 2, 2, 3}, {GTK_FILL | GTK_EXPAND, 0}, {3, 0});
927                                         gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
928                                         w_groupents = label;
929                                 }
930                                 {
931                                         auto label = ui::Label( "Ingame Group Entities:" );
932                                         label.show();
933                                         table.attach(label, {2, 3, 2, 3}, {GTK_FILL, 0});
934                                         gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
935                                 }
936                                 {
937                                         auto label = ui::Label( "" );
938                                         label.show();
939                                         table.attach(label, {3, 4, 2, 3}, {GTK_FILL | GTK_EXPAND, 0}, {3, 0});
940                                         gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
941                                         w_groupents_ingame = label;
942                                 }
943
944                         }
945                         {
946                                 auto vbox2 = create_dialog_vbox( 4 );
947                                 hbox.pack_start( vbox2, FALSE, FALSE, 0 );
948
949                                 {
950                                         auto button = create_dialog_button( "Close", G_CALLBACK( dialog_button_ok ), &dialog );
951                                         vbox2.pack_start( button, FALSE, FALSE, 0 );
952                                 }
953                         }
954                 }
955                 {
956                         ui::Widget label = ui::Label( "*** Entity breakdown ***" );
957                         label.show();
958                         vbox.pack_start( label, FALSE, TRUE, 0 );
959                         gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
960                 }
961                 {
962                         auto scr = create_scrolled_window( ui::Policy::NEVER, ui::Policy::AUTOMATIC, 4 );
963                         vbox.pack_start( scr, TRUE, TRUE, 0 );
964
965                         {
966                                 auto store = ui::ListStore::from(gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_UINT ));
967
968                                 auto view = ui::TreeView(ui::TreeModel::from(store._handle));
969                                 gtk_tree_view_set_headers_clickable(view, TRUE );
970
971                                 {
972                                         auto renderer = ui::CellRendererText(ui::New);
973                                         auto column = ui::TreeViewColumn( "Entity", renderer, {{"text", 0}} );
974                                         gtk_tree_view_append_column(view, column );
975                                         gtk_tree_view_column_set_sort_column_id( column, 0 );
976                                 }
977
978                                 {
979                                         auto renderer = ui::CellRendererText(ui::New);
980                                         auto column = ui::TreeViewColumn( "Count", renderer, {{"text", 1}} );
981                                         gtk_tree_view_append_column(view, column );
982                                         gtk_tree_view_column_set_sort_column_id( column, 1 );
983                                 }
984
985                                 view.show();
986
987                                 scr.add(view);
988
989                                 EntityBreakdownWalker = store;
990                         }
991                 }
992         }
993
994         // Initialize fields
995
996         {
997                 EntityBreakdown entitymap;
998                 Scene_EntityBreakdown( entitymap );
999
1000                 for ( EntityBreakdown::iterator i = entitymap.begin(); i != entitymap.end(); ++i )
1001                 {
1002                         char tmp[16];
1003                         sprintf( tmp, "%u", Unsigned( ( *i ).second ) );
1004                         EntityBreakdownWalker.append(0, (*i).first.c_str(), 1, tmp);
1005                 }
1006         }
1007
1008         EntityBreakdownWalker.unref();
1009
1010         int n_patches = 0;
1011         int n_ents_ingame = 0;
1012         int n_groupents = 0;
1013         int n_groupents_ingame = 0;
1014         Scene_CountStuff( n_patches, n_ents_ingame, n_groupents, n_groupents_ingame );
1015         //globalOutputStream() << n_patches << n_ents_ingame << n_groupents << n_groupents_ingame << "\n";
1016
1017         char *markup;
1018
1019         markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%u</b></span>  ", Unsigned( g_brushCount.get() ) );
1020         gtk_label_set_markup( GTK_LABEL( w_brushes ), markup );
1021         g_free( markup );
1022
1023         markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%i</b></span>  ", n_patches );
1024         gtk_label_set_markup( GTK_LABEL( w_patches ), markup );
1025         g_free( markup );
1026
1027         markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%u</b></span>  ", Unsigned( g_entityCount.get() ) );
1028         gtk_label_set_markup( GTK_LABEL( w_ents ), markup );
1029         g_free( markup );
1030
1031         markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%i</b></span>  ", n_ents_ingame );
1032         gtk_label_set_markup( GTK_LABEL( w_ents_ingame ), markup );
1033         g_free( markup );
1034
1035         markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%i</b></span>  ", n_groupents );
1036         gtk_label_set_markup( GTK_LABEL( w_groupents ), markup );
1037         g_free( markup );
1038
1039         markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%i</b></span>  ", n_groupents_ingame );
1040         gtk_label_set_markup( GTK_LABEL( w_groupents_ingame ), markup );
1041         g_free( markup );
1042
1043
1044         modal_dialog_show( window, dialog );
1045
1046         // save before exit
1047         window_get_position( window, g_posMapInfoWnd );
1048
1049     window.destroy();
1050 }
1051
1052
1053
1054 class ScopeTimer
1055 {
1056 Timer m_timer;
1057 const char* m_message;
1058 public:
1059 ScopeTimer( const char* message )
1060         : m_message( message ){
1061         m_timer.start();
1062 }
1063
1064 ~ScopeTimer(){
1065         double elapsed_time = m_timer.elapsed_msec() / 1000.f;
1066         globalOutputStream() << m_message << " timer: " << FloatFormat( elapsed_time, 5, 2 ) << " second(s) elapsed\n";
1067 }
1068 };
1069
1070 CopiedString g_strLastMapFolder = "";
1071
1072 /*
1073    ================
1074    Map_LoadFile
1075    ================
1076  */
1077
1078 void Map_LoadFile( const char *filename ){
1079         g_map.m_name = filename;
1080
1081         // refresh VFS to apply new pak filtering based on mapname
1082         // needed for daemon DPK VFS
1083         VFS_Refresh();
1084
1085         globalOutputStream() << "Loading map from " << filename << "\n";
1086         ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
1087
1088         MRU_AddFile( filename );
1089         g_strLastMapFolder = g_path_get_dirname( filename );
1090
1091         bool switch_format = false;
1092
1093         {
1094                 ScopeTimer timer( "map load" );
1095
1096                 const MapFormat* format = NULL;
1097                 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
1098                 if ( string_not_empty( moduleName ) ) {
1099                         format = ReferenceAPI_getMapModules().findModule( moduleName );
1100                 }
1101
1102                 for ( int i = 0; i < Brush_toggleFormatCount(); ++i )
1103                 {
1104                         if ( i ) {
1105                                 Map_Free();
1106                         }
1107                         Brush_toggleFormat( i );
1108                         Map_UpdateTitle( g_map );
1109
1110                         g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
1111                         if ( format ) {
1112                                 format->wrongFormat = false;
1113                         }
1114                         g_map.m_resource->attach( g_map );
1115                         if ( format ) {
1116                                 if ( !format->wrongFormat ) {
1117                                         break;
1118                                 }
1119                                 switch_format = !switch_format;
1120                         }
1121                 }
1122
1123                 Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() );
1124         }
1125
1126         globalOutputStream() << "--- LoadMapFile ---\n";
1127         globalOutputStream() << g_map.m_name.c_str() << "\n";
1128
1129         globalOutputStream() << Unsigned( g_brushCount.get() ) << " primitive\n";
1130         globalOutputStream() << Unsigned( g_entityCount.get() ) << " entities\n";
1131
1132         //GlobalEntityCreator().printStatistics();
1133
1134         //
1135         // move the view to a start position
1136         //
1137         Map_StartPosition();
1138
1139         g_currentMap = &g_map;
1140
1141         Brush_switchFormat( switch_format );
1142 }
1143
1144 class Excluder
1145 {
1146 public:
1147 virtual bool excluded( scene::Node& node ) const = 0;
1148 };
1149
1150 class ExcludeWalker : public scene::Traversable::Walker
1151 {
1152 const scene::Traversable::Walker& m_walker;
1153 const Excluder* m_exclude;
1154 mutable bool m_skip;
1155 public:
1156 ExcludeWalker( const scene::Traversable::Walker& walker, const Excluder& exclude )
1157         : m_walker( walker ), m_exclude( &exclude ), m_skip( false ){
1158 }
1159
1160 bool pre( scene::Node& node ) const {
1161         if ( m_exclude->excluded( node ) || node.isRoot() ) {
1162                 m_skip = true;
1163                 return false;
1164         }
1165         else
1166         {
1167                 m_walker.pre( node );
1168         }
1169         return true;
1170 }
1171
1172 void post( scene::Node& node ) const {
1173         if ( m_skip ) {
1174                 m_skip = false;
1175         }
1176         else
1177         {
1178                 m_walker.post( node );
1179         }
1180 }
1181 };
1182
1183 class AnyInstanceSelected : public scene::Instantiable::Visitor
1184 {
1185 bool& m_selected;
1186 public:
1187 AnyInstanceSelected( bool& selected ) : m_selected( selected ){
1188         m_selected = false;
1189 }
1190
1191 void visit( scene::Instance& instance ) const {
1192         Selectable* selectable = Instance_getSelectable( instance );
1193         if ( selectable != 0
1194                  && selectable->isSelected() ) {
1195                 m_selected = true;
1196         }
1197 }
1198 };
1199
1200 bool Node_instanceSelected( scene::Node& node ){
1201         scene::Instantiable* instantiable = Node_getInstantiable( node );
1202         ASSERT_NOTNULL( instantiable );
1203         bool selected;
1204         instantiable->forEachInstance( AnyInstanceSelected( selected ) );
1205         return selected;
1206 }
1207
1208 class SelectedDescendantWalker : public scene::Traversable::Walker
1209 {
1210 bool& m_selected;
1211 public:
1212 SelectedDescendantWalker( bool& selected ) : m_selected( selected ){
1213         m_selected = false;
1214 }
1215
1216 bool pre( scene::Node& node ) const {
1217         if ( node.isRoot() ) {
1218                 return false;
1219         }
1220
1221         if ( Node_instanceSelected( node ) ) {
1222                 m_selected = true;
1223         }
1224
1225         return true;
1226 }
1227 };
1228
1229 bool Node_selectedDescendant( scene::Node& node ){
1230         bool selected;
1231         Node_traverseSubgraph( node, SelectedDescendantWalker( selected ) );
1232         return selected;
1233 }
1234
1235 class SelectionExcluder : public Excluder
1236 {
1237 public:
1238 bool excluded( scene::Node& node ) const {
1239         return !Node_selectedDescendant( node );
1240 }
1241 };
1242
1243 class IncludeSelectedWalker : public scene::Traversable::Walker
1244 {
1245 const scene::Traversable::Walker& m_walker;
1246 mutable std::size_t m_selected;
1247 mutable bool m_skip;
1248
1249 bool selectedParent() const {
1250         return m_selected != 0;
1251 }
1252
1253 public:
1254 IncludeSelectedWalker( const scene::Traversable::Walker& walker )
1255         : m_walker( walker ), m_selected( 0 ), m_skip( false ){
1256 }
1257
1258 bool pre( scene::Node& node ) const {
1259         // include node if:
1260         // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
1261         if ( !node.isRoot() && ( Node_selectedDescendant( node ) || selectedParent() ) ) {
1262                 if ( Node_instanceSelected( node ) ) {
1263                         ++m_selected;
1264                 }
1265                 m_walker.pre( node );
1266                 return true;
1267         }
1268         else
1269         {
1270                 m_skip = true;
1271                 return false;
1272         }
1273 }
1274
1275 void post( scene::Node& node ) const {
1276         if ( m_skip ) {
1277                 m_skip = false;
1278         }
1279         else
1280         {
1281                 if ( Node_instanceSelected( node ) ) {
1282                         --m_selected;
1283                 }
1284                 m_walker.post( node );
1285         }
1286 }
1287 };
1288
1289 void Map_Traverse_Selected( scene::Node& root, const scene::Traversable::Walker& walker ){
1290         scene::Traversable* traversable = Node_getTraversable( root );
1291         if ( traversable != 0 ) {
1292 #if 0
1293                 traversable->traverse( ExcludeWalker( walker, SelectionExcluder() ) );
1294 #else
1295                 traversable->traverse( IncludeSelectedWalker( walker ) );
1296 #endif
1297         }
1298 }
1299
1300 void Map_ExportSelected( TextOutputStream& out, const MapFormat& format ){
1301         format.writeGraph( GlobalSceneGraph().root(), Map_Traverse_Selected, out, g_writeMapComments );
1302 }
1303
1304 void Map_Traverse( scene::Node& root, const scene::Traversable::Walker& walker ){
1305         scene::Traversable* traversable = Node_getTraversable( root );
1306         if ( traversable != 0 ) {
1307                 traversable->traverse( walker );
1308         }
1309 }
1310
1311 class RegionExcluder : public Excluder
1312 {
1313 public:
1314 bool excluded( scene::Node& node ) const {
1315         return node.excluded();
1316 }
1317 };
1318
1319 void Map_Traverse_Region( scene::Node& root, const scene::Traversable::Walker& walker ){
1320         scene::Traversable* traversable = Node_getTraversable( root );
1321         if ( traversable != 0 ) {
1322                 traversable->traverse( ExcludeWalker( walker, RegionExcluder() ) );
1323         }
1324 }
1325
1326 bool Map_SaveRegion( const char *filename ){
1327         AddRegionBrushes();
1328
1329         bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Region, filename );
1330
1331         RemoveRegionBrushes();
1332
1333         return success;
1334 }
1335
1336
1337 void Map_RenameAbsolute( const char* absolute ){
1338         Resource* resource = GlobalReferenceCache().capture( absolute );
1339         NodeSmartReference clone( NewMapRoot( path_make_relative( absolute, GlobalFileSystem().findRoot( absolute ) ) ) );
1340         resource->setNode( clone.get_pointer() );
1341
1342         {
1343                 //ScopeTimer timer("clone subgraph");
1344                 Node_getTraversable( GlobalSceneGraph().root() )->traverse( CloneAll( clone ) );
1345         }
1346
1347         g_map.m_resource->detach( g_map );
1348         GlobalReferenceCache().release( g_map.m_name.c_str() );
1349
1350         g_map.m_resource = resource;
1351
1352         g_map.m_name = absolute;
1353         Map_UpdateTitle( g_map );
1354
1355         g_map.m_resource->attach( g_map );
1356         // refresh VFS to apply new pak filtering based on mapname
1357         // needed for daemon DPK VFS
1358         VFS_Refresh();
1359 }
1360
1361 void Map_Rename( const char* filename ){
1362         if ( !string_equal( g_map.m_name.c_str(), filename ) ) {
1363                 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1364
1365                 Map_RenameAbsolute( filename );
1366
1367                 SceneChangeNotify();
1368         }
1369         else
1370         {
1371                 SaveReferences();
1372         }
1373 }
1374
1375 bool Map_Save(){
1376         Pointfile_Clear();
1377
1378         ScopeTimer timer( "map save" );
1379         SaveReferences();
1380         return true; // assume success..
1381 }
1382
1383 /*
1384    ===========
1385    Map_New
1386
1387    ===========
1388  */
1389 void Map_New(){
1390         //globalOutputStream() << "Map_New\n";
1391
1392         g_map.m_name = "unnamed.map";
1393         Map_UpdateTitle( g_map );
1394
1395         {
1396                 g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
1397 //    ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
1398                 g_map.m_resource->attach( g_map );
1399
1400                 SceneChangeNotify();
1401         }
1402
1403         FocusViews( g_vector3_identity, 0 );
1404
1405         g_currentMap = &g_map;
1406
1407         // restart VFS to apply new pak filtering based on mapname
1408         // needed for daemon DPK VFS
1409         VFS_Restart();
1410 }
1411
1412 extern void ConstructRegionBrushes( scene::Node * brushes[6], const Vector3 &region_mins, const Vector3 &region_maxs );
1413
1414 void ConstructRegionStartpoint( scene::Node* startpoint, const Vector3& region_mins, const Vector3& region_maxs ){
1415         /*!
1416            \todo we need to make sure that the player start IS inside the region and bail out if it's not
1417            the compiler will refuse to compile a map with a player_start somewhere in empty space..
1418            for now, let's just print an error
1419          */
1420
1421         Vector3 vOrig( Camera_getOrigin( *g_pParentWnd->GetCamWnd() ) );
1422
1423         for ( int i = 0 ; i < 3 ; i++ )
1424         {
1425                 if ( vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i] ) {
1426                         globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
1427                         break;
1428                 }
1429         }
1430
1431         // write the info_playerstart
1432         char sTmp[1024];
1433         sprintf( sTmp, "%d %d %d", (int)vOrig[0], (int)vOrig[1], (int)vOrig[2] );
1434         Node_getEntity( *startpoint )->setKeyValue( "origin", sTmp );
1435         sprintf( sTmp, "%d", (int)Camera_getAngles( *g_pParentWnd->GetCamWnd() )[CAMERA_YAW] );
1436         Node_getEntity( *startpoint )->setKeyValue( "angle", sTmp );
1437 }
1438
1439 /*
1440    ===========================================================
1441
1442    REGION
1443
1444    ===========================================================
1445  */
1446 bool region_active = false;
1447
1448 ConstReferenceCaller<bool, void(const Callback<void(bool)> &), PropertyImpl<bool>::Export> g_region_caller( region_active );
1449
1450 ToggleItem g_region_item( g_region_caller );
1451
1452 /*void Map_ToggleRegion(){
1453         region_active = !region_active;
1454         g_region_item.update();
1455 }*/
1456
1457 Vector3 region_mins( g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord );
1458 Vector3 region_maxs( g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord );
1459
1460 scene::Node* region_sides[6];
1461 scene::Node* region_startpoint = 0;
1462
1463 /*
1464    ===========
1465    AddRegionBrushes
1466    a regioned map will have temp walls put up at the region boundary
1467    \todo TODO TTimo old implementation of region brushes
1468    we still add them straight in the worldspawn and take them out after the map is saved
1469    with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
1470    ===========
1471  */
1472 void AddRegionBrushes( void ){
1473         int i;
1474
1475         for ( i = 0; i < 6; i++ )
1476         {
1477                 region_sides[i] = &GlobalBrushCreator().createBrush();
1478                 Node_getTraversable( Map_FindOrInsertWorldspawn( g_map ) )->insert( NodeSmartReference( *region_sides[i] ) );
1479         }
1480
1481         region_startpoint = &GlobalEntityCreator().createEntity( GlobalEntityClassManager().findOrInsert( "info_player_start", false ) );
1482
1483         ConstructRegionBrushes( region_sides, region_mins, region_maxs );
1484         ConstructRegionStartpoint( region_startpoint, region_mins, region_maxs );
1485
1486         Node_getTraversable( GlobalSceneGraph().root() )->insert( NodeSmartReference( *region_startpoint ) );
1487 }
1488
1489 void RemoveRegionBrushes( void ){
1490         for ( std::size_t i = 0; i < 6; i++ )
1491         {
1492                 Node_getTraversable( *Map_GetWorldspawn( g_map ) )->erase( *region_sides[i] );
1493         }
1494         Node_getTraversable( GlobalSceneGraph().root() )->erase( *region_startpoint );
1495 }
1496
1497 inline void exclude_node( scene::Node& node, bool exclude ){
1498         exclude
1499         ? node.enable( scene::Node::eExcluded )
1500         : node.disable( scene::Node::eExcluded );
1501 }
1502
1503 class ExcludeAllWalker : public scene::Graph::Walker
1504 {
1505 bool m_exclude;
1506 public:
1507 ExcludeAllWalker( bool exclude )
1508         : m_exclude( exclude ){
1509 }
1510
1511 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1512         exclude_node( path.top(), m_exclude );
1513
1514         return true;
1515 }
1516 };
1517
1518 void Scene_Exclude_All( bool exclude ){
1519         GlobalSceneGraph().traverse( ExcludeAllWalker( exclude ) );
1520 }
1521
1522 bool Instance_isSelected( const scene::Instance& instance ){
1523         const Selectable* selectable = Instance_getSelectable( instance );
1524         return selectable != 0 && selectable->isSelected();
1525 }
1526
1527 class ExcludeSelectedWalker : public scene::Graph::Walker
1528 {
1529 bool m_exclude;
1530 public:
1531 ExcludeSelectedWalker( bool exclude )
1532         : m_exclude( exclude ){
1533 }
1534
1535 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1536         exclude_node( path.top(), ( instance.isSelected() || instance.childSelected() || instance.parentSelected() ) == m_exclude );
1537         return true;
1538 }
1539 };
1540
1541 void Scene_Exclude_Selected( bool exclude ){
1542         GlobalSceneGraph().traverse( ExcludeSelectedWalker( exclude ) );
1543 }
1544
1545 class ExcludeRegionedWalker : public scene::Graph::Walker
1546 {
1547 bool m_exclude;
1548 public:
1549 ExcludeRegionedWalker( bool exclude )
1550         : m_exclude( exclude ){
1551 }
1552
1553 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1554         exclude_node(
1555                 path.top(),
1556                 !(
1557                         (
1558                                 aabb_intersects_aabb(
1559                                         instance.worldAABB(),
1560                                         aabb_for_minmax( region_mins, region_maxs )
1561                                         ) != 0
1562                         ) ^ m_exclude
1563                         )
1564                 );
1565
1566         return true;
1567 }
1568 };
1569
1570 void Scene_Exclude_Region( bool exclude ){
1571         GlobalSceneGraph().traverse( ExcludeRegionedWalker( exclude ) );
1572 }
1573
1574 /*
1575    ===========
1576    Map_RegionOff
1577
1578    Other filtering options may still be on
1579    ===========
1580  */
1581 void Map_RegionOff(){
1582         region_active = false;
1583         g_region_item.update();
1584
1585         region_maxs[0] = g_MaxWorldCoord - 64;
1586         region_mins[0] = g_MinWorldCoord + 64;
1587         region_maxs[1] = g_MaxWorldCoord - 64;
1588         region_mins[1] = g_MinWorldCoord + 64;
1589         region_maxs[2] = g_MaxWorldCoord - 64;
1590         region_mins[2] = g_MinWorldCoord + 64;
1591
1592         Scene_Exclude_All( false );
1593 }
1594
1595 void Map_ApplyRegion( void ){
1596         region_active = true;
1597         g_region_item.update();
1598
1599         Scene_Exclude_Region( false );
1600 }
1601
1602
1603 /*
1604    ========================
1605    Map_RegionSelectedBrushes
1606    ========================
1607  */
1608 void Map_RegionSelectedBrushes( void ){
1609         Map_RegionOff();
1610
1611         if ( GlobalSelectionSystem().countSelected() != 0
1612                  && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
1613                 region_active = true;
1614                 g_region_item.update();
1615                 Select_GetBounds( region_mins, region_maxs );
1616
1617                 Scene_Exclude_Selected( false );
1618
1619                 GlobalSelectionSystem().setSelectedAll( false );
1620         }
1621 }
1622
1623
1624 /*
1625    ===========
1626    Map_RegionXY
1627    ===========
1628  */
1629 void Map_RegionXY( float x_min, float y_min, float x_max, float y_max ){
1630         Map_RegionOff();
1631
1632         region_mins[0] = x_min;
1633         region_maxs[0] = x_max;
1634         region_mins[1] = y_min;
1635         region_maxs[1] = y_max;
1636         region_mins[2] = g_MinWorldCoord + 64;
1637         region_maxs[2] = g_MaxWorldCoord - 64;
1638
1639         Map_ApplyRegion();
1640 }
1641
1642 void Map_RegionBounds( const AABB& bounds ){
1643         Map_RegionOff();
1644
1645         region_mins = vector3_subtracted( bounds.origin, bounds.extents );
1646         region_maxs = vector3_added( bounds.origin, bounds.extents );
1647
1648         deleteSelection();
1649
1650         Map_ApplyRegion();
1651 }
1652
1653 /*
1654    ===========
1655    Map_RegionBrush
1656    ===========
1657  */
1658 void Map_RegionBrush( void ){
1659         if ( GlobalSelectionSystem().countSelected() != 0 ) {
1660                 scene::Instance& instance = GlobalSelectionSystem().ultimateSelected();
1661                 Map_RegionBounds( instance.worldAABB() );
1662         }
1663 }
1664
1665 //
1666 //================
1667 //Map_ImportFile
1668 //================
1669 //
1670 bool Map_ImportFile( const char* filename ){
1671         ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
1672
1673         g_strLastMapFolder = g_path_get_dirname( filename );
1674
1675         bool success = false;
1676
1677         if ( extension_equal( path_get_extension( filename ), "bsp" ) ) {
1678                 goto tryDecompile;
1679         }
1680
1681         {
1682                 const MapFormat* format = NULL;
1683                 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
1684                 if ( string_not_empty( moduleName ) ) {
1685                         format = ReferenceAPI_getMapModules().findModule( moduleName );
1686                 }
1687
1688                 if ( format ) {
1689                         format->wrongFormat = false;
1690                 }
1691                 Resource* resource = GlobalReferenceCache().capture( filename );
1692                 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1693                 if ( !resource->load() ) {
1694                         GlobalReferenceCache().release( filename );
1695                         goto tryDecompile;
1696                 }
1697                 if ( format ) {
1698                         if ( format->wrongFormat ) {
1699                                 GlobalReferenceCache().release( filename );
1700                                 goto tryDecompile;
1701                         }
1702                 }
1703                 NodeSmartReference clone( NewMapRoot( "" ) );
1704                 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1705                 Map_gatherNamespaced( clone );
1706                 Map_mergeClonedNames();
1707                 MergeMap( clone );
1708                 success = true;
1709                 GlobalReferenceCache().release( filename );
1710         }
1711
1712         SceneChangeNotify();
1713
1714         return success;
1715
1716 tryDecompile:
1717
1718         const char *type = GlobalRadiant().getGameDescriptionKeyValue( "q3map2_type" );
1719         int n = string_length( path_get_extension( filename ) );
1720         if ( n && ( extension_equal( path_get_extension( filename ), "bsp" ) || extension_equal( path_get_extension( filename ), "map" ) ) ) {
1721                 std::string output;
1722                 output += AppPath_get();
1723                 output += "q3map2";
1724                 output += GDEF_OS_EXE_EXT;
1725
1726                 output += " -v -game ";
1727                 output += ( type && *type ) ? type : "quake3";
1728                 output += " -fs_basepath \"";
1729                 output += EnginePath_get();
1730                 output += "\" -fs_homepath \"";
1731                 output += g_qeglobals.m_userEnginePath.c_str();
1732                 output += "\"";
1733
1734                 // extra pakpaths
1735                 for ( int i = 0; i < g_pakPathCount; i++ ) {
1736                         if ( g_strcmp0( g_strPakPath[i].c_str(), "") ) {
1737                                 output += " -fs_pakpath \"";
1738                                 output += g_strPakPath[i].c_str();
1739                                 output += "\"";
1740                         }
1741                 }
1742
1743                 // extra switches
1744                 if ( g_disableEnginePath ) {
1745                         output += " -fs_nobasepath ";
1746                 }
1747
1748                 if ( g_disableHomePath ) {
1749                         output += " -fs_nohomepath ";
1750                 }
1751
1752                 output += " -fs_game ";
1753                 output += gamename_get();
1754                 output += " -convert -format ";
1755                 output += Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map";
1756                 if ( extension_equal( path_get_extension( filename ), "map" ) ) {
1757                         output += " -readmap ";
1758                 }
1759                 output += " \"";
1760                 output += filename;
1761                 output += "\"";
1762
1763                 // run
1764                 Q_Exec( NULL, output.c_str(), NULL, false, true );
1765
1766                 // rebuild filename as "filenamewithoutext_converted.map"
1767                 output = "";
1768                 output.append( filename, string_length( filename ) - ( n + 1 ) );
1769                 output += "_converted.map";
1770                 filename = output.c_str();
1771
1772                 // open
1773                 Resource* resource = GlobalReferenceCache().capture( filename );
1774                 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1775                 if ( !resource->load() ) {
1776                         GlobalReferenceCache().release( filename );
1777                         goto tryDecompile;
1778                 }
1779                 NodeSmartReference clone( NewMapRoot( "" ) );
1780                 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1781                 Map_gatherNamespaced( clone );
1782                 Map_mergeClonedNames();
1783                 MergeMap( clone );
1784                 success = true;
1785                 GlobalReferenceCache().release( filename );
1786         }
1787
1788         SceneChangeNotify();
1789         return success;
1790 }
1791
1792 /*
1793    ===========
1794    Map_SaveFile
1795    ===========
1796  */
1797 bool Map_SaveFile( const char* filename ){
1798         ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1799         bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse, filename );
1800         if ( success ) {
1801                 // refresh VFS to apply new pak filtering based on mapname
1802                 // needed for daemon DPK VFS
1803                 VFS_Refresh();
1804         }
1805         return success;
1806 }
1807
1808 //
1809 //===========
1810 //Map_SaveSelected
1811 //===========
1812 //
1813 // Saves selected world brushes and whole entities with partial/full selections
1814 //
1815 bool Map_SaveSelected( const char* filename ){
1816         return MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Selected, filename );
1817 }
1818
1819 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker
1820 {
1821         scene::Node& m_parent;
1822         mutable bool m_emptyOldParent;
1823
1824 public:
1825 ParentSelectedBrushesToEntityWalker( scene::Node& parent ) : m_parent( parent ), m_emptyOldParent( false ){
1826 }
1827
1828 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1829         if ( path.top().get_pointer() != &m_parent && ( Node_isPrimitive( path.top() ) || m_emptyOldParent ) ) {
1830                 Selectable* selectable = Instance_getSelectable( instance );
1831                 if ( selectable && selectable->isSelected() && path.size() > 1 ) {
1832                         return false;
1833                 }
1834         }
1835         return true;
1836 }
1837
1838 void post( const scene::Path& path, scene::Instance& instance ) const {
1839         if ( path.top().get_pointer() == &m_parent )
1840                 return;
1841
1842         if ( Node_isPrimitive( path.top() ) ){
1843                 m_emptyOldParent = false;
1844                 Selectable* selectable = Instance_getSelectable( instance );
1845
1846                 if ( selectable && selectable->isSelected() && path.size() > 1 ){
1847                         scene::Node& parent = path.parent();
1848                         if ( &parent != &m_parent ){
1849                                 NodeSmartReference node( path.top().get() );
1850                                 scene::Traversable* traversable_parent = Node_getTraversable( parent );
1851                                 traversable_parent->erase( node );
1852                                 Node_getTraversable( m_parent )->insert( node );
1853                                 if ( traversable_parent->empty() )
1854                                         m_emptyOldParent = true;
1855                         }
1856                 }
1857         }
1858         else if ( m_emptyOldParent ){
1859                 m_emptyOldParent = false;
1860                 // delete empty entities
1861                 Entity* entity = Node_getEntity( path.top() );
1862                 if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map )     && Node_getTraversable( path.top() )->empty() ) {
1863                         Path_deleteTop( path );
1864                 }
1865         }
1866 }
1867 };
1868
1869 void Scene_parentSelectedBrushesToEntity( scene::Graph& graph, scene::Node& parent ){
1870         graph.traverse( ParentSelectedBrushesToEntityWalker( parent ) );
1871 }
1872
1873 class CountSelectedBrushes : public scene::Graph::Walker
1874 {
1875 std::size_t& m_count;
1876 mutable std::size_t m_depth;
1877 public:
1878 CountSelectedBrushes( std::size_t& count ) : m_count( count ), m_depth( 0 ){
1879         m_count = 0;
1880 }
1881
1882 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1883         if ( ++m_depth != 1 && path.top().get().isRoot() ) {
1884                 return false;
1885         }
1886         Selectable* selectable = Instance_getSelectable( instance );
1887         if ( selectable != 0
1888                  && selectable->isSelected()
1889                  && Node_isPrimitive( path.top() ) ) {
1890                 ++m_count;
1891         }
1892         return true;
1893 }
1894
1895 void post( const scene::Path& path, scene::Instance& instance ) const {
1896         --m_depth;
1897 }
1898 };
1899
1900 std::size_t Scene_countSelectedBrushes( scene::Graph& graph ){
1901         std::size_t count;
1902         graph.traverse( CountSelectedBrushes( count ) );
1903         return count;
1904 }
1905
1906 enum ENodeType
1907 {
1908         eNodeUnknown,
1909         eNodeMap,
1910         eNodeEntity,
1911         eNodePrimitive,
1912 };
1913
1914 const char* nodetype_get_name( ENodeType type ){
1915         if ( type == eNodeMap ) {
1916                 return "map";
1917         }
1918         if ( type == eNodeEntity ) {
1919                 return "entity";
1920         }
1921         if ( type == eNodePrimitive ) {
1922                 return "primitive";
1923         }
1924         return "unknown";
1925 }
1926
1927 ENodeType node_get_nodetype( scene::Node& node ){
1928         if ( Node_isEntity( node ) ) {
1929                 return eNodeEntity;
1930         }
1931         if ( Node_isPrimitive( node ) ) {
1932                 return eNodePrimitive;
1933         }
1934         return eNodeUnknown;
1935 }
1936
1937 bool contains_entity( scene::Node& node ){
1938         return Node_getTraversable( node ) != 0 && !Node_isBrush( node ) && !Node_isPatch( node ) && !Node_isEntity( node );
1939 }
1940
1941 bool contains_primitive( scene::Node& node ){
1942         return Node_isEntity( node ) && Node_getTraversable( node ) != 0 && Node_getEntity( node )->isContainer();
1943 }
1944
1945 ENodeType node_get_contains( scene::Node& node ){
1946         if ( contains_entity( node ) ) {
1947                 return eNodeEntity;
1948         }
1949         if ( contains_primitive( node ) ) {
1950                 return eNodePrimitive;
1951         }
1952         return eNodeUnknown;
1953 }
1954
1955 void Path_parent( const scene::Path& parent, const scene::Path& child ){
1956         ENodeType contains = node_get_contains( parent.top() );
1957         ENodeType type = node_get_nodetype( child.top() );
1958
1959         if ( contains != eNodeUnknown && contains == type ) {
1960                 NodeSmartReference node( child.top().get() );
1961                 Path_deleteTop( child );
1962                 Node_getTraversable( parent.top() )->insert( node );
1963                 SceneChangeNotify();
1964         }
1965         else
1966         {
1967                 globalErrorStream() << "failed - " << nodetype_get_name( type ) << " cannot be parented to " << nodetype_get_name( contains ) << " container.\n";
1968         }
1969 }
1970
1971 void Scene_parentSelected(){
1972         UndoableCommand undo( "parentSelected" );
1973
1974         if ( GlobalSelectionSystem().countSelected() > 1 ) {
1975                 class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor
1976                 {
1977                 const scene::Path& m_parent;
1978 public:
1979                 ParentSelectedBrushesToEntityWalker( const scene::Path& parent ) : m_parent( parent ){
1980                 }
1981
1982                 void visit( scene::Instance& instance ) const {
1983                         if ( &m_parent != &instance.path() ) {
1984                                 Path_parent( m_parent, instance.path() );
1985                         }
1986                 }
1987                 };
1988
1989                 ParentSelectedBrushesToEntityWalker visitor( GlobalSelectionSystem().ultimateSelected().path() );
1990                 GlobalSelectionSystem().foreachSelected( visitor );
1991         }
1992         else
1993         {
1994                 globalOutputStream() << "failed - did not find two selected nodes.\n";
1995         }
1996 }
1997
1998
1999 void NewMap(){
2000         if ( ConfirmModified( "New Map" ) ) {
2001                 Map_RegionOff();
2002                 Map_Free();
2003                 Map_New();
2004         }
2005 }
2006
2007 CopiedString g_mapsPath;
2008
2009 const char* getMapsPath(){
2010         return g_mapsPath.c_str();
2011 }
2012
2013 const char* getLastMapFolderPath(){
2014         if (g_strLastMapFolder.empty()) {
2015                 GlobalPreferenceSystem().registerPreference( "LastMapFolder", make_property_string( g_strLastMapFolder ) );
2016                 if (g_strLastMapFolder.empty()) {
2017                         StringOutputStream buffer( 1024 );
2018                         buffer << getMapsPath();
2019                         if ( !file_readable( buffer.c_str() ) ) {
2020                                 buffer.clear();
2021                                 buffer << g_qeglobals.m_userGamePath.c_str() << "/";
2022                         }
2023                         g_strLastMapFolder = buffer.c_str();
2024                 }
2025         }
2026         return g_strLastMapFolder.c_str();
2027 }
2028
2029 const char* map_open( const char* title ){
2030         return MainFrame_getWindow().file_dialog( TRUE, title, getLastMapFolderPath(), MapFormat::Name(), true, false, false );
2031 }
2032
2033 const char* map_import( const char* title ){
2034         return MainFrame_getWindow().file_dialog( TRUE, title, getLastMapFolderPath(), MapFormat::Name(), false, true, false );
2035 }
2036
2037 const char* map_save( const char* title ){
2038         return MainFrame_getWindow().file_dialog( FALSE, title, getLastMapFolderPath(), MapFormat::Name(), false, false, true );
2039 }
2040
2041 void OpenMap(){
2042         if ( !ConfirmModified( "Open Map" ) ) {
2043                 return;
2044         }
2045
2046         const char* filename = map_open( "Open Map" );
2047
2048         if ( filename != NULL ) {
2049                 MRU_AddFile( filename );
2050                 Map_RegionOff();
2051                 Map_Free();
2052                 Map_LoadFile( filename );
2053         }
2054 }
2055
2056 void ImportMap(){
2057         const char* filename = map_import( "Import Map" );
2058
2059         if ( filename != NULL ) {
2060                 UndoableCommand undo( "mapImport" );
2061                 Map_ImportFile( filename );
2062         }
2063 }
2064
2065 bool Map_SaveAs(){
2066         const char* filename = map_save( "Save Map" );
2067
2068         if ( filename != NULL ) {
2069                 g_strLastMapFolder = g_path_get_dirname( filename );
2070                 MRU_AddFile( filename );
2071                 Map_Rename( filename );
2072                 return Map_Save();
2073         }
2074         return false;
2075 }
2076
2077 void SaveMapAs(){
2078         Map_SaveAs();
2079 }
2080
2081 void SaveMap(){
2082         if ( Map_Unnamed( g_map ) ) {
2083                 SaveMapAs();
2084         }
2085         else if ( Map_Modified( g_map ) ) {
2086                 Map_Save();
2087                 MRU_AddFile( g_map.m_name.c_str() );    //add on saving, but not opening via cmd line: spoils the list
2088         }
2089 }
2090
2091 void ExportMap(){
2092         const char* filename = map_save( "Export Selection" );
2093
2094         if ( filename != NULL ) {
2095                 g_strLastMapFolder = g_path_get_dirname( filename );
2096                 Map_SaveSelected( filename );
2097         }
2098 }
2099
2100 void SaveRegion(){
2101         const char* filename = map_save( "Export Region" );
2102
2103         if ( filename != NULL ) {
2104                 g_strLastMapFolder = g_path_get_dirname( filename );
2105                 Map_SaveRegion( filename );
2106         }
2107 }
2108
2109
2110 void RegionOff(){
2111         Map_RegionOff();
2112         SceneChangeNotify();
2113 }
2114
2115 void RegionXY(){
2116         Map_RegionXY(
2117                 g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
2118                 g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
2119                 g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
2120                 g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
2121                 );
2122         SceneChangeNotify();
2123 }
2124
2125 void RegionBrush(){
2126         Map_RegionBrush();
2127         SceneChangeNotify();
2128 }
2129
2130 void RegionSelected(){
2131         Map_RegionSelectedBrushes();
2132         SceneChangeNotify();
2133 }
2134
2135
2136
2137
2138
2139 class BrushFindByIndexWalker : public scene::Traversable::Walker
2140 {
2141 mutable std::size_t m_index;
2142 scene::Path& m_path;
2143 public:
2144 BrushFindByIndexWalker( std::size_t index, scene::Path& path )
2145         : m_index( index ), m_path( path ){
2146 }
2147
2148 bool pre( scene::Node& node ) const {
2149         if ( Node_isPrimitive( node ) && m_index-- == 0 ) {
2150                 m_path.push( makeReference( node ) );
2151         }
2152         return false;
2153 }
2154 };
2155
2156 class EntityFindByIndexWalker : public scene::Traversable::Walker
2157 {
2158 mutable std::size_t m_index;
2159 scene::Path& m_path;
2160 public:
2161 EntityFindByIndexWalker( std::size_t index, scene::Path& path )
2162         : m_index( index ), m_path( path ){
2163 }
2164
2165 bool pre( scene::Node& node ) const {
2166         if ( Node_isEntity( node ) && m_index-- == 0 ) {
2167                 m_path.push( makeReference( node ) );
2168         }
2169         return false;
2170 }
2171 };
2172
2173 void Scene_FindEntityBrush( std::size_t entity, std::size_t brush, scene::Path& path ){
2174         path.push( makeReference( GlobalSceneGraph().root() ) );
2175         {
2176                 Node_getTraversable( path.top() )->traverse( EntityFindByIndexWalker( entity, path ) );
2177         }
2178         if ( path.size() == 2 ) {
2179                 scene::Traversable* traversable = Node_getTraversable( path.top() );
2180                 if ( traversable != 0 ) {
2181                         traversable->traverse( BrushFindByIndexWalker( brush, path ) );
2182                 }
2183         }
2184 }
2185
2186 inline bool Node_hasChildren( scene::Node& node ){
2187         scene::Traversable* traversable = Node_getTraversable( node );
2188         return traversable != 0 && !traversable->empty();
2189 }
2190
2191 void SelectBrush( int entitynum, int brushnum ){
2192         scene::Path path;
2193         Scene_FindEntityBrush( entitynum, brushnum, path );
2194         if ( path.size() == 3 || ( path.size() == 2 && !Node_hasChildren( path.top() ) ) ) {
2195                 scene::Instance* instance = GlobalSceneGraph().find( path );
2196                 ASSERT_MESSAGE( instance != 0, "SelectBrush: path not found in scenegraph" );
2197                 Selectable* selectable = Instance_getSelectable( *instance );
2198                 ASSERT_MESSAGE( selectable != 0, "SelectBrush: path not selectable" );
2199                 selectable->setSelected( true );
2200                 g_pParentWnd->GetXYWnd()->PositionView( instance->worldAABB().origin );
2201         }
2202 }
2203
2204
2205 class BrushFindIndexWalker : public scene::Graph::Walker
2206 {
2207 mutable const scene::Node* m_node;
2208 std::size_t& m_count;
2209 public:
2210 BrushFindIndexWalker( const scene::Node& node, std::size_t& count )
2211         : m_node( &node ), m_count( count ){
2212 }
2213
2214 bool pre( const scene::Path& path, scene::Instance& instance ) const {
2215         if ( Node_isPrimitive( path.top() ) ) {
2216                 if ( m_node == path.top().get_pointer() ) {
2217                         m_node = 0;
2218                 }
2219                 if ( m_node ) {
2220                         ++m_count;
2221                 }
2222         }
2223         return true;
2224 }
2225 };
2226
2227 class EntityFindIndexWalker : public scene::Graph::Walker
2228 {
2229 mutable const scene::Node* m_node;
2230 std::size_t& m_count;
2231 public:
2232 EntityFindIndexWalker( const scene::Node& node, std::size_t& count )
2233         : m_node( &node ), m_count( count ){
2234 }
2235
2236 bool pre( const scene::Path& path, scene::Instance& instance ) const {
2237         if ( Node_isEntity( path.top() ) ) {
2238                 if ( m_node == path.top().get_pointer() ) {
2239                         m_node = 0;
2240                 }
2241                 if ( m_node ) {
2242                         ++m_count;
2243                 }
2244         }
2245         return true;
2246 }
2247 };
2248
2249 static void GetSelectionIndex( int *ent, int *brush ){
2250         std::size_t count_brush = 0;
2251         std::size_t count_entity = 0;
2252         if ( GlobalSelectionSystem().countSelected() != 0 ) {
2253                 const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
2254
2255                 GlobalSceneGraph().traverse( BrushFindIndexWalker( path.top(), count_brush ) );
2256                 GlobalSceneGraph().traverse( EntityFindIndexWalker( path.parent(), count_entity ) );
2257         }
2258         *brush = int(count_brush);
2259         *ent = int(count_entity);
2260 }
2261
2262 void DoFind(){
2263         ModalDialog dialog;
2264         ui::Entry entity{ui::null};
2265         ui::Entry brush{ui::null};
2266
2267         ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback ), &dialog );
2268
2269         auto accel = ui::AccelGroup(ui::New);
2270         window.add_accel_group( accel );
2271
2272         {
2273                 auto vbox = create_dialog_vbox( 4, 4 );
2274                 window.add(vbox);
2275                 {
2276                         auto table = create_dialog_table( 2, 2, 4, 4 );
2277                         vbox.pack_start( table, TRUE, TRUE, 0 );
2278                         {
2279                                 ui::Widget label = ui::Label( "Entity number" );
2280                                 label.show();
2281                 (table).attach(label, {0, 1, 0, 1}, {0, 0});
2282                         }
2283                         {
2284                                 ui::Widget label = ui::Label( "Brush number" );
2285                                 label.show();
2286                 (table).attach(label, {0, 1, 1, 2}, {0, 0});
2287                         }
2288                         {
2289                                 auto entry = ui::Entry(ui::New);
2290                                 entry.show();
2291                 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
2292                                 gtk_widget_grab_focus( entry  );
2293                                 entity = entry;
2294                         }
2295                         {
2296                                 auto entry = ui::Entry(ui::New);
2297                                 entry.show();
2298                 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
2299
2300                                 brush = entry;
2301                         }
2302                 }
2303                 {
2304                         auto hbox = create_dialog_hbox( 4 );
2305                         vbox.pack_start( hbox, TRUE, TRUE, 0 );
2306                         {
2307                                 auto button = create_dialog_button( "Find", G_CALLBACK( dialog_button_ok ), &dialog );
2308                                 hbox.pack_start( button, FALSE, FALSE, 0 );
2309                                 widget_make_default( button );
2310                                 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
2311                         }
2312                         {
2313                                 auto button = create_dialog_button( "Close", G_CALLBACK( dialog_button_cancel ), &dialog );
2314                                 hbox.pack_start( button, FALSE, FALSE, 0 );
2315                                 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
2316                         }
2317                 }
2318         }
2319
2320         // Initialize dialog
2321         char buf[16];
2322         int ent, br;
2323
2324         GetSelectionIndex( &ent, &br );
2325         sprintf( buf, "%i", ent );
2326         entity.text(buf);
2327         sprintf( buf, "%i", br );
2328         brush.text(buf);
2329
2330         if ( modal_dialog_show( window, dialog ) == eIDOK ) {
2331                 const char *entstr = gtk_entry_get_text( entity );
2332                 const char *brushstr = gtk_entry_get_text( brush );
2333                 SelectBrush( atoi( entstr ), atoi( brushstr ) );
2334         }
2335
2336     window.destroy();
2337 }
2338
2339 void Map_constructPreferences( PreferencesPage& page ){
2340         page.appendCheckBox( "", "Load last map at startup", g_bLoadLastMap );
2341         page.appendCheckBox( "", "Add entity and brush number comments on map write", g_writeMapComments );
2342 }
2343
2344
2345 class MapEntityClasses : public ModuleObserver
2346 {
2347 std::size_t m_unrealised;
2348 public:
2349 MapEntityClasses() : m_unrealised( 1 ){
2350 }
2351
2352 void realise(){
2353         if ( --m_unrealised == 0 ) {
2354                 if ( g_map.m_resource != 0 ) {
2355                         ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
2356                         g_map.m_resource->realise();
2357                 }
2358         }
2359 }
2360
2361 void unrealise(){
2362         if ( ++m_unrealised == 1 ) {
2363                 if ( g_map.m_resource != 0 ) {
2364                         g_map.m_resource->flush();
2365                         g_map.m_resource->unrealise();
2366                 }
2367         }
2368 }
2369 };
2370
2371 MapEntityClasses g_MapEntityClasses;
2372
2373
2374 class MapModuleObserver : public ModuleObserver
2375 {
2376 std::size_t m_unrealised;
2377 public:
2378 MapModuleObserver() : m_unrealised( 1 ){
2379 }
2380
2381 void realise(){
2382         if ( --m_unrealised == 0 ) {
2383                 ASSERT_MESSAGE( !string_empty( g_qeglobals.m_userGamePath.c_str() ), "maps_directory: user-game-path is empty" );
2384                 StringOutputStream buffer( 256 );
2385                 buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2386                 Q_mkdir( buffer.c_str() );
2387                 g_mapsPath = buffer.c_str();
2388         }
2389 }
2390
2391 void unrealise(){
2392         if ( ++m_unrealised == 1 ) {
2393                 g_mapsPath = "";
2394         }
2395 }
2396 };
2397
2398 MapModuleObserver g_MapModuleObserver;
2399
2400 CopiedString g_strLastMap;
2401 bool g_bLoadLastMap = false;
2402
2403 void Map_Construct(){
2404         GlobalCommands_insert( "RegionOff", makeCallbackF(RegionOff) );
2405         GlobalCommands_insert( "RegionSetXY", makeCallbackF(RegionXY) );
2406         GlobalCommands_insert( "RegionSetBrush", makeCallbackF(RegionBrush) );
2407         //GlobalCommands_insert( "RegionSetSelection", makeCallbackF(RegionSelected), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
2408         GlobalToggles_insert( "RegionSetSelection", makeCallbackF(RegionSelected), ToggleItem::AddCallbackCaller( g_region_item ), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
2409
2410         GlobalPreferenceSystem().registerPreference( "LastMap", make_property_string( g_strLastMap ) );
2411         GlobalPreferenceSystem().registerPreference( "LoadLastMap", make_property_string( g_bLoadLastMap ) );
2412         GlobalPreferenceSystem().registerPreference( "MapInfoDlg", make_property<WindowPosition_String>( g_posMapInfoWnd ) );
2413         GlobalPreferenceSystem().registerPreference( "WriteMapComments", make_property_string( g_writeMapComments ) );
2414
2415         PreferencesDialog_addSettingsPreferences( makeCallbackF(Map_constructPreferences) );
2416
2417         GlobalEntityClassManager().attach( g_MapEntityClasses );
2418         Radiant_attachHomePathsObserver( g_MapModuleObserver );
2419 }
2420
2421 void Map_Destroy(){
2422         Radiant_detachHomePathsObserver( g_MapModuleObserver );
2423         GlobalEntityClassManager().detach( g_MapEntityClasses );
2424 }