File: HFile.cpp

package info (click to toggle)
descent3 1.5.0%2Bds-2
  • links: PTS, VCS
  • area: contrib
  • in suites: forky, sid
  • size: 35,256 kB
  • sloc: cpp: 416,147; ansic: 3,233; sh: 10; makefile: 8
file content (821 lines) | stat: -rw-r--r-- 23,311 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
/*
* Descent 3
* Copyright (C) 2024 Parallax Software
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.

--- HISTORICAL COMMENTS FOLLOW ---

 * $Logfile: /DescentIII/Main/editor/HFile.cpp $
 * $Revision: 1.1.1.1 $
 * $Date: 2003-08-26 03:57:38 $
 * $Author: kevinb $
 *
 * Handler funcs for File menu
 *
 * $Log: not supported by cvs2svn $
 *
 * 81    10/13/99 2:19p Chris
 * Added the ability to disable wind for powerups on a level
 *
 * 80    10/13/99 10:40a Chris
 * Added 'special forcefields' that have custom bounce factors
 *
 * 79    10/08/99 4:29p Chris
 * Added the forcefield and glass breaking override options
 *
 * 78    5/18/99 11:10a Matt
 * Added variable ceiling height.
 *
 * 77    4/27/99 3:24p Jeff
 * showlevelstats shows information about goals and special flags
 *
 * 76    4/21/99 1:08p Chris
 * Fixed new mines from having the always check ceiling  flag
 *
 * 75    4/20/99 8:14p Chris
 * Added support for object's that hit the ceiling and for making the
 * level always check for the ceiling (inside and outside the mine)
 *
 * 74    4/18/99 5:42a Chris
 * Added the FQ_IGNORE_RENDER_THROUGH_PORTALS flag
 *
 * 73    3/22/99 6:26p Matt
 * Cleaned up error handling in cfile and editor level loads.
 *
 * 72    3/02/99 4:54p Matt
 * Fixed another stupid bug
 *
 * 71    3/02/99 4:07p Matt
 * Fixed stupid bug in duplicate room name check
 *
 * 70    3/01/99 10:39a Matt
 * Strip leading and trailing spaces from object, trigger, and room names
 * on level load and when the names are entered.
 *
 * 69    2/28/99 10:41p Matt
 * After a level is loaded, check for duplicate trigger, room, & object
 * names.
 *
 * 68    2/28/99 9:28p Matt
 * Cleaned up load level error handing and version checking.
 *
 * 67    2/21/99 12:28p Matt
 * Added terrain sound system
 *
 * 66    2/17/99 3:06p Matt
 * Updated copyrights.
 *
 * 65    2/16/99 3:47p Jason
 * added marker updates to multiplayer server stream
 *
 * 64    2/02/99 10:46a Matt
 * Only reset the wireframe view radius when loading an actual new level,
 * and not whenever a level is loading, which incudes going from the game
 * to the editor.
 *
 * 63    1/29/99 12:48p Matt
 * Rewrote the doorway system
 *
 * 62    1/21/99 11:15p Jeff
 * pulled out some structs and defines from header files and moved them
 * into seperate header files so that multiplayer dlls don't require major
 * game headers, just those new headers.  Side effect is a shorter build
 * time.  Also cleaned up some header file #includes that weren't needed.
 * This affected polymodel.h, object.h, player.h, vecmat.h, room.h,
 * manage.h and multi.h
 *
 * 61    1/08/99 2:56p Samir
 * Ripped out OSIRIS1.
 *
 * 60    1/04/99 6:19p Jason
 * counts number of specular faces in mine
 *
 * 59    12/18/98 5:40p Chris
 * Ambient life is now all sequenced up
 *
 * 58    10/08/98 8:14p Matt
 * Fixed stupid bug that's just started causing problems.
 *
 * 57    10/08/98 4:23p Kevin
 * Changed code to comply with memory library usage. Always use mem_malloc
 * , mem_free and mem_strdup
 *
 * 56    10/05/98 2:42a Chris
 * Level goals are relatively functional
 *
 * 55    9/24/98 5:00p Matt
 * Improved error checking for running out of rooms.
 *
 * 54    9/16/98 12:07p Chris
 * Improved BOA AABB computation with room checksums
 *
 * 53    9/10/98 3:07p Chris
 * Improved matcen sequencing code and test effect1
 *
 * 52    9/08/98 12:05p Jason
 * moved doorway.h out of room.h
 *
 * 51    8/17/98 2:16p Jason
 * fixed erroneous reporting of specular lightmap bytes
 *
 * 50    8/10/98 11:25a Jason
 * added better specular lighting
 *
 * 49    8/03/98 5:47p Chris
 * Improved the partial AABB computation
 *
 * 48    5/15/98 5:41p Jason
 * implemented volume lighting system
 *
 * 47    5/13/98 3:06p Jason
 * got rid of square-power-of-two lightmap limitations
 *
 * 46    5/13/98 12:06p Jason
 * trimmed some memory usage
 *
 * 45    4/21/98 2:40p Matt
 * Added option to show level stats
 *
 * 44    4/14/98 7:50p Matt
 * Added system to keep info for each level
 *
 * 43    2/18/98 8:43p Chris
 * Made BOA get saved with the level.  It should only get built when it
 * needs to be updated.
 *
 * 42    2/04/98 1:26p Matt
 * Got rid of compile warning
 *
 * 41    2/03/98 5:01p Chris
 * UPdated the path system
 *
 * 40    1/27/98 12:01p Chris
 * Game path system now works with new, load, and save in the editor.
 * Also, the game path system is further bug proved.
 *
 * 39    1/21/98 12:32p Matt
 * Revamped viewer system
 *
 * 38    1/20/98 1:36p Matt
 * Moved mine origin to the center of the terrain
 *
 * 37    1/19/98 10:04a Matt
 * Added new object handle system
 *
 * 36    1/15/98 7:34p Matt
 * Revamped error checking when computing face normals
 *
 * 35    1/02/98 12:00p Jason
 * got rid of redundant rebuilding of min/max terrain
 *
 * 34    10/08/97 5:20p Jason
 * clear object memory when "New" is selected
 *
 * 33    10/01/97 7:49p Matt
 * Fixed bug w/ script memory freeing (as per Samir's instructions)
 *
 * 32    10/01/97 4:49p Samir
 * Free level script after loading it, since the editor doesn't need the
 * compiled program.
 *
 * 31    9/25/97 5:28p Samir
 * Even newer script code due to more changes in ObjCScript.cpp
 *
 * 30    9/24/97 2:57p Samir
 * Modified NewScript for level function.
 *
 * 29    9/22/97 5:59p Samir
 * Changed ObjCScript system, so everything is broken, but it shouldn't
 * crash the game.
 *
 * 28    9/17/97 1:24p Matt
 * Ripped out segment code
 *
 * 27    9/17/97 12:53p Samir
 * BIG SEGMENT RIPOUT
 *
 * 26    9/10/97 1:55p Samir
 * Create a default level script for a new mine.
 *
 * 25    9/09/97 2:38p Samir
 * Added include for string.h
 *
 * 24    9/09/97 1:04p Samir
 * Added code to create new script and save level script name.
 *
 * 23    9/09/97 10:44a Matt
 * Fixed a small bug, & ripped out some segment engine code
 *
 * 22    9/06/97 3:35p Matt
 * Reset cur porral when room changes
 * Ripped out some old segment engine code
 *
 * 21    8/29/97 5:21p Matt
 * Made CreateNewMine() clear the room (not segment) selected list, and
 * clear Placed_group
 *
 * 20    8/21/97 2:06p Matt
 * Clear marked room when creating a new level
 *
 * 19    8/18/97 6:59p Matt
 * Implemented Place Room/Attach room system
 *
 * 18    8/15/97 6:32p Samir
 * Took out references to default/level scripts due to newer system.
 *
 * 17    8/12/97 3:22p Samir
 * D3XReallocProgram now has two more args.
 *
 * 16    8/06/97 10:36a Samir
 * Fixed D3XReallocProgram calls.
 *
 * 15    8/04/97 4:12p Samir
 * Reset level script code every new mine.
 *
 * 14    7/24/97 2:58p Matt
 * Reset editor viewer variables when creating a new mine
 *
 * 13    7/22/97 7:07p Matt
 * Cleaned up D3EditState, moving some vars in and some out, and renaming
 * and changing a few others
 *
 * 12    7/18/97 5:42p Matt
 * Changed default level script to use CR/LF pair instead of Newline
 *
 * 11    7/17/97 7:22p Matt
 * Initialize level script when create new level
 *
 * 26    6/30/97 1:30p Jason
 * added netherspace stuff
 *
 * 25    6/24/97 3:50p Jason
 * changes for y only terrain positions
 *
 * 24    5/13/97 10:29p Matt
 * Deleted some unused code
 *
 * 23    5/13/97 12:01p Matt
 * Added support for floating segments
 *
 * 22    5/08/97 7:29p Matt
 * Made seperate viewers for mine & terrain, and cleaned up code the
 * switched between modes
 *
 * 21    5/06/97 2:30p Jason
 * added check for terrain viewer
 *
 * 20    4/25/97 2:31p Jason
 * implemented game event system
 *
 * 19    4/04/97 2:57p Matt
 * Added code to initialize all the type-specific data for an object from
 * the page for that object type.  This means that we need to pass less
 * info to ObjCreate(), and that we save less info in the level save file.
 * It also makes it easy to reset all the objects when an object page has
 * changed.
 *
 * 18    4/01/97 11:00p Matt
 * Changed editor to keep a viewer object (type camera) seperate from the
 * player object.  This camera, and not the player, is now moved by
 * slewing, the C key, etc.  When going into the game, the viewer position
 * & orientation are copied to the player.  When going back to the editor,
 * the player position is copied to the viewer, and the player object is
 * reset to its start location.
 *
 * 17    3/31/97 5:58p Matt
 * Revamped mine update flags
 *
 * 16    3/31/97 3:47p Matt
 * Added code to keep track of, render, and display current vertex.
 *
 * 15    3/21/97 5:01p Jason
 * incremental terrain improvments
 *
 *
 * 14    3/18/97 11:27a Samir
 * Reset current trigger and doorway to 0 when loading a level in.
 *
 * 13    3/17/97 2:54p Samir
 * Dont initialize initial doorways or triggers.
 *
 * 12    3/17/97 12:00p Matt
 * Reset warning segments when creating new level and loading
 * level.
 *
 * 11    3/12/97 1:14p Matt
 * Clear selected list when creating a new mine
 *
 * 10    3/12/97 12:37p Samir
 * Initialized doorways
 *
 * 9     3/05/97 3:33p Samir
 * Define initial default Doorway for mine.
 *
 * 8     3/04/97 7:12p Samir
 * Initialized a default trigger when new level
 *
 * 7     2/26/97 12:23p Matt
 * Use real funcs to create segment sides & assign uvs.
 * Deleted temp func that did the same.
 *
 * 6     2/24/97 6:00p Matt
 * Added code to set marked segment.
 * Changed code to not say that mine has changed when curside or curside
 * has changed.
 *
 * 5     2/21/97 5:23p Matt
 * Added new ResetVertices() to set Vertex_active array after level
 * loaded.  Renamed old ResetVertices() to InitVertices();
 *
 * 4     2/20/97 9:56a Matt
 * Moved some general-purpose segment code from HFile.cpp to ESegments.cpp
 *
 * 3     2/11/97 6:44p Matt
 * Reset player when do a new level
 * Added new function: ResetFreeSegList()
 *
 * 2     2/10/97 5:40p Matt
 * Code to handle several File menu options
 *
 * 1     2/10/97 12:16a Matt
 *
 * $NoKeywords: $
 */

