2 Copyright (C) 1999-2007 id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
5 This file is part of GtkRadiant.
7 GtkRadiant is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 GtkRadiant is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with GtkRadiant; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24 #if defined (__linux__) || defined (__APPLE__)
27 #include "preferences.h"
28 #include "mainframe.h"
32 extern MainFrame* g_pParentWnd;
34 int modified; // for quit confirmation (0 = clean, 1 = unsaved,
35 // 2 = autosaved, but not regular saved)
37 char currentmap[1024];
39 brush_t active_brushes; // brushes currently being displayed
40 brush_t selected_brushes; // highlighted
42 face_t *selected_face;
43 brush_t *selected_face_brush;
45 brush_t filtered_brushes; // brushes that have been filtered or regioned
47 entity_t entities; // head/tail of doubly linked list
49 entity_t *world_entity = NULL; // "classname" "worldspawn" !
57 bool g_bCancel_Map_LoadFile; // Hydra: moved this here
60 // need that in a variable, will have to tweak depending on the game
61 int g_MaxWorldCoord = 64*1024;
62 int g_MinWorldCoord = -64*1024;
64 // the max size we allow on brushes, this is dependant on world coords too
65 // makes more sense to say smaller I think?
66 int g_MaxBrushSize = (g_MaxWorldCoord-1)*2;
68 void AddRegionBrushes (void);
69 void RemoveRegionBrushes (void);
72 =============================================================
74 Cross map selection saving
76 this could fuck up if you have only part of a complex entity selected...
77 =============================================================
80 brush_t between_brushes;
81 entity_t between_entities;
83 bool g_bRestoreBetween = false;
85 void Map_SaveBetween (void)
87 if (g_pParentWnd->ActiveXY())
89 g_bRestoreBetween = true;
90 g_pParentWnd->ActiveXY()->Copy();
95 void Map_RestoreBetween (void)
97 if (g_pParentWnd->ActiveXY() && g_bRestoreBetween)
98 g_pParentWnd->ActiveXY()->Paste();
101 //============================================================================
103 bool CheckForTinyBrush(brush_t* b, int n, float fSize)
106 for (int i=0 ; i<3 ; i++)
108 if (b->maxs[i] - b->mins[i] < fSize)
112 Sys_Printf("Possible problem brush (too small) #%i ", n);
116 void Map_BuildBrushData(void)
120 if (active_brushes.next == NULL)
123 Sys_BeginWait (); // this could take a while
126 for (b=active_brushes.next ; b != NULL && b != &active_brushes ; b=next)
129 Brush_Build( b, true, false, false );
130 if (!b->brush_faces || (g_PrefsDlg.m_bCleanTiny && CheckForTinyBrush(b, n++, g_PrefsDlg.m_fTinySize)))
133 Sys_Printf ("Removed degenerate brush\n");
139 entity_t *Map_FindClass (char *cname)
143 for (ent = entities.next ; ent != &entities ; ent=ent->next)
145 if (!strcmp(cname, ValueForKey (ent, "classname")))
154 free all map elements, reinitialize the structures that depend on them
159 g_bRestoreBetween = false;
160 if (selected_brushes.next &&
161 (selected_brushes.next != &selected_brushes))
163 if (gtk_MessageBox (g_pParentWnd->m_pWidget, "Copy selection?", " ", MB_YESNO) == IDYES)
167 QERApp_ActiveShaders_SetInUse( false );
169 g_qeglobals.d_num_entities = 0;
171 if (!active_brushes.next)
174 active_brushes.prev = active_brushes.next = &active_brushes;
175 selected_brushes.prev = selected_brushes.next = &selected_brushes;
176 filtered_brushes.prev = filtered_brushes.next = &filtered_brushes;
177 entities.prev = entities.next = &entities;
181 // free selected faces array
182 g_ptrSelectedFaces.RemoveAll();
183 g_ptrSelectedFaceBrushes.RemoveAll();
184 while (active_brushes.next != &active_brushes)
185 Brush_Free (active_brushes.next);
186 while (selected_brushes.next != &selected_brushes)
187 Brush_Free (selected_brushes.next);
188 while (filtered_brushes.next != &filtered_brushes)
189 Brush_Free (filtered_brushes.next);
190 while (entities.next != &entities)
191 Entity_Free (entities.next);
195 Entity_Free(world_entity);
199 entity_t *AngledEntity()
201 entity_t *ent = Map_FindClass ("info_player_start");
204 ent = Map_FindClass ("info_player_deathmatch");
208 ent = Map_FindClass ("info_player_deathmatch");
212 ent = Map_FindClass ("team_CTF_redplayer");
216 ent = Map_FindClass ("team_CTF_blueplayer");
220 ent = Map_FindClass ("team_CTF_redspawn");
224 ent = Map_FindClass ("team_CTF_bluespawn");
230 // move the view to a start position
232 void Map_StartPosition()
234 entity_t *ent = AngledEntity();
236 g_pParentWnd->GetCamWnd()->Camera()->angles[PITCH] = 0;
239 GetVectorForKey (ent, "origin", g_pParentWnd->GetCamWnd()->Camera()->origin);
240 GetVectorForKey (ent, "origin", g_pParentWnd->GetXYWnd()->GetOrigin());
241 g_pParentWnd->GetCamWnd()->Camera()->angles[YAW] = FloatForKey (ent, "angle");
245 g_pParentWnd->GetCamWnd()->Camera()->angles[YAW] = 0;
246 VectorCopy (vec3_origin, g_pParentWnd->GetCamWnd()->Camera()->origin);
247 VectorCopy (vec3_origin, g_pParentWnd->GetXYWnd()->GetOrigin());
251 void Map_FreeEntities(CPtrArray *ents)
253 int i, j, num_ents, num_brushes;
257 num_ents = ents->GetSize();
258 for(i=0; i<num_ents; i++)
260 e = (entity_t*)ents->GetAt(i);
261 brushes = (CPtrArray*)e->pData;
262 num_brushes = brushes->GetSize();
263 for(j=0; j<num_brushes; j++)
264 Brush_Free((brush_t*)brushes->GetAt(j));
265 brushes->RemoveAll();
266 delete (CPtrArray*)e->pData;
273 /*!\todo Possibly make the import Undo-friendly by calling Undo_End for new brushes and ents */
274 void Map_ImportEntities(CPtrArray *ents, bool bAddSelected = false)
276 int num_ents, num_brushes;
284 GPtrArray *new_ents = g_ptr_array_new();
286 g_qeglobals.bPrimitBrushes = false;
288 brush_t *pBrushList = (bAddSelected) ? &selected_brushes : &active_brushes;
290 bool bDoneBPCheck = false;
291 g_qeglobals.bNeedConvert = false;
292 // HACK: find out if this map file was a BP one
293 // check the first brush in the file that is NOT a patch
294 // this will not be necessary when we allow both formats in the same file
295 num_ents = ents->GetSize();
296 for(i=0; !bDoneBPCheck && i<num_ents; i++)
298 e = (entity_t*)ents->GetAt(i);
299 brushes = (CPtrArray*)e->pData;
300 num_brushes = brushes->GetSize();
301 for(j=0; !bDoneBPCheck && j<num_brushes; j++)
303 /*!todo Allow mixing texdef formats per-face. */
304 b = (brush_t *)brushes->GetAt(j);
305 if(b->patchBrush) continue;
308 if(b->bBrushDef && !g_qeglobals.m_bBrushPrimitMode)
310 else if(!b->bBrushDef && g_qeglobals.m_bBrushPrimitMode)
315 switch(BP_MessageBox(BP_param))
318 Map_FreeEntities(ents);
321 g_qeglobals.bNeedConvert = true;
324 g_qeglobals.bNeedConvert = false;
331 // process the entities into the world geometry
332 num_ents = ents->GetSize();
333 for(i=0; i<num_ents; i++)
336 e = (entity_t*)ents->GetAt(i);
337 brushes = (CPtrArray*)e->pData;
339 num_brushes = brushes->GetSize();
340 // link brushes into entity
341 for(j=0; j<num_brushes; j++)
343 Entity_LinkBrush(e, (brush_t *)brushes->GetAt(j));
344 g_qeglobals.d_parsed_brushes++;
346 brushes->RemoveAll();
351 GetVectorForKey (e, "origin", e->origin);
353 /*!\todo Make SetKeyValue check for "classname" change and assign appropriate eclass */
354 e->eclass = Eclass_ForName (ValueForKey (e, "classname"),
355 (e->brushes.onext != &e->brushes));
357 // go through all parsed brushes and build stuff
358 for(b = e->brushes.onext; b!=&e->brushes; b=b->onext)
360 for(f = b->brush_faces; f != NULL; f = f->next)
362 f->pShader = QERApp_Shader_ForName(f->texdef.GetName());
363 f->d_texture = f->pShader->getTexture();
366 // when brushes are in final state, build the planes and windings
367 // NOTE: also converts BP brushes if g_qeglobals.bNeedConvert is true
371 //#define TERRAIN_HACK
375 if ((strcmp(ValueForKey(e, "terrain"),"1") == 0 && strcmp(e->eclass->name,"func_group") == 0))
378 // two aux pointers to the shaders used in the terrain entity
379 // we don't keep refcount on them since they are only temporary
380 // this avoids doing expensive lookups by name for all faces
381 IShader *pTerrainShader, *pCaulk;
383 pTerrainShader = NULL;
384 pCaulk = QERApp_Shader_ForName(SHADER_CAULK);
386 for(b = e->brushes.onext; b!=&e->brushes; b=b->onext)
388 if (pTerrainShader == NULL)
389 for(f = b->brush_faces; f != NULL; f = f->next)
390 if (strcmp(f->texdef.GetName(), SHADER_CAULK)!=0)
391 pTerrainShader = f->pShader;
395 for(f = b->brush_faces; f != NULL; f = f->next)
397 if (strcmp(f->texdef.GetName(), SHADER_CAULK)!=0) // not caulk
398 Face_SetShader(f, pTerrainShader->getName());
400 Face_SetShader(f, pCaulk->getName());
404 Sys_Printf("WARNING: no terrain shader found for brush\n");
411 for(b = e->brushes.onext; b!=&e->brushes; b=b->onext)
413 // patch hack, to be removed when dependency on brush_faces is removed
416 Patch_CalcBounds(b->pPatch, mins, maxs);
417 for (int i=0; i<3; i++)
419 if ((int)mins[i] == (int)maxs[i])
425 Brush_Resize(b, mins, maxs);
430 // add brush for fixedsize entity
431 if (e->eclass->fixedsize)
434 VectorAdd (e->eclass->mins, e->origin, mins);
435 VectorAdd (e->eclass->maxs, e->origin, maxs);
436 b = Brush_Create (mins, maxs, &e->eclass->texdef);
437 Entity_LinkBrush(e, b);
441 for(b = e->brushes.onext; b!=&e->brushes; b=b->onext)
442 Brush_AddToList(b, pBrushList);
444 if (strcmp(e->eclass->name, "worldspawn") == 0)
448 while(e->brushes.onext != &e->brushes)
450 b = e->brushes.onext;
451 Entity_UnlinkBrush(b);
452 Entity_LinkBrush(world_entity, b);
461 else if (strcmp(e->eclass->name, "group_info") == 0)
463 // it's a group thing!
469 // fix target/targetname collisions
470 if ((g_PrefsDlg.m_bDoTargetFix) && (strcmp(ValueForKey(e, "target"), "") != 0))
472 GPtrArray *t_ents = g_ptr_array_new();
474 const char *target = ValueForKey(e, "target");
475 qboolean bCollision=FALSE;
477 // check the current map entities for an actual collision
478 for (e_target = entities.next; e_target != &entities; e_target = e_target->next)
480 if(!strcmp(target, ValueForKey(e_target, "target")))
483 // make sure the collision is not between two imported entities
484 for(j=0; j<(int)new_ents->len; j++)
486 if(e_target == g_ptr_array_index(new_ents, j))
492 // find the matching targeted entity(s)
495 for(j=num_ents-1; j>0; j--)
497 e_target = (entity_t*)ents->GetAt(j);
498 if(e_target != NULL && e_target != e)
500 const char *targetname = ValueForKey(e_target, "targetname");
501 if( (targetname != NULL) && (strcmp(target, targetname) == 0) )
502 g_ptr_array_add(t_ents, (gpointer)e_target);
507 // link the first to get a unique target/targetname
508 Entity_Connect(e, (entity_t*)g_ptr_array_index(t_ents,0));
509 // set the targetname of the rest of them manually
510 for(j = 1; j < (int)t_ents->len; j++)
511 SetKeyValue( (entity_t*)g_ptr_array_index(t_ents, j), "targetname", ValueForKey(e, "target") );
513 g_ptr_array_free(t_ents, FALSE);
517 // add the entity to the end of the entity list
518 Entity_AddToList(e, &entities);
519 g_qeglobals.d_num_entities++;
521 // keep a list of ents added to avoid testing collisions against them
522 g_ptr_array_add(new_ents, (gpointer)e);
525 g_ptr_array_free(new_ents, FALSE);
529 g_qeglobals.bNeedConvert = false;
532 void Map_Import(IDataStream *in, const char *type, bool bAddSelected)
536 g_pParentWnd->GetSynapseClient().ImportMap(in, &ents, type);
537 Map_ImportEntities(&ents, bAddSelected);
545 void Map_LoadFile (const char *filename)
547 clock_t start, finish;
554 \todo FIXME TTimo why is this commented out?
555 stability issues maybe? or duplicate feature?
556 forcing to show the console during map load was a good thing IMO
558 //SetInspectorMode(W_CONSOLE);
559 Sys_Printf ("Loading map from %s\n", filename );
562 //++timo FIXME: maybe even easier to have Group_Init called from Map_Free?
564 g_qeglobals.d_num_entities = 0;
565 g_qeglobals.d_parsed_brushes = 0;
568 // cancel the map loading process
569 // used when conversion between standard map format and BP format is required and the user cancels the process
570 g_bCancel_Map_LoadFile = false;
572 strcpy (currentmap, filename);
574 g_bScreenUpdates = false; // leo: avoid redraws while loading the map (see fenris:1952)
576 // prepare to let the map module do the parsing
578 const char* type = strrchr(filename,'.');
579 if(type!=NULL) type++;
580 // NOTE TTimo opening has binary doesn't make a lot of sense
581 // but opening as text confuses the scriptlib parser
582 // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=261
583 // this may be a problem if we "rb" and use the XML parser, might have an incompatibility
584 if (file.Open(filename, "rb"))
585 Map_Import(&file, type);
587 Sys_FPrintf(SYS_ERR, "ERROR: failed to open %s for read\n", filename);
590 g_bScreenUpdates = true;
592 if (g_bCancel_Map_LoadFile)
594 Sys_Printf("Map_LoadFile canceled\n");
602 Sys_Printf ("No worldspawn in map.\n");
608 elapsed_time = (double)(finish - start) / CLOCKS_PER_SEC;
610 Sys_Printf ("--- LoadMapFile ---\n");
611 Sys_Printf ("%s\n", filename );
613 Sys_Printf ("%5i brushes\n", g_qeglobals.d_parsed_brushes );
614 Sys_Printf ("%5i entities\n", g_qeglobals.d_num_entities);
615 Sys_Printf ("%5.2f second(s) load time\n", elapsed_time );
619 Map_RestoreBetween ();
622 // move the view to a start position
629 Sys_SetTitle (filename);
631 Texture_ShowInuse ();
632 QERApp_SortActiveShaders();
634 Sys_UpdateWindows (W_ALL);
639 Supporting functions for Map_SaveFile, builds a CPtrArray with the filtered / non filtered brushes
642 void CleanFilter(entity_t *ent)
646 delete static_cast<CPtrArray*>(ent->pData);
652 filters out the region brushes if necessary
653 returns true if this entity as a whole is out of the region
654 (if all brushes are filtered out, then the entity will be completely dropped .. except if it's worldspawn of course)
656 bool FilterChildren(entity_t *ent, bool bRegionOnly = false, bool bSelectedOnly = false)
658 if(ent->brushes.onext == &ent->brushes)
660 // entity without a brush, ignore it... this can be caused by Undo
662 // filter fixedsize ents by their eclass bounding box
663 // don't add their brushes
664 if (ent->eclass->fixedsize)
666 if(bSelectedOnly && !IsBrushSelected(ent->brushes.onext))
669 if(bRegionOnly && region_active)
671 for (int i=0 ; i<3 ; i++)
673 if ((ent->origin[i] + ent->eclass->mins[i]) > region_maxs[i])
675 if ((ent->origin[i] + ent->eclass->maxs[i]) < region_mins[i])
682 for (brush_t *b = ent->brushes.onext ; b != &ent->brushes ; b=b->onext)
684 // set flag to use brushprimit_texdef
685 if(g_qeglobals.m_bBrushPrimitMode)
688 b->bBrushDef = false;
690 // add brush, unless it's excluded by region
691 if ( !(bRegionOnly && Map_IsBrushFiltered(b)) &&
692 !(bSelectedOnly && !IsBrushSelected(b)) )
693 ((CPtrArray*)ent->pData)->Add(b);
696 if (((CPtrArray*)ent->pData)->GetSize() <= 0)
702 entity_t *region_startpoint = NULL;
703 void Map_ExportEntities(CPtrArray* ents, bool bRegionOnly = false, bool bSelectedOnly = false)
709 \todo the entity_t needs to be reworked and asbtracted some more
711 keeping the entity_t as the struct providing access to a list of map objects, a list of epairs and various other info?
712 but separating some more the data that belongs to the entity_t and the 'sons' data
713 on a side note, I don't think that doing that with linked list would be a good thing
715 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
716 the next step is very likely to be a change of the brush_t* to a more abstract object?
719 FilterChildren(world_entity, bRegionOnly, bSelectedOnly);
720 ents->Add(world_entity);
722 for (e=entities.next ; e!=&entities ; e=e->next)
724 // not sure this still happens, probably safe to leave it in
725 if ((!strcmp(ValueForKey (e, "classname"), "worldspawn")) && (e!=world_entity))
727 Sys_FPrintf(SYS_ERR, "Dropping parasite worldspawn entity\n");
731 // entities which brushes are completely filtered out by regioning are not printed to the map
732 if (FilterChildren(e, bRegionOnly, bSelectedOnly))
736 if (bRegionOnly && region_active)
739 ((CPtrArray*)world_entity->pData)->Add(region_sides[i]);
741 ents->Add(region_startpoint);
745 void Map_Export(IDataStream *out, const char *type, bool bRegionOnly, bool bSelectedOnly)
751 if (bRegionOnly && region_active)
754 // create the filters
755 world_entity->pData = new CPtrArray();
756 for(e = entities.next; e != &entities; e = e->next)
757 e->pData = new CPtrArray();
759 Map_ExportEntities(&ents, bRegionOnly, bSelectedOnly);
761 g_pParentWnd->GetSynapseClient().ExportMap(&ents, out, type);
763 // cleanup the filters
764 CleanFilter(world_entity);
765 for (e=entities.next ; e!=&entities ; e=e->next)
768 if (bRegionOnly && region_active)
769 RemoveRegionBrushes();
772 const char* filename_get_extension(const char* filename)
774 const char* type = strrchr(filename,'.');
783 \todo FIXME remove the use_region, this is broken .. work with a global flag to set region mode or not
786 void Map_SaveFile (const char *filename, qboolean use_region )
788 clock_t start, finish;
791 Sys_Printf("Saving map to %s\n",filename);
799 // rename current to .bak
800 strcpy (backup, filename);
801 StripExtension (backup);
802 strcat (backup, ".bak");
804 rename (filename, backup);
807 Sys_Printf ("Map_SaveFile: %s\n", filename);
809 // build the out data stream
811 if (!file.Open(filename,"w"))
813 Sys_FPrintf(SYS_ERR, "ERROR: couldn't open %s for write\n", filename);
818 Map_Export(&file, filename_get_extension(filename), use_region);
823 elapsed_time = (double)(finish - start) / CLOCKS_PER_SEC;
825 Sys_Printf ("Saved in %-.2f second(s).\n",elapsed_time);
828 if ( !strstr( filename, "autosave" ) )
829 Sys_SetTitle (filename);
839 Sys_Status ("Saved.", 0);
851 Sys_Printf ("Map_New\n");
854 strcpy (currentmap, "unnamed.map");
855 Sys_SetTitle (currentmap);
857 world_entity = (entity_s*)qmalloc(sizeof(*world_entity));
858 world_entity->brushes.onext =
859 world_entity->brushes.oprev = &world_entity->brushes;
860 SetKeyValue (world_entity, "classname", "worldspawn");
861 world_entity->eclass = Eclass_ForName ("worldspawn", true);
863 g_pParentWnd->GetCamWnd()->Camera()->angles[YAW] = 0;
864 g_pParentWnd->GetCamWnd()->Camera()->angles[PITCH] = 0;
865 VectorCopy (vec3_origin, g_pParentWnd->GetCamWnd()->Camera()->origin);
866 g_pParentWnd->GetCamWnd()->Camera()->origin[2] = 48;
867 VectorCopy (vec3_origin, g_pParentWnd->GetXYWnd()->GetOrigin());
869 Map_RestoreBetween ();
873 Sys_UpdateWindows (W_ALL);
878 ===========================================================
882 ===========================================================
884 qboolean region_active;
885 vec3_t region_mins = {g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord};
886 vec3_t region_maxs = {g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord};
888 brush_t *region_sides[6];
893 a regioned map will have temp walls put up at the region boundary
894 \todo TODO TTimo old implementation of region brushes
895 we still add them straight in the worldspawn and take them out after the map is saved
896 with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
899 void AddRegionBrushes (void)
908 Sys_FPrintf( SYS_WRN, "Unexpected AddRegionBrushes call.\n");
913 memset (&td, 0, sizeof(td));
914 td.SetName(SHADER_NOT_FOUND);
917 VectorSet(mins, region_mins[0]-32, region_mins[1]-32, region_mins[2]-32);
922 VectorSet(maxs, region_maxs[0]+32, region_maxs[1]+32, region_maxs[2]+32);
923 maxs[i] = region_mins[i];
924 region_sides[i] = Brush_Create (mins, maxs, &td);
928 VectorSet(maxs, region_maxs[0]+32, region_maxs[1]+32, region_maxs[2]+32);
933 VectorSet(mins, region_mins[0]-32, region_mins[1]-32, region_mins[2]-32);
934 mins[i] = region_maxs[i];
935 region_sides[i+3] = Brush_Create (mins, maxs, &td);
939 // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=503
940 // this is a safe check, but it should not really happen anymore
943 (int)g_pParentWnd->GetCamWnd()->Camera()->origin[0],
944 (int)g_pParentWnd->GetCamWnd()->Camera()->origin[1],
945 (int)g_pParentWnd->GetCamWnd()->Camera()->origin[2]);
947 for (i=0 ; i<3 ; i++)
949 if (vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i])
951 Sys_FPrintf(SYS_ERR, "Camera is NOT in the region, it's likely that the region won't compile correctly\n");
955 // write the info_playerstart
956 region_startpoint = Entity_Alloc();
957 SetKeyValue(region_startpoint, "classname", "info_player_start");
958 region_startpoint->eclass = Eclass_ForName ("info_player_start", false);
960 sprintf(sTmp, "%d %d %d", (int)vOrig[0], (int)vOrig[1], (int)vOrig[2]);
961 SetKeyValue(region_startpoint, "origin", sTmp);
962 sprintf(sTmp, "%d", (int)g_pParentWnd->GetCamWnd()->Camera()->angles[YAW]);
963 SetKeyValue(region_startpoint, "angle", sTmp);
964 // empty array of children
965 region_startpoint->pData = new CPtrArray;
968 void RemoveRegionBrushes (void)
974 for (i=0 ; i<6 ; i++)
975 Brush_Free (region_sides[i]);
977 CleanFilter(region_startpoint);
978 Entity_Free(region_startpoint);
981 qboolean Map_IsBrushFiltered (brush_t *b)
985 for (i=0 ; i<3 ; i++)
987 if (b->mins[i] > region_maxs[i])
989 if (b->maxs[i] < region_mins[i])
999 Other filtering options may still be on
1002 void Map_RegionOff (void)
1007 region_active = false;
1008 for (i=0 ; i<3 ; i++)
1010 region_maxs[i] = g_MaxWorldCoord-64;
1011 region_mins[i] = g_MinWorldCoord+64;
1014 for (b=filtered_brushes.next ; b != &filtered_brushes ; b=next)
1017 if (Map_IsBrushFiltered (b))
1018 continue; // still filtered
1019 Brush_RemoveFromList (b);
1020 if (active_brushes.next == NULL || active_brushes.prev == NULL)
1022 active_brushes.next = &active_brushes;
1023 active_brushes.prev = &active_brushes;
1025 Brush_AddToList (b, &active_brushes);
1026 b->bFiltered = FilterBrush(b);
1028 Sys_UpdateWindows (W_ALL);
1031 void Map_ApplyRegion (void)
1035 region_active = true;
1036 for (b=active_brushes.next ; b != &active_brushes ; b=next)
1039 if (!Map_IsBrushFiltered (b))
1040 continue; // still filtered
1041 Brush_RemoveFromList (b);
1042 Brush_AddToList (b, &filtered_brushes);
1045 Sys_UpdateWindows (W_ALL);
1050 ========================
1051 Map_RegionSelectedBrushes
1052 ========================
1054 void Map_RegionSelectedBrushes (void)
1058 if (selected_brushes.next == &selected_brushes) // nothing selected
1060 Sys_Printf("Tried to region with no selection...\n");
1063 region_active = true;
1064 Select_GetBounds (region_mins, region_maxs);
1067 if (filtered_brushes.next != &filtered_brushes)
1068 Sys_Printf("WARNING: filtered_brushes list may not be empty in Map_RegionSelectedBrushes\n");
1071 if (active_brushes.next == &active_brushes)
1073 // just have an empty filtered_brushes list
1074 // this happens if you set region after selecting all the brushes in your map (some weird people do that, ask MrE!)
1075 filtered_brushes.next = filtered_brushes.prev = &filtered_brushes;
1079 // move the entire active_brushes list to filtered_brushes
1080 filtered_brushes.next = active_brushes.next;
1081 filtered_brushes.prev = active_brushes.prev;
1082 filtered_brushes.next->prev = &filtered_brushes;
1083 filtered_brushes.prev->next = &filtered_brushes;
1086 // move the entire selected_brushes list to active_brushes
1087 active_brushes.next = selected_brushes.next;
1088 active_brushes.prev = selected_brushes.prev;
1089 active_brushes.next->prev = &active_brushes;
1090 active_brushes.prev->next = &active_brushes;
1093 for (brush_t *b = active_brushes.next; b != &active_brushes; b = b->next)
1095 b->pPatch->bSelected = false;
1097 // clear selected_brushes
1098 selected_brushes.next = selected_brushes.prev = &selected_brushes;
1100 Sys_UpdateWindows (W_ALL);
1109 void Map_RegionXY (void)
1113 region_mins[0] = g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5 * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale();
1114 region_maxs[0] = g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5 * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale();
1115 region_mins[1] = g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5 * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale();
1116 region_maxs[1] = g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5 * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale();
1117 region_mins[2] = g_MinWorldCoord+64;
1118 region_maxs[2] = g_MaxWorldCoord-64;
1127 void Map_RegionTallBrush (void)
1131 if (!QE_SingleBrush ())
1134 b = selected_brushes.next;
1138 VectorCopy (b->mins, region_mins);
1139 VectorCopy (b->maxs, region_maxs);
1140 region_mins[2] = g_MinWorldCoord+64;
1141 region_maxs[2] = g_MaxWorldCoord-64;
1143 Undo_Start("delete");
1144 Undo_AddBrushList(&selected_brushes);
1145 Undo_AddEntity(b->owner);
1147 Undo_EndBrushList(&selected_brushes);
1158 void Map_RegionBrush (void)
1162 if (!QE_SingleBrush ())
1165 b = selected_brushes.next;
1169 VectorCopy (b->mins, region_mins);
1170 VectorCopy (b->maxs, region_maxs);
1172 Undo_Start("delete");
1173 Undo_AddBrushList(&selected_brushes);
1174 Undo_AddEntity(b->owner);
1176 Undo_EndBrushList(&selected_brushes);
1182 GList *find_string(GList *glist, const char *buf)
1186 if (strcmp((char *)glist->data, buf) == 0)
1187 break; // this name is in our list already
1188 glist = glist->next;
1193 void Map_ImportBuffer(char *buf)
1197 Undo_Start("import buffer");
1201 stream.Write(buf, strlen(buf));
1202 Map_Import(&stream, "xmap");
1205 Sys_UpdateWindows (W_ALL);
1206 Sys_MarkMapModified();
1217 void Map_ImportFile (const char *filename)
1222 Sys_Printf("Importing map from %s\n",filename);
1224 const char* type = strrchr(filename,'.');
1225 if(type!=NULL) type++;
1226 /*!\todo Resolve "r" problem in scriptlib" */
1227 if(file.Open(filename, "rb"))
1228 Map_Import(&file, type, true);
1230 Sys_FPrintf(SYS_ERR, "ERROR: couldn't open %s for read\n", filename);
1234 Sys_UpdateWindows (W_ALL);
1244 // Saves selected world brushes and whole entities with partial/full selections
1246 void Map_SaveSelected(const char* filename)
1250 Sys_Printf("Saving selection to %s\n",filename);
1252 const char* type = strrchr(filename,'.');
1253 if(type!=NULL) type++;
1254 if(file.Open(filename, "w"))
1255 Map_Export (&file, type, false, true);
1257 Sys_FPrintf(SYS_ERR, "ERROR: failed to open %s for write\n", filename);
1268 // Saves selected world brushes and whole entities with partial/full selections
1270 void Map_SaveSelected (MemStream* pMemFile, MemStream* pPatchFile)
1272 Map_Export (pMemFile, "xmap", false, true);
1275 // write world entity first
1276 Entity_WriteSelected(world_entity, pMemFile);
1278 // then write all other ents
1280 for (e=entities.next ; e != &entities ; e=next)
1282 MemFile_fprintf(pMemFile, "// entity %i\n", count);
1284 Entity_WriteSelected(e, pMemFile);
1289 // Patch_WriteFile(pPatchFile);
1294 void MemFile_fprintf(MemStream* pMemFile, const char* pText, ...)
1298 va_start (args,pText);
1299 vsprintf(Buffer, pText, args);
1300 pMemFile->Write(Buffer, strlen(Buffer));
1306 push the region spawn point
1307 \todo FIXME TTimo this was in the #1 MAP module implementation (in the core)
1308 not sure it has any use anymore, should prolly drop it
1311 void Region_SpawnPoint(FILE *f)
1313 // write the info_player_start, we use the camera position
1315 fprintf (f, "\"classname\" \"info_player_start\"\n");
1316 fprintf (f, "\"origin\" \"%i %i %i\"\n",
1317 (int)g_pParentWnd->GetCamWnd()->Camera()->origin[0],
1318 (int)g_pParentWnd->GetCamWnd()->Camera()->origin[1],
1319 (int)g_pParentWnd->GetCamWnd()->Camera()->origin[2]);
1320 fprintf (f, "\"angle\" \"%i\"\n", (int)g_pParentWnd->GetCamWnd()->Camera()->angles[YAW]);