]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - radiant/map.cpp
uncrustify! now the code is only ugly on the *inside*
[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         Map_Free();
53 }
54
55
56 bool g_bCancel_Map_LoadFile;  // Hydra: moved this here
57
58 // TTimo
59 // need that in a variable, will have to tweak depending on the game
60 int g_MaxWorldCoord = 64 * 1024;
61 int g_MinWorldCoord = -64 * 1024;
62
63 // the max size we allow on brushes, this is dependant on world coords too
64 // makes more sense to say smaller I think?
65 int g_MaxBrushSize = ( g_MaxWorldCoord - 1 ) * 2;
66
67 void AddRegionBrushes( void );
68 void RemoveRegionBrushes( void );
69
70 /*
71    =============================================================
72
73    Cross map selection saving
74
75    this could fuck up if you have only part of a complex entity selected...
76    =============================================================
77  */
78
79 brush_t between_brushes;
80 entity_t between_entities;
81
82 bool g_bRestoreBetween = false;
83
84 void Map_SaveBetween( void ){
85         if ( g_pParentWnd->ActiveXY() ) {
86                 g_bRestoreBetween = true;
87                 g_pParentWnd->ActiveXY()->Copy();
88         }
89         return;
90 }
91
92 void Map_RestoreBetween( void ){
93         if ( g_pParentWnd->ActiveXY() && g_bRestoreBetween ) {
94                 g_pParentWnd->ActiveXY()->Paste();
95         }
96 }
97
98 //============================================================================
99
100 bool CheckForTinyBrush( brush_t* b, int n, float fSize ){
101         bool bTiny = false;
102         for ( int i = 0 ; i < 3 ; i++ )
103         {
104                 if ( b->maxs[i] - b->mins[i] < fSize ) {
105                         bTiny = true;
106                 }
107         }
108         if ( bTiny ) {
109                 Sys_Printf( "Possible problem brush (too small) #%i ", n );
110         }
111         return bTiny;
112 }
113
114 void Map_BuildBrushData( void ){
115         brush_t *b, *next;
116
117         if ( active_brushes.next == NULL ) {
118                 return;
119         }
120
121         Sys_BeginWait(); // this could take a while
122
123         int n = 0;
124         for ( b = active_brushes.next ; b != NULL && b != &active_brushes ; b = next )
125         {
126                 next = b->next;
127                 Brush_Build( b, true, false, false );
128                 if ( !b->brush_faces || ( g_PrefsDlg.m_bCleanTiny && CheckForTinyBrush( b, n++, g_PrefsDlg.m_fTinySize ) ) ) {
129                         Brush_Free( b );
130                         Sys_Printf( "Removed degenerate brush\n" );
131                 }
132         }
133         Sys_EndWait();
134 }
135
136 entity_t *Map_FindClass( const char *cname ){
137         entity_t    *ent;
138
139         for ( ent = entities.next ; ent != &entities ; ent = ent->next )
140         {
141                 if ( !strcmp( cname, ValueForKey( ent, "classname" ) ) ) {
142                         return ent;
143                 }
144         }
145         return NULL;
146 }
147
148 /*
149    ================
150    Map_Free
151    free all map elements, reinitialize the structures that depend on them
152    ================
153  */
154 void Map_Free( void ){
155         g_bRestoreBetween = false;
156         if ( selected_brushes.next &&
157                  ( selected_brushes.next != &selected_brushes ) ) {
158                 if ( gtk_MessageBox( g_pParentWnd->m_pWidget, "Copy selection?", " ", MB_YESNO ) == IDYES ) {
159                         Map_SaveBetween();
160                 }
161         }
162
163         QERApp_ActiveShaders_SetInUse( false );
164         Pointfile_Clear();
165         g_qeglobals.d_num_entities = 0;
166
167         if ( !active_brushes.next ) {
168                 // first map
169                 active_brushes.prev = active_brushes.next = &active_brushes;
170                 selected_brushes.prev = selected_brushes.next = &selected_brushes;
171                 filtered_brushes.prev = filtered_brushes.next = &filtered_brushes;
172                 entities.prev = entities.next = &entities;
173         }
174         else
175         {
176                 // free selected faces array
177                 g_ptrSelectedFaces.RemoveAll();
178                 g_ptrSelectedFaceBrushes.RemoveAll();
179                 while ( active_brushes.next != &active_brushes )
180                         Brush_Free( active_brushes.next );
181                 while ( selected_brushes.next != &selected_brushes )
182                         Brush_Free( selected_brushes.next );
183                 while ( filtered_brushes.next != &filtered_brushes )
184                         Brush_Free( filtered_brushes.next );
185                 while ( entities.next != &entities )
186                         Entity_Free( entities.next );
187         }
188
189         if ( world_entity ) {
190                 Entity_Free( world_entity );
191         }
192         world_entity = NULL;
193 }
194
195 entity_t *AngledEntity(){
196         entity_t *ent = Map_FindClass( "info_player_start" );
197         if ( !ent ) {
198                 ent = Map_FindClass( "info_player_deathmatch" );
199         }
200         if ( !ent ) {
201                 ent = Map_FindClass( "info_player_deathmatch" );
202         }
203         if ( !ent ) {
204                 ent = Map_FindClass( "team_CTF_redplayer" );
205         }
206         if ( !ent ) {
207                 ent = Map_FindClass( "team_CTF_blueplayer" );
208         }
209         if ( !ent ) {
210                 ent = Map_FindClass( "team_CTF_redspawn" );
211         }
212         if ( !ent ) {
213                 ent = Map_FindClass( "team_CTF_bluespawn" );
214         }
215         return ent;
216 }
217
218 //
219 // move the view to a start position
220 //
221 void Map_StartPosition(){
222         entity_t *ent = AngledEntity();
223
224         g_pParentWnd->GetCamWnd()->Camera()->angles[PITCH] = 0;
225         if ( ent ) {
226                 GetVectorForKey( ent, "origin", g_pParentWnd->GetCamWnd()->Camera()->origin );
227                 GetVectorForKey( ent, "origin", g_pParentWnd->GetXYWnd()->GetOrigin() );
228                 g_pParentWnd->GetCamWnd()->Camera()->angles[YAW] = FloatForKey( ent, "angle" );
229         }
230         else
231         {
232                 g_pParentWnd->GetCamWnd()->Camera()->angles[YAW] = 0;
233                 VectorCopy( vec3_origin, g_pParentWnd->GetCamWnd()->Camera()->origin );
234                 VectorCopy( vec3_origin, g_pParentWnd->GetXYWnd()->GetOrigin() );
235         }
236 }
237
238 void Map_FreeEntities( CPtrArray *ents ){
239         int i, j, num_ents, num_brushes;
240         entity_t* e;
241         CPtrArray* brushes;
242
243         num_ents = ents->GetSize();
244         for ( i = 0; i < num_ents; i++ )
245         {
246                 e = (entity_t*)ents->GetAt( i );
247                 brushes = (CPtrArray*)e->pData;
248                 num_brushes = brushes->GetSize();
249                 for ( j = 0; j < num_brushes; j++ )
250                         Brush_Free( (brush_t*)brushes->GetAt( j ) );
251                 brushes->RemoveAll();
252                 delete (CPtrArray*)e->pData;
253                 e->pData = NULL;
254                 Entity_Free( e );
255         }
256         ents->RemoveAll();
257 }
258
259 /*!\todo Possibly make the import Undo-friendly by calling Undo_End for new brushes and ents */
260 void Map_ImportEntities( CPtrArray *ents, bool bAddSelected = false ){
261         int num_ents, num_brushes;
262         CPtrArray *brushes;
263         vec3_t mins, maxs;
264         entity_t *e;
265         brush_t *b;
266         face_t *f;
267         int i,j;
268
269         GPtrArray *new_ents = g_ptr_array_new();
270
271         g_qeglobals.bPrimitBrushes = false;
272
273         brush_t *pBrushList = ( bAddSelected ) ? &selected_brushes : &active_brushes;
274
275         bool bDoneBPCheck = false;
276         g_qeglobals.bNeedConvert = false;
277         // HACK: find out if this map file was a BP one
278         // check the first brush in the file that is NOT a patch
279         // this will not be necessary when we allow both formats in the same file
280         num_ents = ents->GetSize();
281         for ( i = 0; !bDoneBPCheck && i < num_ents; i++ )
282         {
283                 e = (entity_t*)ents->GetAt( i );
284                 brushes = (CPtrArray*)e->pData;
285                 num_brushes = brushes->GetSize();
286                 for ( j = 0; !bDoneBPCheck && j < num_brushes; j++ )
287                 {
288                         /*!todo Allow mixing texdef formats per-face. */
289                         b = (brush_t *)brushes->GetAt( j );
290                         if ( b->patchBrush ) {
291                                 continue;
292                         }
293                         bDoneBPCheck = true;
294                         int BP_param = -1;
295                         if ( b->bBrushDef && !g_qeglobals.m_bBrushPrimitMode ) {
296                                 BP_param = 0;
297                         }
298                         else if ( !b->bBrushDef && g_qeglobals.m_bBrushPrimitMode ) {
299                                 BP_param = 1;
300                         }
301
302                         if ( BP_param != -1 ) {
303                                 switch ( BP_MessageBox( BP_param ) )
304                                 {
305                                 case 0:
306                                         Map_FreeEntities( ents );
307                                         return;
308                                 case 1:
309                                         g_qeglobals.bNeedConvert = true;
310                                         break;
311                                 case 2:
312                                         g_qeglobals.bNeedConvert = false;
313                                         break;
314                                 }
315                         }
316                 }
317         }
318
319         // process the entities into the world geometry
320         num_ents = ents->GetSize();
321         for ( i = 0; i < num_ents; i++ )
322         {
323                 num_brushes = 0;
324                 e = (entity_t*)ents->GetAt( i );
325                 brushes = (CPtrArray*)e->pData;
326
327                 num_brushes = brushes->GetSize();
328                 // link brushes into entity
329                 for ( j = 0; j < num_brushes; j++ )
330                 {
331                         Entity_LinkBrush( e, (brush_t *)brushes->GetAt( j ) );
332                         g_qeglobals.d_parsed_brushes++;
333                 }
334                 brushes->RemoveAll();
335                 delete brushes;
336                 e->pData = NULL;
337
338                 // set entity origin
339                 GetVectorForKey( e, "origin", e->origin );
340                 // set entity eclass
341                 /*!\todo Make SetKeyValue check for "classname" change and assign appropriate eclass */
342                 e->eclass = Eclass_ForName( ValueForKey( e, "classname" ),
343                                                                         ( e->brushes.onext != &e->brushes ) );
344
345                 // go through all parsed brushes and build stuff
346                 for ( b = e->brushes.onext; b != &e->brushes; b = b->onext )
347                 {
348                         for ( f = b->brush_faces; f != NULL; f = f->next )
349                         {
350                                 f->pShader = QERApp_Shader_ForName( f->texdef.GetName() );
351                                 f->d_texture = f->pShader->getTexture();
352                         }
353
354                         // when brushes are in final state, build the planes and windings
355                         // NOTE: also converts BP brushes if g_qeglobals.bNeedConvert is true
356                         Brush_Build( b );
357                 }
358
359 //#define TERRAIN_HACK
360 #undef TERRAIN_HACK
361
362 #ifdef TERRAIN_HACK
363                 if ( ( strcmp( ValueForKey( e, "terrain" ),"1" ) == 0 && strcmp( e->eclass->name,"func_group" ) == 0 ) ) {
364
365                         // two aux pointers to the shaders used in the terrain entity
366                         // we don't keep refcount on them since they are only temporary
367                         // this avoids doing expensive lookups by name for all faces
368                         IShader *pTerrainShader, *pCaulk;
369
370                         pTerrainShader = NULL;
371                         pCaulk = QERApp_Shader_ForName( SHADER_CAULK );
372
373                         for ( b = e->brushes.onext; b != &e->brushes; b = b->onext )
374                         {
375                                 if ( pTerrainShader == NULL ) {
376                                         for ( f = b->brush_faces; f != NULL; f = f->next )
377                                                 if ( strcmp( f->texdef.GetName(), SHADER_CAULK ) != 0 ) {
378                                                         pTerrainShader = f->pShader;
379                                                 }
380                                 }
381
382                                 if ( pTerrainShader ) {
383                                         for ( f = b->brush_faces; f != NULL; f = f->next )
384                                         {
385                                                 if ( strcmp( f->texdef.GetName(), SHADER_CAULK ) != 0 ) { // not caulk
386                                                         Face_SetShader( f, pTerrainShader->getName() );
387                                                 }
388                                                 else{
389                                                         Face_SetShader( f, pCaulk->getName() );
390                                                 }
391                                         }
392                                 }
393                                 else{
394                                         Sys_Printf( "WARNING: no terrain shader found for brush\n" );
395                                 }
396                         }
397                 }
398 #endif
399
400 #define PATCH_HACK
401 #ifdef PATCH_HACK
402                 for ( b = e->brushes.onext; b != &e->brushes; b = b->onext )
403                 {
404                         // patch hack, to be removed when dependency on brush_faces is removed
405                         if ( b->patchBrush ) {
406                                 Patch_CalcBounds( b->pPatch, mins, maxs );
407                                 for ( int i = 0; i < 3; i++ )
408                                 {
409                                         if ( (int)mins[i] == (int)maxs[i] ) {
410                                                 mins[i] -= 4;
411                                                 maxs[i] += 4;
412                                         }
413                                 }
414                                 Brush_Resize( b, mins, maxs );
415                                 Brush_Build( b );
416                         }
417                 }
418 #endif
419                 // add brush for fixedsize entity
420                 if ( e->eclass->fixedsize ) {
421                         vec3_t mins, maxs;
422                         VectorAdd( e->eclass->mins, e->origin, mins );
423                         VectorAdd( e->eclass->maxs, e->origin, maxs );
424                         b = Brush_Create( mins, maxs, &e->eclass->texdef );
425                         Entity_LinkBrush( e, b );
426                         Brush_Build( b );
427                 }
428
429                 for ( b = e->brushes.onext; b != &e->brushes; b = b->onext )
430                         Brush_AddToList( b, pBrushList );
431
432                 if ( strcmp( e->eclass->name, "worldspawn" ) == 0 ) {
433                         if ( world_entity ) {
434                                 while ( e->brushes.onext != &e->brushes )
435                                 {
436                                         b = e->brushes.onext;
437                                         Entity_UnlinkBrush( b );
438                                         Entity_LinkBrush( world_entity, b );
439                                 }
440                                 Entity_Free( e );
441                         }
442                         else
443                         {
444                                 world_entity = e;
445                         }
446                 }
447                 else if ( strcmp( e->eclass->name, "group_info" ) == 0 ) {
448                         // it's a group thing!
449                         Group_Add( e );
450                         Entity_Free( e );
451                 }
452                 else
453                 {
454                         // fix target/targetname collisions
455                         if ( ( g_PrefsDlg.m_bDoTargetFix ) && ( strcmp( ValueForKey( e, "target" ), "" ) != 0 ) ) {
456                                 GPtrArray *t_ents = g_ptr_array_new();
457                                 entity_t *e_target;
458                                 const char *target = ValueForKey( e, "target" );
459                                 qboolean bCollision = FALSE;
460
461                                 // check the current map entities for an actual collision
462                                 for ( e_target = entities.next; e_target != &entities; e_target = e_target->next )
463                                 {
464                                         if ( !strcmp( target, ValueForKey( e_target, "target" ) ) ) {
465                                                 bCollision = TRUE;
466                                                 // make sure the collision is not between two imported entities
467                                                 for ( j = 0; j < (int)new_ents->len; j++ )
468                                                 {
469                                                         if ( e_target == g_ptr_array_index( new_ents, j ) ) {
470                                                                 bCollision = FALSE;
471                                                         }
472                                                 }
473                                         }
474                                 }
475
476                                 // find the matching targeted entity(s)
477                                 if ( bCollision ) {
478                                         for ( j = num_ents - 1; j > 0; j-- )
479                                         {
480                                                 e_target = (entity_t*)ents->GetAt( j );
481                                                 if ( e_target != NULL && e_target != e ) {
482                                                         const char *targetname = ValueForKey( e_target, "targetname" );
483                                                         if ( ( targetname != NULL ) && ( strcmp( target, targetname ) == 0 ) ) {
484                                                                 g_ptr_array_add( t_ents, (gpointer)e_target );
485                                                         }
486                                                 }
487                                         }
488                                         if ( t_ents->len > 0 ) {
489                                                 // link the first to get a unique target/targetname
490                                                 Entity_Connect( e, (entity_t*)g_ptr_array_index( t_ents,0 ) );
491                                                 // set the targetname of the rest of them manually
492                                                 for ( j = 1; j < (int)t_ents->len; j++ )
493                                                         SetKeyValue( (entity_t*)g_ptr_array_index( t_ents, j ), "targetname", ValueForKey( e, "target" ) );
494                                         }
495                                         g_ptr_array_free( t_ents, FALSE );
496                                 }
497                         }
498
499                         // add the entity to the end of the entity list
500                         Entity_AddToList( e, &entities );
501                         g_qeglobals.d_num_entities++;
502
503                         // keep a list of ents added to avoid testing collisions against them
504                         g_ptr_array_add( new_ents, (gpointer)e );
505                 }
506         }
507         g_ptr_array_free( new_ents, FALSE );
508
509         ents->RemoveAll();
510
511         g_qeglobals.bNeedConvert = false;
512 }
513
514 void Map_Import( IDataStream *in, const char *type, bool bAddSelected ){
515         CPtrArray ents;
516
517         g_pParentWnd->GetSynapseClient().ImportMap( in, &ents, type );
518         Map_ImportEntities( &ents, bAddSelected );
519 }
520
521 /*
522    ================
523    Map_LoadFile
524    ================
525  */
526 void Map_LoadFile( const char *filename ){
527         clock_t start, finish;
528         double elapsed_time;
529         start = clock();
530
531         Sys_BeginWait();
532         Select_Deselect();
533         /*!
534            \todo FIXME TTimo why is this commented out?
535            stability issues maybe? or duplicate feature?
536            forcing to show the console during map load was a good thing IMO
537          */
538         //SetInspectorMode(W_CONSOLE);
539         Sys_Printf( "Loading map from %s\n", filename );
540
541         Map_Free();
542         //++timo FIXME: maybe even easier to have Group_Init called from Map_Free?
543         Group_Init();
544         g_qeglobals.d_num_entities = 0;
545         g_qeglobals.d_parsed_brushes = 0;
546
547
548         // cancel the map loading process
549         // used when conversion between standard map format and BP format is required and the user cancels the process
550         g_bCancel_Map_LoadFile = false;
551
552         strcpy( currentmap, filename );
553
554         g_bScreenUpdates = false; // leo: avoid redraws while loading the map (see fenris:1952)
555
556         // prepare to let the map module do the parsing
557         FileStream file;
558         const char* type = strrchr( filename,'.' );
559         if ( type != NULL ) {
560                 type++;
561         }
562         // NOTE TTimo opening has binary doesn't make a lot of sense
563         // but opening as text confuses the scriptlib parser
564         // this may be a problem if we "rb" and use the XML parser, might have an incompatibility
565         if ( file.Open( filename, "rb" ) ) {
566                 Map_Import( &file, type );
567         }
568         else{
569                 Sys_FPrintf( SYS_ERR, "ERROR: failed to open %s for read\n", filename );
570         }
571         file.Close();
572
573         g_bScreenUpdates = true;
574
575         if ( g_bCancel_Map_LoadFile ) {
576                 Sys_Printf( "Map_LoadFile canceled\n" );
577                 Map_New();
578                 Sys_EndWait();
579                 return;
580         }
581
582         if ( !world_entity ) {
583                 Sys_Printf( "No worldspawn in map.\n" );
584                 Map_New();
585                 Sys_EndWait();
586                 return;
587         }
588         finish = clock();
589         elapsed_time = (double)( finish - start ) / CLOCKS_PER_SEC;
590
591         Sys_Printf( "--- LoadMapFile ---\n" );
592         Sys_Printf( "%s\n", filename );
593
594         Sys_Printf( "%5i brushes\n",  g_qeglobals.d_parsed_brushes );
595         Sys_Printf( "%5i entities\n", g_qeglobals.d_num_entities );
596         Sys_Printf( "%5.2f second(s) load time\n", elapsed_time );
597
598         Sys_EndWait();
599
600         Map_RestoreBetween();
601
602         //
603         // move the view to a start position
604         //
605         Map_StartPosition();
606
607         Map_RegionOff();
608
609         modified = false;
610         Sys_SetTitle( filename );
611
612         Texture_ShowInuse();
613         QERApp_SortActiveShaders();
614
615         Sys_UpdateWindows( W_ALL );
616 }
617
618 /*!
619    ===========
620    Supporting functions for Map_SaveFile, builds a CPtrArray with the filtered / non filtered brushes
621    ===========
622  */
623 void CleanFilter( entity_t *ent ){
624         if ( ent->pData ) {
625                 delete static_cast<CPtrArray*>( ent->pData );
626                 ent->pData = NULL;
627         }
628 }
629
630 /*!
631    filters out the region brushes if necessary
632    returns true if this entity as a whole is out of the region
633    (if all brushes are filtered out, then the entity will be completely dropped .. except if it's worldspawn of course)
634  */
635 bool FilterChildren( entity_t *ent, bool bRegionOnly = false, bool bSelectedOnly = false ){
636         if ( ent->brushes.onext == &ent->brushes ) {
637                 return false;
638         }
639         // entity without a brush, ignore it... this can be caused by Undo
640
641         // filter fixedsize ents by their eclass bounding box
642         // don't add their brushes
643         if ( ent->eclass->fixedsize ) {
644                 if ( bSelectedOnly && !IsBrushSelected( ent->brushes.onext ) ) {
645                         return false;
646                 }
647
648                 if ( bRegionOnly && region_active ) {
649                         for ( int i = 0 ; i < 3 ; i++ )
650                         {
651                                 if ( ( ent->origin[i] + ent->eclass->mins[i] ) > region_maxs[i] ) {
652                                         return false;
653                                 }
654                                 if ( ( ent->origin[i] + ent->eclass->maxs[i] ) < region_mins[i] ) {
655                                         return false;
656                                 }
657                         }
658                 }
659         }
660         else
661         {
662                 for ( brush_t *b = ent->brushes.onext ; b != &ent->brushes ; b = b->onext )
663                 {
664                         // set flag to use brushprimit_texdef
665                         if ( g_qeglobals.m_bBrushPrimitMode ) {
666                                 b->bBrushDef = true;
667                         }
668                         else{
669                                 b->bBrushDef = false;
670                         }
671
672                         // add brush, unless it's excluded by region
673                         if ( !( bRegionOnly && Map_IsBrushFiltered( b ) ) &&
674                                  !( bSelectedOnly && !IsBrushSelected( b ) ) ) {
675                                 ( (CPtrArray*)ent->pData )->Add( b );
676                         }
677                 }
678
679                 if ( ( (CPtrArray*)ent->pData )->GetSize() <= 0 ) {
680                         return false;
681                 }
682         }
683         return true;
684 }
685
686 entity_t *region_startpoint = NULL;
687 void Map_ExportEntities( CPtrArray* ents, bool bRegionOnly = false, bool bSelectedOnly = false ){
688         int i;
689         entity_t *e;
690
691         /*!
692            \todo the entity_t needs to be reworked and asbtracted some more
693
694            keeping the entity_t as the struct providing access to a list of map objects, a list of epairs and various other info?
695            but separating some more the data that belongs to the entity_t and the 'sons' data
696            on a side note, I don't think that doing that with linked list would be a good thing
697
698            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
699            the next step is very likely to be a change of the brush_t* to a more abstract object?
700          */
701
702         FilterChildren( world_entity, bRegionOnly, bSelectedOnly );
703         ents->Add( world_entity );
704
705         for ( e = entities.next ; e != &entities ; e = e->next )
706         {
707                 // not sure this still happens, probably safe to leave it in
708                 if ( ( !strcmp( ValueForKey( e, "classname" ), "worldspawn" ) ) && ( e != world_entity ) ) {
709                         Sys_FPrintf( SYS_ERR, "Dropping parasite worldspawn entity\n" );
710                         continue;
711                 }
712
713                 // entities which brushes are completely filtered out by regioning are not printed to the map
714                 if ( FilterChildren( e, bRegionOnly, bSelectedOnly ) ) {
715                         ents->Add( e );
716                 }
717         }
718
719         if ( bRegionOnly && region_active ) {
720                 for ( i = 0; i < 6; i++ )
721                         ( (CPtrArray*)world_entity->pData )->Add( region_sides[i] );
722
723                 ents->Add( region_startpoint );
724         }
725 }
726
727 void Map_Export( IDataStream *out, const char *type, bool bRegionOnly, bool bSelectedOnly ){
728         entity_t  *e;
729
730         CPtrArray ents;
731
732         if ( bRegionOnly && region_active ) {
733                 AddRegionBrushes();
734         }
735
736         // create the filters
737         world_entity->pData = new CPtrArray();
738         for ( e = entities.next; e != &entities; e = e->next )
739                 e->pData = new CPtrArray();
740
741         Map_ExportEntities( &ents, bRegionOnly, bSelectedOnly );
742
743         g_pParentWnd->GetSynapseClient().ExportMap( &ents, out, type );
744
745         // cleanup the filters
746         CleanFilter( world_entity );
747         for ( e = entities.next ; e != &entities ; e = e->next )
748                 CleanFilter( e );
749
750         if ( bRegionOnly && region_active ) {
751                 RemoveRegionBrushes();
752         }
753 }
754
755 const char* filename_get_extension( const char* filename ){
756         const char* type = strrchr( filename,'.' );
757         if ( type != NULL ) {
758                 return ++type;
759         }
760         return "";
761 }
762
763 /*
764    ===========
765    Map_SaveFile
766    \todo FIXME remove the use_region, this is broken .. work with a global flag to set region mode or not
767    ===========
768  */
769 void Map_SaveFile( const char *filename, qboolean use_region ){
770         clock_t start, finish;
771         double elapsed_time;
772         start = clock();
773         Sys_Printf( "Saving map to %s\n",filename );
774
775         Pointfile_Clear();
776
777         if ( !use_region ) {
778                 char backup[1024];
779
780                 // rename current to .bak
781                 strcpy( backup, filename );
782                 StripExtension( backup );
783                 strcat( backup, ".bak" );
784                 unlink( backup );
785                 rename( filename, backup );
786         }
787
788         Sys_Printf( "Map_SaveFile: %s\n", filename );
789
790         // build the out data stream
791         FileStream file;
792         if ( !file.Open( filename,"w" ) ) {
793                 Sys_FPrintf( SYS_ERR, "ERROR: couldn't open %s for write\n", filename );
794                 return;
795         }
796
797         // extract filetype
798         Map_Export( &file, filename_get_extension( filename ), use_region );
799
800         file.Close();
801
802         finish = clock();
803         elapsed_time = (double)( finish - start ) / CLOCKS_PER_SEC;
804
805         Sys_Printf( "Saved in %-.2f second(s).\n",elapsed_time );
806         modified = false;
807
808         if ( !strstr( filename, "autosave" ) ) {
809                 Sys_SetTitle( filename );
810         }
811
812         if ( !use_region ) {
813                 time_t timer;
814
815                 time( &timer );
816
817                 Sys_Beep();
818
819                 Sys_Status( "Saved.", 0 );
820         }
821 }
822
823 /*
824    ===========
825    Map_New
826
827    ===========
828  */
829 void Map_New( void ){
830         Sys_Printf( "Map_New\n" );
831         Map_Free();
832
833         strcpy( currentmap, "unnamed.map" );
834         Sys_SetTitle( currentmap );
835
836         world_entity = (entity_s*)qmalloc( sizeof( *world_entity ) );
837         world_entity->brushes.onext =
838                 world_entity->brushes.oprev = &world_entity->brushes;
839         SetKeyValue( world_entity, "classname", "worldspawn" );
840         world_entity->eclass = Eclass_ForName( "worldspawn", true );
841
842         g_pParentWnd->GetCamWnd()->Camera()->angles[YAW] = 0;
843         g_pParentWnd->GetCamWnd()->Camera()->angles[PITCH] = 0;
844         VectorCopy( vec3_origin, g_pParentWnd->GetCamWnd()->Camera()->origin );
845         g_pParentWnd->GetCamWnd()->Camera()->origin[2] = 48;
846         VectorCopy( vec3_origin, g_pParentWnd->GetXYWnd()->GetOrigin() );
847
848         Map_RestoreBetween();
849
850         Group_Init();
851
852         Sys_UpdateWindows( W_ALL );
853         modified = false;
854 }
855
856 /*
857    ===========================================================
858
859    REGION
860
861    ===========================================================
862  */
863 qboolean region_active;
864 vec3_t region_mins = {g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord};
865 vec3_t region_maxs = {g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord};
866
867 brush_t *region_sides[6];
868
869 /*
870    ===========
871    AddRegionBrushes
872    a regioned map will have temp walls put up at the region boundary
873    \todo TODO TTimo old implementation of region brushes
874    we still add them straight in the worldspawn and take them out after the map is saved
875    with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
876    ===========
877  */
878 void AddRegionBrushes( void ){
879         vec3_t mins, maxs;
880         int i;
881         texdef_t td;
882
883         if ( !region_active ) {
884 #ifdef _DEBUG
885                 Sys_FPrintf( SYS_WRN, "Unexpected AddRegionBrushes call.\n" );
886 #endif
887                 return;
888         }
889
890         memset( &td, 0, sizeof( td ) );
891         td.SetName( SHADER_NOT_FOUND );
892
893         // set mins
894         VectorSet( mins, region_mins[0] - 32, region_mins[1] - 32, region_mins[2] - 32 );
895
896         // vary maxs
897         for ( i = 0; i < 3; i++ )
898         {
899                 VectorSet( maxs, region_maxs[0] + 32, region_maxs[1] + 32, region_maxs[2] + 32 );
900                 maxs[i] = region_mins[i];
901                 region_sides[i] = Brush_Create( mins, maxs, &td );
902         }
903
904         // set maxs
905         VectorSet( maxs, region_maxs[0] + 32, region_maxs[1] + 32, region_maxs[2] + 32 );
906
907         // vary mins
908         for ( i = 0; i < 3; i++ )
909         {
910                 VectorSet( mins, region_mins[0] - 32, region_mins[1] - 32, region_mins[2] - 32 );
911                 mins[i] = region_maxs[i];
912                 region_sides[i + 3] = Brush_Create( mins, maxs, &td );
913         }
914
915
916         // this is a safe check, but it should not really happen anymore
917         vec3_t vOrig;
918         VectorSet( vOrig,
919                            (int)g_pParentWnd->GetCamWnd()->Camera()->origin[0],
920                            (int)g_pParentWnd->GetCamWnd()->Camera()->origin[1],
921                            (int)g_pParentWnd->GetCamWnd()->Camera()->origin[2] );
922
923         for ( i = 0 ; i < 3 ; i++ )
924         {
925                 if ( vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i] ) {
926                         Sys_FPrintf( SYS_ERR, "Camera is NOT in the region, it's likely that the region won't compile correctly\n" );
927                 }
928         }
929
930         // write the info_playerstart
931         region_startpoint = Entity_Alloc();
932         SetKeyValue( region_startpoint, "classname", "info_player_start" );
933         region_startpoint->eclass = Eclass_ForName( "info_player_start", false );
934         char sTmp[1024];
935         sprintf( sTmp, "%d %d %d", (int)vOrig[0], (int)vOrig[1], (int)vOrig[2] );
936         SetKeyValue( region_startpoint, "origin", sTmp );
937         sprintf( sTmp, "%d", (int)g_pParentWnd->GetCamWnd()->Camera()->angles[YAW] );
938         SetKeyValue( region_startpoint, "angle", sTmp );
939         // empty array of children
940         region_startpoint->pData = new CPtrArray;
941 }
942
943 void RemoveRegionBrushes( void ){
944         int i;
945
946         if ( !region_active ) {
947                 return;
948         }
949         for ( i = 0 ; i < 6 ; i++ )
950                 Brush_Free( region_sides[i] );
951
952         CleanFilter( region_startpoint );
953         Entity_Free( region_startpoint );
954 }
955
956 qboolean Map_IsBrushFiltered( brush_t *b ){
957         int i;
958
959         for ( i = 0 ; i < 3 ; i++ )
960         {
961                 if ( b->mins[i] > region_maxs[i] ) {
962                         return true;
963                 }
964                 if ( b->maxs[i] < region_mins[i] ) {
965                         return true;
966                 }
967         }
968         return false;
969 }
970
971 /*
972    ===========
973    Map_RegionOff
974
975    Other filtering options may still be on
976    ===========
977  */
978 void Map_RegionOff( void ){
979         brush_t *b, *next;
980         int i;
981
982         region_active = false;
983         for ( i = 0 ; i < 3 ; i++ )
984         {
985                 region_maxs[i] = g_MaxWorldCoord - 64;
986                 region_mins[i] = g_MinWorldCoord + 64;
987         }
988
989         for ( b = filtered_brushes.next ; b != &filtered_brushes ; b = next )
990         {
991                 next = b->next;
992                 if ( Map_IsBrushFiltered( b ) ) {
993                         continue;       // still filtered
994                 }
995                 Brush_RemoveFromList( b );
996                 if ( active_brushes.next == NULL || active_brushes.prev == NULL ) {
997                         active_brushes.next = &active_brushes;
998                         active_brushes.prev = &active_brushes;
999                 }
1000                 Brush_AddToList( b, &active_brushes );
1001                 b->bFiltered = FilterBrush( b );
1002         }
1003         Sys_UpdateWindows( W_ALL );
1004 }
1005
1006 void Map_ApplyRegion( void ){
1007         brush_t *b, *next;
1008
1009         region_active = true;
1010         for ( b = active_brushes.next ; b != &active_brushes ; b = next )
1011         {
1012                 next = b->next;
1013                 if ( !Map_IsBrushFiltered( b ) ) {
1014                         continue;       // still filtered
1015                 }
1016                 Brush_RemoveFromList( b );
1017                 Brush_AddToList( b, &filtered_brushes );
1018         }
1019
1020         Sys_UpdateWindows( W_ALL );
1021 }
1022
1023
1024 /*
1025    ========================
1026    Map_RegionSelectedBrushes
1027    ========================
1028  */
1029 void Map_RegionSelectedBrushes( void ){
1030         Map_RegionOff();
1031
1032         if ( selected_brushes.next == &selected_brushes ) { // nothing selected
1033                 Sys_Printf( "Tried to region with no selection...\n" );
1034                 return;
1035         }
1036         region_active = true;
1037         Select_GetBounds( region_mins, region_maxs );
1038
1039 #ifdef _DEBUG
1040         if ( filtered_brushes.next != &filtered_brushes ) {
1041                 Sys_Printf( "WARNING: filtered_brushes list may not be empty in Map_RegionSelectedBrushes\n" );
1042         }
1043 #endif
1044
1045         if ( active_brushes.next == &active_brushes ) {
1046                 // just have an empty filtered_brushes list
1047                 // this happens if you set region after selecting all the brushes in your map (some weird people do that, ask MrE!)
1048                 filtered_brushes.next = filtered_brushes.prev = &filtered_brushes;
1049         }
1050         else
1051         {
1052                 // move the entire active_brushes list to filtered_brushes
1053                 filtered_brushes.next = active_brushes.next;
1054                 filtered_brushes.prev = active_brushes.prev;
1055                 filtered_brushes.next->prev = &filtered_brushes;
1056                 filtered_brushes.prev->next = &filtered_brushes;
1057         }
1058
1059         // move the entire selected_brushes list to active_brushes
1060         active_brushes.next = selected_brushes.next;
1061         active_brushes.prev = selected_brushes.prev;
1062         active_brushes.next->prev = &active_brushes;
1063         active_brushes.prev->next = &active_brushes;
1064
1065         // deselect patches
1066         for ( brush_t *b = active_brushes.next; b != &active_brushes; b = b->next )
1067                 if ( b->patchBrush ) {
1068                         b->pPatch->bSelected = false;
1069                 }
1070
1071         // clear selected_brushes
1072         selected_brushes.next = selected_brushes.prev = &selected_brushes;
1073
1074         Sys_UpdateWindows( W_ALL );
1075 }
1076
1077
1078 /*
1079    ===========
1080    Map_RegionXY
1081    ===========
1082  */
1083 void Map_RegionXY( void ){
1084         Map_RegionOff();
1085
1086         region_mins[0] = g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5 * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale();
1087         region_maxs[0] = g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5 * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale();
1088         region_mins[1] = g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5 * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale();
1089         region_maxs[1] = g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5 * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale();
1090         region_mins[2] = g_MinWorldCoord + 64;
1091         region_maxs[2] = g_MaxWorldCoord - 64;
1092         Map_ApplyRegion();
1093 }
1094
1095 /*
1096    ===========
1097    Map_RegionTallBrush
1098    ===========
1099  */
1100 void Map_RegionTallBrush( void ){
1101         brush_t *b;
1102
1103         if ( !QE_SingleBrush() ) {
1104                 return;
1105         }
1106
1107         b = selected_brushes.next;
1108
1109         Map_RegionOff();
1110
1111         VectorCopy( b->mins, region_mins );
1112         VectorCopy( b->maxs, region_maxs );
1113         region_mins[2] = g_MinWorldCoord + 64;
1114         region_maxs[2] = g_MaxWorldCoord - 64;
1115
1116         Undo_Start( "delete" );
1117         Undo_AddBrushList( &selected_brushes );
1118         Undo_AddEntity( b->owner );
1119         Select_Delete();
1120         Undo_EndBrushList( &selected_brushes );
1121         Undo_End();
1122
1123         Map_ApplyRegion();
1124 }
1125
1126 /*
1127    ===========
1128    Map_RegionBrush
1129    ===========
1130  */
1131 void Map_RegionBrush( void ){
1132         brush_t *b;
1133
1134         if ( !QE_SingleBrush() ) {
1135                 return;
1136         }
1137
1138         b = selected_brushes.next;
1139
1140         Map_RegionOff();
1141
1142         VectorCopy( b->mins, region_mins );
1143         VectorCopy( b->maxs, region_maxs );
1144
1145         Undo_Start( "delete" );
1146         Undo_AddBrushList( &selected_brushes );
1147         Undo_AddEntity( b->owner );
1148         Select_Delete();
1149         Undo_EndBrushList( &selected_brushes );
1150         Undo_End();
1151
1152         Map_ApplyRegion();
1153 }
1154
1155 GList *find_string( GList *glist, const char *buf ){
1156         while ( glist )
1157         {
1158                 if ( strcmp( (char *)glist->data, buf ) == 0 ) {
1159                         break; // this name is in our list already
1160                 }
1161                 glist = glist->next;
1162         }
1163         return glist;
1164 }
1165
1166 void Map_ImportBuffer( char *buf ){
1167         Select_Deselect();
1168
1169         Undo_Start( "import buffer" );
1170
1171         MemStream stream;
1172
1173         stream.Write( buf, strlen( buf ) );
1174         Map_Import( &stream, "xmap" );
1175         stream.Close();
1176
1177         Sys_UpdateWindows( W_ALL );
1178         Sys_MarkMapModified();
1179
1180         Undo_End();
1181 }
1182
1183
1184 //
1185 //================
1186 //Map_ImportFile
1187 //================
1188 //
1189 void Map_ImportFile( const char *filename ){
1190         FileStream file;
1191         Sys_BeginWait();
1192
1193         Sys_Printf( "Importing map from %s\n",filename );
1194
1195         const char* type = strrchr( filename,'.' );
1196         if ( type != NULL ) {
1197                 type++;
1198         }
1199         /*!\todo Resolve "r" problem in scriptlib" */
1200         if ( file.Open( filename, "rb" ) ) {
1201                 Map_Import( &file, type, true );
1202         }
1203         else{
1204                 Sys_FPrintf( SYS_ERR, "ERROR: couldn't open %s for read\n", filename );
1205         }
1206
1207         file.Close();
1208
1209         Sys_UpdateWindows( W_ALL );
1210         modified = true;
1211         Sys_EndWait();
1212 }
1213
1214 //
1215 //===========
1216 //Map_SaveSelected
1217 //===========
1218 //
1219 // Saves selected world brushes and whole entities with partial/full selections
1220 //
1221 void Map_SaveSelected( const char* filename ){
1222         FileStream file;
1223
1224         Sys_Printf( "Saving selection to %s\n",filename );
1225
1226         const char* type = strrchr( filename,'.' );
1227         if ( type != NULL ) {
1228                 type++;
1229         }
1230         if ( file.Open( filename, "w" ) ) {
1231                 Map_Export( &file, type, false, true );
1232         }
1233         else{
1234                 Sys_FPrintf( SYS_ERR, "ERROR: failed to open %s for write\n", filename );
1235         }
1236
1237         file.Close();
1238
1239 }
1240
1241 //
1242 //===========
1243 //Map_SaveSelected
1244 //===========
1245 //
1246 // Saves selected world brushes and whole entities with partial/full selections
1247 //
1248 void Map_SaveSelected( MemStream* pMemFile, MemStream* pPatchFile ){
1249         Map_Export( pMemFile, "xmap", false, true );
1250
1251         /*
1252            // write world entity first
1253            Entity_WriteSelected(world_entity, pMemFile);
1254
1255            // then write all other ents
1256            count = 1;
1257            for (e=entities.next ; e != &entities ; e=next)
1258            {
1259                MemFile_fprintf(pMemFile, "// entity %i\n", count);
1260                count++;
1261                Entity_WriteSelected(e, pMemFile);
1262                next = e->next;
1263            }
1264
1265            //if (pPatchFile)
1266            //  Patch_WriteFile(pPatchFile);
1267          */
1268 }
1269
1270
1271 void MemFile_fprintf( MemStream* pMemFile, const char* pText, ... ){
1272         char Buffer[4096];
1273         va_list args;
1274         va_start( args,pText );
1275         vsprintf( Buffer, pText, args );
1276         pMemFile->Write( Buffer, strlen( Buffer ) );
1277 }
1278
1279 /*!
1280    ==============
1281    Region_SpawnPoint
1282    push the region spawn point
1283    \todo FIXME TTimo this was in the #1 MAP module implementation (in the core)
1284    not sure it has any use anymore, should prolly drop it
1285    ==============
1286  */
1287 void Region_SpawnPoint( FILE *f ){
1288         // write the info_player_start, we use the camera position
1289         fprintf( f, "{\n" );
1290         fprintf( f, "\"classname\" \"info_player_start\"\n" );
1291         fprintf( f, "\"origin\" \"%i %i %i\"\n",
1292                          (int)g_pParentWnd->GetCamWnd()->Camera()->origin[0],
1293                          (int)g_pParentWnd->GetCamWnd()->Camera()->origin[1],
1294                          (int)g_pParentWnd->GetCamWnd()->Camera()->origin[2] );
1295         fprintf( f, "\"angle\" \"%i\"\n", (int)g_pParentWnd->GetCamWnd()->Camera()->angles[YAW] );
1296         fprintf( f, "}\n" );
1297 }