#include <string.h>

#include "descent.h"
#include "HFile.h"
#include "d3edit.h"
#include "room.h"
#include "object.h"
#include "game.h"
#include "LoadLevel.h"
#include "MoveWorld.h"
#include "ERooms.h"
#include "trigger.h"
#include "door.h"
#include "selectedroom.h"
#include "terrain.h"
#include "player.h"
#include "gameevent.h"
#include "HView.h"
#include "gamepath.h"
#include "boa.h"
#include "mission.h"
#include "lighting.h"
#include "lightmap_info.h"
#include "lightmap.h"
#include "special_face.h"
#include "doorway.h"
#include "matcen.h"
#include "levelgoal.h"
#include "aiambient.h"
#include "polymodel.h"
#include "bnode.h"
#include "findintersection.h"

//	Scripting stuff
// #include "d3x.h"
// #include "ObjScript.h"

// vertices for the default room created by CreateNewMine()
vector default_room_verts[] = {{-10, 8, 20},  {-5, 10, 20},  {5, 10, 20},    {10, 8, 20},   {10, -8, 20}, {5, -10, 20},
                               {-5, -10, 20}, {-10, -8, 20}, {-10, 8, -20},  {-5, 10, -20}, {5, 10, -20}, {10, 8, -20},
                               {10, -8, -20}, {5, -10, -20}, {-5, -10, -20}, {-10, -8, -20}};

