diff options
Diffstat (limited to 'engines/sherlock/scalpel/scalpel.cpp')
-rw-r--r-- | engines/sherlock/scalpel/scalpel.cpp | 1159 |
1 files changed, 1159 insertions, 0 deletions
diff --git a/engines/sherlock/scalpel/scalpel.cpp b/engines/sherlock/scalpel/scalpel.cpp new file mode 100644 index 0000000000..faead3be07 --- /dev/null +++ b/engines/sherlock/scalpel/scalpel.cpp @@ -0,0 +1,1159 @@ +/* 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 "sherlock/scalpel/scalpel.h" +#include "sherlock/scalpel/scalpel_map.h" +#include "sherlock/scalpel/scalpel_people.h" +#include "sherlock/scalpel/scalpel_scene.h" +#include "sherlock/scalpel/tsage/logo.h" +#include "sherlock/sherlock.h" +#include "sherlock/music.h" +#include "sherlock/animation.h" +// for 3DO +#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; + const char *name; + byte stillSequences[MAX_TALK_SEQUENCES]; + byte talkSequences[MAX_TALK_SEQUENCES]; +}; + +const PeopleData PEOPLE_DATA[MAX_PEOPLE] = { + { "HOLM", "Sherlock Holmes", { 1, 0, 0 }, { 1, 0, 0 } }, + { "WATS", "Dr. Watson", { 6, 0, 0 }, { 5, 5, 6, 7, 8, 7, 8, 6, 0, 0 } }, + { "LEST", "Inspector Lestrade", { 4, 0, 0 }, { 2, 0, 0 } }, + { "CON1", "Constable O'Brien", { 2, 0, 0 }, { 1, 0, 0 } }, + { "CON2", "Constable Lewis", { 2, 0, 0 }, { 1, 0, 0 } }, + { "SHEI", "Sheila Parker", { 2, 0, 0 }, { 2, 3, 0, 0 } }, + { "HENR", "Henry Carruthers", { 3, 0, 0 }, { 3, 0, 0 } }, + { "LESL", "Lesley", { 9, 0, 0 }, { 1, 2, 3, 2, 1, 2, 3, 0, 0 } }, + { "USH1", "An Usher", { 13, 0, 0 }, { 13, 14, 0, 0 } }, + { "USH2", "An Usher", { 2, 0, 0 }, { 2, 0, 0 } }, + { "FRED", "Fredrick Epstein", { 4, 0, 0 }, { 1, 2, 3, 4, 3, 4, 3, 2, 0, 0 } }, + { "WORT", "Mrs. Worthington", { 9, 0, 0 }, { 8, 0, 0 } }, + { "COAC", "The Coach", { 2, 0, 0 }, { 1, 2, 3, 4, 5, 4, 3, 2, 0, 0 } }, + { "PLAY", "A Player", { 8, 0, 0 }, { 7, 8, 0, 0 } }, + { "WBOY", "Tim", { 13, 0, 0 }, { 12, 13, 0, 0 } }, + { "JAME", "James Sanders", { 6, 0, 0 }, { 3, 4, 0, 0 } }, + { "BELL", "Belle", { 1, 0, 0 }, { 4, 5, 0, 0 } }, + { "GIRL", "Cleaning Girl", { 20, 0, 0 }, { 14, 15, 16, 17, 18, 19, 20, 20, 20, 0, 0 } }, + { "EPST", "Fredrick Epstein", { 17, 0, 0 }, { 16, 17, 18, 18, 18, 17, 17, 0, 0 } }, + { "WIGG", "Wiggins", { 3, 0, 0 }, { 2, 3, 0, 0 } }, + { "PAUL", "Paul", { 2, 0, 0 }, { 1, 2, 0, 0 } }, + { "BART", "The Bartender", { 1, 0, 0 }, { 1, 0, 0 } }, + { "DIRT", "A Dirty Drunk", { 1, 0, 0 }, { 1, 0, 0 } }, + { "SHOU", "A Shouting Drunk", { 1, 0, 0 }, { 1, 0, 0 } }, + { "STAG", "A Staggering Drunk", { 1, 0, 0 }, { 1, 0, 0 } }, + { "BOUN", "The Bouncer", { 1, 0, 0 }, { 1, 0, 0 } }, + { "SAND", "James Sanders", { 6, 0, 0 }, { 5, 6, 0, 0 } }, + { "CORO", "The Coroner", { 6, 0, 0 }, { 4, 5, 0, 0 } }, + { "EQUE", "Reginald Snipes", { 1, 0, 0 }, { 1, 0, 0 } }, + { "GEOR", "George Blackwood", { 1, 0, 0 }, { 1, 0, 0 } }, + { "LARS", "Lars", { 7, 0, 0 }, { 5, 6, 0, 0 } }, + { "PARK", "Sheila Parker", { 1, 0, 0 }, { 1, 0, 0 } }, + { "CHEM", "The Chemist", { 8, 0, 0 }, { 8, 9, 0, 0 } }, + { "GREG", "Inspector Gregson", { 6, 0, 0 }, { 5, 6, 0, 0 } }, + { "LAWY", "Jacob Farthington", { 1, 0, 0 }, { 1, 0, 0 } }, + { "MYCR", "Mycroft", { 1, 0, 0 }, { 1, 0, 0 } }, + { "SHER", "Old Sherman", { 7, 0, 0 }, { 7, 8, 0, 0 } }, + { "CHMB", "Richard", { 1, 0, 0 }, { 1, 0, 0 } }, + { "BARM", "The Barman", { 1, 0, 0 }, { 1, 0, 0 } }, + { "DAND", "A Dandy Player", { 1, 0, 0 }, { 1, 0, 0 } }, + { "ROUG", "A Rough-looking Player", { 1, 0, 0 }, { 1, 0, 0 } }, + { "SPEC", "A Spectator", { 1, 0, 0 }, { 1, 0, 0 } }, + { "HUNT", "Robert Hunt", { 1, 0, 0 }, { 1, 0, 0 } }, + { "VIOL", "Violet", { 3, 0, 0 }, { 3, 4, 0, 0 } }, + { "PETT", "Pettigrew", { 1, 0, 0 }, { 1, 0, 0 } }, + { "APPL", "Augie", { 8, 0, 0 }, { 14, 15, 0, 0 } }, + { "ANNA", "Anna Carroway", { 16, 0, 0 }, { 3, 4, 5, 6, 0, 0 } }, + { "GUAR", "A Guard", { 1, 0, 0 }, { 4, 5, 6, 0, 0 } }, + { "ANTO", "Antonio Caruso", { 8, 0, 0 }, { 7, 8, 0, 0 } }, + { "TOBY", "Toby the Dog", { 1, 0, 0 }, { 1, 0, 0 } }, + { "KING", "Simon Kingsley", { 13, 0, 0 }, { 13, 14, 0, 0 } }, + { "ALFR", "Alfred", { 2, 0, 0 }, { 2, 3, 0, 0 } }, + { "LADY", "Lady Brumwell", { 1, 0, 0 }, { 3, 4, 0, 0 } }, + { "ROSA", "Madame Rosa", { 1, 0, 0 }, { 1, 30, 0, 0 } }, + { "LADB", "Lady Brumwell", { 1, 0, 0 }, { 3, 4, 0, 0 } }, + { "MOOR", "Joseph Moorehead", { 1, 0, 0 }, { 1, 0, 0 } }, + { "BEAL", "Mrs. Beale", { 5, 0, 0 }, { 14, 15, 16, 17, 18, 19, 20, 0, 0 } }, + { "LION", "Felix", { 1, 0, 0 }, { 1, 0, 0 } }, + { "HOLL", "Hollingston", { 1, 0, 0 }, { 1, 0, 0 } }, + { "CALL", "Constable Callaghan", { 1, 0, 0 }, { 1, 0, 0 } }, + { "JERE", "Sergeant Duncan", { 2, 0, 0 }, { 1, 1, 2, 2, 0, 0 } }, + { "LORD", "Lord Brumwell", { 1, 0, 0 }, { 9, 10, 0, 0 } }, + { "NIGE", "Nigel Jaimeson", { 1, 0, 0 }, { 1, 2, 0, 138, 3, 4, 0, 138, 0, 0 } }, + { "JONA", "Jonas", { 1, 0, 0 }, { 1, 8, 0, 0 } }, + { "DUGA", "Constable Dugan", { 1, 0, 0 }, { 1, 0, 0 } }, + { "INSP", "Inspector Lestrade", { 4, 0, 0 }, { 2, 0, 0 } } +}; + +/*----------------------------------------------------------------*/ + +ScalpelEngine::ScalpelEngine(OSystem *syst, const SherlockGameDescription *gameDesc) : + SherlockEngine(syst, gameDesc) { + _darts = nullptr; + _mapResult = 0; +} + +ScalpelEngine::~ScalpelEngine() { + delete _darts; +} + +void ScalpelEngine::initialize() { + // 3DO actually uses RGB555, but some platforms of ours only support RGB565, so we use that + + if (getPlatform() == Common::kPlatform3DO) { + const Graphics::PixelFormat pixelFormatRGB565 = Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0); + // 320x200 16-bit RGB565 for 3DO support + initGraphics(320, 200, false, &pixelFormatRGB565); + } else { + // 320x200 palettized + initGraphics(320, 200, false); + } + + // 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 + for (int idx = 0; idx < MAX_PEOPLE; ++idx) + _people->_characters.push_back(PersonData(PEOPLE_DATA[idx].name, 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; + + if (getPlatform() == Common::kPlatform3DO) { + show3DOSplash(); + + finished = showCityCutscene3DO(); + if (finished) + finished = showAlleyCutscene3DO(); + if (finished) + finished = showStreetCutscene3DO(); + if (finished) + showOfficeCutscene3DO(); + + _events->clearEvents(); + _music->stopMusic(); + return; + } + + TsAGE::Logo::show(this); + finished = showCityCutscene(); + if (finished) + finished = showAlleyCutscene(); + if (finished) + finished = showStreetCutscene(); + if (finished) + showOfficeCutscene(); + + _events->clearEvents(); + _music->stopMusic(); +} + +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->playMusic("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.blitFrom(*_screen); + _screen->_backBuffer2.blitFrom(*_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.transBlitFrom(titleImages_LondonNovember[0], londonPosition); + _screen->randomTransition(); + finished = _events->delay(1000, true); + + // November, 1888 + if (finished) { + _screen->_backBuffer1.transBlitFrom(titleImages_LondonNovember[1], Common::Point(100, 100)); + _screen->randomTransition(); + finished = _events->delay(5000, true); + } + + // Transition out the title + _screen->_backBuffer1.blitFrom(_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.blitFrom(*_screen); + _screen->_backBuffer2.blitFrom(*_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.transBlitFrom(titleImages_SherlockHolmesTitle[0], lostFilesPosition); + // Sherlock Holmes + _screen->_backBuffer1.transBlitFrom(titleImages_SherlockHolmesTitle[1], sherlockHolmesPosition); + // copyright + _screen->_backBuffer1.transBlitFrom(titleImages_SherlockHolmesTitle[2], copyrightPosition); + + _screen->verticalTransition(); + finished = _events->delay(4000, true); + + if (finished) { + _screen->_backBuffer1.blitFrom(_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->transBlitFrom(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->playMusic("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->transBlitFrom(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->playMusic("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->playMusic("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.blitFrom(*_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->blitFrom(_screen->_backBuffer1); + + // Write the text appropriate for the next frame + if (idx < 400) + _screen->transBlitFrom(creditsImages[0], Common::Point(10, 200 - idx), false, 0); + if (idx > 200) + _screen->transBlitFrom(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->blitFrom(_screen->_backBuffer1, Common::Point(0, 0), Common::Rect(0, 0, _screen->w(), 10)); + _screen->blitFrom(_screen->_backBuffer1, Common::Point(0, _screen->h() - 10), + Common::Rect(0, _screen->h() - 10, _screen->w(), _screen->h())); + + _events->delay(100); + } + + return true; +} + +// 3DO variant +bool ScalpelEngine::show3DOSplash() { + // 3DO EA Splash screen + ImageFile3DO titleImage_3DOSplash("3DOSplash.cel", kImageFile3DOType_Cel); + + _screen->transBlitFrom(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 + Scalpel3DOMoviePlay("EAlogo.stream", Common::Point(20, 0)); + } + + // Always clear screen + _screen->clear(); + return finished; +} + +bool ScalpelEngine::showCityCutscene3DO() { + _animation->_soundLibraryFilename = "TITLE.SND"; + + _screen->clear(); + bool finished = _events->delay(2500, true); + + // rain.aiff seems to be playing in an endless loop until + // sherlock logo fades away TODO + + if (finished) { + finished = _events->delay(2500, true); + + // Play intro music + _music->playMusic("prolog"); + + // Fade screen to grey + _screen->_backBuffer1.fill(0xCE59); // RGB565: 25, 50, 25 (grey) + _screen->fadeIntoScreen3DO(2); + } + + if (finished) { + finished = _music->waitUntilMSec(3400, 0, 0, 3400); + } + + if (finished) { + _screen->_backBuffer1.fill(0); // fill backbuffer with black to avoid issues during fade from white + finished = _animation->play3DO("26open1", true, 1, true, 2); + } + + if (finished) { + _screen->_backBuffer1.blitFrom(*_screen); // save into backbuffer 1, used for fade + _screen->_backBuffer2.blitFrom(*_screen); // save into backbuffer 2, for restoring later + + // "London, England" + ImageFile3DO titleImage_London("title2a.cel", kImageFile3DOType_Cel); + _screen->_backBuffer1.transBlitFrom(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.transBlitFrom(titleImage_November[0]._frame, Common::Point(100, 100)); + + _screen->fadeIntoScreen3DO(1); + finished = _music->waitUntilMSec(14700, 0, 0, 5000); + } + + if (finished) { + // Restore screen + _screen->blitFrom(_screen->_backBuffer2); + } + } + + if (finished) + finished = _animation->play3DO("26open2", true, 1, false, 2); + + if (finished) { + _screen->_backBuffer1.blitFrom(*_screen); // save into backbuffer 1, used for fade + + // "Sherlock Holmes" (title) + ImageFile3DO titleImage_SherlockHolmesTitle("title1ab.cel", kImageFile3DOType_Cel); + _screen->_backBuffer1.transBlitFrom(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->transBlitFrom(titleImage_Copyright[0]._frame, Common::Point(20, 190)); + finished = _events->delay(3500, true); + } + } + + if (finished) + finished = _music->waitUntilMSec(33600, 0, 0, 2000); + + 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.transBlitFrom(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() { + 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->transBlitFrom(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.transBlitFrom(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() { + 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->transBlitFrom(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->_backBuffer1.blitFrom(*_screen); + + 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->blitFrom(_screen->_backBuffer1); + _screen->transBlitFrom(creditsFrame->_frame, Common::Point((320 - creditsFrame->_width) / 2, 200 - i)); + if (!_events->delay(70, true)) + finished = false; + } + delete creditsImage; + } + } + + return finished; +} + +void ScalpelEngine::loadInventory() { + FixedText &fixedText = *_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.blitFrom(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->_hSavedPos = Common::Point(-1, -1); + _people->_hSavedFacing = -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::eraseMirror12() { + 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.blitFrom(_screen->_backBuffer2, Common::Point(137, 18), + Common::Rect(137, 18, 184, 74)); + } +} + +void ScalpelEngine::doMirror12() { + 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.transBlitFrom(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.blitFrom(_screen->_backBuffer2, Common::Point(114, 18), + Common::Rect(114, 18, 137, 114)); + _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(137, 70), + Common::Rect(137, 70, 142, 114)); + _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(142, 71), + Common::Rect(142, 71, 159, 114)); + _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(159, 72), + Common::Rect(159, 72, 170, 116)); + _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(170, 73), + Common::Rect(170, 73, 184, 114)); + _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(184, 18), + Common::Rect(184, 18, 212, 114)); + } +} + +void ScalpelEngine::flushMirror12() { + 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); +} + +} // End of namespace Scalpel + +} // End of namespace Sherlock |