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.  * Movie Playing Stuff
  23.  *
  24.  */
  25.  
  26. #include <string.h>
  27. #ifndef macintosh
  28. # include <sys/types.h>
  29. # include <sys/stat.h>
  30. # include <fcntl.h>
  31. # ifndef _MSC_VER
  32. #  include <unistd.h>
  33. # endif
  34. #endif // ! macintosh
  35. #include <ctype.h>
  36.  
  37. #include "movie.h"
  38. #include "window.h"
  39. #include "console.h"
  40. #include "config.h"
  41. #include "physfsx.h"
  42. #include "key.h"
  43. #include "mouse.h"
  44. #include "digi.h"
  45. #include "songs.h"
  46. #include "inferno.h"
  47. #include "palette.h"
  48. #include "strutil.h"
  49. #include "dxxerror.h"
  50. #include "u_mem.h"
  51. #include "gr.h"
  52. #include "gamefont.h"
  53. #include "menu.h"
  54. #include "libmve.h"
  55. #include "text.h"
  56. #include "screens.h"
  57. #include "physfsrwops.h"
  58. #if DXX_USE_OGL
  59. #include "ogl_init.h"
  60. #endif
  61. #include "args.h"
  62.  
  63. #include "compiler-range_for.h"
  64. #include "partial_range.h"
  65.  
  66. namespace {
  67.  
  68. // Subtitle data
  69. struct subtitle {
  70.         typename std::conditional<sizeof(char *) == sizeof(uint32_t), uint16_t, uint32_t>::type first_frame, last_frame;
  71.         const char *msg;
  72. };
  73.  
  74. #define MAX_ACTIVE_SUBTITLES 3
  75.  
  76. struct d_subtitle_state
  77. {
  78.         unsigned Num_subtitles = 0;
  79.         std::unique_ptr<char[]> subtitle_raw_data;
  80.         std::array<subtitle, 500> Subtitles;
  81. };
  82.  
  83. static int init_subtitles(d_subtitle_state &SubtitleState, const char *filename);
  84.  
  85. // Movielib data
  86.  
  87. constexpr std::array<std::array<char, 8>, 3> movielib_files{{
  88.         {"intro"}, {"other"}, {"robots"}
  89. }};
  90.  
  91. struct loaded_movie_t
  92. {
  93.         std::array<char, FILENAME_LEN + 2> filename;
  94. };
  95.  
  96. static loaded_movie_t extra_robot_movie_mission;
  97.  
  98. static RWops_ptr RoboFile;
  99.  
  100. // Function Prototypes
  101. static int RunMovie(const char *filename, const char *subtitles, int highres_flag, int allow_abort,int dx,int dy);
  102.  
  103. static void draw_subtitles(const d_subtitle_state &, int frame_num);
  104.  
  105. // ----------------------------------------------------------------------
  106. static void* MPlayAlloc(size_t size)
  107. {
  108.     return d_malloc(size);
  109. }
  110.  
  111. static void MPlayFree(void *p)
  112. {
  113.     d_free(p);
  114. }
  115.  
  116. //-----------------------------------------------------------------------
  117.  
  118. static unsigned int FileRead(void *handle, void *buf, unsigned int count)
  119. {
  120.     unsigned numread;
  121.     numread = SDL_RWread(reinterpret_cast<SDL_RWops *>(handle), buf, 1, count);
  122.     return (numread == count);
  123. }
  124.  
  125. }
  126.  
  127. //-----------------------------------------------------------------------
  128.  
  129.  
  130. //filename will actually get modified to be either low-res or high-res
  131. //returns status.  see values in movie.h
  132. int PlayMovie(const char *subtitles, const char *filename, int must_have)
  133. {
  134.         char name[FILENAME_LEN],*p;
  135.         int ret;
  136.  
  137.         if (GameArg.SysNoMovies)
  138.                 return MOVIE_NOT_PLAYED;
  139.  
  140.         strcpy(name,filename);
  141.  
  142.         if ((p=strchr(name,'.')) == NULL)               //add extension, if missing
  143.                 strcat(name,".MVE");
  144.  
  145.         // Stop all digital sounds currently playing.
  146.         digi_stop_digi_sounds();
  147.  
  148.         // Stop all songs
  149.         songs_stop_all();
  150.  
  151.         // MD2211: if using SDL_Mixer, we never reinit the sound system
  152.         if (CGameArg.SndDisableSdlMixer)
  153.                 digi_close();
  154.  
  155.         // Start sound
  156.         MVE_sndInit(!CGameArg.SndNoSound ? 1 : -1);
  157.  
  158.         ret = RunMovie(name, subtitles, !GameArg.GfxSkipHiresMovie, must_have, -1, -1);
  159.  
  160.         // MD2211: if using SDL_Mixer, we never reinit the sound system
  161.         if (!CGameArg.SndNoSound
  162.                 && CGameArg.SndDisableSdlMixer
  163.         )
  164.                 digi_init();
  165.  
  166.         Screen_mode = -1;               //force screen reset
  167.  
  168.         return ret;
  169. }
  170.  
  171. namespace {
  172.  
  173. static void MovieShowFrame(ubyte *buf, int dstx, int dsty, int bufw, int bufh, int sw, int sh)
  174. {
  175.         grs_bitmap source_bm;
  176.         static palette_array_t old_pal;
  177.         float scale = 1.0;
  178.  
  179.         if (old_pal != gr_palette)
  180.         {
  181.                 old_pal = gr_palette;
  182.                 return;
  183.         }
  184.         old_pal = gr_palette;
  185.  
  186.         source_bm.bm_x = source_bm.bm_y = 0;
  187.         source_bm.bm_w = source_bm.bm_rowsize = bufw;
  188.         source_bm.bm_h = bufh;
  189.         source_bm.set_type(bm_mode::linear);
  190.         source_bm.clear_flags();
  191.         source_bm.bm_data = buf;
  192.  
  193.         if (dstx == -1 && dsty == -1) // Fullscreen movie so set scale to fit the actual screen size
  194.         {
  195.                 if ((static_cast<float>(SWIDTH)/SHEIGHT) < (static_cast<float>(sw)/bufh))
  196.                         scale = (static_cast<float>(SWIDTH)/sw);
  197.                 else
  198.                         scale = (static_cast<float>(SHEIGHT)/bufh);
  199.         }
  200.         else // Other (robot) movie so set scale to min. screen dimension
  201.         {
  202.                 if ((static_cast<float>(SWIDTH)/bufw) < (static_cast<float>(SHEIGHT)/bufh))
  203.                         scale = (static_cast<float>(SWIDTH)/sw);
  204.                 else
  205.                         scale = (static_cast<float>(SHEIGHT)/sh);
  206.         }
  207.  
  208.         if (dstx == -1) // center it
  209.                 dstx = (SWIDTH/2)-((bufw*scale)/2);
  210.         if (dsty == -1) // center it
  211.                 dsty = (SHEIGHT/2)-((bufh*scale)/2);
  212.  
  213. #if DXX_USE_OGL
  214.         glDisable (GL_BLEND);
  215.  
  216.         ogl_ubitblt_i(
  217.                 bufw*scale, bufh*scale,
  218.                 dstx, dsty,
  219.                 bufw, bufh, 0, 0, source_bm, grd_curcanv->cv_bitmap, (GameCfg.MovieTexFilt)?OGL_TEXFILT_TRLINEAR:OGL_TEXFILT_CLASSIC);
  220.  
  221.         glEnable (GL_BLEND);
  222. #else
  223.         gr_bm_ubitbltm(*grd_curcanv, bufw, bufh, dstx, dsty, 0, 0, source_bm);
  224. #endif
  225. }
  226.  
  227. //our routine to set the pallete, called from the movie code
  228. static void MovieSetPalette(const unsigned char *p, unsigned start, unsigned count)
  229. {
  230.         if (count == 0)
  231.                 return;
  232.  
  233.         //Color 0 should be black, and we get color 255
  234.         Assert(start>=1 && start+count-1<=254);
  235.  
  236.         //Set color 0 to be black
  237.         gr_palette[0].r = gr_palette[0].g = gr_palette[0].b = 0;
  238.  
  239.         //Set color 255 to be our subtitle color
  240.         gr_palette[255].r = gr_palette[255].g = gr_palette[255].b = 50;
  241.  
  242.         //movie libs palette into our array
  243.         memcpy(&gr_palette[start],p+start*3,count*3);
  244. }
  245.  
  246. struct movie : ignore_window_pointer_t
  247. {
  248.         MVE_StepStatus result;
  249.         int aborted;
  250.         int frame_num;
  251.         int paused;
  252.         MVESTREAM_ptr_t pMovie;
  253.         d_subtitle_state SubtitleState;
  254. };
  255.  
  256. static window_event_result show_pause_message(window *, const d_event &event, const unused_window_userdata_t *)
  257. {
  258.         window_event_result result;
  259.  
  260.         switch (event.type)
  261.         {
  262.                 case EVENT_MOUSE_BUTTON_DOWN:
  263.                         if (event_mouse_get_button(event) != 0)
  264.                                 return window_event_result::ignored;
  265.                         DXX_BOOST_FALLTHROUGH;
  266.                 case EVENT_KEY_COMMAND:
  267.                         if ((result = call_default_handler(event)) == window_event_result::ignored)
  268.                         {
  269.                                 return window_event_result::close;
  270.                         }
  271.                         return result;
  272.  
  273.                 case EVENT_WINDOW_DRAW:
  274.                 {
  275.                         const char *msg = TXT_PAUSE;
  276.                         int h;
  277.                         int y;
  278.  
  279.                         gr_set_default_canvas();
  280.                         auto &canvas = *grd_curcanv;
  281.                         const auto &game_font = *GAME_FONT;
  282.                         gr_get_string_size(game_font, msg, nullptr, &h, nullptr);
  283.  
  284.                         y = (grd_curscreen->get_screen_height() - h) / 2;
  285.  
  286.                         gr_set_fontcolor(canvas, 255, -1);
  287.  
  288.                         gr_ustring(canvas, game_font, 0x8000, y, msg);
  289.                         break;
  290.                 }
  291.  
  292.                 default:
  293.                         break;
  294.         }
  295.         return window_event_result::ignored;
  296. }
  297.  
  298. static window_event_result MovieHandler(window *, const d_event &event, movie *m)
  299. {
  300.         int key;
  301.  
  302.         switch (event.type)
  303.         {
  304.                 case EVENT_WINDOW_ACTIVATED:
  305.                         m->paused = 0;
  306.                         break;
  307.  
  308.                 case EVENT_WINDOW_DEACTIVATED:
  309.                         m->paused = 1;
  310.                         MVE_rmHoldMovie();
  311.                         break;
  312.  
  313.                 case EVENT_KEY_COMMAND:
  314.                         key = event_key_get(event);
  315.  
  316.                         // If ESCAPE pressed, then quit movie.
  317.                         if (key == KEY_ESC) {
  318.                                 m->result = MVE_StepStatus::EndOfFile;
  319.                                 m->aborted = 1;
  320.                                 return window_event_result::close;
  321.                         }
  322.  
  323.                         // If PAUSE pressed, then pause movie
  324.                         if ((key == KEY_PAUSE) || (key == KEY_COMMAND + KEY_P))
  325.                         {
  326.                                 if (window_create(grd_curscreen->sc_canvas, 0, 0, SWIDTH, SHEIGHT, show_pause_message, unused_window_userdata))
  327.                                         MVE_rmHoldMovie();
  328.                                 return window_event_result::handled;
  329.                         }
  330.                         break;
  331.  
  332.                 case EVENT_WINDOW_DRAW:
  333.                         if (!m->paused)
  334.                         {
  335.                                 m->result = MVE_rmStepMovie(*m->pMovie.get());
  336.                                 if (m->result != MVE_StepStatus::Continue)
  337.                                 {
  338.                                         return window_event_result::close;
  339.                                 }
  340.                         }
  341.  
  342.                         draw_subtitles(m->SubtitleState, m->frame_num);
  343.  
  344.                         gr_palette_load(gr_palette);
  345.  
  346.                         if (!m->paused)
  347.                                 m->frame_num++;
  348.                         break;
  349.  
  350.                 case EVENT_WINDOW_CLOSE:
  351.                         if (Quitting)
  352.                         {
  353.                                 m->result = MVE_StepStatus::EndOfFile;
  354.                                 m->aborted = 1;
  355.                         }
  356.                         break;
  357.                        
  358.                 default:
  359.                         break;
  360.         }
  361.         return window_event_result::ignored;
  362. }
  363.  
  364. //returns status.  see movie.h
  365. int RunMovie(const char *const filename, const char *const subtitles, const int hires_flag, const int must_have, const int dx, const int dy)
  366. {
  367.         movie m;
  368.         int track = 0;
  369.         int aborted = 0;
  370. #if DXX_USE_OGL
  371.         palette_array_t pal_save;
  372. #endif
  373.  
  374.         init_subtitles(m.SubtitleState, subtitles);
  375.  
  376.         m.result = MVE_StepStatus::EndOfFile;
  377.         m.aborted = 0;
  378.         m.frame_num = 0;
  379.         m.paused = 0;
  380.  
  381.         // Open Movie file.  If it doesn't exist, no movie, just return.
  382.  
  383.         auto filehndl = PHYSFSRWOPS_openRead(filename);
  384.         if (!filehndl)
  385.         {
  386.                 con_printf(must_have ? CON_URGENT : CON_VERBOSE, "Failed to open movie <%s>: %s", filename, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
  387.                 return MOVIE_NOT_PLAYED;
  388.         }
  389.         const auto reshow = hide_menus();
  390.         const auto wind = window_create(grd_curscreen->sc_canvas, 0, 0, SWIDTH, SHEIGHT, MovieHandler, &m);
  391.         if (!wind)
  392.         {
  393.                 if (reshow)
  394.                         show_menus();
  395.                 return MOVIE_NOT_PLAYED;
  396.         }
  397.  
  398.         MVE_memCallbacks(MPlayAlloc, MPlayFree);
  399.         MVE_ioCallbacks(FileRead);
  400.  
  401. #if DXX_USE_OGL
  402.         set_screen_mode(SCREEN_MOVIE);
  403.         gr_copy_palette(pal_save, gr_palette);
  404.         gr_palette_load(gr_palette);
  405.         (void)hires_flag;
  406. #else
  407.         gr_set_mode(hires_flag ? screen_mode{640, 480} : screen_mode{320, 200});
  408. #endif
  409.         MVE_sfCallbacks(MovieShowFrame);
  410.         MVE_palCallbacks(MovieSetPalette);
  411.  
  412.         if (MVE_rmPrepMovie(m.pMovie, filehndl, dx, dy, track)) {
  413.                 Int3();
  414.                 window_close(wind);
  415.                 if (reshow)
  416.                         show_menus();
  417.                 return MOVIE_NOT_PLAYED;
  418.         }
  419.  
  420.         MVE_sfCallbacks(MovieShowFrame);
  421.         MVE_palCallbacks(MovieSetPalette);
  422.  
  423.         do {
  424.                 event_process_all();
  425.         } while(window_get_front() == wind);
  426.  
  427.         assert(m.aborted || m.result == MVE_StepStatus::EndOfFile);      ///movie should be over
  428.  
  429.         m.pMovie.reset();
  430.  
  431.         //filehndl.reset();                           // Close Movie File
  432.         if (reshow)
  433.                 show_menus();
  434.         aborted = m.aborted;
  435.  
  436.         // Restore old graphic state
  437.  
  438.         Screen_mode=-1;  //force reset of screen mode
  439. #if DXX_USE_OGL
  440.         gr_copy_palette(gr_palette, pal_save);
  441.         gr_palette_load(pal_save);
  442. #endif
  443.  
  444.         return (aborted?MOVIE_ABORTED:MOVIE_PLAYED_FULL);
  445. }
  446.  
  447. }
  448.  
  449. //returns 1 if frame updated ok
  450. int RotateRobot(MVESTREAM_ptr_t &pMovie)
  451. {
  452.         auto err = MVE_rmStepMovie(*pMovie.get());
  453.         gr_palette_load(gr_palette);
  454.  
  455.         if (err == MVE_StepStatus::EndOfFile)     //end of movie, so reset
  456.         {
  457.                 SDL_RWseek(RoboFile, 0, SEEK_SET);
  458.                 if (MVE_rmPrepMovie(pMovie, RoboFile, SWIDTH/2.3, SHEIGHT/2.3, 0))
  459.                 {
  460.                         Int3();
  461.                         return 0;
  462.                 }
  463.                 err = MVE_rmStepMovie(*pMovie.get());
  464.         }
  465.         if (err != MVE_StepStatus::Continue)
  466.         {
  467.                 Int3();
  468.                 return 0;
  469.         }
  470.  
  471.         return 1;
  472. }
  473.  
  474.  
  475. void DeInitRobotMovie(MVESTREAM_ptr_t &pMovie)
  476. {
  477.         pMovie.reset();
  478.         //RoboFile.reset();                           // Close Movie File
  479. }
  480.  
  481.  
  482. int InitRobotMovie(const char *filename, MVESTREAM_ptr_t &pMovie)
  483. {
  484.         if (GameArg.SysNoMovies)
  485.                 return 0;
  486.  
  487.         con_printf(CON_DEBUG, "RoboFile=%s", filename);
  488.  
  489.         MVE_memCallbacks(MPlayAlloc, MPlayFree);
  490.         MVE_ioCallbacks(FileRead);
  491.         MVE_sfCallbacks(MovieShowFrame);
  492.         MVE_palCallbacks(MovieSetPalette);
  493.         MVE_sndInit(-1);        //tell movies to play no sound for robots
  494.  
  495.         RoboFile = PHYSFSRWOPS_openRead(filename);
  496.  
  497.         if (!RoboFile)
  498.         {
  499.                 con_printf(CON_URGENT, "Can't open movie <%s>: %s", filename, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
  500.                 return MOVIE_NOT_PLAYED;
  501.         }
  502.         if (MVE_rmPrepMovie(pMovie, RoboFile, SWIDTH/2.3, SHEIGHT/2.3, 0)) {
  503.                 Int3();
  504.                 return 0;
  505.         }
  506.  
  507.         return 1;
  508. }
  509.  
  510. namespace {
  511.  
  512. /*
  513.  *              Subtitle system code
  514.  */
  515.  
  516. //search for next field following whitespace
  517. static char *next_field (char *p)
  518. {
  519.         while (*p && !isspace(*p))
  520.                 p++;
  521.  
  522.         if (!*p)
  523.                 return NULL;
  524.  
  525.         while (*p && isspace(*p))
  526.                 p++;
  527.  
  528.         if (!*p)
  529.                 return NULL;
  530.  
  531.         return p;
  532. }
  533.  
  534. static int init_subtitles(d_subtitle_state &SubtitleState, const char *const filename)
  535. {
  536.         if (!filename)
  537.                 return 0;
  538.         int size,read_count;
  539.         char *p;
  540.         int have_binary = 0;
  541.  
  542.         SubtitleState.Num_subtitles = 0;
  543.  
  544.         if (!GameCfg.MovieSubtitles)
  545.         {
  546.                 con_puts(CON_VERBOSE, "Rebirth: movie subtitles are disabled");
  547.                 return 0;
  548.         }
  549.  
  550.         auto ifile = PHYSFSX_openReadBuffered(filename);                //try text version
  551.  
  552.         if (!ifile) {                                                           //no text version, try binary version
  553.                 char filename2[FILENAME_LEN];
  554.                 change_filename_extension(filename2, filename, ".txb");
  555.                 ifile = PHYSFSX_openReadBuffered(filename2);
  556.                 if (!ifile)
  557.                 {
  558.                         con_printf(CON_VERBOSE, "Rebirth: skipping subtitles because cannot open \"%s\" or \"%s\"", filename, filename2);
  559.                         return 0;
  560.                 }
  561.                 have_binary = 1;
  562.                 con_printf(CON_VERBOSE, "Rebirth: found encoded subtitles in \"%s\"", filename2);
  563.         }
  564.         else
  565.                 con_printf(CON_VERBOSE, "Rebirth: found text subtitles in \"%s\"", filename);
  566.  
  567.         size = PHYSFS_fileLength(ifile);
  568.  
  569.         const auto subtitle_raw_data = (SubtitleState.subtitle_raw_data = std::make_unique<char[]>(size + 1)).get();
  570.         read_count = PHYSFS_read(ifile, subtitle_raw_data, 1, size);
  571.         ifile.reset();
  572.  
  573.         if (read_count != size) {
  574.                 con_puts(CON_VERBOSE, "Rebirth: skipping subtitles because cannot read full subtitle file");
  575.                 return 0;
  576.         }
  577.  
  578.         subtitle_raw_data[size] = 0;
  579.         p = subtitle_raw_data;
  580.  
  581.         while (p && p < subtitle_raw_data+size) {
  582.                 char *endp;
  583.  
  584.                 endp = strchr(p,'\n');
  585.                 if (endp) {
  586.                         if (endp[-1] == '\r')
  587.                                 endp[-1] = 0;           //handle 0d0a pair
  588.                         *endp = 0;                      //string termintor
  589.                 }
  590.  
  591.                 if (have_binary)
  592.                         decode_text_line(p);
  593.  
  594.                 if (*p != ';') {
  595.                         const auto Num_subtitles = SubtitleState.Num_subtitles;
  596.                         auto &Subtitles = SubtitleState.Subtitles;
  597.                         auto &s = Subtitles[SubtitleState.Num_subtitles++];
  598.                         s.first_frame = atoi(p);
  599.                         p = next_field(p); if (!p) continue;
  600.                         s.last_frame = atoi(p);
  601.                         p = next_field(p); if (!p) continue;
  602.                         s.msg = p;
  603.  
  604.                         if (Num_subtitles)
  605.                         {
  606.                                 assert(s.first_frame >= Subtitles[Num_subtitles - 1].first_frame);
  607.                         }
  608.                         assert(s.last_frame >= s.first_frame);
  609.                 }
  610.  
  611.                 p = endp+1;
  612.         }
  613.         return 1;
  614. }
  615.  
  616. //draw the subtitles for this frame
  617. static void draw_subtitles(const d_subtitle_state &SubtitleState, const int frame_num)
  618. {
  619.         static int active_subtitles[MAX_ACTIVE_SUBTITLES];
  620.         static int next_subtitle;
  621.         static unsigned num_active_subtitles;
  622.         int y;
  623.         int must_erase=0;
  624.  
  625.         if (frame_num == 0) {
  626.                 num_active_subtitles = 0;
  627.                 next_subtitle = 0;
  628.                 gr_set_curfont(*grd_curcanv, GAME_FONT);
  629.                 gr_set_fontcolor(*grd_curcanv, 255, -1);
  630.         }
  631.  
  632.         //get rid of any subtitles that have expired
  633.         auto &Subtitles = SubtitleState.Subtitles;
  634.         for (int t=0;t<num_active_subtitles;)
  635.                 if (frame_num > Subtitles[active_subtitles[t]].last_frame) {
  636.                         int t2;
  637.                         for (t2=t;t2<num_active_subtitles-1;t2++)
  638.                                 active_subtitles[t2] = active_subtitles[t2+1];
  639.                         num_active_subtitles--;
  640.                         must_erase = 1;
  641.                 }
  642.                 else
  643.                         t++;
  644.  
  645.         //get any subtitles new for this frame
  646.         while (next_subtitle < SubtitleState.Num_subtitles && frame_num >= Subtitles[next_subtitle].first_frame) {
  647.                 if (num_active_subtitles >= MAX_ACTIVE_SUBTITLES)
  648.                         Error("Too many active subtitles!");
  649.                 active_subtitles[num_active_subtitles++] = next_subtitle;
  650.                 next_subtitle++;
  651.         }
  652.  
  653.         //find y coordinate for first line of subtitles
  654.         const auto &&line_spacing = LINE_SPACING(*grd_curcanv->cv_font, *GAME_FONT);
  655.         y = grd_curcanv->cv_bitmap.bm_h - (line_spacing * (MAX_ACTIVE_SUBTITLES + 2));
  656.  
  657.         //erase old subtitles if necessary
  658.         if (must_erase) {
  659.                 gr_rect(*grd_curcanv, 0,y,grd_curcanv->cv_bitmap.bm_w-1,grd_curcanv->cv_bitmap.bm_h-1, color_palette_index{0});
  660.         }
  661.  
  662.         //now draw the current subtitles
  663.         range_for (const auto &t, partial_range(active_subtitles, num_active_subtitles))
  664.                 if (t != -1)
  665.                 {
  666.                         gr_string(*grd_curcanv, *grd_curcanv->cv_font, 0x8000, y, Subtitles[t].msg);
  667.                         y += line_spacing;
  668.                 }
  669. }
  670.  
  671. static int init_movie(const char *movielib, char resolution, int required, loaded_movie_t &movie)
  672. {
  673.         snprintf(&movie.filename[0], movie.filename.size(), "%s-%c.mvl", movielib, resolution);
  674.         auto r = PHYSFSX_contfile_init(&movie.filename[0], 0);
  675.         if (!r)
  676.         {
  677.                 if (required || CGameArg.DbgVerbose)
  678.                         con_printf(CON_URGENT, "Can't open movielib <%s>: %s", &movie.filename[0], PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
  679.                 movie.filename[0] = 0;
  680.         }
  681.         return r;
  682. }
  683.  
  684. static void init_movie(const char *movielib, int required, loaded_movie_t &movie)
  685. {
  686.         if (!GameArg.GfxSkipHiresMovie)
  687.         {
  688.                 if (init_movie(movielib, 'h', required, movie))
  689.                         return;
  690.         }
  691.         init_movie(movielib, 'l', required, movie);
  692. }
  693.  
  694. }
  695.  
  696. //find and initialize the movie libraries
  697. void init_movies()
  698. {
  699.         if (GameArg.SysNoMovies)
  700.                 return;
  701.  
  702.         range_for (auto &i, movielib_files)
  703.         {
  704.                 loaded_movie_t m;
  705.                 init_movie(&i[0], 1, m);
  706.         }
  707. }
  708.  
  709. void close_extra_robot_movie()
  710. {
  711.         const auto movielib = &extra_robot_movie_mission.filename[0];
  712.         if (!*movielib)
  713.                 return;
  714.         if (!PHYSFSX_contfile_close(movielib))
  715.         {
  716.                 con_printf(CON_URGENT, "Can't close movielib <%s>: %s", movielib, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
  717.         }
  718.         *movielib = 0;
  719. }
  720.  
  721. void init_extra_robot_movie(const char *movielib)
  722. {
  723.         if (GameArg.SysNoMovies)
  724.                 return;
  725.         init_movie(movielib, 0, extra_robot_movie_mission);
  726. }
  727.