2 Copyright (C) 1999-2006 Id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
5 This file is part of GtkRadiant.
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.
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.
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
26 #include "debugging/debugging.h"
30 MapModules& ReferenceAPI_getMapModules();
32 #include "iselection.h"
36 #include "ireference.h"
37 #include "ifiletypes.h"
43 #include "ifilesystem.h"
44 #include "namespace.h"
45 #include "moduleobserver.h"
49 #include <gdk/gdkkeysyms.h>
50 #include "uilib/uilib.h"
53 #include "transformlib.h"
54 #include "selectionlib.h"
55 #include "instancelib.h"
56 #include "traverselib.h"
58 #include "eclasslib.h"
60 #include "stream/textfilestream.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"
69 #include "gtkutil/filechooser.h"
73 #include "filetypes.h"
75 #include "entityinspector.h"
78 #include "camwindow.h"
80 #include "mainframe.h"
81 #include "preferences.h"
82 #include "preferencesystem.h"
83 #include "referencecache.h"
87 #include "brushmodule.h"
90 bool g_writeMapComments = true;
99 //globalOutputStream() << "construct " << makeQuoted(c_str()) << "\n";
100 m_names.insert( name_read( c_str() ) );
106 //globalOutputStream() << "destroy " << makeQuoted(c_str()) << "\n";
107 m_names.erase( name_read( c_str() ) );
111 NameObserver& operator=( const NameObserver& other );
114 NameObserver( UniqueNames& names ) : m_names( names ){
117 NameObserver( const NameObserver& other ) : m_names( other.m_names ), m_name( other.m_name ){
126 return string_empty( c_str() );
129 const char* c_str() const {
130 return m_name.c_str();
133 void nameChanged( const char* name ){
139 typedef MemberCaller<NameObserver, void(const char*), &NameObserver::nameChanged> NameChangedCaller;
142 class BasicNamespace : public Namespace
144 typedef std::map<NameCallback, NameObserver> Names;
146 UniqueNames m_uniqueNames;
149 ASSERT_MESSAGE( m_names.empty(), "namespace: names still registered at shutdown" );
152 void attach( const NameCallback& setName, const NameCallbackCallback& attachObserver ){
153 std::pair<Names::iterator, bool> result = m_names.insert( Names::value_type( setName, m_uniqueNames ) );
154 ASSERT_MESSAGE( result.second, "cannot attach name" );
155 attachObserver( NameObserver::NameChangedCaller( ( *result.first ).second ) );
156 //globalOutputStream() << "attach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
159 void detach( const NameCallback& setName, const NameCallbackCallback& detachObserver ){
160 Names::iterator i = m_names.find( setName );
161 ASSERT_MESSAGE( i != m_names.end(), "cannot detach name" );
162 //globalOutputStream() << "detach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
163 detachObserver( NameObserver::NameChangedCaller( ( *i ).second ) );
167 void makeUnique( const char* name, const NameCallback& setName ) const {
169 name_write( buffer, m_uniqueNames.make_unique( name_read( name ) ) );
173 void mergeNames( const BasicNamespace& other ) const {
174 typedef std::list<NameCallback> SetNameCallbacks;
175 typedef std::map<CopiedString, SetNameCallbacks> NameGroups;
178 UniqueNames uniqueNames( other.m_uniqueNames );
180 for ( Names::const_iterator i = m_names.begin(); i != m_names.end(); ++i )
182 groups[( *i ).second.c_str()].push_back( ( *i ).first );
185 for ( NameGroups::iterator i = groups.begin(); i != groups.end(); ++i )
187 name_t uniqueName( uniqueNames.make_unique( name_read( ( *i ).first.c_str() ) ) );
188 uniqueNames.insert( uniqueName );
191 name_write( buffer, uniqueName );
193 //globalOutputStream() << "renaming " << makeQuoted((*i).first.c_str()) << " to " << makeQuoted(buffer) << "\n";
195 SetNameCallbacks& setNameCallbacks = ( *i ).second;
197 for ( SetNameCallbacks::const_iterator j = setNameCallbacks.begin(); j != setNameCallbacks.end(); ++j )
205 BasicNamespace g_defaultNamespace;
206 BasicNamespace g_cloneNamespace;
210 Namespace* m_namespace;
212 typedef Namespace Type;
214 STRING_CONSTANT( Name, "*" );
217 m_namespace = &g_defaultNamespace;
220 Namespace* getTable(){
225 typedef SingletonModule<NamespaceAPI> NamespaceModule;
226 typedef Static<NamespaceModule> StaticNamespaceModule;
227 StaticRegisterModule staticRegisterDefaultNamespace( StaticNamespaceModule::instance() );
230 std::list<Namespaced*> g_cloned;
232 inline Namespaced* Node_getNamespaced( scene::Node& node ){
233 return NodeTypeCast<Namespaced>::cast( node );
236 void Node_gatherNamespaced( scene::Node& node ){
237 Namespaced* namespaced = Node_getNamespaced( node );
238 if ( namespaced != 0 ) {
239 g_cloned.push_back( namespaced );
243 class GatherNamespaced : public scene::Traversable::Walker
246 bool pre( scene::Node& node ) const {
247 Node_gatherNamespaced( node );
252 void Map_gatherNamespaced( scene::Node& root ){
253 Node_traverseSubgraph( root, GatherNamespaced() );
256 void Map_mergeClonedNames(){
257 for ( std::list<Namespaced*>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i )
259 ( *i )->setNamespace( g_cloneNamespace );
261 g_cloneNamespace.mergeNames( g_defaultNamespace );
262 for ( std::list<Namespaced*>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i )
264 ( *i )->setNamespace( g_defaultNamespace );
278 void set( scene::Node* node ){
288 scene::Node* get() const {
294 void Map_SetValid( Map& map, bool valid );
296 void Map_UpdateTitle( const Map& map );
298 void Map_SetWorldspawn( Map& map, scene::Node* node );
301 class Map : public ModuleObserver
305 Resource* m_resource;
310 void ( *m_modified_changed )( const Map& );
312 Signal0 m_mapValidCallbacks;
314 WorldNode m_world_node; // "classname" "worldspawn" !
316 Map() : m_resource( 0 ), m_valid( false ), m_modified_changed( Map_UpdateTitle ){
320 if ( m_resource != 0 ) {
321 if ( Map_Unnamed( *this ) ) {
322 g_map.m_resource->setNode( NewMapRoot( "" ).get_pointer() );
323 MapFile* map = Node_getMapFile( *g_map.m_resource->getNode() );
333 GlobalSceneGraph().insert_root( *m_resource->getNode() );
337 Map_SetValid( g_map, true );
342 if ( m_resource != 0 ) {
343 Map_SetValid( g_map, false );
344 Map_SetWorldspawn( g_map, 0 );
347 GlobalUndoSystem().clear();
349 GlobalSceneGraph().erase_root();
355 Map* g_currentMap = 0;
357 void Map_addValidCallback( Map& map, const SignalHandler& handler ){
358 map.m_mapValidCallbacks.connectLast( handler );
361 bool Map_Valid( const Map& map ){
365 void Map_SetValid( Map& map, bool valid ){
367 map.m_mapValidCallbacks();
371 const char* Map_Name( const Map& map ){
372 return map.m_name.c_str();
375 bool Map_Unnamed( const Map& map ){
376 return string_equal( Map_Name( map ), "unnamed.map" );
379 inline const MapFormat& MapFormat_forFile( const char* filename ){
380 const char* moduleName = findModuleName( GetFileTypeRegistry(), MapFormat::Name(), path_get_extension( filename ) );
381 MapFormat* format = Radiant_getMapModules().findModule( moduleName );
382 ASSERT_MESSAGE( format != 0, "map format not found for file " << makeQuoted( filename ) );
386 const MapFormat& Map_getFormat( const Map& map ){
387 return MapFormat_forFile( Map_Name( map ) );
391 bool Map_Modified( const Map& map ){
392 return map.m_modified;
395 void Map_SetModified( Map& map, bool modified ){
396 if ( map.m_modified ^ modified ) {
397 map.m_modified = modified;
399 map.m_modified_changed( map );
403 void Map_UpdateTitle( const Map& map ){
404 Sys_SetTitle( map.m_name.c_str(), Map_Modified( map ) );
408 scene::Node* Map_GetWorldspawn( const Map& map ){
409 return map.m_world_node.get();
412 void Map_SetWorldspawn( Map& map, scene::Node* node ){
413 map.m_world_node.set( node );
418 // need that in a variable, will have to tweak depending on the game
419 float g_MaxWorldCoord = 64 * 1024;
420 float g_MinWorldCoord = -64 * 1024;
422 void AddRegionBrushes( void );
424 void RemoveRegionBrushes( void );
430 free all map elements, reinitialize the structures that depend on them
436 g_map.m_resource->detach( g_map );
437 GlobalReferenceCache().release( g_map.m_name.c_str() );
438 g_map.m_resource = 0;
443 Brush_unlatchPreferences();
446 class EntityFindByClassname : public scene::Graph::Walker
451 EntityFindByClassname( const char* name, Entity*& entity ) : m_name( name ), m_entity( entity ){
455 bool pre( const scene::Path& path, scene::Instance& instance ) const {
456 if ( m_entity == 0 ) {
457 Entity* entity = Node_getEntity( path.top() );
459 && string_equal( m_name, entity->getKeyValue( "classname" ) ) ) {
467 Entity* Scene_FindEntityByClass( const char* name ){
469 GlobalSceneGraph().traverse( EntityFindByClassname( name, entity ) );
473 Entity *Scene_FindPlayerStart(){
474 typedef const char* StaticString;
475 StaticString strings[] = {
477 "info_player_deathmatch",
478 "team_CTF_redplayer",
479 "team_CTF_blueplayer",
481 "team_CTF_bluespawn",
483 typedef const StaticString* StaticStringIterator;
484 for ( StaticStringIterator i = strings, end = strings + ( sizeof( strings ) / sizeof( StaticString ) ); i != end; ++i )
486 Entity* entity = Scene_FindEntityByClass( *i );
495 // move the view to a start position
499 void FocusViews( const Vector3& point, float angle ){
500 CamWnd& camwnd = *g_pParentWnd->GetCamWnd();
501 Camera_setOrigin( camwnd, point );
502 Vector3 angles( Camera_getAngles( camwnd ) );
503 angles[CAMERA_PITCH] = 0;
504 angles[CAMERA_YAW] = angle;
505 Camera_setAngles( camwnd, angles );
507 XYWnd* xywnd = g_pParentWnd->GetXYWnd();
508 xywnd->SetOrigin( point );
511 #include "stringio.h"
513 void Map_StartPosition(){
514 Entity* entity = Scene_FindPlayerStart();
518 string_parse_vector3( entity->getKeyValue( "origin" ), origin );
519 FocusViews( origin, string_read_float( entity->getKeyValue( "angle" ) ) );
523 FocusViews( g_vector3_identity, 0 );
528 inline bool node_is_worldspawn( scene::Node& node ){
529 Entity* entity = Node_getEntity( node );
530 return entity != 0 && string_equal( entity->getKeyValue( "classname" ), "worldspawn" );
534 // use first worldspawn
535 class entity_updateworldspawn : public scene::Traversable::Walker
538 bool pre( scene::Node& node ) const {
539 if ( node_is_worldspawn( node ) ) {
540 if ( Map_GetWorldspawn( g_map ) == 0 ) {
541 Map_SetWorldspawn( g_map, &node );
548 scene::Node* Map_FindWorldspawn( Map& map ){
549 Map_SetWorldspawn( map, 0 );
551 Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() );
553 return Map_GetWorldspawn( map );
557 class CollectAllWalker : public scene::Traversable::Walker
560 UnsortedNodeSet& m_nodes;
562 CollectAllWalker( scene::Node& root, UnsortedNodeSet& nodes ) : m_root( root ), m_nodes( nodes ){
565 bool pre( scene::Node& node ) const {
566 m_nodes.insert( NodeSmartReference( node ) );
567 Node_getTraversable( m_root )->erase( node );
572 void Node_insertChildFirst( scene::Node& parent, scene::Node& child ){
573 UnsortedNodeSet nodes;
574 Node_getTraversable( parent )->traverse( CollectAllWalker( parent, nodes ) );
575 Node_getTraversable( parent )->insert( child );
577 for ( UnsortedNodeSet::iterator i = nodes.begin(); i != nodes.end(); ++i )
579 Node_getTraversable( parent )->insert( ( *i ) );
583 scene::Node& createWorldspawn(){
584 NodeSmartReference worldspawn( GlobalEntityCreator().createEntity( GlobalEntityClassManager().findOrInsert( "worldspawn", true ) ) );
585 Node_insertChildFirst( GlobalSceneGraph().root(), worldspawn );
589 void Map_UpdateWorldspawn( Map& map ){
590 if ( Map_FindWorldspawn( map ) == 0 ) {
591 Map_SetWorldspawn( map, &createWorldspawn() );
595 scene::Node& Map_FindOrInsertWorldspawn( Map& map ){
596 Map_UpdateWorldspawn( map );
597 return *Map_GetWorldspawn( map );
601 class MapMergeAll : public scene::Traversable::Walker
603 mutable scene::Path m_path;
605 MapMergeAll( const scene::Path& root )
609 bool pre( scene::Node& node ) const {
610 Node_getTraversable( m_path.top() )->insert( node );
611 m_path.push( makeReference( node ) );
612 selectPath( m_path, true );
616 void post( scene::Node& node ) const {
621 class MapMergeEntities : public scene::Traversable::Walker
623 mutable scene::Path m_path;
625 MapMergeEntities( const scene::Path& root )
629 bool pre( scene::Node& node ) const {
630 if ( node_is_worldspawn( node ) ) {
631 scene::Node* world_node = Map_FindWorldspawn( g_map );
632 if ( world_node == 0 ) {
633 Map_SetWorldspawn( g_map, &node );
634 Node_getTraversable( m_path.top().get() )->insert( node );
635 m_path.push( makeReference( node ) );
636 Node_getTraversable( node )->traverse( SelectChildren( m_path ) );
640 m_path.push( makeReference( *world_node ) );
641 Node_getTraversable( node )->traverse( MapMergeAll( m_path ) );
646 Node_getTraversable( m_path.top() )->insert( node );
647 m_path.push( makeReference( node ) );
648 if ( node_is_group( node ) ) {
649 Node_getTraversable( node )->traverse( SelectChildren( m_path ) );
653 selectPath( m_path, true );
659 void post( scene::Node& node ) const {
664 class BasicContainer : public scene::Node::Symbiot
668 NodeTypeCastTable m_casts;
671 NodeContainedCast<BasicContainer, scene::Traversable>::install( m_casts );
674 NodeTypeCastTable& get(){
680 TraversableNodeSet m_traverse;
683 typedef LazyStatic<TypeCasts> StaticTypeCasts;
685 scene::Traversable& get( NullType<scene::Traversable>){
689 BasicContainer() : m_node( this, this, StaticTypeCasts::instance().get() ){
701 /// Merges the map graph rooted at \p node into the global scene-graph.
702 void MergeMap( scene::Node& node ){
703 Node_getTraversable( node )->traverse( MapMergeEntities( scene::Path( makeReference( GlobalSceneGraph().root() ) ) ) );
706 void Map_ImportSelected( TextInputStream& in, const MapFormat& format ){
707 NodeSmartReference node( ( new BasicContainer )->node() );
708 format.readGraph( node, in, GlobalEntityCreator() );
709 Map_gatherNamespaced( node );
710 Map_mergeClonedNames();
714 inline scene::Cloneable* Node_getCloneable( scene::Node& node ){
715 return NodeTypeCast<scene::Cloneable>::cast( node );
718 inline scene::Node& node_clone( scene::Node& node ){
719 scene::Cloneable* cloneable = Node_getCloneable( node );
720 if ( cloneable != 0 ) {
721 return cloneable->clone();
724 return ( new scene::NullNode )->node();
727 class CloneAll : public scene::Traversable::Walker
729 mutable scene::Path m_path;
731 CloneAll( scene::Node& root )
732 : m_path( makeReference( root ) ){
735 bool pre( scene::Node& node ) const {
736 if ( node.isRoot() ) {
740 m_path.push( makeReference( node_clone( node ) ) );
741 m_path.top().get().IncRef();
746 void post( scene::Node& node ) const {
747 if ( node.isRoot() ) {
751 Node_getTraversable( m_path.parent() )->insert( m_path.top() );
753 m_path.top().get().DecRef();
758 scene::Node& Node_Clone( scene::Node& node ){
759 scene::Node& clone = node_clone( node );
760 scene::Traversable* traversable = Node_getTraversable( node );
761 if ( traversable != 0 ) {
762 traversable->traverse( CloneAll( clone ) );
768 typedef std::map<CopiedString, std::size_t> EntityBreakdown;
770 class EntityBreakdownWalker : public scene::Graph::Walker
772 EntityBreakdown& m_entitymap;
774 EntityBreakdownWalker( EntityBreakdown& entitymap )
775 : m_entitymap( entitymap ){
778 bool pre( const scene::Path& path, scene::Instance& instance ) const {
779 Entity* entity = Node_getEntity( path.top() );
781 const EntityClass& eclass = entity->getEntityClass();
782 if ( m_entitymap.find( eclass.name() ) == m_entitymap.end() ) {
783 m_entitymap[eclass.name()] = 1;
786 ++m_entitymap[eclass.name()];
793 void Scene_EntityBreakdown( EntityBreakdown& entitymap ){
794 GlobalSceneGraph().traverse( EntityBreakdownWalker( entitymap ) );
798 WindowPosition g_posMapInfoWnd( c_default_window_pos );
802 ui::Entry brushes_entry{ui::null};
803 ui::Entry entities_entry{ui::null};
804 ui::ListStore EntityBreakdownWalker{ui::null};
806 ui::Window window = MainFrame_getWindow().create_dialog_window("Map Info", G_CALLBACK(dialog_delete_callback ), &dialog );
808 window_set_position( window, g_posMapInfoWnd );
811 auto vbox = create_dialog_vbox( 4, 4 );
815 auto hbox = create_dialog_hbox( 4 );
816 vbox.pack_start( hbox, FALSE, TRUE, 0 );
819 auto table = create_dialog_table( 2, 2, 4, 4 );
820 hbox.pack_start( table, TRUE, TRUE, 0 );
823 auto entry = ui::Entry(ui::New);
825 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
826 gtk_editable_set_editable( GTK_EDITABLE(entry), FALSE );
828 brushes_entry = entry;
831 auto entry = ui::Entry(ui::New);
833 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
834 gtk_editable_set_editable( GTK_EDITABLE(entry), FALSE );
836 entities_entry = entry;
839 ui::Widget label = ui::Label( "Total Brushes" );
841 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
842 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
845 ui::Widget label = ui::Label( "Total Entities" );
847 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
848 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
852 auto vbox2 = create_dialog_vbox( 4 );
853 hbox.pack_start( vbox2, FALSE, FALSE, 0 );
856 auto button = create_dialog_button( "Close", G_CALLBACK( dialog_button_ok ), &dialog );
857 vbox2.pack_start( button, FALSE, FALSE, 0 );
862 ui::Widget label = ui::Label( "Entity breakdown" );
864 vbox.pack_start( label, FALSE, TRUE, 0 );
865 gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
868 auto scr = create_scrolled_window( ui::Policy::NEVER, ui::Policy::AUTOMATIC, 4 );
869 vbox.pack_start( scr, TRUE, TRUE, 0 );
872 auto store = ui::ListStore::from(gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_STRING ));
874 auto view = ui::TreeView(ui::TreeModel::from(store._handle));
875 gtk_tree_view_set_headers_clickable(view, TRUE );
878 auto renderer = ui::CellRendererText(ui::New);
879 auto column = ui::TreeViewColumn( "Entity", renderer, {{"text", 0}} );
880 gtk_tree_view_append_column(view, column );
881 gtk_tree_view_column_set_sort_column_id( column, 0 );
885 auto renderer = ui::CellRendererText(ui::New);
886 auto column = ui::TreeViewColumn( "Count", renderer, {{"text", 1}} );
887 gtk_tree_view_append_column(view, column );
888 gtk_tree_view_column_set_sort_column_id( column, 1 );
895 EntityBreakdownWalker = store;
903 EntityBreakdown entitymap;
904 Scene_EntityBreakdown( entitymap );
906 for ( EntityBreakdown::iterator i = entitymap.begin(); i != entitymap.end(); ++i )
909 sprintf( tmp, "%u", Unsigned( ( *i ).second ) );
910 EntityBreakdownWalker.append(0, (*i).first.c_str(), 1, tmp);
914 EntityBreakdownWalker.unref();
917 sprintf( tmp, "%u", Unsigned( g_brushCount.get() ) );
918 brushes_entry.text(tmp);
919 sprintf( tmp, "%u", Unsigned( g_entityCount.get() ) );
920 entities_entry.text(tmp);
922 modal_dialog_show( window, dialog );
925 window_get_position( window, g_posMapInfoWnd );
935 const char* m_message;
937 ScopeTimer( const char* message )
938 : m_message( message ){
943 double elapsed_time = m_timer.elapsed_msec() / 1000.f;
944 globalOutputStream() << m_message << " timer: " << FloatFormat( elapsed_time, 5, 2 ) << " second(s) elapsed\n";
948 CopiedString g_strLastMapFolder = "";
956 void Map_LoadFile( const char *filename ){
957 g_map.m_name = filename;
959 // refresh VFS to apply new pak filtering based on mapname
960 // needed for daemon DPK VFS
963 globalOutputStream() << "Loading map from " << filename << "\n";
964 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
966 MRU_AddFile( filename );
967 g_strLastMapFolder = g_path_get_dirname( filename );
969 bool switch_format = false;
972 ScopeTimer timer( "map load" );
974 const MapFormat* format = NULL;
975 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
976 if ( string_not_empty( moduleName ) ) {
977 format = ReferenceAPI_getMapModules().findModule( moduleName );
980 for ( int i = 0; i < Brush_toggleFormatCount(); ++i )
985 Brush_toggleFormat( i );
986 Map_UpdateTitle( g_map );
988 g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
990 format->wrongFormat = false;
992 g_map.m_resource->attach( g_map );
994 if ( !format->wrongFormat ) {
997 switch_format = !switch_format;
1001 Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() );
1004 globalOutputStream() << "--- LoadMapFile ---\n";
1005 globalOutputStream() << g_map.m_name.c_str() << "\n";
1007 globalOutputStream() << Unsigned( g_brushCount.get() ) << " primitive\n";
1008 globalOutputStream() << Unsigned( g_entityCount.get() ) << " entities\n";
1010 //GlobalEntityCreator().printStatistics();
1013 // move the view to a start position
1015 Map_StartPosition();
1017 g_currentMap = &g_map;
1019 Brush_switchFormat( switch_format );
1025 virtual bool excluded( scene::Node& node ) const = 0;
1028 class ExcludeWalker : public scene::Traversable::Walker
1030 const scene::Traversable::Walker& m_walker;
1031 const Excluder* m_exclude;
1032 mutable bool m_skip;
1034 ExcludeWalker( const scene::Traversable::Walker& walker, const Excluder& exclude )
1035 : m_walker( walker ), m_exclude( &exclude ), m_skip( false ){
1038 bool pre( scene::Node& node ) const {
1039 if ( m_exclude->excluded( node ) || node.isRoot() ) {
1045 m_walker.pre( node );
1050 void post( scene::Node& node ) const {
1056 m_walker.post( node );
1061 class AnyInstanceSelected : public scene::Instantiable::Visitor
1065 AnyInstanceSelected( bool& selected ) : m_selected( selected ){
1069 void visit( scene::Instance& instance ) const {
1070 Selectable* selectable = Instance_getSelectable( instance );
1071 if ( selectable != 0
1072 && selectable->isSelected() ) {
1078 bool Node_instanceSelected( scene::Node& node ){
1079 scene::Instantiable* instantiable = Node_getInstantiable( node );
1080 ASSERT_NOTNULL( instantiable );
1082 instantiable->forEachInstance( AnyInstanceSelected( selected ) );
1086 class SelectedDescendantWalker : public scene::Traversable::Walker
1090 SelectedDescendantWalker( bool& selected ) : m_selected( selected ){
1094 bool pre( scene::Node& node ) const {
1095 if ( node.isRoot() ) {
1099 if ( Node_instanceSelected( node ) ) {
1107 bool Node_selectedDescendant( scene::Node& node ){
1109 Node_traverseSubgraph( node, SelectedDescendantWalker( selected ) );
1113 class SelectionExcluder : public Excluder
1116 bool excluded( scene::Node& node ) const {
1117 return !Node_selectedDescendant( node );
1121 class IncludeSelectedWalker : public scene::Traversable::Walker
1123 const scene::Traversable::Walker& m_walker;
1124 mutable std::size_t m_selected;
1125 mutable bool m_skip;
1127 bool selectedParent() const {
1128 return m_selected != 0;
1132 IncludeSelectedWalker( const scene::Traversable::Walker& walker )
1133 : m_walker( walker ), m_selected( 0 ), m_skip( false ){
1136 bool pre( scene::Node& node ) const {
1138 // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
1139 if ( !node.isRoot() && ( Node_selectedDescendant( node ) || selectedParent() ) ) {
1140 if ( Node_instanceSelected( node ) ) {
1143 m_walker.pre( node );
1153 void post( scene::Node& node ) const {
1159 if ( Node_instanceSelected( node ) ) {
1162 m_walker.post( node );
1167 void Map_Traverse_Selected( scene::Node& root, const scene::Traversable::Walker& walker ){
1168 scene::Traversable* traversable = Node_getTraversable( root );
1169 if ( traversable != 0 ) {
1171 traversable->traverse( ExcludeWalker( walker, SelectionExcluder() ) );
1173 traversable->traverse( IncludeSelectedWalker( walker ) );
1178 void Map_ExportSelected( TextOutputStream& out, const MapFormat& format ){
1179 format.writeGraph( GlobalSceneGraph().root(), Map_Traverse_Selected, out, g_writeMapComments );
1182 void Map_Traverse( scene::Node& root, const scene::Traversable::Walker& walker ){
1183 scene::Traversable* traversable = Node_getTraversable( root );
1184 if ( traversable != 0 ) {
1185 traversable->traverse( walker );
1189 class RegionExcluder : public Excluder
1192 bool excluded( scene::Node& node ) const {
1193 return node.excluded();
1197 void Map_Traverse_Region( scene::Node& root, const scene::Traversable::Walker& walker ){
1198 scene::Traversable* traversable = Node_getTraversable( root );
1199 if ( traversable != 0 ) {
1200 traversable->traverse( ExcludeWalker( walker, RegionExcluder() ) );
1204 bool Map_SaveRegion( const char *filename ){
1207 bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Region, filename );
1209 RemoveRegionBrushes();
1215 void Map_RenameAbsolute( const char* absolute ){
1216 Resource* resource = GlobalReferenceCache().capture( absolute );
1217 NodeSmartReference clone( NewMapRoot( path_make_relative( absolute, GlobalFileSystem().findRoot( absolute ) ) ) );
1218 resource->setNode( clone.get_pointer() );
1221 //ScopeTimer timer("clone subgraph");
1222 Node_getTraversable( GlobalSceneGraph().root() )->traverse( CloneAll( clone ) );
1225 g_map.m_resource->detach( g_map );
1226 GlobalReferenceCache().release( g_map.m_name.c_str() );
1228 g_map.m_resource = resource;
1230 g_map.m_name = absolute;
1231 Map_UpdateTitle( g_map );
1233 g_map.m_resource->attach( g_map );
1234 // refresh VFS to apply new pak filtering based on mapname
1235 // needed for daemon DPK VFS
1239 void Map_Rename( const char* filename ){
1240 if ( !string_equal( g_map.m_name.c_str(), filename ) ) {
1241 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1243 Map_RenameAbsolute( filename );
1245 SceneChangeNotify();
1256 ScopeTimer timer( "map save" );
1258 return true; // assume success..
1268 //globalOutputStream() << "Map_New\n";
1270 g_map.m_name = "unnamed.map";
1271 Map_UpdateTitle( g_map );
1274 g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
1275 // ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
1276 g_map.m_resource->attach( g_map );
1278 SceneChangeNotify();
1281 FocusViews( g_vector3_identity, 0 );
1283 g_currentMap = &g_map;
1285 // restart VFS to apply new pak filtering based on mapname
1286 // needed for daemon DPK VFS
1290 extern void ConstructRegionBrushes( scene::Node * brushes[6], const Vector3 ®ion_mins, const Vector3 ®ion_maxs );
1292 void ConstructRegionStartpoint( scene::Node* startpoint, const Vector3& region_mins, const Vector3& region_maxs ){
1294 \todo we need to make sure that the player start IS inside the region and bail out if it's not
1295 the compiler will refuse to compile a map with a player_start somewhere in empty space..
1296 for now, let's just print an error
1299 Vector3 vOrig( Camera_getOrigin( *g_pParentWnd->GetCamWnd() ) );
1301 for ( int i = 0 ; i < 3 ; i++ )
1303 if ( vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i] ) {
1304 globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
1309 // write the info_playerstart
1311 sprintf( sTmp, "%d %d %d", (int)vOrig[0], (int)vOrig[1], (int)vOrig[2] );
1312 Node_getEntity( *startpoint )->setKeyValue( "origin", sTmp );
1313 sprintf( sTmp, "%d", (int)Camera_getAngles( *g_pParentWnd->GetCamWnd() )[CAMERA_YAW] );
1314 Node_getEntity( *startpoint )->setKeyValue( "angle", sTmp );
1318 ===========================================================
1322 ===========================================================
1324 bool region_active = false;
1326 ConstReferenceCaller<bool, void(const Callback<void(bool)> &), PropertyImpl<bool>::Export> g_region_caller( region_active );
1328 ToggleItem g_region_item( g_region_caller );
1330 /*void Map_ToggleRegion(){
1331 region_active = !region_active;
1332 g_region_item.update();
1335 Vector3 region_mins( g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord );
1336 Vector3 region_maxs( g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord );
1338 scene::Node* region_sides[6];
1339 scene::Node* region_startpoint = 0;
1344 a regioned map will have temp walls put up at the region boundary
1345 \todo TODO TTimo old implementation of region brushes
1346 we still add them straight in the worldspawn and take them out after the map is saved
1347 with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
1350 void AddRegionBrushes( void ){
1353 for ( i = 0; i < 6; i++ )
1355 region_sides[i] = &GlobalBrushCreator().createBrush();
1356 Node_getTraversable( Map_FindOrInsertWorldspawn( g_map ) )->insert( NodeSmartReference( *region_sides[i] ) );
1359 region_startpoint = &GlobalEntityCreator().createEntity( GlobalEntityClassManager().findOrInsert( "info_player_start", false ) );
1361 ConstructRegionBrushes( region_sides, region_mins, region_maxs );
1362 ConstructRegionStartpoint( region_startpoint, region_mins, region_maxs );
1364 Node_getTraversable( GlobalSceneGraph().root() )->insert( NodeSmartReference( *region_startpoint ) );
1367 void RemoveRegionBrushes( void ){
1368 for ( std::size_t i = 0; i < 6; i++ )
1370 Node_getTraversable( *Map_GetWorldspawn( g_map ) )->erase( *region_sides[i] );
1372 Node_getTraversable( GlobalSceneGraph().root() )->erase( *region_startpoint );
1375 inline void exclude_node( scene::Node& node, bool exclude ){
1377 ? node.enable( scene::Node::eExcluded )
1378 : node.disable( scene::Node::eExcluded );
1381 class ExcludeAllWalker : public scene::Graph::Walker
1385 ExcludeAllWalker( bool exclude )
1386 : m_exclude( exclude ){
1389 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1390 exclude_node( path.top(), m_exclude );
1396 void Scene_Exclude_All( bool exclude ){
1397 GlobalSceneGraph().traverse( ExcludeAllWalker( exclude ) );
1400 bool Instance_isSelected( const scene::Instance& instance ){
1401 const Selectable* selectable = Instance_getSelectable( instance );
1402 return selectable != 0 && selectable->isSelected();
1405 class ExcludeSelectedWalker : public scene::Graph::Walker
1409 ExcludeSelectedWalker( bool exclude )
1410 : m_exclude( exclude ){
1413 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1414 exclude_node( path.top(), ( instance.isSelected() || instance.childSelected() || instance.parentSelected() ) == m_exclude );
1419 void Scene_Exclude_Selected( bool exclude ){
1420 GlobalSceneGraph().traverse( ExcludeSelectedWalker( exclude ) );
1423 class ExcludeRegionedWalker : public scene::Graph::Walker
1427 ExcludeRegionedWalker( bool exclude )
1428 : m_exclude( exclude ){
1431 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1436 aabb_intersects_aabb(
1437 instance.worldAABB(),
1438 aabb_for_minmax( region_mins, region_maxs )
1448 void Scene_Exclude_Region( bool exclude ){
1449 GlobalSceneGraph().traverse( ExcludeRegionedWalker( exclude ) );
1456 Other filtering options may still be on
1459 void Map_RegionOff(){
1460 region_active = false;
1461 g_region_item.update();
1463 region_maxs[0] = g_MaxWorldCoord - 64;
1464 region_mins[0] = g_MinWorldCoord + 64;
1465 region_maxs[1] = g_MaxWorldCoord - 64;
1466 region_mins[1] = g_MinWorldCoord + 64;
1467 region_maxs[2] = g_MaxWorldCoord - 64;
1468 region_mins[2] = g_MinWorldCoord + 64;
1470 Scene_Exclude_All( false );
1473 void Map_ApplyRegion( void ){
1474 region_active = true;
1475 g_region_item.update();
1477 Scene_Exclude_Region( false );
1482 ========================
1483 Map_RegionSelectedBrushes
1484 ========================
1486 void Map_RegionSelectedBrushes( void ){
1489 if ( GlobalSelectionSystem().countSelected() != 0
1490 && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
1491 region_active = true;
1492 g_region_item.update();
1493 Select_GetBounds( region_mins, region_maxs );
1495 Scene_Exclude_Selected( false );
1497 GlobalSelectionSystem().setSelectedAll( false );
1507 void Map_RegionXY( float x_min, float y_min, float x_max, float y_max ){
1510 region_mins[0] = x_min;
1511 region_maxs[0] = x_max;
1512 region_mins[1] = y_min;
1513 region_maxs[1] = y_max;
1514 region_mins[2] = g_MinWorldCoord + 64;
1515 region_maxs[2] = g_MaxWorldCoord - 64;
1520 void Map_RegionBounds( const AABB& bounds ){
1523 region_mins = vector3_subtracted( bounds.origin, bounds.extents );
1524 region_maxs = vector3_added( bounds.origin, bounds.extents );
1536 void Map_RegionBrush( void ){
1537 if ( GlobalSelectionSystem().countSelected() != 0 ) {
1538 scene::Instance& instance = GlobalSelectionSystem().ultimateSelected();
1539 Map_RegionBounds( instance.worldAABB() );
1548 bool Map_ImportFile( const char* filename ){
1549 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
1551 g_strLastMapFolder = g_path_get_dirname( filename );
1553 bool success = false;
1555 if ( extension_equal( path_get_extension( filename ), "bsp" ) ) {
1560 const MapFormat* format = NULL;
1561 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
1562 if ( string_not_empty( moduleName ) ) {
1563 format = ReferenceAPI_getMapModules().findModule( moduleName );
1567 format->wrongFormat = false;
1569 Resource* resource = GlobalReferenceCache().capture( filename );
1570 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1571 if ( !resource->load() ) {
1572 GlobalReferenceCache().release( filename );
1576 if ( format->wrongFormat ) {
1577 GlobalReferenceCache().release( filename );
1581 NodeSmartReference clone( NewMapRoot( "" ) );
1582 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1583 Map_gatherNamespaced( clone );
1584 Map_mergeClonedNames();
1587 GlobalReferenceCache().release( filename );
1590 SceneChangeNotify();
1596 const char *type = GlobalRadiant().getGameDescriptionKeyValue( "q3map2_type" );
1597 int n = string_length( path_get_extension( filename ) );
1598 if ( n && ( extension_equal( path_get_extension( filename ), "bsp" ) || extension_equal( path_get_extension( filename ), "map" ) ) ) {
1600 output += AppPath_get();
1602 output += GDEF_OS_EXE_EXT;
1604 output += " -v -game ";
1605 output += ( type && *type ) ? type : "quake3";
1606 output += " -fs_basepath \"";
1607 output += EnginePath_get();
1608 output += "\" -fs_homepath \"";
1609 output += g_qeglobals.m_userEnginePath.c_str();
1613 for ( int i = 0; i < g_pakPathCount; i++ ) {
1614 if ( g_strcmp0( g_strPakPath[i].c_str(), "") ) {
1615 output += " -fs_pakpath \"";
1616 output += g_strPakPath[i].c_str();
1622 if ( g_disableEnginePath ) {
1623 output += " -fs_nobasepath ";
1626 if ( g_disableHomePath ) {
1627 output += " -fs_nohomepath ";
1630 output += " -fs_game ";
1631 output += gamename_get();
1632 output += " -convert -format ";
1633 output += Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map";
1634 if ( extension_equal( path_get_extension( filename ), "map" ) ) {
1635 output += " -readmap ";
1642 Q_Exec( NULL, output.c_str(), NULL, false, true );
1644 // rebuild filename as "filenamewithoutext_converted.map"
1646 output.append( filename, string_length( filename ) - ( n + 1 ) );
1647 output += "_converted.map";
1648 filename = output.c_str();
1651 Resource* resource = GlobalReferenceCache().capture( filename );
1652 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1653 if ( !resource->load() ) {
1654 GlobalReferenceCache().release( filename );
1657 NodeSmartReference clone( NewMapRoot( "" ) );
1658 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1659 Map_gatherNamespaced( clone );
1660 Map_mergeClonedNames();
1663 GlobalReferenceCache().release( filename );
1666 SceneChangeNotify();
1675 bool Map_SaveFile( const char* filename ){
1676 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1677 bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse, filename );
1679 // refresh VFS to apply new pak filtering based on mapname
1680 // needed for daemon DPK VFS
1691 // Saves selected world brushes and whole entities with partial/full selections
1693 bool Map_SaveSelected( const char* filename ){
1694 return MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Selected, filename );
1697 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker
1699 scene::Node& m_parent;
1700 mutable bool m_emptyOldParent;
1703 ParentSelectedBrushesToEntityWalker( scene::Node& parent ) : m_parent( parent ), m_emptyOldParent( false ){
1706 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1707 if ( path.top().get_pointer() != &m_parent && ( Node_isPrimitive( path.top() ) || m_emptyOldParent ) ) {
1708 Selectable* selectable = Instance_getSelectable( instance );
1709 if ( selectable && selectable->isSelected() && path.size() > 1 ) {
1716 void post( const scene::Path& path, scene::Instance& instance ) const {
1717 if ( path.top().get_pointer() == &m_parent )
1720 if ( Node_isPrimitive( path.top() ) ){
1721 m_emptyOldParent = false;
1722 Selectable* selectable = Instance_getSelectable( instance );
1724 if ( selectable && selectable->isSelected() && path.size() > 1 ){
1725 scene::Node& parent = path.parent();
1726 if ( &parent != &m_parent ){
1727 NodeSmartReference node( path.top().get() );
1728 scene::Traversable* traversable_parent = Node_getTraversable( parent );
1729 traversable_parent->erase( node );
1730 Node_getTraversable( m_parent )->insert( node );
1731 if ( traversable_parent->empty() )
1732 m_emptyOldParent = true;
1736 else if ( m_emptyOldParent ){
1737 m_emptyOldParent = false;
1738 // delete empty entities
1739 Entity* entity = Node_getEntity( path.top() );
1740 if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map ) && Node_getTraversable( path.top() )->empty() ) {
1741 Path_deleteTop( path );
1747 void Scene_parentSelectedBrushesToEntity( scene::Graph& graph, scene::Node& parent ){
1748 graph.traverse( ParentSelectedBrushesToEntityWalker( parent ) );
1751 class CountSelectedBrushes : public scene::Graph::Walker
1753 std::size_t& m_count;
1754 mutable std::size_t m_depth;
1756 CountSelectedBrushes( std::size_t& count ) : m_count( count ), m_depth( 0 ){
1760 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1761 if ( ++m_depth != 1 && path.top().get().isRoot() ) {
1764 Selectable* selectable = Instance_getSelectable( instance );
1765 if ( selectable != 0
1766 && selectable->isSelected()
1767 && Node_isPrimitive( path.top() ) ) {
1773 void post( const scene::Path& path, scene::Instance& instance ) const {
1778 std::size_t Scene_countSelectedBrushes( scene::Graph& graph ){
1780 graph.traverse( CountSelectedBrushes( count ) );
1792 const char* nodetype_get_name( ENodeType type ){
1793 if ( type == eNodeMap ) {
1796 if ( type == eNodeEntity ) {
1799 if ( type == eNodePrimitive ) {
1805 ENodeType node_get_nodetype( scene::Node& node ){
1806 if ( Node_isEntity( node ) ) {
1809 if ( Node_isPrimitive( node ) ) {
1810 return eNodePrimitive;
1812 return eNodeUnknown;
1815 bool contains_entity( scene::Node& node ){
1816 return Node_getTraversable( node ) != 0 && !Node_isBrush( node ) && !Node_isPatch( node ) && !Node_isEntity( node );
1819 bool contains_primitive( scene::Node& node ){
1820 return Node_isEntity( node ) && Node_getTraversable( node ) != 0 && Node_getEntity( node )->isContainer();
1823 ENodeType node_get_contains( scene::Node& node ){
1824 if ( contains_entity( node ) ) {
1827 if ( contains_primitive( node ) ) {
1828 return eNodePrimitive;
1830 return eNodeUnknown;
1833 void Path_parent( const scene::Path& parent, const scene::Path& child ){
1834 ENodeType contains = node_get_contains( parent.top() );
1835 ENodeType type = node_get_nodetype( child.top() );
1837 if ( contains != eNodeUnknown && contains == type ) {
1838 NodeSmartReference node( child.top().get() );
1839 Path_deleteTop( child );
1840 Node_getTraversable( parent.top() )->insert( node );
1841 SceneChangeNotify();
1845 globalErrorStream() << "failed - " << nodetype_get_name( type ) << " cannot be parented to " << nodetype_get_name( contains ) << " container.\n";
1849 void Scene_parentSelected(){
1850 UndoableCommand undo( "parentSelected" );
1852 if ( GlobalSelectionSystem().countSelected() > 1 ) {
1853 class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor
1855 const scene::Path& m_parent;
1857 ParentSelectedBrushesToEntityWalker( const scene::Path& parent ) : m_parent( parent ){
1860 void visit( scene::Instance& instance ) const {
1861 if ( &m_parent != &instance.path() ) {
1862 Path_parent( m_parent, instance.path() );
1867 ParentSelectedBrushesToEntityWalker visitor( GlobalSelectionSystem().ultimateSelected().path() );
1868 GlobalSelectionSystem().foreachSelected( visitor );
1872 globalOutputStream() << "failed - did not find two selected nodes.\n";
1878 if ( ConfirmModified( "New Map" ) ) {
1885 CopiedString g_mapsPath;
1887 const char* getMapsPath(){
1888 return g_mapsPath.c_str();
1891 const char* getLastMapFolderPath(){
1892 if (g_strLastMapFolder.empty()) {
1893 GlobalPreferenceSystem().registerPreference( "LastMapFolder", make_property_string( g_strLastMapFolder ) );
1894 if (g_strLastMapFolder.empty()) {
1895 StringOutputStream buffer( 1024 );
1896 buffer << getMapsPath();
1897 if ( !file_readable( buffer.c_str() ) ) {
1899 buffer << g_qeglobals.m_userGamePath.c_str() << "/";
1901 g_strLastMapFolder = buffer.c_str();
1904 return g_strLastMapFolder.c_str();
1907 const char* map_open( const char* title ){
1908 return MainFrame_getWindow().file_dialog( TRUE, title, getLastMapFolderPath(), MapFormat::Name(), true, false, false );
1911 const char* map_import( const char* title ){
1912 return MainFrame_getWindow().file_dialog( TRUE, title, getLastMapFolderPath(), MapFormat::Name(), false, true, false );
1915 const char* map_save( const char* title ){
1916 return MainFrame_getWindow().file_dialog( FALSE, title, getLastMapFolderPath(), MapFormat::Name(), false, false, true );
1920 if ( !ConfirmModified( "Open Map" ) ) {
1924 const char* filename = map_open( "Open Map" );
1926 if ( filename != NULL ) {
1927 MRU_AddFile( filename );
1930 Map_LoadFile( filename );
1935 const char* filename = map_import( "Import Map" );
1937 if ( filename != NULL ) {
1938 UndoableCommand undo( "mapImport" );
1939 Map_ImportFile( filename );
1944 const char* filename = map_save( "Save Map" );
1946 if ( filename != NULL ) {
1947 g_strLastMapFolder = g_path_get_dirname( filename );
1948 MRU_AddFile( filename );
1949 Map_Rename( filename );
1960 if ( Map_Unnamed( g_map ) ) {
1963 else if ( Map_Modified( g_map ) ) {
1965 MRU_AddFile( g_map.m_name.c_str() ); //add on saving, but not opening via cmd line: spoils the list
1970 const char* filename = map_save( "Export Selection" );
1972 if ( filename != NULL ) {
1973 g_strLastMapFolder = g_path_get_dirname( filename );
1974 Map_SaveSelected( filename );
1979 const char* filename = map_save( "Export Region" );
1981 if ( filename != NULL ) {
1982 g_strLastMapFolder = g_path_get_dirname( filename );
1983 Map_SaveRegion( filename );
1990 SceneChangeNotify();
1995 g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
1996 g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
1997 g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
1998 g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
2000 SceneChangeNotify();
2005 SceneChangeNotify();
2008 void RegionSelected(){
2009 Map_RegionSelectedBrushes();
2010 SceneChangeNotify();
2017 class BrushFindByIndexWalker : public scene::Traversable::Walker
2019 mutable std::size_t m_index;
2020 scene::Path& m_path;
2022 BrushFindByIndexWalker( std::size_t index, scene::Path& path )
2023 : m_index( index ), m_path( path ){
2026 bool pre( scene::Node& node ) const {
2027 if ( Node_isPrimitive( node ) && m_index-- == 0 ) {
2028 m_path.push( makeReference( node ) );
2034 class EntityFindByIndexWalker : public scene::Traversable::Walker
2036 mutable std::size_t m_index;
2037 scene::Path& m_path;
2039 EntityFindByIndexWalker( std::size_t index, scene::Path& path )
2040 : m_index( index ), m_path( path ){
2043 bool pre( scene::Node& node ) const {
2044 if ( Node_isEntity( node ) && m_index-- == 0 ) {
2045 m_path.push( makeReference( node ) );
2051 void Scene_FindEntityBrush( std::size_t entity, std::size_t brush, scene::Path& path ){
2052 path.push( makeReference( GlobalSceneGraph().root() ) );
2054 Node_getTraversable( path.top() )->traverse( EntityFindByIndexWalker( entity, path ) );
2056 if ( path.size() == 2 ) {
2057 scene::Traversable* traversable = Node_getTraversable( path.top() );
2058 if ( traversable != 0 ) {
2059 traversable->traverse( BrushFindByIndexWalker( brush, path ) );
2064 inline bool Node_hasChildren( scene::Node& node ){
2065 scene::Traversable* traversable = Node_getTraversable( node );
2066 return traversable != 0 && !traversable->empty();
2069 void SelectBrush( int entitynum, int brushnum ){
2071 Scene_FindEntityBrush( entitynum, brushnum, path );
2072 if ( path.size() == 3 || ( path.size() == 2 && !Node_hasChildren( path.top() ) ) ) {
2073 scene::Instance* instance = GlobalSceneGraph().find( path );
2074 ASSERT_MESSAGE( instance != 0, "SelectBrush: path not found in scenegraph" );
2075 Selectable* selectable = Instance_getSelectable( *instance );
2076 ASSERT_MESSAGE( selectable != 0, "SelectBrush: path not selectable" );
2077 selectable->setSelected( true );
2078 g_pParentWnd->GetXYWnd()->PositionView( instance->worldAABB().origin );
2083 class BrushFindIndexWalker : public scene::Graph::Walker
2085 mutable const scene::Node* m_node;
2086 std::size_t& m_count;
2088 BrushFindIndexWalker( const scene::Node& node, std::size_t& count )
2089 : m_node( &node ), m_count( count ){
2092 bool pre( const scene::Path& path, scene::Instance& instance ) const {
2093 if ( Node_isPrimitive( path.top() ) ) {
2094 if ( m_node == path.top().get_pointer() ) {
2105 class EntityFindIndexWalker : public scene::Graph::Walker
2107 mutable const scene::Node* m_node;
2108 std::size_t& m_count;
2110 EntityFindIndexWalker( const scene::Node& node, std::size_t& count )
2111 : m_node( &node ), m_count( count ){
2114 bool pre( const scene::Path& path, scene::Instance& instance ) const {
2115 if ( Node_isEntity( path.top() ) ) {
2116 if ( m_node == path.top().get_pointer() ) {
2127 static void GetSelectionIndex( int *ent, int *brush ){
2128 std::size_t count_brush = 0;
2129 std::size_t count_entity = 0;
2130 if ( GlobalSelectionSystem().countSelected() != 0 ) {
2131 const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
2133 GlobalSceneGraph().traverse( BrushFindIndexWalker( path.top(), count_brush ) );
2134 GlobalSceneGraph().traverse( EntityFindIndexWalker( path.parent(), count_entity ) );
2136 *brush = int(count_brush);
2137 *ent = int(count_entity);
2142 ui::Entry entity{ui::null};
2143 ui::Entry brush{ui::null};
2145 ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback ), &dialog );
2147 auto accel = ui::AccelGroup(ui::New);
2148 window.add_accel_group( accel );
2151 auto vbox = create_dialog_vbox( 4, 4 );
2154 auto table = create_dialog_table( 2, 2, 4, 4 );
2155 vbox.pack_start( table, TRUE, TRUE, 0 );
2157 ui::Widget label = ui::Label( "Entity number" );
2159 (table).attach(label, {0, 1, 0, 1}, {0, 0});
2162 ui::Widget label = ui::Label( "Brush number" );
2164 (table).attach(label, {0, 1, 1, 2}, {0, 0});
2167 auto entry = ui::Entry(ui::New);
2169 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
2170 gtk_widget_grab_focus( entry );
2174 auto entry = ui::Entry(ui::New);
2176 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
2182 auto hbox = create_dialog_hbox( 4 );
2183 vbox.pack_start( hbox, TRUE, TRUE, 0 );
2185 auto button = create_dialog_button( "Find", G_CALLBACK( dialog_button_ok ), &dialog );
2186 hbox.pack_start( button, FALSE, FALSE, 0 );
2187 widget_make_default( button );
2188 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
2191 auto button = create_dialog_button( "Close", G_CALLBACK( dialog_button_cancel ), &dialog );
2192 hbox.pack_start( button, FALSE, FALSE, 0 );
2193 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
2198 // Initialize dialog
2202 GetSelectionIndex( &ent, &br );
2203 sprintf( buf, "%i", ent );
2205 sprintf( buf, "%i", br );
2208 if ( modal_dialog_show( window, dialog ) == eIDOK ) {
2209 const char *entstr = gtk_entry_get_text( entity );
2210 const char *brushstr = gtk_entry_get_text( brush );
2211 SelectBrush( atoi( entstr ), atoi( brushstr ) );
2217 void Map_constructPreferences( PreferencesPage& page ){
2218 page.appendCheckBox( "", "Load last map at startup", g_bLoadLastMap );
2219 page.appendCheckBox( "", "Add entity and brush number comments on map write", g_writeMapComments );
2223 class MapEntityClasses : public ModuleObserver
2225 std::size_t m_unrealised;
2227 MapEntityClasses() : m_unrealised( 1 ){
2231 if ( --m_unrealised == 0 ) {
2232 if ( g_map.m_resource != 0 ) {
2233 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
2234 g_map.m_resource->realise();
2240 if ( ++m_unrealised == 1 ) {
2241 if ( g_map.m_resource != 0 ) {
2242 g_map.m_resource->flush();
2243 g_map.m_resource->unrealise();
2249 MapEntityClasses g_MapEntityClasses;
2252 class MapModuleObserver : public ModuleObserver
2254 std::size_t m_unrealised;
2256 MapModuleObserver() : m_unrealised( 1 ){
2260 if ( --m_unrealised == 0 ) {
2261 ASSERT_MESSAGE( !string_empty( g_qeglobals.m_userGamePath.c_str() ), "maps_directory: user-game-path is empty" );
2262 StringOutputStream buffer( 256 );
2263 buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2264 Q_mkdir( buffer.c_str() );
2265 g_mapsPath = buffer.c_str();
2270 if ( ++m_unrealised == 1 ) {
2276 MapModuleObserver g_MapModuleObserver;
2278 CopiedString g_strLastMap;
2279 bool g_bLoadLastMap = false;
2281 void Map_Construct(){
2282 GlobalCommands_insert( "RegionOff", makeCallbackF(RegionOff) );
2283 GlobalCommands_insert( "RegionSetXY", makeCallbackF(RegionXY) );
2284 GlobalCommands_insert( "RegionSetBrush", makeCallbackF(RegionBrush) );
2285 //GlobalCommands_insert( "RegionSetSelection", makeCallbackF(RegionSelected), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
2286 GlobalToggles_insert( "RegionSetSelection", makeCallbackF(RegionSelected), ToggleItem::AddCallbackCaller( g_region_item ), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
2288 GlobalPreferenceSystem().registerPreference( "LastMap", make_property_string( g_strLastMap ) );
2289 GlobalPreferenceSystem().registerPreference( "LoadLastMap", make_property_string( g_bLoadLastMap ) );
2290 GlobalPreferenceSystem().registerPreference( "MapInfoDlg", make_property<WindowPosition_String>( g_posMapInfoWnd ) );
2291 GlobalPreferenceSystem().registerPreference( "WriteMapComments", make_property_string( g_writeMapComments ) );
2293 PreferencesDialog_addSettingsPreferences( makeCallbackF(Map_constructPreferences) );
2295 GlobalEntityClassManager().attach( g_MapEntityClasses );
2296 Radiant_attachHomePathsObserver( g_MapModuleObserver );
2300 Radiant_detachHomePathsObserver( g_MapModuleObserver );
2301 GlobalEntityClassManager().detach( g_MapEntityClasses );