X-Git-Url: http://de.git.xonotic.org/?a=blobdiff_plain;f=radiant%2Fmap.cpp;h=a4d8719a5130965e5ca529601a501cf2a04bbc5d;hb=cab6f1e40f69020aae8c59d45ab51a026ee3e6f6;hp=051c0a51ac6f90d4f47d64897b7221557900d0a9;hpb=80378101101ca1762bbf5638a9e3566893096d8a;p=xonotic%2Fnetradiant.git diff --git a/radiant/map.cpp b/radiant/map.cpp index 051c0a51..a4d8719a 100644 --- a/radiant/map.cpp +++ b/radiant/map.cpp @@ -1,1322 +1,2199 @@ -/* -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 -#if defined (__linux__) || defined (__APPLE__) -#include -#endif -#include "preferences.h" -#include "mainframe.h" -#include "gtkmisc.h" -#include "filters.h" - -extern MainFrame* g_pParentWnd; - -int modified; // for quit confirmation (0 = clean, 1 = unsaved, - // 2 = autosaved, but not regular saved) - -char currentmap[1024]; - -brush_t active_brushes; // brushes currently being displayed -brush_t selected_brushes; // highlighted - -face_t *selected_face; -brush_t *selected_face_brush; - -brush_t filtered_brushes; // brushes that have been filtered or regioned - -entity_t entities; // head/tail of doubly linked list - -entity_t *world_entity = NULL; // "classname" "worldspawn" ! - -void Map_Init() -{ - Map_Free(); -} - - -bool g_bCancel_Map_LoadFile; // Hydra: moved this here - -// TTimo -// need that in a variable, will have to tweak depending on the game -int g_MaxWorldCoord = 64*1024; -int g_MinWorldCoord = -64*1024; - -// 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 AddRegionBrushes (void); -void RemoveRegionBrushes (void); - -/* -============================================================= - - Cross map selection saving - - this could fuck up if you have only part of a complex entity selected... -============================================================= -*/ - -brush_t between_brushes; -entity_t between_entities; - -bool g_bRestoreBetween = false; - -void Map_SaveBetween (void) -{ - if (g_pParentWnd->ActiveXY()) - { - g_bRestoreBetween = true; - g_pParentWnd->ActiveXY()->Copy(); - } - return; -} - -void Map_RestoreBetween (void) -{ - if (g_pParentWnd->ActiveXY() && g_bRestoreBetween) - g_pParentWnd->ActiveXY()->Paste(); -} - -//============================================================================ - -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; -} - -void Map_BuildBrushData(void) -{ - brush_t *b, *next; - - if (active_brushes.next == NULL) - return; - - 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(); -} - -entity_t *Map_FindClass (char *cname) -{ - entity_t *ent; - - for (ent = entities.next ; ent != &entities ; ent=ent->next) - { - if (!strcmp(cname, ValueForKey (ent, "classname"))) - return ent; - } - return NULL; -} - -/* -================ -Map_Free -free all map elements, reinitialize the structures that depend on them -================ -*/ -void Map_Free (void) -{ - 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) - { - // 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; - } - else - { - // 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); - } - - if (world_entity) - Entity_Free(world_entity); - world_entity = NULL; -} - -entity_t *AngledEntity() -{ - 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; -} - -// -// 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; iGetAt(i); - brushes = (CPtrArray*)e->pData; - num_brushes = brushes->GetSize(); - for(j=0; jGetAt(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 && iGetAt(i); - brushes = (CPtrArray*)e->pData; - num_brushes = brushes->GetSize(); - for(j=0; !bDoneBPCheck && jGetAt(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; iGetAt(i); - brushes = (CPtrArray*)e->pData; - - num_brushes = brushes->GetSize(); - // link brushes into entity - for(j=0; jGetAt(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; - - g_pParentWnd->GetSynapseClient().ImportMap(in, &ents, type); - Map_ImportEntities(&ents, bAddSelected); -} - -/* -================ -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 - // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=261 - // 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(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; - - /*! - \todo the entity_t needs to be reworked and asbtracted some more - - 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 - - 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); - - 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; - } - - // entities which brushes are completely filtered out by regioning are not printed to the map - if (FilterChildren(e, bRegionOnly, bSelectedOnly)) - ents->Add(e); - } - - if (bRegionOnly && region_active) - { - for(i=0; i<6; i++) - ((CPtrArray*)world_entity->pData)->Add(region_sides[i]); - - ents->Add(region_startpoint); - } -} - -void Map_Export(IDataStream *out, const char *type, bool bRegionOnly, bool bSelectedOnly) -{ - entity_t *e; - - CPtrArray ents; - - if (bRegionOnly && region_active) - AddRegionBrushes(); - - // create the filters - world_entity->pData = new CPtrArray(); - for(e = entities.next; e != &entities; e = e->next) - e->pData = new CPtrArray(); - - Map_ExportEntities(&ents, bRegionOnly, bSelectedOnly); - - g_pParentWnd->GetSynapseClient().ExportMap(&ents, out, type); - - // cleanup the filters - CleanFilter(world_entity); - for (e=entities.next ; e!=&entities ; e=e->next) - CleanFilter(e); - - if (bRegionOnly && region_active) - RemoveRegionBrushes(); -} - -const char* filename_get_extension(const char* filename) -{ - const char* type = strrchr(filename,'.'); - if(type != NULL) - return ++type; - return ""; -} - -/* -=========== -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); - - Pointfile_Clear (); - - if (!use_region) - { - char backup[1024]; - - // rename current to .bak - strcpy (backup, filename); - StripExtension (backup); - strcat (backup, ".bak"); - unlink (backup); - rename (filename, backup); - } - - Sys_Printf ("Map_SaveFile: %s\n", filename); - - // 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; - } - - // extract filetype - Map_Export(&file, filename_get_extension(filename), use_region); - - file.Close(); - - finish = clock(); - elapsed_time = (double)(finish - start) / CLOCKS_PER_SEC; - - Sys_Printf ("Saved in %-.2f second(s).\n",elapsed_time); - modified = false; - - if ( !strstr( filename, "autosave" ) ) - Sys_SetTitle (filename); - - if (!use_region) - { - time_t timer; - - time (&timer); - - Sys_Beep (); - - Sys_Status ("Saved.", 0); - } -} - -/* -=========== -Map_New - -=========== -*/ -void Map_New (void) -{ - Sys_Printf ("Map_New\n"); - Map_Free (); - - strcpy (currentmap, "unnamed.map"); - Sys_SetTitle (currentmap); - - 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); - - 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()); - - Map_RestoreBetween (); - - Group_Init(); - - Sys_UpdateWindows (W_ALL); - modified = false; -} - -/* -=========================================================== - - REGION - -=========================================================== -*/ -qboolean region_active; -vec3_t region_mins = {g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord}; -vec3_t region_maxs = {g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord}; - -brush_t *region_sides[6]; - -/* -=========== -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); - } - - - // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=503 - // 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]); - - CleanFilter(region_startpoint); - Entity_Free(region_startpoint); -} - -qboolean Map_IsBrushFiltered (brush_t *b) -{ - int i; - - for (i=0 ; i<3 ; i++) - { - if (b->mins[i] > region_maxs[i]) - return true; - if (b->maxs[i] < region_mins[i]) - return true; - } - return false; -} - -/* -=========== -Map_RegionOff - -Other filtering options may still be on -=========== -*/ -void Map_RegionOff (void) -{ - brush_t *b, *next; - int i; - - 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; - } - Brush_AddToList (b, &active_brushes); - b->bFiltered = FilterBrush(b); - } - Sys_UpdateWindows (W_ALL); -} - -void Map_ApplyRegion (void) -{ - brush_t *b, *next; - - 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); - } - - Sys_UpdateWindows (W_ALL); -} - - -/* -======================== -Map_RegionSelectedBrushes -======================== -*/ -void Map_RegionSelectedBrushes (void) -{ - Map_RegionOff (); - - 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); - -#ifdef _DEBUG - if (filtered_brushes.next != &filtered_brushes) - Sys_Printf("WARNING: filtered_brushes list may not be empty in Map_RegionSelectedBrushes\n"); -#endif - - if (active_brushes.next == &active_brushes) - { - // 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; - } - 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; - } - - // 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; - - // 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; - - Sys_UpdateWindows (W_ALL); -} - - -/* -=========== -Map_RegionXY -=========== -*/ -void Map_RegionXY (void) -{ - Map_RegionOff (); - - 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 (); -} - -/* -=========== -Map_RegionTallBrush -=========== -*/ -void Map_RegionTallBrush (void) -{ - brush_t *b; - - if (!QE_SingleBrush ()) - return; - - b = selected_brushes.next; - - Map_RegionOff (); - - VectorCopy (b->mins, region_mins); - VectorCopy (b->maxs, region_maxs); - region_mins[2] = g_MinWorldCoord+64; - region_maxs[2] = g_MaxWorldCoord-64; - - Undo_Start("delete"); - Undo_AddBrushList(&selected_brushes); - Undo_AddEntity(b->owner); - Select_Delete (); - Undo_EndBrushList(&selected_brushes); - Undo_End(); - - Map_ApplyRegion (); -} - -/* -=========== -Map_RegionBrush -=========== -*/ -void Map_RegionBrush (void) -{ - brush_t *b; - - if (!QE_SingleBrush ()) - return; - - b = selected_brushes.next; - - Map_RegionOff (); - - VectorCopy (b->mins, region_mins); - VectorCopy (b->maxs, region_maxs); - - Undo_Start("delete"); - Undo_AddBrushList(&selected_brushes); - Undo_AddEntity(b->owner); - Select_Delete (); - Undo_EndBrushList(&selected_brushes); - Undo_End(); - - Map_ApplyRegion (); -} - -GList *find_string(GList *glist, const char *buf) -{ - while (glist) - { - if (strcmp((char *)glist->data, buf) == 0) - break; // this name is in our list already - glist = glist->next; - } - return glist; -} - -void Map_ImportBuffer(char *buf) -{ - Select_Deselect(); - - Undo_Start("import buffer"); - - MemStream stream; - - stream.Write(buf, strlen(buf)); - Map_Import(&stream, "xmap"); - stream.Close(); - - Sys_UpdateWindows (W_ALL); - Sys_MarkMapModified(); - - Undo_End(); -} - - -// -//================ -//Map_ImportFile -//================ -// -void Map_ImportFile (const char *filename) -{ - FileStream file; - Sys_BeginWait (); - - Sys_Printf("Importing map from %s\n",filename); - - 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); - - file.Close(); - - Sys_UpdateWindows (W_ALL); - modified = true; - Sys_EndWait(); -} - -// -//=========== -//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); - - 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); - - file.Close(); - -} - -// -//=========== -//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); - - /* - // write world entity first - Entity_WriteSelected(world_entity, pMemFile); - - // 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; - } - - //if (pPatchFile) - // Patch_WriteFile(pPatchFile); - */ -} - - -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) -{ - // 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"); -} +/* + 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 "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 + +#include +#include +#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 "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 "preferences.h" +#include "preferencesystem.h" +#include "referencecache.h" +#include "mru.h" +#include "commands.h" +#include "autosave.h" +#include "brushmodule.h" +#include "brush.h" + +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() ) ); + } +} +void destroy(){ + if ( !empty() ) { + //globalOutputStream() << "destroy " << makeQuoted(c_str()) << "\n"; + m_names.erase( name_read( c_str() ) ); + } +} + +NameObserver& operator=( const NameObserver& other ); +public: +NameObserver( UniqueNames& names ) : m_names( names ){ + construct(); +} +NameObserver( const NameObserver& other ) : m_names( other.m_names ), m_name( other.m_name ){ + construct(); +} +~NameObserver(){ + destroy(); +} +bool empty() const { + return string_empty( c_str() ); +} +const char* c_str() const { + return m_name.c_str(); +} +void nameChanged( const char* name ){ + destroy(); + m_name = name; + construct(); +} +typedef MemberCaller1 NameChangedCaller; +}; + +class BasicNamespace : public Namespace +{ +typedef std::map 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 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(setName) << "\n"; +} +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(setName) << "\n"; + detachObserver( NameObserver::NameChangedCaller( ( *i ).second ) ); + m_names.erase( i ); +} + +void makeUnique( const char* name, const NameCallback& setName ) const { + char buffer[1024]; + name_write( buffer, m_uniqueNames.make_unique( name_read( name ) ) ); + setName( buffer ); +} + +void mergeNames( const BasicNamespace& other ) const { + typedef std::list SetNameCallbacks; + typedef std::map NameGroups; + NameGroups groups; + + 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 ); + } + + 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 ); + + char buffer[1024]; + name_write( buffer, uniqueName ); + + //globalOutputStream() << "renaming " << makeQuoted((*i).first.c_str()) << " to " << makeQuoted(buffer) << "\n"; + + SetNameCallbacks& setNameCallbacks = ( *i ).second; + + for ( SetNameCallbacks::const_iterator j = setNameCallbacks.begin(); j != setNameCallbacks.end(); ++j ) + { + ( *j )( buffer ); + } + } +} +}; + +BasicNamespace g_defaultNamespace; +BasicNamespace g_cloneNamespace; + +class NamespaceAPI +{ +Namespace* m_namespace; +public: +typedef Namespace Type; +STRING_CONSTANT( Name, "*" ); + +NamespaceAPI(){ + m_namespace = &g_defaultNamespace; +} +Namespace* getTable(){ + return m_namespace; +} +}; + +typedef SingletonModule NamespaceModule; +typedef Static StaticNamespaceModule; +StaticRegisterModule staticRegisterDefaultNamespace( StaticNamespaceModule::instance() ); + + +std::list g_cloned; + +inline Namespaced* Node_getNamespaced( scene::Node& node ){ + return NodeTypeCast::cast( node ); +} + +void Node_gatherNamespaced( scene::Node& node ){ + Namespaced* namespaced = Node_getNamespaced( node ); + if ( namespaced != 0 ) { + g_cloned.push_back( namespaced ); + } +} + +class GatherNamespaced : public scene::Traversable::Walker +{ +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::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i ) + { + ( *i )->setNamespace( g_cloneNamespace ); + } + g_cloneNamespace.mergeNames( g_defaultNamespace ); + for ( std::list::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i ) + { + ( *i )->setNamespace( g_defaultNamespace ); + } + + g_cloned.clear(); +} + +class WorldNode +{ +scene::Node* m_node; +public: +WorldNode() + : m_node( 0 ){ +} +void set( scene::Node* node ){ + if ( m_node != 0 ) { + m_node->DecRef(); + } + m_node = node; + if ( m_node != 0 ) { + m_node->IncRef(); + } +} +scene::Node* get() const { + return m_node; +} +}; + +class Map; +void Map_SetValid( Map& map, bool valid ); +void Map_UpdateTitle( const Map& map ); +void Map_SetWorldspawn( Map& map, scene::Node* node ); + + +class Map : public ModuleObserver +{ +public: +CopiedString m_name; +Resource* m_resource; +bool m_valid; + +bool m_modified; +void ( *m_modified_changed )( const Map& ); + +Signal0 m_mapValidCallbacks; + +WorldNode m_world_node; // "classname" "worldspawn" ! + +Map() : m_resource( 0 ), m_valid( false ), m_modified_changed( Map_UpdateTitle ){ +} + +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(); + } + + GlobalSceneGraph().insert_root( *m_resource->getNode() ); + + AutoSave_clear(); + + Map_SetValid( g_map, true ); + } +} +void unrealise(){ + if ( m_resource != 0 ) { + Map_SetValid( g_map, false ); + Map_SetWorldspawn( g_map, 0 ); + + + GlobalUndoSystem().clear(); + + GlobalSceneGraph().erase_root(); + } +} +}; + +Map g_map; +Map* g_currentMap = 0; + +void Map_addValidCallback( Map& map, const SignalHandler& handler ){ + map.m_mapValidCallbacks.connectLast( handler ); +} + +bool Map_Valid( const Map& map ){ + return map.m_valid; +} + +void Map_SetValid( Map& map, bool valid ){ + map.m_valid = valid; + map.m_mapValidCallbacks(); +} + + +const char* Map_Name( const Map& map ){ + return map.m_name.c_str(); +} + +bool Map_Unnamed( const Map& map ){ + return string_equal( Map_Name( map ), "unnamed.map" ); +} + +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; +} + +const MapFormat& Map_getFormat( const Map& map ){ + return MapFormat_forFile( Map_Name( map ) ); +} + + +bool Map_Modified( const Map& map ){ + return map.m_modified; +} + +void Map_SetModified( Map& map, bool modified ){ + if ( map.m_modified ^ modified ) { + map.m_modified = modified; + + map.m_modified_changed( map ); + } +} + +void Map_UpdateTitle( const Map& map ){ + Sys_SetTitle( map.m_name.c_str(), Map_Modified( map ) ); +} + + + +scene::Node* Map_GetWorldspawn( const Map& map ){ + return map.m_world_node.get(); +} + +void Map_SetWorldspawn( Map& map, scene::Node* node ){ + map.m_world_node.set( node ); +} + + +// TTimo +// need that in a variable, will have to tweak depending on the game +float g_MaxWorldCoord = 64 * 1024; +float g_MinWorldCoord = -64 * 1024; + +void AddRegionBrushes( void ); +void RemoveRegionBrushes( void ); + + + +/* + ================ + 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(); +} + +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; +} +}; + +Entity* Scene_FindEntityByClass( const char* name ){ + Entity* entity; + GlobalSceneGraph().traverse( EntityFindByClassname( name, entity ) ); + return entity; +} + +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; +} + +// +// move the view to a start position +// + + +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 ); + + XYWnd* xywnd = g_pParentWnd->GetXYWnd(); + xywnd->SetOrigin( point ); +} + +#include "stringio.h" + +void Map_StartPosition(){ + Entity* entity = Scene_FindPlayerStart(); + + if ( entity ) { + Vector3 origin; + string_parse_vector3( entity->getKeyValue( "origin" ), origin ); + FocusViews( origin, string_read_float( entity->getKeyValue( "angle" ) ) ); + } + else + { + FocusViews( g_vector3_identity, 0 ); + } +} + + +inline bool node_is_worldspawn( scene::Node& node ){ + Entity* entity = Node_getEntity( node ); + return entity != 0 && string_equal( entity->getKeyValue( "classname" ), "worldspawn" ); +} + + +// 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 ); + } + } + return false; +} +}; + +scene::Node* Map_FindWorldspawn( Map& map ){ + Map_SetWorldspawn( map, 0 ); + + Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() ); + + return Map_GetWorldspawn( map ); +} + + +class CollectAllWalker : public scene::Traversable::Walker +{ +scene::Node& m_root; +UnsortedNodeSet& m_nodes; +public: +CollectAllWalker( scene::Node& root, UnsortedNodeSet& nodes ) : m_root( root ), m_nodes( nodes ){ +} +bool pre( scene::Node& node ) const { + m_nodes.insert( NodeSmartReference( node ) ); + Node_getTraversable( m_root )->erase( node ); + return false; +} +}; + +void Node_insertChildFirst( scene::Node& parent, scene::Node& child ){ + UnsortedNodeSet nodes; + Node_getTraversable( parent )->traverse( CollectAllWalker( parent, nodes ) ); + Node_getTraversable( parent )->insert( child ); + + for ( UnsortedNodeSet::iterator i = nodes.begin(); i != nodes.end(); ++i ) + { + Node_getTraversable( parent )->insert( ( *i ) ); + } +} + +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() ); + } +} + +scene::Node& Map_FindOrInsertWorldspawn( Map& map ){ + Map_UpdateWorldspawn( map ); + return *Map_GetWorldspawn( map ); +} + + +class MapMergeAll : public scene::Traversable::Walker +{ +mutable scene::Path m_path; +public: +MapMergeAll( const scene::Path& root ) + : m_path( root ){ +} +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(); +} +}; + +class MapMergeEntities : public scene::Traversable::Walker +{ +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; +} +void post( scene::Node& node ) const { + m_path.pop(); +} +}; + +class BasicContainer : public scene::Node::Symbiot +{ +class TypeCasts +{ +NodeTypeCastTable m_casts; +public: +TypeCasts(){ + NodeContainedCast::install( m_casts ); +} +NodeTypeCastTable& get(){ + return m_casts; +} +}; + +scene::Node m_node; +TraversableNodeSet m_traverse; +public: + +typedef LazyStatic StaticTypeCasts; + +scene::Traversable& get( NullType){ + return m_traverse; +} + +BasicContainer() : m_node( this, this, StaticTypeCasts::instance().get() ){ +} +void release(){ + delete this; +} +scene::Node& node(){ + return m_node; +} +}; + +/// 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() ) ) ) ); +} +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 ); +} + +inline scene::Cloneable* Node_getCloneable( scene::Node& node ){ + return NodeTypeCast::cast( node ); +} + +inline scene::Node& node_clone( scene::Node& node ){ + scene::Cloneable* cloneable = Node_getCloneable( node ); + if ( cloneable != 0 ) { + return cloneable->clone(); + } + + return ( new scene::NullNode )->node(); +} + +class CloneAll : public scene::Traversable::Walker +{ +mutable scene::Path m_path; +public: +CloneAll( scene::Node& root ) + : m_path( makeReference( root ) ){ +} +bool pre( scene::Node& node ) const { + if ( node.isRoot() ) { + return false; + } + + m_path.push( makeReference( node_clone( node ) ) ); + m_path.top().get().IncRef(); + + return true; +} +void post( scene::Node& node ) const { + if ( node.isRoot() ) { + return; + } + + Node_getTraversable( m_path.parent() )->insert( m_path.top() ); + + 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; +} + + +typedef std::map EntityBreakdown; + +class EntityBreakdownWalker : public scene::Graph::Walker +{ +EntityBreakdown& m_entitymap; +public: +EntityBreakdownWalker( EntityBreakdown& entitymap ) + : m_entitymap( entitymap ){ +} +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; + GtkEntry* brushes_entry; + GtkEntry* entities_entry; + ui::ListStore EntityBreakdownWalker{nullptr}; + + ui::Window window = MainFrame_getWindow().create_dialog_window("Map Info", G_CALLBACK(dialog_delete_callback ), &dialog ); + + window_set_position( window, g_posMapInfoWnd ); + + { + GtkVBox* vbox = create_dialog_vbox( 4, 4 ); + gtk_container_add( GTK_CONTAINER( window ), GTK_WIDGET( vbox ) ); + + { + GtkHBox* hbox = create_dialog_hbox( 4 ); + gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( hbox ), FALSE, TRUE, 0 ); + + { + GtkTable* table = create_dialog_table( 2, 2, 4, 4 ); + gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 ); + + { + auto entry = ui::Entry(); + gtk_widget_show( GTK_WIDGET( entry ) ); + gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 0, 1, + (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ), + (GtkAttachOptions) ( 0 ), 0, 0 ); + gtk_editable_set_editable( GTK_EDITABLE(entry), FALSE ); + + brushes_entry = entry; + } + { + auto entry = ui::Entry(); + gtk_widget_show( GTK_WIDGET( entry ) ); + gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 1, 2, + (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ), + (GtkAttachOptions) ( 0 ), 0, 0 ); + gtk_editable_set_editable( GTK_EDITABLE(entry), FALSE ); + + entities_entry = entry; + } + { + ui::Widget label = ui::Label( "Total Brushes" ); + gtk_widget_show( label ); + gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 0, 1, + (GtkAttachOptions) ( GTK_FILL ), + (GtkAttachOptions) ( 0 ), 0, 0 ); + gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 ); + } + { + ui::Widget label = ui::Label( "Total Entities" ); + gtk_widget_show( label ); + gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 1, 2, + (GtkAttachOptions) ( GTK_FILL ), + (GtkAttachOptions) ( 0 ), 0, 0 ); + gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 ); + } + } + { + GtkVBox* vbox2 = create_dialog_vbox( 4 ); + gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( vbox2 ), FALSE, FALSE, 0 ); + + { + GtkButton* button = create_dialog_button( "Close", G_CALLBACK( dialog_button_ok ), &dialog ); + gtk_box_pack_start( GTK_BOX( vbox2 ), GTK_WIDGET( button ), FALSE, FALSE, 0 ); + } + } + } + { + ui::Widget label = ui::Label( "Entity breakdown" ); + gtk_widget_show( label ); + gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( label ), FALSE, TRUE, 0 ); + gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 ); + } + { + GtkScrolledWindow* scr = create_scrolled_window( GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC, 4 ); + gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( scr ), TRUE, TRUE, 0 ); + + { + ui::ListStore store = ui::ListStore(gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_STRING )); + + ui::Widget view = ui::TreeView(ui::TreeModel( GTK_TREE_MODEL( store ) )); + gtk_tree_view_set_headers_clickable( GTK_TREE_VIEW( view ), TRUE ); + + { + auto renderer = ui::CellRendererText(); + GtkTreeViewColumn* column = ui::TreeViewColumn( "Entity", renderer, {{"text", 0}} ); + gtk_tree_view_append_column( GTK_TREE_VIEW( view ), column ); + gtk_tree_view_column_set_sort_column_id( column, 0 ); + } + + { + auto renderer = ui::CellRendererText(); + GtkTreeViewColumn* column = ui::TreeViewColumn( "Count", renderer, {{"text", 1}} ); + gtk_tree_view_append_column( GTK_TREE_VIEW( view ), column ); + gtk_tree_view_column_set_sort_column_id( column, 1 ); + } + + gtk_widget_show( view ); + + gtk_container_add( GTK_CONTAINER( scr ), 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 ) ); + GtkTreeIter iter; + gtk_list_store_append( GTK_LIST_STORE( EntityBreakdownWalker ), &iter ); + gtk_list_store_set( GTK_LIST_STORE( EntityBreakdownWalker ), &iter, 0, ( *i ).first.c_str(), 1, tmp, -1 ); + } + } + + g_object_unref( G_OBJECT( EntityBreakdownWalker ) ); + + char tmp[16]; + sprintf( tmp, "%u", Unsigned( g_brushCount.get() ) ); + gtk_entry_set_text( GTK_ENTRY( brushes_entry ), tmp ); + sprintf( tmp, "%u", Unsigned( g_entityCount.get() ) ); + gtk_entry_set_text( GTK_ENTRY( entities_entry ), tmp ); + + modal_dialog_show( window, dialog ); + + // save before exit + window_get_position( window, g_posMapInfoWnd ); + + gtk_widget_destroy( GTK_WIDGET( window ) ); +} + + + +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_strLastFolder = ""; + +/* + ================ + Map_LoadFile + ================ + */ + +void Map_LoadFile( const char *filename ){ + globalOutputStream() << "Loading map from " << filename << "\n"; + ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" ); + + MRU_AddFile( filename ); + g_strLastFolder = 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 ); + g_map.m_name = filename; + 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; + + // restart VFS to apply new pak filtering based on mapname + // needed for daemon DPK VFS + VFS_Restart(); +} + +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 ); +} + +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_strLastFolder = 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().getRequiredGameDescriptionKeyValue( "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( "\" -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 ); + } + } + } +} +}; + +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* getLastFolderPath(){ + if (g_strLastFolder.empty()) { + GlobalPreferenceSystem().registerPreference( "LastFolder", CopiedStringImportStringCaller( g_strLastFolder ), CopiedStringExportStringCaller( g_strLastFolder ) ); + if (g_strLastFolder.empty()) { + g_strLastFolder = g_qeglobals.m_userGamePath; + } + } + return g_strLastFolder.c_str(); +} + +const char* map_open( const char* title ){ + return MainFrame_getWindow().file_dialog( TRUE, title, getLastFolderPath(), MapFormat::Name(), true, false, false ); +} + +const char* map_import( const char* title ){ + return MainFrame_getWindow().file_dialog( TRUE, title, getLastFolderPath(), MapFormat::Name(), false, true, false ); +} + +const char* map_save( const char* title ){ + return MainFrame_getWindow().file_dialog( FALSE, title, getLastFolderPath(), 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_strLastFolder = 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_strLastFolder = g_path_get_dirname( filename ); + Map_SaveSelected( filename ); + } +} + +void SaveRegion(){ + const char* filename = map_save( "Export Region" ); + + if ( filename != NULL ) { + g_strLastFolder = 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; + GtkEntry* entity; + GtkEntry* brush; + + ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback ), &dialog ); + + auto accel = ui::AccelGroup(); + window.add_accel_group( accel ); + + { + GtkVBox* vbox = create_dialog_vbox( 4, 4 ); + gtk_container_add( GTK_CONTAINER( window ), GTK_WIDGET( vbox ) ); + { + GtkTable* table = create_dialog_table( 2, 2, 4, 4 ); + gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( table ), TRUE, TRUE, 0 ); + { + ui::Widget label = ui::Label( "Entity number" ); + gtk_widget_show( label ); + gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 0, 1, + (GtkAttachOptions) ( 0 ), + (GtkAttachOptions) ( 0 ), 0, 0 ); + } + { + ui::Widget label = ui::Label( "Brush number" ); + gtk_widget_show( label ); + gtk_table_attach( GTK_TABLE( table ), label, 0, 1, 1, 2, + (GtkAttachOptions) ( 0 ), + (GtkAttachOptions) ( 0 ), 0, 0 ); + } + { + auto entry = ui::Entry(); + gtk_widget_show( GTK_WIDGET( entry ) ); + gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 0, 1, + (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ), + (GtkAttachOptions) ( 0 ), 0, 0 ); + gtk_widget_grab_focus( GTK_WIDGET( entry ) ); + entity = entry; + } + { + auto entry = ui::Entry(); + gtk_widget_show( GTK_WIDGET( entry ) ); + gtk_table_attach( table, GTK_WIDGET( entry ), 1, 2, 1, 2, + (GtkAttachOptions) ( GTK_EXPAND | GTK_FILL ), + (GtkAttachOptions) ( 0 ), 0, 0 ); + + brush = entry; + } + } + { + GtkHBox* hbox = create_dialog_hbox( 4 ); + gtk_box_pack_start( GTK_BOX( vbox ), GTK_WIDGET( hbox ), TRUE, TRUE, 0 ); + { + auto button = create_dialog_button( "Find", G_CALLBACK( dialog_button_ok ), &dialog ); + gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 ); + widget_make_default( button ); + gtk_widget_add_accelerator( GTK_WIDGET( button ), "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 ); + } + { + GtkButton* button = create_dialog_button( "Close", G_CALLBACK( dialog_button_cancel ), &dialog ); + gtk_box_pack_start( GTK_BOX( hbox ), GTK_WIDGET( button ), FALSE, FALSE, 0 ); + gtk_widget_add_accelerator( GTK_WIDGET( 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 ); + gtk_entry_set_text( entity, buf ); + sprintf( buf, "%i", br ); + gtk_entry_set_text( brush, 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 ) ); + } + + gtk_widget_destroy( GTK_WIDGET( window ) ); +} + +void Map_constructPreferences( PreferencesPage& page ){ + page.appendCheckBox( "", "Load last map on open", g_bLoadLastMap ); +} + + +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", FreeCaller() ); + GlobalCommands_insert( "RegionSetXY", FreeCaller() ); + GlobalCommands_insert( "RegionSetBrush", FreeCaller() ); + GlobalCommands_insert( "RegionSetSelection", FreeCaller(), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) ); + + GlobalPreferenceSystem().registerPreference( "LastMap", CopiedStringImportStringCaller( g_strLastMap ), CopiedStringExportStringCaller( g_strLastMap ) ); + GlobalPreferenceSystem().registerPreference( "LoadLastMap", BoolImportStringCaller( g_bLoadLastMap ), BoolExportStringCaller( g_bLoadLastMap ) ); + GlobalPreferenceSystem().registerPreference( "MapInfoDlg", WindowPositionImportStringCaller( g_posMapInfoWnd ), WindowPositionExportStringCaller( g_posMapInfoWnd ) ); + + PreferencesDialog_addSettingsPreferences( FreeCaller1() ); + + GlobalEntityClassManager().attach( g_MapEntityClasses ); + Radiant_attachHomePathsObserver( g_MapModuleObserver ); +} + +void Map_Destroy(){ + Radiant_detachHomePathsObserver( g_MapModuleObserver ); + GlobalEntityClassManager().detach( g_MapEntityClasses ); +}