2 Copyright (C) 1999-2007 id Software, Inc. and contributors.
3 For a list of contributors, see the accompanying CONTRIBUTORS file.
5 This file is part of GtkRadiant.
7 GtkRadiant is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 GtkRadiant is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with GtkRadiant; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24 #if defined ( __linux__ ) || defined ( __APPLE__ )
27 #include "preferences.h"
28 #include "mainframe.h"
32 extern MainFrame* g_pParentWnd;
34 int modified; // for quit confirmation (0 = clean, 1 = unsaved,
35 // 2 = autosaved, but not regular saved)
37 char currentmap[1024];
39 brush_t active_brushes; // brushes currently being displayed
40 brush_t selected_brushes; // highlighted
42 face_t *selected_face;
43 brush_t *selected_face_brush;
45 brush_t filtered_brushes; // brushes that have been filtered or regioned
47 entity_t entities; // head/tail of doubly linked list
49 entity_t *world_entity = NULL; // "classname" "worldspawn" !
56 bool g_bCancel_Map_LoadFile; // Hydra: moved this here
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;
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;
67 void AddRegionBrushes( void );
68 void RemoveRegionBrushes( void );
71 =============================================================
73 Cross map selection saving
75 this could fuck up if you have only part of a complex entity selected...
76 =============================================================
79 brush_t between_brushes;
80 entity_t between_entities;
82 bool g_bRestoreBetween = false;
84 void Map_SaveBetween( void ){
85 if ( g_pParentWnd->ActiveXY() ) {
86 g_bRestoreBetween = true;
87 g_pParentWnd->ActiveXY()->Copy();
92 void Map_RestoreBetween( void ){
93 if ( g_pParentWnd->ActiveXY() && g_bRestoreBetween ) {
94 g_pParentWnd->ActiveXY()->Paste();
98 //============================================================================
100 bool CheckForTinyBrush( brush_t* b, int n, float fSize ){
102 for ( int i = 0 ; i < 3 ; i++ )
104 if ( b->maxs[i] - b->mins[i] < fSize ) {
109 Sys_Printf( "Possible problem brush (too small) #%i ", n );
114 void Map_BuildBrushData( void ){
117 if ( active_brushes.next == NULL ) {
121 Sys_BeginWait(); // this could take a while
124 for ( b = active_brushes.next ; b != NULL && b != &active_brushes ; 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 ) ) ) {
130 Sys_Printf( "Removed degenerate brush\n" );
136 entity_t *Map_FindClass( const char *cname ){
139 for ( ent = entities.next ; ent != &entities ; ent = ent->next )
141 if ( !strcmp( cname, ValueForKey( ent, "classname" ) ) ) {
151 free all map elements, reinitialize the structures that depend on them
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 ) {
163 QERApp_ActiveShaders_SetInUse( false );
165 g_qeglobals.d_num_entities = 0;
167 if ( !active_brushes.next ) {
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;
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 );
189 if ( world_entity ) {
190 Entity_Free( world_entity );
195 entity_t *AngledEntity(){
196 entity_t *ent = Map_FindClass( "info_player_start" );
198 ent = Map_FindClass( "info_player_deathmatch" );
201 ent = Map_FindClass( "info_player_deathmatch" );
204 ent = Map_FindClass( "team_CTF_redplayer" );
207 ent = Map_FindClass( "team_CTF_blueplayer" );
210 ent = Map_FindClass( "team_CTF_redspawn" );
213 ent = Map_FindClass( "team_CTF_bluespawn" );
219 // move the view to a start position
221 void Map_StartPosition(){
222 entity_t *ent = AngledEntity();
224 g_pParentWnd->GetCamWnd()->Camera()->angles[PITCH] = 0;
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" );
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() );
238 void Map_FreeEntities( CPtrArray *ents ){
239 int i, j, num_ents, num_brushes;
243 num_ents = ents->GetSize();
244 for ( i = 0; i < num_ents; i++ )
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;
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;
269 GPtrArray *new_ents = g_ptr_array_new();
271 g_qeglobals.bPrimitBrushes = false;
273 brush_t *pBrushList = ( bAddSelected ) ? &selected_brushes : &active_brushes;
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++ )
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++ )
288 /*!todo Allow mixing texdef formats per-face. */
289 b = (brush_t *)brushes->GetAt( j );
290 if ( b->patchBrush ) {
295 if ( b->bBrushDef && !g_qeglobals.m_bBrushPrimitMode ) {
298 else if ( !b->bBrushDef && g_qeglobals.m_bBrushPrimitMode ) {
302 if ( BP_param != -1 ) {
303 switch ( BP_MessageBox( BP_param ) )
306 Map_FreeEntities( ents );
309 g_qeglobals.bNeedConvert = true;
312 g_qeglobals.bNeedConvert = false;
319 // process the entities into the world geometry
320 num_ents = ents->GetSize();
321 for ( i = 0; i < num_ents; i++ )
324 e = (entity_t*)ents->GetAt( i );
325 brushes = (CPtrArray*)e->pData;
327 num_brushes = brushes->GetSize();
328 // link brushes into entity
329 for ( j = 0; j < num_brushes; j++ )
331 Entity_LinkBrush( e, (brush_t *)brushes->GetAt( j ) );
332 g_qeglobals.d_parsed_brushes++;
334 brushes->RemoveAll();
339 GetVectorForKey( e, "origin", e->origin );
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 ) );
345 // go through all parsed brushes and build stuff
346 for ( b = e->brushes.onext; b != &e->brushes; b = b->onext )
348 for ( f = b->brush_faces; f != NULL; f = f->next )
350 f->pShader = QERApp_Shader_ForName( f->texdef.GetName() );
351 f->d_texture = f->pShader->getTexture();
354 // when brushes are in final state, build the planes and windings
355 // NOTE: also converts BP brushes if g_qeglobals.bNeedConvert is true
359 //#define TERRAIN_HACK
363 if ( ( strcmp( ValueForKey( e, "terrain" ),"1" ) == 0 && strcmp( e->eclass->name,"func_group" ) == 0 ) ) {
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;
370 pTerrainShader = NULL;
371 pCaulk = QERApp_Shader_ForName( SHADER_CAULK );
373 for ( b = e->brushes.onext; b != &e->brushes; b = b->onext )
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;
382 if ( pTerrainShader ) {
383 for ( f = b->brush_faces; f != NULL; f = f->next )
385 if ( strcmp( f->texdef.GetName(), SHADER_CAULK ) != 0 ) { // not caulk
386 Face_SetShader( f, pTerrainShader->getName() );
389 Face_SetShader( f, pCaulk->getName() );
394 Sys_Printf( "WARNING: no terrain shader found for brush\n" );
402 for ( b = e->brushes.onext; b != &e->brushes; b = b->onext )
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++ )
409 if ( (int)mins[i] == (int)maxs[i] ) {
414 Brush_Resize( b, mins, maxs );
419 // add brush for fixedsize entity
420 if ( e->eclass->fixedsize ) {
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 );
429 for ( b = e->brushes.onext; b != &e->brushes; b = b->onext )
430 Brush_AddToList( b, pBrushList );
432 if ( strcmp( e->eclass->name, "worldspawn" ) == 0 ) {
433 if ( world_entity ) {
434 while ( e->brushes.onext != &e->brushes )
436 b = e->brushes.onext;
437 Entity_UnlinkBrush( b );
438 Entity_LinkBrush( world_entity, b );
447 else if ( strcmp( e->eclass->name, "group_info" ) == 0 ) {
448 // it's a group thing!
454 // fix target/targetname collisions
455 if ( ( g_PrefsDlg.m_bDoTargetFix ) && ( strcmp( ValueForKey( e, "target" ), "" ) != 0 ) ) {
456 GPtrArray *t_ents = g_ptr_array_new();
458 const char *target = ValueForKey( e, "target" );
459 qboolean bCollision = FALSE;
461 // check the current map entities for an actual collision
462 for ( e_target = entities.next; e_target != &entities; e_target = e_target->next )
464 if ( !strcmp( target, ValueForKey( e_target, "target" ) ) ) {
466 // make sure the collision is not between two imported entities
467 for ( j = 0; j < (int)new_ents->len; j++ )
469 if ( e_target == g_ptr_array_index( new_ents, j ) ) {
476 // find the matching targeted entity(s)
478 for ( j = num_ents - 1; j > 0; j-- )
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 );
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" ) );
495 g_ptr_array_free( t_ents, FALSE );
499 // add the entity to the end of the entity list
500 Entity_AddToList( e, &entities );
501 g_qeglobals.d_num_entities++;
503 // keep a list of ents added to avoid testing collisions against them
504 g_ptr_array_add( new_ents, (gpointer)e );
507 g_ptr_array_free( new_ents, FALSE );
511 g_qeglobals.bNeedConvert = false;
514 void Map_Import( IDataStream *in, const char *type, bool bAddSelected ){
517 g_pParentWnd->GetSynapseClient().ImportMap( in, &ents, type );
518 Map_ImportEntities( &ents, bAddSelected );
526 void Map_LoadFile( const char *filename ){
527 clock_t start, finish;
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
538 //SetInspectorMode(W_CONSOLE);
539 Sys_Printf( "Loading map from %s\n", filename );
542 //++timo FIXME: maybe even easier to have Group_Init called from Map_Free?
544 g_qeglobals.d_num_entities = 0;
545 g_qeglobals.d_parsed_brushes = 0;
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;
552 strcpy( currentmap, filename );
554 g_bScreenUpdates = false; // leo: avoid redraws while loading the map (see fenris:1952)
556 // prepare to let the map module do the parsing
558 const char* type = strrchr( filename,'.' );
559 if ( type != NULL ) {
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 );
569 Sys_FPrintf( SYS_ERR, "ERROR: failed to open %s for read\n", filename );
573 g_bScreenUpdates = true;
575 if ( g_bCancel_Map_LoadFile ) {
576 Sys_Printf( "Map_LoadFile canceled\n" );
582 if ( !world_entity ) {
583 Sys_Printf( "No worldspawn in map.\n" );
589 elapsed_time = (double)( finish - start ) / CLOCKS_PER_SEC;
591 Sys_Printf( "--- LoadMapFile ---\n" );
592 Sys_Printf( "%s\n", filename );
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 );
600 Map_RestoreBetween();
603 // move the view to a start position
610 Sys_SetTitle( filename );
613 QERApp_SortActiveShaders();
615 Sys_UpdateWindows( W_ALL );
620 Supporting functions for Map_SaveFile, builds a CPtrArray with the filtered / non filtered brushes
623 void CleanFilter( entity_t *ent ){
625 delete static_cast<CPtrArray*>( ent->pData );
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)
635 bool FilterChildren( entity_t *ent, bool bRegionOnly = false, bool bSelectedOnly = false ){
636 if ( ent->brushes.onext == &ent->brushes ) {
639 // entity without a brush, ignore it... this can be caused by Undo
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 ) ) {
648 if ( bRegionOnly && region_active ) {
649 for ( int i = 0 ; i < 3 ; i++ )
651 if ( ( ent->origin[i] + ent->eclass->mins[i] ) > region_maxs[i] ) {
654 if ( ( ent->origin[i] + ent->eclass->maxs[i] ) < region_mins[i] ) {
662 for ( brush_t *b = ent->brushes.onext ; b != &ent->brushes ; b = b->onext )
664 // set flag to use brushprimit_texdef
665 if ( g_qeglobals.m_bBrushPrimitMode ) {
669 b->bBrushDef = false;
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 );
679 if ( ( (CPtrArray*)ent->pData )->GetSize() <= 0 ) {
686 entity_t *region_startpoint = NULL;
687 void Map_ExportEntities( CPtrArray* ents, bool bRegionOnly = false, bool bSelectedOnly = false ){
692 \todo the entity_t needs to be reworked and asbtracted some more
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
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?
702 FilterChildren( world_entity, bRegionOnly, bSelectedOnly );
703 ents->Add( world_entity );
705 for ( e = entities.next ; e != &entities ; e = e->next )
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" );
713 // entities which brushes are completely filtered out by regioning are not printed to the map
714 if ( FilterChildren( e, bRegionOnly, bSelectedOnly ) ) {
719 if ( bRegionOnly && region_active ) {
720 for ( i = 0; i < 6; i++ )
721 ( (CPtrArray*)world_entity->pData )->Add( region_sides[i] );
723 ents->Add( region_startpoint );
727 void Map_Export( IDataStream *out, const char *type, bool bRegionOnly, bool bSelectedOnly ){
732 if ( bRegionOnly && region_active ) {
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();
741 Map_ExportEntities( &ents, bRegionOnly, bSelectedOnly );
743 g_pParentWnd->GetSynapseClient().ExportMap( &ents, out, type );
745 // cleanup the filters
746 CleanFilter( world_entity );
747 for ( e = entities.next ; e != &entities ; e = e->next )
750 if ( bRegionOnly && region_active ) {
751 RemoveRegionBrushes();
755 const char* filename_get_extension( const char* filename ){
756 const char* type = strrchr( filename,'.' );
757 if ( type != NULL ) {
766 \todo FIXME remove the use_region, this is broken .. work with a global flag to set region mode or not
769 void Map_SaveFile( const char *filename, qboolean use_region ){
770 clock_t start, finish;
773 Sys_Printf( "Saving map to %s\n",filename );
780 // rename current to .bak
781 strcpy( backup, filename );
782 StripExtension( backup );
783 strcat( backup, ".bak" );
785 rename( filename, backup );
788 Sys_Printf( "Map_SaveFile: %s\n", filename );
790 // build the out data stream
792 if ( !file.Open( filename,"w" ) ) {
793 Sys_FPrintf( SYS_ERR, "ERROR: couldn't open %s for write\n", filename );
798 Map_Export( &file, filename_get_extension( filename ), use_region );
803 elapsed_time = (double)( finish - start ) / CLOCKS_PER_SEC;
805 Sys_Printf( "Saved in %-.2f second(s).\n",elapsed_time );
808 if ( !strstr( filename, "autosave" ) ) {
809 Sys_SetTitle( filename );
819 Sys_Status( "Saved.", 0 );
829 void Map_New( void ){
830 Sys_Printf( "Map_New\n" );
833 strcpy( currentmap, "unnamed.map" );
834 Sys_SetTitle( currentmap );
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 );
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() );
848 Map_RestoreBetween();
852 Sys_UpdateWindows( W_ALL );
857 ===========================================================
861 ===========================================================
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};
867 brush_t *region_sides[6];
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
878 void AddRegionBrushes( void ){
883 if ( !region_active ) {
885 Sys_FPrintf( SYS_WRN, "Unexpected AddRegionBrushes call.\n" );
890 memset( &td, 0, sizeof( td ) );
891 td.SetName( SHADER_NOT_FOUND );
894 VectorSet( mins, region_mins[0] - 32, region_mins[1] - 32, region_mins[2] - 32 );
897 for ( i = 0; i < 3; i++ )
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 );
905 VectorSet( maxs, region_maxs[0] + 32, region_maxs[1] + 32, region_maxs[2] + 32 );
908 for ( i = 0; i < 3; i++ )
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 );
916 // this is a safe check, but it should not really happen anymore
919 (int)g_pParentWnd->GetCamWnd()->Camera()->origin[0],
920 (int)g_pParentWnd->GetCamWnd()->Camera()->origin[1],
921 (int)g_pParentWnd->GetCamWnd()->Camera()->origin[2] );
923 for ( i = 0 ; i < 3 ; i++ )
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" );
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 );
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;
943 void RemoveRegionBrushes( void ){
946 if ( !region_active ) {
949 for ( i = 0 ; i < 6 ; i++ )
950 Brush_Free( region_sides[i] );
952 CleanFilter( region_startpoint );
953 Entity_Free( region_startpoint );
956 qboolean Map_IsBrushFiltered( brush_t *b ){
959 for ( i = 0 ; i < 3 ; i++ )
961 if ( b->mins[i] > region_maxs[i] ) {
964 if ( b->maxs[i] < region_mins[i] ) {
975 Other filtering options may still be on
978 void Map_RegionOff( void ){
982 region_active = false;
983 for ( i = 0 ; i < 3 ; i++ )
985 region_maxs[i] = g_MaxWorldCoord - 64;
986 region_mins[i] = g_MinWorldCoord + 64;
989 for ( b = filtered_brushes.next ; b != &filtered_brushes ; b = next )
992 if ( Map_IsBrushFiltered( b ) ) {
993 continue; // still filtered
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;
1000 Brush_AddToList( b, &active_brushes );
1001 b->bFiltered = FilterBrush( b );
1003 Sys_UpdateWindows( W_ALL );
1006 void Map_ApplyRegion( void ){
1009 region_active = true;
1010 for ( b = active_brushes.next ; b != &active_brushes ; b = next )
1013 if ( !Map_IsBrushFiltered( b ) ) {
1014 continue; // still filtered
1016 Brush_RemoveFromList( b );
1017 Brush_AddToList( b, &filtered_brushes );
1020 Sys_UpdateWindows( W_ALL );
1025 ========================
1026 Map_RegionSelectedBrushes
1027 ========================
1029 void Map_RegionSelectedBrushes( void ){
1032 if ( selected_brushes.next == &selected_brushes ) { // nothing selected
1033 Sys_Printf( "Tried to region with no selection...\n" );
1036 region_active = true;
1037 Select_GetBounds( region_mins, region_maxs );
1040 if ( filtered_brushes.next != &filtered_brushes ) {
1041 Sys_Printf( "WARNING: filtered_brushes list may not be empty in Map_RegionSelectedBrushes\n" );
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;
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;
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;
1066 for ( brush_t *b = active_brushes.next; b != &active_brushes; b = b->next )
1067 if ( b->patchBrush ) {
1068 b->pPatch->bSelected = false;
1071 // clear selected_brushes
1072 selected_brushes.next = selected_brushes.prev = &selected_brushes;
1074 Sys_UpdateWindows( W_ALL );
1083 void Map_RegionXY( void ){
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;
1100 void Map_RegionTallBrush( void ){
1103 if ( !QE_SingleBrush() ) {
1107 b = selected_brushes.next;
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;
1116 Undo_Start( "delete" );
1117 Undo_AddBrushList( &selected_brushes );
1118 Undo_AddEntity( b->owner );
1120 Undo_EndBrushList( &selected_brushes );
1131 void Map_RegionBrush( void ){
1134 if ( !QE_SingleBrush() ) {
1138 b = selected_brushes.next;
1142 VectorCopy( b->mins, region_mins );
1143 VectorCopy( b->maxs, region_maxs );
1145 Undo_Start( "delete" );
1146 Undo_AddBrushList( &selected_brushes );
1147 Undo_AddEntity( b->owner );
1149 Undo_EndBrushList( &selected_brushes );
1155 GList *find_string( GList *glist, const char *buf ){
1158 if ( strcmp( (char *)glist->data, buf ) == 0 ) {
1159 break; // this name is in our list already
1161 glist = glist->next;
1166 void Map_ImportBuffer( char *buf ){
1169 Undo_Start( "import buffer" );
1173 stream.Write( buf, strlen( buf ) );
1174 Map_Import( &stream, "xmap" );
1177 Sys_UpdateWindows( W_ALL );
1178 Sys_MarkMapModified();
1189 void Map_ImportFile( const char *filename ){
1193 Sys_Printf( "Importing map from %s\n",filename );
1195 const char* type = strrchr( filename,'.' );
1196 if ( type != NULL ) {
1199 /*!\todo Resolve "r" problem in scriptlib" */
1200 if ( file.Open( filename, "rb" ) ) {
1201 Map_Import( &file, type, true );
1204 Sys_FPrintf( SYS_ERR, "ERROR: couldn't open %s for read\n", filename );
1209 Sys_UpdateWindows( W_ALL );
1219 // Saves selected world brushes and whole entities with partial/full selections
1221 void Map_SaveSelected( const char* filename ){
1224 Sys_Printf( "Saving selection to %s\n",filename );
1226 const char* type = strrchr( filename,'.' );
1227 if ( type != NULL ) {
1230 if ( file.Open( filename, "w" ) ) {
1231 Map_Export( &file, type, false, true );
1234 Sys_FPrintf( SYS_ERR, "ERROR: failed to open %s for write\n", filename );
1246 // Saves selected world brushes and whole entities with partial/full selections
1248 void Map_SaveSelected( MemStream* pMemFile, MemStream* pPatchFile ){
1249 Map_Export( pMemFile, "xmap", false, true );
1252 // write world entity first
1253 Entity_WriteSelected(world_entity, pMemFile);
1255 // then write all other ents
1257 for (e=entities.next ; e != &entities ; e=next)
1259 MemFile_fprintf(pMemFile, "// entity %i\n", count);
1261 Entity_WriteSelected(e, pMemFile);
1266 // Patch_WriteFile(pPatchFile);
1271 void MemFile_fprintf( MemStream* pMemFile, const char* pText, ... ){
1274 va_start( args,pText );
1275 vsprintf( Buffer, pText, args );
1276 pMemFile->Write( Buffer, strlen( Buffer ) );
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
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" );