Subversion Repositories Games.Descent

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 pmbaty 1
/*
2
 * Portions of this file are copyright Rebirth contributors and licensed as
3
 * described in COPYING.txt.
4
 * Portions of this file are copyright Parallax Software and licensed
5
 * according to the Parallax license below.
6
 * See COPYING.txt for license details.
7
 
8
THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
9
SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
10
END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
11
ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
12
IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
13
SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
14
FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
15
CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
16
AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
17
COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
18
*/
19
 
20
/*
21
 *
22
 * Save game information
23
 *
24
 */
25
 
26
#include <stdexcept>
27
#include <stdio.h>
28
#include <string.h>
29
#include "pstypes.h"
30
#include "strutil.h"
31
#include "console.h"
32
#include "key.h"
33
#include "gr.h"
34
#include "palette.h"
35
#include "newmenu.h"
36
#include "inferno.h"
37
#if DXX_USE_EDITOR
38
#include "editor/editor.h"
39
#include "editor/esegment.h"
40
#include "editor/eswitch.h"
41
#endif
42
#include "dxxerror.h"
43
#include "object.h"
44
#include "game.h"
45
#include "gameseg.h"
46
#include "screens.h"
47
#include "wall.h"
48
#include "gamemine.h"
49
#include "robot.h"
50
#include "bm.h"
51
#include "menu.h"
52
#include "fireball.h"
53
#include "switch.h"
54
#include "fuelcen.h"
55
#include "cntrlcen.h"
56
#include "powerup.h"
57
#include "hostage.h"
58
#include "weapon.h"
59
#include "player.h"
60
#include "newdemo.h"
61
#include "gameseq.h"
62
#include "automap.h"
63
#include "polyobj.h"
64
#include "text.h"
65
#include "gamefont.h"
66
#include "gamesave.h"
67
#include "gamepal.h"
68
#include "physics.h"
69
#include "laser.h"
70
#include "multi.h"
71
#include "makesig.h"
72
#include "textures.h"
73
#include "d_enumerate.h"
74
#include "d_range.h"
75
 
76
#include "dxxsconf.h"
77
#include "compiler-range_for.h"
78
#include "d_zip.h"
79
#include "partial_range.h"
80
 
81
#if defined(DXX_BUILD_DESCENT_I)
82
#if DXX_USE_EDITOR
83
const char Shareware_level_names[NUM_SHAREWARE_LEVELS][12] = {
84
        "level01.rdl",
85
        "level02.rdl",
86
        "level03.rdl",
87
        "level04.rdl",
88
        "level05.rdl",
89
        "level06.rdl",
90
        "level07.rdl"
91
};
92
 
93
const char Registered_level_names[NUM_REGISTERED_LEVELS][12] = {
94
        "level08.rdl",
95
        "level09.rdl",
96
        "level10.rdl",
97
        "level11.rdl",
98
        "level12.rdl",
99
        "level13.rdl",
100
        "level14.rdl",
101
        "level15.rdl",
102
        "level16.rdl",
103
        "level17.rdl",
104
        "level18.rdl",
105
        "level19.rdl",
106
        "level20.rdl",
107
        "level21.rdl",
108
        "level22.rdl",
109
        "level23.rdl",
110
        "level24.rdl",
111
        "level25.rdl",
112
        "level26.rdl",
113
        "level27.rdl",
114
        "levels1.rdl",
115
        "levels2.rdl",
116
        "levels3.rdl"
117
};
118
#endif
119
#endif
120
 
121
char Gamesave_current_filename[PATH_MAX];
122
 
123
int Gamesave_current_version;
124
 
125
#if defined(DXX_BUILD_DESCENT_I)
126
#define GAME_VERSION                                    25
127
#elif defined(DXX_BUILD_DESCENT_II)
128
#define GAME_VERSION            32
129
#endif
130
#define GAME_COMPATIBLE_VERSION 22
131
 
132
//version 28->29  add delta light support
133
//version 27->28  controlcen id now is reactor number, not model number
134
//version 28->29  ??
135
//version 29->30  changed trigger structure
136
//version 30->31  changed trigger structure some more
137
//version 31->32  change segment structure, make it 512 bytes w/o editor, add Segment2s array.
138
 
139
#define MENU_CURSOR_X_MIN       MENU_X
140
#define MENU_CURSOR_X_MAX       MENU_X+6
141
 
142
int Gamesave_num_org_robots = 0;
143
//--unused-- grs_bitmap * Gamesave_saved_bitmap = NULL;
144
 
145
#if DXX_USE_EDITOR
146
// Return true if this level has a name of the form "level??"
147
// Note that a pathspec can appear at the beginning of the filename.
148
static int is_real_level(const char *filename)
149
{
150
        int len = strlen(filename);
151
 
152
        if (len < 6)
153
                return 0;
154
 
155
        return !d_strnicmp(&filename[len-11], "level");
156
 
157
}
158
#endif
159
 
160
//--unused-- vms_angvec zero_angles={0,0,0};
161
 
162
int Gamesave_num_players=0;
163
 
164
#if defined(DXX_BUILD_DESCENT_I)
165
#define MAX_POLYGON_MODELS_NEW 167
166
static std::array<char[FILENAME_LEN], MAX_POLYGON_MODELS_NEW> Save_pof_names;
167
 
168
static int convert_vclip(const d_vclip_array &Vclip, int vc)
169
{
170
        if (vc < 0)
171
                return vc;
172
        if (vc < Vclip.size() && (Vclip[vc].num_frames != ~0u))
173
                return vc;
174
        return 0;
175
}
176
static int convert_wclip(int wc) {
177
        return (wc < Num_wall_anims) ? wc : wc % Num_wall_anims;
178
}
179
int convert_tmap(int tmap)
180
{
181
        if (tmap == -1)
182
                return tmap;
183
    return (tmap >= NumTextures) ? tmap % NumTextures : tmap;
184
}
185
static int convert_polymod(int polymod) {
186
    return (polymod >= N_polygon_models) ? polymod % N_polygon_models : polymod;
187
}
188
#elif defined(DXX_BUILD_DESCENT_II)
189
static std::array<char[FILENAME_LEN], MAX_POLYGON_MODELS> Save_pof_names;
190
#endif
191
 
192
namespace dsx {
193
 
194
static void verify_object(const d_vclip_array &Vclip, object &obj)
195
{
196
        auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
197
        obj.lifeleft = IMMORTAL_TIME;           //all loaded object are immortal, for now
198
 
199
        auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
200
        if (obj.type == OBJ_ROBOT)
201
        {
202
                Gamesave_num_org_robots++;
203
 
204
                // Make sure valid id...
205
                const auto N_robot_types = LevelSharedRobotInfoState.N_robot_types;
206
                if (get_robot_id(obj) >= N_robot_types )
207
                        set_robot_id(obj, get_robot_id(obj) % N_robot_types);
208
 
209
                // Make sure model number & size are correct...
210
                if (obj.render_type == RT_POLYOBJ)
211
                {
212
                        auto &ri = Robot_info[get_robot_id(obj)];
213
#if defined(DXX_BUILD_DESCENT_II)
214
                        assert(ri.model_num != -1);
215
                                //if you fail this assert, it means that a robot in this level
216
                                //hasn't been loaded, possibly because he's marked as
217
                                //non-shareware.  To see what robot number, print obj.id.
218
 
219
                        assert(ri.always_0xabcd == 0xabcd);
220
                                //if you fail this assert, it means that the robot_ai for
221
                                //a robot in this level hasn't been loaded, possibly because
222
                                //it's marked as non-shareware.  To see what robot number,
223
                                //print obj.id.
224
#endif
225
 
226
                        obj.rtype.pobj_info.model_num = ri.model_num;
227
                        obj.size = Polygon_models[obj.rtype.pobj_info.model_num].rad;
228
 
229
                        //@@Took out this ugly hack 1/12/96, because Mike has added code
230
                        //@@that should fix it in a better way.
231
                        //@@//this is a super-ugly hack.  Since the baby stripe robots have
232
                        //@@//their firing point on their bounding sphere, the firing points
233
                        //@@//can poke through a wall if the robots are very close to it. So
234
                        //@@//we make their radii bigger so the guns can't get too close to 
235
                        //@@//the walls
236
                        //@@if (Robot_info[obj.id].flags & RIF_BIG_RADIUS)
237
                        //@@    obj.size = (obj.size*3)/2;
238
 
239
                        //@@if (obj.control_type==CT_AI && Robot_info[obj.id].attack_type)
240
                        //@@    obj.size = obj.size*3/4;
241
                }
242
 
243
#if defined(DXX_BUILD_DESCENT_II)
244
                if (get_robot_id(obj) == 65)                                            //special "reactor" robots
245
                        obj.movement_type = MT_NONE;
246
#endif
247
 
248
                if (obj.movement_type == MT_PHYSICS)
249
                {
250
                        auto &ri = Robot_info[get_robot_id(obj)];
251
                        obj.mtype.phys_info.mass = ri.mass;
252
                        obj.mtype.phys_info.drag = ri.drag;
253
                }
254
        }
255
        else {          //Robots taken care of above
256
                if (obj.render_type == RT_POLYOBJ)
257
                {
258
                        char *name = Save_pof_names[obj.rtype.pobj_info.model_num];
259
                        for (uint_fast32_t i = 0;i < N_polygon_models;i++)
260
                                if (!d_stricmp(Pof_names[i],name)) {            //found it!     
261
                                        obj.rtype.pobj_info.model_num = i;
262
                                        break;
263
                                }
264
                }
265
        }
266
 
267
        if (obj.type == OBJ_POWERUP)
268
        {
269
                if ( get_powerup_id(obj) >= N_powerup_types )   {
270
                        set_powerup_id(Powerup_info, Vclip, obj, POW_SHIELD_BOOST);
271
                        Assert( obj.render_type != RT_POLYOBJ );
272
                }
273
                obj.control_type = CT_POWERUP;
274
                obj.size = Powerup_info[get_powerup_id(obj)].size;
275
                obj.ctype.powerup_info.creation_time = 0;
276
        }
277
 
278
        if (obj.type == OBJ_WEAPON)
279
        {
280
                if ( get_weapon_id(obj) >= N_weapon_types )     {
281
                        set_weapon_id(obj, weapon_id_type::LASER_ID_L1);
282
                        Assert( obj.render_type != RT_POLYOBJ );
283
                }
284
 
285
#if defined(DXX_BUILD_DESCENT_II)
286
                const auto weapon_id = get_weapon_id(obj);
287
                if (weapon_id == weapon_id_type::PMINE_ID)
288
                {               //make sure pmines have correct values
289
                        obj.mtype.phys_info.mass = Weapon_info[weapon_id].mass;
290
                        obj.mtype.phys_info.drag = Weapon_info[weapon_id].drag;
291
                        obj.mtype.phys_info.flags |= PF_FREE_SPINNING;
292
 
293
                        // Make sure model number & size are correct...         
294
                        Assert( obj.render_type == RT_POLYOBJ );
295
 
296
                        obj.rtype.pobj_info.model_num = Weapon_info[weapon_id].model_num;
297
                        obj.size = Polygon_models[obj.rtype.pobj_info.model_num].rad;
298
                }
299
#endif
300
        }
301
 
302
        if (obj.type == OBJ_CNTRLCEN)
303
        {
304
                obj.render_type = RT_POLYOBJ;
305
                obj.control_type = CT_CNTRLCEN;
306
 
307
#if defined(DXX_BUILD_DESCENT_I)
308
                // Make model number is correct...      
309
                for (int i=0; i<Num_total_object_types; i++ )  
310
                        if ( ObjType[i] == OL_CONTROL_CENTER )          {
311
                                obj.rtype.pobj_info.model_num = ObjId[i];
312
                                obj.shields = ObjStrength[i];
313
                                break;         
314
                        }
315
#elif defined(DXX_BUILD_DESCENT_II)
316
                if (Gamesave_current_version <= 1) { // descent 1 reactor
317
                        set_reactor_id(obj, 0);                         // used to be only one kind of reactor
318
                        obj.rtype.pobj_info.model_num = Reactors[0].model_num;// descent 1 reactor
319
                }
320
 
321
                // Make sure model number is correct...
322
                //obj.rtype.pobj_info.model_num = Reactors[obj.id].model_num;
323
#endif
324
        }
325
 
326
        if (obj.type == OBJ_PLAYER)
327
        {
328
                //int i;
329
 
330
                //Assert(obj == Player);
331
 
332
                if (&obj == ConsoleObject)
333
                        init_player_object();
334
                else
335
                        if (obj.render_type == RT_POLYOBJ)      //recover from Matt's pof file matchup bug
336
                                obj.rtype.pobj_info.model_num = Player_ship->model_num;
337
 
338
                //Make sure orient matrix is orthogonal
339
                check_and_fix_matrix(obj.orient);
340
 
341
                set_player_id(obj, Gamesave_num_players++);
342
        }
343
 
344
        if (obj.type == OBJ_HOSTAGE)
345
        {
346
                obj.render_type = RT_HOSTAGE;
347
                obj.control_type = CT_POWERUP;
348
        }
349
}
350
 
351
}
352
 