extern void AssignDefaultUVsToRoom(room *rp);

// Where the center of the (mine's) world is
vector Mine_origin = {TERRAIN_WIDTH * (TERRAIN_SIZE / 2), -100, TERRAIN_DEPTH *(TERRAIN_SIZE / 2)};

// Create a default room for a new mine
room *CreateDefaultRoom() {
  room *rp;
  int i;

  // Get a pointer to our room
  rp = CreateNewRoom(16, 10, 0); // 16 verts, 10 faces, normal room
  ASSERT(rp != NULL);

  // Set the vertices for the room
  for (i = 0; i < 16; i++)
    rp->verts[i] = default_room_verts[i] + Mine_origin;

  // Set the faces for the room
  InitRoomFace(&rp->faces[0], 8);
  for (i = 0; i < 8; i++)
    rp->faces[0].face_verts[i] = i;

  InitRoomFace(&rp->faces[1], 8);
  for (i = 0; i < 8; i++)
    rp->faces[1].face_verts[i] = 15 - i;

  for (i = 0; i < 8; i++) {
    InitRoomFace(&rp->faces[i + 2], 4);
    rp->faces[i + 2].face_verts[0] = i;
    rp->faces[i + 2].face_verts[1] = i + 8;
    rp->faces[i + 2].face_verts[2] = ((i + 1) % 8) + 8;
    rp->faces[i + 2].face_verts[3] = (i + 1) % 8;
  }

  // Set normals, textures and UVLs for face
  for (i = 0; i < 10; i++) {
    if (!ComputeFaceNormal(rp, i))
      Int3(); // this is odd.  Get Matt!
    rp->faces[i].tmap = i + 1;
    AssignDefaultUVsToRoomFace(rp, i);
  }

  return rp;
}

