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 );
970 ScopeTimer timer( "map load" );
972 const MapFormat* format = NULL;
973 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
974 if ( string_not_empty( moduleName ) ) {
975 format = ReferenceAPI_getMapModules().findModule( moduleName );
978 for ( int i = 0; i < Brush_toggleFormatCount(); ++i )
983 Brush_toggleFormat( i );
984 Map_UpdateTitle( g_map );
986 g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
988 format->wrongFormat = false;
990 g_map.m_resource->attach( g_map );
992 if ( !format->wrongFormat ) {
998 Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() );
1001 globalOutputStream() << "--- LoadMapFile ---\n";
1002 globalOutputStream() << g_map.m_name.c_str() << "\n";
1004 globalOutputStream() << Unsigned( g_brushCount.get() ) << " primitive\n";
1005 globalOutputStream() << Unsigned( g_entityCount.get() ) << " entities\n";
1007 //GlobalEntityCreator().printStatistics();
1010 // move the view to a start position
1012 Map_StartPosition();
1014 g_currentMap = &g_map;
1020 virtual bool excluded( scene::Node& node ) const = 0;
1023 class ExcludeWalker : public scene::Traversable::Walker
1025 const scene::Traversable::Walker& m_walker;
1026 const Excluder* m_exclude;
1027 mutable bool m_skip;
1029 ExcludeWalker( const scene::Traversable::Walker& walker, const Excluder& exclude )
1030 : m_walker( walker ), m_exclude( &exclude ), m_skip( false ){
1033 bool pre( scene::Node& node ) const {
1034 if ( m_exclude->excluded( node ) || node.isRoot() ) {
1040 m_walker.pre( node );
1045 void post( scene::Node& node ) const {
1051 m_walker.post( node );
1056 class AnyInstanceSelected : public scene::Instantiable::Visitor
1060 AnyInstanceSelected( bool& selected ) : m_selected( selected ){
1064 void visit( scene::Instance& instance ) const {
1065 Selectable* selectable = Instance_getSelectable( instance );
1066 if ( selectable != 0
1067 && selectable->isSelected() ) {
1073 bool Node_instanceSelected( scene::Node& node ){
1074 scene::Instantiable* instantiable = Node_getInstantiable( node );
1075 ASSERT_NOTNULL( instantiable );
1077 instantiable->forEachInstance( AnyInstanceSelected( selected ) );
1081 class SelectedDescendantWalker : public scene::Traversable::Walker
1085 SelectedDescendantWalker( bool& selected ) : m_selected( selected ){
1089 bool pre( scene::Node& node ) const {
1090 if ( node.isRoot() ) {
1094 if ( Node_instanceSelected( node ) ) {
1102 bool Node_selectedDescendant( scene::Node& node ){
1104 Node_traverseSubgraph( node, SelectedDescendantWalker( selected ) );
1108 class SelectionExcluder : public Excluder
1111 bool excluded( scene::Node& node ) const {
1112 return !Node_selectedDescendant( node );
1116 class IncludeSelectedWalker : public scene::Traversable::Walker
1118 const scene::Traversable::Walker& m_walker;
1119 mutable std::size_t m_selected;
1120 mutable bool m_skip;
1122 bool selectedParent() const {
1123 return m_selected != 0;
1127 IncludeSelectedWalker( const scene::Traversable::Walker& walker )
1128 : m_walker( walker ), m_selected( 0 ), m_skip( false ){
1131 bool pre( scene::Node& node ) const {
1133 // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
1134 if ( !node.isRoot() && ( Node_selectedDescendant( node ) || selectedParent() ) ) {
1135 if ( Node_instanceSelected( node ) ) {
1138 m_walker.pre( node );
1148 void post( scene::Node& node ) const {
1154 if ( Node_instanceSelected( node ) ) {
1157 m_walker.post( node );
1162 void Map_Traverse_Selected( scene::Node& root, const scene::Traversable::Walker& walker ){
1163 scene::Traversable* traversable = Node_getTraversable( root );
1164 if ( traversable != 0 ) {
1166 traversable->traverse( ExcludeWalker( walker, SelectionExcluder() ) );
1168 traversable->traverse( IncludeSelectedWalker( walker ) );
1173 void Map_ExportSelected( TextOutputStream& out, const MapFormat& format ){
1174 format.writeGraph( GlobalSceneGraph().root(), Map_Traverse_Selected, out, g_writeMapComments );
1177 void Map_Traverse( scene::Node& root, const scene::Traversable::Walker& walker ){
1178 scene::Traversable* traversable = Node_getTraversable( root );
1179 if ( traversable != 0 ) {
1180 traversable->traverse( walker );
1184 class RegionExcluder : public Excluder
1187 bool excluded( scene::Node& node ) const {
1188 return node.excluded();
1192 void Map_Traverse_Region( scene::Node& root, const scene::Traversable::Walker& walker ){
1193 scene::Traversable* traversable = Node_getTraversable( root );
1194 if ( traversable != 0 ) {
1195 traversable->traverse( ExcludeWalker( walker, RegionExcluder() ) );
1199 bool Map_SaveRegion( const char *filename ){
1202 bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Region, filename );
1204 RemoveRegionBrushes();
1210 void Map_RenameAbsolute( const char* absolute ){
1211 Resource* resource = GlobalReferenceCache().capture( absolute );
1212 NodeSmartReference clone( NewMapRoot( path_make_relative( absolute, GlobalFileSystem().findRoot( absolute ) ) ) );
1213 resource->setNode( clone.get_pointer() );
1216 //ScopeTimer timer("clone subgraph");
1217 Node_getTraversable( GlobalSceneGraph().root() )->traverse( CloneAll( clone ) );
1220 g_map.m_resource->detach( g_map );
1221 GlobalReferenceCache().release( g_map.m_name.c_str() );
1223 g_map.m_resource = resource;
1225 g_map.m_name = absolute;
1226 Map_UpdateTitle( g_map );
1228 g_map.m_resource->attach( g_map );
1229 // refresh VFS to apply new pak filtering based on mapname
1230 // needed for daemon DPK VFS
1234 void Map_Rename( const char* filename ){
1235 if ( !string_equal( g_map.m_name.c_str(), filename ) ) {
1236 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1238 Map_RenameAbsolute( filename );
1240 SceneChangeNotify();
1251 ScopeTimer timer( "map save" );
1253 return true; // assume success..
1263 //globalOutputStream() << "Map_New\n";
1265 g_map.m_name = "unnamed.map";
1266 Map_UpdateTitle( g_map );
1269 g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
1270 // ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
1271 g_map.m_resource->attach( g_map );
1273 SceneChangeNotify();
1276 FocusViews( g_vector3_identity, 0 );
1278 g_currentMap = &g_map;
1280 // restart VFS to apply new pak filtering based on mapname
1281 // needed for daemon DPK VFS
1285 extern void ConstructRegionBrushes( scene::Node * brushes[6], const Vector3 ®ion_mins, const Vector3 ®ion_maxs );
1287 void ConstructRegionStartpoint( scene::Node* startpoint, const Vector3& region_mins, const Vector3& region_maxs ){
1289 \todo we need to make sure that the player start IS inside the region and bail out if it's not
1290 the compiler will refuse to compile a map with a player_start somewhere in empty space..
1291 for now, let's just print an error
1294 Vector3 vOrig( Camera_getOrigin( *g_pParentWnd->GetCamWnd() ) );
1296 for ( int i = 0 ; i < 3 ; i++ )
1298 if ( vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i] ) {
1299 globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
1304 // write the info_playerstart
1306 sprintf( sTmp, "%d %d %d", (int)vOrig[0], (int)vOrig[1], (int)vOrig[2] );
1307 Node_getEntity( *startpoint )->setKeyValue( "origin", sTmp );
1308 sprintf( sTmp, "%d", (int)Camera_getAngles( *g_pParentWnd->GetCamWnd() )[CAMERA_YAW] );
1309 Node_getEntity( *startpoint )->setKeyValue( "angle", sTmp );
1313 ===========================================================
1317 ===========================================================
1319 bool region_active = false;
1321 ConstReferenceCaller<bool, void(const Callback<void(bool)> &), PropertyImpl<bool>::Export> g_region_caller( region_active );
1323 ToggleItem g_region_item( g_region_caller );
1325 /*void Map_ToggleRegion(){
1326 region_active = !region_active;
1327 g_region_item.update();
1330 Vector3 region_mins( g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord );
1331 Vector3 region_maxs( g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord );
1333 scene::Node* region_sides[6];
1334 scene::Node* region_startpoint = 0;
1339 a regioned map will have temp walls put up at the region boundary
1340 \todo TODO TTimo old implementation of region brushes
1341 we still add them straight in the worldspawn and take them out after the map is saved
1342 with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
1345 void AddRegionBrushes( void ){
1348 for ( i = 0; i < 6; i++ )
1350 region_sides[i] = &GlobalBrushCreator().createBrush();
1351 Node_getTraversable( Map_FindOrInsertWorldspawn( g_map ) )->insert( NodeSmartReference( *region_sides[i] ) );
1354 region_startpoint = &GlobalEntityCreator().createEntity( GlobalEntityClassManager().findOrInsert( "info_player_start", false ) );
1356 ConstructRegionBrushes( region_sides, region_mins, region_maxs );
1357 ConstructRegionStartpoint( region_startpoint, region_mins, region_maxs );
1359 Node_getTraversable( GlobalSceneGraph().root() )->insert( NodeSmartReference( *region_startpoint ) );
1362 void RemoveRegionBrushes( void ){
1363 for ( std::size_t i = 0; i < 6; i++ )
1365 Node_getTraversable( *Map_GetWorldspawn( g_map ) )->erase( *region_sides[i] );
1367 Node_getTraversable( GlobalSceneGraph().root() )->erase( *region_startpoint );
1370 inline void exclude_node( scene::Node& node, bool exclude ){
1372 ? node.enable( scene::Node::eExcluded )
1373 : node.disable( scene::Node::eExcluded );
1376 class ExcludeAllWalker : public scene::Graph::Walker
1380 ExcludeAllWalker( bool exclude )
1381 : m_exclude( exclude ){
1384 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1385 exclude_node( path.top(), m_exclude );
1391 void Scene_Exclude_All( bool exclude ){
1392 GlobalSceneGraph().traverse( ExcludeAllWalker( exclude ) );
1395 bool Instance_isSelected( const scene::Instance& instance ){
1396 const Selectable* selectable = Instance_getSelectable( instance );
1397 return selectable != 0 && selectable->isSelected();
1400 class ExcludeSelectedWalker : public scene::Graph::Walker
1404 ExcludeSelectedWalker( bool exclude )
1405 : m_exclude( exclude ){
1408 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1409 exclude_node( path.top(), ( instance.isSelected() || instance.childSelected() || instance.parentSelected() ) == m_exclude );
1414 void Scene_Exclude_Selected( bool exclude ){
1415 GlobalSceneGraph().traverse( ExcludeSelectedWalker( exclude ) );
1418 class ExcludeRegionedWalker : public scene::Graph::Walker
1422 ExcludeRegionedWalker( bool exclude )
1423 : m_exclude( exclude ){
1426 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1431 aabb_intersects_aabb(
1432 instance.worldAABB(),
1433 aabb_for_minmax( region_mins, region_maxs )
1443 void Scene_Exclude_Region( bool exclude ){
1444 GlobalSceneGraph().traverse( ExcludeRegionedWalker( exclude ) );
1451 Other filtering options may still be on
1454 void Map_RegionOff(){
1455 region_active = false;
1456 g_region_item.update();
1458 region_maxs[0] = g_MaxWorldCoord - 64;
1459 region_mins[0] = g_MinWorldCoord + 64;
1460 region_maxs[1] = g_MaxWorldCoord - 64;
1461 region_mins[1] = g_MinWorldCoord + 64;
1462 region_maxs[2] = g_MaxWorldCoord - 64;
1463 region_mins[2] = g_MinWorldCoord + 64;
1465 Scene_Exclude_All( false );
1468 void Map_ApplyRegion( void ){
1469 region_active = true;
1470 g_region_item.update();
1472 Scene_Exclude_Region( false );
1477 ========================
1478 Map_RegionSelectedBrushes
1479 ========================
1481 void Map_RegionSelectedBrushes( void ){
1484 if ( GlobalSelectionSystem().countSelected() != 0
1485 && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
1486 region_active = true;
1487 g_region_item.update();
1488 Select_GetBounds( region_mins, region_maxs );
1490 Scene_Exclude_Selected( false );
1492 GlobalSelectionSystem().setSelectedAll( false );
1502 void Map_RegionXY( float x_min, float y_min, float x_max, float y_max ){
1505 region_mins[0] = x_min;
1506 region_maxs[0] = x_max;
1507 region_mins[1] = y_min;
1508 region_maxs[1] = y_max;
1509 region_mins[2] = g_MinWorldCoord + 64;
1510 region_maxs[2] = g_MaxWorldCoord - 64;
1515 void Map_RegionBounds( const AABB& bounds ){
1518 region_mins = vector3_subtracted( bounds.origin, bounds.extents );
1519 region_maxs = vector3_added( bounds.origin, bounds.extents );
1531 void Map_RegionBrush( void ){
1532 if ( GlobalSelectionSystem().countSelected() != 0 ) {
1533 scene::Instance& instance = GlobalSelectionSystem().ultimateSelected();
1534 Map_RegionBounds( instance.worldAABB() );
1543 bool Map_ImportFile( const char* filename ){
1544 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
1546 g_strLastMapFolder = g_path_get_dirname( filename );
1548 bool success = false;
1550 if ( extension_equal( path_get_extension( filename ), "bsp" ) ) {
1555 const MapFormat* format = NULL;
1556 const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
1557 if ( string_not_empty( moduleName ) ) {
1558 format = ReferenceAPI_getMapModules().findModule( moduleName );
1562 format->wrongFormat = false;
1564 Resource* resource = GlobalReferenceCache().capture( filename );
1565 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1566 if ( !resource->load() ) {
1567 GlobalReferenceCache().release( filename );
1571 if ( format->wrongFormat ) {
1572 GlobalReferenceCache().release( filename );
1576 NodeSmartReference clone( NewMapRoot( "" ) );
1577 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1578 Map_gatherNamespaced( clone );
1579 Map_mergeClonedNames();
1582 GlobalReferenceCache().release( filename );
1585 SceneChangeNotify();
1591 const char *type = GlobalRadiant().getGameDescriptionKeyValue( "q3map2_type" );
1592 int n = string_length( path_get_extension( filename ) );
1593 if ( n && ( extension_equal( path_get_extension( filename ), "bsp" ) || extension_equal( path_get_extension( filename ), "map" ) ) ) {
1594 StringBuffer output;
1595 output.push_string( AppPath_get() );
1596 output.push_string( "q3map2." );
1597 output.push_string( RADIANT_EXECUTABLE );
1598 output.push_string( " -v -game " );
1599 output.push_string( ( type && *type ) ? type : "quake3" );
1600 output.push_string( " -fs_basepath \"" );
1601 output.push_string( EnginePath_get() );
1602 output.push_string( "\" -fs_homepath \"" );
1603 output.push_string( g_qeglobals.m_userEnginePath.c_str() );
1604 output.push_string( "\"" );
1607 for ( int i = 0; i < g_pakPathCount; i++ ) {
1608 if ( g_strcmp0( g_strPakPath[i].c_str(), "") ) {
1609 output.push_string( " -fs_pakpath \"" );
1610 output.push_string( g_strPakPath[i].c_str() );
1611 output.push_string( "\"" );
1616 if ( g_disableEnginePath ) {
1617 output.push_string( " -fs_nobasepath " );
1620 if ( g_disableHomePath ) {
1621 output.push_string( " -fs_nohomepath " );
1624 output.push_string( " -fs_game " );
1625 output.push_string( gamename_get() );
1626 output.push_string( " -convert -format " );
1627 output.push_string( Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map" );
1628 if ( extension_equal( path_get_extension( filename ), "map" ) ) {
1629 output.push_string( " -readmap " );
1631 output.push_string( " \"" );
1632 output.push_string( filename );
1633 output.push_string( "\"" );
1636 Q_Exec( NULL, output.c_str(), NULL, false, true );
1638 // rebuild filename as "filenamewithoutext_converted.map"
1640 output.push_range( filename, filename + string_length( filename ) - ( n + 1 ) );
1641 output.push_string( "_converted.map" );
1642 filename = output.c_str();
1645 Resource* resource = GlobalReferenceCache().capture( filename );
1646 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1647 if ( !resource->load() ) {
1648 GlobalReferenceCache().release( filename );
1651 NodeSmartReference clone( NewMapRoot( "" ) );
1652 Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
1653 Map_gatherNamespaced( clone );
1654 Map_mergeClonedNames();
1657 GlobalReferenceCache().release( filename );
1660 SceneChangeNotify();
1669 bool Map_SaveFile( const char* filename ){
1670 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
1671 bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse, filename );
1673 // refresh VFS to apply new pak filtering based on mapname
1674 // needed for daemon DPK VFS
1685 // Saves selected world brushes and whole entities with partial/full selections
1687 bool Map_SaveSelected( const char* filename ){
1688 return MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Selected, filename );
1691 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker
1693 scene::Node& m_parent;
1694 mutable bool m_emptyOldParent;
1697 ParentSelectedBrushesToEntityWalker( scene::Node& parent ) : m_parent( parent ), m_emptyOldParent( false ){
1700 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1701 if ( path.top().get_pointer() != &m_parent && ( Node_isPrimitive( path.top() ) || m_emptyOldParent ) ) {
1702 Selectable* selectable = Instance_getSelectable( instance );
1703 if ( selectable && selectable->isSelected() && path.size() > 1 ) {
1710 void post( const scene::Path& path, scene::Instance& instance ) const {
1711 if ( path.top().get_pointer() == &m_parent )
1714 if ( Node_isPrimitive( path.top() ) ){
1715 m_emptyOldParent = false;
1716 Selectable* selectable = Instance_getSelectable( instance );
1718 if ( selectable && selectable->isSelected() && path.size() > 1 ){
1719 scene::Node& parent = path.parent();
1720 if ( &parent != &m_parent ){
1721 NodeSmartReference node( path.top().get() );
1722 scene::Traversable* traversable_parent = Node_getTraversable( parent );
1723 traversable_parent->erase( node );
1724 Node_getTraversable( m_parent )->insert( node );
1725 if ( traversable_parent->empty() )
1726 m_emptyOldParent = true;
1730 else if ( m_emptyOldParent ){
1731 m_emptyOldParent = false;
1732 // delete empty entities
1733 Entity* entity = Node_getEntity( path.top() );
1734 if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map ) && Node_getTraversable( path.top() )->empty() ) {
1735 Path_deleteTop( path );
1741 void Scene_parentSelectedBrushesToEntity( scene::Graph& graph, scene::Node& parent ){
1742 graph.traverse( ParentSelectedBrushesToEntityWalker( parent ) );
1745 class CountSelectedBrushes : public scene::Graph::Walker
1747 std::size_t& m_count;
1748 mutable std::size_t m_depth;
1750 CountSelectedBrushes( std::size_t& count ) : m_count( count ), m_depth( 0 ){
1754 bool pre( const scene::Path& path, scene::Instance& instance ) const {
1755 if ( ++m_depth != 1 && path.top().get().isRoot() ) {
1758 Selectable* selectable = Instance_getSelectable( instance );
1759 if ( selectable != 0
1760 && selectable->isSelected()
1761 && Node_isPrimitive( path.top() ) ) {
1767 void post( const scene::Path& path, scene::Instance& instance ) const {
1772 std::size_t Scene_countSelectedBrushes( scene::Graph& graph ){
1774 graph.traverse( CountSelectedBrushes( count ) );
1786 const char* nodetype_get_name( ENodeType type ){
1787 if ( type == eNodeMap ) {
1790 if ( type == eNodeEntity ) {
1793 if ( type == eNodePrimitive ) {
1799 ENodeType node_get_nodetype( scene::Node& node ){
1800 if ( Node_isEntity( node ) ) {
1803 if ( Node_isPrimitive( node ) ) {
1804 return eNodePrimitive;
1806 return eNodeUnknown;
1809 bool contains_entity( scene::Node& node ){
1810 return Node_getTraversable( node ) != 0 && !Node_isBrush( node ) && !Node_isPatch( node ) && !Node_isEntity( node );
1813 bool contains_primitive( scene::Node& node ){
1814 return Node_isEntity( node ) && Node_getTraversable( node ) != 0 && Node_getEntity( node )->isContainer();
1817 ENodeType node_get_contains( scene::Node& node ){
1818 if ( contains_entity( node ) ) {
1821 if ( contains_primitive( node ) ) {
1822 return eNodePrimitive;
1824 return eNodeUnknown;
1827 void Path_parent( const scene::Path& parent, const scene::Path& child ){
1828 ENodeType contains = node_get_contains( parent.top() );
1829 ENodeType type = node_get_nodetype( child.top() );
1831 if ( contains != eNodeUnknown && contains == type ) {
1832 NodeSmartReference node( child.top().get() );
1833 Path_deleteTop( child );
1834 Node_getTraversable( parent.top() )->insert( node );
1835 SceneChangeNotify();
1839 globalErrorStream() << "failed - " << nodetype_get_name( type ) << " cannot be parented to " << nodetype_get_name( contains ) << " container.\n";
1843 void Scene_parentSelected(){
1844 UndoableCommand undo( "parentSelected" );
1846 if ( GlobalSelectionSystem().countSelected() > 1 ) {
1847 class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor
1849 const scene::Path& m_parent;
1851 ParentSelectedBrushesToEntityWalker( const scene::Path& parent ) : m_parent( parent ){
1854 void visit( scene::Instance& instance ) const {
1855 if ( &m_parent != &instance.path() ) {
1856 Path_parent( m_parent, instance.path() );
1861 ParentSelectedBrushesToEntityWalker visitor( GlobalSelectionSystem().ultimateSelected().path() );
1862 GlobalSelectionSystem().foreachSelected( visitor );
1866 globalOutputStream() << "failed - did not find two selected nodes.\n";
1872 if ( ConfirmModified( "New Map" ) ) {
1879 CopiedString g_mapsPath;
1881 const char* getMapsPath(){
1882 return g_mapsPath.c_str();
1885 const char* getLastMapFolderPath(){
1886 if (g_strLastMapFolder.empty()) {
1887 GlobalPreferenceSystem().registerPreference( "LastMapFolder", make_property_string( g_strLastMapFolder ) );
1888 if (g_strLastMapFolder.empty()) {
1889 StringOutputStream buffer( 1024 );
1890 buffer << getMapsPath();
1891 if ( !file_readable( buffer.c_str() ) ) {
1893 buffer << g_qeglobals.m_userGamePath.c_str() << "/";
1895 g_strLastMapFolder = buffer.c_str();
1898 return g_strLastMapFolder.c_str();
1901 const char* map_open( const char* title ){
1902 return MainFrame_getWindow().file_dialog( TRUE, title, getLastMapFolderPath(), MapFormat::Name(), true, false, false );
1905 const char* map_import( const char* title ){
1906 return MainFrame_getWindow().file_dialog( TRUE, title, getLastMapFolderPath(), MapFormat::Name(), false, true, false );
1909 const char* map_save( const char* title ){
1910 return MainFrame_getWindow().file_dialog( FALSE, title, getLastMapFolderPath(), MapFormat::Name(), false, false, true );
1914 if ( !ConfirmModified( "Open Map" ) ) {
1918 const char* filename = map_open( "Open Map" );
1920 if ( filename != NULL ) {
1921 MRU_AddFile( filename );
1924 Map_LoadFile( filename );
1929 const char* filename = map_import( "Import Map" );
1931 if ( filename != NULL ) {
1932 UndoableCommand undo( "mapImport" );
1933 Map_ImportFile( filename );
1938 const char* filename = map_save( "Save Map" );
1940 if ( filename != NULL ) {
1941 g_strLastMapFolder = g_path_get_dirname( filename );
1942 MRU_AddFile( filename );
1943 Map_Rename( filename );
1954 if ( Map_Unnamed( g_map ) ) {
1957 else if ( Map_Modified( g_map ) ) {
1959 MRU_AddFile( g_map.m_name.c_str() ); //add on saving, but not opening via cmd line: spoils the list
1964 const char* filename = map_save( "Export Selection" );
1966 if ( filename != NULL ) {
1967 g_strLastMapFolder = g_path_get_dirname( filename );
1968 Map_SaveSelected( filename );
1973 const char* filename = map_save( "Export Region" );
1975 if ( filename != NULL ) {
1976 g_strLastMapFolder = g_path_get_dirname( filename );
1977 Map_SaveRegion( filename );
1984 SceneChangeNotify();
1989 g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
1990 g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
1991 g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
1992 g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
1994 SceneChangeNotify();
1999 SceneChangeNotify();
2002 void RegionSelected(){
2003 Map_RegionSelectedBrushes();
2004 SceneChangeNotify();
2011 class BrushFindByIndexWalker : public scene::Traversable::Walker
2013 mutable std::size_t m_index;
2014 scene::Path& m_path;
2016 BrushFindByIndexWalker( std::size_t index, scene::Path& path )
2017 : m_index( index ), m_path( path ){
2020 bool pre( scene::Node& node ) const {
2021 if ( Node_isPrimitive( node ) && m_index-- == 0 ) {
2022 m_path.push( makeReference( node ) );
2028 class EntityFindByIndexWalker : public scene::Traversable::Walker
2030 mutable std::size_t m_index;
2031 scene::Path& m_path;
2033 EntityFindByIndexWalker( std::size_t index, scene::Path& path )
2034 : m_index( index ), m_path( path ){
2037 bool pre( scene::Node& node ) const {
2038 if ( Node_isEntity( node ) && m_index-- == 0 ) {
2039 m_path.push( makeReference( node ) );
2045 void Scene_FindEntityBrush( std::size_t entity, std::size_t brush, scene::Path& path ){
2046 path.push( makeReference( GlobalSceneGraph().root() ) );
2048 Node_getTraversable( path.top() )->traverse( EntityFindByIndexWalker( entity, path ) );
2050 if ( path.size() == 2 ) {
2051 scene::Traversable* traversable = Node_getTraversable( path.top() );
2052 if ( traversable != 0 ) {
2053 traversable->traverse( BrushFindByIndexWalker( brush, path ) );
2058 inline bool Node_hasChildren( scene::Node& node ){
2059 scene::Traversable* traversable = Node_getTraversable( node );
2060 return traversable != 0 && !traversable->empty();
2063 void SelectBrush( int entitynum, int brushnum ){
2065 Scene_FindEntityBrush( entitynum, brushnum, path );
2066 if ( path.size() == 3 || ( path.size() == 2 && !Node_hasChildren( path.top() ) ) ) {
2067 scene::Instance* instance = GlobalSceneGraph().find( path );
2068 ASSERT_MESSAGE( instance != 0, "SelectBrush: path not found in scenegraph" );
2069 Selectable* selectable = Instance_getSelectable( *instance );
2070 ASSERT_MESSAGE( selectable != 0, "SelectBrush: path not selectable" );
2071 selectable->setSelected( true );
2072 g_pParentWnd->GetXYWnd()->PositionView( instance->worldAABB().origin );
2077 class BrushFindIndexWalker : public scene::Graph::Walker
2079 mutable const scene::Node* m_node;
2080 std::size_t& m_count;
2082 BrushFindIndexWalker( const scene::Node& node, std::size_t& count )
2083 : m_node( &node ), m_count( count ){
2086 bool pre( const scene::Path& path, scene::Instance& instance ) const {
2087 if ( Node_isPrimitive( path.top() ) ) {
2088 if ( m_node == path.top().get_pointer() ) {
2099 class EntityFindIndexWalker : public scene::Graph::Walker
2101 mutable const scene::Node* m_node;
2102 std::size_t& m_count;
2104 EntityFindIndexWalker( const scene::Node& node, std::size_t& count )
2105 : m_node( &node ), m_count( count ){
2108 bool pre( const scene::Path& path, scene::Instance& instance ) const {
2109 if ( Node_isEntity( path.top() ) ) {
2110 if ( m_node == path.top().get_pointer() ) {
2121 static void GetSelectionIndex( int *ent, int *brush ){
2122 std::size_t count_brush = 0;
2123 std::size_t count_entity = 0;
2124 if ( GlobalSelectionSystem().countSelected() != 0 ) {
2125 const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
2127 GlobalSceneGraph().traverse( BrushFindIndexWalker( path.top(), count_brush ) );
2128 GlobalSceneGraph().traverse( EntityFindIndexWalker( path.parent(), count_entity ) );
2130 *brush = int(count_brush);
2131 *ent = int(count_entity);
2136 ui::Entry entity{ui::null};
2137 ui::Entry brush{ui::null};
2139 ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback ), &dialog );
2141 auto accel = ui::AccelGroup(ui::New);
2142 window.add_accel_group( accel );
2145 auto vbox = create_dialog_vbox( 4, 4 );
2148 auto table = create_dialog_table( 2, 2, 4, 4 );
2149 vbox.pack_start( table, TRUE, TRUE, 0 );
2151 ui::Widget label = ui::Label( "Entity number" );
2153 (table).attach(label, {0, 1, 0, 1}, {0, 0});
2156 ui::Widget label = ui::Label( "Brush number" );
2158 (table).attach(label, {0, 1, 1, 2}, {0, 0});
2161 auto entry = ui::Entry(ui::New);
2163 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
2164 gtk_widget_grab_focus( entry );
2168 auto entry = ui::Entry(ui::New);
2170 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
2176 auto hbox = create_dialog_hbox( 4 );
2177 vbox.pack_start( hbox, TRUE, TRUE, 0 );
2179 auto button = create_dialog_button( "Find", G_CALLBACK( dialog_button_ok ), &dialog );
2180 hbox.pack_start( button, FALSE, FALSE, 0 );
2181 widget_make_default( button );
2182 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
2185 auto button = create_dialog_button( "Close", G_CALLBACK( dialog_button_cancel ), &dialog );
2186 hbox.pack_start( button, FALSE, FALSE, 0 );
2187 gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
2192 // Initialize dialog
2196 GetSelectionIndex( &ent, &br );
2197 sprintf( buf, "%i", ent );
2199 sprintf( buf, "%i", br );
2202 if ( modal_dialog_show( window, dialog ) == eIDOK ) {
2203 const char *entstr = gtk_entry_get_text( entity );
2204 const char *brushstr = gtk_entry_get_text( brush );
2205 SelectBrush( atoi( entstr ), atoi( brushstr ) );
2211 void Map_constructPreferences( PreferencesPage& page ){
2212 page.appendCheckBox( "", "Load last map at startup", g_bLoadLastMap );
2213 page.appendCheckBox( "", "Add entity and brush number comments on map write", g_writeMapComments );
2217 class MapEntityClasses : public ModuleObserver
2219 std::size_t m_unrealised;
2221 MapEntityClasses() : m_unrealised( 1 ){
2225 if ( --m_unrealised == 0 ) {
2226 if ( g_map.m_resource != 0 ) {
2227 ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
2228 g_map.m_resource->realise();
2234 if ( ++m_unrealised == 1 ) {
2235 if ( g_map.m_resource != 0 ) {
2236 g_map.m_resource->flush();
2237 g_map.m_resource->unrealise();
2243 MapEntityClasses g_MapEntityClasses;
2246 class MapModuleObserver : public ModuleObserver
2248 std::size_t m_unrealised;
2250 MapModuleObserver() : m_unrealised( 1 ){
2254 if ( --m_unrealised == 0 ) {
2255 ASSERT_MESSAGE( !string_empty( g_qeglobals.m_userGamePath.c_str() ), "maps_directory: user-game-path is empty" );
2256 StringOutputStream buffer( 256 );
2257 buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2258 Q_mkdir( buffer.c_str() );
2259 g_mapsPath = buffer.c_str();
2264 if ( ++m_unrealised == 1 ) {
2270 MapModuleObserver g_MapModuleObserver;
2272 CopiedString g_strLastMap;
2273 bool g_bLoadLastMap = false;
2275 void Map_Construct(){
2276 GlobalCommands_insert( "RegionOff", makeCallbackF(RegionOff) );
2277 GlobalCommands_insert( "RegionSetXY", makeCallbackF(RegionXY) );
2278 GlobalCommands_insert( "RegionSetBrush", makeCallbackF(RegionBrush) );
2279 //GlobalCommands_insert( "RegionSetSelection", makeCallbackF(RegionSelected), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
2280 GlobalToggles_insert( "RegionSetSelection", makeCallbackF(RegionSelected), ToggleItem::AddCallbackCaller( g_region_item ), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
2282 GlobalPreferenceSystem().registerPreference( "LastMap", make_property_string( g_strLastMap ) );
2283 GlobalPreferenceSystem().registerPreference( "LoadLastMap", make_property_string( g_bLoadLastMap ) );
2284 GlobalPreferenceSystem().registerPreference( "MapInfoDlg", make_property<WindowPosition_String>( g_posMapInfoWnd ) );
2285 GlobalPreferenceSystem().registerPreference( "WriteMapComments", make_property_string( g_writeMapComments ) );
2287 PreferencesDialog_addSettingsPreferences( makeCallbackF(Map_constructPreferences) );
2289 GlobalEntityClassManager().attach( g_MapEntityClasses );
2290 Radiant_attachHomePathsObserver( g_MapModuleObserver );
2294 Radiant_detachHomePathsObserver( g_MapModuleObserver );
2295 GlobalEntityClassManager().detach( g_MapEntityClasses );