Subversion Repositories Games.Descent

Rev

Blame | Last modification | View Log | Download | RSS feed

  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
  4428.