]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/map.cpp
* use CMD_SEP in sample plugin (minor cleanup)
[xonotic/netradiant.git] / radiant / map.cpp
1 /*
2 Copyright (C) 1999-2007 id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
4
5 This file is part of GtkRadiant.
6
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.
11
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.
16
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
20 */
21
22 #include "stdafx.h"
23 #include <string.h>
24 #if defined (__linux__) || defined (__APPLE__)
25 #include <unistd.h>
26 #endif
27 #include "preferences.h"
28 #include "mainframe.h"
29 #include "gtkmisc.h"
30 #include "filters.h"
31
32 extern MainFrame* g_pParentWnd;
33
34 int     modified;       // for quit confirmation (0 = clean, 1 = unsaved,
35                                 // 2 = autosaved, but not regular saved) 
36
37 char            currentmap[1024];
38
39 brush_t active_brushes;         // brushes currently being displayed
40 brush_t selected_brushes;       // highlighted
41
42 face_t  *selected_face;
43 brush_t *selected_face_brush;
44
45 brush_t filtered_brushes;       // brushes that have been filtered or regioned
46
47 entity_t        entities;               // head/tail of doubly linked list
48
49 entity_t        *world_entity = NULL; // "classname" "worldspawn" !
50
51 void Map_Init()
52 {
53   Map_Free();
54 }
55
56
57 bool  g_bCancel_Map_LoadFile; // Hydra: moved this here
58
59 // TTimo
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;
63
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;
67
68 void AddRegionBrushes (void);
69 void RemoveRegionBrushes (void);
70
71 /*
72 =============================================================
73
74   Cross map selection saving
75
76   this could fuck up if you have only part of a complex entity selected...
77 =============================================================
78 */
79
80 brush_t         between_brushes;
81 entity_t        between_entities;
82
83 bool g_bRestoreBetween = false;
84
85 void Map_SaveBetween (void)
86 {
87   if (g_pParentWnd->ActiveXY())
88   {
89     g_bRestoreBetween = true;
90     g_pParentWnd->ActiveXY()->Copy();
91   }
92   return;
93 }
94
95 void Map_RestoreBetween (void)
96 {
97   if (g_pParentWnd->ActiveXY() && g_bRestoreBetween)
98     g_pParentWnd->ActiveXY()->Paste();
99 }
100
101 //============================================================================
102
103 bool CheckForTinyBrush(brush_t* b, int n, float fSize)
104 {
105   bool bTiny = false;
106         for (int i=0 ; i<3 ; i++)
107         {
108     if (b->maxs[i] - b->mins[i] < fSize)
109       bTiny = true;
110   }
111   if (bTiny)
112     Sys_Printf("Possible problem brush (too small) #%i ", n);
113   return bTiny;
114 }
115
116 void Map_BuildBrushData(void)
117 {
118   brush_t *b, *next;
119
120   if (active_brushes.next == NULL)
121     return;
122
123   Sys_BeginWait ();     // this could take a while
124
125   int n = 0;
126   for (b=active_brushes.next ; b != NULL && b != &active_brushes ; b=next)
127   {
128     next = 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)))
131     {
132       Brush_Free (b);
133       Sys_Printf ("Removed degenerate brush\n");
134     }
135   }
136   Sys_EndWait();
137 }
138
139 entity_t *Map_FindClass (char *cname)
140 {
141         entity_t        *ent;
142
143         for (ent = entities.next ; ent != &entities ; ent=ent->next)
144         {
145                 if (!strcmp(cname, ValueForKey (ent, "classname")))
146                         return ent;
147         }
148         return NULL;
149 }
150
151 /*
152 ================
153 Map_Free
154 free all map elements, reinitialize the structures that depend on them
155 ================
156 */
157 void Map_Free (void)
158 {
159   g_bRestoreBetween = false;
160   if (selected_brushes.next &&
161       (selected_brushes.next != &selected_brushes))
162     {
163       if (gtk_MessageBox (g_pParentWnd->m_pWidget, "Copy selection?", " ", MB_YESNO) == IDYES)
164         Map_SaveBetween ();
165     }
166
167         QERApp_ActiveShaders_SetInUse( false );
168         Pointfile_Clear ();
169         g_qeglobals.d_num_entities = 0;
170
171         if (!active_brushes.next)
172         {
173           // first map
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;
178         }
179         else
180         {
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);
192         }
193
194   if (world_entity)
195     Entity_Free(world_entity);
196         world_entity = NULL;
197 }
198
199 entity_t *AngledEntity()
200 {
201   entity_t *ent = Map_FindClass ("info_player_start");
202         if (!ent)
203   {
204                 ent = Map_FindClass ("info_player_deathmatch");
205   }
206   if (!ent)
207   {
208                 ent = Map_FindClass ("info_player_deathmatch");
209   }
210   if (!ent)
211   {
212     ent = Map_FindClass ("team_CTF_redplayer");
213   }
214   if (!ent)
215   {
216     ent = Map_FindClass ("team_CTF_blueplayer");
217   }
218   if (!ent)
219   {
220     ent = Map_FindClass ("team_CTF_redspawn");
221   }
222   if (!ent)
223   {
224     ent = Map_FindClass ("team_CTF_bluespawn");
225   }
226   return ent;
227 }
228
229 //
230 // move the view to a start position
231 //
232 void Map_StartPosition()
233 {
234   entity_t *ent = AngledEntity();
235
236   g_pParentWnd->GetCamWnd()->Camera()->angles[PITCH] = 0;
237   if (ent)
238   {
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");
242   }
243   else
244   {
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());
248   }
249 }
250
251 void Map_FreeEntities(CPtrArray *ents)
252 {
253   int i, j, num_ents, num_brushes;
254   entity_t* e;
255   CPtrArray* brushes;
256
257   num_ents = ents->GetSize();
258   for(i=0; i<num_ents; i++)
259   {
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;
267     e->pData = NULL;
268     Entity_Free(e);
269   }
270   ents->RemoveAll();
271 }
272
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)
275 {
276   int num_ents, num_brushes;
277   CPtrArray *brushes;
278   vec3_t mins, maxs;
279   entity_t *e;
280   brush_t *b;
281   face_t *f;
282   int i,j;
283
284   GPtrArray *new_ents = g_ptr_array_new();
285
286   g_qeglobals.bPrimitBrushes = false;
287
288   brush_t *pBrushList = (bAddSelected) ? &selected_brushes : &active_brushes;
289
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++)
297   {
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++)
302     {
303       /*!todo Allow mixing texdef formats per-face. */
304       b = (brush_t *)brushes->GetAt(j);
305       if(b->patchBrush) continue;
306       bDoneBPCheck = true;
307       int BP_param = -1;
308       if(b->bBrushDef && !g_qeglobals.m_bBrushPrimitMode)
309         BP_param = 0;
310       else if(!b->bBrushDef && g_qeglobals.m_bBrushPrimitMode)
311         BP_param = 1;
312
313       if(BP_param != -1)
314       {
315         switch(BP_MessageBox(BP_param))
316         {
317         case 0:
318           Map_FreeEntities(ents);
319           return;
320         case 1:
321           g_qeglobals.bNeedConvert = true;
322           break;
323         case 2:
324           g_qeglobals.bNeedConvert = false;
325           break;
326         }
327       }
328     }
329   }
330
331   // process the entities into the world geometry
332   num_ents = ents->GetSize();
333   for(i=0; i<num_ents; i++)
334   {
335     num_brushes = 0;
336     e = (entity_t*)ents->GetAt(i);
337     brushes = (CPtrArray*)e->pData;
338  
339     num_brushes = brushes->GetSize();
340     // link brushes into entity
341     for(j=0; j<num_brushes; j++)
342     {
343       Entity_LinkBrush(e, (brush_t *)brushes->GetAt(j));
344       g_qeglobals.d_parsed_brushes++;
345     }
346     brushes->RemoveAll();
347     delete brushes;
348     e->pData = NULL;
349
350     // set entity origin
351     GetVectorForKey (e, "origin", e->origin);
352     // set entity eclass
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));
356
357     // go through all parsed brushes and build stuff
358     for(b = e->brushes.onext; b!=&e->brushes; b=b->onext)
359     {
360       for(f = b->brush_faces; f != NULL; f = f->next)
361       {
362         f->pShader = QERApp_Shader_ForName(f->texdef.GetName());
363         f->d_texture = f->pShader->getTexture();
364       }
365
366       // when brushes are in final state, build the planes and windings
367       // NOTE: also converts BP brushes if g_qeglobals.bNeedConvert is true
368       Brush_Build(b);
369     }
370
371 //#define TERRAIN_HACK
372 #undef TERRAIN_HACK
373
374 #ifdef TERRAIN_HACK
375     if ((strcmp(ValueForKey(e, "terrain"),"1") == 0 && strcmp(e->eclass->name,"func_group") == 0))
376     {
377       
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;
382       
383       pTerrainShader = NULL;
384       pCaulk = QERApp_Shader_ForName(SHADER_CAULK);
385       
386       for(b = e->brushes.onext; b!=&e->brushes; b=b->onext)
387       {
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;
392             
393             if (pTerrainShader)
394             {
395               for(f = b->brush_faces; f != NULL; f = f->next)
396               {
397                 if (strcmp(f->texdef.GetName(), SHADER_CAULK)!=0) // not caulk
398                   Face_SetShader(f, pTerrainShader->getName());
399                 else
400                   Face_SetShader(f, pCaulk->getName());
401               }
402             }
403             else
404               Sys_Printf("WARNING: no terrain shader found for brush\n");
405       }
406     }
407 #endif
408
409 #define PATCH_HACK
410 #ifdef PATCH_HACK
411     for(b = e->brushes.onext; b!=&e->brushes; b=b->onext)
412     {
413       // patch hack, to be removed when dependency on brush_faces is removed
414       if (b->patchBrush)
415       {
416         Patch_CalcBounds(b->pPatch, mins, maxs);
417         for (int i=0; i<3; i++)
418         {
419           if ((int)mins[i] == (int)maxs[i])
420           {
421             mins[i] -= 4;
422             maxs[i] += 4;
423           }
424         }
425         Brush_Resize(b, mins, maxs);
426         Brush_Build(b);
427       }
428     }
429 #endif
430     // add brush for fixedsize entity
431     if (e->eclass->fixedsize)
432     {
433       vec3_t mins, maxs;
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);
438       Brush_Build(b);
439     }
440
441     for(b = e->brushes.onext; b!=&e->brushes; b=b->onext)
442       Brush_AddToList(b, pBrushList);
443
444     if (strcmp(e->eclass->name, "worldspawn") == 0)
445     {
446       if (world_entity)
447       {
448         while(e->brushes.onext != &e->brushes)
449         {
450           b = e->brushes.onext;
451           Entity_UnlinkBrush(b);
452           Entity_LinkBrush(world_entity, b);
453         }
454         Entity_Free(e);
455       }
456       else
457       {
458         world_entity = e;
459       }
460     }
461     else if (strcmp(e->eclass->name, "group_info") == 0)
462     {
463       // it's a group thing!
464       Group_Add(e);
465       Entity_Free(e);
466     }
467     else
468     {
469       // fix target/targetname collisions
470       if ((g_PrefsDlg.m_bDoTargetFix) && (strcmp(ValueForKey(e, "target"), "") != 0))
471       {
472         GPtrArray *t_ents = g_ptr_array_new();
473         entity_t *e_target;
474         const char *target = ValueForKey(e, "target");
475         qboolean bCollision=FALSE;
476         
477         // check the current map entities for an actual collision
478         for (e_target = entities.next; e_target != &entities; e_target = e_target->next)
479         {
480           if(!strcmp(target, ValueForKey(e_target, "target")))
481           {
482             bCollision = TRUE;
483             // make sure the collision is not between two imported entities
484             for(j=0; j<(int)new_ents->len; j++)
485             {
486               if(e_target == g_ptr_array_index(new_ents, j))
487                 bCollision = FALSE;
488             }
489           }
490         }
491         
492         // find the matching targeted entity(s)
493         if(bCollision)
494         {
495           for(j=num_ents-1; j>0; j--)
496           {
497             e_target = (entity_t*)ents->GetAt(j);
498             if(e_target != NULL && e_target != e)
499             {
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);
503             }
504           }
505           if(t_ents->len > 0)
506           {
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") );
512           }
513           g_ptr_array_free(t_ents, FALSE);
514         }
515       }
516       
517       // add the entity to the end of the entity list
518       Entity_AddToList(e, &entities);
519       g_qeglobals.d_num_entities++;
520       
521       // keep a list of ents added to avoid testing collisions against them
522       g_ptr_array_add(new_ents, (gpointer)e);
523     }
524   }
525   g_ptr_array_free(new_ents, FALSE);
526   
527   ents->RemoveAll();
528
529   g_qeglobals.bNeedConvert = false;
530 }
531
532 void Map_Import(IDataStream *in, const char *type, bool bAddSelected)
533 {
534   CPtrArray ents;
535
536   g_pParentWnd->GetSynapseClient().ImportMap(in, &ents, type);
537   Map_ImportEntities(&ents, bAddSelected);
538 }
539
540 /*
541 ================
542 Map_LoadFile
543 ================
544 */
545 void Map_LoadFile (const char *filename)
546 {
547   clock_t start, finish;
548   double elapsed_time;
549   start = clock();
550
551   Sys_BeginWait ();
552   Select_Deselect();
553   /*!
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
557   */
558   //SetInspectorMode(W_CONSOLE);
559   Sys_Printf ("Loading map from %s\n", filename );
560
561   Map_Free ();
562   //++timo FIXME: maybe even easier to have Group_Init called from Map_Free?
563   Group_Init();
564   g_qeglobals.d_num_entities = 0;
565   g_qeglobals.d_parsed_brushes = 0;
566
567
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;
571
572   strcpy (currentmap, filename);
573
574   g_bScreenUpdates = false; // leo: avoid redraws while loading the map (see fenris:1952)
575
576   // prepare to let the map module do the parsing
577   FileStream file;
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);
586   else
587     Sys_FPrintf(SYS_ERR, "ERROR: failed to open %s for read\n", filename);
588   file.Close();
589
590   g_bScreenUpdates = true;
591
592   if (g_bCancel_Map_LoadFile)
593   {
594     Sys_Printf("Map_LoadFile canceled\n");
595     Map_New();
596     Sys_EndWait();
597     return;
598   }
599
600   if (!world_entity)
601   {
602     Sys_Printf ("No worldspawn in map.\n");
603     Map_New ();
604     Sys_EndWait();
605     return;
606   }
607   finish = clock();
608   elapsed_time = (double)(finish - start) / CLOCKS_PER_SEC;
609
610   Sys_Printf ("--- LoadMapFile ---\n");
611   Sys_Printf ("%s\n", filename );
612   
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 );
616   
617   Sys_EndWait();
618   
619   Map_RestoreBetween ();
620   
621   //
622   // move the view to a start position
623   //
624   Map_StartPosition();
625
626   Map_RegionOff ();
627
628   modified = false;
629   Sys_SetTitle (filename);
630
631   Texture_ShowInuse ();
632   QERApp_SortActiveShaders();
633
634   Sys_UpdateWindows (W_ALL);
635 }
636
637 /*!
638 ===========
639 Supporting functions for Map_SaveFile, builds a CPtrArray with the filtered / non filtered brushes
640 ===========
641 */
642 void CleanFilter(entity_t *ent)
643 {
644   if (ent->pData)
645   {
646     delete static_cast<CPtrArray*>(ent->pData);
647     ent->pData = NULL;
648   }
649 }
650
651 /*!
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)
655 */
656 bool FilterChildren(entity_t *ent, bool bRegionOnly = false, bool bSelectedOnly = false)
657 {
658   if(ent->brushes.onext == &ent->brushes)
659     return false;
660   // entity without a brush, ignore it... this can be caused by Undo
661
662   // filter fixedsize ents by their eclass bounding box
663   // don't add their brushes
664   if (ent->eclass->fixedsize)
665   {
666     if(bSelectedOnly && !IsBrushSelected(ent->brushes.onext))
667       return false;
668
669     if(bRegionOnly && region_active)
670     {
671       for (int i=0 ; i<3 ; i++)
672             {
673                     if ((ent->origin[i] + ent->eclass->mins[i]) > region_maxs[i])
674                             return false;
675                     if ((ent->origin[i] + ent->eclass->maxs[i]) < region_mins[i])
676                             return false;
677             }
678     }
679   }
680   else
681   {
682     for (brush_t *b = ent->brushes.onext ; b != &ent->brushes ; b=b->onext)
683     {
684       // set flag to use brushprimit_texdef
685       if(g_qeglobals.m_bBrushPrimitMode)
686         b->bBrushDef = true;
687       else
688         b->bBrushDef = false;
689
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);
694     }
695
696     if (((CPtrArray*)ent->pData)->GetSize() <= 0)
697       return false;
698   }
699   return true;
700 }
701
702 entity_t *region_startpoint = NULL;
703 void Map_ExportEntities(CPtrArray* ents, bool bRegionOnly = false, bool bSelectedOnly = false)
704 {
705   int i;
706   entity_t *e;
707
708   /*!
709   \todo the entity_t needs to be reworked and asbtracted some more
710   
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
714   
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?
717   */
718
719   FilterChildren(world_entity, bRegionOnly, bSelectedOnly);
720   ents->Add(world_entity);
721
722   for (e=entities.next ; e!=&entities ; e=e->next)
723   {
724     // not sure this still happens, probably safe to leave it in
725     if ((!strcmp(ValueForKey (e, "classname"), "worldspawn")) && (e!=world_entity))
726     {
727       Sys_FPrintf(SYS_ERR, "Dropping parasite worldspawn entity\n");
728       continue;
729     }
730
731     // entities which brushes are completely filtered out by regioning are not printed to the map
732     if (FilterChildren(e, bRegionOnly, bSelectedOnly))
733       ents->Add(e);
734   }
735
736   if (bRegionOnly && region_active)
737   {
738     for(i=0; i<6; i++)
739       ((CPtrArray*)world_entity->pData)->Add(region_sides[i]);
740
741     ents->Add(region_startpoint);
742   }
743 }
744
745 void Map_Export(IDataStream *out, const char *type, bool bRegionOnly, bool bSelectedOnly)
746 {
747   entity_t      *e;
748
749   CPtrArray ents;
750   
751   if (bRegionOnly && region_active)
752     AddRegionBrushes();
753
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();
758
759   Map_ExportEntities(&ents, bRegionOnly, bSelectedOnly);
760
761   g_pParentWnd->GetSynapseClient().ExportMap(&ents, out, type);
762
763   // cleanup the filters
764   CleanFilter(world_entity);
765   for (e=entities.next ; e!=&entities ; e=e->next)
766     CleanFilter(e);
767
768   if (bRegionOnly && region_active)
769     RemoveRegionBrushes();
770 }
771
772 const char* filename_get_extension(const char* filename)
773 {
774   const char* type = strrchr(filename,'.');
775   if(type != NULL)
776     return ++type;
777   return "";
778 }
779
780 /*
781 ===========
782 Map_SaveFile
783 \todo FIXME remove the use_region, this is broken .. work with a global flag to set region mode or not
784 ===========
785 */
786 void Map_SaveFile (const char *filename, qboolean use_region )
787 {
788   clock_t start, finish;
789   double elapsed_time;
790   start = clock();
791   Sys_Printf("Saving map to %s\n",filename);
792
793         Pointfile_Clear ();
794
795         if (!use_region)
796         {
797                 char    backup[1024];
798
799                 // rename current to .bak
800                 strcpy (backup, filename);
801                 StripExtension (backup);
802                 strcat (backup, ".bak");
803                 unlink (backup);
804                 rename (filename, backup);
805         }
806
807         Sys_Printf ("Map_SaveFile: %s\n", filename);
808
809   // build the out data stream
810   FileStream file;
811   if (!file.Open(filename,"w"))
812   {
813     Sys_FPrintf(SYS_ERR, "ERROR: couldn't open %s for write\n", filename);
814     return;
815   }
816
817   // extract filetype
818   Map_Export(&file, filename_get_extension(filename), use_region);
819
820   file.Close();
821
822   finish = clock();
823   elapsed_time = (double)(finish - start) / CLOCKS_PER_SEC;
824
825   Sys_Printf ("Saved in %-.2f second(s).\n",elapsed_time);
826         modified = false;
827
828         if ( !strstr( filename, "autosave" ) )
829                 Sys_SetTitle (filename);
830
831         if (!use_region)
832         {
833                 time_t  timer;
834
835                 time (&timer);
836
837     Sys_Beep ();
838
839                 Sys_Status ("Saved.", 0);
840         }
841 }
842
843 /*
844 ===========
845 Map_New
846
847 ===========
848 */
849 void Map_New (void)
850 {
851         Sys_Printf ("Map_New\n");
852         Map_Free ();
853
854         strcpy (currentmap, "unnamed.map");
855         Sys_SetTitle (currentmap);
856
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);
862
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());
868
869         Map_RestoreBetween ();
870
871   Group_Init();
872
873         Sys_UpdateWindows (W_ALL);
874         modified = false;
875 }
876
877 /*
878 ===========================================================
879
880   REGION
881
882 ===========================================================
883 */
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};
887
888 brush_t *region_sides[6];
889
890 /*
891 ===========
892 AddRegionBrushes
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
897 ===========
898 */
899 void AddRegionBrushes (void)
900 {
901         vec3_t  mins, maxs;
902         int             i;
903         texdef_t        td;
904
905         if (!region_active)
906   {
907 #ifdef _DEBUG
908     Sys_FPrintf( SYS_WRN, "Unexpected AddRegionBrushes call.\n");
909 #endif
910                 return;
911   }
912
913         memset (&td, 0, sizeof(td));
914         td.SetName(SHADER_NOT_FOUND);
915
916   // set mins
917   VectorSet(mins, region_mins[0]-32, region_mins[1]-32, region_mins[2]-32);
918
919   // vary maxs
920   for(i=0; i<3; i++)
921   {
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);
925   }
926
927   // set maxs
928   VectorSet(maxs, region_maxs[0]+32, region_maxs[1]+32, region_maxs[2]+32);
929
930   // vary mins
931   for(i=0; i<3; i++)
932   {
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);
936   }
937
938   
939   // http://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=503
940   // this is a safe check, but it should not really happen anymore  
941   vec3_t vOrig;
942   VectorSet(vOrig,
943     (int)g_pParentWnd->GetCamWnd()->Camera()->origin[0],
944     (int)g_pParentWnd->GetCamWnd()->Camera()->origin[1], 
945     (int)g_pParentWnd->GetCamWnd()->Camera()->origin[2]);
946
947   for (i=0 ; i<3 ; i++)
948   {
949     if (vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i])
950     {
951       Sys_FPrintf(SYS_ERR, "Camera is NOT in the region, it's likely that the region won't compile correctly\n");
952     }
953   }
954   
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);
959   char sTmp[1024];
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;
966 }
967
968 void RemoveRegionBrushes (void)
969 {
970         int             i;
971
972         if (!region_active)
973                 return;
974         for (i=0 ; i<6 ; i++)
975                 Brush_Free (region_sides[i]);
976
977   CleanFilter(region_startpoint);
978   Entity_Free(region_startpoint);
979 }
980
981 qboolean Map_IsBrushFiltered (brush_t *b)
982 {
983         int             i;
984
985         for (i=0 ; i<3 ; i++)
986         {
987                 if (b->mins[i] > region_maxs[i])
988                         return true;
989                 if (b->maxs[i] < region_mins[i])
990                         return true;
991         }
992         return false;
993 }
994
995 /*
996 ===========
997 Map_RegionOff
998
999 Other filtering options may still be on
1000 ===========
1001 */
1002 void Map_RegionOff (void)
1003 {
1004         brush_t *b, *next;
1005         int                     i;
1006
1007         region_active = false;
1008         for (i=0 ; i<3 ; i++)
1009         {
1010                 region_maxs[i] = g_MaxWorldCoord-64;
1011                 region_mins[i] = g_MinWorldCoord+64;
1012         }
1013         
1014         for (b=filtered_brushes.next ; b != &filtered_brushes ; b=next)
1015         {
1016                 next = 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)
1021                 {
1022                         active_brushes.next = &active_brushes;
1023                         active_brushes.prev = &active_brushes;
1024                 }
1025                 Brush_AddToList (b, &active_brushes);
1026                 b->bFiltered = FilterBrush(b);
1027         }
1028         Sys_UpdateWindows (W_ALL);
1029 }
1030
1031 void Map_ApplyRegion (void)
1032 {
1033         brush_t *b, *next;
1034
1035         region_active = true;
1036         for (b=active_brushes.next ; b != &active_brushes ; b=next)
1037         {
1038                 next = b->next;
1039                 if (!Map_IsBrushFiltered (b))
1040                         continue;               // still filtered
1041                 Brush_RemoveFromList (b);
1042                 Brush_AddToList (b, &filtered_brushes);
1043         }
1044
1045         Sys_UpdateWindows (W_ALL);
1046 }
1047
1048
1049 /*
1050 ========================
1051 Map_RegionSelectedBrushes
1052 ========================
1053 */
1054 void Map_RegionSelectedBrushes (void)
1055 {
1056         Map_RegionOff ();
1057
1058         if (selected_brushes.next == &selected_brushes)  // nothing selected
1059   {
1060     Sys_Printf("Tried to region with no selection...\n");
1061     return;
1062   }
1063         region_active = true;
1064         Select_GetBounds (region_mins, region_maxs);
1065
1066 #ifdef _DEBUG
1067         if (filtered_brushes.next != &filtered_brushes)
1068                 Sys_Printf("WARNING: filtered_brushes list may not be empty in Map_RegionSelectedBrushes\n");
1069 #endif
1070
1071         if (active_brushes.next == &active_brushes)
1072         {
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;
1076         }
1077         else
1078         {
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;
1084         }
1085
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;
1091
1092         // deselect patches
1093         for (brush_t *b = active_brushes.next; b != &active_brushes; b = b->next)
1094           if (b->patchBrush)
1095             b->pPatch->bSelected = false;
1096
1097         // clear selected_brushes
1098         selected_brushes.next = selected_brushes.prev = &selected_brushes;
1099
1100         Sys_UpdateWindows (W_ALL);
1101 }
1102
1103
1104 /*
1105 ===========
1106 Map_RegionXY
1107 ===========
1108 */
1109 void Map_RegionXY (void)
1110 {
1111         Map_RegionOff ();
1112
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;
1119         Map_ApplyRegion ();
1120 }
1121
1122 /*
1123 ===========
1124 Map_RegionTallBrush
1125 ===========
1126 */
1127 void Map_RegionTallBrush (void)
1128 {
1129   brush_t *b;
1130
1131   if (!QE_SingleBrush ())
1132     return;
1133
1134   b = selected_brushes.next;
1135
1136   Map_RegionOff ();
1137
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;
1142
1143   Undo_Start("delete");
1144   Undo_AddBrushList(&selected_brushes);
1145   Undo_AddEntity(b->owner);
1146   Select_Delete ();
1147   Undo_EndBrushList(&selected_brushes);
1148   Undo_End();
1149
1150   Map_ApplyRegion ();
1151 }
1152
1153 /*
1154 ===========
1155 Map_RegionBrush
1156 ===========
1157 */
1158 void Map_RegionBrush (void)
1159 {
1160   brush_t *b;
1161
1162   if (!QE_SingleBrush ())
1163     return;
1164
1165   b = selected_brushes.next;
1166
1167   Map_RegionOff ();
1168
1169   VectorCopy (b->mins, region_mins);
1170   VectorCopy (b->maxs, region_maxs);
1171
1172   Undo_Start("delete");
1173   Undo_AddBrushList(&selected_brushes);
1174   Undo_AddEntity(b->owner);
1175   Select_Delete ();
1176   Undo_EndBrushList(&selected_brushes);
1177   Undo_End();
1178
1179   Map_ApplyRegion ();
1180 }
1181
1182 GList *find_string(GList *glist, const char *buf)
1183 {
1184   while (glist)
1185   {
1186     if (strcmp((char *)glist->data, buf) == 0)
1187       break; // this name is in our list already
1188     glist = glist->next;
1189   }
1190   return glist;
1191 }
1192
1193 void Map_ImportBuffer(char *buf)
1194 {
1195   Select_Deselect();
1196   
1197   Undo_Start("import buffer");
1198
1199   MemStream stream;
1200
1201   stream.Write(buf, strlen(buf));
1202   Map_Import(&stream, "xmap");
1203   stream.Close();
1204   
1205   Sys_UpdateWindows (W_ALL);
1206   Sys_MarkMapModified();
1207   
1208   Undo_End();
1209 }
1210
1211
1212 //
1213 //================
1214 //Map_ImportFile
1215 //================
1216 //
1217 void Map_ImportFile (const char *filename)
1218 {
1219   FileStream file;
1220         Sys_BeginWait ();
1221
1222   Sys_Printf("Importing map from %s\n",filename);
1223
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);
1229   else
1230     Sys_FPrintf(SYS_ERR, "ERROR: couldn't open %s for read\n", filename);
1231
1232   file.Close();
1233
1234         Sys_UpdateWindows (W_ALL);
1235         modified = true;
1236         Sys_EndWait();
1237 }
1238
1239 //
1240 //===========
1241 //Map_SaveSelected
1242 //===========
1243 //
1244 // Saves selected world brushes and whole entities with partial/full selections
1245 //
1246 void Map_SaveSelected(const char* filename)
1247 {
1248   FileStream file;
1249
1250   Sys_Printf("Saving selection to %s\n",filename);
1251
1252   const char* type = strrchr(filename,'.');
1253   if(type!=NULL) type++;
1254   if(file.Open(filename, "w"))
1255     Map_Export (&file, type, false, true);
1256   else
1257     Sys_FPrintf(SYS_ERR, "ERROR: failed to open %s for write\n", filename);
1258
1259   file.Close();
1260
1261 }
1262
1263 //
1264 //===========
1265 //Map_SaveSelected
1266 //===========
1267 //
1268 // Saves selected world brushes and whole entities with partial/full selections
1269 //
1270 void Map_SaveSelected (MemStream* pMemFile, MemStream* pPatchFile)
1271 {
1272   Map_Export (pMemFile, "xmap", false, true);
1273
1274  /*
1275         // write world entity first
1276         Entity_WriteSelected(world_entity, pMemFile);
1277
1278         // then write all other ents
1279         count = 1;
1280         for (e=entities.next ; e != &entities ; e=next)
1281         {
1282                 MemFile_fprintf(pMemFile, "// entity %i\n", count);
1283                 count++;
1284                 Entity_WriteSelected(e, pMemFile);
1285                 next = e->next;
1286         }
1287
1288   //if (pPatchFile)
1289   //  Patch_WriteFile(pPatchFile);
1290   */
1291 }
1292
1293
1294 void MemFile_fprintf(MemStream* pMemFile, const char* pText, ...)
1295 {
1296   char Buffer[4096];
1297   va_list args;
1298         va_start (args,pText);
1299   vsprintf(Buffer, pText, args);
1300   pMemFile->Write(Buffer, strlen(Buffer));
1301 }
1302
1303 /*!
1304 ==============
1305 Region_SpawnPoint
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
1309 ==============
1310 */
1311 void Region_SpawnPoint(FILE *f)
1312 {
1313   // write the info_player_start, we use the camera position
1314   fprintf (f, "{\n");
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]);
1321   fprintf (f, "}\n");
1322 }