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  * Code to make a complete demo playback system.
23  *
24  */
25 
26 #include <cstdlib>
27 #include <ctime>
28 #include <stdio.h>
29 #include <stdarg.h>
30 #include <errno.h>
31 #include <ctype.h>
32 #include <type_traits>
33 #include "d_range.h"
34 
35 #include "u_mem.h"
36 #include "inferno.h"
37 #include "game.h"
38 #include "gr.h"
39 #include "bm.h"
40 #include "3d.h"
41 #include "weapon.h"
42 #include "segment.h"
43 #include "strutil.h"
44 #include "gameseg.h"
45 
46 #include "object.h"
47 #include "render.h"
48 #include "wall.h"
49 #include "vclip.h"
50 #include "robot.h"
51 #include "dxxerror.h"
52 #include "ai.h"
53 #include "hostage.h"
54 #include "morph.h"
55 #include "physfs_list.h"
56 
57 #include "powerup.h"
58 
59 #include "sounds.h"
60 #include "collide.h"
61 
62 #include "lighting.h"
63 #include "newdemo.h"
64 #include "newmenu.h"
65 #include "gameseq.h"
66 #include "hudmsg.h"
67 #include "switch.h"
68 #include "gauges.h"
69 #include "player.h"
70 #include "vecmat.h"
71 #include "menu.h"
72 #include "args.h"
73 #include "palette.h"
74 #include "multi.h"
75 #include "text.h"
76 #include "cntrlcen.h"
77 #include "aistruct.h"
78 #include "mission.h"
79 #include "piggy.h"
80 #include "console.h"
81 #include "controls.h"
82 #include "playsave.h"
83 
84 #include "compiler-range_for.h"
85 #include "d_levelstate.h"
86 #include "partial_range.h"
87 #include <utility>
88 
89 #define ND_EVENT_EOF				0	// EOF
90 #define ND_EVENT_START_DEMO			1	// Followed by 16 character, NULL terminated filename of .SAV file to use
91 #define ND_EVENT_START_FRAME			2	// Followed by integer frame number, then a fix FrameTime
92 #define ND_EVENT_VIEWER_OBJECT			3	// Followed by an object structure
93 #define ND_EVENT_RENDER_OBJECT			4	// Followed by an object structure
94 #define ND_EVENT_SOUND				5	// Followed by int soundum
95 #define ND_EVENT_SOUND_ONCE			6	// Followed by int soundum
96 #define ND_EVENT_SOUND_3D			7	// Followed by int soundum, int angle, int volume
97 #define ND_EVENT_WALL_HIT_PROCESS		8	// Followed by int segnum, int side, fix damage
98 #define ND_EVENT_TRIGGER			9	// Followed by int segnum, int side, int objnum
99 #define ND_EVENT_HOSTAGE_RESCUED 		10	// Followed by int hostage_type
100 #define ND_EVENT_SOUND_3D_ONCE			11	// Followed by int soundum, int angle, int volume
101 #define ND_EVENT_MORPH_FRAME			12	// Followed by ? data
102 #define ND_EVENT_WALL_TOGGLE			13	// Followed by int seg, int side
103 #define ND_EVENT_HUD_MESSAGE			14	// Followed by char size, char * string (+null)
104 #define ND_EVENT_CONTROL_CENTER_DESTROYED 	15	// Just a simple flag
105 #define ND_EVENT_PALETTE_EFFECT			16	// Followed by short r,g,b
106 #define ND_EVENT_PLAYER_ENERGY			17	// followed by byte energy
107 #define ND_EVENT_PLAYER_SHIELD			18	// followed by byte shields
108 #define ND_EVENT_PLAYER_FLAGS			19	// followed by player flags
109 #define ND_EVENT_PLAYER_WEAPON			20	// followed by weapon type and weapon number
110 #define ND_EVENT_EFFECT_BLOWUP			21	// followed by segment, side, and pnt
111 #define ND_EVENT_HOMING_DISTANCE		22	// followed by homing distance
112 #define ND_EVENT_LETTERBOX			23	// letterbox mode for death seq.
113 #define ND_EVENT_RESTORE_COCKPIT		24	// restore cockpit after death
114 #define ND_EVENT_REARVIEW			25	// going to rear view mode
115 #define ND_EVENT_WALL_SET_TMAP_NUM1		26	// Wall changed
116 #define ND_EVENT_WALL_SET_TMAP_NUM2		27	// Wall changed
117 #define ND_EVENT_NEW_LEVEL			28	// followed by level number
118 #define ND_EVENT_MULTI_CLOAK			29	// followed by player num
119 #define ND_EVENT_MULTI_DECLOAK			30	// followed by player num
120 #define ND_EVENT_RESTORE_REARVIEW		31	// restore cockpit after rearview mode
121 #define ND_EVENT_MULTI_DEATH			32	// with player number
122 #define ND_EVENT_MULTI_KILL			33	// with player number
123 #define ND_EVENT_MULTI_CONNECT			34	// with player number
124 #define ND_EVENT_MULTI_RECONNECT		35	// with player number
125 #define ND_EVENT_MULTI_DISCONNECT		36	// with player number
126 #define ND_EVENT_MULTI_SCORE			37	// playernum / score
127 #define ND_EVENT_PLAYER_SCORE			38	// followed by score
128 #define ND_EVENT_PRIMARY_AMMO			39	// with old/new ammo count
129 #define ND_EVENT_SECONDARY_AMMO			40	// with old/new ammo count
130 #define ND_EVENT_DOOR_OPENING			41	// with segment/side
131 #if defined(DXX_BUILD_DESCENT_I)
132 #define ND_EVENT_LASER_LEVEL			42	// with old/new level
133 #define ND_EVENT_LINK_SOUND_TO_OBJ		43	// record digi_link_sound_to_object3
134 #define ND_EVENT_KILL_SOUND_TO_OBJ		44	// record digi_kill_sound_linked_to_object
135 #elif defined(DXX_BUILD_DESCENT_II)
136 #define ND_EVENT_LASER_LEVEL			42	// no data
137 #define ND_EVENT_PLAYER_AFTERBURNER		43	// followed by byte old ab, current ab
138 #define ND_EVENT_CLOAKING_WALL			44	// info changing while wall cloaking
139 #define ND_EVENT_CHANGE_COCKPIT			45	// change the cockpit
140 #define ND_EVENT_START_GUIDED			46	// switch to guided view
141 #define ND_EVENT_END_GUIDED			47	// stop guided view/return to ship
142 #define ND_EVENT_SECRET_THINGY			48	// 0/1 = secret exit functional/non-functional
143 #define ND_EVENT_LINK_SOUND_TO_OBJ		49	// record digi_link_sound_to_object3
144 #define ND_EVENT_KILL_SOUND_TO_OBJ		50	// record digi_kill_sound_linked_to_object
145 #endif
146 
147 #define NORMAL_PLAYBACK 			0
148 #define SKIP_PLAYBACK				1
149 #define INTERPOLATE_PLAYBACK			2
150 #define INTERPOL_FACTOR				(F1_0 + (F1_0/5))
151 
152 #if defined(DXX_BUILD_DESCENT_I)
153 #define DEMO_VERSION_SHAREWARE		5
154 #define DEMO_VERSION				13
155 #define DEMO_GAME_TYPE_SHAREWARE	1
156 #define DEMO_GAME_TYPE				2
157 #elif defined(DXX_BUILD_DESCENT_II)
158 #define DEMO_VERSION				15      // last D1 version was 13
159 #define DEMO_GAME_TYPE				3       // 1 was shareware, 2 registered
160 #endif
161 
162 #define DEMO_FILENAME				DEMO_DIR "tmpdemo.dem"
163 
164 #define DEMO_MAX_LEVELS				29
165 
166 const std::array<file_extension_t, 1> demo_file_extensions{{DEMO_EXT}};
167 
168 // In- and Out-files
169 static RAIIPHYSFS_File infile;
170 static RAIIPHYSFS_File outfile;
171 
172 namespace dcx {
173 game_mode_flags Newdemo_game_mode;
174 }
175 // Some globals
176 int Newdemo_state = 0;
177 int Newdemo_vcr_state = 0;
178 int Newdemo_show_percentage=1;
179 sbyte Newdemo_do_interpolate = 1;
180 int Newdemo_num_written;
181 #if defined(DXX_BUILD_DESCENT_II)
182 ubyte DemoDoRight=0,DemoDoLeft=0;
183 object DemoRightExtra,DemoLeftExtra;
184 
185 static void nd_render_extras (ubyte which,const object &obj);
186 #endif
187 
188 // local var used for swapping endian demos
189 static int swap_endian = 0;
190 
191 // playback variables
192 static unsigned int nd_playback_v_demosize;
193 static callsign_t nd_playback_v_save_callsign;
194 static sbyte nd_playback_v_at_eof;
195 static sbyte nd_playback_v_cntrlcen_destroyed = 0;
196 static sbyte nd_playback_v_bad_read;
197 static int nd_playback_v_framecount;
198 static fix nd_playback_total, nd_recorded_total, nd_recorded_time;
199 static sbyte nd_playback_v_style;
200 static ubyte nd_playback_v_dead = 0, nd_playback_v_rear = 0;
201 #if defined(DXX_BUILD_DESCENT_II)
202 static ubyte nd_playback_v_guided = 0;
203 int nd_playback_v_juststarted=0;
204 #endif
205 
206 // record variables
207 #define REC_DELAY F1_0/20
208 static int nd_record_v_start_frame = -1;
209 static int nd_record_v_frame_number = -1;
210 static short nd_record_v_framebytes_written = 0;
211 static int nd_record_v_recordframe = 1;
212 static fix64 nd_record_v_recordframe_last_time = 0;
213 static sbyte nd_record_v_no_space;
214 #if defined(DXX_BUILD_DESCENT_II)
215 static int nd_record_v_juststarted = 0;
216 static std::array<sbyte, MAX_OBJECTS> nd_record_v_objs,
217 	nd_record_v_viewobjs;
218 static std::array<sbyte, 32> nd_record_v_rendering;
219 static fix nd_record_v_player_afterburner = -1;
220 #endif
221 static int nd_record_v_player_energy = -1;
222 static int nd_record_v_player_shields = -1;
223 static uint nd_record_v_player_flags = -1;
224 static int nd_record_v_weapon_type = -1;
225 static int nd_record_v_weapon_num = -1;
226 static fix nd_record_v_homing_distance = -1;
227 static int nd_record_v_primary_ammo = -1;
228 static int nd_record_v_secondary_ammo = -1;
229 
230 namespace dsx {
231 static void newdemo_record_oneframeevent_update(int wallupdate);
232 }
233 #if defined(DXX_BUILD_DESCENT_I)
234 static int shareware = 0;	// reading shareware demo?
235 #elif defined(DXX_BUILD_DESCENT_II)
236 constexpr std::integral_constant<int, 0> shareware{};
237 #endif
238 
newdemo_get_percent_done()239 int newdemo_get_percent_done()	{
240 	if ( Newdemo_state == ND_STATE_PLAYBACK ) {
241 		return (PHYSFS_tell(infile) * 100) / nd_playback_v_demosize;
242 	}
243 	if ( Newdemo_state == ND_STATE_RECORDING ) {
244 		return PHYSFS_tell(outfile);
245 	}
246 	return 0;
247 }
248 
249 #define VEL_PRECISION 12
250 
my_extract_shortpos(object_base & objp,const shortpos * const spp)251 static void my_extract_shortpos(object_base &objp, const shortpos *const spp)
252 {
253 	auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
254 	auto &Vertices = LevelSharedVertexState.get_vertices();
255 	auto sp = spp->bytemat.data();
256 	objp.orient.rvec.x = *sp++ << MATRIX_PRECISION;
257 	objp.orient.uvec.x = *sp++ << MATRIX_PRECISION;
258 	objp.orient.fvec.x = *sp++ << MATRIX_PRECISION;
259 
260 	objp.orient.rvec.y = *sp++ << MATRIX_PRECISION;
261 	objp.orient.uvec.y = *sp++ << MATRIX_PRECISION;
262 	objp.orient.fvec.y = *sp++ << MATRIX_PRECISION;
263 
264 	objp.orient.rvec.z = *sp++ << MATRIX_PRECISION;
265 	objp.orient.uvec.z = *sp++ << MATRIX_PRECISION;
266 	objp.orient.fvec.z = *sp++ << MATRIX_PRECISION;
267 
268 	segnum_t segnum = spp->segment;
269 	objp.segnum = segnum;
270 
271 	auto &vcvertptr = Vertices.vcptr;
272 	auto &v = *vcvertptr(vcsegptr(segnum)->verts[0]);
273 	objp.pos.x = (spp->xo << RELPOS_PRECISION) + v.x;
274 	objp.pos.y = (spp->yo << RELPOS_PRECISION) + v.y;
275 	objp.pos.z = (spp->zo << RELPOS_PRECISION) + v.z;
276 
277 	objp.mtype.phys_info.velocity.x = (spp->velx << VEL_PRECISION);
278 	objp.mtype.phys_info.velocity.y = (spp->vely << VEL_PRECISION);
279 	objp.mtype.phys_info.velocity.z = (spp->velz << VEL_PRECISION);
280 }
281 
_newdemo_read(void * buffer,int elsize,int nelem)282 static int _newdemo_read( void *buffer, int elsize, int nelem )
283 {
284 	int num_read;
285 	num_read = (PHYSFS_read)(infile, buffer, elsize, nelem);
286 	if (num_read < nelem || PHYSFS_eof(infile))
287 		nd_playback_v_bad_read = -1;
288 
289 	return num_read;
290 }
291 
292 template <typename T>
newdemo_read(T * buffer,int elsize,int nelem)293 static typename std::enable_if<std::is_integral<T>::value, int>::type newdemo_read( T *buffer, int elsize, int nelem )
294 {
295 	return _newdemo_read(buffer, elsize, nelem);
296 }
297 
298 namespace dsx {
299 
newdemo_find_object(object_signature_t signature)300 icobjptridx_t newdemo_find_object(object_signature_t signature)
301 {
302 	auto &Objects = LevelUniqueObjectState.Objects;
303 	auto &vcobjptridx = Objects.vcptridx;
304 	range_for (const auto &&objp, vcobjptridx)
305 	{
306 		if ( (objp->type != OBJ_NONE) && (objp->signature == signature))
307 			return objp;
308 	}
309 	return object_none;
310 }
311 
312 }
313 
314 namespace {
315 
_newdemo_write(const void * buffer,int elsize,int nelem)316 static int _newdemo_write(const void *buffer, int elsize, int nelem )
317 {
318 	int num_written, total_size;
319 
320 	if (unlikely(nd_record_v_no_space))
321 		return -1;
322 
323 	total_size = elsize * nelem;
324 	nd_record_v_framebytes_written += total_size;
325 	Newdemo_num_written += total_size;
326 	Assert(outfile);
327 	num_written = (PHYSFS_write)(outfile, buffer, elsize, nelem);
328 
329 	if (likely(num_written == nelem))
330 		return num_written;
331 
332 	nd_record_v_no_space=2;
333 	newdemo_stop_recording();
334 	return -1;
335 }
336 
337 template <typename T>
newdemo_write(const T * buffer,int elsize,int nelem)338 static typename std::enable_if<std::is_integral<T>::value, int>::type newdemo_write(const T *buffer, int elsize, int nelem )
339 {
340 	return _newdemo_write(buffer, elsize, nelem);
341 }
342 
343 /*
344  *  The next bunch of files taken from Matt's gamesave.c.  We have to modify
345  *  these since the demo must save more information about objects that
346  *  just a gamesave
347 */
348 
nd_write_byte(sbyte b)349 static void nd_write_byte(sbyte b)
350 {
351 	newdemo_write(&b, 1, 1);
352 }
353 
nd_write_short(short s)354 static void nd_write_short(short s)
355 {
356 	newdemo_write(&s, 2, 1);
357 }
358 
nd_write_int(int i)359 static void nd_write_int(int i)
360 {
361 	newdemo_write(&i, 4, 1);
362 }
363 
nd_write_string(const char * str)364 static void nd_write_string(const char *str)
365 {
366 	nd_write_byte(strlen(str) + 1);
367 	newdemo_write(str, strlen(str) + 1, 1);
368 }
369 
nd_write_fix(fix f)370 static void nd_write_fix(fix f)
371 {
372 	newdemo_write(&f, sizeof(fix), 1);
373 }
374 
nd_write_fixang(fixang f)375 static void nd_write_fixang(fixang f)
376 {
377 	newdemo_write(&f, sizeof(fixang), 1);
378 }
379 
nd_write_vector(const vms_vector & v)380 static void nd_write_vector(const vms_vector &v)
381 {
382 	nd_write_fix(v.x);
383 	nd_write_fix(v.y);
384 	nd_write_fix(v.z);
385 }
386 
nd_write_angvec(const vms_angvec & v)387 static void nd_write_angvec(const vms_angvec &v)
388 {
389 	nd_write_fixang(v.p);
390 	nd_write_fixang(v.b);
391 	nd_write_fixang(v.h);
392 }
393 
nd_write_shortpos(const object_base & obj)394 static void nd_write_shortpos(const object_base &obj)
395 {
396 	shortpos sp;
397 	ubyte render_type;
398 
399 	create_shortpos_native(LevelSharedSegmentState, sp, obj);
400 
401 	render_type = obj.render_type;
402 	if ((render_type == RT_POLYOBJ || render_type == RT_HOSTAGE || render_type == RT_MORPH) || obj.type == OBJ_CAMERA)
403 	{
404 		uint8_t mask = 0;
405 		range_for (auto &i, sp.bytemat)
406 		{
407 			nd_write_byte(i);
408 			mask |= i;
409 		}
410 		if (!mask)
411 		{
412 			Int3();         // contact Allender about this.
413 		}
414 	}
415 
416 	nd_write_short(sp.xo);
417 	nd_write_short(sp.yo);
418 	nd_write_short(sp.zo);
419 	nd_write_short(sp.segment);
420 	nd_write_short(sp.velx);
421 	nd_write_short(sp.vely);
422 	nd_write_short(sp.velz);
423 }
424 
nd_read_byte(int8_t * const b)425 static void nd_read_byte(int8_t *const b)
426 {
427 	newdemo_read(b, 1, 1);
428 }
429 
nd_read_byte(uint8_t * const b)430 static void nd_read_byte(uint8_t *const b)
431 {
432 	nd_read_byte(reinterpret_cast<int8_t *>(b));
433 }
434 
nd_read_short(int16_t * const s)435 static void nd_read_short(int16_t *const s)
436 {
437 	newdemo_read(s, 2, 1);
438 	if (swap_endian)
439 		*s = SWAPSHORT(*s);
440 }
441 
nd_read_short(uint16_t * const s)442 static void nd_read_short(uint16_t *const s)
443 {
444 	nd_read_short(reinterpret_cast<int16_t *>(s));
445 }
446 
nd_read_segnum16(segnum_t & s)447 static void nd_read_segnum16(segnum_t &s)
448 {
449 	short i;
450 	nd_read_short(&i);
451 	s = i;
452 }
453 
nd_read_objnum16(objnum_t & o)454 static void nd_read_objnum16(objnum_t &o)
455 {
456 	short s;
457 	nd_read_short(&s);
458 	o = s;
459 }
460 
nd_read_int(int * i)461 static void nd_read_int(int *i)
462 {
463 	newdemo_read(i, 4, 1);
464 	if (swap_endian)
465 		*i = SWAPINT(*i);
466 }
467 
468 #if defined(DXX_BUILD_DESCENT_II)
nd_read_int(unsigned * i)469 static void nd_read_int(unsigned *i)
470 {
471 	newdemo_read(i, 4, 1);
472 	if (swap_endian)
473 		*i = SWAPINT(*i);
474 }
475 #endif
476 
nd_read_segnum32(segnum_t & s)477 static void nd_read_segnum32(segnum_t &s)
478 {
479 	int i;
480 	nd_read_int(&i);
481 	s = i;
482 }
483 
nd_read_objnum32(objnum_t & o)484 static void nd_read_objnum32(objnum_t &o)
485 {
486 	int i;
487 	nd_read_int(&i);
488 	o = i;
489 }
490 
nd_read_string(char * str,std::size_t max_length)491 static void nd_read_string(char *str, std::size_t max_length)
492 {
493 	sbyte len;
494 
495 	nd_read_byte(&len);
496 	if (static_cast<unsigned>(len) > max_length)
497 		throw std::runtime_error("demo string too long");
498 	newdemo_read(str, len, 1);
499 }
500 
501 template <std::size_t max_length>
nd_read_string(char (& str)[max_length])502 static void nd_read_string(char (&str)[max_length])
503 {
504 	nd_read_string(str, max_length);
505 }
506 
nd_read_fix(fix * f)507 static void nd_read_fix(fix *f)
508 {
509 	newdemo_read(f, sizeof(fix), 1);
510 	if (swap_endian)
511 		*f = SWAPINT(*f);
512 }
513 
nd_read_fixang(fixang * f)514 static void nd_read_fixang(fixang *f)
515 {
516 	newdemo_read(f, sizeof(fixang), 1);
517 	if (swap_endian)
518 		*f = SWAPSHORT(*f);
519 }
520 
nd_read_vector(vms_vector & v)521 static void nd_read_vector(vms_vector &v)
522 {
523 	nd_read_fix(&v.x);
524 	nd_read_fix(&v.y);
525 	nd_read_fix(&v.z);
526 }
527 
nd_read_angvec(vms_angvec & v)528 static void nd_read_angvec(vms_angvec &v)
529 {
530 	nd_read_fixang(&v.p);
531 	nd_read_fixang(&v.b);
532 	nd_read_fixang(&v.h);
533 }
534 
nd_read_shortpos(object_base & obj)535 static void nd_read_shortpos(object_base &obj)
536 {
537 	auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
538 	auto &Vertices = LevelSharedVertexState.get_vertices();
539 	ubyte render_type;
540 
541 	shortpos sp{};
542 
543 	render_type = obj.render_type;
544 	if ((render_type == RT_POLYOBJ || render_type == RT_HOSTAGE || render_type == RT_MORPH) || obj.type == OBJ_CAMERA)
545 	{
546 		range_for (auto &i, sp.bytemat)
547 			nd_read_byte(&i);
548 	}
549 
550 	nd_read_short(&sp.xo);
551 	nd_read_short(&sp.yo);
552 	nd_read_short(&sp.zo);
553 	nd_read_short(&sp.segment);
554 	nd_read_short(&sp.velx);
555 	nd_read_short(&sp.vely);
556 	nd_read_short(&sp.velz);
557 
558 	my_extract_shortpos(obj, &sp);
559 	if (obj.type == OBJ_FIREBALL && get_fireball_id(obj) == VCLIP_MORPHING_ROBOT && render_type == RT_FIREBALL && obj.control_source == object::control_type::explosion)
560 	{
561 		auto &vcvertptr = Vertices.vcptr;
562 		extract_orient_from_segment(vcvertptr, obj.orient, vcsegptr(obj.segnum));
563 	}
564 }
565 
566 object *prev_obj=NULL;      //ptr to last object read in
567 
568 }
569 
570 namespace dsx {
571 
572 namespace {
573 
nd_get_object_signature(const vcobjptridx_t objp)574 static uint16_t nd_get_object_signature(const vcobjptridx_t objp)
575 {
576 	return (static_cast<uint16_t>(objp->signature) << 9) ^ objp.get_unchecked_index();  // It's OKAY! We made sure, obj->signature is never has a value which short cannot handle!!! We cannot do this otherwise, without breaking the demo format!
577 }
578 
nd_read_object(const vmobjptridx_t obj)579 static void nd_read_object(const vmobjptridx_t obj)
580 {
581 	auto &Objects = LevelUniqueObjectState.Objects;
582 	auto &vmobjptr = Objects.vmptr;
583 	auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
584 	short shortsig = 0;
585 	const auto &pl_info = get_local_plrobj().ctype.player_info;
586 	const auto saved = (&pl_info == &obj->ctype.player_info)
587 		? std::pair<fix, player_info>(obj->shields, pl_info)
588 		: std::pair<fix, player_info>(0, {});
589 
590 	*obj = {};
591 	obj->shields = saved.first;
592 	obj->ctype.player_info = saved.second;
593 	obj->next = obj->prev = object_none;
594 	obj->segnum = segment_none;
595 
596 	/*
597 	 * Do render type first, since with render_type == RT_NONE, we
598 	 * blow by all other object information
599 	 */
600 	{
601 		uint8_t render_type;
602 		nd_read_byte(&render_type);
603 		if (valid_render_type(render_type))
604 			obj->render_type = render_type_t{render_type};
605 		else
606 		{
607 			con_printf(CON_URGENT, "demo used bogus render type %#x for object %p; using none instead", render_type, &*obj);
608 			obj->render_type = RT_NONE;
609 		}
610 	}
611 	{
612 		uint8_t object_type;
613 		nd_read_byte(&object_type);
614 		set_object_type(*obj, object_type);
615 	}
616 	if ((obj->render_type == RT_NONE) && (obj->type != OBJ_CAMERA))
617 		return;
618 
619 	nd_read_byte(&obj->id);
620 	nd_read_byte(&obj->flags);
621 	nd_read_short(&shortsig);
622 	// It's OKAY! We made sure, obj->signature is never has a value which short cannot handle!!! We cannot do this otherwise, without breaking the demo format!
623 	obj->signature = object_signature_t{static_cast<uint16_t>(shortsig)};
624 	nd_read_shortpos(obj);
625 
626 #if defined(DXX_BUILD_DESCENT_II)
627 	if ((obj->type == OBJ_ROBOT) && (get_robot_id(obj) == SPECIAL_REACTOR_ROBOT))
628 		Int3();
629 #endif
630 
631 	obj->attached_obj = object_none;
632 
633 	auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
634 	switch(obj->type) {
635 
636 	case OBJ_HOSTAGE:
637 		obj->control_source = object::control_type::powerup;
638 		obj->movement_source = object::movement_type::None;
639 		obj->size = HOSTAGE_SIZE;
640 		break;
641 
642 	case OBJ_ROBOT:
643 		obj->control_source = object::control_type::ai;
644 		// (MarkA and MikeK said we should not do the crazy last secret stuff with multiple reactors...
645 		// This necessary code is our vindication. --MK, 2/15/96)
646 #if defined(DXX_BUILD_DESCENT_II)
647 		if (get_robot_id(obj) == SPECIAL_REACTOR_ROBOT)
648 			obj->movement_source = object::movement_type::None;
649 		else
650 #endif
651 			obj->movement_source = object::movement_type::physics;
652 		obj->size = Polygon_models[Robot_info[get_robot_id(obj)].model_num].rad;
653 		obj->rtype.pobj_info.model_num = Robot_info[get_robot_id(obj)].model_num;
654 		obj->rtype.pobj_info.subobj_flags = 0;
655 		obj->ctype.ai_info.CLOAKED = (Robot_info[get_robot_id(obj)].cloak_type?1:0);
656 		break;
657 
658 	case OBJ_POWERUP:
659 		obj->control_source = object::control_type::powerup;
660 		{
661 			uint8_t movement_type;
662 			nd_read_byte(&movement_type);        // might have physics movement
663 			obj->movement_source = typename object::movement_type{movement_type};
664 		}
665 		obj->size = Powerup_info[get_powerup_id(obj)].size;
666 		break;
667 
668 	case OBJ_PLAYER:
669 		obj->control_source = object::control_type::None;
670 		obj->movement_source = object::movement_type::physics;
671 		obj->size = Polygon_models[Player_ship->model_num].rad;
672 		obj->rtype.pobj_info.model_num = Player_ship->model_num;
673 		obj->rtype.pobj_info.subobj_flags = 0;
674 		break;
675 
676 	case OBJ_CLUTTER:
677 		obj->control_source = object::control_type::None;
678 		obj->movement_source = object::movement_type::None;
679 		obj->size = Polygon_models[obj->id].rad;
680 		obj->rtype.pobj_info.model_num = obj->id;
681 		obj->rtype.pobj_info.subobj_flags = 0;
682 		break;
683 
684 	default:
685 		{
686 			uint8_t control_type;
687 			nd_read_byte(&control_type);
688 			obj->control_source = typename object::control_type{control_type};
689 		}
690 		{
691 			uint8_t movement_type;
692 			nd_read_byte(&movement_type);
693 			obj->movement_source = typename object::movement_type{movement_type};
694 		}
695 		nd_read_fix(&(obj->size));
696 		break;
697 	}
698 
699 
700 	{
701 		vms_vector last_pos;
702 		nd_read_vector(last_pos);
703 	}
704 	if ((obj->type == OBJ_WEAPON) && (obj->render_type == RT_WEAPON_VCLIP))
705 		nd_read_fix(&(obj->lifeleft));
706 	else {
707 		sbyte b;
708 
709 		// MWA old way -- won't work with big endian machines       nd_read_byte((uint8_t *)&(obj->lifeleft));
710 		nd_read_byte(&b);
711 		obj->lifeleft = static_cast<fix>(b);
712 		if (obj->lifeleft == -1)
713 			obj->lifeleft = IMMORTAL_TIME;
714 		else
715 			obj->lifeleft = obj->lifeleft << 12;
716 	}
717 
718 	if ((obj->type == OBJ_ROBOT) && !shareware) {
719 		if (Robot_info[get_robot_id(obj)].boss_flag) {
720 			sbyte cloaked;
721 
722 			nd_read_byte(&cloaked);
723 			obj->ctype.ai_info.CLOAKED = cloaked;
724 		}
725 	}
726 
727 	switch (obj->movement_source) {
728 
729 	case object::movement_type::physics:
730 		nd_read_vector(obj->mtype.phys_info.velocity);
731 		nd_read_vector(obj->mtype.phys_info.thrust);
732 		break;
733 
734 	case object::movement_type::spinning:
735 		nd_read_vector(obj->mtype.spin_rate);
736 		break;
737 
738 	case object::movement_type::None:
739 		break;
740 
741 	default:
742 		Int3();
743 	}
744 
745 	switch (obj->control_source) {
746 
747 	case object::control_type::explosion:
748 
749 		nd_read_fix(&(obj->ctype.expl_info.spawn_time));
750 		nd_read_fix(&(obj->ctype.expl_info.delete_time));
751 		nd_read_objnum16(obj->ctype.expl_info.delete_objnum);
752 
753 		obj->ctype.expl_info.next_attach = obj->ctype.expl_info.prev_attach = obj->ctype.expl_info.attach_parent = object_none;
754 
755 		if (obj->flags & OF_ATTACHED) {     //attach to previous object
756 			Assert(prev_obj!=NULL);
757 			if (prev_obj->control_source == object::control_type::explosion)
758 			{
759 				if (prev_obj->flags & OF_ATTACHED && prev_obj->ctype.expl_info.attach_parent!=object_none)
760 					obj_attach(Objects, Objects.vmptridx(prev_obj->ctype.expl_info.attach_parent), obj);
761 				else
762 					obj->flags &= ~OF_ATTACHED;
763 			}
764 			else
765 				obj_attach(Objects, Objects.vmptridx(prev_obj), obj);
766 		}
767 
768 		break;
769 
770 	case object::control_type::light:
771 		nd_read_fix(&(obj->ctype.light_info.intensity));
772 		break;
773 
774 	case object::control_type::ai:
775 	case object::control_type::weapon:
776 	case object::control_type::None:
777 	case object::control_type::flying:
778 	case object::control_type::slew:
779 	case object::control_type::morph:
780 	case object::control_type::debris:
781 	case object::control_type::powerup:
782 	case object::control_type::remote:
783 	case object::control_type::cntrlcen:
784 		break;
785 
786 	case object::control_type::flythrough:
787 	case object::control_type::repaircen:
788 	default:
789 		Int3();
790 
791 	}
792 
793 	switch (obj->render_type) {
794 
795 	case RT_NONE:
796 		break;
797 
798 	case RT_MORPH:
799 	case RT_POLYOBJ: {
800 		int tmo;
801 
802 		if ((obj->type != OBJ_ROBOT) && (obj->type != OBJ_PLAYER) && (obj->type != OBJ_CLUTTER)) {
803 			nd_read_int(&(obj->rtype.pobj_info.model_num));
804 			nd_read_int(&(obj->rtype.pobj_info.subobj_flags));
805 		}
806 
807 		if ((obj->type != OBJ_PLAYER) && (obj->type != OBJ_DEBRIS))
808 #if 0
809 			range_for (auto &i, obj->pobj_info.anim_angles)
810 				nd_read_angvec(&(i));
811 #endif
812 		range_for (auto &i, partial_range(obj->rtype.pobj_info.anim_angles, Polygon_models[obj->rtype.pobj_info.model_num].n_models))
813 			nd_read_angvec(i);
814 
815 		nd_read_int(&tmo);
816 
817 #if !DXX_USE_EDITOR
818 		obj->rtype.pobj_info.tmap_override = tmo;
819 #else
820 		if (tmo==-1)
821 			obj->rtype.pobj_info.tmap_override = -1;
822 		else {
823 			int xlated_tmo = tmap_xlate_table[tmo];
824 			if (xlated_tmo < 0) {
825 				Int3();
826 				xlated_tmo = 0;
827 			}
828 			obj->rtype.pobj_info.tmap_override = xlated_tmo;
829 		}
830 #endif
831 
832 		break;
833 	}
834 
835 	case RT_POWERUP:
836 	case RT_WEAPON_VCLIP:
837 	case RT_FIREBALL:
838 	case RT_HOSTAGE:
839 		nd_read_int(&(obj->rtype.vclip_info.vclip_num));
840 		nd_read_fix(&(obj->rtype.vclip_info.frametime));
841 		nd_read_byte(&obj->rtype.vclip_info.framenum);
842 		break;
843 
844 	case RT_LASER:
845 		break;
846 
847 	default:
848 		Int3();
849 
850 	}
851 
852 	prev_obj = obj;
853 }
854 
nd_write_object(const vcobjptridx_t objp)855 static void nd_write_object(const vcobjptridx_t objp)
856 {
857 	auto &BossUniqueState = LevelUniqueObjectState.BossState;
858 	auto &obj = *objp;
859 	auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
860 	int life;
861 	short shortsig = 0;
862 
863 #if defined(DXX_BUILD_DESCENT_II)
864 	if (obj.type == OBJ_ROBOT && get_robot_id(obj) == SPECIAL_REACTOR_ROBOT)
865 		Int3();
866 #endif
867 
868 	/*
869 	 * Do render_type first so on read, we can make determination of
870 	 * what else to read in
871 	 */
872 	nd_write_byte(obj.render_type);
873 	nd_write_byte(obj.type);
874 	if (obj.render_type == RT_NONE && obj.type != OBJ_CAMERA)
875 		return;
876 
877 	nd_write_byte(obj.id);
878 	nd_write_byte(obj.flags);
879 	shortsig = nd_get_object_signature(objp);
880 	nd_write_short(shortsig);
881 	nd_write_shortpos(obj);
882 
883 	if (obj.type != OBJ_HOSTAGE && obj.type != OBJ_ROBOT && obj.type != OBJ_PLAYER && obj.type != OBJ_POWERUP && obj.type != OBJ_CLUTTER)
884 	{
885 		nd_write_byte(static_cast<uint8_t>(obj.control_source));
886 		nd_write_byte(static_cast<uint8_t>(obj.movement_source));
887 		nd_write_fix(obj.size);
888 	}
889 	if (obj.type == OBJ_POWERUP)
890 		nd_write_byte(static_cast<uint8_t>(obj.movement_source));
891 
892 	nd_write_vector(obj.pos);
893 
894 	if (obj.type == OBJ_WEAPON && obj.render_type == RT_WEAPON_VCLIP)
895 		nd_write_fix(obj.lifeleft);
896 	else {
897 		life = static_cast<int>(obj.lifeleft);
898 		life = life >> 12;
899 		if (life > 255)
900 			life = 255;
901 		nd_write_byte(static_cast<uint8_t>(life));
902 	}
903 
904 	if (obj.type == OBJ_ROBOT) {
905 		if (Robot_info[get_robot_id(obj)].boss_flag) {
906 			const auto Boss_cloak_start_time = BossUniqueState.Boss_cloak_start_time;
907 			if (GameTime64 > Boss_cloak_start_time &&
908 				GameTime64 < (Boss_cloak_start_time + Boss_cloak_duration))
909 				nd_write_byte(1);
910 			else
911 				nd_write_byte(0);
912 		}
913 	}
914 
915 	switch (obj.movement_source) {
916 
917 	case object::movement_type::physics:
918 		nd_write_vector(obj.mtype.phys_info.velocity);
919 		nd_write_vector(obj.mtype.phys_info.thrust);
920 		break;
921 
922 	case object::movement_type::spinning:
923 		nd_write_vector(obj.mtype.spin_rate);
924 		break;
925 
926 	case object::movement_type::None:
927 		break;
928 
929 	default:
930 		Int3();
931 	}
932 
933 	switch (obj.control_source) {
934 
935 		case object::control_type::ai:
936 		break;
937 
938 		case object::control_type::explosion:
939 		nd_write_fix(obj.ctype.expl_info.spawn_time);
940 		nd_write_fix(obj.ctype.expl_info.delete_time);
941 		nd_write_short(obj.ctype.expl_info.delete_objnum);
942 		break;
943 
944 		case object::control_type::weapon:
945 		break;
946 
947 		case object::control_type::light:
948 
949 		nd_write_fix(obj.ctype.light_info.intensity);
950 		break;
951 
952 		case object::control_type::None:
953 		case object::control_type::flying:
954 		case object::control_type::debris:
955 		case object::control_type::powerup:
956 		case object::control_type::slew:       //the player is generally saved as slew
957 		case object::control_type::cntrlcen:
958 		case object::control_type::remote:
959 		case object::control_type::morph:
960 		break;
961 
962 		case object::control_type::repaircen:
963 		case object::control_type::flythrough:
964 	default:
965 		Int3();
966 
967 	}
968 
969 	auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
970 	switch (obj.render_type) {
971 
972 	case RT_NONE:
973 		break;
974 
975 	case RT_MORPH:
976 	case RT_POLYOBJ: {
977 		if ((obj.type != OBJ_ROBOT) && (obj.type != OBJ_PLAYER) && (obj.type != OBJ_CLUTTER)) {
978 			nd_write_int(obj.rtype.pobj_info.model_num);
979 			nd_write_int(obj.rtype.pobj_info.subobj_flags);
980 		}
981 
982 		if ((obj.type != OBJ_PLAYER) && (obj.type != OBJ_DEBRIS))
983 			range_for (auto &i, partial_const_range(obj.rtype.pobj_info.anim_angles, Polygon_models[obj.rtype.pobj_info.model_num].n_models))
984 			nd_write_angvec(i);
985 
986 		nd_write_int(obj.rtype.pobj_info.tmap_override);
987 
988 		break;
989 	}
990 
991 	case RT_POWERUP:
992 	case RT_WEAPON_VCLIP:
993 	case RT_FIREBALL:
994 	case RT_HOSTAGE:
995 		nd_write_int(obj.rtype.vclip_info.vclip_num);
996 		nd_write_fix(obj.rtype.vclip_info.frametime);
997 		nd_write_byte(obj.rtype.vclip_info.framenum);
998 		break;
999 
1000 	case RT_LASER:
1001 		break;
1002 
1003 	default:
1004 		Int3();
1005 
1006 	}
1007 
1008 }
1009 
1010 }
1011 
1012 }
1013 
1014 namespace {
1015 
nd_record_meta(char (& buf)[7],const char * s)1016 static void nd_record_meta(char (&buf)[7], const char *s)
1017 {
1018 	for (auto l = strlen(s) + 1; l;)
1019 	{
1020 		const auto n = std::min(l, sizeof(buf) - 3);
1021 		std::copy_n(s, n, &buf[3]);
1022 		s += n;
1023 		if (!(l -= n))
1024 			/* On final fragment, overwrite any trailing garbage from
1025 			 * previous iteration.
1026 			 */
1027 			std::fill_n(&buf[3 + n], sizeof(buf) - 3 - n, 0);
1028 		newdemo_write(buf, 1, sizeof(buf));
1029 	}
1030 }
1031 
nd_rdt(char (& buf)[7])1032 static void nd_rdt(char (&buf)[7])
1033 {
1034 	time_t t;
1035 	if (time(&t) == static_cast<time_t>(-1))
1036 		return;
1037 	/* UTC for easy comparison among demos from players in different
1038 	 * timezones
1039 	 */
1040 	if (const auto g = gmtime(&t))
1041 		if (const auto st = asctime(g))
1042 			nd_record_meta(buf, st);
1043 }
1044 
1045 }
1046 
nd_rbe()1047 static void nd_rbe()
1048 {
1049 	char buf[7]{ND_EVENT_PALETTE_EFFECT, 0x80};
1050 	newdemo_write(buf, 1, sizeof(buf));
1051 	++buf[2];
1052 	nd_rdt(buf);
1053 	++buf[2];
1054 #define DXX_RBE(A)	\
1055 	extern const char g_descent_##A[];	\
1056 	nd_record_meta(buf, g_descent_##A);
1057 	DXX_RBE(CPPFLAGS);
1058 	DXX_RBE(CXX);
1059 	DXX_RBE(CXXFLAGS);
1060 	DXX_RBE(CXX_version);
1061 	DXX_RBE(LINKFLAGS);
1062 	++buf[2];
1063 	DXX_RBE(build_datetime);
1064 	DXX_RBE(git_diffstat);
1065 	DXX_RBE(git_status);
1066 	DXX_RBE(version);
1067 	std::fill_n(&buf[1], sizeof(buf) - 1, 0);
1068 	newdemo_write(buf, 1, sizeof(buf));
1069 }
1070 
1071 namespace dsx {
newdemo_record_start_demo()1072 void newdemo_record_start_demo()
1073 {
1074 	auto &Objects = LevelUniqueObjectState.Objects;
1075 	auto &vcobjptr = Objects.vcptr;
1076 	auto &vmobjptr = Objects.vmptr;
1077 	auto &player_info = get_local_plrobj().ctype.player_info;
1078 	nd_record_v_recordframe_last_time=GameTime64-REC_DELAY; // make sure first frame is recorded!
1079 
1080 	pause_game_world_time p;
1081 	nd_write_byte(ND_EVENT_START_DEMO);
1082 	nd_write_byte(DEMO_VERSION);
1083 	nd_write_byte(DEMO_GAME_TYPE);
1084 	nd_write_fix(0); // NOTE: This is supposed to write GameTime (in fix). Since our GameTime64 is fix64 and the demos do not NEED this time actually, just write 0.
1085 
1086 	nd_write_int((Game_mode & GM_MULTI) ? (underlying_value(Game_mode) | (Player_num << 16)) : underlying_value(Game_mode));
1087 
1088 	if (Game_mode & GM_TEAM) {
1089 		nd_write_byte(Netgame.team_vector);
1090 		nd_write_string(Netgame.team_name[0]);
1091 		nd_write_string(Netgame.team_name[1]);
1092 	}
1093 
1094 	if (Game_mode & GM_MULTI) {
1095 		nd_write_byte(static_cast<int8_t>(N_players));
1096 		range_for (auto &i, partial_const_range(Players, N_players)) {
1097 			nd_write_string(static_cast<const char *>(i.callsign));
1098 			nd_write_byte(i.connected);
1099 
1100 			auto &pl_info = vcobjptr(i.objnum)->ctype.player_info;
1101 			if (Game_mode & GM_MULTI_COOP) {
1102 				nd_write_int(pl_info.mission.score);
1103 			} else {
1104 				nd_write_short(pl_info.net_killed_total);
1105 				nd_write_short(pl_info.net_kills_total);
1106 			}
1107 		}
1108 	} else
1109 		// NOTE LINK TO ABOVE!!!
1110 		nd_write_int(get_local_plrobj().ctype.player_info.mission.score);
1111 
1112 	nd_record_v_weapon_type = -1;
1113 	nd_record_v_weapon_num = -1;
1114 	nd_record_v_homing_distance = -1;
1115 	nd_record_v_primary_ammo = -1;
1116 	nd_record_v_secondary_ammo = -1;
1117 
1118 	for (int i = 0; i < MAX_PRIMARY_WEAPONS; i++)
1119 		nd_write_short(i == primary_weapon_index_t::VULCAN_INDEX ? player_info.vulcan_ammo : 0);
1120 
1121 	range_for (auto &i, player_info.secondary_ammo)
1122 		nd_write_short(i);
1123 
1124 	nd_write_byte(static_cast<sbyte>(player_info.laser_level));
1125 
1126 //  Support for missions added here
1127 
1128 	nd_write_string(&*Current_mission->filename);
1129 
1130 	nd_write_byte(nd_record_v_player_energy = static_cast<int8_t>(f2ir(player_info.energy)));
1131 	nd_write_byte(nd_record_v_player_shields = static_cast<int8_t>(f2ir(get_local_plrobj().shields)));
1132 	nd_write_int(nd_record_v_player_flags = player_info.powerup_flags.get_player_flags());        // be sure players flags are set
1133 	nd_write_byte(static_cast<int8_t>(static_cast<primary_weapon_index_t>(player_info.Primary_weapon)));
1134 	nd_write_byte(static_cast<int8_t>(static_cast<secondary_weapon_index_t>(player_info.Secondary_weapon)));
1135 	nd_record_v_start_frame = nd_record_v_frame_number = 0;
1136 #if defined(DXX_BUILD_DESCENT_II)
1137 	nd_record_v_player_afterburner = 0;
1138 	nd_record_v_juststarted=1;
1139 #endif
1140 	nd_rbe();
1141 	newdemo_set_new_level(Current_level_num);
1142 #if defined(DXX_BUILD_DESCENT_I)
1143 	newdemo_record_oneframeevent_update(1);
1144 #elif defined(DXX_BUILD_DESCENT_II)
1145 	newdemo_record_oneframeevent_update(0);
1146 #endif
1147 }
1148 
newdemo_record_start_frame(fix frame_time)1149 void newdemo_record_start_frame(fix frame_time )
1150 {
1151 	if (nd_record_v_no_space)
1152 	{
1153 		// Shouldn't happen - we should have stopped demo recording,
1154 		// in which case this function shouldn't have been called in the first place
1155 		Int3();
1156 		return;
1157 	}
1158 
1159 	// Make demo recording waste a bit less space.
1160 	// First check if if at least REC_DELAY has passed since last recorded frame. If yes, record frame and set nd_record_v_recordframe true.
1161 	// nd_record_v_recordframe will be used for various other frame-by-frame events to drop some unnecessary bytes.
1162 	// frame_time must be modified to get the right playback speed.
1163 	if (nd_record_v_recordframe_last_time > GameTime64)
1164 		nd_record_v_recordframe_last_time=GameTime64-REC_DELAY;
1165 
1166 	if (nd_record_v_recordframe_last_time + REC_DELAY <= GameTime64 || frame_time >= REC_DELAY)
1167 	{
1168 		if (frame_time < REC_DELAY)
1169 			frame_time = REC_DELAY;
1170 		nd_record_v_recordframe_last_time = GameTime64-(GameTime64-(nd_record_v_recordframe_last_time + REC_DELAY));
1171 		nd_record_v_recordframe=1;
1172 
1173 		pause_game_world_time p;
1174 #if defined(DXX_BUILD_DESCENT_II)
1175 
1176 		for (int i=0;i<MAX_OBJECTS;i++)
1177 		{
1178 			nd_record_v_objs[i]=0;
1179 			nd_record_v_viewobjs[i]=0;
1180 		}
1181 		range_for (auto &i, nd_record_v_rendering)
1182 			i=0;
1183 
1184 #endif
1185 		nd_record_v_frame_number -= nd_record_v_start_frame;
1186 
1187 		nd_write_byte(ND_EVENT_START_FRAME);
1188 		nd_write_short(nd_record_v_framebytes_written - 1);        // from previous frame
1189 		nd_record_v_framebytes_written=3;
1190 		nd_write_int(nd_record_v_frame_number);
1191 		nd_record_v_frame_number++;
1192 		nd_write_int(frame_time);
1193 	}
1194 	else
1195 	{
1196 		nd_record_v_recordframe=0;
1197 	}
1198 
1199 }
1200 
newdemo_record_render_object(const vmobjptridx_t obj)1201 void newdemo_record_render_object(const vmobjptridx_t obj)
1202 {
1203 	if (!nd_record_v_recordframe)
1204 		return;
1205 #if defined(DXX_BUILD_DESCENT_II)
1206 	if (nd_record_v_objs[obj])
1207 		return;
1208 	if (nd_record_v_viewobjs[obj])
1209 		return;
1210 
1211 	nd_record_v_objs[obj] = 1;
1212 #endif
1213 	pause_game_world_time p;
1214 	nd_write_byte(ND_EVENT_RENDER_OBJECT);
1215 	nd_write_object(obj);
1216 }
1217 
newdemo_record_viewer_object(const vcobjptridx_t obj)1218 void newdemo_record_viewer_object(const vcobjptridx_t obj)
1219 {
1220 	if (!nd_record_v_recordframe)
1221 		return;
1222 #if defined(DXX_BUILD_DESCENT_II)
1223 	if (nd_record_v_viewobjs[obj] && (nd_record_v_viewobjs[obj]-1)==RenderingType)
1224 		return;
1225 	if (nd_record_v_rendering[RenderingType])
1226 		return;
1227 #endif
1228 
1229 	pause_game_world_time p;
1230 	nd_write_byte(ND_EVENT_VIEWER_OBJECT);
1231 #if defined(DXX_BUILD_DESCENT_II)
1232 	nd_record_v_viewobjs[obj]=RenderingType+1;
1233 	nd_record_v_rendering[RenderingType]=1;
1234 	nd_write_byte(RenderingType);
1235 #endif
1236 	nd_write_object(obj);
1237 }
1238 }
1239 
newdemo_record_sound(int soundno)1240 void newdemo_record_sound( int soundno )
1241 {
1242 	pause_game_world_time p;
1243 	nd_write_byte(ND_EVENT_SOUND);
1244 	nd_write_int( soundno );
1245 }
1246 
newdemo_record_sound_3d_once(const int soundno,const sound_pan angle,const int volume)1247 void newdemo_record_sound_3d_once(const int soundno, const sound_pan angle, const int volume)
1248 {
1249 	pause_game_world_time p;
1250 	nd_write_byte( ND_EVENT_SOUND_3D_ONCE );
1251 	nd_write_int( soundno );
1252 	nd_write_int(static_cast<int>(angle));
1253 	nd_write_int( volume );
1254 }
1255 
1256 
newdemo_record_link_sound_to_object3(int soundno,objnum_t objnum,fix max_volume,fix max_distance,int loop_start,int loop_end)1257 void newdemo_record_link_sound_to_object3( int soundno, objnum_t objnum, fix max_volume, fix  max_distance, int loop_start, int loop_end )
1258 {
1259 	auto &Objects = LevelUniqueObjectState.Objects;
1260 	pause_game_world_time p;
1261 	nd_write_byte( ND_EVENT_LINK_SOUND_TO_OBJ );
1262 	nd_write_int( soundno );
1263 	nd_write_int(nd_get_object_signature(Objects.vcptridx(objnum)));
1264 	nd_write_int( max_volume );
1265 	nd_write_int( max_distance );
1266 	nd_write_int( loop_start );
1267 	nd_write_int( loop_end );
1268 }
1269 
1270 namespace dsx {
newdemo_record_kill_sound_linked_to_object(const vcobjptridx_t objp)1271 void newdemo_record_kill_sound_linked_to_object(const vcobjptridx_t objp)
1272 {
1273 	pause_game_world_time p;
1274 	nd_write_byte( ND_EVENT_KILL_SOUND_TO_OBJ );
1275 	nd_write_int(nd_get_object_signature(objp));
1276 }
1277 }
1278 
newdemo_record_wall_hit_process(segnum_t segnum,int side,int damage,int playernum)1279 void newdemo_record_wall_hit_process( segnum_t segnum, int side, int damage, int playernum )
1280 {
1281 	pause_game_world_time p;
1282 	nd_write_byte( ND_EVENT_WALL_HIT_PROCESS );
1283 	nd_write_int( segnum );
1284 	nd_write_int( side );
1285 	nd_write_int( damage );
1286 	nd_write_int( playernum );
1287 }
1288 
1289 namespace dsx {
1290 
1291 #if defined(DXX_BUILD_DESCENT_II)
newdemo_record_guided_start()1292 void newdemo_record_guided_start ()
1293 {
1294 	nd_write_byte (ND_EVENT_START_GUIDED);
1295 }
1296 
newdemo_record_guided_end()1297 void newdemo_record_guided_end ()
1298 {
1299 	nd_write_byte (ND_EVENT_END_GUIDED);
1300 }
1301 
newdemo_record_secret_exit_blown(int truth)1302 void newdemo_record_secret_exit_blown(int truth)
1303 {
1304 	pause_game_world_time p;
1305 	nd_write_byte( ND_EVENT_SECRET_THINGY );
1306 	nd_write_int( truth );
1307 }
1308 
newdemo_record_trigger(const vcsegidx_t segnum,const unsigned side,const objnum_t objnum,const unsigned shot)1309 void newdemo_record_trigger(const vcsegidx_t segnum, const unsigned side, const objnum_t objnum, const unsigned shot)
1310 {
1311 	pause_game_world_time p;
1312 	nd_write_byte( ND_EVENT_TRIGGER );
1313 	nd_write_int( segnum );
1314 	nd_write_int( side );
1315 	nd_write_int( objnum );
1316 	nd_write_int(shot);
1317 }
1318 #endif
1319 
newdemo_record_morph_frame(const vcobjptridx_t obj)1320 void newdemo_record_morph_frame(const vcobjptridx_t obj)
1321 {
1322 	if (!nd_record_v_recordframe)
1323 		return;
1324 	pause_game_world_time p;
1325 	nd_write_byte( ND_EVENT_MORPH_FRAME );
1326 	nd_write_object(obj);
1327 }
1328 
1329 }
1330 
newdemo_record_wall_toggle(segnum_t segnum,int side)1331 void newdemo_record_wall_toggle( segnum_t segnum, int side )
1332 {
1333 	pause_game_world_time p;
1334 	nd_write_byte( ND_EVENT_WALL_TOGGLE );
1335 	nd_write_int( segnum );
1336 	nd_write_int( side );
1337 }
1338 
newdemo_record_control_center_destroyed()1339 void newdemo_record_control_center_destroyed()
1340 {
1341 	auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
1342 	if (!nd_record_v_recordframe)
1343 		return;
1344 	pause_game_world_time p;
1345 	nd_write_byte( ND_EVENT_CONTROL_CENTER_DESTROYED );
1346 	nd_write_int(LevelUniqueControlCenterState.Countdown_seconds_left);
1347 }
1348 
newdemo_record_hud_message(const char * message)1349 void newdemo_record_hud_message(const char * message )
1350 {
1351 	pause_game_world_time p;
1352 	nd_write_byte( ND_EVENT_HUD_MESSAGE );
1353 	nd_write_string(message);
1354 }
1355 
newdemo_record_palette_effect(short r,short g,short b)1356 void newdemo_record_palette_effect(short r, short g, short b )
1357 {
1358 	if (!nd_record_v_recordframe)
1359 		return;
1360 	pause_game_world_time p;
1361 	nd_write_byte( ND_EVENT_PALETTE_EFFECT );
1362 	nd_write_short( r );
1363 	nd_write_short( g );
1364 	nd_write_short( b );
1365 }
1366 
newdemo_record_player_energy(int energy)1367 void newdemo_record_player_energy(int energy)
1368 {
1369 	if (nd_record_v_player_energy == energy)
1370 		return;
1371 	pause_game_world_time p;
1372 	nd_write_byte( ND_EVENT_PLAYER_ENERGY );
1373 	nd_write_byte(static_cast<int8_t>(std::exchange(nd_record_v_player_energy, energy)));
1374 	nd_write_byte(static_cast<int8_t>(energy));
1375 }
1376 
1377 namespace dsx {
1378 
1379 #if defined(DXX_BUILD_DESCENT_II)
newdemo_record_player_afterburner(fix afterburner)1380 void newdemo_record_player_afterburner(fix afterburner)
1381 {
1382 	if ((nd_record_v_player_afterburner>>9) == (afterburner>>9))
1383 		return;
1384 	pause_game_world_time p;
1385 	nd_write_byte( ND_EVENT_PLAYER_AFTERBURNER );
1386 	nd_write_byte(static_cast<int8_t>(std::exchange(nd_record_v_player_afterburner, afterburner) >> 9));
1387 	nd_write_byte(static_cast<int8_t>(afterburner >> 9));
1388 }
1389 #endif
1390 
1391 }
1392 
newdemo_record_player_shields(int shield)1393 void newdemo_record_player_shields(int shield)
1394 {
1395 	if (nd_record_v_player_shields == shield)
1396 		return;
1397 	pause_game_world_time p;
1398 	nd_write_byte( ND_EVENT_PLAYER_SHIELD );
1399 	nd_write_byte(static_cast<int8_t>(std::exchange(nd_record_v_player_shields, shield)));
1400 	nd_write_byte(static_cast<int8_t>(shield));
1401 }
1402 
newdemo_record_player_flags(uint flags)1403 void newdemo_record_player_flags(uint flags)
1404 {
1405 	if (nd_record_v_player_flags == flags)
1406 		return;
1407 	pause_game_world_time p;
1408 	nd_write_byte( ND_EVENT_PLAYER_FLAGS );
1409 	nd_write_int((static_cast<short>(std::exchange(nd_record_v_player_flags, flags)) << 16) | static_cast<short>(flags));
1410 }
1411 
newdemo_record_player_weapon(int weapon_type,int weapon_num)1412 void newdemo_record_player_weapon(int weapon_type, int weapon_num)
1413 {
1414 	auto &Objects = LevelUniqueObjectState.Objects;
1415 	auto &vmobjptr = Objects.vmptr;
1416 	if (nd_record_v_weapon_type == weapon_type && nd_record_v_weapon_num == weapon_num)
1417 		return;
1418 	pause_game_world_time p;
1419 	nd_write_byte( ND_EVENT_PLAYER_WEAPON );
1420 	nd_write_byte(static_cast<int8_t>(nd_record_v_weapon_type = weapon_type));
1421 	nd_write_byte(static_cast<int8_t>(nd_record_v_weapon_num = weapon_num));
1422 	auto &player_info = get_local_plrobj().ctype.player_info;
1423 	nd_write_byte(weapon_type
1424 		? static_cast<int8_t>(static_cast<secondary_weapon_index_t>(player_info.Secondary_weapon))
1425 		: static_cast<int8_t>(static_cast<primary_weapon_index_t>(player_info.Primary_weapon))
1426 	);
1427 }
1428 
newdemo_record_effect_blowup(segnum_t segment,int side,const vms_vector & pnt)1429 void newdemo_record_effect_blowup(segnum_t segment, int side, const vms_vector &pnt)
1430 {
1431 	pause_game_world_time p;
1432 	nd_write_byte (ND_EVENT_EFFECT_BLOWUP);
1433 	nd_write_short(segment);
1434 	nd_write_byte(static_cast<int8_t>(side));
1435 	nd_write_vector(pnt);
1436 }
1437 
newdemo_record_homing_distance(fix distance)1438 void newdemo_record_homing_distance(fix distance)
1439 {
1440 	if ((nd_record_v_homing_distance>>16) == (distance>>16))
1441 		return;
1442 	pause_game_world_time p;
1443 	nd_write_byte(ND_EVENT_HOMING_DISTANCE);
1444 	nd_write_short(static_cast<short>((nd_record_v_homing_distance = distance) >> 16));
1445 }
1446 
newdemo_record_letterbox(void)1447 void newdemo_record_letterbox(void)
1448 {
1449 	pause_game_world_time p;
1450 	nd_write_byte(ND_EVENT_LETTERBOX);
1451 }
1452 
newdemo_record_rearview(void)1453 void newdemo_record_rearview(void)
1454 {
1455 	pause_game_world_time p;
1456 	nd_write_byte(ND_EVENT_REARVIEW);
1457 }
1458 
newdemo_record_restore_cockpit(void)1459 void newdemo_record_restore_cockpit(void)
1460 {
1461 	pause_game_world_time p;
1462 	nd_write_byte(ND_EVENT_RESTORE_COCKPIT);
1463 }
1464 
newdemo_record_restore_rearview(void)1465 void newdemo_record_restore_rearview(void)
1466 {
1467 	pause_game_world_time p;
1468 	nd_write_byte(ND_EVENT_RESTORE_REARVIEW);
1469 }
1470 
newdemo_record_wall_set_tmap_num1(const vcsegidx_t seg,const unsigned side,const vcsegidx_t cseg,const unsigned cside,const texture1_value tmap)1471 void newdemo_record_wall_set_tmap_num1(const vcsegidx_t seg, const unsigned side, const vcsegidx_t cseg, const unsigned cside, const texture1_value tmap)
1472 {
1473 	pause_game_world_time p;
1474 	nd_write_byte(ND_EVENT_WALL_SET_TMAP_NUM1);
1475 	nd_write_short(seg);
1476 	nd_write_byte(side);
1477 	nd_write_short(cseg);
1478 	nd_write_byte(cside);
1479 	nd_write_short(static_cast<uint16_t>(tmap));
1480 }
1481 
newdemo_record_wall_set_tmap_num2(const vcsegidx_t seg,const unsigned side,const vcsegidx_t cseg,const unsigned cside,const texture2_value tmap)1482 void newdemo_record_wall_set_tmap_num2(const vcsegidx_t seg, const unsigned side, const vcsegidx_t cseg, const unsigned cside, const texture2_value tmap)
1483 {
1484 	pause_game_world_time p;
1485 	nd_write_byte(ND_EVENT_WALL_SET_TMAP_NUM2);
1486 	nd_write_short(seg);
1487 	nd_write_byte(side);
1488 	nd_write_short(cseg);
1489 	nd_write_byte(cside);
1490 	nd_write_short(static_cast<uint16_t>(tmap));
1491 }
1492 
newdemo_record_multi_cloak(int pnum)1493 void newdemo_record_multi_cloak(int pnum)
1494 {
1495 	pause_game_world_time p;
1496 	nd_write_byte(ND_EVENT_MULTI_CLOAK);
1497 	nd_write_byte(static_cast<int8_t>(pnum));
1498 }
1499 
newdemo_record_multi_decloak(int pnum)1500 void newdemo_record_multi_decloak(int pnum)
1501 {
1502 	pause_game_world_time p;
1503 	nd_write_byte(ND_EVENT_MULTI_DECLOAK);
1504 	nd_write_byte(static_cast<int8_t>(pnum));
1505 }
1506 
newdemo_record_multi_death(int pnum)1507 void newdemo_record_multi_death(int pnum)
1508 {
1509 	pause_game_world_time p;
1510 	nd_write_byte(ND_EVENT_MULTI_DEATH);
1511 	nd_write_byte(static_cast<int8_t>(pnum));
1512 }
1513 
newdemo_record_multi_kill(int pnum,sbyte kill)1514 void newdemo_record_multi_kill(int pnum, sbyte kill)
1515 {
1516 	pause_game_world_time p;
1517 	nd_write_byte(ND_EVENT_MULTI_KILL);
1518 	nd_write_byte(static_cast<int8_t>(pnum));
1519 	nd_write_byte(kill);
1520 }
1521 
newdemo_record_multi_connect(const unsigned pnum,const unsigned new_player,const char * const new_callsign)1522 void newdemo_record_multi_connect(const unsigned pnum, const unsigned new_player, const char *const new_callsign)
1523 {
1524 	auto &Objects = LevelUniqueObjectState.Objects;
1525 	auto &vcobjptr = Objects.vcptr;
1526 	pause_game_world_time p;
1527 	nd_write_byte(ND_EVENT_MULTI_CONNECT);
1528 	nd_write_byte(static_cast<int8_t>(pnum));
1529 	nd_write_byte(static_cast<int8_t>(new_player));
1530 	if (!new_player) {
1531 		auto &plr = *vcplayerptr(pnum);
1532 		nd_write_string(static_cast<const char *>(plr.callsign));
1533 		auto &player_info = vcobjptr(plr.objnum)->ctype.player_info;
1534 		nd_write_int(player_info.net_killed_total);
1535 		nd_write_int(player_info.net_kills_total);
1536 	}
1537 	nd_write_string(new_callsign);
1538 }
1539 
newdemo_record_multi_reconnect(int pnum)1540 void newdemo_record_multi_reconnect(int pnum)
1541 {
1542 	pause_game_world_time p;
1543 	nd_write_byte(ND_EVENT_MULTI_RECONNECT);
1544 	nd_write_byte(static_cast<int8_t>(pnum));
1545 }
1546 
newdemo_record_multi_disconnect(int pnum)1547 void newdemo_record_multi_disconnect(int pnum)
1548 {
1549 	pause_game_world_time p;
1550 	nd_write_byte(ND_EVENT_MULTI_DISCONNECT);
1551 	nd_write_byte(static_cast<int8_t>(pnum));
1552 }
1553 
newdemo_record_player_score(int score)1554 void newdemo_record_player_score(int score)
1555 {
1556 	pause_game_world_time p;
1557 	nd_write_byte(ND_EVENT_PLAYER_SCORE);
1558 	nd_write_int(score);
1559 }
1560 
newdemo_record_multi_score(const unsigned pnum,const int score)1561 void newdemo_record_multi_score(const unsigned pnum, const int score)
1562 {
1563 	auto &Objects = LevelUniqueObjectState.Objects;
1564 	auto &vcobjptr = Objects.vcptr;
1565 	pause_game_world_time p;
1566 	nd_write_byte(ND_EVENT_MULTI_SCORE);
1567 	nd_write_byte(static_cast<int8_t>(pnum));
1568 	nd_write_int(score - vcobjptr(vcplayerptr(pnum)->objnum)->ctype.player_info.mission.score);      // called before score is changed!!!!
1569 }
1570 
newdemo_record_primary_ammo(int new_ammo)1571 void newdemo_record_primary_ammo(int new_ammo)
1572 {
1573 	if (nd_record_v_primary_ammo == new_ammo)
1574 		return;
1575 	pause_game_world_time p;
1576 	nd_write_byte(ND_EVENT_PRIMARY_AMMO);
1577 	nd_write_short(nd_record_v_primary_ammo < 0 ? static_cast<short>(new_ammo) : static_cast<short>(nd_record_v_primary_ammo));
1578 	nd_write_short(static_cast<short>(nd_record_v_primary_ammo = new_ammo));
1579 }
1580 
newdemo_record_secondary_ammo(int new_ammo)1581 void newdemo_record_secondary_ammo(int new_ammo)
1582 {
1583 	if (nd_record_v_secondary_ammo == new_ammo)
1584 		return;
1585 	pause_game_world_time p;
1586 	nd_write_byte(ND_EVENT_SECONDARY_AMMO);
1587 	nd_write_short(nd_record_v_secondary_ammo < 0 ? static_cast<short>(new_ammo) : static_cast<short>(nd_record_v_secondary_ammo));
1588 	nd_write_short(static_cast<short>(nd_record_v_secondary_ammo = new_ammo));
1589 }
1590 
newdemo_record_door_opening(segnum_t segnum,int side)1591 void newdemo_record_door_opening(segnum_t segnum, int side)
1592 {
1593 	pause_game_world_time p;
1594 	nd_write_byte(ND_EVENT_DOOR_OPENING);
1595 	nd_write_short(segnum);
1596 	nd_write_byte(static_cast<int8_t>(side));
1597 }
1598 
newdemo_record_laser_level(const laser_level old_level,const laser_level new_level)1599 void newdemo_record_laser_level(const laser_level old_level, const laser_level new_level)
1600 {
1601 	pause_game_world_time p;
1602 	nd_write_byte(ND_EVENT_LASER_LEVEL);
1603 	nd_write_byte(static_cast<uint8_t>(old_level));
1604 	nd_write_byte(static_cast<uint8_t>(new_level));
1605 }
1606 
1607 namespace dsx {
1608 
1609 #if defined(DXX_BUILD_DESCENT_II)
newdemo_record_cloaking_wall(wallnum_t front_wall_num,wallnum_t back_wall_num,ubyte type,const wall_state state,fix cloak_value,fix l0,fix l1,fix l2,fix l3)1610 void newdemo_record_cloaking_wall(wallnum_t front_wall_num, wallnum_t back_wall_num, ubyte type, const wall_state state, fix cloak_value, fix l0, fix l1, fix l2, fix l3)
1611 {
1612 	pause_game_world_time p;
1613 	nd_write_byte(ND_EVENT_CLOAKING_WALL);
1614 	nd_write_byte(static_cast<uint8_t>(front_wall_num));
1615 	nd_write_byte(static_cast<uint8_t>(back_wall_num));
1616 	nd_write_byte(type);
1617 	nd_write_byte(underlying_value(state));
1618 	nd_write_byte(cloak_value);
1619 	nd_write_short(l0>>8);
1620 	nd_write_short(l1>>8);
1621 	nd_write_short(l2>>8);
1622 	nd_write_short(l3>>8);
1623 }
1624 #endif
1625 
newdemo_set_new_level(int level_num)1626 void newdemo_set_new_level(int level_num)
1627 {
1628 	pause_game_world_time p;
1629 	nd_write_byte(ND_EVENT_NEW_LEVEL);
1630 	nd_write_byte(static_cast<int8_t>(level_num));
1631 	nd_write_byte(static_cast<int8_t>(Current_level_num));
1632 #if defined(DXX_BUILD_DESCENT_II)
1633 	if (nd_record_v_juststarted==1)
1634 	{
1635 		auto &Walls = LevelUniqueWallSubsystemState.Walls;
1636 		auto &vcwallptr = Walls.vcptr;
1637 		nd_write_int(Walls.get_count());
1638 		range_for (const auto &&wp, vcwallptr)
1639 		{
1640 			auto &w = *wp;
1641 			nd_write_byte (w.type);
1642 			nd_write_byte (underlying_value(w.flags));
1643 			nd_write_byte (underlying_value(w.state));
1644 
1645 			const auto &side = vcsegptr(w.segnum)->unique_segment::sides[w.sidenum];
1646 			nd_write_short(underlying_value(side.tmap_num));
1647 			nd_write_short(underlying_value(side.tmap_num2));
1648 			nd_record_v_juststarted=0;
1649 		}
1650 	}
1651 #endif
1652 }
1653 }
1654 
1655 /*
1656  * By design, the demo code does not record certain events when demo recording starts or ends.
1657  * To not break compability this function can be applied at start/end of demo recording to
1658  * re-record these events. It will "simulate" those events without using functions older game
1659  * versions cannot handle.
1660  */
1661 namespace dsx {
newdemo_record_oneframeevent_update(int wallupdate)1662 static void newdemo_record_oneframeevent_update(int wallupdate)
1663 {
1664 	if (Player_dead_state != player_dead_state::no)
1665 		newdemo_record_letterbox();
1666 	else
1667 		newdemo_record_restore_cockpit();
1668 
1669 	if (Rear_view)
1670 		newdemo_record_rearview();
1671 	else
1672 		newdemo_record_restore_rearview();
1673 
1674 #if defined(DXX_BUILD_DESCENT_I)
1675 	// This will record tmaps for all walls and properly show doors which were opened before demo recording started.
1676 	if (wallupdate)
1677 	{
1678 		auto &Walls = LevelUniqueWallSubsystemState.Walls;
1679 		auto &vcwallptr = Walls.vcptr;
1680 		range_for (const auto &&wp, vcwallptr)
1681 		{
1682 			auto &w = *wp;
1683 			int side;
1684 			auto seg = &Segments[w.segnum];
1685 			side = w.sidenum;
1686 			// actually this is kinda stupid: when playing ther same tmap will be put on front and back side of the wall ... for doors this is stupid so just record the front side which will do for doors just fine ...
1687 			auto &uside = seg->unique_segment::sides[side];
1688 			if (const auto tmap_num = uside.tmap_num; tmap_num != texture1_value::None)
1689 				newdemo_record_wall_set_tmap_num1(w.segnum,side,w.segnum,side,tmap_num);
1690 			if (const auto tmap_num2 = uside.tmap_num2; tmap_num2 != texture2_value::None)
1691 				newdemo_record_wall_set_tmap_num2(w.segnum,side,w.segnum,side,tmap_num2);
1692 		}
1693 	}
1694 #elif defined(DXX_BUILD_DESCENT_II)
1695 	(void)wallupdate;
1696 	if (Viewer == LevelUniqueObjectState.Guided_missile.get_player_active_guided_missile(LevelUniqueObjectState.get_objects().vmptr, Player_num))
1697 		newdemo_record_guided_start();
1698 	else
1699 		newdemo_record_guided_end();
1700 #endif
1701 }
1702 }
1703 
1704 enum purpose_type
1705 {
1706 	PURPOSE_CHOSE_PLAY = 0,
1707 	PURPOSE_RANDOM_PLAY,
1708 	PURPOSE_REWRITE
1709 };
1710 
1711 namespace dsx {
newdemo_read_demo_start(enum purpose_type purpose)1712 static int newdemo_read_demo_start(enum purpose_type purpose)
1713 {
1714 	auto &Objects = LevelUniqueObjectState.Objects;
1715 	auto &vmobjptr = Objects.vmptr;
1716 	sbyte version=0, game_type=0, c=0;
1717 	ubyte energy=0, shield=0;
1718 	char current_mission[9];
1719 	fix nd_GameTime32 = 0;
1720 
1721 	Rear_view=0;
1722 #if defined(DXX_BUILD_DESCENT_I)
1723 	shareware = 0;
1724 #elif defined(DXX_BUILD_DESCENT_II)
1725 	auto &BossUniqueState = LevelUniqueObjectState.BossState;
1726 #endif
1727 
1728 	nd_read_byte(&c);
1729 	if (purpose == PURPOSE_REWRITE)
1730 		nd_write_byte(c);
1731 	if ((c != ND_EVENT_START_DEMO) || nd_playback_v_bad_read) {
1732 		nm_messagebox(menu_title{nullptr}, 1, TXT_OK, "%s %s", TXT_CANT_PLAYBACK, TXT_DEMO_CORRUPT);
1733 		return 1;
1734 	}
1735 	nd_read_byte(&version);
1736 	if (purpose == PURPOSE_REWRITE)
1737 		nd_write_byte(version);
1738 #if defined(DXX_BUILD_DESCENT_I)
1739 	if (version == DEMO_VERSION_SHAREWARE)
1740 		shareware = 1;
1741 	else if (version < DEMO_VERSION) {
1742 		if (purpose == PURPOSE_CHOSE_PLAY) {
1743 			nm_messagebox(menu_title{nullptr}, 1, TXT_OK, "%s %s", TXT_CANT_PLAYBACK, TXT_DEMO_OLD);
1744 		}
1745 		return 1;
1746 	}
1747 #endif
1748 	nd_read_byte(&game_type);
1749 	if (purpose == PURPOSE_REWRITE)
1750 		nd_write_byte(game_type);
1751 #if defined(DXX_BUILD_DESCENT_I)
1752 	if ((game_type == DEMO_GAME_TYPE_SHAREWARE) && shareware)
1753 		;	// all good
1754 	else if (game_type != DEMO_GAME_TYPE) {
1755 		nm_messagebox(menu_title{nullptr}, 1, TXT_OK, "%s %s", TXT_CANT_PLAYBACK, TXT_DEMO_OLD);
1756 
1757 		return 1;
1758 	}
1759 #elif defined(DXX_BUILD_DESCENT_II)
1760 	if (game_type < DEMO_GAME_TYPE) {
1761 		nm_messagebox(menu_title{nullptr}, 1, TXT_OK, "%s %s\n%s", TXT_CANT_PLAYBACK, TXT_RECORDED, "    In Descent: First Strike");
1762 		return 1;
1763 	}
1764 	if (game_type != DEMO_GAME_TYPE) {
1765 		nm_messagebox(menu_title{nullptr}, 1, TXT_OK, "%s %s\n%s", TXT_CANT_PLAYBACK, TXT_RECORDED, "   In Unknown Descent version");
1766 		return 1;
1767 	}
1768 	if (version < DEMO_VERSION) {
1769 		if (purpose == PURPOSE_CHOSE_PLAY) {
1770 			nm_messagebox(menu_title{nullptr}, 1, TXT_OK, "%s %s", TXT_CANT_PLAYBACK, TXT_DEMO_OLD);
1771 		}
1772 		return 1;
1773 	}
1774 #endif
1775 	nd_read_fix(&nd_GameTime32); // NOTE: Demos write GameTime in fix.
1776 	GameTime64 = nd_GameTime32;
1777 	int recorded_demo_game_mode;
1778 	nd_read_int(&recorded_demo_game_mode);
1779 	Newdemo_game_mode = static_cast<game_mode_flags>(recorded_demo_game_mode);
1780 #if defined(DXX_BUILD_DESCENT_II)
1781 	if (purpose == PURPOSE_REWRITE)
1782 	{
1783 		nd_write_fix(nd_GameTime32);
1784 		nd_write_int(underlying_value(Newdemo_game_mode));
1785 	}
1786 
1787 	BossUniqueState.Boss_cloak_start_time = GameTime64;
1788 #endif
1789 
1790 	change_playernum_to((recorded_demo_game_mode >> 16) & 0x7);
1791 	if (shareware)
1792 	{
1793 		if (Newdemo_game_mode & GM_TEAM)
1794 		{
1795 			nd_read_byte(&Netgame.team_vector);
1796 			if (purpose == PURPOSE_REWRITE)
1797 				nd_write_byte(Netgame.team_vector);
1798 		}
1799 
1800 		range_for (auto &i, Players)
1801 		{
1802 			auto &objp = *vmobjptr(i.objnum);
1803 			auto &player_info = objp.ctype.player_info;
1804 			player_info.powerup_flags &= ~(PLAYER_FLAGS_CLOAKED | PLAYER_FLAGS_INVULNERABLE);
1805 			DXX_MAKE_VAR_UNDEFINED(player_info.cloak_time);
1806 			DXX_MAKE_VAR_UNDEFINED(player_info.invulnerable_time);
1807 		}
1808 	}
1809 	else
1810 	{
1811 		if (Newdemo_game_mode & GM_TEAM) {
1812 			nd_read_byte(&Netgame.team_vector);
1813 			nd_read_string(Netgame.team_name[0].buffer());
1814 			nd_read_string(Netgame.team_name[1].buffer());
1815 			if (purpose == PURPOSE_REWRITE)
1816 			{
1817 				nd_write_byte(Netgame.team_vector);
1818 				nd_write_string(Netgame.team_name[0]);
1819 				nd_write_string(Netgame.team_name[1]);
1820 			}
1821 		}
1822 		if (Newdemo_game_mode & GM_MULTI) {
1823 
1824 			if (purpose != PURPOSE_REWRITE)
1825 				multi_new_game();
1826 			nd_read_byte(&c);
1827 			N_players = static_cast<int>(c);
1828 			// changed this to above two lines -- breaks on the mac because of
1829 			// endian issues
1830 			//		nd_read_byte(&N_players);
1831 			if (purpose == PURPOSE_REWRITE)
1832 				nd_write_byte(N_players);
1833 			range_for (auto &i, partial_range(Players, N_players)) {
1834 				const auto &&objp = vmobjptr(i.objnum);
1835 				auto &player_info = objp->ctype.player_info;
1836 				player_info.powerup_flags &= ~(PLAYER_FLAGS_CLOAKED | PLAYER_FLAGS_INVULNERABLE);
1837 				DXX_MAKE_VAR_UNDEFINED(player_info.cloak_time);
1838 				DXX_MAKE_VAR_UNDEFINED(player_info.invulnerable_time);
1839 				nd_read_string(i.callsign.buffer());
1840 				nd_read_byte(&i.connected);
1841 				if (purpose == PURPOSE_REWRITE)
1842 				{
1843 					nd_write_string(static_cast<const char *>(i.callsign));
1844 					nd_write_byte(i.connected);
1845 				}
1846 
1847 				if (Newdemo_game_mode & GM_MULTI_COOP) {
1848 					nd_read_int(&player_info.mission.score);
1849 					if (purpose == PURPOSE_REWRITE)
1850 						nd_write_int(player_info.mission.score);
1851 				} else {
1852 					nd_read_short(&player_info.net_killed_total);
1853 					nd_read_short(&player_info.net_kills_total);
1854 					if (purpose == PURPOSE_REWRITE)
1855 					{
1856 						nd_write_short(player_info.net_killed_total);
1857 						nd_write_short(player_info.net_kills_total);
1858 					}
1859 				}
1860 			}
1861 			Game_mode = Newdemo_game_mode;
1862 			if (purpose != PURPOSE_REWRITE)
1863 				multi_sort_kill_list();
1864 			Game_mode = GM_NORMAL;
1865 		} else
1866 		{
1867 #if defined(DXX_BUILD_DESCENT_II)
1868 			auto &player_info = get_local_plrobj().ctype.player_info;
1869 			nd_read_int(&player_info.mission.score);      // Note link to above if!
1870 			if (purpose == PURPOSE_REWRITE)
1871 				nd_write_int(player_info.mission.score);
1872 #endif
1873 		}
1874 	}
1875 	auto &player_info = get_local_plrobj().ctype.player_info;
1876 #if defined(DXX_BUILD_DESCENT_I)
1877 	if (!(Newdemo_game_mode & GM_MULTI))
1878 	{
1879 		auto &score = player_info.mission.score;
1880 		nd_read_int(&score);      // Note link to above if!
1881 		if (purpose == PURPOSE_REWRITE)
1882 			nd_write_int(score);
1883 	}
1884 #endif
1885 
1886 	for (int i = 0; i < MAX_PRIMARY_WEAPONS; i++)
1887 	{
1888 		short s;
1889 		nd_read_short(&s);
1890 		if (i == primary_weapon_index_t::VULCAN_INDEX)
1891 			player_info.vulcan_ammo = s;
1892 		if (purpose == PURPOSE_REWRITE)
1893 			nd_write_short(s);
1894 	}
1895 
1896 	range_for (auto &i, player_info.secondary_ammo)
1897 	{
1898 		uint16_t u;
1899 		nd_read_short(&u);
1900 		i = u;
1901 		if (purpose == PURPOSE_REWRITE)
1902 			nd_write_short(i);
1903 	}
1904 
1905 	uint8_t i;
1906 	nd_read_byte(&i);
1907 	const enum laser_level laser_level{i};
1908 	if ((purpose != PURPOSE_REWRITE) && (laser_level != player_info.laser_level)) {
1909 		player_info.laser_level = laser_level;
1910 	}
1911 	else if (purpose == PURPOSE_REWRITE)
1912 		nd_write_byte(static_cast<uint8_t>(laser_level));
1913 
1914 	// Support for missions
1915 
1916 	nd_read_string(current_mission);
1917 	if (purpose == PURPOSE_REWRITE)
1918 		nd_write_string(current_mission);
1919 #if defined(DXX_BUILD_DESCENT_I)
1920 	if (!shareware)
1921 	{
1922 		if ((purpose != PURPOSE_REWRITE) && load_mission_by_name(mission_entry_predicate{current_mission}, mission_name_type::guess))
1923 		{
1924 			if (purpose == PURPOSE_CHOSE_PLAY) {
1925 				nm_messagebox(menu_title{nullptr}, 1, TXT_OK, TXT_NOMISSION4DEMO, current_mission);
1926 			}
1927 			return 1;
1928 		}
1929 	}
1930 #elif defined(DXX_BUILD_DESCENT_II)
1931 	{
1932 		mission_entry_predicate mission_predicate;
1933 		mission_predicate.filesystem_name = current_mission;
1934 		mission_predicate.check_version = false;
1935 		if (load_mission_by_name(mission_predicate, mission_name_type::guess))
1936 		{
1937 		if (purpose != PURPOSE_RANDOM_PLAY) {
1938 			nm_messagebox(menu_title{nullptr}, 1, TXT_OK, TXT_NOMISSION4DEMO, current_mission);
1939 		}
1940 		return 1;
1941 		}
1942 	}
1943 #endif
1944 
1945 	nd_recorded_total = 0;
1946 	nd_playback_total = 0;
1947 	nd_read_byte(&energy);
1948 	nd_read_byte(&shield);
1949 	if (purpose == PURPOSE_REWRITE)
1950 	{
1951 		nd_write_byte(energy);
1952 		nd_write_byte(shield);
1953 	}
1954 
1955 	int recorded_player_flags;
1956 	nd_read_int(&recorded_player_flags);
1957 	player_info.powerup_flags = player_flags(recorded_player_flags);
1958 	if (purpose == PURPOSE_REWRITE)
1959 		nd_write_int(recorded_player_flags);
1960 	if (player_info.powerup_flags & PLAYER_FLAGS_CLOAKED) {
1961 		player_info.cloak_time = GameTime64 - (CLOAK_TIME_MAX / 2);
1962 	}
1963 	if (player_info.powerup_flags & PLAYER_FLAGS_INVULNERABLE)
1964 		player_info.invulnerable_time = GameTime64 - (INVULNERABLE_TIME_MAX / 2);
1965 
1966 	auto &Primary_weapon = player_info.Primary_weapon;
1967 	{
1968 		int8_t v;
1969 		nd_read_byte(&v);
1970 		Primary_weapon = static_cast<primary_weapon_index_t>(v);
1971 	}
1972 	auto &Secondary_weapon = player_info.Secondary_weapon;
1973 	{
1974 		int8_t v;
1975 		nd_read_byte(&v);
1976 		Secondary_weapon = static_cast<secondary_weapon_index_t>(v);
1977 	}
1978 	if (purpose == PURPOSE_REWRITE)
1979 	{
1980 		nd_write_byte(Primary_weapon);
1981 		nd_write_byte(Secondary_weapon);
1982 	}
1983 
1984 // Next bit of code to fix problem that I introduced between 1.0 and 1.1
1985 // check the next byte -- it _will_ be a load_new_level event.  If it is
1986 // not, then we must shift all bytes up by one.
1987 
1988 #if defined(DXX_BUILD_DESCENT_I)
1989 	if (shareware)
1990 	{
1991 		nd_read_byte(&c);
1992 		if (c != ND_EVENT_NEW_LEVEL) {
1993 			auto flags = player_info.powerup_flags.get_player_flags();
1994 			energy = shield;
1995 			shield = static_cast<uint8_t>(flags);
1996 			Primary_weapon = static_cast<primary_weapon_index_t>(static_cast<uint8_t>(Secondary_weapon));
1997 			Secondary_weapon = static_cast<secondary_weapon_index_t>(c);
1998 		} else
1999 			PHYSFS_seek(infile, PHYSFS_tell(infile) - 1);
2000 	}
2001 #endif
2002 
2003 #if defined(DXX_BUILD_DESCENT_II)
2004 	nd_playback_v_juststarted=1;
2005 #endif
2006 	player_info.energy = i2f(energy);
2007 	get_local_plrobj().shields = i2f(shield);
2008 	return 0;
2009 }
2010 }
2011 
newdemo_pop_ctrlcen_triggers()2012 static void newdemo_pop_ctrlcen_triggers()
2013 {
2014 	auto &WallAnims = GameSharedState.WallAnims;
2015 	auto &Walls = LevelUniqueWallSubsystemState.Walls;
2016 	auto &vcwallptr = Walls.vcptr;
2017 	for (int i = 0; i < ControlCenterTriggers.num_links; i++) {
2018 		const auto &&seg = vmsegptridx(ControlCenterTriggers.seg[i]);
2019 		const auto side = ControlCenterTriggers.side[i];
2020 		const auto csegi = seg->shared_segment::children[side];
2021 		if (csegi == segment_none)
2022 		{
2023 			/* Some levels specify control center triggers for
2024 			 * segments/sides that have no wall.  `Descent 2:
2025 			 * Counterstrike` level 11, control center trigger 0 would
2026 			 * fault without this test.
2027 			 */
2028 			continue;
2029 		}
2030 		const auto &&csegp = vmsegptr(csegi);
2031 		auto cside = find_connect_side(seg, csegp);
2032 		const auto wall_num = seg->shared_segment::sides[side].wall_num;
2033 		if (wall_num == wall_none)
2034 		{
2035 			/* Some levels specify control center triggers for
2036 			 * segments/sides that have no wall.  `Descent 2:
2037 			 * Counterstrike` level 9, control center trigger 2 would
2038 			 * fault without this test.
2039 			 */
2040 			continue;
2041 		}
2042 		const auto anim_num = vcwallptr(wall_num)->clip_num;
2043 		auto &wa = WallAnims[anim_num];
2044 		const auto n = wa.num_frames;
2045 		auto &seg0uside = seg->unique_segment::sides[side];
2046 		auto &seg1uside = csegp->unique_segment::sides[cside];
2047 		const auto next_tmap = wa.frames[n - 1];
2048 		if (wa.flags & WCF_TMAP1)
2049 			seg0uside.tmap_num = seg1uside.tmap_num = texture1_value{next_tmap};
2050 		else
2051 			seg0uside.tmap_num2 = seg1uside.tmap_num2 = texture2_value{next_tmap};
2052 	}
2053 }
2054 
2055 namespace dsx {
newdemo_read_frame_information(int rewrite)2056 static int newdemo_read_frame_information(int rewrite)
2057 {
2058 	auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
2059 	auto &Objects = LevelUniqueObjectState.Objects;
2060 	auto &WallAnims = GameSharedState.WallAnims;
2061 	auto &vmobjptr = Objects.vmptr;
2062 	auto &vmobjptridx = Objects.vmptridx;
2063 	int done, angle, volume;
2064 	sbyte c;
2065 
2066 	done = 0;
2067 
2068 	if (Newdemo_vcr_state != ND_STATE_PAUSED)
2069 		for (unique_segment &useg : vmsegptr)
2070 		{
2071 			useg.objects = object_none;
2072 		}
2073 
2074 	reset_objects(LevelUniqueObjectState, 1);
2075 	auto &plrobj = get_local_plrobj();
2076 	plrobj.ctype.player_info.homing_object_dist = -1;
2077 
2078 	prev_obj = NULL;
2079 
2080 	auto &Polygon_models = LevelSharedPolygonModelState.Polygon_models;
2081 	auto &Walls = LevelUniqueWallSubsystemState.Walls;
2082 #if defined(DXX_BUILD_DESCENT_II)
2083 	auto &vcwallptr = Walls.vcptr;
2084 #endif
2085 	auto &vmwallptr = Walls.vmptr;
2086 	while( !done ) {
2087 		nd_read_byte(&c);
2088 		if (nd_playback_v_bad_read) { done = -1; break; }
2089 		if (rewrite && (c != ND_EVENT_EOF))
2090 			nd_write_byte(c);
2091 
2092 		switch( c ) {
2093 
2094 		case ND_EVENT_START_FRAME: {        // Followed by an integer frame number, then a fix FrameTime
2095 			short last_frame_length;
2096 
2097 			done=1;
2098 			nd_read_short(&last_frame_length);
2099 			nd_read_int(&nd_playback_v_framecount);
2100 			nd_read_int(&nd_recorded_time);
2101 			if (nd_playback_v_bad_read) { done = -1; break; }
2102 			if (rewrite)
2103 			{
2104 				nd_write_short(last_frame_length);
2105 				nd_record_v_framebytes_written = 3;
2106 				nd_write_int(nd_playback_v_framecount);
2107 				nd_write_int(nd_recorded_time);
2108 				break;
2109 			}
2110 			if (Newdemo_vcr_state == ND_STATE_PLAYBACK)
2111 				nd_recorded_total += nd_recorded_time;
2112 			nd_playback_v_framecount--;
2113 			break;
2114 		}
2115 
2116 		case ND_EVENT_VIEWER_OBJECT:        // Followed by an object structure
2117 		{
2118 #if defined(DXX_BUILD_DESCENT_II)
2119 			sbyte WhichWindow;
2120 			nd_read_byte (&WhichWindow);
2121 			if (rewrite)
2122 				nd_write_byte(WhichWindow);
2123 			if (WhichWindow&15)
2124 			{
2125 				const auto &&obj = vmobjptridx(static_cast<objnum_t>(MAX_OBJECTS - 1));
2126 				nd_read_object(obj);
2127 				if (nd_playback_v_bad_read)
2128 				{
2129 					done = -1;
2130 					break;
2131 				}
2132 				if (rewrite)
2133 				{
2134 					nd_write_object(obj);
2135 					break;
2136 				}
2137 				// offset to compensate inaccuracy between object and viewer
2138 				vm_vec_scale_add(obj->pos, obj->pos, obj->orient.fvec, F1_0*5 );
2139 				nd_render_extras (WhichWindow,obj);
2140 			}
2141 			else
2142 #endif
2143 			{
2144 				/* As usual, the demo code breaks the rules. */
2145 				const auto &&viewer_vmobj = Objects.vmptridx(const_cast<object *>(Viewer));
2146 			nd_read_object(viewer_vmobj);
2147 			if (nd_playback_v_bad_read) { done = -1; break; }
2148 			if (rewrite)
2149 			{
2150 				nd_write_object(viewer_vmobj);
2151 				break;
2152 			}
2153 			if (Newdemo_vcr_state != ND_STATE_PAUSED) {
2154 				auto segnum = Viewer->segnum;
2155 
2156 				// HACK HACK HACK -- since we have multiple level recording, it can be the case
2157 				// HACK HACK HACK -- that when rewinding the demo, the viewer is in a segment
2158 				// HACK HACK HACK -- that is greater than the highest index of segments.  Bash
2159 				// HACK HACK HACK -- the viewer to segment 0 for bogus view.
2160 
2161 				if (segnum > Highest_segment_index)
2162 					segnum = 0;
2163 				obj_link_unchecked(Objects.vmptr, viewer_vmobj, Segments.vmptridx(segnum));
2164 			}
2165 			}
2166 		}
2167 			break;
2168 
2169 		case ND_EVENT_RENDER_OBJECT:       // Followed by an object structure
2170 		{
2171 			const auto &&obj = obj_allocate(LevelUniqueObjectState);
2172 			if (obj==object_none)
2173 				break;
2174 			nd_read_object(obj);
2175 			if (nd_playback_v_bad_read) { done = -1; break; }
2176 			if (rewrite)
2177 			{
2178 				nd_write_object(obj);
2179 				break;
2180 			}
2181 			if (Newdemo_vcr_state != ND_STATE_PAUSED) {
2182 				auto segnum = obj->segnum;
2183 
2184 				// HACK HACK HACK -- don't render objects is segments greater than Highest_segment_index
2185 				// HACK HACK HACK -- (see above)
2186 
2187 				if (segnum > Highest_segment_index)
2188 					break;
2189 
2190 				obj_link_unchecked(Objects.vmptr, obj, Segments.vmptridx(segnum));
2191 				if ((obj->type == OBJ_PLAYER) && (Newdemo_game_mode & GM_MULTI)) {
2192 					int player;
2193 
2194 					if (Newdemo_game_mode & GM_TEAM)
2195 						player = get_team(get_player_id(obj));
2196 					else
2197 						player = get_player_id(obj);
2198 					if (player == 0)
2199 						break;
2200 					player--;
2201 
2202 					for (int i=0;i<Polygon_models[obj->rtype.pobj_info.model_num].n_textures;i++)
2203 						multi_player_textures[player][i] = ObjBitmaps[ObjBitmapPtrs[Polygon_models[obj->rtype.pobj_info.model_num].first_texture+i]];
2204 
2205 					multi_player_textures[player][4] = ObjBitmaps[ObjBitmapPtrs[First_multi_bitmap_num+(player)*2]];
2206 					multi_player_textures[player][5] = ObjBitmaps[ObjBitmapPtrs[First_multi_bitmap_num+(player)*2+1]];
2207 					obj->rtype.pobj_info.alt_textures = player+1;
2208 				}
2209 			}
2210 		}
2211 			break;
2212 
2213 		case ND_EVENT_SOUND:
2214 			{
2215 				int soundno;
2216 			nd_read_int(&soundno);
2217 			if (nd_playback_v_bad_read) {done = -1; break; }
2218 			if (rewrite)
2219 			{
2220 				nd_write_int(soundno);
2221 				break;
2222 			}
2223 			if (Newdemo_vcr_state == ND_STATE_PLAYBACK)
2224 				digi_play_sample( soundno, F1_0 );
2225 			}
2226 			break;
2227 
2228 		case ND_EVENT_SOUND_3D:
2229 			{
2230 				int soundno;
2231 			nd_read_int(&soundno);
2232 			nd_read_int(&angle);
2233 			nd_read_int(&volume);
2234 			if (nd_playback_v_bad_read) { done = -1; break; }
2235 			if (rewrite)
2236 			{
2237 				nd_write_int(soundno);
2238 				nd_write_int(angle);
2239 				nd_write_int(volume);
2240 				break;
2241 			}
2242 			if (Newdemo_vcr_state == ND_STATE_PLAYBACK)
2243 				digi_play_sample_3d(soundno, sound_pan{angle}, volume);
2244 			}
2245 			break;
2246 
2247 		case ND_EVENT_SOUND_3D_ONCE:
2248 			{
2249 				int soundno;
2250 			nd_read_int(&soundno);
2251 			nd_read_int(&angle);
2252 			nd_read_int(&volume);
2253 			if (nd_playback_v_bad_read) { done = -1; break; }
2254 			if (rewrite)
2255 			{
2256 				nd_write_int(soundno);
2257 				nd_write_int(angle);
2258 				nd_write_int(volume);
2259 				break;
2260 			}
2261 			if (Newdemo_vcr_state == ND_STATE_PLAYBACK)
2262 				digi_play_sample_3d(soundno, sound_pan{angle}, volume);
2263 			}
2264 			break;
2265 
2266 		case ND_EVENT_LINK_SOUND_TO_OBJ:
2267 			{
2268 				int soundno, max_volume, max_distance, loop_start, loop_end;
2269 				int signature;
2270 				nd_read_int( &soundno );
2271 				nd_read_int( &signature );
2272 				nd_read_int( &max_volume );
2273 				nd_read_int( &max_distance );
2274 				nd_read_int( &loop_start );
2275 				nd_read_int( &loop_end );
2276 				if (rewrite)
2277 				{
2278 					nd_write_int( soundno );
2279 					nd_write_int( signature );
2280 					nd_write_int( max_volume );
2281 					nd_write_int( max_distance );
2282 					nd_write_int( loop_start );
2283 					nd_write_int( loop_end );
2284 					break;
2285 				}
2286 				auto objnum = newdemo_find_object(object_signature_t{static_cast<uint16_t>(signature)});
2287 				if ( objnum != object_none && Newdemo_vcr_state == ND_STATE_PLAYBACK)  {   //  @mk, 2/22/96, John told me to.
2288 					digi_link_sound_to_object3(soundno, objnum, 1, max_volume, sound_stack::allow_stacking, vm_distance{max_distance}, loop_start, loop_end);
2289 				}
2290 			}
2291 			break;
2292 
2293 		case ND_EVENT_KILL_SOUND_TO_OBJ:
2294 			{
2295 				int signature;
2296 				nd_read_int( &signature );
2297 				if (rewrite)
2298 				{
2299 					nd_write_int( signature );
2300 					break;
2301 				}
2302 				auto objnum = newdemo_find_object(object_signature_t{static_cast<uint16_t>(signature)});
2303 				if ( objnum != object_none && Newdemo_vcr_state == ND_STATE_PLAYBACK)  {   //  @mk, 2/22/96, John told me to.
2304 					digi_kill_sound_linked_to_object(objnum);
2305 				}
2306 			}
2307 			break;
2308 
2309 		case ND_EVENT_WALL_HIT_PROCESS: {
2310 			int player;
2311 			int side;
2312 			segnum_t segnum;
2313 			fix damage;
2314 
2315 			nd_read_segnum32(segnum);
2316 			nd_read_int(&side);
2317 			nd_read_fix(&damage);
2318 			nd_read_int(&player);
2319 			if (nd_playback_v_bad_read) { done = -1; break; }
2320 			if (rewrite)
2321 			{
2322 				nd_write_int(segnum);
2323 				nd_write_int(side);
2324 				nd_write_fix(damage);
2325 				nd_write_int(player);
2326 				break;
2327 			}
2328 			if (Newdemo_vcr_state != ND_STATE_PAUSED)
2329 			{
2330 				auto &player_info = ConsoleObject->ctype.player_info;
2331 				wall_hit_process(player_info.powerup_flags, vmsegptridx(segnum), side, damage, player, vmobjptr(ConsoleObject));
2332 			}
2333 			break;
2334 		}
2335 
2336 		case ND_EVENT_TRIGGER:
2337 		{
2338 			int side;
2339 			segnum_t segnum;
2340 			objnum_t objnum;
2341 			nd_read_segnum32(segnum);
2342 			nd_read_int(&side);
2343 			nd_read_objnum32(objnum);
2344 			int shot;
2345 #if defined(DXX_BUILD_DESCENT_I)
2346 			shot = 0;
2347 #elif defined(DXX_BUILD_DESCENT_II)
2348 			nd_read_int(&shot);
2349 #endif
2350 			if (nd_playback_v_bad_read) { done = -1; break; }
2351 			if (rewrite)
2352 			{
2353 				nd_write_int(segnum);
2354 				nd_write_int(side);
2355 				nd_write_int(objnum);
2356 #if defined(DXX_BUILD_DESCENT_I)
2357 				break;
2358 #elif defined(DXX_BUILD_DESCENT_II)
2359 				nd_write_int(shot);
2360 #endif
2361 			}
2362 
2363                         const auto &&segp = vmsegptridx(segnum);
2364                         /* Demo recording is buggy.  Descent records
2365                             * ND_EVENT_TRIGGER for every segment transition, even
2366                             * if there is no wall.
2367 							*
2368 							* Likewise, ND_EVENT_TRIGGER can be recorded
2369 							* when the wall is valid, but there is no
2370 							* trigger on the wall.
2371                             */
2372 						auto &sside = segp->shared_segment::sides[side];
2373 						const auto wall_num = sside.wall_num;
2374                         if (wall_num != wall_none)
2375                         {
2376 #if defined(DXX_BUILD_DESCENT_II)
2377 							auto &w = *vcwallptr(wall_num);
2378 							auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
2379 							auto &vctrgptr = Triggers.vcptr;
2380 							if (w.trigger != trigger_none && vctrgptr(w.trigger)->type == trigger_action::secret_exit)
2381 							{
2382                                         int truth;
2383 
2384                                         nd_read_byte(&c);
2385                                         Assert(c == ND_EVENT_SECRET_THINGY);
2386                                         nd_read_int(&truth);
2387                                         if (Newdemo_vcr_state == ND_STATE_PAUSED)
2388                                                 break;
2389                                         if (rewrite)
2390                                         {
2391                                                 nd_write_byte(c);
2392                                                 nd_write_int(truth);
2393                                                 break;
2394                                         }
2395                                         if (!truth)
2396 											check_trigger(segp, side, plrobj, vmobjptridx(objnum), shot);
2397                                 } else if (!rewrite)
2398 #endif
2399                                 {
2400                                         if (Newdemo_vcr_state != ND_STATE_PAUSED)
2401 											check_trigger(segp, side, plrobj, vmobjptridx(objnum), shot);
2402                                 }
2403                         }
2404 		}
2405 			break;
2406 
2407 		case ND_EVENT_HOSTAGE_RESCUED: {
2408 			int hostage_number;
2409 
2410 			nd_read_int(&hostage_number);
2411 			if (nd_playback_v_bad_read) { done = -1; break; }
2412 			if (rewrite)
2413 			{
2414 				nd_write_int(hostage_number);
2415 				break;
2416 			}
2417 			if (Newdemo_vcr_state != ND_STATE_PAUSED)
2418 				hostage_rescue();
2419 			break;
2420 		}
2421 
2422 		case ND_EVENT_MORPH_FRAME: {
2423 #if 0
2424 			morph_data *md;
2425 
2426 			md = &morph_objects[0];
2427 			if (newdemo_read( md->morph_vecs, sizeof(md->morph_vecs), 1 )!=1) { done=-1; break; }
2428 			if (newdemo_read( md->submodel_active, sizeof(md->submodel_active), 1 )!=1) { done=-1; break; }
2429 			if (newdemo_read( md->submodel_startpoints, sizeof(md->submodel_startpoints), 1 )!=1) { done=-1; break; }
2430 #endif
2431 			const auto &&obj = obj_allocate(LevelUniqueObjectState);
2432 			if (obj==object_none)
2433 				break;
2434 			nd_read_object(obj);
2435 			if (nd_playback_v_bad_read) { done = -1; break; }
2436 			if (rewrite)
2437 			{
2438 				nd_write_object(obj);
2439 				break;
2440 			}
2441 			obj->render_type = RT_POLYOBJ;
2442 			if (Newdemo_vcr_state != ND_STATE_PAUSED) {
2443 				if (Newdemo_vcr_state != ND_STATE_PAUSED) {
2444 					auto segnum = obj->segnum;
2445 					obj_link_unchecked(Objects.vmptr, obj, Segments.vmptridx(segnum));
2446 				}
2447 			}
2448 			break;
2449 		}
2450 
2451 		case ND_EVENT_WALL_TOGGLE:
2452 		{
2453 			int side;
2454 			segnum_t segnum;
2455 			nd_read_segnum32(segnum);
2456 			nd_read_int(&side);
2457 			if (nd_playback_v_bad_read) {done = -1; break; }
2458 			if (rewrite)
2459 			{
2460 				nd_write_int(segnum);
2461 				nd_write_int(side);
2462 				break;
2463 			}
2464 			if (Newdemo_vcr_state != ND_STATE_PAUSED)
2465 				wall_toggle(vmwallptr, vmsegptridx(segnum), side);
2466 		}
2467 			break;
2468 
2469 		case ND_EVENT_CONTROL_CENTER_DESTROYED:
2470 			nd_read_int(&LevelUniqueControlCenterState.Countdown_seconds_left);
2471 			LevelUniqueControlCenterState.Control_center_destroyed = 1;
2472 			if (nd_playback_v_bad_read) { done = -1; break; }
2473 			if (rewrite)
2474 			{
2475 				nd_write_int(LevelUniqueControlCenterState.Countdown_seconds_left);
2476 				break;
2477 			}
2478 			if (!nd_playback_v_cntrlcen_destroyed) {
2479 				newdemo_pop_ctrlcen_triggers();
2480 				nd_playback_v_cntrlcen_destroyed = 1;
2481 			}
2482 			break;
2483 
2484 		case ND_EVENT_HUD_MESSAGE: {
2485 			char hud_msg[60];
2486 
2487 			nd_read_string(hud_msg);
2488 			if (nd_playback_v_bad_read) { done = -1; break; }
2489 			if (rewrite)
2490 			{
2491 				nd_write_string(&(hud_msg[0]));
2492 				break;
2493 			}
2494 			if (Newdemo_vcr_state != ND_STATE_PAUSED)
2495 				HUD_init_message_literal( HM_DEFAULT, hud_msg );
2496 			break;
2497 			}
2498 #if defined(DXX_BUILD_DESCENT_II)
2499 		case ND_EVENT_START_GUIDED:
2500 			if ((Newdemo_vcr_state == ND_STATE_PLAYBACK) || (Newdemo_vcr_state == ND_STATE_FASTFORWARD) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEFORWARD)) {
2501 				nd_playback_v_guided = 1;
2502 			} else if ((Newdemo_vcr_state == ND_STATE_REWINDING) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD)) {
2503 				nd_playback_v_guided = 0;
2504 			}
2505 			break;
2506 		case ND_EVENT_END_GUIDED:
2507 			if ((Newdemo_vcr_state == ND_STATE_REWINDING) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD)) {
2508 				nd_playback_v_guided = 1;
2509 			} else if ((Newdemo_vcr_state == ND_STATE_PLAYBACK) || (Newdemo_vcr_state == ND_STATE_FASTFORWARD) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEFORWARD)) {
2510 				nd_playback_v_guided = 0;
2511 			}
2512 			break;
2513 #endif
2514 
2515 		case ND_EVENT_PALETTE_EFFECT: {
2516 			short r, g, b;
2517 
2518 			nd_read_short(&r);
2519 			nd_read_short(&g);
2520 			nd_read_short(&b);
2521 			if (nd_playback_v_bad_read) { done = -1; break; }
2522 			if (rewrite)
2523 			{
2524 				nd_write_short(r);
2525 				nd_write_short(g);
2526 				nd_write_short(b);
2527 				break;
2528 			}
2529 			PALETTE_FLASH_SET(r,g,b);
2530 			break;
2531 		}
2532 
2533 		case ND_EVENT_PLAYER_ENERGY: {
2534 			ubyte energy;
2535 			ubyte old_energy;
2536 
2537 			if (!shareware)
2538 			nd_read_byte(&old_energy);
2539 			nd_read_byte(&energy);
2540 
2541 			if (nd_playback_v_bad_read) {done = -1; break; }
2542 			if (rewrite)
2543 			{
2544 				nd_write_byte(old_energy);
2545 				nd_write_byte(energy);
2546 				break;
2547 			}
2548 			auto &player_info = get_local_plrobj().ctype.player_info;
2549 			if (shareware)
2550 				player_info.energy = i2f(energy);
2551 			else
2552 			{
2553 			if ((Newdemo_vcr_state == ND_STATE_PLAYBACK) || (Newdemo_vcr_state == ND_STATE_FASTFORWARD) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEFORWARD)) {
2554 				player_info.energy = i2f(energy);
2555 			} else if ((Newdemo_vcr_state == ND_STATE_REWINDING) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD)) {
2556 				if (old_energy != 255)
2557 					player_info.energy = i2f(old_energy);
2558 			}
2559 			}
2560 			break;
2561 		}
2562 
2563 #if defined(DXX_BUILD_DESCENT_II)
2564 		case ND_EVENT_PLAYER_AFTERBURNER: {
2565 			ubyte afterburner;
2566 			ubyte old_afterburner;
2567 
2568 			nd_read_byte(&old_afterburner);
2569 			nd_read_byte(&afterburner);
2570 			if (nd_playback_v_bad_read) {done = -1; break; }
2571 			if (rewrite)
2572 			{
2573 				nd_write_byte(old_afterburner);
2574 				nd_write_byte(afterburner);
2575 				break;
2576 			}
2577 			if ((Newdemo_vcr_state == ND_STATE_PLAYBACK) || (Newdemo_vcr_state == ND_STATE_FASTFORWARD) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEFORWARD)) {
2578 				Afterburner_charge = afterburner<<9;
2579 // 				if (Afterburner_charge < 0) Afterburner_charge=f1_0;
2580 			} else if ((Newdemo_vcr_state == ND_STATE_REWINDING) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD)) {
2581 				if (old_afterburner != 255)
2582 					Afterburner_charge = old_afterburner<<9;
2583 			}
2584 			break;
2585 		}
2586 #endif
2587 
2588 		case ND_EVENT_PLAYER_SHIELD: {
2589 			ubyte shield;
2590 			ubyte old_shield;
2591 
2592 			if (!shareware)
2593 			nd_read_byte(&old_shield);
2594 			nd_read_byte(&shield);
2595 			if (nd_playback_v_bad_read) {done = -1; break; }
2596 			if (rewrite)
2597 			{
2598 				nd_write_byte(old_shield);
2599 				nd_write_byte(shield);
2600 				break;
2601 			}
2602 			if (shareware)
2603 				get_local_plrobj().shields = i2f(shield);
2604 			else
2605 			{
2606 			if ((Newdemo_vcr_state == ND_STATE_PLAYBACK) || (Newdemo_vcr_state == ND_STATE_FASTFORWARD) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEFORWARD)) {
2607 				get_local_plrobj().shields = i2f(shield);
2608 			} else if ((Newdemo_vcr_state == ND_STATE_REWINDING) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD)) {
2609 				if (old_shield != 255)
2610 					get_local_plrobj().shields = i2f(old_shield);
2611 			}
2612 			}
2613 			break;
2614 		}
2615 
2616 		case ND_EVENT_PLAYER_FLAGS: {
2617 			int recorded_player_flags;
2618 			nd_read_int(&recorded_player_flags);
2619 			if (nd_playback_v_bad_read) {done = -1; break; }
2620 			if (rewrite)
2621 			{
2622 				nd_write_int(recorded_player_flags);
2623 				break;
2624 			}
2625 
2626 			const auto old_player_flags = player_flags(static_cast<unsigned>(recorded_player_flags) >> 16);
2627 			const auto new_player_flags = player_flags(static_cast<unsigned>(recorded_player_flags));
2628 
2629 			const auto old_cloaked = old_player_flags & PLAYER_FLAGS_CLOAKED;
2630 			const auto new_cloaked = new_player_flags & PLAYER_FLAGS_CLOAKED;
2631 			const auto old_invul = old_player_flags & PLAYER_FLAGS_INVULNERABLE;
2632 			const auto new_invul = new_player_flags & PLAYER_FLAGS_INVULNERABLE;
2633 			if ((Newdemo_vcr_state == ND_STATE_REWINDING) || ((Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD) && (old_player_flags.get_player_flags() != 0xffff)) ) {
2634 				auto &player_info = get_local_plrobj().ctype.player_info;
2635 				if (old_cloaked != new_cloaked)
2636 				{
2637 					auto &t = player_info.cloak_time;
2638 					if (!old_cloaked)
2639 						DXX_MAKE_VAR_UNDEFINED(t);
2640 					else
2641 						t = GameTime64 - (CLOAK_TIME_MAX / 2);
2642 				}
2643 				if (old_invul != new_invul)
2644 				{
2645 					auto &t = player_info.invulnerable_time;
2646 					if (!old_invul)
2647 						DXX_MAKE_VAR_UNDEFINED(t);
2648 					else
2649 						t = GameTime64 - (INVULNERABLE_TIME_MAX / 2);
2650 				}
2651 				player_info.powerup_flags = old_player_flags;
2652 			} else if ((Newdemo_vcr_state == ND_STATE_PLAYBACK) || (Newdemo_vcr_state == ND_STATE_FASTFORWARD) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEFORWARD)) {
2653 				auto &player_info = get_local_plrobj().ctype.player_info;
2654 				if (old_cloaked != new_cloaked)
2655 				{
2656 					auto &t = player_info.cloak_time;
2657 					if (!old_cloaked)
2658 						t = GameTime64 - (CLOAK_TIME_MAX / 2);
2659 					else
2660 						DXX_MAKE_VAR_UNDEFINED(t);
2661 				}
2662 				if (old_invul != new_invul)
2663 				{
2664 					auto &t = player_info.invulnerable_time;
2665 					if (!old_invul)
2666 						t = GameTime64 - (INVULNERABLE_TIME_MAX / 2);
2667 					else
2668 						DXX_MAKE_VAR_UNDEFINED(t);
2669 				}
2670 				player_info.powerup_flags = new_player_flags;
2671 			}
2672 			if ((old_player_flags & PLAYER_FLAGS_QUAD_LASERS) != (new_player_flags & PLAYER_FLAGS_QUAD_LASERS))
2673 				update_laser_weapon_info();
2674 			break;
2675 		}
2676 
2677 		case ND_EVENT_PLAYER_WEAPON:
2678 		if (shareware)
2679 		{
2680 			ubyte weapon_type, weapon_num;
2681 
2682 			nd_read_byte(&weapon_type);
2683 			nd_read_byte(&weapon_num);
2684 			if (rewrite)
2685 			{
2686 				nd_write_byte(weapon_type);
2687 				nd_write_byte(weapon_num);
2688 				break;
2689 			}
2690 
2691 			auto &player_info = get_local_plrobj().ctype.player_info;
2692 			if (weapon_type == 0)
2693 				player_info.Primary_weapon = static_cast<primary_weapon_index_t>(weapon_num);
2694 			else
2695 				player_info.Secondary_weapon = static_cast<secondary_weapon_index_t>(weapon_num);
2696 
2697 			break;
2698 		}
2699 		else
2700 			{
2701 			ubyte weapon_type, weapon_num;
2702 			ubyte old_weapon;
2703 
2704 			nd_read_byte(&weapon_type);
2705 			nd_read_byte(&weapon_num);
2706 			nd_read_byte(&old_weapon);
2707 			if (rewrite)
2708 			{
2709 				nd_write_byte(weapon_type);
2710 				nd_write_byte(weapon_num);
2711 				nd_write_byte(old_weapon);
2712 				break;
2713 			}
2714 			auto &player_info = get_local_plrobj().ctype.player_info;
2715 			if ((Newdemo_vcr_state == ND_STATE_PLAYBACK) || (Newdemo_vcr_state == ND_STATE_FASTFORWARD) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEFORWARD)) {
2716 				if (weapon_type == 0)
2717 					player_info.Primary_weapon = static_cast<primary_weapon_index_t>(weapon_num);
2718 				else
2719 					player_info.Secondary_weapon = static_cast<secondary_weapon_index_t>(weapon_num);
2720 			} else if ((Newdemo_vcr_state == ND_STATE_REWINDING) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD)) {
2721 				if (weapon_type == 0)
2722 					player_info.Primary_weapon = static_cast<primary_weapon_index_t>(old_weapon);
2723 				else
2724 					player_info.Secondary_weapon = static_cast<secondary_weapon_index_t>(old_weapon);
2725 			}
2726 			break;
2727 		}
2728 
2729 		case ND_EVENT_EFFECT_BLOWUP: {
2730 			segnum_t segnum;
2731 			sbyte side;
2732 			vms_vector pnt;
2733 
2734 			nd_read_segnum16(segnum);
2735 			nd_read_byte(&side);
2736 			nd_read_vector(pnt);
2737 			if (rewrite)
2738 			{
2739 				nd_write_short(segnum);
2740 				nd_write_byte(side);
2741 				nd_write_vector(pnt);
2742 				break;
2743 			}
2744 			if (Newdemo_vcr_state != ND_STATE_PAUSED)
2745 			{
2746 #if defined(DXX_BUILD_DESCENT_I)
2747 				check_effect_blowup(LevelSharedDestructibleLightState, Vclip, vmsegptridx(segnum), side, pnt, nullptr, 0, 0);
2748 #elif defined(DXX_BUILD_DESCENT_II)
2749 				auto &LevelSharedDestructibleLightState = LevelSharedSegmentState.DestructibleLights;
2750 			//create a dummy object which will be the weapon that hits
2751 			//the monitor. the blowup code wants to know who the parent of the
2752 			//laser is, so create a laser whose parent is the player
2753 				laser_parent dummy;
2754 				dummy.parent_type = OBJ_PLAYER;
2755 				dummy.parent_num = Player_num;
2756 				check_effect_blowup(LevelSharedDestructibleLightState, Vclip, vmsegptridx(segnum), side, pnt, dummy, 0, 0);
2757 #endif
2758 			}
2759 			break;
2760 		}
2761 
2762 		case ND_EVENT_HOMING_DISTANCE: {
2763 			short distance;
2764 
2765 			nd_read_short(&distance);
2766 			if (rewrite)
2767 			{
2768 				nd_write_short(distance);
2769 				break;
2770 			}
2771 			get_local_plrobj().ctype.player_info.homing_object_dist = i2f(distance << 16);
2772 			break;
2773 		}
2774 
2775 		case ND_EVENT_LETTERBOX:
2776 			if ((Newdemo_vcr_state == ND_STATE_PLAYBACK) || (Newdemo_vcr_state == ND_STATE_FASTFORWARD) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEFORWARD)) {
2777 				nd_playback_v_dead = 1;
2778 			} else if ((Newdemo_vcr_state == ND_STATE_REWINDING) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD))
2779 				nd_playback_v_dead = 0;
2780 			break;
2781 
2782 #if defined(DXX_BUILD_DESCENT_II)
2783 		case ND_EVENT_CHANGE_COCKPIT: {
2784 			int dummy;
2785 			nd_read_int (&dummy);
2786 			if (rewrite)
2787 				nd_write_int(dummy);
2788 			break;
2789 		}
2790 #endif
2791 		case ND_EVENT_REARVIEW:
2792 			if ((Newdemo_vcr_state == ND_STATE_PLAYBACK) || (Newdemo_vcr_state == ND_STATE_FASTFORWARD) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEFORWARD)) {
2793 				nd_playback_v_rear = 1;
2794 			} else if ((Newdemo_vcr_state == ND_STATE_REWINDING) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD)) {
2795 				nd_playback_v_rear = 0;
2796 			}
2797 			break;
2798 
2799 		case ND_EVENT_RESTORE_COCKPIT:
2800 			if ((Newdemo_vcr_state == ND_STATE_REWINDING) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD)) {
2801 				nd_playback_v_dead = 1;
2802 			} else if ((Newdemo_vcr_state == ND_STATE_PLAYBACK) || (Newdemo_vcr_state == ND_STATE_FASTFORWARD) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEFORWARD))
2803 				nd_playback_v_dead = 0;
2804 			break;
2805 
2806 
2807 		case ND_EVENT_RESTORE_REARVIEW:
2808 			if ((Newdemo_vcr_state == ND_STATE_REWINDING) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD)) {
2809 				nd_playback_v_rear= 1;
2810 			} else if ((Newdemo_vcr_state == ND_STATE_PLAYBACK) || (Newdemo_vcr_state == ND_STATE_FASTFORWARD) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEFORWARD)) {
2811 				nd_playback_v_rear = 0;
2812 			}
2813 			break;
2814 
2815 
2816 		case ND_EVENT_WALL_SET_TMAP_NUM1: {
2817 			uint16_t seg, cseg, tmap;
2818 			sbyte side,cside;
2819 
2820 			nd_read_short(&seg);
2821 			nd_read_byte(&side);
2822 			nd_read_short(&cseg);
2823 			nd_read_byte(&cside);
2824 			nd_read_short( &tmap );
2825 			if (rewrite)
2826 			{
2827 				nd_write_short(seg);
2828 				nd_write_byte(side);
2829 				nd_write_short(cseg);
2830 				nd_write_byte(cside);
2831 				nd_write_short(tmap);
2832 				break;
2833 			}
2834 			if ((Newdemo_vcr_state != ND_STATE_PAUSED) && (Newdemo_vcr_state != ND_STATE_REWINDING) && (Newdemo_vcr_state != ND_STATE_ONEFRAMEBACKWARD))
2835 				vmsegptr(seg)->unique_segment::sides[side].tmap_num = vmsegptr(cseg)->unique_segment::sides[cside].tmap_num = texture1_value{tmap};
2836 			break;
2837 		}
2838 
2839 		case ND_EVENT_WALL_SET_TMAP_NUM2: {
2840 			uint16_t seg, cseg, tmap;
2841 			sbyte side,cside;
2842 
2843 			nd_read_short(&seg);
2844 			nd_read_byte(&side);
2845 			nd_read_short(&cseg);
2846 			nd_read_byte(&cside);
2847 			nd_read_short( &tmap );
2848 			if (rewrite)
2849 			{
2850 				nd_write_short(seg);
2851 				nd_write_byte(side);
2852 				nd_write_short(cseg);
2853 				nd_write_byte(cside);
2854 				nd_write_short(tmap);
2855 				break;
2856 			}
2857 			if ((Newdemo_vcr_state != ND_STATE_PAUSED) && (Newdemo_vcr_state != ND_STATE_REWINDING) && (Newdemo_vcr_state != ND_STATE_ONEFRAMEBACKWARD)) {
2858 				unique_segment &s0 = *vmsegptr(seg);
2859 				auto &tmap_num2 = s0.sides[side].tmap_num2;
2860 				tmap_num2 = vmsegptr(cseg)->unique_segment::sides[cside].tmap_num2 = texture2_value{tmap};
2861 			}
2862 			break;
2863 		}
2864 
2865 		case ND_EVENT_MULTI_CLOAK: {
2866 			sbyte pnum;
2867 
2868 			nd_read_byte(&pnum);
2869 			if (rewrite)
2870 			{
2871 				nd_write_byte(pnum);
2872 				break;
2873 			}
2874 			auto &player_info = get_local_plrobj().ctype.player_info;
2875 			if ((Newdemo_vcr_state == ND_STATE_REWINDING) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD)) {
2876 				player_info.powerup_flags &= ~PLAYER_FLAGS_CLOAKED;
2877 				DXX_MAKE_VAR_UNDEFINED(player_info.cloak_time);
2878 			} else if ((Newdemo_vcr_state == ND_STATE_PLAYBACK) || (Newdemo_vcr_state == ND_STATE_FASTFORWARD) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEFORWARD)) {
2879 				player_info.powerup_flags |= PLAYER_FLAGS_CLOAKED;
2880 				player_info.cloak_time = GameTime64  - (CLOAK_TIME_MAX / 2);
2881 			}
2882 			break;
2883 		}
2884 
2885 		case ND_EVENT_MULTI_DECLOAK: {
2886 			sbyte pnum;
2887 
2888 			nd_read_byte(&pnum);
2889 			if (rewrite)
2890 			{
2891 				nd_write_byte(pnum);
2892 				break;
2893 			}
2894 
2895 			auto &player_info = get_local_plrobj().ctype.player_info;
2896 			if ((Newdemo_vcr_state == ND_STATE_REWINDING) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD)) {
2897 				player_info.powerup_flags |= PLAYER_FLAGS_CLOAKED;
2898 				player_info.cloak_time = GameTime64 - (CLOAK_TIME_MAX / 2);
2899 			} else if ((Newdemo_vcr_state == ND_STATE_PLAYBACK) || (Newdemo_vcr_state == ND_STATE_FASTFORWARD) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEFORWARD)) {
2900 				player_info.powerup_flags &= ~PLAYER_FLAGS_CLOAKED;
2901 				DXX_MAKE_VAR_UNDEFINED(player_info.cloak_time);
2902 			}
2903 			break;
2904 		}
2905 
2906 		case ND_EVENT_MULTI_DEATH: {
2907 			uint8_t pnum;
2908 
2909 			nd_read_byte(&pnum);
2910 			if (rewrite)
2911 			{
2912 				nd_write_byte(pnum);
2913 				break;
2914 			}
2915 			auto &player_info = vmobjptr(vcplayerptr(static_cast<unsigned>(pnum))->objnum)->ctype.player_info;
2916 			if ((Newdemo_vcr_state == ND_STATE_REWINDING) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD))
2917 				player_info.net_killed_total--;
2918 			else if ((Newdemo_vcr_state == ND_STATE_PLAYBACK) || (Newdemo_vcr_state == ND_STATE_FASTFORWARD) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEFORWARD))
2919 				player_info.net_killed_total++;
2920 			break;
2921 		}
2922 
2923 		case ND_EVENT_MULTI_KILL: {
2924 			uint8_t pnum, kill;
2925 
2926 			nd_read_byte(&pnum);
2927 			nd_read_byte(&kill);
2928 			if (rewrite)
2929 			{
2930 				nd_write_byte(pnum);
2931 				nd_write_byte(kill);
2932 				break;
2933 			}
2934 			auto &player_info = vmobjptr(vcplayerptr(static_cast<unsigned>(pnum))->objnum)->ctype.player_info;
2935 			if ((Newdemo_vcr_state == ND_STATE_REWINDING) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD)) {
2936 				player_info.net_kills_total -= kill;
2937 				if (Newdemo_game_mode & GM_TEAM)
2938 					team_kills[get_team(pnum)] -= kill;
2939 			} else if ((Newdemo_vcr_state == ND_STATE_PLAYBACK) || (Newdemo_vcr_state == ND_STATE_FASTFORWARD) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEFORWARD)) {
2940 				player_info.net_kills_total += kill;
2941 				if (Newdemo_game_mode & GM_TEAM)
2942 					team_kills[get_team(pnum)] += kill;
2943 			}
2944 			Game_mode = Newdemo_game_mode;
2945 			multi_sort_kill_list();
2946 			Game_mode = GM_NORMAL;
2947 			break;
2948 		}
2949 
2950 		case ND_EVENT_MULTI_CONNECT: {
2951 			uint8_t pnum, new_player;
2952 			int killed_total, kills_total;
2953 			callsign_t new_callsign, old_callsign;
2954 
2955 			nd_read_byte(&pnum);
2956 			nd_read_byte(&new_player);
2957 			if (!new_player) {
2958 				nd_read_string(old_callsign.buffer());
2959 				nd_read_int(&killed_total);
2960 				nd_read_int(&kills_total);
2961 			}
2962 			nd_read_string(new_callsign.buffer());
2963 			if (rewrite)
2964 			{
2965 				nd_write_byte(pnum);
2966 				nd_write_byte(new_player);
2967 				if (!new_player) {
2968 					nd_write_string(old_callsign);
2969 					nd_write_int(killed_total);
2970 					nd_write_int(kills_total);
2971 				}
2972 				nd_write_string(static_cast<const char *>(new_callsign));
2973 				break;
2974 			}
2975 			auto &plr = *vmplayerptr(static_cast<unsigned>(pnum));
2976 			auto &player_info = vmobjptr(plr.objnum)->ctype.player_info;
2977 			if ((Newdemo_vcr_state == ND_STATE_REWINDING) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD)) {
2978 				plr.connected = CONNECT_DISCONNECTED;
2979 				if (!new_player) {
2980 					plr.callsign = old_callsign;
2981 					player_info.net_killed_total = killed_total;
2982 					player_info.net_kills_total = kills_total;
2983 				} else {
2984 					N_players--;
2985 				}
2986 			} else if ((Newdemo_vcr_state == ND_STATE_PLAYBACK) || (Newdemo_vcr_state == ND_STATE_FASTFORWARD) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEFORWARD)) {
2987 				plr.connected = CONNECT_PLAYING;
2988 				player_info.net_kills_total = 0;
2989 				player_info.net_killed_total = 0;
2990 				plr.callsign = new_callsign;
2991 				if (new_player)
2992 					N_players++;
2993 			}
2994 			break;
2995 		}
2996 
2997 		case ND_EVENT_MULTI_RECONNECT: {
2998 			uint8_t pnum;
2999 
3000 			nd_read_byte(&pnum);
3001 			if (rewrite)
3002 			{
3003 				nd_write_byte(pnum);
3004 				break;
3005 			}
3006 			if ((Newdemo_vcr_state == ND_STATE_REWINDING) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD))
3007 				vmplayerptr(static_cast<unsigned>(pnum))->connected = CONNECT_DISCONNECTED;
3008 			else if ((Newdemo_vcr_state == ND_STATE_PLAYBACK) || (Newdemo_vcr_state == ND_STATE_FASTFORWARD) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEFORWARD))
3009 				vmplayerptr(static_cast<unsigned>(pnum))->connected = CONNECT_PLAYING;
3010 			break;
3011 		}
3012 
3013 		case ND_EVENT_MULTI_DISCONNECT: {
3014 			uint8_t pnum;
3015 
3016 			nd_read_byte(&pnum);
3017 			if (rewrite)
3018 			{
3019 				nd_write_byte(pnum);
3020 				break;
3021 			}
3022 #if defined(DXX_BUILD_DESCENT_I)
3023 			if ((Newdemo_vcr_state == ND_STATE_REWINDING) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD))
3024 				vmplayerptr(static_cast<unsigned>(pnum))->connected = CONNECT_DISCONNECTED;
3025 			else if ((Newdemo_vcr_state == ND_STATE_PLAYBACK) || (Newdemo_vcr_state == ND_STATE_FASTFORWARD) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEFORWARD))
3026 				vmplayerptr(static_cast<unsigned>(pnum))->connected = CONNECT_PLAYING;
3027 #elif defined(DXX_BUILD_DESCENT_II)
3028 			if ((Newdemo_vcr_state == ND_STATE_REWINDING) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD))
3029 				vmplayerptr(static_cast<unsigned>(pnum))->connected = CONNECT_PLAYING;
3030 			else if ((Newdemo_vcr_state == ND_STATE_PLAYBACK) || (Newdemo_vcr_state == ND_STATE_FASTFORWARD) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEFORWARD))
3031 				vmplayerptr(static_cast<unsigned>(pnum))->connected = CONNECT_DISCONNECTED;
3032 #endif
3033 			break;
3034 		}
3035 
3036 		case ND_EVENT_MULTI_SCORE: {
3037 			int score;
3038 			sbyte pnum;
3039 
3040 			nd_read_byte(&pnum);
3041 			nd_read_int(&score);
3042 			if (rewrite)
3043 			{
3044 				nd_write_byte(pnum);
3045 				nd_write_int(score);
3046 				break;
3047 			}
3048 			auto &player_info = get_local_plrobj().ctype.player_info;
3049 			if ((Newdemo_vcr_state == ND_STATE_REWINDING) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD))
3050 				player_info.mission.score -= score;
3051 			else if ((Newdemo_vcr_state == ND_STATE_PLAYBACK) || (Newdemo_vcr_state == ND_STATE_FASTFORWARD) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEFORWARD))
3052 				player_info.mission.score += score;
3053 			Game_mode = Newdemo_game_mode;
3054 			multi_sort_kill_list();
3055 			Game_mode = GM_NORMAL;
3056 			break;
3057 		}
3058 
3059 		case ND_EVENT_PLAYER_SCORE: {
3060 			int score;
3061 
3062 			nd_read_int(&score);
3063 			if (rewrite)
3064 			{
3065 				nd_write_int(score);
3066 				break;
3067 			}
3068 			auto &player_info = get_local_plrobj().ctype.player_info;
3069 			if ((Newdemo_vcr_state == ND_STATE_REWINDING) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD))
3070 				player_info.mission.score -= score;
3071 			else if ((Newdemo_vcr_state == ND_STATE_PLAYBACK) || (Newdemo_vcr_state == ND_STATE_FASTFORWARD) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEFORWARD))
3072 				player_info.mission.score += score;
3073 			break;
3074 		}
3075 
3076 
3077 		case ND_EVENT_PRIMARY_AMMO: {
3078 			unsigned short old_ammo, new_ammo;
3079 
3080 			nd_read_short(&old_ammo);
3081 			nd_read_short(&new_ammo);
3082 			if (rewrite)
3083 			{
3084 				nd_write_short(old_ammo);
3085 				nd_write_short(new_ammo);
3086 				break;
3087 			}
3088 
3089 			unsigned short value;
3090 			// NOTE: Used (Primary_weapon==GAUSS_INDEX?VULCAN_INDEX:Primary_weapon) because game needs VULCAN_INDEX updated to show Gauss ammo
3091 			if ((Newdemo_vcr_state == ND_STATE_REWINDING) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD))
3092 				value = old_ammo;
3093 			else if ((Newdemo_vcr_state == ND_STATE_PLAYBACK) || (Newdemo_vcr_state == ND_STATE_FASTFORWARD) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEFORWARD))
3094 				value = new_ammo;
3095 			else
3096 				break;
3097 			auto &player_info = get_local_plrobj().ctype.player_info;
3098 #if defined(DXX_BUILD_DESCENT_II)
3099 			if (player_info.Primary_weapon == primary_weapon_index_t::OMEGA_INDEX) // If Omega cannon, we need to update Omega_charge - not stored in primary_ammo
3100 				player_info.Omega_charge = (value<=0?f1_0:value);
3101 			else
3102 #endif
3103 			if (weapon_index_uses_vulcan_ammo(player_info.Primary_weapon))
3104 				player_info.vulcan_ammo = value;
3105 			break;
3106 		}
3107 
3108 		case ND_EVENT_SECONDARY_AMMO: {
3109 			short old_ammo, new_ammo;
3110 
3111 			nd_read_short(&old_ammo);
3112 			nd_read_short(&new_ammo);
3113 			if (rewrite)
3114 			{
3115 				nd_write_short(old_ammo);
3116 				nd_write_short(new_ammo);
3117 				break;
3118 			}
3119 
3120 			auto &player_info = get_local_plrobj().ctype.player_info;
3121 			if ((Newdemo_vcr_state == ND_STATE_REWINDING) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD))
3122 				player_info.secondary_ammo[player_info.Secondary_weapon] = old_ammo;
3123 			else if ((Newdemo_vcr_state == ND_STATE_PLAYBACK) || (Newdemo_vcr_state == ND_STATE_FASTFORWARD) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEFORWARD))
3124 				player_info.secondary_ammo[player_info.Secondary_weapon] = new_ammo;
3125 			break;
3126 		}
3127 
3128 		case ND_EVENT_DOOR_OPENING: {
3129 			segnum_t segnum;
3130 			sbyte side;
3131 
3132 			nd_read_segnum16(segnum);
3133 			nd_read_byte(&side);
3134 			if (rewrite)
3135 			{
3136 				nd_write_short(segnum);
3137 				nd_write_byte(side);
3138 				break;
3139 			}
3140 			if ((Newdemo_vcr_state == ND_STATE_REWINDING) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD)) {
3141 				const auto &&segp = vmsegptridx(segnum);
3142 				const shared_segment &sseg = segp;
3143 				const auto &&csegp = vmsegptr(sseg.children[side]);
3144 				const auto &&cside = find_connect_side(segp, csegp);
3145 				const auto anim_num = vmwallptr(sseg.sides[side].wall_num)->clip_num;
3146 				auto &wa = WallAnims[anim_num];
3147 				auto &seg0uside = segp->unique_segment::sides[side];
3148 				auto &seg1uside = csegp->unique_segment::sides[cside];
3149 				const auto next_tmap = wa.frames[0];
3150 				if (wa.flags & WCF_TMAP1)
3151 					seg0uside.tmap_num = seg1uside.tmap_num = texture1_value{next_tmap};
3152 				else
3153 					seg0uside.tmap_num2 = seg1uside.tmap_num2 = texture2_value{next_tmap};
3154 			}
3155 			break;
3156 		}
3157 
3158 		case ND_EVENT_LASER_LEVEL: {
3159 			uint8_t old_level, new_level;
3160 
3161 			nd_read_byte(&old_level);
3162 			nd_read_byte(&new_level);
3163 			if (rewrite)
3164 			{
3165 				nd_write_byte(old_level);
3166 				nd_write_byte(new_level);
3167 				break;
3168 			}
3169 			auto &player_info = get_local_plrobj().ctype.player_info;
3170 			if ((Newdemo_vcr_state == ND_STATE_REWINDING) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD)) {
3171 				player_info.laser_level = laser_level{old_level};
3172 			} else if ((Newdemo_vcr_state == ND_STATE_PLAYBACK) || (Newdemo_vcr_state == ND_STATE_FASTFORWARD) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEFORWARD)) {
3173 				player_info.laser_level = laser_level{new_level};
3174 			}
3175 			break;
3176 		}
3177 
3178 #if defined(DXX_BUILD_DESCENT_II)
3179 		case ND_EVENT_CLOAKING_WALL: {
3180 			uint8_t type, state, cloak_value;
3181 			wallnum_t back_wall_num, front_wall_num;
3182 			short l0,l1,l2,l3;
3183 
3184 			nd_read_byte(&type);
3185 			front_wall_num = wallnum_t{type};
3186 			nd_read_byte(&type);
3187 			back_wall_num = wallnum_t{type};
3188 			nd_read_byte(&type);
3189 			nd_read_byte(&state);
3190 			nd_read_byte(&cloak_value);
3191 			nd_read_short(&l0);
3192 			nd_read_short(&l1);
3193 			nd_read_short(&l2);
3194 			nd_read_short(&l3);
3195 			if (rewrite)
3196 			{
3197 				nd_write_byte(static_cast<uint8_t>(front_wall_num));
3198 				nd_write_byte(static_cast<uint8_t>(back_wall_num));
3199 				nd_write_byte(type);
3200 				nd_write_byte(state);
3201 				nd_write_byte(cloak_value);
3202 				nd_write_short(l0);
3203 				nd_write_short(l1);
3204 				nd_write_short(l2);
3205 				nd_write_short(l3);
3206 				break;
3207 			}
3208 
3209 			{
3210 				auto &w = *vmwallptr(front_wall_num);
3211 				w.type = type;
3212 				w.state = wall_state{state};
3213 				w.cloak_value = cloak_value;
3214 				auto &uvl = vmsegptr(w.segnum)->unique_segment::sides[w.sidenum].uvls;
3215 				uvl[0].l = (static_cast<int>(l0)) << 8;
3216 				uvl[1].l = (static_cast<int>(l1)) << 8;
3217 				uvl[2].l = (static_cast<int>(l2)) << 8;
3218 				uvl[3].l = (static_cast<int>(l3)) << 8;
3219 			}
3220 			{
3221 				auto &w = *vmwallptr(back_wall_num);
3222 				w.type = type;
3223 				w.state = wall_state{state};
3224 				w.cloak_value = cloak_value;
3225 				auto &uvl = vmsegptr(w.segnum)->unique_segment::sides[w.sidenum].uvls;
3226 				uvl[0].l = (static_cast<int>(l0)) << 8;
3227 				uvl[1].l = (static_cast<int>(l1)) << 8;
3228 				uvl[2].l = (static_cast<int>(l2)) << 8;
3229 				uvl[3].l = (static_cast<int>(l3)) << 8;
3230 			}
3231 			break;
3232 		}
3233 #endif
3234 
3235 		case ND_EVENT_NEW_LEVEL: {
3236 			sbyte new_level, old_level, loaded_level;
3237 
3238 			nd_read_byte (&new_level);
3239 			nd_read_byte (&old_level);
3240 			if (rewrite)
3241 			{
3242 				nd_write_byte (new_level);
3243 				nd_write_byte (old_level);
3244 #if defined(DXX_BUILD_DESCENT_I)
3245 				break;
3246 #elif defined(DXX_BUILD_DESCENT_II)
3247 				load_level_robots(new_level);	// for correct robot info reading (specifically boss flag)
3248 #endif
3249 			}
3250 			else
3251 			{
3252 				if (Newdemo_vcr_state == ND_STATE_PAUSED)
3253 					break;
3254 
3255 				pause_game_world_time p;
3256 				if ((Newdemo_vcr_state == ND_STATE_REWINDING) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD))
3257 					loaded_level = old_level;
3258 				else {
3259 					loaded_level = new_level;
3260 					range_for (auto &i, Players)
3261 					{
3262 						const auto &&objp = vmobjptr(i.objnum);
3263 						auto &player_info = objp->ctype.player_info;
3264 						player_info.powerup_flags &= ~PLAYER_FLAGS_CLOAKED;
3265 						DXX_MAKE_VAR_UNDEFINED(player_info.cloak_time);
3266 					}
3267 				}
3268 				if (loaded_level < Current_mission->last_secret_level || loaded_level > Current_mission->last_level)
3269 				{
3270 					nm_messagebox(menu_title{nullptr}, 1, TXT_OK, "%s\n%s\n%s", TXT_CANT_PLAYBACK, TXT_LEVEL_CANT_LOAD, TXT_DEMO_OLD_CORRUPT);
3271 					Current_mission.reset();
3272 					return -1;
3273 				}
3274 
3275 				LoadLevel(static_cast<int>(loaded_level),1);
3276 				nd_playback_v_cntrlcen_destroyed = 0;
3277 			}
3278 
3279                         if (!rewrite)
3280                                 stop_time();
3281 #if defined(DXX_BUILD_DESCENT_II)
3282 			if (nd_playback_v_juststarted)
3283 			{
3284 				unsigned num_walls;
3285 				nd_read_int (&num_walls);
3286 				Walls.set_count(num_walls);
3287 				if (rewrite)
3288 					nd_write_int (Walls.get_count());
3289 				range_for (const auto &&wp, vmwallptr)
3290 				// restore the walls
3291 				{
3292 					auto &w = *wp;
3293 					nd_read_byte(&w.type);
3294 					uint8_t wf;
3295 					nd_read_byte(&wf);
3296 					w.flags = wall_flags{wf};
3297 					uint8_t ws;
3298 					nd_read_byte(&ws);
3299 					w.state = wall_state{ws};
3300 
3301 					auto &side = vmsegptr(w.segnum)->unique_segment::sides[w.sidenum];
3302 					uint16_t tmap_num1;
3303 					nd_read_short(&tmap_num1);
3304 					side.tmap_num = texture1_value{tmap_num1};
3305 					uint16_t tmap_num2;
3306 					nd_read_short(&tmap_num2);
3307 					side.tmap_num2 = texture2_value{tmap_num2};
3308 
3309 					if (rewrite)
3310 					{
3311 						nd_write_byte (w.type);
3312 						nd_write_byte (underlying_value(w.flags));
3313 						nd_write_byte (underlying_value(w.state));
3314 						nd_write_short(underlying_value(side.tmap_num));
3315 						nd_write_short(underlying_value(side.tmap_num2));
3316 					}
3317 				}
3318 
3319 				nd_playback_v_juststarted=0;
3320 				if (rewrite)
3321 					break;
3322 
3323 				Game_mode = Newdemo_game_mode;
3324 				if (game_mode_hoard())
3325 					init_hoard_data(Vclip);
3326 
3327 				if (game_mode_capture_flag() || game_mode_hoard())
3328 					multi_apply_goal_textures ();
3329 				Game_mode = GM_NORMAL;
3330 			}
3331 
3332 			if (rewrite)
3333 				break;
3334 #endif
3335 
3336 			reset_palette_add();                // get palette back to normal
3337 			full_palette_save();				// initialise for palette_restore()
3338 
3339                         if (!rewrite)
3340                                 start_time();
3341 			break;
3342 		}
3343 
3344 		case ND_EVENT_EOF: {
3345 			done=-1;
3346 			PHYSFS_seek(infile, PHYSFS_tell(infile) - 1);        // get back to the EOF marker
3347 			nd_playback_v_at_eof = 1;
3348 			nd_playback_v_framecount++;
3349 			break;
3350 		}
3351 
3352 		default:
3353 			Int3();
3354 		}
3355 	}
3356 
3357 	// Now set up cockpit and views according to what we read out. Note that the demo itself cannot determinate the right views since it does not use a good portion of the real game code.
3358 	if (nd_playback_v_dead)
3359 	{
3360 		Rear_view = 0;
3361 		if (PlayerCfg.CockpitMode[1] != CM_LETTERBOX)
3362 			select_cockpit(CM_LETTERBOX);
3363 	}
3364 #if defined(DXX_BUILD_DESCENT_II)
3365 	else if (nd_playback_v_guided)
3366 	{
3367 		Rear_view = 0;
3368 		if (PlayerCfg.CockpitMode[1] != CM_FULL_SCREEN || PlayerCfg.CockpitMode[1] != CM_STATUS_BAR)
3369 		{
3370 			select_cockpit((PlayerCfg.CockpitMode[0] == CM_FULL_SCREEN)?CM_FULL_SCREEN:CM_STATUS_BAR);
3371 		}
3372 	}
3373 #endif
3374 	else if (nd_playback_v_rear)
3375 	{
3376 		Rear_view = nd_playback_v_rear;
3377 		if (PlayerCfg.CockpitMode[0] == CM_FULL_COCKPIT)
3378 			select_cockpit(CM_REAR_VIEW);
3379 	}
3380 	else
3381 	{
3382 		Rear_view = 0;
3383 		if (PlayerCfg.CockpitMode[1] != PlayerCfg.CockpitMode[0])
3384 			select_cockpit(PlayerCfg.CockpitMode[0]);
3385 	}
3386 
3387 	if (nd_playback_v_bad_read) {
3388 		nm_messagebox(menu_title{nullptr}, 1, TXT_OK, "%s %s", TXT_DEMO_ERR_READING, TXT_DEMO_OLD_CORRUPT);
3389 		Current_mission.reset();
3390 	}
3391 
3392 	return done;
3393 }
3394 }
3395 
newdemo_goto_beginning()3396 window_event_result newdemo_goto_beginning()
3397 {
3398 	//if (nd_playback_v_framecount == 0)
3399 	//	return;
3400 	PHYSFS_seek(infile, 0);
3401 	Newdemo_vcr_state = ND_STATE_PLAYBACK;
3402 	if (newdemo_read_demo_start(PURPOSE_CHOSE_PLAY))
3403 		newdemo_stop_playback();
3404 	if (newdemo_read_frame_information(0) == -1)
3405 		newdemo_stop_playback();
3406 	if (newdemo_read_frame_information(0) == -1)
3407 		newdemo_stop_playback();
3408 	Newdemo_vcr_state = ND_STATE_PAUSED;
3409 	nd_playback_v_at_eof = 0;
3410 
3411 	// check if we stopped playback
3412 	return Newdemo_state == ND_STATE_NORMAL ? window_event_result::close : window_event_result::handled;
3413 }
3414 
3415 namespace dsx {
newdemo_goto_end(int to_rewrite)3416 window_event_result newdemo_goto_end(int to_rewrite)
3417 {
3418 	auto &Objects = LevelUniqueObjectState.Objects;
3419 	auto &vmobjptr = Objects.vmptr;
3420 	short frame_length=0, byte_count=0, bshort=0;
3421 	sbyte level=0, bbyte=0, c=0, cloaked=0;
3422 	ubyte energy=0, shield=0;
3423 	int loc=0, bint=0;
3424 
3425 	PHYSFSX_fseek(infile, -2, SEEK_END);
3426 	nd_read_byte(&level);
3427 
3428 	if (!to_rewrite)
3429 	{
3430 		if (level < Current_mission->last_secret_level || level > Current_mission->last_level)
3431 		{
3432 			nm_messagebox(menu_title{nullptr}, 1, TXT_OK, "%s\n%s\n%s", TXT_CANT_PLAYBACK, TXT_LEVEL_CANT_LOAD, TXT_DEMO_OLD_CORRUPT);
3433 			Current_mission.reset();
3434 			newdemo_stop_playback();
3435 			return window_event_result::close;
3436 		}
3437 		if (level != Current_level_num)
3438 			LoadLevel(level,1);
3439 	}
3440 	else
3441 #if defined(DXX_BUILD_DESCENT_II)
3442 		if (level != Current_level_num)
3443 #endif
3444 	{
3445 #if defined(DXX_BUILD_DESCENT_II)
3446 		load_level_robots(level);	// for correct robot info reading (specifically boss flag)
3447 #endif
3448 		Current_level_num = level;
3449 	}
3450 
3451 #if defined(DXX_BUILD_DESCENT_I)
3452 	if (shareware)
3453 	{
3454 		if (Newdemo_game_mode & GM_MULTI) {
3455 			PHYSFSX_fseek(infile, -10, SEEK_END);
3456 			nd_read_byte(&cloaked);
3457 			for (playernum_t i = 0; i < MAX_PLAYERS; i++)
3458 			{
3459 				const auto &&objp = vmobjptr(vcplayerptr(i)->objnum);
3460 				auto &player_info = objp->ctype.player_info;
3461 				if ((1 << i) & cloaked)
3462 				{
3463 					player_info.powerup_flags |= PLAYER_FLAGS_CLOAKED;
3464 				}
3465 				player_info.cloak_time = GameTime64 - (CLOAK_TIME_MAX / 2);
3466 			}
3467 		}
3468 
3469 		if (to_rewrite)
3470 			return window_event_result::handled;
3471 
3472 		PHYSFSX_fseek(infile, -12, SEEK_END);
3473 		nd_read_short(&frame_length);
3474 	}
3475 	else
3476 #endif
3477 	{
3478 	PHYSFSX_fseek(infile, -4, SEEK_END);
3479 	nd_read_short(&byte_count);
3480 	PHYSFSX_fseek(infile, -2 - byte_count, SEEK_CUR);
3481 
3482 	nd_read_short(&frame_length);
3483 	loc = PHYSFS_tell(infile);
3484 	if (Newdemo_game_mode & GM_MULTI)
3485 	{
3486 		nd_read_byte(&cloaked);
3487 		for (unsigned i = 0; i < MAX_PLAYERS; ++i)
3488 			if (cloaked & (1 << i))
3489 				{
3490 					const auto &&objp = vmobjptr(vcplayerptr(i)->objnum);
3491 					objp->ctype.player_info.powerup_flags |= PLAYER_FLAGS_CLOAKED;
3492 				}
3493 	}
3494 	else
3495 		nd_read_byte(&bbyte);
3496 	nd_read_byte(&bbyte);
3497 	nd_read_short(&bshort);
3498 	nd_read_int(&bint);
3499 
3500 	nd_read_byte(&energy);
3501 	nd_read_byte(&shield);
3502 	auto &player_info = get_local_plrobj().ctype.player_info;
3503 	player_info.energy = i2f(energy);
3504 	get_local_plrobj().shields = i2f(shield);
3505 	int recorded_player_flags;
3506 	nd_read_int(&recorded_player_flags);
3507 	player_info.powerup_flags = player_flags(recorded_player_flags);
3508 	if (player_info.powerup_flags & PLAYER_FLAGS_CLOAKED) {
3509 		player_info.cloak_time = GameTime64 - (CLOAK_TIME_MAX / 2);
3510 	}
3511 	if (player_info.powerup_flags & PLAYER_FLAGS_INVULNERABLE)
3512 		player_info.invulnerable_time = GameTime64 - (INVULNERABLE_TIME_MAX / 2);
3513 	{
3514 		int8_t v;
3515 		nd_read_byte(&v);
3516 		player_info.Primary_weapon = static_cast<primary_weapon_index_t>(v);
3517 	}
3518 	{
3519 		int8_t v;
3520 		nd_read_byte(&v);
3521 		player_info.Secondary_weapon = static_cast<secondary_weapon_index_t>(v);
3522 	}
3523 	for (int i = 0; i < MAX_PRIMARY_WEAPONS; i++)
3524 	{
3525 		short s;
3526 		nd_read_short(&s);
3527 		if (i == primary_weapon_index_t::VULCAN_INDEX)
3528 			player_info.vulcan_ammo = s;
3529 	}
3530 	range_for (auto &i, player_info.secondary_ammo)
3531 	{
3532 		uint16_t u;
3533 		nd_read_short(&u);
3534 		i = u;
3535 	}
3536 	{
3537 		uint8_t i;
3538 	nd_read_byte(&i);
3539 		player_info.laser_level = laser_level{i};
3540 	}
3541 
3542 	if (Newdemo_game_mode & GM_MULTI) {
3543 		nd_read_byte(&c);
3544 		N_players = static_cast<int>(c);
3545 		// see newdemo_read_start_demo for explanation of
3546 		// why this is commented out
3547 		//		nd_read_byte(&N_players);
3548 		range_for (auto &i, partial_range(Players, N_players)) {
3549 			nd_read_string(i.callsign.buffer());
3550 			nd_read_byte(&i.connected);
3551 			auto &pl_info = vmobjptr(i.objnum)->ctype.player_info;
3552 			if (Newdemo_game_mode & GM_MULTI_COOP) {
3553 				nd_read_int(&pl_info.mission.score);
3554 			} else {
3555 				nd_read_short(&pl_info.net_killed_total);
3556 				nd_read_short(&pl_info.net_kills_total);
3557 			}
3558 		}
3559 	} else {
3560 		nd_read_int(&player_info.mission.score);
3561 	}
3562 
3563 	if (to_rewrite)
3564 		return window_event_result::handled;
3565 
3566 	PHYSFSX_fseek(infile, loc, SEEK_SET);
3567 	}
3568 	PHYSFSX_fseek(infile, -frame_length, SEEK_CUR);
3569 	nd_read_int(&nd_playback_v_framecount);            // get the frame count
3570 	nd_playback_v_framecount--;
3571 	PHYSFSX_fseek(infile, 4, SEEK_CUR);
3572 	Newdemo_vcr_state = ND_STATE_PLAYBACK;
3573 	newdemo_read_frame_information(0); // then the frame information
3574 	Newdemo_vcr_state = ND_STATE_PAUSED;
3575 	return window_event_result::handled;
3576 }
3577 }
3578 
newdemo_back_frames(int frames)3579 static window_event_result newdemo_back_frames(int frames)
3580 {
3581 	short last_frame_length;
3582 	for (int i = 0; i < frames; i++)
3583 	{
3584 		PHYSFS_seek(infile, PHYSFS_tell(infile) - 10);
3585 		nd_read_short(&last_frame_length);
3586 		PHYSFS_seek(infile, PHYSFS_tell(infile) + 8 - last_frame_length);
3587 
3588 		if (!nd_playback_v_at_eof && newdemo_read_frame_information(0) == -1) {
3589 			newdemo_stop_playback();
3590 			return window_event_result::close;
3591 		}
3592 		if (nd_playback_v_at_eof)
3593 			nd_playback_v_at_eof = 0;
3594 
3595 		PHYSFS_seek(infile, PHYSFS_tell(infile) - 10);
3596 		nd_read_short(&last_frame_length);
3597 		PHYSFS_seek(infile, PHYSFS_tell(infile) + 8 - last_frame_length);
3598 	}
3599 
3600 	return window_event_result::handled;
3601 }
3602 
3603 /*
3604  *  routine to interpolate the viewer position.  the current position is
3605  *  stored in the Viewer object.  Save this position, and read the next
3606  *  frame to get all objects read in.  Calculate the delta playback and
3607  *  the delta recording frame times between the two frames, then intepolate
3608  *  the viewers position accordingly.  nd_recorded_time is the time that it
3609  *  took the recording to render the frame that we are currently looking
3610  *  at.
3611 */
3612 
interpolate_frame(fix d_play,fix d_recorded)3613 static window_event_result interpolate_frame(fix d_play, fix d_recorded)
3614 {
3615 	auto &Objects = LevelUniqueObjectState.Objects;
3616 	auto &vmobjptr = Objects.vmptr;
3617 	fix factor;
3618 	static fix InterpolStep = fl2f(.01);
3619 
3620 	if (nd_playback_v_framecount < 1)
3621 		return window_event_result::ignored;
3622 
3623 	factor = fixdiv(d_play, d_recorded);
3624 	if (factor > F1_0)
3625 		factor = F1_0;
3626 
3627 	const auto num_cur_objs = Objects.get_count();
3628 	std::vector<object> cur_objs(Objects.begin(), Objects.begin() + num_cur_objs);
3629 
3630 	Newdemo_vcr_state = ND_STATE_PAUSED;
3631 	if (newdemo_read_frame_information(0) == -1) {
3632 		newdemo_stop_playback();
3633 		return window_event_result::close;
3634 	}
3635 
3636 	InterpolStep -= FrameTime;
3637 
3638 	// This interpolating looks just more crappy on high FPS, so let's not even waste performance on it.
3639 	if (InterpolStep <= 0)
3640 	{
3641 		range_for (auto &i, partial_range(cur_objs, num_cur_objs)) {
3642 			range_for (const auto &&objp, vmobjptr)
3643 			{
3644 				if (i.signature == objp->signature) {
3645 					sbyte render_type = i.render_type;
3646 					fix delta_x, delta_y, delta_z;
3647 
3648 					//  Extract the angles from the object orientation matrix.
3649 					//  Some of this code taken from ai_turn_towards_vector
3650 					//  Don't do the interpolation on certain render types which don't use an orientation matrix
3651 
3652 					if (!((render_type == RT_LASER) || (render_type == RT_FIREBALL) || (render_type == RT_POWERUP))) {
3653 						vms_vector  fvec1, fvec2, rvec1, rvec2;
3654 
3655 						fvec1 = i.orient.fvec;
3656 						vm_vec_scale(fvec1, F1_0-factor);
3657 						fvec2 = objp->orient.fvec;
3658 						vm_vec_scale(fvec2, factor);
3659 						vm_vec_add2(fvec1, fvec2);
3660 						const auto mag1 = vm_vec_normalize_quick(fvec1);
3661 						if (mag1 > F1_0/256) {
3662 							rvec1 = i.orient.rvec;
3663 							vm_vec_scale(rvec1, F1_0-factor);
3664 							rvec2 = objp->orient.rvec;
3665 							vm_vec_scale(rvec2, factor);
3666 							vm_vec_add2(rvec1, rvec2);
3667 							vm_vec_normalize_quick(rvec1); // Note: Doesn't matter if this is null, if null, vm_vector_2_matrix will just use fvec1
3668 							vm_vector_2_matrix(i.orient, fvec1, nullptr, &rvec1);
3669 						}
3670 					}
3671 
3672 					// Interpolate the object position.  This is just straight linear
3673 					// interpolation.
3674 
3675 					delta_x = objp->pos.x - i.pos.x;
3676 					delta_y = objp->pos.y - i.pos.y;
3677 					delta_z = objp->pos.z - i.pos.z;
3678 
3679 					delta_x = fixmul(delta_x, factor);
3680 					delta_y = fixmul(delta_y, factor);
3681 					delta_z = fixmul(delta_z, factor);
3682 
3683 					i.pos.x += delta_x;
3684 					i.pos.y += delta_y;
3685 					i.pos.z += delta_z;
3686 				}
3687 			}
3688 		}
3689 		InterpolStep = fl2f(.01);
3690 	}
3691 
3692 	// get back to original position in the demo file.  Reread the current
3693 	// frame information again to reset all of the object stuff not covered
3694 	// with Highest_object_index and the object array (previously rendered
3695 	// objects, etc....)
3696 
3697 	auto result = newdemo_back_frames(1);
3698 	result = std::max(newdemo_back_frames(1), result);
3699 	if (newdemo_read_frame_information(0) == -1)
3700 	{
3701 		newdemo_stop_playback();
3702 		result =  window_event_result::close;
3703 	}
3704 	Newdemo_vcr_state = ND_STATE_PLAYBACK;
3705 
3706 	std::copy(cur_objs.begin(), cur_objs.begin() + num_cur_objs, Objects.begin());
3707 	Objects.set_count(num_cur_objs);
3708 
3709 	return result;
3710 }
3711 
newdemo_playback_one_frame()3712 window_event_result newdemo_playback_one_frame()
3713 {
3714 	auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
3715 	auto &Objects = LevelUniqueObjectState.Objects;
3716 	auto &vmobjptr = Objects.vmptr;
3717 	int frames_back;
3718 	static fix base_interpol_time = 0;
3719 	static fix d_recorded = 0;
3720 	auto result = window_event_result::handled;
3721 
3722 	range_for (auto &i, Players)
3723 	{
3724 		const auto &&objp = vmobjptr(i.objnum);
3725 		auto &player_info = objp->ctype.player_info;
3726 		if (player_info.powerup_flags & PLAYER_FLAGS_CLOAKED)
3727 			player_info.cloak_time = GameTime64 - (CLOAK_TIME_MAX / 2);
3728 		if (player_info.powerup_flags & PLAYER_FLAGS_INVULNERABLE)
3729 			player_info.invulnerable_time = GameTime64 - (INVULNERABLE_TIME_MAX / 2);
3730 	}
3731 
3732 	if (Newdemo_vcr_state == ND_STATE_PAUSED)       // render a frame or not
3733 		return window_event_result::ignored;
3734 
3735 	LevelUniqueControlCenterState.Control_center_destroyed = 0;
3736 	LevelUniqueControlCenterState.Countdown_seconds_left = -1;
3737 	PALETTE_FLASH_SET(0,0,0);       //clear flash
3738 
3739 	if ((Newdemo_vcr_state == ND_STATE_REWINDING) || (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD))
3740 	{
3741 		const int level = Current_level_num;
3742 		if (nd_playback_v_framecount == 0)
3743 			return window_event_result::ignored;
3744 		else if ((Newdemo_vcr_state == ND_STATE_REWINDING) && (nd_playback_v_framecount < 10)) {
3745 			return newdemo_goto_beginning();
3746 		}
3747 		if (Newdemo_vcr_state == ND_STATE_REWINDING)
3748 			frames_back = 10;
3749 		else
3750 			frames_back = 1;
3751 		if (nd_playback_v_at_eof) {
3752 			PHYSFS_seek(infile, PHYSFS_tell(infile) + (shareware ? -2 : +11));
3753 		}
3754 		result = newdemo_back_frames(frames_back);
3755 
3756 		if (level != Current_level_num)
3757 			newdemo_pop_ctrlcen_triggers();
3758 
3759 		if (Newdemo_vcr_state == ND_STATE_ONEFRAMEBACKWARD) {
3760 			if (level != Current_level_num)
3761 				result = std::max(newdemo_back_frames(1), result);
3762 			Newdemo_vcr_state = ND_STATE_PAUSED;
3763 		}
3764 	}
3765 	else if (Newdemo_vcr_state == ND_STATE_FASTFORWARD) {
3766 		if (!nd_playback_v_at_eof)
3767 		{
3768 			range_for (const auto i, xrange(10u))
3769 			{
3770 				(void)i;
3771 				if (newdemo_read_frame_information(0) == -1)
3772 				{
3773 					if (nd_playback_v_at_eof)
3774 						Newdemo_vcr_state = ND_STATE_PAUSED;
3775 					else
3776 					{
3777 						newdemo_stop_playback();
3778 						result = window_event_result::close;
3779 					}
3780 					break;
3781 				}
3782 			}
3783 		}
3784 		else
3785 			Newdemo_vcr_state = ND_STATE_PAUSED;
3786 	}
3787 	else if (Newdemo_vcr_state == ND_STATE_ONEFRAMEFORWARD) {
3788 		if (!nd_playback_v_at_eof) {
3789 			const int level = Current_level_num;
3790 			if (newdemo_read_frame_information(0) == -1) {
3791 				if (!nd_playback_v_at_eof)
3792 				{
3793 					newdemo_stop_playback();
3794 					result = window_event_result::close;
3795 				}
3796 			}
3797 			if (level != Current_level_num) {
3798 				if (newdemo_read_frame_information(0) == -1) {
3799 					if (!nd_playback_v_at_eof)
3800 					{
3801 						newdemo_stop_playback();
3802 						result = window_event_result::close;
3803 					}
3804 				}
3805 			}
3806 			Newdemo_vcr_state = ND_STATE_PAUSED;
3807 		} else
3808 			Newdemo_vcr_state = ND_STATE_PAUSED;
3809 	}
3810 	else {
3811 
3812 		//  First, uptate the total playback time to date.  Then we check to see
3813 		//  if we need to change the playback style to interpolate frames or
3814 		//  skip frames based on where the playback time is relative to the
3815 		//  recorded time.
3816 
3817 		if (nd_playback_v_framecount <= 0)
3818 			nd_playback_total = nd_recorded_total;      // baseline total playback time
3819 		else
3820 			nd_playback_total += FrameTime;
3821 
3822 		if (nd_playback_v_style == NORMAL_PLAYBACK)
3823 		{
3824 			if (nd_playback_total > nd_recorded_total)
3825 				nd_playback_v_style = SKIP_PLAYBACK;
3826 
3827 			if (nd_recorded_total > 0 && nd_recorded_time > 0)
3828 			{
3829 				nd_playback_v_style = INTERPOLATE_PLAYBACK;
3830 				nd_playback_total = nd_recorded_total + FrameTime; // baseline playback time
3831 				base_interpol_time = nd_recorded_total;
3832 				d_recorded = nd_recorded_time; // baseline delta recorded
3833 			}
3834 		}
3835 
3836 
3837 		if ((nd_playback_v_style == INTERPOLATE_PLAYBACK) && Newdemo_do_interpolate) {
3838 			fix d_play = 0;
3839 
3840 			if (nd_recorded_total - nd_playback_total < FrameTime) {
3841 				d_recorded = nd_recorded_total - nd_playback_total;
3842 
3843 				while (nd_recorded_total - nd_playback_total < FrameTime) {
3844 					unsigned num_objs;
3845 
3846 					num_objs = Highest_object_index;
3847 					std::vector<object> cur_objs(Objects.begin(), Objects.begin() + num_objs + 1);
3848 
3849 					const int level = Current_level_num;
3850 					if (newdemo_read_frame_information(0) == -1) {
3851 						newdemo_stop_playback();
3852 						return window_event_result::close;
3853 					}
3854 					if (level != Current_level_num) {
3855 						if (newdemo_read_frame_information(0) == -1)
3856 						{
3857 							newdemo_stop_playback();
3858 							result = window_event_result::close;
3859 						}
3860 						break;
3861 					}
3862 
3863 					//  for each new object in the frame just read in, determine if there is
3864 					//  a corresponding object that we have been interpolating.  If so, then
3865 					//  copy that interpolated object to the new Objects array so that the
3866 					//  interpolated position and orientation can be preserved.
3867 
3868 					range_for (auto &i, partial_const_range(cur_objs, 1 + num_objs)) {
3869 						range_for (const auto &&objp, vmobjptr)
3870 						{
3871 							if (i.signature == objp->signature) {
3872 								objp->orient = i.orient;
3873 								objp->pos = i.pos;
3874 								break;
3875 							}
3876 						}
3877 					}
3878 					d_recorded += nd_recorded_time;
3879 					base_interpol_time = nd_playback_total - FrameTime;
3880 				}
3881 			}
3882 
3883 			d_play = nd_playback_total - base_interpol_time;
3884 			return std::max(interpolate_frame(d_play, d_recorded), result);
3885 		}
3886 		else {
3887 			if (newdemo_read_frame_information(0) == -1) {
3888 				newdemo_stop_playback();
3889 				return window_event_result::close;
3890 			}
3891 			if (nd_playback_v_style == SKIP_PLAYBACK) {
3892 				while (nd_playback_total > nd_recorded_total) {
3893 					if (newdemo_read_frame_information(0) == -1) {
3894 						newdemo_stop_playback();
3895 						return window_event_result::close;
3896 					}
3897 				}
3898 			}
3899 		}
3900 	}
3901 
3902 	return result;
3903 }
3904 
newdemo_start_recording()3905 void newdemo_start_recording()
3906 {
3907 	Newdemo_num_written = 0;
3908 	nd_record_v_no_space=0;
3909 	Newdemo_state = ND_STATE_RECORDING;
3910 
3911 	PHYSFS_mkdir(DEMO_DIR); //always try making directory - could only exist in read-only path
3912 
3913 	auto &&[o, physfserr] = PHYSFSX_openWriteBuffered(DEMO_FILENAME);
3914 	outfile = std::move(o);
3915 	if (!outfile)
3916 	{
3917 		Newdemo_state = ND_STATE_NORMAL;
3918 		struct error_writing_demo :
3919 			std::array<char, 96>,
3920 			passive_messagebox
3921 		{
3922 			error_writing_demo(const char *errstr) :
3923 				passive_messagebox(menu_title{nullptr}, menu_subtitle{prepare_subtitle(*this, errstr)}, "Cancel recording", grd_curscreen->sc_canvas)
3924 			{
3925 			}
3926 			static const char *prepare_subtitle(std::array<char, 96> &b, const char *errstr)
3927 			{
3928 				auto r = b.data();
3929 				std::snprintf(r, b.size(), "Failed to open demo temporary file\n" DEMO_FILENAME "\n\n%s", errstr);
3930 				return r;
3931 			}
3932 		};
3933 		const auto errstr = PHYSFS_getErrorByCode(physfserr);
3934 		run_blocking_newmenu<error_writing_demo>(errstr);
3935 	}
3936 	else
3937 		newdemo_record_start_demo();
3938 }
3939 
newdemo_write_end()3940 static void newdemo_write_end()
3941 {
3942 	auto &Objects = LevelUniqueObjectState.Objects;
3943 	auto &vcobjptr = Objects.vcptr;
3944 	auto &vmobjptr = Objects.vmptr;
3945 	sbyte cloaked = 0;
3946 	unsigned short byte_count = 0;
3947 	nd_write_byte(ND_EVENT_EOF);
3948 	nd_write_short(nd_record_v_framebytes_written - 1);
3949 	if (Game_mode & GM_MULTI) {
3950 		for (unsigned i = 0; i < N_players; ++i)
3951 		{
3952 			const auto &&objp = vmobjptr(vcplayerptr(i)->objnum);
3953 			if (objp->ctype.player_info.powerup_flags & PLAYER_FLAGS_CLOAKED)
3954 				cloaked |= (1 << i);
3955 		}
3956 		nd_write_byte(cloaked);
3957 		nd_write_byte(ND_EVENT_EOF);
3958 	} else {
3959 		nd_write_short(ND_EVENT_EOF);
3960 	}
3961 	nd_write_short(ND_EVENT_EOF);
3962 	nd_write_int(ND_EVENT_EOF);
3963 
3964 	if (!shareware)
3965 	{
3966 	byte_count += 10;       // from nd_record_v_framebytes_written
3967 
3968 	auto &player_info = get_local_plrobj().ctype.player_info;
3969 	nd_write_byte(static_cast<int8_t>(f2ir(player_info.energy)));
3970 	nd_write_byte(static_cast<int8_t>(f2ir(get_local_plrobj().shields)));
3971 	nd_write_int(player_info.powerup_flags.get_player_flags());        // be sure players flags are set
3972 	nd_write_byte(static_cast<int8_t>(static_cast<primary_weapon_index_t>(player_info.Primary_weapon)));
3973 	nd_write_byte(static_cast<int8_t>(static_cast<secondary_weapon_index_t>(player_info.Secondary_weapon)));
3974 	byte_count += 8;
3975 
3976 	for (int i = 0; i < MAX_PRIMARY_WEAPONS; i++)
3977 		nd_write_short(i == primary_weapon_index_t::VULCAN_INDEX ? player_info.vulcan_ammo : 0);
3978 
3979 	range_for (auto &i, player_info.secondary_ammo)
3980 		nd_write_short(i);
3981 	byte_count += (sizeof(short) * (MAX_PRIMARY_WEAPONS + MAX_SECONDARY_WEAPONS));
3982 
3983 	nd_write_byte(static_cast<uint8_t>(player_info.laser_level));
3984 	byte_count++;
3985 
3986 	if (Game_mode & GM_MULTI) {
3987 		nd_write_byte(static_cast<int8_t>(N_players));
3988 		byte_count++;
3989 		range_for (auto &i, partial_const_range(Players, N_players)) {
3990 			nd_write_string(static_cast<const char *>(i.callsign));
3991 			byte_count += (strlen(static_cast<const char *>(i.callsign)) + 2);
3992 			nd_write_byte(i.connected);
3993 			auto &pl_info = vcobjptr(i.objnum)->ctype.player_info;
3994 			if (Game_mode & GM_MULTI_COOP) {
3995 				nd_write_int(pl_info.mission.score);
3996 				byte_count += 5;
3997 			} else {
3998 				nd_write_short(pl_info.net_killed_total);
3999 				nd_write_short(pl_info.net_kills_total);
4000 				byte_count += 5;
4001 			}
4002 		}
4003 	} else {
4004 		nd_write_int(player_info.mission.score);
4005 		byte_count += 4;
4006 	}
4007 	nd_write_short(byte_count);
4008 	}
4009 
4010 	nd_write_byte(Current_level_num);
4011 	nd_write_byte(ND_EVENT_EOF);
4012 }
4013 
guess_demo_name(ntstring<PATH_MAX-16> & filename)4014 static bool guess_demo_name(ntstring<PATH_MAX - 16> &filename)
4015 {
4016 	filename[0] = 0;
4017 	const auto &n = CGameArg.SysRecordDemoNameTemplate;
4018 	if (n.empty())
4019 		return false;
4020 	auto p = n.c_str();
4021 	if (!strcmp(p, "."))
4022 		p = "%Y%m%d.%H%M%S-$p-$m";
4023 	std::size_t i = 0;
4024 	time_t t = 0;
4025 	tm *ptm = nullptr;
4026 	for (;; ++p)
4027 	{
4028 		if (*p == '%')
4029 		{
4030 			if (!p[1])
4031 				/* Trailing bare % is ill-formed.  Ignore entire
4032 				 * template.
4033 				 */
4034 				return false;
4035 			/* Time conversions */
4036 			if (unlikely(!t))
4037 				t = time(nullptr);
4038 			if (unlikely(t == -1 || !(ptm = gmtime(&t))))
4039 				continue;
4040 			char sbuf[4];
4041 			sbuf[0] = '%';
4042 			sbuf[1] = *++p;
4043 #ifndef _WIN32
4044 			/* Not supported on Windows */
4045 			if (sbuf[1] == 'O' || sbuf[1] == 'E')
4046 			{
4047 				sbuf[2] = *++p;
4048 				sbuf[3] = 0;
4049 			}
4050 			else
4051 #endif
4052 			{
4053 				sbuf[2] = 0;
4054 			}
4055 			filename[i] = 0;
4056 #ifdef __GNUC__
4057 #pragma GCC diagnostic push
4058 #pragma GCC diagnostic ignored "-Wformat-nonliteral"
4059 #endif
4060 			const auto a = strftime(&filename[i], sizeof(filename) - i, sbuf, ptm);
4061 #ifdef __GNUC__
4062 #pragma GCC diagnostic pop
4063 #endif
4064 			if (a >= sizeof(filename) - i)
4065 				return false;
4066 			i += a;
4067 			continue;
4068 		}
4069 		if (*p == '$')
4070 		{
4071 			/* Variable conversions */
4072 			const char *insert;
4073 			switch(*++p)
4074 			{
4075 				case 'm':	/* mission */
4076 					insert = &*Current_mission->filename;
4077 					break;
4078 				case 'p':	/* pilot */
4079 					insert = get_local_player().callsign;
4080 					break;
4081 				default:
4082 					return false;
4083 			}
4084 			i += filename.copy_if(i, insert);
4085 			continue;
4086 		}
4087 		filename[i++] = *p;
4088 		if (!*p)
4089 			break;
4090 		if (i >= sizeof(filename) - 1)
4091 			return false;
4092 	}
4093 	return filename[0];
4094 }
4095 
4096 constexpr char demoname_allowed_chars[] = "azAZ09__--";
4097 #define DEMO_FORMAT_STRING(S)	DEMO_DIR S "." DEMO_EXT
newdemo_stop_recording()4098 void newdemo_stop_recording()
4099 {
4100 	int exit;
4101 	static sbyte tmpcnt = 0;
4102 	ntstring<PATH_MAX - 16> filename;
4103 
4104 	exit = 0;
4105 
4106 	if (!nd_record_v_no_space)
4107 	{
4108 		newdemo_record_oneframeevent_update(0);
4109 		newdemo_write_end();
4110 	}
4111 
4112 	outfile.reset();
4113 	Newdemo_state = ND_STATE_NORMAL;
4114 	gr_palette_load( gr_palette );
4115 try_again:
4116 	;
4117 
4118 	if (guess_demo_name(filename))
4119 	{
4120 	}
4121 	else if (!nd_record_v_no_space) {
4122 		std::array<newmenu_item, 1> m{{
4123 			newmenu_item::nm_item_input(filename, demoname_allowed_chars),
4124 		}};
4125 		exit = newmenu_do2(menu_title{nullptr}, menu_subtitle{TXT_SAVE_DEMO_AS}, m, unused_newmenu_subfunction, unused_newmenu_userdata);
4126 	} else if (nd_record_v_no_space == 2) {
4127 		std::array<newmenu_item, 2> m{{
4128 			newmenu_item::nm_item_text{TXT_DEMO_SAVE_NOSPACE},
4129 			newmenu_item::nm_item_input(filename, demoname_allowed_chars),
4130 		}};
4131 		exit = newmenu_do2(menu_title{nullptr}, menu_subtitle{nullptr}, m, unused_newmenu_subfunction, unused_newmenu_userdata);
4132 	}
4133 
4134 	if (exit == -2) {                   // got bumped out from network menu
4135 		char save_file[PATH_MAX];
4136 
4137 		if (filename[0] != '\0') {
4138 			snprintf(save_file, sizeof(save_file), DEMO_FORMAT_STRING("%s"), filename.data());
4139 		} else
4140 			snprintf(save_file, sizeof(save_file), DEMO_FORMAT_STRING("tmp%d"), tmpcnt++);
4141 		remove(save_file);
4142 		PHYSFSX_rename(DEMO_FILENAME, save_file);
4143 		return;
4144 	}
4145 	if (exit == -1) {               // pressed ESC
4146 		PHYSFS_delete(DEMO_FILENAME);   // might as well remove the file
4147 		return;                     // return without doing anything
4148 	}
4149 
4150 	if (filename[0]==0) //null string
4151 		goto try_again;
4152 
4153 	//check to make sure name is ok
4154 	range_for (const unsigned c, filename)
4155 	{
4156 		if (!c)
4157 			break;
4158 		if (!isalnum(c) && c != '_' && c != '-' && c != '.')
4159 		{
4160 			struct error_invalid_demo_filename : passive_messagebox
4161 			{
4162 				error_invalid_demo_filename() :
4163 					passive_messagebox(menu_title{nullptr}, menu_subtitle{TXT_DEMO_USE_LETTERS}, TXT_CONTINUE, grd_curscreen->sc_canvas)
4164 					{
4165 					}
4166 			};
4167 			run_blocking_newmenu<error_invalid_demo_filename>();
4168 			goto try_again;
4169 		}
4170 	}
4171 
4172 	char fullname[PATH_MAX];
4173 	snprintf(fullname, sizeof(fullname), DEMO_FORMAT_STRING("%s"), filename.data());
4174 	PHYSFS_delete(fullname);
4175 	PHYSFSX_rename(DEMO_FILENAME, fullname);
4176 }
4177 
4178 //returns the number of demo files on the disk
newdemo_count_demos()4179 int newdemo_count_demos()
4180 {
4181 	int NumFiles=0;
4182 
4183 	range_for (const auto i, PHYSFSX_findFiles(DEMO_DIR, demo_file_extensions))
4184 	{
4185 		(void)i;
4186 		NumFiles++;
4187 	}
4188 	return NumFiles;
4189 }
4190 
4191 namespace dsx {
4192 
newdemo_start_playback(const char * filename)4193 void newdemo_start_playback(const char * filename)
4194 {
4195 	auto &Objects = LevelUniqueObjectState.Objects;
4196 	enum purpose_type rnd_demo = PURPOSE_CHOSE_PLAY;
4197 	char filename2[PATH_MAX+FILENAME_LEN] = DEMO_DIR;
4198 
4199 	change_playernum_to(0);
4200 
4201 	if (filename)
4202 		strcat(filename2, filename);
4203 	else
4204 	{
4205 		// Randomly pick a filename
4206 		int NumFiles = 0, RandFileNum;
4207 
4208 		rnd_demo = PURPOSE_RANDOM_PLAY;
4209 		NumFiles = newdemo_count_demos();
4210 
4211 		if ( NumFiles == 0 ) {
4212 			CGameArg.SysAutoDemo = false;
4213 			return;     // No files found!
4214 		}
4215 		RandFileNum = d_rand() % NumFiles;
4216 		NumFiles = 0;
4217 
4218 		range_for (const auto i, PHYSFSX_findFiles(DEMO_DIR, demo_file_extensions))
4219 		{
4220 			if (NumFiles == RandFileNum)
4221 			{
4222 				strcat(filename2, i);
4223 				break;
4224 			}
4225 			NumFiles++;
4226 		}
4227 		if (NumFiles > RandFileNum)
4228 		{
4229 			CGameArg.SysAutoDemo = false;
4230 			return;
4231 		}
4232 	}
4233 
4234 	infile = PHYSFSX_openReadBuffered(filename2).first;
4235 
4236 	if (!infile) {
4237 		return;
4238 	}
4239 
4240 	nd_playback_v_bad_read = 0;
4241 	change_playernum_to(0);                 // force playernum to 0
4242 	auto &plr = get_local_player();
4243 	nd_playback_v_save_callsign = plr.callsign;
4244 	plr.lives = 0;
4245 	Viewer = ConsoleObject = &Objects.front();   // play properly as if console player
4246 
4247 	if (newdemo_read_demo_start(rnd_demo)) {
4248 		infile.reset();
4249 		return;
4250 	}
4251 
4252 	Game_mode = GM_NORMAL;
4253 	Newdemo_state = ND_STATE_PLAYBACK;
4254 	Newdemo_vcr_state = ND_STATE_PLAYBACK;
4255 	nd_playback_v_demosize = PHYSFS_fileLength(infile);
4256 	nd_playback_v_bad_read = 0;
4257 	nd_playback_v_at_eof = 0;
4258 	nd_playback_v_framecount = 0;
4259 	nd_playback_v_style = NORMAL_PLAYBACK;
4260 #if defined(DXX_BUILD_DESCENT_II)
4261 	init_seismic_disturbances();
4262 	//turn off 3d views on cockpit
4263 	PlayerCfg.Cockpit3DView = {};
4264 	DemoDoLeft = DemoDoRight = 0;
4265 	nd_playback_v_guided = 0;
4266 #endif
4267 	nd_playback_v_dead = nd_playback_v_rear = 0;
4268 	HUD_clear_messages();
4269 	if (!Game_wind)
4270 		hide_menus();
4271 	auto result = newdemo_playback_one_frame();       // this one loads new level
4272 	result = std::max(newdemo_playback_one_frame(), result);       // get all of the objects to renderb game
4273 
4274 	if (result == window_event_result::close)
4275 		return;	// whoops, there was an error reading the first two frames! Abort!
4276 
4277 	if (!Game_wind)
4278 		Game_wind = game_setup();							// create game environment
4279 }
4280 
4281 }
4282 
4283 namespace dsx {
newdemo_stop_playback()4284 void newdemo_stop_playback()
4285 {
4286 	infile.reset();
4287 	Newdemo_state = ND_STATE_NORMAL;
4288 	change_playernum_to(0);             //this is reality
4289 	get_local_player().callsign = nd_playback_v_save_callsign;
4290 	Rear_view=0;
4291 	nd_playback_v_dead = nd_playback_v_rear = 0;
4292 #if defined(DXX_BUILD_DESCENT_II)
4293 	nd_playback_v_guided = 0;
4294 #endif
4295 	Newdemo_game_mode = Game_mode = {};
4296 	// Required for the editor
4297 	obj_relink_all();
4298 }
4299 }
4300 
4301 
newdemo_swap_endian(const char * filename)4302 int newdemo_swap_endian(const char *filename)
4303 {
4304 	char inpath[PATH_MAX+FILENAME_LEN] = DEMO_DIR;
4305 	int complete = 0;
4306 
4307 	if (filename)
4308 		strcat(inpath, filename);
4309 	else
4310 		return 0;
4311 
4312 	infile = PHYSFSX_openReadBuffered(inpath).first;
4313 	if (!infile)
4314 		goto read_error;
4315 
4316 	nd_playback_v_demosize = PHYSFS_fileLength(infile);	// should be exactly the same size
4317 	outfile = PHYSFSX_openWriteBuffered(DEMO_FILENAME).first;
4318 	if (!outfile)
4319 	{
4320 		infile.reset();
4321 		goto read_error;
4322 	}
4323 
4324 	Newdemo_num_written = 0;
4325 	nd_playback_v_bad_read = 0;
4326 	swap_endian = 1;
4327 	nd_playback_v_at_eof = 0;
4328 	Newdemo_state = ND_STATE_NORMAL;	// not doing anything special really
4329 
4330 	if (newdemo_read_demo_start(PURPOSE_REWRITE)) {
4331 		infile.reset();
4332 		outfile.reset();
4333 		swap_endian = 0;
4334 		return 0;
4335 	}
4336 
4337 	while (newdemo_read_frame_information(1) == 1) {}	// rewrite all frames
4338 
4339 	newdemo_goto_end(1);	// get end of demo data
4340 	newdemo_write_end();	// and write it
4341 
4342 	swap_endian = 0;
4343 	complete = nd_playback_v_demosize == Newdemo_num_written;
4344 	infile.reset();
4345 	outfile.reset();
4346 
4347 	if (complete)
4348 	{
4349 		char bakpath[PATH_MAX+FILENAME_LEN];
4350 
4351 		change_filename_extension(bakpath, inpath, DEMO_BACKUP_EXT);
4352 		PHYSFSX_rename(inpath, bakpath);
4353 		PHYSFSX_rename(DEMO_FILENAME, inpath);
4354 	}
4355 	else
4356 		PHYSFS_delete(DEMO_FILENAME);	// clean up the mess
4357 
4358 read_error:
4359 	{
4360 		nm_messagebox(menu_title{nullptr}, 1, TXT_OK, complete ? "Demo %s converted%s" : "Error converting demo\n%s\n%s", filename,
4361 					  complete ? "" : (nd_playback_v_at_eof ? TXT_DEMO_CORRUPT : PHYSFS_getLastError()));
4362 	}
4363 
4364 	return nd_playback_v_at_eof;
4365 }
4366 
4367 #if defined(DXX_BUILD_DESCENT_II)
nd_render_extras(ubyte which,const object & obj)4368 static void nd_render_extras (ubyte which,const object &obj)
4369 {
4370 	ubyte w=which>>4;
4371 	ubyte type=which&15;
4372 
4373 	if (which==255)
4374 	{
4375 		Int3(); // how'd we get here?
4376 		return;
4377 	}
4378 
4379 	if (w)
4380 	{
4381 		DemoRightExtra = obj;  DemoDoRight=type;
4382 	}
4383 	else
4384 	{
4385 		DemoLeftExtra = obj; DemoDoLeft=type;
4386 	}
4387 
4388 }
4389 #endif
4390