Subversion Repositories Games.Descent

Rev

Details | Last modification | View Log | RSS feed

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