353
//static gs_skip(int len,PHYSFS_File *file)
354
//{
355
//
356
//      PHYSFSX_fseek(file,len,SEEK_CUR);
357
//}
358
 
359
//reads one object of the given version from the given file
360
namespace dsx {
361
static void read_object(const vmobjptr_t obj,PHYSFS_File *f,int version)
362
{
363
        const auto poison_obj = reinterpret_cast<uint8_t *>(&*obj);
364
        DXX_POISON_MEMORY(poison_obj, sizeof(*obj), 0xfd);
365
        obj->signature = object_signature_t{0};
366
        set_object_type(*obj, PHYSFSX_readByte(f));
367
        obj->id             = PHYSFSX_readByte(f);
368
 
369
        if (obj->type == OBJ_ROBOT)
370
        {
371
#if defined(DXX_BUILD_DESCENT_I)
372
                const auto id = get_robot_id(obj);
373
                if (id > 23)
374
                        set_robot_id(obj, id % 24);
375
#endif
376
                obj->matcen_creator = 0;
377
        }
378
        obj->control_type   = PHYSFSX_readByte(f);
379
        set_object_movement_type(*obj, PHYSFSX_readByte(f));
380
        const uint8_t render_type = PHYSFSX_readByte(f);
381
        if (valid_render_type(render_type))
382
                obj->render_type = render_type_t{render_type};
383
        else
384
        {
385
                LevelError("Level contains bogus render type %#x for object %p; using none instead", render_type, &*obj);
386
                obj->render_type = RT_NONE;
387
        }
388
        obj->flags          = PHYSFSX_readByte(f);
389
 
390
        obj->segnum         = PHYSFSX_readShort(f);
391
        obj->attached_obj   = object_none;
392
 
393
        PHYSFSX_readVector(f, obj->pos);
394
        PHYSFSX_readMatrix(&obj->orient,f);
395
 
396
        obj->size           = PHYSFSX_readFix(f);
397
        obj->shields        = PHYSFSX_readFix(f);
398
 
399
        {
400
                vms_vector last_pos;
401
                PHYSFSX_readVector(f, last_pos);
402
        }
403
 
404
        obj->contains_type  = PHYSFSX_readByte(f);
405
        obj->contains_id    = PHYSFSX_readByte(f);
406
        obj->contains_count = PHYSFSX_readByte(f);
407
 
408
        switch (obj->movement_type) {
409
 
410
                case MT_PHYSICS:
411
 
412
                        PHYSFSX_readVector(f, obj->mtype.phys_info.velocity);
413
                        PHYSFSX_readVector(f, obj->mtype.phys_info.thrust);
414
 
415
                        obj->mtype.phys_info.mass               = PHYSFSX_readFix(f);
416
                        obj->mtype.phys_info.drag               = PHYSFSX_readFix(f);
417
                        PHYSFSX_readFix(f);     /* brakes */
418
 
419
                        PHYSFSX_readVector(f, obj->mtype.phys_info.rotvel);
420
                        PHYSFSX_readVector(f, obj->mtype.phys_info.rotthrust);
421
 
422
                        obj->mtype.phys_info.turnroll   = PHYSFSX_readFixAng(f);
423
                        obj->mtype.phys_info.flags              = PHYSFSX_readShort(f);
424
 
425
                        break;
426
 
427
                case MT_SPINNING:
428
 
429
                        PHYSFSX_readVector(f, obj->mtype.spin_rate);
430
                        break;
431
 
432
                case MT_NONE:
433
                        break;
434
 
435
                default:
436
                        Int3();
437
        }
438
 
439
        switch (obj->control_type) {
440
 
441
                case CT_AI: {
442
                        obj->ctype.ai_info.behavior                             = static_cast<ai_behavior>(PHYSFSX_readByte(f));
443
 
444
                        range_for (auto &i, obj->ctype.ai_info.flags)
445
                                i = PHYSFSX_readByte(f);
446
 
447
                        obj->ctype.ai_info.hide_segment                 = PHYSFSX_readShort(f);
448
                        obj->ctype.ai_info.hide_index                   = PHYSFSX_readShort(f);
449
                        obj->ctype.ai_info.path_length                  = PHYSFSX_readShort(f);
450
                        obj->ctype.ai_info.cur_path_index               = PHYSFSX_readShort(f);
451
 
452
                        if (version <= 25) {
453
                                PHYSFSX_readShort(f);   //                              obj->ctype.ai_info.follow_path_start_seg        = 
454
                                PHYSFSX_readShort(f);   //                              obj->ctype.ai_info.follow_path_end_seg          = 
455
                        }
456
 
457
                        break;
458
                }
459
 
460
                case CT_EXPLOSION:
461
 
462
                        obj->ctype.expl_info.spawn_time         = PHYSFSX_readFix(f);
463
                        obj->ctype.expl_info.delete_time                = PHYSFSX_readFix(f);
464
                        obj->ctype.expl_info.delete_objnum      = PHYSFSX_readShort(f);
465
                        obj->ctype.expl_info.next_attach = obj->ctype.expl_info.prev_attach = obj->ctype.expl_info.attach_parent = object_none;
466
 
467
                        break;
468
 
469
                case CT_WEAPON:
470
 
471
                        //do I really need to read these?  Are they even saved to disk?
472
 
473
                        obj->ctype.laser_info.parent_type               = PHYSFSX_readShort(f);
474
                        obj->ctype.laser_info.parent_num                = PHYSFSX_readShort(f);
475
                        obj->ctype.laser_info.parent_signature  = object_signature_t{static_cast<uint16_t>(PHYSFSX_readInt(f))};
476
#if defined(DXX_BUILD_DESCENT_II)
477
                        obj->ctype.laser_info.last_afterburner_time = 0;
478
#endif
479
                        obj->ctype.laser_info.clear_hitobj();
480
 
481
                        break;
482
 
483
                case CT_LIGHT:
484
 
485
                        obj->ctype.light_info.intensity = PHYSFSX_readFix(f);
486
                        break;
487
 
488
                case CT_POWERUP:
489
 
490
                        if (version >= 25)
491
                                obj->ctype.powerup_info.count = PHYSFSX_readInt(f);
492
                        else
493
                                obj->ctype.powerup_info.count = 1;
494
 
495
                        if (obj->type == OBJ_POWERUP)
496
                        {
497
                                /* Objects loaded from a level file were not ejected by
498
                                 * the player.
499
                                 */
500
                                obj->ctype.powerup_info.flags = 0;
501
                                /* Hostages have control type CT_POWERUP, but object
502
                                 * type OBJ_HOSTAGE.  Hostages are never weapons, so
503
                                 * prevent checking their IDs.
504
                                 */
505
                        if (get_powerup_id(obj) == POW_VULCAN_WEAPON)
506
                                        obj->ctype.powerup_info.count = VULCAN_WEAPON_AMMO_AMOUNT;
507
 
508
#if defined(DXX_BUILD_DESCENT_II)
509
                        else if (get_powerup_id(obj) == POW_GAUSS_WEAPON)
510
                                        obj->ctype.powerup_info.count = VULCAN_WEAPON_AMMO_AMOUNT;
511
 
512
                        else if (get_powerup_id(obj) == POW_OMEGA_WEAPON)
513
                                        obj->ctype.powerup_info.count = MAX_OMEGA_CHARGE;
514
#endif
515
                        }
516
 
517
                        break;
518
 
519
 
520
                case CT_NONE:
521
                case CT_FLYING:
522
                case CT_DEBRIS:
523
                        break;
524
 
525
                case CT_SLEW:           //the player is generally saved as slew
526
                        break;
527
 
528
                case CT_CNTRLCEN:
529
                        break;
530
 
531
                case CT_MORPH:
532
                case CT_FLYTHROUGH:
533
                case CT_REPAIRCEN:
534
                default:
535
                        Int3();
536
 
537
        }
538
 
539
        switch (obj->render_type) {
540
 
541
                case RT_NONE:
542
                        break;
543
 
544
                case RT_MORPH:
545
                case RT_POLYOBJ: {
546
                        int tmo;
547
 
548
#if defined(DXX_BUILD_DESCENT_I)
549
                        obj->rtype.pobj_info.model_num          = convert_polymod(PHYSFSX_readInt(f));
550
#elif defined(DXX_BUILD_DESCENT_II)
551
                        obj->rtype.pobj_info.model_num          = PHYSFSX_readInt(f);
552
#endif
553
 
554
                        range_for (auto &i, obj->rtype.pobj_info.anim_angles)
555
                                PHYSFSX_readAngleVec(&i, f);
556
 
557
                        obj->rtype.pobj_info.subobj_flags       = PHYSFSX_readInt(f);
558
 
559
                        tmo = PHYSFSX_readInt(f);
560
 
561
#if !DXX_USE_EDITOR
562
#if defined(DXX_BUILD_DESCENT_I)
563
                        obj->rtype.pobj_info.tmap_override      = convert_tmap(tmo);
564
#elif defined(DXX_BUILD_DESCENT_II)
565
                        obj->rtype.pobj_info.tmap_override      = tmo;
566
#endif
567
                        #else
568
                        if (tmo==-1)
569
                                obj->rtype.pobj_info.tmap_override      = -1;
570
                        else {
571
                                int xlated_tmo = tmap_xlate_table[tmo];
572
                                if (xlated_tmo < 0)     {
573
                                        Int3();
574
                                        xlated_tmo = 0;
575
                                }
576
                                obj->rtype.pobj_info.tmap_override      = xlated_tmo;
577
                        }
578
                        #endif
579
 
580
                        obj->rtype.pobj_info.alt_textures       = 0;
581
 
582
                        break;
583
                }
584
 
585
                case RT_WEAPON_VCLIP:
586
                case RT_HOSTAGE:
587
                case RT_POWERUP:
588
                case RT_FIREBALL:
589
 
590
#if defined(DXX_BUILD_DESCENT_I)
591
                        obj->rtype.vclip_info.vclip_num = convert_vclip(Vclip, PHYSFSX_readInt(f));
592
#elif defined(DXX_BUILD_DESCENT_II)
593
                        obj->rtype.vclip_info.vclip_num = PHYSFSX_readInt(f);
594
#endif
595
                        obj->rtype.vclip_info.frametime = PHYSFSX_readFix(f);
596
                        obj->rtype.vclip_info.framenum  = PHYSFSX_readByte(f);
597
 
598
                        break;
599
 
600
                case RT_LASER:
601
                        break;
602
 
603
                default:
604
                        Int3();
605
 
606
        }
607
 
608
}
609
}
610
 
