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