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 (const 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 // this may be a problem if we "rb" and use the XML parser, might have an incompatibility
583 if (file.Open(filename, "rb"))
584 Map_Import(&file, type);
586 Sys_FPrintf(SYS_ERR, "ERROR: failed to open %s for read\n", filename);
589 g_bScreenUpdates = true;
591 if (g_bCancel_Map_LoadFile)
593 Sys_Printf("Map_LoadFile canceled\n");
601 Sys_Printf ("No worldspawn in map.\n");
607 elapsed_time = (double)(finish - start) / CLOCKS_PER_SEC;
609 Sys_Printf ("--- LoadMapFile ---\n");
610 Sys_Printf ("%s\n", filename );
612 Sys_Printf ("%5i brushes\n", g_qeglobals.d_parsed_brushes );
613 Sys_Printf ("%5i entities\n", g_qeglobals.d_num_entities);
614 Sys_Printf ("%5.2f second(s) load time\n", elapsed_time );
618 Map_RestoreBetween ();
621 // move the view to a start position
628 Sys_SetTitle (filename);
630 Texture_ShowInuse ();
631 QERApp_SortActiveShaders();
633 Sys_UpdateWindows (W_ALL);
638 Supporting functions for Map_SaveFile, builds a CPtrArray with the filtered / non filtered brushes
641 void CleanFilter(entity_t *ent)
645 delete static_cast<CPtrArray*>(ent->pData);
651 filters out the region brushes if necessary
652 returns true if this entity as a whole is out of the region
653 (if all brushes are filtered out, then the entity will be completely dropped .. except if it's worldspawn of course)
655 bool FilterChildren(entity_t *ent, bool bRegionOnly = false, bool bSelectedOnly = false)
657 if(ent->brushes.onext == &ent->brushes)
659 // entity without a brush, ignore it... this can be caused by Undo
661 // filter fixedsize ents by their eclass bounding box
662 // don't add their brushes
663 if (ent->eclass->fixedsize)
665 if(bSelectedOnly && !IsBrushSelected(ent->brushes.onext))
668 if(bRegionOnly && region_active)
670 for (int i=0 ; i<3 ; i++)
672 if ((ent->origin[i] + ent->eclass->mins[i]) > region_maxs[i])
674 if ((ent->origin[i] + ent->eclass->maxs[i]) < region_mins[i])
681 for (brush_t *b = ent->brushes.onext ; b != &ent->brushes ; b=b->onext)
683 // set flag to use brushprimit_texdef
684 if(g_qeglobals.m_bBrushPrimitMode)
687 b->bBrushDef = false;
689 // add brush, unless it's excluded by region
690 if ( !(bRegionOnly && Map_IsBrushFiltered(b)) &&
691 !(bSelectedOnly && !IsBrushSelected(b)) )
692 ((CPtrArray*)ent->pData)->Add(b);
695 if (((CPtrArray*)ent->pData)->GetSize() <= 0)
701 entity_t *region_startpoint = NULL;
702 void Map_ExportEntities(CPtrArray* ents, bool bRegionOnly = false, bool bSelectedOnly = false)
708 \todo the entity_t needs to be reworked and asbtracted some more
710 keeping the entity_t as the struct providing access to a list of map objects, a list of epairs and various other info?
711 but separating some more the data that belongs to the entity_t and the 'sons' data
712 on a side note, I don't think that doing that with linked list would be a good thing
714 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
715 the next step is very likely to be a change of the brush_t* to a more abstract object?
718 FilterChildren(world_entity, bRegionOnly, bSelectedOnly);
719 ents->Add(world_entity);
721 for (e=entities.next ; e!=&entities ; e=e->next)
723 // not sure this still happens, probably safe to leave it in
724 if ((!strcmp(ValueForKey (e, "classname"), "worldspawn")) && (e!=world_entity))
726 Sys_FPrintf(SYS_ERR, "Dropping parasite worldspawn entity\n");
730 // entities which brushes are completely filtered out by regioning are not printed to the map
731 if (FilterChildren(e, bRegionOnly, bSelectedOnly))
735 if (bRegionOnly && region_active)
738 ((CPtrArray*)world_entity->pData)->Add(region_sides[i]);
740 ents->Add(region_startpoint);
744 void Map_Export(IDataStream *out, const char *type, bool bRegionOnly, bool bSelectedOnly)
750 if (bRegionOnly && region_active)
753 // create the filters
754 world_entity->pData = new CPtrArray();
755 for(e = entities.next; e != &entities; e = e->next)
756 e->pData = new CPtrArray();
758 Map_ExportEntities(&ents, bRegionOnly, bSelectedOnly);
760 g_pParentWnd->GetSynapseClient().ExportMap(&ents, out, type);
762 // cleanup the filters
763 CleanFilter(world_entity);
764 for (e=entities.next ; e!=&entities ; e=e->next)
767 if (bRegionOnly && region_active)
768 RemoveRegionBrushes();
771 const char* filename_get_extension(const char* filename)
773 const char* type = strrchr(filename,'.');
782 \todo FIXME remove the use_region, this is broken .. work with a global flag to set region mode or not
785 void Map_SaveFile (const char *filename, qboolean use_region )
787 clock_t start, finish;
790 Sys_Printf("Saving map to %s\n",filename);
798 // rename current to .bak
799 strcpy (backup, filename);
800 StripExtension (backup);
801 strcat (backup, ".bak");
803 rename (filename, backup);
806 Sys_Printf ("Map_SaveFile: %s\n", filename);
808 // build the out data stream
810 if (!file.Open(filename,"w"))
812 Sys_FPrintf(SYS_ERR, "ERROR: couldn't open %s for write\n", filename);
817 Map_Export(&file, filename_get_extension(filename), use_region);
822 elapsed_time = (double)(finish - start) / CLOCKS_PER_SEC;
824 Sys_Printf ("Saved in %-.2f second(s).\n",elapsed_time);
827 if ( !strstr( filename, "autosave" ) )
828 Sys_SetTitle (filename);
838 Sys_Status ("Saved.", 0);
850 Sys_Printf ("Map_New\n");
853 strcpy (currentmap, "unnamed.map");
854 Sys_SetTitle (currentmap);
856 world_entity = (entity_s*)qmalloc(sizeof(*world_entity));
857 world_entity->brushes.onext =
858 world_entity->brushes.oprev = &world_entity->brushes;
859 SetKeyValue (world_entity, "classname", "worldspawn");
860 world_entity->eclass = Eclass_ForName ("worldspawn", true);
862 g_pParentWnd->GetCamWnd()->Camera()->angles[YAW] = 0;
863 g_pParentWnd->GetCamWnd()->Camera()->angles[PITCH] = 0;
864 VectorCopy (vec3_origin, g_pParentWnd->GetCamWnd()->Camera()->origin);
865 g_pParentWnd->GetCamWnd()->Camera()->origin[2] = 48;
866 VectorCopy (vec3_origin, g_pParentWnd->GetXYWnd()->GetOrigin());
868 Map_RestoreBetween ();
872 Sys_UpdateWindows (W_ALL);
877 ===========================================================
881 ===========================================================
883 qboolean region_active;
884 vec3_t region_mins = {g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord};
885 vec3_t region_maxs = {g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord};
887 brush_t *region_sides[6];
892 a regioned map will have temp walls put up at the region boundary
893 \todo TODO TTimo old implementation of region brushes
894 we still add them straight in the worldspawn and take them out after the map is saved
895 with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
898 void AddRegionBrushes (void)
907 Sys_FPrintf( SYS_WRN, "Unexpected AddRegionBrushes call.\n");
912 memset (&td, 0, sizeof(td));
913 td.SetName(SHADER_NOT_FOUND);
916 VectorSet(mins, region_mins[0]-32, region_mins[1]-32, region_mins[2]-32);
921 VectorSet(maxs, region_maxs[0]+32, region_maxs[1]+32, region_maxs[2]+32);
922 maxs[i] = region_mins[i];
923 region_sides[i] = Brush_Create (mins, maxs, &td);
927 VectorSet(maxs, region_maxs[0]+32, region_maxs[1]+32, region_maxs[2]+32);
932 VectorSet(mins, region_mins[0]-32, region_mins[1]-32, region_mins[2]-32);
933 mins[i] = region_maxs[i];
934 region_sides[i+3] = Brush_Create (mins, maxs, &td);
938 // this is a safe check, but it should not really happen anymore
941 (int)g_pParentWnd->GetCamWnd()->Camera()->origin[0],
942 (int)g_pParentWnd->GetCamWnd()->Camera()->origin[1],
943 (int)g_pParentWnd->GetCamWnd()->Camera()->origin[2]);
945 for (i=0 ; i<3 ; i++)
947 if (vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i])
949 Sys_FPrintf(SYS_ERR, "Camera is NOT in the region, it's likely that the region won't compile correctly\n");
953 // write the info_playerstart
954 region_startpoint = Entity_Alloc();
955 SetKeyValue(region_startpoint, "classname", "info_player_start");
956 region_startpoint->eclass = Eclass_ForName ("info_player_start", false);
958 sprintf(sTmp, "%d %d %d", (int)vOrig[0], (int)vOrig[1], (int)vOrig[2]);
959 SetKeyValue(region_startpoint, "origin", sTmp);
960 sprintf(sTmp, "%d", (int)g_pParentWnd->GetCamWnd()->Camera()->angles[YAW]);
961 SetKeyValue(region_startpoint, "angle", sTmp);
962 // empty array of children
963 region_startpoint->pData = new CPtrArray;
966 void RemoveRegionBrushes (void)
972 for (i=0 ; i<6 ; i++)
973 Brush_Free (region_sides[i]);
975 CleanFilter(region_startpoint);
976 Entity_Free(region_startpoint);
979 qboolean Map_IsBrushFiltered (brush_t *b)
983 for (i=0 ; i<3 ; i++)
985 if (b->mins[i] > region_maxs[i])
987 if (b->maxs[i] < region_mins[i])
997 Other filtering options may still be on
1000 void Map_RegionOff (void)
1005 region_active = false;
1006 for (i=0 ; i<3 ; i++)
1008 region_maxs[i] = g_MaxWorldCoord-64;
1009 region_mins[i] = g_MinWorldCoord+64;
1012 for (b=filtered_brushes.next ; b != &filtered_brushes ; b=next)
1015 if (Map_IsBrushFiltered (b))
1016 continue; // still filtered
1017 Brush_RemoveFromList (b);
1018 if (active_brushes.next == NULL || active_brushes.prev == NULL)
1020 active_brushes.next = &active_brushes;
1021 active_brushes.prev = &active_brushes;
1023 Brush_AddToList (b, &active_brushes);
1024 b->bFiltered = FilterBrush(b);
1026 Sys_UpdateWindows (W_ALL);
1029 void Map_ApplyRegion (void)
1033 region_active = true;
1034 for (b=active_brushes.next ; b != &active_brushes ; b=next)
1037 if (!Map_IsBrushFiltered (b))
1038 continue; // still filtered
1039 Brush_RemoveFromList (b);
1040 Brush_AddToList (b, &filtered_brushes);
1043 Sys_UpdateWindows (W_ALL);
1048 ========================
1049 Map_RegionSelectedBrushes
1050 ========================
1052 void Map_RegionSelectedBrushes (void)
1056 if (selected_brushes.next == &selected_brushes) // nothing selected
1058 Sys_Printf("Tried to region with no selection...\n");
1061 region_active = true;
1062 Select_GetBounds (region_mins, region_maxs);
1065 if (filtered_brushes.next != &filtered_brushes)
1066 Sys_Printf("WARNING: filtered_brushes list may not be empty in Map_RegionSelectedBrushes\n");
1069 if (active_brushes.next == &active_brushes)
1071 // just have an empty filtered_brushes list
1072 // this happens if you set region after selecting all the brushes in your map (some weird people do that, ask MrE!)
1073 filtered_brushes.next = filtered_brushes.prev = &filtered_brushes;
1077 // move the entire active_brushes list to filtered_brushes
1078 filtered_brushes.next = active_brushes.next;
1079 filtered_brushes.prev = active_brushes.prev;
1080 filtered_brushes.next->prev = &filtered_brushes;
1081 filtered_brushes.prev->next = &filtered_brushes;
1084 // move the entire selected_brushes list to active_brushes
1085 active_brushes.next = selected_brushes.next;
1086 active_brushes.prev = selected_brushes.prev;
1087 active_brushes.next->prev = &active_brushes;
1088 active_brushes.prev->next = &active_brushes;
1091 for (brush_t *b = active_brushes.next; b != &active_brushes; b = b->next)
1093 b->pPatch->bSelected = false;
1095 // clear selected_brushes
1096 selected_brushes.next = selected_brushes.prev = &selected_brushes;
1098 Sys_UpdateWindows (W_ALL);
1107 void Map_RegionXY (void)
1111 region_mins[0] = g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5 * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale();
1112 region_maxs[0] = g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5 * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale();
1113 region_mins[1] = g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5 * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale();
1114 region_maxs[1] = g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5 * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale();
1115 region_mins[2] = g_MinWorldCoord+64;
1116 region_maxs[2] = g_MaxWorldCoord-64;
1125 void Map_RegionTallBrush (void)
1129 if (!QE_SingleBrush ())
1132 b = selected_brushes.next;
1136 VectorCopy (b->mins, region_mins);
1137 VectorCopy (b->maxs, region_maxs);
1138 region_mins[2] = g_MinWorldCoord+64;
1139 region_maxs[2] = g_MaxWorldCoord-64;
1141 Undo_Start("delete");
1142 Undo_AddBrushList(&selected_brushes);
1143 Undo_AddEntity(b->owner);
1145 Undo_EndBrushList(&selected_brushes);
1156 void Map_RegionBrush (void)
1160 if (!QE_SingleBrush ())
1163 b = selected_brushes.next;
1167 VectorCopy (b->mins, region_mins);
1168 VectorCopy (b->maxs, region_maxs);
1170 Undo_Start("delete");
1171 Undo_AddBrushList(&selected_brushes);
1172 Undo_AddEntity(b->owner);
1174 Undo_EndBrushList(&selected_brushes);
1180 GList *find_string(GList *glist, const char *buf)
1184 if (strcmp((char *)glist->data, buf) == 0)
1185 break; // this name is in our list already
1186 glist = glist->next;
1191 void Map_ImportBuffer(char *buf)
1195 Undo_Start("import buffer");
1199 stream.Write(buf, strlen(buf));
1200 Map_Import(&stream, "xmap");
1203 Sys_UpdateWindows (W_ALL);
1204 Sys_MarkMapModified();
1215 void Map_ImportFile (const char *filename)
1220 Sys_Printf("Importing map from %s\n",filename);
1222 const char* type = strrchr(filename,'.');
1223 if(type!=NULL) type++;
1224 /*!\todo Resolve "r" problem in scriptlib" */
1225 if(file.Open(filename, "rb"))
1226 Map_Import(&file, type, true);
1228 Sys_FPrintf(SYS_ERR, "ERROR: couldn't open %s for read\n", filename);
1232 Sys_UpdateWindows (W_ALL);
1242 // Saves selected world brushes and whole entities with partial/full selections
1244 void Map_SaveSelected(const char* filename)
1248 Sys_Printf("Saving selection to %s\n",filename);
1250 const char* type = strrchr(filename,'.');
1251 if(type!=NULL) type++;
1252 if(file.Open(filename, "w"))
1253 Map_Export (&file, type, false, true);
1255 Sys_FPrintf(SYS_ERR, "ERROR: failed to open %s for write\n", filename);
1266 // Saves selected world brushes and whole entities with partial/full selections
1268 void Map_SaveSelected (MemStream* pMemFile, MemStream* pPatchFile)
1270 Map_Export (pMemFile, "xmap", false, true);
1273 // write world entity first
1274 Entity_WriteSelected(world_entity, pMemFile);
1276 // then write all other ents
1278 for (e=entities.next ; e != &entities ; e=next)
1280 MemFile_fprintf(pMemFile, "// entity %i\n", count);
1282 Entity_WriteSelected(e, pMemFile);
1287 // Patch_WriteFile(pPatchFile);
1292 void MemFile_fprintf(MemStream* pMemFile, const char* pText, ...)
1296 va_start (args,pText);
1297 vsprintf(Buffer, pText, args);
1298 pMemFile->Write(Buffer, strlen(Buffer));
1304 push the region spawn point
1305 \todo FIXME TTimo this was in the #1 MAP module implementation (in the core)
1306 not sure it has any use anymore, should prolly drop it
1309 void Region_SpawnPoint(FILE *f)
1311 // write the info_player_start, we use the camera position
1313 fprintf (f, "\"classname\" \"info_player_start\"\n");
1314 fprintf (f, "\"origin\" \"%i %i %i\"\n",
1315 (int)g_pParentWnd->GetCamWnd()->Camera()->origin[0],
1316 (int)g_pParentWnd->GetCamWnd()->Camera()->origin[1],
1317 (int)g_pParentWnd->GetCamWnd()->Camera()->origin[2]);
1318 fprintf (f, "\"angle\" \"%i\"\n", (int)g_pParentWnd->GetCamWnd()->Camera()->angles[YAW]);