/*
-Copyright (C) 1999-2007 id Software, Inc. and contributors.
-For a list of contributors, see the accompanying CONTRIBUTORS file.
-
-This file is part of GtkRadiant.
-
-GtkRadiant is free software; you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation; either version 2 of the License, or
-(at your option) any later version.
-
-GtkRadiant is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with GtkRadiant; if not, write to the Free Software
-Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-*/
-
-#include "stdafx.h"
-#include <string.h>
-#if defined (__linux__) || defined (__APPLE__)
-#include <unistd.h>
-#endif
-#include "preferences.h"
+ Copyright (C) 1999-2006 Id Software, Inc. and contributors.
+ For a list of contributors, see the accompanying CONTRIBUTORS file.
+
+ This file is part of GtkRadiant.
+
+ GtkRadiant is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ GtkRadiant is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with GtkRadiant; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "map.h"
+
+#include <gtk/gtk.h>
+
+#include "debugging/debugging.h"
+
+#include "imap.h"
+
+MapModules& ReferenceAPI_getMapModules();
+
+#include "iselection.h"
+#include "iundo.h"
+#include "ibrush.h"
+#include "ifilter.h"
+#include "ireference.h"
+#include "ifiletypes.h"
+#include "ieclass.h"
+#include "irender.h"
+#include "ientity.h"
+#include "editable.h"
+#include "iarchive.h"
+#include "ifilesystem.h"
+#include "namespace.h"
+#include "moduleobserver.h"
+
+#include <set>
+
+#include <gdk/gdkkeysyms.h>
+#include "uilib/uilib.h"
+
+#include "scenelib.h"
+#include "transformlib.h"
+#include "selectionlib.h"
+#include "instancelib.h"
+#include "traverselib.h"
+#include "maplib.h"
+#include "eclasslib.h"
+#include "cmdlib.h"
+#include "stream/textfilestream.h"
+#include "os/path.h"
+#include "os/file.h"
+#include "uniquenames.h"
+#include "modulesystem/singletonmodule.h"
+#include "modulesystem/moduleregistry.h"
+#include "stream/stringstream.h"
+#include "signal/signal.h"
+
+#include "gtkutil/filechooser.h"
+#include "timer.h"
+#include "select.h"
+#include "plugin.h"
+#include "filetypes.h"
+#include "gtkdlgs.h"
+#include "entityinspector.h"
+#include "points.h"
+#include "qe3.h"
+#include "camwindow.h"
+#include "xywindow.h"
#include "mainframe.h"
-#include "gtkmisc.h"
-#include "filters.h"
+#include "preferences.h"
+#include "preferencesystem.h"
+#include "referencecache.h"
+#include "mru.h"
+#include "commands.h"
+#include "autosave.h"
+#include "brushmodule.h"
+#include "brush.h"
+
+bool g_writeMapComments = true;
+
+class NameObserver
+{
+UniqueNames& m_names;
+CopiedString m_name;
+
+void construct(){
+ if ( !empty() ) {
+ //globalOutputStream() << "construct " << makeQuoted(c_str()) << "\n";
+ m_names.insert( name_read( c_str() ) );
+ }
+}
-extern MainFrame* g_pParentWnd;
+void destroy(){
+ if ( !empty() ) {
+ //globalOutputStream() << "destroy " << makeQuoted(c_str()) << "\n";
+ m_names.erase( name_read( c_str() ) );
+ }
+}
-int modified; // for quit confirmation (0 = clean, 1 = unsaved,
- // 2 = autosaved, but not regular saved)
+NameObserver& operator=( const NameObserver& other );
-char currentmap[1024];
+public:
+NameObserver( UniqueNames& names ) : m_names( names ){
+ construct();
+}
+NameObserver( const NameObserver& other ) : m_names( other.m_names ), m_name( other.m_name ){
+ construct();
+}
-brush_t active_brushes; // brushes currently being displayed
-brush_t selected_brushes; // highlighted
+~NameObserver(){
+ destroy();
+}
-face_t *selected_face;
-brush_t *selected_face_brush;
+bool empty() const {
+ return string_empty( c_str() );
+}
-brush_t filtered_brushes; // brushes that have been filtered or regioned
+const char* c_str() const {
+ return m_name.c_str();
+}
-entity_t entities; // head/tail of doubly linked list
+void nameChanged( const char* name ){
+ destroy();
+ m_name = name;
+ construct();
+}
-entity_t *world_entity = NULL; // "classname" "worldspawn" !
+typedef MemberCaller<NameObserver, void(const char*), &NameObserver::nameChanged> NameChangedCaller;
+};
-void Map_Init()
+class BasicNamespace : public Namespace
{
- Map_Free();
+typedef std::map<NameCallback, NameObserver> Names;
+Names m_names;
+UniqueNames m_uniqueNames;
+public:
+~BasicNamespace(){
+ ASSERT_MESSAGE( m_names.empty(), "namespace: names still registered at shutdown" );
}
+void attach( const NameCallback& setName, const NameCallbackCallback& attachObserver ){
+ std::pair<Names::iterator, bool> result = m_names.insert( Names::value_type( setName, m_uniqueNames ) );
+ ASSERT_MESSAGE( result.second, "cannot attach name" );
+ attachObserver( NameObserver::NameChangedCaller( ( *result.first ).second ) );
+ //globalOutputStream() << "attach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
+}
-bool g_bCancel_Map_LoadFile; // Hydra: moved this here
+void detach( const NameCallback& setName, const NameCallbackCallback& detachObserver ){
+ Names::iterator i = m_names.find( setName );
+ ASSERT_MESSAGE( i != m_names.end(), "cannot detach name" );
+ //globalOutputStream() << "detach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
+ detachObserver( NameObserver::NameChangedCaller( ( *i ).second ) );
+ m_names.erase( i );
+}
-// TTimo
-// need that in a variable, will have to tweak depending on the game
-int g_MaxWorldCoord = 64*1024;
-int g_MinWorldCoord = -64*1024;
+void makeUnique( const char* name, const NameCallback& setName ) const {
+ char buffer[1024];
+ name_write( buffer, m_uniqueNames.make_unique( name_read( name ) ) );
+ setName( buffer );
+}
-// the max size we allow on brushes, this is dependant on world coords too
-// makes more sense to say smaller I think?
-int g_MaxBrushSize = (g_MaxWorldCoord-1)*2;
+void mergeNames( const BasicNamespace& other ) const {
+ typedef std::list<NameCallback> SetNameCallbacks;
+ typedef std::map<CopiedString, SetNameCallbacks> NameGroups;
+ NameGroups groups;
-void AddRegionBrushes (void);
-void RemoveRegionBrushes (void);
+ UniqueNames uniqueNames( other.m_uniqueNames );
-/*
-=============================================================
+ for ( Names::const_iterator i = m_names.begin(); i != m_names.end(); ++i )
+ {
+ groups[( *i ).second.c_str()].push_back( ( *i ).first );
+ }
- Cross map selection saving
+ for ( NameGroups::iterator i = groups.begin(); i != groups.end(); ++i )
+ {
+ name_t uniqueName( uniqueNames.make_unique( name_read( ( *i ).first.c_str() ) ) );
+ uniqueNames.insert( uniqueName );
- this could fuck up if you have only part of a complex entity selected...
-=============================================================
-*/
+ char buffer[1024];
+ name_write( buffer, uniqueName );
-brush_t between_brushes;
-entity_t between_entities;
+ //globalOutputStream() << "renaming " << makeQuoted((*i).first.c_str()) << " to " << makeQuoted(buffer) << "\n";
-bool g_bRestoreBetween = false;
+ SetNameCallbacks& setNameCallbacks = ( *i ).second;
-void Map_SaveBetween (void)
-{
- if (g_pParentWnd->ActiveXY())
- {
- g_bRestoreBetween = true;
- g_pParentWnd->ActiveXY()->Copy();
- }
- return;
+ for ( SetNameCallbacks::const_iterator j = setNameCallbacks.begin(); j != setNameCallbacks.end(); ++j )
+ {
+ ( *j )( buffer );
+ }
+ }
}
+};
-void Map_RestoreBetween (void)
+BasicNamespace g_defaultNamespace;
+BasicNamespace g_cloneNamespace;
+
+class NamespaceAPI
{
- if (g_pParentWnd->ActiveXY() && g_bRestoreBetween)
- g_pParentWnd->ActiveXY()->Paste();
-}
+Namespace* m_namespace;
+public:
+typedef Namespace Type;
-//============================================================================
+STRING_CONSTANT( Name, "*" );
-bool CheckForTinyBrush(brush_t* b, int n, float fSize)
-{
- bool bTiny = false;
- for (int i=0 ; i<3 ; i++)
- {
- if (b->maxs[i] - b->mins[i] < fSize)
- bTiny = true;
- }
- if (bTiny)
- Sys_Printf("Possible problem brush (too small) #%i ", n);
- return bTiny;
+NamespaceAPI(){
+ m_namespace = &g_defaultNamespace;
}
-void Map_BuildBrushData(void)
-{
- brush_t *b, *next;
+Namespace* getTable(){
+ return m_namespace;
+}
+};
- if (active_brushes.next == NULL)
- return;
+typedef SingletonModule<NamespaceAPI> NamespaceModule;
+typedef Static<NamespaceModule> StaticNamespaceModule;
+StaticRegisterModule staticRegisterDefaultNamespace( StaticNamespaceModule::instance() );
- Sys_BeginWait (); // this could take a while
- int n = 0;
- for (b=active_brushes.next ; b != NULL && b != &active_brushes ; b=next)
- {
- next = b->next;
- Brush_Build( b, true, false, false );
- if (!b->brush_faces || (g_PrefsDlg.m_bCleanTiny && CheckForTinyBrush(b, n++, g_PrefsDlg.m_fTinySize)))
- {
- Brush_Free (b);
- Sys_Printf ("Removed degenerate brush\n");
- }
- }
- Sys_EndWait();
-}
+std::list<Namespaced*> g_cloned;
-entity_t *Map_FindClass (const char *cname)
-{
- entity_t *ent;
+inline Namespaced* Node_getNamespaced( scene::Node& node ){
+ return NodeTypeCast<Namespaced>::cast( node );
+}
- for (ent = entities.next ; ent != &entities ; ent=ent->next)
- {
- if (!strcmp(cname, ValueForKey (ent, "classname")))
- return ent;
+void Node_gatherNamespaced( scene::Node& node ){
+ Namespaced* namespaced = Node_getNamespaced( node );
+ if ( namespaced != 0 ) {
+ g_cloned.push_back( namespaced );
}
- return NULL;
}
-/*
-================
-Map_Free
-free all map elements, reinitialize the structures that depend on them
-================
-*/
-void Map_Free (void)
+class GatherNamespaced : public scene::Traversable::Walker
{
- g_bRestoreBetween = false;
- if (selected_brushes.next &&
- (selected_brushes.next != &selected_brushes))
- {
- if (gtk_MessageBox (g_pParentWnd->m_pWidget, "Copy selection?", " ", MB_YESNO) == IDYES)
- Map_SaveBetween ();
- }
-
- QERApp_ActiveShaders_SetInUse( false );
- Pointfile_Clear ();
- g_qeglobals.d_num_entities = 0;
-
- if (!active_brushes.next)
+public:
+bool pre( scene::Node& node ) const {
+ Node_gatherNamespaced( node );
+ return true;
+}
+};
+
+void Map_gatherNamespaced( scene::Node& root ){
+ Node_traverseSubgraph( root, GatherNamespaced() );
+}
+
+void Map_mergeClonedNames(){
+ for ( std::list<Namespaced*>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i )
{
- // first map
- active_brushes.prev = active_brushes.next = &active_brushes;
- selected_brushes.prev = selected_brushes.next = &selected_brushes;
- filtered_brushes.prev = filtered_brushes.next = &filtered_brushes;
- entities.prev = entities.next = &entities;
+ ( *i )->setNamespace( g_cloneNamespace );
}
- else
+ g_cloneNamespace.mergeNames( g_defaultNamespace );
+ for ( std::list<Namespaced*>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i )
{
- // free selected faces array
- g_ptrSelectedFaces.RemoveAll();
- g_ptrSelectedFaceBrushes.RemoveAll();
- while (active_brushes.next != &active_brushes)
- Brush_Free (active_brushes.next);
- while (selected_brushes.next != &selected_brushes)
- Brush_Free (selected_brushes.next);
- while (filtered_brushes.next != &filtered_brushes)
- Brush_Free (filtered_brushes.next);
- while (entities.next != &entities)
- Entity_Free (entities.next);
+ ( *i )->setNamespace( g_defaultNamespace );
}
- if (world_entity)
- Entity_Free(world_entity);
- world_entity = NULL;
+ g_cloned.clear();
}
-entity_t *AngledEntity()
+class WorldNode
{
- entity_t *ent = Map_FindClass ("info_player_start");
- if (!ent)
- {
- ent = Map_FindClass ("info_player_deathmatch");
- }
- if (!ent)
- {
- ent = Map_FindClass ("info_player_deathmatch");
- }
- if (!ent)
- {
- ent = Map_FindClass ("team_CTF_redplayer");
- }
- if (!ent)
- {
- ent = Map_FindClass ("team_CTF_blueplayer");
- }
- if (!ent)
- {
- ent = Map_FindClass ("team_CTF_redspawn");
- }
- if (!ent)
- {
- ent = Map_FindClass ("team_CTF_bluespawn");
- }
- return ent;
+scene::Node* m_node;
+public:
+WorldNode()
+ : m_node( 0 ){
}
-//
-// move the view to a start position
-//
-void Map_StartPosition()
-{
- entity_t *ent = AngledEntity();
-
- g_pParentWnd->GetCamWnd()->Camera()->angles[PITCH] = 0;
- if (ent)
- {
- GetVectorForKey (ent, "origin", g_pParentWnd->GetCamWnd()->Camera()->origin);
- GetVectorForKey (ent, "origin", g_pParentWnd->GetXYWnd()->GetOrigin());
- g_pParentWnd->GetCamWnd()->Camera()->angles[YAW] = FloatForKey (ent, "angle");
- }
- else
- {
- g_pParentWnd->GetCamWnd()->Camera()->angles[YAW] = 0;
- VectorCopy (vec3_origin, g_pParentWnd->GetCamWnd()->Camera()->origin);
- VectorCopy (vec3_origin, g_pParentWnd->GetXYWnd()->GetOrigin());
- }
-}
-
-void Map_FreeEntities(CPtrArray *ents)
-{
- int i, j, num_ents, num_brushes;
- entity_t* e;
- CPtrArray* brushes;
-
- num_ents = ents->GetSize();
- for(i=0; i<num_ents; i++)
- {
- e = (entity_t*)ents->GetAt(i);
- brushes = (CPtrArray*)e->pData;
- num_brushes = brushes->GetSize();
- for(j=0; j<num_brushes; j++)
- Brush_Free((brush_t*)brushes->GetAt(j));
- brushes->RemoveAll();
- delete (CPtrArray*)e->pData;
- e->pData = NULL;
- Entity_Free(e);
- }
- ents->RemoveAll();
-}
-
-/*!\todo Possibly make the import Undo-friendly by calling Undo_End for new brushes and ents */
-void Map_ImportEntities(CPtrArray *ents, bool bAddSelected = false)
-{
- int num_ents, num_brushes;
- CPtrArray *brushes;
- vec3_t mins, maxs;
- entity_t *e;
- brush_t *b;
- face_t *f;
- int i,j;
-
- GPtrArray *new_ents = g_ptr_array_new();
-
- g_qeglobals.bPrimitBrushes = false;
-
- brush_t *pBrushList = (bAddSelected) ? &selected_brushes : &active_brushes;
-
- bool bDoneBPCheck = false;
- g_qeglobals.bNeedConvert = false;
- // HACK: find out if this map file was a BP one
- // check the first brush in the file that is NOT a patch
- // this will not be necessary when we allow both formats in the same file
- num_ents = ents->GetSize();
- for(i=0; !bDoneBPCheck && i<num_ents; i++)
- {
- e = (entity_t*)ents->GetAt(i);
- brushes = (CPtrArray*)e->pData;
- num_brushes = brushes->GetSize();
- for(j=0; !bDoneBPCheck && j<num_brushes; j++)
- {
- /*!todo Allow mixing texdef formats per-face. */
- b = (brush_t *)brushes->GetAt(j);
- if(b->patchBrush) continue;
- bDoneBPCheck = true;
- int BP_param = -1;
- if(b->bBrushDef && !g_qeglobals.m_bBrushPrimitMode)
- BP_param = 0;
- else if(!b->bBrushDef && g_qeglobals.m_bBrushPrimitMode)
- BP_param = 1;
-
- if(BP_param != -1)
- {
- switch(BP_MessageBox(BP_param))
- {
- case 0:
- Map_FreeEntities(ents);
- return;
- case 1:
- g_qeglobals.bNeedConvert = true;
- break;
- case 2:
- g_qeglobals.bNeedConvert = false;
- break;
- }
- }
- }
- }
-
- // process the entities into the world geometry
- num_ents = ents->GetSize();
- for(i=0; i<num_ents; i++)
- {
- num_brushes = 0;
- e = (entity_t*)ents->GetAt(i);
- brushes = (CPtrArray*)e->pData;
-
- num_brushes = brushes->GetSize();
- // link brushes into entity
- for(j=0; j<num_brushes; j++)
- {
- Entity_LinkBrush(e, (brush_t *)brushes->GetAt(j));
- g_qeglobals.d_parsed_brushes++;
- }
- brushes->RemoveAll();
- delete brushes;
- e->pData = NULL;
-
- // set entity origin
- GetVectorForKey (e, "origin", e->origin);
- // set entity eclass
- /*!\todo Make SetKeyValue check for "classname" change and assign appropriate eclass */
- e->eclass = Eclass_ForName (ValueForKey (e, "classname"),
- (e->brushes.onext != &e->brushes));
-
- // go through all parsed brushes and build stuff
- for(b = e->brushes.onext; b!=&e->brushes; b=b->onext)
- {
- for(f = b->brush_faces; f != NULL; f = f->next)
- {
- f->pShader = QERApp_Shader_ForName(f->texdef.GetName());
- f->d_texture = f->pShader->getTexture();
- }
-
- // when brushes are in final state, build the planes and windings
- // NOTE: also converts BP brushes if g_qeglobals.bNeedConvert is true
- Brush_Build(b);
- }
-
-//#define TERRAIN_HACK
-#undef TERRAIN_HACK
-
-#ifdef TERRAIN_HACK
- if ((strcmp(ValueForKey(e, "terrain"),"1") == 0 && strcmp(e->eclass->name,"func_group") == 0))
- {
-
- // two aux pointers to the shaders used in the terrain entity
- // we don't keep refcount on them since they are only temporary
- // this avoids doing expensive lookups by name for all faces
- IShader *pTerrainShader, *pCaulk;
-
- pTerrainShader = NULL;
- pCaulk = QERApp_Shader_ForName(SHADER_CAULK);
-
- for(b = e->brushes.onext; b!=&e->brushes; b=b->onext)
- {
- if (pTerrainShader == NULL)
- for(f = b->brush_faces; f != NULL; f = f->next)
- if (strcmp(f->texdef.GetName(), SHADER_CAULK)!=0)
- pTerrainShader = f->pShader;
-
- if (pTerrainShader)
- {
- for(f = b->brush_faces; f != NULL; f = f->next)
- {
- if (strcmp(f->texdef.GetName(), SHADER_CAULK)!=0) // not caulk
- Face_SetShader(f, pTerrainShader->getName());
- else
- Face_SetShader(f, pCaulk->getName());
- }
- }
- else
- Sys_Printf("WARNING: no terrain shader found for brush\n");
- }
- }
-#endif
-
-#define PATCH_HACK
-#ifdef PATCH_HACK
- for(b = e->brushes.onext; b!=&e->brushes; b=b->onext)
- {
- // patch hack, to be removed when dependency on brush_faces is removed
- if (b->patchBrush)
- {
- Patch_CalcBounds(b->pPatch, mins, maxs);
- for (int i=0; i<3; i++)
- {
- if ((int)mins[i] == (int)maxs[i])
- {
- mins[i] -= 4;
- maxs[i] += 4;
- }
- }
- Brush_Resize(b, mins, maxs);
- Brush_Build(b);
- }
- }
-#endif
- // add brush for fixedsize entity
- if (e->eclass->fixedsize)
- {
- vec3_t mins, maxs;
- VectorAdd (e->eclass->mins, e->origin, mins);
- VectorAdd (e->eclass->maxs, e->origin, maxs);
- b = Brush_Create (mins, maxs, &e->eclass->texdef);
- Entity_LinkBrush(e, b);
- Brush_Build(b);
- }
-
- for(b = e->brushes.onext; b!=&e->brushes; b=b->onext)
- Brush_AddToList(b, pBrushList);
-
- if (strcmp(e->eclass->name, "worldspawn") == 0)
- {
- if (world_entity)
- {
- while(e->brushes.onext != &e->brushes)
- {
- b = e->brushes.onext;
- Entity_UnlinkBrush(b);
- Entity_LinkBrush(world_entity, b);
- }
- Entity_Free(e);
- }
- else
- {
- world_entity = e;
- }
- }
- else if (strcmp(e->eclass->name, "group_info") == 0)
- {
- // it's a group thing!
- Group_Add(e);
- Entity_Free(e);
- }
- else
- {
- // fix target/targetname collisions
- if ((g_PrefsDlg.m_bDoTargetFix) && (strcmp(ValueForKey(e, "target"), "") != 0))
- {
- GPtrArray *t_ents = g_ptr_array_new();
- entity_t *e_target;
- const char *target = ValueForKey(e, "target");
- qboolean bCollision=FALSE;
-
- // check the current map entities for an actual collision
- for (e_target = entities.next; e_target != &entities; e_target = e_target->next)
- {
- if(!strcmp(target, ValueForKey(e_target, "target")))
- {
- bCollision = TRUE;
- // make sure the collision is not between two imported entities
- for(j=0; j<(int)new_ents->len; j++)
- {
- if(e_target == g_ptr_array_index(new_ents, j))
- bCollision = FALSE;
- }
- }
- }
-
- // find the matching targeted entity(s)
- if(bCollision)
- {
- for(j=num_ents-1; j>0; j--)
- {
- e_target = (entity_t*)ents->GetAt(j);
- if(e_target != NULL && e_target != e)
- {
- const char *targetname = ValueForKey(e_target, "targetname");
- if( (targetname != NULL) && (strcmp(target, targetname) == 0) )
- g_ptr_array_add(t_ents, (gpointer)e_target);
- }
- }
- if(t_ents->len > 0)
- {
- // link the first to get a unique target/targetname
- Entity_Connect(e, (entity_t*)g_ptr_array_index(t_ents,0));
- // set the targetname of the rest of them manually
- for(j = 1; j < (int)t_ents->len; j++)
- SetKeyValue( (entity_t*)g_ptr_array_index(t_ents, j), "targetname", ValueForKey(e, "target") );
- }
- g_ptr_array_free(t_ents, FALSE);
- }
- }
-
- // add the entity to the end of the entity list
- Entity_AddToList(e, &entities);
- g_qeglobals.d_num_entities++;
-
- // keep a list of ents added to avoid testing collisions against them
- g_ptr_array_add(new_ents, (gpointer)e);
- }
- }
- g_ptr_array_free(new_ents, FALSE);
-
- ents->RemoveAll();
-
- g_qeglobals.bNeedConvert = false;
-}
-
-void Map_Import(IDataStream *in, const char *type, bool bAddSelected)
-{
- CPtrArray ents;
+void set( scene::Node* node ){
+ if ( m_node != 0 ) {
+ m_node->DecRef();
+ }
+ m_node = node;
+ if ( m_node != 0 ) {
+ m_node->IncRef();
+ }
+}
- g_pParentWnd->GetSynapseClient().ImportMap(in, &ents, type);
- Map_ImportEntities(&ents, bAddSelected);
+scene::Node* get() const {
+ return m_node;
}
+};
-/*
-================
-Map_LoadFile
-================
-*/
-void Map_LoadFile (const char *filename)
-{
- clock_t start, finish;
- double elapsed_time;
- start = clock();
-
- Sys_BeginWait ();
- Select_Deselect();
- /*!
- \todo FIXME TTimo why is this commented out?
- stability issues maybe? or duplicate feature?
- forcing to show the console during map load was a good thing IMO
- */
- //SetInspectorMode(W_CONSOLE);
- Sys_Printf ("Loading map from %s\n", filename );
-
- Map_Free ();
- //++timo FIXME: maybe even easier to have Group_Init called from Map_Free?
- Group_Init();
- g_qeglobals.d_num_entities = 0;
- g_qeglobals.d_parsed_brushes = 0;
-
-
- // cancel the map loading process
- // used when conversion between standard map format and BP format is required and the user cancels the process
- g_bCancel_Map_LoadFile = false;
-
- strcpy (currentmap, filename);
-
- g_bScreenUpdates = false; // leo: avoid redraws while loading the map (see fenris:1952)
-
- // prepare to let the map module do the parsing
- FileStream file;
- const char* type = strrchr(filename,'.');
- if(type!=NULL) type++;
- // NOTE TTimo opening has binary doesn't make a lot of sense
- // but opening as text confuses the scriptlib parser
- // this may be a problem if we "rb" and use the XML parser, might have an incompatibility
- if (file.Open(filename, "rb"))
- Map_Import(&file, type);
- else
- Sys_FPrintf(SYS_ERR, "ERROR: failed to open %s for read\n", filename);
- file.Close();
-
- g_bScreenUpdates = true;
-
- if (g_bCancel_Map_LoadFile)
- {
- Sys_Printf("Map_LoadFile canceled\n");
- Map_New();
- Sys_EndWait();
- return;
- }
-
- if (!world_entity)
- {
- Sys_Printf ("No worldspawn in map.\n");
- Map_New ();
- Sys_EndWait();
- return;
- }
- finish = clock();
- elapsed_time = (double)(finish - start) / CLOCKS_PER_SEC;
-
- Sys_Printf ("--- LoadMapFile ---\n");
- Sys_Printf ("%s\n", filename );
-
- Sys_Printf ("%5i brushes\n", g_qeglobals.d_parsed_brushes );
- Sys_Printf ("%5i entities\n", g_qeglobals.d_num_entities);
- Sys_Printf ("%5.2f second(s) load time\n", elapsed_time );
-
- Sys_EndWait();
-
- Map_RestoreBetween ();
-
- //
- // move the view to a start position
- //
- Map_StartPosition();
-
- Map_RegionOff ();
-
- modified = false;
- Sys_SetTitle (filename);
-
- Texture_ShowInuse ();
- QERApp_SortActiveShaders();
-
- Sys_UpdateWindows (W_ALL);
-}
-
-/*!
-===========
-Supporting functions for Map_SaveFile, builds a CPtrArray with the filtered / non filtered brushes
-===========
-*/
-void CleanFilter(entity_t *ent)
-{
- if (ent->pData)
- {
- delete static_cast<CPtrArray*>(ent->pData);
- ent->pData = NULL;
- }
-}
-
-/*!
-filters out the region brushes if necessary
-returns true if this entity as a whole is out of the region
-(if all brushes are filtered out, then the entity will be completely dropped .. except if it's worldspawn of course)
-*/
-bool FilterChildren(entity_t *ent, bool bRegionOnly = false, bool bSelectedOnly = false)
-{
- if(ent->brushes.onext == &ent->brushes)
- return false;
- // entity without a brush, ignore it... this can be caused by Undo
-
- // filter fixedsize ents by their eclass bounding box
- // don't add their brushes
- if (ent->eclass->fixedsize)
- {
- if(bSelectedOnly && !IsBrushSelected(ent->brushes.onext))
- return false;
-
- if(bRegionOnly && region_active)
- {
- for (int i=0 ; i<3 ; i++)
- {
- if ((ent->origin[i] + ent->eclass->mins[i]) > region_maxs[i])
- return false;
- if ((ent->origin[i] + ent->eclass->maxs[i]) < region_mins[i])
- return false;
- }
- }
- }
- else
- {
- for (brush_t *b = ent->brushes.onext ; b != &ent->brushes ; b=b->onext)
- {
- // set flag to use brushprimit_texdef
- if(g_qeglobals.m_bBrushPrimitMode)
- b->bBrushDef = true;
- else
- b->bBrushDef = false;
-
- // add brush, unless it's excluded by region
- if ( !(bRegionOnly && Map_IsBrushFiltered(b)) &&
- !(bSelectedOnly && !IsBrushSelected(b)) )
- ((CPtrArray*)ent->pData)->Add(b);
- }
-
- if (((CPtrArray*)ent->pData)->GetSize() <= 0)
- return false;
- }
- return true;
-}
-
-entity_t *region_startpoint = NULL;
-void Map_ExportEntities(CPtrArray* ents, bool bRegionOnly = false, bool bSelectedOnly = false)
-{
- int i;
- entity_t *e;
+class Map;
+void Map_SetValid( Map& map, bool valid );
- /*!
- \todo the entity_t needs to be reworked and asbtracted some more
+void Map_UpdateTitle( const Map& map );
- keeping the entity_t as the struct providing access to a list of map objects, a list of epairs and various other info?
- but separating some more the data that belongs to the entity_t and the 'sons' data
- on a side note, I don't think that doing that with linked list would be a good thing
+void Map_SetWorldspawn( Map& map, scene::Node* node );
- for now, we use the blind void* in entity_t casted to a CPtrArray of brush_t* to hand out a list of the brushes for map write
- the next step is very likely to be a change of the brush_t* to a more abstract object?
- */
- FilterChildren(world_entity, bRegionOnly, bSelectedOnly);
- ents->Add(world_entity);
+class Map : public ModuleObserver
+{
+public:
+CopiedString m_name;
+Resource* m_resource;
+bool m_valid;
+
+bool m_modified;
- for (e=entities.next ; e!=&entities ; e=e->next)
- {
- // not sure this still happens, probably safe to leave it in
- if ((!strcmp(ValueForKey (e, "classname"), "worldspawn")) && (e!=world_entity))
- {
- Sys_FPrintf(SYS_ERR, "Dropping parasite worldspawn entity\n");
- continue;
- }
+void ( *m_modified_changed )( const Map& );
- // entities which brushes are completely filtered out by regioning are not printed to the map
- if (FilterChildren(e, bRegionOnly, bSelectedOnly))
- ents->Add(e);
- }
+Signal0 m_mapValidCallbacks;
- if (bRegionOnly && region_active)
- {
- for(i=0; i<6; i++)
- ((CPtrArray*)world_entity->pData)->Add(region_sides[i]);
+WorldNode m_world_node; // "classname" "worldspawn" !
- ents->Add(region_startpoint);
- }
+Map() : m_resource( 0 ), m_valid( false ), m_modified_changed( Map_UpdateTitle ){
}
-void Map_Export(IDataStream *out, const char *type, bool bRegionOnly, bool bSelectedOnly)
-{
- entity_t *e;
+void realise(){
+ if ( m_resource != 0 ) {
+ if ( Map_Unnamed( *this ) ) {
+ g_map.m_resource->setNode( NewMapRoot( "" ).get_pointer() );
+ MapFile* map = Node_getMapFile( *g_map.m_resource->getNode() );
+ if ( map != 0 ) {
+ map->save();
+ }
+ }
+ else
+ {
+ m_resource->load();
+ }
- CPtrArray ents;
+ GlobalSceneGraph().insert_root( *m_resource->getNode() );
- if (bRegionOnly && region_active)
- AddRegionBrushes();
+ AutoSave_clear();
- // create the filters
- world_entity->pData = new CPtrArray();
- for(e = entities.next; e != &entities; e = e->next)
- e->pData = new CPtrArray();
+ Map_SetValid( g_map, true );
+ }
+}
- Map_ExportEntities(&ents, bRegionOnly, bSelectedOnly);
+void unrealise(){
+ if ( m_resource != 0 ) {
+ Map_SetValid( g_map, false );
+ Map_SetWorldspawn( g_map, 0 );
- g_pParentWnd->GetSynapseClient().ExportMap(&ents, out, type);
- // cleanup the filters
- CleanFilter(world_entity);
- for (e=entities.next ; e!=&entities ; e=e->next)
- CleanFilter(e);
+ GlobalUndoSystem().clear();
- if (bRegionOnly && region_active)
- RemoveRegionBrushes();
+ GlobalSceneGraph().erase_root();
+ }
}
+};
-const char* filename_get_extension(const char* filename)
-{
- const char* type = strrchr(filename,'.');
- if(type != NULL)
- return ++type;
- return "";
-}
+Map g_map;
+Map* g_currentMap = 0;
-/*
-===========
-Map_SaveFile
-\todo FIXME remove the use_region, this is broken .. work with a global flag to set region mode or not
-===========
-*/
-void Map_SaveFile (const char *filename, qboolean use_region )
-{
- clock_t start, finish;
- double elapsed_time;
- start = clock();
- Sys_Printf("Saving map to %s\n",filename);
+void Map_addValidCallback( Map& map, const SignalHandler& handler ){
+ map.m_mapValidCallbacks.connectLast( handler );
+}
- Pointfile_Clear ();
+bool Map_Valid( const Map& map ){
+ return map.m_valid;
+}
- if (!use_region)
- {
- char backup[1024];
+void Map_SetValid( Map& map, bool valid ){
+ map.m_valid = valid;
+ map.m_mapValidCallbacks();
+}
- // rename current to .bak
- strcpy (backup, filename);
- StripExtension (backup);
- strcat (backup, ".bak");
- unlink (backup);
- rename (filename, backup);
- }
- Sys_Printf ("Map_SaveFile: %s\n", filename);
+const char* Map_Name( const Map& map ){
+ return map.m_name.c_str();
+}
- // build the out data stream
- FileStream file;
- if (!file.Open(filename,"w"))
- {
- Sys_FPrintf(SYS_ERR, "ERROR: couldn't open %s for write\n", filename);
- return;
- }
+bool Map_Unnamed( const Map& map ){
+ return string_equal( Map_Name( map ), "unnamed.map" );
+}
- // extract filetype
- Map_Export(&file, filename_get_extension(filename), use_region);
+inline const MapFormat& MapFormat_forFile( const char* filename ){
+ const char* moduleName = findModuleName( GetFileTypeRegistry(), MapFormat::Name(), path_get_extension( filename ) );
+ MapFormat* format = Radiant_getMapModules().findModule( moduleName );
+ ASSERT_MESSAGE( format != 0, "map format not found for file " << makeQuoted( filename ) );
+ return *format;
+}
- file.Close();
+const MapFormat& Map_getFormat( const Map& map ){
+ return MapFormat_forFile( Map_Name( map ) );
+}
- finish = clock();
- elapsed_time = (double)(finish - start) / CLOCKS_PER_SEC;
- Sys_Printf ("Saved in %-.2f second(s).\n",elapsed_time);
- modified = false;
+bool Map_Modified( const Map& map ){
+ return map.m_modified;
+}
- if ( !strstr( filename, "autosave" ) )
- Sys_SetTitle (filename);
+void Map_SetModified( Map& map, bool modified ){
+ if ( map.m_modified ^ modified ) {
+ map.m_modified = modified;
- if (!use_region)
- {
- time_t timer;
+ map.m_modified_changed( map );
+ }
+}
- time (&timer);
+void Map_UpdateTitle( const Map& map ){
+ Sys_SetTitle( map.m_name.c_str(), Map_Modified( map ) );
+}
- Sys_Beep ();
- Sys_Status ("Saved.", 0);
- }
+scene::Node* Map_GetWorldspawn( const Map& map ){
+ return map.m_world_node.get();
}
-/*
-===========
-Map_New
+void Map_SetWorldspawn( Map& map, scene::Node* node ){
+ map.m_world_node.set( node );
+}
-===========
-*/
-void Map_New (void)
-{
- Sys_Printf ("Map_New\n");
- Map_Free ();
- strcpy (currentmap, "unnamed.map");
- Sys_SetTitle (currentmap);
+// TTimo
+// need that in a variable, will have to tweak depending on the game
+float g_MaxWorldCoord = 64 * 1024;
+float g_MinWorldCoord = -64 * 1024;
- world_entity = (entity_s*)qmalloc(sizeof(*world_entity));
- world_entity->brushes.onext =
- world_entity->brushes.oprev = &world_entity->brushes;
- SetKeyValue (world_entity, "classname", "worldspawn");
- world_entity->eclass = Eclass_ForName ("worldspawn", true);
+void AddRegionBrushes( void );
- g_pParentWnd->GetCamWnd()->Camera()->angles[YAW] = 0;
- g_pParentWnd->GetCamWnd()->Camera()->angles[PITCH] = 0;
- VectorCopy (vec3_origin, g_pParentWnd->GetCamWnd()->Camera()->origin);
- g_pParentWnd->GetCamWnd()->Camera()->origin[2] = 48;
- VectorCopy (vec3_origin, g_pParentWnd->GetXYWnd()->GetOrigin());
+void RemoveRegionBrushes( void );
- Map_RestoreBetween ();
- Group_Init();
+/*
+ ================
+ Map_Free
+ free all map elements, reinitialize the structures that depend on them
+ ================
+ */
+void Map_Free(){
+ Pointfile_Clear();
+
+ g_map.m_resource->detach( g_map );
+ GlobalReferenceCache().release( g_map.m_name.c_str() );
+ g_map.m_resource = 0;
+
+ FlushReferences();
+
+ g_currentMap = 0;
+ Brush_unlatchPreferences();
+}
- Sys_UpdateWindows (W_ALL);
- modified = false;
+class EntityFindByClassname : public scene::Graph::Walker
+{
+const char* m_name;
+Entity*& m_entity;
+public:
+EntityFindByClassname( const char* name, Entity*& entity ) : m_name( name ), m_entity( entity ){
+ m_entity = 0;
}
-/*
-===========================================================
+bool pre( const scene::Path& path, scene::Instance& instance ) const {
+ if ( m_entity == 0 ) {
+ Entity* entity = Node_getEntity( path.top() );
+ if ( entity != 0
+ && string_equal( m_name, entity->getKeyValue( "classname" ) ) ) {
+ m_entity = entity;
+ }
+ }
+ return true;
+}
+};
- REGION
+Entity* Scene_FindEntityByClass( const char* name ){
+ Entity* entity;
+ GlobalSceneGraph().traverse( EntityFindByClassname( name, entity ) );
+ return entity;
+}
-===========================================================
-*/
-qboolean region_active;
-vec3_t region_mins = {g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord};
-vec3_t region_maxs = {g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord};
+Entity *Scene_FindPlayerStart(){
+ typedef const char* StaticString;
+ StaticString strings[] = {
+ "info_player_start",
+ "info_player_deathmatch",
+ "team_CTF_redplayer",
+ "team_CTF_blueplayer",
+ "team_CTF_redspawn",
+ "team_CTF_bluespawn",
+ };
+ typedef const StaticString* StaticStringIterator;
+ for ( StaticStringIterator i = strings, end = strings + ( sizeof( strings ) / sizeof( StaticString ) ); i != end; ++i )
+ {
+ Entity* entity = Scene_FindEntityByClass( *i );
+ if ( entity != 0 ) {
+ return entity;
+ }
+ }
+ return 0;
+}
-brush_t *region_sides[6];
+//
+// move the view to a start position
+//
-/*
-===========
-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)
-{
- vec3_t mins, maxs;
- int i;
- texdef_t td;
-
- if (!region_active)
- {
-#ifdef _DEBUG
- Sys_FPrintf( SYS_WRN, "Unexpected AddRegionBrushes call.\n");
-#endif
- return;
- }
-
- memset (&td, 0, sizeof(td));
- td.SetName(SHADER_NOT_FOUND);
-
- // set mins
- VectorSet(mins, region_mins[0]-32, region_mins[1]-32, region_mins[2]-32);
-
- // vary maxs
- for(i=0; i<3; i++)
- {
- VectorSet(maxs, region_maxs[0]+32, region_maxs[1]+32, region_maxs[2]+32);
- maxs[i] = region_mins[i];
- region_sides[i] = Brush_Create (mins, maxs, &td);
- }
-
- // set maxs
- VectorSet(maxs, region_maxs[0]+32, region_maxs[1]+32, region_maxs[2]+32);
-
- // vary mins
- for(i=0; i<3; i++)
- {
- VectorSet(mins, region_mins[0]-32, region_mins[1]-32, region_mins[2]-32);
- mins[i] = region_maxs[i];
- region_sides[i+3] = Brush_Create (mins, maxs, &td);
- }
-
-
- // this is a safe check, but it should not really happen anymore
- vec3_t vOrig;
- VectorSet(vOrig,
- (int)g_pParentWnd->GetCamWnd()->Camera()->origin[0],
- (int)g_pParentWnd->GetCamWnd()->Camera()->origin[1],
- (int)g_pParentWnd->GetCamWnd()->Camera()->origin[2]);
-
- for (i=0 ; i<3 ; i++)
- {
- if (vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i])
- {
- Sys_FPrintf(SYS_ERR, "Camera is NOT in the region, it's likely that the region won't compile correctly\n");
- }
- }
-
- // write the info_playerstart
- region_startpoint = Entity_Alloc();
- SetKeyValue(region_startpoint, "classname", "info_player_start");
- region_startpoint->eclass = Eclass_ForName ("info_player_start", false);
- char sTmp[1024];
- sprintf(sTmp, "%d %d %d", (int)vOrig[0], (int)vOrig[1], (int)vOrig[2]);
- SetKeyValue(region_startpoint, "origin", sTmp);
- sprintf(sTmp, "%d", (int)g_pParentWnd->GetCamWnd()->Camera()->angles[YAW]);
- SetKeyValue(region_startpoint, "angle", sTmp);
- // empty array of children
- region_startpoint->pData = new CPtrArray;
-}
-
-void RemoveRegionBrushes (void)
-{
- int i;
- if (!region_active)
- return;
- for (i=0 ; i<6 ; i++)
- Brush_Free (region_sides[i]);
+void FocusViews( const Vector3& point, float angle ){
+ CamWnd& camwnd = *g_pParentWnd->GetCamWnd();
+ Camera_setOrigin( camwnd, point );
+ Vector3 angles( Camera_getAngles( camwnd ) );
+ angles[CAMERA_PITCH] = 0;
+ angles[CAMERA_YAW] = angle;
+ Camera_setAngles( camwnd, angles );
- CleanFilter(region_startpoint);
- Entity_Free(region_startpoint);
+ XYWnd* xywnd = g_pParentWnd->GetXYWnd();
+ xywnd->SetOrigin( point );
}
-qboolean Map_IsBrushFiltered (brush_t *b)
-{
- int i;
+#include "stringio.h"
+
+void Map_StartPosition(){
+ Entity* entity = Scene_FindPlayerStart();
- for (i=0 ; i<3 ; i++)
+ if ( entity ) {
+ Vector3 origin;
+ string_parse_vector3( entity->getKeyValue( "origin" ), origin );
+ FocusViews( origin, string_read_float( entity->getKeyValue( "angle" ) ) );
+ }
+ else
{
- if (b->mins[i] > region_maxs[i])
- return true;
- if (b->maxs[i] < region_mins[i])
- return true;
+ FocusViews( g_vector3_identity, 0 );
}
- return false;
}
-/*
-===========
-Map_RegionOff
-Other filtering options may still be on
-===========
-*/
-void Map_RegionOff (void)
-{
- brush_t *b, *next;
- int i;
+inline bool node_is_worldspawn( scene::Node& node ){
+ Entity* entity = Node_getEntity( node );
+ return entity != 0 && string_equal( entity->getKeyValue( "classname" ), "worldspawn" );
+}
- region_active = false;
- for (i=0 ; i<3 ; i++)
- {
- region_maxs[i] = g_MaxWorldCoord-64;
- region_mins[i] = g_MinWorldCoord+64;
- }
- for (b=filtered_brushes.next ; b != &filtered_brushes ; b=next)
- {
- next = b->next;
- if (Map_IsBrushFiltered (b))
- continue; // still filtered
- Brush_RemoveFromList (b);
- if (active_brushes.next == NULL || active_brushes.prev == NULL)
- {
- active_brushes.next = &active_brushes;
- active_brushes.prev = &active_brushes;
+// use first worldspawn
+class entity_updateworldspawn : public scene::Traversable::Walker
+{
+public:
+bool pre( scene::Node& node ) const {
+ if ( node_is_worldspawn( node ) ) {
+ if ( Map_GetWorldspawn( g_map ) == 0 ) {
+ Map_SetWorldspawn( g_map, &node );
}
- Brush_AddToList (b, &active_brushes);
- b->bFiltered = FilterBrush(b);
}
- Sys_UpdateWindows (W_ALL);
+ return false;
}
+};
-void Map_ApplyRegion (void)
-{
- brush_t *b, *next;
+scene::Node* Map_FindWorldspawn( Map& map ){
+ Map_SetWorldspawn( map, 0 );
- region_active = true;
- for (b=active_brushes.next ; b != &active_brushes ; b=next)
- {
- next = b->next;
- if (!Map_IsBrushFiltered (b))
- continue; // still filtered
- Brush_RemoveFromList (b);
- Brush_AddToList (b, &filtered_brushes);
- }
+ Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() );
- Sys_UpdateWindows (W_ALL);
+ return Map_GetWorldspawn( map );
}
-/*
-========================
-Map_RegionSelectedBrushes
-========================
-*/
-void Map_RegionSelectedBrushes (void)
+class CollectAllWalker : public scene::Traversable::Walker
{
- Map_RegionOff ();
+scene::Node& m_root;
+UnsortedNodeSet& m_nodes;
+public:
+CollectAllWalker( scene::Node& root, UnsortedNodeSet& nodes ) : m_root( root ), m_nodes( nodes ){
+}
- if (selected_brushes.next == &selected_brushes) // nothing selected
- {
- Sys_Printf("Tried to region with no selection...\n");
- return;
- }
- region_active = true;
- Select_GetBounds (region_mins, region_maxs);
+bool pre( scene::Node& node ) const {
+ m_nodes.insert( NodeSmartReference( node ) );
+ Node_getTraversable( m_root )->erase( node );
+ return false;
+}
+};
-#ifdef _DEBUG
- if (filtered_brushes.next != &filtered_brushes)
- Sys_Printf("WARNING: filtered_brushes list may not be empty in Map_RegionSelectedBrushes\n");
-#endif
+void Node_insertChildFirst( scene::Node& parent, scene::Node& child ){
+ UnsortedNodeSet nodes;
+ Node_getTraversable( parent )->traverse( CollectAllWalker( parent, nodes ) );
+ Node_getTraversable( parent )->insert( child );
- if (active_brushes.next == &active_brushes)
+ for ( UnsortedNodeSet::iterator i = nodes.begin(); i != nodes.end(); ++i )
{
- // just have an empty filtered_brushes list
- // this happens if you set region after selecting all the brushes in your map (some weird people do that, ask MrE!)
- filtered_brushes.next = filtered_brushes.prev = &filtered_brushes;
+ Node_getTraversable( parent )->insert( ( *i ) );
}
- else
- {
- // move the entire active_brushes list to filtered_brushes
- filtered_brushes.next = active_brushes.next;
- filtered_brushes.prev = active_brushes.prev;
- filtered_brushes.next->prev = &filtered_brushes;
- filtered_brushes.prev->next = &filtered_brushes;
+}
+
+scene::Node& createWorldspawn(){
+ NodeSmartReference worldspawn( GlobalEntityCreator().createEntity( GlobalEntityClassManager().findOrInsert( "worldspawn", true ) ) );
+ Node_insertChildFirst( GlobalSceneGraph().root(), worldspawn );
+ return worldspawn;
+}
+
+void Map_UpdateWorldspawn( Map& map ){
+ if ( Map_FindWorldspawn( map ) == 0 ) {
+ Map_SetWorldspawn( map, &createWorldspawn() );
}
+}
- // move the entire selected_brushes list to active_brushes
- active_brushes.next = selected_brushes.next;
- active_brushes.prev = selected_brushes.prev;
- active_brushes.next->prev = &active_brushes;
- active_brushes.prev->next = &active_brushes;
+scene::Node& Map_FindOrInsertWorldspawn( Map& map ){
+ Map_UpdateWorldspawn( map );
+ return *Map_GetWorldspawn( map );
+}
- // deselect patches
- for (brush_t *b = active_brushes.next; b != &active_brushes; b = b->next)
- if (b->patchBrush)
- b->pPatch->bSelected = false;
- // clear selected_brushes
- selected_brushes.next = selected_brushes.prev = &selected_brushes;
+class MapMergeAll : public scene::Traversable::Walker
+{
+mutable scene::Path m_path;
+public:
+MapMergeAll( const scene::Path& root )
+ : m_path( root ){
+}
- Sys_UpdateWindows (W_ALL);
+bool pre( scene::Node& node ) const {
+ Node_getTraversable( m_path.top() )->insert( node );
+ m_path.push( makeReference( node ) );
+ selectPath( m_path, true );
+ return false;
}
+void post( scene::Node& node ) const {
+ m_path.pop();
+}
+};
-/*
-===========
-Map_RegionXY
-===========
-*/
-void Map_RegionXY (void)
+class MapMergeEntities : public scene::Traversable::Walker
{
- Map_RegionOff ();
+mutable scene::Path m_path;
+public:
+MapMergeEntities( const scene::Path& root )
+ : m_path( root ){
+}
+
+bool pre( scene::Node& node ) const {
+ if ( node_is_worldspawn( node ) ) {
+ scene::Node* world_node = Map_FindWorldspawn( g_map );
+ if ( world_node == 0 ) {
+ Map_SetWorldspawn( g_map, &node );
+ Node_getTraversable( m_path.top().get() )->insert( node );
+ m_path.push( makeReference( node ) );
+ Node_getTraversable( node )->traverse( SelectChildren( m_path ) );
+ }
+ else
+ {
+ m_path.push( makeReference( *world_node ) );
+ Node_getTraversable( node )->traverse( MapMergeAll( m_path ) );
+ }
+ }
+ else
+ {
+ Node_getTraversable( m_path.top() )->insert( node );
+ m_path.push( makeReference( node ) );
+ if ( node_is_group( node ) ) {
+ Node_getTraversable( node )->traverse( SelectChildren( m_path ) );
+ }
+ else
+ {
+ selectPath( m_path, true );
+ }
+ }
+ return false;
+}
- region_mins[0] = g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5 * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale();
- region_maxs[0] = g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5 * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale();
- region_mins[1] = g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5 * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale();
- region_maxs[1] = g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5 * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale();
- region_mins[2] = g_MinWorldCoord+64;
- region_maxs[2] = g_MaxWorldCoord-64;
- Map_ApplyRegion ();
+void post( scene::Node& node ) const {
+ m_path.pop();
}
+};
-/*
-===========
-Map_RegionTallBrush
-===========
-*/
-void Map_RegionTallBrush (void)
+class BasicContainer : public scene::Node::Symbiot
{
- brush_t *b;
-
- if (!QE_SingleBrush ())
- return;
+class TypeCasts
+{
+NodeTypeCastTable m_casts;
+public:
+TypeCasts(){
+ NodeContainedCast<BasicContainer, scene::Traversable>::install( m_casts );
+}
- b = selected_brushes.next;
+NodeTypeCastTable& get(){
+ return m_casts;
+}
+};
- Map_RegionOff ();
+scene::Node m_node;
+TraversableNodeSet m_traverse;
+public:
- VectorCopy (b->mins, region_mins);
- VectorCopy (b->maxs, region_maxs);
- region_mins[2] = g_MinWorldCoord+64;
- region_maxs[2] = g_MaxWorldCoord-64;
+typedef LazyStatic<TypeCasts> StaticTypeCasts;
- Undo_Start("delete");
- Undo_AddBrushList(&selected_brushes);
- Undo_AddEntity(b->owner);
- Select_Delete ();
- Undo_EndBrushList(&selected_brushes);
- Undo_End();
+scene::Traversable& get( NullType<scene::Traversable>){
+ return m_traverse;
+}
- Map_ApplyRegion ();
+BasicContainer() : m_node( this, this, StaticTypeCasts::instance().get() ){
}
-/*
-===========
-Map_RegionBrush
-===========
-*/
-void Map_RegionBrush (void)
-{
- brush_t *b;
+void release(){
+ delete this;
+}
- if (!QE_SingleBrush ())
- return;
+scene::Node& node(){
+ return m_node;
+}
+};
- b = selected_brushes.next;
+/// Merges the map graph rooted at \p node into the global scene-graph.
+void MergeMap( scene::Node& node ){
+ Node_getTraversable( node )->traverse( MapMergeEntities( scene::Path( makeReference( GlobalSceneGraph().root() ) ) ) );
+}
- Map_RegionOff ();
+void Map_ImportSelected( TextInputStream& in, const MapFormat& format ){
+ NodeSmartReference node( ( new BasicContainer )->node() );
+ format.readGraph( node, in, GlobalEntityCreator() );
+ Map_gatherNamespaced( node );
+ Map_mergeClonedNames();
+ MergeMap( node );
+}
- VectorCopy (b->mins, region_mins);
- VectorCopy (b->maxs, region_maxs);
+inline scene::Cloneable* Node_getCloneable( scene::Node& node ){
+ return NodeTypeCast<scene::Cloneable>::cast( node );
+}
- Undo_Start("delete");
- Undo_AddBrushList(&selected_brushes);
- Undo_AddEntity(b->owner);
- Select_Delete ();
- Undo_EndBrushList(&selected_brushes);
- Undo_End();
+inline scene::Node& node_clone( scene::Node& node ){
+ scene::Cloneable* cloneable = Node_getCloneable( node );
+ if ( cloneable != 0 ) {
+ return cloneable->clone();
+ }
- Map_ApplyRegion ();
+ return ( new scene::NullNode )->node();
}
-GList *find_string(GList *glist, const char *buf)
+class CloneAll : public scene::Traversable::Walker
{
- while (glist)
- {
- if (strcmp((char *)glist->data, buf) == 0)
- break; // this name is in our list already
- glist = glist->next;
- }
- return glist;
+mutable scene::Path m_path;
+public:
+CloneAll( scene::Node& root )
+ : m_path( makeReference( root ) ){
}
-void Map_ImportBuffer(char *buf)
-{
- Select_Deselect();
+bool pre( scene::Node& node ) const {
+ if ( node.isRoot() ) {
+ return false;
+ }
- Undo_Start("import buffer");
+ m_path.push( makeReference( node_clone( node ) ) );
+ m_path.top().get().IncRef();
- MemStream stream;
+ return true;
+}
- stream.Write(buf, strlen(buf));
- Map_Import(&stream, "xmap");
- stream.Close();
+void post( scene::Node& node ) const {
+ if ( node.isRoot() ) {
+ return;
+ }
- Sys_UpdateWindows (W_ALL);
- Sys_MarkMapModified();
+ Node_getTraversable( m_path.parent() )->insert( m_path.top() );
- Undo_End();
+ m_path.top().get().DecRef();
+ m_path.pop();
}
+};
+scene::Node& Node_Clone( scene::Node& node ){
+ scene::Node& clone = node_clone( node );
+ scene::Traversable* traversable = Node_getTraversable( node );
+ if ( traversable != 0 ) {
+ traversable->traverse( CloneAll( clone ) );
+ }
+ return clone;
+}
-//
-//================
-//Map_ImportFile
-//================
-//
-void Map_ImportFile (const char *filename)
-{
- FileStream file;
- Sys_BeginWait ();
- Sys_Printf("Importing map from %s\n",filename);
+typedef std::map<CopiedString, std::size_t> EntityBreakdown;
- const char* type = strrchr(filename,'.');
- if(type!=NULL) type++;
- /*!\todo Resolve "r" problem in scriptlib" */
- if(file.Open(filename, "rb"))
- Map_Import(&file, type, true);
- else
- Sys_FPrintf(SYS_ERR, "ERROR: couldn't open %s for read\n", filename);
+class EntityBreakdownWalker : public scene::Graph::Walker
+{
+EntityBreakdown& m_entitymap;
+public:
+EntityBreakdownWalker( EntityBreakdown& entitymap )
+ : m_entitymap( entitymap ){
+}
- file.Close();
+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;
+}
+};
- Sys_UpdateWindows (W_ALL);
- modified = true;
- Sys_EndWait();
+void Scene_EntityBreakdown( EntityBreakdown& entitymap ){
+ GlobalSceneGraph().traverse( EntityBreakdownWalker( entitymap ) );
}
-//
-//===========
-//Map_SaveSelected
-//===========
-//
-// Saves selected world brushes and whole entities with partial/full selections
-//
-void Map_SaveSelected(const char* filename)
-{
- FileStream file;
- Sys_Printf("Saving selection to %s\n",filename);
+WindowPosition g_posMapInfoWnd( c_default_window_pos );
- const char* type = strrchr(filename,'.');
- if(type!=NULL) type++;
- if(file.Open(filename, "w"))
- Map_Export (&file, type, false, true);
- else
- Sys_FPrintf(SYS_ERR, "ERROR: failed to open %s for write\n", filename);
+void DoMapInfo(){
+ ModalDialog dialog;
+ ui::Entry brushes_entry{ui::null};
+ ui::Entry entities_entry{ui::null};
+ ui::ListStore EntityBreakdownWalker{ui::null};
- file.Close();
+ ui::Window window = MainFrame_getWindow().create_dialog_window("Map Info", G_CALLBACK(dialog_delete_callback ), &dialog );
-}
+ window_set_position( window, g_posMapInfoWnd );
-//
-//===========
-//Map_SaveSelected
-//===========
-//
-// Saves selected world brushes and whole entities with partial/full selections
-//
-void Map_SaveSelected (MemStream* pMemFile, MemStream* pPatchFile)
-{
- Map_Export (pMemFile, "xmap", false, true);
+ {
+ 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();
- /*
- // write world entity first
- Entity_WriteSelected(world_entity, pMemFile);
+ scr.add(view);
+
+ EntityBreakdownWalker = store;
+ }
+ }
+ }
+
+ // Initialize fields
- // then write all other ents
- count = 1;
- for (e=entities.next ; e != &entities ; e=next)
{
- MemFile_fprintf(pMemFile, "// entity %i\n", count);
- count++;
- Entity_WriteSelected(e, pMemFile);
- next = e->next;
+ 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);
+ }
}
- //if (pPatchFile)
- // Patch_WriteFile(pPatchFile);
- */
+ 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();
}
-void MemFile_fprintf(MemStream* pMemFile, const char* pText, ...)
-{
- char Buffer[4096];
- va_list args;
- va_start (args,pText);
- vsprintf(Buffer, pText, args);
- pMemFile->Write(Buffer, strlen(Buffer));
-}
-
-/*!
-==============
-Region_SpawnPoint
-push the region spawn point
-\todo FIXME TTimo this was in the #1 MAP module implementation (in the core)
-not sure it has any use anymore, should prolly drop it
-==============
-*/
-void Region_SpawnPoint(FILE *f)
+
+class ScopeTimer
{
- // write the info_player_start, we use the camera position
- fprintf (f, "{\n");
- fprintf (f, "\"classname\" \"info_player_start\"\n");
- fprintf (f, "\"origin\" \"%i %i %i\"\n",
- (int)g_pParentWnd->GetCamWnd()->Camera()->origin[0],
- (int)g_pParentWnd->GetCamWnd()->Camera()->origin[1],
- (int)g_pParentWnd->GetCamWnd()->Camera()->origin[2]);
- fprintf (f, "\"angle\" \"%i\"\n", (int)g_pParentWnd->GetCamWnd()->Camera()->angles[YAW]);
- fprintf (f, "}\n");
+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 );
+
+ bool switch_format = false;
+
+ {
+ 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;
+ }
+ switch_format = !switch_format;
+ }
+ }
+
+ 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;
+
+ Brush_switchFormat( switch_format );
+}
+
+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& region_mins, const Vector3& region_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" ) ) ) {
+ std::string output;
+ output += AppPath_get();
+ output += "q3map2";
+ output += GDEF_OS_EXE_EXT;
+
+ output += " -v -game ";
+ output += ( type && *type ) ? type : "quake3";
+ output += " -fs_basepath \"";
+ output += EnginePath_get();
+ output += "\" -fs_homepath \"";
+ output += g_qeglobals.m_userEnginePath.c_str();
+ output += "\"";
+
+ // extra pakpaths
+ for ( int i = 0; i < g_pakPathCount; i++ ) {
+ if ( g_strcmp0( g_strPakPath[i].c_str(), "") ) {
+ output += " -fs_pakpath \"";
+ output += g_strPakPath[i].c_str();
+ output += "\"";
+ }
+ }
+
+ // extra switches
+ if ( g_disableEnginePath ) {
+ output += " -fs_nobasepath ";
+ }
+
+ if ( g_disableHomePath ) {
+ output += " -fs_nohomepath ";
+ }
+
+ output += " -fs_game ";
+ output += gamename_get();
+ output += " -convert -format ";
+ output += Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map";
+ if ( extension_equal( path_get_extension( filename ), "map" ) ) {
+ output += " -readmap ";
+ }
+ output += " \"";
+ output += filename;
+ output += "\"";
+
+ // run
+ Q_Exec( NULL, output.c_str(), NULL, false, true );
+
+ // rebuild filename as "filenamewithoutext_converted.map"
+ output = "";
+ output.append( filename, string_length( filename ) - ( n + 1 ) );
+ output += "_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 );
+ }
+ }
+ }
+}
+};
+
+void Scene_parentSelectedBrushesToEntity( scene::Graph& graph, scene::Node& parent ){
+ graph.traverse( ParentSelectedBrushesToEntityWalker( parent ) );
+}
+
+class CountSelectedBrushes : public scene::Graph::Walker
+{
+std::size_t& m_count;
+mutable std::size_t m_depth;
+public:
+CountSelectedBrushes( std::size_t& count ) : m_count( count ), m_depth( 0 ){
+ m_count = 0;
+}
+
+bool pre( const scene::Path& path, scene::Instance& instance ) const {
+ if ( ++m_depth != 1 && path.top().get().isRoot() ) {
+ return false;
+ }
+ Selectable* selectable = Instance_getSelectable( instance );
+ if ( selectable != 0
+ && selectable->isSelected()
+ && Node_isPrimitive( path.top() ) ) {
+ ++m_count;
+ }
+ return true;
+}
+
+void post( const scene::Path& path, scene::Instance& instance ) const {
+ --m_depth;
+}
+};
+
+std::size_t Scene_countSelectedBrushes( scene::Graph& graph ){
+ std::size_t count;
+ graph.traverse( CountSelectedBrushes( count ) );
+ return count;
+}
+
+enum ENodeType
+{
+ eNodeUnknown,
+ eNodeMap,
+ eNodeEntity,
+ eNodePrimitive,
+};
+
+const char* nodetype_get_name( ENodeType type ){
+ if ( type == eNodeMap ) {
+ return "map";
+ }
+ if ( type == eNodeEntity ) {
+ return "entity";
+ }
+ if ( type == eNodePrimitive ) {
+ return "primitive";
+ }
+ return "unknown";
+}
+
+ENodeType node_get_nodetype( scene::Node& node ){
+ if ( Node_isEntity( node ) ) {
+ return eNodeEntity;
+ }
+ if ( Node_isPrimitive( node ) ) {
+ return eNodePrimitive;
+ }
+ return eNodeUnknown;
+}
+
+bool contains_entity( scene::Node& node ){
+ return Node_getTraversable( node ) != 0 && !Node_isBrush( node ) && !Node_isPatch( node ) && !Node_isEntity( node );
+}
+
+bool contains_primitive( scene::Node& node ){
+ return Node_isEntity( node ) && Node_getTraversable( node ) != 0 && Node_getEntity( node )->isContainer();
+}
+
+ENodeType node_get_contains( scene::Node& node ){
+ if ( contains_entity( node ) ) {
+ return eNodeEntity;
+ }
+ if ( contains_primitive( node ) ) {
+ return eNodePrimitive;
+ }
+ return eNodeUnknown;
+}
+
+void Path_parent( const scene::Path& parent, const scene::Path& child ){
+ ENodeType contains = node_get_contains( parent.top() );
+ ENodeType type = node_get_nodetype( child.top() );
+
+ if ( contains != eNodeUnknown && contains == type ) {
+ NodeSmartReference node( child.top().get() );
+ Path_deleteTop( child );
+ Node_getTraversable( parent.top() )->insert( node );
+ SceneChangeNotify();
+ }
+ else
+ {
+ globalErrorStream() << "failed - " << nodetype_get_name( type ) << " cannot be parented to " << nodetype_get_name( contains ) << " container.\n";
+ }
+}
+
+void Scene_parentSelected(){
+ UndoableCommand undo( "parentSelected" );
+
+ if ( GlobalSelectionSystem().countSelected() > 1 ) {
+ class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor
+ {
+ const scene::Path& m_parent;
+public:
+ ParentSelectedBrushesToEntityWalker( const scene::Path& parent ) : m_parent( parent ){
+ }
+
+ void visit( scene::Instance& instance ) const {
+ if ( &m_parent != &instance.path() ) {
+ Path_parent( m_parent, instance.path() );
+ }
+ }
+ };
+
+ ParentSelectedBrushesToEntityWalker visitor( GlobalSelectionSystem().ultimateSelected().path() );
+ GlobalSelectionSystem().foreachSelected( visitor );
+ }
+ else
+ {
+ globalOutputStream() << "failed - did not find two selected nodes.\n";
+ }
+}
+
+
+void NewMap(){
+ if ( ConfirmModified( "New Map" ) ) {
+ Map_RegionOff();
+ Map_Free();
+ Map_New();
+ }
+}
+
+CopiedString g_mapsPath;
+
+const char* getMapsPath(){
+ return g_mapsPath.c_str();
+}
+
+const char* getLastMapFolderPath(){
+ if (g_strLastMapFolder.empty()) {
+ GlobalPreferenceSystem().registerPreference( "LastMapFolder", make_property_string( g_strLastMapFolder ) );
+ if (g_strLastMapFolder.empty()) {
+ StringOutputStream buffer( 1024 );
+ buffer << getMapsPath();
+ if ( !file_readable( buffer.c_str() ) ) {
+ buffer.clear();
+ buffer << g_qeglobals.m_userGamePath.c_str() << "/";
+ }
+ g_strLastMapFolder = buffer.c_str();
+ }
+ }
+ return g_strLastMapFolder.c_str();
+}
+
+const char* map_open( const char* title ){
+ return MainFrame_getWindow().file_dialog( TRUE, title, getLastMapFolderPath(), MapFormat::Name(), true, false, false );
+}
+
+const char* map_import( const char* title ){
+ return MainFrame_getWindow().file_dialog( TRUE, title, getLastMapFolderPath(), MapFormat::Name(), false, true, false );
+}
+
+const char* map_save( const char* title ){
+ return MainFrame_getWindow().file_dialog( FALSE, title, getLastMapFolderPath(), MapFormat::Name(), false, false, true );
+}
+
+void OpenMap(){
+ if ( !ConfirmModified( "Open Map" ) ) {
+ return;
+ }
+
+ const char* filename = map_open( "Open Map" );
+
+ if ( filename != NULL ) {
+ MRU_AddFile( filename );
+ Map_RegionOff();
+ Map_Free();
+ Map_LoadFile( filename );
+ }
+}
+
+void ImportMap(){
+ const char* filename = map_import( "Import Map" );
+
+ if ( filename != NULL ) {
+ UndoableCommand undo( "mapImport" );
+ Map_ImportFile( filename );
+ }
+}
+
+bool Map_SaveAs(){
+ const char* filename = map_save( "Save Map" );
+
+ if ( filename != NULL ) {
+ g_strLastMapFolder = g_path_get_dirname( filename );
+ MRU_AddFile( filename );
+ Map_Rename( filename );
+ return Map_Save();
+ }
+ return false;
+}
+
+void SaveMapAs(){
+ Map_SaveAs();
+}
+
+void SaveMap(){
+ if ( Map_Unnamed( g_map ) ) {
+ SaveMapAs();
+ }
+ else if ( Map_Modified( g_map ) ) {
+ Map_Save();
+ }
+}
+
+void ExportMap(){
+ const char* filename = map_save( "Export Selection" );
+
+ if ( filename != NULL ) {
+ g_strLastMapFolder = g_path_get_dirname( filename );
+ Map_SaveSelected( filename );
+ }
+}
+
+void SaveRegion(){
+ const char* filename = map_save( "Export Region" );
+
+ if ( filename != NULL ) {
+ g_strLastMapFolder = g_path_get_dirname( filename );
+ Map_SaveRegion( filename );
+ }
+}
+
+
+void RegionOff(){
+ Map_RegionOff();
+ SceneChangeNotify();
+}
+
+void RegionXY(){
+ Map_RegionXY(
+ g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
+ g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
+ g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
+ g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
+ );
+ SceneChangeNotify();
+}
+
+void RegionBrush(){
+ Map_RegionBrush();
+ SceneChangeNotify();
+}
+
+void RegionSelected(){
+ Map_RegionSelectedBrushes();
+ SceneChangeNotify();
+}
+
+
+
+
+
+class BrushFindByIndexWalker : public scene::Traversable::Walker
+{
+mutable std::size_t m_index;
+scene::Path& m_path;
+public:
+BrushFindByIndexWalker( std::size_t index, scene::Path& path )
+ : m_index( index ), m_path( path ){
+}
+
+bool pre( scene::Node& node ) const {
+ if ( Node_isPrimitive( node ) && m_index-- == 0 ) {
+ m_path.push( makeReference( node ) );
+ }
+ return false;
+}
+};
+
+class EntityFindByIndexWalker : public scene::Traversable::Walker
+{
+mutable std::size_t m_index;
+scene::Path& m_path;
+public:
+EntityFindByIndexWalker( std::size_t index, scene::Path& path )
+ : m_index( index ), m_path( path ){
+}
+
+bool pre( scene::Node& node ) const {
+ if ( Node_isEntity( node ) && m_index-- == 0 ) {
+ m_path.push( makeReference( node ) );
+ }
+ return false;
+}
+};
+
+void Scene_FindEntityBrush( std::size_t entity, std::size_t brush, scene::Path& path ){
+ path.push( makeReference( GlobalSceneGraph().root() ) );
+ {
+ Node_getTraversable( path.top() )->traverse( EntityFindByIndexWalker( entity, path ) );
+ }
+ if ( path.size() == 2 ) {
+ scene::Traversable* traversable = Node_getTraversable( path.top() );
+ if ( traversable != 0 ) {
+ traversable->traverse( BrushFindByIndexWalker( brush, path ) );
+ }
+ }
+}
+
+inline bool Node_hasChildren( scene::Node& node ){
+ scene::Traversable* traversable = Node_getTraversable( node );
+ return traversable != 0 && !traversable->empty();
+}
+
+void SelectBrush( int entitynum, int brushnum ){
+ scene::Path path;
+ Scene_FindEntityBrush( entitynum, brushnum, path );
+ if ( path.size() == 3 || ( path.size() == 2 && !Node_hasChildren( path.top() ) ) ) {
+ scene::Instance* instance = GlobalSceneGraph().find( path );
+ ASSERT_MESSAGE( instance != 0, "SelectBrush: path not found in scenegraph" );
+ Selectable* selectable = Instance_getSelectable( *instance );
+ ASSERT_MESSAGE( selectable != 0, "SelectBrush: path not selectable" );
+ selectable->setSelected( true );
+ g_pParentWnd->GetXYWnd()->PositionView( instance->worldAABB().origin );
+ }
+}
+
+
+class BrushFindIndexWalker : public scene::Graph::Walker
+{
+mutable const scene::Node* m_node;
+std::size_t& m_count;
+public:
+BrushFindIndexWalker( const scene::Node& node, std::size_t& count )
+ : m_node( &node ), m_count( count ){
+}
+
+bool pre( const scene::Path& path, scene::Instance& instance ) const {
+ if ( Node_isPrimitive( path.top() ) ) {
+ if ( m_node == path.top().get_pointer() ) {
+ m_node = 0;
+ }
+ if ( m_node ) {
+ ++m_count;
+ }
+ }
+ return true;
+}
+};
+
+class EntityFindIndexWalker : public scene::Graph::Walker
+{
+mutable const scene::Node* m_node;
+std::size_t& m_count;
+public:
+EntityFindIndexWalker( const scene::Node& node, std::size_t& count )
+ : m_node( &node ), m_count( count ){
+}
+
+bool pre( const scene::Path& path, scene::Instance& instance ) const {
+ if ( Node_isEntity( path.top() ) ) {
+ if ( m_node == path.top().get_pointer() ) {
+ m_node = 0;
+ }
+ if ( m_node ) {
+ ++m_count;
+ }
+ }
+ return true;
+}
+};
+
+static void GetSelectionIndex( int *ent, int *brush ){
+ std::size_t count_brush = 0;
+ std::size_t count_entity = 0;
+ if ( GlobalSelectionSystem().countSelected() != 0 ) {
+ const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();
+
+ GlobalSceneGraph().traverse( BrushFindIndexWalker( path.top(), count_brush ) );
+ GlobalSceneGraph().traverse( EntityFindIndexWalker( path.parent(), count_entity ) );
+ }
+ *brush = int(count_brush);
+ *ent = int(count_entity);
+}
+
+void DoFind(){
+ ModalDialog dialog;
+ ui::Entry entity{ui::null};
+ ui::Entry brush{ui::null};
+
+ ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback ), &dialog );
+
+ auto accel = ui::AccelGroup(ui::New);
+ window.add_accel_group( accel );
+
+ {
+ auto vbox = create_dialog_vbox( 4, 4 );
+ window.add(vbox);
+ {
+ auto table = create_dialog_table( 2, 2, 4, 4 );
+ vbox.pack_start( table, TRUE, TRUE, 0 );
+ {
+ ui::Widget label = ui::Label( "Entity number" );
+ label.show();
+ (table).attach(label, {0, 1, 0, 1}, {0, 0});
+ }
+ {
+ ui::Widget label = ui::Label( "Brush number" );
+ label.show();
+ (table).attach(label, {0, 1, 1, 2}, {0, 0});
+ }
+ {
+ auto entry = ui::Entry(ui::New);
+ entry.show();
+ table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
+ gtk_widget_grab_focus( entry );
+ entity = entry;
+ }
+ {
+ auto entry = ui::Entry(ui::New);
+ entry.show();
+ table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});
+
+ brush = entry;
+ }
+ }
+ {
+ auto hbox = create_dialog_hbox( 4 );
+ vbox.pack_start( hbox, TRUE, TRUE, 0 );
+ {
+ auto button = create_dialog_button( "Find", G_CALLBACK( dialog_button_ok ), &dialog );
+ hbox.pack_start( button, FALSE, FALSE, 0 );
+ widget_make_default( button );
+ gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
+ }
+ {
+ auto button = create_dialog_button( "Close", G_CALLBACK( dialog_button_cancel ), &dialog );
+ hbox.pack_start( button, FALSE, FALSE, 0 );
+ gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
+ }
+ }
+ }
+
+ // Initialize dialog
+ char buf[16];
+ int ent, br;
+
+ GetSelectionIndex( &ent, &br );
+ sprintf( buf, "%i", ent );
+ entity.text(buf);
+ sprintf( buf, "%i", br );
+ brush.text(buf);
+
+ if ( modal_dialog_show( window, dialog ) == eIDOK ) {
+ const char *entstr = gtk_entry_get_text( entity );
+ const char *brushstr = gtk_entry_get_text( brush );
+ SelectBrush( atoi( entstr ), atoi( brushstr ) );
+ }
+
+ window.destroy();
+}
+
+void Map_constructPreferences( PreferencesPage& page ){
+ page.appendCheckBox( "", "Load last map at startup", g_bLoadLastMap );
+ page.appendCheckBox( "", "Add entity and brush number comments on map write", g_writeMapComments );
+}
+
+
+class MapEntityClasses : public ModuleObserver
+{
+std::size_t m_unrealised;
+public:
+MapEntityClasses() : m_unrealised( 1 ){
+}
+
+void realise(){
+ if ( --m_unrealised == 0 ) {
+ if ( g_map.m_resource != 0 ) {
+ ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
+ g_map.m_resource->realise();
+ }
+ }
+}
+
+void unrealise(){
+ if ( ++m_unrealised == 1 ) {
+ if ( g_map.m_resource != 0 ) {
+ g_map.m_resource->flush();
+ g_map.m_resource->unrealise();
+ }
+ }
+}
+};
+
+MapEntityClasses g_MapEntityClasses;
+
+
+class MapModuleObserver : public ModuleObserver
+{
+std::size_t m_unrealised;
+public:
+MapModuleObserver() : m_unrealised( 1 ){
+}
+
+void realise(){
+ if ( --m_unrealised == 0 ) {
+ ASSERT_MESSAGE( !string_empty( g_qeglobals.m_userGamePath.c_str() ), "maps_directory: user-game-path is empty" );
+ StringOutputStream buffer( 256 );
+ buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
+ Q_mkdir( buffer.c_str() );
+ g_mapsPath = buffer.c_str();
+ }
+}
+
+void unrealise(){
+ if ( ++m_unrealised == 1 ) {
+ g_mapsPath = "";
+ }
+}
+};
+
+MapModuleObserver g_MapModuleObserver;
+
+CopiedString g_strLastMap;
+bool g_bLoadLastMap = false;
+
+void Map_Construct(){
+ GlobalCommands_insert( "RegionOff", makeCallbackF(RegionOff) );
+ GlobalCommands_insert( "RegionSetXY", makeCallbackF(RegionXY) );
+ GlobalCommands_insert( "RegionSetBrush", makeCallbackF(RegionBrush) );
+ GlobalCommands_insert( "RegionSetSelection", makeCallbackF(RegionSelected), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
+
+ GlobalPreferenceSystem().registerPreference( "LastMap", make_property_string( g_strLastMap ) );
+ GlobalPreferenceSystem().registerPreference( "LoadLastMap", make_property_string( g_bLoadLastMap ) );
+ GlobalPreferenceSystem().registerPreference( "MapInfoDlg", make_property<WindowPosition_String>( g_posMapInfoWnd ) );
+ GlobalPreferenceSystem().registerPreference( "WriteMapComments", make_property_string( g_writeMapComments ) );
+
+ PreferencesDialog_addSettingsPreferences( makeCallbackF(Map_constructPreferences) );
+
+ GlobalEntityClassManager().attach( g_MapEntityClasses );
+ Radiant_attachHomePathsObserver( g_MapModuleObserver );
+}
+
+void Map_Destroy(){
+ Radiant_detachHomePathsObserver( g_MapModuleObserver );
+ GlobalEntityClassManager().detach( g_MapEntityClasses );
}