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.  * Stuff for rendering the HUD
  23.  *
  24.  */
  25.  
  26. #include <stdio.h>
  27. #include <string.h>
  28. #include <stdlib.h>
  29. #include "timer.h"
  30. #include "pstypes.h"
  31. #include "console.h"
  32. #include "inferno.h"
  33. #include "dxxerror.h"
  34. #include "gr.h"
  35. #include "palette.h"
  36. #include "bm.h"
  37. #include "player.h"
  38. #include "render.h"
  39. #include "menu.h"
  40. #include "newmenu.h"
  41. #include "screens.h"
  42. #include "config.h"
  43. #include "maths.h"
  44. #include "robot.h"
  45. #include "game.h"
  46. #include "gauges.h"
  47. #include "gamefont.h"
  48. #include "newdemo.h"
  49. #include "text.h"
  50. #include "multi.h"
  51. #include "hudmsg.h"
  52. #include "endlevel.h"
  53. #include "cntrlcen.h"
  54. #include "powerup.h"
  55. #include "laser.h"
  56. #include "playsave.h"
  57. #include "automap.h"
  58. #include "mission.h"
  59. #include "gameseq.h"
  60. #include "args.h"
  61. #include "object.h"
  62.  
  63. #include "compiler-range_for.h"
  64. #include "d_range.h"
  65.  
  66. #if DXX_USE_OGL
  67. #include "ogl_init.h"
  68. #endif
  69.  
  70. namespace dcx {
  71. int netplayerinfo_on;
  72. }
  73.  
  74. namespace dsx {
  75. #if defined(DXX_BUILD_DESCENT_I)
  76. static inline void game_draw_marker_message(grs_canvas &)
  77. {
  78. }
  79. #elif defined(DXX_BUILD_DESCENT_II)
  80. static void game_draw_marker_message(grs_canvas &canvas)
  81. {
  82.         if (MarkerState.DefiningMarkerMessage())
  83.         {
  84.                 gr_set_fontcolor(canvas, BM_XRGB(0, 63, 0),-1);
  85.                 auto &game_font = *GAME_FONT;
  86.                 gr_printf(canvas, game_font, 0x8000, (LINE_SPACING(game_font, game_font) * 5) + FSPACY(1), "Marker: %s%c", &Marker_input[0], Marker_input[Marker_input.size() - 2] ? 0 : '_');
  87.         }
  88. }
  89. #endif
  90. }
  91.  
  92. namespace dcx {
  93.  
  94. static void game_draw_multi_message(grs_canvas &canvas)
  95. {
  96.         if (!(Game_mode&GM_MULTI))
  97.                 return;
  98.         const auto sending = multi_sending_message[Player_num];
  99.         int defining;
  100.         if (!sending && !(defining = multi_defining_message))
  101.                 return;
  102.         gr_set_fontcolor(canvas, BM_XRGB(0, 63, 0),-1);
  103.         auto &game_font = *GAME_FONT;
  104.         const auto &&y = (LINE_SPACING(game_font, game_font) * 5) + FSPACY(1);
  105.         if (sending)
  106.                 gr_printf(canvas, game_font, 0x8000, y, "%s: %s_", TXT_MESSAGE, Network_message.data());
  107.         else
  108.                 gr_printf(canvas, game_font, 0x8000, y, "%s #%d: %s_", TXT_MACRO, defining, Network_message.data());
  109. }
  110.  
  111. static void show_framerate(grs_canvas &canvas)
  112. {
  113.         static int fps_count = 0, fps_rate = 0;
  114.         static fix64 fps_time = 0;
  115.         fps_count++;
  116.         const auto tq = timer_query();
  117.         if (tq >= fps_time + F1_0)
  118.         {
  119.                 fps_rate = fps_count;
  120.                 fps_count = 0;
  121.                 fps_time += F1_0;
  122.         }
  123.         const auto &&line_spacing = LINE_SPACING(*canvas.cv_font, *GAME_FONT);
  124.         unsigned line_displacement;
  125.         switch (PlayerCfg.CockpitMode[1])
  126.         {
  127.                 case CM_FULL_COCKPIT:
  128.                         line_displacement = line_spacing * 2;
  129.                         break;
  130.                 case CM_STATUS_BAR:
  131.                         line_displacement = line_spacing;
  132.                         break;
  133.                 case CM_FULL_SCREEN:
  134.                         switch (PlayerCfg.HudMode)
  135.                         {
  136.                                 case HudType::Standard:
  137.                                         line_displacement = line_spacing * 4;
  138.                                         break;
  139.                                 case HudType::Alternate1:
  140.                                         line_displacement = line_spacing * 6;
  141.                                         if (Game_mode & GM_MULTI)
  142.                                                 line_displacement -= line_spacing * 4;
  143.                                         break;
  144.                                 case HudType::Alternate2:
  145.                                         line_displacement = line_spacing;
  146.                                         break;
  147.                                 case HudType::Hidden:
  148.                                 default:
  149.                                         return;
  150.                         }
  151.                         break;
  152.                 case CM_LETTERBOX:
  153.                 case CM_REAR_VIEW:
  154.                 default:
  155.                         return;
  156.         }
  157.         const auto &game_font = *GAME_FONT;
  158.         gr_set_fontcolor(canvas, BM_XRGB(0, 31, 0),-1);
  159.         char buf[16];
  160.         if (CGameArg.DbgVerbose)
  161.                 snprintf(buf, sizeof(buf), "%iFPS (%.2fms)", fps_rate, (FrameTime * 1000.) / F1_0);
  162.         else
  163.                 snprintf(buf, sizeof(buf), "%iFPS", fps_rate);
  164.         int w, h;
  165.         gr_get_string_size(game_font, buf, &w, &h, nullptr);
  166.         const auto bm_h = canvas.cv_bitmap.bm_h;
  167.         gr_string(canvas, game_font, FSPACX(318) - w, bm_h - line_displacement, buf, w, h);
  168. }
  169.  
  170. }
  171.  
  172. namespace dsx {
  173. static void show_netplayerinfo()
  174. {
  175.         auto &Objects = LevelUniqueObjectState.Objects;
  176.         auto &vcobjptr = Objects.vcptr;
  177.         int x=0, y=0;
  178.         static const char *const eff_strings[]={"trashing","really hurting","seriously affecting","hurting","affecting","tarnishing"};
  179.  
  180.         gr_set_default_canvas();
  181.         auto &canvas = *grd_curcanv;
  182.         gr_set_fontcolor(canvas, 255, -1);
  183.  
  184.         const auto &&fspacx = FSPACX();
  185.         const auto &&fspacx120 = fspacx(120);
  186.         const auto &&fspacy84 = FSPACY(84);
  187.         x = (SWIDTH / 2) - fspacx120;
  188.         y = (SHEIGHT / 2) - fspacy84;
  189.  
  190.         gr_settransblend(canvas, 14, gr_blend::normal);
  191.         const uint8_t color000 = BM_XRGB(0, 0, 0);
  192.         gr_rect(canvas, (SWIDTH / 2) - fspacx120, (SHEIGHT / 2) - fspacy84, (SWIDTH / 2) + fspacx120, (SHEIGHT / 2) + fspacy84, color000);
  193.         gr_settransblend(canvas, GR_FADE_OFF, gr_blend::normal);
  194.  
  195.         // general game information
  196.         const auto &&line_spacing = LINE_SPACING(*canvas.cv_font, *GAME_FONT);
  197.         y += line_spacing;
  198.         auto &game_font = *GAME_FONT;
  199.         gr_string(canvas, game_font, 0x8000, y, Netgame.game_name.data());
  200.         y += line_spacing;
  201.         gr_printf(canvas, game_font, 0x8000, y, "%s - lvl: %i", Netgame.mission_title.data(), Netgame.levelnum);
  202.  
  203.         const auto &&fspacx8 = fspacx(8);
  204.         x += fspacx8;
  205.         y += line_spacing * 2;
  206.         unsigned gamemode = Netgame.gamemode;
  207.         gr_printf(canvas, game_font, x, y, "game mode: %s", gamemode < GMNames.size() ? GMNames[gamemode] : "INVALID");
  208.         y += line_spacing;
  209.         gr_printf(canvas, game_font, x, y,"difficulty: %s", MENU_DIFFICULTY_TEXT(Netgame.difficulty));
  210.         y += line_spacing;
  211.         {
  212.                 auto &plr = get_local_player();
  213.                 gr_printf(canvas, game_font, x, y,"level time: %i:%02i:%02i", plr.hours_level, f2i(plr.time_level) / 60 % 60, f2i(plr.time_level) % 60);
  214.         y += line_spacing;
  215.                 gr_printf(canvas, game_font, x, y,"total time: %i:%02i:%02i", plr.hours_total, f2i(plr.time_total) / 60 % 60, f2i(plr.time_total) % 60);
  216.         }
  217.         y += line_spacing;
  218.         if (Netgame.KillGoal)
  219.                 gr_printf(canvas, game_font, x, y,"Kill goal: %d",Netgame.KillGoal*5);
  220.  
  221.         // player information (name, kills, ping, game efficiency)
  222.         y += line_spacing * 2;
  223.         gr_string(canvas, game_font, x, y, "player");
  224.         gr_string(canvas, game_font, x + fspacx8 * 7, y, ((Game_mode & GM_MULTI_COOP)
  225.                 ? "score"
  226.                 : (gr_string(canvas, game_font, x + fspacx8 * 12, y, "deaths"), "kills")
  227.         ));
  228.         gr_string(canvas, game_font, x + fspacx8 * 18, y, "ping");
  229.         gr_string(canvas, game_font, x + fspacx8 * 23, y, "efficiency");
  230.  
  231.         // process players table
  232.         for (unsigned i = 0; i < MAX_PLAYERS; ++i)
  233.         {
  234.                 auto &plr = *vcplayerptr(i);
  235.                 if (!plr.connected)
  236.                         continue;
  237.  
  238.                 y += line_spacing;
  239.  
  240.                 const auto color = get_player_or_team_color(i);
  241.                 auto &prgb = player_rgb[color];
  242.                 gr_set_fontcolor(canvas, BM_XRGB(prgb.r, prgb.g, prgb.b), -1);
  243.                 gr_string(canvas, game_font, x, y, plr.callsign);
  244.                 {
  245.                         auto &plrobj = *vcobjptr(plr.objnum);
  246.                         auto &player_info = plrobj.ctype.player_info;
  247.                         auto v = ((Game_mode & GM_MULTI_COOP)
  248.                                 ? player_info.mission.score
  249.                                 : (gr_printf(canvas, game_font, x + fspacx8 * 12, y,"%-6d", player_info.net_killed_total), player_info.net_kills_total)
  250.                         );
  251.                         gr_printf(canvas, game_font, x + fspacx8 * 7, y, "%-6d", v);
  252.                 }
  253.  
  254.                 gr_printf(canvas, game_font, x + fspacx8 * 18, y,"%-6d", Netgame.players[i].ping);
  255.                 if (i != Player_num)
  256.                         gr_printf(canvas, game_font, x + fspacx8 * 23, y, "%hu/%hu", kill_matrix[Player_num][i], kill_matrix[i][Player_num]);
  257.         }
  258.  
  259.         y += (line_spacing * 2) + (line_spacing * (MAX_PLAYERS - N_players));
  260.  
  261.         // printf team scores
  262.         if (Game_mode & GM_TEAM)
  263.         {
  264.                 gr_set_fontcolor(canvas, 255, -1);
  265.                 gr_string(canvas, game_font, x, y, "team");
  266.                 gr_string(canvas, game_font, x + fspacx8 * 8, y, "score");
  267.                 y += line_spacing;
  268.                 gr_set_fontcolor(canvas, BM_XRGB(player_rgb[0].r, player_rgb[0].g, player_rgb[0].b),-1);
  269.                 gr_printf(canvas, game_font, x, y, "%s:", static_cast<const char *>(Netgame.team_name[0]));
  270.                 gr_printf(canvas, game_font, x + fspacx8 * 8, y, "%i", team_kills[0]);
  271.                 y += line_spacing;
  272.                 gr_set_fontcolor(canvas, BM_XRGB(player_rgb[1].r, player_rgb[1].g, player_rgb[1].b),-1);
  273.                 gr_printf(canvas, game_font, x, y, "%s:", static_cast<const char *>(Netgame.team_name[1]));
  274.                 gr_printf(canvas, game_font, x + fspacx8 * 8, y, "%i", team_kills[1]);
  275.                 y += line_spacing * 2;
  276.         }
  277.         else
  278.                 y += line_spacing * 4;
  279.  
  280.         gr_set_fontcolor(canvas, 255, -1);
  281.  
  282.         // additional information about game - hoard, ranking
  283.  
  284. #if defined(DXX_BUILD_DESCENT_II)
  285.         if (game_mode_hoard())
  286.         {
  287.                 if (hoard_highest_record_stats.player >= Players.size())
  288.                         gr_string(canvas, game_font, 0x8000, y, "There is no record yet for this level.");
  289.                 else
  290.                         gr_printf(canvas, game_font, 0x8000, y, "%s has the record at %d points.", static_cast<const char *>(vcplayerptr(hoard_highest_record_stats.player)->callsign), hoard_highest_record_stats.points);
  291.         }
  292.         else
  293. #endif
  294.         if (!PlayerCfg.NoRankings)
  295.         {
  296.                 const int ieff = (PlayerCfg.NetlifeKills + PlayerCfg.NetlifeKilled <= 0)
  297.                         ? 0
  298.                         : static_cast<int>(
  299.                                 static_cast<float>(PlayerCfg.NetlifeKills) / (
  300.                                         static_cast<float>(PlayerCfg.NetlifeKilled) + static_cast<float>(PlayerCfg.NetlifeKills)
  301.                                 ) * 100.0
  302.                         );
  303.                 const unsigned eff = ieff < 0 ? 0 : static_cast<unsigned>(ieff);
  304.                 gr_printf(canvas, game_font, 0x8000, y, "Your lifetime efficiency of %d%% (%d/%d)", eff, PlayerCfg.NetlifeKills, PlayerCfg.NetlifeKilled);
  305.                 y += line_spacing;
  306.                 if (eff<60)
  307.                         gr_printf(canvas, game_font, 0x8000, y, "is %s your ranking.", eff_strings[eff / 10]);
  308.                 else
  309.                         gr_string(canvas, game_font, 0x8000, y, "is serving you well.");
  310.                 y += line_spacing;
  311.                 gr_printf(canvas, game_font, 0x8000, y, "your rank is: %s", RankStrings[GetMyNetRanking()]);
  312.         }
  313. }
  314. }
  315.  
  316. #ifndef NDEBUG
  317.  
  318. fix Show_view_text_timer = -1;
  319.  
  320. static void draw_window_label(object_array &Objects, grs_canvas &canvas)
  321. {
  322.         auto &vcobjptridx = Objects.vcptridx;
  323.         if ( Show_view_text_timer > 0 )
  324.         {
  325.                 const char      *viewer_name,*control_name,*viewer_id;
  326.                 Show_view_text_timer -= FrameTime;
  327.  
  328.                 viewer_id = "";
  329.                 switch( Viewer->type )
  330.                 {
  331.                         case OBJ_FIREBALL:      viewer_name = "Fireball"; break;
  332.                         case OBJ_ROBOT:         viewer_name = "Robot";
  333. #if DXX_USE_EDITOR
  334.                                 viewer_id = Robot_names[get_robot_id(Objects.vcptr(Viewer))].data();
  335. #endif
  336.                                 break;
  337.                         case OBJ_HOSTAGE:               viewer_name = "Hostage"; break;
  338.                         case OBJ_PLAYER:                viewer_name = "Player"; break;
  339.                         case OBJ_WEAPON:                viewer_name = "Weapon"; break;
  340.                         case OBJ_CAMERA:                viewer_name = "Camera"; break;
  341.                         case OBJ_POWERUP:               viewer_name = "Powerup";
  342. #if DXX_USE_EDITOR
  343.                                 viewer_id = Powerup_names[get_powerup_id(Objects.vcptr(Viewer))].data();
  344. #endif
  345.                                 break;
  346.                         case OBJ_DEBRIS:                viewer_name = "Debris"; break;
  347.                         case OBJ_CNTRLCEN:      viewer_name = "Reactor"; break;
  348.                         default:                                        viewer_name = "Unknown"; break;
  349.                 }
  350.  
  351.                 switch ( Viewer->control_type) {
  352.                         case CT_NONE:                   control_name = "Stopped"; break;
  353.                         case CT_AI:                             control_name = "AI"; break;
  354.                         case CT_FLYING:         control_name = "Flying"; break;
  355.                         case CT_SLEW:                   control_name = "Slew"; break;
  356.                         case CT_FLYTHROUGH:     control_name = "Flythrough"; break;
  357.                         case CT_MORPH:                  control_name = "Morphing"; break;
  358.                         default:                                        control_name = "Unknown"; break;
  359.                 }
  360.  
  361.                 gr_set_fontcolor(canvas, BM_XRGB(31, 0, 0),-1);
  362.                 auto &game_font = *GAME_FONT;
  363.                 gr_printf(canvas, game_font, 0x8000, (SHEIGHT / 10), "%hu: %s [%s] View - %s", static_cast<objnum_t>(vcobjptridx(Viewer)), viewer_name, viewer_id, control_name);
  364.  
  365.         }
  366. }
  367. #endif
  368.  
  369. namespace dsx {
  370. static void render_countdown_gauge(grs_canvas &canvas)
  371. {
  372.         auto &LevelUniqueControlCenterState = LevelUniqueObjectState.ControlCenterState;
  373.         int Countdown_seconds_left;
  374.         if (!Endlevel_sequence && LevelUniqueControlCenterState.Control_center_destroyed && (Countdown_seconds_left = LevelUniqueControlCenterState.Countdown_seconds_left) > -1)
  375.         { // && (Countdown_seconds_left<127))
  376. #if defined(DXX_BUILD_DESCENT_II)
  377.                 if (!is_D2_OEM && !is_MAC_SHARE && !is_SHAREWARE)    // no countdown on registered only
  378.                 {
  379.                         //      On last level, we don't want a countdown.
  380.                         if (PLAYING_BUILTIN_MISSION && Current_level_num == Last_level)
  381.                         {
  382.                                 if (!(Game_mode & GM_MULTI))
  383.                                         return;
  384.                                 if (Game_mode & GM_MULTI_ROBOTS)
  385.                                         return;
  386.                         }
  387.                 }
  388. #endif
  389.                 gr_set_fontcolor(canvas, BM_XRGB(0, 63, 0),-1);
  390.                 auto &game_font = *GAME_FONT;
  391.                 gr_printf(canvas, game_font, 0x8000, (LINE_SPACING(game_font, game_font) * 6) + FSPACY(1), "T-%d s", Countdown_seconds_left);
  392.         }
  393. }
  394. }
  395.  
  396. static void game_draw_hud_stuff(grs_canvas &canvas)
  397. {
  398.         auto &Objects = LevelUniqueObjectState.Objects;
  399.         auto &vmobjptr = Objects.vmptr;
  400. #ifndef NDEBUG
  401.         draw_window_label(Objects, canvas);
  402. #endif
  403.  
  404.         game_draw_multi_message(canvas);
  405.  
  406.         game_draw_marker_message(canvas);
  407.  
  408.         if (((Newdemo_state == ND_STATE_PLAYBACK) || (Newdemo_state == ND_STATE_RECORDING)) && (PlayerCfg.CockpitMode[1] != CM_REAR_VIEW)) {
  409.                 int y;
  410.  
  411.                 auto &game_font = *GAME_FONT;
  412.                 gr_set_curfont(canvas, GAME_FONT);
  413.                 gr_set_fontcolor(canvas, BM_XRGB(27, 0, 0), -1);
  414.  
  415.                 y = canvas.cv_bitmap.bm_h - (LINE_SPACING(*canvas.cv_font, *GAME_FONT) * 2);
  416.  
  417.                 if (PlayerCfg.CockpitMode[1] == CM_FULL_COCKPIT)
  418.                         y = canvas.cv_bitmap.bm_h / 1.2 ;
  419.                 if (Newdemo_state == ND_STATE_PLAYBACK) {
  420.                         if (Newdemo_show_percentage) {
  421.                                 gr_printf(canvas, game_font, 0x8000, y, "%s (%d%% %s)", TXT_DEMO_PLAYBACK, newdemo_get_percent_done(), TXT_DONE);
  422.                         }
  423.                 } else {
  424.                         gr_printf(canvas, game_font, 0x8000, y, "%s (%dK)", TXT_DEMO_RECORDING, (Newdemo_num_written / 1024));
  425.                 }
  426.         }
  427.  
  428.         render_countdown_gauge(canvas);
  429.  
  430.         if (CGameCfg.FPSIndicator && PlayerCfg.CockpitMode[1] != CM_REAR_VIEW)
  431.                 show_framerate(canvas);
  432.  
  433.         if (Newdemo_state == ND_STATE_PLAYBACK)
  434.                 Game_mode = Newdemo_game_mode;
  435.  
  436.         auto &plrobj = get_local_plrobj();
  437.         draw_hud(canvas, plrobj);
  438.  
  439.         if (Newdemo_state == ND_STATE_PLAYBACK)
  440.                 Game_mode = GM_NORMAL;
  441.  
  442.         if (Player_dead_state != player_dead_state::no)
  443.                 player_dead_message(canvas);
  444. }
  445.  
  446. namespace dsx {
  447.  
  448. #if defined(DXX_BUILD_DESCENT_II)
  449.  
  450. ubyte RenderingType=0;
  451. ubyte DemoDoingRight=0,DemoDoingLeft=0;
  452.  
  453. constexpr char DemoWBUType[]={0,WBU_GUIDED,WBU_MISSILE,WBU_REAR,WBU_ESCORT,WBU_MARKER,0};
  454. constexpr char DemoRearCheck[]={0,0,0,1,0,0,0};
  455. constexpr char DemoExtraMessage[][10] = {
  456.         "PLAYER",
  457.         "GUIDED",
  458.         "MISSILE",
  459.         "REAR",
  460.         "GUIDE-BOT",
  461.         "MARKER",
  462.         "SHIP"
  463. };
  464.  
  465. static const char *get_missile_name(const unsigned laser_type)
  466. {
  467.         switch(laser_type)
  468.         {
  469.                 case weapon_id_type::CONCUSSION_ID:
  470.                         return "CONCUSSION";
  471.                 case weapon_id_type::HOMING_ID:
  472.                         return "HOMING";
  473.                 case weapon_id_type::SMART_ID:
  474.                         return "SMART";
  475.                 case weapon_id_type::MEGA_ID:
  476.                         return "MEGA";
  477.                 case weapon_id_type::FLASH_ID:
  478.                         return "FLASH";
  479.                 case weapon_id_type::MERCURY_ID:
  480.                         return "MERCURY";
  481.                 case weapon_id_type::EARTHSHAKER_ID:
  482.                         return "SHAKER";
  483.                 default:
  484.                         return "MISSILE";       // Bad!
  485.         }
  486. }
  487.  
  488. static void set_missile_viewer(object &o)
  489. {
  490.         Missile_viewer = &o;
  491.         Missile_viewer_sig = o.signature;
  492. }
  493.  
  494. static int clear_missile_viewer()
  495. {
  496.         if (!Missile_viewer)
  497.                 return 0;
  498.         Missile_viewer = nullptr;
  499.         return 1;
  500. }
  501.  
  502. __attribute_warn_unused_result
  503. static bool is_viewable_missile(weapon_id_type laser_type)
  504. {
  505.         return laser_type == weapon_id_type::CONCUSSION_ID ||
  506.                 laser_type == weapon_id_type::HOMING_ID ||
  507.                 laser_type == weapon_id_type::SMART_ID ||
  508.                 laser_type == weapon_id_type::MEGA_ID ||
  509.                 laser_type == weapon_id_type::FLASH_ID ||
  510.                 laser_type == weapon_id_type::GUIDEDMISS_ID ||
  511.                 laser_type == weapon_id_type::MERCURY_ID ||
  512.                 laser_type == weapon_id_type::EARTHSHAKER_ID;
  513. }
  514.  
  515. static bool choose_missile_viewer()
  516. {
  517.         auto &Objects = LevelUniqueObjectState.Objects;
  518.         auto &vcobjptr = Objects.vcptr;
  519.         auto &vmobjptr = Objects.vmptr;
  520.         const auto MissileViewEnabled = PlayerCfg.MissileViewEnabled;
  521.         if (unlikely(MissileViewEnabled == MissileViewMode::None))
  522.                 return false;
  523.         const auto need_new_missile_viewer = []{
  524.                 if (!Missile_viewer)
  525.                         return true;
  526.                 if (Missile_viewer->type != OBJ_WEAPON)
  527.                         return true;
  528.                 if (Missile_viewer->signature != Missile_viewer_sig)
  529.                         return true;
  530.                 /* No check on parent here.  Missile_viewer is cleared if needed
  531.                  * when a missile is fired.
  532.                  */
  533.                 return false;
  534.         };
  535.         if (likely(!need_new_missile_viewer()))
  536.                 /* Valid viewer already set */
  537.                 return true;
  538.         const auto better_match = [](object *const a, object &b) {
  539.                 if (!a)
  540.                         return true;
  541.                 return a->lifeleft < b.lifeleft;
  542.         };
  543.         /* Find new missile */
  544.         object *local_player_missile = nullptr, *other_player_missile = nullptr;
  545.         const auto game_mode = Game_mode;
  546.         const auto local_player_objnum = get_local_player().objnum;
  547.         range_for (object &o, vmobjptr)
  548.         {
  549.                 if (o.type != OBJ_WEAPON)
  550.                         continue;
  551.                 if (o.ctype.laser_info.parent_type != OBJ_PLAYER)
  552.                         continue;
  553.                 const auto laser_type = get_weapon_id(o);
  554.                 if (!is_viewable_missile(laser_type))
  555.                         continue;
  556.                 if (o.ctype.laser_info.parent_num == local_player_objnum)
  557.                 {
  558.                         if (!better_match(local_player_missile, o))
  559.                                 continue;
  560.                         local_player_missile = &o;
  561.                 }
  562.                 else
  563.                 {
  564.                         if (MissileViewEnabled != MissileViewMode::EnabledSelfAndAllies)
  565.                                 continue;
  566.                         if (!better_match(other_player_missile, o))
  567.                                 continue;
  568.                         else if (game_mode & GM_MULTI_COOP)
  569.                         {
  570.                                 /* Always allow missiles of other players */
  571.                         }
  572.                         else if (game_mode & GM_TEAM)
  573.                         {
  574.                                 /* Allow missiles from same team */
  575.                                 if (get_team(Player_num) != get_team(get_player_id(vcobjptr(o.ctype.laser_info.parent_num))))
  576.                                         continue;
  577.                         }
  578.                         else
  579.                                 continue;
  580.                         other_player_missile = &o;
  581.                 }
  582.         }
  583.         object *o;
  584.         if ((o = local_player_missile) != nullptr ||
  585.                 (o = other_player_missile) != nullptr)
  586.                 set_missile_viewer(*o);
  587.         else
  588.                 return false;
  589.         return true;
  590. }
  591.  
  592. static void show_one_extra_view(const int w);
  593. static void show_extra_views()
  594. {
  595.         auto &Objects = LevelUniqueObjectState.Objects;
  596.         auto &vmobjptr = Objects.vmptr;
  597.         int did_missile_view=0;
  598.         int save_newdemo_state = Newdemo_state;
  599.         if (Newdemo_state==ND_STATE_PLAYBACK)
  600.         {
  601.                 if (DemoDoLeft)
  602.                 {
  603.                         DemoDoingLeft=DemoDoLeft;
  604.  
  605.                         if (DemoDoLeft==3)
  606.                                 do_cockpit_window_view(0, *ConsoleObject, 1, WBU_REAR, "REAR");
  607.                         else
  608.                                 do_cockpit_window_view(0, DemoLeftExtra, DemoRearCheck[DemoDoLeft], DemoWBUType[DemoDoLeft], DemoExtraMessage[DemoDoLeft], DemoDoLeft == 1 ? &get_local_plrobj().ctype.player_info : nullptr);
  609.                 }
  610.                 else
  611.                         do_cockpit_window_view(0,WBU_WEAPON);
  612.        
  613.                 if (DemoDoRight)
  614.                 {
  615.                         DemoDoingRight=DemoDoRight;
  616.                        
  617.                         if (DemoDoRight==3)
  618.                                 do_cockpit_window_view(1, *ConsoleObject, 1, WBU_REAR, "REAR");
  619.                         else
  620.                         {
  621.                                 do_cockpit_window_view(1, DemoRightExtra, DemoRearCheck[DemoDoRight], DemoWBUType[DemoDoRight], DemoExtraMessage[DemoDoRight], DemoDoLeft == 1 ? &get_local_plrobj().ctype.player_info : nullptr);
  622.                         }
  623.                 }
  624.                 else
  625.                         do_cockpit_window_view(1,WBU_WEAPON);
  626.                
  627.                 DemoDoLeft=DemoDoRight=0;
  628.                 DemoDoingLeft=DemoDoingRight=0;
  629.                 return;
  630.         }
  631.  
  632.         const auto &&gimobj = LevelUniqueObjectState.Guided_missile.get_player_active_guided_missile(vmobjptr, Player_num);
  633.         if (gimobj != nullptr)
  634.         {
  635.                 if (PlayerCfg.GuidedInBigWindow)
  636.                 {
  637.                         RenderingType=6+(1<<4);
  638.                         do_cockpit_window_view(1, *Viewer, 0, WBU_MISSILE, "SHIP");
  639.                 }
  640.                 else
  641.                 {
  642.                         RenderingType=1+(1<<4);
  643.                         auto &player_info = get_local_plrobj().ctype.player_info;
  644.                         do_cockpit_window_view(1, *gimobj, 0, WBU_GUIDED, "GUIDED", &player_info);
  645.                 }
  646.                        
  647.                 did_missile_view=1;
  648.         }
  649.         else {
  650.                 if (choose_missile_viewer())
  651.                 //do missile view
  652.                         {
  653.                                 RenderingType=2+(1<<4);
  654.                                 do_cockpit_window_view(1, *Missile_viewer, 0, WBU_MISSILE, get_missile_name(get_weapon_id(*Missile_viewer)));
  655.                                 did_missile_view=1;
  656.                         }
  657.                         else {
  658.                                 if (clear_missile_viewer())
  659.                                         do_cockpit_window_view(1,WBU_STATIC);
  660.                                 RenderingType=255;
  661.                         }
  662.         }
  663.  
  664.         range_for (const int w, xrange(2u)) {
  665.  
  666.                 if (w==1 && did_missile_view)
  667.                         continue;               //if showing missile view in right window, can't show anything else
  668.  
  669.                 show_one_extra_view(w);
  670.         }
  671.         RenderingType=0;
  672.         Newdemo_state = save_newdemo_state;
  673. }
  674.  
  675. static void show_one_extra_view(const int w)
  676. {
  677.         auto &Objects = LevelUniqueObjectState.Objects;
  678.         auto &vcobjptr = Objects.vcptr;
  679.         auto &vmobjptridx = Objects.vmptridx;
  680.         auto &Robot_info = LevelSharedRobotInfoState.Robot_info;
  681.                 //show special views if selected
  682.                 switch (PlayerCfg.Cockpit3DView[w]) {
  683.                         case CV_NONE:
  684.                                 RenderingType=255;
  685.                                 do_cockpit_window_view(w,WBU_WEAPON);
  686.                                 break;
  687.                         case CV_REAR:
  688.                                 RenderingType=3 + (w << 4);
  689.                                 {
  690.                                         int rear_view_flag;
  691.                                         const char *label;
  692.                                         if (Rear_view)  //if big window is rear view, show front here
  693.                                         {
  694.                                                 rear_view_flag = 0;
  695.                                                 label = "FRONT";
  696.                                         }
  697.                                         else    //show normal rear view
  698.                                         {
  699.                                                 rear_view_flag = 1;
  700.                                                 label = "REAR";
  701.                                         }
  702.                                         do_cockpit_window_view(w, *ConsoleObject, rear_view_flag, WBU_REAR, label);
  703.                                 }
  704.                                 break;
  705.                         case CV_ESCORT: {
  706.                                 const auto &&buddy = find_escort(vmobjptridx, Robot_info);
  707.                                 if (buddy == object_none) {
  708.                                         do_cockpit_window_view(w,WBU_WEAPON);
  709.                                         PlayerCfg.Cockpit3DView[w] = CV_NONE;
  710.                                 }
  711.                                 else {
  712.                                         RenderingType=4+(w<<4);
  713.                                         do_cockpit_window_view(w, *buddy, 0, WBU_ESCORT, PlayerCfg.GuidebotName);
  714.                                 }
  715.                                 break;
  716.                         }
  717.                         case CV_COOP: {
  718.                                 const auto player = Coop_view_player[w];
  719.  
  720.                  RenderingType=255; // don't handle coop stuff                 
  721.                                
  722.                                 if (player < Players.size() && vcplayerptr(player)->connected && ((Game_mode & GM_MULTI_COOP) || ((Game_mode & GM_TEAM) && (get_team(player) == get_team(Player_num)))))
  723.                                 {
  724.                                         auto &p = *vcplayerptr(player);
  725.                                         do_cockpit_window_view(w, *vcobjptr(p.objnum), 0, WBU_COOP, p.callsign);
  726.                                 }
  727.                                 else {
  728.                                         do_cockpit_window_view(w,WBU_WEAPON);
  729.                                         PlayerCfg.Cockpit3DView[w] = CV_NONE;
  730.                                 }
  731.                                 break;
  732.                         }
  733.                         case CV_MARKER: {
  734.                                 char label[10];
  735.                                 RenderingType=5+(w<<4);
  736.                                 const auto mvn = Marker_viewer_num[w];
  737.                                 if (!MarkerState.imobjidx.valid_index(mvn))
  738.                                 {
  739.                                         PlayerCfg.Cockpit3DView[w] = CV_NONE;
  740.                                         break;
  741.                                 }
  742.                                 const auto mo = MarkerState.imobjidx[mvn];
  743.                                 if (mo == object_none)
  744.                                 {
  745.                                         PlayerCfg.Cockpit3DView[w] = CV_NONE;
  746.                                         break;
  747.                                 }
  748.                                 const auto displayed_marker_id = static_cast<unsigned>(mvn) + 1;
  749.                                 snprintf(label, sizeof(label), "Marker %u", displayed_marker_id);
  750.                                 do_cockpit_window_view(w, *vcobjptr(mo), 0, WBU_MARKER, label);
  751.                                 break;
  752.                         }
  753.                         default:
  754.                                 Int3();         //invalid window type
  755.                 }
  756. }
  757.  
  758. int BigWindowSwitch=0;
  759. #endif
  760.  
  761. static void update_cockpits();
  762.  
  763. //render a frame for the game
  764. void game_render_frame_mono()
  765. {
  766.         int no_draw_hud=0;
  767.  
  768.         gr_set_current_canvas(Screen_3d_window);
  769. #if defined(DXX_BUILD_DESCENT_II)
  770.         auto &Objects = LevelUniqueObjectState.Objects;
  771.         auto &vmobjptr = Objects.vmptr;
  772.         if (const auto &&gimobj = (
  773.                         PlayerCfg.GuidedInBigWindow
  774.                         ? LevelUniqueObjectState.Guided_missile.get_player_active_guided_missile(LevelUniqueObjectState.get_objects().vmptr, Player_num)
  775.                         : nullptr))
  776.         {
  777.                 const auto viewer_save = Viewer;
  778.  
  779.                 if (PlayerCfg.CockpitMode[1]==CM_FULL_COCKPIT || PlayerCfg.CockpitMode[1]==CM_REAR_VIEW)
  780.                 {
  781.                          BigWindowSwitch=1;
  782.                          force_cockpit_redraw=1;
  783.                          PlayerCfg.CockpitMode[1]=CM_STATUS_BAR;
  784.                          return;
  785.                 }
  786.  
  787.                 Viewer = gimobj;
  788.  
  789.                 window_rendered_data window;
  790.                 update_rendered_data(window, *Viewer, 0);
  791.                 render_frame(*grd_curcanv, 0, window);
  792.  
  793.                 wake_up_rendered_objects(*Viewer, window);
  794.                 show_HUD_names(*grd_curcanv);
  795.  
  796.                 Viewer = viewer_save;
  797.  
  798.                 auto &game_font = *GAME_FONT;
  799.                 gr_set_fontcolor(*grd_curcanv, BM_XRGB(27, 0, 0), -1);
  800.  
  801.                 gr_string(*grd_curcanv, game_font, 0x8000, FSPACY(1), "Guided Missile View");
  802.  
  803.                 auto &player_info = get_local_plrobj().ctype.player_info;
  804.                 show_reticle(*grd_curcanv, player_info, RET_TYPE_CROSS_V1, 0);
  805.  
  806.                 HUD_render_message_frame(*grd_curcanv);
  807.  
  808.                 no_draw_hud=1;
  809.         }
  810.         else
  811. #endif
  812.         {
  813. #if defined(DXX_BUILD_DESCENT_II)
  814.                 if (BigWindowSwitch)
  815.                 {
  816.                         force_cockpit_redraw=1;
  817.                         PlayerCfg.CockpitMode[1]=(Rear_view?CM_REAR_VIEW:CM_FULL_COCKPIT);
  818.                         BigWindowSwitch=0;
  819.                         return;
  820.                 }
  821. #endif
  822.                 window_rendered_data window;
  823. #if defined(DXX_BUILD_DESCENT_II)
  824.                 update_rendered_data(window, *Viewer, Rear_view);
  825. #endif
  826.                 render_frame(*grd_curcanv, 0, window);
  827.         }
  828.  
  829. #if defined(DXX_BUILD_DESCENT_II)
  830.         gr_set_current_canvas(Screen_3d_window);
  831. #endif
  832.  
  833.         update_cockpits();
  834.  
  835.         if (Newdemo_state == ND_STATE_PLAYBACK)
  836.                 Game_mode = Newdemo_game_mode;
  837.  
  838.         if (PlayerCfg.CockpitMode[1]==CM_FULL_COCKPIT || PlayerCfg.CockpitMode[1]==CM_STATUS_BAR)
  839.                 render_gauges();
  840.  
  841.         if (Newdemo_state == ND_STATE_PLAYBACK)
  842.                 Game_mode = GM_NORMAL;
  843.  
  844.         gr_set_current_canvas(Screen_3d_window);
  845.         if (!no_draw_hud)
  846.                 game_draw_hud_stuff(*grd_curcanv);
  847.  
  848. #if defined(DXX_BUILD_DESCENT_II)
  849.         gr_set_default_canvas();
  850.  
  851.         show_extra_views();             //missile view, buddy bot, etc.
  852. #endif
  853.  
  854.         if (netplayerinfo_on && Game_mode & GM_MULTI)
  855.                 show_netplayerinfo();
  856. }
  857.  
  858. }
  859.  
  860. void toggle_cockpit()
  861. {
  862.         enum cockpit_mode_t new_mode=CM_FULL_SCREEN;
  863.  
  864.         if (Rear_view || Player_dead_state != player_dead_state::no)
  865.                 return;
  866.  
  867.         switch (PlayerCfg.CockpitMode[1])
  868.         {
  869.                 case CM_FULL_COCKPIT:
  870.                         new_mode = CM_STATUS_BAR;
  871.                         break;
  872.                 case CM_STATUS_BAR:
  873.                         new_mode = CM_FULL_SCREEN;
  874.                         break;
  875.                 case CM_FULL_SCREEN:
  876.                         new_mode = CM_FULL_COCKPIT;
  877.                         break;
  878.                 case CM_REAR_VIEW:
  879.                 case CM_LETTERBOX:
  880.                         break;
  881.         }
  882.  
  883.         select_cockpit(new_mode);
  884.         HUD_clear_messages();
  885.         PlayerCfg.CockpitMode[0] = new_mode;
  886.         write_player_file();
  887. }
  888.  
  889. namespace dcx {
  890. int last_drawn_cockpit = -1;
  891. }
  892.  
  893. namespace dsx {
  894.  
  895. // This actually renders the new cockpit onto the screen.
  896. static void update_cockpits()
  897. {
  898.         grs_bitmap *bm;
  899.         int mode = PlayerCfg.CockpitMode[1];
  900. #if defined(DXX_BUILD_DESCENT_II)
  901.         mode += (HIRESMODE?(Num_cockpits/2):0);
  902. #endif
  903.  
  904.         switch( PlayerCfg.CockpitMode[1] )      {
  905.                 case CM_FULL_COCKPIT:
  906.                 case CM_REAR_VIEW:
  907.                         PIGGY_PAGE_IN(cockpit_bitmap[mode]);
  908.                         bm=&GameBitmaps[cockpit_bitmap[mode].index];
  909.                         gr_set_default_canvas();
  910. #if DXX_USE_OGL
  911.                         ogl_ubitmapm_cs(*grd_curcanv, 0, 0, -1, -1, *bm, 255, F1_0);
  912. #else
  913.                         gr_ubitmapm(*grd_curcanv, 0, 0, *bm);
  914. #endif
  915.                         break;
  916.        
  917.                 case CM_FULL_SCREEN:
  918.                         break;
  919.        
  920.                 case CM_STATUS_BAR:
  921.                         PIGGY_PAGE_IN(cockpit_bitmap[mode]);
  922.                         bm=&GameBitmaps[cockpit_bitmap[mode].index];
  923.                         gr_set_default_canvas();
  924. #if DXX_USE_OGL
  925.                         ogl_ubitmapm_cs(*grd_curcanv, 0, (HIRESMODE ? (SHEIGHT * 2) / 2.6 : (SHEIGHT * 2) / 2.72), -1, (static_cast<int>(static_cast<double>(bm->bm_h) * (HIRESMODE ? static_cast<double>(SHEIGHT) / 480 : static_cast<double>(SHEIGHT) / 200) + 0.5)), *bm, 255, F1_0);
  926. #else
  927.                         gr_ubitmapm(*grd_curcanv, 0, SHEIGHT - bm->bm_h, *bm);
  928. #endif
  929.                         break;
  930.        
  931.                 case CM_LETTERBOX:
  932.                         break;
  933.         }
  934.         gr_set_default_canvas();
  935.  
  936.         if (PlayerCfg.CockpitMode[1] != last_drawn_cockpit)
  937.                 last_drawn_cockpit = PlayerCfg.CockpitMode[1];
  938.         else
  939.                 return;
  940.  
  941.         if (PlayerCfg.CockpitMode[1]==CM_FULL_COCKPIT || PlayerCfg.CockpitMode[1]==CM_STATUS_BAR)
  942.                 init_gauges();
  943.  
  944. }
  945.  
  946. }
  947.  
  948. void game_render_frame()
  949. {
  950.         auto &Objects = LevelUniqueObjectState.Objects;
  951.         auto &vmobjptr = Objects.vmptr;
  952.         set_screen_mode( SCREEN_GAME );
  953.         auto &player_info = get_local_plrobj().ctype.player_info;
  954.         play_homing_warning(player_info);
  955.         game_render_frame_mono();
  956. }
  957.  
  958. //show a message in a nice little box
  959. void show_boxed_message(const char *msg, int RenderFlag)
  960. {
  961.         int w,h;
  962.         int x,y;
  963.        
  964.         gr_set_default_canvas();
  965.         auto &canvas = *grd_curcanv;
  966.         gr_set_fontcolor(canvas, BM_XRGB(31, 31, 31), -1);
  967.         auto &medium1_font = *MEDIUM1_FONT;
  968.         gr_get_string_size(medium1_font, msg, &w, &h, nullptr);
  969.        
  970.         x = (SWIDTH-w)/2;
  971.         y = (SHEIGHT-h)/2;
  972.        
  973.         nm_draw_background(canvas, x - BORDERX, y - BORDERY, x + w + BORDERX, y + h + BORDERY);
  974.        
  975.         gr_string(canvas, medium1_font, 0x8000, y, msg, w, h);
  976.        
  977.         // If we haven't drawn behind it, need to flip
  978.         if (!RenderFlag)
  979.                 gr_flip();
  980. }
  981.