/* 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 "engines/util.h" #include "gui/saveload.h" #include "common/translation.h" #include "sherlock/scalpel/scalpel.h" #include "sherlock/scalpel/scalpel_fixed_text.h" #include "sherlock/scalpel/scalpel_map.h" #include "sherlock/scalpel/scalpel_people.h" #include "sherlock/scalpel/scalpel_scene.h" #include "sherlock/scalpel/scalpel_screen.h" #include "sherlock/scalpel/3do/scalpel_3do_screen.h" #include "sherlock/scalpel/tsage/logo.h" #include "sherlock/sherlock.h" #include "sherlock/music.h" #include "sherlock/animation.h" #include "sherlock/scalpel/3do/movie_decoder.h" namespace Sherlock { namespace Scalpel { #define PROLOGUE_NAMES_COUNT 6 // The following are a list of filenames played in the prologue that have // special effects associated with them at specific frames static const char *const PROLOGUE_NAMES[PROLOGUE_NAMES_COUNT] = { "subway1", "subway2", "finale2", "suicid", "coff3", "coff4" }; static const int PROLOGUE_FRAMES[6][9] = { { 4, 26, 54, 72, 92, 134, FRAMES_END }, { 2, 80, 95, 117, 166, FRAMES_END }, { 1, FRAMES_END }, { 42, FRAMES_END }, { FRAMES_END }, { FRAMES_END } }; #define TITLE_NAMES_COUNT 7 // Title animations file list static const char *const TITLE_NAMES[TITLE_NAMES_COUNT] = { "27pro1", "14note", "coff1", "coff2", "coff3", "coff4", "14kick" }; static const int TITLE_FRAMES[7][9] = { { 29, 131, FRAMES_END }, { 55, 80, 95, 117, 166, FRAMES_END }, { 15, FRAMES_END }, { 4, 37, 92, FRAMES_END }, { 2, 43, FRAMES_END }, { 2, FRAMES_END }, { 10, 50, FRAMES_END } }; #define NUM_PLACES 100 static const int MAP_X[NUM_PLACES] = { 0, 368, 0, 219, 0, 282, 0, 43, 0, 0, 396, 408, 0, 0, 0, 568, 37, 325, 28, 0, 263, 36, 148, 469, 342, 143, 443, 229, 298, 0, 157, 260, 432, 174, 0, 351, 0, 528, 0, 136, 0, 0, 0, 555, 165, 0, 506, 0, 0, 344, 0, 0 }; static const int MAP_Y[NUM_PLACES] = { 0, 147, 0, 166, 0, 109, 0, 61, 0, 0, 264, 70, 0, 0, 0, 266, 341, 30, 275, 0, 294, 146, 311, 230, 184, 268, 133, 94, 207, 0, 142, 142, 330, 255, 0, 37, 0, 70, 0, 116, 0, 0, 0, 50, 21, 0, 303, 0, 0, 229, 0, 0 }; static const int MAP_TRANSLATE[NUM_PLACES] = { 0, 0, 0, 1, 0, 2, 0, 3, 4, 0, 4, 6, 0, 0, 0, 8, 9, 10, 11, 0, 12, 13, 14, 7, 15, 16, 17, 18, 19, 0, 20, 21, 22, 23, 0, 24, 0, 25, 0, 26, 0, 0, 0, 27, 28, 0, 29, 0, 0, 30, 0 }; static const byte MAP_SEQUENCES[3][MAX_FRAME] = { { 1, 1, 2, 3, 4, 0 }, // Overview Still { 5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 0 }, { 5, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 0 } }; #define MAX_PEOPLE 66 struct PeopleData { const char *portrait; int fixedTextId; byte stillSequences[MAX_TALK_SEQUENCES]; byte talkSequences[MAX_TALK_SEQUENCES]; }; const PeopleData PEOPLE_DATA[MAX_PEOPLE] = { { "HOLM", kFixedText_People_SherlockHolmes, { 1, 0, 0 }, { 1, 0, 0 } }, { "WATS", kFixedText_People_DrWatson, { 6, 0, 0 }, { 5, 5, 6, 7, 8, 7, 8, 6, 0, 0 } }, { "LEST", kFixedText_People_InspectorLestrade, { 4, 0, 0 }, { 2, 0, 0 } }, { "CON1", kFixedText_People_ConstableOBrien, { 2, 0, 0 }, { 1, 0, 0 } }, { "CON2", kFixedText_People_ConstableLewis, { 2, 0, 0 }, { 1, 0, 0 } }, { "SHEI", kFixedText_People_SheilaParker, { 2, 0, 0 }, { 2, 3, 0, 0 } }, { "HENR", kFixedText_People_HenryCarruthers, { 3, 0, 0 }, { 3, 0, 0 } }, { "LESL", kFixedText_People_Lesley, { 9, 0, 0 }, { 1, 2, 3, 2, 1, 2, 3, 0, 0 } }, { "USH1", kFixedText_People_AnUsher, { 13, 0, 0 }, { 13, 14, 0, 0 } }, { "USH2", kFixedText_People_AnUsher, { 2, 0, 0 }, { 2, 0, 0 } }, { "FRED", kFixedText_People_FredrickEpstein, { 4, 0, 0 }, { 1, 2, 3, 4, 3, 4, 3, 2, 0, 0 } }, { "WORT", kFixedText_People_MrsWorthington, { 9, 0, 0 }, { 8, 0, 0 } }, { "COAC", kFixedText_People_TheCoach, { 2, 0, 0 }, { 1, 2, 3, 4, 5, 4, 3, 2, 0, 0 } }, { "PLAY", kFixedText_People_APlayer, { 8, 0, 0 }, { 7, 8, 0, 0 } }, { "WBOY", kFixedText_People_Tim, { 13, 0, 0 }, { 12, 13, 0, 0 } }, { "JAME", kFixedText_People_JamesSanders, { 6, 0, 0 }, { 3, 4, 0, 0 } }, { "BELL", kFixedText_People_Belle, { 1, 0, 0 }, { 4, 5, 0, 0 } }, { "GIRL", kFixedText_People_CleaningGirl, { 20, 0, 0 }, { 14, 15, 16, 17, 18, 19, 20, 20, 20, 0, 0 } }, { "EPST", kFixedText_People_FredrickEpstein, { 17, 0, 0 }, { 16, 17, 18, 18, 18, 17, 17, 0, 0 } }, { "WIGG", kFixedText_People_Wiggins, { 3, 0, 0 }, { 2, 3, 0, 0 } }, { "PAUL", kFixedText_People_Paul, { 2, 0, 0 }, { 1, 2, 0, 0 } }, { "BART", kFixedText_People_TheBartender, { 1, 0, 0 }, { 1, 0, 0 } }, { "DIRT", kFixedText_People_ADirtyDrunk, { 1, 0, 0 }, { 1, 0, 0 } }, { "SHOU", kFixedText_People_AShoutingDrunk, { 1, 0, 0 }, { 1, 0, 0 } }, { "STAG", kFixedText_People_AStaggeringDrunk, { 1, 0, 0 }, { 1, 0, 0 } }, { "BOUN", kFixedText_People_TheBouncer, { 1, 0, 0 }, { 1, 0, 0 } }, { "SAND", kFixedText_People_JamesSanders, { 6, 0, 0 }, { 5, 6, 0, 0 } }, { "CORO", kFixedText_People_TheCoroner, { 6, 0, 0 }, { 4, 5, 0, 0 } }, { "EQUE", kFixedText_People_ReginaldSnipes, { 1, 0, 0 }, { 1, 0, 0 } }, { "GEOR", kFixedText_People_GeorgeBlackwood, { 1, 0, 0 }, { 1, 0, 0 } }, { "LARS", kFixedText_People_Lars, { 7, 0, 0 }, { 5, 6, 0, 0 } }, { "PARK", kFixedText_People_SheilaParker, { 1, 0, 0 }, { 1, 0, 0 } }, { "CHEM", kFixedText_People_TheChemist, { 8, 0, 0 }, { 8, 9, 0, 0 } }, { "GREG", kFixedText_People_InspectorGregson, { 6, 0, 0 }, { 5, 6, 0, 0 } }, { "LAWY", kFixedText_People_JacobFarthington, { 1, 0, 0 }, { 1, 0, 0 } }, { "MYCR", kFixedText_People_Mycroft, { 1, 0, 0 }, { 1, 0, 0 } }, { "SHER", kFixedText_People_OldSherman, { 7, 0, 0 }, { 7, 8, 0, 0 } }, { "CHMB", kFixedText_People_Richard, { 1, 0, 0 }, { 1, 0, 0 } }, { "BARM", kFixedText_People_TheBarman, { 1, 0, 0 }, { 1, 0, 0 } }, { "DAND", kFixedText_People_ADandyPlayer, { 1, 0, 0 }, { 1, 0, 0 } }, { "ROUG", kFixedText_People_ARoughlookingPlayer, { 1, 0, 0 }, { 1, 0, 0 } }, { "SPEC", kFixedText_People_ASpectator, { 1, 0, 0 }, { 1, 0, 0 } }, { "HUNT", kFixedText_People_RobertHunt, { 1, 0, 0 }, { 1, 0, 0 } }, { "VIOL", kFixedText_People_Violet, { 3, 0, 0 }, { 3, 4, 0, 0 } }, { "PETT", kFixedText_People_Pettigrew, { 1, 0, 0 }, { 1, 0, 0 } }, { "APPL", kFixedText_People_Augie, { 8, 0, 0 }, { 14, 15, 0, 0 } }, { "ANNA", kFixedText_People_AnnaCarroway, { 16, 0, 0 }, { 3, 4, 5, 6, 0, 0 } }, { "GUAR", kFixedText_People_AGuard, { 1, 0, 0 }, { 4, 5, 6, 0, 0 } }, { "ANTO", kFixedText_People_AntonioCaruso, { 8, 0, 0 }, { 7, 8, 0, 0 } }, { "TOBY", kFixedText_People_TobyTheDog, { 1, 0, 0 }, { 1, 0, 0 } }, { "KING", kFixedText_People_SimonKingsley, { 13, 0, 0 }, { 13, 14, 0, 0 } }, { "ALFR", kFixedText_People_Alfred, { 2, 0, 0 }, { 2, 3, 0, 0 } }, { "LADY", kFixedText_People_LadyBrumwell, { 1, 0, 0 }, { 3, 4, 0, 0 } }, { "ROSA", kFixedText_People_MadameRosa, { 1, 0, 0 }, { 1, 30, 0, 0 } }, { "LADB", kFixedText_People_LadyBrumwell, { 1, 0, 0 }, { 3, 4, 0, 0 } }, { "MOOR", kFixedText_People_JosephMoorehead, { 1, 0, 0 }, { 1, 0, 0 } }, { "BEAL", kFixedText_People_MrsBeale, { 5, 0, 0 }, { 14, 15, 16, 17, 18, 19, 20, 0, 0 } }, { "LION", kFixedText_People_Felix, { 1, 0, 0 }, { 1, 0, 0 } }, { "HOLL", kFixedText_People_Hollingston, { 1, 0, 0 }, { 1, 0, 0 } }, { "CALL", kFixedText_People_ConstableCallaghan, { 1, 0, 0 }, { 1, 0, 0 } }, { "JERE", kFixedText_People_SergeantDuncan, { 2, 0, 0 }, { 1, 1, 2, 2, 0, 0 } }, { "LORD", kFixedText_People_LordBrumwell, { 1, 0, 0 }, { 9, 10, 0, 0 } }, { "NIGE", kFixedText_People_NigelJaimeson, { 1, 0, 0 }, { 1, 2, 0, 138, 3, 4, 0, 138, 0, 0 } }, { "JONA", kFixedText_People_Jonas, { 1, 0, 0 }, { 1, 8, 0, 0 } }, { "DUGA", kFixedText_People_ConstableDugan, { 1, 0, 0 }, { 1, 0, 0 } }, { "INSP", kFixedText_People_InspectorLestrade, { 4, 0, 0 }, { 2, 0, 0 } } }; uint INFO_BLACK; uint BORDER_COLOR; uint COMMAND_BACKGROUND; uint BUTTON_BACKGROUND; uint TALK_FOREGROUND; uint TALK_NULL; uint BUTTON_TOP; uint BUTTON_MIDDLE; uint BUTTON_BOTTOM; uint COMMAND_FOREGROUND; uint COMMAND_HIGHLIGHTED; uint COMMAND_NULL; uint INFO_FOREGROUND; uint INFO_BACKGROUND; uint INV_FOREGROUND; uint INV_BACKGROUND; uint PEN_COLOR; /*----------------------------------------------------------------*/ #define FROM_RGB(r, g, b) pixelFormatRGB565.RGBToColor(r, g, b) ScalpelEngine::ScalpelEngine(OSystem *syst, const SherlockGameDescription *gameDesc) : SherlockEngine(syst, gameDesc) { _darts = nullptr; _mapResult = 0; if (getPlatform() == Common::kPlatform3DO) { const Graphics::PixelFormat pixelFormatRGB565 = Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0); INFO_BLACK = FROM_RGB(0, 0, 0); BORDER_COLOR = FROM_RGB(0x6d, 0x38, 0x10); COMMAND_BACKGROUND = FROM_RGB(0x38, 0x38, 0xce); BUTTON_BACKGROUND = FROM_RGB(0x95, 0x5d, 0x24); TALK_FOREGROUND = FROM_RGB(0xff, 0x55, 0x55); TALK_NULL = FROM_RGB(0xce, 0xc6, 0xc2); BUTTON_TOP = FROM_RGB(0xbe, 0x85, 0x3c); BUTTON_MIDDLE = FROM_RGB(0x9d, 0x40, 0); BUTTON_BOTTOM = FROM_RGB(0x69, 0x24, 0); COMMAND_FOREGROUND = FROM_RGB(0xFF, 0xFF, 0xFF); COMMAND_HIGHLIGHTED = FROM_RGB(0x55, 0xff, 0x55); COMMAND_NULL = FROM_RGB(0x69, 0x24, 0); INFO_FOREGROUND = FROM_RGB(0x55, 0xff, 0xff); INFO_BACKGROUND = FROM_RGB(0, 0, 0x48); INV_FOREGROUND = FROM_RGB(0xff, 0xff, 0x55); INV_BACKGROUND = FROM_RGB(0, 0, 0x48); PEN_COLOR = FROM_RGB(0x50, 0x18, 0); } else { INFO_BLACK = 1; BORDER_COLOR = 237; COMMAND_BACKGROUND = 4; BUTTON_BACKGROUND = 235; TALK_FOREGROUND = 12; TALK_NULL = 16; BUTTON_TOP = 233; BUTTON_MIDDLE = 244; BUTTON_BOTTOM = 248; COMMAND_FOREGROUND = 15; COMMAND_HIGHLIGHTED = 10; COMMAND_NULL = 248; INFO_FOREGROUND = 11; INFO_BACKGROUND = 1; INV_FOREGROUND = 14; INV_BACKGROUND = 1; PEN_COLOR = 250; } } ScalpelEngine::~ScalpelEngine() { delete _darts; } void ScalpelEngine::setupGraphics() { if (getPlatform() != Common::kPlatform3DO) { // 320x200 palettized initGraphics(320, 200, false); } else { // 3DO actually uses RGB555, but some platforms of ours only support RGB565, so we use that const Graphics::PixelFormat pixelFormatRGB565 = Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0); // First try for a 640x400 mode g_system->beginGFXTransaction(); initCommonGFX(true); g_system->initSize(640, 400, &pixelFormatRGB565); OSystem::TransactionError gfxError = g_system->endGFXTransaction(); if (gfxError == OSystem::kTransactionSuccess) { _isScreenDoubled = true; } else { // System doesn't support it, so fall back on 320x200 mode initGraphics(320, 200, false, &pixelFormatRGB565); } } } void ScalpelEngine::initialize() { // Setup graphics mode setupGraphics(); // Let the base engine intialize SherlockEngine::initialize(); _darts = new Darts(this); _flags.resize(100 * 8); _flags[3] = true; // Turn on Alley _flags[39] = true; // Turn on Baker Street if (!isDemo()) { // Load the map co-ordinates for each scene and sequence data ScalpelMap &map = *(ScalpelMap *)_map; map.loadPoints(NUM_PLACES, &MAP_X[0], &MAP_Y[0], &MAP_TRANSLATE[0]); map.loadSequences(3, &MAP_SEQUENCES[0][0]); map._oldCharPoint = BAKER_ST_EXTERIOR; } // Load the inventory loadInventory(); // Set up list of people ScalpelFixedText &fixedText = *(ScalpelFixedText *)_fixedText; const char *peopleNamePtr = nullptr; for (int idx = 0; idx < MAX_PEOPLE; ++idx) { peopleNamePtr = fixedText.getText(PEOPLE_DATA[idx].fixedTextId); _people->_characters.push_back(PersonData(peopleNamePtr, PEOPLE_DATA[idx].portrait, PEOPLE_DATA[idx].stillSequences, PEOPLE_DATA[idx].talkSequences)); } _animation->setPrologueNames(&PROLOGUE_NAMES[0], PROLOGUE_NAMES_COUNT); _animation->setPrologueFrames(&PROLOGUE_FRAMES[0][0], 6, 9); _animation->setTitleNames(&TITLE_NAMES[0], TITLE_NAMES_COUNT); _animation->setTitleFrames(&TITLE_FRAMES[0][0], 7, 9); // Starting scene if (isDemo() && _interactiveFl) _scene->_goToScene = 3; else _scene->_goToScene = 4; } void ScalpelEngine::showOpening() { bool finished = true; if (isDemo() && _interactiveFl) return; _events->setFrameRate(60); if (getPlatform() == Common::kPlatform3DO) { show3DOSplash(); finished = showCityCutscene3DO(); if (finished) finished = showAlleyCutscene3DO(); if (finished) finished = showStreetCutscene3DO(); if (finished) showOfficeCutscene3DO(); _events->clearEvents(); _music->stopMusic(); } else { TsAGE::Logo::show(this); finished = showCityCutscene(); if (finished) finished = showAlleyCutscene(); if (finished) finished = showStreetCutscene(); if (finished) showOfficeCutscene(); _events->clearEvents(); _music->stopMusic(); } _events->setFrameRate(GAME_FRAME_RATE); } bool ScalpelEngine::showCityCutscene() { byte greyPalette[PALETTE_SIZE]; byte palette[PALETTE_SIZE]; // Demo fades from black into grey and then fades from grey into the scene Common::fill(&greyPalette[0], &greyPalette[PALETTE_SIZE], 142); _screen->fadeIn((const byte *)greyPalette, 3); _music->loadSong("prolog1"); _animation->_gfxLibraryFilename = "title.lib"; _animation->_soundLibraryFilename = "title.snd"; bool finished = _animation->play("26open1", true, 1, 255, true, 2); if (finished) { ImageFile titleImages_LondonNovember("title2.vgs", true); _screen->_backBuffer1.SHblitFrom(*_screen); _screen->_backBuffer2.SHblitFrom(*_screen); Common::Point londonPosition; if ((titleImages_LondonNovember[0]._width == 302) && (titleImages_LondonNovember[0]._height == 39)) { // Spanish londonPosition = Common::Point(9, 8); } else { // English (German uses the same English graphics), width 272, height 37 // In the German version this is placed differently, check against German floppy version TODO londonPosition = Common::Point(30, 50); } // London, England _screen->_backBuffer1.SHtransBlitFrom(titleImages_LondonNovember[0], londonPosition); _screen->randomTransition(); finished = _events->delay(1000, true); // November, 1888 if (finished) { _screen->_backBuffer1.SHtransBlitFrom(titleImages_LondonNovember[1], Common::Point(100, 100)); _screen->randomTransition(); finished = _events->delay(5000, true); } // Transition out the title _screen->_backBuffer1.SHblitFrom(_screen->_backBuffer2); _screen->randomTransition(); } if (finished) finished = _animation->play("26open2", true, 1, 0, false, 2); if (finished) { ImageFile titleImages_SherlockHolmesTitle("title.vgs", true); _screen->_backBuffer1.SHblitFrom(*_screen); _screen->_backBuffer2.SHblitFrom(*_screen); Common::Point lostFilesPosition; Common::Point sherlockHolmesPosition; Common::Point copyrightPosition; if ((titleImages_SherlockHolmesTitle[0]._width == 306) && (titleImages_SherlockHolmesTitle[0]._height == 39)) { // Spanish lostFilesPosition = Common::Point(5, 5); sherlockHolmesPosition = Common::Point(24, 40); copyrightPosition = Common::Point(3, 190); } else { // English (German uses the same English graphics), width 208, height 39 lostFilesPosition = Common::Point(75, 6); sherlockHolmesPosition = Common::Point(34, 21); copyrightPosition = Common::Point(4, 190); } // The Lost Files of _screen->_backBuffer1.SHtransBlitFrom(titleImages_SherlockHolmesTitle[0], lostFilesPosition); // Sherlock Holmes _screen->_backBuffer1.SHtransBlitFrom(titleImages_SherlockHolmesTitle[1], sherlockHolmesPosition); // copyright _screen->_backBuffer1.SHtransBlitFrom(titleImages_SherlockHolmesTitle[2], copyrightPosition); _screen->verticalTransition(); finished = _events->delay(4000, true); if (finished) { _screen->_backBuffer1.SHblitFrom(_screen->_backBuffer2); _screen->randomTransition(); finished = _events->delay(2000); } if (finished) { _screen->getPalette(palette); _screen->fadeToBlack(2); } if (finished) { // In the alley... Common::Point alleyPosition; if ((titleImages_SherlockHolmesTitle[3]._width == 105) && (titleImages_SherlockHolmesTitle[3]._height == 16)) { // German alleyPosition = Common::Point(72, 50); } else if ((titleImages_SherlockHolmesTitle[3]._width == 166) && (titleImages_SherlockHolmesTitle[3]._height == 36)) { // Spanish alleyPosition = Common::Point(71, 50); } else { // English, width 175, height 38 alleyPosition = Common::Point(72, 51); } _screen->SHtransBlitFrom(titleImages_SherlockHolmesTitle[3], alleyPosition); _screen->fadeIn(palette, 3); // Wait until the track got looped and the first few notes were played finished = _music->waitUntilMSec(4300, 21300, 0, 2500); // ticks 0x104 / ticks 0x500 } } _animation->_gfxLibraryFilename = ""; _animation->_soundLibraryFilename = ""; return finished; } bool ScalpelEngine::showAlleyCutscene() { byte palette[PALETTE_SIZE]; _music->loadSong("prolog2"); _animation->_gfxLibraryFilename = "TITLE.LIB"; _animation->_soundLibraryFilename = "TITLE.SND"; // Fade "In The Alley..." text to black _screen->fadeToBlack(2); bool finished = _animation->play("27PRO1", true, 1, 3, true, 2); if (finished) { _screen->getPalette(palette); _screen->fadeToBlack(2); // wait until second lower main note finished = _music->waitUntilMSec(26800, 0xFFFFFFFF, 0, 1000); // ticks 0x64A } if (finished) { _screen->setPalette(palette); finished = _animation->play("27PRO2", true, 1, 0, false, 2); } if (finished) { showLBV("scream.lbv"); // wait until first "scream" in music happened finished = _music->waitUntilMSec(45800, 0xFFFFFFFF, 0, 6000); // ticks 0xABE } if (finished) { // quick fade out _screen->fadeToBlack(1); // wait until after third "scream" in music happened finished = _music->waitUntilMSec(49000, 0xFFFFFFFF, 0, 2000); // ticks 0xB80 } if (finished) finished = _animation->play("27PRO3", true, 1, 0, true, 2); if (finished) { _screen->getPalette(palette); _screen->fadeToBlack(2); } if (finished) { ImageFile titleImages_EarlyTheFollowingMorning("title3.vgs", true); // "Early the following morning on Baker Street..." Common::Point earlyTheFollowingMorningPosition; if ((titleImages_EarlyTheFollowingMorning[0]._width == 164) && (titleImages_EarlyTheFollowingMorning[0]._height == 19)) { // German earlyTheFollowingMorningPosition = Common::Point(35, 50); } else if ((titleImages_EarlyTheFollowingMorning[0]._width == 171) && (titleImages_EarlyTheFollowingMorning[0]._height == 32)) { // Spanish earlyTheFollowingMorningPosition = Common::Point(35, 50); } else { // English, width 218, height 31 earlyTheFollowingMorningPosition = Common::Point(35, 52); } _screen->SHtransBlitFrom(titleImages_EarlyTheFollowingMorning[0], earlyTheFollowingMorningPosition); // fast fade-in _screen->fadeIn(palette, 1); // wait for music to end and wait an additional 2.5 seconds finished = _music->waitUntilMSec(0xFFFFFFFF, 0xFFFFFFFF, 2500, 3000); } _animation->_gfxLibraryFilename = ""; _animation->_soundLibraryFilename = ""; return finished; } bool ScalpelEngine::showStreetCutscene() { _animation->_gfxLibraryFilename = "TITLE.LIB"; _animation->_soundLibraryFilename = "TITLE.SND"; _music->loadSong("prolog3"); // wait a bit bool finished = _events->delay(500); if (finished) { // fade out "Early the following morning..." _screen->fadeToBlack(2); // wait for music a bit finished = _music->waitUntilMSec(3800, 0xFFFFFFFF, 0, 1000); // ticks 0xE4 } if (finished) finished = _animation->play("14KICK", true, 1, 3, true, 2); // Constable animation plays slower than speed 2 // If we play it with speed 2, music gets obviously out of sync if (finished) finished = _animation->play("14NOTE", true, 1, 0, false, 3); // Fade to black if (finished) _screen->fadeToBlack(1); _animation->_gfxLibraryFilename = ""; _animation->_soundLibraryFilename = ""; return finished; } bool ScalpelEngine::showOfficeCutscene() { _music->loadSong("prolog4"); _animation->_gfxLibraryFilename = "TITLE2.LIB"; _animation->_soundLibraryFilename = "TITLE.SND"; bool finished = _animation->play("COFF1", true, 1, 3, true, 3); if (finished) finished = _animation->play("COFF2", true, 1, 0, false, 3); if (finished) { showLBV("note.lbv"); if (_sound->_voices) { finished = _sound->playSound("NOTE1", WAIT_KBD_OR_FINISH); if (finished) finished = _sound->playSound("NOTE2", WAIT_KBD_OR_FINISH); if (finished) finished = _sound->playSound("NOTE3", WAIT_KBD_OR_FINISH); if (finished) finished = _sound->playSound("NOTE4", WAIT_KBD_OR_FINISH); } else finished = _events->delay(19000); if (finished) { _events->clearEvents(); finished = _events->delay(500); } } if (finished) finished = _animation->play("COFF3", true, 1, 0, true, 3); if (finished) finished = _animation->play("COFF4", true, 1, 0, false, 3); if (finished) finished = scrollCredits(); if (finished) _screen->fadeToBlack(3); _animation->_gfxLibraryFilename = ""; _animation->_soundLibraryFilename = ""; return finished; } bool ScalpelEngine::scrollCredits() { // Load the images for displaying credit text Common::SeekableReadStream *stream = _res->load("credits.vgs", "title.lib"); ImageFile creditsImages(*stream); // Demo fades slowly from the scene into credits palette _screen->fadeIn(creditsImages._palette, 3); delete stream; // Save a copy of the screen background for use in drawing each credit frame _screen->_backBuffer1.SHblitFrom(*_screen); // Loop for showing the credits for(int idx = 0; idx < 600 && !_events->kbHit() && !shouldQuit(); ++idx) { // Copy the entire screen background before writing text _screen->SHblitFrom(_screen->_backBuffer1); // Write the text appropriate for the next frame if (idx < 400) _screen->SHtransBlitFrom(creditsImages[0], Common::Point(10, 200 - idx), false, 0); if (idx > 200) _screen->SHtransBlitFrom(creditsImages[1], Common::Point(10, 400 - idx), false, 0); // Don't show credit text on the top and bottom ten rows of the screen _screen->SHblitFrom(_screen->_backBuffer1, Common::Point(0, 0), Common::Rect(0, 0, _screen->width(), 10)); _screen->SHblitFrom(_screen->_backBuffer1, Common::Point(0, _screen->height() - 10), Common::Rect(0, _screen->height() - 10, _screen->width(), _screen->height())); _events->delay(100); } return true; } // 3DO variant bool ScalpelEngine::show3DOSplash() { // 3DO EA Splash screen ImageFile3DO titleImage_3DOSplash("3DOSplash.cel", kImageFile3DOType_Cel); _screen->SHtransBlitFrom(titleImage_3DOSplash[0]._frame, Common::Point(0, -20)); bool finished = _events->delay(3000, true); if (finished) { _screen->clear(); finished = _events->delay(500, true); } if (finished) { // EA logo movie play3doMovie("EAlogo.stream", Common::Point(20, 0)); } // Always clear screen _screen->clear(); return finished; } bool ScalpelEngine::showCityCutscene3DO() { Scalpel3DOScreen &screen = *(Scalpel3DOScreen *)_screen; _animation->_soundLibraryFilename = "TITLE.SND"; screen.clear(); bool finished = _events->delay(2500, true); if (finished) { finished = _events->delay(2500, true); // Play intro music _music->loadSong("prolog"); // Loop rain.aiff until the Sherlock logo fades away. // TODO: The volume is just a guess. _sound->playAiff("prologue/sounds/rain.aiff", 15, true); // Fade screen to grey screen._backBuffer1.clear(0xCE59); // RGB565: 25, 50, 25 (grey) screen.fadeIntoScreen3DO(2); } if (finished) { finished = _music->waitUntilMSec(3400, 0, 0, 3400); } if (finished) { screen._backBuffer1.clear(0); // fill backbuffer with black to avoid issues during fade from white finished = _animation->play3DO("26open1", true, 1, true, 2); } if (finished) { screen._backBuffer2.SHblitFrom(screen._backBuffer1); // "London, England" ImageFile3DO titleImage_London("title2a.cel", kImageFile3DOType_Cel); screen._backBuffer1.SHtransBlitFrom(titleImage_London[0]._frame, Common::Point(30, 50)); screen.fadeIntoScreen3DO(1); finished = _events->delay(1500, true); if (finished) { // "November, 1888" ImageFile3DO titleImage_November("title2b.cel", kImageFile3DOType_Cel); screen._backBuffer1.SHtransBlitFrom(titleImage_November[0]._frame, Common::Point(100, 100)); screen.fadeIntoScreen3DO(1); finished = _music->waitUntilMSec(14700, 0, 0, 5000); } if (finished) { // Restore screen _screen->_backBuffer1.SHblitFrom(screen._backBuffer2); _screen->SHblitFrom(screen._backBuffer1); } } if (finished) finished = _animation->play3DO("26open2", true, 1, false, 2); if (finished) { // "Sherlock Holmes" (title) ImageFile3DO titleImage_SherlockHolmesTitle("title1ab.cel", kImageFile3DOType_Cel); screen._backBuffer1.SHtransBlitFrom(titleImage_SherlockHolmesTitle[0]._frame, Common::Point(34, 5)); // Blend in screen.fadeIntoScreen3DO(2); finished = _events->delay(500, true); // Title should fade in, Copyright should be displayed a bit after that if (finished) { ImageFile3DO titleImage_Copyright("title1c.cel", kImageFile3DOType_Cel); screen.SHtransBlitFrom(titleImage_Copyright[0]._frame, Common::Point(20, 190)); finished = _events->delay(3500, true); } } if (finished) finished = _music->waitUntilMSec(33600, 0, 0, 2000); _sound->stopAiff(); if (finished) { // Fade to black screen._backBuffer1.clear(); screen.fadeIntoScreen3DO(3); } if (finished) { // "In the alley behind the Regency Theatre..." ImageFile3DO titleImage_InTheAlley("title1d.cel", kImageFile3DOType_Cel); screen._backBuffer1.SHtransBlitFrom(titleImage_InTheAlley[0]._frame, Common::Point(72, 51)); // Fade in screen.fadeIntoScreen3DO(4); finished = _music->waitUntilMSec(39900, 0, 0, 2500); // Fade out screen._backBuffer1.clear(); screen.fadeIntoScreen3DO(4); } return finished; } bool ScalpelEngine::showAlleyCutscene3DO() { Scalpel3DOScreen &screen = *(Scalpel3DOScreen *)_screen; bool finished = _music->waitUntilMSec(43500, 0, 0, 1000); if (finished) finished = _animation->play3DO("27PRO1", true, 1, false, 2); if (finished) { // Fade out... screen._backBuffer1.clear(); screen.fadeIntoScreen3DO(3); finished = _music->waitUntilMSec(67100, 0, 0, 1000); // 66700 } if (finished) finished = _animation->play3DO("27PRO2", true, 1, false, 2); if (finished) finished = _music->waitUntilMSec(76000, 0, 0, 1000); if (finished) { // Show screaming victim ImageFile3DO titleImage_ScreamingVictim("scream.cel", kImageFile3DOType_Cel); screen.clear(); screen.SHtransBlitFrom(titleImage_ScreamingVictim[0]._frame, Common::Point(0, 0)); // Play "scream.aiff" if (_sound->_voices) _sound->playSound("prologue/sounds/scream.aiff", WAIT_RETURN_IMMEDIATELY, 100); finished = _music->waitUntilMSec(81600, 0, 0, 6000); } if (finished) { // Fade out screen._backBuffer1.clear(); screen.fadeIntoScreen3DO(5); finished = _music->waitUntilMSec(84400, 0, 0, 2000); } if (finished) finished = _animation->play3DO("27PRO3", true, 1, false, 2); if (finished) { // Fade out screen._backBuffer1.clear(); screen.fadeIntoScreen3DO(5); } if (finished) { // "Early the following morning on Baker Street..." ImageFile3DO titleImage_EarlyTheFollowingMorning("title3.cel", kImageFile3DOType_Cel); screen._backBuffer1.SHtransBlitFrom(titleImage_EarlyTheFollowingMorning[0]._frame, Common::Point(35, 51)); // Fade in screen.fadeIntoScreen3DO(4); finished = _music->waitUntilMSec(96700, 0, 0, 3000); } return finished; } bool ScalpelEngine::showStreetCutscene3DO() { Scalpel3DOScreen &screen = *(Scalpel3DOScreen *)_screen; bool finished = true; if (finished) { // fade out "Early the following morning..." screen._backBuffer1.clear(); screen.fadeIntoScreen3DO(4); // wait for music a bit finished = _music->waitUntilMSec(100300, 0, 0, 1000); } if (finished) finished = _animation->play3DO("14KICK", true, 1, false, 2); // note: part of the constable is sticking to the door during the following // animation, when he walks away. This is a bug of course, but it actually happened on 3DO! // I'm not sure if it happens because the door is pure black (0, 0, 0) and it's because // of transparency - or if the animation itself is bad. We will definitely have to adjust // the animation data to fix it. if (finished) finished = _animation->play3DO("14NOTE", true, 1, false, 3); if (finished) { // Fade out screen._backBuffer1.clear(); screen.fadeIntoScreen3DO(4); } return finished; } bool ScalpelEngine::showOfficeCutscene3DO() { bool finished = _music->waitUntilMSec(151000, 0, 0, 1000); if (finished) finished = _animation->play3DO("COFF1", true, 1, false, 3); if (finished) finished = _animation->play3DO("COFF2", true, 1, false, 3); if (finished) finished = _music->waitUntilMSec(182400, 0, 0, 1000); if (finished) { // Show the note ImageFile3DO titleImage_CoffeeNote("note.cel", kImageFile3DOType_Cel); _screen->clear(); _screen->SHtransBlitFrom(titleImage_CoffeeNote[0]._frame, Common::Point(0, 0)); if (_sound->_voices) { finished = _sound->playSound("prologue/sounds/note.aiff", WAIT_KBD_OR_FINISH); } else finished = _events->delay(19000); if (finished) finished = _music->waitUntilMSec(218800, 0, 0, 1000); // Fade out _screen->clear(); } if (finished) finished = _music->waitUntilMSec(222200, 0, 0, 1000); if (finished) finished = _animation->play3DO("COFF3", true, 1, false, 3); if (finished) finished = _animation->play3DO("COFF4", true, 1, false, 3); if (finished) { finished = _music->waitUntilMSec(244500, 0, 0, 2000); // TODO: Brighten the image, possibly by doing a partial fade // to white. _screen->_backBuffer2.SHblitFrom(_screen->_backBuffer1); for (int nr = 1; finished && nr <= 4; nr++) { char filename[15]; sprintf(filename, "credits%d.cel", nr); ImageFile3DO *creditsImage = new ImageFile3DO(filename, kImageFile3DOType_Cel); ImageFrame *creditsFrame = &(*creditsImage)[0]; for (int i = 0; finished && i < 200 + creditsFrame->_height; i++) { _screen->SHblitFrom(_screen->_backBuffer2); _screen->SHtransBlitFrom(creditsFrame->_frame, Common::Point((320 - creditsFrame->_width) / 2, 200 - i)); if (!_events->delay(70, true)) finished = false; } delete creditsImage; } } return finished; } void ScalpelEngine::loadInventory() { ScalpelFixedText &fixedText = *(ScalpelFixedText *)_fixedText; Inventory &inv = *_inventory; Common::String fixedText_Message = fixedText.getText(kFixedText_InitInventory_Message); Common::String fixedText_HolmesCard = fixedText.getText(kFixedText_InitInventory_HolmesCard); Common::String fixedText_Tickets = fixedText.getText(kFixedText_InitInventory_Tickets); Common::String fixedText_CuffLink = fixedText.getText(kFixedText_InitInventory_CuffLink); Common::String fixedText_WireHook = fixedText.getText(kFixedText_InitInventory_WireHook); Common::String fixedText_Note = fixedText.getText(kFixedText_InitInventory_Note); Common::String fixedText_OpenWatch = fixedText.getText(kFixedText_InitInventory_OpenWatch); Common::String fixedText_Paper = fixedText.getText(kFixedText_InitInventory_Paper); Common::String fixedText_Letter = fixedText.getText(kFixedText_InitInventory_Letter); Common::String fixedText_Tarot = fixedText.getText(kFixedText_InitInventory_Tarot); Common::String fixedText_OrnateKey = fixedText.getText(kFixedText_InitInventory_OrnateKey); Common::String fixedText_PawnTicket = fixedText.getText(kFixedText_InitInventory_PawnTicket); // Initial inventory inv._holdings = 2; inv.push_back(InventoryItem(0, "Message", fixedText_Message, "_ITEM03A")); inv.push_back(InventoryItem(0, "Holmes Card", fixedText_HolmesCard, "_ITEM07A")); // Hidden items inv.push_back(InventoryItem(95, "Tickets", fixedText_Tickets, "_ITEM10A")); inv.push_back(InventoryItem(138, "Cuff Link", fixedText_CuffLink, "_ITEM04A")); inv.push_back(InventoryItem(138, "Wire Hook", fixedText_WireHook, "_ITEM06A")); inv.push_back(InventoryItem(150, "Note", fixedText_Note, "_ITEM13A")); inv.push_back(InventoryItem(481, "Open Watch", fixedText_OpenWatch, "_ITEM62A")); inv.push_back(InventoryItem(481, "Paper", fixedText_Paper, "_ITEM44A")); inv.push_back(InventoryItem(532, "Letter", fixedText_Letter, "_ITEM68A")); inv.push_back(InventoryItem(544, "Tarot", fixedText_Tarot, "_ITEM71A")); inv.push_back(InventoryItem(544, "Ornate Key", fixedText_OrnateKey, "_ITEM70A")); inv.push_back(InventoryItem(586, "Pawn ticket", fixedText_PawnTicket, "_ITEM16A")); } void ScalpelEngine::showLBV(const Common::String &filename) { Common::SeekableReadStream *stream = _res->load(filename, "title.lib"); ImageFile images(*stream); delete stream; _screen->setPalette(images._palette); _screen->_backBuffer1.SHblitFrom(images[0]); _screen->verticalTransition(); } void ScalpelEngine::startScene() { if (_scene->_goToScene == OVERHEAD_MAP || _scene->_goToScene == OVERHEAD_MAP2) { // Show the map if (_music->_musicOn && _music->loadSong(100)) _music->startSong(); _scene->_goToScene = _map->show(); _music->freeSong(); _people->_savedPos = Common::Point(-1, -1); _people->_savedPos._facing = -1; } // Some rooms are prologue cutscenes, rather than normal game scenes. These are: // 2: Blackwood's capture // 52: Rescuing Anna // 53: Moorehead's death / subway train // 55: Fade out and exit // 70: Brumwell suicide switch (_scene->_goToScene) { case BLACKWOOD_CAPTURE: case RESCUE_ANNA: case MOOREHEAD_DEATH: case BRUMWELL_SUICIDE: if (_music->_musicOn && _music->loadSong(_scene->_goToScene)) _music->startSong(); switch (_scene->_goToScene) { case BLACKWOOD_CAPTURE: // Blackwood's capture _res->addToCache("final2.vda", "epilogue.lib"); _res->addToCache("final2.vdx", "epilogue.lib"); _animation->play("final1", false, 1, 3, true, 4); _animation->play("final2", false, 1, 0, false, 4); break; case RESCUE_ANNA: // Rescuing Anna _res->addToCache("finalr2.vda", "epilogue.lib"); _res->addToCache("finalr2.vdx", "epilogue.lib"); _res->addToCache("finale1.vda", "epilogue.lib"); _res->addToCache("finale1.vdx", "epilogue.lib"); _res->addToCache("finale2.vda", "epilogue.lib"); _res->addToCache("finale2.vdx", "epilogue.lib"); _res->addToCache("finale3.vda", "epilogue.lib"); _res->addToCache("finale3.vdx", "epilogue.lib"); _res->addToCache("finale4.vda", "EPILOG2.lib"); _res->addToCache("finale4.vdx", "EPILOG2.lib"); _animation->play("finalr1", false, 1, 3, true, 4); _animation->play("finalr2", false, 1, 0, false, 4); if (!_res->isInCache("finale2.vda")) { // Finale file isn't cached _res->addToCache("finale2.vda", "epilogue.lib"); _res->addToCache("finale2.vdx", "epilogue.lib"); _res->addToCache("finale3.vda", "epilogue.lib"); _res->addToCache("finale3.vdx", "epilogue.lib"); _res->addToCache("finale4.vda", "EPILOG2.lib"); _res->addToCache("finale4.vdx", "EPILOG2.lib"); } _animation->play("finale1", false, 1, 0, false, 4); _animation->play("finale2", false, 1, 0, false, 4); _animation->play("finale3", false, 1, 0, false, 4); _useEpilogue2 = true; _animation->play("finale4", false, 1, 0, false, 4); _useEpilogue2 = false; break; case MOOREHEAD_DEATH: // Moorehead's death / subway train _res->addToCache("SUBWAY2.vda", "epilogue.lib"); _res->addToCache("SUBWAY2.vdx", "epilogue.lib"); _res->addToCache("SUBWAY3.vda", "epilogue.lib"); _res->addToCache("SUBWAY3.vdx", "epilogue.lib"); _animation->play("SUBWAY1", false, 1, 3, true, 4); _animation->play("SUBWAY2", false, 1, 0, false, 4); _animation->play("SUBWAY3", false, 1, 0, false, 4); // Set fading to direct fade temporary so the transition goes quickly. _scene->_tempFadeStyle = _screen->_fadeStyle ? 257 : 256; _screen->_fadeStyle = false; break; case BRUMWELL_SUICIDE: // Brumwell suicide _animation->play("suicid", false, 1, 3, true, 4); break; default: break; } // Except for the Moorehead Murder scene, fade to black first if (_scene->_goToScene != MOOREHEAD_DEATH) { _events->wait(40); _screen->fadeToBlack(3); } switch (_scene->_goToScene) { case 52: _scene->_goToScene = LAWYER_OFFICE; // Go to the Lawyer's Office _map->_bigPos = Common::Point(0, 0); // Overland scroll position _map->_overPos = Common::Point(22900 - 600, 9400 + 900); // Overland position _map->_oldCharPoint = LAWYER_OFFICE; break; case 53: _scene->_goToScene = STATION; // Go to St. Pancras Station _map->_bigPos = Common::Point(0, 0); // Overland scroll position _map->_overPos = Common::Point(32500 - 600, 3000 + 900); // Overland position _map->_oldCharPoint = STATION; break; default: _scene->_goToScene = BAKER_STREET; // Back to Baker st. _map->_bigPos = Common::Point(0, 0); // Overland scroll position _map->_overPos = Common::Point(14500 - 600, 8400 + 900); // Overland position _map->_oldCharPoint = BAKER_STREET; break; } // Free any song from the previous scene _music->freeSong(); break; case EXIT_GAME: // Exit game _screen->fadeToBlack(3); quitGame(); return; default: break; } _events->setCursor(ARROW); if (_scene->_goToScene == 99) { // Darts Board minigame _darts->playDarts(); _mapResult = _scene->_goToScene = PUB_INTERIOR; } _mapResult = _scene->_goToScene; } void ScalpelEngine::eraseBrumwellMirror() { Common::Point pt((*_people)[HOLMES]._position.x / FIXED_INT_MULTIPLIER, (*_people)[HOLMES]._position.y / FIXED_INT_MULTIPLIER); // If player is in range of the mirror, then restore background from the secondary back buffer if (Common::Rect(70, 100, 200, 200).contains(pt)) { _screen->_backBuffer1.SHblitFrom(_screen->_backBuffer2, Common::Point(137, 18), Common::Rect(137, 18, 184, 74)); } } void ScalpelEngine::doBrumwellMirror() { People &people = *_people; Person &player = people[HOLMES]; Common::Point pt((*_people)[HOLMES]._position.x / FIXED_INT_MULTIPLIER, (*_people)[HOLMES]._position.y / FIXED_INT_MULTIPLIER); int frameNum = player._walkSequences[player._sequenceNumber][player._frameNumber] + player._walkSequences[player._sequenceNumber][0] - 2; switch ((*_people)[HOLMES]._sequenceNumber) { case WALK_DOWN: frameNum -= 7; break; case WALK_UP: frameNum += 7; break; case WALK_DOWNRIGHT: frameNum += 7; break; case WALK_UPRIGHT: frameNum -= 7; break; case WALK_DOWNLEFT: frameNum += 7; break; case WALK_UPLEFT: frameNum -= 7; break; case STOP_DOWN: frameNum -= 10; break; case STOP_UP: frameNum += 11; break; case STOP_DOWNRIGHT: frameNum -= 15; break; case STOP_DOWNLEFT: frameNum -= 15; break; case STOP_UPRIGHT: case STOP_UPLEFT: frameNum += 15; if (frameNum == 55) frameNum = 54; break; default: break; } if (Common::Rect(80, 100, 145, 138).contains(pt)) { // Get the frame of Sherlock to draw ImageFrame &imageFrame = (*people[HOLMES]._images)[frameNum]; // Draw the mirror image of Holmes bool flipped = people[HOLMES]._sequenceNumber == WALK_LEFT || people[HOLMES]._sequenceNumber == STOP_LEFT || people[HOLMES]._sequenceNumber == WALK_UPRIGHT || people[HOLMES]._sequenceNumber == STOP_UPRIGHT || people[HOLMES]._sequenceNumber == WALK_DOWNLEFT || people[HOLMES]._sequenceNumber == STOP_DOWNLEFT; _screen->_backBuffer1.SHtransBlitFrom(imageFrame, pt + Common::Point(38, -imageFrame._frame.h - 25), flipped); // Redraw the mirror borders to prevent the drawn image of Holmes from appearing outside of the mirror _screen->_backBuffer1.SHblitFrom(_screen->_backBuffer2, Common::Point(114, 18), Common::Rect(114, 18, 137, 114)); _screen->_backBuffer1.SHblitFrom(_screen->_backBuffer2, Common::Point(137, 70), Common::Rect(137, 70, 142, 114)); _screen->_backBuffer1.SHblitFrom(_screen->_backBuffer2, Common::Point(142, 71), Common::Rect(142, 71, 159, 114)); _screen->_backBuffer1.SHblitFrom(_screen->_backBuffer2, Common::Point(159, 72), Common::Rect(159, 72, 170, 116)); _screen->_backBuffer1.SHblitFrom(_screen->_backBuffer2, Common::Point(170, 73), Common::Rect(170, 73, 184, 114)); _screen->_backBuffer1.SHblitFrom(_screen->_backBuffer2, Common::Point(184, 18), Common::Rect(184, 18, 212, 114)); } } void ScalpelEngine::flushBrumwellMirror() { Common::Point pt((*_people)[HOLMES]._position.x / FIXED_INT_MULTIPLIER, (*_people)[HOLMES]._position.y / FIXED_INT_MULTIPLIER); // If player is in range of the mirror, then draw the entire mirror area to the screen if (Common::Rect(70, 100, 200, 200).contains(pt)) _screen->slamArea(137, 18, 47, 56); } void ScalpelEngine::showScummVMSaveDialog() { GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true); int slot = dialog->runModalWithCurrentTarget(); if (slot >= 0) { Common::String desc = dialog->getResultString(); saveGameState(slot, desc); } delete dialog; } void ScalpelEngine::showScummVMRestoreDialog() { GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false); int slot = dialog->runModalWithCurrentTarget(); if (slot >= 0) { loadGameState(slot); } delete dialog; } bool ScalpelEngine::play3doMovie(const Common::String &filename, const Common::Point &pos, bool isPortrait) { Scalpel3DOScreen &screen = *(Scalpel3DOScreen *)_screen; Scalpel3DOMovieDecoder *videoDecoder = new Scalpel3DOMovieDecoder(); Graphics::ManagedSurface tempSurface; Common::Point framePos(pos.x, pos.y); ImageFile3DO *frameImageFile = nullptr; ImageFrame *frameImage = nullptr; bool frameShown = false; if (!videoDecoder->loadFile(filename)) { warning("Scalpel3DOMoviePlay: could not open '%s'", filename.c_str()); return false; } bool halfSize = isPortrait && !_isScreenDoubled; if (isPortrait) { // only for portrait videos, not for EA intro logo and such if ((framePos.x >= 8) && (framePos.y >= 8)) { // safety check framePos.x -= 8; framePos.y -= 8; // frame is 8 pixels on left + top, and 7 pixels on right + bottom } frameImageFile = new ImageFile3DO("vidframe.cel", kImageFile3DOType_Cel); frameImage = &(*frameImageFile)[0]; } bool skipVideo = false; //byte bytesPerPixel = videoDecoder->getPixelFormat().bytesPerPixel; uint16 width = videoDecoder->getWidth(); uint16 height = videoDecoder->getHeight(); //uint16 pitch = videoDecoder->getWidth() * bytesPerPixel; _events->clearEvents(); videoDecoder->start(); // If we're to show the movie at half-size, we'll need a temporary intermediate surface if (halfSize) tempSurface.create(width / 2, height / 2); while (!shouldQuit() && !videoDecoder->endOfVideo() && !skipVideo) { if (videoDecoder->needsUpdate()) { const Graphics::Surface *frame = videoDecoder->decodeNextFrame(); if (frame) { if (halfSize) { // movies are 152 x 200 // Downscale, but calculate average color out of 4 pixels and put that average into the target pixel // TODO: 3DO actually did pixel weighting, exact details about this are unknown // It's also unknown what 3DO exactly did for interpolation // and it's also unknown atm if the CinePak videos contained pixel weighting information if ((height & 1) || (width & 1)) { error("Scalpel3DOMoviePlay: critical error, half-size requested on video with uneven height/width"); } for (int downscaleY = 0; downscaleY < height / 2; downscaleY++) { const uint16 *downscaleSource1Ptr = (const uint16 *)frame->getBasePtr(0, downscaleY * 2); const uint16 *downscaleSource2Ptr = (const uint16 *)frame->getBasePtr(0, (downscaleY * 2) + 1); uint16 *downscaleTargetPtr = (uint16 *)tempSurface.getBasePtr(0, downscaleY); for (int downscaleX = 0; downscaleX < width / 2; downscaleX++) { // get 4 pixel colors uint16 downscaleColor = *downscaleSource1Ptr; uint32 downscaleRed = downscaleColor >> 11; // 5 bits uint32 downscaleGreen = (downscaleColor >> 5) & 0x3f; // 6 bits uint32 downscaleBlue = downscaleColor & 0x1f; downscaleSource1Ptr++; downscaleColor = *downscaleSource1Ptr; downscaleRed += downscaleColor >> 11; downscaleGreen += (downscaleColor >> 5) & 0x3f; downscaleBlue += downscaleColor & 0x1f; downscaleColor = *downscaleSource2Ptr; downscaleRed += downscaleColor >> 11; downscaleGreen += (downscaleColor >> 5) & 0x3f; downscaleBlue += downscaleColor & 0x1f; downscaleSource2Ptr++; downscaleColor = *downscaleSource2Ptr; downscaleRed += downscaleColor >> 11; downscaleGreen += (downscaleColor >> 5) & 0x3f; downscaleBlue += downscaleColor & 0x1f; // Divide colors by 4, so that we get the average downscaleRed = downscaleRed >> 2; downscaleGreen = downscaleGreen >> 2; downscaleBlue = downscaleBlue >> 2; // write new color to target pixel downscaleColor = (downscaleRed << 11) | (downscaleGreen << 5) | downscaleBlue; *downscaleTargetPtr = downscaleColor; downscaleSource1Ptr++; downscaleSource2Ptr++; downscaleTargetPtr++; } } // Point the drawing frame to the temporary surface frame = &tempSurface.rawSurface(); } if (isPortrait && !frameShown) { // Draw the frame (not the frame of the video, but a frame around the video) itself _screen->SHtransBlitFrom(frameImage->_frame, framePos); frameShown = true; } if (isPortrait && !halfSize) { screen.rawBlitFrom(*frame, Common::Point(pos.x * 2, pos.y * 2)); } else { _screen->SHblitFrom(*frame, pos); } _screen->update(); } } _events->pollEventsAndWait(); _events->setButtonState(); if (_events->kbHit()) { Common::KeyState keyState = _events->getKey(); if (keyState.keycode == Common::KEYCODE_ESCAPE) skipVideo = true; } else if (_events->_pressed) { skipVideo = true; } } if (halfSize) tempSurface.free(); videoDecoder->close(); delete videoDecoder; if (isPortrait) { delete frameImageFile; } // Restore scene screen._backBuffer1.SHblitFrom(screen._backBuffer2); _scene->updateBackground(); screen.slamArea(0, 0, screen.width(), CONTROLS_Y); return !skipVideo; } } // End of namespace Scalpel } // End of namespace Sherlock