#define DEFAULT_SCRIPT "//Empty script\xd\xa"

// Create a new mine with one segment
void CreateNewMine() {
  // Get rid of old mine
  FreeAllRooms();
  FreeAllObjects();

  // Create a new room
  Curroomp = CreateDefaultRoom();
  ASSERT(Curroomp != NULL);

  // Reset misc. vars
  Curface = Curedge = Curvert = 0;
  Curportal = -1;

  // Say that this is a new mine
  New_mine = 1;

  // Reset the view position, etc. for the wireframe view
  ResetWireframeView();

  // Reintialize the objects
  ResetObjectList();

  // Create the player and put him in the center of the fa
  CreatePlayerObject(ROOMNUM(Curroomp));

  // Initialize editor viewer
  Editor_view_mode = VM_MINE;
  Editor_viewer_id = -1;

  // Create a camera for this level
  SetEditorViewer();

  // Look for player objects & set player starts
  FindPlayerStarts();

  // Clear the marked room
  Markedroomp = NULL;

  // Clear the selected segments
  ClearRoomSelectedList();

  // Clear the placed room & group
  Placed_room = -1;
  Placed_group = NULL;

  // Reset the triggers
  Num_triggers = 0;
  Current_trigger = -1;

  // Reset the terrain
  ResetTerrain(1);

  // Reset terrain sound
  ClearTerrainSound();

  // Clear game events
  ClearAllEvents();

  int i;
  BOA_AABB_checksum = BOA_mine_checksum = 0;
  for (i = 0; i < MAX_ROOMS; i++) {
    BOA_AABB_ROOM_checksum[i] = 0;
  }
  InitGamePaths();
  DestroyAllMatcens();
  // Resets the ambient life controller
  a_life.ALReset();
  Level_goals.CleanupAfterLevel();

  BNode_ClearBNodeInfo();
  FVI_always_check_ceiling = false;
  Ceiling_height = MAX_TERRAIN_HEIGHT;

  sound_override_force_field = -1;
  sound_override_glass_breaking = -1;

  for (i = 0; i < MAX_FORCE_FIELD_BOUNCE_TEXTURES; i++) {
    force_field_bounce_texture[i] = -1;
    force_field_bounce_multiplier[i] = 1.0f;
  }

  Level_powerups_ignore_wind = false;

  // Init level info
  strcpy(Level_info.name, "Unnamed");
  strcpy(Level_info.designer, "Anonymous");
  strcpy(Level_info.copyright, "Copyright (c) 1999 Outrage Entertainment, Inc.");
  strcpy(Level_info.notes, "");
}

