]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/map.cpp
* applied patch by StefanV (from mailinglist) that fixes an error in config.py (broke...
[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 (const 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   // 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);
585   else
586     Sys_FPrintf(SYS_ERR, "ERROR: failed to open %s for read\n", filename);
587   file.Close();
588
589   g_bScreenUpdates = true;
590
591   if (g_bCancel_Map_LoadFile)
592   {
593     Sys_Printf("Map_LoadFile canceled\n");
594     Map_New();
595     Sys_EndWait();
596     return;
597   }
598
599   if (!world_entity)
600   {
601     Sys_Printf ("No worldspawn in map.\n");
602     Map_New ();
603     Sys_EndWait();
604     return;
605   }
606   finish = clock();
607   elapsed_time = (double)(finish - start) / CLOCKS_PER_SEC;
608
609   Sys_Printf ("--- LoadMapFile ---\n");
610   Sys_Printf ("%s\n", filename );
611
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 );
615
616   Sys_EndWait();
617
618   Map_RestoreBetween ();
619
620   //
621   // move the view to a start position
622   //
623   Map_StartPosition();
624
625   Map_RegionOff ();
626
627   modified = false;
628   Sys_SetTitle (filename);
629
630   Texture_ShowInuse ();
631   QERApp_SortActiveShaders();
632
633   Sys_UpdateWindows (W_ALL);
634 }
635
636 /*!
637 ===========
638 Supporting functions for Map_SaveFile, builds a CPtrArray with the filtered / non filtered brushes
639 ===========
640 */
641 void CleanFilter(entity_t *ent)
642 {
643   if (ent->pData)
644   {
645     delete static_cast<CPtrArray*>(ent->pData);
646     ent->pData = NULL;
647   }
648 }
649
650 /*!
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)
654 */
655 bool FilterChildren(entity_t *ent, bool bRegionOnly = false, bool bSelectedOnly = false)
656 {
657   if(ent->brushes.onext == &ent->brushes)
658     return false;
659   // entity without a brush, ignore it... this can be caused by Undo
660
661   // filter fixedsize ents by their eclass bounding box
662   // don't add their brushes
663   if (ent->eclass->fixedsize)
664   {
665     if(bSelectedOnly && !IsBrushSelected(ent->brushes.onext))
666       return false;
667
668     if(bRegionOnly && region_active)
669     {
670       for (int i=0 ; i<3 ; i++)
671             {
672                     if ((ent->origin[i] + ent->eclass->mins[i]) > region_maxs[i])
673                             return false;
674                     if ((ent->origin[i] + ent->eclass->maxs[i]) < region_mins[i])
675                             return false;
676             }
677     }
678   }
679   else
680   {
681     for (brush_t *b = ent->brushes.onext ; b != &ent->brushes ; b=b->onext)
682     {
683       // set flag to use brushprimit_texdef
684       if(g_qeglobals.m_bBrushPrimitMode)
685         b->bBrushDef = true;
686       else
687         b->bBrushDef = false;
688
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);
693     }
694
695     if (((CPtrArray*)ent->pData)->GetSize() <= 0)
696       return false;
697   }
698   return true;
699 }
700
701 entity_t *region_startpoint = NULL;
702 void Map_ExportEntities(CPtrArray* ents, bool bRegionOnly = false, bool bSelectedOnly = false)
703 {
704   int i;
705   entity_t *e;
706
707   /*!
708   \todo the entity_t needs to be reworked and asbtracted some more
709
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
713
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?
716   */
717
718   FilterChildren(world_entity, bRegionOnly, bSelectedOnly);
719   ents->Add(world_entity);
720
721   for (e=entities.next ; e!=&entities ; e=e->next)
722   {
723     // not sure this still happens, probably safe to leave it in
724     if ((!strcmp(ValueForKey (e, "classname"), "worldspawn")) && (e!=world_entity))
725     {
726       Sys_FPrintf(SYS_ERR, "Dropping parasite worldspawn entity\n");
727       continue;
728     }
729
730     // entities which brushes are completely filtered out by regioning are not printed to the map
731     if (FilterChildren(e, bRegionOnly, bSelectedOnly))
732       ents->Add(e);
733   }
734
735   if (bRegionOnly && region_active)
736   {
737     for(i=0; i<6; i++)
738       ((CPtrArray*)world_entity->pData)->Add(region_sides[i]);
739
740     ents->Add(region_startpoint);
741   }
742 }
743
744 void Map_Export(IDataStream *out, const char *type, bool bRegionOnly, bool bSelectedOnly)
745 {
746   entity_t      *e;
747
748   CPtrArray ents;
749
750   if (bRegionOnly && region_active)
751     AddRegionBrushes();
752
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();
757
758   Map_ExportEntities(&ents, bRegionOnly, bSelectedOnly);
759
760   g_pParentWnd->GetSynapseClient().ExportMap(&ents, out, type);
761
762   // cleanup the filters
763   CleanFilter(world_entity);
764   for (e=entities.next ; e!=&entities ; e=e->next)
765     CleanFilter(e);
766
767   if (bRegionOnly && region_active)
768     RemoveRegionBrushes();
769 }
770
771 const char* filename_get_extension(const char* filename)
772 {
773   const char* type = strrchr(filename,'.');
774   if(type != NULL)
775     return ++type;
776   return "";
777 }
778
779 /*
780 ===========
781 Map_SaveFile
782 \todo FIXME remove the use_region, this is broken .. work with a global flag to set region mode or not
783 ===========
784 */
785 void Map_SaveFile (const char *filename, qboolean use_region )
786 {
787   clock_t start, finish;
788   double elapsed_time;
789   start = clock();
790   Sys_Printf("Saving map to %s\n",filename);
791
792         Pointfile_Clear ();
793
794         if (!use_region)
795         {
796                 char    backup[1024];
797
798                 // rename current to .bak
799                 strcpy (backup, filename);
800                 StripExtension (backup);
801                 strcat (backup, ".bak");
802                 unlink (backup);
803                 rename (filename, backup);
804         }
805
806         Sys_Printf ("Map_SaveFile: %s\n", filename);
807
808   // build the out data stream
809   FileStream file;
810   if (!file.Open(filename,"w"))
811   {
812     Sys_FPrintf(SYS_ERR, "ERROR: couldn't open %s for write\n", filename);
813     return;
814   }
815
816   // extract filetype
817   Map_Export(&file, filename_get_extension(filename), use_region);
818
819   file.Close();
820
821   finish = clock();
822   elapsed_time = (double)(finish - start) / CLOCKS_PER_SEC;
823
824   Sys_Printf ("Saved in %-.2f second(s).\n",elapsed_time);
825         modified = false;
826
827         if ( !strstr( filename, "autosave" ) )
828                 Sys_SetTitle (filename);
829
830         if (!use_region)
831         {
832                 time_t  timer;
833
834                 time (&timer);
835
836     Sys_Beep ();
837
838                 Sys_Status ("Saved.", 0);
839         }
840 }
841
842 /*
843 ===========
844 Map_New
845
846 ===========
847 */
848 void Map_New (void)
849 {
850         Sys_Printf ("Map_New\n");
851         Map_Free ();
852
853         strcpy (currentmap, "unnamed.map");
854         Sys_SetTitle (currentmap);
855
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);
861
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());
867
868         Map_RestoreBetween ();
869
870   Group_Init();
871
872         Sys_UpdateWindows (W_ALL);
873         modified = false;
874 }
875
876 /*
877 ===========================================================
878
879   REGION
880
881 ===========================================================
882 */
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};
886
887 brush_t *region_sides[6];
888
889 /*
890 ===========
891 AddRegionBrushes
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
896 ===========
897 */
898 void AddRegionBrushes (void)
899 {
900         vec3_t  mins, maxs;
901         int             i;
902         texdef_t        td;
903
904         if (!region_active)
905   {
906 #ifdef _DEBUG
907     Sys_FPrintf( SYS_WRN, "Unexpected AddRegionBrushes call.\n");
908 #endif
909                 return;
910   }
911
912         memset (&td, 0, sizeof(td));
913         td.SetName(SHADER_NOT_FOUND);
914
915   // set mins
916   VectorSet(mins, region_mins[0]-32, region_mins[1]-32, region_mins[2]-32);
917
918   // vary maxs
919   for(i=0; i<3; i++)
920   {
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);
924   }
925
926   // set maxs
927   VectorSet(maxs, region_maxs[0]+32, region_maxs[1]+32, region_maxs[2]+32);
928
929   // vary mins
930   for(i=0; i<3; i++)
931   {
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);
935   }
936
937
938   // this is a safe check, but it should not really happen anymore
939   vec3_t vOrig;
940   VectorSet(vOrig,
941     (int)g_pParentWnd->GetCamWnd()->Camera()->origin[0],
942     (int)g_pParentWnd->GetCamWnd()->Camera()->origin[1],
943     (int)g_pParentWnd->GetCamWnd()->Camera()->origin[2]);
944
945   for (i=0 ; i<3 ; i++)
946   {
947     if (vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i])
948     {
949       Sys_FPrintf(SYS_ERR, "Camera is NOT in the region, it's likely that the region won't compile correctly\n");
950     }
951   }
952
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);
957   char sTmp[1024];
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;
964 }
965
966 void RemoveRegionBrushes (void)
967 {
968         int             i;
969
970         if (!region_active)
971                 return;
972         for (i=0 ; i<6 ; i++)
973                 Brush_Free (region_sides[i]);
974
975   CleanFilter(region_startpoint);
976   Entity_Free(region_startpoint);
977 }
978
979 qboolean Map_IsBrushFiltered (brush_t *b)
980 {
981         int             i;
982
983         for (i=0 ; i<3 ; i++)
984         {
985                 if (b->mins[i] > region_maxs[i])
986                         return true;
987                 if (b->maxs[i] < region_mins[i])
988                         return true;
989         }
990         return false;
991 }
992
993 /*
994 ===========
995 Map_RegionOff
996
997 Other filtering options may still be on
998 ===========
999 */
1000 void Map_RegionOff (void)
1001 {
1002         brush_t *b, *next;
1003         int                     i;
1004
1005         region_active = false;
1006         for (i=0 ; i<3 ; i++)
1007         {
1008                 region_maxs[i] = g_MaxWorldCoord-64;
1009                 region_mins[i] = g_MinWorldCoord+64;
1010         }
1011
1012         for (b=filtered_brushes.next ; b != &filtered_brushes ; b=next)
1013         {
1014                 next = 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)
1019                 {
1020                         active_brushes.next = &active_brushes;
1021                         active_brushes.prev = &active_brushes;
1022                 }
1023                 Brush_AddToList (b, &active_brushes);
1024                 b->bFiltered = FilterBrush(b);
1025         }
1026         Sys_UpdateWindows (W_ALL);
1027 }
1028
1029 void Map_ApplyRegion (void)
1030 {
1031         brush_t *b, *next;
1032
1033         region_active = true;
1034         for (b=active_brushes.next ; b != &active_brushes ; b=next)
1035         {
1036                 next = b->next;
1037                 if (!Map_IsBrushFiltered (b))
1038                         continue;               // still filtered
1039                 Brush_RemoveFromList (b);
1040                 Brush_AddToList (b, &filtered_brushes);
1041         }
1042
1043         Sys_UpdateWindows (W_ALL);
1044 }
1045
1046
1047 /*
1048 ========================
1049 Map_RegionSelectedBrushes
1050 ========================
1051 */
1052 void Map_RegionSelectedBrushes (void)
1053 {
1054         Map_RegionOff ();
1055
1056         if (selected_brushes.next == &selected_brushes)  // nothing selected
1057   {
1058     Sys_Printf("Tried to region with no selection...\n");
1059     return;
1060   }
1061         region_active = true;
1062         Select_GetBounds (region_mins, region_maxs);
1063
1064 #ifdef _DEBUG
1065         if (filtered_brushes.next != &filtered_brushes)
1066                 Sys_Printf("WARNING: filtered_brushes list may not be empty in Map_RegionSelectedBrushes\n");
1067 #endif
1068
1069         if (active_brushes.next == &active_brushes)
1070         {
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;
1074         }
1075         else
1076         {
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;
1082         }
1083
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;
1089
1090         // deselect patches
1091         for (brush_t *b = active_brushes.next; b != &active_brushes; b = b->next)
1092           if (b->patchBrush)
1093             b->pPatch->bSelected = false;
1094
1095         // clear selected_brushes
1096         selected_brushes.next = selected_brushes.prev = &selected_brushes;
1097
1098         Sys_UpdateWindows (W_ALL);
1099 }
1100
1101
1102 /*
1103 ===========
1104 Map_RegionXY
1105 ===========
1106 */
1107 void Map_RegionXY (void)
1108 {
1109         Map_RegionOff ();
1110
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;
1117         Map_ApplyRegion ();
1118 }
1119
1120 /*
1121 ===========
1122 Map_RegionTallBrush
1123 ===========
1124 */
1125 void Map_RegionTallBrush (void)
1126 {
1127   brush_t *b;
1128
1129   if (!QE_SingleBrush ())
1130     return;
1131
1132   b = selected_brushes.next;
1133
1134   Map_RegionOff ();
1135
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;
1140
1141   Undo_Start("delete");
1142   Undo_AddBrushList(&selected_brushes);
1143   Undo_AddEntity(b->owner);
1144   Select_Delete ();
1145   Undo_EndBrushList(&selected_brushes);
1146   Undo_End();
1147
1148   Map_ApplyRegion ();
1149 }
1150
1151 /*
1152 ===========
1153 Map_RegionBrush
1154 ===========
1155 */
1156 void Map_RegionBrush (void)
1157 {
1158   brush_t *b;
1159
1160   if (!QE_SingleBrush ())
1161     return;
1162
1163   b = selected_brushes.next;
1164
1165   Map_RegionOff ();
1166
1167   VectorCopy (b->mins, region_mins);
1168   VectorCopy (b->maxs, region_maxs);
1169
1170   Undo_Start("delete");
1171   Undo_AddBrushList(&selected_brushes);
1172   Undo_AddEntity(b->owner);
1173   Select_Delete ();
1174   Undo_EndBrushList(&selected_brushes);
1175   Undo_End();
1176
1177   Map_ApplyRegion ();
1178 }
1179
1180 GList *find_string(GList *glist, const char *buf)
1181 {
1182   while (glist)
1183   {
1184     if (strcmp((char *)glist->data, buf) == 0)
1185       break; // this name is in our list already
1186     glist = glist->next;
1187   }
1188   return glist;
1189 }
1190
1191 void Map_ImportBuffer(char *buf)
1192 {
1193   Select_Deselect();
1194
1195   Undo_Start("import buffer");
1196
1197   MemStream stream;
1198
1199   stream.Write(buf, strlen(buf));
1200   Map_Import(&stream, "xmap");
1201   stream.Close();
1202
1203   Sys_UpdateWindows (W_ALL);
1204   Sys_MarkMapModified();
1205
1206   Undo_End();
1207 }
1208
1209
1210 //
1211 //================
1212 //Map_ImportFile
1213 //================
1214 //
1215 void Map_ImportFile (const char *filename)
1216 {
1217   FileStream file;
1218         Sys_BeginWait ();
1219
1220   Sys_Printf("Importing map from %s\n",filename);
1221
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);
1227   else
1228     Sys_FPrintf(SYS_ERR, "ERROR: couldn't open %s for read\n", filename);
1229
1230   file.Close();
1231
1232         Sys_UpdateWindows (W_ALL);
1233         modified = true;
1234         Sys_EndWait();
1235 }
1236
1237 //
1238 //===========
1239 //Map_SaveSelected
1240 //===========
1241 //
1242 // Saves selected world brushes and whole entities with partial/full selections
1243 //
1244 void Map_SaveSelected(const char* filename)
1245 {
1246   FileStream file;
1247
1248   Sys_Printf("Saving selection to %s\n",filename);
1249
1250   const char* type = strrchr(filename,'.');
1251   if(type!=NULL) type++;
1252   if(file.Open(filename, "w"))
1253     Map_Export (&file, type, false, true);
1254   else
1255     Sys_FPrintf(SYS_ERR, "ERROR: failed to open %s for write\n", filename);
1256
1257   file.Close();
1258
1259 }
1260
1261 //
1262 //===========
1263 //Map_SaveSelected
1264 //===========
1265 //
1266 // Saves selected world brushes and whole entities with partial/full selections
1267 //
1268 void Map_SaveSelected (MemStream* pMemFile, MemStream* pPatchFile)
1269 {
1270   Map_Export (pMemFile, "xmap", false, true);
1271
1272  /*
1273         // write world entity first
1274         Entity_WriteSelected(world_entity, pMemFile);
1275
1276         // then write all other ents
1277         count = 1;
1278         for (e=entities.next ; e != &entities ; e=next)
1279         {
1280                 MemFile_fprintf(pMemFile, "// entity %i\n", count);
1281                 count++;
1282                 Entity_WriteSelected(e, pMemFile);
1283                 next = e->next;
1284         }
1285
1286   //if (pPatchFile)
1287   //  Patch_WriteFile(pPatchFile);
1288   */
1289 }
1290
1291
1292 void MemFile_fprintf(MemStream* pMemFile, const char* pText, ...)
1293 {
1294   char Buffer[4096];
1295   va_list args;
1296         va_start (args,pText);
1297   vsprintf(Buffer, pText, args);
1298   pMemFile->Write(Buffer, strlen(Buffer));
1299 }
1300
1301 /*!
1302 ==============
1303 Region_SpawnPoint
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
1307 ==============
1308 */
1309 void Region_SpawnPoint(FILE *f)
1310 {
1311   // write the info_player_start, we use the camera position
1312   fprintf (f, "{\n");
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]);
1319   fprintf (f, "}\n");
1320 }