611
#if DXX_USE_EDITOR
612
static int PHYSFSX_writeMatrix(PHYSFS_File *file, const vms_matrix &m)
613
{
614
        if (PHYSFSX_writeVector(file, m.rvec) < 1 ||
615
                PHYSFSX_writeVector(file, m.uvec) < 1 ||
616
                PHYSFSX_writeVector(file, m.fvec) < 1)
617
                return 0;
618
        return 1;
619
}
620
 
621
static int PHYSFSX_writeAngleVec(PHYSFS_File *file, const vms_angvec &v)
622
{
623
        if (PHYSFSX_writeFixAng(file, v.p) < 1 ||
624
                PHYSFSX_writeFixAng(file, v.b) < 1 ||
625
                PHYSFSX_writeFixAng(file, v.h) < 1)
626
                return 0;
627
        return 1;
628
}
629
 
630
//writes one object to the given file
631
namespace dsx {
632
static void write_object(const object &obj, short version, PHYSFS_File *f)
633
{
634
#if defined(DXX_BUILD_DESCENT_I)
635
        (void)version;
636
#endif
637
        PHYSFSX_writeU8(f, obj.type);
638
        PHYSFSX_writeU8(f, obj.id);
639
 
640
        PHYSFSX_writeU8(f, obj.control_type);
641
        PHYSFSX_writeU8(f, obj.movement_type);
642
        PHYSFSX_writeU8(f, obj.render_type);
643
        PHYSFSX_writeU8(f, obj.flags);
644
 
645
        PHYSFS_writeSLE16(f, obj.segnum);
646
 
647
        PHYSFSX_writeVector(f, obj.pos);
648
        PHYSFSX_writeMatrix(f, obj.orient);
649
 
650
        PHYSFSX_writeFix(f, obj.size);
651
        PHYSFSX_writeFix(f, obj.shields);
652
 
653
        PHYSFSX_writeVector(f, obj.pos);
654
 
655
        PHYSFSX_writeU8(f, obj.contains_type);
656
        PHYSFSX_writeU8(f, obj.contains_id);
657
        PHYSFSX_writeU8(f, obj.contains_count);
658
 
659
        switch (obj.movement_type) {
660
 
661
                case MT_PHYSICS:
662
 
663
                        PHYSFSX_writeVector(f, obj.mtype.phys_info.velocity);
664
                        PHYSFSX_writeVector(f, obj.mtype.phys_info.thrust);
665
 
666
                        PHYSFSX_writeFix(f, obj.mtype.phys_info.mass);
667
                        PHYSFSX_writeFix(f, obj.mtype.phys_info.drag);
668
                        PHYSFSX_writeFix(f, 0); /* brakes */
669
 
670
                        PHYSFSX_writeVector(f, obj.mtype.phys_info.rotvel);
671
                        PHYSFSX_writeVector(f, obj.mtype.phys_info.rotthrust);
672
 
673
                        PHYSFSX_writeFixAng(f, obj.mtype.phys_info.turnroll);
674
                        PHYSFS_writeSLE16(f, obj.mtype.phys_info.flags);
675
 
676
                        break;
677
 
678
                case MT_SPINNING:
679
 
680
                        PHYSFSX_writeVector(f, obj.mtype.spin_rate);
681
                        break;
682
 
683
                case MT_NONE:
684
                        break;
685
 
686
                default:
687
                        Int3();
688
        }
689
 
690
        switch (obj.control_type) {
691
 
692
                case CT_AI: {
693
                        PHYSFSX_writeU8(f, static_cast<uint8_t>(obj.ctype.ai_info.behavior));
694
 
695
                        range_for (auto &i, obj.ctype.ai_info.flags)
696
                                PHYSFSX_writeU8(f, i);
697
 
698
                        PHYSFS_writeSLE16(f, obj.ctype.ai_info.hide_segment);
699
                        PHYSFS_writeSLE16(f, obj.ctype.ai_info.hide_index);
700
                        PHYSFS_writeSLE16(f, obj.ctype.ai_info.path_length);
701
                        PHYSFS_writeSLE16(f, obj.ctype.ai_info.cur_path_index);
702
 
703
#if defined(DXX_BUILD_DESCENT_I)
704
                        PHYSFS_writeSLE16(f, segment_none);
705
                        PHYSFS_writeSLE16(f, segment_none);
706
#elif defined(DXX_BUILD_DESCENT_II)
707
                        if (version <= 25)
708
                        {
709
                                PHYSFS_writeSLE16(f, -1);       //obj.ctype.ai_info.follow_path_start_seg
710
                                PHYSFS_writeSLE16(f, -1);       //obj.ctype.ai_info.follow_path_end_seg
711
                        }
712
#endif
713
 
714
                        break;
715
                }
716
 
717
                case CT_EXPLOSION:
718
 
719
                        PHYSFSX_writeFix(f, obj.ctype.expl_info.spawn_time);
720
                        PHYSFSX_writeFix(f, obj.ctype.expl_info.delete_time);
721
                        PHYSFS_writeSLE16(f, obj.ctype.expl_info.delete_objnum);
722
 
723
                        break;
724
 
725
                case CT_WEAPON:
726
 
727
                        //do I really need to write these objects?
728
 
729
                        PHYSFS_writeSLE16(f, obj.ctype.laser_info.parent_type);
730
                        PHYSFS_writeSLE16(f, obj.ctype.laser_info.parent_num);
731
                        PHYSFS_writeSLE32(f, obj.ctype.laser_info.parent_signature.get());
732
 
733
                        break;
734
 
735
                case CT_LIGHT:
736
 
737
                        PHYSFSX_writeFix(f, obj.ctype.light_info.intensity);
738
                        break;
739
 
740
                case CT_POWERUP:
741
 
742
#if defined(DXX_BUILD_DESCENT_I)
743
                        PHYSFS_writeSLE32(f, obj.ctype.powerup_info.count);
744
#elif defined(DXX_BUILD_DESCENT_II)
745
                        if (version >= 25)
746
                                PHYSFS_writeSLE32(f, obj.ctype.powerup_info.count);
747
#endif
748
                        break;
749
 
750
                case CT_NONE:
751
                case CT_FLYING:
752
                case CT_DEBRIS:
753
                        break;
754
 
755
                case CT_SLEW:           //the player is generally saved as slew
756
                        break;
757
 
758
                case CT_CNTRLCEN:
759
                        break;                  //control center object.
760
 
761
                case CT_MORPH:
762
                case CT_REPAIRCEN:
763
                case CT_FLYTHROUGH:
764
                default:
765
                        Int3();
766
 
767
        }
768
 
769
        switch (obj.render_type) {
770
 
771
                case RT_NONE:
772
                        break;
773
 
774
                case RT_MORPH:
775
                case RT_POLYOBJ: {
776
                        PHYSFS_writeSLE32(f, obj.rtype.pobj_info.model_num);
777
 
778
                        range_for (auto &i, obj.rtype.pobj_info.anim_angles)
779
                                PHYSFSX_writeAngleVec(f, i);
780
 
781
                        PHYSFS_writeSLE32(f, obj.rtype.pobj_info.subobj_flags);
782
 
783
                        PHYSFS_writeSLE32(f, obj.rtype.pobj_info.tmap_override);
784
 
785
                        break;
786
                }
787
 
788
                case RT_WEAPON_VCLIP:
789
                case RT_HOSTAGE:
790
                case RT_POWERUP:
791
                case RT_FIREBALL:
792
 
793
                        PHYSFS_writeSLE32(f, obj.rtype.vclip_info.vclip_num);
794
                        PHYSFSX_writeFix(f, obj.rtype.vclip_info.frametime);
795
                        PHYSFSX_writeU8(f, obj.rtype.vclip_info.framenum);
796
 
797
                        break;
798
 
799
                case RT_LASER:
800
                        break;
801
 
802
                default:
803
                        Int3();
804
 
805
        }
806
 
807
}
808
}
809
#endif
810
 