#include "osiris_predefs.h"

// Strips leading and trailing spaces from a string
// Returns true if spaces were stripped
bool StripLeadingTrailingSpaces(char *s) {
  char *t;
  bool stripped = 0;

  // Look for leading spaces
  t = s;
  while (*t == ' ')
    t++;

  // Leading spaces found, so copy string over spaces
  if (t != s) {
    strcpy(s, t);
    stripped = 1;
  }

  // Strip any trailing spaces
  for (t = s + strlen(s) - 1; (t >= s) && (*t == ' '); t--) {
    *t = 0;
    stripped = 1;
  }

  return stripped;
}

// Check for duplicate names in the level
void CheckLevelNames() {
  int i;

  // Check objects
  object *objp;
  for (i = 0, objp = Objects; i <= Highest_object_index; i++, objp++) {
    if ((objp->type != OBJ_NONE) && objp->name) {
      int handle = osipf_FindObjectName(objp->name);
      if (handle != objp->handle)
        EditorMessageBox("Error: Objects %d and %d are both named \"%s\"", i, OBJNUM(ObjGet(handle)), objp->name);
      if (StripLeadingTrailingSpaces(objp->name))
        EditorMessageBox(
            "Note: Object %d (\"%s\") had leading and/or trailing spaces in its name that have been removed.", i,
            objp->name);
    }
  }

  // Check triggers
  trigger *tp;
  for (i = 0, tp = Triggers; i < Num_triggers; i++, tp++) {
    if (tp->name) {
      int n = osipf_FindTriggerName(tp->name);
      if (n != i)
        EditorMessageBox("Error: Triggers %d and %d are both named \"%s\"", i, n, tp->name);
      if (StripLeadingTrailingSpaces(tp->name))
        EditorMessageBox(
            "Note: Trigger %d (\"%s\") had leading and/or trailing spaces in its name that have been removed.", i,
            tp->name);
    }
  }

  // Check rooms
  room *rp;
  for (i = 0, rp = Rooms; i <= Highest_room_index; i++, rp++) {
    if (rp->used && rp->name) {
      int n = osipf_FindRoomName(rp->name);
      if (n != i)
        EditorMessageBox("Error: Rooms %d and %d are both named \"%s\"", i, n, rp->name);
      if (StripLeadingTrailingSpaces(rp->name))
        EditorMessageBox(
            "Note: Room %d (\"%s\") had leading and/or trailing spaces in its name that have been removed.", i,
            rp->name);
    }
  }
}

