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 g_map.m_name = filename;
1006 // refresh VFS to apply new pak filtering based on mapname
1007 // needed for daemon DPK VFS
1010 globalOutputStream() << "Loading map from " << filename << "\n";
1011 ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
1013 MRU_AddFile(filename);
1014 g_strLastMapFolder = g_path_get_dirname(filename);
1017 ScopeTimer timer("map load");
1019 const MapFormat *format = NULL;
1020 const char *moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), path_get_extension(filename));
1021 if (string_not_empty(moduleName)) {
1022 format = ReferenceAPI_getMapModules().findModule(moduleName);
1025 for (int i = 0; i < Brush_toggleFormatCount(); ++i) {
1029 Brush_toggleFormat(i);
1030 Map_UpdateTitle(g_map);
1032 g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str());
1034 format->wrongFormat = false;
1036 g_map.m_resource->attach(g_map);
1038 if (!format->wrongFormat) {
1044 Node_getTraversable(GlobalSceneGraph().root())->traverse(entity_updateworldspawn());
1047 globalOutputStream() << "--- LoadMapFile ---\n";
1048 globalOutputStream() << g_map.m_name.c_str() << "\n";
1050 globalOutputStream() << Unsigned(g_brushCount.get()) << " primitive\n";
1051 globalOutputStream() << Unsigned(g_entityCount.get()) << " entities\n";
1053 //GlobalEntityCreator().printStatistics();
1056 // move the view to a start position
1058 Map_StartPosition();
1060 g_currentMap = &g_map;
1065 virtual bool excluded(scene::Node &node) const = 0;
1068 class ExcludeWalker : public scene::Traversable::Walker {
1069 const scene::Traversable::Walker &m_walker;
1070 const Excluder *m_exclude;
1071 mutable bool m_skip;
1073 ExcludeWalker(const scene::Traversable::Walker &walker, const Excluder &exclude)
1074 : m_walker(walker), m_exclude(&exclude), m_skip(false)
1078 bool pre(scene::Node &node) const
1080 if (m_exclude->excluded(node) || node.isRoot()) {
1089 void post(scene::Node &node) const
1094 m_walker.post(node);
1099 class AnyInstanceSelected : public scene::Instantiable::Visitor {
1102 AnyInstanceSelected(bool &selected) : m_selected(selected)
1107 void visit(scene::Instance &instance) const
1109 Selectable *selectable = Instance_getSelectable(instance);
1111 && selectable->isSelected()) {
1117 bool Node_instanceSelected(scene::Node &node)
1119 scene::Instantiable *instantiable = Node_getInstantiable(node);
1120 ASSERT_NOTNULL(instantiable);
1122 instantiable->forEachInstance(AnyInstanceSelected(selected));
1126 class SelectedDescendantWalker : public scene::Traversable::Walker {
1129 SelectedDescendantWalker(bool &selected) : m_selected(selected)
1134 bool pre(scene::Node &node) const
1136 if (node.isRoot()) {
1140 if (Node_instanceSelected(node)) {
1148 bool Node_selectedDescendant(scene::Node &node)
1151 Node_traverseSubgraph(node, SelectedDescendantWalker(selected));
1155 class SelectionExcluder : public Excluder {
1157 bool excluded(scene::Node &node) const
1159 return !Node_selectedDescendant(node);
1163 class IncludeSelectedWalker : public scene::Traversable::Walker {
1164 const scene::Traversable::Walker &m_walker;
1165 mutable std::size_t m_selected;
1166 mutable bool m_skip;
1168 bool selectedParent() const
1170 return m_selected != 0;
1174 IncludeSelectedWalker(const scene::Traversable::Walker &walker)
1175 : m_walker(walker), m_selected(0), m_skip(false)
1179 bool pre(scene::Node &node) const
1182 // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
1183 if (!node.isRoot() && (Node_selectedDescendant(node) || selectedParent())) {
1184 if (Node_instanceSelected(node)) {
1195 void post(scene::Node &node) const
1200 if (Node_instanceSelected(node)) {
1203 m_walker.post(node);
1208 void Map_Traverse_Selected(scene::Node &root, const scene::Traversable::Walker &walker)
1210 scene::Traversable *traversable = Node_getTraversable(root);
1211 if (traversable != 0) {
1213 traversable->traverse( ExcludeWalker( walker, SelectionExcluder() ) );
1215 traversable->traverse(IncludeSelectedWalker(walker));
1220 void Map_ExportSelected(TextOutputStream &out, const MapFormat &format)
1222 format.writeGraph(GlobalSceneGraph().root(), Map_Traverse_Selected, out, g_writeMapComments);
1225 void Map_Traverse(scene::Node &root, const scene::Traversable::Walker &walker)
1227 scene::Traversable *traversable = Node_getTraversable(root);
1228 if (traversable != 0) {
1229 traversable->traverse(walker);
1233 class RegionExcluder : public Excluder {
1235 bool excluded(scene::Node &node) const
1237 return node.excluded();
1241 void Map_Traverse_Region(scene::Node &root, const scene::Traversable::Walker &walker)
1243 scene::Traversable *traversable = Node_getTraversable(root);
1244 if (traversable != 0) {
1245 traversable->traverse(ExcludeWalker(walker, RegionExcluder()));
1249 bool Map_SaveRegion(const char *filename)
1253 bool success = MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Region,
1256 RemoveRegionBrushes();
1262 void Map_RenameAbsolute(const char *absolute)
1264 Resource *resource = GlobalReferenceCache().capture(absolute);
1265 NodeSmartReference clone(NewMapRoot(path_make_relative(absolute, GlobalFileSystem().findRoot(absolute))));
1266 resource->setNode(clone.get_pointer());
1269 //ScopeTimer timer("clone subgraph");
1270 Node_getTraversable(GlobalSceneGraph().root())->traverse(CloneAll(clone));
1273 g_map.m_resource->detach(g_map);
1274 GlobalReferenceCache().release(g_map.m_name.c_str());
1276 g_map.m_resource = resource;
1278 g_map.m_name = absolute;
1279 Map_UpdateTitle(g_map);
1281 g_map.m_resource->attach(g_map);
1282 // refresh VFS to apply new pak filtering based on mapname
1283 // needed for daemon DPK VFS
1287 void Map_Rename(const char *filename)
1289 if (!string_equal(g_map.m_name.c_str(), filename)) {
1290 ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
1292 Map_RenameAbsolute(filename);
1294 SceneChangeNotify();
1304 ScopeTimer timer("map save");
1306 return true; // assume success..
1317 //globalOutputStream() << "Map_New\n";
1319 g_map.m_name = "unnamed.map";
1320 Map_UpdateTitle(g_map);
1323 g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str());
1324 // ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
1325 g_map.m_resource->attach(g_map);
1327 SceneChangeNotify();
1330 FocusViews(g_vector3_identity, 0);
1332 g_currentMap = &g_map;
1334 // restart VFS to apply new pak filtering based on mapname
1335 // needed for daemon DPK VFS
1339 extern void ConstructRegionBrushes(scene::Node *brushes[6], const Vector3 ®ion_mins, const Vector3 ®ion_maxs);
1341 void ConstructRegionStartpoint(scene::Node *startpoint, const Vector3 ®ion_mins, const Vector3 ®ion_maxs)
1344 \todo we need to make sure that the player start IS inside the region and bail out if it's not
1345 the compiler will refuse to compile a map with a player_start somewhere in empty space..
1346 for now, let's just print an error
1349 Vector3 vOrig(Camera_getOrigin(*g_pParentWnd->GetCamWnd()));
1351 for (int i = 0; i < 3; i++) {
1352 if (vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i]) {
1353 globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
1358 // write the info_playerstart
1360 sprintf(sTmp, "%d %d %d", (int) vOrig[0], (int) vOrig[1], (int) vOrig[2]);
1361 Node_getEntity(*startpoint)->setKeyValue("origin", sTmp);
1362 sprintf(sTmp, "%d", (int) Camera_getAngles(*g_pParentWnd->GetCamWnd())[CAMERA_YAW]);
1363 Node_getEntity(*startpoint)->setKeyValue("angle", sTmp);
1367 ===========================================================
1371 ===========================================================
1374 Vector3 region_mins(g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord);
1375 Vector3 region_maxs(g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord);
1377 scene::Node *region_sides[6];
1378 scene::Node *region_startpoint = 0;
1383 a regioned map will have temp walls put up at the region boundary
1384 \todo TODO TTimo old implementation of region brushes
1385 we still add them straight in the worldspawn and take them out after the map is saved
1386 with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
1389 void AddRegionBrushes(void)
1393 for (i = 0; i < 6; i++) {
1394 region_sides[i] = &GlobalBrushCreator().createBrush();
1395 Node_getTraversable(Map_FindOrInsertWorldspawn(g_map))->insert(NodeSmartReference(*region_sides[i]));
1398 region_startpoint = &GlobalEntityCreator().createEntity(
1399 GlobalEntityClassManager().findOrInsert("info_player_start", false));
1401 ConstructRegionBrushes(region_sides, region_mins, region_maxs);
1402 ConstructRegionStartpoint(region_startpoint, region_mins, region_maxs);
1404 Node_getTraversable(GlobalSceneGraph().root())->insert(NodeSmartReference(*region_startpoint));
1407 void RemoveRegionBrushes(void)
1409 for (std::size_t i = 0; i < 6; i++) {
1410 Node_getTraversable(*Map_GetWorldspawn(g_map))->erase(*region_sides[i]);
1412 Node_getTraversable(GlobalSceneGraph().root())->erase(*region_startpoint);
1415 inline void exclude_node(scene::Node &node, bool exclude)
1418 ? node.enable(scene::Node::eExcluded)
1419 : node.disable(scene::Node::eExcluded);
1422 class ExcludeAllWalker : public scene::Graph::Walker {
1425 ExcludeAllWalker(bool exclude)
1426 : m_exclude(exclude)
1430 bool pre(const scene::Path &path, scene::Instance &instance) const
1432 exclude_node(path.top(), m_exclude);
1438 void Scene_Exclude_All(bool exclude)
1440 GlobalSceneGraph().traverse(ExcludeAllWalker(exclude));
1443 bool Instance_isSelected(const scene::Instance &instance)
1445 const Selectable *selectable = Instance_getSelectable(instance);
1446 return selectable != 0 && selectable->isSelected();
1449 class ExcludeSelectedWalker : public scene::Graph::Walker {
1452 ExcludeSelectedWalker(bool exclude)
1453 : m_exclude(exclude)
1457 bool pre(const scene::Path &path, scene::Instance &instance) const
1459 exclude_node(path.top(),
1460 (instance.isSelected() || instance.childSelected() || instance.parentSelected()) == m_exclude);
1465 void Scene_Exclude_Selected(bool exclude)
1467 GlobalSceneGraph().traverse(ExcludeSelectedWalker(exclude));
1470 class ExcludeRegionedWalker : public scene::Graph::Walker {
1473 ExcludeRegionedWalker(bool exclude)
1474 : m_exclude(exclude)
1478 bool pre(const scene::Path &path, scene::Instance &instance) const
1484 aabb_intersects_aabb(
1485 instance.worldAABB(),
1486 aabb_for_minmax(region_mins, region_maxs)
1496 void Scene_Exclude_Region(bool exclude)
1498 GlobalSceneGraph().traverse(ExcludeRegionedWalker(exclude));
1505 Other filtering options may still be on
1508 void Map_RegionOff()
1510 region_active = false;
1512 region_maxs[0] = g_MaxWorldCoord - 64;
1513 region_mins[0] = g_MinWorldCoord + 64;
1514 region_maxs[1] = g_MaxWorldCoord - 64;
1515 region_mins[1] = g_MinWorldCoord + 64;
1516 region_maxs[2] = g_MaxWorldCoord - 64;
1517 region_mins[2] = g_MinWorldCoord + 64;
1519 Scene_Exclude_All(false);
1522 void Map_ApplyRegion(void)
1524 region_active = true;
1526 Scene_Exclude_Region(false);
1531 ========================
1532 Map_RegionSelectedBrushes
1533 ========================
1535 void Map_RegionSelectedBrushes(void)
1539 if (GlobalSelectionSystem().countSelected() != 0
1540 && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive) {
1541 region_active = true;
1542 Select_GetBounds(region_mins, region_maxs);
1544 Scene_Exclude_Selected(false);
1546 GlobalSelectionSystem().setSelectedAll(false);
1556 void Map_RegionXY(float x_min, float y_min, float x_max, float y_max)
1560 region_mins[0] = x_min;
1561 region_maxs[0] = x_max;
1562 region_mins[1] = y_min;
1563 region_maxs[1] = y_max;
1564 region_mins[2] = g_MinWorldCoord + 64;
1565 region_maxs[2] = g_MaxWorldCoord - 64;
1570 void Map_RegionBounds(const AABB &bounds)
1574 region_mins = vector3_subtracted(bounds.origin, bounds.extents);
1575 region_maxs = vector3_added(bounds.origin, bounds.extents);
1587 void Map_RegionBrush(void)
1589 if (GlobalSelectionSystem().countSelected() != 0) {
1590 scene::Instance &instance = GlobalSelectionSystem().ultimateSelected();
1591 Map_RegionBounds(instance.worldAABB());
1600 bool Map_ImportFile(const char *filename)
1602 ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
1604 g_strLastMapFolder = g_path_get_dirname(filename);
1606 bool success = false;
1608 if (extension_equal(path_get_extension(filename), "bsp")) {
1613 const MapFormat *format = NULL;
1614 const char *moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), path_get_extension(filename));
1615 if (string_not_empty(moduleName)) {
1616 format = ReferenceAPI_getMapModules().findModule(moduleName);
1620 format->wrongFormat = false;
1622 Resource *resource = GlobalReferenceCache().capture(filename);
1623 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1624 if (!resource->load()) {
1625 GlobalReferenceCache().release(filename);
1629 if (format->wrongFormat) {
1630 GlobalReferenceCache().release(filename);
1634 NodeSmartReference clone(NewMapRoot(""));
1635 Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone));
1636 Map_gatherNamespaced(clone);
1637 Map_mergeClonedNames();
1640 GlobalReferenceCache().release(filename);
1643 SceneChangeNotify();
1649 const char *type = GlobalRadiant().getGameDescriptionKeyValue("q3map2_type");
1650 int n = string_length(path_get_extension(filename));
1651 if (n && (extension_equal(path_get_extension(filename), "bsp") ||
1652 extension_equal(path_get_extension(filename), "map"))) {
1653 StringBuffer output;
1654 output.push_string(AppPath_get());
1655 output.push_string("q3map2.");
1656 output.push_string(RADIANT_EXECUTABLE);
1657 output.push_string(" -v -game ");
1658 output.push_string((type && *type) ? type : "quake3");
1659 output.push_string(" -fs_basepath \"");
1660 output.push_string(EnginePath_get());
1661 output.push_string("\" -fs_homepath \"");
1662 output.push_string(g_qeglobals.m_userEnginePath.c_str());
1663 output.push_string("\"");
1666 for (int i = 0; i < g_pakPathCount; i++) {
1667 if (g_strcmp0(g_strPakPath[i].c_str(), "")) {
1668 output.push_string(" -fs_pakpath \"");
1669 output.push_string(g_strPakPath[i].c_str());
1670 output.push_string("\"");
1675 if (g_disableEnginePath) {
1676 output.push_string(" -fs_nobasepath ");
1679 if (g_disableHomePath) {
1680 output.push_string(" -fs_nohomepath ");
1683 output.push_string(" -fs_game ");
1684 output.push_string(gamename_get());
1685 output.push_string(" -convert -format ");
1686 output.push_string(Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map");
1687 if (extension_equal(path_get_extension(filename), "map")) {
1688 output.push_string(" -readmap ");
1690 output.push_string(" \"");
1691 output.push_string(filename);
1692 output.push_string("\"");
1695 Q_Exec(NULL, output.c_str(), NULL, false, true);
1697 // rebuild filename as "filenamewithoutext_converted.map"
1699 output.push_range(filename, filename + string_length(filename) - (n + 1));
1700 output.push_string("_converted.map");
1701 filename = output.c_str();
1704 Resource *resource = GlobalReferenceCache().capture(filename);
1705 resource->refresh(); // avoid loading old version if map has changed on disk since last import
1706 if (!resource->load()) {
1707 GlobalReferenceCache().release(filename);
1710 NodeSmartReference clone(NewMapRoot(""));
1711 Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone));
1712 Map_gatherNamespaced(clone);
1713 Map_mergeClonedNames();
1716 GlobalReferenceCache().release(filename);
1719 SceneChangeNotify();
1728 bool Map_SaveFile(const char *filename)
1730 ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
1731 bool success = MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse, filename);
1733 // refresh VFS to apply new pak filtering based on mapname
1734 // needed for daemon DPK VFS
1745 // Saves selected world brushes and whole entities with partial/full selections
1747 bool Map_SaveSelected(const char *filename)
1749 return MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Selected,
1754 class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker {
1755 scene::Node &m_parent;
1757 ParentSelectedBrushesToEntityWalker(scene::Node &parent) : m_parent(parent)
1761 bool pre(const scene::Path &path, scene::Instance &instance) const
1763 if (path.top().get_pointer() != &m_parent
1764 && Node_isPrimitive(path.top())) {
1765 Selectable *selectable = Instance_getSelectable(instance);
1767 && selectable->isSelected()
1768 && path.size() > 1) {
1775 void post(const scene::Path &path, scene::Instance &instance) const
1777 if (path.top().get_pointer() != &m_parent
1778 && Node_isPrimitive(path.top())) {
1779 Selectable *selectable = Instance_getSelectable(instance);
1781 && selectable->isSelected()
1782 && path.size() > 1) {
1783 scene::Node &parent = path.parent();
1784 if (&parent != &m_parent) {
1785 NodeSmartReference node(path.top().get());
1786 Node_getTraversable(parent)->erase(node);
1787 Node_getTraversable(m_parent)->insert(node);
1794 void Scene_parentSelectedBrushesToEntity(scene::Graph &graph, scene::Node &parent)
1796 graph.traverse(ParentSelectedBrushesToEntityWalker(parent));
1799 class CountSelectedBrushes : public scene::Graph::Walker {
1800 std::size_t &m_count;
1801 mutable std::size_t m_depth;
1803 CountSelectedBrushes(std::size_t &count) : m_count(count), m_depth(0)
1808 bool pre(const scene::Path &path, scene::Instance &instance) const
1810 if (++m_depth != 1 && path.top().get().isRoot()) {
1813 Selectable *selectable = Instance_getSelectable(instance);
1815 && selectable->isSelected()
1816 && Node_isPrimitive(path.top())) {
1822 void post(const scene::Path &path, scene::Instance &instance) const
1828 std::size_t Scene_countSelectedBrushes(scene::Graph &graph)
1831 graph.traverse(CountSelectedBrushes(count));
1842 const char *nodetype_get_name(ENodeType type)
1844 if (type == eNodeMap) {
1847 if (type == eNodeEntity) {
1850 if (type == eNodePrimitive) {
1856 ENodeType node_get_nodetype(scene::Node &node)
1858 if (Node_isEntity(node)) {
1861 if (Node_isPrimitive(node)) {
1862 return eNodePrimitive;
1864 return eNodeUnknown;
1867 bool contains_entity(scene::Node &node)
1869 return Node_getTraversable(node) != 0 && !Node_isBrush(node) && !Node_isPatch(node) && !Node_isEntity(node);
1872 bool contains_primitive(scene::Node &node)
1874 return Node_isEntity(node) && Node_getTraversable(node) != 0 && Node_getEntity(node)->isContainer();
1877 ENodeType node_get_contains(scene::Node &node)
1879 if (contains_entity(node)) {
1882 if (contains_primitive(node)) {
1883 return eNodePrimitive;
1885 return eNodeUnknown;
1888 void Path_parent(const scene::Path &parent, const scene::Path &child)
1890 ENodeType contains = node_get_contains(parent.top());
1891 ENodeType type = node_get_nodetype(child.top());
1893 if (contains != eNodeUnknown && contains == type) {
1894 NodeSmartReference node(child.top().get());
1895 Path_deleteTop(child);
1896 Node_getTraversable(parent.top())->insert(node);
1897 SceneChangeNotify();
1899 globalErrorStream() << "failed - " << nodetype_get_name(type) << " cannot be parented to "
1900 << nodetype_get_name(contains) << " container.\n";
1904 void Scene_parentSelected()
1906 UndoableCommand undo("parentSelected");
1908 if (GlobalSelectionSystem().countSelected() > 1) {
1909 class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor {
1910 const scene::Path &m_parent;
1912 ParentSelectedBrushesToEntityWalker(const scene::Path &parent) : m_parent(parent)
1916 void visit(scene::Instance &instance) const
1918 if (&m_parent != &instance.path()) {
1919 Path_parent(m_parent, instance.path());
1924 ParentSelectedBrushesToEntityWalker visitor(GlobalSelectionSystem().ultimateSelected().path());
1925 GlobalSelectionSystem().foreachSelected(visitor);
1927 globalOutputStream() << "failed - did not find two selected nodes.\n";
1934 if (ConfirmModified("New Map")) {
1941 CopiedString g_mapsPath;
1943 const char *getMapsPath()
1945 return g_mapsPath.c_str();
1948 const char *getLastMapFolderPath()
1950 if (g_strLastMapFolder.empty()) {
1951 GlobalPreferenceSystem().registerPreference("LastMapFolder", make_property_string(g_strLastMapFolder));
1952 if (g_strLastMapFolder.empty()) {
1953 StringOutputStream buffer(1024);
1954 buffer << getMapsPath();
1955 if (!file_readable(buffer.c_str())) {
1957 buffer << g_qeglobals.m_userGamePath.c_str() << "/";
1959 g_strLastMapFolder = buffer.c_str();
1962 return g_strLastMapFolder.c_str();
1965 const char *map_open(const char *title)
1967 return MainFrame_getWindow().file_dialog(TRUE, title, getLastMapFolderPath(), MapFormat::Name(), true, false, false);
1970 const char *map_import(const char *title)
1972 return MainFrame_getWindow().file_dialog(TRUE, title, getLastMapFolderPath(), MapFormat::Name(), false, true, false);
1975 const char *map_save(const char *title)
1977 return MainFrame_getWindow().file_dialog(FALSE, title, getLastMapFolderPath(), MapFormat::Name(), false, false, true);
1982 if (!ConfirmModified("Open Map")) {
1986 const char *filename = map_open("Open Map");
1988 if (filename != NULL) {
1989 MRU_AddFile(filename);
1992 Map_LoadFile(filename);
1998 const char *filename = map_import("Import Map");
2000 if (filename != NULL) {
2001 UndoableCommand undo("mapImport");
2002 Map_ImportFile(filename);
2008 const char *filename = map_save("Save Map");
2010 if (filename != NULL) {
2011 g_strLastMapFolder = g_path_get_dirname(filename);
2012 MRU_AddFile(filename);
2013 Map_Rename(filename);
2026 if (Map_Unnamed(g_map)) {
2028 } else if (Map_Modified(g_map)) {
2035 const char *filename = map_save("Export Selection");
2037 if (filename != NULL) {
2038 g_strLastMapFolder = g_path_get_dirname(filename);
2039 Map_SaveSelected(filename);
2045 const char *filename = map_save("Export Region");
2047 if (filename != NULL) {
2048 g_strLastMapFolder = g_path_get_dirname(filename);
2049 Map_SaveRegion(filename);
2057 SceneChangeNotify();
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(),
2067 g_pParentWnd->GetXYWnd()->GetOrigin()[0] +
2068 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
2069 g_pParentWnd->GetXYWnd()->GetOrigin()[1] +
2070 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
2072 SceneChangeNotify();
2078 SceneChangeNotify();
2081 void RegionSelected()
2083 Map_RegionSelectedBrushes();
2084 SceneChangeNotify();
2088 class BrushFindByIndexWalker : public scene::Traversable::Walker {
2089 mutable std::size_t m_index;
2090 scene::Path &m_path;
2092 BrushFindByIndexWalker(std::size_t index, scene::Path &path)
2093 : m_index(index), m_path(path)
2097 bool pre(scene::Node &node) const
2099 if (Node_isPrimitive(node) && m_index-- == 0) {
2100 m_path.push(makeReference(node));
2106 class EntityFindByIndexWalker : public scene::Traversable::Walker {
2107 mutable std::size_t m_index;
2108 scene::Path &m_path;
2110 EntityFindByIndexWalker(std::size_t index, scene::Path &path)
2111 : m_index(index), m_path(path)
2115 bool pre(scene::Node &node) const
2117 if (Node_isEntity(node) && m_index-- == 0) {
2118 m_path.push(makeReference(node));
2124 void Scene_FindEntityBrush(std::size_t entity, std::size_t brush, scene::Path &path)
2126 path.push(makeReference(GlobalSceneGraph().root()));
2128 Node_getTraversable(path.top())->traverse(EntityFindByIndexWalker(entity, path));
2130 if (path.size() == 2) {
2131 scene::Traversable *traversable = Node_getTraversable(path.top());
2132 if (traversable != 0) {
2133 traversable->traverse(BrushFindByIndexWalker(brush, path));
2138 inline bool Node_hasChildren(scene::Node &node)
2140 scene::Traversable *traversable = Node_getTraversable(node);
2141 return traversable != 0 && !traversable->empty();
2144 void SelectBrush(int entitynum, int brushnum)
2147 Scene_FindEntityBrush(entitynum, brushnum, path);
2148 if (path.size() == 3 || (path.size() == 2 && !Node_hasChildren(path.top()))) {
2149 scene::Instance *instance = GlobalSceneGraph().find(path);
2150 ASSERT_MESSAGE(instance != 0, "SelectBrush: path not found in scenegraph");
2151 Selectable *selectable = Instance_getSelectable(*instance);
2152 ASSERT_MESSAGE(selectable != 0, "SelectBrush: path not selectable");
2153 selectable->setSelected(true);
2154 g_pParentWnd->GetXYWnd()->PositionView(instance->worldAABB().origin);
2159 class BrushFindIndexWalker : public scene::Graph::Walker {
2160 mutable const scene::Node *m_node;
2161 std::size_t &m_count;
2163 BrushFindIndexWalker(const scene::Node &node, std::size_t &count)
2164 : m_node(&node), m_count(count)
2168 bool pre(const scene::Path &path, scene::Instance &instance) const
2170 if (Node_isPrimitive(path.top())) {
2171 if (m_node == path.top().get_pointer()) {
2182 class EntityFindIndexWalker : public scene::Graph::Walker {
2183 mutable const scene::Node *m_node;
2184 std::size_t &m_count;
2186 EntityFindIndexWalker(const scene::Node &node, std::size_t &count)
2187 : m_node(&node), m_count(count)
2191 bool pre(const scene::Path &path, scene::Instance &instance) const
2193 if (Node_isEntity(path.top())) {
2194 if (m_node == path.top().get_pointer()) {
2205 static void GetSelectionIndex(int *ent, int *brush)
2207 std::size_t count_brush = 0;
2208 std::size_t count_entity = 0;
2209 if (GlobalSelectionSystem().countSelected() != 0) {
2210 const scene::Path &path = GlobalSelectionSystem().ultimateSelected().path();
2212 GlobalSceneGraph().traverse(BrushFindIndexWalker(path.top(), count_brush));
2213 GlobalSceneGraph().traverse(EntityFindIndexWalker(path.parent(), count_entity));
2215 *brush = int(count_brush);
2216 *ent = int(count_entity);
2222 ui::Entry entity{ui::null};
2223 ui::Entry brush{ui::null};
2225 ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback),
2228 auto accel = ui::AccelGroup(ui::New);
2229 window.add_accel_group(accel);
2232 auto vbox = create_dialog_vbox(4, 4);
2235 auto table = create_dialog_table(2, 2, 4, 4);
2236 vbox.pack_start(table, TRUE, TRUE, 0);
2238 ui::Widget label = ui::Label("Entity number");
2240 (table).attach(label, {0, 1, 0, 1}, {0, 0});
2243 ui::Widget label = ui::Label("Brush number");
2245 (table).attach(label, {0, 1, 1, 2}, {0, 0});
2248 auto entry = ui::Entry(ui::New);
2250 table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
2251 gtk_widget_grab_focus(entry);
2255 auto entry = ui::Entry(ui::New);
2257 table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
2263 auto hbox = create_dialog_hbox(4);
2264 vbox.pack_start(hbox, TRUE, TRUE, 0);
2266 auto button = create_dialog_button("Find", G_CALLBACK(dialog_button_ok), &dialog);
2267 hbox.pack_start(button, FALSE, FALSE, 0);
2268 widget_make_default(button);
2269 gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Return, (GdkModifierType) 0,
2273 auto button = create_dialog_button("Close", G_CALLBACK(dialog_button_cancel), &dialog);
2274 hbox.pack_start(button, FALSE, FALSE, 0);
2275 gtk_widget_add_accelerator(button, "clicked", accel, GDK_KEY_Escape, (GdkModifierType) 0,
2281 // Initialize dialog
2285 GetSelectionIndex(&ent, &br);
2286 sprintf(buf, "%i", ent);
2288 sprintf(buf, "%i", br);
2291 if (modal_dialog_show(window, dialog) == eIDOK) {
2292 const char *entstr = gtk_entry_get_text(entity);
2293 const char *brushstr = gtk_entry_get_text(brush);
2294 SelectBrush(atoi(entstr), atoi(brushstr));
2300 void Map_constructPreferences(PreferencesPage &page)
2302 page.appendCheckBox("", "Load last map at startup", g_bLoadLastMap);
2303 page.appendCheckBox("", "Add entity and brush number comments on map write", g_writeMapComments);
2307 class MapEntityClasses : public ModuleObserver {
2308 std::size_t m_unrealised;
2310 MapEntityClasses() : m_unrealised(1)
2316 if (--m_unrealised == 0) {
2317 if (g_map.m_resource != 0) {
2318 ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
2319 g_map.m_resource->realise();
2326 if (++m_unrealised == 1) {
2327 if (g_map.m_resource != 0) {
2328 g_map.m_resource->flush();
2329 g_map.m_resource->unrealise();
2335 MapEntityClasses g_MapEntityClasses;
2338 class MapModuleObserver : public ModuleObserver {
2339 std::size_t m_unrealised;
2341 MapModuleObserver() : m_unrealised(1)
2347 if (--m_unrealised == 0) {
2348 ASSERT_MESSAGE(!string_empty(g_qeglobals.m_userGamePath.c_str()),
2349 "maps_directory: user-game-path is empty");
2350 StringOutputStream buffer(256);
2351 buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
2352 Q_mkdir(buffer.c_str());
2353 g_mapsPath = buffer.c_str();
2359 if (++m_unrealised == 1) {
2365 MapModuleObserver g_MapModuleObserver;
2367 CopiedString g_strLastMap;
2368 bool g_bLoadLastMap = false;
2370 void Map_Construct()
2372 GlobalCommands_insert("RegionOff", makeCallbackF(RegionOff));
2373 GlobalCommands_insert("RegionSetXY", makeCallbackF(RegionXY));
2374 GlobalCommands_insert("RegionSetBrush", makeCallbackF(RegionBrush));
2375 GlobalCommands_insert("RegionSetSelection", makeCallbackF(RegionSelected),
2376 Accelerator('R', (GdkModifierType) (GDK_SHIFT_MASK | GDK_CONTROL_MASK)));
2378 GlobalPreferenceSystem().registerPreference("LastMap", make_property_string(g_strLastMap));
2379 GlobalPreferenceSystem().registerPreference("LoadLastMap", make_property_string(g_bLoadLastMap));
2380 GlobalPreferenceSystem().registerPreference("MapInfoDlg", make_property<WindowPosition_String>(g_posMapInfoWnd));
2381 GlobalPreferenceSystem().registerPreference("WriteMapComments", make_property_string(g_writeMapComments));
2383 PreferencesDialog_addSettingsPreferences(makeCallbackF(Map_constructPreferences));
2385 GlobalEntityClassManager().attach(g_MapEntityClasses);
2386 Radiant_attachHomePathsObserver(g_MapModuleObserver);
2391 Radiant_detachHomePathsObserver(g_MapModuleObserver);
2392 GlobalEntityClassManager().detach(g_MapEntityClasses);