811
// --------------------------------------------------------------------
812
// Load game 
813
// Loads all the relevant data for a level.
814
// If level != -1, it loads the filename with extension changed to .min
815
// Otherwise it loads the appropriate level mine.
816
// returns 0=everything ok, 1=old version, -1=error
817
namespace dsx {
818
 
819
static void validate_segment_wall(const vcsegptridx_t seg, shared_side &side, const unsigned sidenum)
820
{
821
        auto &rwn0 = side.wall_num;
822
        const auto wn0 = rwn0;
823
        auto &Walls = LevelUniqueWallSubsystemState.Walls;
824
        auto &vcwallptr = Walls.vcptr;
825
        auto &w0 = *vcwallptr(wn0);
826
        switch (w0.type)
827
        {
828
                case WALL_DOOR:
829
                        {
830
                                const auto connected_seg = seg->children[sidenum];
831
                                if (connected_seg == segment_none)
832
                                {
833
                                        rwn0 = wall_none;
834
                                        LevelError("segment %u side %u wall %u has no child segment; removing orphan wall.", seg.get_unchecked_index(), sidenum, wn0);
835
                                        return;
836
                                }
837
                                const shared_segment &vcseg = *vcsegptr(connected_seg);
838
                                const unsigned connected_side = find_connect_side(seg, vcseg);
839
                                const auto wn1 = vcseg.sides[connected_side].wall_num;
840
                                if (wn1 == wall_none)
841
                                {
842
                                        rwn0 = wall_none;
843
                                        LevelError("segment %u side %u wall %u has child segment %u side %u, but no wall; removing orphan wall.", seg.get_unchecked_index(), sidenum, wn0, connected_seg, connected_side);
844
                                        return;
845
                                }
846
                        }
847
                        break;
848
                default:
849
                        break;
850
        }
851
}
852
 
853
static int load_game_data(
854
#if defined(DXX_BUILD_DESCENT_II)
855
        d_level_shared_destructible_light_state &LevelSharedDestructibleLightState,
856
#endif
857
        fvmobjptridx &vmobjptridx, fvmsegptridx &vmsegptridx, PHYSFS_File *LoadFile)
858
{
859
        auto &Objects = LevelUniqueObjectState.Objects;
860
        auto &vmobjptr = Objects.vmptr;
861
        auto &WallAnims = GameSharedState.WallAnims;
862
        auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters;
863
        const auto &vcsegptridx = vmsegptridx;
864
        short game_top_fileinfo_version;
865
        int object_offset;
866
        unsigned gs_num_objects;
867
        int trig_size;
868
 
869
        //===================== READ FILE INFO ========================
870
 
871
        // Check signature
872
        if (PHYSFSX_readShort(LoadFile) != 0x6705)
873
                return -1;
874
 
875
        // Read and check version number
876
        game_top_fileinfo_version = PHYSFSX_readShort(LoadFile);
877
        if (game_top_fileinfo_version < GAME_COMPATIBLE_VERSION )
878
                return -1;
879
 
880
        // We skip some parts of the former game_top_fileinfo
881
        PHYSFSX_fseek(LoadFile, 31, SEEK_CUR);
882
 
883
        object_offset = PHYSFSX_readInt(LoadFile);
884
        gs_num_objects = PHYSFSX_readInt(LoadFile);
885
        PHYSFSX_fseek(LoadFile, 8, SEEK_CUR);
886
 
887
        init_exploding_walls();
888
        auto &Walls = LevelUniqueWallSubsystemState.Walls;
889
        Walls.set_count(PHYSFSX_readInt(LoadFile));
890
        PHYSFSX_fseek(LoadFile, 20, SEEK_CUR);
891
 
892
        auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
893
        Triggers.set_count(PHYSFSX_readInt(LoadFile));
894
        PHYSFSX_fseek(LoadFile, 24, SEEK_CUR);
895
 
896
        trig_size = PHYSFSX_readInt(LoadFile);
897
        Assert(trig_size == sizeof(ControlCenterTriggers));
898
        (void)trig_size;
899
        PHYSFSX_fseek(LoadFile, 4, SEEK_CUR);
900
 
901
        const unsigned Num_robot_centers = PHYSFSX_readInt(LoadFile);
902
        LevelSharedRobotcenterState.Num_robot_centers = Num_robot_centers;
903
        PHYSFSX_fseek(LoadFile, 4, SEEK_CUR);
904
 
905
#if defined(DXX_BUILD_DESCENT_I)
906
#elif defined(DXX_BUILD_DESCENT_II)
907
        unsigned num_delta_lights;
908
        unsigned Num_static_lights;
909
        if (game_top_fileinfo_version >= 29) {
910
                PHYSFSX_fseek(LoadFile, 4, SEEK_CUR);
911
                Num_static_lights = PHYSFSX_readInt(LoadFile);
912
                PHYSFSX_fseek(LoadFile, 8, SEEK_CUR);
913
                num_delta_lights = PHYSFSX_readInt(LoadFile);
914
                PHYSFSX_fseek(LoadFile, 4, SEEK_CUR);
915
        } else {
916
                Num_static_lights = 0;
917
                num_delta_lights = 0;
918
        }
919
#endif
920
 
921
        if (game_top_fileinfo_version >= 31) //load mine filename
922
                // read newline-terminated string, not sure what version this changed.
923
                PHYSFSX_fgets(Current_level_name,LoadFile);
924
        else if (game_top_fileinfo_version >= 14) { //load mine filename
925
                // read null-terminated string
926
                //must do read one char at a time, since no PHYSFSX_fgets()
927
                for (auto p = Current_level_name.next().begin(); (*p = PHYSFSX_fgetc(LoadFile));)
928
                {
929
                        if (++p == Current_level_name.line().end())
930
                        {
931
                                p[-1] = 0;
932
                                while (PHYSFSX_fgetc(LoadFile))
933
                                        ;
934
                                break;
935
                        }
936
                }
937
        }
938
        else
939
                Current_level_name.next()[0]=0;
940
 
941
        if (game_top_fileinfo_version >= 19) {  //load pof names
942
                const unsigned N_save_pof_names = PHYSFSX_readShort(LoadFile);
943
                if (N_save_pof_names < MAX_POLYGON_MODELS)
944
                        PHYSFS_read(LoadFile,Save_pof_names,N_save_pof_names,FILENAME_LEN);
945
                else
946
                        LevelError("Level contains bogus N_save_pof_names %#x; ignoring", N_save_pof_names);
947
        }
948
 
949
        //===================== READ PLAYER INFO ==========================
950
 
951
 
952
        //===================== READ OBJECT INFO ==========================
953
 
954
        Gamesave_num_org_robots = 0;
955
        Gamesave_num_players = 0;
956
 
957
        if (object_offset > -1) {
958
                if (PHYSFSX_fseek( LoadFile, object_offset, SEEK_SET ))
959
                        Error( "Error seeking to object_offset in gamesave.c" );
960
 
961
                range_for (auto &i, partial_range(Objects, gs_num_objects))
962
                {
963
                        const auto &&o = vmobjptr(&i);
964
                        read_object(o, LoadFile, game_top_fileinfo_version);
965
                        verify_object(Vclip, o);
966
                }
967
        }
968
 
969
        //===================== READ WALL INFO ============================
970
 
971
        auto &vmwallptr = Walls.vmptr;
972
        range_for (const auto &&vw, vmwallptr)
973
        {
974
                auto &nw = *vw;
975
                if (game_top_fileinfo_version >= 20)
976
                        wall_read(LoadFile, nw); // v20 walls and up.
977
                else if (game_top_fileinfo_version >= 17) {
978
                        v19_wall w;
979
                        v19_wall_read(LoadFile, w);
980
                        nw.segnum               = w.segnum;
981
                        nw.sidenum      = w.sidenum;
982
                        nw.linked_wall  = w.linked_wall;
983
                        nw.type         = w.type;
984
                        nw.flags                = w.flags & ~WALL_EXPLODING;
985
                        nw.hps          = w.hps;
986
                        nw.trigger      = w.trigger;
987
#if defined(DXX_BUILD_DESCENT_I)
988
                        nw.clip_num     = convert_wclip(w.clip_num);
989
#elif defined(DXX_BUILD_DESCENT_II)
990
                        nw.clip_num     = w.clip_num;
991
#endif
992
                        nw.keys         = w.keys;
993
                        nw.state                = WALL_DOOR_CLOSED;
994
                } else {
995
                        v16_wall w;
996
                        v16_wall_read(LoadFile, w);
997
                        nw.segnum = segment_none;
998
                        nw.sidenum = nw.linked_wall = -1;
999
                        nw.type         = w.type;
1000
                        nw.flags                = w.flags & ~WALL_EXPLODING;
1001
                        nw.hps          = w.hps;
1002
                        nw.trigger      = w.trigger;
1003
#if defined(DXX_BUILD_DESCENT_I)
1004
                        nw.clip_num     = convert_wclip(w.clip_num);
1005
#elif defined(DXX_BUILD_DESCENT_II)
1006
                        nw.clip_num     = w.clip_num;
1007
#endif
1008
                        nw.keys         = w.keys;
1009
                }
1010
        }
1011
 
1012
        //==================== READ TRIGGER INFO ==========================
1013
 
1014
        auto &vmtrgptr = Triggers.vmptr;
1015
        range_for (const auto vt, vmtrgptr)
1016
        {
1017
                auto &i = *vt;
1018
#if defined(DXX_BUILD_DESCENT_I)
1019
                if (game_top_fileinfo_version <= 25)
1020
                        v25_trigger_read(LoadFile, &i);
1021
                else {
1022
                        v26_trigger_read(LoadFile, i);
1023
                }
1024
#elif defined(DXX_BUILD_DESCENT_II)
1025
                if (game_top_fileinfo_version < 31)
1026
                {
1027
                        if (game_top_fileinfo_version < 30) {
1028
                                v29_trigger_read_as_v31(LoadFile, i);
1029
                        }
1030
                        else
1031
                                v30_trigger_read_as_v31(LoadFile, i);
1032
                }
1033
                else
1034
                        trigger_read(&i, LoadFile);
1035
#endif
1036
        }
1037
 
1038
        //================ READ CONTROL CENTER TRIGGER INFO ===============
1039
 
1040
        control_center_triggers_read(&ControlCenterTriggers, LoadFile);
1041
 
1042
        //================ READ MATERIALOGRIFIZATIONATORS INFO ===============
1043
 
1044
        range_for (auto &&e, enumerate(partial_range(RobotCenters, Num_robot_centers)))
1045
        {
1046
                const uint_fast32_t i = e.idx;
1047
                auto &r = e.value;
1048
#if defined(DXX_BUILD_DESCENT_I)
1049
                matcen_info_read(LoadFile, r, game_top_fileinfo_version);
1050
#elif defined(DXX_BUILD_DESCENT_II)
1051
                if (game_top_fileinfo_version < 27) {
1052
                        d1_matcen_info_read(LoadFile, r);
1053
                }
1054
                else
1055
                        matcen_info_read(LoadFile, r);
1056
#endif
1057
                        //      Set links in RobotCenters to Station array
1058
                range_for (const shared_segment &seg, partial_const_range(Segments, Highest_segment_index + 1))
1059
                        if (seg.special == SEGMENT_IS_ROBOTMAKER)
1060
                                if (seg.matcen_num == i)
1061
                                        r.fuelcen_num = seg.station_idx;
1062
        }
1063
 
1064
#if defined(DXX_BUILD_DESCENT_II)
1065
        //================ READ DL_INDICES INFO ===============
1066
 
1067
        {
1068
        auto &Dl_indices = LevelSharedDestructibleLightState.Dl_indices;
1069
        Dl_indices.set_count(Num_static_lights);
1070
        if (game_top_fileinfo_version < 29)
1071
        {
1072
                if (Num_static_lights)
1073
                        throw std::logic_error("Static lights in old file");
1074
        }
1075
        else
1076
        {
1077
                const auto &&lr = partial_range(Dl_indices, Num_static_lights);
1078
                range_for (auto &i, lr)
1079
                        dl_index_read(&i, LoadFile);
1080
                std::sort(lr.begin(), lr.end());
1081
        }
1082
        }
1083
 
1084
        //      Indicate that no light has been subtracted from any vertices.
1085
        clear_light_subtracted();
1086
 
1087
        //================ READ DELTA LIGHT INFO ===============
1088
 
1089
                if (game_top_fileinfo_version < 29) {
1090
                        ;
1091
                } else
1092
        {
1093
                auto &Delta_lights = LevelSharedDestructibleLightState.Delta_lights;
1094
                range_for (auto &i, partial_range(Delta_lights, num_delta_lights))
1095
                        delta_light_read(&i, LoadFile);
1096
        }
1097
#endif
1098
 
1099
        //========================= UPDATE VARIABLES ======================
1100
 
1101
        reset_objects(LevelUniqueObjectState, gs_num_objects);
1102
 
1103
        range_for (auto &i, Objects)
1104
        {
1105
                if (i.type != OBJ_NONE) {
1106
                        auto objsegnum = i.segnum;
1107
                        if (objsegnum > Highest_segment_index)          //bogus object
1108
                        {
1109
                                Warning("Object %p is in non-existent segment %i, highest=%i", &i, objsegnum, Highest_segment_index);
1110
                                i.type = OBJ_NONE;
1111
                        }
1112
                        else {
1113
                                obj_link_unchecked(Objects.vmptr, vmobjptridx(&i), vmsegptridx(objsegnum));
1114
                        }
1115
                }
1116
        }
1117
 
1118
        clear_transient_objects(1);             //1 means clear proximity bombs
1119
 
1120
        // Make sure non-transparent doors are set correctly.
1121
        range_for (auto &&i, vmsegptridx)
1122
                for (auto &&[sside, uside, side_idx] : zip(i->shared_segment::sides, i->unique_segment::sides, xrange(MAX_SIDES_PER_SEGMENT)))
1123
                {
1124
                        if (sside.wall_num == wall_none)
1125
                                continue;
1126
                        auto &w = *vmwallptr(sside.wall_num);
1127
                        if (w.clip_num != -1)
1128
                        {
1129
                                auto &wa = WallAnims[w.clip_num];
1130
                                if (wa.flags & WCF_TMAP1)
1131
                                {
1132
                                        uside.tmap_num = wa.frames[0];
1133
                                        uside.tmap_num2 = 0;
1134
                                }
1135
                        }
1136
                        validate_segment_wall(i, sside, side_idx);
1137
                }
1138
 
1139
        auto &ActiveDoors = LevelUniqueWallSubsystemState.ActiveDoors;
1140
        ActiveDoors.set_count(0);
1141
 
1142
        //go through all walls, killing references to invalid triggers
1143
        range_for (const auto &&p, vmwallptr)
1144
        {
1145
                auto &w = *p;
1146
                if (w.trigger >= Triggers.get_count()) {
1147
                        w.trigger = trigger_none;       //kill trigger
1148
                }
1149
        }
1150
 
1151
#if DXX_USE_EDITOR
1152
        //go through all triggers, killing unused ones
1153
        {
1154
                const auto &&wr = make_range(vmwallptr);
1155
        for (uint_fast32_t i = 0;i < Triggers.get_count();) {
1156
                auto a = [i](const wall &w) { return w.trigger == i; };
1157
                //      Find which wall this trigger is connected to.
1158
                auto w = std::find_if(wr.begin(), wr.end(), a);
1159
                if (w == wr.end())
1160
                {
1161
                        remove_trigger_num(i);
1162
                }
1163
                else
1164
                        i++;
1165
        }
1166
        }
1167
#endif
1168
 
1169
        //      MK, 10/17/95: Make walls point back at the triggers that control them.
1170
        //      Go through all triggers, stuffing controlling_trigger field in Walls.
1171
        {
1172
#if defined(DXX_BUILD_DESCENT_II)
1173
                range_for (const auto &&w, vmwallptr)
1174
                        w->controlling_trigger = -1;
1175
#endif
1176
 
1177
                auto &vctrgptridx = Triggers.vcptridx;
1178
                range_for (const auto &&t, vctrgptridx)
1179
                {
1180
                        auto &tr = *t;
1181
                        for (unsigned l = 0; l < tr.num_links; ++l)
1182
                        {
1183
                                //check to see that if a trigger requires a wall that it has one,
1184
                                //and if it requires a matcen that it has one
1185
                                const auto seg_num = tr.seg[l];
1186
                                if (trigger_is_matcen(tr))
1187
                                {
1188
                                        if (Segments[seg_num].special != SEGMENT_IS_ROBOTMAKER)
1189
                                                con_printf(CON_URGENT, "matcen %u triggers non-matcen segment %hu", t.get_unchecked_index(), seg_num);
1190
                                }
1191
#if defined(DXX_BUILD_DESCENT_II)
1192
                                else if (tr.type != trigger_action::light_off && tr.type != trigger_action::light_on)
1193
                                {       //light triggers don't require walls
1194
                                        const auto side_num = tr.side[l];
1195
                                        auto wall_num = vmsegptr(seg_num)->shared_segment::sides[side_num].wall_num;
1196
                                        if (const auto &&uwall = vmwallptr.check_untrusted(wall_num))
1197
                                                (*uwall)->controlling_trigger = t;
1198
                                        else
1199
                                        {
1200
                                                LevelError("trigger %u link %u type %u references segment %hu, side %u which is an invalid wall; ignoring.", static_cast<trgnum_t>(t), l, static_cast<unsigned>(tr.type), seg_num, side_num);
1201
                                        }
1202
                                }
1203
#endif
1204
                        }
1205
                }
1206
        }
1207
 
1208
        //fix old wall structs
1209
        if (game_top_fileinfo_version < 17) {
1210
                range_for (const auto &&segp, vcsegptridx)
1211
                {
1212
                        range_for (const int sidenum, xrange(6u))
1213
                        {
1214
                                const auto wallnum = segp->shared_segment::sides[sidenum].wall_num;
1215
                                if (wallnum != wall_none)
1216
                                {
1217
                                        auto &w = *vmwallptr(wallnum);
1218
                                        w.segnum = segp;
1219
                                        w.sidenum = sidenum;
1220
                                }
1221
                        }
1222
                }
1223
        }
1224
        fix_object_segs();
1225
 
1226
        if (game_top_fileinfo_version < GAME_VERSION
1227
#if defined(DXX_BUILD_DESCENT_II)
1228
            && !(game_top_fileinfo_version == 25 && GAME_VERSION == 26)
1229
#endif
1230
                )
1231
                return 1;               //means old version
1232
        else
1233
                return 0;
1234
}
1235
}
1236
 
