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