mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-14 13:50:13 +00:00
1439 lines
46 KiB
C++
1439 lines
46 KiB
C++
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#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 "video/3do_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);
|
|
} 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();
|
|
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, &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(40, 380));
|
|
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];
|
|
Common::sprintf_s(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::Path &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, 2);
|
|
_animation->play("final2", false, 1, 0, false, 2);
|
|
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, 2);
|
|
_animation->play("finalr2", false, 1, 0, false, 2);
|
|
|
|
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, 2);
|
|
_animation->play("finale2", false, 1, 0, false, 2);
|
|
_animation->play("finale3", false, 1, 0, false, 2);
|
|
|
|
_useEpilogue2 = true;
|
|
_animation->play("finale4", false, 1, 0, false, 2);
|
|
_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, 2);
|
|
_animation->play("SUBWAY2", false, 1, 0, false, 2);
|
|
_animation->play("SUBWAY3", false, 1, 0, false, 2);
|
|
|
|
// 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, 2);
|
|
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::Path &filename, const Common::Point &pos, bool isPortrait) {
|
|
Scalpel3DOScreen &screen = *(Scalpel3DOScreen *)_screen;
|
|
Video::ThreeDOMovieDecoder *videoDecoder = new Video::ThreeDOMovieDecoder();
|
|
Graphics::ManagedSurface tempSurface;
|
|
|
|
ImageFile3DO *frameImageFile = nullptr;
|
|
ImageFrame *frameImage = nullptr;
|
|
bool frameShown = false;
|
|
|
|
if (!videoDecoder->loadFile(filename)) {
|
|
warning("Scalpel3DOMoviePlay: could not open '%s'", filename.toString().c_str());
|
|
delete videoDecoder;
|
|
return false;
|
|
}
|
|
|
|
Common::Point moviePos(pos.x, pos.y);
|
|
int frameWidth = 8;
|
|
|
|
bool halfSize = isPortrait && !_isScreenDoubled;
|
|
|
|
if (isPortrait) {
|
|
if (!halfSize) {
|
|
moviePos.x *= 2;
|
|
moviePos.y *= 2;
|
|
frameWidth *= 2;
|
|
}
|
|
|
|
// Safety check. Only for portrait videos, not for EA intro logo and such
|
|
if (moviePos.x < frameWidth)
|
|
moviePos.x = frameWidth;
|
|
if (moviePos.y < frameWidth)
|
|
moviePos.y = frameWidth;
|
|
|
|
frameImageFile = new ImageFile3DO("vidframe.cel", kImageFile3DOType_Cel);
|
|
frameImage = &(*frameImageFile)[0];
|
|
}
|
|
|
|
// frame is 8 pixels on left + top, and 7 pixels on right + bottom
|
|
Common::Point framePos(moviePos.x - frameWidth, moviePos.y - frameWidth);
|
|
|
|
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, videoDecoder->getPixelFormat());
|
|
|
|
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, moviePos);
|
|
} else {
|
|
_screen->SHblitFrom(*frame, moviePos);
|
|
}
|
|
|
|
_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
|