1237
// ----------------------------------------------------------------------------
1238
 
1239
#if defined(DXX_BUILD_DESCENT_I)
1240
#define LEVEL_FILE_VERSION              1
1241
#elif defined(DXX_BUILD_DESCENT_II)
1242
#define LEVEL_FILE_VERSION      8
1243
#endif
1244
//1 -> 2  add palette name
1245
//2 -> 3  add control center explosion time
1246
//3 -> 4  add reactor strength
1247
//4 -> 5  killed hostage text stuff
1248
//5 -> 6  added Secret_return_segment and Secret_return_orient
1249
//6 -> 7  added flickering lights
1250
//7 -> 8  made version 8 to be not compatible with D2 1.0 & 1.1
1251
 
1252
#ifndef RELEASE
1253
const char *Level_being_loaded=NULL;
1254
#endif
1255
 
1256
#if defined(DXX_BUILD_DESCENT_II)
1257
int no_old_level_file_error=0;
1258
#endif
1259
 
1260
//loads a level (.LVL) file from disk
1261
//returns 0 if success, else error code
1262
namespace dsx {
1263
int load_level(
1264
#if defined(DXX_BUILD_DESCENT_II)
1265
        d_level_shared_destructible_light_state &LevelSharedDestructibleLightState,
1266
#endif
1267
        const char * filename_passed)
1268
{
1269
        auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
1270
        auto &Objects = LevelUniqueObjectState.Objects;
1271
        auto &Vertices = LevelSharedVertexState.get_vertices();
1272
        auto &vmobjptridx = Objects.vmptridx;
1273
#if DXX_USE_EDITOR
1274
        int use_compiled_level=1;
1275
#endif
1276
        char filename[PATH_MAX];
1277
        int sig, minedata_offset, gamedata_offset;
1278
        int mine_err, game_err;
1279
 
1280
        #ifndef RELEASE
1281
        Level_being_loaded = filename_passed;
1282
        #endif
1283
 
1284
        strcpy(filename,filename_passed);
1285
 
1286
#if DXX_USE_EDITOR
1287
        //if we have the editor, try the LVL first, no matter what was passed.
1288
        //if we don't have an LVL, try what was passed or RL2  
1289
        //if we don't have the editor, we just use what was passed
1290
 
1291
        change_filename_extension(filename,filename_passed,".lvl");
1292
        use_compiled_level = 0;
1293
 
1294
        if (!PHYSFSX_exists(filename,1))
1295
        {
1296
                const char *p = strrchr(filename_passed, '.');
1297
 
1298
                if (d_stricmp(p, ".lvl"))
1299
                        strcpy(filename, filename_passed);      // set to what was passed
1300
                else
1301
                        change_filename_extension(filename, filename, "." DXX_LEVEL_FILE_EXTENSION);
1302
                use_compiled_level = 1;
1303
        }              
1304
#endif
1305
 
1306
        auto LoadFile = PHYSFSX_openReadBuffered(filename);
1307
        if (!LoadFile)
1308
        {
1309
                snprintf(filename, sizeof(filename), "%.*s%s", DXX_ptrdiff_cast_int(std::distance(Current_mission->path.cbegin(), Current_mission->filename)), Current_mission->path.c_str(), filename_passed);
1310
                LoadFile = PHYSFSX_openReadBuffered(filename);
1311
        }
1312
 
1313
        if (!LoadFile)  {
1314
#if DXX_USE_EDITOR
1315
                        return 1;
1316
                #else
1317
                        Error("Can't open file <%s>\n",filename);
1318
                #endif
1319
        }
1320
 
1321
        sig                      = PHYSFSX_readInt(LoadFile);
1322
        Gamesave_current_version = PHYSFSX_readInt(LoadFile);
1323
        minedata_offset          = PHYSFSX_readInt(LoadFile);
1324
        gamedata_offset          = PHYSFSX_readInt(LoadFile);
1325
 
1326
        Assert(sig == MAKE_SIG('P','L','V','L'));
1327
        (void)sig;
1328
 
1329
        if (Gamesave_current_version < 5)
1330
                PHYSFSX_readInt(LoadFile);       //was hostagetext_offset
1331
        init_exploding_walls();
1332
#if defined(DXX_BUILD_DESCENT_II)
1333
        if (Gamesave_current_version >= 8) {    //read dummy data
1334
                PHYSFSX_readInt(LoadFile);
1335
                PHYSFSX_readShort(LoadFile);
1336
                PHYSFSX_readByte(LoadFile);
1337
        }
1338
 
1339
        if (Gamesave_current_version > 1)
1340
                PHYSFSX_fgets(Current_level_palette,LoadFile);
1341
        if (Gamesave_current_version <= 1 || Current_level_palette[0]==0) // descent 1 level
1342
                strcpy(Current_level_palette.next().data(), DEFAULT_LEVEL_PALETTE);
1343
 
1344
        if (Gamesave_current_version >= 3)
1345
                LevelSharedControlCenterState.Base_control_center_explosion_time = PHYSFSX_readInt(LoadFile);
1346
        else
1347
                LevelSharedControlCenterState.Base_control_center_explosion_time = DEFAULT_CONTROL_CENTER_EXPLOSION_TIME;
1348
 
1349
        if (Gamesave_current_version >= 4)
1350
                LevelSharedControlCenterState.Reactor_strength = PHYSFSX_readInt(LoadFile);
1351
        else
1352
                LevelSharedControlCenterState.Reactor_strength = -1;  //use old defaults
1353
 
1354
        if (Gamesave_current_version >= 7) {
1355
                Flickering_light_state.Num_flickering_lights = PHYSFSX_readInt(LoadFile);
1356
                range_for (auto &i, partial_range(Flickering_light_state.Flickering_lights, Flickering_light_state.Num_flickering_lights))
1357
                        flickering_light_read(i, LoadFile);
1358
        }
1359
        else
1360
                Flickering_light_state.Num_flickering_lights = 0;
1361
 
1362
        {
1363
                auto &Secret_return_orient = LevelSharedSegmentState.Secret_return_orient;
1364
        if (Gamesave_current_version < 6) {
1365
                LevelSharedSegmentState.Secret_return_segment = segment_first;
1366
                Secret_return_orient.rvec.x = F1_0;
1367
                Secret_return_orient.rvec.y = 0;
1368
                Secret_return_orient.rvec.z = 0;
1369
                Secret_return_orient.fvec.x = 0;
1370
                Secret_return_orient.fvec.y = F1_0;
1371
                Secret_return_orient.fvec.z = 0;
1372
                Secret_return_orient.uvec.x = 0;
1373
                Secret_return_orient.uvec.y = 0;
1374
                Secret_return_orient.uvec.z = F1_0;
1375
        } else {
1376
                LevelSharedSegmentState.Secret_return_segment = PHYSFSX_readInt(LoadFile);
1377
                Secret_return_orient.rvec.x = PHYSFSX_readInt(LoadFile);
1378
                Secret_return_orient.rvec.y = PHYSFSX_readInt(LoadFile);
1379
                Secret_return_orient.rvec.z = PHYSFSX_readInt(LoadFile);
1380
                Secret_return_orient.fvec.x = PHYSFSX_readInt(LoadFile);
1381
                Secret_return_orient.fvec.y = PHYSFSX_readInt(LoadFile);
1382
                Secret_return_orient.fvec.z = PHYSFSX_readInt(LoadFile);
1383
                Secret_return_orient.uvec.x = PHYSFSX_readInt(LoadFile);
1384
                Secret_return_orient.uvec.y = PHYSFSX_readInt(LoadFile);
1385
                Secret_return_orient.uvec.z = PHYSFSX_readInt(LoadFile);
1386
        }
1387
        }
1388
#endif
1389
 
1390
        PHYSFSX_fseek(LoadFile,minedata_offset,SEEK_SET);
1391
#if DXX_USE_EDITOR
1392
        if (!use_compiled_level) {
1393
                mine_err = load_mine_data(LoadFile);
1394
#if 0 // get from d1src if needed
1395
                // Compress all uv coordinates in mine, improves texmap precision. --MK, 02/19/96
1396
                compress_uv_coordinates_all();
1397
#endif
1398
        } else
1399
        #endif
1400
                //NOTE LINK TO ABOVE!!
1401
                mine_err = load_mine_data_compiled(LoadFile, filename);
1402
 
1403
        /* !!!HACK!!!
1404
         * Descent 1 - Level 19: OBERON MINE has some ugly overlapping rooms (segment 484).
1405
         * HACK to make this issue less visible by moving one vertex a little.
1406
         */
1407
        auto &vmvertptr = Vertices.vmptr;
1408
        if (Current_mission && !d_stricmp("Descent: First Strike",Current_mission_longname) && !d_stricmp("level19.rdl",filename) && PHYSFS_fileLength(LoadFile) == 136706)
1409
                vmvertptr(1905u)->z = -385 * F1_0;
1410
#if defined(DXX_BUILD_DESCENT_II)
1411
        /* !!!HACK!!!
1412
         * Descent 2 - Level 12: MAGNACORE STATION has a segment (104) with illegal dimensions.
1413
         * HACK to fix this by moving the Vertex and fixing the associated Normals.
1414
         * NOTE: This only fixes the normals of segment 104, not the other ones connected to this Vertex but this is unsignificant.
1415
         */
1416
        if (Current_mission && !d_stricmp("Descent 2: Counterstrike!",Current_mission_longname) && !d_stricmp("d2levc-4.rl2",filename))
1417
        {
1418
                shared_segment &s104 = *vmsegptr(vmsegidx_t(104));
1419
                auto &s104v0 = *vmvertptr(s104.verts[0]);
1420
                auto &s104s1 = s104.sides[1];
1421
                auto &s104s1n0 = s104s1.normals[0];
1422
                auto &s104s1n1 = s104s1.normals[1];
1423
                auto &s104s2 = s104.sides[2];
1424
                auto &s104s2n0 = s104s2.normals[0];
1425
                auto &s104s2n1 = s104s2.normals[1];
1426
                if (
1427
                        (s104v0.x == -53990800 && s104v0.y == -59927741 && s104v0.z == 23034584) &&
1428
                        (s104s1n0.x == 56775 && s104s1n0.y == -27796 && s104s1n0.z == -17288 && s104s1n1.x == 50157 && s104s1n1.y == -34561 && s104s1n1.z == -24180) &&
1429
                        (s104s2n0.x == 60867 && s104s2n0.y == -19485 && s104s2n0.z == -14507 && s104s2n1.x == 55485 && s104s2n1.y == -29668 && s104s2n1.z == -18332)
1430
                )
1431
                {
1432
                        s104v0.x = -53859726;
1433
                        s104v0.y = -59927743;
1434
                        s104v0.z = 23034586;
1435
                        s104s1n0.x = 56123;
1436
                        s104s1n0.y = -27725;
1437
                        s104s1n0.z = -19401;
1438
                        s104s1n1.x = 49910;
1439
                        s104s1n1.y = -33946;
1440
                        s104s1n1.z = -25525;
1441
                        s104s2n0.x = 60903;
1442
                        s104s2n0.y = -18371;
1443
                        s104s2n0.z = -15753;
1444
                        s104s2n1.x = 57004;
1445
                        s104s2n1.y = -26385;
1446
                        s104s2n1.z = -18688;
1447
                        // I feel so dirty now ...
1448
                }
1449
        }
1450
#endif
1451
 
1452
        if (mine_err == -1) {   //error!!
1453
                return 2;
1454
        }
1455
 
1456
        PHYSFSX_fseek(LoadFile,gamedata_offset,SEEK_SET);
1457
        game_err = load_game_data(
1458
#if defined(DXX_BUILD_DESCENT_II)
1459
                LevelSharedDestructibleLightState,
1460
#endif
1461
                vmobjptridx, vmsegptridx, LoadFile);
1462
 
1463
        if (game_err == -1) {   //error!!
1464
                return 3;
1465
        }
1466
 
1467
        //======================== CLOSE FILE =============================
1468
        LoadFile.reset();
1469
#if defined(DXX_BUILD_DESCENT_II)
1470
        set_ambient_sound_flags();
1471
#endif
1472
 
1473
#if DXX_USE_EDITOR
1474
#if defined(DXX_BUILD_DESCENT_I)
1475
        //If an old version, ask the use if he wants to save as new version
1476
        if (((LEVEL_FILE_VERSION>1) && Gamesave_current_version < LEVEL_FILE_VERSION) || mine_err==1 || game_err==1) {
1477
                gr_palette_load(gr_palette);
1478
                if (nm_messagebox( NULL, 2, "Don't Save", "Save", "You just loaded a old version level.  Would\n"
1479
                                                "you like to save it as a current version level?")==1)
1480
                        save_level(filename);
1481
        }
1482
#elif defined(DXX_BUILD_DESCENT_II)
1483
        //If a Descent 1 level and the Descent 1 pig isn't present, pretend it's a Descent 2 level.
1484
        if (EditorWindow && (Gamesave_current_version <= 3) && !d1_pig_present)
1485
        {
1486
                if (!no_old_level_file_error)
1487
                        Warning("A Descent 1 level was loaded,\n"
1488
                                        "and there is no Descent 1 texture\n"
1489
                                        "set available. Saving it will\n"
1490
                                        "convert it to a Descent 2 level.");
1491
 
1492
                Gamesave_current_version = LEVEL_FILE_VERSION;
1493
        }
1494
#endif
1495
        #endif
1496
 
1497
#if DXX_USE_EDITOR
1498
        if (EditorWindow)
1499
                editor_status_fmt("Loaded NEW mine %s, \"%s\"", filename, static_cast<const char *>(Current_level_name));
1500
        #endif
1501
 
1502
#ifdef NDEBUG
1503
        if (!PLAYING_BUILTIN_MISSION)
1504
#endif
1505
        if (check_segment_connections())
1506
        {
1507
#ifndef NDEBUG
1508
                nm_messagebox( "ERROR", 1, "Ok",
1509
                                "Connectivity errors detected in\n"
1510
                                "mine.  See monochrome screen for\n"
1511
                                "details, and contact Matt or Mike." );
1512
        #endif
1513
        }
1514
 
1515
 
1516
#if defined(DXX_BUILD_DESCENT_II)
1517
        compute_slide_segs();
1518
#endif
1519
        return 0;
1520
}
1521
}
1522
 
