Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
1 | pmbaty | 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 | } |