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.  * Game Controls Stuff
  23.  *
  24.  */
  25.  
  26. #include <algorithm>
  27. #include <stdio.h>
  28. #include <cstdlib>
  29. #include <string.h>
  30. #include <stdarg.h>
  31.  
  32. #include "pstypes.h"
  33. #include "window.h"
  34. #include "console.h"
  35. #include "collide.h"
  36. #include "strutil.h"
  37. #include "game.h"
  38. #include "player.h"
  39. #include "key.h"
  40. #include "object.h"
  41. #include "menu.h"
  42. #include "physics.h"
  43. #include "dxxerror.h"
  44. #include "joy.h"
  45. #include "iff.h"
  46. #include "pcx.h"
  47. #include "timer.h"
  48. #include "render.h"
  49. #include "laser.h"
  50. #include "screens.h"
  51. #include "textures.h"
  52. #include "slew.h"
  53. #include "gauges.h"
  54. #include "texmap.h"
  55. #include "3d.h"
  56. #include "effects.h"
  57. #include "gameseg.h"
  58. #include "wall.h"
  59. #include "ai.h"
  60. #include "rbaudio.h"
  61. #include "digi.h"
  62. #include "u_mem.h"
  63. #include "palette.h"
  64. #include "morph.h"
  65. #include "robot.h"
  66. #include "lighting.h"
  67. #include "newdemo.h"
  68. #include "weapon.h"
  69. #include "sounds.h"
  70. #include "args.h"
  71. #include "gameseq.h"
  72. #include "automap.h"
  73. #include "text.h"
  74. #include "powerup.h"
  75. #include "songs.h"
  76. #include "newmenu.h"
  77. #include "gamefont.h"
  78. #include "endlevel.h"
  79. #include "config.h"
  80. #include "hudmsg.h"
  81. #include "kconfig.h"
  82. #include "mouse.h"
  83. #include "titles.h"
  84. #include "gr.h"
  85. #include "playsave.h"
  86. #include "scores.h"
  87.  
  88. #include "multi.h"
  89. #include "cntrlcen.h"
  90. #include "fuelcen.h"
  91. #include "pcx.h"
  92. #include "state.h"
  93. #include "piggy.h"
  94. #include "multibot.h"
  95. #include "ai.h"
  96. #include "rbaudio.h"
  97. #include "switch.h"
  98. #if defined(DXX_BUILD_DESCENT_II)
  99. #include "escort.h"
  100. #include "movie.h"
  101. #endif
  102. #include "window.h"
  103.  
  104. #if DXX_USE_EDITOR
  105. #include "editor/editor.h"
  106. #include "editor/esegment.h"
  107. #endif
  108.  
  109. #include "compiler-range_for.h"
  110. #include "d_range.h"
  111. #include "partial_range.h"
  112. #include <array>
  113. #include <utility>
  114.  
  115. #include <SDL.h>
  116.  
  117. #if defined(__GNUC__) && defined(WIN32)
  118. /* Mingw64 _mingw_print_pop.h changes PRIi64 to POSIX-style.  Change it
  119.  * back here.
  120.  *
  121.  * Some outdated mingw32 users are also affected.
  122.  */
  123. #undef PRIi64
  124. #define PRIi64 "I64i"
  125. #endif
  126.  
  127. using std::min;
  128.  
  129. // Global Variables -----------------------------------------------------------
  130.  
  131. //      Function prototypes --------------------------------------------------------
  132. #ifndef RELEASE
  133. static void do_cheat_menu();
  134. static void play_test_sound();
  135. #endif
  136.  
  137. #define key_isfunc(k) (((k&0xff)>=KEY_F1 && (k&0xff)<=KEY_F10) || (k&0xff)==KEY_F11 || (k&0xff)==KEY_F12)
  138.  
  139. // Functions ------------------------------------------------------------------
  140.  
  141. #if defined(DXX_BUILD_DESCENT_II)
  142. #define CONVERTER_RATE  20              //10 units per second xfer rate
  143. #define CONVERTER_SCALE  2              //2 units energy -> 1 unit shields
  144.  
  145. #define CONVERTER_SOUND_DELAY (f1_0/2)          //play every half second
  146.  
  147. static void transfer_energy_to_shield(object &plrobj)
  148. {
  149.         static fix64 last_play_time=0;
  150.  
  151.         auto &player_info = plrobj.ctype.player_info;
  152.         auto &shields = plrobj.shields;
  153.         auto &energy = player_info.energy;
  154.         //how much energy gets transfered
  155.         const fix e = min(min(FrameTime*CONVERTER_RATE, energy - INITIAL_ENERGY), (MAX_SHIELDS - shields) * CONVERTER_SCALE);
  156.  
  157.         if (e <= 0) {
  158.  
  159.                 if (energy <= INITIAL_ENERGY) {
  160.                         HUD_init_message(HM_DEFAULT, "Need more than %i energy to enable transfer", f2i(INITIAL_ENERGY));
  161.                 }
  162.                 else if (shields >= MAX_SHIELDS)
  163.                 {
  164.                         HUD_init_message_literal(HM_DEFAULT, "No transfer: Shields already at max");
  165.                 }
  166.                 return;
  167.         }
  168.  
  169.         energy  -= e;
  170.         shields += e / CONVERTER_SCALE;
  171.  
  172.         if (last_play_time > GameTime64)
  173.                 last_play_time = 0;
  174.  
  175.         if (GameTime64 > last_play_time+CONVERTER_SOUND_DELAY) {
  176.                 digi_play_sample_once(SOUND_CONVERT_ENERGY, F1_0);
  177.                 last_play_time = GameTime64;
  178.         }
  179. }
  180. #endif
  181.  
  182.  
  183. // Control Functions
  184.  
  185. static fix64 newdemo_single_frame_time;
  186.  
  187. static void update_vcr_state(void)
  188. {
  189.         if ((keyd_pressed[KEY_LSHIFT] || keyd_pressed[KEY_RSHIFT]) && keyd_pressed[KEY_RIGHT] && d_tick_step)
  190.                 Newdemo_vcr_state = ND_STATE_FASTFORWARD;
  191.         else if ((keyd_pressed[KEY_LSHIFT] || keyd_pressed[KEY_RSHIFT]) && keyd_pressed[KEY_LEFT] && d_tick_step)
  192.                 Newdemo_vcr_state = ND_STATE_REWINDING;
  193.         else if (!(keyd_pressed[KEY_LCTRL] || keyd_pressed[KEY_RCTRL]) && keyd_pressed[KEY_RIGHT] && ((GameTime64 - newdemo_single_frame_time) >= F1_0) && d_tick_step)
  194.                 Newdemo_vcr_state = ND_STATE_ONEFRAMEFORWARD;
  195.         else if (!(keyd_pressed[KEY_LCTRL] || keyd_pressed[KEY_RCTRL]) && keyd_pressed[KEY_LEFT] && ((GameTime64 - newdemo_single_frame_time) >= F1_0) && d_tick_step)
  196.                 Newdemo_vcr_state = ND_STATE_ONEFRAMEBACKWARD;
  197.         else if ((Newdemo_vcr_state == ND_STATE_FASTFORWARD) || (Newdemo_vcr_state == ND_STATE_REWINDING))
  198.                 Newdemo_vcr_state = ND_STATE_PLAYBACK;
  199. }
  200.  
  201. namespace dsx {
  202. #if defined(DXX_BUILD_DESCENT_II)
  203. //returns which bomb will be dropped next time the bomb key is pressed
  204. int which_bomb()
  205. {
  206.         auto &Objects = LevelUniqueObjectState.Objects;
  207.         auto &vmobjptr = Objects.vmptr;
  208.         //use the last one selected, unless there aren't any, in which case use
  209.         //the other if there are any
  210.         auto &player_info = get_local_plrobj().ctype.player_info;
  211.         auto &Secondary_last_was_super = player_info.Secondary_last_was_super;
  212.         const auto mask = 1 << PROXIMITY_INDEX;
  213.         const auto bomb = (Secondary_last_was_super & mask) ? SMART_MINE_INDEX : PROXIMITY_INDEX;
  214.  
  215.         auto &secondary_ammo = player_info.secondary_ammo;
  216.         if (secondary_ammo[bomb] == 0 &&
  217.                 secondary_ammo[SMART_MINE_INDEX + PROXIMITY_INDEX - bomb] != 0)
  218.         {
  219.                 Secondary_last_was_super ^= mask;
  220.         }
  221.         return bomb;
  222. }
  223. #endif
  224.  
  225. static void do_weapon_n_item_stuff(object_array &Objects)
  226. {
  227.         auto &vmobjptridx = Objects.vmptridx;
  228.         auto &vmobjptr = Objects.vmptr;
  229.         auto &plrobj = get_local_plrobj();
  230.         auto &player_info = plrobj.ctype.player_info;
  231.         if (Controls.state.fire_flare > 0)
  232.         {
  233.                 Controls.state.fire_flare = 0;
  234.                 if (allowed_to_fire_flare(player_info))
  235.                         Flare_create(vmobjptridx(ConsoleObject));
  236.         }
  237.  
  238.         if (allowed_to_fire_missile(player_info) && Controls.state.fire_secondary)
  239.         {
  240.                 Global_missile_firing_count += Weapon_info[Secondary_weapon_to_weapon_info[player_info.Secondary_weapon]].fire_count;
  241.         }
  242.  
  243.         if (Global_missile_firing_count) {
  244.                 do_missile_firing(0);
  245.                 Global_missile_firing_count--;
  246.         }
  247.  
  248.         if (Controls.state.cycle_primary > 0)
  249.         {
  250.                 for (uint_fast32_t i = std::exchange(Controls.state.cycle_primary, 0); i--;)
  251.                         CyclePrimary(player_info);
  252.         }
  253.         if (Controls.state.cycle_secondary > 0)
  254.         {
  255.                 for (uint_fast32_t i = std::exchange(Controls.state.cycle_secondary, 0); i--;)
  256.                         CycleSecondary(player_info);
  257.         }
  258.         if (Controls.state.select_weapon > 0)
  259.         {
  260.                 const auto select_weapon = std::exchange(Controls.state.select_weapon, 0) - 1;
  261.                 const auto weapon_num = select_weapon > 4 ? select_weapon - 5 : select_weapon;
  262.                 if (select_weapon > 4)
  263.                         do_secondary_weapon_select(player_info, static_cast<secondary_weapon_index_t>(select_weapon - 5));
  264.                 else
  265.                         do_primary_weapon_select(player_info, weapon_num);
  266.         }
  267. #if defined(DXX_BUILD_DESCENT_II)
  268.         if (auto &headlight = Controls.state.headlight)
  269.         {
  270.                 if (std::exchange(headlight, 0) & 1)
  271.                         toggle_headlight_active(plrobj);
  272.         }
  273. #endif
  274.  
  275.         if (Global_missile_firing_count < 0)
  276.                 Global_missile_firing_count = 0;
  277.  
  278.         //      Drop proximity bombs.
  279.         if (Controls.state.drop_bomb > 0)
  280.         {
  281.                 for (uint_fast32_t i = std::exchange(Controls.state.drop_bomb, 0); i--;)
  282.                 do_missile_firing(1);
  283.         }
  284. #if defined(DXX_BUILD_DESCENT_II)
  285.         if (Controls.state.toggle_bomb > 0)
  286.         {
  287.                 auto &Secondary_last_was_super = player_info.Secondary_last_was_super;
  288.                 auto &secondary_ammo = player_info.secondary_ammo;
  289.                 int sound;
  290.                 if (!secondary_ammo[PROXIMITY_INDEX] && !secondary_ammo[SMART_MINE_INDEX])
  291.                 {
  292.                         HUD_init_message_literal(HM_DEFAULT, "No bombs available!");
  293.                         sound = SOUND_BAD_SELECTION;
  294.                 }
  295.                 else
  296.                 {      
  297.                         const auto mask = (1 << PROXIMITY_INDEX);
  298.                         const char *desc;
  299.                         const auto bomb = (Secondary_last_was_super & mask) ? (desc = "Proximity bombs", PROXIMITY_INDEX) : (desc = "Smart mines", SMART_MINE_INDEX);
  300.                         if (secondary_ammo[bomb] == 0)
  301.                         {
  302.                                 HUD_init_message(HM_DEFAULT, "No %s available!", desc);
  303.                                 sound = SOUND_BAD_SELECTION;
  304.                         }
  305.                         else
  306.                         {
  307.                                 Secondary_last_was_super ^= mask;
  308.                                 sound = SOUND_GOOD_SELECTION_SECONDARY;
  309.                         }
  310.                 }
  311.                 digi_play_sample_once(sound, F1_0);
  312.                 Controls.state.toggle_bomb = 0;
  313.         }
  314.  
  315.         if (Controls.state.energy_to_shield && (player_info.powerup_flags & PLAYER_FLAGS_CONVERTER))
  316.                 transfer_energy_to_shield(plrobj);
  317. #endif
  318. }
  319. }
  320.  
  321. static void format_time(char (&str)[9], unsigned secs_int, unsigned hours_extra)
  322. {
  323.         auto d1 = std::div(secs_int, 60);
  324.         const unsigned s = d1.rem;
  325.         const unsigned m1 = d1.quot;
  326.         auto d2 = std::div(m1, 60);
  327.         const unsigned m = d2.rem;
  328.         const unsigned h = d2.quot + hours_extra;
  329.         snprintf(str, sizeof(str), "%1u:%02u:%02u", h, m, s);
  330. }
  331.  
  332. struct pause_window : ignore_window_pointer_t
  333. {
  334.         std::array<char, 1024> msg;
  335. };
  336.  
  337. //Process selected keys until game unpaused
  338. static window_event_result pause_handler(window *, const d_event &event, pause_window *p)
  339. {
  340.         int key;
  341.  
  342.         switch (event.type)
  343.         {
  344.                 case EVENT_WINDOW_ACTIVATED:
  345.                         game_flush_inputs();
  346.                         break;
  347.  
  348.                 case EVENT_KEY_COMMAND:
  349.                         key = event_key_get(event);
  350.  
  351.                         switch (key)
  352.                         {
  353.                                 case 0:
  354.                                         break;
  355.                                 case KEY_ESC:
  356.                                         return window_event_result::close;
  357.                                 case KEY_F1:
  358.                                         show_help();
  359.                                         return window_event_result::handled;
  360.                                 case KEY_PAUSE:
  361.                                         return window_event_result::close;
  362.                                 default:
  363.                                         break;
  364.                         }
  365.                         break;
  366.  
  367.                 case EVENT_IDLE:
  368.                         timer_delay2(50);
  369.                         break;
  370.  
  371.                 case EVENT_WINDOW_DRAW:
  372.                         show_boxed_message(&p->msg[0], 1);
  373.                         break;
  374.  
  375.                 case EVENT_WINDOW_CLOSE:
  376.                         songs_resume();
  377.                         delete p;
  378.                         break;
  379.  
  380.                 default:
  381.                         break;
  382.         }
  383.         return window_event_result::ignored;
  384. }
  385.  
  386. static int do_game_pause()
  387. {
  388.         auto &Objects = LevelUniqueObjectState.Objects;
  389.         auto &vcobjptr = Objects.vcptr;
  390.         char total_time[9],level_time[9];
  391.  
  392.         if (Game_mode & GM_MULTI)
  393.         {
  394.                 netplayerinfo_on= !netplayerinfo_on;
  395.                 return(KEY_PAUSE);
  396.         }
  397.  
  398.         pause_window *p = new pause_window;
  399.         songs_pause();
  400.  
  401.         auto &plr = get_local_player();
  402.         format_time(total_time, f2i(plr.time_total), plr.hours_total);
  403.         format_time(level_time, f2i(plr.time_level), plr.hours_level);
  404.         auto &player_info = vcobjptr(plr.objnum)->ctype.player_info;
  405.         if (Newdemo_state!=ND_STATE_PLAYBACK)
  406.                 snprintf(&p->msg[0], p->msg.size(), "PAUSE\n\nSkill level:  %s\nHostages on board:  %d\nTime on level: %s\nTotal time in game: %s", MENU_DIFFICULTY_TEXT(GameUniqueState.Difficulty_level), player_info.mission.hostages_on_board, level_time, total_time);
  407.         else
  408.                 snprintf(&p->msg[0], p->msg.size(), "PAUSE\n\n\n\n");
  409.         set_screen_mode(SCREEN_MENU);
  410.  
  411.         if (!window_create(grd_curscreen->sc_canvas, 0, 0, SWIDTH, SHEIGHT, pause_handler, p))
  412.                 delete p;
  413.  
  414.         return 0 /*key*/;       // Keycode returning ripped out (kreatordxx)
  415. }
  416.  
  417. static window_event_result HandleEndlevelKey(int key)
  418. {
  419.         switch (key)
  420.         {
  421.                 case KEY_COMMAND+KEY_P:
  422.                 case KEY_PAUSE:
  423.                         do_game_pause();
  424.                         return window_event_result::handled;
  425.  
  426.                 case KEY_ESC:
  427.                         last_drawn_cockpit=-1;
  428.                         return stop_endlevel_sequence();
  429.         }
  430.  
  431.         return window_event_result::ignored;
  432. }
  433.  
  434. static int HandleDeathInput(const d_event &event)
  435. {
  436.         const auto input_aborts_death_sequence = [&]() {
  437.                 const auto RespawnMode = PlayerCfg.RespawnMode;
  438.                 if (event.type == EVENT_KEY_COMMAND)
  439.                 {
  440.                         const auto key = event_key_get(event);
  441.                         if ((RespawnMode == RespawnPress::Any && !key_isfunc(key) && key != KEY_PAUSE && key) ||
  442.                                 (key == KEY_ESC && ConsoleObject->flags & OF_EXPLODING))
  443.                                 return 1;
  444.                 }
  445.  
  446.                 if (RespawnMode == RespawnPress::Any
  447.                         ? (event.type == EVENT_JOYSTICK_BUTTON_UP || event.type == EVENT_MOUSE_BUTTON_UP)
  448.                         : (Controls.state.fire_primary || Controls.state.fire_secondary || Controls.state.fire_flare))
  449.                         return 1;
  450.                 return 0;
  451.         };
  452.         if (Player_dead_state == player_dead_state::exploded && input_aborts_death_sequence())
  453.         {
  454.                 GameViewUniqueState.Death_sequence_aborted = 1;
  455.         }
  456.  
  457.         if (GameViewUniqueState.Death_sequence_aborted)
  458.         {
  459.                 game_flush_respawn_inputs();
  460.                 return 1;
  461.         }
  462.  
  463.         return 0;
  464. }
  465.  
  466. #if DXX_USE_SCREENSHOT
  467. static void save_pr_screenshot()
  468. {
  469.         gr_set_default_canvas();
  470.         auto &canvas = *grd_curcanv;
  471.         render_frame(canvas, 0);
  472.         auto &medium2_font = *MEDIUM2_FONT;
  473.         gr_string(canvas, medium2_font, SWIDTH - FSPACX(92), SHEIGHT - LINE_SPACING(medium2_font, *GAME_FONT), "DXX-Rebirth\n");
  474.         gr_flip();
  475.         save_screen_shot(0);
  476. }
  477.  
  478. static void save_clean_screenshot()
  479. {
  480.         game_render_frame_mono(CGameArg.DbgNoDoubleBuffer);
  481.         save_screen_shot(0);
  482. }
  483. #endif
  484.  
  485. static window_event_result HandleDemoKey(int key)
  486. {
  487.         switch (key) {
  488.                 KEY_MAC(case KEY_COMMAND+KEY_1:)
  489.                 case KEY_F1:    show_newdemo_help();    break;
  490.                 KEY_MAC(case KEY_COMMAND+KEY_2:)
  491.                 case KEY_F2:    do_options_menu();      break;
  492.                 KEY_MAC(case KEY_COMMAND+KEY_3:)
  493.                 case KEY_F3:
  494.                          if (Viewer->type == OBJ_PLAYER)
  495.                                 toggle_cockpit();
  496.                          break;
  497.                 KEY_MAC(case KEY_COMMAND+KEY_4:)
  498.                 case KEY_F4:    Newdemo_show_percentage = !Newdemo_show_percentage; break;
  499.                 KEY_MAC(case KEY_COMMAND+KEY_7:)
  500.                 case KEY_F7:
  501.                         Show_kill_list = (Show_kill_list+1) % ((Newdemo_game_mode & GM_TEAM) ? 4 : 3);
  502.                         break;
  503.                 case KEY_ESC:
  504.                         if (CGameArg.SysAutoDemo)
  505.                         {
  506.                                 int choice;
  507.                                 choice = nm_messagebox( NULL, 2, TXT_YES, TXT_NO, TXT_ABORT_AUTODEMO );
  508.                                 if (choice == 0)
  509.                                         CGameArg.SysAutoDemo = false;
  510.                                 else
  511.                                         break;
  512.                         }
  513.                         newdemo_stop_playback();
  514.                         return window_event_result::close;
  515.                         break;
  516.                 case KEY_UP:
  517.                         Newdemo_vcr_state = ND_STATE_PLAYBACK;
  518.                         break;
  519.                 case KEY_DOWN:
  520.                         Newdemo_vcr_state = ND_STATE_PAUSED;
  521.                         break;
  522.                 case KEY_LEFT:
  523.                         newdemo_single_frame_time = GameTime64;
  524.                         Newdemo_vcr_state = ND_STATE_ONEFRAMEBACKWARD;
  525.                         break;
  526.                 case KEY_RIGHT:
  527.                         newdemo_single_frame_time = GameTime64;
  528.                         Newdemo_vcr_state = ND_STATE_ONEFRAMEFORWARD;
  529.                         break;
  530.                 case KEY_CTRLED + KEY_RIGHT:
  531.                         return newdemo_goto_end(0);
  532.                         break;
  533.                 case KEY_CTRLED + KEY_LEFT:
  534.                         return newdemo_goto_beginning();
  535.                         break;
  536.  
  537.                 KEY_MAC(case KEY_COMMAND+KEY_P:)
  538.                 case KEY_PAUSE:
  539.                         do_game_pause();
  540.                         break;
  541.  
  542. #if DXX_USE_SCREENSHOT
  543. #ifdef macintosh
  544.                 case KEY_COMMAND + KEY_SHIFTED + KEY_3:
  545. #endif
  546.                 case KEY_PRINT_SCREEN:
  547.                 {
  548.                         if (PlayerCfg.PRShot)
  549.                         {
  550.                                 save_pr_screenshot();
  551.                         }
  552.                         else
  553.                         {
  554.                                 int old_state;
  555.                                 old_state = Newdemo_show_percentage;
  556.                                 Newdemo_show_percentage = 0;
  557.                                 save_clean_screenshot();
  558.                                 Newdemo_show_percentage = old_state;
  559.                         }
  560.                         break;
  561.                 }
  562. #endif
  563. #ifndef NDEBUG
  564.                 case KEY_DEBUGGED + KEY_I:
  565.                         Newdemo_do_interpolate = !Newdemo_do_interpolate;
  566.                         HUD_init_message(HM_DEFAULT, "Demo playback interpolation %s", Newdemo_do_interpolate?"ON":"OFF");
  567.                         break;
  568.                 case KEY_DEBUGGED + KEY_K: {
  569.                         int how_many, c;
  570.                         char filename[FILENAME_LEN], num[16];
  571.                         std::array<newmenu_item, 2> m{{
  572.                                 nm_item_text("output file name"),
  573.                                 nm_item_input(filename),
  574.                         }};
  575.                         filename[0] = '\0';
  576.                         c = newmenu_do( NULL, NULL, m, unused_newmenu_subfunction, unused_newmenu_userdata);
  577.                         if (c == -2)
  578.                                 break;
  579.                         strcat(filename, DEMO_EXT);
  580.                         num[0] = '\0';
  581.                         m = {{
  582.                                 nm_item_text("strip how many bytes"),
  583.                                 nm_item_input(num),
  584.                         }};
  585.                         c = newmenu_do( NULL, NULL, m, unused_newmenu_subfunction, unused_newmenu_userdata);
  586.                         if (c == -2)
  587.                                 break;
  588.                         how_many = atoi(num);
  589.                         if (how_many <= 0)
  590.                                 break;
  591.                         newdemo_strip_frames(filename, how_many);
  592.  
  593.                         break;
  594.                 }
  595. #endif
  596.  
  597.                 default:
  598.                         return window_event_result::ignored;
  599.         }
  600.  
  601.         return window_event_result::handled;
  602. }
  603.  
  604. #if defined(DXX_BUILD_DESCENT_II)
  605. //switch a cockpit window to the next function
  606. static int select_next_window_function(int w)
  607. {
  608.         auto &Objects = LevelUniqueObjectState.Objects;
  609.         auto &vmobjptridx = Objects.vmptridx;
  610.         Assert(w==0 || w==1);
  611.  
  612.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  613.         switch (PlayerCfg.Cockpit3DView[w]) {
  614.                 case CV_NONE:
  615.                         PlayerCfg.Cockpit3DView[w] = CV_REAR;
  616.                         break;
  617.                 case CV_REAR:
  618.                         if (find_escort(vmobjptridx, Robot_info) != object_none)
  619.                         {
  620.                                 PlayerCfg.Cockpit3DView[w] = CV_ESCORT;
  621.                                 break;
  622.                         }
  623.                         //if no ecort, fall through
  624.                         DXX_BOOST_FALLTHROUGH;
  625.                 case CV_ESCORT:
  626.                         Coop_view_player[w] = UINT_MAX;         //force first player
  627.                         DXX_BOOST_FALLTHROUGH;
  628.                 case CV_COOP:
  629.                         Marker_viewer_num[w] = game_marker_index::None;
  630.                         if ((Game_mode & GM_MULTI_COOP) || (Game_mode & GM_TEAM)) {
  631.                                 PlayerCfg.Cockpit3DView[w] = CV_COOP;
  632.                                 for (;;)
  633.                                 {
  634.                                         const auto cvp = ++ Coop_view_player[w];
  635.                                         if (cvp == MAX_PLAYERS - 1)
  636.                                         {
  637.                                                 PlayerCfg.Cockpit3DView[w] = CV_MARKER;
  638.                                                 goto case_marker;
  639.                                         }
  640.                                         if (cvp == Player_num)
  641.                                                 continue;
  642.                                         if (vcplayerptr(cvp)->connected != CONNECT_PLAYING)
  643.                                                 continue;
  644.  
  645.                                         if (Game_mode & GM_MULTI_COOP)
  646.                                                 break;
  647.                                         else if (get_team(cvp) == get_team(Player_num))
  648.                                                 break;
  649.                                 }
  650.                                 break;
  651.                         }
  652.                         //if not multi,
  653.                         DXX_BOOST_FALLTHROUGH;
  654.                 case CV_MARKER:
  655.                 case_marker:;
  656.                         if ((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP) && Netgame.Allow_marker_view) {      //anarchy only
  657.                                 PlayerCfg.Cockpit3DView[w] = CV_MARKER;
  658.                                 auto &mvn = Marker_viewer_num[w];
  659.                                 const auto gmi0 = convert_player_marker_index_to_game_marker_index(Game_mode, Netgame.max_numplayers, Player_num, player_marker_index::_0);
  660.                                 if (!MarkerState.imobjidx.valid_index(mvn))
  661.                                         mvn = gmi0;
  662.                                 else
  663.                                 {
  664.                                         ++ mvn;
  665.                                         if (!MarkerState.imobjidx.valid_index(mvn))
  666.                                                 PlayerCfg.Cockpit3DView[w] = CV_NONE;
  667.                                 }
  668.                         }
  669.                         else
  670.                                 PlayerCfg.Cockpit3DView[w] = CV_NONE;
  671.                         break;
  672.         }
  673.         write_player_file();
  674.  
  675.         return 1;        //screen_changed
  676. }
  677. #endif
  678.  
  679. //this is for system-level keys, such as help, etc.
  680. //returns 1 if screen changed
  681. namespace dsx {
  682. static window_event_result HandleSystemKey(int key)
  683. {
  684.         if (Player_dead_state == player_dead_state::no)
  685.                 switch (key)
  686.                 {
  687.                         case KEY_ESC:
  688.                         {
  689.                                 const bool allow_saveload = !(Game_mode & GM_MULTI) || ((Game_mode & GM_MULTI_COOP) && Player_num == 0);
  690.                                 const auto choice = nm_messagebox_str(nullptr, allow_saveload ? nm_messagebox_tie("Abort Game", TXT_OPTIONS_, "Save Game...", TXT_LOAD_GAME) : nm_messagebox_tie("Abort Game", TXT_OPTIONS_), "Game Menu");
  691.                                 switch(choice)
  692.                                 {
  693.                                         case 0:
  694.                                                 return window_event_result::close;
  695.                                         case 1:
  696.                                                 return HandleSystemKey(KEY_F2);
  697.                                         case 2:
  698.                                                 return HandleSystemKey(KEY_ALTED | KEY_F2);
  699.                                         case 3:
  700.                                                 return HandleSystemKey(KEY_ALTED | KEY_F3);
  701.                                 }
  702.                                 return window_event_result::handled;
  703.                         }
  704. #if defined(DXX_BUILD_DESCENT_II)
  705. // fleshed these out because F1 and F2 aren't sequenctial keycodes on mac -- MWA
  706.  
  707.                         KEY_MAC(case KEY_COMMAND+KEY_SHIFTED+KEY_1:)
  708.                         case KEY_SHIFTED+KEY_F1:
  709.                                 select_next_window_function(0);
  710.                                 return window_event_result::handled;
  711.                         KEY_MAC(case KEY_COMMAND+KEY_SHIFTED+KEY_2:)
  712.                         case KEY_SHIFTED+KEY_F2:
  713.                                 select_next_window_function(1);
  714.                                 return window_event_result::handled;
  715. #endif
  716.                 }
  717.  
  718.         switch (key)
  719.         {
  720.                 KEY_MAC( case KEY_COMMAND+KEY_PAUSE+KEY_SHIFTED: )
  721.                 case KEY_PAUSE+KEY_SHIFTED:
  722.                         if (Game_mode & GM_MULTI)
  723.                                 show_netgame_info(Netgame);
  724.                         break;
  725.                 KEY_MAC( case KEY_COMMAND+KEY_P: )
  726.                 case KEY_PAUSE:
  727.                         do_game_pause();        break;
  728.  
  729.  
  730. #if DXX_USE_SCREENSHOT
  731. #ifdef macintosh
  732.                 case KEY_COMMAND + KEY_SHIFTED + KEY_3:
  733. #endif
  734.                 case KEY_PRINT_SCREEN:
  735.                 {
  736.                         if (PlayerCfg.PRShot)
  737.                         {
  738.                                 save_pr_screenshot();
  739.                         }
  740.                         else
  741.                         {
  742.                                 save_clean_screenshot();
  743.                         }
  744.                         break;
  745.                 }
  746. #endif
  747.  
  748.                 KEY_MAC(case KEY_COMMAND+KEY_1:)
  749.                 case KEY_F1:                            if (Game_mode & GM_MULTI) show_netgame_help(); else show_help();        break;
  750.  
  751.                 KEY_MAC(case KEY_COMMAND+KEY_2:)
  752.                 case KEY_F2:
  753.                         {
  754.                                 do_options_menu();
  755.                                 break;
  756.                         }
  757.  
  758.  
  759.                 KEY_MAC(case KEY_COMMAND+KEY_3:)
  760.  
  761.                 case KEY_F3:
  762.                         if (Player_dead_state == player_dead_state::no && Viewer->type == OBJ_PLAYER)
  763.                         {
  764.                                 toggle_cockpit();
  765.                         }
  766.                         break;
  767.  
  768.                 KEY_MAC(case KEY_COMMAND+KEY_5:)
  769.                 case KEY_F5:
  770.                         if ( Newdemo_state == ND_STATE_RECORDING )
  771.                                 newdemo_stop_recording();
  772.                         else if ( Newdemo_state == ND_STATE_NORMAL )
  773.                                 newdemo_start_recording();
  774.                         break;
  775.                 KEY_MAC(case KEY_COMMAND+KEY_ALTED+KEY_4:)
  776.                 case KEY_ALTED + KEY_F4:
  777.                         Show_reticle_name = (Show_reticle_name+1)%2;
  778.                         break;
  779.  
  780.                 KEY_MAC(case KEY_COMMAND+KEY_7:)
  781.                 case KEY_F7:
  782.                         Show_kill_list = (Show_kill_list+1) % ((Game_mode & GM_TEAM) ? 4 : 3);
  783.                         if (Game_mode & GM_MULTI)
  784.                                 multi_sort_kill_list();
  785.                         break;
  786.  
  787.                 KEY_MAC(case KEY_COMMAND+KEY_8:)
  788.                 case KEY_F8:
  789.                         multi_send_message_start();
  790.                         break;
  791.  
  792.                 case KEY_F9:
  793.                 case KEY_F10:
  794.                 case KEY_F11:
  795.                 case KEY_F12:
  796.                         multi_send_macro(key);
  797.                         break;          // send taunt macros
  798.  
  799. #if defined(__APPLE__) || defined(macintosh)
  800.                 case KEY_9 + KEY_COMMAND:
  801.                         multi_send_macro(KEY_F9);
  802.                         break;
  803.                 case KEY_0 + KEY_COMMAND:
  804.                         multi_send_macro(KEY_F10);
  805.                         break;
  806.                 case KEY_1 + KEY_COMMAND + KEY_CTRLED:
  807.                         multi_send_macro(KEY_F11);
  808.                         break;
  809.                 case KEY_2 + KEY_COMMAND + KEY_CTRLED:
  810.                         multi_send_macro(KEY_F12);
  811.                         break;
  812. #endif
  813.  
  814.                 case KEY_SHIFTED + KEY_F9:
  815.                 case KEY_SHIFTED + KEY_F10:
  816.                 case KEY_SHIFTED + KEY_F11:
  817.                 case KEY_SHIFTED + KEY_F12:
  818.                         multi_define_macro(key);
  819.                         break;          // redefine taunt macros
  820.  
  821. #if defined(__APPLE__) || defined(macintosh)
  822.                 case KEY_9 + KEY_SHIFTED + KEY_COMMAND:
  823.                         multi_define_macro(KEY_F9);
  824.                         break;
  825.                 case KEY_0 + KEY_SHIFTED + KEY_COMMAND:
  826.                         multi_define_macro(KEY_F10);
  827.                         break;
  828.                 case KEY_1 + KEY_SHIFTED + KEY_COMMAND + KEY_CTRLED:
  829.                         multi_define_macro(KEY_F11);
  830.                         break;
  831.                 case KEY_2 + KEY_SHIFTED + KEY_COMMAND + KEY_CTRLED:
  832.                         multi_define_macro(KEY_F12);
  833.                         break;
  834. #endif
  835.  
  836.  
  837.                 KEY_MAC(case KEY_COMMAND+KEY_SHIFTED+KEY_S:)
  838.                 KEY_MAC(case KEY_COMMAND+KEY_ALTED+KEY_2:)
  839.                 case KEY_ALTED+KEY_F2:
  840.                         if (Player_dead_state == player_dead_state::no)
  841.                                 state_save_all(secret_save::none, blind_save::no); // 0 means not between levels.
  842.                         break;
  843.  
  844.                 KEY_MAC(case KEY_COMMAND+KEY_S:)
  845.                 case KEY_ALTED+KEY_SHIFTED+KEY_F2:
  846.                         if (Player_dead_state == player_dead_state::no)
  847.                                 state_save_all(secret_save::none, blind_save::yes);
  848.                         break;
  849.                 KEY_MAC(case KEY_COMMAND+KEY_SHIFTED+KEY_O:)
  850.                 KEY_MAC(case KEY_COMMAND+KEY_ALTED+KEY_3:)
  851.                 case KEY_ALTED+KEY_F3:
  852.                         if (!((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP)))
  853.                                 state_restore_all(1, secret_restore::none, nullptr, blind_save::no);
  854.                         break;
  855.                 KEY_MAC(case KEY_COMMAND+KEY_O:)
  856.                 case KEY_ALTED+KEY_SHIFTED+KEY_F3:
  857.                         if (!((Game_mode & GM_MULTI) && !(Game_mode & GM_MULTI_COOP)))
  858.                                 state_restore_all(1, secret_restore::none, nullptr, blind_save::yes);
  859.                         break;
  860.  
  861. #if defined(DXX_BUILD_DESCENT_II)
  862.                 KEY_MAC(case KEY_COMMAND+KEY_SHIFTED+KEY_4:)
  863.                 case KEY_F4 + KEY_SHIFTED:
  864.                         do_escort_menu();
  865.                         break;
  866.  
  867.  
  868.                 KEY_MAC(case KEY_COMMAND+KEY_SHIFTED+KEY_ALTED+KEY_4:)
  869.                 case KEY_F4 + KEY_SHIFTED + KEY_ALTED:
  870.                         change_guidebot_name();
  871.                         break;
  872. #endif
  873.  
  874.                         /*
  875.                          * Jukebox hotkeys -- MD2211, 2007
  876.                          * Now for all music
  877.                          * ==============================================
  878.                          */
  879.                 case KEY_ALTED + KEY_SHIFTED + KEY_F9:
  880.                 KEY_MAC(case KEY_COMMAND+KEY_E:)
  881. #if DXX_USE_SDL_REDBOOK_AUDIO
  882.                         if (GameCfg.MusicType == MUSIC_TYPE_REDBOOK)
  883.                         {
  884.                                 songs_stop_all();
  885.                                 RBAEjectDisk();
  886.                         }
  887. #endif
  888.                         break;
  889.  
  890.                 case KEY_ALTED + KEY_SHIFTED + KEY_F10:
  891.                 KEY_MAC(case KEY_COMMAND+KEY_UP:)
  892.                 KEY_MAC(case KEY_COMMAND+KEY_DOWN:)
  893.                         songs_pause_resume();
  894.                         break;
  895.  
  896.                 case KEY_MINUS + KEY_ALTED:
  897.                 case KEY_ALTED + KEY_SHIFTED + KEY_F11:
  898.                 KEY_MAC(case KEY_COMMAND+KEY_LEFT:)
  899.                         songs_play_level_song( Current_level_num, -1 );
  900.                         break;
  901.                 case KEY_EQUAL + KEY_ALTED:
  902.                 case KEY_ALTED + KEY_SHIFTED + KEY_F12:
  903.                 KEY_MAC(case KEY_COMMAND+KEY_RIGHT:)
  904.                         songs_play_level_song( Current_level_num, 1 );
  905.                         break;
  906.  
  907.                 default:
  908.                         return window_event_result::ignored;
  909.         }
  910.         return window_event_result::handled;
  911. }
  912. }
  913.  
  914. namespace dsx {
  915. static window_event_result HandleGameKey(int key)
  916. {
  917.         auto &Objects = LevelUniqueObjectState.Objects;
  918.         auto &vmobjptr = Objects.vmptr;
  919.         switch (key) {
  920. #if defined(DXX_BUILD_DESCENT_II)
  921.                 case KEY_1 + KEY_SHIFTED:
  922.                 case KEY_2 + KEY_SHIFTED:
  923.                 case KEY_3 + KEY_SHIFTED:
  924.                 case KEY_4 + KEY_SHIFTED:
  925.                 case KEY_5 + KEY_SHIFTED:
  926.                 case KEY_6 + KEY_SHIFTED:
  927.                 case KEY_7 + KEY_SHIFTED:
  928.                 case KEY_8 + KEY_SHIFTED:
  929.                 case KEY_9 + KEY_SHIFTED:
  930.                 case KEY_0 + KEY_SHIFTED:
  931.                         if (PlayerCfg.EscortHotKeys)
  932.                         {
  933.                                 auto &BuddyState = LevelUniqueObjectState.BuddyState;
  934.                                 if (Game_mode & GM_MULTI)
  935.                                 {
  936.                                         if (!check_warn_local_player_can_control_guidebot(Objects.vcptr, BuddyState, Netgame))
  937.                                                 return window_event_result::handled;
  938.                                 }
  939.                                 set_escort_special_goal(BuddyState, key);
  940.                                 game_flush_inputs();
  941.                                 return window_event_result::handled;
  942.                         }
  943.                         else
  944.                                 return window_event_result::ignored;
  945. #endif
  946.  
  947.                 case KEY_ALTED+KEY_F7:
  948.                 KEY_MAC(case KEY_COMMAND+KEY_ALTED+KEY_7:)
  949.                         PlayerCfg.HudMode = static_cast<HudType>((static_cast<unsigned>(PlayerCfg.HudMode) + 1) % GAUGE_HUD_NUMMODES);
  950.                         write_player_file();
  951.                         switch (PlayerCfg.HudMode)
  952.                         {
  953.                                 case HudType::Standard:
  954.                                         HUD_init_message_literal(HM_DEFAULT, "Standard HUD");
  955.                                         break;
  956.                                 case HudType::Alternate1:
  957.                                         HUD_init_message_literal(HM_DEFAULT, "Alternative HUD #1");
  958.                                         break;
  959.                                 case HudType::Alternate2:
  960.                                         HUD_init_message_literal(HM_DEFAULT, "Alternative HUD #2");
  961.                                         break;
  962.                                 case HudType::Hidden:
  963.                                         HUD_init_message_literal(HM_DEFAULT, "No HUD");
  964.                                         break;
  965.                         }
  966.                         return window_event_result::handled;
  967.  
  968.                 KEY_MAC(case KEY_COMMAND+KEY_6:)
  969.                 case KEY_F6:
  970.                         if (Netgame.RefusePlayers && WaitForRefuseAnswer && !(Game_mode & GM_TEAM))
  971.                         {
  972.                                 RefuseThisPlayer=1;
  973.                                 HUD_init_message_literal(HM_MULTI, "Player accepted!");
  974.                         }
  975.                         return window_event_result::handled;
  976.                 case KEY_ALTED + KEY_1:
  977.                         if (Netgame.RefusePlayers && WaitForRefuseAnswer && (Game_mode & GM_TEAM))
  978.                                 {
  979.                                         RefuseThisPlayer=1;
  980.                                         HUD_init_message_literal(HM_MULTI, "Player accepted!");
  981.                                         RefuseTeam=1;
  982.                                         game_flush_inputs();
  983.                                 }
  984.                         return window_event_result::handled;
  985.                 case KEY_ALTED + KEY_2:
  986.                         if (Netgame.RefusePlayers && WaitForRefuseAnswer && (Game_mode & GM_TEAM))
  987.                                 {
  988.                                         RefuseThisPlayer=1;
  989.                                         HUD_init_message_literal(HM_MULTI, "Player accepted!");
  990.                                         RefuseTeam=2;
  991.                                         game_flush_inputs();
  992.                                 }
  993.                         return window_event_result::handled;
  994.  
  995.                 default:
  996.                         break;
  997.  
  998.         }        //switch (key)
  999.  
  1000.         if (Player_dead_state == player_dead_state::no)
  1001.                 switch (key)
  1002.                 {
  1003.                                 KEY_MAC(case KEY_COMMAND+KEY_SHIFTED+KEY_5:)
  1004.                         case KEY_F5 + KEY_SHIFTED:
  1005.                                 DropCurrentWeapon(get_local_plrobj().ctype.player_info);
  1006.                                 break;
  1007.  
  1008.                         KEY_MAC(case KEY_COMMAND+KEY_SHIFTED+KEY_6:)
  1009.                         case KEY_F6 + KEY_SHIFTED:
  1010.                                 DropSecondaryWeapon(get_local_plrobj().ctype.player_info);
  1011.                                 break;
  1012.  
  1013. #if defined(DXX_BUILD_DESCENT_II)
  1014.                         case KEY_0 + KEY_ALTED:
  1015.                                 DropFlag ();
  1016.                                 game_flush_inputs();
  1017.                                 break;
  1018.  
  1019.                         KEY_MAC(case KEY_COMMAND+KEY_4:)
  1020.                         case KEY_F4:
  1021.                                 if (!MarkerState.DefiningMarkerMessage())
  1022.                                         InitMarkerInput();
  1023.                                 break;
  1024. #endif
  1025.  
  1026.                         default:
  1027.                                 return window_event_result::ignored;
  1028.                 }
  1029.         else
  1030.                 return window_event_result::ignored;
  1031.  
  1032.         return window_event_result::handled;
  1033. }
  1034. }
  1035.  
  1036. #if defined(DXX_BUILD_DESCENT_II)
  1037. static void kill_all_robots(void)
  1038. {
  1039.         auto &Objects = LevelUniqueObjectState.Objects;
  1040.         auto &vmobjptr = Objects.vmptr;
  1041.         int     dead_count=0;
  1042.         //int   boss_index = -1;
  1043.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  1044.  
  1045.         // Kill all bots except for Buddy bot and boss.  However, if only boss and buddy left, kill boss.
  1046.         range_for (const auto &&objp, vmobjptr)
  1047.         {
  1048.                 if (objp->type == OBJ_ROBOT)
  1049.                 {
  1050.                         if (!Robot_info[get_robot_id(objp)].companion && !Robot_info[get_robot_id(objp)].boss_flag) {
  1051.                                 dead_count++;
  1052.                                 objp->flags |= OF_EXPLODING|OF_SHOULD_BE_DEAD;
  1053.                         }
  1054.                 }
  1055.         }
  1056.  
  1057. // --           // Now, if more than boss and buddy left, un-kill boss.
  1058. // --           if ((dead_count > 2) && (boss_index != -1)) {
  1059. // --                   Objects[boss_index].flags &= ~(OF_EXPLODING|OF_SHOULD_BE_DEAD);
  1060. // --                   dead_count--;
  1061. // --           } else if (boss_index != -1)
  1062. // --                   HUD_init_message(HM_DEFAULT, "Toasted the BOSS!");
  1063.  
  1064.         // Toast the buddy if nothing else toasted!
  1065.         if (dead_count == 0)
  1066.                 range_for (const auto &&objp, vmobjptr)
  1067.                 {
  1068.                         if (objp->type == OBJ_ROBOT)
  1069.                                 if (Robot_info[get_robot_id(objp)].companion) {
  1070.                                         objp->flags |= OF_EXPLODING|OF_SHOULD_BE_DEAD;
  1071.                                         HUD_init_message_literal(HM_DEFAULT, "Toasted the Buddy! *sniff*");
  1072.                                         dead_count++;
  1073.                                 }
  1074.                 }
  1075.  
  1076.         HUD_init_message(HM_DEFAULT, "%i robots toasted!", dead_count);
  1077. }
  1078. #endif
  1079.  
  1080. //      --------------------------------------------------------------------------
  1081. //      Detonate reactor.
  1082. //      Award player all powerups in mine.
  1083. //      Place player just outside exit.
  1084. //      Kill all bots in mine.
  1085. //      Yippee!!
  1086. static void kill_and_so_forth(fvmobjptridx &vmobjptridx, fvmsegptridx &vmsegptridx)
  1087. {
  1088.         auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
  1089.         auto &Objects = LevelUniqueObjectState.Objects;
  1090.         auto &Vertices = LevelSharedVertexState.get_vertices();
  1091.         auto &vmobjptr = Objects.vmptr;
  1092.         HUD_init_message_literal(HM_DEFAULT, "Killing, awarding, etc.!");
  1093.  
  1094.         range_for (const auto &&o, vmobjptridx)
  1095.         {
  1096.                 switch (o->type) {
  1097.                         case OBJ_ROBOT:
  1098.                                 apply_damage_to_robot(o, o->shields + 1, get_local_player().objnum);
  1099.                                 break;
  1100.                         case OBJ_POWERUP:
  1101.                                 do_powerup(o);
  1102.                                 break;
  1103.                         default:
  1104.                                 break;
  1105.                 }
  1106.         }
  1107.  
  1108.         do_controlcen_destroyed_stuff(object_none);
  1109.  
  1110.         auto &Triggers = LevelUniqueWallSubsystemState.Triggers;
  1111.         auto &vctrgptr = Triggers.vcptr;
  1112.         auto &Walls = LevelUniqueWallSubsystemState.Walls;
  1113.         auto &vcwallptr = Walls.vcptr;
  1114.         for (trgnum_t i = 0; i < Triggers.get_count(); i++)
  1115.         {
  1116.                 const auto &&t = vctrgptr(i);
  1117.                 if (trigger_is_exit(t))
  1118.                 {
  1119.                         range_for (const auto &&wp, vcwallptr)
  1120.                         {
  1121.                                 auto &w = *wp;
  1122.                                 if (w.trigger == i)
  1123.                                 {
  1124.                                         const auto &&segp = vmsegptridx(w.segnum);
  1125.                                         auto &vcvertptr = Vertices.vcptr;
  1126.                                         compute_segment_center(vcvertptr, ConsoleObject->pos, segp);
  1127.                                         obj_relink(vmobjptr, vmsegptr, vmobjptridx(ConsoleObject), segp);
  1128.                                         return;
  1129.                                 }
  1130.                         }
  1131.                 }
  1132.         }
  1133. }
  1134.  
  1135. #ifndef RELEASE
  1136. #if defined(DXX_BUILD_DESCENT_II)
  1137. static void kill_all_snipers(void) __attribute_used;
  1138. static void kill_all_snipers(void)
  1139. {
  1140.         auto &Objects = LevelUniqueObjectState.Objects;
  1141.         auto &vmobjptr = Objects.vmptr;
  1142.         int     dead_count=0;
  1143.  
  1144.         //      Kill all snipers.
  1145.         range_for (const auto &&objp, vmobjptr)
  1146.         {
  1147.                 if (objp->type == OBJ_ROBOT)
  1148.                         if (objp->ctype.ai_info.behavior == ai_behavior::AIB_SNIPE)
  1149.                         {
  1150.                                 dead_count++;
  1151.                                 objp->flags |= OF_EXPLODING|OF_SHOULD_BE_DEAD;
  1152.                         }
  1153.         }
  1154.  
  1155.         HUD_init_message(HM_DEFAULT, "%i robots toasted!", dead_count);
  1156. }
  1157.  
  1158. static void kill_thief(void) __attribute_used;
  1159. static void kill_thief(void)
  1160. {
  1161.         auto &Objects = LevelUniqueObjectState.Objects;
  1162.         auto &vmobjptr = Objects.vmptr;
  1163.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  1164.         //      Kill thief.
  1165.         range_for (const auto &&objp, vmobjptr)
  1166.         {
  1167.                 if (objp->type == OBJ_ROBOT)
  1168.                         if (Robot_info[get_robot_id(objp)].thief)
  1169.                         {
  1170.                                 objp->flags |= OF_EXPLODING|OF_SHOULD_BE_DEAD;
  1171.                                 HUD_init_message_literal(HM_DEFAULT, "Thief toasted!");
  1172.                         }
  1173.         }
  1174. }
  1175.  
  1176. static void kill_buddy(void) __attribute_used;
  1177. static void kill_buddy(void)
  1178. {
  1179.         auto &Objects = LevelUniqueObjectState.Objects;
  1180.         auto &vmobjptr = Objects.vmptr;
  1181.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  1182.         //      Kill buddy.
  1183.         range_for (const auto &&objp, vmobjptr)
  1184.         {
  1185.                 if (objp->type == OBJ_ROBOT)
  1186.                         if (Robot_info[get_robot_id(objp)].companion)
  1187.                         {
  1188.                                 objp->flags |= OF_EXPLODING|OF_SHOULD_BE_DEAD;
  1189.                                 HUD_init_message_literal(HM_DEFAULT, "Buddy toasted!");
  1190.                         }
  1191.         }
  1192. }
  1193. #endif
  1194.  
  1195. namespace dsx {
  1196. static window_event_result HandleTestKey(fvmsegptridx &vmsegptridx, int key)
  1197. {
  1198.         auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
  1199.         auto &LevelUniqueMorphObjectState = LevelUniqueObjectState.MorphObjectState;
  1200.         auto &Objects = LevelUniqueObjectState.Objects;
  1201.         auto &Vertices = LevelSharedVertexState.get_vertices();
  1202.         auto &vmobjptr = Objects.vmptr;
  1203.         auto &vmobjptridx = Objects.vmptridx;
  1204.         switch (key)
  1205.         {
  1206.  
  1207. #ifdef SHOW_EXIT_PATH
  1208.                 case KEY_DEBUGGED+KEY_1:        create_special_path();  break;
  1209. #endif
  1210.  
  1211.                 case KEY_DEBUGGED+KEY_Y:
  1212.                         do_controlcen_destroyed_stuff(object_none);
  1213.                         break;
  1214.  
  1215. #if defined(DXX_BUILD_DESCENT_II)
  1216.         case KEY_DEBUGGED+KEY_ALTED+KEY_D:
  1217.                         PlayerCfg.NetlifeKills=4000; PlayerCfg.NetlifeKilled=5;
  1218.                         multi_add_lifetime_kills(1);
  1219.                         break;
  1220.  
  1221.                 case KEY_DEBUGGED+KEY_R+KEY_SHIFTED:
  1222.                         kill_all_robots();
  1223.                         break;
  1224. #endif
  1225.  
  1226.                 case KEY_BACKSP:
  1227.                 case KEY_CTRLED+KEY_BACKSP:
  1228.                 case KEY_ALTED+KEY_BACKSP:
  1229.                 case KEY_SHIFTED+KEY_BACKSP:
  1230.                 case KEY_SHIFTED+KEY_ALTED+KEY_BACKSP:
  1231.                 case KEY_CTRLED+KEY_ALTED+KEY_BACKSP:
  1232.                 case KEY_SHIFTED+KEY_CTRLED+KEY_BACKSP:
  1233.                 case KEY_SHIFTED+KEY_CTRLED+KEY_ALTED+KEY_BACKSP:
  1234.  
  1235.                         Int3(); break;
  1236.  
  1237.                 case KEY_DEBUGGED+KEY_S:                                digi_reset(); break;
  1238.  
  1239.                 case KEY_DEBUGGED+KEY_P:
  1240.                         if (Game_suspended & SUSP_ROBOTS)
  1241.                                 Game_suspended &= ~SUSP_ROBOTS;         //robots move
  1242.                         else
  1243.                                 Game_suspended |= SUSP_ROBOTS;          //robots don't move
  1244.                         break;
  1245.                 case KEY_DEBUGGED+KEY_M:
  1246.                 {
  1247.                         static int i = 0;
  1248.                         const auto &&segp = vmsegptridx(ConsoleObject->segnum);
  1249.                         auto &vcvertptr = Vertices.vcptr;
  1250.                         const auto &&new_obj = create_morph_robot(segp, compute_segment_center(vcvertptr, segp), i);
  1251.                         if (new_obj != object_none)
  1252.                                 morph_start(LevelUniqueMorphObjectState, LevelSharedPolygonModelState, new_obj);
  1253.                         i++;
  1254.                         if (i >= LevelSharedRobotInfoState.N_robot_types)
  1255.                                 i = 0;
  1256.                         break;
  1257.                 }
  1258.                 case KEY_DEBUGGED+KEY_K:
  1259.                         get_local_plrobj().shields = 1;
  1260.                         break;                                                  //      a virtual kill
  1261.                 case KEY_DEBUGGED+KEY_SHIFTED + KEY_K:
  1262.                         get_local_plrobj().shields = -1;
  1263.                         break;  //      an actual kill
  1264.                 case KEY_DEBUGGED+KEY_X: get_local_player().lives++; break; // Extra life cheat key.
  1265.                 case KEY_DEBUGGED+KEY_H:
  1266.                 {
  1267.                         if (Player_dead_state != player_dead_state::no)
  1268.                                 return window_event_result::ignored;
  1269.  
  1270.                         auto &player_info = get_local_plrobj().ctype.player_info;
  1271.                         auto &pl_flags = player_info.powerup_flags;
  1272.                         pl_flags ^= PLAYER_FLAGS_CLOAKED;
  1273.                         if (pl_flags & PLAYER_FLAGS_CLOAKED) {
  1274.                                 if (Game_mode & GM_MULTI)
  1275.                                         multi_send_cloak();
  1276.                                 ai_do_cloak_stuff();
  1277.                                 player_info.cloak_time = GameTime64;
  1278.                         }
  1279.                         break;
  1280.                 }
  1281.  
  1282.                 case KEY_DEBUGGED+KEY_R:
  1283.                         cheats.robotfiringsuspended = !cheats.robotfiringsuspended;
  1284.                         break;
  1285.  
  1286. #if DXX_USE_EDITOR              //editor-specific functions
  1287.  
  1288.                 case KEY_E + KEY_DEBUGGED:
  1289.                 {
  1290.                         window_set_visible(Game_wind, 0);       // don't let the game do anything while we set the editor up
  1291.                         auto old_gamestate = gamestate;
  1292.                         gamestate = editor_gamestate::unsaved;  // saved game editing mode
  1293.  
  1294.                         init_editor();
  1295.                         // If editor failed to load, carry on playing
  1296.                         if (!EditorWindow)
  1297.                         {
  1298.                                 window_set_visible(Game_wind, 1);
  1299.                                 gamestate = old_gamestate;
  1300.                                 return window_event_result::handled;
  1301.                         }
  1302.                         return window_event_result::close;
  1303.                 }
  1304.  
  1305. #if defined(DXX_BUILD_DESCENT_II)
  1306.         case KEY_Q + KEY_SHIFTED + KEY_DEBUGGED:
  1307.                 {
  1308.                         palette_array_t save_pal;
  1309.                         save_pal = gr_palette;
  1310.                         PlayMovie ("end.tex", "end.mve",MOVIE_ABORT_ON);
  1311.                         Screen_mode = -1;
  1312.                         set_screen_mode(SCREEN_GAME);
  1313.                         reset_cockpit();
  1314.                         gr_palette = save_pal;
  1315.                         gr_palette_load(gr_palette);
  1316.                         break;
  1317.                 }
  1318. #endif
  1319.                 case KEY_C + KEY_SHIFTED + KEY_DEBUGGED:
  1320.                         if (Player_dead_state == player_dead_state::no &&
  1321.                                 !(Game_mode & GM_MULTI))
  1322.                                 move_player_2_segment(Cursegp,Curside);
  1323.                         break;   //move eye to curseg
  1324.  
  1325.  
  1326.                 case KEY_DEBUGGED+KEY_W:        draw_world_from_game(); break;
  1327.  
  1328.                 #endif  //#ifdef EDITOR
  1329.  
  1330.                 case KEY_DEBUGGED+KEY_LAPOSTRO: Show_view_text_timer = 0x30000; object_goto_next_viewer(); break;
  1331.                 case KEY_DEBUGGED+KEY_SHIFTED+KEY_LAPOSTRO: Viewer=ConsoleObject; break;
  1332.                 case KEY_DEBUGGED+KEY_O: toggle_outline_mode(); break;
  1333.                 case KEY_DEBUGGED+KEY_T:
  1334. #if defined(DXX_BUILD_DESCENT_II)
  1335.                 {
  1336.                         static int Toggle_var;
  1337.                         Toggle_var = !Toggle_var;
  1338.                         if (Toggle_var)
  1339.                                 CGameArg.SysMaxFPS = 300;
  1340.                         else
  1341.                                 CGameArg.SysMaxFPS = 30;
  1342.                 }
  1343. #endif
  1344.                         break;
  1345.                 case KEY_DEBUGGED + KEY_L:
  1346. #if !DXX_USE_OGL
  1347.                         if (++Lighting_on >= 2)
  1348.                                 Lighting_on = 0;
  1349. #endif
  1350.                         break;
  1351.                 case KEY_PAD5: slew_stop(); break;
  1352.  
  1353. #ifndef NDEBUG
  1354.                 case KEY_DEBUGGED + KEY_F11: play_test_sound(); break;
  1355. #endif
  1356.  
  1357.                 case KEY_DEBUGGED + KEY_C:
  1358.                         do_cheat_menu();
  1359.                         break;
  1360.                 case KEY_DEBUGGED + KEY_SHIFTED + KEY_A:
  1361.                         do_megawow_powerup(10);
  1362.                         break;
  1363.                 case KEY_DEBUGGED + KEY_A:      {
  1364.                         do_megawow_powerup(200);
  1365.                                 break;
  1366.                 }
  1367.  
  1368.                 case KEY_DEBUGGED+KEY_SPACEBAR:         //KEY_F7:                               // Toggle physics flying
  1369.                         slew_stop();
  1370.                         game_flush_inputs();
  1371.                         if ( ConsoleObject->control_type != CT_FLYING ) {
  1372.                                 fly_init(*ConsoleObject);
  1373.                                 Game_suspended &= ~SUSP_ROBOTS; //robots move
  1374.                         } else {
  1375.                                 slew_init(vmobjptr(ConsoleObject));                     //start player slewing
  1376.                                 Game_suspended |= SUSP_ROBOTS;  //robots don't move
  1377.                         }
  1378.                         break;
  1379.  
  1380.                 case KEY_DEBUGGED+KEY_COMMA: Render_zoom = fixmul(Render_zoom,62259); break;
  1381.                 case KEY_DEBUGGED+KEY_PERIOD: Render_zoom = fixmul(Render_zoom,68985); break;
  1382.  
  1383.                 #ifndef NDEBUG
  1384.                 case KEY_DEBUGGED+KEY_D:
  1385.                         if ((CGameArg.DbgNoDoubleBuffer = !CGameArg.DbgNoDoubleBuffer)!=0)
  1386.                                 init_cockpit();
  1387.                         break;
  1388.                 #endif
  1389.  
  1390. #if DXX_USE_EDITOR
  1391.                 case KEY_DEBUGGED+KEY_Q:
  1392.                         {
  1393.                                 pause_game_world_time p;
  1394.                         dump_used_textures_all();
  1395.                         }
  1396.                         break;
  1397. #endif
  1398.  
  1399.                 case KEY_DEBUGGED+KEY_B: {
  1400.                         d_fname text{};
  1401.                         int item;
  1402.                         std::array<newmenu_item, 1> m{{
  1403.                                 nm_item_input(text),
  1404.                         }};
  1405.                         item = newmenu_do( NULL, "Briefing to play?", m, unused_newmenu_subfunction, unused_newmenu_userdata);
  1406.                         if (item != -1) {
  1407.                                 do_briefing_screens(text,1);
  1408.                         }
  1409.                         break;
  1410.                 }
  1411.  
  1412.                 case KEY_DEBUGGED+KEY_SHIFTED+KEY_B:
  1413.                         if (Player_dead_state != player_dead_state::no)
  1414.                                 return window_event_result::ignored;
  1415.  
  1416.                         kill_and_so_forth(vmobjptridx, vmsegptridx);
  1417.                         break;
  1418.                 case KEY_DEBUGGED+KEY_G:
  1419.                         GameTime64 = (INT64_MAX) - (F1_0*10);
  1420.                         HUD_init_message(HM_DEFAULT, "GameTime %" PRIi64 " - Reset in 10 seconds!", GameTime64);
  1421.                         break;
  1422.                 default:
  1423.                         return window_event_result::ignored;
  1424.         }
  1425.         return window_event_result::handled;
  1426. }
  1427. }
  1428. #endif          //#ifndef RELEASE
  1429.  
  1430. #define CHEAT_MAX_LEN 15
  1431. struct cheat_code
  1432. {
  1433.         const char string[CHEAT_MAX_LEN];
  1434.         int game_cheats::*stateptr;
  1435. };
  1436.  
  1437. constexpr cheat_code cheat_codes[] = {
  1438. #if defined(DXX_BUILD_DESCENT_I)
  1439.         { "gabbagabbahey", &game_cheats::enabled },
  1440.         { "scourge", &game_cheats::wowie },
  1441.         { "bigred", &game_cheats::wowie2 },
  1442.         { "mitzi", &game_cheats::allkeys },
  1443.         { "racerx", &game_cheats::invul },
  1444.         { "guile", &game_cheats::cloak },
  1445.         { "twilight", &game_cheats::shields },
  1446.         { "poboys", &game_cheats::killreactor },
  1447.         { "farmerjoe", &game_cheats::levelwarp },
  1448.         { "bruin", &game_cheats::extralife },
  1449.         { "porgys", &game_cheats::rapidfire },
  1450.         { "ahimsa", &game_cheats::robotfiringsuspended },
  1451.         { "baldguy", &game_cheats::baldguy },
  1452. #elif defined(DXX_BUILD_DESCENT_II)
  1453.         { "gabbagabbahey", &game_cheats::lamer },
  1454.         { "motherlode", &game_cheats::lamer },
  1455.         { "currygoat", &game_cheats::lamer },
  1456.         { "zingermans", &game_cheats::lamer },
  1457.         { "eatangelos", &game_cheats::lamer },
  1458.         { "ericaanne", &game_cheats::lamer },
  1459.         { "joshuaakira", &game_cheats::lamer },
  1460.         { "whammazoom", &game_cheats::lamer },
  1461.         { "honestbob", &game_cheats::wowie },
  1462.         { "oralgroove", &game_cheats::allkeys },
  1463.         { "alifalafel", &game_cheats::accessory },
  1464.         { "almighty", &game_cheats::invul },
  1465.         { "blueorb", &game_cheats::shields },
  1466.         { "delshiftb", &game_cheats::killreactor },
  1467.         { "freespace", &game_cheats::levelwarp },
  1468.         { "rockrgrl", &game_cheats::fullautomap },
  1469.         { "wildfire", &game_cheats::rapidfire },
  1470.         { "duddaboo", &game_cheats::bouncyfire },
  1471.         { "lpnlizard", &game_cheats::homingfire },
  1472.         { "imagespace", &game_cheats::robotfiringsuspended },
  1473.         { "spaniard", &game_cheats::killallrobots },
  1474.         { "silkwing", &game_cheats::robotskillrobots },
  1475.         { "godzilla", &game_cheats::monsterdamage },
  1476.         { "helpvishnu", &game_cheats::buddyclone },
  1477.         { "gowingnut", &game_cheats::buddyangry },
  1478. #endif
  1479.         { "flash", &game_cheats::exitpath },
  1480.         { "astral", &game_cheats::ghostphysics },
  1481.         { "buggin", &game_cheats::turbo },
  1482.         { "bittersweet", &game_cheats::acid },
  1483. };
  1484.  
  1485. namespace dsx {
  1486. static window_event_result FinalCheats()
  1487. {
  1488.         auto &Objects = LevelUniqueObjectState.Objects;
  1489.         auto &vmobjptr = Objects.vmptr;
  1490.         auto &vmobjptridx = Objects.vmptridx;
  1491.         int game_cheats::*gotcha;
  1492.  
  1493.         if (Game_mode & GM_MULTI)
  1494.                 return window_event_result::ignored;
  1495.  
  1496.         static std::array<char, CHEAT_MAX_LEN> cheat_buffer;
  1497.         std::move(std::next(cheat_buffer.begin()), cheat_buffer.end(), cheat_buffer.begin());
  1498.         cheat_buffer.back() = key_ascii();
  1499.         for (unsigned i = 0;; i++)
  1500.         {
  1501.                 if (i >= std::size(cheat_codes))
  1502.                         return window_event_result::ignored;
  1503.                 int cheatlen = strlen(cheat_codes[i].string);
  1504.                 Assert(cheatlen <= CHEAT_MAX_LEN);
  1505.                 if (d_strnicmp(cheat_codes[i].string, &cheat_buffer[CHEAT_MAX_LEN-cheatlen], cheatlen)==0)
  1506.                 {
  1507.                         gotcha = cheat_codes[i].stateptr;
  1508. #if defined(DXX_BUILD_DESCENT_I)
  1509.                         if (!cheats.enabled && gotcha != &game_cheats::enabled)
  1510.                                 return window_event_result::ignored;
  1511.                         if (!cheats.enabled)
  1512.                                 HUD_init_message_literal(HM_DEFAULT, TXT_CHEATS_ENABLED);
  1513. #endif
  1514.                         cheats.*gotcha = !(cheats.*gotcha);
  1515.                         cheats.enabled = 1;
  1516.                         digi_play_sample( SOUND_CHEATER, F1_0);
  1517.                         break;
  1518.                 }
  1519.         }
  1520.         auto &plrobj = get_local_plrobj();
  1521.         auto &player_info = plrobj.ctype.player_info;
  1522.         player_info.mission.score = 0;
  1523.  
  1524. #if defined(DXX_BUILD_DESCENT_I)
  1525.         if (gotcha == &game_cheats::wowie)
  1526.         {
  1527.                 HUD_init_message_literal(HM_DEFAULT, TXT_WOWIE_ZOWIE);
  1528.  
  1529.                 player_info.primary_weapon_flags |= (HAS_LASER_FLAG | HAS_VULCAN_FLAG | HAS_SPREADFIRE_FLAG);
  1530.  
  1531.                 player_info.vulcan_ammo = VULCAN_AMMO_MAX;
  1532.                 auto &secondary_ammo = player_info.secondary_ammo;
  1533.                 range_for (const unsigned i, xrange(3u))
  1534.                         secondary_ammo[i] = Secondary_ammo_max[i];
  1535.  
  1536.                 if (Newdemo_state == ND_STATE_RECORDING)
  1537.                         newdemo_record_laser_level(player_info.laser_level, MAX_LASER_LEVEL);
  1538.  
  1539.                 player_info.energy = MAX_ENERGY;
  1540.                 player_info.laser_level = MAX_LASER_LEVEL;
  1541.                 player_info.powerup_flags |= PLAYER_FLAGS_QUAD_LASERS;
  1542.                 update_laser_weapon_info();
  1543.         }
  1544.  
  1545.         if (gotcha == &game_cheats::wowie2)
  1546.         {
  1547.                 HUD_init_message(HM_DEFAULT, "SUPER %s",TXT_WOWIE_ZOWIE);
  1548.  
  1549.                 player_info.primary_weapon_flags = (HAS_LASER_FLAG | HAS_VULCAN_FLAG | HAS_SPREADFIRE_FLAG | HAS_PLASMA_FLAG | HAS_FUSION_FLAG);
  1550.  
  1551.                 player_info.vulcan_ammo = VULCAN_AMMO_MAX;
  1552.                 auto &secondary_ammo = player_info.secondary_ammo;
  1553.                 secondary_ammo = Secondary_ammo_max;
  1554.  
  1555.                 if (Newdemo_state == ND_STATE_RECORDING)
  1556.                         newdemo_record_laser_level(player_info.laser_level, MAX_LASER_LEVEL);
  1557.  
  1558.                 player_info.energy = MAX_ENERGY;
  1559.                 player_info.laser_level = MAX_LASER_LEVEL;
  1560.                 player_info.powerup_flags |= PLAYER_FLAGS_QUAD_LASERS;
  1561.                 update_laser_weapon_info();
  1562.         }
  1563. #elif defined(DXX_BUILD_DESCENT_II)
  1564.         if (gotcha == &game_cheats::lamer)
  1565.         {
  1566.                 plrobj.shields = player_info.energy = i2f(1);
  1567.                 HUD_init_message_literal(HM_DEFAULT, "Take that...cheater!");
  1568.         }
  1569.  
  1570.         if (gotcha == &game_cheats::wowie)
  1571.         {
  1572.                 HUD_init_message_literal(HM_DEFAULT, TXT_WOWIE_ZOWIE);
  1573.  
  1574.                 if (Piggy_hamfile_version < 3) // SHAREWARE
  1575.                 {
  1576.                         player_info.primary_weapon_flags |= (HAS_LASER_FLAG | HAS_VULCAN_FLAG | HAS_SPREADFIRE_FLAG | HAS_PLASMA_FLAG) | (HAS_GAUSS_FLAG | HAS_HELIX_FLAG);
  1577.                 }
  1578.                 else
  1579.                 {
  1580.                         player_info.primary_weapon_flags = (HAS_LASER_FLAG | HAS_VULCAN_FLAG | HAS_SPREADFIRE_FLAG | HAS_PLASMA_FLAG | HAS_FUSION_FLAG) | (HAS_GAUSS_FLAG | HAS_HELIX_FLAG | HAS_PHOENIX_FLAG | HAS_OMEGA_FLAG);
  1581.                 }
  1582.  
  1583.                 player_info.vulcan_ammo = VULCAN_AMMO_MAX;
  1584.                 auto &secondary_ammo = player_info.secondary_ammo;
  1585.                 secondary_ammo = Secondary_ammo_max;
  1586.  
  1587.                 if (Piggy_hamfile_version < 3) // SHAREWARE
  1588.                 {
  1589.                         secondary_ammo[SMISSILE4_INDEX] = 0;
  1590.                         secondary_ammo[SMISSILE5_INDEX] = 0;
  1591.                         secondary_ammo[MEGA_INDEX] = 0;
  1592.                 }
  1593.  
  1594.                 if (Newdemo_state == ND_STATE_RECORDING)
  1595.                         newdemo_record_laser_level(player_info.laser_level, MAX_SUPER_LASER_LEVEL);
  1596.  
  1597.                 player_info.energy = MAX_ENERGY;
  1598.                 player_info.laser_level = MAX_SUPER_LASER_LEVEL;
  1599.                 player_info.powerup_flags |= PLAYER_FLAGS_QUAD_LASERS;
  1600.                 update_laser_weapon_info();
  1601.         }
  1602.  
  1603.         if (gotcha == &game_cheats::accessory)
  1604.         {
  1605.                 player_info.powerup_flags |= PLAYER_FLAGS_HEADLIGHT | PLAYER_FLAGS_AFTERBURNER | PLAYER_FLAGS_AMMO_RACK | PLAYER_FLAGS_CONVERTER;
  1606.                 HUD_init_message_literal(HM_DEFAULT, "Accessories!!");
  1607.         }
  1608. #endif
  1609.  
  1610.         if (gotcha == &game_cheats::allkeys)
  1611.         {
  1612.                 HUD_init_message_literal(HM_DEFAULT, TXT_ALL_KEYS);
  1613.                 player_info.powerup_flags |= PLAYER_FLAGS_BLUE_KEY | PLAYER_FLAGS_RED_KEY | PLAYER_FLAGS_GOLD_KEY;
  1614.         }
  1615.  
  1616.         if (gotcha == &game_cheats::invul)
  1617.         {
  1618.                 player_info.invulnerable_time = GameTime64+i2f(1000);
  1619.                 auto &pl_flags = player_info.powerup_flags;
  1620.                 pl_flags ^= PLAYER_FLAGS_INVULNERABLE;
  1621.                 HUD_init_message(HM_DEFAULT, "%s %s!", TXT_INVULNERABILITY, (pl_flags & PLAYER_FLAGS_INVULNERABLE) ? (player_info.FakingInvul = 0, TXT_ON) : TXT_OFF);
  1622.         }
  1623.  
  1624.         if (gotcha == &game_cheats::shields)
  1625.         {
  1626.                 HUD_init_message_literal(HM_DEFAULT, TXT_FULL_SHIELDS);
  1627.                 plrobj.shields = MAX_SHIELDS;
  1628.         }
  1629.  
  1630. #if defined(DXX_BUILD_DESCENT_I)
  1631.         if (gotcha == &game_cheats::cloak)
  1632.         {
  1633.                 auto &pl_flags = player_info.powerup_flags;
  1634.                 pl_flags ^= PLAYER_FLAGS_CLOAKED;
  1635.                 const auto have_cloaked = pl_flags & PLAYER_FLAGS_CLOAKED;
  1636.                 HUD_init_message(HM_DEFAULT, "%s %s!", TXT_CLOAK, have_cloaked ? TXT_ON : TXT_OFF);
  1637.                 if (have_cloaked)
  1638.                 {
  1639.                         ai_do_cloak_stuff();
  1640.                         player_info.cloak_time = GameTime64;
  1641.                 }
  1642.         }
  1643.  
  1644.         if (gotcha == &game_cheats::extralife)
  1645.         {
  1646.                 auto &plr = get_local_player();
  1647.                 if (plr.lives < 50)
  1648.                 {
  1649.                         plr.lives++;
  1650.                         HUD_init_message_literal(HM_DEFAULT, "Extra life!");
  1651.                 }
  1652.         }
  1653. #endif
  1654.  
  1655.         if (gotcha == &game_cheats::killreactor)
  1656.         {
  1657.                 kill_and_so_forth(vmobjptridx, vmsegptridx);
  1658.         }
  1659.  
  1660.         if (gotcha == &game_cheats::exitpath)
  1661.         {
  1662.                 if (create_special_path())
  1663.                         HUD_init_message_literal(HM_DEFAULT, "Exit path illuminated!");
  1664.         }
  1665.  
  1666.         if (gotcha == &game_cheats::levelwarp)
  1667.         {
  1668.                 char text[10]="";
  1669.                 int new_level_num;
  1670.                 int item;
  1671.                 std::array<newmenu_item, 1> m{{
  1672.                         nm_item_input(text),
  1673.                 }};
  1674.                 item = newmenu_do( NULL, TXT_WARP_TO_LEVEL, m, unused_newmenu_subfunction, unused_newmenu_userdata);
  1675.                 if (item != -1) {
  1676.                         new_level_num = atoi(m[0].text);
  1677.                         if (new_level_num!=0 && new_level_num>=0 && new_level_num<=Last_level) {
  1678.                                 window_set_visible(Game_wind, 0);
  1679.                                 StartNewLevel(new_level_num);
  1680.                                 window_set_visible(Game_wind, 1);
  1681.                         }
  1682.                 }
  1683.         }
  1684.  
  1685. #if defined(DXX_BUILD_DESCENT_II)
  1686.         if (gotcha == &game_cheats::fullautomap)
  1687.                 HUD_init_message_literal(HM_DEFAULT, cheats.fullautomap ? "FULL MAP!" : "REGULAR MAP");
  1688. #endif
  1689.  
  1690.         if (gotcha == &game_cheats::ghostphysics)
  1691.         {
  1692.                 HUD_init_message(HM_DEFAULT, "%s %s!", "Ghosty mode", cheats.ghostphysics?TXT_ON:TXT_OFF);
  1693.         }
  1694.  
  1695.         if (gotcha == &game_cheats::rapidfire)
  1696.         {
  1697.                 HUD_init_message(HM_DEFAULT, "Rapid fire %s!", cheats.rapidfire?TXT_ON:TXT_OFF);
  1698. #if defined(DXX_BUILD_DESCENT_I)
  1699.                 if (cheats.rapidfire)
  1700.                         do_megawow_powerup(200);
  1701. #endif
  1702.         }
  1703.  
  1704. #if defined(DXX_BUILD_DESCENT_II)
  1705.         if (gotcha == &game_cheats::bouncyfire)
  1706.         {
  1707.                
  1708.                 HUD_init_message(HM_DEFAULT, "Bouncing weapons %s!", cheats.bouncyfire?TXT_ON:TXT_OFF);
  1709.         }
  1710.  
  1711.         if (gotcha == &game_cheats::homingfire)
  1712.         {
  1713.                 HUD_init_message(HM_DEFAULT, "Homing weapons %s!", cheats.homingfire ? (weapons_homing_all(), TXT_ON) : (weapons_homing_all_reset(), TXT_OFF));
  1714.         }
  1715. #endif
  1716.  
  1717.         if (gotcha == &game_cheats::turbo)
  1718.         {
  1719.                 HUD_init_message(HM_DEFAULT, "%s %s!", "Turbo mode", cheats.turbo?TXT_ON:TXT_OFF);
  1720.         }
  1721.  
  1722.         if (gotcha == &game_cheats::robotfiringsuspended)
  1723.         {
  1724.                 HUD_init_message(HM_DEFAULT, "Robot firing %s!", cheats.robotfiringsuspended?TXT_OFF:TXT_ON);
  1725.         }
  1726.  
  1727. #if defined(DXX_BUILD_DESCENT_II)
  1728.         if (gotcha == &game_cheats::killallrobots)
  1729.         {
  1730.                 kill_all_robots();
  1731.         }
  1732.  
  1733.         if (gotcha == &game_cheats::robotskillrobots)
  1734.         {
  1735.                 HUD_init_message_literal(HM_DEFAULT, cheats.robotskillrobots?"Rabid robots!":"Kill the player!");
  1736.         }
  1737.  
  1738.         if (gotcha == &game_cheats::monsterdamage)
  1739.         {
  1740.                 HUD_init_message_literal(HM_DEFAULT, cheats.monsterdamage?"Oh no, there goes Tokyo!":"What have you done, I'm shrinking!!");
  1741.         }
  1742.  
  1743.         if (gotcha == &game_cheats::buddyclone)
  1744.         {
  1745.                 HUD_init_message_literal(HM_DEFAULT, "What's this? Another buddy bot!");
  1746.                 create_buddy_bot();
  1747.         }
  1748.  
  1749.         if (gotcha == &game_cheats::buddyangry)
  1750.         {
  1751.                
  1752.                 if (cheats.buddyangry)
  1753.                 {
  1754.                         HUD_init_message(HM_DEFAULT, "%s gets angry!", static_cast<const char *>(PlayerCfg.GuidebotName));
  1755.                         PlayerCfg.GuidebotName = "Wingnut";
  1756.                 }
  1757.                 else
  1758.                 {
  1759.                         PlayerCfg.GuidebotName = PlayerCfg.GuidebotNameReal;
  1760.                         HUD_init_message(HM_DEFAULT, "%s calms down", static_cast<const char *>(PlayerCfg.GuidebotName));
  1761.                 }
  1762.         }
  1763. #endif
  1764.  
  1765.         if (gotcha == &game_cheats::acid)
  1766.         {
  1767.                 HUD_init_message_literal(HM_DEFAULT, cheats.acid?"Going up!":"Coming down!");
  1768.         }
  1769.  
  1770.         return window_event_result::handled;
  1771. }
  1772. }
  1773.  
  1774. // Internal Cheat Menu
  1775. #ifndef RELEASE
  1776.  
  1777. namespace {
  1778.  
  1779. class menu_fix_wrapper
  1780. {
  1781.         fix &m_value;
  1782. public:
  1783.         constexpr menu_fix_wrapper(fix &t) :
  1784.                 m_value(t)
  1785.         {
  1786.         }
  1787.         operator int() const
  1788.         {
  1789.                 return f2i(m_value);
  1790.         }
  1791.         menu_fix_wrapper &operator=(int n)
  1792.         {
  1793.                 m_value = i2f(n);
  1794.                 return *this;
  1795.         }
  1796. };
  1797.  
  1798. class cheat_menu_bit_invulnerability :
  1799.         std::reference_wrapper<player_info>,
  1800.         public menu_bit_wrapper_t<player_flags, std::integral_constant<PLAYER_FLAG, PLAYER_FLAGS_INVULNERABLE>>
  1801. {
  1802. public:
  1803.         cheat_menu_bit_invulnerability(object &player) :
  1804.                 reference_wrapper(player.ctype.player_info),
  1805.                 menu_bit_wrapper_t(get().powerup_flags, {})
  1806.         {
  1807.         }
  1808.         cheat_menu_bit_invulnerability &operator=(const uint32_t n)
  1809.         {
  1810.                 this->menu_bit_wrapper_t::operator=(n);
  1811.                 if (n)
  1812.                 {
  1813.                         auto &player_info = get();
  1814.                         player_info.FakingInvul = 0;
  1815.                         player_info.invulnerable_time = GameTime64+i2f(1000);
  1816.                 }
  1817.                 return *this;
  1818.         }
  1819. };
  1820.  
  1821. class cheat_menu_bit_cloak :
  1822.         std::reference_wrapper<player_info>,
  1823.         public menu_bit_wrapper_t<player_flags, std::integral_constant<PLAYER_FLAG, PLAYER_FLAGS_CLOAKED>>
  1824. {
  1825. public:
  1826.         cheat_menu_bit_cloak(object &player) :
  1827.                 reference_wrapper(player.ctype.player_info),
  1828.                 menu_bit_wrapper_t(get().powerup_flags, {})
  1829.         {
  1830.         }
  1831.         cheat_menu_bit_cloak &operator=(const uint32_t n)
  1832.         {
  1833.                 this->menu_bit_wrapper_t::operator=(n);
  1834.                 if (n)
  1835.                 {
  1836.                         if (Game_mode & GM_MULTI)
  1837.                                 multi_send_cloak();
  1838.                         ai_do_cloak_stuff();
  1839.                         get().cloak_time = GameTime64;
  1840.                 }
  1841.                 return *this;
  1842.         }
  1843. };
  1844.  
  1845. }
  1846.  
  1847. #if defined(DXX_BUILD_DESCENT_I)
  1848. #define WIMP_MENU_DXX(VERB)
  1849. #elif defined(DXX_BUILD_DESCENT_II)
  1850. /* Adding an afterburner like this adds it at 0% charge.  This is OK for
  1851.  * a cheat.  The player can change his energy up if he needs more.
  1852.  */
  1853. #define WIMP_MENU_DXX(VERB)     \
  1854.         DXX_MENUITEM(VERB, CHECK, TXT_AFTERBURNER, opt_afterburner, menu_bit_wrapper(player_info.powerup_flags, PLAYER_FLAGS_AFTERBURNER))      \
  1855.  
  1856. #endif
  1857.  
  1858. #define DXX_WIMP_MENU(VERB)     \
  1859.         DXX_MENUITEM(VERB, CHECK, TXT_INVULNERABILITY, opt_invul, cheat_menu_bit_invulnerability(plrobj))       \
  1860.         DXX_MENUITEM(VERB, CHECK, TXT_CLOAKED, opt_cloak, cheat_menu_bit_cloak(plrobj)) \
  1861.         DXX_MENUITEM(VERB, CHECK, "BLUE KEY", opt_key_blue, menu_bit_wrapper(player_info.powerup_flags, PLAYER_FLAGS_BLUE_KEY)) \
  1862.         DXX_MENUITEM(VERB, CHECK, "GOLD KEY", opt_key_gold, menu_bit_wrapper(player_info.powerup_flags, PLAYER_FLAGS_GOLD_KEY)) \
  1863.         DXX_MENUITEM(VERB, CHECK, "RED KEY", opt_key_red, menu_bit_wrapper(player_info.powerup_flags, PLAYER_FLAGS_RED_KEY))    \
  1864.         WIMP_MENU_DXX(VERB)     \
  1865.         DXX_MENUITEM(VERB, NUMBER, TXT_ENERGY, opt_energy, menu_fix_wrapper(plrobj.ctype.player_info.energy), 0, 200)   \
  1866.         DXX_MENUITEM(VERB, NUMBER, "Shields", opt_shields, menu_fix_wrapper(plrobj.shields), 0, 200)    \
  1867.         DXX_MENUITEM(VERB, TEXT, TXT_SCORE, opt_txt_score)      \
  1868.         DXX_MENUITEM(VERB, INPUT, score_text, opt_score)        \
  1869.         DXX_MENUITEM(VERB, NUMBER, "Laser Level", opt_laser_level, menu_number_bias_wrapper<1>(plr_laser_level), LASER_LEVEL_1 + 1, DXX_MAXIMUM_LASER_LEVEL + 1)        \
  1870.         DXX_MENUITEM(VERB, NUMBER, "Concussion", opt_concussion, plrobj.ctype.player_info.secondary_ammo[CONCUSSION_INDEX], 0, 200)     \
  1871.  
  1872. static void do_cheat_menu()
  1873. {
  1874.         auto &Objects = LevelUniqueObjectState.Objects;
  1875.         auto &vmobjptr = Objects.vmptr;
  1876.         enum {
  1877.                 DXX_WIMP_MENU(ENUM)
  1878.         };
  1879.         int mmn;
  1880.         std::array<newmenu_item, DXX_WIMP_MENU(COUNT)> m;
  1881.         char score_text[sizeof("2147483647")];
  1882.         auto &plrobj = get_local_plrobj();
  1883.         auto &player_info = plrobj.ctype.player_info;
  1884.         snprintf(score_text, sizeof(score_text), "%d", player_info.mission.score);
  1885.         uint8_t plr_laser_level = player_info.laser_level;
  1886.         DXX_WIMP_MENU(ADD);
  1887.         mmn = newmenu_do("Wimp Menu",NULL,m, unused_newmenu_subfunction, unused_newmenu_userdata);
  1888.         if (mmn > -1 )  {
  1889.                 DXX_WIMP_MENU(READ);
  1890.                 player_info.laser_level = laser_level_t(plr_laser_level);
  1891.                 char *p;
  1892.                 auto ul = strtoul(score_text, &p, 10);
  1893.                 if (!*p)
  1894.                         player_info.mission.score = static_cast<int>(ul);
  1895.                 init_gauges();
  1896.         }
  1897. }
  1898. #endif
  1899.  
  1900.  
  1901.  
  1902. //      Testing functions ----------------------------------------------------------
  1903.  
  1904. #ifndef NDEBUG
  1905. //      Sounds for testing
  1906. __attribute_used
  1907. static int Test_sound;
  1908.  
  1909. static void play_test_sound()
  1910. {
  1911.         digi_play_sample(Test_sound, F1_0);
  1912. }
  1913. #endif  //ifndef NDEBUG
  1914.  
  1915. namespace dsx {
  1916.  
  1917. window_event_result ReadControls(const d_event &event)
  1918. {
  1919.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  1920.         auto &Objects = LevelUniqueObjectState.Objects;
  1921.         int key;
  1922.         static ubyte exploding_flag=0;
  1923.  
  1924.         Player_fired_laser_this_frame=object_none;
  1925.  
  1926.         if (Player_dead_state == player_dead_state::exploded)
  1927.         {
  1928.                 if (exploding_flag==0)  {
  1929.                         exploding_flag = 1;                     // When player starts exploding, clear all input devices...
  1930.                         game_flush_inputs();
  1931.                 }
  1932.         } else {
  1933.                 exploding_flag=0;
  1934.         }
  1935.         if (Player_dead_state != player_dead_state::no &&
  1936.                 !((Game_mode & GM_MULTI) &&
  1937.                         (multi_sending_message[Player_num] || multi_defining_message)
  1938.                 )
  1939.         )
  1940.         HandleDeathInput(event);
  1941.  
  1942.         if (Newdemo_state == ND_STATE_PLAYBACK)
  1943.                 update_vcr_state();
  1944.  
  1945.         if (event.type == EVENT_KEY_COMMAND)
  1946.         {
  1947.                 key = event_key_get(event);
  1948. #if defined(DXX_BUILD_DESCENT_II)
  1949.                 if (MarkerState.DefiningMarkerMessage())
  1950.                 {
  1951.                         return MarkerInputMessage(key);
  1952.                 }
  1953. #endif
  1954.                 if ( (Game_mode & GM_MULTI) && (multi_sending_message[Player_num] || multi_defining_message) )
  1955.                 {
  1956.                         return multi_message_input_sub(key);
  1957.                 }
  1958.  
  1959. #ifndef RELEASE
  1960.                 if ((key&KEY_DEBUGGED)&&(Game_mode&GM_MULTI))   {
  1961.                         Network_message_reciever = 100;         // Send to everyone...
  1962.                         snprintf(Network_message.data(), Network_message.size(), "%s %s", TXT_I_AM_A, TXT_CHEATER);
  1963.                 }
  1964. #endif
  1965.  
  1966.                 if (Endlevel_sequence)
  1967.                 {
  1968.                         auto result = HandleEndlevelKey(key);
  1969.                         if (result != window_event_result::ignored)
  1970.                                 return result;
  1971.                 }
  1972.                 else if (Newdemo_state == ND_STATE_PLAYBACK )
  1973.                 {
  1974.                         auto r = HandleDemoKey(key);
  1975.                         if (r != window_event_result::ignored)
  1976.                                 return r;
  1977.                 }
  1978.                 else
  1979.                 {
  1980.                         window_event_result r = FinalCheats();
  1981.                         if (r == window_event_result::ignored)
  1982.                                 r = HandleSystemKey(key);
  1983.                         if (r == window_event_result::ignored)
  1984.                                 r = HandleGameKey(key);
  1985.                         if (r != window_event_result::ignored)
  1986.                                 return r;
  1987.                 }
  1988.  
  1989. #ifndef RELEASE
  1990.                 {
  1991.                         window_event_result r = HandleTestKey(vmsegptridx, key);
  1992.                         if (r != window_event_result::ignored)
  1993.                                 return r;
  1994.                 }
  1995. #endif
  1996.  
  1997.                 auto result = call_default_handler(event);
  1998.                 if (result != window_event_result::ignored)
  1999.                         return result;
  2000.         }
  2001.  
  2002.         if (!Endlevel_sequence && Newdemo_state != ND_STATE_PLAYBACK)
  2003.         {
  2004.                 kconfig_read_controls(Controls, event, 0);
  2005.                 const auto Player_is_dead = Player_dead_state;
  2006.                 if (Player_is_dead != player_dead_state::no && HandleDeathInput(event))
  2007.                         return window_event_result::handled;
  2008.  
  2009.                 check_rear_view();
  2010.  
  2011.                 // If automap key pressed, enable automap unless you are in network mode, control center destroyed and < 10 seconds left
  2012.                 if ( Controls.state.automap )
  2013.                 {
  2014.                         Controls.state.automap = 0;
  2015.                         if (Player_is_dead != player_dead_state::no || !((Game_mode & GM_MULTI) && LevelUniqueControlCenterState.Control_center_destroyed && LevelUniqueControlCenterState.Countdown_seconds_left < 10))
  2016.                         {
  2017.                                 do_automap();
  2018.                                 return window_event_result::handled;
  2019.                         }
  2020.                 }
  2021.                 if (Player_is_dead != player_dead_state::no)
  2022.                         return window_event_result::ignored;
  2023.                 do_weapon_n_item_stuff(Objects);
  2024.         }
  2025.  
  2026.         if (Controls.state.show_menu)
  2027.         {
  2028.                 Controls.state.show_menu = 0;
  2029.                 return HandleSystemKey(KEY_ESC);
  2030.         }
  2031.  
  2032.         return window_event_result::ignored;
  2033. }
  2034.  
  2035. }
  2036.