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"
97 //globalOutputStream() << "construct " << makeQuoted(c_str()) << "\n";
98 m_names.insert(name_read(c_str()));
105 //globalOutputStream() << "destroy " << makeQuoted(c_str()) << "\n";
106 m_names.erase(name_read(c_str()));
110 NameObserver &operator=(const NameObserver &other);
113 NameObserver(UniqueNames &names) : m_names(names)
118 NameObserver(const NameObserver &other) : m_names(other.m_names), m_name(other.m_name)
130 return string_empty(c_str());
133 const char *c_str() const
135 return m_name.c_str();
138 void nameChanged(const char *name)
145 typedef MemberCaller<NameObserver, void(const char *), &NameObserver::nameChanged> NameChangedCaller;
148 class BasicNamespace : public Namespace {
149 typedef std::map<NameCallback, NameObserver> Names;
151 UniqueNames m_uniqueNames;
155 ASSERT_MESSAGE(m_names.empty(), "namespace: names still registered at shutdown");
158 void attach(const NameCallback &setName, const NameCallbackCallback &attachObserver)
160 std::pair<Names::iterator, bool> result = m_names.insert(Names::value_type(setName, m_uniqueNames));
161 ASSERT_MESSAGE(result.second, "cannot attach name");
162 attachObserver(NameObserver::NameChangedCaller((*result.first).second));
163 //globalOutputStream() << "attach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
166 void detach(const NameCallback &setName, const NameCallbackCallback &detachObserver)
168 Names::iterator i = m_names.find(setName);
169 ASSERT_MESSAGE(i != m_names.end(), "cannot detach name");
170 //globalOutputStream() << "detach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
171 detachObserver(NameObserver::NameChangedCaller((*i).second));
175 void makeUnique(const char *name, const NameCallback &setName) const
178 name_write(buffer, m_uniqueNames.make_unique(name_read(name)));
182 void mergeNames(const BasicNamespace &other) const
184 typedef std::list<NameCallback> SetNameCallbacks;
185 typedef std::map<CopiedString, SetNameCallbacks> NameGroups;
188 UniqueNames uniqueNames(other.m_uniqueNames);
190 for (Names::const_iterator i = m_names.begin(); i != m_names.end(); ++i) {
191 groups[(*i).second.c_str()].push_back((*i).first);
194 for (NameGroups::iterator i = groups.begin(); i != groups.end(); ++i) {
195 name_t uniqueName(uniqueNames.make_unique(name_read((*i).first.c_str())));
196 uniqueNames.insert(uniqueName);
199 name_write(buffer, uniqueName);
201 //globalOutputStream() << "renaming " << makeQuoted((*i).first.c_str()) << " to " << makeQuoted(buffer) << "\n";
203 SetNameCallbacks &setNameCallbacks = (*i).second;
205 for (SetNameCallbacks::const_iterator j = setNameCallbacks.begin(); j != setNameCallbacks.end(); ++j) {
212 BasicNamespace g_defaultNamespace;
213 BasicNamespace g_cloneNamespace;
216 Namespace *m_namespace;
218 typedef Namespace Type;
220 STRING_CONSTANT(Name, "*");
224 m_namespace = &g_defaultNamespace;
227 Namespace *getTable()
233 typedef SingletonModule<NamespaceAPI> NamespaceModule;
234 typedef Static<NamespaceModule> StaticNamespaceModule;
235 StaticRegisterModule staticRegisterDefaultNamespace(StaticNamespaceModule::instance());
238 std::list<Namespaced *> g_cloned;
240 inline Namespaced *Node_getNamespaced(scene::Node &node)
242 return NodeTypeCast<Namespaced>::cast(node);
245 void Node_gatherNamespaced(scene::Node &node)
247 Namespaced *namespaced = Node_getNamespaced(node);
248 if (namespaced != 0) {
249 g_cloned.push_back(namespaced);
253 class GatherNamespaced : public scene::Traversable::Walker {
255 bool pre(scene::Node &node) const
257 Node_gatherNamespaced(node);
262 void Map_gatherNamespaced(scene::Node &root)
264 Node_traverseSubgraph(root, GatherNamespaced());
267 void Map_mergeClonedNames()
269 for (std::list<Namespaced *>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i) {
270 (*i)->setNamespace(g_cloneNamespace);
272 g_cloneNamespace.mergeNames(g_defaultNamespace);
273 for (std::list<Namespaced *>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i) {
274 (*i)->setNamespace(g_defaultNamespace);
288 void set(scene::Node *node)
299 scene::Node *get() const
307 void Map_SetValid(Map &map, bool valid);
309 void Map_UpdateTitle(const Map &map);
311 void Map_SetWorldspawn(Map &map, scene::Node *node);
314 class Map : public ModuleObserver {
317 Resource *m_resource;
322 void ( *m_modified_changed )(const Map &);
324 Signal0 m_mapValidCallbacks;
326 WorldNode m_world_node; // "classname" "worldspawn" !
328 Map() : m_resource(0), m_valid(false), m_modified_changed(Map_UpdateTitle)
334 if (m_resource != 0) {
335 if (Map_Unnamed(*this)) {
336 g_map.m_resource->setNode(NewMapRoot("").get_pointer());
337 MapFile *map = Node_getMapFile(*g_map.m_resource->getNode());
345 GlobalSceneGraph().insert_root(*m_resource->getNode());
349 Map_SetValid(g_map, true);
355 if (m_resource != 0) {
356 Map_SetValid(g_map, false);
357 Map_SetWorldspawn(g_map, 0);
360 GlobalUndoSystem().clear();
362 GlobalSceneGraph().erase_root();
368 Map *g_currentMap = 0;
370 void Map_addValidCallback(Map &map, const SignalHandler &handler)
372 map.m_mapValidCallbacks.connectLast(handler);
375 bool Map_Valid(const Map &map)
380 void Map_SetValid(Map &map, bool valid)
383 map.m_mapValidCallbacks();
387 const char *Map_Name(const Map &map)
389 return map.m_name.c_str();
392 bool Map_Unnamed(const Map &map)
394 return string_equal(Map_Name(map), "unnamed.map");
397 inline const MapFormat &MapFormat_forFile(const char *filename)
399 const char *moduleName = findModuleName(GetFileTypeRegistry(), MapFormat::Name(), path_get_extension(filename));
400 MapFormat *format = Radiant_getMapModules().findModule(moduleName);
401 ASSERT_MESSAGE(format != 0, "map format not found for file " << makeQuoted(filename));
405 const MapFormat &Map_getFormat(const Map &map)
407 return MapFormat_forFile(Map_Name(map));
411 bool Map_Modified(const Map &map)
413 return map.m_modified;
416 void Map_SetModified(Map &map, bool modified)
418 if (map.m_modified ^ modified) {
419 map.m_modified = modified;
421 map.m_modified_changed(map);
425 void Map_UpdateTitle(const Map &map)
427 Sys_SetTitle(map.m_name.c_str(), Map_Modified(map));
431 scene::Node *Map_GetWorldspawn(const Map &map)
433 return map.m_world_node.get();
436 void Map_SetWorldspawn(Map &map, scene::Node *node)
438 map.m_world_node.set(node);
443 // need that in a variable, will have to tweak depending on the game
444 float g_MaxWorldCoord = 64 * 1024;
445 float g_MinWorldCoord = -64 * 1024;
447 void AddRegionBrushes(void);
449 void RemoveRegionBrushes(void);
455 free all map elements, reinitialize the structures that depend on them
462 g_map.m_resource->detach(g_map);
463 GlobalReferenceCache().release(g_map.m_name.c_str());
464 g_map.m_resource = 0;
469 Brush_unlatchPreferences();
472 class EntityFindByClassname : public scene::Graph::Walker {
476 EntityFindByClassname(const char *name, Entity *&entity) : m_name(name), m_entity(entity)
481 bool pre(const scene::Path &path, scene::Instance &instance) const
484 Entity *entity = Node_getEntity(path.top());
486 && string_equal(m_name, entity->getKeyValue("classname"))) {
494 Entity *Scene_FindEntityByClass(const char *name)
497 GlobalSceneGraph().traverse(EntityFindByClassname(name, entity));
501 Entity *Scene_FindPlayerStart()
503 typedef const char *StaticString;
504 StaticString strings[] = {
506 "info_player_deathmatch",
507 "team_CTF_redplayer",
508 "team_CTF_blueplayer",
510 "team_CTF_bluespawn",
512 typedef const StaticString *StaticStringIterator;
513 for (StaticStringIterator i = strings, end = strings + (sizeof(strings) / sizeof(StaticString)); i != end; ++i) {
514 Entity *entity = Scene_FindEntityByClass(*i);
523 // move the view to a start position
527 void FocusViews(const Vector3 &point, float angle)
529 CamWnd &camwnd = *g_pParentWnd->GetCamWnd();
530 Camera_setOrigin(camwnd, point);
531 Vector3 angles(Camera_getAngles(camwnd));
532 angles[CAMERA_PITCH] = 0;
533 angles[CAMERA_YAW] = angle;
534 Camera_setAngles(camwnd, angles);
536 XYWnd *xywnd = g_pParentWnd->GetXYWnd();
537 xywnd->SetOrigin(point);
540 #include "stringio.h"
542 void Map_StartPosition()
544 Entity *entity = Scene_FindPlayerStart();
548 string_parse_vector3(entity->getKeyValue("origin"), origin);
549 FocusViews(origin, string_read_float(entity->getKeyValue("angle")));
551 FocusViews(g_vector3_identity, 0);
556 inline bool node_is_worldspawn(scene::Node &node)
558 Entity *entity = Node_getEntity(node);
559 return entity != 0 && string_equal(entity->getKeyValue("classname"), "worldspawn");
563 // use first worldspawn
564 class entity_updateworldspawn : public scene::Traversable::Walker {
566 bool pre(scene::Node &node) const
568 if (node_is_worldspawn(node)) {
569 if (Map_GetWorldspawn(g_map) == 0) {
570 Map_SetWorldspawn(g_map, &node);
577 scene::Node *Map_FindWorldspawn(Map &map)
579 Map_SetWorldspawn(map, 0);
581 Node_getTraversable(GlobalSceneGraph().root())->traverse(entity_updateworldspawn());
583 return Map_GetWorldspawn(map);
587 class CollectAllWalker : public scene::Traversable::Walker {
589 UnsortedNodeSet &m_nodes;
591 CollectAllWalker(scene::Node &root, UnsortedNodeSet &nodes) : m_root(root), m_nodes(nodes)
595 bool pre(scene::Node &node) const
597 m_nodes.insert(NodeSmartReference(node));
598 Node_getTraversable(m_root)->erase(node);
603 void Node_insertChildFirst(scene::Node &parent, scene::Node &child)
605 UnsortedNodeSet nodes;
606 Node_getTraversable(parent)->traverse(CollectAllWalker(parent, nodes));
607 Node_getTraversable(parent)->insert(child);
609 for (UnsortedNodeSet::iterator i = nodes.begin(); i != nodes.end(); ++i) {
610 Node_getTraversable(parent)->insert((*i));
614 scene::Node &createWorldspawn()
616 NodeSmartReference worldspawn(
617 GlobalEntityCreator().createEntity(GlobalEntityClassManager().findOrInsert("worldspawn", true)));
618 Node_insertChildFirst(GlobalSceneGraph().root(), worldspawn);
622 void Map_UpdateWorldspawn(Map &map)
624 if (Map_FindWorldspawn(map) == 0) {
625 Map_SetWorldspawn(map, &createWorldspawn());
629 scene::Node &Map_FindOrInsertWorldspawn(Map &map)
631 Map_UpdateWorldspawn(map);
632 return *Map_GetWorldspawn(map);
636 class MapMergeAll : public scene::Traversable::Walker {
637 mutable scene::Path m_path;
639 MapMergeAll(const scene::Path &root)
644 bool pre(scene::Node &node) const
646 Node_getTraversable(m_path.top())->insert(node);
647 m_path.push(makeReference(node));
648 selectPath(m_path, true);
652 void post(scene::Node &node) const
658 class MapMergeEntities : public scene::Traversable::Walker {
659 mutable scene::Path m_path;
661 MapMergeEntities(const scene::Path &root)
666 bool pre(scene::Node &node) const
668 if (node_is_worldspawn(node)) {
669 scene::Node *world_node = Map_FindWorldspawn(g_map);
670 if (world_node == 0) {
671 Map_SetWorldspawn(g_map, &node);
672 Node_getTraversable(m_path.top().get())->insert(node);
673 m_path.push(makeReference(node));
674 Node_getTraversable(node)->traverse(SelectChildren(m_path));
676 m_path.push(makeReference(*world_node));
677 Node_getTraversable(node)->traverse(MapMergeAll(m_path));
680 Node_getTraversable(m_path.top())->insert(node);
681 m_path.push(makeReference(node));
682 if (node_is_group(node)) {
683 Node_getTraversable(node)->traverse(SelectChildren(m_path));
685 selectPath(m_path, true);
691 void post(scene::Node &node) const
697 class BasicContainer : public scene::Node::Symbiot {
699 NodeTypeCastTable m_casts;
703 NodeContainedCast<BasicContainer, scene::Traversable>::install(m_casts);
706 NodeTypeCastTable &get()
713 TraversableNodeSet m_traverse;
716 typedef LazyStatic<TypeCasts> StaticTypeCasts;
718 scene::Traversable &get(NullType<scene::Traversable>)
723 BasicContainer() : m_node(this, this, StaticTypeCasts::instance().get())
738 /// Merges the map graph rooted at \p node into the global scene-graph.
739 void MergeMap(scene::Node &node)
741 Node_getTraversable(node)->traverse(MapMergeEntities(scene::Path(makeReference(GlobalSceneGraph().root()))));
744 void Map_ImportSelected(TextInputStream &in, const MapFormat &format)
746 NodeSmartReference node((new BasicContainer)->node());
747 format.readGraph(node, in, GlobalEntityCreator());
748 Map_gatherNamespaced(node);
749 Map_mergeClonedNames();
753 inline scene::Cloneable *Node_getCloneable(scene::Node &node)
755 return NodeTypeCast<scene::Cloneable>::cast(node);
758 inline scene::Node &node_clone(scene::Node &node)
760 scene::Cloneable *cloneable = Node_getCloneable(node);
761 if (cloneable != 0) {
762 return cloneable->clone();
765 return (new scene::NullNode)->node();
768 class CloneAll : public scene::Traversable::Walker {
769 mutable scene::Path m_path;
771 CloneAll(scene::Node &root)
772 : m_path(makeReference(root))
776 bool pre(scene::Node &node) const
782 m_path.push(makeReference(node_clone(node)));
783 m_path.top().get().IncRef();
788 void post(scene::Node &node) const
794 Node_getTraversable(m_path.parent())->insert(m_path.top());
796 m_path.top().get().DecRef();
801 scene::Node &Node_Clone(scene::Node &node)
803 scene::Node &clone = node_clone(node);
804 scene::Traversable *traversable = Node_getTraversable(node);
805 if (traversable != 0) {
806 traversable->traverse(CloneAll(clone));
812 typedef std::map<CopiedString, std::size_t> EntityBreakdown;
814 class EntityBreakdownWalker : public scene::Graph::Walker {
815 EntityBreakdown &m_entitymap;
817 EntityBreakdownWalker(EntityBreakdown &entitymap)
818 : m_entitymap(entitymap)
822 bool pre(const scene::Path &path, scene::Instance &instance) const
824 Entity *entity = Node_getEntity(path.top());
826 const EntityClass &eclass = entity->getEntityClass();
827 if (m_entitymap.find(eclass.name()) == m_entitymap.end()) {
828 m_entitymap[eclass.name()] = 1;
829 } else { ++m_entitymap[eclass.name()]; }
835 void Scene_EntityBreakdown(EntityBreakdown &entitymap)
837 GlobalSceneGraph().traverse(EntityBreakdownWalker(entitymap));
841 WindowPosition g_posMapInfoWnd(c_default_window_pos);
846 ui::Entry brushes_entry{ui::null};
847 ui::Entry entities_entry{ui::null};
848 ui::ListStore EntityBreakdownWalker{ui::null};
850 ui::Window window = MainFrame_getWindow().create_dialog_window("Map Info", G_CALLBACK(dialog_delete_callback),
853 window_set_position(window, g_posMapInfoWnd);
856 auto vbox = create_dialog_vbox(4, 4);
860 auto hbox = create_dialog_hbox(4);
861 vbox.pack_start(hbox, FALSE, TRUE, 0);
864 auto table = create_dialog_table(2, 2, 4, 4);
865 hbox.pack_start(table, TRUE, TRUE, 0);
868 auto entry = ui::Entry(ui::New);
870 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
871 gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
873 brushes_entry = entry;
876 auto entry = ui::Entry(ui::New);
878 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
879 gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
881 entities_entry = entry;
884 ui::Widget label = ui::Label("Total Brushes");
886 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
887 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
890 ui::Widget label = ui::Label("Total Entities");
892 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
893 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
897 auto vbox2 = create_dialog_vbox(4);
898 hbox.pack_start(vbox2, FALSE, FALSE, 0);
901 auto button = create_dialog_button("Close", G_CALLBACK(dialog_button_ok), &dialog);
902 vbox2.pack_start(button, FALSE, FALSE, 0);
907 ui::Widget label = ui::Label("Entity breakdown");
909 vbox.pack_start(label, FALSE, TRUE, 0);
910 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
913 auto scr = create_scrolled_window(ui::Policy::NEVER, ui::Policy::AUTOMATIC, 4);
914 vbox.pack_start(scr, TRUE, TRUE, 0);
917 auto store = ui::ListStore::from(gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING));
919 auto view = ui::TreeView(ui::TreeModel::from(store._handle));
920 gtk_tree_view_set_headers_clickable(view, TRUE);
923 auto renderer = ui::CellRendererText(ui::New);
924 auto column = ui::TreeViewColumn("Entity", renderer, {{"text", 0}});
925 gtk_tree_view_append_column(view, column);
926 gtk_tree_view_column_set_sort_column_id(column, 0);
930 auto renderer = ui::CellRendererText(ui::New);
931 auto column = ui::TreeViewColumn("Count", renderer, {{"text", 1}});
932 gtk_tree_view_append_column(view, column);
933 gtk_tree_view_column_set_sort_column_id(column, 1);
940 EntityBreakdownWalker = store;
948 EntityBreakdown entitymap;
949 Scene_EntityBreakdown(entitymap);
951 for (EntityBreakdown::iterator i = entitymap.begin(); i != entitymap.end(); ++i) {
953 sprintf(tmp, "%u", Unsigned((*i).second));
954 EntityBreakdownWalker.append(0, (*i).first.c_str(), 1, tmp);
958 EntityBreakdownWalker.unref();
961 sprintf(tmp, "%u", Unsigned(g_brushCount.get()));
962 brushes_entry.text(tmp);
963 sprintf(tmp, "%u", Unsigned(g_entityCount.get()));
964 entities_entry.text(tmp);
966 modal_dialog_show(window, dialog);
969 window_get_position(window, g_posMapInfoWnd);
977 const char *m_message;
979 ScopeTimer(const char *message)
987 double elapsed_time = m_timer.elapsed_msec() / 1000.f;
988 globalOutputStream() << m_message << " timer: " << FloatFormat(elapsed_time, 5, 2) << " second(s) elapsed\n";
992 CopiedString g_strLastMapFolder = "";
1000 void Map_LoadFile(const char *filename)
1002 globalOutputStream() << "Loading map from " << filename << "\n";
1003 ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
1005 MRU_AddFile(filename);
1006 g_strLastMapFolder = g_path_get_dirname(filename);
1009 ScopeTimer timer("map load");
1011 const MapFormat *format = NULL;
1012 const char *moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), path_get_extension(filename));
1013 if (string_not_empty(moduleName)) {
1014 format = ReferenceAPI_getMapModules().findModule(moduleName);
1017 for (int i = 0; i < Brush_toggleFormatCount(); ++i) {
1021 Brush_toggleFormat(i);
1022 g_map.m_name = filename;
1023 Map_UpdateTitle(g_map);
1024 g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str());
1026 format->wrongFormat = false;
1028 g_map.m_resource->attach(g_map);
1030 if (!format->wrongFormat) {
1036 Node_getTraversable(GlobalSceneGraph().root())->traverse(entity_updateworldspawn());
1039 globalOutputStream() << "--- LoadMapFile ---\n";
1040 globalOutputStream() << g_map.m_name.c_str() << "\n";
1042 globalOutputStream() << Unsigned(g_brushCount.get()) << " primitive\n";
1043 globalOutputStream() << Unsigned(g_entityCount.get()) << " entities\n";
1045 //GlobalEntityCreator().printStatistics();
1048 // move the view to a start position
1050 Map_StartPosition();
1052 g_currentMap = &g_map;
1054 // refresh VFS to apply new pak filtering based on mapname
1055 // needed for daemon DPK VFS
1061 virtual bool excluded(scene::Node &node) const = 0;
1064 class ExcludeWalker : public scene::Traversable::Walker {
1065 const scene::Traversable::Walker &m_walker;
1066 const Excluder *m_exclude;
1067 mutable bool m_skip;
1069 ExcludeWalker(const scene::Traversable::Walker &walker, const Excluder &exclude)
1070 : m_walker(walker), m_exclude(&exclude), m_skip(false)
1074 bool pre(scene::Node &node) const
1076 if (m_exclude->excluded(node) || node.isRoot()) {
1085 void post(scene::Node &node) const
1090 m_walker.post(node);
1095 class AnyInstanceSelected : public scene::Instantiable::Visitor {
1098 AnyInstanceSelected(bool &selected) : m_selected(selected)
1103 void visit(scene::Instance &instance) const
1105 Selectable *selectable = Instance_getSelectable(instance);
1107 && selectable->isSelected()) {
1113 bool Node_instanceSelected(scene::Node &node)
1115 scene::Instantiable *instantiable = Node_getInstantiable(node);
1116 ASSERT_NOTNULL(instantiable);
1118 instantiable->forEachInstance(AnyInstanceSelected(selected));
1122 class SelectedDescendantWalker : public scene::Traversable::Walker {
1125 SelectedDescendantWalker(bool &selected) : m_selected(selected)
1130 bool pre(scene::Node &node) const
1132 if (node.isRoot()) {
1136 if (Node_instanceSelected(node)) {
1144 bool Node_selectedDescendant(scene::Node &node)
1147 Node_traverseSubgraph(node, SelectedDescendantWalker(selected));
1151 class SelectionExcluder : public Excluder {
1153 bool excluded(scene::Node &node) const
1155 return !Node_selectedDescendant(node);
1159 class IncludeSelectedWalker : public scene::Traversable::Walker {
1160 const scene::Traversable::Walker &m_walker;
1161 mutable std::size_t m_selected;
1162 mutable bool m_skip;
1164 bool selectedParent() const
1166 return m_selected != 0;
1170 IncludeSelectedWalker(const scene::Traversable::Walker &walker)
1171 : m_walker(walker), m_selected(0), m_skip(false)
1175 bool pre(scene::Node &node) const
1178 // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
1179 if (!node.isRoot() && (Node_selectedDescendant(node) || selectedParent())) {
1180 if (Node_instanceSelected(node)) {
1191 void post(scene::Node &node) const
1196 if (Node_instanceSelected(node)) {
1199 m_walker.post(node);
1204 void Map_Traverse_Selected(scene::Node &root, const scene::Traversable::Walker &walker)
1206 scene::Traversable *traversable = Node_getTraversable(root);
1207 if (traversable != 0) {
1209 traversable->traverse( ExcludeWalker( walker, SelectionExcluder() ) );
1211 traversable->traverse(IncludeSelectedWalker(walker));
1216 void Map_ExportSelected(TextOutputStream &out, const MapFormat &format)
1218 format.writeGraph(GlobalSceneGraph().root(), Map_Traverse_Selected, out);
1221 void Map_Traverse(scene::Node &root, const scene::Traversable::Walker &walker)
1223 scene::Traversable *traversable = Node_getTraversable(root);
1224 if (traversable != 0) {
1225 traversable->traverse(walker);
1229 class RegionExcluder : public Excluder {
1231 bool excluded(scene::Node &node) const
1233 return node.excluded();
1237 void Map_Traverse_Region(scene::Node &root, const scene::Traversable::Walker &walker)
1239 scene::Traversable *traversable = Node_getTraversable(root);
1240 if (traversable != 0) {
1241 traversable->traverse(ExcludeWalker(walker, RegionExcluder()));
1245 bool Map_SaveRegion(const char *filename)
1249 bool success = MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Region,
1252 RemoveRegionBrushes();
1258 void Map_RenameAbsolute(const char *absolute)
1260 Resource *resource = GlobalReferenceCache().capture(absolute);
1261 NodeSmartReference clone(NewMapRoot(path_make_relative(absolute, GlobalFileSystem().findRoot(absolute))));
1262 resource->setNode(clone.get_pointer());
1265 //ScopeTimer timer("clone subgraph");
1266 Node_getTraversable(GlobalSceneGraph().root())->traverse(CloneAll(clone));
1269 g_map.m_resource->detach(g_map);
1270 GlobalReferenceCache().release(g_map.m_name.c_str());
1272 g_map.m_resource = resource;
1274 g_map.m_name = absolute;
1275 Map_UpdateTitle(g_map);
1277 g_map.m_resource->attach(g_map);
1278 // refresh VFS to apply new pak filtering based on mapname
1279 // needed for daemon DPK VFS
1283 void Map_Rename(const char *filename)
1285 if (!string_equal(g_map.m_name.c_str(), filename)) {
1286 ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
1288 Map_RenameAbsolute(filename);
1290 SceneChangeNotify();
1300 ScopeTimer timer("map save");
1302 return true; // assume success..
1313 //globalOutputStream() << "Map_New\n";
1315 g_map.m_name = "unnamed.map";
1316 Map_UpdateTitle(g_map);
1319 g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str());
1320 // ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
1321 g_map.m_resource->attach(g_map);
1323 SceneChangeNotify();
1326 FocusViews(g_vector3_identity, 0);
1328 g_currentMap = &g_map;
1330 // restart VFS to apply new pak filtering based on mapname
1331 // needed for daemon DPK VFS
1335 extern void ConstructRegionBrushes(scene::Node *brushes[6], const Vector3 ®ion_mins, const Vector3 ®ion_maxs);
1337 void ConstructRegionStartpoint(scene::Node *startpoint, const Vector3 ®ion_mins, const Vector3 ®ion_maxs)
1340 \todo we need to make sure that the player start IS inside the region and bail out if it's not
1341 the compiler will refuse to compile a map with a player_start somewhere in empty space..
1342 for now, let's just print an error
1345 Vector3 vOrig(Camera_getOrigin(*g_pParentWnd->GetCamWnd()));
1347 for (int i = 0; i < 3; i++) {
1348 if (vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i]) {
1349 globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
1354 // write the info_playerstart
1356 sprintf(sTmp, "%d %d %d", (int) vOrig[0], (int) vOrig[1], (int) vOrig[2]);
1357 Node_getEntity(*startpoint)->setKeyValue("origin", sTmp);
1358 sprintf(sTmp, "%d", (int) Camera_getAngles(*g_pParentWnd->GetCamWnd())[CAMERA_YAW]);
1359 Node_getEntity(*startpoint)->setKeyValue("angle", sTmp);
1363 ===========================================================
1367 ===========================================================
1370 Vector3 region_mins(g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord);
1371 Vector3 region_maxs(g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord);
1373 scene::Node *region_sides[6];
1374 scene::Node *region_startpoint = 0;
1379 a regioned map will have temp walls put up at the region boundary
1380 \todo TODO TTimo old implementation of region brushes
1381 we still add them straight in the worldspawn and take them out after the map is saved
1382 with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
1385 void AddRegionBrushes(void)
1389 for (i = 0; i < 6; i++) {
1390 region_sides[i] = &GlobalBrushCreator().createBrush();
1391 Node_getTraversable(Map_FindOrInsertWorldspawn(g_map))->insert(NodeSmartReference(*region_sides[i]));
1394 region_startpoint = &GlobalEntityCreator().createEntity(
1395 GlobalEntityClassManager().findOrInsert("info_player_start", false));
1397 ConstructRegionBrushes(region_sides, region_mins, region_maxs);
1398 ConstructRegionStartpoint(region_startpoint, region_mins, region_maxs);
1400 Node_getTraversable(GlobalSceneGraph().root())->insert(NodeSmartReference(*region_startpoint));
1403 void RemoveRegionBrushes(void)
1405 for (std::size_t i = 0; i < 6; i++) {
1406 Node_getTraversable(*Map_GetWorldspawn(g_map))->erase(*region_sides[i]);
1408 Node_getTraversable(GlobalSceneGraph().root())->erase(*region_startpoint);
1411 inline void exclude_node(scene::Node &node, bool exclude)
1414 ? node.enable(scene::Node::eExcluded)
1415 : node.disable(scene::Node::eExcluded);
1418 class ExcludeAllWalker : public scene::Graph::Walker {
1421 ExcludeAllWalker(bool exclude)
1422 : m_exclude(exclude)
1426 bool pre(const scene::Path &path, scene::Instance &instance) const
1428 exclude_node(path.top(), m_exclude);
1434 void Scene_Exclude_All(bool exclude)
1436 GlobalSceneGraph().traverse(ExcludeAllWalker(exclude));
1439 bool Instance_isSelected(const scene::Instance &instance)
1441 const Selectable *selectable = Instance_getSelectable(instance);
1442 return selectable != 0 && selectable->isSelected();
1445 class ExcludeSelectedWalker : public scene::Graph::Walker {
1448 ExcludeSelectedWalker(bool exclude)
1449 : m_exclude(exclude)
1453 bool pre(const scene::Path &path, scene::Instance &instance) const
1455 exclude_node(path.top(),
1456 (instance.isSelected() || instance.childSelected() || instance.parentSelected()) == m_exclude);
1461 void Scene_Exclude_Selected(bool exclude)
1463 GlobalSceneGraph().traverse(ExcludeSelectedWalker(exclude));
1466 class ExcludeRegionedWalker : public scene::Graph::Walker {
1469 ExcludeRegionedWalker(bool exclude)
1470 : m_exclude(exclude)
1474 bool pre(const scene::Path &path, scene::Instance &instance) const
1480 aabb_intersects_aabb(
1481 instance.worldAABB(),
1482 aabb_for_minmax(region_mins, region_maxs)
1492 void Scene_Exclude_Region(bool exclude)
1494 GlobalSceneGraph().traverse(ExcludeRegionedWalker(exclude));
1501 Other filtering options may still be on
1504 void Map_RegionOff()
1506 region_active = false;
1508 region_maxs[0] = g_MaxWorldCoord - 64;
1509 region_mins[0] = g_MinWorldCoord + 64;
1510 region_maxs[1] = g_MaxWorldCoord - 64;
1511 region_mins[1] = g_MinWorldCoord + 64;
1512 region_maxs[2] = g_MaxWorldCoord - 64;
1513 region_mins[2] = g_MinWorldCoord + 64;
1515 Scene_Exclude_All(false);
1518 void Map_ApplyRegion(void)
1520 region_active = true;
1522 Scene_Exclude_Region(false);
1527 ========================
1528 Map_RegionSelectedBrushes
1529 ========================
1531 void Map_RegionSelectedBrushes(void)
1535 if (GlobalSelectionSystem().countSelected() != 0
1536 && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive) {
1537 region_active = true;
1538 Select_GetBounds(region_mins, region_maxs);
1540 Scene_Exclude_Selected(false);
1542 GlobalSelectionSystem().setSelectedAll(false);
1552 void Map_RegionXY(float x_min, float y_min, float x_max, float y_max)
1556 region_mins[0] = x_min;
1557 region_maxs[0] = x_max;
1558 region_mins[1] = y_min;
1559 region_maxs[1] = y_max;
1560 region_mins[2] = g_MinWorldCoord + 64;
1561 region_maxs[2] = g_MaxWorldCoord - 64;
1566 void Map_RegionBounds(const AABB &bounds)
1570 region_mins = vector3_subtracted(bounds.origin, bounds.extents);
1571 region_maxs = vector3_added(bounds.origin, bounds.extents);
1583 void Map_RegionBrush(void)
1585 if (GlobalSelectionSystem().countSelected() != 0) {
1586 scene::Instance &instance = GlobalSelectionSystem().ultimateSelected();
1587 Map_RegionBounds(instance.worldAABB());
1596 bool Map_ImportFile(const char *filename)
1598 ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
1600 g_strLastMapFolder = g_path_get_dirname(filename);
1602 bool success = false;
1604 if (extension_equal(path_get_extension(filename), "bsp")) {
1609 const MapFormat *format = NULL;
1610 const char *moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), path_get_extension(filename));
1611 if (string_not_empty(moduleName)) {
1612 format = ReferenceAPI_getMapModules().findModule(moduleName);
1616 format->wrongFormat = false;
1618 Resource *resource = GlobalReferenceCache().capture(filename);
1619 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1620 if (!resource->load()) {
1621 GlobalReferenceCache().release(filename);
1625 if (format->wrongFormat) {
1626 GlobalReferenceCache().release(filename);
1630 NodeSmartReference clone(NewMapRoot(""));
1631 Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone));
1632 Map_gatherNamespaced(clone);
1633 Map_mergeClonedNames();
1636 GlobalReferenceCache().release(filename);
1639 SceneChangeNotify();
1645 const char *type = GlobalRadiant().getGameDescriptionKeyValue("q3map2_type");
1646 int n = string_length(path_get_extension(filename));
1647 if (n && (extension_equal(path_get_extension(filename), "bsp") ||
1648 extension_equal(path_get_extension(filename), "map"))) {
1649 StringBuffer output;
1650 output.push_string(AppPath_get());
1651 output.push_string("q3map2.");
1652 output.push_string(RADIANT_EXECUTABLE);
1653 output.push_string(" -v -game ");
1654 output.push_string((type && *type) ? type : "quake3");
1655 output.push_string(" -fs_basepath \"");
1656 output.push_string(EnginePath_get());
1657 output.push_string("\" -fs_homepath \"");
1658 output.push_string(g_qeglobals.m_userEnginePath.c_str());
1659 output.push_string("\"");
1662 for (int i = 0; i < g_pakPathCount; i++) {
1663 if (g_strcmp0(g_strPakPath[i].c_str(), "")) {
1664 output.push_string(" -fs_pakpath \"");
1665 output.push_string(g_strPakPath[i].c_str());
1666 output.push_string("\"");
1671 if (g_disableEnginePath) {
1672 output.push_string(" -fs_nobasepath ");
1675 if (g_disableHomePath) {
1676 output.push_string(" -fs_nohomepath ");
1679 output.push_string(" -fs_game ");
1680 output.push_string(gamename_get());
1681 output.push_string(" -convert -format ");
1682 output.push_string(Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map");
1683 if (extension_equal(path_get_extension(filename), "map")) {
1684 output.push_string(" -readmap ");
1686 output.push_string(" \"");
1687 output.push_string(filename);
1688 output.push_string("\"");
1691 Q_Exec(NULL, output.c_str(), NULL, false, true);
1693 // rebuild filename as "filenamewithoutext_converted.map"
1695 output.push_range(filename, filename + string_length(filename) - (n + 1));
1696 output.push_string("_converted.map");
1697 filename = output.c_str();
1700 Resource *resource = GlobalReferenceCache().capture(filename);
1701 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1702 if (!resource->load()) {
1703 GlobalReferenceCache().release(filename);
1706 NodeSmartReference clone(NewMapRoot(""));
1707 Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone));
1708 Map_gatherNamespaced(clone);
1709 Map_mergeClonedNames();
1712 GlobalReferenceCache().release(filename);
1715 SceneChangeNotify();
1724 bool Map_SaveFile(const char *filename)
1726 ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
1727 bool success = MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse, filename);
1729 // refresh VFS to apply new pak filtering based on mapname
1730 // needed for daemon DPK VFS
1741 // Saves selected world brushes and whole entities with partial/full selections
1743 bool Map_SaveSelected(const char *filename)
1745 return MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Selected,
1750 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker {
1751 scene::Node &m_parent;
1753 ParentSelectedBrushesToEntityWalker(scene::Node &parent) : m_parent(parent)
1757 bool pre(const scene::Path &path, scene::Instance &instance) const
1759 if (path.top().get_pointer() != &m_parent
1760 && Node_isPrimitive(path.top())) {
1761 Selectable *selectable = Instance_getSelectable(instance);
1763 && selectable->isSelected()
1764 && path.size() > 1) {
1771 void post(const scene::Path &path, scene::Instance &instance) const
1773 if (path.top().get_pointer() != &m_parent
1774 && Node_isPrimitive(path.top())) {
1775 Selectable *selectable = Instance_getSelectable(instance);
1777 && selectable->isSelected()
1778 && path.size() > 1) {
1779 scene::Node &parent = path.parent();
1780 if (&parent != &m_parent) {
1781 NodeSmartReference node(path.top().get());
1782 Node_getTraversable(parent)->erase(node);
1783 Node_getTraversable(m_parent)->insert(node);
1790 void Scene_parentSelectedBrushesToEntity(scene::Graph &graph, scene::Node &parent)
1792 graph.traverse(ParentSelectedBrushesToEntityWalker(parent));
1795 class CountSelectedBrushes : public scene::Graph::Walker {
1796 std::size_t &m_count;
1797 mutable std::size_t m_depth;
1799 CountSelectedBrushes(std::size_t &count) : m_count(count), m_depth(0)
1804 bool pre(const scene::Path &path, scene::Instance &instance) const
1806 if (++m_depth != 1 && path.top().get().isRoot()) {
1809 Selectable *selectable = Instance_getSelectable(instance);
1811 && selectable->isSelected()
1812 && Node_isPrimitive(path.top())) {
1818 void post(const scene::Path &path, scene::Instance &instance) const
1824 std::size_t Scene_countSelectedBrushes(scene::Graph &graph)
1827 graph.traverse(CountSelectedBrushes(count));
1838 const char *nodetype_get_name(ENodeType type)
1840 if (type == eNodeMap) {
1843 if (type == eNodeEntity) {
1846 if (type == eNodePrimitive) {
1852 ENodeType node_get_nodetype(scene::Node &node)
1854 if (Node_isEntity(node)) {
1857 if (Node_isPrimitive(node)) {
1858 return eNodePrimitive;
1860 return eNodeUnknown;
1863 bool contains_entity(scene::Node &node)
1865 return Node_getTraversable(node) != 0 && !Node_isBrush(node) && !Node_isPatch(node) && !Node_isEntity(node);
1868 bool contains_primitive(scene::Node &node)
1870 return Node_isEntity(node) && Node_getTraversable(node) != 0 && Node_getEntity(node)->isContainer();
1873 ENodeType node_get_contains(scene::Node &node)
1875 if (contains_entity(node)) {
1878 if (contains_primitive(node)) {
1879 return eNodePrimitive;
1881 return eNodeUnknown;
1884 void Path_parent(const scene::Path &parent, const scene::Path &child)
1886 ENodeType contains = node_get_contains(parent.top());
1887 ENodeType type = node_get_nodetype(child.top());
1889 if (contains != eNodeUnknown && contains == type) {
1890 NodeSmartReference node(child.top().get());
1891 Path_deleteTop(child);
1892 Node_getTraversable(parent.top())->insert(node);
1893 SceneChangeNotify();
1895 globalErrorStream() << "failed - " << nodetype_get_name(type) << " cannot be parented to "
1896 << nodetype_get_name(contains) << " container.\n";
1900 void Scene_parentSelected()
1902 UndoableCommand undo("parentSelected");
1904 if (GlobalSelectionSystem().countSelected() > 1) {
1905 class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor {
1906 const scene::Path &m_parent;
1908 ParentSelectedBrushesToEntityWalker(const scene::Path &parent) : m_parent(parent)
1912 void visit(scene::Instance &instance) const
1914 if (&m_parent != &instance.path()) {
1915 Path_parent(m_parent, instance.path());
1920 ParentSelectedBrushesToEntityWalker visitor(GlobalSelectionSystem().ultimateSelected().path());
1921 GlobalSelectionSystem().foreachSelected(visitor);
1923 globalOutputStream() << "failed - did not find two selected nodes.\n";
1930 if (ConfirmModified("New Map")) {
1937 CopiedString g_mapsPath;
1939 const char *getMapsPath()
1941 return g_mapsPath.c_str();
1944 const char *getLastMapFolderPath()
1946 if (g_strLastMapFolder.empty()) {
1947 GlobalPreferenceSystem().registerPreference("LastMapFolder", make_property_string(g_strLastMapFolder));
1948 if (g_strLastMapFolder.empty()) {
1949 StringOutputStream buffer(1024);
1950 buffer << getMapsPath();
1951 if (!file_readable(buffer.c_str())) {
1953 buffer << g_qeglobals.m_userGamePath.c_str() << "/";
1955 g_strLastMapFolder = buffer.c_str();
1958 return g_strLastMapFolder.c_str();
1961 const char *map_open(const char *title)
1963 return MainFrame_getWindow().file_dialog(TRUE, title, getLastMapFolderPath(), MapFormat::Name(), true, false, false);
1966 const char *map_import(const char *title)
1968 return MainFrame_getWindow().file_dialog(TRUE, title, getLastMapFolderPath(), MapFormat::Name(), false, true, false);
1971 const char *map_save(const char *title)
1973 return MainFrame_getWindow().file_dialog(FALSE, title, getLastMapFolderPath(), MapFormat::Name(), false, false, true);
1978 if (!ConfirmModified("Open Map")) {
1982 const char *filename = map_open("Open Map");
1984 if (filename != NULL) {
1985 MRU_AddFile(filename);
1988 Map_LoadFile(filename);
1994 const char *filename = map_import("Import Map");
1996 if (filename != NULL) {
1997 UndoableCommand undo("mapImport");
1998 Map_ImportFile(filename);
2004 const char *filename = map_save("Save Map");
2006 if (filename != NULL) {
2007 g_strLastMapFolder = g_path_get_dirname(filename);
2008 MRU_AddFile(filename);
2009 Map_Rename(filename);
2022 if (Map_Unnamed(g_map)) {
2024 } else if (Map_Modified(g_map)) {
2031 const char *filename = map_save("Export Selection");
2033 if (filename != NULL) {
2034 g_strLastMapFolder = g_path_get_dirname(filename);
2035 Map_SaveSelected(filename);
2041 const char *filename = map_save("Export Region");
2043 if (filename != NULL) {
2044 g_strLastMapFolder = g_path_get_dirname(filename);
2045 Map_SaveRegion(filename);
2053 SceneChangeNotify();
2059 g_pParentWnd->GetXYWnd()->GetOrigin()[0] -
2060 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
2061 g_pParentWnd->GetXYWnd()->GetOrigin()[1] -
2062 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
2063 g_pParentWnd->GetXYWnd()->GetOrigin()[0] +
2064 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
2065 g_pParentWnd->GetXYWnd()->GetOrigin()[1] +
2066 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
2068 SceneChangeNotify();
2074 SceneChangeNotify();
2077 void RegionSelected()
2079 Map_RegionSelectedBrushes();
2080 SceneChangeNotify();
2084 class BrushFindByIndexWalker : public scene::Traversable::Walker {
2085 mutable std::size_t m_index;
2086 scene::Path &m_path;
2088 BrushFindByIndexWalker(std::size_t index, scene::Path &path)
2089 : m_index(index), m_path(path)
2093 bool pre(scene::Node &node) const
2095 if (Node_isPrimitive(node) && m_index-- == 0) {
2096 m_path.push(makeReference(node));
2102 class EntityFindByIndexWalker : public scene::Traversable::Walker {
2103 mutable std::size_t m_index;
2104 scene::Path &m_path;
2106 EntityFindByIndexWalker(std::size_t index, scene::Path &path)
2107 : m_index(index), m_path(path)
2111 bool pre(scene::Node &node) const
2113 if (Node_isEntity(node) && m_index-- == 0) {
2114 m_path.push(makeReference(node));
2120 void Scene_FindEntityBrush(std::size_t entity, std::size_t brush, scene::Path &path)
2122 path.push(makeReference(GlobalSceneGraph().root()));
2124 Node_getTraversable(path.top())->traverse(EntityFindByIndexWalker(entity, path));
2126 if (path.size() == 2) {
2127 scene::Traversable *traversable = Node_getTraversable(path.top());
2128 if (traversable != 0) {
2129 traversable->traverse(BrushFindByIndexWalker(brush, path));
2134 inline bool Node_hasChildren(scene::Node &node)
2136 scene::Traversable *traversable = Node_getTraversable(node);
2137 return traversable != 0 && !traversable->empty();
2140 void SelectBrush(int entitynum, int brushnum)
2143 Scene_FindEntityBrush(entitynum, brushnum, path);
2144 if (path.size() == 3 || (path.size() == 2 && !Node_hasChildren(path.top()))) {
2145 scene::Instance *instance = GlobalSceneGraph().find(path);
2146 ASSERT_MESSAGE(instance != 0, "SelectBrush: path not found in scenegraph");
2147 Selectable *selectable = Instance_getSelectable(*instance);
2148 ASSERT_MESSAGE(selectable != 0, "SelectBrush: path not selectable");
2149 selectable->setSelected(true);
2150 g_pParentWnd->GetXYWnd()->PositionView(instance->worldAABB().origin);
2155 class BrushFindIndexWalker : public scene::Graph::Walker {
2156 mutable const scene::Node *m_node;
2157 std::size_t &m_count;
2159 BrushFindIndexWalker(const scene::Node &node, std::size_t &count)
2160 : m_node(&node), m_count(count)
2164 bool pre(const scene::Path &path, scene::Instance &instance) const
2166 if (Node_isPrimitive(path.top())) {
2167 if (m_node == path.top().get_pointer()) {
2178 class EntityFindIndexWalker : public scene::Graph::Walker {
2179 mutable const scene::Node *m_node;
2180 std::size_t &m_count;
2182 EntityFindIndexWalker(const scene::Node &node, std::size_t &count)
2183 : m_node(&node), m_count(count)
2187 bool pre(const scene::Path &path, scene::Instance &instance) const
2189 if (Node_isEntity(path.top())) {
2190 if (m_node == path.top().get_pointer()) {
2201 static void GetSelectionIndex(int *ent, int *brush)
2203 std::size_t count_brush = 0;
2204 std::size_t count_entity = 0;
2205 if (GlobalSelectionSystem().countSelected() != 0) {
2206 const scene::Path &path = GlobalSelectionSystem().ultimateSelected().path();
2208 GlobalSceneGraph().traverse(BrushFindIndexWalker(path.top(), count_brush));
2209 GlobalSceneGraph().traverse(EntityFindIndexWalker(path.parent(), count_entity));
2211 *brush = int(count_brush);
2212 *ent = int(count_entity);
2218 ui::Entry entity{ui::null};
2219 ui::Entry brush{ui::null};
2221 ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback),
2224 auto accel = ui::AccelGroup(ui::New);
2225 window.add_accel_group(accel);
2228 auto vbox = create_dialog_vbox(4, 4);
2231 auto table = create_dialog_table(2, 2, 4, 4);
2232 vbox.pack_start(table, TRUE, TRUE, 0);
2234 ui::Widget label = ui::Label("Entity number");
2236 (table).attach(label, {0, 1, 0, 1}, {0, 0});
2239 ui::Widget label = ui::Label("Brush number");
2241 (table).attach(label, {0, 1, 1, 2}, {0, 0});
2244 auto entry = ui::Entry(ui::New);
2246 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
2247 gtk_widget_grab_focus(entry);
2251 auto entry = ui::Entry(ui::New);
2253 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
2259 auto hbox = create_dialog_hbox(4);
2260 vbox.pack_start(hbox, TRUE, TRUE, 0);
2262 auto button = create_dialog_button("Find", G_CALLBACK(dialog_button_ok), &dialog);
2263 hbox.pack_start(button, FALSE, FALSE, 0);
2264 widget_make_default(button);
2265 gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Return, (GdkModifierType) 0,
2269 auto button = create_dialog_button("Close", G_CALLBACK(dialog_button_cancel), &dialog);
2270 hbox.pack_start(button, FALSE, FALSE, 0);
2271 gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Escape, (GdkModifierType) 0,
2277 // Initialize dialog
2281 GetSelectionIndex(&ent, &br);
2282 sprintf(buf, "%i", ent);
2284 sprintf(buf, "%i", br);
2287 if (modal_dialog_show(window, dialog) == eIDOK) {
2288 const char *entstr = gtk_entry_get_text(entity);
2289 const char *brushstr = gtk_entry_get_text(brush);
2290 SelectBrush(atoi(entstr), atoi(brushstr));
2296 void Map_constructPreferences(PreferencesPage &page)
2298 page.appendCheckBox("", "Load last map on open", g_bLoadLastMap);
2302 class MapEntityClasses : public ModuleObserver {
2303 std::size_t m_unrealised;
2305 MapEntityClasses() : m_unrealised(1)
2311 if (--m_unrealised == 0) {
2312 if (g_map.m_resource != 0) {
2313 ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
2314 g_map.m_resource->realise();
2321 if (++m_unrealised == 1) {
2322 if (g_map.m_resource != 0) {
2323 g_map.m_resource->flush();
2324 g_map.m_resource->unrealise();
2330 MapEntityClasses g_MapEntityClasses;
2333 class MapModuleObserver : public ModuleObserver {
2334 std::size_t m_unrealised;
2336 MapModuleObserver() : m_unrealised(1)
2342 if (--m_unrealised == 0) {
2343 ASSERT_MESSAGE(!string_empty(g_qeglobals.m_userGamePath.c_str()),
2344 "maps_directory: user-game-path is empty");
2345 StringOutputStream buffer(256);
2346 buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2347 Q_mkdir(buffer.c_str());
2348 g_mapsPath = buffer.c_str();
2354 if (++m_unrealised == 1) {
2360 MapModuleObserver g_MapModuleObserver;
2362 CopiedString g_strLastMap;
2363 bool g_bLoadLastMap = false;
2365 void Map_Construct()
2367 GlobalCommands_insert("RegionOff", makeCallbackF(RegionOff));
2368 GlobalCommands_insert("RegionSetXY", makeCallbackF(RegionXY));
2369 GlobalCommands_insert("RegionSetBrush", makeCallbackF(RegionBrush));
2370 GlobalCommands_insert("RegionSetSelection", makeCallbackF(RegionSelected),
2371 Accelerator('R', (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK)));
2373 GlobalPreferenceSystem().registerPreference("LastMap", make_property_string(g_strLastMap));
2374 GlobalPreferenceSystem().registerPreference("LoadLastMap", make_property_string(g_bLoadLastMap));
2375 GlobalPreferenceSystem().registerPreference("MapInfoDlg", make_property<WindowPosition_String>(g_posMapInfoWnd));
2377 PreferencesDialog_addSettingsPreferences(makeCallbackF(Map_constructPreferences));
2379 GlobalEntityClassManager().attach(g_MapEntityClasses);
2380 Radiant_attachHomePathsObserver(g_MapModuleObserver);
2385 Radiant_detachHomePathsObserver(g_MapModuleObserver);
2386 GlobalEntityClassManager().detach(g_MapEntityClasses);