1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * Additional copyright for this file:
8 * Copyright (C) 1994-1998 Revolution Software Ltd.
9 *
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU General Public License
12 * as published by the Free Software Foundation; either version 2
13 * of the License, or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 */
24
25 #include "common/file.h"
26 #include "common/mutex.h"
27 #include "common/system.h"
28 #include "common/textconsole.h"
29 #include "common/translation.h"
30
31 #include "sword2/sword2.h"
32 #include "sword2/defs.h"
33 #include "sword2/header.h"
34 #include "sword2/logic.h"
35 #include "sword2/maketext.h"
36 #include "sword2/resman.h"
37 #include "sword2/sound.h"
38 #include "sword2/screen.h"
39 #include "sword2/animation.h"
40
41 #include "graphics/palette.h"
42
43 #include "gui/message.h"
44
45 #ifdef USE_MPEG2
46 #include "video/avi_decoder.h"
47 #endif
48
49 #ifdef USE_ZLIB
50 #include "video/dxa_decoder.h"
51 #endif
52
53 #include "video/smk_decoder.h"
54 #include "video/psx_decoder.h"
55
56 #include "engines/util.h"
57
58 namespace Sword2 {
59
60 ///////////////////////////////////////////////////////////////////////////////
61 // Basic movie player
62 ///////////////////////////////////////////////////////////////////////////////
63
MoviePlayer(Sword2Engine * vm,OSystem * system,Video::VideoDecoder * decoder,DecoderType decoderType)64 MoviePlayer::MoviePlayer(Sword2Engine *vm, OSystem *system, Video::VideoDecoder *decoder, DecoderType decoderType)
65 : _vm(vm), _system(system) {
66 _decoderType = decoderType;
67 _decoder = decoder;
68
69 _white = 255;
70 _black = 0;
71 }
72
~MoviePlayer()73 MoviePlayer::~MoviePlayer() {
74 delete _decoder;
75 }
76
77 /**
78 * Plays an animated cutscene.
79 * @param id the id of the file
80 */
load(const char * name)81 bool MoviePlayer::load(const char *name) {
82 // This happens when quitting during the "eye" cutscene.
83 if (_vm->shouldQuit())
84 return false;
85
86 _textSurface = NULL;
87
88 Common::String filename;
89 switch (_decoderType) {
90 case kVideoDecoderDXA:
91 filename = Common::String::format("%s.dxa", name);
92 break;
93 case kVideoDecoderSMK:
94 filename = Common::String::format("%s.smk", name);
95 break;
96 case kVideoDecoderPSX:
97 filename = Common::String::format("%s.str", name);
98 break;
99 case kVideoDecoderMP2:
100 filename = Common::String::format("%s.mp2", name);
101 break;
102 }
103
104 // Need to switch to true color for PSX/MP2 videos
105 if (_decoderType == kVideoDecoderPSX || _decoderType == kVideoDecoderMP2)
106 initGraphics(g_system->getWidth(), g_system->getHeight(), nullptr);
107
108 if (!_decoder->loadFile(filename)) {
109 // Go back to 8bpp color
110 if (_decoderType == kVideoDecoderPSX || _decoderType == kVideoDecoderMP2)
111 initGraphics(g_system->getWidth(), g_system->getHeight());
112
113 return false;
114 }
115
116 // For DXA/MP2, also add the external sound file
117 if (_decoderType == kVideoDecoderDXA || _decoderType == kVideoDecoderMP2)
118 _decoder->addStreamFileTrack(name);
119
120 _decoder->start();
121 return true;
122 }
123
play(MovieText * movieTexts,uint32 numMovieTexts,uint32 leadIn,uint32 leadOut)124 void MoviePlayer::play(MovieText *movieTexts, uint32 numMovieTexts, uint32 leadIn, uint32 leadOut) {
125 _leadOutFrame = _decoder->getFrameCount();
126 if (_leadOutFrame > 60)
127 _leadOutFrame -= 60;
128
129 _movieTexts = movieTexts;
130 _numMovieTexts = numMovieTexts;
131 _currentMovieText = 0;
132 _leadOut = leadOut;
133
134 if (leadIn)
135 _vm->_sound->playMovieSound(leadIn, kLeadInSound);
136
137 bool terminated = !playVideo();
138
139 closeTextObject(_currentMovieText, NULL, 0);
140
141 if (terminated) {
142 _vm->_sound->stopMovieSounds();
143 _vm->_sound->stopSpeech();
144 }
145
146 // Need to jump back to paletted color
147 if (_decoderType == kVideoDecoderPSX || _decoderType == kVideoDecoderMP2)
148 initGraphics(640, 480);
149 }
150
openTextObject(uint32 index)151 void MoviePlayer::openTextObject(uint32 index) {
152 MovieText *text = &_movieTexts[index];
153
154 // Pull out the text line to get the official text number (for WAV id)
155
156 uint32 res = text->_textNumber / SIZE;
157 uint32 localText = text->_textNumber & 0xffff;
158
159 // Open text resource and get the line
160
161 byte *textData = _vm->fetchTextLine(_vm->_resman->openResource(res), localText);
162
163 text->_speechId = READ_LE_UINT16(textData);
164
165 // Is it speech or subtitles, or both?
166
167 // If we want subtitles, or there was no sound
168
169 if (_vm->getSubtitles() || !text->_speechId) {
170 text->_textMem = _vm->_fontRenderer->makeTextSprite(textData + 2, 600, 255, _vm->_speechFontId, 1);
171 }
172
173 _vm->_resman->closeResource(res);
174
175 if (text->_textMem) {
176 FrameHeader frame;
177
178 frame.read(text->_textMem);
179
180 text->_textSprite.x = 320 - frame.width / 2;
181 text->_textSprite.y = 440 - frame.height;
182 text->_textSprite.w = frame.width;
183 text->_textSprite.h = frame.height;
184 text->_textSprite.type = RDSPR_DISPLAYALIGN | RDSPR_NOCOMPRESSION;
185 text->_textSprite.data = text->_textMem + FrameHeader::size();
186 text->_textSprite.isText = true;
187 _vm->_screen->createSurface(&text->_textSprite, &_textSurface);
188
189 _textX = 320 - text->_textSprite.w / 2;
190 _textY = 420 - text->_textSprite.h;
191 }
192 }
193
closeTextObject(uint32 index,Graphics::Surface * screen,uint16 pitch)194 void MoviePlayer::closeTextObject(uint32 index, Graphics::Surface *screen, uint16 pitch) {
195 if (index < _numMovieTexts) {
196 MovieText *text = &_movieTexts[index];
197
198 free(text->_textMem);
199 text->_textMem = NULL;
200
201 if (_textSurface) {
202 if (screen) {
203 // If the frame doesn't cover the entire
204 // screen, we have to erase the subtitles
205 // manually.
206
207 int frameWidth = _decoder->getWidth();
208 int frameHeight = _decoder->getHeight();
209
210 if (_decoderType == kVideoDecoderPSX)
211 frameHeight *= 2;
212
213 int frameX = (_system->getWidth() - frameWidth) / 2;
214 int frameY = (_system->getHeight() - frameHeight) / 2;
215 uint32 black = getBlackColor();
216
217 for (int y = 0; y < text->_textSprite.h; y++) {
218 if (_textY + y < frameY || _textY + y >= frameY + frameHeight) {
219 screen->hLine(_textX, _textY + y, _textX + text->_textSprite.w, black);
220 } else {
221 if (frameX > _textX)
222 screen->hLine(_textX, _textY + y, frameX, black);
223 if (frameX + frameWidth < _textX + text->_textSprite.w)
224 screen->hLine(frameX + frameWidth, _textY + y, text->_textSprite.w, black);
225 }
226 }
227 }
228
229 _vm->_screen->deleteSurface(_textSurface);
230 _textSurface = NULL;
231 }
232 }
233 }
234
235 #define PUT_PIXEL(c) \
236 switch (screen->format.bytesPerPixel) { \
237 case 1: \
238 *dst = (c); \
239 break; \
240 case 2: \
241 WRITE_UINT16(dst, (c)); \
242 break; \
243 case 4: \
244 WRITE_UINT32(dst, (c)); \
245 break; \
246 }
247
drawTextObject(uint32 index,Graphics::Surface * screen,uint16 pitch)248 void MoviePlayer::drawTextObject(uint32 index, Graphics::Surface *screen, uint16 pitch) {
249 MovieText *text = &_movieTexts[index];
250
251 uint32 white = getWhiteColor();
252 uint32 black = getBlackColor();
253
254 if (text->_textMem && _textSurface) {
255 byte *src = text->_textSprite.data;
256 uint16 width = text->_textSprite.w;
257 uint16 height = text->_textSprite.h;
258
259 // Resize text sprites for PSX version
260 byte *psxSpriteBuffer = 0;
261 if (Sword2Engine::isPsx()) {
262 height *= 2;
263 psxSpriteBuffer = (byte *)malloc(width * height);
264 Screen::resizePsxSprite(psxSpriteBuffer, src, width, height);
265 src = psxSpriteBuffer;
266 }
267
268 for (int y = 0; y < height; y++) {
269 byte *dst = (byte *)screen->getBasePtr(_textX, _textY + y);
270
271 for (int x = 0; x < width; x++) {
272 if (src[x] == 1) {
273 PUT_PIXEL(black);
274 } else if (src[x] == 255) {
275 PUT_PIXEL(white);
276 }
277
278 dst += screen->format.bytesPerPixel;
279 }
280
281 src += width;
282 }
283
284 // Free buffer used to resize psx sprite
285 if (Sword2Engine::isPsx())
286 free(psxSpriteBuffer);
287 }
288 }
289
290 #undef PUT_PIXEL
291
performPostProcessing(Graphics::Surface * screen,uint16 pitch)292 void MoviePlayer::performPostProcessing(Graphics::Surface *screen, uint16 pitch) {
293 MovieText *text;
294 int frame = _decoder->getCurFrame();
295
296 if (_currentMovieText < _numMovieTexts) {
297 text = &_movieTexts[_currentMovieText];
298 } else {
299 text = NULL;
300 }
301
302 if (text && frame == text->_startFrame) {
303 if ((_vm->getSubtitles() || !text->_speechId) && _currentMovieText < _numMovieTexts) {
304 openTextObject(_currentMovieText);
305 }
306 }
307
308 if (text && frame >= text->_startFrame) {
309 if (text->_speechId && !text->_played && _vm->_sound->amISpeaking() == RDSE_QUIET) {
310 text->_played = true;
311 _vm->_sound->playCompSpeech(text->_speechId, 16, 0);
312 }
313 if (frame < text->_endFrame) {
314 drawTextObject(_currentMovieText, screen, pitch);
315 } else {
316 closeTextObject(_currentMovieText, screen, pitch);
317 _currentMovieText++;
318 }
319 }
320
321 if (_leadOut && _decoder->getCurFrame() == _leadOutFrame) {
322 _vm->_sound->playMovieSound(_leadOut, kLeadOutSound);
323 }
324 }
325
playVideo()326 bool MoviePlayer::playVideo() {
327 uint16 x = (g_system->getWidth() - _decoder->getWidth()) / 2;
328 uint16 y = (g_system->getHeight() - _decoder->getHeight()) / 2;
329
330 while (!_vm->shouldQuit() && !_decoder->endOfVideo()) {
331 if (_decoder->needsUpdate()) {
332 const Graphics::Surface *frame = _decoder->decodeNextFrame();
333 if (frame) {
334 if (_decoderType == kVideoDecoderPSX)
335 drawFramePSX(frame);
336 else
337 _vm->_system->copyRectToScreen(frame->getPixels(), frame->pitch, x, y, frame->w, frame->h);
338 }
339
340 if (_decoder->hasDirtyPalette()) {
341 _vm->_system->getPaletteManager()->setPalette(_decoder->getPalette(), 0, 256);
342
343 uint32 maxWeight = 0;
344 uint32 minWeight = 0xFFFFFFFF;
345 uint32 weight;
346 byte r, g, b;
347
348 const byte *palette = _decoder->getPalette();
349
350 for (int i = 0; i < 256; i++) {
351 r = *palette++;
352 g = *palette++;
353 b = *palette++;
354
355 weight = 3 * r * r + 6 * g * g + 2 * b * b;
356
357 if (weight >= maxWeight) {
358 maxWeight = weight;
359 _white = i;
360 }
361
362 if (weight <= minWeight) {
363 minWeight = weight;
364 _black = i;
365 }
366 }
367 }
368
369 Graphics::Surface *screen = _vm->_system->lockScreen();
370 performPostProcessing(screen, screen->pitch);
371 _vm->_system->unlockScreen();
372 _vm->_system->updateScreen();
373 }
374
375 Common::Event event;
376 while (_vm->_system->getEventManager()->pollEvent(event))
377 if ((event.type == Common::EVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_ESCAPE) || event.type == Common::EVENT_LBUTTONUP)
378 return false;
379
380 _vm->_system->delayMillis(10);
381 }
382
383 return !_vm->shouldQuit();
384 }
385
getBlackColor()386 uint32 MoviePlayer::getBlackColor() {
387 return (_decoderType == kVideoDecoderPSX || _decoderType == kVideoDecoderMP2) ? g_system->getScreenFormat().RGBToColor(0x00, 0x00, 0x00) : _black;
388 }
389
getWhiteColor()390 uint32 MoviePlayer::getWhiteColor() {
391 return (_decoderType == kVideoDecoderPSX || _decoderType == kVideoDecoderMP2) ? g_system->getScreenFormat().RGBToColor(0xFF, 0xFF, 0xFF) : _white;
392 }
393
drawFramePSX(const Graphics::Surface * frame)394 void MoviePlayer::drawFramePSX(const Graphics::Surface *frame) {
395 // The PSX videos have half resolution
396
397 Graphics::Surface scaledFrame;
398 scaledFrame.create(frame->w, frame->h * 2, frame->format);
399
400 for (int y = 0; y < scaledFrame.h; y++)
401 memcpy(scaledFrame.getBasePtr(0, y), frame->getBasePtr(0, y / 2), scaledFrame.w * scaledFrame.format.bytesPerPixel);
402
403 uint16 x = (g_system->getWidth() - scaledFrame.w) / 2;
404 uint16 y = (g_system->getHeight() - scaledFrame.h) / 2;
405
406 _vm->_system->copyRectToScreen(scaledFrame.getPixels(), scaledFrame.pitch, x, y, scaledFrame.w, scaledFrame.h);
407
408 scaledFrame.free();
409 }
410
411 ///////////////////////////////////////////////////////////////////////////////
412 // Factory function for creating the appropriate cutscene player
413 ///////////////////////////////////////////////////////////////////////////////
414
makeMoviePlayer(const char * name,Sword2Engine * vm,OSystem * system,uint32 frameCount)415 MoviePlayer *makeMoviePlayer(const char *name, Sword2Engine *vm, OSystem *system, uint32 frameCount) {
416 Common::String filename;
417
418 filename = Common::String::format("%s.str", name);
419
420 if (Common::File::exists(filename)) {
421 #ifdef USE_RGB_COLOR
422 Video::VideoDecoder *psxDecoder = new Video::PSXStreamDecoder(Video::PSXStreamDecoder::kCD2x, frameCount);
423 return new MoviePlayer(vm, system, psxDecoder, kVideoDecoderPSX);
424 #else
425 GUI::MessageDialog dialog(_("PSX cutscenes found but ScummVM has been built without RGB color support"), _("OK"));
426 dialog.runModal();
427 return NULL;
428 #endif
429 }
430
431 filename = Common::String::format("%s.smk", name);
432
433 if (Common::File::exists(filename)) {
434 Video::SmackerDecoder *smkDecoder = new Video::SmackerDecoder();
435 return new MoviePlayer(vm, system, smkDecoder, kVideoDecoderSMK);
436 }
437
438 filename = Common::String::format("%s.dxa", name);
439
440 if (Common::File::exists(filename)) {
441 #ifdef USE_ZLIB
442 Video::DXADecoder *dxaDecoder = new Video::DXADecoder();
443 return new MoviePlayer(vm, system, dxaDecoder, kVideoDecoderDXA);
444 #else
445 GUI::MessageDialog dialog(_("DXA cutscenes found but ScummVM has been built without zlib"), _("OK"));
446 dialog.runModal();
447 return NULL;
448 #endif
449 }
450
451 // Old MPEG2 cutscenes
452 filename = Common::String::format("%s.mp2", name);
453
454 if (Common::File::exists(filename)) {
455 #ifdef USE_MPEG2
456 // HACK: Old ScummVM builds ignored the AVI frame rate field and forced the video
457 // to be played back at 12fps.
458 Video::AVIDecoder *aviDecoder = new Video::AVIDecoder(12);
459 return new MoviePlayer(vm, system, aviDecoder, kVideoDecoderMP2);
460 #else
461 GUI::MessageDialog dialog(_("MPEG-2 cutscenes found but ScummVM has been built without MPEG-2 support"), _("OK"));
462 dialog.runModal();
463 return NULL;
464 #endif
465 }
466
467 // The demo tries to play some cutscenes that aren't there, so make those warnings more discreet.
468 // In addition, some of the later re-releases of the game don't have the "eye" Virgin logo movie.
469 if (!vm->_logic->readVar(DEMO) && strcmp(name, "eye") != 0) {
470 Common::String buf = Common::String::format(_("Cutscene '%s' not found"), name);
471 GUI::MessageDialog dialog(buf, _("OK"));
472 dialog.runModal();
473 } else
474 warning("Cutscene '%s' not found", name);
475
476 return NULL;
477 }
478
479 } // End of namespace Sword2
480