movie improvements
[btb/d2x.git] / main / movie.c
1 /* $Id: movie.c,v 1.11 2002-08-31 12:14:01 btb Exp $ */
2 /*
3 THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
4 SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
5 END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
6 ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
7 IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
8 SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
9 FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
10 CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
11 AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
12 COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
13 */
14
15 #ifdef HAVE_CONFIG_H
16 #include <conf.h>
17 #endif
18
19 #ifdef RCS
20 static char rcsid[] = "$Id: movie.c,v 1.11 2002-08-31 12:14:01 btb Exp $";
21 #endif
22
23 #define DEBUG_LEVEL CON_NORMAL
24
25 #include <string.h>
26 #include <sys/types.h>
27 #include <sys/stat.h>
28 #include <fcntl.h>
29 #include <unistd.h>
30 #include <ctype.h>
31
32 #include "movie.h"
33 #include "console.h"
34 #include "args.h"
35 #include "key.h"
36 #include "digi.h"
37 #include "songs.h"
38 #include "inferno.h"
39 #include "palette.h"
40 #include "strutil.h"
41 #include "error.h"
42 #include "u_mem.h"
43 #include "byteswap.h"
44 #include "gr.h"
45 #include "gamefont.h"
46 #include "cfile.h"
47 #include "menu.h"
48 #include "mvelib.h"
49 #include "text.h"
50 #include "fileutil.h"
51
52 extern int MenuHiresAvailable;
53 extern char CDROM_dir[];
54
55 #define VID_PLAY 0
56 #define VID_PAUSE 1
57
58 int Vid_State;
59
60
61 // Subtitle data
62 typedef struct {
63         short first_frame,last_frame;
64         char *msg;
65 } subtitle;
66
67 #define MAX_SUBTITLES 500
68 #define MAX_ACTIVE_SUBTITLES 3
69 subtitle Subtitles[MAX_SUBTITLES];
70 int Num_subtitles;
71
72
73 typedef struct {
74         char name[FILENAME_LEN];
75         int offset,len;
76 } ml_entry;
77
78 #define MLF_ON_CD    1
79 #define MAX_MOVIES_PER_LIB    50    //determines size of malloc
80
81
82 typedef struct {
83         char     name[100]; //[FILENAME_LEN];
84         int      n_movies;
85         ubyte    flags,pad[3];
86         ml_entry *movies;
87 } movielib;
88
89 #ifdef D2_OEM
90 char movielib_files[][FILENAME_LEN] = {"intro-l.mvl","other-l.mvl","robots-l.mvl","oem-l.mvl"};
91 #else
92 char movielib_files[][FILENAME_LEN] = {"intro-l.mvl","other-l.mvl","robots-l.mvl"};
93 #endif
94
95 #define N_BUILTIN_MOVIE_LIBS (sizeof(movielib_files)/sizeof(*movielib_files))
96 #define N_MOVIE_LIBS (N_BUILTIN_MOVIE_LIBS+1)
97 #define EXTRA_ROBOT_LIB N_BUILTIN_MOVIE_LIBS
98 movielib *movie_libs[N_MOVIE_LIBS];
99
100 int MVEPaletteCalls = 0;
101
102 //do we have the robot movies available
103 int robot_movies = 0; //0 means none, 1 means lowres, 2 means hires
104
105 int MovieHires = 0;   //default for now is lores
106
107 int RoboFile = 0;
108 char Robo_filename[FILENAME_LEN];
109 MVESTREAM *Robo_mve;
110 grs_bitmap *Robo_bitmap;
111
112
113 //      Function Prototypes
114 int RunMovie(char *filename, int highres_flag, int allow_abort,int dx,int dy);
115
116 int open_movie_file(char *filename,int must_have);
117
118 void change_filename_ext( char *dest, char *src, char *ext );
119 void decode_text_line(char *p);
120 void draw_subtitles(int frame_num);
121
122
123 //filename will actually get modified to be either low-res or high-res
124 //returns status.  see values in movie.h
125 int PlayMovie(const char *filename, int must_have)
126 {
127         char name[FILENAME_LEN],*p;
128         int c, ret;
129 #if 0
130         int save_sample_rate;
131 #endif
132
133 #ifndef RELEASE
134         if (FindArg("-nomovies"))
135                 return MOVIE_NOT_PLAYED;
136 #endif
137
138         strcpy(name,filename);
139
140         if ((p=strchr(name,'.')) == NULL)               //add extension, if missing
141                 strcat(name,".mve");
142
143         //check for escape already pressed & abort if so
144         while ((c=key_inkey()) != 0)
145                 if (c == KEY_ESC)
146                         return MOVIE_ABORTED;
147
148         // Stop all digital sounds currently playing.
149         digi_stop_all();
150
151         // Stop all songs
152         songs_stop_all();
153
154 #if 0
155         save_sample_rate = digi_sample_rate;
156         digi_sample_rate = SAMPLE_RATE_22K;             //always 22K for movies
157         digi_reset(); digi_reset();
158 #else
159         digi_close();
160 #endif
161
162         ret = RunMovie(name,MovieHires,must_have,-1,-1);
163
164 #if 0
165         gr_palette_clear();             //clear out palette in case movie aborted
166 #endif
167
168 #if 0
169         digi_sample_rate = save_sample_rate;            //restore rate for game
170         digi_reset(); digi_reset();
171 #else
172         digi_init();
173 #endif
174
175         Screen_mode = -1;               //force screen reset
176
177         return ret;
178 }
179
180 void initializeMovie(MVESTREAM *mve, grs_bitmap *mve_bitmap);
181 void shutdownMovie(MVESTREAM *mve);
182
183 //returns status.  see movie.h
184 int RunMovie(char *filename, int hires_flag, int must_have,int dx,int dy)
185 {
186         int filehndl;
187         int result=1,aborted=0;
188         int frame_num;
189         MVESTREAM *mve;
190         grs_bitmap *mve_bitmap;
191
192         result=1;
193         con_printf(DEBUG_LEVEL, "movie: RunMovie: %s %d %d %d %d\n", filename, hires_flag, must_have, dx, dy);
194
195         // Open Movie file.  If it doesn't exist, no movie, just return.
196
197         filehndl = open_movie_file(filename,must_have);
198
199         if (filehndl == -1) {
200 #ifndef EDITOR
201                 if (must_have)
202                         Warning("movie: RunMovie: Cannot open movie <%s>\n",filename);
203 #endif
204                 return MOVIE_NOT_PLAYED;
205         }
206
207         if (hires_flag)
208                 gr_set_mode(SM(640,480));
209         else
210                 gr_set_mode(SM(320,200));
211
212         frame_num = 0;
213
214         FontHires = FontHiresAvailable && hires_flag;
215
216     mve = mve_open(filehndl);
217     if (mve == NULL)
218     {
219         fprintf(stderr, "can't open MVE file '%s'\n", filename);
220         return 1;
221     }
222
223         mve_bitmap = gr_create_bitmap(hires_flag?592:320, hires_flag?312:200);
224
225     initializeMovie(mve, mve_bitmap);
226
227         while((result = mve_play_next_chunk(mve))) {
228                 int key;
229
230                 gr_bitmap(hires_flag?24:12, hires_flag?84:42, mve_bitmap);
231
232                 draw_subtitles(frame_num);
233
234                 gr_update();
235
236                 key = key_inkey();
237
238                 // If ESCAPE pressed, then quit movie.
239                 if (key == KEY_ESC) {
240                         result = 0;
241                         aborted = 1;
242                         break;
243                 }
244
245                 // If PAUSE pressed, then pause movie
246                 if (key == KEY_PAUSE) {
247                         //MVE_rmHoldMovie();
248                         //show_pause_message(TXT_PAUSE);
249                         while (!key_inkey()) ;
250                         //clear_pause_message();
251                 }
252
253                 frame_num++;
254         }
255
256         Assert(aborted || !result);      ///movie should be over
257
258     shutdownMovie(mve);
259
260         gr_free_bitmap(mve_bitmap);
261
262     mve_close(mve);
263
264         close(filehndl);                           // Close Movie File
265
266         // Restore old graphic state
267
268         Screen_mode=-1;  //force reset of screen mode
269
270         return (aborted?MOVIE_ABORTED:MOVIE_PLAYED_FULL);
271 }
272
273
274 int InitMovieBriefing()
275 {
276         if (MenuHires)
277                 gr_set_mode(SM(640,480));
278         else
279                 gr_set_mode(SM(320,200));
280
281         return 1;
282 }
283
284
285 //returns 1 if frame updated ok
286 int RotateRobot()
287 {
288         int ret;
289
290         ret = mve_play_next_chunk(Robo_mve);
291         gr_bitmap(MenuHires?280:200, MenuHires?140:80, Robo_bitmap);
292
293         if (!ret) {
294                 DeInitRobotMovie();
295                 InitRobotMovie(Robo_filename);
296         }
297
298         return 1;
299 }
300
301
302 void DeInitRobotMovie(void)
303 {
304         con_printf(DEBUG_LEVEL, "movie: DeInitRobotMovie\n");
305
306         shutdownMovie(Robo_mve);
307
308         gr_free_bitmap(Robo_bitmap);
309
310         close(RoboFile);
311 }
312
313
314 int InitRobotMovie(char *filename)
315 {
316         con_printf(DEBUG_LEVEL, "movie: InitRobotMovie: %s\n", filename);
317
318         RoboFile = open_movie_file(filename, 1);
319
320         if (RoboFile == -1) {
321                 Warning("movie: InitRobotMovie: Cannot open movie file <%s>",filename);
322                 return MOVIE_NOT_PLAYED;
323         }
324
325         strcpy(Robo_filename, filename);
326
327         Robo_mve = mve_open(RoboFile);
328     if (Robo_mve == NULL)
329     {
330         fprintf(stderr, "can't open MVE file '%s'\n", filename);
331         return 0;
332     }
333
334         Robo_bitmap = gr_create_bitmap(MenuHires?320:160, MenuHires?200:100);
335
336         initializeMovie(Robo_mve, Robo_bitmap);
337
338         return 1;
339 }
340
341
342 /*
343  *              Subtitle system code
344  */
345
346 ubyte *subtitle_raw_data;
347
348
349 //search for next field following whitespace 
350 ubyte *next_field(ubyte *p)
351 {
352         while (*p && !isspace(*p))
353                 p++;
354
355         if (!*p)
356                 return NULL;
357
358         while (*p && isspace(*p))
359                 p++;
360
361         if (!*p)
362                 return NULL;
363
364         return p;
365 }
366
367
368 int init_subtitles(char *filename)
369 {
370         CFILE *ifile;
371         int size,read_count;
372         ubyte *p;
373         int have_binary = 0;
374
375         Num_subtitles = 0;
376
377         if (! FindArg("-subtitles"))
378                 return 0;
379
380         ifile = cfopen(filename,"rb");          //try text version
381
382         if (!ifile) {                                                           //no text version, try binary version
383                 char filename2[FILENAME_LEN];
384                 change_filename_ext(filename2,filename,".TXB");
385                 ifile = cfopen(filename2,"rb");
386                 if (!ifile)
387                         return 0;
388                 have_binary = 1;
389         }
390
391         size = cfilelength(ifile);
392    
393         MALLOC (subtitle_raw_data, ubyte, size+1);
394
395         read_count = cfread(subtitle_raw_data, 1, size, ifile);
396
397         cfclose(ifile);
398
399         subtitle_raw_data[size] = 0;
400
401         if (read_count != size) {
402                 d_free(subtitle_raw_data);
403                 return 0;
404         }
405
406         p = subtitle_raw_data;
407
408         while (p && p < subtitle_raw_data+size) {
409                 char *endp;
410
411                 endp = strchr(p,'\n'); 
412                 if (endp) {
413                         if (endp[-1] == '\r')
414                                 endp[-1] = 0;           //handle 0d0a pair
415                         *endp = 0;                      //string termintor
416                 }
417
418                 if (have_binary)
419                         decode_text_line(p);
420
421                 if (*p != ';') {
422                         Subtitles[Num_subtitles].first_frame = atoi(p);
423                         p = next_field(p); if (!p) continue;
424                         Subtitles[Num_subtitles].last_frame = atoi(p);
425                         p = next_field(p); if (!p) continue;
426                         Subtitles[Num_subtitles].msg = p;
427
428                         Assert(Num_subtitles==0 || Subtitles[Num_subtitles].first_frame >= Subtitles[Num_subtitles-1].first_frame);
429                         Assert(Subtitles[Num_subtitles].last_frame >= Subtitles[Num_subtitles].first_frame);
430
431                         Num_subtitles++;
432                 }
433
434                 p = endp+1;
435
436         }
437
438         return 1;
439 }
440
441
442 void close_subtitles()
443 {
444         if (subtitle_raw_data)
445                 d_free(subtitle_raw_data);
446         subtitle_raw_data = NULL;
447         Num_subtitles = 0;
448 }
449
450
451 //draw the subtitles for this frame
452 void draw_subtitles(int frame_num)
453 {
454         static int active_subtitles[MAX_ACTIVE_SUBTITLES];
455         static int num_active_subtitles,next_subtitle,line_spacing;
456         int t,y;
457         int must_erase=0;
458
459         if (frame_num == 0) {
460                 num_active_subtitles = 0;
461                 next_subtitle = 0;
462                 gr_set_curfont( GAME_FONT );
463                 line_spacing = grd_curcanv->cv_font->ft_h + (grd_curcanv->cv_font->ft_h >> 2);
464                 gr_set_fontcolor(255,-1);
465         }
466
467         //get rid of any subtitles that have expired
468         for (t=0;t<num_active_subtitles;)
469                 if (frame_num > Subtitles[active_subtitles[t]].last_frame) {
470                         int t2;
471                         for (t2=t;t2<num_active_subtitles-1;t2++)
472                                 active_subtitles[t2] = active_subtitles[t2+1];
473                         num_active_subtitles--;
474                         must_erase = 1;
475                 }
476                 else
477                         t++;
478
479         //get any subtitles new for this frame 
480         while (next_subtitle < Num_subtitles && frame_num >= Subtitles[next_subtitle].first_frame) {
481                 if (num_active_subtitles >= MAX_ACTIVE_SUBTITLES)
482                         Error("Too many active subtitles!");
483                 active_subtitles[num_active_subtitles++] = next_subtitle;
484                 next_subtitle++;
485         }
486
487         //find y coordinate for first line of subtitles
488         y = grd_curcanv->cv_bitmap.bm_h-((line_spacing+1)*MAX_ACTIVE_SUBTITLES+2);
489
490         //erase old subtitles if necessary
491         if (must_erase) {
492                 gr_setcolor(0);
493                 gr_rect(0,y,grd_curcanv->cv_bitmap.bm_w-1,grd_curcanv->cv_bitmap.bm_h-1);
494         }
495
496         //now draw the current subtitles
497         for (t=0;t<num_active_subtitles;t++)
498                 if (active_subtitles[t] != -1) {
499                         gr_string(0x8000,y,Subtitles[active_subtitles[t]].msg);
500                         y += line_spacing+1;
501                 }
502 }
503
504
505 int request_cd(void)
506 {
507         con_printf(DEBUG_LEVEL, "STUB: movie: request_cd\n");
508         return 0;
509 }
510
511
512 movielib *init_new_movie_lib(char *filename,FILE *fp)
513 {
514         int nfiles,offset;
515         int i,n;
516         movielib *table;
517
518         //read movie file header
519
520         nfiles = file_read_int(fp);             //get number of files
521
522         //table = d_malloc(sizeof(*table) + sizeof(ml_entry)*nfiles);
523         MALLOC(table, movielib, 1);
524         MALLOC(table->movies, ml_entry, nfiles);
525
526         strcpy(table->name,filename);
527         table->n_movies = nfiles;
528
529         offset = 4+4+nfiles*(13+4);     //id + nfiles + nfiles * (filename + size)
530
531         for (i=0;i<nfiles;i++) {
532                 int len;
533
534                 n = fread( table->movies[i].name, 13, 1, fp );
535                 if ( n != 1 )
536                         break;          //end of file (probably)
537
538                 len = file_read_int(fp);
539
540                 table->movies[i].len = len;
541                 table->movies[i].offset = offset;
542
543                 offset += table->movies[i].len;
544
545         }
546
547         fclose(fp);
548
549         table->flags = 0;
550
551         return table;
552
553 }
554
555
556 movielib *init_old_movie_lib(char *filename,FILE *fp)
557 {
558         int nfiles,size;
559         int i;
560         movielib *table,*table2;
561
562         nfiles = 0;
563
564         //allocate big table
565         table = d_malloc(sizeof(*table) + sizeof(ml_entry)*MAX_MOVIES_PER_LIB);
566
567         while( 1 ) {
568                 int len;
569
570                 i = fread( table->movies[nfiles].name, 13, 1, fp );
571                 if ( i != 1 )
572                         break;          //end of file (probably)
573
574                 i = fread( &len, 4, 1, fp );
575                 if ( i != 1 )
576                         Error("error reading movie library <%s>",filename);
577
578                 table->movies[nfiles].len = INTEL_INT(len);
579                 table->movies[nfiles].offset = ftell( fp );
580
581                 fseek( fp, INTEL_INT(len), SEEK_CUR );          //skip data
582
583                 nfiles++;
584         }
585
586         //allocate correct-sized table
587         size = sizeof(*table) + sizeof(ml_entry)*nfiles;
588         table2 = d_malloc(size);
589         memcpy(table2,table,size);
590         d_free(table);
591         table = table2;
592
593         strcpy(table->name,filename);
594
595         table->n_movies = nfiles;
596
597         fclose(fp);
598
599         table->flags = 0;
600
601         return table;
602
603 }
604
605
606 //find the specified movie library, and read in list of movies in it
607 movielib *init_movie_lib(char *filename)
608 {
609         //note: this based on cfile_init_hogfile()
610
611         char id[4];
612         FILE * fp;
613
614         fp = fopen( filename, "rb" );
615
616         if ((fp == NULL) && (AltHogdir_initialized)) {
617                 char temp[128];
618                 strcpy(temp, AltHogDir);
619                 strcat(temp, "/");
620                 strcat(temp, filename);
621                 fp = fopen(temp, "rb");
622         }
623
624         if ( fp == NULL )
625                 return NULL;
626
627         fread( id, 4, 1, fp );
628         if ( !strncmp( id, "DMVL", 4 ) )
629                 return init_new_movie_lib(filename,fp);
630         else if ( !strncmp( id, "DHF", 3 ) ) {
631                 fseek(fp,-1,SEEK_CUR);          //old file had 3 char id
632                 return init_old_movie_lib(filename,fp);
633         }
634         else {
635                 fclose(fp);
636                 return NULL;
637         }
638 }
639
640
641 void close_movie(int i)
642 {
643         if (movie_libs[i]) {
644                 d_free(movie_libs[i]->movies);
645                 d_free(movie_libs[i]);
646         }
647 }
648
649
650 void close_movies()
651 {
652         int i;
653
654         for (i=0;i<N_MOVIE_LIBS;i++)
655                 close_movie(i);
656 }
657
658
659 void init_movie(char *filename,int libnum,int is_robots,int required)
660 {
661         int high_res;
662         int try = 0;
663
664 #ifndef RELEASE
665         if (FindArg("-nomovies")) {
666                 movie_libs[libnum] = NULL;
667                 return;
668         }
669 #endif
670
671         //for robots, load highres versions if highres menus set
672         if (is_robots)
673                 high_res = MenuHiresAvailable;
674         else
675                 high_res = MovieHires;
676
677         if (high_res)
678                 strchr(filename,'.')[-1] = 'h';
679
680 try_again:;
681
682         if ((movie_libs[libnum] = init_movie_lib(filename)) == NULL) {
683                 char name2[100];
684
685                 strcpy(name2,CDROM_dir);
686                 strcat(name2,filename);
687                 movie_libs[libnum] = init_movie_lib(name2);
688
689                 if (movie_libs[libnum] != NULL)
690                         movie_libs[libnum]->flags |= MLF_ON_CD;
691                 else {
692                         if (required) {
693                                 Warning("Cannot open movie file <%s>\n",filename);
694                         }
695
696                         if (!try) {                                         // first try
697                                 if (strchr(filename, '.')[-1] == 'h') {         // try again with lowres
698                                         strchr(filename, '.')[-1] = 'l';
699                                         Warning("Trying to open movie file <%s> instead\n", filename);
700                                         try++;
701                                         goto try_again;
702                                 } else if (strchr(filename, '.')[-1] == 'l') {  // try again with highres
703                                         strchr(filename, '.')[-1] = 'h';
704                                         Warning("Trying to open movie file <%s> instead\n", filename);
705                                         try++;
706                                         goto try_again;
707                                 }
708                         }
709                 }
710         }
711
712         if (is_robots && movie_libs[libnum]!=NULL)
713                 robot_movies = high_res?2:1;
714 }
715
716
717 //find and initialize the movie libraries
718 void init_movies()
719 {
720         int i;
721         int is_robots;
722
723         for (i=0;i<N_BUILTIN_MOVIE_LIBS;i++) {
724
725                 if (!strnicmp(movielib_files[i],"robot",5))
726                         is_robots = 1;
727                 else
728                         is_robots = 0;
729
730                 init_movie(movielib_files[i],i,is_robots,1);
731         }
732
733         movie_libs[EXTRA_ROBOT_LIB] = NULL;
734
735         atexit(close_movies);
736 }
737
738
739 void init_extra_robot_movie(char *filename)
740 {
741         con_printf(DEBUG_LEVEL, "movie: init_extra_robot_movie: %s\n", filename);
742
743         close_movie(EXTRA_ROBOT_LIB);
744         init_movie(filename,EXTRA_ROBOT_LIB,1,0);
745 }
746
747
748 //looks through a movie library for a movie file
749 //returns filehandle, with fileposition at movie, or -1 if can't find
750 int search_movie_lib(movielib *lib,char *filename,int must_have)
751 {
752         int i;
753         int filehandle;
754
755         if (lib == NULL)
756                 return -1;
757
758         for (i=0;i<lib->n_movies;i++)
759                 if (!stricmp(filename,lib->movies[i].name)) {   //found the movie in a library 
760                         int from_cd;
761
762                         from_cd = (lib->flags & MLF_ON_CD);
763
764                         if (from_cd)
765                                 songs_stop_redbook();           //ready to read from CD
766
767                         do {            //keep trying until we get the file handle
768
769                                 /* movie_handle = */ filehandle = open(lib->name, O_RDONLY);
770
771                                 if ((filehandle == -1) && (AltHogdir_initialized)) {
772                                         char temp[128];
773                                         strcpy(temp, AltHogDir);
774                                         strcat(temp, "/");
775                                         strcat(temp, lib->name);
776                                         filehandle = open(temp, O_RDONLY);
777                                 }
778
779                                 if (must_have && from_cd && filehandle == -1) {         //didn't get file!
780
781                                         if (request_cd() == -1)         //ESC from requester
782                                                 break;                                          //bail from here. will get error later
783                                 }
784
785                         } while (must_have && from_cd && filehandle == -1);
786
787                         if (filehandle != -1)
788                                 lseek(filehandle,(/* movie_start = */ lib->movies[i].offset),SEEK_SET);
789
790                         return filehandle;
791                 }
792
793         return -1;
794 }
795
796
797 //returns file handle
798 int open_movie_file(char *filename,int must_have)
799 {
800         int filehandle,i;
801
802         for (i=0;i<N_MOVIE_LIBS;i++) {
803                 if ((filehandle = search_movie_lib(movie_libs[i],filename,must_have)) != -1)
804                         return filehandle;
805         }
806
807         return -1;              //couldn't find it
808 }
809
810