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"
62 #include "uniquenames.h"
63 #include "modulesystem/singletonmodule.h"
64 #include "modulesystem/moduleregistry.h"
65 #include "stream/stringstream.h"
66 #include "signal/signal.h"
68 #include "gtkutil/filechooser.h"
72 #include "filetypes.h"
74 #include "entityinspector.h"
77 #include "camwindow.h"
79 #include "mainframe.h"
80 #include "preferences.h"
81 #include "preferencesystem.h"
82 #include "referencecache.h"
86 #include "brushmodule.h"
96 //globalOutputStream() << "construct " << makeQuoted(c_str()) << "\n";
97 m_names.insert(name_read(c_str()));
104 //globalOutputStream() << "destroy " << makeQuoted(c_str()) << "\n";
105 m_names.erase(name_read(c_str()));
109 NameObserver &operator=(const NameObserver &other);
112 NameObserver(UniqueNames &names) : m_names(names)
117 NameObserver(const NameObserver &other) : m_names(other.m_names), m_name(other.m_name)
129 return string_empty(c_str());
132 const char *c_str() const
134 return m_name.c_str();
137 void nameChanged(const char *name)
144 typedef MemberCaller<NameObserver, void(const char *), &NameObserver::nameChanged> NameChangedCaller;
147 class BasicNamespace : public Namespace {
148 typedef std::map<NameCallback, NameObserver> Names;
150 UniqueNames m_uniqueNames;
154 ASSERT_MESSAGE(m_names.empty(), "namespace: names still registered at shutdown");
157 void attach(const NameCallback &setName, const NameCallbackCallback &attachObserver)
159 std::pair<Names::iterator, bool> result = m_names.insert(Names::value_type(setName, m_uniqueNames));
160 ASSERT_MESSAGE(result.second, "cannot attach name");
161 attachObserver(NameObserver::NameChangedCaller((*result.first).second));
162 //globalOutputStream() << "attach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
165 void detach(const NameCallback &setName, const NameCallbackCallback &detachObserver)
167 Names::iterator i = m_names.find(setName);
168 ASSERT_MESSAGE(i != m_names.end(), "cannot detach name");
169 //globalOutputStream() << "detach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
170 detachObserver(NameObserver::NameChangedCaller((*i).second));
174 void makeUnique(const char *name, const NameCallback &setName) const
177 name_write(buffer, m_uniqueNames.make_unique(name_read(name)));
181 void mergeNames(const BasicNamespace &other) const
183 typedef std::list<NameCallback> SetNameCallbacks;
184 typedef std::map<CopiedString, SetNameCallbacks> NameGroups;
187 UniqueNames uniqueNames(other.m_uniqueNames);
189 for (Names::const_iterator i = m_names.begin(); i != m_names.end(); ++i) {
190 groups[(*i).second.c_str()].push_back((*i).first);
193 for (NameGroups::iterator i = groups.begin(); i != groups.end(); ++i) {
194 name_t uniqueName(uniqueNames.make_unique(name_read((*i).first.c_str())));
195 uniqueNames.insert(uniqueName);
198 name_write(buffer, uniqueName);
200 //globalOutputStream() << "renaming " << makeQuoted((*i).first.c_str()) << " to " << makeQuoted(buffer) << "\n";
202 SetNameCallbacks &setNameCallbacks = (*i).second;
204 for (SetNameCallbacks::const_iterator j = setNameCallbacks.begin(); j != setNameCallbacks.end(); ++j) {
211 BasicNamespace g_defaultNamespace;
212 BasicNamespace g_cloneNamespace;
215 Namespace *m_namespace;
217 typedef Namespace Type;
219 STRING_CONSTANT(Name, "*");
223 m_namespace = &g_defaultNamespace;
226 Namespace *getTable()
232 typedef SingletonModule<NamespaceAPI> NamespaceModule;
233 typedef Static<NamespaceModule> StaticNamespaceModule;
234 StaticRegisterModule staticRegisterDefaultNamespace(StaticNamespaceModule::instance());
237 std::list<Namespaced *> g_cloned;
239 inline Namespaced *Node_getNamespaced(scene::Node &node)
241 return NodeTypeCast<Namespaced>::cast(node);
244 void Node_gatherNamespaced(scene::Node &node)
246 Namespaced *namespaced = Node_getNamespaced(node);
247 if (namespaced != 0) {
248 g_cloned.push_back(namespaced);
252 class GatherNamespaced : public scene::Traversable::Walker {
254 bool pre(scene::Node &node) const
256 Node_gatherNamespaced(node);
261 void Map_gatherNamespaced(scene::Node &root)
263 Node_traverseSubgraph(root, GatherNamespaced());
266 void Map_mergeClonedNames()
268 for (std::list<Namespaced *>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i) {
269 (*i)->setNamespace(g_cloneNamespace);
271 g_cloneNamespace.mergeNames(g_defaultNamespace);
272 for (std::list<Namespaced *>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i) {
273 (*i)->setNamespace(g_defaultNamespace);
287 void set(scene::Node *node)
298 scene::Node *get() const
306 void Map_SetValid(Map &map, bool valid);
308 void Map_UpdateTitle(const Map &map);
310 void Map_SetWorldspawn(Map &map, scene::Node *node);
313 class Map : public ModuleObserver {
316 Resource *m_resource;
321 void ( *m_modified_changed )(const Map &);
323 Signal0 m_mapValidCallbacks;
325 WorldNode m_world_node; // "classname" "worldspawn" !
327 Map() : m_resource(0), m_valid(false), m_modified_changed(Map_UpdateTitle)
333 if (m_resource != 0) {
334 if (Map_Unnamed(*this)) {
335 g_map.m_resource->setNode(NewMapRoot("").get_pointer());
336 MapFile *map = Node_getMapFile(*g_map.m_resource->getNode());
344 GlobalSceneGraph().insert_root(*m_resource->getNode());
348 Map_SetValid(g_map, true);
354 if (m_resource != 0) {
355 Map_SetValid(g_map, false);
356 Map_SetWorldspawn(g_map, 0);
359 GlobalUndoSystem().clear();
361 GlobalSceneGraph().erase_root();
367 Map *g_currentMap = 0;
369 void Map_addValidCallback(Map &map, const SignalHandler &handler)
371 map.m_mapValidCallbacks.connectLast(handler);
374 bool Map_Valid(const Map &map)
379 void Map_SetValid(Map &map, bool valid)
382 map.m_mapValidCallbacks();
386 const char *Map_Name(const Map &map)
388 return map.m_name.c_str();
391 bool Map_Unnamed(const Map &map)
393 return string_equal(Map_Name(map), "unnamed.map");
396 inline const MapFormat &MapFormat_forFile(const char *filename)
398 const char *moduleName = findModuleName(GetFileTypeRegistry(), MapFormat::Name(), path_get_extension(filename));
399 MapFormat *format = Radiant_getMapModules().findModule(moduleName);
400 ASSERT_MESSAGE(format != 0, "map format not found for file " << makeQuoted(filename));
404 const MapFormat &Map_getFormat(const Map &map)
406 return MapFormat_forFile(Map_Name(map));
410 bool Map_Modified(const Map &map)
412 return map.m_modified;
415 void Map_SetModified(Map &map, bool modified)
417 if (map.m_modified ^ modified) {
418 map.m_modified = modified;
420 map.m_modified_changed(map);
424 void Map_UpdateTitle(const Map &map)
426 Sys_SetTitle(map.m_name.c_str(), Map_Modified(map));
430 scene::Node *Map_GetWorldspawn(const Map &map)
432 return map.m_world_node.get();
435 void Map_SetWorldspawn(Map &map, scene::Node *node)
437 map.m_world_node.set(node);
442 // need that in a variable, will have to tweak depending on the game
443 float g_MaxWorldCoord = 64 * 1024;
444 float g_MinWorldCoord = -64 * 1024;
446 void AddRegionBrushes(void);
448 void RemoveRegionBrushes(void);
454 free all map elements, reinitialize the structures that depend on them
461 g_map.m_resource->detach(g_map);
462 GlobalReferenceCache().release(g_map.m_name.c_str());
463 g_map.m_resource = 0;
468 Brush_unlatchPreferences();
471 class EntityFindByClassname : public scene::Graph::Walker {
475 EntityFindByClassname(const char *name, Entity *&entity) : m_name(name), m_entity(entity)
480 bool pre(const scene::Path &path, scene::Instance &instance) const
483 Entity *entity = Node_getEntity(path.top());
485 && string_equal(m_name, entity->getKeyValue("classname"))) {
493 Entity *Scene_FindEntityByClass(const char *name)
496 GlobalSceneGraph().traverse(EntityFindByClassname(name, entity));
500 Entity *Scene_FindPlayerStart()
502 typedef const char *StaticString;
503 StaticString strings[] = {
505 "info_player_deathmatch",
506 "team_CTF_redplayer",
507 "team_CTF_blueplayer",
509 "team_CTF_bluespawn",
511 typedef const StaticString *StaticStringIterator;
512 for (StaticStringIterator i = strings, end = strings + (sizeof(strings) / sizeof(StaticString)); i != end; ++i) {
513 Entity *entity = Scene_FindEntityByClass(*i);
522 // move the view to a start position
526 void FocusViews(const Vector3 &point, float angle)
528 CamWnd &camwnd = *g_pParentWnd->GetCamWnd();
529 Camera_setOrigin(camwnd, point);
530 Vector3 angles(Camera_getAngles(camwnd));
531 angles[CAMERA_PITCH] = 0;
532 angles[CAMERA_YAW] = angle;
533 Camera_setAngles(camwnd, angles);
535 XYWnd *xywnd = g_pParentWnd->GetXYWnd();
536 xywnd->SetOrigin(point);
539 #include "stringio.h"
541 void Map_StartPosition()
543 Entity *entity = Scene_FindPlayerStart();
547 string_parse_vector3(entity->getKeyValue("origin"), origin);
548 FocusViews(origin, string_read_float(entity->getKeyValue("angle")));
550 FocusViews(g_vector3_identity, 0);
555 inline bool node_is_worldspawn(scene::Node &node)
557 Entity *entity = Node_getEntity(node);
558 return entity != 0 && string_equal(entity->getKeyValue("classname"), "worldspawn");
562 // use first worldspawn
563 class entity_updateworldspawn : public scene::Traversable::Walker {
565 bool pre(scene::Node &node) const
567 if (node_is_worldspawn(node)) {
568 if (Map_GetWorldspawn(g_map) == 0) {
569 Map_SetWorldspawn(g_map, &node);
576 scene::Node *Map_FindWorldspawn(Map &map)
578 Map_SetWorldspawn(map, 0);
580 Node_getTraversable(GlobalSceneGraph().root())->traverse(entity_updateworldspawn());
582 return Map_GetWorldspawn(map);
586 class CollectAllWalker : public scene::Traversable::Walker {
588 UnsortedNodeSet &m_nodes;
590 CollectAllWalker(scene::Node &root, UnsortedNodeSet &nodes) : m_root(root), m_nodes(nodes)
594 bool pre(scene::Node &node) const
596 m_nodes.insert(NodeSmartReference(node));
597 Node_getTraversable(m_root)->erase(node);
602 void Node_insertChildFirst(scene::Node &parent, scene::Node &child)
604 UnsortedNodeSet nodes;
605 Node_getTraversable(parent)->traverse(CollectAllWalker(parent, nodes));
606 Node_getTraversable(parent)->insert(child);
608 for (UnsortedNodeSet::iterator i = nodes.begin(); i != nodes.end(); ++i) {
609 Node_getTraversable(parent)->insert((*i));
613 scene::Node &createWorldspawn()
615 NodeSmartReference worldspawn(
616 GlobalEntityCreator().createEntity(GlobalEntityClassManager().findOrInsert("worldspawn", true)));
617 Node_insertChildFirst(GlobalSceneGraph().root(), worldspawn);
621 void Map_UpdateWorldspawn(Map &map)
623 if (Map_FindWorldspawn(map) == 0) {
624 Map_SetWorldspawn(map, &createWorldspawn());
628 scene::Node &Map_FindOrInsertWorldspawn(Map &map)
630 Map_UpdateWorldspawn(map);
631 return *Map_GetWorldspawn(map);
635 class MapMergeAll : public scene::Traversable::Walker {
636 mutable scene::Path m_path;
638 MapMergeAll(const scene::Path &root)
643 bool pre(scene::Node &node) const
645 Node_getTraversable(m_path.top())->insert(node);
646 m_path.push(makeReference(node));
647 selectPath(m_path, true);
651 void post(scene::Node &node) const
657 class MapMergeEntities : public scene::Traversable::Walker {
658 mutable scene::Path m_path;
660 MapMergeEntities(const scene::Path &root)
665 bool pre(scene::Node &node) const
667 if (node_is_worldspawn(node)) {
668 scene::Node *world_node = Map_FindWorldspawn(g_map);
669 if (world_node == 0) {
670 Map_SetWorldspawn(g_map, &node);
671 Node_getTraversable(m_path.top().get())->insert(node);
672 m_path.push(makeReference(node));
673 Node_getTraversable(node)->traverse(SelectChildren(m_path));
675 m_path.push(makeReference(*world_node));
676 Node_getTraversable(node)->traverse(MapMergeAll(m_path));
679 Node_getTraversable(m_path.top())->insert(node);
680 m_path.push(makeReference(node));
681 if (node_is_group(node)) {
682 Node_getTraversable(node)->traverse(SelectChildren(m_path));
684 selectPath(m_path, true);
690 void post(scene::Node &node) const
696 class BasicContainer : public scene::Node::Symbiot {
698 NodeTypeCastTable m_casts;
702 NodeContainedCast<BasicContainer, scene::Traversable>::install(m_casts);
705 NodeTypeCastTable &get()
712 TraversableNodeSet m_traverse;
715 typedef LazyStatic<TypeCasts> StaticTypeCasts;
717 scene::Traversable &get(NullType<scene::Traversable>)
722 BasicContainer() : m_node(this, this, StaticTypeCasts::instance().get())
737 /// Merges the map graph rooted at \p node into the global scene-graph.
738 void MergeMap(scene::Node &node)
740 Node_getTraversable(node)->traverse(MapMergeEntities(scene::Path(makeReference(GlobalSceneGraph().root()))));
743 void Map_ImportSelected(TextInputStream &in, const MapFormat &format)
745 NodeSmartReference node((new BasicContainer)->node());
746 format.readGraph(node, in, GlobalEntityCreator());
747 Map_gatherNamespaced(node);
748 Map_mergeClonedNames();
752 inline scene::Cloneable *Node_getCloneable(scene::Node &node)
754 return NodeTypeCast<scene::Cloneable>::cast(node);
757 inline scene::Node &node_clone(scene::Node &node)
759 scene::Cloneable *cloneable = Node_getCloneable(node);
760 if (cloneable != 0) {
761 return cloneable->clone();
764 return (new scene::NullNode)->node();
767 class CloneAll : public scene::Traversable::Walker {
768 mutable scene::Path m_path;
770 CloneAll(scene::Node &root)
771 : m_path(makeReference(root))
775 bool pre(scene::Node &node) const
781 m_path.push(makeReference(node_clone(node)));
782 m_path.top().get().IncRef();
787 void post(scene::Node &node) const
793 Node_getTraversable(m_path.parent())->insert(m_path.top());
795 m_path.top().get().DecRef();
800 scene::Node &Node_Clone(scene::Node &node)
802 scene::Node &clone = node_clone(node);
803 scene::Traversable *traversable = Node_getTraversable(node);
804 if (traversable != 0) {
805 traversable->traverse(CloneAll(clone));
811 typedef std::map<CopiedString, std::size_t> EntityBreakdown;
813 class EntityBreakdownWalker : public scene::Graph::Walker {
814 EntityBreakdown &m_entitymap;
816 EntityBreakdownWalker(EntityBreakdown &entitymap)
817 : m_entitymap(entitymap)
821 bool pre(const scene::Path &path, scene::Instance &instance) const
823 Entity *entity = Node_getEntity(path.top());
825 const EntityClass &eclass = entity->getEntityClass();
826 if (m_entitymap.find(eclass.name()) == m_entitymap.end()) {
827 m_entitymap[eclass.name()] = 1;
828 } else { ++m_entitymap[eclass.name()]; }
834 void Scene_EntityBreakdown(EntityBreakdown &entitymap)
836 GlobalSceneGraph().traverse(EntityBreakdownWalker(entitymap));
840 WindowPosition g_posMapInfoWnd(c_default_window_pos);
845 ui::Entry brushes_entry{ui::null};
846 ui::Entry entities_entry{ui::null};
847 ui::ListStore EntityBreakdownWalker{ui::null};
849 ui::Window window = MainFrame_getWindow().create_dialog_window("Map Info", G_CALLBACK(dialog_delete_callback),
852 window_set_position(window, g_posMapInfoWnd);
855 auto vbox = create_dialog_vbox(4, 4);
859 auto hbox = create_dialog_hbox(4);
860 vbox.pack_start(hbox, FALSE, TRUE, 0);
863 auto table = create_dialog_table(2, 2, 4, 4);
864 hbox.pack_start(table, TRUE, TRUE, 0);
867 auto entry = ui::Entry(ui::New);
869 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
870 gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
872 brushes_entry = entry;
875 auto entry = ui::Entry(ui::New);
877 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
878 gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
880 entities_entry = entry;
883 ui::Widget label = ui::Label("Total Brushes");
885 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
886 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
889 ui::Widget label = ui::Label("Total Entities");
891 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
892 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
896 auto vbox2 = create_dialog_vbox(4);
897 hbox.pack_start(vbox2, FALSE, FALSE, 0);
900 auto button = create_dialog_button("Close", G_CALLBACK(dialog_button_ok), &dialog);
901 vbox2.pack_start(button, FALSE, FALSE, 0);
906 ui::Widget label = ui::Label("Entity breakdown");
908 vbox.pack_start(label, FALSE, TRUE, 0);
909 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
912 auto scr = create_scrolled_window(ui::Policy::NEVER, ui::Policy::AUTOMATIC, 4);
913 vbox.pack_start(scr, TRUE, TRUE, 0);
916 auto store = ui::ListStore::from(gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING));
918 auto view = ui::TreeView(ui::TreeModel::from(store._handle));
919 gtk_tree_view_set_headers_clickable(view, TRUE);
922 auto renderer = ui::CellRendererText(ui::New);
923 auto column = ui::TreeViewColumn("Entity", renderer, {{"text", 0}});
924 gtk_tree_view_append_column(view, column);
925 gtk_tree_view_column_set_sort_column_id(column, 0);
929 auto renderer = ui::CellRendererText(ui::New);
930 auto column = ui::TreeViewColumn("Count", renderer, {{"text", 1}});
931 gtk_tree_view_append_column(view, column);
932 gtk_tree_view_column_set_sort_column_id(column, 1);
939 EntityBreakdownWalker = store;
947 EntityBreakdown entitymap;
948 Scene_EntityBreakdown(entitymap);
950 for (EntityBreakdown::iterator i = entitymap.begin(); i != entitymap.end(); ++i) {
952 sprintf(tmp, "%u", Unsigned((*i).second));
953 EntityBreakdownWalker.append(0, (*i).first.c_str(), 1, tmp);
957 EntityBreakdownWalker.unref();
960 sprintf(tmp, "%u", Unsigned(g_brushCount.get()));
961 brushes_entry.text(tmp);
962 sprintf(tmp, "%u", Unsigned(g_entityCount.get()));
963 entities_entry.text(tmp);
965 modal_dialog_show(window, dialog);
968 window_get_position(window, g_posMapInfoWnd);
976 const char *m_message;
978 ScopeTimer(const char *message)
986 double elapsed_time = m_timer.elapsed_msec() / 1000.f;
987 globalOutputStream() << m_message << " timer: " << FloatFormat(elapsed_time, 5, 2) << " second(s) elapsed\n";
991 CopiedString g_strLastFolder = "";
999 void Map_LoadFile(const char *filename)
1001 globalOutputStream() << "Loading map from " << filename << "\n";
1002 ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
1004 MRU_AddFile(filename);
1005 g_strLastFolder = g_path_get_dirname(filename);
1008 ScopeTimer timer("map load");
1010 const MapFormat *format = NULL;
1011 const char *moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), path_get_extension(filename));
1012 if (string_not_empty(moduleName)) {
1013 format = ReferenceAPI_getMapModules().findModule(moduleName);
1016 for (int i = 0; i < Brush_toggleFormatCount(); ++i) {
1020 Brush_toggleFormat(i);
1021 g_map.m_name = filename;
1022 Map_UpdateTitle(g_map);
1023 g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str());
1025 format->wrongFormat = false;
1027 g_map.m_resource->attach(g_map);
1029 if (!format->wrongFormat) {
1035 Node_getTraversable(GlobalSceneGraph().root())->traverse(entity_updateworldspawn());
1038 globalOutputStream() << "--- LoadMapFile ---\n";
1039 globalOutputStream() << g_map.m_name.c_str() << "\n";
1041 globalOutputStream() << Unsigned(g_brushCount.get()) << " primitive\n";
1042 globalOutputStream() << Unsigned(g_entityCount.get()) << " entities\n";
1044 //GlobalEntityCreator().printStatistics();
1047 // move the view to a start position
1049 Map_StartPosition();
1051 g_currentMap = &g_map;
1053 // restart VFS to apply new pak filtering based on mapname
1054 // needed for daemon DPK VFS
1060 virtual bool excluded(scene::Node &node) const = 0;
1063 class ExcludeWalker : public scene::Traversable::Walker {
1064 const scene::Traversable::Walker &m_walker;
1065 const Excluder *m_exclude;
1066 mutable bool m_skip;
1068 ExcludeWalker(const scene::Traversable::Walker &walker, const Excluder &exclude)
1069 : m_walker(walker), m_exclude(&exclude), m_skip(false)
1073 bool pre(scene::Node &node) const
1075 if (m_exclude->excluded(node) || node.isRoot()) {
1084 void post(scene::Node &node) const
1089 m_walker.post(node);
1094 class AnyInstanceSelected : public scene::Instantiable::Visitor {
1097 AnyInstanceSelected(bool &selected) : m_selected(selected)
1102 void visit(scene::Instance &instance) const
1104 Selectable *selectable = Instance_getSelectable(instance);
1106 && selectable->isSelected()) {
1112 bool Node_instanceSelected(scene::Node &node)
1114 scene::Instantiable *instantiable = Node_getInstantiable(node);
1115 ASSERT_NOTNULL(instantiable);
1117 instantiable->forEachInstance(AnyInstanceSelected(selected));
1121 class SelectedDescendantWalker : public scene::Traversable::Walker {
1124 SelectedDescendantWalker(bool &selected) : m_selected(selected)
1129 bool pre(scene::Node &node) const
1131 if (node.isRoot()) {
1135 if (Node_instanceSelected(node)) {
1143 bool Node_selectedDescendant(scene::Node &node)
1146 Node_traverseSubgraph(node, SelectedDescendantWalker(selected));
1150 class SelectionExcluder : public Excluder {
1152 bool excluded(scene::Node &node) const
1154 return !Node_selectedDescendant(node);
1158 class IncludeSelectedWalker : public scene::Traversable::Walker {
1159 const scene::Traversable::Walker &m_walker;
1160 mutable std::size_t m_selected;
1161 mutable bool m_skip;
1163 bool selectedParent() const
1165 return m_selected != 0;
1169 IncludeSelectedWalker(const scene::Traversable::Walker &walker)
1170 : m_walker(walker), m_selected(0), m_skip(false)
1174 bool pre(scene::Node &node) const
1177 // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
1178 if (!node.isRoot() && (Node_selectedDescendant(node) || selectedParent())) {
1179 if (Node_instanceSelected(node)) {
1190 void post(scene::Node &node) const
1195 if (Node_instanceSelected(node)) {
1198 m_walker.post(node);
1203 void Map_Traverse_Selected(scene::Node &root, const scene::Traversable::Walker &walker)
1205 scene::Traversable *traversable = Node_getTraversable(root);
1206 if (traversable != 0) {
1208 traversable->traverse( ExcludeWalker( walker, SelectionExcluder() ) );
1210 traversable->traverse(IncludeSelectedWalker(walker));
1215 void Map_ExportSelected(TextOutputStream &out, const MapFormat &format)
1217 format.writeGraph(GlobalSceneGraph().root(), Map_Traverse_Selected, out);
1220 void Map_Traverse(scene::Node &root, const scene::Traversable::Walker &walker)
1222 scene::Traversable *traversable = Node_getTraversable(root);
1223 if (traversable != 0) {
1224 traversable->traverse(walker);
1228 class RegionExcluder : public Excluder {
1230 bool excluded(scene::Node &node) const
1232 return node.excluded();
1236 void Map_Traverse_Region(scene::Node &root, const scene::Traversable::Walker &walker)
1238 scene::Traversable *traversable = Node_getTraversable(root);
1239 if (traversable != 0) {
1240 traversable->traverse(ExcludeWalker(walker, RegionExcluder()));
1244 bool Map_SaveRegion(const char *filename)
1248 bool success = MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Region,
1251 RemoveRegionBrushes();
1257 void Map_RenameAbsolute(const char *absolute)
1259 Resource *resource = GlobalReferenceCache().capture(absolute);
1260 NodeSmartReference clone(NewMapRoot(path_make_relative(absolute, GlobalFileSystem().findRoot(absolute))));
1261 resource->setNode(clone.get_pointer());
1264 //ScopeTimer timer("clone subgraph");
1265 Node_getTraversable(GlobalSceneGraph().root())->traverse(CloneAll(clone));
1268 g_map.m_resource->detach(g_map);
1269 GlobalReferenceCache().release(g_map.m_name.c_str());
1271 g_map.m_resource = resource;
1273 g_map.m_name = absolute;
1274 Map_UpdateTitle(g_map);
1276 g_map.m_resource->attach(g_map);
1277 // refresh VFS to apply new pak filtering based on mapname
1278 // needed for daemon DPK VFS
1282 void Map_Rename(const char *filename)
1284 if (!string_equal(g_map.m_name.c_str(), filename)) {
1285 ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
1287 Map_RenameAbsolute(filename);
1289 SceneChangeNotify();
1299 ScopeTimer timer("map save");
1301 return true; // assume success..
1312 //globalOutputStream() << "Map_New\n";
1314 g_map.m_name = "unnamed.map";
1315 Map_UpdateTitle(g_map);
1318 g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str());
1319 // ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
1320 g_map.m_resource->attach(g_map);
1322 SceneChangeNotify();
1325 FocusViews(g_vector3_identity, 0);
1327 g_currentMap = &g_map;
1329 // restart VFS to apply new pak filtering based on mapname
1330 // needed for daemon DPK VFS
1334 extern void ConstructRegionBrushes(scene::Node *brushes[6], const Vector3 ®ion_mins, const Vector3 ®ion_maxs);
1336 void ConstructRegionStartpoint(scene::Node *startpoint, const Vector3 ®ion_mins, const Vector3 ®ion_maxs)
1339 \todo we need to make sure that the player start IS inside the region and bail out if it's not
1340 the compiler will refuse to compile a map with a player_start somewhere in empty space..
1341 for now, let's just print an error
1344 Vector3 vOrig(Camera_getOrigin(*g_pParentWnd->GetCamWnd()));
1346 for (int i = 0; i < 3; i++) {
1347 if (vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i]) {
1348 globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
1353 // write the info_playerstart
1355 sprintf(sTmp, "%d %d %d", (int) vOrig[0], (int) vOrig[1], (int) vOrig[2]);
1356 Node_getEntity(*startpoint)->setKeyValue("origin", sTmp);
1357 sprintf(sTmp, "%d", (int) Camera_getAngles(*g_pParentWnd->GetCamWnd())[CAMERA_YAW]);
1358 Node_getEntity(*startpoint)->setKeyValue("angle", sTmp);
1362 ===========================================================
1366 ===========================================================
1369 Vector3 region_mins(g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord);
1370 Vector3 region_maxs(g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord);
1372 scene::Node *region_sides[6];
1373 scene::Node *region_startpoint = 0;
1378 a regioned map will have temp walls put up at the region boundary
1379 \todo TODO TTimo old implementation of region brushes
1380 we still add them straight in the worldspawn and take them out after the map is saved
1381 with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
1384 void AddRegionBrushes(void)
1388 for (i = 0; i < 6; i++) {
1389 region_sides[i] = &GlobalBrushCreator().createBrush();
1390 Node_getTraversable(Map_FindOrInsertWorldspawn(g_map))->insert(NodeSmartReference(*region_sides[i]));
1393 region_startpoint = &GlobalEntityCreator().createEntity(
1394 GlobalEntityClassManager().findOrInsert("info_player_start", false));
1396 ConstructRegionBrushes(region_sides, region_mins, region_maxs);
1397 ConstructRegionStartpoint(region_startpoint, region_mins, region_maxs);
1399 Node_getTraversable(GlobalSceneGraph().root())->insert(NodeSmartReference(*region_startpoint));
1402 void RemoveRegionBrushes(void)
1404 for (std::size_t i = 0; i < 6; i++) {
1405 Node_getTraversable(*Map_GetWorldspawn(g_map))->erase(*region_sides[i]);
1407 Node_getTraversable(GlobalSceneGraph().root())->erase(*region_startpoint);
1410 inline void exclude_node(scene::Node &node, bool exclude)
1413 ? node.enable(scene::Node::eExcluded)
1414 : node.disable(scene::Node::eExcluded);
1417 class ExcludeAllWalker : public scene::Graph::Walker {
1420 ExcludeAllWalker(bool exclude)
1421 : m_exclude(exclude)
1425 bool pre(const scene::Path &path, scene::Instance &instance) const
1427 exclude_node(path.top(), m_exclude);
1433 void Scene_Exclude_All(bool exclude)
1435 GlobalSceneGraph().traverse(ExcludeAllWalker(exclude));
1438 bool Instance_isSelected(const scene::Instance &instance)
1440 const Selectable *selectable = Instance_getSelectable(instance);
1441 return selectable != 0 && selectable->isSelected();
1444 class ExcludeSelectedWalker : public scene::Graph::Walker {
1447 ExcludeSelectedWalker(bool exclude)
1448 : m_exclude(exclude)
1452 bool pre(const scene::Path &path, scene::Instance &instance) const
1454 exclude_node(path.top(),
1455 (instance.isSelected() || instance.childSelected() || instance.parentSelected()) == m_exclude);
1460 void Scene_Exclude_Selected(bool exclude)
1462 GlobalSceneGraph().traverse(ExcludeSelectedWalker(exclude));
1465 class ExcludeRegionedWalker : public scene::Graph::Walker {
1468 ExcludeRegionedWalker(bool exclude)
1469 : m_exclude(exclude)
1473 bool pre(const scene::Path &path, scene::Instance &instance) const
1479 aabb_intersects_aabb(
1480 instance.worldAABB(),
1481 aabb_for_minmax(region_mins, region_maxs)
1491 void Scene_Exclude_Region(bool exclude)
1493 GlobalSceneGraph().traverse(ExcludeRegionedWalker(exclude));
1500 Other filtering options may still be on
1503 void Map_RegionOff()
1505 region_active = false;
1507 region_maxs[0] = g_MaxWorldCoord - 64;
1508 region_mins[0] = g_MinWorldCoord + 64;
1509 region_maxs[1] = g_MaxWorldCoord - 64;
1510 region_mins[1] = g_MinWorldCoord + 64;
1511 region_maxs[2] = g_MaxWorldCoord - 64;
1512 region_mins[2] = g_MinWorldCoord + 64;
1514 Scene_Exclude_All(false);
1517 void Map_ApplyRegion(void)
1519 region_active = true;
1521 Scene_Exclude_Region(false);
1526 ========================
1527 Map_RegionSelectedBrushes
1528 ========================
1530 void Map_RegionSelectedBrushes(void)
1534 if (GlobalSelectionSystem().countSelected() != 0
1535 && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive) {
1536 region_active = true;
1537 Select_GetBounds(region_mins, region_maxs);
1539 Scene_Exclude_Selected(false);
1541 GlobalSelectionSystem().setSelectedAll(false);
1551 void Map_RegionXY(float x_min, float y_min, float x_max, float y_max)
1555 region_mins[0] = x_min;
1556 region_maxs[0] = x_max;
1557 region_mins[1] = y_min;
1558 region_maxs[1] = y_max;
1559 region_mins[2] = g_MinWorldCoord + 64;
1560 region_maxs[2] = g_MaxWorldCoord - 64;
1565 void Map_RegionBounds(const AABB &bounds)
1569 region_mins = vector3_subtracted(bounds.origin, bounds.extents);
1570 region_maxs = vector3_added(bounds.origin, bounds.extents);
1582 void Map_RegionBrush(void)
1584 if (GlobalSelectionSystem().countSelected() != 0) {
1585 scene::Instance &instance = GlobalSelectionSystem().ultimateSelected();
1586 Map_RegionBounds(instance.worldAABB());
1595 bool Map_ImportFile(const char *filename)
1597 ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
1599 g_strLastFolder = g_path_get_dirname(filename);
1601 bool success = false;
1603 if (extension_equal(path_get_extension(filename), "bsp")) {
1608 const MapFormat *format = NULL;
1609 const char *moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), path_get_extension(filename));
1610 if (string_not_empty(moduleName)) {
1611 format = ReferenceAPI_getMapModules().findModule(moduleName);
1615 format->wrongFormat = false;
1617 Resource *resource = GlobalReferenceCache().capture(filename);
1618 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1619 if (!resource->load()) {
1620 GlobalReferenceCache().release(filename);
1624 if (format->wrongFormat) {
1625 GlobalReferenceCache().release(filename);
1629 NodeSmartReference clone(NewMapRoot(""));
1630 Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone));
1631 Map_gatherNamespaced(clone);
1632 Map_mergeClonedNames();
1635 GlobalReferenceCache().release(filename);
1638 SceneChangeNotify();
1644 const char *type = GlobalRadiant().getGameDescriptionKeyValue("q3map2_type");
1645 int n = string_length(path_get_extension(filename));
1646 if (n && (extension_equal(path_get_extension(filename), "bsp") ||
1647 extension_equal(path_get_extension(filename), "map"))) {
1648 StringBuffer output;
1649 output.push_string(AppPath_get());
1650 output.push_string("q3map2.");
1651 output.push_string(RADIANT_EXECUTABLE);
1652 output.push_string(" -v -game ");
1653 output.push_string((type && *type) ? type : "quake3");
1654 output.push_string(" -fs_basepath \"");
1655 output.push_string(EnginePath_get());
1656 output.push_string("\" -fs_homepath \"");
1657 output.push_string(g_qeglobals.m_userEnginePath.c_str());
1658 output.push_string("\"");
1661 for (int i = 0; i < g_pakPathCount; i++) {
1662 if (g_strcmp0(g_strPakPath[i].c_str(), "")) {
1663 output.push_string(" -fs_pakpath \"");
1664 output.push_string(g_strPakPath[i].c_str());
1665 output.push_string("\"");
1670 if (g_disableEnginePath) {
1671 output.push_string(" -fs_nobasepath ");
1674 if (g_disableHomePath) {
1675 output.push_string(" -fs_nohomepath ");
1678 output.push_string(" -fs_game ");
1679 output.push_string(gamename_get());
1680 output.push_string(" -convert -format ");
1681 output.push_string(Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map");
1682 if (extension_equal(path_get_extension(filename), "map")) {
1683 output.push_string(" -readmap ");
1685 output.push_string(" \"");
1686 output.push_string(filename);
1687 output.push_string("\"");
1690 Q_Exec(NULL, output.c_str(), NULL, false, true);
1692 // rebuild filename as "filenamewithoutext_converted.map"
1694 output.push_range(filename, filename + string_length(filename) - (n + 1));
1695 output.push_string("_converted.map");
1696 filename = output.c_str();
1699 Resource *resource = GlobalReferenceCache().capture(filename);
1700 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1701 if (!resource->load()) {
1702 GlobalReferenceCache().release(filename);
1705 NodeSmartReference clone(NewMapRoot(""));
1706 Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone));
1707 Map_gatherNamespaced(clone);
1708 Map_mergeClonedNames();
1711 GlobalReferenceCache().release(filename);
1714 SceneChangeNotify();
1723 bool Map_SaveFile(const char *filename)
1725 ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
1726 bool success = MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse, filename);
1728 // refresh VFS to apply new pak filtering based on mapname
1729 // needed for daemon DPK VFS
1740 // Saves selected world brushes and whole entities with partial/full selections
1742 bool Map_SaveSelected(const char *filename)
1744 return MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Selected,
1749 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker {
1750 scene::Node &m_parent;
1752 ParentSelectedBrushesToEntityWalker(scene::Node &parent) : m_parent(parent)
1756 bool pre(const scene::Path &path, scene::Instance &instance) const
1758 if (path.top().get_pointer() != &m_parent
1759 && Node_isPrimitive(path.top())) {
1760 Selectable *selectable = Instance_getSelectable(instance);
1762 && selectable->isSelected()
1763 && path.size() > 1) {
1770 void post(const scene::Path &path, scene::Instance &instance) const
1772 if (path.top().get_pointer() != &m_parent
1773 && Node_isPrimitive(path.top())) {
1774 Selectable *selectable = Instance_getSelectable(instance);
1776 && selectable->isSelected()
1777 && path.size() > 1) {
1778 scene::Node &parent = path.parent();
1779 if (&parent != &m_parent) {
1780 NodeSmartReference node(path.top().get());
1781 Node_getTraversable(parent)->erase(node);
1782 Node_getTraversable(m_parent)->insert(node);
1789 void Scene_parentSelectedBrushesToEntity(scene::Graph &graph, scene::Node &parent)
1791 graph.traverse(ParentSelectedBrushesToEntityWalker(parent));
1794 class CountSelectedBrushes : public scene::Graph::Walker {
1795 std::size_t &m_count;
1796 mutable std::size_t m_depth;
1798 CountSelectedBrushes(std::size_t &count) : m_count(count), m_depth(0)
1803 bool pre(const scene::Path &path, scene::Instance &instance) const
1805 if (++m_depth != 1 && path.top().get().isRoot()) {
1808 Selectable *selectable = Instance_getSelectable(instance);
1810 && selectable->isSelected()
1811 && Node_isPrimitive(path.top())) {
1817 void post(const scene::Path &path, scene::Instance &instance) const
1823 std::size_t Scene_countSelectedBrushes(scene::Graph &graph)
1826 graph.traverse(CountSelectedBrushes(count));
1837 const char *nodetype_get_name(ENodeType type)
1839 if (type == eNodeMap) {
1842 if (type == eNodeEntity) {
1845 if (type == eNodePrimitive) {
1851 ENodeType node_get_nodetype(scene::Node &node)
1853 if (Node_isEntity(node)) {
1856 if (Node_isPrimitive(node)) {
1857 return eNodePrimitive;
1859 return eNodeUnknown;
1862 bool contains_entity(scene::Node &node)
1864 return Node_getTraversable(node) != 0 && !Node_isBrush(node) && !Node_isPatch(node) && !Node_isEntity(node);
1867 bool contains_primitive(scene::Node &node)
1869 return Node_isEntity(node) && Node_getTraversable(node) != 0 && Node_getEntity(node)->isContainer();
1872 ENodeType node_get_contains(scene::Node &node)
1874 if (contains_entity(node)) {
1877 if (contains_primitive(node)) {
1878 return eNodePrimitive;
1880 return eNodeUnknown;
1883 void Path_parent(const scene::Path &parent, const scene::Path &child)
1885 ENodeType contains = node_get_contains(parent.top());
1886 ENodeType type = node_get_nodetype(child.top());
1888 if (contains != eNodeUnknown && contains == type) {
1889 NodeSmartReference node(child.top().get());
1890 Path_deleteTop(child);
1891 Node_getTraversable(parent.top())->insert(node);
1892 SceneChangeNotify();
1894 globalErrorStream() << "failed - " << nodetype_get_name(type) << " cannot be parented to "
1895 << nodetype_get_name(contains) << " container.\n";
1899 void Scene_parentSelected()
1901 UndoableCommand undo("parentSelected");
1903 if (GlobalSelectionSystem().countSelected() > 1) {
1904 class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor {
1905 const scene::Path &m_parent;
1907 ParentSelectedBrushesToEntityWalker(const scene::Path &parent) : m_parent(parent)
1911 void visit(scene::Instance &instance) const
1913 if (&m_parent != &instance.path()) {
1914 Path_parent(m_parent, instance.path());
1919 ParentSelectedBrushesToEntityWalker visitor(GlobalSelectionSystem().ultimateSelected().path());
1920 GlobalSelectionSystem().foreachSelected(visitor);
1922 globalOutputStream() << "failed - did not find two selected nodes.\n";
1929 if (ConfirmModified("New Map")) {
1936 CopiedString g_mapsPath;
1938 const char *getMapsPath()
1940 return g_mapsPath.c_str();
1943 const char *getLastFolderPath()
1945 if (g_strLastFolder.empty()) {
1946 GlobalPreferenceSystem().registerPreference("LastFolder", make_property_string(g_strLastFolder));
1947 if (g_strLastFolder.empty()) {
1948 g_strLastFolder = g_qeglobals.m_userGamePath;
1951 return g_strLastFolder.c_str();
1954 const char *map_open(const char *title)
1956 return MainFrame_getWindow().file_dialog(TRUE, title, getLastFolderPath(), MapFormat::Name(), true, false, false);
1959 const char *map_import(const char *title)
1961 return MainFrame_getWindow().file_dialog(TRUE, title, getLastFolderPath(), MapFormat::Name(), false, true, false);
1964 const char *map_save(const char *title)
1966 return MainFrame_getWindow().file_dialog(FALSE, title, getLastFolderPath(), MapFormat::Name(), false, false, true);
1971 if (!ConfirmModified("Open Map")) {
1975 const char *filename = map_open("Open Map");
1977 if (filename != NULL) {
1978 MRU_AddFile(filename);
1981 Map_LoadFile(filename);
1987 const char *filename = map_import("Import Map");
1989 if (filename != NULL) {
1990 UndoableCommand undo("mapImport");
1991 Map_ImportFile(filename);
1997 const char *filename = map_save("Save Map");
1999 if (filename != NULL) {
2000 g_strLastFolder = g_path_get_dirname(filename);
2001 MRU_AddFile(filename);
2002 Map_Rename(filename);
2015 if (Map_Unnamed(g_map)) {
2017 } else if (Map_Modified(g_map)) {
2024 const char *filename = map_save("Export Selection");
2026 if (filename != NULL) {
2027 g_strLastFolder = g_path_get_dirname(filename);
2028 Map_SaveSelected(filename);
2034 const char *filename = map_save("Export Region");
2036 if (filename != NULL) {
2037 g_strLastFolder = g_path_get_dirname(filename);
2038 Map_SaveRegion(filename);
2046 SceneChangeNotify();
2052 g_pParentWnd->GetXYWnd()->GetOrigin()[0] -
2053 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
2054 g_pParentWnd->GetXYWnd()->GetOrigin()[1] -
2055 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
2056 g_pParentWnd->GetXYWnd()->GetOrigin()[0] +
2057 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
2058 g_pParentWnd->GetXYWnd()->GetOrigin()[1] +
2059 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
2061 SceneChangeNotify();
2067 SceneChangeNotify();
2070 void RegionSelected()
2072 Map_RegionSelectedBrushes();
2073 SceneChangeNotify();
2077 class BrushFindByIndexWalker : public scene::Traversable::Walker {
2078 mutable std::size_t m_index;
2079 scene::Path &m_path;
2081 BrushFindByIndexWalker(std::size_t index, scene::Path &path)
2082 : m_index(index), m_path(path)
2086 bool pre(scene::Node &node) const
2088 if (Node_isPrimitive(node) && m_index-- == 0) {
2089 m_path.push(makeReference(node));
2095 class EntityFindByIndexWalker : public scene::Traversable::Walker {
2096 mutable std::size_t m_index;
2097 scene::Path &m_path;
2099 EntityFindByIndexWalker(std::size_t index, scene::Path &path)
2100 : m_index(index), m_path(path)
2104 bool pre(scene::Node &node) const
2106 if (Node_isEntity(node) && m_index-- == 0) {
2107 m_path.push(makeReference(node));
2113 void Scene_FindEntityBrush(std::size_t entity, std::size_t brush, scene::Path &path)
2115 path.push(makeReference(GlobalSceneGraph().root()));
2117 Node_getTraversable(path.top())->traverse(EntityFindByIndexWalker(entity, path));
2119 if (path.size() == 2) {
2120 scene::Traversable *traversable = Node_getTraversable(path.top());
2121 if (traversable != 0) {
2122 traversable->traverse(BrushFindByIndexWalker(brush, path));
2127 inline bool Node_hasChildren(scene::Node &node)
2129 scene::Traversable *traversable = Node_getTraversable(node);
2130 return traversable != 0 && !traversable->empty();
2133 void SelectBrush(int entitynum, int brushnum)
2136 Scene_FindEntityBrush(entitynum, brushnum, path);
2137 if (path.size() == 3 || (path.size() == 2 && !Node_hasChildren(path.top()))) {
2138 scene::Instance *instance = GlobalSceneGraph().find(path);
2139 ASSERT_MESSAGE(instance != 0, "SelectBrush: path not found in scenegraph");
2140 Selectable *selectable = Instance_getSelectable(*instance);
2141 ASSERT_MESSAGE(selectable != 0, "SelectBrush: path not selectable");
2142 selectable->setSelected(true);
2143 g_pParentWnd->GetXYWnd()->PositionView(instance->worldAABB().origin);
2148 class BrushFindIndexWalker : public scene::Graph::Walker {
2149 mutable const scene::Node *m_node;
2150 std::size_t &m_count;
2152 BrushFindIndexWalker(const scene::Node &node, std::size_t &count)
2153 : m_node(&node), m_count(count)
2157 bool pre(const scene::Path &path, scene::Instance &instance) const
2159 if (Node_isPrimitive(path.top())) {
2160 if (m_node == path.top().get_pointer()) {
2171 class EntityFindIndexWalker : public scene::Graph::Walker {
2172 mutable const scene::Node *m_node;
2173 std::size_t &m_count;
2175 EntityFindIndexWalker(const scene::Node &node, std::size_t &count)
2176 : m_node(&node), m_count(count)
2180 bool pre(const scene::Path &path, scene::Instance &instance) const
2182 if (Node_isEntity(path.top())) {
2183 if (m_node == path.top().get_pointer()) {
2194 static void GetSelectionIndex(int *ent, int *brush)
2196 std::size_t count_brush = 0;
2197 std::size_t count_entity = 0;
2198 if (GlobalSelectionSystem().countSelected() != 0) {
2199 const scene::Path &path = GlobalSelectionSystem().ultimateSelected().path();
2201 GlobalSceneGraph().traverse(BrushFindIndexWalker(path.top(), count_brush));
2202 GlobalSceneGraph().traverse(EntityFindIndexWalker(path.parent(), count_entity));
2204 *brush = int(count_brush);
2205 *ent = int(count_entity);
2211 ui::Entry entity{ui::null};
2212 ui::Entry brush{ui::null};
2214 ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback),
2217 auto accel = ui::AccelGroup(ui::New);
2218 window.add_accel_group(accel);
2221 auto vbox = create_dialog_vbox(4, 4);
2224 auto table = create_dialog_table(2, 2, 4, 4);
2225 vbox.pack_start(table, TRUE, TRUE, 0);
2227 ui::Widget label = ui::Label("Entity number");
2229 (table).attach(label, {0, 1, 0, 1}, {0, 0});
2232 ui::Widget label = ui::Label("Brush number");
2234 (table).attach(label, {0, 1, 1, 2}, {0, 0});
2237 auto entry = ui::Entry(ui::New);
2239 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
2240 gtk_widget_grab_focus(entry);
2244 auto entry = ui::Entry(ui::New);
2246 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
2252 auto hbox = create_dialog_hbox(4);
2253 vbox.pack_start(hbox, TRUE, TRUE, 0);
2255 auto button = create_dialog_button("Find", G_CALLBACK(dialog_button_ok), &dialog);
2256 hbox.pack_start(button, FALSE, FALSE, 0);
2257 widget_make_default(button);
2258 gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Return, (GdkModifierType) 0,
2262 auto button = create_dialog_button("Close", G_CALLBACK(dialog_button_cancel), &dialog);
2263 hbox.pack_start(button, FALSE, FALSE, 0);
2264 gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Escape, (GdkModifierType) 0,
2270 // Initialize dialog
2274 GetSelectionIndex(&ent, &br);
2275 sprintf(buf, "%i", ent);
2277 sprintf(buf, "%i", br);
2280 if (modal_dialog_show(window, dialog) == eIDOK) {
2281 const char *entstr = gtk_entry_get_text(entity);
2282 const char *brushstr = gtk_entry_get_text(brush);
2283 SelectBrush(atoi(entstr), atoi(brushstr));
2289 void Map_constructPreferences(PreferencesPage &page)
2291 page.appendCheckBox("", "Load last map on open", g_bLoadLastMap);
2295 class MapEntityClasses : public ModuleObserver {
2296 std::size_t m_unrealised;
2298 MapEntityClasses() : m_unrealised(1)
2304 if (--m_unrealised == 0) {
2305 if (g_map.m_resource != 0) {
2306 ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
2307 g_map.m_resource->realise();
2314 if (++m_unrealised == 1) {
2315 if (g_map.m_resource != 0) {
2316 g_map.m_resource->flush();
2317 g_map.m_resource->unrealise();
2323 MapEntityClasses g_MapEntityClasses;
2326 class MapModuleObserver : public ModuleObserver {
2327 std::size_t m_unrealised;
2329 MapModuleObserver() : m_unrealised(1)
2335 if (--m_unrealised == 0) {
2336 ASSERT_MESSAGE(!string_empty(g_qeglobals.m_userGamePath.c_str()),
2337 "maps_directory: user-game-path is empty");
2338 StringOutputStream buffer(256);
2339 buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2340 Q_mkdir(buffer.c_str());
2341 g_mapsPath = buffer.c_str();
2347 if (++m_unrealised == 1) {
2353 MapModuleObserver g_MapModuleObserver;
2355 CopiedString g_strLastMap;
2356 bool g_bLoadLastMap = false;
2358 void Map_Construct()
2360 GlobalCommands_insert("RegionOff", makeCallbackF(RegionOff));
2361 GlobalCommands_insert("RegionSetXY", makeCallbackF(RegionXY));
2362 GlobalCommands_insert("RegionSetBrush", makeCallbackF(RegionBrush));
2363 GlobalCommands_insert("RegionSetSelection", makeCallbackF(RegionSelected),
2364 Accelerator('R', (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK)));
2366 GlobalPreferenceSystem().registerPreference("LastMap", make_property_string(g_strLastMap));
2367 GlobalPreferenceSystem().registerPreference("LoadLastMap", make_property_string(g_bLoadLastMap));
2368 GlobalPreferenceSystem().registerPreference("MapInfoDlg", make_property<WindowPosition_String>(g_posMapInfoWnd));
2370 PreferencesDialog_addSettingsPreferences(makeCallbackF(Map_constructPreferences));
2372 GlobalEntityClassManager().attach(g_MapEntityClasses);
2373 Radiant_attachHomePathsObserver(g_MapModuleObserver);
2378 Radiant_detachHomePathsObserver(g_MapModuleObserver);
2379 GlobalEntityClassManager().detach(g_MapEntityClasses);