1523
#if DXX_USE_EDITOR
1524
int get_level_name()
1525
{
1526
        std::array<newmenu_item, 2> m{{
1527
                nm_item_text("Please enter a name for this mine:"),
1528
                nm_item_input(Current_level_name.next()),
1529
        }};
1530
        return newmenu_do( NULL, "Enter mine name", m, unused_newmenu_subfunction, unused_newmenu_userdata ) >= 0;
1531
}
1532
#endif
1533
 
1534
 
1535
#if DXX_USE_EDITOR
1536
 
1537
// --------------------------------------------------------------------------------------
1538
//      Create a new mine, set global variables.
1539
namespace dsx {
1540
int create_new_mine(void)
1541
{
1542
        auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
1543
        auto &Vertices = LevelSharedVertexState.get_vertices();
1544
        vms_matrix      m1 = IDENTITY_MATRIX;
1545
 
1546
        // initialize_mine_arrays();
1547
 
1548
        //      gamestate_not_restored = 1;
1549
 
1550
        // Clear refueling center code
1551
        fuelcen_reset();
1552
        init_all_vertices();
1553
 
1554
        Current_level_num = 1;          // make level 1 (for now)
1555
        Current_level_name.next()[0] = 0;
1556
#if defined(DXX_BUILD_DESCENT_I)
1557
        Gamesave_current_version = LEVEL_FILE_VERSION;
1558
#elif defined(DXX_BUILD_DESCENT_II)
1559
        Gamesave_current_version = GAME_VERSION;
1560
 
1561
        strcpy(Current_level_palette.next().data(), DEFAULT_LEVEL_PALETTE);
1562
#endif
1563
 
1564
        Cur_object_index = -1;
1565
        reset_objects(LevelUniqueObjectState, 1);               //just one object, the player
1566
 
1567
        num_groups = 0;
1568
        current_group = -1;
1569
 
1570
 
1571
        LevelSharedVertexState.Num_vertices = 0;                // Number of vertices in global array.
1572
        Vertices.set_count(1);
1573
        LevelSharedSegmentState.Num_segments = 0;               // Number of segments in global array, will get increased in med_create_segment
1574
        Segments.set_count(1);
1575
        Cursegp = imsegptridx(segment_first);   // Say current segment is the only segment.
1576
        Curside = WBACK;                // The active side is the back side
1577
        Markedsegp = segment_none;              // Say there is no marked segment.
1578
        Markedside = WBACK;     //      Shouldn't matter since Markedsegp == 0, but just in case...
1579
        for (int s=0;s<MAX_GROUPS+1;s++) {
1580
                GroupList[s].clear();
1581
                Groupsegp[s] = NULL;
1582
                Groupside[s] = 0;
1583
        }
1584
 
1585
        LevelSharedRobotcenterState.Num_robot_centers = 0;
1586
        auto &ActiveDoors = LevelUniqueWallSubsystemState.ActiveDoors;
1587
        ActiveDoors.set_count(0);
1588
        wall_init();
1589
        trigger_init();
1590
 
1591
        // Create New_segment, which is the segment we will be adding at each instance.
1592
        med_create_new_segment({DEFAULT_X_SIZE, DEFAULT_Y_SIZE, DEFAULT_Z_SIZE});               // New_segment = Segments[0];
1593
        //      med_create_segment(Segments,0,0,0,DEFAULT_X_SIZE,DEFAULT_Y_SIZE,DEFAULT_Z_SIZE,vm_mat_make(&m1,F1_0,0,0,0,F1_0,0,0,0,F1_0));
1594
        med_create_segment(vmsegptridx(segment_first), 0, 0, 0, DEFAULT_X_SIZE, DEFAULT_Y_SIZE, DEFAULT_Z_SIZE, m1);
1595
 
1596
        Found_segs.clear();
1597
        Selected_segs.clear();
1598
        Warning_segs.clear();
1599
 
1600
        //--repair-- create_local_segment_data();
1601
 
1602
        ControlCenterTriggers.num_links = 0;
1603
 
1604
        create_new_mission();
1605
 
1606
    //editor_status("New mine created.");
1607
        return  0;                      // say no error
1608
}
1609
}
1610
 
