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.  * Inferno main menu.
  23.  *
  24.  */
  25.  
  26. #include <stdio.h>
  27. #include <string.h>
  28. #include <SDL.h>
  29. #include <unistd.h> // Pierre-Marie Baty -- for getuid()
  30. #include <pwd.h> // Pierre-Marie Baty -- for getpwuid()
  31.  
  32. #include "menu.h"
  33. #include "inferno.h"
  34. #include "game.h"
  35. #include "gr.h"
  36. #include "key.h"
  37. #include "mouse.h"
  38. #include "iff.h"
  39. #include "u_mem.h"
  40. #include "dxxerror.h"
  41. #include "bm.h"
  42. #include "screens.h"
  43. #include "joy.h"
  44. #include "player.h"
  45. #include "vecmat.h"
  46. #include "effects.h"
  47. #include "game.h"
  48. #include "slew.h"
  49. #include "gamemine.h"
  50. #include "gamesave.h"
  51. #include "palette.h"
  52. #include "args.h"
  53. #include "newdemo.h"
  54. #include "timer.h"
  55. #include "object.h"
  56. #include "sounds.h"
  57. #include "gameseq.h"
  58. #include "text.h"
  59. #include "gamefont.h"
  60. #include "newmenu.h"
  61. #include "scores.h"
  62. #include "playsave.h"
  63. #include "kconfig.h"
  64. #include "titles.h"
  65. #include "credits.h"
  66. #include "texmap.h"
  67. #include "polyobj.h"
  68. #include "state.h"
  69. #include "mission.h"
  70. #include "songs.h"
  71. #if DXX_USE_SDLMIXER
  72. #include "jukebox.h" // for jukebox_exts
  73. #endif
  74. #include "config.h"
  75. #if defined(DXX_BUILD_DESCENT_II)
  76. #include "movie.h"
  77. #endif
  78. #include "gamepal.h"
  79. #include "gauges.h"
  80. #include "powerup.h"
  81. #include "strutil.h"
  82. #include "multi.h"
  83. #include "vers_id.h"
  84. #if DXX_USE_UDP
  85. #include "net_udp.h"
  86. #endif
  87. #if DXX_USE_EDITOR
  88. #include "editor/editor.h"
  89. #include "editor/kdefs.h"
  90. #endif
  91. #if DXX_USE_OGL
  92. #include "ogl_init.h"
  93. #endif
  94. #include "physfs_list.h"
  95.  
  96. #include "dxxsconf.h"
  97. #include "dsx-ns.h"
  98. #include "compiler-range_for.h"
  99. #include "d_range.h"
  100. #include "partial_range.h"
  101. #include <memory>
  102. #include <utility>
  103.  
  104. // Menu IDs...
  105. enum MENUS
  106. {
  107.     MENU_NEW_GAME = 0,
  108.     MENU_GAME,
  109.     MENU_EDITOR,
  110.     MENU_VIEW_SCORES,
  111.     MENU_QUIT,
  112.     MENU_LOAD_GAME,
  113.     MENU_SAVE_GAME,
  114.     MENU_DEMO_PLAY,
  115.     MENU_CONFIG,
  116.     MENU_REJOIN_NETGAME,
  117.     MENU_DIFFICULTY,
  118.     MENU_HELP,
  119.     MENU_NEW_PLAYER,
  120. #if DXX_USE_UDP
  121.         MENU_MULTIPLAYER,
  122.     #endif
  123.  
  124.     MENU_SHOW_CREDITS,
  125.     MENU_ORDER_INFO,
  126.  
  127. #if DXX_USE_UDP
  128.     MENU_START_UDP_NETGAME,
  129.     MENU_JOIN_MANUAL_UDP_NETGAME,
  130.     MENU_JOIN_LIST_UDP_NETGAME,
  131.     #endif
  132.     #ifndef RELEASE
  133.     MENU_SANDBOX
  134.     #endif
  135. };
  136.  
  137. //ADD_ITEM("Start netgame...", MENU_START_NETGAME, -1 );
  138. //ADD_ITEM("Send net message...", MENU_SEND_NET_MESSAGE, -1 );
  139.  
  140. #define ADD_ITEM(t,value,key)  do { nm_set_item_menu(m[num_options], t); menu_choice[num_options]=value;num_options++; } while (0)
  141.  
  142. static std::array<window *, 16> menus;
  143.  
  144. // Function Prototypes added after LINTING
  145. static int do_option(int select);
  146. static window_event_result do_new_game_menu(void);
  147. #if DXX_USE_UDP
  148. static void do_multi_player_menu();
  149. #endif
  150. #ifndef RELEASE
  151. static void do_sandbox_menu();
  152. #endif
  153.  
  154. namespace dcx {
  155.  
  156. extern bool isDirectory(const char *fname); // Pierre-Marie Baty -- work around PHYSFS_isDirectory() deprecation
  157.  
  158. namespace {
  159.  
  160. template <typename T>
  161. using select_file_subfunction = window_event_result (*)(T *, const char *);
  162.  
  163. void format_human_readable_time(char *const data, std::size_t size, const int duration_seconds)
  164. {
  165.         const auto &&split_interval = std::div(duration_seconds, static_cast<int>(std::chrono::minutes::period::num));
  166.         snprintf(data, size, "%im%is", split_interval.quot, split_interval.rem);
  167. }
  168.  
  169. std::pair<std::chrono::seconds, bool> parse_human_readable_time(const char *const buf)
  170. {
  171.         char *p{};
  172.         const std::chrono::minutes m(strtoul(buf, &p, 10));
  173.         if (*p == 0)
  174.                 /* Assume that a pure-integer string is a count of minutes. */
  175.                 return {m, true};
  176.         const auto c0 = *p;
  177.         if (c0 == 'm')
  178.         {
  179.                 const std::chrono::seconds s(strtoul(p + 1, &p, 10));
  180.                 if (*p == 's')
  181.                         /* The trailing 's' is optional, but no character other than
  182.                          * the optional 's' can follow the number.
  183.                          */
  184.                         ++p;
  185.                 if (*p == 0)
  186.                         return {m + s, true};
  187.         }
  188.         else if (c0 == 's' && p[1] == 0)
  189.                 /* Input is only seconds.  Use `.count()` to extract the raw
  190.                  * value without scaling.
  191.                  */
  192.                 return {std::chrono::seconds(m.count()), true};
  193.         return {{}, false};
  194. }
  195.  
  196. }
  197.  
  198. template <typename Rep, std::size_t S>
  199. void format_human_readable_time(std::array<char, S> &buf, const std::chrono::duration<Rep, std::chrono::seconds::period> duration)
  200. {
  201.         static_assert(S >= std::tuple_size<human_readable_mmss_time<Rep>>::value, "array is too small");
  202.         static_assert(std::numeric_limits<Rep>::max() <= std::numeric_limits<int>::max(), "Rep allows too large a value");
  203.         format_human_readable_time(buf.data(), buf.size(), duration.count());
  204. }
  205.  
  206. template <typename Rep, std::size_t S>
  207. void parse_human_readable_time(std::chrono::duration<Rep, std::chrono::seconds::period> &duration, const std::array<char, S> &buf)
  208. {
  209.         const auto &&r = parse_human_readable_time(buf.data());
  210.         if (r.second)
  211.                 duration = r.first;
  212. }
  213.  
  214. template void format_human_readable_time(human_readable_mmss_time<autosave_interval_type::rep> &buf, autosave_interval_type);
  215. template void parse_human_readable_time(autosave_interval_type &, const human_readable_mmss_time<autosave_interval_type::rep> &buf);
  216.  
  217. }
  218.  
  219. __attribute_nonnull()
  220. static int select_file_recursive2(const char *title, const std::array<char, PATH_MAX> &orig_path, const partial_range_t<const file_extension_t *> &ext_list, int select_dir, select_file_subfunction<void> when_selected, void *userdata);
  221.  
  222. template <typename T>
  223. __attribute_nonnull()
  224. static int select_file_recursive(const char *title, const std::array<char, PATH_MAX> &orig_path, const partial_range_t<const file_extension_t *> &ext_list, int select_dir, select_file_subfunction<T> when_selected, T *userdata)
  225. {
  226.         return select_file_recursive2(title, orig_path, ext_list, select_dir, reinterpret_cast<select_file_subfunction<void>>(when_selected), reinterpret_cast<void *>(userdata));
  227. }
  228.  
  229. // Hide all menus
  230. int hide_menus(void)
  231. {
  232.         window *wind;
  233.         if (menus[0])
  234.                 return 0;               // there are already hidden menus
  235.  
  236.         wind = window_get_front();
  237.         range_for (auto &i, menus)
  238.         {
  239.                 i = wind;
  240.                 if (!wind)
  241.                         break;
  242.                 wind = window_set_visible(*wind, 0);
  243.         }
  244.         Assert(window_get_front() == NULL);
  245.         return 1;
  246. }
  247.  
  248. // Show all menus, with the front one shown first
  249. // This makes sure EVENT_WINDOW_ACTIVATED is only sent to that window
  250. void show_menus(void)
  251. {
  252.         range_for (auto &i, menus)
  253.         {
  254.                 if (!i)
  255.                         break;
  256.  
  257.                 // Hidden windows don't receive events, so the only way to close is outside its handler
  258.                 // Which there should be no cases of here
  259.                 // window_exists could return a false positive if a new window was created
  260.                 // with the same pointer value as the deleted one, so killing window_exists (call and function)
  261.                 // if (window_exists(i))
  262.                 window_set_visible(std::exchange(i, nullptr), 1);
  263.         }
  264. }
  265.  
  266. namespace dcx {
  267.  
  268. /* This is a hack to prevent writing to freed memory.  Various points in
  269.  * the game code call `hide_menus()`, then later use `show_menus()` to
  270.  * reverse the effect.  If the forcibly hidden window is deleted before
  271.  * `show_menus()` is called, the attempt to show it would write to freed
  272.  * memory.  This hook is called when a window is deleted, so that the
  273.  * deleted window can be removed from menus[].  Removing it from menus[]
  274.  * prevents `show_menus()` trying to make it visible later.
  275.  *
  276.  * It would be cleaner, but more invasive, to restructure the code so
  277.  * that the menus[] array does not need to exist and window pointers are
  278.  * not stored outside the control of their owner.
  279.  */
  280. void menu_destroy_hook(window *w)
  281. {
  282.         const auto &&e = menus.end();
  283.         const auto &&i = std::find(menus.begin(), e, w);
  284.         if (i == e)
  285.                 /* Not a hidden menu */
  286.                 return;
  287.         /* This is not run often enough to merit a clever loop that stops
  288.          * when it reaches an unused element.
  289.          */
  290.         std::move(std::next(i), e, i);
  291.         menus.back() = nullptr;
  292. }
  293.  
  294. //pairs of chars describing ranges
  295. constexpr char playername_allowed_chars[] = "azAZ09__--";
  296.  
  297. }
  298.  
  299. static int MakeNewPlayerFile(int allow_abort)
  300. {
  301.         int x;
  302.         char filename[PATH_MAX];
  303.         auto text = InterfaceUniqueState.PilotName;
  304.  
  305. try_again:
  306.         {
  307.                 std::array<newmenu_item, 1> m{{
  308.                         nm_item_input(text.buffer()),
  309.                 }};
  310.         Newmenu_allowed_chars = playername_allowed_chars;
  311.                 x = newmenu_do( NULL, TXT_ENTER_PILOT_NAME, m, unused_newmenu_subfunction, unused_newmenu_userdata );
  312.         }
  313.         Newmenu_allowed_chars = NULL;
  314.  
  315.         if ( x < 0 ) {
  316.                 if ( allow_abort ) return 0;
  317.                 goto try_again;
  318.         }
  319.  
  320.         if (!*static_cast<const char *>(text))  //null string
  321.                 goto try_again;
  322.  
  323.         text.lower();
  324.  
  325.         snprintf(filename, sizeof(filename), PLAYER_DIRECTORY_STRING("%s.plr"), static_cast<const char *>(text) );
  326.  
  327.         if (PHYSFSX_exists(filename,0))
  328.         {
  329.                 nm_messagebox(NULL, 1, TXT_OK, "%s '%s' %s", TXT_PLAYER, static_cast<const char *>(text), TXT_ALREADY_EXISTS );
  330.                 goto try_again;
  331.         }
  332.  
  333.         if ( !new_player_config() )
  334.                 goto try_again;                 // They hit Esc during New player config
  335.  
  336.         InterfaceUniqueState.PilotName = text;
  337.         InterfaceUniqueState.update_window_title();
  338.         write_player_file();
  339.  
  340.         return 1;
  341. }
  342.  
  343. static void delete_player_saved_games(const char * name);
  344.  
  345. static window_event_result player_menu_keycommand( listbox *lb,const d_event &event )
  346. {
  347.         const char **items = listbox_get_items(lb);
  348.         int citem = listbox_get_citem(lb);
  349.  
  350.         switch (event_key_get(event))
  351.         {
  352.                 case KEY_CTRLED+KEY_D:
  353.                         if (citem > 0)
  354.                         {
  355.                                 int x = 1;
  356.                                 x = nm_messagebox( NULL, 2, TXT_YES, TXT_NO, "%s %s?", TXT_DELETE_PILOT, items[citem]+((items[citem][0]=='$')?1:0) );
  357.                                 if (x==0)       {
  358.                                         char plxfile[PATH_MAX], efffile[PATH_MAX], ngpfile[PATH_MAX];
  359.                                         int ret;
  360.                                         char name[PATH_MAX];
  361.  
  362.                                         snprintf(name, sizeof(name), PLAYER_DIRECTORY_STRING("%.8s.plr"), items[citem]);
  363.  
  364.                                         ret = !PHYSFS_delete(name);
  365.  
  366.                                         if (!ret)
  367.                                         {
  368.                                                 delete_player_saved_games( items[citem] );
  369.                                                 // delete PLX file
  370.                                                 snprintf(plxfile, sizeof(plxfile), PLAYER_DIRECTORY_STRING("%.8s.plx"), items[citem]);
  371.                                                 if (PHYSFSX_exists(plxfile,0))
  372.                                                         PHYSFS_delete(plxfile);
  373.                                                 // delete EFF file
  374.                                                 snprintf(efffile, sizeof(efffile), PLAYER_DIRECTORY_STRING("%.8s.eff"), items[citem]);
  375.                                                 if (PHYSFSX_exists(efffile,0))
  376.                                                         PHYSFS_delete(efffile);
  377.                                                 // delete NGP file
  378.                                                 snprintf(ngpfile, sizeof(ngpfile), PLAYER_DIRECTORY_STRING("%.8s.ngp"), items[citem]);
  379.                                                 if (PHYSFSX_exists(ngpfile,0))
  380.                                                         PHYSFS_delete(ngpfile);
  381.                                         }
  382.  
  383.                                         if (ret)
  384.                                                 nm_messagebox( NULL, 1, TXT_OK, "%s %s %s", TXT_COULDNT, TXT_DELETE_PILOT, items[citem]+((items[citem][0]=='$')?1:0) );
  385.                                         else
  386.                                                 listbox_delete_item(lb, citem);
  387.                                 }
  388.  
  389.                                 return window_event_result::handled;
  390.                         }
  391.                         break;
  392.         }
  393.  
  394.         return window_event_result::ignored;
  395. }
  396.  
  397. static window_event_result player_menu_handler( listbox *lb,const d_event &event, char **list )
  398. {
  399.         const char **items = listbox_get_items(lb);
  400.         switch (event.type)
  401.         {
  402.                 case EVENT_KEY_COMMAND:
  403.                         return player_menu_keycommand(lb, event);
  404.                 case EVENT_NEWMENU_SELECTED:
  405.                 {
  406.                         auto &citem = static_cast<const d_select_event &>(event).citem;
  407.                         if (citem < 0)
  408.                                 return window_event_result::ignored;            // shouldn't happen
  409.                         else if (citem == 0)
  410.                         {
  411.                                 // They selected 'create new pilot'
  412.                                 return MakeNewPlayerFile(1) ? window_event_result::close : window_event_result::handled;
  413.                         }
  414.                         else
  415.                         {
  416.                                 InterfaceUniqueState.PilotName.copy_lower(items[citem], strlen(items[citem]));
  417.                                 InterfaceUniqueState.update_window_title();
  418.                         }
  419.                         return window_event_result::close;
  420.                 }
  421.  
  422.                 case EVENT_WINDOW_CLOSE:
  423.                         if (read_player_file() != EZERO)
  424.                                 return window_event_result::handled;            // abort close!
  425.  
  426.                         WriteConfigFile();              // Update lastplr
  427.  
  428.                         PHYSFS_freeList(list);
  429.                         d_free(items);
  430.                         break;
  431.  
  432.                 default:
  433.                         break;
  434.         }
  435.  
  436.         return window_event_result::ignored;
  437. }
  438.  
  439. //Inputs the player's name, without putting up the background screen
  440. static void RegisterPlayer()
  441. {
  442.         static const std::array<file_extension_t, 1> types{{"plr"}};
  443.         int i = 0, NumItems;
  444.         int citem = 0;
  445.         int allow_abort_flag = 1;
  446.  
  447.         auto &callsign = InterfaceUniqueState.PilotName;
  448.         if (!callsign[0u])
  449.         {
  450.                 if (!*static_cast<const char *>(GameCfg.LastPlayer))
  451.                 {
  452.                         callsign = "player";
  453.                         allow_abort_flag = 0;
  454.                 }
  455.                 else
  456.                 {
  457.                         // Read the last player's name from config file, not lastplr.txt
  458.                         callsign = GameCfg.LastPlayer;
  459.                 }
  460.                 InterfaceUniqueState.update_window_title();
  461.         }
  462.  
  463.         auto list = PHYSFSX_findFiles(PLAYER_DIRECTORY_STRING(""), types);
  464.         if (!list)
  465.                 return; // memory error
  466.         if (!list[0])
  467.         {
  468.                 MakeNewPlayerFile(0);   // make a new player without showing listbox
  469.                 return;
  470.         }
  471.  
  472.  
  473.         for (NumItems = 0; list[NumItems] != NULL; NumItems++) {}
  474.         NumItems++;             // for TXT_CREATE_NEW
  475.  
  476.         RAIIdmem<const char *[]> m;
  477.         MALLOC(m, const char *[], NumItems);
  478.         if (m == NULL)
  479.                 return;
  480.  
  481.         m[i++] = TXT_CREATE_NEW;
  482.  
  483.         range_for (const auto f, list)
  484.         {
  485.                 char *p;
  486.  
  487.                 size_t lenf = strlen(f);
  488.                 if (lenf > FILENAME_LEN-1 || lenf < 5) // sorry guys, can only have up to eight chars for the player name
  489.                 {
  490.                         NumItems--;
  491.                         continue;
  492.                 }
  493.                 m[i++] = f;
  494.                 p = strchr(f, '.');
  495.                 if (p)
  496.                         *p = '\0';              // chop the .plr
  497.         }
  498.  
  499.         if (NumItems <= 1) // so it seems all plr files we found were too long. funny. let's make a real player
  500.         {
  501.                 MakeNewPlayerFile(0);   // make a new player without showing listbox
  502.                 return;
  503.         }
  504.  
  505.         // Sort by name, except the <Create New Player> string
  506.         qsort(&m[1], NumItems - 1, sizeof(char *), string_array_sort_func);
  507.  
  508.         for ( i=0; i<NumItems; i++ )
  509.                 if (!d_stricmp(static_cast<const char *>(callsign), m[i]))
  510.                         citem = i;
  511.  
  512.         newmenu_listbox1(TXT_SELECT_PILOT, NumItems, m.release(), allow_abort_flag, citem, player_menu_handler, list.release());
  513. }
  514.  
  515. // Draw Copyright and Version strings
  516. static void draw_copyright()
  517. {
  518.         gr_set_default_canvas();
  519.         auto &canvas = *grd_curcanv;
  520.         auto &game_font = *GAME_FONT;
  521.         gr_set_fontcolor(canvas, BM_XRGB(6, 6, 6), -1);
  522.         const auto &&line_spacing = LINE_SPACING(game_font, game_font);
  523.         gr_string(canvas, game_font, 0x8000, SHEIGHT - line_spacing, TXT_COPYRIGHT);
  524.         gr_set_fontcolor(canvas, BM_XRGB(25, 0, 0), -1);
  525.         gr_string(canvas, game_font, 0x8000, SHEIGHT - (line_spacing * 2), DESCENT_VERSION);
  526. }
  527.  
  528. namespace dsx {
  529.  
  530. // ------------------------------------------------------------------------
  531. static int main_menu_handler(newmenu *menu,const d_event &event, int *menu_choice )
  532. {
  533.         newmenu_item *items = newmenu_get_items(menu);
  534.  
  535.         switch (event.type)
  536.         {
  537.                 case EVENT_WINDOW_CREATED:
  538.                         if (InterfaceUniqueState.PilotName[0u])
  539.                                 break;
  540.                         RegisterPlayer();
  541.                         break;
  542.                 case EVENT_WINDOW_ACTIVATED:
  543.                         load_palette(MENU_PALETTE,0,1);         //get correct palette
  544.                         keyd_time_when_last_pressed = timer_query();            // .. 20 seconds from now!
  545.                         break;
  546.  
  547.                 case EVENT_KEY_COMMAND:
  548.                         // Don't allow them to hit ESC in the main menu.
  549.                         if (event_key_get(event)==KEY_ESC)
  550.                                 return 1;
  551.                         break;
  552.  
  553.                 case EVENT_MOUSE_BUTTON_DOWN:
  554.                 case EVENT_MOUSE_BUTTON_UP:
  555.                         // Don't allow mousebutton-closing in main menu.
  556.                         if (event_mouse_get_button(event) == MBTN_RIGHT)
  557.                                 return 1;
  558.                         break;
  559.  
  560.                 case EVENT_IDLE:
  561. #if defined(DXX_BUILD_DESCENT_I)
  562. #define DXX_DEMO_KEY_DELAY      45
  563. #elif defined(DXX_BUILD_DESCENT_II)
  564. #define DXX_DEMO_KEY_DELAY      25
  565. #endif
  566.                         if (keyd_time_when_last_pressed + i2f(DXX_DEMO_KEY_DELAY) < timer_query() || CGameArg.SysAutoDemo)
  567.                         {
  568.                                 keyd_time_when_last_pressed = timer_query();                    // Reset timer so that disk won't thrash if no demos.
  569.  
  570. #if defined(DXX_BUILD_DESCENT_II)
  571.                                 int n_demos = newdemo_count_demos();
  572.                                 if ((d_rand() % (n_demos+1)) == 0 && !CGameArg.SysAutoDemo)
  573.                                 {
  574. #if DXX_USE_OGL
  575.                                         Screen_mode = -1;
  576. #endif
  577.                                         PlayMovie("intro.tex", "intro.mve",0);
  578.                                         songs_play_song(SONG_TITLE,1);
  579.                                         set_screen_mode(SCREEN_MENU);
  580.                                 }
  581.                                 else
  582. #endif
  583.                                 {
  584.                                         newdemo_start_playback(NULL);           // Randomly pick a file, assume native endian (crashes if not)
  585. #if defined(DXX_BUILD_DESCENT_II)
  586.                                         if (Newdemo_state == ND_STATE_PLAYBACK)
  587.                                                 return 0;
  588. #endif
  589.                                 }
  590.                         }
  591.                         break;
  592.  
  593.                 case EVENT_NEWMENU_DRAW:
  594.                         draw_copyright();
  595.                         break;
  596.  
  597.                 case EVENT_NEWMENU_SELECTED:
  598.                 {
  599.                         auto &citem = static_cast<const d_select_event &>(event).citem;
  600.                         return do_option(menu_choice[citem]);
  601.                 }
  602.  
  603.                 case EVENT_WINDOW_CLOSE:
  604.                         d_free(menu_choice);
  605.                         d_free(items);
  606.                         break;
  607.  
  608.                 default:
  609.                         break;
  610.         }
  611.  
  612.         return 0;
  613. }
  614.  
  615. //      -----------------------------------------------------------------------------
  616. //      Create the main menu.
  617. static void create_main_menu(newmenu_item *m, int *menu_choice, int *callers_num_options)
  618. {
  619.         int num_options = 0;
  620.  
  621.         #ifndef DEMO_ONLY
  622.         ADD_ITEM(TXT_NEW_GAME,MENU_NEW_GAME,KEY_N);
  623.  
  624.         ADD_ITEM(TXT_LOAD_GAME,MENU_LOAD_GAME,KEY_L);
  625. #if DXX_USE_UDP
  626.         ADD_ITEM(TXT_MULTIPLAYER_,MENU_MULTIPLAYER,-1);
  627. #endif
  628.  
  629.         ADD_ITEM(TXT_OPTIONS_, MENU_CONFIG, -1 );
  630.         ADD_ITEM(TXT_CHANGE_PILOTS,MENU_NEW_PLAYER,unused);
  631.         ADD_ITEM(TXT_VIEW_DEMO,MENU_DEMO_PLAY,0);
  632.         ADD_ITEM(TXT_VIEW_SCORES,MENU_VIEW_SCORES,KEY_V);
  633. #if defined(DXX_BUILD_DESCENT_I)
  634.         if (!PHYSFSX_exists("warning.pcx",1)) /* SHAREWARE */
  635. #elif defined(DXX_BUILD_DESCENT_II)
  636.         if (PHYSFSX_exists("orderd2.pcx",1)) /* SHAREWARE */
  637. #endif
  638.                 ADD_ITEM(TXT_ORDERING_INFO,MENU_ORDER_INFO,-1);
  639.         ADD_ITEM(TXT_CREDITS,MENU_SHOW_CREDITS,-1);
  640.         #endif
  641.         ADD_ITEM(TXT_QUIT,MENU_QUIT,KEY_Q);
  642.  
  643.         #ifndef RELEASE
  644.         if (!(Game_mode & GM_MULTI ))   {
  645. #if DXX_USE_EDITOR
  646.                 ADD_ITEM("  Editor", MENU_EDITOR, KEY_E);
  647.                 #endif
  648.         }
  649.         ADD_ITEM("  SANDBOX", MENU_SANDBOX, -1);
  650.         #endif
  651.  
  652.         *callers_num_options = num_options;
  653. }
  654.  
  655. //returns number of item chosen
  656. int DoMenu()
  657. {
  658.         int *menu_choice;
  659.         newmenu_item *m;
  660.         int num_options = 0;
  661.  
  662.         CALLOC(menu_choice, int, 25);
  663.         if (!menu_choice)
  664.                 return -1;
  665.         CALLOC(m, newmenu_item, 25);
  666.         if (!m)
  667.         {
  668.                 d_free(menu_choice);
  669.                 return -1;
  670.         }
  671.  
  672.         create_main_menu(m, menu_choice, &num_options); // may have to change, eg, maybe selected pilot and no save games.
  673.  
  674.         newmenu_do3( "", NULL, num_options, m, main_menu_handler, menu_choice, 0, Menu_pcx_name);
  675.  
  676.         return 0;
  677. }
  678.  
  679. }
  680.  
  681. //returns flag, true means quit menu
  682. int do_option ( int select)
  683. {
  684.         switch (select) {
  685.                 case MENU_NEW_GAME:
  686.                         select_mission(mission_filter_mode::exclude_anarchy, "New Game\n\nSelect mission", do_new_game_menu);
  687.                         break;
  688.                 case MENU_GAME:
  689.                         break;
  690.                 case MENU_DEMO_PLAY:
  691.                         select_demo();
  692.                         break;
  693.                 case MENU_LOAD_GAME:
  694.                         state_restore_all(0, secret_restore::none, nullptr, blind_save::no);
  695.                         break;
  696. #if DXX_USE_EDITOR
  697.                 case MENU_EDITOR:
  698.                         if (!Current_mission)
  699.                         {
  700.                                 create_new_mine();
  701.                                 SetPlayerFromCurseg();
  702.                         }
  703.  
  704.                         hide_menus();
  705.                         init_editor();
  706.                         break;
  707.                 #endif
  708.                 case MENU_VIEW_SCORES:
  709.                         scores_view(NULL, -1);
  710.                         break;
  711. #if 1 //def SHAREWARE
  712.                 case MENU_ORDER_INFO:
  713.                         show_order_form();
  714.                         break;
  715. #endif
  716.                 case MENU_QUIT:
  717. #if DXX_USE_EDITOR
  718.                         if (! SafetyCheck()) break;
  719.                         #endif
  720.                         return 0;
  721.  
  722.                 case MENU_NEW_PLAYER:
  723.                         RegisterPlayer();
  724.                         break;
  725.  
  726. #if DXX_USE_UDP
  727.                 case MENU_START_UDP_NETGAME:
  728.                         multi_protocol = MULTI_PROTO_UDP;
  729.                         select_mission(mission_filter_mode::include_anarchy, TXT_MULTI_MISSION, net_udp_setup_game);
  730.                         break;
  731.                 case MENU_JOIN_MANUAL_UDP_NETGAME:
  732.                         multi_protocol = MULTI_PROTO_UDP;
  733.                         net_udp_manual_join_game();
  734.                         break;
  735.                 case MENU_JOIN_LIST_UDP_NETGAME:
  736.                         multi_protocol = MULTI_PROTO_UDP;
  737.                         net_udp_list_join_game();
  738.                         break;
  739. #endif
  740. #if DXX_USE_UDP
  741.                 case MENU_MULTIPLAYER:
  742.                         do_multi_player_menu();
  743.                         break;
  744. #endif
  745.                 case MENU_CONFIG:
  746.                         do_options_menu();
  747.                         break;
  748.                 case MENU_SHOW_CREDITS:
  749.                         credits_show();
  750.                         break;
  751. #ifndef RELEASE
  752.                 case MENU_SANDBOX:
  753.                         do_sandbox_menu();
  754.                         break;
  755. #endif
  756.                 default:
  757.                         Error("Unknown option %d in do_option",select);
  758.                         break;
  759.         }
  760.  
  761.         return 1;               // stay in main menu unless quitting
  762. }
  763.  
  764. static void delete_player_saved_games(const char * name)
  765. {
  766.         char filename[PATH_MAX];
  767.         for (unsigned i = 0; i < 11; ++i)
  768.         {
  769.                 snprintf( filename, sizeof(filename), PLAYER_DIRECTORY_STRING("%s.sg%x"), name, i );
  770.                 PHYSFS_delete(filename);
  771.                 snprintf( filename, sizeof(filename), PLAYER_DIRECTORY_STRING("%s.mg%x"), name, i );
  772.                 PHYSFS_delete(filename);
  773.         }
  774. }
  775.  
  776. static window_event_result demo_menu_keycommand( listbox *lb,const d_event &event )
  777. {
  778.         const char **items = listbox_get_items(lb);
  779.         int citem = listbox_get_citem(lb);
  780.  
  781.         switch (event_key_get(event))
  782.         {
  783.                 case KEY_CTRLED+KEY_D:
  784.                         if (citem >= 0)
  785.                         {
  786.                                 int x = 1;
  787.                                 x = nm_messagebox( NULL, 2, TXT_YES, TXT_NO, "%s %s?", TXT_DELETE_DEMO, items[citem]+((items[citem][0]=='$')?1:0) );
  788.                                 if (x==0)
  789.                                 {
  790.                                         int ret;
  791.                                         char name[PATH_MAX];
  792.  
  793.                                         strcpy(name, DEMO_DIR);
  794.                                         strcat(name,items[citem]);
  795.  
  796.                                         ret = !PHYSFS_delete(name);
  797.  
  798.                                         if (ret)
  799.                                                 nm_messagebox( NULL, 1, TXT_OK, "%s %s %s", TXT_COULDNT, TXT_DELETE_DEMO, items[citem]+((items[citem][0]=='$')?1:0) );
  800.                                         else
  801.                                                 listbox_delete_item(lb, citem);
  802.                                 }
  803.  
  804.                                 return window_event_result::handled;
  805.                         }
  806.                         break;
  807.  
  808.                 case KEY_CTRLED+KEY_C:
  809.                         {
  810.                                 int x = 1;
  811.                                 char bakname[PATH_MAX];
  812.  
  813.                                 // Get backup name
  814.                                 change_filename_extension(bakname, items[citem]+((items[citem][0]=='$')?1:0), DEMO_BACKUP_EXT);
  815.                                 x = nm_messagebox( NULL, 2, TXT_YES, TXT_NO,    "Are you sure you want to\n"
  816.                                                                   "swap the endianness of\n"
  817.                                                                   "%s? If the file is\n"
  818.                                                                   "already endian native, D1X\n"
  819.                                                                   "will likely crash. A backup\n"
  820.                                                                   "%s will be created", items[citem]+((items[citem][0]=='$')?1:0), bakname );
  821.                                 if (!x)
  822.                                         newdemo_swap_endian(items[citem]);
  823.  
  824.                                 return window_event_result::handled;
  825.                         }
  826.                         break;
  827.         }
  828.  
  829.         return window_event_result::ignored;
  830. }
  831.  
  832. static window_event_result demo_menu_handler(listbox *lb, const d_event &event, char **items)
  833. {
  834.         switch (event.type)
  835.         {
  836.                 case EVENT_KEY_COMMAND:
  837.                         return demo_menu_keycommand(lb, event);
  838.                 case EVENT_NEWMENU_SELECTED:
  839.                 {
  840.                         auto &citem = static_cast<const d_select_event &>(event).citem;
  841.                         if (citem < 0)
  842.                                 return window_event_result::ignored;            // shouldn't happen
  843.                         newdemo_start_playback(items[citem]);
  844.                         return window_event_result::handled;            // stay in demo selector
  845.                 }
  846.                 case EVENT_WINDOW_CLOSE:
  847.                         PHYSFS_freeList(items);
  848.                         break;
  849.                 default:
  850.                         break;
  851.         }
  852.         return window_event_result::ignored;
  853. }
  854.  
  855. int select_demo(void)
  856. {
  857.         int NumItems;
  858.  
  859.         auto list = PHYSFSX_findFiles(DEMO_DIR, demo_file_extensions);
  860.         if (!list)
  861.                 return 0;       // memory error
  862.         if (!list[0])
  863.         {
  864.                 nm_messagebox( NULL, 1, TXT_OK, "%s %s\n%s", TXT_NO_DEMO_FILES, TXT_USE_F5, TXT_TO_CREATE_ONE);
  865.                 return 0;
  866.         }
  867.  
  868.         for (NumItems = 0; list[NumItems] != NULL; NumItems++) {}
  869.  
  870.         // Sort by name
  871.         qsort(list.get(), NumItems, sizeof(char *), string_array_sort_func);
  872.  
  873.         auto clist = const_cast<const char **>(list.get());
  874.         newmenu_listbox1(TXT_SELECT_DEMO, NumItems, clist, 1, 0, demo_menu_handler, list.release());
  875.  
  876.         return 1;
  877. }
  878.  
  879. static int do_difficulty_menu()
  880. {
  881.         std::array<newmenu_item, NDL> m{{
  882.                 nm_item_menu(MENU_DIFFICULTY_TEXT(0)),
  883.                 nm_item_menu(MENU_DIFFICULTY_TEXT(1)),
  884.                 nm_item_menu(MENU_DIFFICULTY_TEXT(2)),
  885.                 nm_item_menu(MENU_DIFFICULTY_TEXT(3)),
  886.                 nm_item_menu(MENU_DIFFICULTY_TEXT(4)),
  887.         }};
  888.  
  889.         auto &Difficulty_level = GameUniqueState.Difficulty_level;
  890.         const unsigned s = newmenu_do1(nullptr, TXT_DIFFICULTY_LEVEL, m.size(), &m.front(), unused_newmenu_subfunction, unused_newmenu_userdata, Difficulty_level);
  891.  
  892.         if (s <= Difficulty_4)
  893.         {
  894.                 const auto d = static_cast<Difficulty_level_type>(s);
  895.                 if (d != Difficulty_level)
  896.                 {
  897.                         PlayerCfg.DefaultDifficulty = d;
  898.                         write_player_file();
  899.                 }
  900.                 Difficulty_level = d;
  901.                 return 1;
  902.         }
  903.         return 0;
  904. }
  905.  
  906. window_event_result do_new_game_menu()
  907. {
  908.         int new_level_num;
  909.  
  910.         new_level_num = 1;
  911.         const auto recorded_player_highest_level = get_highest_level();
  912.         const auto clamped_player_highest_level = std::min<decltype(recorded_player_highest_level)>(recorded_player_highest_level, Last_level);
  913.         const auto allowed_highest_level =
  914. #ifdef NDEBUG
  915. #define DXX_START_ANY_LEVEL_FORMAT      ""
  916. #define DXX_START_ANY_LEVEL_ARGS
  917.                 clamped_player_highest_level
  918. #else
  919. #define DXX_START_ANY_LEVEL_FORMAT      "\n\nYou have beaten level %d."
  920. #define DXX_START_ANY_LEVEL_ARGS        , clamped_player_highest_level
  921.                 Last_level
  922. #endif
  923.                 ;
  924.         if (allowed_highest_level > 1)
  925.         {
  926.                 char info_text[128];
  927.  
  928.                 snprintf(info_text, sizeof(info_text), "This mission has\n%u levels.\n\n%s %d." DXX_START_ANY_LEVEL_FORMAT, static_cast<unsigned>(Last_level), TXT_START_ANY_LEVEL, allowed_highest_level DXX_START_ANY_LEVEL_ARGS);
  929. #undef DXX_START_ANY_LEVEL_ARGS
  930. #undef DXX_START_ANY_LEVEL_FORMAT
  931.                 for (;;)
  932.                 {
  933.                         std::array<char, 10> num_text{"1"};
  934.                         std::array<newmenu_item, 2> m{{
  935.                                 nm_item_text(info_text),
  936.                                 nm_item_input(num_text),
  937.                         }};
  938.                         const int choice = newmenu_do(nullptr, TXT_SELECT_START_LEV, m, unused_newmenu_subfunction, unused_newmenu_userdata);
  939.  
  940.                         if (choice == -1 || !num_text[0])
  941.                                 return window_event_result::handled;
  942.  
  943.                         char *p = nullptr;
  944.                         new_level_num = strtol(num_text.data(), &p, 10);
  945.  
  946.                         if (*p || new_level_num <= 0 || new_level_num > Last_level)
  947.                         {
  948.                                 nm_messagebox(TXT_INVALID_LEVEL, 1, TXT_OK, "You must enter a\npositive level number\nless than or\nequal to %u.\n", static_cast<unsigned>(Last_level));
  949.                         }
  950.                         else if (new_level_num > allowed_highest_level)
  951.                                 nm_messagebox(TXT_INVALID_LEVEL, 1, TXT_OK, "You have beaten level %d.\n\nYou cannot start on level %d.", allowed_highest_level, new_level_num);
  952.                         else
  953.                                 break;
  954.                 }
  955.         }
  956.  
  957.         GameUniqueState.Difficulty_level = PlayerCfg.DefaultDifficulty;
  958.  
  959.         if (!do_difficulty_menu())
  960.                 return window_event_result::handled;
  961.  
  962.         StartNewGame(new_level_num);
  963.  
  964.         return window_event_result::close;      // exit mission listbox
  965. }
  966.  
  967. static void do_sound_menu();
  968. static void input_config();
  969. static void change_res();
  970. namespace dsx {
  971. static void hud_config();
  972. static void graphics_config();
  973. }
  974. static void gameplay_config();
  975.  
  976. #define DXX_OPTIONS_MENU(VERB)  \
  977.         DXX_MENUITEM(VERB, MENU, "Sound & music...", sfx)       \
  978.         DXX_MENUITEM(VERB, MENU, TXT_CONTROLS_, controls)       \
  979.         DXX_MENUITEM(VERB, MENU, "Graphics...", graphics)       \
  980.         DXX_MENUITEM(VERB, MENU, "Gameplay...", misc)   \
  981.  
  982. namespace {
  983.  
  984. class options_menu_items
  985. {
  986. public:
  987.         enum
  988.         {
  989.                 DXX_OPTIONS_MENU(ENUM)
  990.         };
  991.         std::array<newmenu_item, DXX_OPTIONS_MENU(COUNT)> m;
  992.         options_menu_items()
  993.         {
  994.                 DXX_OPTIONS_MENU(ADD);
  995.         }
  996. };
  997.  
  998. }
  999.  
  1000. static int options_menuset(newmenu *, const d_event &event, options_menu_items *items)
  1001. {
  1002.         switch (event.type)
  1003.         {
  1004.                 case EVENT_NEWMENU_CHANGED:
  1005.                         break;
  1006.  
  1007.                 case EVENT_NEWMENU_SELECTED:
  1008.                 {
  1009.                         auto &citem = static_cast<const d_select_event &>(event).citem;
  1010.                         switch (citem)
  1011.                         {
  1012.                                 case options_menu_items::sfx:
  1013.                                         do_sound_menu();
  1014.                                         break;
  1015.                                 case options_menu_items::controls:
  1016.                                         input_config();
  1017.                                         break;
  1018.                                 case options_menu_items::graphics:
  1019.                                         graphics_config();
  1020.                                         break;
  1021.                                 case options_menu_items::misc:
  1022.                                         gameplay_config();
  1023.                                         break;
  1024.                         }
  1025.                         return 1;       // stay in menu until escape
  1026.                 }
  1027.  
  1028.                 case EVENT_WINDOW_CLOSE:
  1029.                 {
  1030.                         std::default_delete<options_menu_items>()(items);
  1031.                         write_player_file();
  1032.                         break;
  1033.                 }
  1034.  
  1035.                 default:
  1036.                         break;
  1037.         }
  1038.         return 0;
  1039. }
  1040.  
  1041. static int gcd(int a, int b)
  1042. {
  1043.         if (!b)
  1044.                 return a;
  1045.  
  1046.         return gcd(b, a%b);
  1047. }
  1048.  
  1049. void change_res()
  1050. {
  1051.         newmenu_item m[50+8];
  1052.         std::array<char, 12> crestext, casptext;
  1053.  
  1054.         int mc = 0, citem = -1;
  1055.  
  1056. #if SDL_MAJOR_VERSION == 1
  1057.         std::array<screen_mode, 50> modes;
  1058.         const auto num_presets = gr_list_modes(modes);
  1059.         std::array<std::array<char, 12>, 50> restext;
  1060.  
  1061.         range_for (auto &i, partial_const_range(modes, num_presets))
  1062.         {
  1063.                 const auto &&sm_w = SM_W(i);
  1064.                 const auto &&sm_h = SM_H(i);
  1065.                 snprintf(restext[mc].data(), restext[mc].size(), "%ix%i", sm_w, sm_h);
  1066.                 const auto checked = (citem == -1 && Game_screen_mode == i && GameCfg.AspectY == sm_w / gcd(sm_w, sm_h) && GameCfg.AspectX == sm_h / gcd(sm_w, sm_h));
  1067.                 if (checked)
  1068.                         citem = mc;
  1069.                 nm_set_item_radio(m[mc], restext[mc].data(), checked, 0);
  1070.                 mc++;
  1071.         }
  1072.  
  1073.         nm_set_item_text(m[mc], ""); mc++; // little space for overview
  1074.         // the fields for custom resolution and aspect
  1075.         const auto opt_cval = mc;
  1076. #endif
  1077.         nm_set_item_radio(m[mc], "use custom values", (citem == -1), 0); mc++;
  1078.         nm_set_item_text(m[mc], "resolution:"); mc++;
  1079.         snprintf(crestext.data(), crestext.size(), "%ix%i", SM_W(Game_screen_mode), SM_H(Game_screen_mode));
  1080.         nm_set_item_input(m[mc], crestext);
  1081.         mc++;
  1082.         nm_set_item_text(m[mc], "aspect:"); mc++;
  1083.         snprintf(casptext.data(), casptext.size(), "%ix%i", GameCfg.AspectY, GameCfg.AspectX);
  1084.         nm_set_item_input(m[mc], casptext);
  1085.         mc++;
  1086.         nm_set_item_text(m[mc], ""); mc++; // little space for overview
  1087.         // fullscreen
  1088. #if SDL_MAJOR_VERSION == 1
  1089.         const auto opt_fullscr = mc;
  1090.         nm_set_item_checkbox(m[mc], "Fullscreen", gr_check_fullscreen());
  1091.         mc++;
  1092. #endif
  1093.  
  1094.         // create the menu
  1095.         newmenu_do1(NULL, "Screen Resolution", mc, m, unused_newmenu_subfunction, unused_newmenu_userdata, 0);
  1096.  
  1097.         // menu is done, now do what we need to do
  1098.  
  1099.         // check which resolution field was selected
  1100. #if SDL_MAJOR_VERSION == 1
  1101.         unsigned i;
  1102.         for (i = 0; i <= mc; i++)
  1103.                 if (m[i].type == NM_TYPE_RADIO && m[i].radio().group == 0 && m[i].value == 1)
  1104.                         break;
  1105.  
  1106.         // now check for fullscreen toggle and apply if necessary
  1107.         if (m[opt_fullscr].value != gr_check_fullscreen())
  1108.                 gr_toggle_fullscreen();
  1109. #endif
  1110.  
  1111.         screen_mode new_mode;
  1112. #if SDL_MAJOR_VERSION == 1
  1113.         if (i == opt_cval) // set custom resolution and aspect
  1114. #endif
  1115.         {
  1116.                 char revert[32];
  1117.                 char *x;
  1118.                 const char *errstr;
  1119.                 unsigned long w = strtoul(crestext.data(), &x, 10), h;
  1120.                 screen_mode cmode;
  1121.                 if (
  1122.                         ((x == crestext.data() || *x != 'x' || !x[1] || ((h = strtoul(x + 1, &x, 10)), *x)) && (errstr = "Entered resolution must\nbe formatted as\n<number>x<number>", true)) ||
  1123.                         ((w < 320 || h < 200) && (errstr = "Entered resolution must\nbe at least 320x200", true))
  1124.                         )
  1125.                 {
  1126.                         cmode = Game_screen_mode;
  1127.                         w = SM_W(cmode);
  1128.                         h = SM_H(cmode);
  1129.                         snprintf(revert, sizeof(revert), "Revert to %lux%lu", w, h);
  1130.                         nm_messagebox_str(TXT_WARNING, revert, errstr);
  1131.                 }
  1132.                 else
  1133.                 {
  1134.                         cmode.width = w;
  1135.                         cmode.height = h;
  1136.                 }
  1137.                 auto casp = cmode;
  1138.                 w = strtoul(casptext.data(), &x, 10);
  1139.                 if (
  1140.                         ((x == casptext.data() || *x != 'x' || !x[1] || ((h = strtoul(x + 1, &x, 10)), *x)) && (errstr = "Entered aspect ratio must\nbe formatted as\n<number>x<number>", true)) ||
  1141.                         ((!w || !h) && (errstr = "Entered aspect ratio must\nnot use 0 term", true))
  1142.                         )
  1143.                 {
  1144.                         nm_messagebox_str(TXT_WARNING, "IGNORE ASPECT RATIO", errstr);
  1145.                 }
  1146.                 else
  1147.                 {
  1148.                         // we even have a custom aspect set up
  1149.                         casp.width = w;
  1150.                         casp.height = h;
  1151.                 }
  1152.                 const auto g = gcd(SM_W(casp), SM_H(casp));
  1153.                 GameCfg.AspectY = SM_W(casp) / g;
  1154.                 GameCfg.AspectX = SM_H(casp) / g;
  1155.                 new_mode = cmode;
  1156.         }
  1157. #if SDL_MAJOR_VERSION == 1
  1158.         else if (i < num_presets) // set preset resolution
  1159.         {
  1160.                 new_mode = modes[i];
  1161.                 const auto g = gcd(SM_W(new_mode), SM_H(new_mode));
  1162.                 GameCfg.AspectY = SM_W(new_mode) / g;
  1163.                 GameCfg.AspectX = SM_H(new_mode) / g;
  1164.         }
  1165. #endif
  1166.  
  1167.         // clean up and apply everything
  1168.         newmenu_free_background();
  1169.         set_screen_mode(SCREEN_MENU);
  1170.         if (new_mode != Game_screen_mode)
  1171.         {
  1172.                 gr_set_mode(new_mode);
  1173.                 Game_screen_mode = new_mode;
  1174.                 if (Game_wind) // shortly activate Game_wind so it's canvas will align to new resolution. really minor glitch but whatever
  1175.                 {
  1176.                         {
  1177.                                 const d_event event{EVENT_WINDOW_ACTIVATED};
  1178.                                 WINDOW_SEND_EVENT(Game_wind);
  1179.                         }
  1180.                         {
  1181.                                 const d_event event{EVENT_WINDOW_DEACTIVATED};
  1182.                                 WINDOW_SEND_EVENT(Game_wind);
  1183.                         }
  1184.                 }
  1185.         }
  1186.         game_init_render_buffers(SM_W(Game_screen_mode), SM_H(Game_screen_mode));
  1187. }
  1188.  
  1189. static void input_config_keyboard()
  1190. {
  1191. #define DXX_INPUT_SENSITIVITY(VERB,OPT,VAL)     \
  1192.         DXX_MENUITEM(VERB, SLIDER, TXT_TURN_LR, opt_##OPT##_turn_lr, VAL[0], 0, 16)     \
  1193.         DXX_MENUITEM(VERB, SLIDER, TXT_PITCH_UD, opt_##OPT##_pitch_ud, VAL[1], 0, 16)   \
  1194.         DXX_MENUITEM(VERB, SLIDER, TXT_SLIDE_LR, opt_##OPT##_slide_lr, VAL[2], 0, 16)   \
  1195.         DXX_MENUITEM(VERB, SLIDER, TXT_SLIDE_UD, opt_##OPT##_slide_ud, VAL[3], 0, 16)   \
  1196.         DXX_MENUITEM(VERB, SLIDER, TXT_BANK_LR, opt_##OPT##_bank_lr, VAL[4], 0, 16)     \
  1197.  
  1198. #define DXX_INPUT_CONFIG_MENU(VERB)     \
  1199.         DXX_MENUITEM(VERB, TEXT, "Keyboard Sensitivity:", opt_label_kb) \
  1200.         DXX_INPUT_SENSITIVITY(VERB,kb,PlayerCfg.KeyboardSens)                \
  1201.  
  1202.  
  1203.         class menu_items
  1204.         {
  1205.         public:
  1206.                 enum
  1207.                 {
  1208.                         DXX_INPUT_CONFIG_MENU(ENUM)
  1209.                 };
  1210.                 std::array<newmenu_item, DXX_INPUT_CONFIG_MENU(COUNT)> m;
  1211.                 menu_items()
  1212.                 {
  1213.                         DXX_INPUT_CONFIG_MENU(ADD);
  1214.                 }
  1215.         };
  1216. #undef DXX_INPUT_CONFIG_MENU
  1217. #undef DXX_INPUT_SENSITIVITY
  1218.         menu_items items;
  1219.         newmenu_do1(nullptr, "Keyboard Calibration", items.m.size(), items.m.data(), unused_newmenu_subfunction, unused_newmenu_userdata, 1);
  1220.  
  1221.         constexpr uint_fast32_t keysens = items.opt_label_kb + 1;
  1222.         const auto &m = items.m;
  1223.  
  1224.         range_for (const unsigned i, xrange(5u))
  1225.         {
  1226.                 PlayerCfg.KeyboardSens[i] = m[keysens+i].value;
  1227.         }
  1228. }
  1229.  
  1230. static void input_config_mouse()
  1231. {
  1232. #define DXX_INPUT_SENSITIVITY(VERB,OPT,VAL)                                \
  1233.         DXX_MENUITEM(VERB, SLIDER, TXT_TURN_LR, opt_##OPT##_turn_lr, VAL[0], 0, 16)     \
  1234.         DXX_MENUITEM(VERB, SLIDER, TXT_PITCH_UD, opt_##OPT##_pitch_ud, VAL[1], 0, 16)   \
  1235.         DXX_MENUITEM(VERB, SLIDER, TXT_SLIDE_LR, opt_##OPT##_slide_lr, VAL[2], 0, 16)   \
  1236.         DXX_MENUITEM(VERB, SLIDER, TXT_SLIDE_UD, opt_##OPT##_slide_ud, VAL[3], 0, 16)   \
  1237.         DXX_MENUITEM(VERB, SLIDER, TXT_BANK_LR, opt_##OPT##_bank_lr, VAL[4], 0, 16)     \
  1238.  
  1239. #define DXX_INPUT_THROTTLE_SENSITIVITY(VERB,OPT,VAL)    \
  1240.         DXX_INPUT_SENSITIVITY(VERB,OPT,VAL)     \
  1241.         DXX_MENUITEM(VERB, SLIDER, TXT_THROTTLE, opt_##OPT##_throttle, VAL[5], 0, 16)   \
  1242.  
  1243. #define DXX_INPUT_CONFIG_MENU(VERB)                                        \
  1244.         DXX_MENUITEM(VERB, TEXT, "Mouse Sensitivity:", opt_label_ms)                 \
  1245.         DXX_INPUT_THROTTLE_SENSITIVITY(VERB,ms,PlayerCfg.MouseSens)     \
  1246.         DXX_MENUITEM(VERB, TEXT, "", opt_label_blank_ms)        \
  1247.         DXX_MENUITEM(VERB, TEXT, "Mouse Overrun Buffer:", opt_label_mo) \
  1248.         DXX_INPUT_THROTTLE_SENSITIVITY(VERB,mo,PlayerCfg.MouseOverrun)  \
  1249.         DXX_MENUITEM(VERB, TEXT, "", opt_label_blank_mo)        \
  1250.         DXX_MENUITEM(VERB, TEXT, "Mouse FlightSim Deadzone:", opt_label_mfsd)   \
  1251.         DXX_MENUITEM(VERB, SLIDER, "X/Y", opt_mfsd_deadzone, PlayerCfg.MouseFSDead, 0, 16)      \
  1252.  
  1253.         class menu_items
  1254.         {
  1255.         public:
  1256.                 enum
  1257.                 {
  1258.                         DXX_INPUT_CONFIG_MENU(ENUM)
  1259.                 };
  1260.                 std::array<newmenu_item, DXX_INPUT_CONFIG_MENU(COUNT)> m;
  1261.                 menu_items()
  1262.                 {
  1263.                         DXX_INPUT_CONFIG_MENU(ADD);
  1264.                 }
  1265.         };
  1266. #undef DXX_INPUT_CONFIG_MENU
  1267.         menu_items items;
  1268.         newmenu_do1(nullptr, "Mouse Calibration", items.m.size(), items.m.data(), unused_newmenu_subfunction, unused_newmenu_userdata, 1);
  1269.  
  1270.         constexpr uint_fast32_t mousesens = items.opt_label_ms + 1;
  1271.     constexpr uint_fast32_t mouseoverrun = items.opt_label_mo + 1;
  1272.         const auto &m = items.m;
  1273.  
  1274.         for (unsigned i = 0; i <= 5; i++)
  1275.         {
  1276.  
  1277.                 PlayerCfg.MouseSens[i] = m[mousesens+i].value;
  1278.         PlayerCfg.MouseOverrun[i] = m[mouseoverrun+i].value;
  1279.         }
  1280.         constexpr uint_fast32_t mousefsdead = items.opt_mfsd_deadzone;
  1281.         PlayerCfg.MouseFSDead = m[mousefsdead].value;
  1282. }
  1283.  
  1284. #if DXX_MAX_AXES_PER_JOYSTICK
  1285. static void input_config_joystick()
  1286. {
  1287. #define DXX_INPUT_CONFIG_MENU(VERB)                                        \
  1288.         DXX_MENUITEM(VERB, TEXT, "Joystick Sensitivity:", opt_label_js)           \
  1289.         DXX_INPUT_THROTTLE_SENSITIVITY(VERB,js,PlayerCfg.JoystickSens)  \
  1290.         DXX_MENUITEM(VERB, TEXT, "", opt_label_blank_js)        \
  1291.         DXX_MENUITEM(VERB, TEXT, "Joystick Linearity:", opt_label_jl)   \
  1292.         DXX_INPUT_THROTTLE_SENSITIVITY(VERB,jl,PlayerCfg.JoystickLinear)          \
  1293.         DXX_MENUITEM(VERB, TEXT, "", opt_label_blank_jl)        \
  1294.         DXX_MENUITEM(VERB, TEXT, "Joystick Linear Speed:", opt_label_jp)        \
  1295.         DXX_INPUT_THROTTLE_SENSITIVITY(VERB,jp,PlayerCfg.JoystickSpeed)    \
  1296.         DXX_MENUITEM(VERB, TEXT, "", opt_label_blank_jp)        \
  1297.         DXX_MENUITEM(VERB, TEXT, "Joystick Deadzone:", opt_label_jd)    \
  1298.         DXX_INPUT_THROTTLE_SENSITIVITY(VERB,jd,PlayerCfg.JoystickDead)      \
  1299.  
  1300.         class menu_items
  1301.         {
  1302.         public:
  1303.                 enum
  1304.                 {
  1305.                         DXX_INPUT_CONFIG_MENU(ENUM)
  1306.                 };
  1307.                 std::array<newmenu_item, DXX_INPUT_CONFIG_MENU(COUNT)> m;
  1308.                 menu_items()
  1309.                 {
  1310.                         DXX_INPUT_CONFIG_MENU(ADD);
  1311.                 }
  1312.         };
  1313. #undef DXX_INPUT_CONFIG_MENU
  1314.         menu_items items;
  1315.         newmenu_do1(nullptr, "Joystick Calibration", items.m.size(), items.m.data(), unused_newmenu_subfunction, unused_newmenu_userdata, 1);
  1316.  
  1317.         constexpr uint_fast32_t joysens = items.opt_label_js + 1;
  1318.         constexpr uint_fast32_t joylin = items.opt_label_jl + 1;
  1319.         constexpr uint_fast32_t joyspd = items.opt_label_jp + 1;
  1320.         constexpr uint_fast32_t joydead = items.opt_label_jd + 1;
  1321.         const auto &m = items.m;
  1322.  
  1323.         for (unsigned i = 0; i <= 5; i++)
  1324.         {
  1325.                 PlayerCfg.JoystickLinear[i] = m[joylin+i].value;
  1326.                 PlayerCfg.JoystickSpeed[i] = m[joyspd+i].value;
  1327.                 PlayerCfg.JoystickSens[i] = m[joysens+i].value;
  1328.                 PlayerCfg.JoystickDead[i] = m[joydead+i].value;
  1329.         }
  1330. }
  1331. #endif
  1332.  
  1333. #undef DXX_INPUT_THROTTLE_SENSITIVITY
  1334. #undef DXX_INPUT_SENSITIVITY
  1335.  
  1336. namespace {
  1337.  
  1338. class input_config_menu_items
  1339. {
  1340. #if DXX_MAX_JOYSTICKS
  1341. #define DXX_INPUT_CONFIG_JOYSTICK_ITEM(I)       I
  1342. #else
  1343. #define DXX_INPUT_CONFIG_JOYSTICK_ITEM(I)
  1344. #endif
  1345.  
  1346. #if DXX_MAX_AXES_PER_JOYSTICK
  1347. #define DXX_INPUT_CONFIG_JOYSTICK_AXIS_ITEM(I)  I
  1348. #else
  1349. #define DXX_INPUT_CONFIG_JOYSTICK_AXIS_ITEM(I)
  1350. #endif
  1351.  
  1352. #define DXX_INPUT_CONFIG_MENU(VERB)     \
  1353.         DXX_INPUT_CONFIG_JOYSTICK_ITEM(DXX_MENUITEM(VERB, CHECK, "Use joystick", opt_ic_usejoy, PlayerCfg.ControlType & CONTROL_USING_JOYSTICK))        \
  1354.         DXX_MENUITEM(VERB, CHECK, "Use mouse", opt_ic_usemouse, PlayerCfg.ControlType & CONTROL_USING_MOUSE)    \
  1355.         DXX_MENUITEM(VERB, TEXT, "", opt_label_blank_use)       \
  1356.         DXX_MENUITEM(VERB, MENU, TXT_CUST_KEYBOARD, opt_ic_confkey)     \
  1357.         DXX_INPUT_CONFIG_JOYSTICK_ITEM(DXX_MENUITEM(VERB, MENU, "Customize Joystick", opt_ic_confjoy))  \
  1358.         DXX_MENUITEM(VERB, MENU, "Customize Mouse", opt_ic_confmouse)   \
  1359.         DXX_MENUITEM(VERB, MENU, "Customize Weapon Keys", opt_ic_confweap)      \
  1360.         DXX_MENUITEM(VERB, TEXT, "", opt_label_blank_customize) \
  1361.         DXX_MENUITEM(VERB, TEXT, "Mouse Control Type:", opt_label_mouse_control_type)   \
  1362.         DXX_MENUITEM(VERB, RADIO, "Normal", opt_mouse_control_normal, PlayerCfg.MouseFlightSim == 0, optgrp_mouse_control_type) \
  1363.         DXX_MENUITEM(VERB, RADIO, "FlightSim", opt_mouse_control_flightsim, PlayerCfg.MouseFlightSim == 1, optgrp_mouse_control_type)   \
  1364.         DXX_MENUITEM(VERB, TEXT, "", opt_label_blank_mouse_control_type)        \
  1365.         DXX_MENUITEM(VERB, MENU, "Keyboard Calibration", opt_ic_keyboard)               \
  1366.         DXX_MENUITEM(VERB, MENU, "Mouse Calibration", opt_ic_mouse)                   \
  1367.         DXX_INPUT_CONFIG_JOYSTICK_AXIS_ITEM(DXX_MENUITEM(VERB, MENU, "Joystick Calibration", opt_ic_joystick))           \
  1368.         DXX_MENUITEM(VERB, TEXT, "", opt_label_blank_sensitivity_deadzone)      \
  1369.         DXX_MENUITEM(VERB, CHECK, "Keep Keyboard/Mouse focus", opt_ic_grabinput, CGameCfg.Grabinput)    \
  1370.         DXX_MENUITEM(VERB, CHECK, "Mouse FlightSim Indicator", opt_ic_mousefsgauge, PlayerCfg.MouseFSIndicator) \
  1371.         DXX_MENUITEM(VERB, TEXT, "", opt_label_blank_focus)     \
  1372.         DXX_MENUITEM(VERB, TEXT, "When dead, respawn by pressing:", opt_label_respawn_mode)     \
  1373.         DXX_MENUITEM(VERB, RADIO, "Any key", opt_respawn_any_key, PlayerCfg.RespawnMode == RespawnPress::Any, optgrp_respawn_mode)      \
  1374.         DXX_MENUITEM(VERB, RADIO, "Fire keys (pri., sec., flare)", opt_respawn_fire_key, PlayerCfg.RespawnMode == RespawnPress::Fire, optgrp_respawn_mode)      \
  1375.         DXX_MENUITEM(VERB, TEXT, "", opt_label_blank_respawn)   \
  1376.         DXX_MENUITEM(VERB, TEXT, "Uncapped turning in:", opt_label_mouselook_mode)      \
  1377.         DXX_MENUITEM(VERB, CHECK, "Single player", opt_ic_mouselook_sp, PlayerCfg.MouselookFlags & MouselookMode::Singleplayer) \
  1378.         DXX_MENUITEM(VERB, CHECK, "Multi Coop (if host allows)", opt_ic_mouselook_mp_cooperative, PlayerCfg.MouselookFlags & MouselookMode::MPCoop)     \
  1379.         DXX_MENUITEM(VERB, CHECK, "Multi Anarchy (if host allows)", opt_ic_mouselook_mp_anarchy, PlayerCfg.MouselookFlags & MouselookMode::MPAnarchy)   \
  1380.         DXX_MENUITEM(VERB, TEXT, "", opt_label_blank_mouselook) \
  1381.         DXX_MENUITEM(VERB, MENU, "GAME SYSTEM KEYS", opt_ic_help0)      \
  1382.         DXX_MENUITEM(VERB, MENU, "NETGAME SYSTEM KEYS", opt_ic_help1)   \
  1383.         DXX_MENUITEM(VERB, MENU, "DEMO SYSTEM KEYS", opt_ic_help2)      \
  1384.  
  1385. public:
  1386.         enum
  1387.         {
  1388.                 optgrp_mouse_control_type,
  1389.                 optgrp_respawn_mode,
  1390.         };
  1391.         enum
  1392.         {
  1393.                 DXX_INPUT_CONFIG_MENU(ENUM)
  1394.         };
  1395.         std::array<newmenu_item, DXX_INPUT_CONFIG_MENU(COUNT)> m;
  1396.         input_config_menu_items()
  1397.         {
  1398.                 DXX_INPUT_CONFIG_MENU(ADD);
  1399.         }
  1400.         static int menuset(newmenu *, const d_event &event, input_config_menu_items *pitems);
  1401. #undef DXX_INPUT_CONFIG_MENU
  1402. #undef DXX_INPUT_CONFIG_JOYSTICK_AXIS_ITEM
  1403. #undef DXX_INPUT_CONFIG_JOYSTICK_ITEM
  1404. };
  1405.  
  1406. }
  1407.  
  1408. int input_config_menu_items::menuset(newmenu *, const d_event &event, input_config_menu_items *pitems)
  1409. {
  1410.         const auto &items = pitems->m;
  1411.         switch (event.type)
  1412.         {
  1413.                 case EVENT_NEWMENU_CHANGED:
  1414.                 {
  1415.                         const auto citem = static_cast<const d_change_event &>(event).citem;
  1416.                         MouselookMode mousemode;
  1417. #if DXX_MAX_JOYSTICKS
  1418.                         if (citem == opt_ic_usejoy)
  1419.                         {
  1420.                                 constexpr auto flag = CONTROL_USING_JOYSTICK;
  1421.                                 if (items[citem].value)
  1422.                                         PlayerCfg.ControlType |= flag;
  1423.                                 else
  1424.                                         PlayerCfg.ControlType &= ~flag;
  1425.                         }
  1426. #endif
  1427.                         if (citem == opt_ic_usemouse)
  1428.                         {
  1429.                                 constexpr auto flag = CONTROL_USING_MOUSE;
  1430.                                 if (items[citem].value)
  1431.                                         PlayerCfg.ControlType |= flag;
  1432.                                 else
  1433.                                         PlayerCfg.ControlType &= ~flag;
  1434.                         }
  1435.                         if (citem == opt_mouse_control_normal)
  1436.                                 PlayerCfg.MouseFlightSim = 0;
  1437.                         if (citem == opt_mouse_control_flightsim)
  1438.                                 PlayerCfg.MouseFlightSim = 1;
  1439.                         if (citem == opt_ic_grabinput)
  1440.                                 CGameCfg.Grabinput = items[citem].value;
  1441.                         if (citem == opt_ic_mousefsgauge)
  1442.                                 PlayerCfg.MouseFSIndicator = items[citem].value;
  1443.                         else if (citem == opt_respawn_any_key)
  1444.                                 PlayerCfg.RespawnMode = RespawnPress::Any;
  1445.                         else if (citem == opt_respawn_fire_key)
  1446.                                 PlayerCfg.RespawnMode = RespawnPress::Fire;
  1447.                         else if ((citem == opt_ic_mouselook_sp && (mousemode = MouselookMode::Singleplayer, true)) ||
  1448.                                 (citem == opt_ic_mouselook_mp_cooperative && (mousemode = MouselookMode::MPCoop, true)) ||
  1449.                                 (citem == opt_ic_mouselook_mp_anarchy && (mousemode = MouselookMode::MPAnarchy, true)))
  1450.                         {
  1451.                                 if (items[citem].value)
  1452.                                         PlayerCfg.MouselookFlags |= mousemode;
  1453.                                 else
  1454.                                         PlayerCfg.MouselookFlags &= ~mousemode;
  1455.                         }
  1456.                         break;
  1457.                 }
  1458.                 case EVENT_NEWMENU_SELECTED:
  1459.                 {
  1460.                         const auto citem = static_cast<const d_select_event &>(event).citem;
  1461.                         if (citem == opt_ic_confkey)
  1462.                                 kconfig(kconfig_type::keyboard);
  1463. #if DXX_MAX_JOYSTICKS
  1464.                         if (citem == opt_ic_confjoy)
  1465.                                 kconfig(kconfig_type::joystick);
  1466. #endif
  1467.                         if (citem == opt_ic_confmouse)
  1468.                                 kconfig(kconfig_type::mouse);
  1469.                         if (citem == opt_ic_confweap)
  1470.                                 kconfig(kconfig_type::rebirth);
  1471.                         if (citem == opt_ic_keyboard)
  1472.                                 input_config_keyboard();
  1473.                         if (citem == opt_ic_mouse)
  1474.                                 input_config_mouse();
  1475. #if DXX_MAX_AXES_PER_JOYSTICK
  1476.                         if (citem == opt_ic_joystick)
  1477.                                 input_config_joystick();
  1478. #endif
  1479.                         if (citem == opt_ic_help0)
  1480.                                 show_help();
  1481.                         if (citem == opt_ic_help1)
  1482.                                 show_netgame_help();
  1483.                         if (citem == opt_ic_help2)
  1484.                                 show_newdemo_help();
  1485.                         return 1;               // stay in menu
  1486.                 }
  1487.  
  1488.                 default:
  1489.                         break;
  1490.         }
  1491.  
  1492.         return 0;
  1493. }
  1494.  
  1495. void input_config()
  1496. {
  1497.         input_config_menu_items menu_items;
  1498.         newmenu_do1(nullptr, TXT_CONTROLS, menu_items.m.size(), menu_items.m.data(), &input_config_menu_items::menuset, &menu_items, menu_items.opt_ic_confkey);
  1499. }
  1500.  
  1501. static void reticle_config()
  1502. {
  1503. #if DXX_USE_OGL
  1504. #define DXX_RETICLE_TYPE_OGL(VERB)      \
  1505.         DXX_MENUITEM(VERB, RADIO, "Classic Reboot", opt_reticle_classic_reboot, 0, optgrp_reticle)
  1506. #else
  1507. #define DXX_RETICLE_TYPE_OGL(VERB)
  1508. #endif
  1509. #define DXX_RETICLE_CONFIG_MENU(VERB)   \
  1510.         DXX_MENUITEM(VERB, TEXT, "Reticle Type:", opt_label_reticle_type)       \
  1511.         DXX_MENUITEM(VERB, RADIO, "Classic", opt_reticle_classic, 0, optgrp_reticle)    \
  1512.         DXX_RETICLE_TYPE_OGL(VERB)      \
  1513.         DXX_MENUITEM(VERB, RADIO, "None", opt_reticle_none, 0, optgrp_reticle)  \
  1514.         DXX_MENUITEM(VERB, RADIO, "X", opt_reticle_x, 0, optgrp_reticle)        \
  1515.         DXX_MENUITEM(VERB, RADIO, "Dot", opt_reticle_dot, 0, optgrp_reticle)    \
  1516.         DXX_MENUITEM(VERB, RADIO, "Circle", opt_reticle_circle, 0, optgrp_reticle)      \
  1517.         DXX_MENUITEM(VERB, RADIO, "Cross V1", opt_reticle_cross1, 0, optgrp_reticle)    \
  1518.         DXX_MENUITEM(VERB, RADIO, "Cross V2", opt_reticle_cross2, 0, optgrp_reticle)    \
  1519.         DXX_MENUITEM(VERB, RADIO, "Angle", opt_reticle_angle, 0, optgrp_reticle)        \
  1520.         DXX_MENUITEM(VERB, TEXT, "", opt_label_blank_reticle_type)      \
  1521.         DXX_MENUITEM(VERB, TEXT, "Reticle Color:", opt_label_reticle_color)     \
  1522.         DXX_MENUITEM(VERB, SCALE_SLIDER, "Red", opt_reticle_color_red, PlayerCfg.ReticleRGBA[0], 0, 16, 2)      \
  1523.         DXX_MENUITEM(VERB, SCALE_SLIDER, "Green", opt_reticle_color_green, PlayerCfg.ReticleRGBA[1], 0, 16, 2)  \
  1524.         DXX_MENUITEM(VERB, SCALE_SLIDER, "Blue", opt_reticle_color_blue, PlayerCfg.ReticleRGBA[2], 0, 16, 2)    \
  1525.         DXX_MENUITEM(VERB, SCALE_SLIDER, "Alpha", opt_reticle_color_alpha, PlayerCfg.ReticleRGBA[3], 0, 16, 2)  \
  1526.         DXX_MENUITEM(VERB, TEXT, "", opt_label_blank_reticle_color)     \
  1527.         DXX_MENUITEM(VERB, SLIDER, "Reticle Size:", opt_label_reticle_size, PlayerCfg.ReticleSize, 0, 4)        \
  1528.  
  1529.         class menu_items
  1530.         {
  1531.         public:
  1532.                 enum
  1533.                 {
  1534.                         optgrp_reticle,
  1535.                 };
  1536.                 enum
  1537.                 {
  1538.                         DXX_RETICLE_CONFIG_MENU(ENUM)
  1539.                 };
  1540.                 std::array<newmenu_item, DXX_RETICLE_CONFIG_MENU(COUNT)> m;
  1541.                 menu_items()
  1542.                 {
  1543.                         DXX_RETICLE_CONFIG_MENU(ADD);
  1544.                 }
  1545.                 void read()
  1546.                 {
  1547.                         DXX_RETICLE_CONFIG_MENU(READ);
  1548.                 }
  1549.         };
  1550. #undef DXX_RETICLE_CONFIG_MENU
  1551. #undef DXX_RETICLE_TYPE_OGL
  1552.         menu_items items;
  1553.         {
  1554.         auto i = PlayerCfg.ReticleType;
  1555. #if !DXX_USE_OGL
  1556.         if (i > 1) i--;
  1557. #endif
  1558.         items.m[items.opt_reticle_classic + i].value = 1;
  1559.         }
  1560.  
  1561.         newmenu_do1(nullptr, "Reticle Customization", items.m.size(), items.m.data(), unused_newmenu_subfunction, unused_newmenu_userdata, 1);
  1562.  
  1563.         for (uint_fast32_t i = items.opt_reticle_classic; i != items.opt_label_blank_reticle_type; ++i)
  1564.                 if (items.m[i].value)
  1565.                 {
  1566. #if !DXX_USE_OGL
  1567.                         if (i != items.opt_reticle_classic)
  1568.                                 ++i;
  1569. #endif
  1570.                         PlayerCfg.ReticleType = i - items.opt_reticle_classic;
  1571.                         break;
  1572.                 }
  1573.         items.read();
  1574. }
  1575.  
  1576. #if defined(DXX_BUILD_DESCENT_I)
  1577. #define DXX_GAME_SPECIFIC_HUDOPTIONS(VERB)      \
  1578.         DXX_MENUITEM(VERB, CHECK, "Always-on Bomb Counter",opt_d2bomb,PlayerCfg.BombGauge)      \
  1579.  
  1580. #elif defined(DXX_BUILD_DESCENT_II)
  1581. enum {
  1582.         optgrp_missileview,
  1583. };
  1584. #define DXX_GAME_SPECIFIC_HUDOPTIONS(VERB)      \
  1585.         DXX_MENUITEM(VERB, TEXT, "Missile view:", opt_missileview_label)        \
  1586.         DXX_MENUITEM(VERB, RADIO, "Disabled", opt_missileview_none, PlayerCfg.MissileViewEnabled == MissileViewMode::None, optgrp_missileview)  \
  1587.         DXX_MENUITEM(VERB, RADIO, "Only own missiles", opt_missileview_selfonly, PlayerCfg.MissileViewEnabled == MissileViewMode::EnabledSelfOnly, optgrp_missileview)  \
  1588.         DXX_MENUITEM(VERB, RADIO, "Friendly missiles, preferring self", opt_missileview_selfandallies, PlayerCfg.MissileViewEnabled == MissileViewMode::EnabledSelfAndAllies, optgrp_missileview)       \
  1589.         DXX_MENUITEM(VERB, CHECK, "Show guided missile in main display", opt_guidedbigview,PlayerCfg.GuidedInBigWindow )        \
  1590.  
  1591. #endif
  1592. #define DXX_HUD_MENU_OPTIONS(VERB)      \
  1593.         DXX_MENUITEM(VERB, MENU, "Reticle Customization...", opt_hud_reticlemenu)       \
  1594.         DXX_MENUITEM(VERB, CHECK, "Screenshots without HUD",opt_screenshot,PlayerCfg.PRShot)    \
  1595.         DXX_MENUITEM(VERB, CHECK, "No redundant pickup messages",opt_redundant,PlayerCfg.NoRedundancy)  \
  1596.         DXX_MENUITEM(VERB, CHECK, "Show Player chat only (Multi)",opt_playerchat,PlayerCfg.MultiMessages)       \
  1597.         DXX_MENUITEM(VERB, CHECK, "Show Player ping (Multi)",opt_playerping,PlayerCfg.MultiPingHud)     \
  1598.         DXX_MENUITEM(VERB, CHECK, "Cloak/Invulnerability Timers",opt_cloakinvultimer,PlayerCfg.CloakInvulTimer) \
  1599.         DXX_GAME_SPECIFIC_HUDOPTIONS(VERB)      \
  1600.  
  1601. enum {
  1602.         DXX_HUD_MENU_OPTIONS(ENUM)
  1603. };
  1604.  
  1605. static int hud_config_menuset(newmenu *, const d_event &event, const unused_newmenu_userdata_t *)
  1606. {
  1607.         switch (event.type)
  1608.         {
  1609.                 case EVENT_NEWMENU_SELECTED:
  1610.                 {
  1611.                         auto &citem = static_cast<const d_select_event &>(event).citem;
  1612.                         if (citem == opt_hud_reticlemenu)
  1613.                                 reticle_config();
  1614.                         return 1;               // stay in menu
  1615.                 }
  1616.  
  1617.                 default:
  1618.                         break;
  1619.         }
  1620.  
  1621.         return 0;
  1622. }
  1623.  
  1624. namespace dsx {
  1625. void hud_config()
  1626. {
  1627.         for (;;)
  1628.         {
  1629.                 std::array<newmenu_item, DXX_HUD_MENU_OPTIONS(COUNT)> m;
  1630.                 DXX_HUD_MENU_OPTIONS(ADD);
  1631.                 const auto i = newmenu_do1( NULL, "Hud Options", m.size(), m.data(), hud_config_menuset, unused_newmenu_userdata, 0 );
  1632.                 DXX_HUD_MENU_OPTIONS(READ);
  1633. #if defined(DXX_BUILD_DESCENT_II)
  1634.                 PlayerCfg.MissileViewEnabled = m[opt_missileview_selfandallies].value
  1635.                         ? MissileViewMode::EnabledSelfAndAllies
  1636.                         : (m[opt_missileview_selfonly].value
  1637.                                 ? MissileViewMode::EnabledSelfOnly
  1638.                                 : MissileViewMode::None);
  1639. #endif
  1640.                 if (i == -1)
  1641.                         break;
  1642.         }
  1643. }
  1644. }
  1645.  
  1646. #define DXX_GRAPHICS_MENU(VERB) \
  1647.         DXX_MENUITEM(VERB, MENU, "Screen resolution...", opt_gr_screenres)      \
  1648.         DXX_MENUITEM(VERB, MENU, "HUD Options...", opt_gr_hudmenu)      \
  1649.         DXX_MENUITEM(VERB, SLIDER, TXT_BRIGHTNESS, opt_gr_brightness, gr_palette_get_gamma(), 0, 16)    \
  1650.         DXX_MENUITEM(VERB, TEXT, "", blank1)    \
  1651.         DXX_OGL0_GRAPHICS_MENU(VERB)    \
  1652.         DXX_OGL1_GRAPHICS_MENU(VERB)    \
  1653.         DXX_MENUITEM(VERB, CHECK, "FPS Counter", opt_gr_fpsindi, CGameCfg.FPSIndicator) \
  1654.  
  1655. #if DXX_USE_OGL
  1656. enum {
  1657.         optgrp_texfilt,
  1658. };
  1659. #define DXX_OGL0_GRAPHICS_MENU(VERB)    \
  1660.         DXX_MENUITEM(VERB, TEXT, "Texture Filtering:", opt_gr_texfilt)  \
  1661.         DXX_MENUITEM(VERB, RADIO, "Classic", opt_filter_none, 0, optgrp_texfilt)        \
  1662.         DXX_MENUITEM(VERB, RADIO, "Blocky Filtered", opt_filter_upscale, 0, optgrp_texfilt)     \
  1663.         DXX_MENUITEM(VERB, RADIO, "Smooth", opt_filter_trilinear, 0, optgrp_texfilt)    \
  1664.         DXX_MENUITEM(VERB, CHECK, "Anisotropic Filtering", opt_filter_anisotropy, CGameCfg.TexAnisotropy)       \
  1665.         D2X_OGL_GRAPHICS_MENU(VERB)     \
  1666.         DXX_MENUITEM(VERB, TEXT, "", blank2)    \
  1667.  
  1668. #define DXX_OGL1_GRAPHICS_MENU(VERB)    \
  1669.         DXX_MENUITEM(VERB, CHECK, "Transparency Effects", opt_gr_alphafx, PlayerCfg.AlphaEffects)       \
  1670.         DXX_MENUITEM(VERB, CHECK, "Colored Dynamic Light", opt_gr_dynlightcolor, PlayerCfg.DynLightColor)       \
  1671.         DXX_MENUITEM(VERB, CHECK, "VSync", opt_gr_vsync, CGameCfg.VSync)        \
  1672.         DXX_MENUITEM(VERB, CHECK, "4x multisampling", opt_gr_multisample, CGameCfg.Multisample) \
  1673.  
  1674. #if defined(DXX_BUILD_DESCENT_I)
  1675. #define D2X_OGL_GRAPHICS_MENU(VERB)
  1676. #elif defined(DXX_BUILD_DESCENT_II)
  1677. #define D2X_OGL_GRAPHICS_MENU(VERB)     \
  1678.         DXX_MENUITEM(VERB, CHECK, "Cutscene Smoothing", opt_gr_movietexfilt, GameCfg.MovieTexFilt)
  1679. #endif
  1680.  
  1681. #else
  1682. #define DXX_OGL0_GRAPHICS_MENU(VERB)
  1683. #define DXX_OGL1_GRAPHICS_MENU(VERB)
  1684. #endif
  1685.  
  1686. enum {
  1687.         DXX_GRAPHICS_MENU(ENUM)
  1688. };
  1689.  
  1690. static int graphics_config_menuset(newmenu *, const d_event &event, newmenu_item *const items)
  1691. {
  1692.         switch (event.type)
  1693.         {
  1694.                 case EVENT_NEWMENU_CHANGED:
  1695.                 {
  1696.                         auto &citem = static_cast<const d_change_event &>(event).citem;
  1697.                         if (citem == opt_gr_brightness)
  1698.                                 gr_palette_set_gamma(items[citem].value);
  1699. #if DXX_USE_OGL
  1700.                         else
  1701.                         if (citem == opt_filter_anisotropy && ogl_maxanisotropy <= 1.0)
  1702.                         {
  1703.                                 nm_messagebox( TXT_ERROR, 1, TXT_OK, "Anisotropic Filtering not\nsupported by your hardware/driver.");
  1704.                                 items[opt_filter_anisotropy].value = 0;
  1705.                         }
  1706. #endif
  1707.                         break;
  1708.                 }
  1709.                 case EVENT_NEWMENU_SELECTED:
  1710.                 {
  1711.                         auto &citem = static_cast<const d_select_event &>(event).citem;
  1712.                         if (citem == opt_gr_screenres)
  1713.                                 change_res();
  1714.                         if (citem == opt_gr_hudmenu)
  1715.                                 hud_config();
  1716.                         return 1;               // stay in menu
  1717.                 }
  1718.  
  1719.                 default:
  1720.                         break;
  1721.         }
  1722.  
  1723.         return 0;
  1724. }
  1725.  
  1726. namespace dsx {
  1727. void graphics_config()
  1728. {
  1729.         std::array<newmenu_item, DXX_GRAPHICS_MENU(COUNT)> m;
  1730.         DXX_GRAPHICS_MENU(ADD);
  1731.  
  1732. #if DXX_USE_OGL
  1733.         m[opt_filter_none+CGameCfg.TexFilt].value=1;
  1734. #endif
  1735.  
  1736.         newmenu_do1(nullptr, "Graphics Options", m.size(), m.data(), graphics_config_menuset, m.data(), 0);
  1737.  
  1738. #if DXX_USE_OGL
  1739.         if (CGameCfg.VSync != m[opt_gr_vsync].value || CGameCfg.Multisample != m[opt_gr_multisample].value)
  1740.                 nm_messagebox( NULL, 1, TXT_OK, "Setting VSync or 4x Multisample\nrequires restart on some systems.");
  1741.  
  1742.         range_for (const uint_fast32_t i, xrange(3u))
  1743.                 if (m[i+opt_filter_none].value)
  1744.                 {
  1745.                         CGameCfg.TexFilt = i;
  1746.                         break;
  1747.                 }
  1748.         CGameCfg.TexAnisotropy = m[opt_filter_anisotropy].value;
  1749. #if defined(DXX_BUILD_DESCENT_II)
  1750.         GameCfg.MovieTexFilt = m[opt_gr_movietexfilt].value;
  1751. #endif
  1752.         PlayerCfg.AlphaEffects = m[opt_gr_alphafx].value;
  1753.         PlayerCfg.DynLightColor = m[opt_gr_dynlightcolor].value;
  1754.         CGameCfg.VSync = m[opt_gr_vsync].value;
  1755.         CGameCfg.Multisample = m[opt_gr_multisample].value;
  1756. #endif
  1757.         GameCfg.GammaLevel = m[opt_gr_brightness].value;
  1758.         CGameCfg.FPSIndicator = m[opt_gr_fpsindi].value;
  1759. #if DXX_USE_OGL
  1760.         gr_set_attributes();
  1761.         gr_set_mode(Game_screen_mode);
  1762. #endif
  1763. }
  1764. }
  1765.  
  1766. #if PHYSFS_VER_MAJOR >= 2
  1767. namespace {
  1768.  
  1769. struct browser
  1770. {
  1771.         browser(const partial_range_t<const file_extension_t *> &r) :
  1772.                 ext_range(r)
  1773.         {
  1774.         }
  1775.         const char      *title;                 // The title - needed for making another listbox when changing directory
  1776.         window_event_result (*when_selected)(void *userdata, const char *filename);     // What to do when something chosen
  1777.         void    *userdata;              // Whatever you want passed to when_selected
  1778.         string_array_t list;
  1779.         // List of file extensions we're looking for (if looking for a music file many types are possible)
  1780.         const partial_range_t<const file_extension_t *> ext_range;
  1781.         int             select_dir;             // Allow selecting the current directory (e.g. for Jukebox level song directory)
  1782.         int             new_path;               // Whether the view_path is a new searchpath, if so, remove it when finished
  1783.         std::array<char, PATH_MAX> view_path;   // The absolute path we're currently looking at
  1784. };
  1785.  
  1786. }
  1787.  
  1788. static /*void*/PHYSFS_EnumerateCallbackResult list_dir_el(void *vb, const char *, const char *fname) // Pierre-Marie Baty -- work around PHYSFS_enumerateFilesCallback() deprecation
  1789. {
  1790.         browser *b = reinterpret_cast<browser *>(vb);
  1791.         const char *r = PHYSFS_getRealDir(fname);
  1792.         if (!r)
  1793.                 r = "";
  1794.         if (!strcmp(r, b->view_path.data()) && (/*PHYSFS_*/isDirectory(fname) || PHYSFSX_checkMatchingExtension(fname, b->ext_range)) // Pierre-Marie Baty -- work around PHYSFS_isDirectory() deprecation
  1795. #if defined(__APPLE__) && defined(__MACH__)
  1796.                 && d_stricmp(fname, "Volumes")  // this messes things up, use '..' instead
  1797. #endif
  1798.                 )
  1799.                 b->list.add(fname);
  1800.         return PHYSFS_ENUM_OK; // Pierre-Marie Baty -- work around PHYSFS_enumerateFilesCallback() deprecation
  1801. }
  1802.  
  1803. static int list_directory(browser *b)
  1804. {
  1805.         b->list.clear();
  1806.         b->list.add("..");              // go to parent directory
  1807.         if (b->select_dir)
  1808.         {
  1809.                 b->list.add("<this directory>");        // choose the directory being viewed
  1810.         }
  1811.  
  1812.         PHYSFS_enumerate/*FilesCallback*/("", list_dir_el, b); // Pierre-Marie Baty -- work around PHYSFS_enumerateFilesCallback() deprecation
  1813.         b->list.tidy(1 + (b->select_dir ? 1 : 0),
  1814. #ifdef __linux__
  1815.                                           strcmp
  1816. #else
  1817.                                           d_stricmp
  1818. #endif
  1819.                                           );
  1820.  
  1821.         return 1;
  1822. }
  1823.  
  1824. static window_event_result select_file_handler(listbox *menu,const d_event &event, browser *b)
  1825. {
  1826.         std::array<char, PATH_MAX> newpath{};
  1827.         const char **list = listbox_get_items(menu);
  1828.         const char *sep = PHYSFS_getDirSeparator();
  1829.         switch (event.type)
  1830.         {
  1831. #ifdef _WIN32
  1832.                 case EVENT_KEY_COMMAND:
  1833.                 {
  1834.                         if (event_key_get(event) == KEY_CTRLED + KEY_D)
  1835.                         {
  1836.                                 char text[4] = "c";
  1837.                                 int rval = 0;
  1838.  
  1839.                                 std::array<newmenu_item, 1> m{{
  1840.                                         nm_item_input(text),
  1841.                                 }};
  1842.                                 rval = newmenu_do( NULL, "Enter drive letter", m, unused_newmenu_subfunction, unused_newmenu_userdata );
  1843.                                 text[1] = '\0';
  1844.                                 snprintf(newpath.data(), newpath.size(), "%s:%s", text, sep);
  1845.                                 if (!rval && text[0])
  1846.                                 {
  1847.                                         select_file_recursive(b->title, newpath, b->ext_range, b->select_dir, b->when_selected, b->userdata);
  1848.                                         // close old box.
  1849.                                         return window_event_result::close;
  1850.                                 }
  1851.                                 return window_event_result::handled;
  1852.                         }
  1853.                         break;
  1854.                 }
  1855. #endif
  1856.                 case EVENT_NEWMENU_SELECTED:
  1857.                 {
  1858.                         auto &citem = static_cast<const d_select_event &>(event).citem;
  1859.                         newpath = b->view_path;
  1860.                         if (citem == 0)         // go to parent dir
  1861.                         {
  1862.                                 const size_t len_newpath = strlen(newpath.data());
  1863.                                 const size_t len_sep = strlen(sep);
  1864.                                 if (auto p = strstr(&newpath[len_newpath - len_sep], sep))
  1865.                                         if (p != strstr(newpath.data(), sep))   // if this isn't the only separator (i.e. it's not about to look at the root)
  1866.                                                 *p = 0;
  1867.  
  1868.                                 auto p = &newpath[len_newpath - 1];
  1869.                                 while (p != newpath.begin() && strncmp(p, sep, len_sep))        // make sure full separator string is matched (typically is)
  1870.                                         p--;
  1871.  
  1872.                                 if (p == strstr(newpath.data(), sep))   // Look at root directory next, if not already
  1873.                                 {
  1874. #if defined(__APPLE__) && defined(__MACH__)
  1875.                                         if (!d_stricmp(p, "/Volumes"))
  1876.                                                 return window_event_result::handled;
  1877. #endif
  1878.                                         if (p[len_sep] != '\0')
  1879.                                                 p[len_sep] = '\0';
  1880.                                         else
  1881.                                         {
  1882. #if defined(__APPLE__) && defined(__MACH__)
  1883.                                                 // For Mac OS X, list all active volumes if we leave the root
  1884.                                                 strcpy(newpath.data(), "/Volumes");
  1885. #else
  1886.                                                 return window_event_result::handled;
  1887. #endif
  1888.                                         }
  1889.                                 }
  1890.                                 else
  1891.                                         *p = '\0';
  1892.                         }
  1893.                         else if (citem == 1 && b->select_dir)
  1894.                                 return (*b->when_selected)(b->userdata, "");
  1895.                         else
  1896.                         {
  1897.                                 const size_t len_newpath = strlen(newpath.data());
  1898.                                 const size_t len_item = strlen(list[citem]);
  1899.                                 if (len_newpath + len_item < newpath.size())
  1900.                                 {
  1901.                                         const size_t len_sep = strlen(sep);
  1902.                                         snprintf(&newpath[len_newpath], newpath.size() - len_newpath, "%s%s", strncmp(&newpath[len_newpath - len_sep], sep, len_sep) ? sep : "", list[citem]);
  1903.                                 }
  1904.                         }
  1905.                         if ((citem == 0) || /*PHYSFS_*/isDirectory(list[citem])) // Pierre-Marie Baty -- work around PHYSFS_isDirectory() deprecation
  1906.                         {
  1907.                                 // If it fails, stay in this one
  1908.                                 return select_file_recursive(b->title, newpath, b->ext_range, b->select_dir, b->when_selected, b->userdata) ? window_event_result::close : window_event_result::handled;
  1909.                         }
  1910.                         return (*b->when_selected)(b->userdata, list[citem]);
  1911.                 }
  1912.                 case EVENT_WINDOW_CLOSE:
  1913.                         if (b->new_path)
  1914.                                 PHYSFS_unmount(b->view_path.data()); // Pierre-Marie Baty -- work around PHYSFS_removeFromSearchPath() deprecation
  1915.  
  1916.                         std::default_delete<browser>()(b);
  1917.                         break;
  1918.  
  1919.                 default:
  1920.                         break;
  1921.         }
  1922.  
  1923.         return window_event_result::ignored;
  1924. }
  1925.  
  1926. static int select_file_recursive2(const char *title, const std::array<char, PATH_MAX> &orig_path_storage, const partial_range_t<const file_extension_t *> &ext_range, int select_dir, select_file_subfunction<void> when_selected, void *userdata)
  1927. {
  1928.         auto orig_path = orig_path_storage.data();
  1929.         const char *sep = PHYSFS_getDirSeparator();
  1930.         std::array<char, PATH_MAX> new_path;
  1931.  
  1932.         auto b = std::make_unique<browser>(ext_range);
  1933.         b->title = title;
  1934.         b->when_selected = when_selected;
  1935.         b->userdata = userdata;
  1936.         b->select_dir = select_dir;
  1937.         b->view_path[0] = '\0';
  1938.         b->new_path = 1;
  1939.  
  1940.         // Check for a PhysicsFS path first, saves complication!
  1941.         if (strncmp(orig_path, sep, strlen(sep)) && PHYSFSX_exists(orig_path,0))
  1942.         {
  1943.                 PHYSFSX_getRealPath(orig_path, new_path);
  1944.                 orig_path = new_path.data();
  1945.         }
  1946.  
  1947.         // Set the viewing directory to orig_path, or some parent of it
  1948.         if (orig_path)
  1949.         {
  1950.                 const char *base;
  1951.                 // Must make this an absolute path for directory browsing to work properly
  1952. #ifdef _WIN32
  1953.                 if (!(isalpha(orig_path[0]) && (orig_path[1] == ':')))  // drive letter prompt (e.g. "C:"
  1954. #elif defined(macintosh)
  1955.                 if (orig_path[0] == ':')
  1956. #else
  1957.                 if (orig_path[0] != '/')
  1958. #endif
  1959.                 {
  1960. #ifdef macintosh
  1961.                         orig_path++;    // go past ':'
  1962. #endif
  1963.                         base = PHYSFS_getBaseDir();
  1964.                 }
  1965.                 else
  1966.                 {
  1967.                         base = "";
  1968.                 }
  1969.                 auto p = std::next(b->view_path.begin(), snprintf(b->view_path.data(), b->view_path.size(), "%s%s", base, orig_path) - 1);
  1970.                 const size_t len_sep = strlen(sep);
  1971.                 while (b->new_path = PHYSFSX_isNewPath(b->view_path.data()), !PHYSFS_mount(b->view_path.data(), NULL, 0)) // Pierre-Marie Baty -- work around PHYSFS_addToSearchPath() deprecation
  1972.                 {
  1973.                         while (p != b->view_path.begin() && strncmp(p, sep, len_sep))
  1974.                                 p--;
  1975.                         *p = '\0';
  1976.  
  1977.                         if (p == b->view_path.begin())
  1978.                                 break;
  1979.                 }
  1980.         }
  1981.  
  1982.         // Set to user directory if we couldn't find a searchpath
  1983.         if (!b->view_path[0])
  1984.         {
  1985.                 snprintf(b->view_path.data(), b->view_path.size(), "%s", /*PHYSFS_getUserDir()*/getpwuid(getuid())->pw_dir); // Pierre-Marie Baty -- work around PHYSFS_getUserDir() deprecation
  1986.                 b->new_path = PHYSFSX_isNewPath(b->view_path.data());
  1987.                 if (!PHYSFS_mount(b->view_path.data(), NULL, 0)) // Pierre-Marie Baty -- work around PHYSFS_addToSearchPath() deprecation
  1988.                 {
  1989.                         return 0;
  1990.                 }
  1991.         }
  1992.  
  1993.         if (!list_directory(b.get()))
  1994.         {
  1995.                 return 0;
  1996.         }
  1997.  
  1998.         auto pb = b.get();
  1999.         return newmenu_listbox1(title, pb->list.pointer().size(), &pb->list.pointer().front(), 1, 0, select_file_handler, std::move(b)) != NULL;
  2000. }
  2001.  
  2002. #define DXX_MENU_ITEM_BROWSE(VERB, TXT, OPT)    \
  2003.         DXX_MENUITEM(VERB, MENU, TXT " (browse...)", OPT)
  2004. #else
  2005.  
  2006. int select_file_recursive2(const char *title, const char *orig_path, const partial_range_t<const file_extension_t *> &ext_range, int select_dir, int (*when_selected)(void *userdata, const char *filename), void *userdata)
  2007. {
  2008.         return 0;
  2009. }
  2010.  
  2011.         /* Include blank string to force a compile error if TXT cannot be
  2012.          * string-pasted
  2013.          */
  2014. #define DXX_MENU_ITEM_BROWSE(VERB, TXT, OPT)    \
  2015.         DXX_MENUITEM(VERB, TEXT, TXT "", OPT)
  2016. #endif
  2017.  
  2018. #if DXX_USE_SDLMIXER
  2019. static window_event_result get_absolute_path(char *full_path, const char *rel_path)
  2020. {
  2021.         PHYSFSX_getRealPath(rel_path, full_path, PATH_MAX);
  2022.         return window_event_result::close;
  2023. }
  2024.  
  2025. #define SELECT_SONG(t, s)       select_file_recursive(t, CGameCfg.CMMiscMusic[s], jukebox_exts, 0, get_absolute_path, CGameCfg.CMMiscMusic[s].data())
  2026. #endif
  2027.  
  2028. namespace {
  2029.  
  2030. #if defined(DXX_BUILD_DESCENT_I)
  2031. #define REDBOOK_PLAYORDER_TEXT  "force mac cd track order"
  2032. #elif defined(DXX_BUILD_DESCENT_II)
  2033. #define REDBOOK_PLAYORDER_TEXT  "force descent ][ cd track order"
  2034. #endif
  2035.  
  2036. #if DXX_USE_SDLMIXER || defined(_WIN32)
  2037. #define DXX_SOUND_ADDON_MUSIC_MENU_ITEM(VERB)   \
  2038.         DXX_MENUITEM(VERB, RADIO, "Built-in/Addon music", opt_sm_mtype1, GameCfg.MusicType == MUSIC_TYPE_BUILTIN, optgrp_music_type)    \
  2039.  
  2040. #else
  2041. #define DXX_SOUND_ADDON_MUSIC_MENU_ITEM(VERB)
  2042. #endif
  2043.  
  2044. #if DXX_USE_SDL_REDBOOK_AUDIO
  2045. #define DXX_SOUND_CD_MUSIC_MENU_ITEM(VERB)      \
  2046.         DXX_MENUITEM(VERB, RADIO, "CD music", opt_sm_mtype2, GameCfg.MusicType == MUSIC_TYPE_REDBOOK, optgrp_music_type)        \
  2047.  
  2048. #define DXX_MUSIC_OPTIONS_CD_LABEL "CD music"
  2049. #else
  2050. #define DXX_SOUND_CD_MUSIC_MENU_ITEM(VERB)
  2051. #define DXX_MUSIC_OPTIONS_CD_LABEL ""
  2052. #endif
  2053.  
  2054. #if DXX_USE_SDLMIXER
  2055. #define DXX_SOUND_JUKEBOX_MENU_ITEM(VERB)       \
  2056.         DXX_MENUITEM(VERB, RADIO, "Jukebox", opt_sm_mtype3, GameCfg.MusicType == MUSIC_TYPE_CUSTOM, optgrp_music_type)  \
  2057.  
  2058. #define DXX_MUSIC_OPTIONS_JUKEBOX_LABEL "Jukebox"
  2059. #define DXX_SOUND_SDLMIXER_MENU_ITEMS(VERB)     \
  2060.         DXX_MENUITEM(VERB, TEXT, "", opt_label_blank2)  \
  2061.         DXX_MENUITEM(VERB, TEXT, "Jukebox options:", opt_label_jukebox_options) \
  2062.         DXX_MENU_ITEM_BROWSE(VERB, "Path for level music", opt_sm_mtype3_lmpath)        \
  2063.         DXX_MENUITEM(VERB, INPUT, CGameCfg.CMLevelMusicPath, opt_sm_mtype3_lmpath_input)        \
  2064.         DXX_MENUITEM(VERB, TEXT, "", opt_label_blank3)  \
  2065.         DXX_MENUITEM(VERB, TEXT, "Level music play order:", opt_label_lm_order) \
  2066.         DXX_MENUITEM(VERB, RADIO, "continuous", opt_sm_mtype3_lmplayorder1, CGameCfg.CMLevelMusicPlayOrder == LevelMusicPlayOrder::Continuous, optgrp_music_order)      \
  2067.         DXX_MENUITEM(VERB, RADIO, "one track per level", opt_sm_mtype3_lmplayorder2, CGameCfg.CMLevelMusicPlayOrder == LevelMusicPlayOrder::Level, optgrp_music_order)  \
  2068.         DXX_MENUITEM(VERB, RADIO, "random", opt_sm_mtype3_lmplayorder3, CGameCfg.CMLevelMusicPlayOrder == LevelMusicPlayOrder::Random, optgrp_music_order)      \
  2069.         DXX_MENUITEM(VERB, TEXT, "", opt_label_blank4)  \
  2070.         DXX_MENUITEM(VERB, TEXT, "Non-level music:", opt_label_nonlevel_music)  \
  2071.         DXX_MENU_ITEM_BROWSE(VERB, "Main menu", opt_sm_cm_mtype3_file1_b)       \
  2072.         DXX_MENUITEM(VERB, INPUT, CGameCfg.CMMiscMusic[SONG_TITLE], opt_sm_cm_mtype3_file1)     \
  2073.         DXX_MENU_ITEM_BROWSE(VERB, "Briefing", opt_sm_cm_mtype3_file2_b)        \
  2074.         DXX_MENUITEM(VERB, INPUT, CGameCfg.CMMiscMusic[SONG_BRIEFING], opt_sm_cm_mtype3_file2)  \
  2075.         DXX_MENU_ITEM_BROWSE(VERB, "Credits", opt_sm_cm_mtype3_file3_b) \
  2076.         DXX_MENUITEM(VERB, INPUT, CGameCfg.CMMiscMusic[SONG_CREDITS], opt_sm_cm_mtype3_file3)   \
  2077.         DXX_MENU_ITEM_BROWSE(VERB, "Escape sequence", opt_sm_cm_mtype3_file4_b) \
  2078.         DXX_MENUITEM(VERB, INPUT, CGameCfg.CMMiscMusic[SONG_ENDLEVEL], opt_sm_cm_mtype3_file4)  \
  2079.         DXX_MENU_ITEM_BROWSE(VERB, "Game ending", opt_sm_cm_mtype3_file5_b)     \
  2080.         DXX_MENUITEM(VERB, INPUT, CGameCfg.CMMiscMusic[SONG_ENDGAME], opt_sm_cm_mtype3_file5)   \
  2081.  
  2082. #else
  2083. #define DXX_SOUND_JUKEBOX_MENU_ITEM(VERB)
  2084. #define DXX_MUSIC_OPTIONS_JUKEBOX_LABEL ""
  2085. #define DXX_SOUND_SDLMIXER_MENU_ITEMS(VERB)
  2086. #endif
  2087.  
  2088. #if SDL_MAJOR_VERSION == 1 && DXX_USE_SDLMIXER
  2089. #define DXX_MUSIC_OPTIONS_SEPARATOR_TEXT " / "
  2090. #else
  2091. #define DXX_MUSIC_OPTIONS_SEPARATOR_TEXT ""
  2092. #endif
  2093.  
  2094. #define DXX_SOUND_MENU(VERB)    \
  2095.         DXX_MENUITEM(VERB, SLIDER, TXT_FX_VOLUME, opt_sm_digivol, GameCfg.DigiVolume, 0, 8)     \
  2096.         DXX_MENUITEM(VERB, SLIDER, "Music volume", opt_sm_musicvol, GameCfg.MusicVolume, 0, 8)  \
  2097.         DXX_MENUITEM(VERB, CHECK, TXT_REVERSE_STEREO, opt_sm_revstereo, GameCfg.ReverseStereo)  \
  2098.         DXX_MENUITEM(VERB, TEXT, "", opt_label_blank0)  \
  2099.         DXX_MENUITEM(VERB, TEXT, "Music type:", opt_label_music_type)   \
  2100.         DXX_MENUITEM(VERB, RADIO, "No music", opt_sm_mtype0, GameCfg.MusicType == MUSIC_TYPE_NONE, optgrp_music_type)   \
  2101.         DXX_SOUND_ADDON_MUSIC_MENU_ITEM(VERB)   \
  2102.         DXX_SOUND_CD_MUSIC_MENU_ITEM(VERB)      \
  2103.         DXX_SOUND_JUKEBOX_MENU_ITEM(VERB)       \
  2104.         DXX_MENUITEM(VERB, TEXT, "", opt_label_blank1)  \
  2105.         DXX_MENUITEM(VERB, TEXT, DXX_MUSIC_OPTIONS_CD_LABEL DXX_MUSIC_OPTIONS_SEPARATOR_TEXT DXX_MUSIC_OPTIONS_JUKEBOX_LABEL " options:", opt_label_music_options)      \
  2106.         DXX_MENUITEM(VERB, CHECK, REDBOOK_PLAYORDER_TEXT, opt_sm_redbook_playorder, GameCfg.OrigTrackOrder)     \
  2107.         DXX_SOUND_SDLMIXER_MENU_ITEMS(VERB)     \
  2108.  
  2109. class sound_menu_items
  2110. {
  2111. public:
  2112.         enum
  2113.         {
  2114.                 optgrp_music_type,
  2115. #if DXX_USE_SDLMIXER
  2116.                 optgrp_music_order,
  2117. #endif
  2118.         };
  2119.         enum
  2120.         {
  2121.                 DXX_SOUND_MENU(ENUM)
  2122.         };
  2123.         std::array<newmenu_item, DXX_SOUND_MENU(COUNT)> m;
  2124.         sound_menu_items()
  2125.         {
  2126.                 DXX_SOUND_MENU(ADD);
  2127.         }
  2128.         void read()
  2129.         {
  2130.                 DXX_SOUND_MENU(READ);
  2131.         }
  2132.         static int menuset(newmenu *, const d_event &event, sound_menu_items *pitems);
  2133. };
  2134.  
  2135. #undef DXX_SOUND_MENU
  2136.  
  2137. }
  2138.  
  2139. int sound_menu_items::menuset(newmenu *, const d_event &event, sound_menu_items *pitems)
  2140. {
  2141.         const auto &items = pitems->m;
  2142.         int replay = 0;
  2143.         int rval = 0;
  2144.         switch (event.type)
  2145.         {
  2146.                 case EVENT_NEWMENU_CHANGED:
  2147.                 {
  2148.                         auto &citem = static_cast<const d_change_event &>(event).citem;
  2149.                         if (citem == opt_sm_digivol)
  2150.                         {
  2151.                                 GameCfg.DigiVolume = items[citem].value;
  2152.                                 digi_set_digi_volume( (GameCfg.DigiVolume*32768)/8 );
  2153.                                 digi_play_sample_once( SOUND_DROP_BOMB, F1_0 );
  2154.                         }
  2155.                         else if (citem == opt_sm_musicvol)
  2156.                         {
  2157.                                 GameCfg.MusicVolume = items[citem].value;
  2158.                                 songs_set_volume(GameCfg.MusicVolume);
  2159.                         }
  2160.                         else if (citem == opt_sm_revstereo)
  2161.                         {
  2162.                                 GameCfg.ReverseStereo = items[citem].value;
  2163.                         }
  2164.                         else if (citem == opt_sm_mtype0)
  2165.                         {
  2166.                                 GameCfg.MusicType = MUSIC_TYPE_NONE;
  2167.                                 replay = 1;
  2168.                         }
  2169.                         /*
  2170.                          * When builtin music is enabled, the next line expands to
  2171.                          * `#if +1 + 0`; when it is disabled, the line expands to
  2172.                          * `#if + 0`.
  2173.                          */
  2174. #if DXX_SOUND_ADDON_MUSIC_MENU_ITEM(COUNT) + 0
  2175.                         else if (citem == opt_sm_mtype1)
  2176.                         {
  2177.                                 GameCfg.MusicType = MUSIC_TYPE_BUILTIN;
  2178.                                 replay = 1;
  2179.                         }
  2180. #endif
  2181. #if DXX_USE_SDL_REDBOOK_AUDIO
  2182.                         else if (citem == opt_sm_mtype2)
  2183.                         {
  2184.                                 GameCfg.MusicType = MUSIC_TYPE_REDBOOK;
  2185.                                 replay = 1;
  2186.                         }
  2187. #endif
  2188. #if DXX_USE_SDLMIXER
  2189.                         else if (citem == opt_sm_mtype3)
  2190.                         {
  2191.                                 GameCfg.MusicType = MUSIC_TYPE_CUSTOM;
  2192.                                 replay = 1;
  2193.                         }
  2194. #endif
  2195.                         else if (citem == opt_sm_redbook_playorder)
  2196.                         {
  2197.                                 GameCfg.OrigTrackOrder = items[citem].value;
  2198.                                 replay = (Game_wind != NULL);
  2199.                         }
  2200. #if DXX_USE_SDLMIXER
  2201.                         else if (citem == opt_sm_mtype3_lmplayorder1)
  2202.                         {
  2203.                                 CGameCfg.CMLevelMusicPlayOrder = LevelMusicPlayOrder::Continuous;
  2204.                                 replay = (Game_wind != NULL);
  2205.                         }
  2206.                         else if (citem == opt_sm_mtype3_lmplayorder2)
  2207.                         {
  2208.                                 CGameCfg.CMLevelMusicPlayOrder = LevelMusicPlayOrder::Level;
  2209.                                 replay = (Game_wind != NULL);
  2210.                         }
  2211.                         else if (citem == opt_sm_mtype3_lmplayorder3)
  2212.                         {
  2213.                                 CGameCfg.CMLevelMusicPlayOrder = LevelMusicPlayOrder::Random;
  2214.                                 replay = (Game_wind != NULL);
  2215.                         }
  2216. #endif
  2217.                         break;
  2218.                 }
  2219.                 case EVENT_NEWMENU_SELECTED:
  2220.                 {
  2221. #if DXX_USE_SDLMIXER
  2222.                         auto &citem = static_cast<const d_select_event &>(event).citem;
  2223. #ifdef _WIN32
  2224. #define WINDOWS_DRIVE_CHANGE_TEXT       ".\nCTRL-D to change drive"
  2225. #else
  2226. #define WINDOWS_DRIVE_CHANGE_TEXT
  2227. #endif
  2228.                         if (citem == opt_sm_mtype3_lmpath)
  2229.                         {
  2230.                                 static const std::array<file_extension_t, 1> ext_list{{"m3u"}};         // select a directory or M3U playlist
  2231.                                 const auto cfgpath = CGameCfg.CMLevelMusicPath.data();
  2232.                                 select_file_recursive(
  2233.                                         "Select directory or\nM3U playlist to\n play level music from" WINDOWS_DRIVE_CHANGE_TEXT,
  2234.                                                                           CGameCfg.CMLevelMusicPath, ext_list, 1,       // look in current music path for ext_list files and allow directory selection
  2235.                                                                           get_absolute_path, cfgpath);  // just copy the absolute path
  2236.                         }
  2237.                         else if (citem == opt_sm_cm_mtype3_file1_b)
  2238.                                 SELECT_SONG("Select main menu music" WINDOWS_DRIVE_CHANGE_TEXT, SONG_TITLE);
  2239.                         else if (citem == opt_sm_cm_mtype3_file2_b)
  2240.                                 SELECT_SONG("Select briefing music" WINDOWS_DRIVE_CHANGE_TEXT, SONG_BRIEFING);
  2241.                         else if (citem == opt_sm_cm_mtype3_file3_b)
  2242.                                 SELECT_SONG("Select credits music" WINDOWS_DRIVE_CHANGE_TEXT, SONG_CREDITS);
  2243.                         else if (citem == opt_sm_cm_mtype3_file4_b)
  2244.                                 SELECT_SONG("Select escape sequence music" WINDOWS_DRIVE_CHANGE_TEXT, SONG_ENDLEVEL);
  2245.                         else if (citem == opt_sm_cm_mtype3_file5_b)
  2246.                                 SELECT_SONG("Select game ending music" WINDOWS_DRIVE_CHANGE_TEXT, SONG_ENDGAME);
  2247. #endif
  2248.                         rval = 1;       // stay in menu
  2249.                         break;
  2250.                 }
  2251.                 case EVENT_WINDOW_CLOSE:
  2252.                         break;
  2253.  
  2254.                 default:
  2255.                         break;
  2256.         }
  2257.  
  2258.         if (replay)
  2259.         {
  2260.                 songs_uninit();
  2261.  
  2262.                 if (Game_wind)
  2263.                         songs_play_level_song( Current_level_num, 0 );
  2264.                 else
  2265.                         songs_play_song(SONG_TITLE, 1);
  2266.         }
  2267.  
  2268.         return rval;
  2269. }
  2270.  
  2271. void do_sound_menu()
  2272. {
  2273.  
  2274. #if DXX_USE_SDLMIXER
  2275.         const auto old_CMLevelMusicPath = CGameCfg.CMLevelMusicPath;
  2276.         const auto old_CMMiscMusic0 = CGameCfg.CMMiscMusic[SONG_TITLE];
  2277. #endif
  2278.  
  2279.         sound_menu_items items;
  2280.         newmenu_do1(nullptr, "Sound Effects & Music", items.m.size(), items.m.data(), &sound_menu_items::menuset, &items, 0);
  2281.  
  2282. #if DXX_USE_SDLMIXER
  2283.         if ((Game_wind != NULL && strcmp(old_CMLevelMusicPath.data(), CGameCfg.CMLevelMusicPath.data())) || (Game_wind == NULL && strcmp(old_CMMiscMusic0.data(), CGameCfg.CMMiscMusic[SONG_TITLE].data())))
  2284.         {
  2285.                 songs_uninit();
  2286.  
  2287.                 if (Game_wind)
  2288.                         songs_play_level_song( Current_level_num, 0 );
  2289.                 else
  2290.                         songs_play_song(SONG_TITLE, 1);
  2291.         }
  2292. #endif
  2293. }
  2294.  
  2295. #if defined(DXX_BUILD_DESCENT_I)
  2296. #define DXX_GAME_SPECIFIC_OPTIONS(VERB) \
  2297.  
  2298. #elif defined(DXX_BUILD_DESCENT_II)
  2299. #define DXX_GAME_SPECIFIC_OPTIONS(VERB) \
  2300.         DXX_MENUITEM(VERB, CHECK, "Headlight on when picked up", opt_headlighton,PlayerCfg.HeadlightActiveDefault )     \
  2301.         DXX_MENUITEM(VERB, CHECK, "Escort robot hot keys",opt_escorthotkey,PlayerCfg.EscortHotKeys)     \
  2302.         DXX_MENUITEM(VERB, CHECK, "Movie Subtitles",opt_moviesubtitle,GameCfg.MovieSubtitles)   \
  2303.         DXX_MENUITEM(VERB, CHECK, "Remove Thief at level start", opt_thief_presence, thief_absent)      \
  2304.         DXX_MENUITEM(VERB, CHECK, "Prevent Thief Stealing Energy Weapons", opt_thief_steal_energy, thief_cannot_steal_energy_weapons)   \
  2305.  
  2306. #endif
  2307.  
  2308. enum {
  2309.         optgrp_autoselect_firing,
  2310. };
  2311.  
  2312. #define DXX_GAMEPLAY_MENU_OPTIONS(VERB) \
  2313.         DXX_MENUITEM(VERB, CHECK, "Ship auto-leveling",opt_autolevel, PlayerCfg.AutoLeveling)   \
  2314.         DXX_MENUITEM(VERB, CHECK, "Persistent Debris",opt_persist_debris,PlayerCfg.PersistentDebris)    \
  2315.         DXX_MENUITEM(VERB, CHECK, "No Rankings (Multi)",opt_noranking,PlayerCfg.NoRankings)     \
  2316.         DXX_MENUITEM(VERB, CHECK, "Free Flight in Automap",opt_freeflight, PlayerCfg.AutomapFreeFlight) \
  2317.         DXX_GAME_SPECIFIC_OPTIONS(VERB) \
  2318.         DXX_MENUITEM(VERB, TEXT, "", opt_label_blank)   \
  2319.         DXX_MENUITEM(VERB, TEXT, "Weapon Autoselect options:", opt_label_autoselect)    \
  2320.         DXX_MENUITEM(VERB, MENU, "Primary ordering...", opt_gameplay_reorderprimary_menu)       \
  2321.         DXX_MENUITEM(VERB, MENU, "Secondary ordering...", opt_gameplay_reordersecondary_menu)   \
  2322.         DXX_MENUITEM(VERB, TEXT, "Autoselect while firing:", opt_autoselect_firing_label)       \
  2323.         DXX_MENUITEM(VERB, RADIO, "Immediately", opt_autoselect_firing_immediate, PlayerCfg.NoFireAutoselect == FiringAutoselectMode::Immediate, optgrp_autoselect_firing)      \
  2324.         DXX_MENUITEM(VERB, RADIO, "Never", opt_autoselect_firing_never, PlayerCfg.NoFireAutoselect == FiringAutoselectMode::Never, optgrp_autoselect_firing)    \
  2325.         DXX_MENUITEM(VERB, RADIO, "When firing stops", opt_autoselect_firing_delayed, PlayerCfg.NoFireAutoselect == FiringAutoselectMode::Delayed, optgrp_autoselect_firing)    \
  2326.         DXX_MENUITEM(VERB, CHECK, "Only Cycle Autoselect Weapons",opt_only_autoselect,PlayerCfg.CycleAutoselectOnly)    \
  2327.         DXX_MENUITEM_AUTOSAVE_LABEL_INPUT(VERB) \
  2328.  
  2329. enum {
  2330.         DXX_GAMEPLAY_MENU_OPTIONS(ENUM)
  2331. };
  2332.  
  2333. static int gameplay_config_menuset(newmenu *, const d_event &event, const unused_newmenu_userdata_t *)
  2334. {
  2335.         switch (event.type)
  2336.         {
  2337.                 case EVENT_NEWMENU_SELECTED:
  2338.                 {
  2339.                         auto &citem = static_cast<const d_select_event &>(event).citem;
  2340.                         if (citem == opt_gameplay_reorderprimary_menu)
  2341.                                 ReorderPrimary();
  2342.                         else if (citem == opt_gameplay_reordersecondary_menu)
  2343.                                 ReorderSecondary();
  2344.                         return 1;               // stay in menu
  2345.                 }
  2346.  
  2347.                 default:
  2348.                         break;
  2349.         }
  2350.  
  2351.         return 0;
  2352. }
  2353.  
  2354. void gameplay_config()
  2355. {
  2356.         for (;;)
  2357.         {
  2358.                 std::array<newmenu_item, DXX_GAMEPLAY_MENU_OPTIONS(COUNT)> m;
  2359. #if defined(DXX_BUILD_DESCENT_II)
  2360.                 auto thief_absent = PlayerCfg.ThiefModifierFlags & ThiefModifier::Absent;
  2361.                 auto thief_cannot_steal_energy_weapons = PlayerCfg.ThiefModifierFlags & ThiefModifier::NoEnergyWeapons;
  2362. #endif
  2363.                 human_readable_mmss_time<decltype(d_gameplay_options::AutosaveInterval)::rep> AutosaveInterval;
  2364.                 format_human_readable_time(AutosaveInterval, PlayerCfg.SPGameplayOptions.AutosaveInterval);
  2365.                 DXX_GAMEPLAY_MENU_OPTIONS(ADD);
  2366.                 const auto i = newmenu_do1( NULL, "Gameplay Options", m.size(), m.data(), gameplay_config_menuset, unused_newmenu_userdata, 0 );
  2367.                 DXX_GAMEPLAY_MENU_OPTIONS(READ);
  2368.                 PlayerCfg.NoFireAutoselect = m[opt_autoselect_firing_delayed].value
  2369.                         ? FiringAutoselectMode::Delayed
  2370.                         : (m[opt_autoselect_firing_immediate].value
  2371.                                 ? FiringAutoselectMode::Immediate
  2372.                                 : FiringAutoselectMode::Never);
  2373. #if defined(DXX_BUILD_DESCENT_II)
  2374.                 PlayerCfg.ThiefModifierFlags =
  2375.                         (thief_absent ? ThiefModifier::Absent : 0) |
  2376.                         (thief_cannot_steal_energy_weapons ? ThiefModifier::NoEnergyWeapons : 0);
  2377. #endif
  2378.                 parse_human_readable_time(PlayerCfg.SPGameplayOptions.AutosaveInterval, AutosaveInterval);
  2379.                 if (i == -1)
  2380.                         break;
  2381.         }
  2382. }
  2383.  
  2384. #if DXX_USE_UDP
  2385. static int multi_player_menu_handler(newmenu *menu,const d_event &event, int *menu_choice)
  2386. {
  2387.         newmenu_item *items = newmenu_get_items(menu);
  2388.  
  2389.         switch (event.type)
  2390.         {
  2391.                 case EVENT_NEWMENU_SELECTED:
  2392.                 {
  2393.                         auto &citem = static_cast<const d_select_event &>(event).citem;
  2394.                         // stay in multiplayer menu, even after having played a game
  2395.                         return do_option(menu_choice[citem]);
  2396.                 }
  2397.  
  2398.                 case EVENT_WINDOW_CLOSE:
  2399.                         d_free(menu_choice);
  2400.                         d_free(items);
  2401.                         break;
  2402.  
  2403.                 default:
  2404.                         break;
  2405.         }
  2406.  
  2407.         return 0;
  2408. }
  2409.  
  2410. void do_multi_player_menu()
  2411. {
  2412.         int *menu_choice;
  2413.         newmenu_item *m;
  2414.         int num_options = 0;
  2415.  
  2416.         MALLOC(menu_choice, int, 3);
  2417.         if (!menu_choice)
  2418.                 return;
  2419.  
  2420.         MALLOC(m, newmenu_item, 3);
  2421.         if (!m)
  2422.         {
  2423.                 d_free(menu_choice);
  2424.                 return;
  2425.         }
  2426.  
  2427. #if DXX_USE_UDP
  2428.         ADD_ITEM("HOST GAME", MENU_START_UDP_NETGAME, -1);
  2429. #if DXX_USE_TRACKER
  2430.         ADD_ITEM("FIND LAN/ONLINE GAMES", MENU_JOIN_LIST_UDP_NETGAME, -1);
  2431. #else
  2432.         ADD_ITEM("FIND LAN GAMES", MENU_JOIN_LIST_UDP_NETGAME, -1);
  2433. #endif
  2434.         ADD_ITEM("JOIN GAME MANUALLY", MENU_JOIN_MANUAL_UDP_NETGAME, -1);
  2435. #endif
  2436.  
  2437.         newmenu_do3( NULL, TXT_MULTIPLAYER, num_options, m, multi_player_menu_handler, menu_choice, 0, NULL );
  2438. }
  2439. #endif
  2440.  
  2441. void do_options_menu()
  2442. {
  2443.         auto items = new options_menu_items;
  2444.         // Fall back to main event loop
  2445.         // Allows clean closing and re-opening when resolution changes
  2446.         newmenu_do3(nullptr, TXT_OPTIONS, items->m.size(), items->m.data(), options_menuset, items, 0, nullptr);
  2447. }
  2448.  
  2449. #ifndef RELEASE
  2450. namespace dsx {
  2451. static window_event_result polygon_models_viewer_handler(window *, const d_event &event, const unused_window_userdata_t *)
  2452. {
  2453.         static unsigned view_idx;
  2454.         int key = 0;
  2455.         static vms_angvec ang;
  2456.  
  2457.         switch (event.type)
  2458.         {
  2459.                 case EVENT_WINDOW_ACTIVATED:
  2460. #if defined(DXX_BUILD_DESCENT_II)
  2461.                         gr_use_palette_table("groupa.256");
  2462. #endif
  2463.                         key_toggle_repeat(1);
  2464.                         view_idx = 0;
  2465.                         ang.p = ang.b = 0;
  2466.                         ang.h = F0_5-1;
  2467.                         break;
  2468.                 case EVENT_KEY_COMMAND:
  2469.                         key = event_key_get(event);
  2470.                         switch (key)
  2471.                         {
  2472.                                 case KEY_ESC:
  2473.                                         return window_event_result::close;
  2474.                                 case KEY_SPACEBAR:
  2475.                                         view_idx ++;
  2476.                                         if (view_idx >= N_polygon_models) view_idx = 0;
  2477.                                         break;
  2478.                                 case KEY_BACKSP:
  2479.                                         if (!view_idx)
  2480.                                                 view_idx = N_polygon_models - 1;
  2481.                                         else
  2482.                                                 view_idx --;
  2483.                                         break;
  2484.                                 case KEY_A:
  2485.                                         ang.h -= 100;
  2486.                                         break;
  2487.                                 case KEY_D:
  2488.                                         ang.h += 100;
  2489.                                         break;
  2490.                                 case KEY_W:
  2491.                                         ang.p -= 100;
  2492.                                         break;
  2493.                                 case KEY_S:
  2494.                                         ang.p += 100;
  2495.                                         break;
  2496.                                 case KEY_Q:
  2497.                                         ang.b -= 100;
  2498.                                         break;
  2499.                                 case KEY_E:
  2500.                                         ang.b += 100;
  2501.                                         break;
  2502.                                 case KEY_R:
  2503.                                         ang.p = ang.b = 0;
  2504.                                         ang.h = F0_5-1;
  2505.                                         break;
  2506.                                 default:
  2507.                                         break;
  2508.                         }
  2509.                         return window_event_result::handled;
  2510.                 case EVENT_WINDOW_DRAW:
  2511.                         timer_delay(F1_0/60);
  2512.                         {
  2513.                                 auto &canvas = *grd_curcanv;
  2514.                                 draw_model_picture(canvas, view_idx, ang);
  2515.                                 gr_set_fontcolor(canvas, BM_XRGB(255, 255, 255), -1);
  2516.                                 auto &game_font = *GAME_FONT;
  2517.                                 gr_printf(canvas, game_font, FSPACX(1), FSPACY(1), "ESC: leave\nSPACE/BACKSP: next/prev model (%i/%i)\nA/D: rotate y\nW/S: rotate x\nQ/E: rotate z\nR: reset orientation", view_idx, N_polygon_models - 1);
  2518.                         }
  2519.                         break;
  2520.                 case EVENT_WINDOW_CLOSE:
  2521.                         load_palette(MENU_PALETTE,0,1);
  2522.                         key_toggle_repeat(0);
  2523.                         break;
  2524.                 default:
  2525.                         break;
  2526.         }
  2527.         return window_event_result::ignored;
  2528. }
  2529. }
  2530.  
  2531. static void polygon_models_viewer()
  2532. {
  2533.         const auto wind = window_create(grd_curscreen->sc_canvas, 0, 0, SWIDTH, SHEIGHT, polygon_models_viewer_handler, unused_window_userdata);
  2534.         if (!wind)
  2535.         {
  2536.                 d_event event = { EVENT_WINDOW_CLOSE };
  2537.                 polygon_models_viewer_handler(NULL, event, NULL);
  2538.                 return;
  2539.         }
  2540.  
  2541.         event_process_all();
  2542. }
  2543.  
  2544. namespace dsx {
  2545. static window_event_result gamebitmaps_viewer_handler(window *, const d_event &event, const unused_window_userdata_t *)
  2546. {
  2547.         static int view_idx = 0;
  2548.         int key = 0;
  2549. #if DXX_USE_OGL
  2550.         float scale = 1.0;
  2551. #endif
  2552.         bitmap_index bi;
  2553.         grs_bitmap *bm;
  2554.  
  2555.         switch (event.type)
  2556.         {
  2557.                 case EVENT_WINDOW_ACTIVATED:
  2558. #if defined(DXX_BUILD_DESCENT_II)
  2559.                         gr_use_palette_table("groupa.256");
  2560. #endif
  2561.                         key_toggle_repeat(1);
  2562.                         view_idx = 0;
  2563.                         break;
  2564.                 case EVENT_KEY_COMMAND:
  2565.                         key = event_key_get(event);
  2566.                         switch (key)
  2567.                         {
  2568.                                 case KEY_ESC:
  2569.                                         return window_event_result::close;
  2570.                                 case KEY_SPACEBAR:
  2571.                                         view_idx ++;
  2572.                                         if (view_idx >= Num_bitmap_files) view_idx = 0;
  2573.                                         break;
  2574.                                 case KEY_BACKSP:
  2575.                                         view_idx --;
  2576.                                         if (view_idx < 0 ) view_idx = Num_bitmap_files - 1;
  2577.                                         break;
  2578.                                 default:
  2579.                                         break;
  2580.                         }
  2581.                         return window_event_result::handled;
  2582.                 case EVENT_WINDOW_DRAW:
  2583.                         bi.index = view_idx;
  2584.                         bm = &GameBitmaps[view_idx];
  2585.                         timer_delay(F1_0/60);
  2586.                         PIGGY_PAGE_IN(bi);
  2587.                         {
  2588.                                 auto &canvas = *grd_curcanv;
  2589.                                 gr_clear_canvas(canvas, BM_XRGB(0,0,0));
  2590. #if DXX_USE_OGL
  2591.                                 scale = (bm->bm_w > bm->bm_h)?(SHEIGHT/bm->bm_w)*0.8:(SHEIGHT/bm->bm_h)*0.8;
  2592.                                 ogl_ubitmapm_cs(canvas, (SWIDTH / 2) - (bm->bm_w * scale / 2), (SHEIGHT / 2) - (bm->bm_h * scale / 2), bm->bm_w * scale, bm->bm_h * scale, *bm, ogl_colors::white, F1_0);
  2593. #else
  2594.                                 gr_bitmap(canvas, (SWIDTH / 2) - (bm->bm_w / 2), (SHEIGHT / 2) - (bm->bm_h / 2), *bm);
  2595. #endif
  2596.                                 gr_set_fontcolor(canvas, BM_XRGB(255, 255, 255), -1);
  2597.                                 auto &game_font = *GAME_FONT;
  2598.                                 gr_printf(canvas, game_font, FSPACX(1), FSPACY(1), "ESC: leave\nSPACE/BACKSP: next/prev bitmap (%i/%i)", view_idx, Num_bitmap_files-1);
  2599.                         }
  2600.                         break;
  2601.                 case EVENT_WINDOW_CLOSE:
  2602.                         load_palette(MENU_PALETTE,0,1);
  2603.                         key_toggle_repeat(0);
  2604.                         break;
  2605.                 default:
  2606.                         break;
  2607.         }
  2608.         return window_event_result::ignored;
  2609. }
  2610. }
  2611.  
  2612. static void gamebitmaps_viewer()
  2613. {
  2614.         const auto wind = window_create(grd_curscreen->sc_canvas, 0, 0, SWIDTH, SHEIGHT, gamebitmaps_viewer_handler, unused_window_userdata);
  2615.         if (!wind)
  2616.         {
  2617.                 d_event event = { EVENT_WINDOW_CLOSE };
  2618.                 gamebitmaps_viewer_handler(NULL, event, NULL);
  2619.                 return;
  2620.         }
  2621.  
  2622.         event_process_all();
  2623. }
  2624.  
  2625. #define DXX_SANDBOX_MENU(VERB)  \
  2626.         DXX_MENUITEM(VERB, MENU, "Polygon_models viewer", polygon_models)       \
  2627.         DXX_MENUITEM(VERB, MENU, "GameBitmaps viewer", bitmaps) \
  2628.  
  2629. namespace {
  2630.  
  2631. class sandbox_menu_items
  2632. {
  2633. public:
  2634.         enum
  2635.         {
  2636.                 DXX_SANDBOX_MENU(ENUM)
  2637.         };
  2638.         std::array<newmenu_item, DXX_SANDBOX_MENU(COUNT)> m;
  2639.         sandbox_menu_items()
  2640.         {
  2641.                 DXX_SANDBOX_MENU(ADD);
  2642.         }
  2643. };
  2644.  
  2645. }
  2646.  
  2647. static int sandbox_menuset(newmenu *, const d_event &event, sandbox_menu_items *items)
  2648. {
  2649.         switch (event.type)
  2650.         {
  2651.                 case EVENT_NEWMENU_CHANGED:
  2652.                         break;
  2653.  
  2654.                 case EVENT_NEWMENU_SELECTED:
  2655.                 {
  2656.                         auto &citem = static_cast<const d_select_event &>(event).citem;
  2657.                         switch (citem)
  2658.                         {
  2659.                                 case sandbox_menu_items::polygon_models:
  2660.                                         polygon_models_viewer();
  2661.                                         break;
  2662.                                 case sandbox_menu_items::bitmaps:
  2663.                                         gamebitmaps_viewer();
  2664.                                         break;
  2665.                         }
  2666.                         return 1; // stay in menu until escape
  2667.                 }
  2668.  
  2669.                 case EVENT_WINDOW_CLOSE:
  2670.                 {
  2671.                         std::default_delete<sandbox_menu_items>()(items);
  2672.                         break;
  2673.                 }
  2674.  
  2675.                 default:
  2676.                         break;
  2677.         }
  2678.         return 0;
  2679. }
  2680.  
  2681. void do_sandbox_menu()
  2682. {
  2683.         auto items = new sandbox_menu_items;
  2684.         newmenu_do3(nullptr, "Coder's sandbox", items->m.size(), items->m.data(), sandbox_menuset, items, 0, nullptr);
  2685. }
  2686. #endif
  2687.