+ bool pre(const scene::Path &path, scene::Instance &instance) const
+ {
+ Entity *entity = Node_getEntity(path.top());
+ if (entity != 0) {
+ const EntityClass &eclass = entity->getEntityClass();
+ if (m_entitymap.find(eclass.name()) == m_entitymap.end()) {
+ m_entitymap[eclass.name()] = 1;
+ } else { ++m_entitymap[eclass.name()]; }
+ }
+ return true;
+ }
+};
+
+void Scene_EntityBreakdown(EntityBreakdown &entitymap)
+{
+ GlobalSceneGraph().traverse(EntityBreakdownWalker(entitymap));
+}
+
+
+WindowPosition g_posMapInfoWnd(c_default_window_pos);
+
+void DoMapInfo()
+{
+ ModalDialog dialog;
+ ui::Entry brushes_entry{ui::null};
+ ui::Entry entities_entry{ui::null};
+ ui::ListStore EntityBreakdownWalker{ui::null};
+
+ ui::Window window = MainFrame_getWindow().create_dialog_window("Map Info", G_CALLBACK(dialog_delete_callback),
+ &dialog);
+
+ window_set_position(window, g_posMapInfoWnd);
+
+ {
+ auto vbox = create_dialog_vbox(4, 4);
+ window.add(vbox);
+
+ {
+ auto hbox = create_dialog_hbox(4);
+ vbox.pack_start(hbox, FALSE, TRUE, 0);
+
+ {
+ auto table = create_dialog_table(2, 2, 4, 4);
+ hbox.pack_start(table, TRUE, TRUE, 0);
+
+ {
+ auto entry = ui::Entry(ui::New);
+ entry.show();
+ table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
+ gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
+
+ brushes_entry = entry;
+ }
+ {
+ auto entry = ui::Entry(ui::New);
+ entry.show();
+ table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
+ gtk_editable_set_editable(GTK_EDITABLE(entry), FALSE);
+
+ entities_entry = entry;
+ }
+ {
+ ui::Widget label = ui::Label("Total Brushes");
+ label.show();
+ table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
+ gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+ }
+ {
+ ui::Widget label = ui::Label("Total Entities");
+ label.show();
+ table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
+ gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+ }
+ }
+ {
+ auto vbox2 = create_dialog_vbox(4);
+ hbox.pack_start(vbox2, FALSE, FALSE, 0);
+
+ {
+ auto button = create_dialog_button("Close", G_CALLBACK(dialog_button_ok), &dialog);
+ vbox2.pack_start(button, FALSE, FALSE, 0);
+ }
+ }
+ }
+ {
+ ui::Widget label = ui::Label("Entity breakdown");
+ label.show();
+ vbox.pack_start(label, FALSE, TRUE, 0);
+ gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
+ }
+ {
+ auto scr = create_scrolled_window(ui::Policy::NEVER, ui::Policy::AUTOMATIC, 4);
+ vbox.pack_start(scr, TRUE, TRUE, 0);
+
+ {
+ auto store = ui::ListStore::from(gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING));
+
+ auto view = ui::TreeView(ui::TreeModel::from(store._handle));
+ gtk_tree_view_set_headers_clickable(view, TRUE);
+
+ {
+ auto renderer = ui::CellRendererText(ui::New);
+ auto column = ui::TreeViewColumn("Entity", renderer, {{"text", 0}});
+ gtk_tree_view_append_column(view, column);
+ gtk_tree_view_column_set_sort_column_id(column, 0);
+ }
+
+ {
+ auto renderer = ui::CellRendererText(ui::New);
+ auto column = ui::TreeViewColumn("Count", renderer, {{"text", 1}});
+ gtk_tree_view_append_column(view, column);
+ gtk_tree_view_column_set_sort_column_id(column, 1);
+ }
+
+ view.show();
+
+ scr.add(view);
+
+ EntityBreakdownWalker = store;
+ }
+ }
+ }
+
+ // Initialize fields
+
+ {
+ EntityBreakdown entitymap;
+ Scene_EntityBreakdown(entitymap);
+
+ for (EntityBreakdown::iterator i = entitymap.begin(); i != entitymap.end(); ++i) {
+ char tmp[16];
+ sprintf(tmp, "%u", Unsigned((*i).second));
+ EntityBreakdownWalker.append(0, (*i).first.c_str(), 1, tmp);
+ }
+ }
+
+ EntityBreakdownWalker.unref();
+
+ char tmp[16];
+ sprintf(tmp, "%u", Unsigned(g_brushCount.get()));
+ brushes_entry.text(tmp);
+ sprintf(tmp, "%u", Unsigned(g_entityCount.get()));
+ entities_entry.text(tmp);
+
+ modal_dialog_show(window, dialog);
+
+ // save before exit
+ window_get_position(window, g_posMapInfoWnd);
+
+ window.destroy();
+}
+
+
+class ScopeTimer {
+ Timer m_timer;
+ const char *m_message;
+public:
+ ScopeTimer(const char *message)
+ : m_message(message)
+ {
+ m_timer.start();
+ }
+
+ ~ScopeTimer()
+ {
+ double elapsed_time = m_timer.elapsed_msec() / 1000.f;
+ globalOutputStream() << m_message << " timer: " << FloatFormat(elapsed_time, 5, 2) << " second(s) elapsed\n";
+ }
+};
+
+CopiedString g_strLastMapFolder = "";
+
+/*
+ ================
+ Map_LoadFile
+ ================
+ */
+
+void Map_LoadFile(const char *filename)
+{
+ g_map.m_name = filename;
+
+ // refresh VFS to apply new pak filtering based on mapname
+ // needed for daemon DPK VFS
+ VFS_Refresh();
+
+ globalOutputStream() << "Loading map from " << filename << "\n";
+ ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
+
+ MRU_AddFile(filename);
+ g_strLastMapFolder = g_path_get_dirname(filename);
+
+ {
+ ScopeTimer timer("map load");
+
+ const MapFormat *format = NULL;
+ const char *moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), path_get_extension(filename));
+ if (string_not_empty(moduleName)) {
+ format = ReferenceAPI_getMapModules().findModule(moduleName);
+ }
+
+ for (int i = 0; i < Brush_toggleFormatCount(); ++i) {
+ if (i) {
+ Map_Free();
+ }
+ Brush_toggleFormat(i);
+ Map_UpdateTitle(g_map);
+
+ g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str());
+ if (format) {
+ format->wrongFormat = false;
+ }
+ g_map.m_resource->attach(g_map);
+ if (format) {
+ if (!format->wrongFormat) {
+ break;
+ }
+ }
+ }
+
+ Node_getTraversable(GlobalSceneGraph().root())->traverse(entity_updateworldspawn());
+ }
+
+ globalOutputStream() << "--- LoadMapFile ---\n";
+ globalOutputStream() << g_map.m_name.c_str() << "\n";
+
+ globalOutputStream() << Unsigned(g_brushCount.get()) << " primitive\n";
+ globalOutputStream() << Unsigned(g_entityCount.get()) << " entities\n";
+
+ //GlobalEntityCreator().printStatistics();
+
+ //
+ // move the view to a start position
+ //
+ Map_StartPosition();
+
+ g_currentMap = &g_map;
+}
+
+class Excluder {
+public:
+ virtual bool excluded(scene::Node &node) const = 0;
+};
+
+class ExcludeWalker : public scene::Traversable::Walker {
+ const scene::Traversable::Walker &m_walker;
+ const Excluder *m_exclude;
+ mutable bool m_skip;
+public:
+ ExcludeWalker(const scene::Traversable::Walker &walker, const Excluder &exclude)
+ : m_walker(walker), m_exclude(&exclude), m_skip(false)
+ {
+ }
+
+ bool pre(scene::Node &node) const
+ {
+ if (m_exclude->excluded(node) || node.isRoot()) {
+ m_skip = true;
+ return false;
+ } else {
+ m_walker.pre(node);
+ }
+ return true;
+ }
+
+ void post(scene::Node &node) const
+ {
+ if (m_skip) {
+ m_skip = false;
+ } else {
+ m_walker.post(node);
+ }
+ }
+};
+
+class AnyInstanceSelected : public scene::Instantiable::Visitor {
+ bool &m_selected;
+public:
+ AnyInstanceSelected(bool &selected) : m_selected(selected)
+ {
+ m_selected = false;
+ }
+
+ void visit(scene::Instance &instance) const
+ {
+ Selectable *selectable = Instance_getSelectable(instance);
+ if (selectable != 0
+ && selectable->isSelected()) {
+ m_selected = true;
+ }
+ }
+};
+
+bool Node_instanceSelected(scene::Node &node)
+{
+ scene::Instantiable *instantiable = Node_getInstantiable(node);
+ ASSERT_NOTNULL(instantiable);
+ bool selected;
+ instantiable->forEachInstance(AnyInstanceSelected(selected));
+ return selected;
+}
+
+class SelectedDescendantWalker : public scene::Traversable::Walker {
+ bool &m_selected;
+public:
+ SelectedDescendantWalker(bool &selected) : m_selected(selected)
+ {
+ m_selected = false;
+ }
+
+ bool pre(scene::Node &node) const
+ {
+ if (node.isRoot()) {
+ return false;
+ }
+
+ if (Node_instanceSelected(node)) {
+ m_selected = true;
+ }
+
+ return true;
+ }
+};
+
+bool Node_selectedDescendant(scene::Node &node)
+{
+ bool selected;
+ Node_traverseSubgraph(node, SelectedDescendantWalker(selected));
+ return selected;
+}
+
+class SelectionExcluder : public Excluder {
+public:
+ bool excluded(scene::Node &node) const
+ {
+ return !Node_selectedDescendant(node);
+ }
+};
+
+class IncludeSelectedWalker : public scene::Traversable::Walker {
+ const scene::Traversable::Walker &m_walker;
+ mutable std::size_t m_selected;
+ mutable bool m_skip;
+
+ bool selectedParent() const
+ {
+ return m_selected != 0;
+ }
+
+public:
+ IncludeSelectedWalker(const scene::Traversable::Walker &walker)
+ : m_walker(walker), m_selected(0), m_skip(false)
+ {
+ }
+
+ bool pre(scene::Node &node) const
+ {
+ // include node if:
+ // node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
+ if (!node.isRoot() && (Node_selectedDescendant(node) || selectedParent())) {
+ if (Node_instanceSelected(node)) {
+ ++m_selected;
+ }
+ m_walker.pre(node);
+ return true;
+ } else {
+ m_skip = true;
+ return false;
+ }
+ }
+
+ void post(scene::Node &node) const
+ {
+ if (m_skip) {
+ m_skip = false;
+ } else {
+ if (Node_instanceSelected(node)) {
+ --m_selected;
+ }
+ m_walker.post(node);
+ }
+ }
+};
+
+void Map_Traverse_Selected(scene::Node &root, const scene::Traversable::Walker &walker)
+{
+ scene::Traversable *traversable = Node_getTraversable(root);
+ if (traversable != 0) {
+#if 0
+ traversable->traverse( ExcludeWalker( walker, SelectionExcluder() ) );
+#else
+ traversable->traverse(IncludeSelectedWalker(walker));
+#endif
+ }
+}
+
+void Map_ExportSelected(TextOutputStream &out, const MapFormat &format)
+{
+ format.writeGraph(GlobalSceneGraph().root(), Map_Traverse_Selected, out, g_writeMapComments);
+}
+
+void Map_Traverse(scene::Node &root, const scene::Traversable::Walker &walker)
+{
+ scene::Traversable *traversable = Node_getTraversable(root);
+ if (traversable != 0) {
+ traversable->traverse(walker);
+ }
+}
+
+class RegionExcluder : public Excluder {
+public:
+ bool excluded(scene::Node &node) const
+ {
+ return node.excluded();
+ }
+};
+
+void Map_Traverse_Region(scene::Node &root, const scene::Traversable::Walker &walker)
+{
+ scene::Traversable *traversable = Node_getTraversable(root);
+ if (traversable != 0) {
+ traversable->traverse(ExcludeWalker(walker, RegionExcluder()));
+ }
+}
+
+bool Map_SaveRegion(const char *filename)
+{
+ AddRegionBrushes();
+
+ bool success = MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Region,
+ filename);
+
+ RemoveRegionBrushes();
+
+ return success;
+}
+
+
+void Map_RenameAbsolute(const char *absolute)
+{
+ Resource *resource = GlobalReferenceCache().capture(absolute);
+ NodeSmartReference clone(NewMapRoot(path_make_relative(absolute, GlobalFileSystem().findRoot(absolute))));
+ resource->setNode(clone.get_pointer());
+
+ {
+ //ScopeTimer timer("clone subgraph");
+ Node_getTraversable(GlobalSceneGraph().root())->traverse(CloneAll(clone));
+ }
+
+ g_map.m_resource->detach(g_map);
+ GlobalReferenceCache().release(g_map.m_name.c_str());
+
+ g_map.m_resource = resource;
+
+ g_map.m_name = absolute;
+ Map_UpdateTitle(g_map);
+
+ g_map.m_resource->attach(g_map);
+ // refresh VFS to apply new pak filtering based on mapname
+ // needed for daemon DPK VFS
+ VFS_Refresh();
+}
+
+void Map_Rename(const char *filename)
+{
+ if (!string_equal(g_map.m_name.c_str(), filename)) {
+ ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
+
+ Map_RenameAbsolute(filename);
+
+ SceneChangeNotify();
+ } else {
+ SaveReferences();
+ }
+}
+
+bool Map_Save()
+{
+ Pointfile_Clear();
+
+ ScopeTimer timer("map save");
+ SaveReferences();
+ return true; // assume success..
+}
+
+/*
+ ===========
+ Map_New
+
+ ===========
+ */
+void Map_New()
+{
+ //globalOutputStream() << "Map_New\n";
+
+ g_map.m_name = "unnamed.map";
+ Map_UpdateTitle(g_map);
+
+ {
+ g_map.m_resource = GlobalReferenceCache().capture(g_map.m_name.c_str());
+// ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
+ g_map.m_resource->attach(g_map);
+
+ SceneChangeNotify();
+ }
+
+ FocusViews(g_vector3_identity, 0);
+
+ g_currentMap = &g_map;
+
+ // restart VFS to apply new pak filtering based on mapname
+ // needed for daemon DPK VFS
+ VFS_Restart();
+}
+
+extern void ConstructRegionBrushes(scene::Node *brushes[6], const Vector3 ®ion_mins, const Vector3 ®ion_maxs);
+
+void ConstructRegionStartpoint(scene::Node *startpoint, const Vector3 ®ion_mins, const Vector3 ®ion_maxs)
+{
+ /*!
+ \todo we need to make sure that the player start IS inside the region and bail out if it's not
+ the compiler will refuse to compile a map with a player_start somewhere in empty space..
+ for now, let's just print an error
+ */
+
+ Vector3 vOrig(Camera_getOrigin(*g_pParentWnd->GetCamWnd()));
+
+ for (int i = 0; i < 3; i++) {
+ if (vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i]) {
+ globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
+ break;
+ }
+ }
+
+ // write the info_playerstart
+ char sTmp[1024];
+ sprintf(sTmp, "%d %d %d", (int) vOrig[0], (int) vOrig[1], (int) vOrig[2]);
+ Node_getEntity(*startpoint)->setKeyValue("origin", sTmp);
+ sprintf(sTmp, "%d", (int) Camera_getAngles(*g_pParentWnd->GetCamWnd())[CAMERA_YAW]);
+ Node_getEntity(*startpoint)->setKeyValue("angle", sTmp);
+}
+
+/*
+ ===========================================================
+
+ REGION
+
+ ===========================================================
+ */
+bool region_active;
+Vector3 region_mins(g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord);
+Vector3 region_maxs(g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord);
+
+scene::Node *region_sides[6];
+scene::Node *region_startpoint = 0;
+
+/*
+ ===========
+ AddRegionBrushes
+ a regioned map will have temp walls put up at the region boundary
+ \todo TODO TTimo old implementation of region brushes
+ we still add them straight in the worldspawn and take them out after the map is saved
+ with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
+ ===========
+ */
+void AddRegionBrushes(void)
+{
+ int i;
+
+ for (i = 0; i < 6; i++) {
+ region_sides[i] = &GlobalBrushCreator().createBrush();
+ Node_getTraversable(Map_FindOrInsertWorldspawn(g_map))->insert(NodeSmartReference(*region_sides[i]));
+ }
+
+ region_startpoint = &GlobalEntityCreator().createEntity(
+ GlobalEntityClassManager().findOrInsert("info_player_start", false));
+
+ ConstructRegionBrushes(region_sides, region_mins, region_maxs);
+ ConstructRegionStartpoint(region_startpoint, region_mins, region_maxs);
+
+ Node_getTraversable(GlobalSceneGraph().root())->insert(NodeSmartReference(*region_startpoint));
+}
+
+void RemoveRegionBrushes(void)
+{
+ for (std::size_t i = 0; i < 6; i++) {
+ Node_getTraversable(*Map_GetWorldspawn(g_map))->erase(*region_sides[i]);
+ }
+ Node_getTraversable(GlobalSceneGraph().root())->erase(*region_startpoint);
+}
+
+inline void exclude_node(scene::Node &node, bool exclude)
+{
+ exclude
+ ? node.enable(scene::Node::eExcluded)
+ : node.disable(scene::Node::eExcluded);
+}
+
+class ExcludeAllWalker : public scene::Graph::Walker {
+ bool m_exclude;
+public:
+ ExcludeAllWalker(bool exclude)
+ : m_exclude(exclude)
+ {
+ }
+
+ bool pre(const scene::Path &path, scene::Instance &instance) const
+ {
+ exclude_node(path.top(), m_exclude);
+
+ return true;
+ }
+};
+
+void Scene_Exclude_All(bool exclude)
+{
+ GlobalSceneGraph().traverse(ExcludeAllWalker(exclude));
+}
+
+bool Instance_isSelected(const scene::Instance &instance)
+{
+ const Selectable *selectable = Instance_getSelectable(instance);
+ return selectable != 0 && selectable->isSelected();
+}
+
+class ExcludeSelectedWalker : public scene::Graph::Walker {
+ bool m_exclude;
+public:
+ ExcludeSelectedWalker(bool exclude)
+ : m_exclude(exclude)
+ {
+ }
+
+ bool pre(const scene::Path &path, scene::Instance &instance) const
+ {
+ exclude_node(path.top(),
+ (instance.isSelected() || instance.childSelected() || instance.parentSelected()) == m_exclude);
+ return true;
+ }
+};
+
+void Scene_Exclude_Selected(bool exclude)
+{
+ GlobalSceneGraph().traverse(ExcludeSelectedWalker(exclude));
+}
+
+class ExcludeRegionedWalker : public scene::Graph::Walker {
+ bool m_exclude;
+public:
+ ExcludeRegionedWalker(bool exclude)
+ : m_exclude(exclude)
+ {
+ }
+
+ bool pre(const scene::Path &path, scene::Instance &instance) const
+ {
+ exclude_node(
+ path.top(),
+ !(
+ (
+ aabb_intersects_aabb(
+ instance.worldAABB(),
+ aabb_for_minmax(region_mins, region_maxs)
+ ) != 0
+ ) ^ m_exclude
+ )
+ );
+
+ return true;
+ }
+};
+
+void Scene_Exclude_Region(bool exclude)
+{
+ GlobalSceneGraph().traverse(ExcludeRegionedWalker(exclude));
+}
+
+/*
+ ===========
+ Map_RegionOff
+
+ Other filtering options may still be on
+ ===========
+ */
+void Map_RegionOff()
+{
+ region_active = false;
+
+ region_maxs[0] = g_MaxWorldCoord - 64;
+ region_mins[0] = g_MinWorldCoord + 64;
+ region_maxs[1] = g_MaxWorldCoord - 64;
+ region_mins[1] = g_MinWorldCoord + 64;
+ region_maxs[2] = g_MaxWorldCoord - 64;
+ region_mins[2] = g_MinWorldCoord + 64;
+
+ Scene_Exclude_All(false);
+}
+
+void Map_ApplyRegion(void)
+{
+ region_active = true;
+
+ Scene_Exclude_Region(false);
+}
+
+
+/*
+ ========================
+ Map_RegionSelectedBrushes
+ ========================
+ */
+void Map_RegionSelectedBrushes(void)
+{
+ Map_RegionOff();
+
+ if (GlobalSelectionSystem().countSelected() != 0
+ && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive) {
+ region_active = true;
+ Select_GetBounds(region_mins, region_maxs);
+
+ Scene_Exclude_Selected(false);
+
+ GlobalSelectionSystem().setSelectedAll(false);
+ }
+}
+
+
+/*
+ ===========
+ Map_RegionXY
+ ===========
+ */
+void Map_RegionXY(float x_min, float y_min, float x_max, float y_max)
+{
+ Map_RegionOff();
+
+ region_mins[0] = x_min;
+ region_maxs[0] = x_max;
+ region_mins[1] = y_min;
+ region_maxs[1] = y_max;
+ region_mins[2] = g_MinWorldCoord + 64;
+ region_maxs[2] = g_MaxWorldCoord - 64;
+
+ Map_ApplyRegion();
+}
+
+void Map_RegionBounds(const AABB &bounds)
+{
+ Map_RegionOff();
+
+ region_mins = vector3_subtracted(bounds.origin, bounds.extents);
+ region_maxs = vector3_added(bounds.origin, bounds.extents);
+
+ deleteSelection();
+
+ Map_ApplyRegion();
+}
+
+/*
+ ===========
+ Map_RegionBrush
+ ===========
+ */
+void Map_RegionBrush(void)
+{
+ if (GlobalSelectionSystem().countSelected() != 0) {
+ scene::Instance &instance = GlobalSelectionSystem().ultimateSelected();
+ Map_RegionBounds(instance.worldAABB());
+ }
+}
+
+//
+//================
+//Map_ImportFile
+//================
+//
+bool Map_ImportFile(const char *filename)
+{
+ ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Loading Map");
+
+ g_strLastMapFolder = g_path_get_dirname(filename);
+
+ bool success = false;
+
+ if (extension_equal(path_get_extension(filename), "bsp")) {
+ goto tryDecompile;
+ }
+
+ {
+ const MapFormat *format = NULL;
+ const char *moduleName = findModuleName(&GlobalFiletypes(), MapFormat::Name(), path_get_extension(filename));
+ if (string_not_empty(moduleName)) {
+ format = ReferenceAPI_getMapModules().findModule(moduleName);
+ }
+
+ if (format) {
+ format->wrongFormat = false;
+ }
+ Resource *resource = GlobalReferenceCache().capture(filename);
+ resource->refresh(); // avoid loading old version if map has changed on disk since last import
+ if (!resource->load()) {
+ GlobalReferenceCache().release(filename);
+ goto tryDecompile;
+ }
+ if (format) {
+ if (format->wrongFormat) {
+ GlobalReferenceCache().release(filename);
+ goto tryDecompile;
+ }
+ }
+ NodeSmartReference clone(NewMapRoot(""));
+ Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone));
+ Map_gatherNamespaced(clone);
+ Map_mergeClonedNames();
+ MergeMap(clone);
+ success = true;
+ GlobalReferenceCache().release(filename);
+ }
+
+ SceneChangeNotify();
+
+ return success;
+
+ tryDecompile:
+
+ const char *type = GlobalRadiant().getGameDescriptionKeyValue("q3map2_type");
+ int n = string_length(path_get_extension(filename));
+ if (n && (extension_equal(path_get_extension(filename), "bsp") ||
+ extension_equal(path_get_extension(filename), "map"))) {
+ StringBuffer output;
+ output.push_string(AppPath_get());
+ output.push_string("q3map2.");
+ output.push_string(RADIANT_EXECUTABLE);
+ output.push_string(" -v -game ");
+ output.push_string((type && *type) ? type : "quake3");
+ output.push_string(" -fs_basepath \"");
+ output.push_string(EnginePath_get());
+ output.push_string("\" -fs_homepath \"");
+ output.push_string(g_qeglobals.m_userEnginePath.c_str());
+ output.push_string("\"");
+
+ // extra pakpaths
+ for (int i = 0; i < g_pakPathCount; i++) {
+ if (g_strcmp0(g_strPakPath[i].c_str(), "")) {
+ output.push_string(" -fs_pakpath \"");
+ output.push_string(g_strPakPath[i].c_str());
+ output.push_string("\"");
+ }
+ }
+
+ // extra switches
+ if (g_disableEnginePath) {
+ output.push_string(" -fs_nobasepath ");
+ }
+
+ if (g_disableHomePath) {
+ output.push_string(" -fs_nohomepath ");
+ }
+
+ output.push_string(" -fs_game ");
+ output.push_string(gamename_get());
+ output.push_string(" -convert -format ");
+ output.push_string(Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map");
+ if (extension_equal(path_get_extension(filename), "map")) {
+ output.push_string(" -readmap ");
+ }
+ output.push_string(" \"");
+ output.push_string(filename);
+ output.push_string("\"");
+
+ // run
+ Q_Exec(NULL, output.c_str(), NULL, false, true);
+
+ // rebuild filename as "filenamewithoutext_converted.map"
+ output.clear();
+ output.push_range(filename, filename + string_length(filename) - (n + 1));
+ output.push_string("_converted.map");
+ filename = output.c_str();
+
+ // open
+ Resource *resource = GlobalReferenceCache().capture(filename);
+ resource->refresh(); // avoid loading old version if map has changed on disk since last import
+ if (!resource->load()) {
+ GlobalReferenceCache().release(filename);
+ goto tryDecompile;
+ }
+ NodeSmartReference clone(NewMapRoot(""));
+ Node_getTraversable(*resource->getNode())->traverse(CloneAll(clone));
+ Map_gatherNamespaced(clone);
+ Map_mergeClonedNames();
+ MergeMap(clone);
+ success = true;
+ GlobalReferenceCache().release(filename);
+ }
+
+ SceneChangeNotify();
+ return success;
+}
+
+/*
+ ===========
+ Map_SaveFile
+ ===========
+ */
+bool Map_SaveFile(const char *filename)
+{
+ ScopeDisableScreenUpdates disableScreenUpdates("Processing...", "Saving Map");
+ bool success = MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse, filename);
+ if (success) {
+ // refresh VFS to apply new pak filtering based on mapname
+ // needed for daemon DPK VFS
+ VFS_Refresh();
+ }
+ return success;
+}
+
+//
+//===========
+//Map_SaveSelected
+//===========
+//
+// Saves selected world brushes and whole entities with partial/full selections
+//
+bool Map_SaveSelected(const char *filename)
+{
+ return MapResource_saveFile(MapFormat_forFile(filename), GlobalSceneGraph().root(), Map_Traverse_Selected,
+ filename);
+}
+
+
+class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker {
+ scene::Node &m_parent;
+public:
+ ParentSelectedBrushesToEntityWalker(scene::Node &parent) : m_parent(parent)
+ {
+ }
+
+ bool pre(const scene::Path &path, scene::Instance &instance) const
+ {
+ if (path.top().get_pointer() != &m_parent
+ && Node_isPrimitive(path.top())) {
+ Selectable *selectable = Instance_getSelectable(instance);
+ if (selectable != 0
+ && selectable->isSelected()
+ && path.size() > 1) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ void post(const scene::Path &path, scene::Instance &instance) const
+ {
+ if (path.top().get_pointer() != &m_parent
+ && Node_isPrimitive(path.top())) {
+ Selectable *selectable = Instance_getSelectable(instance);
+ if (selectable != 0
+ && selectable->isSelected()
+ && path.size() > 1) {
+ scene::Node &parent = path.parent();
+ if (&parent != &m_parent) {
+ NodeSmartReference node(path.top().get());
+ Node_getTraversable(parent)->erase(node);
+ Node_getTraversable(m_parent)->insert(node);
+ }
+ }
+ }
+ }
+};