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