// Load a new level
bool EditorLoadLevel(char *filename) {
  if (LoadLevel(filename)) {

    // Check for duplicate names in the level
    CheckLevelNames();

    New_mine = 1; // say that this is a new mine

    // Set viewer object
    SetEditorViewer();

    // Reset view radius
    ResetWireframeViewRad();

    // Done
    return TRUE;
  } else
    return FALSE;
}

// Save the current level
// Returns 1 if level saved sucessfully
int EditorSaveLevel(char *filename) {
  int sucess;

  sucess = SaveLevel(filename);

  if (sucess)
    Mine_changed = 0;

  return sucess;
}

// Returns a pointer to enough spaces to right-justify a number that's printed after the spaces
char *IntSpacing(int i) {
  static char *spaces = "               ";

  i = abs(i);

  for (int n = 1; i >= 10; n++)
    i /= 10;

  ASSERT(n <= 20);

  return spaces + n * 2 + n / 2;
}

extern void dump_text_to_clipboard(char *text);

// Shows the stats for a level in a messagebox, and copies to the clipboard
void ShowLevelStats() {
  int n_rooms, n_rooms_external, n_faces, n_verts, n_objects, n_portals, n_doors, n_objects_outside;
  int n_object_faces, n_object_lightmap_faces;
  int i;
  room *rp;
  int bytes_wasted = 0, spec_faces = 0, lm_bytes = 0;
  int total_volume_bytes = 0;
  int num_redgoals = 0, num_bluegoals = 0, num_greengoals = 0, num_yellowgoals = 0;
  int num_sp1 = 0, num_sp2 = 0, num_sp3 = 0, num_sp4 = 0, num_sp5 = 0, num_sp6 = 0;

  uint8_t lightmaps_used[MAX_LIGHTMAPS];

  object *objp;
#define BUF_LEN 5000
  char text_buf[BUF_LEN];

  // Count the number of rooms, and the number of faces & points per room
  n_rooms = n_rooms_external = n_faces = n_verts = n_doors = n_portals = 0;
  for (i = 0, rp = Rooms; i <= Highest_room_index; i++, rp++)
    if (rp->used) {
      n_rooms++;
      n_verts += rp->num_verts;
      n_faces += rp->num_faces;
      n_portals += rp->num_portals;
      if (rp->flags & RF_EXTERNAL)
        n_rooms_external++;
      else {
        // Get volume size of room
        total_volume_bytes += GetVolumeSizeOfRoom(rp);
      }

      for (int t = 0; t < rp->num_faces; t++) {
        face *fp = &rp->faces[t];
        if (fp->special_handle != BAD_SPECIAL_FACE_INDEX && GameTextures[fp->tmap].flags & TF_SPECULAR &&
            fp->lmi_handle != BAD_LMI_INDEX)
          spec_faces++;
      }

      if (rp->flags & RF_DOOR)
        n_doors++;

      if (rp->flags & RF_SPECIAL1)
        num_sp1++;

      if (rp->flags & RF_SPECIAL2)
        num_sp2++;

      if (rp->flags & RF_SPECIAL3)
        num_sp3++;

      if (rp->flags & RF_SPECIAL4)
        num_sp4++;

      if (rp->flags & RF_SPECIAL5)
        num_sp5++;

      if (rp->flags & RF_SPECIAL6)
        num_sp6++;

      if (rp->flags & RF_GOAL1)
        num_redgoals++;

      if (rp->flags & RF_GOAL2)
        num_bluegoals++;

      if (rp->flags & RF_GOAL3)
        num_greengoals++;

      if (rp->flags & RF_GOAL4)
        num_yellowgoals++;
    }

  // Count the number of objects
  n_objects = n_object_faces = n_object_lightmap_faces = n_objects_outside = 0;
  for (i = 0, objp = Objects; i <= Highest_object_index; i++, objp++)
    if ((objp->type != OBJ_NONE) && (objp->type != OBJ_ROOM) && (objp->render_type == RT_POLYOBJ)) {
      n_objects++;
      if (OBJECT_OUTSIDE(objp))
        n_objects_outside++;
      // Count the number of faces in this object
      poly_model *pm;
      pm = GetPolymodelPointer(objp->rtype.pobj_info.model_num);
      for (int m = 0; m < pm->n_models; m++) {
        n_object_faces += pm->submodel[m].num_faces;
        if (objp->lighting_render_type == LRT_LIGHTMAPS)
          n_object_lightmap_faces += pm->submodel[m].num_faces;
      }
    }

  memset(lightmaps_used, 0, MAX_LIGHTMAPS);
  for (i = 0; i < MAX_LIGHTMAP_INFOS; i++) {
    if (!LightmapInfo[i].used)
      continue;
    if (LightmapInfo[i].type == LMI_DYNAMIC || LightmapInfo[i].type == LMI_TERRAIN)
      continue;

    lightmaps_used[LightmapInfo[i].lm_handle] = 1;
  }

  for (i = 0; i < MAX_LIGHTMAPS; i++) {
    if (lightmaps_used[i]) {
      uint16_t *data = lm_data(i);
      int w = lm_w(i);
      int h = lm_h(i);

      for (int j = 0; j < w * h; j++) {
        if (!(data[j] & OPAQUE_FLAG)) {
          bytes_wasted += 2;
        } else {
          lm_bytes += 2;
        }
      }
    }
  }

  i = sprintf(text_buf,
              "Level Stats:\n"
              "\n"
              "%s%d   Rooms (%d external)\n"
              "%s%d   Faces\n"
              "%s%d   Vertices\n"
              "\n"
              "%s%d   Portals\n"
              "%s%d   Doors\n"
              "\n"
              "%s%d   Polygon Objects (%d inside, %d outside)\n"
              "%s%d   Object Faces (%d with lightmaps)\n"
              "\n"
              "%s%d   Total lightmap faces\n"
              "%d	Total volume bytes\n"
              "%d   Total bytes in lightmaps\n"
              "%d   Total specular faces\n"
              "%d   Bytes wasted in lightmaps\n"
              "\n"
              "%d Red Goals\n"
              "%d Blue Goals\n"
              "%d Green Goals\n"
              "%d Yellow Goals\n"
              "%d Special 1 Rooms\n"
              "%d Special 2 Rooms\n"
              "%d Special 3 Rooms\n"
              "%d Special 4 Rooms\n"
              "%d Special 5 Rooms\n"
              "%d Special 6 Rooms\n",
              IntSpacing(n_rooms), n_rooms, n_rooms_external, IntSpacing(n_faces), n_faces, IntSpacing(n_verts),
              n_verts, IntSpacing(n_portals / 2), n_portals / 2, IntSpacing(n_doors), n_doors, IntSpacing(n_objects),
              n_objects, n_objects - n_objects_outside, n_objects_outside, IntSpacing(n_object_faces), n_object_faces,
              n_object_lightmap_faces, IntSpacing(n_faces + n_object_lightmap_faces), n_faces + n_object_lightmap_faces,
              total_volume_bytes, lm_bytes, spec_faces, bytes_wasted, num_redgoals, num_bluegoals, num_greengoals,
              num_yellowgoals, num_sp1, num_sp2, num_sp3, num_sp4, num_sp5, num_sp6);

  ASSERT(i < BUF_LEN);

  dump_text_to_clipboard(text_buf);
  OutrageMessageBox(
      "%s\n\nThis info has also been posted to the clipboard.\n\nWhat other info would you like here?  Let MattT know.",
      text_buf);
}