From a86625700fe69ff27e0f704a41307cdd2135a6a8 Mon Sep 17 00:00:00 2001 From: Thanasis Antoniou Date: Mon, 18 Jun 2018 14:10:00 +0300 Subject: BLADERUNNER: Added subtitles support and checkbox in KIA --- engines/bladerunner/actor.cpp | 17 +- engines/bladerunner/bladerunner.cpp | 35 +- engines/bladerunner/bladerunner.h | 18 + engines/bladerunner/font.cpp | 87 +++ engines/bladerunner/font.h | 12 + engines/bladerunner/game_constants.h | 8 + engines/bladerunner/module.mk | 1 + engines/bladerunner/outtake.cpp | 18 +- engines/bladerunner/subtitles.cpp | 778 ++++++++++++++++++++++++ engines/bladerunner/subtitles.h | 128 ++++ engines/bladerunner/text_resource.cpp | 62 +- engines/bladerunner/text_resource.h | 11 + engines/bladerunner/ui/elevator.cpp | 6 + engines/bladerunner/ui/esper.cpp | 7 +- engines/bladerunner/ui/kia.cpp | 7 + engines/bladerunner/ui/kia_section_settings.cpp | 50 +- engines/bladerunner/ui/kia_section_settings.h | 6 + engines/bladerunner/ui/vk.cpp | 7 + 18 files changed, 1250 insertions(+), 8 deletions(-) create mode 100644 engines/bladerunner/subtitles.cpp create mode 100644 engines/bladerunner/subtitles.h (limited to 'engines') diff --git a/engines/bladerunner/actor.cpp b/engines/bladerunner/actor.cpp index 45d6d08277..062fc0f37c 100644 --- a/engines/bladerunner/actor.cpp +++ b/engines/bladerunner/actor.cpp @@ -41,6 +41,9 @@ #include "bladerunner/slice_animations.h" #include "bladerunner/slice_renderer.h" #include "bladerunner/time.h" +#if SUBTITLES_SUPPORT +#include "bladerunner/subtitles.h" +#endif // SUBTITLES_SUPPORT #include "bladerunner/waypoints.h" #include "bladerunner/zbuffer.h" @@ -552,7 +555,11 @@ bool Actor::tick(bool forceDraw, Common::Rect *screenRect) { needsUpdate = true; timeLeft = 0; } - +#if SUBTITLES_SUPPORT + if(!isSpeeching()) { + _vm->_subtitles->hide(); + } +#endif // SUBTITLES_SUPPORT if (needsUpdate) { int newAnimation = 0, newFrame = 0; _vm->_aiScripts->updateAnimation(_id, &newAnimation, &newFrame); @@ -1092,10 +1099,18 @@ void Actor::speechPlay(int sentenceId, bool voiceOver) { balance = CLIP(balance, -127, 127); } +#if SUBTITLES_SUPPORT + _vm->_subtitles->getInGameSubsText(_id, sentenceId); + _vm->_subtitles->show(); +#endif // SUBTITLES_SUPPORT + _vm->_audioSpeech->playSpeech(name, balance); } void Actor::speechStop() { +#if SUBTITLES_SUPPORT + _vm->_subtitles->hide(); +#endif // SUBTITLES_SUPPORT _vm->_audioSpeech->stopSpeech(); } diff --git a/engines/bladerunner/bladerunner.cpp b/engines/bladerunner/bladerunner.cpp index 5a00863ceb..52501aa651 100644 --- a/engines/bladerunner/bladerunner.cpp +++ b/engines/bladerunner/bladerunner.cpp @@ -60,6 +60,9 @@ #include "bladerunner/shape.h" #include "bladerunner/slice_animations.h" #include "bladerunner/slice_renderer.h" +#if SUBTITLES_SUPPORT +#include "bladerunner/subtitles.h" +#endif // SUBTITLES_SUPPORT #include "bladerunner/suspects_database.h" #include "bladerunner/text_resource.h" #include "bladerunner/time.h" @@ -181,6 +184,9 @@ BladeRunnerEngine::BladeRunnerEngine(OSystem *syst, const ADGameDescription *des _scores = nullptr; _elevator = nullptr; _mainFont = nullptr; + #if SUBTITLES_SUPPORT + _subtitles = nullptr; + #endif // SUBTITLES_SUPPORT _esper = nullptr; _vk = nullptr; _policeMaze = nullptr; @@ -400,6 +406,20 @@ bool BladeRunnerEngine::startup(bool hasSavegames) { _gameFlags = new GameFlags(); _gameFlags->setFlagCount(_gameInfo->getFlagCount()); + + #if BLADERUNNER_RESTORED_CONTENT_GAME + // EDS flags + _extraGameFlagsForRestoredContent = new GameFlags(); //aux flags - custom + _extraGameFlagsForRestoredContent->setFlagCount(RESTORED_CONTENT_EXTRA_FLAGS + 1); // aux flags - custom // +1 since we don't assign something to 0 enum value + #if SUBTITLES_SUPPORT + #if SUBTITLES_ENABLED_BY_DEFAULT + // subtitles enable by default: + _extraGameFlagsForRestoredContent->set(kEDSFlagSubtitlesEnable); + #else + _extraGameFlagsForRestoredContent->reset(kEDSFlagSubtitlesEnable); + #endif // SUBTITLES_ENABLED_BY_DEFAULT + #endif // SUBTITLES_SUPPORT + #endif // BLADERUNNER_RESTORED_CONTENT_GAME _items = new Items(this); @@ -495,6 +515,10 @@ bool BladeRunnerEngine::startup(bool hasSavegames) { _mainFont = new Font(this); _mainFont->open("KIA6PT.FON", 640, 480, -1, 0, 0x252D); _mainFont->setSpacing(1, 0); + +#if SUBTITLES_SUPPORT + _subtitles = new Subtitles(this); +#endif // SUBTITLES_SUPPORT for (int i = 0; i != 43; ++i) { Shape *shape = new Shape(this); @@ -668,6 +692,13 @@ void BladeRunnerEngine::shutdown() { _mainFont = nullptr; } +#if SUBTITLES_SUPPORT + if(_subtitles) { + delete _subtitles; + _subtitles = nullptr; + } +#endif // SUBTITLES_SUPPORT// + delete _items; _items = nullptr; @@ -937,7 +968,9 @@ void BladeRunnerEngine::gameTick() { if (_debugger->_viewObstacles) { _obstacles->draw(); } - + #if SUBTITLES_SUPPORT + _subtitles->tick(_surfaceFront); + #endif // SUBTITLES_SUPPORT blitToScreen(_surfaceFront); _system->delayMillis(10); } diff --git a/engines/bladerunner/bladerunner.h b/engines/bladerunner/bladerunner.h index f056a669d9..5c3cc88b9d 100644 --- a/engines/bladerunner/bladerunner.h +++ b/engines/bladerunner/bladerunner.h @@ -39,6 +39,14 @@ #define BLADERUNNER_DEBUG_CONSOLE 0 #define BLADERUNNER_DEBUG_GAME 0 +#define SUBTITLES_SUPPORT 1 +#if SUBTITLES_SUPPORT +#define SUBTITLES_EXTERNAL_FONT 1 +#define SUBTITLES_ENABLED_BY_DEFAULT 1 +#endif // SUBTITLES_SUPPORT +#define BLADERUNNER_RESTORED_CONTENT_GAME 1 // needed for checkbox setting for subtitles enable/disable +#define RESTORED_CONTENT_EXTRA_FLAGS 1 // needed for checkbox setting for subtitles enable/disable + namespace Common { struct Event; } @@ -88,6 +96,9 @@ class Shape; class SliceAnimations; class SliceRenderer; class Spinner; +#if SUBTITLES_SUPPORT +class Subtitles; +#endif class SuspectsDatabase; class TextResource; class Time; @@ -128,12 +139,19 @@ public: EndCredits *_endCredits; ESPER *_esper; GameFlags *_gameFlags; + #if BLADERUNNER_RESTORED_CONTENT_GAME + // EDS flags + GameFlags *_extraGameFlagsForRestoredContent; + #endif // BLADERUNNER_RESTORED_CONTENT_GAME GameInfo *_gameInfo; ItemPickup *_itemPickup; Items *_items; KIA *_kia; Lights *_lights; Font *_mainFont; + #if SUBTITLES_SUPPORT + Subtitles *_subtitles; + #endif // SUBTITLES_SUPPORT Mouse *_mouse; Music *_music; Obstacles *_obstacles; diff --git a/engines/bladerunner/font.cpp b/engines/bladerunner/font.cpp index d4b0e16515..e3a30aa0d4 100644 --- a/engines/bladerunner/font.cpp +++ b/engines/bladerunner/font.cpp @@ -37,6 +37,48 @@ Font::~Font() { close(); } +#if SUBTITLES_SUPPORT +#if SUBTITLES_EXTERNAL_FONT +// for external FON font file / subtitles support +bool Font::openFromStream(Common::ScopedPtr &stream, int screenWidth, int screenHeight, int spacing1, int spacing2, uint16 color) { + reset(); + + _screenWidth = screenWidth; + _screenHeight = screenHeight; + _spacing1 = spacing1; + _spacing2 = spacing2; + _color = color; + + if (!stream) { + return false; + } + _characterCount = stream->readUint32LE(); + debug("Font's character count: %d", _characterCount); + _maxWidth = stream->readUint32LE(); + _maxHeight = stream->readUint32LE(); + _dataSize = stream->readUint32LE(); + _data = new uint16[_dataSize]; + if (!_data) { + debug("Font::open failed to allocate font buffer"); + return false; + } + + for (int i = 0; i < _characterCount; i++) { + _characters[i].x = stream->readUint32LE(); + _characters[i].y = stream->readUint32LE(); + _characters[i].width = stream->readUint32LE(); + _characters[i].height = stream->readUint32LE(); + _characters[i].dataOffset = stream->readUint32LE(); + debug("char::%d character x: %d, y: %d, w: %d, h:%d, do: %d", i, _characters[i].x, _characters[i].y, _characters[i].width, _characters[i].height, _characters[i].dataOffset); + } + for (int i = 0; i < _dataSize; i++) { + _data[i] = stream->readUint16LE(); + } + return true; +} +#endif // SUBTITLES_EXTERNAL_FONT +#endif // SUBTITLES_SUPPORT + bool Font::open(const Common::String &fileName, int screenWidth, int screenHeight, int spacing1, int spacing2, uint16 color) { reset(); @@ -68,6 +110,28 @@ bool Font::open(const Common::String &fileName, int screenWidth, int screenHeigh _characters[i].width = stream->readUint32LE(); _characters[i].height = stream->readUint32LE(); _characters[i].dataOffset = stream->readUint32LE(); + #if SUBTITLES_SUPPORT + #if !SUBTITLES_EXTERNAL_FONT + // special explicit alignment fixes for TAHOMA18 (INTERNAL) font + if(fileName == "TAHOMA18.FON") { + // fix P -> i = 81 (ascii code 80 + 1) + if(i == 81 || i == 72 || i == 74 || i == 75 // P, G, I, J + || i == 46 // '-' + ) + { + _characters[i].x = 0;// from 1 + } + if(i == 81 // P + || i == 83 || i == 84 // R, S, + || i == 86 // U + || i == 87 || i == 88 || i == 89 || i == 90 || i == 91 // V, W, X, Y ,Z + ) { + _characters[i].y = 7;// from 6 // bring down a pixel + } + } + //debug("char::%d character x: %d, y: %d, w: %d, h:%d, do: %d", i, _characters[i].x, _characters[i].y, _characters[i].width, _characters[i].height, _characters[i].dataOffset); + #endif // SUBTITLES_EXTERNAL_FONT + #endif // SUBTITLES_SUPPORT } for (int i = 0; i < _dataSize; i++) { _data[i] = stream->readUint16LE(); @@ -176,6 +240,21 @@ void Font::replaceColor(uint16 oldColor, uint16 newColor) { } } +#if SUBTITLES_SUPPORT +void Font::setBlackColor() { + // to create a font that can be used as a shadow effect + if (!_data || !_dataSize) { + return; + } + for (int i = 0; i < _dataSize; i++) { + //debug("COLOR EXISTING: %d", _data[i]); + if(_data[i] != 32768) { // 0x8000 transparent + _data[i] = 0x0000; // black + } + } +} +#endif // SUBTITLES_SUPPORT + void Font::drawCharacter(const uint8 character, Graphics::Surface &surface, int x, int y) const { uint8 characterIndex = character + 1; if (x < 0 || x >= _screenWidth || y < 0 || y >= _screenHeight || !_data || characterIndex >= _characterCount) { @@ -192,6 +271,14 @@ void Font::drawCharacter(const uint8 character, Graphics::Surface &surface, int int endY = height + y - 1; int currentY = y; + +#if BLADERUNNER_RESTORED_CONTENT_GAME + // Temp Bug fix - Return if w h unnaturally big: -- the INTERNAL tahoma18 font is corrupted so it could cause crashes + if(width > 100 || height > 100) { + return; + } +#endif + while (currentY <= endY && currentY < _screenHeight) { int currentX = x; int endX = width + x - 1; diff --git a/engines/bladerunner/font.h b/engines/bladerunner/font.h index 4af25468c6..4d43aaa875 100644 --- a/engines/bladerunner/font.h +++ b/engines/bladerunner/font.h @@ -23,6 +23,10 @@ #ifndef BLADERUNNER_FONT_H #define BLADERUNNER_FONT_H +#include "bladerunner/bladerunner.h" // needed for definition of Common::ScopedPtr (subtitles font external font file support) -- and for the subtitles relevant macro defines +#if SUBTITLES_SUPPORT +#include "common/util.h" +#endif #include "common/str.h" namespace Graphics { @@ -61,11 +65,19 @@ public: Font(BladeRunnerEngine *vm); ~Font(); + #if SUBTITLES_SUPPORT + #if SUBTITLES_EXTERNAL_FONT + bool openFromStream(Common::ScopedPtr &s, int screenWidth, int screenHeight, int spacing1, int spacing2, uint16 color); + #endif // SUBTITLES_EXTERNAL_FONT + #endif // SUBTITLES_SUPPORT bool open(const Common::String &fileName, int screenWidth, int screenHeight, int spacing1, int spacing2, uint16 color); void close(); void setSpacing(int spacing1, int spacing2); void setColor(uint16 color); + #if SUBTITLES_SUPPORT + void setBlackColor(); // for subtitles + #endif void draw(const Common::String &text, Graphics::Surface &surface, int x, int y) const; void drawColor(const Common::String &text, Graphics::Surface &surface, int x, int y, uint16 color); diff --git a/engines/bladerunner/game_constants.h b/engines/bladerunner/game_constants.h index 0a9082a60a..280bcc8c92 100644 --- a/engines/bladerunner/game_constants.h +++ b/engines/bladerunner/game_constants.h @@ -547,6 +547,14 @@ enum Variables { kVariableNextTvNews = 52 }; +#if BLADERUNNER_RESTORED_CONTENT_GAME +// Aux enum from added/ restored content +// EDS Flags +enum _extraGameFlagsForRestoredContent { + kEDSFlagSubtitlesEnable = 1 +}; +#endif // BLADERUNNER_RESTORED_CONTENT_GAME + enum Outtakes { kOuttakeIntro = 0, kOuttakeMovieA = 1, diff --git a/engines/bladerunner/module.mk b/engines/bladerunner/module.mk index 243f426a44..7f800e5354 100644 --- a/engines/bladerunner/module.mk +++ b/engines/bladerunner/module.mk @@ -244,6 +244,7 @@ MODULE_OBJS = \ shape.o \ slice_animations.o \ slice_renderer.o \ + subtitles.o \ suspects_database.o \ text_resource.o \ time.o \ diff --git a/engines/bladerunner/outtake.cpp b/engines/bladerunner/outtake.cpp index 187f46c0bd..d52646b488 100644 --- a/engines/bladerunner/outtake.cpp +++ b/engines/bladerunner/outtake.cpp @@ -23,6 +23,9 @@ #include "bladerunner/outtake.h" #include "bladerunner/bladerunner.h" +#if SUBTITLES_SUPPORT +#include "bladerunner/subtitles.h" +#endif // SUBTITLES_SUPPORT #include "bladerunner/vqa_player.h" #include "common/debug.h" @@ -43,8 +46,12 @@ void OuttakePlayer::play(const Common::String &name, bool noLocalization, int co } resName = resName + ".VQA"; - - VQAPlayer vqa_player(_vm, &_vm->_surfaceFront, resName); + +#if SUBTITLES_SUPPORT + VQAPlayer vqa_player(_vm, &_vm->_surfaceBack); // fix for subtitles rendering properly +#else + VQAPlayer vqa_player(_vm, &_vm->_surfaceFront); // original +#endif // SUBTITLES_SUPPORT vqa_player.open(); @@ -56,10 +63,17 @@ void OuttakePlayer::play(const Common::String &name, bool noLocalization, int co return; int frame = vqa_player.update(); + #if SUBTITLES_SUPPORT + blit(_vm->_surfaceBack, _vm->_surfaceFront); // new tha hack - helps to make subtitles disappear if the proper video is rendered in surface back and then pushed to the front surface + #endif // SUBTITLES_SUPPORT if (frame == -3) break; if (frame >= 0) { + #if SUBTITLES_SUPPORT + _vm->_subtitles->getOuttakeSubsText(resName + ".TRE" , frame); + _vm->_subtitles->tickOuttakes(_vm->_surfaceFront); + #endif // SUBTITLES_SUPPORT _vm->blitToScreen(_vm->_surfaceFront); } diff --git a/engines/bladerunner/subtitles.cpp b/engines/bladerunner/subtitles.cpp new file mode 100644 index 0000000000..b66360f81f --- /dev/null +++ b/engines/bladerunner/subtitles.cpp @@ -0,0 +1,778 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "bladerunner/subtitles.h" + +#if SUBTITLES_SUPPORT + +#include "bladerunner/bladerunner.h" +#include "bladerunner/font.h" +#include "bladerunner/text_resource.h" +#include "bladerunner/audio_speech.h" +//#include "bladerunner/script/scene_script.h" // for Game_Flag_Query declaration (actually script.h, but this seems to be included in other source files instead) +#include "bladerunner/game_flags.h" // for Game_Flag_Query declaration (actually script.h, but this seems to be included in other source files instead) +#include "bladerunner/game_constants.h" // for EDS flags - for subtitle checkbox flag state +#include "common/debug.h" +#include "common/util.h" + +namespace BladeRunner { + +/* + * Optional support for subtitles + * CHECK what happens in VQA where the audio plays separately (are the finales such VQAs ?) + * + * TODO in python script (FON from png glyphs) check if you can have semi-transparent pixels to better outline the fringe points of the glyphs - check what happens when MSB is set (transparency) and the rest of the color value is not all 0s. + * TODO Catch error for bad symbol in a quote (one that causes the font to crash) - this could happen with the corrupted internal font (TAHOMA18) -> font crash or bad font display / garbage character + * TODO add a keyboard shortcut key to enable / disable subtitles? + * TODO have a debug script to detect/report problematic lines (too long) + * + * TODO? put external FON and TRE in a new folder "SUBS" - case insensitive (?) + * TODO? Use another escape sequence to progressively display text in a line (like in SCUMM games) <-- this could be very useful with very long lines - might also need an extra manual time or ticks parameter to determine when during the display of the first segment we should switch to the second. + * TODO? A more advanced subtitles system + * TODO: subtitles could be independent from sound playing (but should disappear when switching between UI screens) + * TODO?: Support for queuing subtitles when more than one subtitle should play for a spoken dialogue (due to a very long quote) + * TODO?: Predefine a minimum time for a subtitle to appear, before it is interrupted by the next one. (might need queuing) + * TODO?: If the subtitle is the last one then extend its duration to another predefined delay. + * TODO?: A system to auto-split a dialogue after some max characters per both lines to a new dialogue set (delete previous 2 lines, start a new one(s) with the rest of the quote). + * + * DONE Minor fixes In internal font TAHOMA18 some letters like 'P' and 'o' and not rightly aligned in the font. also not good spacing with '-' and a few other chars + * Also seems that this particular font is corrupted! + * Create and Support proper external FON for subtitles. + * DONE split at new line character (priority over auto-split) + * DONE auto-split a long line into two + * DONE support the basic 2 line subtitles + * DONE support a third line for subtitles (some quotes are too long for 2 lines). Are there quotes that are too long for 3 lines? + * DONE handle missing subtitle files! Gracefully don't show subtitles for VQAs or in-game dialogue if the required respective files are missing! + * DONE add subtitle files for the rest of VQAs that have spoken dialogue + * DONE A system to auto-split a dialogue after some max total width of character glyphs per line. + * DONE - OK - CHECK What happens with skipped dialogue (enter / skip dialogue key pressed) + * DONE - OK - CHECK what happens in VQA when no corresponding TRE subs file? + */ + +#if SUBTITLES_EXTERNAL_FONT +const Common::String Subtitles::SUBTITLES_FONT_FILENAME = "SUBTITLES.FON"; +#else +const Common::String Subtitles::SUBTITLES_FONT_FILENAME = "TAHOMA18.FON"; +#endif + +/* +* All entries need to have the language code appended (after a '_'). +* The outtakes then need a substring ".VQA" +* And all entries should have the suffix extension ".TRE" +* If/When adding new TRE resources here --> Update kMaxTextResourceEntries and also update method getIdxForSubsTreName() +*/ +const Common::String Subtitles::SUBTITLES_FILENAME_PREFIXES[kMaxTextResourceEntries] = { + "outQuotes", // 0 // (in-game subtitles, not VQA subtitles) + "WSTLGO", // 1 + "BRLOGO", // 2 + "INTRO", // 3 + "MW_A", // 4 + "MW_B01", // 5 + "MW_B02", // 6 + "MW_B03", // 7 + "MW_B04", // 8 + "MW_B05", // 9 + "INTRGT", // 10 + "MW_C01", // 11 + "MW_C02", // 12 + "MW_C03", // 13 + "MW_D", // 14 + "END04A", // 15 + "END04B", // 16 + "END04C", // 17 + "END06", // 18 + "END07", // 19 + "END01A", // 20 + "END01B", // 21 + "END01C", // 22 + "END01D", // 23 + "END01E", // 24 + "END01F", // 25 + "END03" // 26 +}; + +/** +* Subtitles Constructor +*/ +Subtitles::Subtitles(BladeRunnerEngine *vm) { + _vm = vm; + // Initializing and reseting Subtitles + for (int i = 0; i < kMaxTextResourceEntries; i++) { + _gameSubsFdEntries[i] = nullptr; + _vqaSubsTextResourceEntries[i] = nullptr; + } +#if SUBTITLES_EXTERNAL_FONT + _gameSubsFontsFd = nullptr; + _subsFont = nullptr; +#else + _subsFont = nullptr; + _subsBgFont = nullptr; +#endif // SUBTITLES_EXTERNAL_FONT + reset(); + // Done - Subtitles Reset + // + // Loading text resources + for (int i = 0; i < kMaxTextResourceEntries; i++) { + _gameSubsFdEntries[i] = new Common::File(); + _vqaSubsTextResourceEntries[i] = new TextResource(_vm); + Common::String tmpConstructedFileName = ""; + tmpConstructedFileName = SUBTITLES_FILENAME_PREFIXES[i] + "_" + _vm->_languageCode; + if (i > 0) { + tmpConstructedFileName += ".VQA"; + } + tmpConstructedFileName += ".TRE"; + if (openGameSubs(tmpConstructedFileName) && loadGameSubsText(i)) { + _gameSubsFdEntriesFound[i] = true; + } + } + // Done - Loading text resources + // + // Initializing/Loading Subtitles' Fonts +#if SUBTITLES_EXTERNAL_FONT + // Open external fonts file (FON file) and load fonts + _gameSubsFontsFd = new Common::File(); + _subsFont = new Font(_vm); + if (openSubsFontFile() && loadSubsFont()) { + _subsFontsLoaded = true; + } +#else + _subsFont = new Font(_vm); + // Use TAHOMA18.FON (is corrupted in places) + // 10PT or TAHOMA24 or KIA6PT have all caps glyphs (and also are too big or too small) so they are not appropriate. + if (_subsFont ->open(SUBTITLES_FONT_FILENAME, 640, 480, -1, 0, 0)) { // Color setting does not seem to affect the TAHOMA fonts or does it affect the black outline since we give 0 here? + _subsFont->setSpacing(1, 0); + _subsFont->setWhiteColor(); + _subsFontsLoaded = true; + } else { + _subsFontsLoaded = false; + } + _subsBgFont = new Font(_vm); + if (_subsFontsLoaded && _subsBgFont ->open(SUBTITLES_FONT_FILENAME, 640, 480, -1, 0, 0)) { // TODO dark color? --- color does not seem to affect the TAHOMA fonts or does it affect the black outline since we give 0 here? ?? - we should give the original color here. What is it for TAHOMA? + _subsBgFont ->setSpacing(1, 0); + _subsBgFont ->setBlackColor(); + } else { + _subsFontsLoaded = false; + } +#endif // SUBTITLES_EXTERNAL_FONT + //Done - Initializing/Loading Subtitles' Fonts + // + // calculate the Screen Y position of the subtitle lines + // getTextHeight("") returns the maxHeight of the font glyphs regardless of the actual text parameter + // debug("Max height %d", _subsFont->getTextHeight("")); + if (_subsFontsLoaded) { + for (int i = 0; i < kMaxNumOfSubtitlesLines; ++i) { + _subtitleLineScreenY[i] = 479 - ((kMaxNumOfSubtitlesLines - i) * (_subsFont->getTextHeight("") + 1)); + } + } +} + +/** +* Subtitles Destructor +*/ +Subtitles::~Subtitles() { + // delete any resource entries in the _vqaSubsTextResourceEntries table + // and close any open text resource files + for (int i = 0; i != kMaxTextResourceEntries; ++i) { + if (_vqaSubsTextResourceEntries[i] != nullptr) { + delete _vqaSubsTextResourceEntries[i]; + _vqaSubsTextResourceEntries[i] = nullptr; + } + if (_gameSubsFdEntries[i] != nullptr) { + + if (isOpenGameSubs(i)) { + closeGameSubs(i); + } + delete _gameSubsFdEntries[i]; + _gameSubsFdEntries[i] = nullptr; + } + } +#if SUBTITLES_EXTERNAL_FONT + if (_subsFont != nullptr) { + _subsFont->close(); + delete _subsFont; + _subsFont = nullptr; + } + if (_gameSubsFontsFd != nullptr) { + if (isOpenSubsFontFile()) { + closeSubsFontFile(); + } + delete _gameSubsFontsFd; + _gameSubsFontsFd = nullptr; + } +#else + if (_subsFont != nullptr) { + _subsFont->close(); + delete _subsFont; + _subsFont = nullptr; + } + if (_subsBgFont != nullptr) { + _subsBgFont->close(); + delete _subsBgFont; + _subsBgFont = nullptr; + } +#endif // SUBTITLES_EXTERNAL_FONT +} + +/** +* +* Returns the index of the specified .TRE filename in the SUBTITLES_FILENAME_PREFIXES table +*/ +int Subtitles::getIdxForSubsTreName(const Common::String &treName) const { + Common::String tmpConstructedFileName = ""; + for (int i = 0; i < kMaxTextResourceEntries; ++i) { + tmpConstructedFileName = SUBTITLES_FILENAME_PREFIXES[i] + "_" + _vm->_languageCode; + if (i > 0) { + tmpConstructedFileName += ".VQA"; + } + tmpConstructedFileName += ".TRE"; + if (tmpConstructedFileName == treName) { + return i; + } + } + // error case + return -1; +} + + +/** +* Open an external subtitles File and store its file descriptor +* @return true if successful, false otherwise +*/ +bool Subtitles::openGameSubs(const Common::String &filename) { + uint32 gameSubsEntryCount = 0; + int subTreIdx = getIdxForSubsTreName(filename); + + if (subTreIdx < 0 || _gameSubsFdEntries[subTreIdx] == nullptr) { + debug("Subtitles::open(): Could not open %s", filename.c_str()); + return false; + } +// debug("Now opening subs file: %s", filename.c_str()); + + if (!_gameSubsFdEntries[subTreIdx]->open(filename)) { + debug("Subtitles::open(): Could not open %s", filename.c_str()); + return false; + } + gameSubsEntryCount = _gameSubsFdEntries[subTreIdx]->readUint32LE(); + + if (_gameSubsFdEntries[subTreIdx]->err()) { + error("Subtitles::open(): Error reading entries in %s", filename.c_str()); + _gameSubsFdEntries[subTreIdx]->close(); + return false; + } + debug("Subtitles::open: Opened in-game external subs file %s with %d entries", filename.c_str(), gameSubsEntryCount); + return true; +} + +/** +* Close an open external subtitles File +*/ +void Subtitles::closeGameSubs(int subTreIdx) { + if (subTreIdx < 0 || _gameSubsFdEntries[subTreIdx] == nullptr) { + debug("Subtitles::close(): Could not close file with Idx %d", subTreIdx); + return; + } + return _gameSubsFdEntries[subTreIdx]->close(); +} + +/** +* Check whether an external subtitles File is open +*/ +bool Subtitles::isOpenGameSubs(int subTreIdx) const { + if (subTreIdx < 0 || _gameSubsFdEntries[subTreIdx] == nullptr) { + return false; + } + return _gameSubsFdEntries[subTreIdx]->isOpen(); +} + +/** +* Load the game subs as a TRE resource and store them in a specific entry in _vqaSubsTextResourceEntries table +*/ +bool Subtitles::loadGameSubsText(int subTreIdx) { + bool r = false; + Common::SeekableReadStream *stream = createReadStreamForGameSubs(subTreIdx); + if (stream != nullptr) { + Common::ScopedPtr s(stream); + r = _vqaSubsTextResourceEntries[subTreIdx]->openFromStream(s); + if (!r) { + error("Failed to load subtitle text"); + } + closeGameSubs(subTreIdx); + } + return r; +} + +/** +* Auxiliary method for loadGameSubsText +* @return nullptr if failure, otherwise return a pointer to a new SafeSeekableSubReadStream +*/ +Common::SeekableReadStream *Subtitles::createReadStreamForGameSubs(int subTreIdx) { + if (subTreIdx < 0 || _gameSubsFdEntries[subTreIdx] == nullptr) { + return nullptr; + } + if (!isOpenGameSubs(subTreIdx)) { + return nullptr; + } + return new Common::SafeSeekableSubReadStream(_gameSubsFdEntries[subTreIdx], 0, _gameSubsFdEntries[subTreIdx]->size(), DisposeAfterUse::YES); // TODO changed to YES from NO is this ok? +} + +#if SUBTITLES_EXTERNAL_FONT +// +// EXTERN FONT MANAGEMENT - Font Open/ Create Read Stream / Load / Close methods +// + +/** +* @return true if successfully opened the external fonts (FON) file, false otherwise +*/ +bool Subtitles::openSubsFontFile() { + uint32 subFontsTableEntryCount = 0; +// debug("Now opening subs file: %s", SUBTITLES_FONT_FILENAME.c_str()); + + if (_gameSubsFontsFd == nullptr || !_gameSubsFontsFd->open(SUBTITLES_FONT_FILENAME)) { + debug("Subtitles FONT::open(): Could not open %s", SUBTITLES_FONT_FILENAME.c_str()); + return false; + } + subFontsTableEntryCount = _gameSubsFontsFd->readUint32LE(); // only for debug report purposes + + if (_gameSubsFontsFd->err()) { + error("Subtitles FONT::open(): Error reading entries in %s", SUBTITLES_FONT_FILENAME.c_str()); + _gameSubsFontsFd->close(); + return false; + } + + debug("Subtitles FONT::open: Opened in-game external subs FONT file %s with %d entries", SUBTITLES_FONT_FILENAME.c_str(), subFontsTableEntryCount); + return true; +} + +/** +* Close the external Fonts (FON) file +*/ +void Subtitles::closeSubsFontFile() { + if (_gameSubsFontsFd != nullptr) { + _gameSubsFontsFd->close(); + } +} + +/** +* Checks whether the external fonts (FON) file has been opened +*/ +bool Subtitles::isOpenSubsFontFile() const { + return _gameSubsFontsFd != nullptr && _gameSubsFontsFd->isOpen(); +} + +/** +* Auxiliary function to create a read stream fro the external fonts file +* @return a pointer to the stream if successful, or nullptr otherwise +*/ +Common::SeekableReadStream *Subtitles::createReadStreamForSubFonts() { + if (_gameSubsFontsFd == nullptr || !isOpenSubsFontFile()) { + return nullptr; + } + return new Common::SafeSeekableSubReadStream(_gameSubsFontsFd, 0, _gameSubsFontsFd->size(), DisposeAfterUse::YES); // TODO changed to YES from NO is this ok? +} + +/** +* Loads the font from the external font file +* @return true if successful, or false otherwise +*/ +bool Subtitles::loadSubsFont() { + bool r = false; + Common::SeekableReadStream *stream = createReadStreamForSubFonts(); + if (stream != nullptr) { + Common::ScopedPtr s(stream); + r = _subsFont->openFromStream(s, 640, 480, -1, 0, 0); + + if (!r) { + error("Failed to load subtitle FONT"); + } else { + _subsFont->setSpacing(-1, 0); + } + //_subsFont->setSpacing(0, 0); + closeSubsFontFile(); + } + return r; +} + +// +// END OF EXTERNAL FONT MANAGEMENT +// +#endif // SUBTITLES_EXTERNAL_FONT + +/** +* Get the active subtitle text by searching with actor ID and speech ID +* Use this method for in-game dialogue - Not dialogue during a VQA cutscene +* Returns the dialogue quote, but also sets the private _currentSubtitleTextFull member +*/ +const char *Subtitles::getInGameSubsText(int actorId, int speech_id) { + int32 id = 10000 * actorId + speech_id; + if (!_gameSubsFdEntriesFound[0]) { + if (_currentSubtitleTextFull != "") { + _currentSubtitleTextFull = ""; + _subtitlesQuoteChanged = true; + } + return ""; + } + // Search in the first TextResource of the _vqaSubsTextResourceEntries table, which is the TextResource for in-game dialogue (i.e. not VQA dialogue) + const Common::String &text = _vqaSubsTextResourceEntries[0]->getText((uint32)id); + _currentSubtitleTextFull = Common::String(text); + _subtitlesQuoteChanged = true; + return _currentSubtitleTextFull.c_str(); +} + +/** +* Use this method for dialogue during VQA cutscenes +* Returns the dialogue quote, but also sets the private _currentSubtitleTextFull member +*/ +const char *Subtitles::getOuttakeSubsText(const Common::String &outtakesName, int frame) { + int fileIdx = getIdxForSubsTreName(outtakesName); + if (fileIdx == -1 || !_gameSubsFdEntriesFound[fileIdx]) { + if (_currentSubtitleTextFull != "") { + _currentSubtitleTextFull = ""; + _subtitlesQuoteChanged = true; + } + return ""; + } + // Search in the requested TextResource at the fileIdx index of the _vqaSubsTextResourceEntries table for a quote that corresponds to the specified video frame + // debug("Number of resource quotes to search: %d, requested frame: %u", _vqaSubsTextResourceEntries[fileIdx]->getCount(), (uint32)frame ); + const Common::String &text = _vqaSubsTextResourceEntries[fileIdx]->getOuttakeTextByFrame((uint32)frame); + //if(text != "") { + // debug("Text = %s", text.c_str()); + //} + if (_currentSubtitleTextFull != Common::String(text)) { + _currentSubtitleTextFull = Common::String(text); + _subtitlesQuoteChanged = true; + } + return _currentSubtitleTextFull.c_str(); +} + +/** +* Explicitly set the active subtitle text to be displayed +* Used for debug purposes mainly. +*/ +void Subtitles::setGameSubsText(Common::String dbgQuote) { + if (_currentSubtitleTextFull != dbgQuote) { + _currentSubtitleTextFull = dbgQuote; + _subtitlesQuoteChanged = true; + } +} + +/** +* Sets the _isVisible member var to true if it's not already set +* @return true if the member was set now, false if the member was already set +*/ +bool Subtitles::show() { + + if (_isVisible) { + return false; + } + _isVisible = true; + return true; +} + +/** +* Clears the _isVisible member var if not already clear. +* @return true if the member was cleared, false if it was already clear. +*/ +bool Subtitles::hide() { + if (!_isVisible) { + return false; + } + + _isVisible = false; + return true; +} + +/** +* Checks whether the subtitles should be visible or not +* @return the value of the _isVisible member boolean var +*/ +bool Subtitles::isVisible() const { + return _isVisible; +} + +/** +* Tick method specific for outtakes (VQA videos) +*/ +void Subtitles::tickOuttakes(Graphics::Surface &s) { + if (_currentSubtitleTextFull.empty()) { + _vm->_subtitles->hide(); + } else { + _vm->_subtitles->show(); + } +#if BLADERUNNER_RESTORED_CONTENT_GAME + if (!_vm->_extraGameFlagsForRestoredContent->query(kEDSFlagSubtitlesEnable)) { + return; + } +#endif + if (!_isVisible) { // keep it as a separate if + return; + } + draw(s); +} + +/** +* Tick method for in-game subtitles -- Not for outtake cutscenes (VQA videos) +*/ +void Subtitles::tick(Graphics::Surface &s) { + if (!_vm->_audioSpeech->isPlaying()) { + _vm->_subtitles->hide(); // TODO might need a better system. Don't call it always. + + } +#if BLADERUNNER_RESTORED_CONTENT_GAME + if (!_vm->_extraGameFlagsForRestoredContent->query(kEDSFlagSubtitlesEnable)) { + return; + } +#endif + if (!_isVisible) { // keep it as a separate if + return; + } + draw(s); +} + +/** +* Draw method for drawing the subtitles on the display surface +*/ +void Subtitles::draw(Graphics::Surface &s) { + if (!_isVisible || _currentSubtitleTextFull.empty() || !_subsFontsLoaded) { + return; + } + if (_subtitlesQuoteChanged) { + calculatePosition(); // Don't always call calc position, only when quote has changed + _subtitlesQuoteChanged = false; + } + +#if SUBTITLES_EXTERNAL_FONT + for (int i = 0; i < _currentSubtitleLines; ++i) { + _subsFont->draw(_subtitleLineQuote[i], s, _subtitleLineScreenX[i], _subtitleLineScreenY[i]); + } +#else + // INTERNAL FONT. NEEDS HACK (_subsBgFont) FOR SHADOW EFFECT + for (int i = 0; i < _currentSubtitleLines; ++i) { + _subsBgFont->draw(_subtitleLineQuote[i], s, _subtitleLineScreenX[i], _subtitleLineScreenY[i] - 1); + _subsBgFont->draw(_subtitleLineQuote[i], s, _subtitleLineScreenX[i], _subtitleLineScreenY[i] + 1); + _subsBgFont->draw(_subtitleLineQuote[i], s, _subtitleLineScreenX[i] + 1, _subtitleLineScreenY[i] + 1); + _subsBgFont->draw(_subtitleLineQuote[i], s, _subtitleLineScreenX[i] + 1, _subtitleLineScreenY[i] - 1); + if (_subtitleLineScreenX[i] > 0) { + _subsBgFont->draw(_subtitleLineQuote[i], s, _subtitleLineScreenX[i] - 1, _subtitleLineScreenY[i] - 1); + _subsBgFont->draw(_subtitleLineQuote[i], s, _subtitleLineScreenX[i] - 1, _subtitleLineScreenY[i] + 1); + } + _subsFont->draw(_subtitleLineQuote[i], s, _subtitleLineScreenX[i], _subtitleLineScreenY[i]); + } +#endif // SUBTITLES_EXTERNAL_FONT +} + +/** +* Calculate the position (X axis - horizontal) where the current active subtitle text should be displayed/drawn +* This also determines if more than one lines should be drawn and what text goes into each line; splitting into multiple lines is done here +*/ +void Subtitles::calculatePosition() { + + // wOrig is in pixels, origQuoteLength is num of chars in string + int wOrig = _subsFont->getTextWidth(_currentSubtitleTextFull) + 2; // +2 to account for left/ right shadow pixels (or for good measure) + int origQuoteLength = _currentSubtitleTextFull.size(); + int tmpCharIndex = 0; + bool drawSingleLineQuote = false; + + const uint8 *textCharacters = (const uint8 *)_currentSubtitleTextFull.c_str(); + int tmpLineWidth[kMaxNumOfSubtitlesLines]; + + _currentSubtitleLines = 1; + for (int i = 0; i < kMaxNumOfSubtitlesLines; ++i) { + _subtitleLineSplitAtCharIndex[i] = 0; + _subtitleLineQuote[i] = ""; + _subtitleLineScreenX[i] = 0; + tmpLineWidth[i] = 0; + } + + while (*textCharacters != 0) { + // check for new line explicit split + if (_currentSubtitleLines < kMaxNumOfSubtitlesLines && *textCharacters == 0x0A && tmpCharIndex != 0 && _subtitleLineSplitAtCharIndex[_currentSubtitleLines - 1] == 0) { + _subtitleLineSplitAtCharIndex[_currentSubtitleLines - 1] = tmpCharIndex; + _currentSubtitleLines += 1; + } + tmpCharIndex += 1; + textCharacters += 1; + } + _subtitleLineSplitAtCharIndex[_currentSubtitleLines - 1] = tmpCharIndex; + if (_currentSubtitleLines > 1) { + // if we can split at new line characters: + // + int j = 0; + textCharacters = (const uint8 *)_currentSubtitleTextFull.c_str(); // reset pointer to the start of subtitle quote + for (int i = 0; i < origQuoteLength ; ++i) { + if (j < _currentSubtitleLines && i < _subtitleLineSplitAtCharIndex[j]) { + _subtitleLineQuote[j] += textCharacters[i]; + } else { // i is at split point + _subtitleLineQuote[j] += '\0'; + j += 1; + } + } + _subtitleLineQuote[j] += '\0'; // the last line should also be NULL terminated + // + // Check widths + for (int i = 0; i < _currentSubtitleLines; ++i) { + tmpLineWidth[i] = _subsFont->getTextWidth(_subtitleLineQuote[i]) + 2; + _subtitleLineScreenX[i] = (639 - tmpLineWidth[i]) / 2; + _subtitleLineScreenX[i] = CLIP(_subtitleLineScreenX[i], 0, 639 - tmpLineWidth[i]); + } + } else { + // Here we initially have _currentSubtitleLines == 1 + // Check quote for auto-splitting + // Auto splitting requires space characters in the quote string (which should be ok for the typical cases) + if (wOrig > kMaxWidthPerLineToAutoSplitThresholdPx) { // kMaxWidthPerLineToAutoSplitThresholdPx is a practical chosen threshold for width for auto-splitting quotes purposes + // Start by splitting in two lines. If the new parts are still too lengthy, re-try by splitting in three lines, etc. + for (int linesToSplitInto = 2; linesToSplitInto <= kMaxNumOfSubtitlesLines; ++linesToSplitInto) { + // find the first blank space after the middle + _subtitleLineQuote[0] = ""; + _currentSubtitleLines = 1; + + textCharacters = (const uint8 *)_currentSubtitleTextFull.c_str(); // reset pointer to the start of subtitle quote + textCharacters += (origQuoteLength / linesToSplitInto); + _subtitleLineSplitAtCharIndex[0] = (origQuoteLength / linesToSplitInto); + while (*textCharacters != 0 && *textCharacters != 0x20) { // seek for a blank space character + _subtitleLineSplitAtCharIndex[0] += 1; + textCharacters += 1; + } +// debug("space blank at: %d", _subtitleLineSplitAtCharIndex[0]); + if (*textCharacters == 0x20) { // if we found a blank space + textCharacters = (const uint8 *)_currentSubtitleTextFull.c_str(); + for (int i = 0; i < _subtitleLineSplitAtCharIndex[0] ; ++i) { + _subtitleLineQuote[0] += textCharacters[i]; + } + _subtitleLineQuote[0] += '\0'; +// debug(" Line 0 quote %s", _subtitleLineQuote[0].c_str()); + tmpLineWidth[0] = _subsFont->getTextWidth(_subtitleLineQuote[0]) + 2; // check the width of the first segment of the quote + if (tmpLineWidth[0] > kMaxWidthPerLineToAutoSplitThresholdPx && linesToSplitInto < kMaxNumOfSubtitlesLines) { + // reset process by trying to split into more lines + continue; // try the for loop with increased linesToSplitInto by 1 + } else { + // keep current split, proceed with splitting the quote for the rest of the subtitle lines (linesToSplitInto) + for (int j = 2; j <= linesToSplitInto; ++j) { + textCharacters = (const uint8 *)_currentSubtitleTextFull.c_str(); // reset pointer to the start of subtitle quote + textCharacters += ((j * origQuoteLength) / linesToSplitInto); + _subtitleLineSplitAtCharIndex[_currentSubtitleLines] = ((j * origQuoteLength) / linesToSplitInto); + while (*textCharacters != 0 && *textCharacters != 0x20) { + _subtitleLineSplitAtCharIndex[_currentSubtitleLines] += 1; + textCharacters += 1; + } + textCharacters = (const uint8 *)_currentSubtitleTextFull.c_str(); // reset pointer to the start of subtitle quote + for (int i = _subtitleLineSplitAtCharIndex[_currentSubtitleLines - 1] + 1; i < _subtitleLineSplitAtCharIndex[_currentSubtitleLines]; ++i) { + _subtitleLineQuote[_currentSubtitleLines] += textCharacters[i]; + } + _subtitleLineQuote[_currentSubtitleLines] += '\0'; +// debug(" Line %d, space blank at: %d, quote %s", _currentSubtitleLines, _subtitleLineSplitAtCharIndex[_currentSubtitleLines], _subtitleLineQuote[_currentSubtitleLines].c_str()); + _currentSubtitleLines += 1; + } + // + // Check widths + for (int i = 0; i < _currentSubtitleLines; ++i) { + tmpLineWidth[i] = _subsFont->getTextWidth(_subtitleLineQuote[i]) + 2; + _subtitleLineScreenX[i] = (639 - tmpLineWidth[i]) / 2; + _subtitleLineScreenX[i] = CLIP(_subtitleLineScreenX[i], 0, 639 - tmpLineWidth[i]); + } + break; // from for loop about linesToSplitInto + } + } else { + drawSingleLineQuote = true; + break; // from for loop about linesToSplitInto + } + } + } else { + drawSingleLineQuote = true; + } + if (drawSingleLineQuote) { + _subtitleLineQuote[0] = _currentSubtitleTextFull; + _subtitleLineScreenX[0] = (639 - wOrig) / 2; + _subtitleLineScreenX[0] = CLIP(_subtitleLineScreenX[0], 0, 639 - wOrig); + } + } + //debug("calculatePosition: %d %d", w, _screenFirstLineX); +} + +/** +* Initialize a few basic member vars +*/ +void Subtitles::clear() { + _isVisible = false; + _currentSubtitleTextFull = ""; + for (int i = 0; i < kMaxNumOfSubtitlesLines; ++i) { + _subtitleLineQuote[i] = ""; + _subtitleLineScreenY[i] = 0; + _subtitleLineScreenX[i] = 0; + _subtitleLineSplitAtCharIndex[i] = 0; + } + _subtitlesQuoteChanged = true; + _currentSubtitleLines = 0; +} + +/** +* Initialize/ reset member vars, close open file descriptors and garbage collect subtitle fonts and text resource +*/ +void Subtitles::reset() { + clear(); + + for (int i = 0; i != kMaxTextResourceEntries; ++i) { + if (_vqaSubsTextResourceEntries[i] != nullptr) { + delete _vqaSubsTextResourceEntries[i]; + _vqaSubsTextResourceEntries[i] = nullptr; + } + _gameSubsFdEntriesFound[i] = false; + + if (_gameSubsFdEntries[i] != nullptr) { + if (isOpenGameSubs(i)) { + closeGameSubs(i); + } + delete _gameSubsFdEntries[i]; + _gameSubsFdEntries[i] = nullptr; + } + } + +#if SUBTITLES_EXTERNAL_FONT + if (_subsFont != nullptr) { + _subsFont->close(); + delete _subsFont; + _subsFont = nullptr; + } + + if (_gameSubsFontsFd != nullptr) { + if (isOpenSubsFontFile()) { + closeSubsFontFile(); + } + delete _gameSubsFontsFd; + _gameSubsFontsFd = nullptr; + } +#else + if (_subsFont != nullptr) { + _subsFont->close(); + delete _subsFont; + _subsFont = nullptr; + } + if (_subsBgFont != nullptr) { + _subsBgFont->close(); + delete _subsBgFont; + _subsBgFont = nullptr; + } +#endif // SUBTITLES_EXTERNAL_FONT + _subsFontsLoaded = false; +} + +} // End of namespace BladeRunner +#endif diff --git a/engines/bladerunner/subtitles.h b/engines/bladerunner/subtitles.h new file mode 100644 index 0000000000..28e970842d --- /dev/null +++ b/engines/bladerunner/subtitles.h @@ -0,0 +1,128 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef BLADERUNNER_SUBTITLES_H +#define BLADERUNNER_SUBTITLES_H + +#include "bladerunner/bladerunner.h" +#if SUBTITLES_SUPPORT + +#include "common/str.h" +#include "graphics/surface.h" + +#include "common/file.h" +#include "common/substream.h" + + +namespace BladeRunner { + +class BladeRunnerEngine; +//class SaveFileReadStream; +//class SaveFileWriteStream; +class TextResource; +class Font; + +class Subtitles { + // + // Subtitles could be in 6 possible languages are EN_ANY, DE_DEU, FR_FRA, IT_ITA, ES_ESP + // with corresponding _vm->_languageCode values: "E", "G", "F", "I", "R", "S" + // TODO Maybe support 1 + 6 * 26 entries to support multiple language subtitles? Would that be useful? + // TODO Or just support the current _vm->_languageCode ? [current implementation] + static const int kMaxNumOfSubtitlesLines = 3; + static const int kMaxWidthPerLineToAutoSplitThresholdPx = 610; + static const int kMaxTextResourceEntries = 1 + 26; // Support in-game subs (1) and all possible VQAs (26) with spoken dialogue! + static const Common::String SUBTITLES_FILENAME_PREFIXES[kMaxTextResourceEntries]; + static const Common::String SUBTITLES_FONT_FILENAME; + + + BladeRunnerEngine *_vm; + + TextResource *_vqaSubsTextResourceEntries[kMaxTextResourceEntries]; + Font *_subsFont; +#if !SUBTITLES_EXTERNAL_FONT + Font *_subsBgFont; // needed for internal font to be used as a shadow effect and make subtitles more legible in certain situations +#endif // SUBTITLES_EXTERNAL_FONT + + bool _isVisible; + Common::String _currentSubtitleTextFull; + Common::String _subtitleLineQuote[kMaxNumOfSubtitlesLines]; + int _subtitleLineScreenY[kMaxNumOfSubtitlesLines]; + int _subtitleLineScreenX[kMaxNumOfSubtitlesLines]; + int _subtitleLineSplitAtCharIndex[kMaxNumOfSubtitlesLines]; + int _currentSubtitleLines; + bool _subtitlesQuoteChanged; + + Common::File *_gameSubsFdEntries[kMaxTextResourceEntries]; // an array of pointers to TRE FILEs + bool _gameSubsFdEntriesFound[kMaxTextResourceEntries]; // false if a TRE file did not open successfully + bool _subsFontsLoaded; // false if external fonts did not load, or internal fonts (fore and background) did not load +#if SUBTITLES_EXTERNAL_FONT + Common::File *_gameSubsFontsFd; // the file for the external FONT for subtitles +#endif // SUBTITLES_EXTERNAL_FONT + +public: + Subtitles(BladeRunnerEngine *vm); + ~Subtitles(); + + const char *getInGameSubsText(int actorId, int speech_id) ; // get the text for actorId, quoteId (in-game subs) + const char *getOuttakeSubsText(const Common::String &outtakesName, int frame); // get the text for this frame if any + + void setGameSubsText(Common::String dbgQuote); // for debugging - explicit set subs text + bool show(); + bool hide(); + bool isVisible() const; + void tick(Graphics::Surface &s); + void tickOuttakes(Graphics::Surface &s); + +private: + Common::SeekableReadStream *createReadStreamForGameSubs(int subTreIdx); + + bool openGameSubs(const Common::String &filename); + void closeGameSubs(int subTreIdx); + bool isOpenGameSubs(int subTreIdx) const; + + bool loadGameSubsText(int subTreIdx); // populate a GAME SUBS TextResource with subtitles + // + // +#if SUBTITLES_EXTERNAL_FONT + Common::SeekableReadStream *createReadStreamForSubFonts(); + bool openSubsFontFile(); + void closeSubsFontFile(); + bool isOpenSubsFontFile() const; // + bool loadSubsFont(); // create a the font object from a FON file (external) +#endif // SUBTITLES_EXTERNAL_FONT + + + void draw(Graphics::Surface &s); + // bool showAt(int x, int y); // TODO maybe future use (?) + void calculatePosition(); + + int getIdxForSubsTreName(const Common::String &treName) const; + + void clear(); + void reset(); + +}; + +} // End of namespace BladeRunner +#endif + +#endif diff --git a/engines/bladerunner/text_resource.cpp b/engines/bladerunner/text_resource.cpp index 8f54f8a976..b7ae136455 100644 --- a/engines/bladerunner/text_resource.cpp +++ b/engines/bladerunner/text_resource.cpp @@ -43,6 +43,50 @@ TextResource::~TextResource() { delete[] _strings; } +#if SUBTITLES_SUPPORT +// for TRE subtitles support +bool TextResource::openFromStream(Common::ScopedPtr &s) { + + if (!s) { + return false; + } + + _count = s->readUint32LE(); + + _ids = new uint32[_count]; + _offsets = new uint32[_count + 1]; + + for (uint32 i = 0; i != _count; ++i) { + _ids[i] = s->readUint32LE(); + } + + for (uint32 i = 0; i != _count + 1; ++i) { + _offsets[i] = s->readUint32LE(); + } + + uint32 stringsStart = s->pos() - 4; + + for (uint32 i = 0; i != _count + 1; ++i) { + _offsets[i] -= stringsStart; + } + + uint32 remain = s->size() - s->pos(); + _strings = new char[remain]; + + assert(remain >= _offsets[_count]); + + s->read(_strings, remain); +#if BLADERUNNER_DEBUG_CONSOLE +// debug("\nRESOURCE:: from Stream\n----------------"); +// for (uint32 i = 0; i != (uint32)_count; ++i) { +// debug("%3d: %s", _ids[i], getText(_ids[i])); +// } +#endif + + return true; +} +#endif + bool TextResource::open(const Common::String &name) { assert(name.size() <= 8); @@ -79,9 +123,9 @@ bool TextResource::open(const Common::String &name) { s->read(_strings, remain); #if BLADERUNNER_DEBUG_CONSOLE - debug("\n%s\n----------------", resName); + debug("\n%s\n----------------", resName.c_str()); for (uint32 i = 0; i != (uint32)_count; ++i) { - debug("%3d: %s", i, getText(i)); + debug("%3d: %s", _ids[i], getText(_ids[i])); } #endif @@ -98,6 +142,20 @@ const char *TextResource::getText(uint32 id) const { return ""; } +#if SUBTITLES_SUPPORT +const char *TextResource::getOuttakeTextByFrame(uint32 frame) const { + for (uint32 i = 0; i != _count; ++i) { + //debug("Checking %d - so within: %d , %d", _ids[i], (0x0000FFFF & _ids[i]), ((_ids[i] >> 16) & 0x0000FFFF ) ); + if ((frame >= (0x0000FFFF & _ids[i]) ) && (frame < ((_ids[i] >> 16) & 0x0000FFFF ) )){ + // we found an id with lower 16bits smaller or equal to our frame key + // and with higher 16 bits higher than the frame key + return _strings + _offsets[i]; + } + } + return ""; +} +#endif + int TextResource::getCount() const { return _count; } diff --git a/engines/bladerunner/text_resource.h b/engines/bladerunner/text_resource.h index b513629d9d..be399bf2d8 100644 --- a/engines/bladerunner/text_resource.h +++ b/engines/bladerunner/text_resource.h @@ -23,6 +23,10 @@ #ifndef BLADERUNNER_TEXT_RESOURCE_H #define BLADERUNNER_TEXT_RESOURCE_H +#include "bladerunner/bladerunner.h" // needed for definition of Common::ScopedPtr (subtitles font external font file support) -- and for the subtitles relevant macro defines +#if SUBTITLES_SUPPORT +#include "common/util.h" +#endif #include "common/str.h" namespace BladeRunner { @@ -43,7 +47,14 @@ public: ~TextResource(); bool open(const Common::String &name); + #if SUBTITLES_SUPPORT + bool openFromStream(Common::ScopedPtr &s); + #endif + const char *getText(uint32 id) const; + #if SUBTITLES_SUPPORT + const char *getOuttakeTextByFrame(uint32 frame) const; + #endif int getCount() const; }; diff --git a/engines/bladerunner/ui/elevator.cpp b/engines/bladerunner/ui/elevator.cpp index 64164f442d..7026f0ea01 100644 --- a/engines/bladerunner/ui/elevator.cpp +++ b/engines/bladerunner/ui/elevator.cpp @@ -32,6 +32,9 @@ #include "bladerunner/time.h" #include "bladerunner/ui/ui_image_picker.h" #include "bladerunner/vqa_player.h" +#if SUBTITLES_SUPPORT +#include "bladerunner/subtitles.h" +#endif #include "common/rect.h" #include "common/str.h" @@ -226,6 +229,9 @@ void Elevator::tick() { _imagePicker->draw(_vm->_surfaceFront); _vm->_mouse->draw(_vm->_surfaceFront, p.x, p.y); +#if SUBTITLES_SUPPORT + _vm->_subtitles->tick(_vm->_surfaceFront); +#endif _vm->blitToScreen(_vm->_surfaceFront); tickDescription(); _vm->_system->delayMillis(10); diff --git a/engines/bladerunner/ui/esper.cpp b/engines/bladerunner/ui/esper.cpp index 51c64f6bc9..2a0431f0d8 100644 --- a/engines/bladerunner/ui/esper.cpp +++ b/engines/bladerunner/ui/esper.cpp @@ -38,6 +38,9 @@ #include "bladerunner/time.h" #include "bladerunner/ui/ui_image_picker.h" #include "bladerunner/vqa_player.h" +#if SUBTITLES_SUPPORT +#include "bladerunner/subtitles.h" +#endif #include "common/rect.h" #include "common/str.h" @@ -230,7 +233,9 @@ void ESPER::tick() { drawMouse(_vm->_surfaceFront); tickSound(); - +#if SUBTITLES_SUPPORT + _vm->_subtitles->tick(_vm->_surfaceFront); +#endif _vm->blitToScreen(_vm->_surfaceFront); // TODO: implement 60hz lock for smoother experience diff --git a/engines/bladerunner/ui/kia.cpp b/engines/bladerunner/ui/kia.cpp index bd0402847b..183bf615d9 100644 --- a/engines/bladerunner/ui/kia.cpp +++ b/engines/bladerunner/ui/kia.cpp @@ -53,6 +53,9 @@ #include "bladerunner/ui/kia_shapes.h" #include "bladerunner/ui/ui_image_picker.h" #include "bladerunner/vqa_player.h" +#if SUBTITLES_SUPPORT +#include "bladerunner/subtitles.h" +#endif // SUBTITLES_SUPPORT #include "common/str.h" #include "common/keyboard.h" @@ -371,6 +374,10 @@ void KIA::tick() { } _vm->_mouse->draw(_vm->_surfaceFront, mouse.x, mouse.y); +#if SUBTITLES_SUPPORT + _vm->_subtitles->tick(_vm->_surfaceFront); +#endif + _vm->blitToScreen(_vm->_surfaceFront); _vm->_system->delayMillis(10); diff --git a/engines/bladerunner/ui/kia_section_settings.cpp b/engines/bladerunner/ui/kia_section_settings.cpp index b557a04877..8ff3c7e7b5 100644 --- a/engines/bladerunner/ui/kia_section_settings.cpp +++ b/engines/bladerunner/ui/kia_section_settings.cpp @@ -55,7 +55,14 @@ KIASectionSettings::KIASectionSettings(BladeRunnerEngine *vm) _ambientSoundVolume = new UISlider(_vm, sliderCallback, this, Common::Rect(180, 210, 460, 220), 101, 0); _speechVolume = new UISlider(_vm, sliderCallback, this, Common::Rect(180, 235, 460, 245), 101, 0); _gammaCorrection = new UISlider(_vm, sliderCallback, this, Common::Rect(180, 260, 460, 270), 101, 0); - _directorsCut = new UICheckBox(_vm, checkBoxCallback, this, Common::Rect(180, 364, 460, 374), 0, false); +#if BLADERUNNER_RESTORED_CONTENT_GAME + _directorsCut = new UICheckBox(_vm, checkBoxCallback, this, Common::Rect(180, 364, 270, 374), 0, false); + #if SUBTITLES_SUPPORT + _subtitlesEnable = new UICheckBox(_vm, checkBoxCallback, this, Common::Rect(291, 364, 360, 374), 0, false); + #endif // SUBTITLES_SUPPORT +#else + _directorsCut = new UICheckBox(_vm, checkBoxCallback, this, Common::Rect(180, 364, 460, 374), 0, false); // original +#endif // BLADERUNNER_RESTORED_CONTENT_GAME _playerAgendaSelector = new UIImagePicker(_vm, 5); _uiContainer->add(_musicVolume); @@ -64,6 +71,11 @@ KIASectionSettings::KIASectionSettings(BladeRunnerEngine *vm) _uiContainer->add(_speechVolume); _uiContainer->add(_gammaCorrection); _uiContainer->add(_directorsCut); +#if BLADERUNNER_RESTORED_CONTENT_GAME + #if SUBTITLES_SUPPORT + _uiContainer->add(_subtitlesEnable); + #endif // SUBTITLES_SUPPORT +#endif // BLADERUNNER_RESTORED_CONTENT_GAME _learyPos = 0; } @@ -76,6 +88,11 @@ KIASectionSettings::~KIASectionSettings() { delete _speechVolume; delete _gammaCorrection; delete _directorsCut; +#if BLADERUNNER_RESTORED_CONTENT_GAME + #if SUBTITLES_SUPPORT + delete _subtitlesEnable; + #endif // SUBTITLES_SUPPORT +#endif // BLADERUNNER_RESTORED_CONTENT_GAME delete _playerAgendaSelector; } @@ -91,6 +108,11 @@ void KIASectionSettings::open() { _playerAgendaSelector->activate(mouseInCallback, nullptr, nullptr, mouseUpCallback, this); _directorsCut->enable(); +#if BLADERUNNER_RESTORED_CONTENT_GAME + #if SUBTITLES_SUPPORT + _subtitlesEnable->enable(); + #endif // SUBTITLES_SUPPORT +#endif // BLADERUNNER_RESTORED_CONTENT_GAME } void KIASectionSettings::close() { @@ -104,6 +126,11 @@ void KIASectionSettings::draw(Graphics::Surface &surface) { _speechVolume->setValue(_vm->_audioSpeech->getVolume()); _gammaCorrection->setValue(100.0f); _directorsCut->setChecked(_vm->_gameFlags->query(kFlagDirectorsCut)); +#if BLADERUNNER_RESTORED_CONTENT_GAME + #if SUBTITLES_SUPPORT + _subtitlesEnable->setChecked(_vm->_extraGameFlagsForRestoredContent->query(kEDSFlagSubtitlesEnable)); + #endif // SUBTITLES_SUPPORT +#endif // BLADERUNNER_RESTORED_CONTENT_GAME const char *textConversationChoices = _vm->_textOptions->getText(0); const char *textMusic = _vm->_textOptions->getText(2); @@ -116,6 +143,11 @@ void KIASectionSettings::draw(Graphics::Surface &surface) { const char *textDark = _vm->_textOptions->getText(14); const char *textLight = _vm->_textOptions->getText(15); const char *textDesignersCut = _vm->_textOptions->getText(18); +#if BLADERUNNER_RESTORED_CONTENT_GAME + #if SUBTITLES_SUPPORT + const char *textSubtitles = "Subtitles"; + #endif // SUBTITLES_SUPPORT +#endif // BLADERUNNER_RESTORED_CONTENT_GAME int posConversationChoices = 320 - _vm->_mainFont->getTextWidth(textConversationChoices) / 2; int posMusic = 320 - _vm->_mainFont->getTextWidth(textMusic) / 2; @@ -152,6 +184,11 @@ void KIASectionSettings::draw(Graphics::Surface &surface) { _vm->_mainFont->drawColor(textLight, surface, 462, 261, 0x6EEE); _vm->_mainFont->drawColor(textDesignersCut, surface, 192, 365, 0x7751); +#if BLADERUNNER_RESTORED_CONTENT_GAME + #if SUBTITLES_SUPPORT + _vm->_mainFont->drawColor(textSubtitles, surface, 303, 365, 0x7751); + #endif // SUBTITLES_SUPPORT +#endif // BLADERUNNER_RESTORED_CONTENT_GAME _playerAgendaSelector->drawTooltip(surface, _mouseX, _mouseY); } @@ -227,6 +264,17 @@ void KIASectionSettings::checkBoxCallback(void *callbackData, void *source) { self->_vm->_gameFlags->reset(kFlagDirectorsCut); } } + #if BLADERUNNER_RESTORED_CONTENT_GAME + #if SUBTITLES_SUPPORT + else if (source == self->_subtitlesEnable) { + if (self->_subtitlesEnable->_isChecked) { + self->_vm->_extraGameFlagsForRestoredContent->set(kEDSFlagSubtitlesEnable); + } else { + self->_vm->_extraGameFlagsForRestoredContent->reset(kEDSFlagSubtitlesEnable); + } + } + #endif // SUBTITLES_SUPPORT + #endif // BLADERUNNER_RESTORED_CONTENT_GAME } void KIASectionSettings::mouseInCallback(int buttonId, void *callbackData) { diff --git a/engines/bladerunner/ui/kia_section_settings.h b/engines/bladerunner/ui/kia_section_settings.h index 49356266b7..4c5452b595 100644 --- a/engines/bladerunner/ui/kia_section_settings.h +++ b/engines/bladerunner/ui/kia_section_settings.h @@ -23,6 +23,7 @@ #ifndef BLADERUNNER_KIA_SECTION_SETTINGS_H #define BLADERUNNER_KIA_SECTION_SETTINGS_H +#include "bladerunner/bladerunner.h" // to get the macro defines #include "bladerunner/ui/kia_section_base.h" namespace BladeRunner { @@ -43,6 +44,11 @@ class KIASectionSettings : public KIASectionBase { UISlider *_speechVolume; UISlider *_gammaCorrection; UICheckBox *_directorsCut; +#if BLADERUNNER_RESTORED_CONTENT_GAME + #if SUBTITLES_SUPPORT + UICheckBox *_subtitlesEnable; + #endif +#endif // BLADERUNNER_RESTORED_CONTENT_GAME UIImagePicker *_playerAgendaSelector; int _mouseX; diff --git a/engines/bladerunner/ui/vk.cpp b/engines/bladerunner/ui/vk.cpp index aa95b6bf9c..8565d39a36 100644 --- a/engines/bladerunner/ui/vk.cpp +++ b/engines/bladerunner/ui/vk.cpp @@ -41,6 +41,9 @@ #include "bladerunner/time.h" #include "bladerunner/ui/ui_image_picker.h" #include "bladerunner/vqa_player.h" +#if SUBTITLES_SUPPORT +#include "bladerunner/subtitles.h" +#endif #include "common/str.h" #include "common/keyboard.h" @@ -196,6 +199,10 @@ void VK::tick() { draw(); +#if SUBTITLES_SUPPORT + _vm->_subtitles->tick(_vm->_surfaceFront); +#endif // SUBTITLES_SUPPORT + _vm->blitToScreen(_vm->_surfaceFront); _vm->_system->delayMillis(10); -- cgit v1.2.3