2 Copyright (C) 1999-2006 Id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
5 This file is part of GtkRadiant.
7 GtkRadiant is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 GtkRadiant is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with GtkRadiant; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
26 #include "debugging/debugging.h"
30 MapModules &ReferenceAPI_getMapModules();
32 #include "iselection.h"
36 #include "ireference.h"
37 #include "ifiletypes.h"
43 #include "ifilesystem.h"
44 #include "namespace.h"
45 #include "moduleobserver.h"
49 #include <gdk/gdkkeysyms.h>
50 #include "uilib/uilib.h"
53 #include "transformlib.h"
54 #include "selectionlib.h"
55 #include "instancelib.h"
56 #include "traverselib.h"
58 #include "eclasslib.h"
60 #include "stream/textfilestream.h"
63 #include "uniquenames.h"
64 #include "modulesystem/singletonmodule.h"
65 #include "modulesystem/moduleregistry.h"
66 #include "stream/stringstream.h"
67 #include "signal/signal.h"
69 #include "gtkutil/filechooser.h"
73 #include "filetypes.h"
75 #include "entityinspector.h"
78 #include "camwindow.h"
80 #include "mainframe.h"
81 #include "preferences.h"
82 #include "preferencesystem.h"
83 #include "referencecache.h"
87 #include "brushmodule.h"
90 bool g_writeMapComments = true;
99 //globalOutputStream() << "construct " << makeQuoted(c_str()) << "\n";
100 m_names.insert(name_read(c_str()));
107 //globalOutputStream() << "destroy " << makeQuoted(c_str()) << "\n";
108 m_names.erase(name_read(c_str()));
112 NameObserver &operator=(const NameObserver &other);
115 NameObserver(UniqueNames &names) : m_names(names)
120 NameObserver(const NameObserver &other) : m_names(other.m_names), m_name(other.m_name)
132 return string_empty(c_str());
135 const char *c_str() const
137 return m_name.c_str();
140 void nameChanged(const char *name)
147 typedef MemberCaller<NameObserver, void(const char *), &NameObserver::nameChanged> NameChangedCaller;
150 class BasicNamespace : public Namespace {
151 typedef std::map<NameCallback, NameObserver> Names;
153 UniqueNames m_uniqueNames;
157 ASSERT_MESSAGE(m_names.empty(), "namespace: names still registered at shutdown");
160 void attach(const NameCallback &setName, const NameCallbackCallback &attachObserver)
162 std::pair<Names::iterator, bool> result = m_names.insert(Names::value_type(setName, m_uniqueNames));
163 ASSERT_MESSAGE(result.second, "cannot attach name");
164 attachObserver(NameObserver::NameChangedCaller((*result.first).second));
165 //globalOutputStream() << "attach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
168 void detach(const NameCallback &setName, const NameCallbackCallback &detachObserver)
170 Names::iterator i = m_names.find(setName);
171 ASSERT_MESSAGE(i != m_names.end(), "cannot detach name");
172 //globalOutputStream() << "detach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
173 detachObserver(NameObserver::NameChangedCaller((*i).second));
177 void makeUnique(const char *name, const NameCallback &setName) const
180 name_write(buffer, m_uniqueNames.make_unique(name_read(name)));
184 void mergeNames(const BasicNamespace &other) const
186 typedef std::list<NameCallback> SetNameCallbacks;
187 typedef std::map<CopiedString, SetNameCallbacks> NameGroups;
190 UniqueNames uniqueNames(other.m_uniqueNames);
192 for (Names::const_iterator i = m_names.begin(); i != m_names.end(); ++i) {
193 groups[(*i).second.c_str()].push_back((*i).first);
196 for (NameGroups::iterator i = groups.begin(); i != groups.end(); ++i) {
197 name_t uniqueName(uniqueNames.make_unique(name_read((*i).first.c_str())));
198 uniqueNames.insert(uniqueName);
201 name_write(buffer, uniqueName);
203 //globalOutputStream() << "renaming " << makeQuoted((*i).first.c_str()) << " to " << makeQuoted(buffer) << "\n";
205 SetNameCallbacks &setNameCallbacks = (*i).second;
207 for (SetNameCallbacks::const_iterator j = setNameCallbacks.begin(); j != setNameCallbacks.end(); ++j) {
214 BasicNamespace g_defaultNamespace;
215 BasicNamespace g_cloneNamespace;
218 Namespace *m_namespace;
220 typedef Namespace Type;
222 STRING_CONSTANT(Name, "*");
226 m_namespace = &g_defaultNamespace;
229 Namespace *getTable()
235 typedef SingletonModule<NamespaceAPI> NamespaceModule;
236 typedef Static<NamespaceModule> StaticNamespaceModule;
237 StaticRegisterModule staticRegisterDefaultNamespace(StaticNamespaceModule::instance());
240 std::list<Namespaced *> g_cloned;
242 inline Namespaced *Node_getNamespaced(scene::Node &node)
244 return NodeTypeCast<Namespaced>::cast(node);
247 void Node_gatherNamespaced(scene::Node &node)
249 Namespaced *namespaced = Node_getNamespaced(node);
250 if (namespaced != 0) {
251 g_cloned.push_back(namespaced);
255 class GatherNamespaced : public scene::Traversable::Walker {
257 bool pre(scene::Node &node) const
259 Node_gatherNamespaced(node);
264 void Map_gatherNamespaced(scene::Node &root)
266 Node_traverseSubgraph(root, GatherNamespaced());
269 void Map_mergeClonedNames()
271 for (std::list<Namespaced *>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i) {
272 (*i)->setNamespace(g_cloneNamespace);
274 g_cloneNamespace.mergeNames(g_defaultNamespace);
275 for (std::list<Namespaced *>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i) {
276 (*i)->setNamespace(g_defaultNamespace);
290 void set(scene::Node *node)
301 scene::Node *get() const
309 void Map_SetValid(Map &map, bool valid);
311 void Map_UpdateTitle(const Map &map);
313 void Map_SetWorldspawn(Map &map, scene::Node *node);
316 class Map : public ModuleObserver {
319 Resource *m_resource;
324 void ( *m_modified_changed )(const Map &);
326 Signal0 m_mapValidCallbacks;
328 WorldNode m_world_node; // "classname" "worldspawn" !
330 Map() : m_resource(0), m_valid(false), m_modified_changed(Map_UpdateTitle)
336 if (m_resource != 0) {
337 if (Map_Unnamed(*this)) {
338 g_map.m_resource->setNode(NewMapRoot("").get_pointer());
339 MapFile *map = Node_getMapFile(*g_map.m_resource->getNode());
347 GlobalSceneGraph().insert_root(*m_resource->getNode());
351 Map_SetValid(g_map, true);
357 if (m_resource != 0) {
358 Map_SetValid(g_map, false);
359 Map_SetWorldspawn(g_map, 0);
362 GlobalUndoSystem().clear();
364 GlobalSceneGraph().erase_root();
370 Map *g_currentMap = 0;
372 void Map_addValidCallback(Map &map, const SignalHandler &handler)
374 map.m_mapValidCallbacks.connectLast(handler);
377 bool Map_Valid(const Map &map)
382 void Map_SetValid(Map &map, bool valid)
385 map.m_mapValidCallbacks();
389 const char *Map_Name(const Map &map)
391 return map.m_name.c_str();
394 bool Map_Unnamed(const Map &map)
396 return string_equal(Map_Name(map), "unnamed.map");
399 inline const MapFormat &MapFormat_forFile(const char *filename)
401 const char *moduleName = findModuleName(GetFileTypeRegistry(), MapFormat::Name(), path_get_extension(filename));
402 MapFormat *format = Radiant_getMapModules().findModule(moduleName);
403 ASSERT_MESSAGE(format != 0, "map format not found for file " << makeQuoted(filename));
407 const MapFormat &Map_getFormat(const Map &map)
409 return MapFormat_forFile(Map_Name(map));
413 bool Map_Modified(const Map &map)
415 return map.m_modified;
418 void Map_SetModified(Map &map, bool modified)
420 if (map.m_modified ^ modified) {
421 map.m_modified = modified;
423 map.m_modified_changed(map);
427 void Map_UpdateTitle(const Map &map)
429 Sys_SetTitle(map.m_name.c_str(), Map_Modified(map));
433 scene::Node *Map_GetWorldspawn(const Map &map)
435 return map.m_world_node.get();
438 void Map_SetWorldspawn(Map &map, scene::Node *node)
440 map.m_world_node.set(node);
445 // need that in a variable, will have to tweak depending on the game
446 float g_MaxWorldCoord = 64 * 1024;
447 float g_MinWorldCoord = -64 * 1024;
449 void AddRegionBrushes(void);
451 void RemoveRegionBrushes(void);
457 free all map elements, reinitialize the structures that depend on them
464 g_map.m_resource->detach(g_map);
465 GlobalReferenceCache().release(g_map.m_name.c_str());
466 g_map.m_resource = 0;
471 Brush_unlatchPreferences();
474 class EntityFindByClassname : public scene::Graph::Walker {
478 EntityFindByClassname(const char *name, Entity *&entity) : m_name(name), m_entity(entity)
483 bool pre(const scene::Path &path, scene::Instance &instance) const
486 Entity *entity = Node_getEntity(path.top());
488 && string_equal(m_name, entity->getKeyValue("classname"))) {
496 Entity *Scene_FindEntityByClass(const char *name)
499 GlobalSceneGraph().traverse(EntityFindByClassname(name, entity));
503 Entity *Scene_FindPlayerStart()
505 typedef const char *StaticString;
506 StaticString strings[] = {
508 "info_player_deathmatch",
509 "team_CTF_redplayer",
510 "team_CTF_blueplayer",
512 "team_CTF_bluespawn",
514 typedef const StaticString *StaticStringIterator;
515 for (StaticStringIterator i = strings, end = strings + (sizeof(strings) / sizeof(StaticString)); i != end; ++i) {
516 Entity *entity = Scene_FindEntityByClass(*i);
525 // move the view to a start position
529 void FocusViews(const Vector3 &point, float angle)
531 CamWnd &camwnd = *g_pParentWnd->GetCamWnd();
532 Camera_setOrigin(camwnd, point);
533 Vector3 angles(Camera_getAngles(camwnd));
534 angles[CAMERA_PITCH] = 0;
535 angles[CAMERA_YAW] = angle;
536 Camera_setAngles(camwnd, angles);
538 XYWnd *xywnd = g_pParentWnd->GetXYWnd();
539 xywnd->SetOrigin(point);
542 #include "stringio.h"
544 void Map_StartPosition()
546 Entity *entity = Scene_FindPlayerStart();
550 string_parse_vector3(entity->getKeyValue("origin"), origin);
551 FocusViews(origin, string_read_float(entity->getKeyValue("angle")));
553 FocusViews(g_vector3_identity, 0);
558 inline bool node_is_worldspawn(scene::Node &node)
560 Entity *entity = Node_getEntity(node);
561 return entity != 0 && string_equal(entity->getKeyValue("classname"), "worldspawn");
565 // use first worldspawn
566 class entity_updateworldspawn : public scene::Traversable::Walker {
568 bool pre(scene::Node &node) const
570 if (node_is_worldspawn(node)) {
571 if (Map_GetWorldspawn(g_map) == 0) {
572 Map_SetWorldspawn(g_map, &node);
579 scene::Node *Map_FindWorldspawn(Map &map)
581 Map_SetWorldspawn(map, 0);
583 Node_getTraversable(GlobalSceneGraph().root())->traverse(entity_updateworldspawn());
585 return Map_GetWorldspawn(map);
589 class CollectAllWalker : public scene::Traversable::Walker {
591 UnsortedNodeSet &m_nodes;
593 CollectAllWalker(scene::Node &root, UnsortedNodeSet &nodes) : m_root(root), m_nodes(nodes)
597 bool pre(scene::Node &node) const
599 m_nodes.insert(NodeSmartReference(node));
600 Node_getTraversable(m_root)->erase(node);
605 void Node_insertChildFirst(scene::Node &parent, scene::Node &child)
607 UnsortedNodeSet nodes;
608 Node_getTraversable(parent)->traverse(CollectAllWalker(parent, nodes));
609 Node_getTraversable(parent)->insert(child);
611 for (UnsortedNodeSet::iterator i = nodes.begin(); i != nodes.end(); ++i) {
612 Node_getTraversable(parent)->insert((*i));
616 scene::Node &createWorldspawn()
618 NodeSmartReference worldspawn(
619 GlobalEntityCreator().createEntity(GlobalEntityClassManager().findOrInsert("worldspawn", true)));
620 Node_insertChildFirst(GlobalSceneGraph().root(), worldspawn);
624 void Map_UpdateWorldspawn(Map &map)
626 if (Map_FindWorldspawn(map) == 0) {
627 Map_SetWorldspawn(map, &createWorldspawn());
631 scene::Node &Map_FindOrInsertWorldspawn(Map &map)
633 Map_UpdateWorldspawn(map);
634 return *Map_GetWorldspawn(map);
638 class MapMergeAll : public scene::Traversable::Walker {
639 mutable scene::Path m_path;
641 MapMergeAll(const scene::Path &root)
646 bool pre(scene::Node &node) const
648 Node_getTraversable(m_path.top())->insert(node);
649 m_path.push(makeReference(node));
650 selectPath(m_path, true);
654 void post(scene::Node &node) const
660 class MapMergeEntities : public scene::Traversable::Walker {
661 mutable scene::Path m_path;
663 MapMergeEntities(const scene::Path &root)
668 bool pre(scene::Node &node) const
670 if (node_is_worldspawn(node)) {
671 scene::Node *world_node = Map_FindWorldspawn(g_map);
672 if (world_node == 0) {
673 Map_SetWorldspawn(g_map, &node);
674 Node_getTraversable(m_path.top().get())->insert(node);
675 m_path.push(makeReference(node));
676 Node_getTraversable(node)->traverse(SelectChildren(m_path));
678 m_path.push(makeReference(*world_node));
679 Node_getTraversable(node)->traverse(MapMergeAll(m_path));
682 Node_getTraversable(m_path.top())->insert(node);
683 m_path.push(makeReference(node));
684 if (node_is_group(node)) {
685 Node_getTraversable(node)->traverse(SelectChildren(m_path));
687 selectPath(m_path, true);
693 void post(scene::Node &node) const
699 class BasicContainer : public scene::Node::Symbiot {
701 NodeTypeCastTable m_casts;
705 NodeContainedCast<BasicContainer, scene::Traversable>::install(m_casts);
708 NodeTypeCastTable &get()
715 TraversableNodeSet m_traverse;
718 typedef LazyStatic<TypeCasts> StaticTypeCasts;
720 scene::Traversable &get(NullType<scene::Traversable>)
725 BasicContainer() : m_node(this, this, StaticTypeCasts::instance().get())
740 /// Merges the map graph rooted at \p node into the global scene-graph.
741 void MergeMap(scene::Node &node)
743 Node_getTraversable(node)->traverse(MapMergeEntities(scene::Path(makeReference(GlobalSceneGraph().root()))));
746 void Map_ImportSelected(TextInputStream &in, const MapFormat &format)
748 NodeSmartReference node((new BasicContainer)->node());
749 format.readGraph(node, in, GlobalEntityCreator());
750 Map_gatherNamespaced(node);
751 Map_mergeClonedNames();
755 inline scene::Cloneable *Node_getCloneable(scene::Node &node)
757 return NodeTypeCast<scene::Cloneable>::cast(node);
760 inline scene::Node &node_clone(scene::Node &node)
762 scene::Cloneable *cloneable = Node_getCloneable(node);
763 if (cloneable != 0) {
764 return cloneable->clone();
767 return (new scene::NullNode)->node();
770 class CloneAll : public scene::Traversable::Walker {
771 mutable scene::Path m_path;
773 CloneAll(scene::Node &root)
774 : m_path(makeReference(root))
778 bool pre(scene::Node &node) const
784 m_path.push(makeReference(node_clone(node)));
785 m_path.top().get().IncRef();
790 void post(scene::Node &node) const
796 Node_getTraversable(m_path.parent())->insert(m_path.top());
798 m_path.top().get().DecRef();
803 scene::Node &Node_Clone(scene::Node &node)
805 scene::Node &clone = node_clone(node);
806 scene::Traversable *traversable = Node_getTraversable(node);
807 if (traversable != 0) {
808 traversable->traverse(CloneAll(clone));
814 typedef std::map<CopiedString, std::size_t> EntityBreakdown;
816 class EntityBreakdownWalker : public scene::Graph::Walker {
817 EntityBreakdown &m_entitymap;
819 EntityBreakdownWalker(EntityBreakdown &entitymap)
820 : m_entitymap(entitymap)
824 bool pre(const scene::Path &path, scene::Instance &instance) const
826 Entity *entity = Node_getEntity(path.top());
828 const EntityClass &eclass = entity->getEntityClass();
829 if (m_entitymap.find(eclass.name()) == m_entitymap.end()) {
830 m_entitymap[eclass.name()] = 1;
831 } else { ++m_entitymap[eclass.name()]; }
837 void Scene_EntityBreakdown(EntityBreakdown &entitymap)
839 GlobalSceneGraph().traverse(EntityBreakdownWalker(entitymap));
843 WindowPosition g_posMapInfoWnd(c_default_window_pos);
848 ui::Entry brushes_entry{ui::null};
849 ui::Entry entities_entry{ui::null};
850 ui::ListStore EntityBreakdownWalker{ui::null};
852 ui::Window window = MainFrame_getWindow().create_dialog_window("Map Info", G_CALLBACK(dialog_delete_callback),
855 window_set_position(window, g_posMapInfoWnd);
858 auto vbox = create_dialog_vbox(4, 4);
862 auto hbox = create_dialog_hbox(4);
863 vbox.pack_start(hbox, FALSE, TRUE, 0);
866 auto table = create_dialog_table(2, 2, 4, 4);
867 hbox.pack_start(table, TRUE, TRUE, 0);
870 auto entry = ui::Entry(ui::New);
872 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
873 gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
875 brushes_entry = entry;
878 auto entry = ui::Entry(ui::New);
880 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
881 gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
883 entities_entry = entry;
886 ui::Widget label = ui::Label("Total Brushes");
888 table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
889 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
892 ui::Widget label = ui::Label("Total Entities");
894 table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
895 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
899 auto vbox2 = create_dialog_vbox(4);
900 hbox.pack_start(vbox2, FALSE, FALSE, 0);
903 auto button = create_dialog_button("Close", G_CALLBACK(dialog_button_ok), &dialog);
904 vbox2.pack_start(button, FALSE, FALSE, 0);
909 ui::Widget label = ui::Label("Entity breakdown");
911 vbox.pack_start(label, FALSE, TRUE, 0);
912 gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
915 auto scr = create_scrolled_window(ui::Policy::NEVER, ui::Policy::AUTOMATIC, 4);
916 vbox.pack_start(scr, TRUE, TRUE, 0);
919 auto store = ui::ListStore::from(gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING));
921 auto view = ui::TreeView(ui::TreeModel::from(store._handle));
922 gtk_tree_view_set_headers_clickable(view, TRUE);
925 auto renderer = ui::CellRendererText(ui::New);
926 auto column = ui::TreeViewColumn("Entity", renderer, {{"text", 0}});
927 gtk_tree_view_append_column(view, column);
928 gtk_tree_view_column_set_sort_column_id(column, 0);
932 auto renderer = ui::CellRendererText(ui::New);
933 auto column = ui::TreeViewColumn("Count", renderer, {{"text", 1}});
934 gtk_tree_view_append_column(view, column);
935 gtk_tree_view_column_set_sort_column_id(column, 1);
942 EntityBreakdownWalker = store;
950 EntityBreakdown entitymap;
951 Scene_EntityBreakdown(entitymap);
953 for (EntityBreakdown::iterator i = entitymap.begin(); i != entitymap.end(); ++i) {
955 sprintf(tmp, "%u", Unsigned((*i).second));
956 EntityBreakdownWalker.append(0, (*i).first.c_str(), 1, tmp);
960 EntityBreakdownWalker.unref();
963 sprintf(tmp, "%u", Unsigned(g_brushCount.get()));
964 brushes_entry.text(tmp);
965 sprintf(tmp, "%u", Unsigned(g_entityCount.get()));
966 entities_entry.text(tmp);
968 modal_dialog_show(window, dialog);
971 window_get_position(window, g_posMapInfoWnd);
979 const char *m_message;
981 ScopeTimer(const char *message)
989 double elapsed_time = m_timer.elapsed_msec() / 1000.f;
990 globalOutputStream() << m_message << " timer: " << FloatFormat(elapsed_time, 5, 2) << " second(s) elapsed\n";
994 CopiedString g_strLastMapFolder = "";
1002 void Map_LoadFile(const char *filename)
1004 globalOutputStream() << "Loading map from " << filename << "\n";
1005 ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
1007 MRU_AddFile(filename);
1008 g_strLastMapFolder = g_path_get_dirname(filename);
1011 ScopeTimer timer("map load");
1013 const MapFormat *format = NULL;
1014 const char *moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), path_get_extension(filename));
1015 if (string_not_empty(moduleName)) {
1016 format = ReferenceAPI_getMapModules().findModule(moduleName);
1019 for (int i = 0; i < Brush_toggleFormatCount(); ++i) {
1023 Brush_toggleFormat(i);
1024 g_map.m_name = filename;
1025 Map_UpdateTitle(g_map);
1026 g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str());
1028 format->wrongFormat = false;
1030 g_map.m_resource->attach(g_map);
1032 if (!format->wrongFormat) {
1038 Node_getTraversable(GlobalSceneGraph().root())->traverse(entity_updateworldspawn());
1041 globalOutputStream() << "--- LoadMapFile ---\n";
1042 globalOutputStream() << g_map.m_name.c_str() << "\n";
1044 globalOutputStream() << Unsigned(g_brushCount.get()) << " primitive\n";
1045 globalOutputStream() << Unsigned(g_entityCount.get()) << " entities\n";
1047 //GlobalEntityCreator().printStatistics();
1050 // move the view to a start position
1052 Map_StartPosition();
1054 g_currentMap = &g_map;
1056 // refresh VFS to apply new pak filtering based on mapname
1057 // needed for daemon DPK VFS
1063 virtual bool excluded(scene::Node &node) const = 0;
1066 class ExcludeWalker : public scene::Traversable::Walker {
1067 const scene::Traversable::Walker &m_walker;
1068 const Excluder *m_exclude;
1069 mutable bool m_skip;
1071 ExcludeWalker(const scene::Traversable::Walker &walker, const Excluder &exclude)
1072 : m_walker(walker), m_exclude(&exclude), m_skip(false)
1076 bool pre(scene::Node &node) const
1078 if (m_exclude->excluded(node) || node.isRoot()) {
1087 void post(scene::Node &node) const
1092 m_walker.post(node);
1097 class AnyInstanceSelected : public scene::Instantiable::Visitor {
1100 AnyInstanceSelected(bool &selected) : m_selected(selected)
1105 void visit(scene::Instance &instance) const
1107 Selectable *selectable = Instance_getSelectable(instance);
1109 && selectable->isSelected()) {
1115 bool Node_instanceSelected(scene::Node &node)
1117 scene::Instantiable *instantiable = Node_getInstantiable(node);
1118 ASSERT_NOTNULL(instantiable);
1120 instantiable->forEachInstance(AnyInstanceSelected(selected));
1124 class SelectedDescendantWalker : public scene::Traversable::Walker {
1127 SelectedDescendantWalker(bool &selected) : m_selected(selected)
1132 bool pre(scene::Node &node) const
1134 if (node.isRoot()) {
1138 if (Node_instanceSelected(node)) {
1146 bool Node_selectedDescendant(scene::Node &node)
1149 Node_traverseSubgraph(node, SelectedDescendantWalker(selected));
1153 class SelectionExcluder : public Excluder {
1155 bool excluded(scene::Node &node) const
1157 return !Node_selectedDescendant(node);
1161 class IncludeSelectedWalker : public scene::Traversable::Walker {
1162 const scene::Traversable::Walker &m_walker;
1163 mutable std::size_t m_selected;
1164 mutable bool m_skip;
1166 bool selectedParent() const
1168 return m_selected != 0;
1172 IncludeSelectedWalker(const scene::Traversable::Walker &walker)
1173 : m_walker(walker), m_selected(0), m_skip(false)
1177 bool pre(scene::Node &node) const
1180 // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
1181 if (!node.isRoot() && (Node_selectedDescendant(node) || selectedParent())) {
1182 if (Node_instanceSelected(node)) {
1193 void post(scene::Node &node) const
1198 if (Node_instanceSelected(node)) {
1201 m_walker.post(node);
1206 void Map_Traverse_Selected(scene::Node &root, const scene::Traversable::Walker &walker)
1208 scene::Traversable *traversable = Node_getTraversable(root);
1209 if (traversable != 0) {
1211 traversable->traverse( ExcludeWalker( walker, SelectionExcluder() ) );
1213 traversable->traverse(IncludeSelectedWalker(walker));
1218 void Map_ExportSelected(TextOutputStream &out, const MapFormat &format)
1220 format.writeGraph(GlobalSceneGraph().root(), Map_Traverse_Selected, out, g_writeMapComments);
1223 void Map_Traverse(scene::Node &root, const scene::Traversable::Walker &walker)
1225 scene::Traversable *traversable = Node_getTraversable(root);
1226 if (traversable != 0) {
1227 traversable->traverse(walker);
1231 class RegionExcluder : public Excluder {
1233 bool excluded(scene::Node &node) const
1235 return node.excluded();
1239 void Map_Traverse_Region(scene::Node &root, const scene::Traversable::Walker &walker)
1241 scene::Traversable *traversable = Node_getTraversable(root);
1242 if (traversable != 0) {
1243 traversable->traverse(ExcludeWalker(walker, RegionExcluder()));
1247 bool Map_SaveRegion(const char *filename)
1251 bool success = MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Region,
1254 RemoveRegionBrushes();
1260 void Map_RenameAbsolute(const char *absolute)
1262 Resource *resource = GlobalReferenceCache().capture(absolute);
1263 NodeSmartReference clone(NewMapRoot(path_make_relative(absolute, GlobalFileSystem().findRoot(absolute))));
1264 resource->setNode(clone.get_pointer());
1267 //ScopeTimer timer("clone subgraph");
1268 Node_getTraversable(GlobalSceneGraph().root())->traverse(CloneAll(clone));
1271 g_map.m_resource->detach(g_map);
1272 GlobalReferenceCache().release(g_map.m_name.c_str());
1274 g_map.m_resource = resource;
1276 g_map.m_name = absolute;
1277 Map_UpdateTitle(g_map);
1279 g_map.m_resource->attach(g_map);
1280 // refresh VFS to apply new pak filtering based on mapname
1281 // needed for daemon DPK VFS
1285 void Map_Rename(const char *filename)
1287 if (!string_equal(g_map.m_name.c_str(), filename)) {
1288 ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
1290 Map_RenameAbsolute(filename);
1292 SceneChangeNotify();
1302 ScopeTimer timer("map save");
1304 return true; // assume success..
1315 //globalOutputStream() << "Map_New\n";
1317 g_map.m_name = "unnamed.map";
1318 Map_UpdateTitle(g_map);
1321 g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str());
1322 // ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
1323 g_map.m_resource->attach(g_map);
1325 SceneChangeNotify();
1328 FocusViews(g_vector3_identity, 0);
1330 g_currentMap = &g_map;
1332 // restart VFS to apply new pak filtering based on mapname
1333 // needed for daemon DPK VFS
1337 extern void ConstructRegionBrushes(scene::Node *brushes[6], const Vector3 ®ion_mins, const Vector3 ®ion_maxs);
1339 void ConstructRegionStartpoint(scene::Node *startpoint, const Vector3 ®ion_mins, const Vector3 ®ion_maxs)
1342 \todo we need to make sure that the player start IS inside the region and bail out if it's not
1343 the compiler will refuse to compile a map with a player_start somewhere in empty space..
1344 for now, let's just print an error
1347 Vector3 vOrig(Camera_getOrigin(*g_pParentWnd->GetCamWnd()));
1349 for (int i = 0; i < 3; i++) {
1350 if (vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i]) {
1351 globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
1356 // write the info_playerstart
1358 sprintf(sTmp, "%d %d %d", (int) vOrig[0], (int) vOrig[1], (int) vOrig[2]);
1359 Node_getEntity(*startpoint)->setKeyValue("origin", sTmp);
1360 sprintf(sTmp, "%d", (int) Camera_getAngles(*g_pParentWnd->GetCamWnd())[CAMERA_YAW]);
1361 Node_getEntity(*startpoint)->setKeyValue("angle", sTmp);
1365 ===========================================================
1369 ===========================================================
1372 Vector3 region_mins(g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord);
1373 Vector3 region_maxs(g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord);
1375 scene::Node *region_sides[6];
1376 scene::Node *region_startpoint = 0;
1381 a regioned map will have temp walls put up at the region boundary
1382 \todo TODO TTimo old implementation of region brushes
1383 we still add them straight in the worldspawn and take them out after the map is saved
1384 with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
1387 void AddRegionBrushes(void)
1391 for (i = 0; i < 6; i++) {
1392 region_sides[i] = &GlobalBrushCreator().createBrush();
1393 Node_getTraversable(Map_FindOrInsertWorldspawn(g_map))->insert(NodeSmartReference(*region_sides[i]));
1396 region_startpoint = &GlobalEntityCreator().createEntity(
1397 GlobalEntityClassManager().findOrInsert("info_player_start", false));
1399 ConstructRegionBrushes(region_sides, region_mins, region_maxs);
1400 ConstructRegionStartpoint(region_startpoint, region_mins, region_maxs);
1402 Node_getTraversable(GlobalSceneGraph().root())->insert(NodeSmartReference(*region_startpoint));
1405 void RemoveRegionBrushes(void)
1407 for (std::size_t i = 0; i < 6; i++) {
1408 Node_getTraversable(*Map_GetWorldspawn(g_map))->erase(*region_sides[i]);
1410 Node_getTraversable(GlobalSceneGraph().root())->erase(*region_startpoint);
1413 inline void exclude_node(scene::Node &node, bool exclude)
1416 ? node.enable(scene::Node::eExcluded)
1417 : node.disable(scene::Node::eExcluded);
1420 class ExcludeAllWalker : public scene::Graph::Walker {
1423 ExcludeAllWalker(bool exclude)
1424 : m_exclude(exclude)
1428 bool pre(const scene::Path &path, scene::Instance &instance) const
1430 exclude_node(path.top(), m_exclude);
1436 void Scene_Exclude_All(bool exclude)
1438 GlobalSceneGraph().traverse(ExcludeAllWalker(exclude));
1441 bool Instance_isSelected(const scene::Instance &instance)
1443 const Selectable *selectable = Instance_getSelectable(instance);
1444 return selectable != 0 && selectable->isSelected();
1447 class ExcludeSelectedWalker : public scene::Graph::Walker {
1450 ExcludeSelectedWalker(bool exclude)
1451 : m_exclude(exclude)
1455 bool pre(const scene::Path &path, scene::Instance &instance) const
1457 exclude_node(path.top(),
1458 (instance.isSelected() || instance.childSelected() || instance.parentSelected()) == m_exclude);
1463 void Scene_Exclude_Selected(bool exclude)
1465 GlobalSceneGraph().traverse(ExcludeSelectedWalker(exclude));
1468 class ExcludeRegionedWalker : public scene::Graph::Walker {
1471 ExcludeRegionedWalker(bool exclude)
1472 : m_exclude(exclude)
1476 bool pre(const scene::Path &path, scene::Instance &instance) const
1482 aabb_intersects_aabb(
1483 instance.worldAABB(),
1484 aabb_for_minmax(region_mins, region_maxs)
1494 void Scene_Exclude_Region(bool exclude)
1496 GlobalSceneGraph().traverse(ExcludeRegionedWalker(exclude));
1503 Other filtering options may still be on
1506 void Map_RegionOff()
1508 region_active = false;
1510 region_maxs[0] = g_MaxWorldCoord - 64;
1511 region_mins[0] = g_MinWorldCoord + 64;
1512 region_maxs[1] = g_MaxWorldCoord - 64;
1513 region_mins[1] = g_MinWorldCoord + 64;
1514 region_maxs[2] = g_MaxWorldCoord - 64;
1515 region_mins[2] = g_MinWorldCoord + 64;
1517 Scene_Exclude_All(false);
1520 void Map_ApplyRegion(void)
1522 region_active = true;
1524 Scene_Exclude_Region(false);
1529 ========================
1530 Map_RegionSelectedBrushes
1531 ========================
1533 void Map_RegionSelectedBrushes(void)
1537 if (GlobalSelectionSystem().countSelected() != 0
1538 && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive) {
1539 region_active = true;
1540 Select_GetBounds(region_mins, region_maxs);
1542 Scene_Exclude_Selected(false);
1544 GlobalSelectionSystem().setSelectedAll(false);
1554 void Map_RegionXY(float x_min, float y_min, float x_max, float y_max)
1558 region_mins[0] = x_min;
1559 region_maxs[0] = x_max;
1560 region_mins[1] = y_min;
1561 region_maxs[1] = y_max;
1562 region_mins[2] = g_MinWorldCoord + 64;
1563 region_maxs[2] = g_MaxWorldCoord - 64;
1568 void Map_RegionBounds(const AABB &bounds)
1572 region_mins = vector3_subtracted(bounds.origin, bounds.extents);
1573 region_maxs = vector3_added(bounds.origin, bounds.extents);
1585 void Map_RegionBrush(void)
1587 if (GlobalSelectionSystem().countSelected() != 0) {
1588 scene::Instance &instance = GlobalSelectionSystem().ultimateSelected();
1589 Map_RegionBounds(instance.worldAABB());
1598 bool Map_ImportFile(const char *filename)
1600 ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
1602 g_strLastMapFolder = g_path_get_dirname(filename);
1604 bool success = false;
1606 if (extension_equal(path_get_extension(filename), "bsp")) {
1611 const MapFormat *format = NULL;
1612 const char *moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), path_get_extension(filename));
1613 if (string_not_empty(moduleName)) {
1614 format = ReferenceAPI_getMapModules().findModule(moduleName);
1618 format->wrongFormat = false;
1620 Resource *resource = GlobalReferenceCache().capture(filename);
1621 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1622 if (!resource->load()) {
1623 GlobalReferenceCache().release(filename);
1627 if (format->wrongFormat) {
1628 GlobalReferenceCache().release(filename);
1632 NodeSmartReference clone(NewMapRoot(""));
1633 Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone));
1634 Map_gatherNamespaced(clone);
1635 Map_mergeClonedNames();
1638 GlobalReferenceCache().release(filename);
1641 SceneChangeNotify();
1647 const char *type = GlobalRadiant().getGameDescriptionKeyValue("q3map2_type");
1648 int n = string_length(path_get_extension(filename));
1649 if (n && (extension_equal(path_get_extension(filename), "bsp") ||
1650 extension_equal(path_get_extension(filename), "map"))) {
1651 StringBuffer output;
1652 output.push_string(AppPath_get());
1653 output.push_string("q3map2.");
1654 output.push_string(RADIANT_EXECUTABLE);
1655 output.push_string(" -v -game ");
1656 output.push_string((type && *type) ? type : "quake3");
1657 output.push_string(" -fs_basepath \"");
1658 output.push_string(EnginePath_get());
1659 output.push_string("\" -fs_homepath \"");
1660 output.push_string(g_qeglobals.m_userEnginePath.c_str());
1661 output.push_string("\"");
1664 for (int i = 0; i < g_pakPathCount; i++) {
1665 if (g_strcmp0(g_strPakPath[i].c_str(), "")) {
1666 output.push_string(" -fs_pakpath \"");
1667 output.push_string(g_strPakPath[i].c_str());
1668 output.push_string("\"");
1673 if (g_disableEnginePath) {
1674 output.push_string(" -fs_nobasepath ");
1677 if (g_disableHomePath) {
1678 output.push_string(" -fs_nohomepath ");
1681 output.push_string(" -fs_game ");
1682 output.push_string(gamename_get());
1683 output.push_string(" -convert -format ");
1684 output.push_string(Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map");
1685 if (extension_equal(path_get_extension(filename), "map")) {
1686 output.push_string(" -readmap ");
1688 output.push_string(" \"");
1689 output.push_string(filename);
1690 output.push_string("\"");
1693 Q_Exec(NULL, output.c_str(), NULL, false, true);
1695 // rebuild filename as "filenamewithoutext_converted.map"
1697 output.push_range(filename, filename + string_length(filename) - (n + 1));
1698 output.push_string("_converted.map");
1699 filename = output.c_str();
1702 Resource *resource = GlobalReferenceCache().capture(filename);
1703 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1704 if (!resource->load()) {
1705 GlobalReferenceCache().release(filename);
1708 NodeSmartReference clone(NewMapRoot(""));
1709 Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone));
1710 Map_gatherNamespaced(clone);
1711 Map_mergeClonedNames();
1714 GlobalReferenceCache().release(filename);
1717 SceneChangeNotify();
1726 bool Map_SaveFile(const char *filename)
1728 ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
1729 bool success = MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse, filename);
1731 // refresh VFS to apply new pak filtering based on mapname
1732 // needed for daemon DPK VFS
1743 // Saves selected world brushes and whole entities with partial/full selections
1745 bool Map_SaveSelected(const char *filename)
1747 return MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Selected,
1752 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker {
1753 scene::Node &m_parent;
1755 ParentSelectedBrushesToEntityWalker(scene::Node &parent) : m_parent(parent)
1759 bool pre(const scene::Path &path, scene::Instance &instance) const
1761 if (path.top().get_pointer() != &m_parent
1762 && Node_isPrimitive(path.top())) {
1763 Selectable *selectable = Instance_getSelectable(instance);
1765 && selectable->isSelected()
1766 && path.size() > 1) {
1773 void post(const scene::Path &path, scene::Instance &instance) const
1775 if (path.top().get_pointer() != &m_parent
1776 && Node_isPrimitive(path.top())) {
1777 Selectable *selectable = Instance_getSelectable(instance);
1779 && selectable->isSelected()
1780 && path.size() > 1) {
1781 scene::Node &parent = path.parent();
1782 if (&parent != &m_parent) {
1783 NodeSmartReference node(path.top().get());
1784 Node_getTraversable(parent)->erase(node);
1785 Node_getTraversable(m_parent)->insert(node);
1792 void Scene_parentSelectedBrushesToEntity(scene::Graph &graph, scene::Node &parent)
1794 graph.traverse(ParentSelectedBrushesToEntityWalker(parent));
1797 class CountSelectedBrushes : public scene::Graph::Walker {
1798 std::size_t &m_count;
1799 mutable std::size_t m_depth;
1801 CountSelectedBrushes(std::size_t &count) : m_count(count), m_depth(0)
1806 bool pre(const scene::Path &path, scene::Instance &instance) const
1808 if (++m_depth != 1 && path.top().get().isRoot()) {
1811 Selectable *selectable = Instance_getSelectable(instance);
1813 && selectable->isSelected()
1814 && Node_isPrimitive(path.top())) {
1820 void post(const scene::Path &path, scene::Instance &instance) const
1826 std::size_t Scene_countSelectedBrushes(scene::Graph &graph)
1829 graph.traverse(CountSelectedBrushes(count));
1840 const char *nodetype_get_name(ENodeType type)
1842 if (type == eNodeMap) {
1845 if (type == eNodeEntity) {
1848 if (type == eNodePrimitive) {
1854 ENodeType node_get_nodetype(scene::Node &node)
1856 if (Node_isEntity(node)) {
1859 if (Node_isPrimitive(node)) {
1860 return eNodePrimitive;
1862 return eNodeUnknown;
1865 bool contains_entity(scene::Node &node)
1867 return Node_getTraversable(node) != 0 && !Node_isBrush(node) && !Node_isPatch(node) && !Node_isEntity(node);
1870 bool contains_primitive(scene::Node &node)
1872 return Node_isEntity(node) && Node_getTraversable(node) != 0 && Node_getEntity(node)->isContainer();
1875 ENodeType node_get_contains(scene::Node &node)
1877 if (contains_entity(node)) {
1880 if (contains_primitive(node)) {
1881 return eNodePrimitive;
1883 return eNodeUnknown;
1886 void Path_parent(const scene::Path &parent, const scene::Path &child)
1888 ENodeType contains = node_get_contains(parent.top());
1889 ENodeType type = node_get_nodetype(child.top());
1891 if (contains != eNodeUnknown && contains == type) {
1892 NodeSmartReference node(child.top().get());
1893 Path_deleteTop(child);
1894 Node_getTraversable(parent.top())->insert(node);
1895 SceneChangeNotify();
1897 globalErrorStream() << "failed - " << nodetype_get_name(type) << " cannot be parented to "
1898 << nodetype_get_name(contains) << " container.\n";
1902 void Scene_parentSelected()
1904 UndoableCommand undo("parentSelected");
1906 if (GlobalSelectionSystem().countSelected() > 1) {
1907 class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor {
1908 const scene::Path &m_parent;
1910 ParentSelectedBrushesToEntityWalker(const scene::Path &parent) : m_parent(parent)
1914 void visit(scene::Instance &instance) const
1916 if (&m_parent != &instance.path()) {
1917 Path_parent(m_parent, instance.path());
1922 ParentSelectedBrushesToEntityWalker visitor(GlobalSelectionSystem().ultimateSelected().path());
1923 GlobalSelectionSystem().foreachSelected(visitor);
1925 globalOutputStream() << "failed - did not find two selected nodes.\n";
1932 if (ConfirmModified("New Map")) {
1939 CopiedString g_mapsPath;
1941 const char *getMapsPath()
1943 return g_mapsPath.c_str();
1946 const char *getLastMapFolderPath()
1948 if (g_strLastMapFolder.empty()) {
1949 GlobalPreferenceSystem().registerPreference("LastMapFolder", make_property_string(g_strLastMapFolder));
1950 if (g_strLastMapFolder.empty()) {
1951 StringOutputStream buffer(1024);
1952 buffer << getMapsPath();
1953 if (!file_readable(buffer.c_str())) {
1955 buffer << g_qeglobals.m_userGamePath.c_str() << "/";
1957 g_strLastMapFolder = buffer.c_str();
1960 return g_strLastMapFolder.c_str();
1963 const char *map_open(const char *title)
1965 return MainFrame_getWindow().file_dialog(TRUE, title, getLastMapFolderPath(), MapFormat::Name(), true, false, false);
1968 const char *map_import(const char *title)
1970 return MainFrame_getWindow().file_dialog(TRUE, title, getLastMapFolderPath(), MapFormat::Name(), false, true, false);
1973 const char *map_save(const char *title)
1975 return MainFrame_getWindow().file_dialog(FALSE, title, getLastMapFolderPath(), MapFormat::Name(), false, false, true);
1980 if (!ConfirmModified("Open Map")) {
1984 const char *filename = map_open("Open Map");
1986 if (filename != NULL) {
1987 MRU_AddFile(filename);
1990 Map_LoadFile(filename);
1996 const char *filename = map_import("Import Map");
1998 if (filename != NULL) {
1999 UndoableCommand undo("mapImport");
2000 Map_ImportFile(filename);
2006 const char *filename = map_save("Save Map");
2008 if (filename != NULL) {
2009 g_strLastMapFolder = g_path_get_dirname(filename);
2010 MRU_AddFile(filename);
2011 Map_Rename(filename);
2024 if (Map_Unnamed(g_map)) {
2026 } else if (Map_Modified(g_map)) {
2033 const char *filename = map_save("Export Selection");
2035 if (filename != NULL) {
2036 g_strLastMapFolder = g_path_get_dirname(filename);
2037 Map_SaveSelected(filename);
2043 const char *filename = map_save("Export Region");
2045 if (filename != NULL) {
2046 g_strLastMapFolder = g_path_get_dirname(filename);
2047 Map_SaveRegion(filename);
2055 SceneChangeNotify();
2061 g_pParentWnd->GetXYWnd()->GetOrigin()[0] -
2062 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
2063 g_pParentWnd->GetXYWnd()->GetOrigin()[1] -
2064 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
2065 g_pParentWnd->GetXYWnd()->GetOrigin()[0] +
2066 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
2067 g_pParentWnd->GetXYWnd()->GetOrigin()[1] +
2068 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
2070 SceneChangeNotify();
2076 SceneChangeNotify();
2079 void RegionSelected()
2081 Map_RegionSelectedBrushes();
2082 SceneChangeNotify();
2086 class BrushFindByIndexWalker : public scene::Traversable::Walker {
2087 mutable std::size_t m_index;
2088 scene::Path &m_path;
2090 BrushFindByIndexWalker(std::size_t index, scene::Path &path)
2091 : m_index(index), m_path(path)
2095 bool pre(scene::Node &node) const
2097 if (Node_isPrimitive(node) && m_index-- == 0) {
2098 m_path.push(makeReference(node));
2104 class EntityFindByIndexWalker : public scene::Traversable::Walker {
2105 mutable std::size_t m_index;
2106 scene::Path &m_path;
2108 EntityFindByIndexWalker(std::size_t index, scene::Path &path)
2109 : m_index(index), m_path(path)
2113 bool pre(scene::Node &node) const
2115 if (Node_isEntity(node) && m_index-- == 0) {
2116 m_path.push(makeReference(node));
2122 void Scene_FindEntityBrush(std::size_t entity, std::size_t brush, scene::Path &path)
2124 path.push(makeReference(GlobalSceneGraph().root()));
2126 Node_getTraversable(path.top())->traverse(EntityFindByIndexWalker(entity, path));
2128 if (path.size() == 2) {
2129 scene::Traversable *traversable = Node_getTraversable(path.top());
2130 if (traversable != 0) {
2131 traversable->traverse(BrushFindByIndexWalker(brush, path));
2136 inline bool Node_hasChildren(scene::Node &node)
2138 scene::Traversable *traversable = Node_getTraversable(node);
2139 return traversable != 0 && !traversable->empty();
2142 void SelectBrush(int entitynum, int brushnum)
2145 Scene_FindEntityBrush(entitynum, brushnum, path);
2146 if (path.size() == 3 || (path.size() == 2 && !Node_hasChildren(path.top()))) {
2147 scene::Instance *instance = GlobalSceneGraph().find(path);
2148 ASSERT_MESSAGE(instance != 0, "SelectBrush: path not found in scenegraph");
2149 Selectable *selectable = Instance_getSelectable(*instance);
2150 ASSERT_MESSAGE(selectable != 0, "SelectBrush: path not selectable");
2151 selectable->setSelected(true);
2152 g_pParentWnd->GetXYWnd()->PositionView(instance->worldAABB().origin);
2157 class BrushFindIndexWalker : public scene::Graph::Walker {
2158 mutable const scene::Node *m_node;
2159 std::size_t &m_count;
2161 BrushFindIndexWalker(const scene::Node &node, std::size_t &count)
2162 : m_node(&node), m_count(count)
2166 bool pre(const scene::Path &path, scene::Instance &instance) const
2168 if (Node_isPrimitive(path.top())) {
2169 if (m_node == path.top().get_pointer()) {
2180 class EntityFindIndexWalker : public scene::Graph::Walker {
2181 mutable const scene::Node *m_node;
2182 std::size_t &m_count;
2184 EntityFindIndexWalker(const scene::Node &node, std::size_t &count)
2185 : m_node(&node), m_count(count)
2189 bool pre(const scene::Path &path, scene::Instance &instance) const
2191 if (Node_isEntity(path.top())) {
2192 if (m_node == path.top().get_pointer()) {
2203 static void GetSelectionIndex(int *ent, int *brush)
2205 std::size_t count_brush = 0;
2206 std::size_t count_entity = 0;
2207 if (GlobalSelectionSystem().countSelected() != 0) {
2208 const scene::Path &path = GlobalSelectionSystem().ultimateSelected().path();
2210 GlobalSceneGraph().traverse(BrushFindIndexWalker(path.top(), count_brush));
2211 GlobalSceneGraph().traverse(EntityFindIndexWalker(path.parent(), count_entity));
2213 *brush = int(count_brush);
2214 *ent = int(count_entity);
2220 ui::Entry entity{ui::null};
2221 ui::Entry brush{ui::null};
2223 ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback),
2226 auto accel = ui::AccelGroup(ui::New);
2227 window.add_accel_group(accel);
2230 auto vbox = create_dialog_vbox(4, 4);
2233 auto table = create_dialog_table(2, 2, 4, 4);
2234 vbox.pack_start(table, TRUE, TRUE, 0);
2236 ui::Widget label = ui::Label("Entity number");
2238 (table).attach(label, {0, 1, 0, 1}, {0, 0});
2241 ui::Widget label = ui::Label("Brush number");
2243 (table).attach(label, {0, 1, 1, 2}, {0, 0});
2246 auto entry = ui::Entry(ui::New);
2248 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
2249 gtk_widget_grab_focus(entry);
2253 auto entry = ui::Entry(ui::New);
2255 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
2261 auto hbox = create_dialog_hbox(4);
2262 vbox.pack_start(hbox, TRUE, TRUE, 0);
2264 auto button = create_dialog_button("Find", G_CALLBACK(dialog_button_ok), &dialog);
2265 hbox.pack_start(button, FALSE, FALSE, 0);
2266 widget_make_default(button);
2267 gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Return, (GdkModifierType) 0,
2271 auto button = create_dialog_button("Close", G_CALLBACK(dialog_button_cancel), &dialog);
2272 hbox.pack_start(button, FALSE, FALSE, 0);
2273 gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Escape, (GdkModifierType) 0,
2279 // Initialize dialog
2283 GetSelectionIndex(&ent, &br);
2284 sprintf(buf, "%i", ent);
2286 sprintf(buf, "%i", br);
2289 if (modal_dialog_show(window, dialog) == eIDOK) {
2290 const char *entstr = gtk_entry_get_text(entity);
2291 const char *brushstr = gtk_entry_get_text(brush);
2292 SelectBrush(atoi(entstr), atoi(brushstr));
2298 void Map_constructPreferences(PreferencesPage &page)
2300 page.appendCheckBox("", "Load last map at startup", g_bLoadLastMap);
2301 page.appendCheckBox("", "Add entity and brush number comments on map write", g_writeMapComments);
2305 class MapEntityClasses : public ModuleObserver {
2306 std::size_t m_unrealised;
2308 MapEntityClasses() : m_unrealised(1)
2314 if (--m_unrealised == 0) {
2315 if (g_map.m_resource != 0) {
2316 ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
2317 g_map.m_resource->realise();
2324 if (++m_unrealised == 1) {
2325 if (g_map.m_resource != 0) {
2326 g_map.m_resource->flush();
2327 g_map.m_resource->unrealise();
2333 MapEntityClasses g_MapEntityClasses;
2336 class MapModuleObserver : public ModuleObserver {
2337 std::size_t m_unrealised;
2339 MapModuleObserver() : m_unrealised(1)
2345 if (--m_unrealised == 0) {
2346 ASSERT_MESSAGE(!string_empty(g_qeglobals.m_userGamePath.c_str()),
2347 "maps_directory: user-game-path is empty");
2348 StringOutputStream buffer(256);
2349 buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2350 Q_mkdir(buffer.c_str());
2351 g_mapsPath = buffer.c_str();
2357 if (++m_unrealised == 1) {
2363 MapModuleObserver g_MapModuleObserver;
2365 CopiedString g_strLastMap;
2366 bool g_bLoadLastMap = false;
2368 void Map_Construct()
2370 GlobalCommands_insert("RegionOff", makeCallbackF(RegionOff));
2371 GlobalCommands_insert("RegionSetXY", makeCallbackF(RegionXY));
2372 GlobalCommands_insert("RegionSetBrush", makeCallbackF(RegionBrush));
2373 GlobalCommands_insert("RegionSetSelection", makeCallbackF(RegionSelected),
2374 Accelerator('R', (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK)));
2376 GlobalPreferenceSystem().registerPreference("LastMap", make_property_string(g_strLastMap));
2377 GlobalPreferenceSystem().registerPreference("LoadLastMap", make_property_string(g_bLoadLastMap));
2378 GlobalPreferenceSystem().registerPreference("MapInfoDlg", make_property<WindowPosition_String>(g_posMapInfoWnd));
2379 GlobalPreferenceSystem().registerPreference("WriteMapComments", make_property_string(g_writeMapComments));
2381 PreferencesDialog_addSettingsPreferences(makeCallbackF(Map_constructPreferences));
2383 GlobalEntityClassManager().attach(g_MapEntityClasses);
2384 Radiant_attachHomePathsObserver(g_MapModuleObserver);
2389 Radiant_detachHomePathsObserver(g_MapModuleObserver);
2390 GlobalEntityClassManager().detach(g_MapEntityClasses);