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