1611
int     Errors_in_mine;
1612
 
1613
namespace dsx {
1614
// -----------------------------------------------------------------------------
1615
#if defined(DXX_BUILD_DESCENT_II)
1616
static unsigned compute_num_delta_light_records(fvcdlindexptr &vcdlindexptr)
1617
{
1618
        unsigned total = 0;
1619
        range_for (const auto &&i, vcdlindexptr)
1620
                total += i->count;
1621
        return total;
1622
 
1623
}
1624
#endif
1625
 
1626
// -----------------------------------------------------------------------------
1627
// Save game
1628
static int save_game_data(
1629
#if defined(DXX_BUILD_DESCENT_II)
1630
        const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState,
1631
#endif
1632
        PHYSFS_File *SaveFile)
1633
{
1634
        auto &Objects = LevelUniqueObjectState.Objects;
1635
        auto &vcobjptr = Objects.vcptr;
1636
        auto &RobotCenters = LevelSharedRobotcenterState.RobotCenters;
1637
#if defined(DXX_BUILD_DESCENT_I)
1638
        short game_top_fileinfo_version = Gamesave_current_version >= 5 ? 31 : GAME_VERSION;
1639
#elif defined(DXX_BUILD_DESCENT_II)
1640
        short game_top_fileinfo_version = Gamesave_current_version >= 5 ? 31 : 25;
1641
        int     dl_indices_offset=0, delta_light_offset=0;
1642
#endif
1643
        int  player_offset=0, object_offset=0, walls_offset=0, doors_offset=0, triggers_offset=0, control_offset=0, matcen_offset=0; //, links_offset;
1644
        int offset_offset=0, end_offset=0;
1645
        //===================== SAVE FILE INFO ========================
1646
 
1647
        PHYSFS_writeSLE16(SaveFile, 0x6705);    // signature
1648
        PHYSFS_writeSLE16(SaveFile, game_top_fileinfo_version);
1649
        PHYSFS_writeSLE32(SaveFile, 0);
1650
        PHYSFS_write(SaveFile, Current_level_name.line(), 15, 1);
1651
        PHYSFS_writeSLE32(SaveFile, Current_level_num);
1652
        offset_offset = PHYSFS_tell(SaveFile);  // write the offsets later
1653
        PHYSFS_writeSLE32(SaveFile, -1);
1654
        PHYSFS_writeSLE32(SaveFile, 0);
1655
 
1656
#define WRITE_HEADER_ENTRY(t, n) do { PHYSFS_writeSLE32(SaveFile, -1); PHYSFS_writeSLE32(SaveFile, n); PHYSFS_writeSLE32(SaveFile, sizeof(t)); } while(0)
1657
 
1658
        WRITE_HEADER_ENTRY(object, Highest_object_index + 1);
1659
        auto &Walls = LevelUniqueWallSubsystemState.Walls;
1660
        WRITE_HEADER_ENTRY(wall, Walls.get_count());
1661
        auto &ActiveDoors = LevelUniqueWallSubsystemState.ActiveDoors;
1662
        WRITE_HEADER_ENTRY(active_door, ActiveDoors.get_count());
1663
        auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
1664
        WRITE_HEADER_ENTRY(trigger, Triggers.get_count());
1665
        WRITE_HEADER_ENTRY(0, 0);               // links (removed by Parallax)
1666
        WRITE_HEADER_ENTRY(control_center_triggers, 1);
1667
        const auto Num_robot_centers = LevelSharedRobotcenterState.Num_robot_centers;
1668
        WRITE_HEADER_ENTRY(matcen_info, Num_robot_centers);
1669
 
1670
#if defined(DXX_BUILD_DESCENT_II)
1671
        unsigned num_delta_lights = 0;
1672
        if (game_top_fileinfo_version >= 29)
1673
        {
1674
                auto &Dl_indices = LevelSharedDestructibleLightState.Dl_indices;
1675
                const unsigned Num_static_lights = Dl_indices.get_count();
1676
                WRITE_HEADER_ENTRY(dl_index, Num_static_lights);
1677
                WRITE_HEADER_ENTRY(delta_light, num_delta_lights = compute_num_delta_light_records(Dl_indices.vcptr));
1678
        }
1679
 
1680
        // Write the mine name
1681
        if (game_top_fileinfo_version >= 31)
1682
#endif
1683
                PHYSFSX_printf(SaveFile, "%s\n", static_cast<const char *>(Current_level_name));
1684
#if defined(DXX_BUILD_DESCENT_II)
1685
        else if (game_top_fileinfo_version >= 14)
1686
                PHYSFSX_writeString(SaveFile, Current_level_name);
1687
 
1688
        if (game_top_fileinfo_version >= 19)
1689
#endif
1690
        {
1691
                PHYSFS_writeSLE16(SaveFile, N_polygon_models);
1692
                range_for (auto &i, partial_const_range(Pof_names, N_polygon_models))
1693
                        PHYSFS_write(SaveFile, &i, sizeof(i), 1);
1694
        }
1695
 
1696
        //==================== SAVE PLAYER INFO ===========================
1697
 
1698
        player_offset = PHYSFS_tell(SaveFile);
1699
 
1700
        //==================== SAVE OBJECT INFO ===========================
1701
 
1702
        object_offset = PHYSFS_tell(SaveFile);
1703
        range_for (const auto &&objp, vcobjptr)
1704
        {
1705
                write_object(objp, game_top_fileinfo_version, SaveFile);
1706
        }
1707
 
1708
        //==================== SAVE WALL INFO =============================
1709
 
1710
        walls_offset = PHYSFS_tell(SaveFile);
1711
        auto &vcwallptr = Walls.vcptr;
1712
        range_for (const auto &&w, vcwallptr)
1713
                wall_write(SaveFile, *w, game_top_fileinfo_version);
1714
 
1715
        //==================== SAVE TRIGGER INFO =============================
1716
 
1717
        triggers_offset = PHYSFS_tell(SaveFile);
1718
        auto &vctrgptr = Triggers.vcptr;
1719
        range_for (const auto vt, vctrgptr)
1720
        {
1721
                auto &t = *vt;
1722
                if (game_top_fileinfo_version <= 29)
1723
                        v29_trigger_write(SaveFile, t);
1724
                else if (game_top_fileinfo_version <= 30)
1725
                        v30_trigger_write(SaveFile, t);
1726
                else if (game_top_fileinfo_version >= 31)
1727
                        v31_trigger_write(SaveFile, t);
1728
        }
1729
 
1730
        //================ SAVE CONTROL CENTER TRIGGER INFO ===============
1731
 
1732
        control_offset = PHYSFS_tell(SaveFile);
1733
        control_center_triggers_write(&ControlCenterTriggers, SaveFile);
1734
 
1735
 
1736
        //================ SAVE MATERIALIZATION CENTER TRIGGER INFO ===============
1737
 
1738
        matcen_offset = PHYSFS_tell(SaveFile);
1739
        range_for (auto &r, partial_const_range(RobotCenters, Num_robot_centers))
1740
                matcen_info_write(SaveFile, r, game_top_fileinfo_version);
1741
 
1742
        //================ SAVE DELTA LIGHT INFO ===============
1743
#if defined(DXX_BUILD_DESCENT_II)
1744
        if (game_top_fileinfo_version >= 29)
1745
        {
1746
                dl_indices_offset = PHYSFS_tell(SaveFile);
1747
                auto &Dl_indices = LevelSharedDestructibleLightState.Dl_indices;
1748
                range_for (const auto &&i, Dl_indices.vcptr)
1749
                        dl_index_write(i, SaveFile);
1750
 
1751
                delta_light_offset = PHYSFS_tell(SaveFile);
1752
                auto &Delta_lights = LevelSharedDestructibleLightState.Delta_lights;
1753
                range_for (auto &i, partial_const_range(Delta_lights, num_delta_lights))
1754
                        delta_light_write(&i, SaveFile);
1755
        }
1756
#endif
1757
 
1758
        //============= SAVE OFFSETS ===============
1759
 
1760
        end_offset = PHYSFS_tell(SaveFile);
1761
 
1762
        // Update the offset fields
1763
 
1764
#define WRITE_OFFSET(o, n) do { PHYSFS_seek(SaveFile, offset_offset); PHYSFS_writeSLE32(SaveFile, o ## _offset); offset_offset += sizeof(int)*n; } while (0)
1765
 
1766
        WRITE_OFFSET(player, 2);
1767
        WRITE_OFFSET(object, 3);
1768
        WRITE_OFFSET(walls, 3);
1769
        WRITE_OFFSET(doors, 3);
1770
        WRITE_OFFSET(triggers, 6);
1771
        WRITE_OFFSET(control, 3);
1772
        WRITE_OFFSET(matcen, 3);
1773
#if defined(DXX_BUILD_DESCENT_II)
1774
        if (game_top_fileinfo_version >= 29)
1775
        {
1776
                WRITE_OFFSET(dl_indices, 3);
1777
                WRITE_OFFSET(delta_light, 0);
1778
        }
1779
#endif
1780
 
1781
        // Go back to end of data
1782
        PHYSFS_seek(SaveFile, end_offset);
1783
 
1784
        return 0;
1785
}
1786
 
1787
// -----------------------------------------------------------------------------
1788
// Save game
1789
static int save_level_sub(
1790
#if defined(DXX_BUILD_DESCENT_II)
1791
        const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState,
1792
#endif
1793
        fvmobjptridx &vmobjptridx, const char *const filename)
1794
{
1795
        auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
1796
        auto &Objects = LevelUniqueObjectState.Objects;
1797
        auto &Vertices = LevelSharedVertexState.get_vertices();
1798
        auto &vmobjptr = Objects.vmptr;
1799
        char temp_filename[PATH_MAX];
1800
        int minedata_offset=0,gamedata_offset=0;
1801
 
1802
//      if ( !compiled_version )
1803
        {
1804
                write_game_text_file(filename);
1805
 
1806
                if (Errors_in_mine) {
1807
                        if (is_real_level(filename)) {
1808
                                gr_palette_load(gr_palette);
1809
 
1810
                                if (nm_messagebox( NULL, 2, "Cancel Save", "Save", "Warning: %i errors in this mine!\n", Errors_in_mine )!=1)   {
1811
                                        return 1;
1812
                                }
1813
                        }
1814
                }
1815
//              change_filename_extension(temp_filename,filename,".LVL");
1816
        }
1817
//      else
1818
        {
1819
#if defined(DXX_BUILD_DESCENT_II)
1820
                if (Gamesave_current_version > 3)
1821
                        change_filename_extension(temp_filename, filename, "." D2X_LEVEL_FILE_EXTENSION);
1822
                else
1823
#endif
1824
                        change_filename_extension(temp_filename, filename, "." D1X_LEVEL_FILE_EXTENSION);
1825
        }
1826
 
1827
        auto SaveFile = PHYSFSX_openWriteBuffered(temp_filename);
1828
        if (!SaveFile)
1829
        {
1830
                gr_palette_load(gr_palette);
1831
                nm_messagebox( NULL, 1, "Ok", "ERROR: Cannot write to '%s'.", temp_filename);
1832
                return 1;
1833
        }
1834
 
1835
        if (Current_level_name[0] == 0)
1836
                strcpy(Current_level_name.next().data(),"Untitled");
1837
 
1838
        clear_transient_objects(1);             //1 means clear proximity bombs
1839
 
1840
        compress_objects();             //after this, Highest_object_index == num objects
1841
 
1842
        //make sure player is in a segment
1843
        {
1844
                const auto &&plr = vmobjptridx(vcplayerptr(0u)->objnum);
1845
                if (update_object_seg(vmobjptr, LevelSharedSegmentState, LevelUniqueSegmentState, plr) == 0)
1846
                {
1847
                        if (plr->segnum > Highest_segment_index)
1848
                                plr->segnum = segment_first;
1849
                        auto &vcvertptr = Vertices.vcptr;
1850
                        compute_segment_center(vcvertptr, plr->pos, vcsegptr(plr->segnum));
1851
                }
1852
        }
1853
 
1854
        fix_object_segs();
1855
 
1856
        //Write the header
1857
 
1858
        PHYSFS_writeSLE32(SaveFile, MAKE_SIG('P','L','V','L'));
1859
        PHYSFS_writeSLE32(SaveFile, Gamesave_current_version);
1860
 
1861
        //save placeholders
1862
        PHYSFS_writeSLE32(SaveFile, minedata_offset);
1863
        PHYSFS_writeSLE32(SaveFile, gamedata_offset);
1864
#if defined(DXX_BUILD_DESCENT_I)
1865
        int hostagetext_offset = 0;
1866
        PHYSFS_writeSLE32(SaveFile, hostagetext_offset);
1867
#endif
1868
 
1869
        //Now write the damn data
1870
 
1871
#if defined(DXX_BUILD_DESCENT_II)
1872
        if (Gamesave_current_version >= 8)
1873
        {
1874
                //write the version 8 data (to make file unreadable by 1.0 & 1.1)
1875
                PHYSFS_writeSLE32(SaveFile, GameTime64);
1876
                PHYSFS_writeSLE16(SaveFile, d_tick_count);
1877
                PHYSFSX_writeU8(SaveFile, FrameTime);
1878
        }
1879
 
1880
        if (Gamesave_current_version < 5)
1881
                PHYSFS_writeSLE32(SaveFile, -1);       //was hostagetext_offset
1882
 
1883
        // Write the palette file name
1884
        if (Gamesave_current_version > 1)
1885
                PHYSFSX_printf(SaveFile, "%s\n", static_cast<const char *>(Current_level_palette));
1886
 
1887
        if (Gamesave_current_version >= 3)
1888
                PHYSFS_writeSLE32(SaveFile, LevelSharedControlCenterState.Base_control_center_explosion_time);
1889
        if (Gamesave_current_version >= 4)
1890
                PHYSFS_writeSLE32(SaveFile, LevelSharedControlCenterState.Reactor_strength);
1891
 
1892
        if (Gamesave_current_version >= 7)
1893
        {
1894
                const auto Num_flickering_lights = Flickering_light_state.Num_flickering_lights;
1895
                PHYSFS_writeSLE32(SaveFile, Num_flickering_lights);
1896
                range_for (auto &i, partial_const_range(Flickering_light_state.Flickering_lights, Num_flickering_lights))
1897
                        flickering_light_write(i, SaveFile);
1898
        }
1899
 
1900
        if (Gamesave_current_version >= 6)
1901
        {
1902
                PHYSFS_writeSLE32(SaveFile, LevelSharedSegmentState.Secret_return_segment);
1903
                auto &Secret_return_orient = LevelSharedSegmentState.Secret_return_orient;
1904
                PHYSFSX_writeVector(SaveFile, Secret_return_orient.rvec);
1905
                PHYSFSX_writeVector(SaveFile, Secret_return_orient.fvec);
1906
                PHYSFSX_writeVector(SaveFile, Secret_return_orient.uvec);
1907
        }
1908
#endif
1909
 
1910
        minedata_offset = PHYSFS_tell(SaveFile);
1911
#if 0   // only save compiled mine data
1912
        if ( !compiled_version )       
1913
                save_mine_data(SaveFile);
1914
        else
1915
#endif
1916
                save_mine_data_compiled(SaveFile);
1917
        gamedata_offset = PHYSFS_tell(SaveFile);
1918
        save_game_data(
1919
#if defined(DXX_BUILD_DESCENT_II)
1920
                LevelSharedDestructibleLightState,
1921
#endif
1922
                SaveFile);
1923
#if defined(DXX_BUILD_DESCENT_I)
1924
        hostagetext_offset = PHYSFS_tell(SaveFile);
1925
#endif
1926
 
1927
        PHYSFS_seek(SaveFile, sizeof(int) + sizeof(Gamesave_current_version));
1928
        PHYSFS_writeSLE32(SaveFile, minedata_offset);
1929
        PHYSFS_writeSLE32(SaveFile, gamedata_offset);
1930
#if defined(DXX_BUILD_DESCENT_I)
1931
        PHYSFS_writeSLE32(SaveFile, hostagetext_offset);
1932
#elif defined(DXX_BUILD_DESCENT_II)
1933
        if (Gamesave_current_version < 5)
1934
                PHYSFS_writeSLE32(SaveFile, PHYSFS_fileLength(SaveFile));
1935
#endif
1936
 
1937
        //==================== CLOSE THE FILE =============================
1938
 
1939
//      if ( !compiled_version )
1940
        {
1941
                if (EditorWindow)
1942
                        editor_status_fmt("Saved mine %s, \"%s\"", filename, static_cast<const char *>(Current_level_name));
1943
        }
1944
 
1945
        return 0;
1946
 
1947
}
1948
 
1949
int save_level(
1950
#if defined(DXX_BUILD_DESCENT_II)
1951
        const d_level_shared_destructible_light_state &LevelSharedDestructibleLightState,
1952
#endif
1953
        const char * filename)
1954
{
1955
        auto &Objects = LevelUniqueObjectState.Objects;
1956
        auto &vmobjptridx = Objects.vmptridx;
1957
        int r1;
1958
 
1959
        // Save compiled version...
1960
        r1 = save_level_sub(
1961
#if defined(DXX_BUILD_DESCENT_II)
1962
                LevelSharedDestructibleLightState,
1963
#endif
1964
                vmobjptridx, filename);
1965
 
1966
        return r1;
1967
}
1968
}
1969
 
1970
#endif  //EDITOR