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