diff options
Diffstat (limited to 'engines')
317 files changed, 36077 insertions, 10128 deletions
diff --git a/engines/access/access.cpp b/engines/access/access.cpp index 0a4e519c91..56fa6c7533 100644 --- a/engines/access/access.cpp +++ b/engines/access/access.cpp @@ -52,11 +52,10 @@ AccessEngine::AccessEngine(OSystem *syst, const AccessGameDescription *gameDesc) _destIn = nullptr; _current = nullptr; _mouseMode = 0; + _playerDataCount = 0; _currentMan = 0; _currentManOld = -1; _converseMode = 0; - _startAboutBox = 0; - _startTravelBox = 0; _numAnimTimers = 0; _startup = 0; _currentCharFlag = false; @@ -97,11 +96,33 @@ AccessEngine::AccessEngine(OSystem *syst, const AccessGameDescription *gameDesc) for (int i = 0; i < 100; i++) _objectsTable[i] = nullptr; _clearSummaryFlag = false; + + for (int i = 0; i < 60; i++) + _travel[i] = 0; + _startTravelItem = _startTravelBox = 0; + for (int i = 0; i < 33; i++) + _ask[i] = 0; + _startAboutItem = _startAboutBox = 0; + _byte26CB5 = 0; + _bcnt = 0; + _boxDataStart = 0; + _boxDataEnd = false; + _boxSelectY = 0; + _boxSelectYOld = -1; + _numLines = 0; + _tempList = nullptr; + _pictureTaken = 0; + + _vidEnd = false; } AccessEngine::~AccessEngine() { delete _animation; delete _bubbleBox; + delete _helpBox; + delete _travelBox; + delete _invBox; + delete _aboutBox; delete _char; delete _debugger; delete _events; @@ -147,7 +168,18 @@ void AccessEngine::initialize() { // Create sub-objects of the engine _animation = new AnimationManager(this); - _bubbleBox = new BubbleBox(this); + _bubbleBox = new BubbleBox(this, TYPE_2, 64, 32, 130, 122, 0, 0, 0, 0, ""); + if (getGameID() == GType_MartianMemorandum) { + _helpBox = new BubbleBox(this, TYPE_1, 64, 24, 146, 122, 1, 32, 2, 76, "HELP"); + _travelBox = new BubbleBox(this, TYPE_1, 64, 32, 194, 122, 1, 24, 2, 74, "TRAVEL"); + _invBox = new BubbleBox(this, TYPE_1, 64, 32, 146, 122, 1, 32, 2, 76, "INVENTORY"); + _aboutBox = new BubbleBox(this, TYPE_1, 64, 32, 194, 122, 1, 32, 2, 76, "ASK ABOUT"); + } else { + _helpBox = nullptr; + _travelBox = nullptr; + _invBox = nullptr; + _aboutBox = nullptr; + } _char = new CharManager(this); _debugger = Debugger::init(this); _events = new EventsManager(this); @@ -416,10 +448,6 @@ void AccessEngine::playVideo(int videoNum, const Common::Point &pt) { } } -void AccessEngine::doLoadSave() { - error("TODO: doLoadSave"); -} - void AccessEngine::freeChar() { _scripts->freeScriptData(); _animation->clearTimers(); @@ -572,6 +600,36 @@ void AccessEngine::writeSavegameHeader(Common::OutSaveFile *out, AccessSavegameH out->writeUint32LE(_events->getFrameCounter()); } +void AccessEngine::SPRINTCHR(char c, int fontNum) { + warning("TODO: SPRINTCHR"); + _fonts._font1.drawChar(_screen, c, _screen->_printOrg); +} + +void AccessEngine::PRINTCHR(Common::String msg, int fontNum) { + _events->hideCursor(); + warning("TODO: PRINTCHR - Handle fontNum"); + + for (int i = 0; msg[i]; i++) { + if (!(_fonts._charSet._hi & 8)) { + _fonts._font1.drawChar(_screen, msg[i], _screen->_printOrg); + continue; + } else if (_fonts._charSet._hi & 2) { + Common::Point oldPos = _screen->_printOrg; + int oldFontLo = _fonts._charFor._lo; + + _fonts._charFor._lo = 0; + _screen->_printOrg.x++; + _screen->_printOrg.y++; + SPRINTCHR(msg[i], fontNum); + + _screen->_printOrg = oldPos; + _fonts._charFor._lo = oldFontLo; + } + SPRINTCHR(msg[i], fontNum); + } + _events->showCursor(); +} + bool AccessEngine::shouldQuitOrRestart() { return shouldQuit() || _restartFl; } diff --git a/engines/access/access.h b/engines/access/access.h index be007e0cb8..37b9fec5a5 100644 --- a/engines/access/access.h +++ b/engines/access/access.h @@ -137,6 +137,10 @@ protected: public: AnimationManager *_animation; BubbleBox *_bubbleBox; + BubbleBox *_helpBox; + BubbleBox *_travelBox; + BubbleBox *_invBox; + BubbleBox *_aboutBox; CharManager *_char; Debugger *_debugger; EventsManager *_events; @@ -173,10 +177,9 @@ public: ImageEntryList _images; int _mouseMode; + int _playerDataCount; int _currentManOld; int _converseMode; - int _startAboutBox; - int _startTravelBox; bool _currentCharFlag; bool _boxSelect; int _scale; @@ -204,6 +207,26 @@ public: uint32 _newDate; int _flags[256]; + // Fields used by MM + // TODO: Refactor + int _travel[60]; + int _ask[40]; + int _startTravelItem; + int _startTravelBox; + int _startAboutItem; + int _startAboutBox; + int _boxDataStart; + bool _boxDataEnd; + int _boxSelectY; + int _boxSelectYOld; + int _numLines; + byte _byte26CB5; + int _bcnt; + byte *_tempList; + int _pictureTaken; + // + + bool _vidEnd; bool _clearSummaryFlag; bool _cheatFl; bool _restartFl; @@ -250,8 +273,6 @@ public: void copyBF2Vid(); - void doLoadSave(); - void freeChar(); /** @@ -289,6 +310,9 @@ public: * Write out a savegame header */ void writeSavegameHeader(Common::OutSaveFile *out, AccessSavegameHeader &header); + + void SPRINTCHR(char c, int fontNum); + void PRINTCHR(Common::String msg, int fontNum); }; } // End of namespace Access diff --git a/engines/access/amazon/amazon_game.cpp b/engines/access/amazon/amazon_game.cpp index 4c9df7b8ff..7a55873d97 100644 --- a/engines/access/amazon/amazon_game.cpp +++ b/engines/access/amazon/amazon_game.cpp @@ -195,8 +195,8 @@ void AmazonEngine::initVariables() { _timers.push_back(te); } - _player->_playerX = _player->_rawPlayer.x = TRAVEL_POS[_player->_roomNumber][0]; - _player->_playerY = _player->_rawPlayer.y = TRAVEL_POS[_player->_roomNumber][1]; + _player->_playerX = _player->_rawPlayer.x = _travelPos[_player->_roomNumber][0]; + _player->_playerY = _player->_rawPlayer.y = _travelPos[_player->_roomNumber][1]; _room->_selectCommand = -1; _events->setNormalCursor(CURSOR_CROSSHAIRS); _mouseMode = 0; @@ -260,13 +260,24 @@ void AmazonEngine::doEstablish(int screenId, int estabIndex) { _screen->setIconPalette(); _screen->forceFadeIn(); - _fonts._charSet._lo = 1; - _fonts._charSet._hi = 10; - _fonts._charFor._lo = 29; - _fonts._charFor._hi = 32; + if (getGameID() == GType_MartianMemorandum) { + _fonts._charSet._lo = 1; + _fonts._charSet._hi = 10; + _fonts._charFor._lo = 0xF7; + _fonts._charFor._hi = 0xFF; + + _screen->_maxChars = 50; + _screen->_printOrg = _screen->_printStart = Common::Point(24, 18); + } else { + _fonts._charSet._lo = 1; + _fonts._charSet._hi = 10; + _fonts._charFor._lo = 29; + _fonts._charFor._hi = 32; + + _screen->_maxChars = 37; + _screen->_printOrg = _screen->_printStart = Common::Point(48, 35); + } - _screen->_maxChars = 37; - _screen->_printOrg = _screen->_printStart = Common::Point(48, 35); loadEstablish(estabIndex); uint16 msgOffset; if (!isCD()) diff --git a/engines/access/amazon/amazon_logic.cpp b/engines/access/amazon/amazon_logic.cpp index 6dffb85e5e..de53da51cd 100644 --- a/engines/access/amazon/amazon_logic.cpp +++ b/engines/access/amazon/amazon_logic.cpp @@ -326,16 +326,8 @@ void Opening::doTitle() { _vm->_buffer2.copyFrom(*_vm->_screen); _vm->_buffer1.copyFrom(*_vm->_screen); screen.forceFadeIn(); - _vm->_sound->playSound(1); - // WORKAROUND: This delay has been added to replace original game delay that - // came from loading resources, since nowadays it would be too fast to be visible - // nowadays to be visible. - _vm->_events->_vbCount = 70; - while (!_vm->shouldQuit() && _vm->_events->_vbCount > 0) - _vm->_events->pollEventsAndWait(); - if (_vm->shouldQuit()) - return; + _vm->_sound->playSound(1, true); Resource *spriteData = _vm->_files->loadFile(0, 2); _vm->_objectsTable[0] = new SpriteResource(_vm, spriteData); @@ -343,7 +335,6 @@ void Opening::doTitle() { _vm->_files->_setPaletteFlag = false; _vm->_files->loadScreen(0, 4); - _vm->_sound->playSound(1); _vm->_buffer2.copyFrom(*_vm->_screen); _vm->_buffer1.copyFrom(*_vm->_screen); @@ -356,7 +347,6 @@ void Opening::doTitle() { _vm->_buffer2.plotImage(_vm->_objectsTable[0], id, Common::Point(xp, 71)); _vm->_buffer2.copyTo(_vm->_screen); - _vm->_sound->playSound(1); _vm->_events->_vbCount = 70; while (!_vm->shouldQuit() && _vm->_events->_vbCount > 0 && !_skipStart) { _vm->_events->pollEventsAndWait(); @@ -368,6 +358,7 @@ void Opening::doTitle() { return; _vm->_sound->stopSound(); + _vm->_sound->checkSoundQueue(); // HACK: Clear sound 1 from the queue _vm->_sound->playSound(0); screen.forceFadeOut(); _vm->_events->_vbCount = 100; @@ -386,7 +377,7 @@ void Opening::doTitle() { _vm->_buffer1.blitFrom(*_vm->_screen); screen.forceFadeIn(); _vm->_midi->newMusic(1, 0); - _vm->_events->_vbCount = 700; + _vm->_events->_vbCount = 950; while (!_vm->shouldQuit() && (_vm->_events->_vbCount > 0) && !_vm->_events->isKeyMousePressed()) { _vm->_events->pollEventsAndWait(); } @@ -1605,7 +1596,7 @@ void River::moveCanoe() { moveCanoe2(); } else { if (events._leftButton && pt.y >= 140) { - if (pt.x < RMOUSE[8][0]) { + if (pt.x < _vm->_room->_rMouse[8][0]) { // Disk icon wasn't clicked _vm->_scripts->printString(BAR_MESSAGE); } else { diff --git a/engines/access/amazon/amazon_player.cpp b/engines/access/amazon/amazon_player.cpp index b1ed501fce..903da6c532 100644 --- a/engines/access/amazon/amazon_player.cpp +++ b/engines/access/amazon/amazon_player.cpp @@ -48,7 +48,7 @@ void AmazonPlayer::load() { _downDelta = -2; _scrollConst = 2; - for (int i = 0; i < PLAYER_DATA_COUNT; ++i) { + for (int i = 0; i < _vm->_playerDataCount; ++i) { _walkOffRight[i] = OVEROFFR[i]; _walkOffLeft[i] = OVEROFFL[i]; _walkOffUp[i] = OVEROFFU[i]; @@ -78,6 +78,22 @@ void AmazonPlayer::load() { _diagDownWalkMin = 0; _diagDownWalkMax = 5; _game->_guard->setPosition(Common::Point(56, 190)); + } else { + for (int i = 0; i < _vm->_playerDataCount; ++i) { + _walkOffRight[i] = SIDEOFFR[i]; + _walkOffLeft[i] = SIDEOFFL[i]; + _walkOffUp[i] = SIDEOFFU[i]; + _walkOffDown[i] = SIDEOFFD[i]; + + _walkOffUR[i].x = DIAGOFFURX[i]; + _walkOffUR[i].y = DIAGOFFURY[i]; + _walkOffDR[i].x = DIAGOFFDRX[i]; + _walkOffDR[i].y = DIAGOFFDRY[i]; + _walkOffUL[i].x = DIAGOFFULX[i]; + _walkOffUL[i].y = DIAGOFFULY[i]; + _walkOffDL[i].x = DIAGOFFDLX[i]; + _walkOffDL[i].y = DIAGOFFDLY[i]; + } } } diff --git a/engines/access/amazon/amazon_resources.cpp b/engines/access/amazon/amazon_resources.cpp index 2010c7d842..430aa64f30 100644 --- a/engines/access/amazon/amazon_resources.cpp +++ b/engines/access/amazon/amazon_resources.cpp @@ -72,6 +72,19 @@ const char *const FILENAMES_DEMO[] = { "CHAPTER.AP", "MUSIC.AP", "SOUND.AP", "INV.AP" }; +const int SIDEOFFR[] = { 5, 5, 5, 5, 5, 5, 5, 5, 0 }; +const int SIDEOFFL[] = { 5, 5, 5, 5, 5, 5, 5, 5, 0 }; +const int SIDEOFFU[] = { 2, 2, 2, 2, 2, 2, 2, 2, 0 }; +const int SIDEOFFD[] = { 2, 2, 2, 2, 2, 2, 2, 2, 0 }; +const int DIAGOFFURX[] = { 4, 5, 2, 2, 3, 4, 2, 2, 0 }; +const int DIAGOFFURY[] = { 2, 3, 2, 2, 2, 3, 1, 1, 0 }; +const int DIAGOFFDRX[] = { 4, 5, 4, 3, 5, 4, 5, 1, 0 }; +const int DIAGOFFDRY[] = { 3, 2, 1, 2, 2, 1, 2, 1, 0 }; +const int DIAGOFFULX[] = { 4, 5, 4, 3, 3, 2, 2, 2, 0 }; +const int DIAGOFFULY[] = { 3, 3, 1, 2, 2, 1, 1, 1, 0 }; +const int DIAGOFFDLX[] = { 4, 5, 3, 3, 5, 4, 6, 1, 0 }; +const int DIAGOFFDLY[] = { 2, 2, 1, 2, 3, 1, 2, 1, 0 }; + const byte MOUSE0[] = { // hotspot x and y, uint16 LE 0, 0, 0, 0, @@ -313,7 +326,7 @@ const byte *const CURSORS[10] = { MOUSE0, MOUSE1, MOUSE2, MOUSE3, CURSEYE, CURSHAND, CURSGET, CURSCLIMB, CURSTALK, CURSHELP }; -const int TRAVEL_POS[][2] = { +const int _travelPos[][2] = { { -1, 0 }, { 228, 117 }, { 28, 98 }, @@ -2406,6 +2419,11 @@ const int CAST_END_OBJ1[4][4] = { { 3, 103, 1300, 10 } }; -} // End of namespace Amazon +const int RMOUSE[10][2] = { + { 0, 35 }, { 0, 0 }, { 36, 70 }, { 71, 106 }, { 107, 141 }, + { 142, 177 }, { 178, 212 }, { 213, 248 }, { 249, 283 }, { 284, 318 } +}; + +} // End of namespace Amazon } // End of namespace Access diff --git a/engines/access/amazon/amazon_resources.h b/engines/access/amazon/amazon_resources.h index a952860bc2..10dea02abc 100644 --- a/engines/access/amazon/amazon_resources.h +++ b/engines/access/amazon/amazon_resources.h @@ -45,9 +45,22 @@ struct RiverStruct { extern const char *const FILENAMES[]; extern const char *const FILENAMES_DEMO[]; +extern const int SIDEOFFR[]; +extern const int SIDEOFFL[]; +extern const int SIDEOFFU[]; +extern const int SIDEOFFD[]; +extern const int DIAGOFFURX[]; +extern const int DIAGOFFURY[]; +extern const int DIAGOFFDRX[]; +extern const int DIAGOFFDRY[]; +extern const int DIAGOFFULX[]; +extern const int DIAGOFFULY[]; +extern const int DIAGOFFDLX[]; +extern const int DIAGOFFDLY[]; + extern const byte *const CURSORS[10]; -extern const int TRAVEL_POS[][2]; +extern const int _travelPos[][2]; extern const int OVEROFFR[]; extern const int OVEROFFL[]; @@ -138,11 +151,11 @@ extern const int HELP1COORDS[2][4]; extern const int RIVER1OBJ[23][4]; extern const int CAST_END_OBJ[26][4]; - extern const int CAST_END_OBJ1[4][4]; -} // End of namespace Amazon +extern const int RMOUSE[10][2]; +} // End of namespace Amazon } // End of namespace Access #endif /* ACCESS_AMAZON_RESOURCES_H */ diff --git a/engines/access/amazon/amazon_scripts.cpp b/engines/access/amazon/amazon_scripts.cpp index 92acb3686d..d8f4663401 100644 --- a/engines/access/amazon/amazon_scripts.cpp +++ b/engines/access/amazon/amazon_scripts.cpp @@ -33,6 +33,8 @@ namespace Amazon { AmazonScripts::AmazonScripts(AccessEngine *vm) : Scripts(vm) { _game = (AmazonEngine *)_vm; + + setOpcodes_v2(); } void AmazonScripts::cLoop() { @@ -378,20 +380,20 @@ void AmazonScripts::executeSpecial(int commandIndex, int param1, int param2) { typedef void(AmazonScripts::*AmazonScriptMethodPtr)(); void AmazonScripts::executeCommand(int commandIndex) { - static const AmazonScriptMethodPtr COMMAND_LIST[] = { - &AmazonScripts::cmdHelp, &AmazonScripts::cmdCycleBack, + static const AmazonScriptMethodPtr AMAZON_COMMAND_LIST[] = { + &AmazonScripts::cmdHelp_v2, &AmazonScripts::cmdCycleBack, &AmazonScripts::cmdChapter, &AmazonScripts::cmdSetHelp, &AmazonScripts::cmdCenterPanel, &AmazonScripts::cmdMainPanel, &AmazonScripts::CMDRETFLASH }; if (commandIndex >= 73) - (this->*COMMAND_LIST[commandIndex - 73])(); + (this->*AMAZON_COMMAND_LIST[commandIndex - 73])(); else Scripts::executeCommand(commandIndex); } -void AmazonScripts::cmdHelp() { +void AmazonScripts::cmdHelp_v2() { Common::String helpMessage = readString(); if (_game->_helpLevel == 0) { diff --git a/engines/access/amazon/amazon_scripts.h b/engines/access/amazon/amazon_scripts.h index e10eefb4f5..6d992667f5 100644 --- a/engines/access/amazon/amazon_scripts.h +++ b/engines/access/amazon/amazon_scripts.h @@ -49,7 +49,7 @@ protected: void setInactive(); void boatWalls(int param1, int param2); - void cmdHelp(); + void cmdHelp_v2(); void cmdCycleBack(); void cmdChapter(); void cmdSetHelp(); diff --git a/engines/access/asurface.cpp b/engines/access/asurface.cpp index 5f4372d5af..526690807a 100644 --- a/engines/access/asurface.cpp +++ b/engines/access/asurface.cpp @@ -54,6 +54,12 @@ SpriteResource::~SpriteResource() { SpriteFrame::SpriteFrame(AccessEngine *vm, Common::SeekableReadStream *stream, int frameSize) { int xSize = stream->readUint16LE(); int ySize = stream->readUint16LE(); + + if (vm->getGameID() == GType_MartianMemorandum) { + int size = stream->readUint16LE(); + if (size != frameSize) + warning("Unexpected file difference: framesize %d - size %d %d - unknown %d", frameSize, xSize, ySize, size); + } create(xSize, ySize); // Empty surface @@ -312,6 +318,21 @@ void ASurface::drawRect() { Graphics::Surface::fillRect(Common::Rect(_orgX1, _orgY1, _orgX2, _orgY2), _lColor); } +void ASurface::drawLine(int x1, int y1, int x2, int y2, int col) { + Graphics::Surface::drawLine(x1, y1, x2, y2, col); +} + +void ASurface::drawLine() { + Graphics::Surface::drawLine(_orgX1, _orgY1, _orgX2, _orgY1, _lColor); +} + +void ASurface::drawBox() { + Graphics::Surface::drawLine(_orgX1, _orgY1, _orgX2, _orgY1, _lColor); + Graphics::Surface::drawLine(_orgX1, _orgY2, _orgX2, _orgY2, _lColor); + Graphics::Surface::drawLine(_orgX2, _orgY1, _orgX2, _orgY1, _lColor); + Graphics::Surface::drawLine(_orgX2, _orgY2, _orgX2, _orgY2, _lColor); +} + void ASurface::flipHorizontal(ASurface &dest) { dest.create(this->w, this->h); for (int y = 0; y < h; ++y) { diff --git a/engines/access/asurface.h b/engines/access/asurface.h index 4fb47b9c09..022e2534c1 100644 --- a/engines/access/asurface.h +++ b/engines/access/asurface.h @@ -95,6 +95,12 @@ public: virtual void drawRect(); + virtual void drawLine(int x1, int y1, int x2, int y2, int col); + + virtual void drawLine(); + + virtual void drawBox(); + virtual void transBlitFrom(ASurface *src, const Common::Point &destPos); virtual void transBlitFrom(ASurface *src, const Common::Rect &bounds); diff --git a/engines/access/bubble_box.cpp b/engines/access/bubble_box.cpp index 28c211991c..df8adc1bc6 100644 --- a/engines/access/bubble_box.cpp +++ b/engines/access/bubble_box.cpp @@ -26,13 +26,26 @@ namespace Access { -BubbleBox::BubbleBox(AccessEngine *vm) : Manager(vm) { - _startItem = 0; - _startBox = 0; - _charCol = _rowOff = 0; - _type = TYPE_2; - _bounds = Common::Rect(64, 32, 64 + 130, 32 + 122); - _bubbleDisplStr = ""; +BubbleBox::BubbleBox(AccessEngine *vm, Access::BoxType type, int x, int y, int w, int h, int val1, int val2, int val3, int val4, Common::String title) : Manager(vm) { + _type = type; + _bounds = Common::Rect(x, y, x + w, y + h); + _bubbleDisplStr = title; + _btnId1 = val1; + _btnX1 = val2; + _btnId2 = val3; + _btnX2 = val4; + _btnId3 = _btnX3 = 0; // Unused in MM and Amazon? + _boxStartX = _boxStartY = 0; + _bIconStartX = _bIconStartY = 0; + _boxEndX = _boxEndY = 0; + _boxPStartX = _boxPStartY = 0; + // Unused in AGoE + for (int i = 0; i < 60; i++) { + _tempList[i] = ""; + _tempListIdx[i] = 0; + } + _btnUpPos = Common::Rect(0, 0, 0, 0); + _btnDownPos = Common::Rect(0, 0, 0, 0); } void BubbleBox::load(Common::SeekableReadStream *stream) { @@ -61,7 +74,7 @@ void BubbleBox::clearBubbles() { } void BubbleBox::placeBubble(const Common::String &msg) { - _vm->_screen->_maxChars = 27; + _vm->_screen->_maxChars = (_vm->getGameID() == GType_MartianMemorandum) ? 30 : 27; placeBubble1(msg); } @@ -69,8 +82,8 @@ void BubbleBox::placeBubble1(const Common::String &msg) { _bubbles.clear(); _vm->_fonts._charSet._lo = 1; _vm->_fonts._charSet._hi = 8; - _vm->_fonts._charFor._lo = 29; - _vm->_fonts._charFor._hi = 32; + _vm->_fonts._charFor._lo = (_vm->getGameID() == GType_MartianMemorandum) ? 247 : 29; + _vm->_fonts._charFor._hi = (_vm->getGameID() == GType_MartianMemorandum) ? 255 : 32; calcBubble(msg); @@ -86,7 +99,7 @@ void BubbleBox::calcBubble(const Common::String &msg) { Common::Point printStart = _vm->_screen->_printStart; // Figure out maximum width allowed - if (_type == TYPE_4) { + if (_type == kBoxTypeFileDialog) { _vm->_fonts._printMaxX = 110; } else { _vm->_fonts._printMaxX = _vm->_fonts._font2.stringWidth(_bubbleDisplStr); @@ -108,7 +121,7 @@ void BubbleBox::calcBubble(const Common::String &msg) { _vm->_screen->_printOrg.x = _vm->_screen->_printStart.x; } while (!lastLine); - if (_type == TYPE_4) + if (_type == kBoxTypeFileDialog) ++_vm->_screen->_printOrg.y += 6; // Determine the width for the area @@ -119,12 +132,12 @@ void BubbleBox::calcBubble(const Common::String &msg) { // Determine the height for area int y = _vm->_screen->_printOrg.y + 6; - if (_type == TYPE_4) + if (_type == kBoxTypeFileDialog) y += 6; int height = y - bounds.top; bounds.setHeight(height); - height -= (_type == TYPE_4) ? 30 : 24; + height -= (_type == kBoxTypeFileDialog) ? 30 : 24; if (height >= 0) bounds.setHeight(bounds.height() + 13 - (height % 13)); @@ -137,6 +150,35 @@ void BubbleBox::calcBubble(const Common::String &msg) { } void BubbleBox::printBubble(const Common::String &msg) { + if (_vm->getGameID() == GType_MartianMemorandum) + printBubble_v1(msg); + else + printBubble_v2(msg); +} + +void BubbleBox::printBubble_v1(const Common::String &msg) { + drawBubble(_bubbles.size() - 1); + + // Loop through drawing the lines + Common::String s = msg; + Common::String line; + int width = 0; + bool lastLine; + do { + // Get next line + Font &font2 = _vm->_fonts._font2; + lastLine = font2.getLine(s, _vm->_screen->_maxChars * 6, line, width); + // Draw the text + printString(line); + + // Move print position + _vm->_screen->_printOrg.y += 6; + _vm->_screen->_printOrg.x = _vm->_screen->_printStart.x; + } while (!lastLine); + +} + +void BubbleBox::printBubble_v2(const Common::String &msg) { drawBubble(_bubbles.size() - 1); // Loop through drawing the lines @@ -156,7 +198,7 @@ void BubbleBox::printBubble(const Common::String &msg) { font2._fontColors[3] = 29; int xp = _vm->_screen->_printOrg.x; - if (_type == TYPE_4) + if (_type == kBoxTypeFileDialog) xp = (_bounds.width() - width) / 2 + _bounds.left - 4; // Draw the text @@ -170,7 +212,11 @@ void BubbleBox::printBubble(const Common::String &msg) { void BubbleBox::drawBubble(int index) { _bounds = _bubbles[index]; - doBox(0, 0); + if (_vm->getGameID() == GType_MartianMemorandum) { + int btnSelected = 0; + doBox_v1(0, 0, btnSelected); + } else + doBox(0, 0); } void BubbleBox::doBox(int item, int box) { @@ -194,7 +240,7 @@ void BubbleBox::doBox(int item, int box) { fonts._charSet._lo = 1; fonts._charSet._hi = 0; - if (_type == TYPE_4) { + if (_type == kBoxTypeFileDialog) { fonts._charFor._lo = 0xFF; error("TODO: filename listing"); return; @@ -212,7 +258,7 @@ void BubbleBox::doBox(int item, int box) { _vm->_screen->_orgY2 = _bounds.bottom; _vm->_screen->_lColor = 1; - int h = _bounds.height() - (_type == TYPE_4 ? 30 : 24); + int h = _bounds.height() - (_type == kBoxTypeFileDialog ? 30 : 24); int ySize = (h < 0) ? 0 : (h + 12) / 13; int w = _bounds.width() - 24; int xSize = (w < 0) ? 0 : (w + 19) / 20; @@ -229,21 +275,21 @@ void BubbleBox::doBox(int item, int box) { screen.plotImage(icons, 21, Common::Point(xp, screen._orgY1)); // Draw images to form the bottom border - yp = screen._orgY2 - (_type == TYPE_4 ? 18 : 12); - screen.plotImage(icons, (_type == TYPE_4) ? 72 : 22, + yp = screen._orgY2 - (_type == kBoxTypeFileDialog ? 18 : 12); + screen.plotImage(icons, (_type == kBoxTypeFileDialog) ? 72 : 22, Common::Point(screen._orgX1, yp)); xp = screen._orgX1 + 12; - yp += (_type == TYPE_4) ? 4 : 8; + yp += (_type == kBoxTypeFileDialog) ? 4 : 8; for (int x = 0; x < xSize; ++x, xp += 20) { - screen.plotImage(icons, (_type == TYPE_4 ? 62 : 34) + x, + screen.plotImage(icons, (_type == kBoxTypeFileDialog ? 62 : 34) + x, Common::Point(xp, yp)); } - yp = screen._orgY2 - (_type == TYPE_4 ? 18 : 12); - screen.plotImage(icons, (_type == TYPE_4) ? 73 : 23, Common::Point(xp, yp)); + yp = screen._orgY2 - (_type == kBoxTypeFileDialog ? 18 : 12); + screen.plotImage(icons, (_type == kBoxTypeFileDialog) ? 73 : 23, Common::Point(xp, yp)); - if (_type == TYPE_4) { + if (_type == kBoxTypeFileDialog) { // Further stuff for filename dialog error("TODO: Box type 4"); } @@ -278,4 +324,440 @@ void BubbleBox::doBox(int item, int box) { delete icons; } +void BubbleBox::setCursorPos(int posX, int posY) { + _vm->_screen->_printStart = _vm->_screen->_printOrg = Common::Point((posX << 3) + _rowOff, posY << 3); + warning("Missing call to setCursorPos"); +} + +void BubbleBox::printString(Common::String msg) { + warning("TODO: Proper implementation of printString"); + _vm->_fonts._font1.drawString(_vm->_screen, msg, _vm->_screen->_printOrg); +} + +void BubbleBox::displayBoxData() { + _vm->_boxDataEnd = false; + _rowOff = 2; + _vm->_fonts._charFor._lo = 0xF7; + _vm->_fonts._charFor._hi = 0xFF; + + if (_tempList[0].size() == 0) + return; + + int idx = 0; + if ((_type == TYPE_1) || (_type == TYPE_3)) { + _vm->_bcnt = 0; + + if (_tempList[idx].size() == 0) { + _vm->_boxDataEnd = true; + return; + } + + _vm->_events->hideCursor(); + + _vm->_screen->_orgX1 = _boxStartX; + _vm->_screen->_orgX2 = _boxEndX; + _vm->_screen->_orgY1 = _boxStartY; + _vm->_screen->_orgY2 = _boxEndY; + _vm->_screen->_lColor = 0xFA; + _vm->_screen->drawRect(); + _vm->_events->showCursor(); + } + + _vm->_events->hideCursor(); + int oldPStartY = _boxPStartY; + ++_boxPStartY; + + idx += _vm->_boxDataStart; + + while (true) { + setCursorPos(_boxPStartX, _boxPStartY); + printString(_tempList[idx]); + + ++idx; + ++_boxPStartY; + ++_vm->_bcnt; + if (_tempList[idx].size() == 0) { + _boxPStartY = oldPStartY; + _vm->_events->showCursor(); + _vm->_boxDataEnd = true; + return; + } + + if (_vm->_bcnt == _vm->_numLines) { + _boxPStartY = oldPStartY; + _vm->_events->showCursor(); + return; + } + } +} + +void BubbleBox::drawSelectBox() { + if (_tempList[0].size() == 0) + return; + + if (((_type != TYPE_1) && (_type != TYPE_3)) || !_vm->_bcnt) + return; + + if (_vm->_boxSelectYOld != -1) { + _vm->_events->hideCursor(); + _vm->_screen->_lColor = 0xFA; + + int val = _vm->_boxSelectYOld + _boxPStartY + 1; + _vm->_screen->_orgY1 = (val << 3) + 2; + _vm->_screen->_orgY2 = _vm->_screen->_orgY1 + 7; + _vm->_screen->_orgX1 = _boxStartX; + _vm->_screen->_orgX2 = _boxEndX; + _vm->_screen->drawBox(); + _vm->_events->showCursor(); + } + + _vm->_events->hideCursor(); + _vm->_boxSelectYOld = _vm->_boxSelectY; + int val = _boxPStartY + _vm->_boxSelectY + 1; + _vm->_screen->_orgY1 = (val << 3) + 2; + _vm->_screen->_orgY2 = _vm->_screen->_orgY1 + 7; + _vm->_screen->_orgX1 = _boxStartX; + _vm->_screen->_orgX2 = _boxEndX; + _vm->_screen->_lColor = 0xFE; + _vm->_screen->drawBox(); + _vm->_events->showCursor(); + + if (_type == TYPE_3) + warning("TODO: List filenames"); +} + +int BubbleBox::doBox_v1(int item, int box, int &btnSelected) { + static const int ICONW[] = { 0, 11, 28, 19, 19, 15 }; + + FontManager &fonts = _vm->_fonts; + int retval_ = -1; + + _startItem = item; + _startBox = box; + + // Save state information + _vm->_screen->saveScreen(); + _vm->_screen->setDisplayScan(); + + fonts._charFor._hi = 0xff; + fonts._charSet._lo = 1; + fonts._charSet._hi = 0; + + _vm->_destIn = _vm->_screen; // TODO: Redundant + + if (_type != TYPE_2) { + Common::Rect r = _bounds; + r.left -= 2; + _vm->_screen->saveBlock(r); + } + + // Set the up boundaries and color to use for the box background + _vm->_screen->_orgX1 = _bounds.left - 2; + _vm->_screen->_orgY1 = _bounds.top; + _vm->_screen->_orgX2 = _bounds.right - 2; + _vm->_screen->_orgY2 = _bounds.bottom; + _vm->_screen->_lColor = 0xFB; + + // Draw a background for the entire area + _vm->_screen->drawRect(); + + // Draw the inner box; + ++_vm->_screen->_orgX1; + ++_vm->_screen->_orgY1; + --_vm->_screen->_orgX2; + --_vm->_screen->_orgY2; + _vm->_screen->_lColor = 0xF9; + + // Draw the inner border + _vm->_screen->drawBox(); + + // Get icons data + Resource *iconData = _vm->_files->loadFile("ICONS.LZ"); + SpriteResource *icons = new SpriteResource(_vm, iconData); + delete iconData; + + // Draw upper border + _vm->_bcnt = (_vm->_screen->_orgX2 - _vm->_screen->_orgX1) >> 4; + int oldX = _vm->_screen->_orgX1; + for ( ;_vm->_bcnt > 0; --_vm->_bcnt) { + _vm->_screen->plotImage(icons, 16, Common::Point(_vm->_screen->_orgX1, _vm->_screen->_orgY1)); + _vm->_screen->_orgX1 += 16; + } + + _vm->_screen->_orgX1 = oldX; + int oldY = _vm->_screen->_orgY2; + _vm->_screen->_orgY2 = _vm->_screen->_orgY1 + 8; + _vm->_screen->_lColor = 0xF9; + + _boxStartY = _vm->_screen->_orgY2 + 1; + _vm->_screen->_orgY2 = oldY; + + int tmpX = 0; + int tmpY = 0; + if (_type != TYPE_2) { + oldY = _vm->_screen->_orgY1; + --_vm->_screen->_orgY2; + _vm->_screen->_orgY1 = _vm->_screen->_orgY2 - 8; + if (_type == TYPE_3) + _vm->_screen->_orgY1 -= 8; + _vm->_screen->drawRect(); + tmpX = _bIconStartX = _vm->_screen->_orgX1; + + _boxStartX = tmpX + 1; + tmpY = _boxEndY = _vm->_screen->_orgY1; + + if (_type == TYPE_3) + _bIconStartY = tmpY + 9; + else + _bIconStartY = tmpY + 1; + + if (_type == TYPE_3) { + _fileStart = Common::Point((tmpX + 2) >> 3, (tmpY + 2) >> 3); + int rowOff = tmpY - (_fileStart.y << 3) + 1; + if (rowOff == 8) { + rowOff = 0; + ++_fileStart.y; + } + _fileOff.y = _rowOff = rowOff; + setCursorPos(_fileStart.x, _fileStart.y); + _vm->_fonts._charFor._lo = 0xF7; + _vm->_fonts._charFor._hi = 0; + printString("FILE: "); + _vm->_fonts._charFor._hi = 0xFF; + } + _vm->_screen->_orgY1 = oldY; + } + + if ((_type != TYPE_0) && (_type != TYPE_2)) { + _vm->_screen->_orgY1 += 8; + if (_type == TYPE_3) + _vm->_screen->_orgY2 -= 8; + + _vm->_screen->_orgY2 -= 8; + _btnUpPos.right = _btnDownPos.right = _vm->_screen->_orgX2; + _btnUpPos.left = _btnDownPos.left = _vm->_screen->_orgX1 = _vm->_screen->_orgX2 - 8; + _boxEndX = _vm->_screen->_orgX1 - 1; + _vm->_screen->drawBox(); + + _vm->_screen->_orgY1 += 6; + _vm->_screen->_orgY2 -= 6; + _vm->_screen->drawBox(); + + _btnUpPos.bottom = _vm->_screen->_orgY1 + 1; + _btnUpPos.top = _btnUpPos.bottom - 5; + _btnDownPos.top = _vm->_screen->_orgY2 + 1; + _btnDownPos.bottom = _btnDownPos.top + 6; + + _vm->_screen->_orgX1 += 4; + _vm->_screen->_orgX2 = _vm->_screen->_orgX1; + _vm->_screen->_orgY1 -= 4; + _vm->_screen->_orgY2 += 2; + _vm->_screen->drawLine(); + + ++_vm->_screen->_orgY1; + --_vm->_screen->_orgX1; + ++_vm->_screen->_orgX2; + _vm->_screen->drawLine(); + + ++_vm->_screen->_orgY1; + --_vm->_screen->_orgX1; + ++_vm->_screen->_orgX2; + _vm->_screen->drawLine(); + + _vm->_screen->_orgY1 = _vm->_screen->_orgY2; + _vm->_screen->drawLine(); + + ++_vm->_screen->_orgX1; + --_vm->_screen->_orgX2; + ++_vm->_screen->_orgY1; + _vm->_screen->drawLine(); + + ++_vm->_screen->_orgX1; + --_vm->_screen->_orgX2; + ++_vm->_screen->_orgY1; + _vm->_screen->drawLine(); + } + + int len = _bubbleDisplStr.size(); + int newX = _bounds.top >> 3; + newX = (len - newX) / 2; + + _boxPStartX = _bounds.left >> 3; + newX += _boxPStartX; + + int newY = _bounds.top >> 3; + int bp = _bounds.top - (newY << 3) + 1; + if (bp == 8) { + ++newY; + bp = 0; + } + + _rowOff = bp; + retval_ = _boxPStartY = newY; + + setCursorPos(newX, newY); + + _vm->_fonts._charFor._lo = 0xFF; + _vm->_fonts._font1.drawString(_vm->_screen, _bubbleDisplStr, _vm->_screen->_printOrg); + + if (_type == TYPE_2) { + _vm->_events->showCursor(); + warning("TODO: pop values"); + _vm->_screen->restoreScreen(); + return retval_; + } + + _vm->_destIn = _vm->_screen; + + // Draw buttons + int ICON1T = 0; + int ICON1X = 0; + int ICON1Y = 0; + int ICON2T = 0; + int ICON2X = 0; + int ICON3T = 0; + int ICON3X = 0; + if (_btnId1) { + ICON1T = _btnId1; + ICON1X = _bIconStartX + _btnX1; + ICON1Y = _bIconStartY; + _vm->_screen->plotImage(icons, ICON1T + 10, Common::Point(ICON1X, ICON1Y)); + + if (_btnId2) { + ICON2T = _btnId2; + ICON2X = _bIconStartX + _btnX2; + _vm->_screen->plotImage(icons, ICON2T + 10, Common::Point(ICON2X, _bIconStartY)); + + if (_btnId3) { + ICON3T = _btnId3; + ICON3X = _bIconStartX + _btnX3; + _vm->_screen->plotImage(icons, ICON3T + 10, Common::Point(ICON3X, _bIconStartY)); + } + } + } + + _vm->_screen->restoreScreen(); + _vm->_boxDataStart = _startItem; + _vm->_boxSelectYOld = -1; + _vm->_boxSelectY = _startBox; + + _vm->_numLines = (_bounds.bottom >> 3) - 2; + if (_type == TYPE_3) + --_vm->_numLines; + + _vm->_events->showCursor(); + displayBoxData(); + drawSelectBox(); + + while (!_vm->shouldQuit()) { + _vm->_events->pollEvents(); + if (!_vm->_events->_leftButton) + continue; + + if (((_type == TYPE_1) || (_type != TYPE_3)) && (_vm->_timers[2]._flag == 0)) { + ++_vm->_timers[2]._flag; + if (_btnUpPos.contains(_vm->_events->_mousePos)) { + if (_vm->_bcnt) { + if (_vm->_boxSelectY != 0) { + --_vm->_boxSelectY; + drawSelectBox(); + } else if (_vm->_boxDataStart != 0) { + --_vm->_boxDataStart; + displayBoxData(); + drawSelectBox(); + } + } + continue; + } else if (_btnDownPos.contains(_vm->_events->_mousePos)) { + if (_vm->_bcnt) { + if (_vm->_bcnt == _vm->_numLines) { + if (_vm->_bcnt != _vm->_boxSelectY + 1) { + ++_vm->_boxSelectY; + drawSelectBox(); + } else if (!_vm->_boxDataEnd) { + ++_vm->_boxDataStart; + displayBoxData(); + drawSelectBox(); + } + } else if (_vm->_bcnt != _vm->_boxSelectY + 1) { + ++_vm->_boxSelectY; + drawSelectBox(); + } + } + continue; + } + } + + if ((_vm->_events->_mousePos.x >= _boxStartX) && (_vm->_events->_mousePos.x <= _boxEndX) + && (_vm->_events->_mousePos.y >= _boxStartY) && (_vm->_events->_mousePos.y <= _boxEndY)) { + int val = (_vm->_events->_mousePos.x >> 3) - _boxPStartY; + if (val > _vm->_bcnt) + continue; + --val; + if (_type == TYPE_3) + _vm->_boxSelect = val; + else { + btnSelected = 1; + if (_vm->_boxSelectY == val) + break; + _vm->_boxSelectY = val; + _vm->_events->debounceLeft(); + drawSelectBox(); + continue; + } + } + + if ((_vm->_events->_mousePos.y >= ICON1Y) && (_vm->_events->_mousePos.y <= ICON1Y + 8) + && (_vm->_events->_mousePos.x >= ICON1X)) { + btnSelected = 1; + if (_vm->_events->_mousePos.x < ICON1X + ICONW[ICON1T]) + break; + + if ((_vm->_events->_mousePos.x >= ICON2X) && (_vm->_events->_mousePos.x < ICON2X + ICONW[ICON2T])) { + btnSelected = 2; + break; + } + + if ((_vm->_events->_mousePos.x >= ICON3X) && (_vm->_events->_mousePos.x < ICON3X + ICONW[ICON3T])) { + btnSelected = 3; + break; + } + + if (_type != TYPE_3) + continue; + + if ((_vm->_events->_mousePos.x < tmpX) || (_vm->_events->_mousePos.x > tmpX + 144)) + continue; + + if ((_vm->_events->_mousePos.y < tmpY) || (_vm->_events->_mousePos.y > tmpY + 8)) + continue; + + warning("TODO: sub175B5 - List of files"); + } + } + + _vm->_events->hideCursor(); + _vm->_screen->restoreBlock(); + _vm->_events->showCursor(); + _vm->_events->debounceLeft(); + if (_vm->_bcnt == 0) + retval_ = -1; + else + retval_ = _vm->_boxDataStart + _vm->_boxSelectY; + return retval_; +} + +void BubbleBox::getList(const char *const data[], int *flags) { + int srcIdx = 0; + int destIdx = 0; + while (data[srcIdx]) { + if (flags[srcIdx]) { + _tempList[destIdx] = Common::String(data[srcIdx]); + _tempListIdx[destIdx] = srcIdx; + ++destIdx; + } + srcIdx++; + } + _tempList[destIdx] = ""; +} } // End of namespace Access diff --git a/engines/access/bubble_box.h b/engines/access/bubble_box.h index 0b3f139520..9a45721108 100644 --- a/engines/access/bubble_box.h +++ b/engines/access/bubble_box.h @@ -36,23 +36,46 @@ namespace Access { class AccessEngine; -enum BoxType { TYPE_2 = 2, TYPE_4 = 4 }; +enum BoxType { TYPE_0 = 0, TYPE_1 = 1, TYPE_2 = 2, TYPE_3 = 3, kBoxTypeFileDialog = 4 }; class BubbleBox : public Manager { private: int _startItem, _startBox; int _charCol, _rowOff; Common::Point _fileStart; + Common::Point _fileOff; + int _boxStartX, _boxStartY; + int _boxEndX, _boxEndY; + int _bIconStartX, _bIconStartY; + int _boxPStartX, _boxPStartY; + + void displayBoxData(); + void drawSelectBox(); + /** + * Prints a text bubble and it's contents + */ + void printBubble_v1(const Common::String &msg); + void printBubble_v2(const Common::String &msg); + public: BoxType _type; Common::Rect _bounds; Common::StringArray _nameIndex; Common::String _bubbleTitle; Common::String _bubbleDisplStr; - + Common::String _tempList[60]; + int _tempListIdx[60]; + int _btnId1; + int _btnX1; + int _btnId2; + int _btnX2; + int _btnId3; + int _btnX3; + Common::Rect _btnUpPos; + Common::Rect _btnDownPos; Common::Array<Common::Rect> _bubbles; public: - BubbleBox(AccessEngine *vm); + BubbleBox(AccessEngine *vm, Access::BoxType type, int x, int y, int w, int h, int val1, int val2, int val3, int val4, Common::String title); void load(Common::SeekableReadStream *stream); @@ -78,6 +101,11 @@ public: void drawBubble(int index); void doBox(int item, int box); + + int doBox_v1(int item, int box, int &btnSelected); + void getList(const char *const data[], int *flags); + void setCursorPos(int posX, int posY); + void printString(Common::String msg); }; } // End of namespace Access diff --git a/engines/access/char.cpp b/engines/access/char.cpp index b359bcf13a..aca7262952 100644 --- a/engines/access/char.cpp +++ b/engines/access/char.cpp @@ -27,15 +27,25 @@ namespace Access { -CharEntry::CharEntry(const byte *data) { +CharEntry::CharEntry(const byte *data, AccessEngine *vm) { Common::MemoryReadStream s(data, 999); _charFlag = s.readByte(); - _estabIndex = s.readSint16LE(); - _screenFile.load(s); + if (vm->getGameID() == GType_MartianMemorandum) { + _screenFile.load(s); + _estabIndex = s.readSint16LE(); + } else { + _estabIndex = s.readSint16LE(); + _screenFile.load(s); + } + _paletteFile.load(s); _startColor = s.readUint16LE(); - _numColors = s.readUint16LE(); + if (vm->getGameID() == GType_MartianMemorandum) { + int lastColor = s.readUint16LE(); + _numColors = lastColor - _startColor; + } else + _numColors = s.readUint16LE(); // Load cells for (byte cell = s.readByte(); cell != 0xff; cell = s.readByte()) { @@ -73,12 +83,18 @@ CharManager::CharManager(AccessEngine *vm) : Manager(vm) { // Setup character list if (_vm->isDemo()) { for (int i = 0; i < 27; ++i) - _charTable.push_back(CharEntry(Amazon::CHARTBL_DEMO[i])); + _charTable.push_back(CharEntry(Amazon::CHARTBL_DEMO[i], vm)); } else { for (int i = 0; i < 37; ++i) - _charTable.push_back(CharEntry(Amazon::CHARTBL[i])); + _charTable.push_back(CharEntry(Amazon::CHARTBL[i], vm)); } break; + + case GType_MartianMemorandum: + for (int i = 0; i < 27; ++i) + _charTable.push_back(CharEntry(Martian::CHARTBL_MM[i], vm)); + break; + default: error("Unknown game"); } @@ -152,8 +168,14 @@ void CharManager::charMenu() { screen.saveScreen(); screen.setDisplayScan(); - screen.plotImage(spr, 17, Common::Point(0, 176)); - screen.plotImage(spr, 18, Common::Point(155, 176)); + if (_vm->getGameID() == GType_MartianMemorandum) { + screen.plotImage(spr, 17, Common::Point(0, 184)); + screen.plotImage(spr, 18, Common::Point(193, 184)); + } else if (_vm->getGameID() == GType_Amazon) { + screen.plotImage(spr, 17, Common::Point(0, 176)); + screen.plotImage(spr, 18, Common::Point(155, 176)); + } else + error("Game not supported"); screen.restoreScreen(); delete spr; diff --git a/engines/access/char.h b/engines/access/char.h index 6fb4934978..f2828e9779 100644 --- a/engines/access/char.h +++ b/engines/access/char.h @@ -41,7 +41,7 @@ public: FileIdent _scriptFile; Common::Array<ExtraCell> _extraCells; public: - CharEntry(const byte *data); + CharEntry(const byte *data, AccessEngine *vm); CharEntry(); }; diff --git a/engines/access/decompress.cpp b/engines/access/decompress.cpp index c5656afa51..3de376c193 100644 --- a/engines/access/decompress.cpp +++ b/engines/access/decompress.cpp @@ -46,7 +46,7 @@ void LzwDecompressor::decompress(byte *source, byte *dest) { maxCodeValue = 512; copyLength = 0; - _bitPos = 0; + _sourceBitsLeft = 8; while (1) { @@ -97,17 +97,39 @@ uint16 LzwDecompressor::getCode() { const byte bitMasks[9] = { 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0x0FF }; - uint16 bits, loCode, hiCode; - loCode = (READ_LE_UINT16(_source) >> _bitPos) & 0xFF; - _source++; - bits = _codeLength - 8; - hiCode = (READ_LE_UINT16(_source) >> _bitPos) & bitMasks[bits]; - _bitPos += bits; - if (_bitPos > 8) { - _source++; - _bitPos -= 8; + + byte resultBitsLeft = _codeLength; + byte resultBitsPos = 0; + uint16 result = 0; + byte currentByte = *_source; + byte currentBits = 0; + + // Get bits of current byte + while (resultBitsLeft) { + if (resultBitsLeft < _sourceBitsLeft) { + // we need less than we have left + currentBits = (currentByte >> (8 - _sourceBitsLeft)) & bitMasks[resultBitsLeft]; + result |= (currentBits << resultBitsPos); + _sourceBitsLeft -= resultBitsLeft; + resultBitsLeft = 0; + + } else { + // we need as much as we have left or more + resultBitsLeft -= _sourceBitsLeft; + currentBits = currentByte >> (8 - _sourceBitsLeft); + result |= (currentBits << resultBitsPos); + resultBitsPos += _sourceBitsLeft; + + // Go to next byte + _source++; + + _sourceBitsLeft = 8; + if (resultBitsLeft) { + currentByte = *_source; + } + } } - return (hiCode << 8) | loCode; + return result; } uint32 decompressDBE(byte *source, byte **dest) { diff --git a/engines/access/decompress.h b/engines/access/decompress.h index eea450086b..bea9a1d3f8 100644 --- a/engines/access/decompress.h +++ b/engines/access/decompress.h @@ -32,7 +32,8 @@ public: void decompress(byte *source, byte *dest); private: byte *_source; - byte _codeLength, _bitPos; + byte _sourceBitsLeft; + byte _codeLength; uint16 getCode(); }; diff --git a/engines/access/detection_tables.h b/engines/access/detection_tables.h index 88a64470c5..124f5fcf0d 100644 --- a/engines/access/detection_tables.h +++ b/engines/access/detection_tables.h @@ -75,7 +75,22 @@ static const AccessGameDescription gameDescriptions[] = { { "martian", nullptr, - AD_ENTRY1s("r00.ap", "af98db5ee7f9ef86c6b1f43187a3691b", 31), + AD_ENTRY1s("r01.ap", "c081daca9b0cfd710157cf946e343df6", 39352), + Common::EN_ANY, + Common::kPlatformDOS, + ADGF_NO_FLAGS, + GUIO1(GUIO_NONE) + }, + GType_MartianMemorandum, + 0 + }, + + { + // Martian Memorandum + { + "martian", + "Demo", + AD_ENTRY1s("r01.rm", "c2facf9c43047211289044ee39a2322a", 2313), Common::EN_ANY, Common::kPlatformDOS, ADGF_NO_FLAGS, diff --git a/engines/access/inventory.cpp b/engines/access/inventory.cpp index df499ba705..3823b17283 100644 --- a/engines/access/inventory.cpp +++ b/engines/access/inventory.cpp @@ -31,10 +31,17 @@ namespace Access { void InventoryEntry::load(const Common::String &name, const int *data) { _value = ITEM_NOT_FOUND; _name = name; - _otherItem1 = *data++; - _newItem1 = *data++; - _otherItem2 = *data++; - _newItem2 = *data; + if (data) { + _otherItem1 = *data++; + _newItem1 = *data++; + _otherItem2 = *data++; + _newItem2 = *data; + } else { + _otherItem1 = -1; + _newItem1 = -1; + _otherItem2 = -1; + _newItem2 = -1; + } } int InventoryEntry::checkItem(int itemId) { @@ -67,20 +74,20 @@ InventoryManager::InventoryManager(AccessEngine *vm) : Manager(vm) { names = Amazon::INVENTORY_NAMES; combineP = &Amazon::COMBO_TABLE[0][0]; _inv.resize(85); + for (uint i = 0; i < _inv.size(); ++i, combineP += 4) + _inv[i].load(names[i], combineP); break; case GType_MartianMemorandum: names = Martian::INVENTORY_NAMES; - combineP = &Martian::COMBO_TABLE[0][0]; - _inv.resize(54); + combineP = nullptr; + _inv.resize(55); + for (uint i = 0; i < _inv.size(); ++i) + _inv[i].load(names[i], nullptr); break; default: error("Unknown game"); } - for (uint i = 0; i < _inv.size(); ++i, combineP += 4) { - _inv[i].load(names[i], combineP); - } - for (uint i = 0; i < 26; ++i) { const int *r = INVCOORDS[i]; _invCoords.push_back(Common::Rect(r[0], r[2], r[1], r[3])); @@ -209,6 +216,31 @@ int InventoryManager::newDisplayInv() { return result; } +int InventoryManager::displayInv() { + int *inv = (int *) malloc (Martian::INVENTORY_SIZE * sizeof(int)); + + for (int i = 0; i < Martian::INVENTORY_SIZE; i++) + inv[i] = _inv[i]._value; + _vm->_events->forceSetCursor(CURSOR_CROSSHAIRS); + _vm->_invBox->getList(Martian::INVENTORY_NAMES, inv); + + int btnSelected = 0; + int boxX = _vm->_invBox->doBox_v1(_startInvItem, _startInvBox, btnSelected); + _startInvItem = _vm->_boxDataStart; + _startInvBox = _vm->_boxSelectY; + + if (boxX == -1) + btnSelected = 2; + + if (btnSelected != 2) + _vm->_useItem = _vm->_invBox->_tempListIdx[boxX]; + else + _vm->_useItem = -1; + + free(inv); + return 0; +} + void InventoryManager::savedFields() { Screen &screen = *_vm->_screen; Room &room = *_vm->_room; diff --git a/engines/access/inventory.h b/engines/access/inventory.h index 6a9390eda9..1d88bf4555 100644 --- a/engines/access/inventory.h +++ b/engines/access/inventory.h @@ -128,6 +128,7 @@ public: void refreshInventory(); int newDisplayInv(); + int displayInv(); /** * Synchronize savegame data diff --git a/engines/access/martian/martian_game.cpp b/engines/access/martian/martian_game.cpp index 4e4a5135a6..3fdba8d260 100644 --- a/engines/access/martian/martian_game.cpp +++ b/engines/access/martian/martian_game.cpp @@ -35,108 +35,256 @@ MartianEngine::MartianEngine(OSystem *syst, const AccessGameDescription *gameDes } MartianEngine::~MartianEngine() { + _introObjects = _spec7Objects = nullptr; } -void MartianEngine::playGame() { - // Do introduction - doIntroduction(); - if (shouldQuit()) - return; +void MartianEngine::initObjects() { + _room = new MartianRoom(this); + _scripts = new MartianScripts(this); +} - // Setup the game - setupGame(); +void MartianEngine::configSelect() { + // No implementation required in MM +} - _screen->clearScreen(); - _screen->setPanel(0); - _screen->forceFadeOut(); +void MartianEngine::initVariables() { + warning("TODO: initVariables"); + + // Set player room and position + _player->_roomNumber = 7; + + _inventory->_startInvItem = 0; + _inventory->_startInvBox = 0; + Common::fill(&_objectsTable[0], &_objectsTable[100], (SpriteResource *)nullptr); + _player->_playerOff = false; + + // Setup timers + const int TIMER_DEFAULTS[] = { 4, 10, 8, 1, 1, 1, 1, 2 }; + for (int i = 0; i < 32; ++i) { + TimerEntry te; + te._initTm = te._timer = (i < 8) ? TIMER_DEFAULTS[i] : 1; + te._flag = 1; + + _timers.push_back(te); + } + + _player->_playerX = _player->_rawPlayer.x = _travelPos[_player->_roomNumber][0]; + _player->_playerY = _player->_rawPlayer.y = _travelPos[_player->_roomNumber][1]; + _room->_selectCommand = -1; + _events->setNormalCursor(CURSOR_CROSSHAIRS); + _mouseMode = 0; + _numAnimTimers = 0; + + for (int i = 0; i < 60; i++) + _travel[i] = 0; + _travel[7] = 1; + + for (int i = 0; i < 40; i++) + _ask[i] = 0; + _ask[33] = 1; +} + +void MartianEngine::setNoteParams() { + _events->hideCursor(); + + _screen->_orgX1 = 58; + _screen->_orgY1 = 124; + _screen->_orgX2 = 297; + _screen->_orgY2 = 199; + _screen->_lColor = 51; + _screen->drawRect(); _events->showCursor(); +} - // Setup and execute the room - _room = new MartianRoom(this); - _scripts = new MartianScripts(this); - _room->doRoom(); +void MartianEngine::displayNote(const Common::String &msg) { + _fonts._charSet._lo = 1; + _fonts._charSet._hi = 8; + _fonts._charFor._lo = 0; + _fonts._charFor._hi = 255; + + _screen->_maxChars = 40; + _screen->_printOrg = _screen->_printStart = Common::Point(59, 124); + + setNoteParams(); + + Common::String lines = msg; + Common::String line; + int width = 0; + bool lastLine = false; + do { + lastLine = _fonts._font1.getLine(lines, _screen->_maxChars * 6, line, width); + _bubbleBox->printString(line); + _screen->_printOrg = Common::Point(_screen->_printStart.x, _screen->_printOrg.y + 6); + + if (_screen->_printOrg.y == 196) { + _events->waitKeyMouse(); + setNoteParams(); + _screen->_printOrg = _screen->_printStart; + } + } while (!lastLine); + _events->waitKeyMouse(); } -void MartianEngine::doIntroduction() { - _screen->setInitialPalettte(); - _events->setCursor(CURSOR_ARROW); +void MartianEngine::doSpecial5(int param1) { + warning("TODO: Push midi song"); + _midi->stopSong(); + _midi->_byte1F781 = false; + _midi->loadMusic(47, 4); + _midi->midiPlay(); + _screen->setDisplayScan(); + _events->clearEvents(); + _screen->forceFadeOut(); + _events->hideCursor(); + _files->loadScreen("DATA.SC"); _events->showCursor(); - _screen->setPanel(0); + _screen->setIconPalette(); + _screen->forceFadeIn(); + + Resource *cellsRes = _files->loadFile("CELLS00.LZ"); + _objectsTable[0] = new SpriteResource(this, cellsRes); + delete cellsRes; + + _timers[20]._timer = _timers[20]._initTm = 30; + Resource *notesRes = _files->loadFile("NOTES.DAT"); + notesRes->_stream->skip(param1 * 2); + int pos = notesRes->_stream->readUint16LE(); + notesRes->_stream->seek(pos); + Common::String msg = ""; + byte c; + while ((c = (char)notesRes->_stream->readByte()) != '\0') + msg += c; + + displayNote(msg); + + _midi->stopSong(); + _midi->freeMusic(); + + warning("TODO: Pop Midi"); + // _midi->_byte1F781 = true; +} + +void MartianEngine::playGame() { + // Initialize Martian Memorandum game-specific objects + initObjects(); - // TODO: Worry about implementing full intro sequence later - return; + // Setup the game + setupGame(); + configSelect(); - doTitle(); - if (shouldQuit()) - return; + if (_loadSaveSlot == -1) { + // Do introduction + doCredits(); + if (shouldQuit()) + return; - if (!_skipStart) { - _screen->setPanel(3); - doOpening(); + // Display Notes screen + doSpecial5(4); if (shouldQuit()) return; + _screen->forceFadeOut(); + } - if (!_skipStart) { - //doTent(); - if (shouldQuit()) - return; + do { + _restartFl = false; + _screen->clearScreen(); + _screen->setPanel(0); + _screen->forceFadeOut(); + _events->showCursor(); + + initVariables(); + + // If there's a pending savegame to load, load it + if (_loadSaveSlot != -1) { + loadGameState(_loadSaveSlot); + _loadSaveSlot = -1; } - } - doTitle(); + // Execute the room + _room->doRoom(); + } while (_restartFl); } -void MartianEngine::doTitle() { - /* - _screen->setDisplayScan(); - _destIn = &_buffer2; - - _screen->forceFadeOut(); +bool MartianEngine::showCredits() { _events->hideCursor(); + _screen->clearScreen(); + _destIn = _screen; - _sound->queueSound(0, 98, 30); - - _files->_setPaletteFlag = false; - _files->loadScreen(0, 3); - - _buffer2.blitFrom(*_screen); - _buffer1.blitFrom(*_screen); - _screen->forceFadeIn(); - _sound->playSound(1); + int posX = _creditsStream->readSint16LE(); + int posY = 0; - Resource *spriteData = _files->loadFile(0, 2); - _objectsTable[0] = new SpriteResource(this, spriteData); - delete spriteData; + while(posX != -1) { + posY = _creditsStream->readSint16LE(); + int frameNum = _creditsStream->readSint16LE(); + _screen->plotImage(_introObjects, frameNum, Common::Point(posX, posY)); - _sound->playSound(1); + posX = _creditsStream->readSint16LE(); + } - _files->_setPaletteFlag = false; - _files->loadScreen(0, 4); - _sound->playSound(1); + posY = _creditsStream->readSint16LE(); + if (posY == -1) { + _events->showCursor(); + _screen->forceFadeOut(); + return true; + } - _buffer2.blitFrom(*_screen); - _buffer1.blitFrom(*_screen); - _sound->playSound(1); + _screen->forceFadeIn(); + _timers[3]._timer = _timers[3]._initTm = posY; - const int COUNTDOWN[6] = { 2, 0x80, 1, 0x7d, 0, 0x87 }; - for (_pCount = 0; _pCount < 3; ++_pCount) { - _buffer2.blitFrom(_buffer1); - int id = READ_LE_UINT16(COUNTDOWN + _pCount * 4); - int xp = READ_LE_UINT16(COUNTDOWN + _pCount * 4 + 2); - _screen->plotImage(_objectsTable[0], id, Common::Point(xp, 71)); + while (!shouldQuit() && !_events->isKeyMousePressed() && _timers[3]._timer) { + _events->pollEventsAndWait(); } - // TODO: More to do - delete _objectsTable[0]; - */ + _events->showCursor(); + _screen->forceFadeOut(); + + if (_events->_rightButton) + return true; + else + return false; } -void MartianEngine::doOpening() { - warning("TODO doOpening"); +void MartianEngine::doCredits() { + _midi->_byte1F781 = false; + _midi->loadMusic(47, 3); + _midi->midiPlay(); + _screen->setDisplayScan(); + _events->hideCursor(); + _screen->forceFadeOut(); + Resource *data = _files->loadFile(41, 1); + _introObjects = new SpriteResource(this, data); + delete data; + + _files->loadScreen(41, 0); + _buffer2.copyFrom(*_screen); + _buffer1.copyFrom(*_screen); + _events->showCursor(); + _creditsStream = new Common::MemoryReadStream(CREDIT_DATA, 180); + + if (!showCredits()) { + _screen->copyFrom(_buffer2); + _screen->forceFadeIn(); + + _events->_vbCount = 550; + while (!shouldQuit() && !_events->isKeyMousePressed() && _events->_vbCount > 0) + _events->pollEventsAndWait(); + + _screen->forceFadeOut(); + while (!shouldQuit() && !_events->isKeyMousePressed()&& !showCredits()) + _events->pollEventsAndWait(); + + warning("TODO: Free word_21E2B"); + _midi->freeMusic(); + } } void MartianEngine::setupGame() { + // Load death list + _deaths.resize(20); + for (int i = 0; i < 20; ++i) { + _deaths[i]._screenId = Martian::DEATH_SCREENS[i]; + _deaths[i]._msg = Martian::DEATHMESSAGE[i]; + } // Setup timers const int TIMER_DEFAULTS[] = { 4, 10, 8, 1, 1, 1, 1, 2 }; @@ -155,12 +303,60 @@ void MartianEngine::setupGame() { // Set player room and position _player->_roomNumber = 7; - _player->_playerX = _player->_rawPlayer.x = TRAVEL_POS[_player->_roomNumber][0]; - _player->_playerY = _player->_rawPlayer.y = TRAVEL_POS[_player->_roomNumber][1]; + _player->_playerX = _player->_rawPlayer.x = _travelPos[_player->_roomNumber][0]; + _player->_playerY = _player->_rawPlayer.y = _travelPos[_player->_roomNumber][1]; +} + +void MartianEngine::showDeathText(Common::String msg) { + Common::String line = ""; + int width = 0; + bool lastLine; + do { + lastLine = _fonts._font2.getLine(msg, _screen->_maxChars * 6, line, width); + // Draw the text + _bubbleBox->printString(line); + + _screen->_printOrg.y += 6; + _screen->_printOrg.x = _screen->_printStart.x; + + if (_screen->_printOrg.y == 180) { + _events->waitKeyMouse(); + _screen->copyBuffer(&_buffer2); + _screen->_printOrg.y = _screen->_printStart.y; + } + } while (!lastLine); + _events->waitKeyMouse(); } -void MartianEngine::drawHelp() { - error("TODO: drawHelp"); +void MartianEngine::dead(int deathId) { + // Load and display death screen + _events->hideCursor(); + _screen->forceFadeOut(); + _files->loadScreen(48, _deaths[deathId]._screenId); + _screen->setIconPalette(); + _buffer2.copyBuffer(_screen); + _screen->forceFadeIn(); + _events->showCursor(); + + // Setup fonts + _fonts._charSet._hi = 10; + _fonts._charSet._lo = 1; + _fonts._charFor._lo = 247; + _fonts._charFor._hi = 255; + _screen->_maxChars = 50; + _screen->_printOrg = Common::Point(24, 18); + _screen->_printStart = Common::Point(24, 18); + + // Display death message + showDeathText(_deaths[deathId]._msg); + + _screen->forceFadeOut(); + _room->clearRoom(); + freeChar(); + + // The original was jumping to the restart label in main + _restartFl = true; + _events->pollEvents(); } } // End of namespace Martian diff --git a/engines/access/martian/martian_game.h b/engines/access/martian/martian_game.h index a83b67a288..9ef6c05c29 100644 --- a/engines/access/martian/martian_game.h +++ b/engines/access/martian/martian_game.h @@ -32,40 +32,41 @@ namespace Martian { class MartianEngine : public AccessEngine { private: bool _skipStart; - + SpriteResource *_introObjects; + Common::MemoryReadStream *_creditsStream; /** * Do the game introduction */ - void doIntroduction(); + void doCredits(); - /** - * Do title sequence - */ - void doTitle(); - - /** - * Do opening sequence - */ - void doOpening(); + bool showCredits(); /** * Setup variables for the game */ void setupGame(); + void initObjects(); + void configSelect(); + void initVariables(); protected: /** * Play the game */ virtual void playGame(); - virtual void dead(int deathId) {} + virtual void dead(int deathId); + + void setNoteParams(); + void displayNote(const Common::String &msg); public: - MartianEngine(OSystem *syst, const AccessGameDescription *gameDesc); + SpriteResource *_spec7Objects; + MartianEngine(OSystem *syst, const AccessGameDescription *gameDesc); virtual ~MartianEngine(); - void drawHelp(); + void doSpecial5(int param1); + void showDeathText(Common::String msg); virtual void establish(int esatabIndex, int sub) {}; }; diff --git a/engines/access/martian/martian_player.cpp b/engines/access/martian/martian_player.cpp new file mode 100644 index 0000000000..598664a600 --- /dev/null +++ b/engines/access/martian/martian_player.cpp @@ -0,0 +1,67 @@ +/* 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 "common/scummsys.h" +#include "access/access.h" +#include "access/room.h" +#include "access/martian/martian_game.h" +#include "access/martian/martian_player.h" +#include "access/martian/martian_resources.h" + +namespace Access { + +namespace Martian { + +MartianPlayer::MartianPlayer(AccessEngine *vm) : Player(vm) { + _game = (MartianEngine *)vm; +} + +void MartianPlayer::load() { + Player::load(); + + // Overwrite game-specific values + _playerOffset.x = _vm->_screen->_scaleTable1[20]; + _playerOffset.y = _vm->_screen->_scaleTable1[52]; + _leftDelta = -9; + _rightDelta = 33; + _upDelta = 5; + _downDelta = -5; + _scrollConst = 5; + + for (int i = 0; i < _vm->_playerDataCount; ++i) { + _walkOffRight[i] = SIDEOFFR[i]; + _walkOffLeft[i] = SIDEOFFL[i]; + _walkOffUp[i] = SIDEOFFU[i]; + _walkOffDown[i] = SIDEOFFD[i]; + } + + _sideWalkMin = 0; + _sideWalkMax = 7; + _upWalkMin = 8; + _upWalkMax = 14; + _downWalkMin = 15; + _downWalkMax = 23; +} + +} // End of namespace Martian + +} // End of namespace Access diff --git a/engines/access/martian/martian_player.h b/engines/access/martian/martian_player.h new file mode 100644 index 0000000000..007a32e9b2 --- /dev/null +++ b/engines/access/martian/martian_player.h @@ -0,0 +1,47 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ACCESS_MARTIAN_PLAYER_H +#define ACCESS_MARTIAN_PLAYER_H + +#include "common/scummsys.h" +#include "access/player.h" + +namespace Access { + +namespace Martian { + +class MartianEngine; + +class MartianPlayer : public Player { +private: + MartianEngine *_game; +public: + MartianPlayer(AccessEngine *vm); + virtual void load(); +}; + +} // End of namespace Martian + +} // End of namespace Access + +#endif /* ACCESS_MARTIAN_PLAYER_H */ diff --git a/engines/access/martian/martian_resources.cpp b/engines/access/martian/martian_resources.cpp index d2b5dfd5d0..474ec2f71c 100644 --- a/engines/access/martian/martian_resources.cpp +++ b/engines/access/martian/martian_resources.cpp @@ -41,51 +41,100 @@ const char *const FILENAMES[] = { }; const byte MOUSE0[] = { - 0, 0, 0, 0, 0, 2, 0xF7, 5, 0, 3, 0xF7, 0xF7, 5, 0, 3, - 0xF7, 0xF7, 5, 0, 4, 0xF7, 0xF7, 0xF7, 5, 0, 4, 0xF7, - 0xF7, 0xF7, 5, 0, 5, 0xF7, 0xF7, 0xF7, 0xF7, 5, 0, 5, - 0xF7, 0xF7, 0xF7, 0xF7, 5, 0, 6, 0xF7, 0xF7, 0xF7, 0xF7, - 0xF7, 5, 0, 6, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 5, 0, 7, - 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 5, 0, 6, 0xF7, 0xF7, - 0xF7, 0xF7, 0xF7, 5, 0, 5, 0xF7, 0xF7, 0xF7, 0xF7, 5, - 2, 3, 0xF7, 0xF7, 5, 3, 3, 0xF7, 0xF7, 5, 3, 3, 0xF7, - 0xF7, 5, 4, 2, 0xF7, 5 + // hotspot x and y, uint16 LE + 0, 0, 0, 0, + // byte 1: number of skipped pixels + // byte 2: number of plotted pixels + // then, pixels + 0, 2, 0xF7, 5, + 0, 3, 0xF7, 0xF7, 5, + 0, 3, 0xF7, 0xF7, 5, + 0, 4, 0xF7, 0xF7, 0xF7, 5, + 0, 4, 0xF7, 0xF7, 0xF7, 5, + 0, 5, 0xF7, 0xF7, 0xF7, 0xF7, 5, + 0, 5, 0xF7, 0xF7, 0xF7, 0xF7, 5, + 0, 6, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 5, + 0, 6, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 5, + 0, 7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 5, + 0, 6, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 5, + 0, 5, 0xF7, 0xF7, 0xF7, 0xF7, 5, + 2, 3, 0xF7, 0xF7, 5, + 3, 3, 0xF7, 0xF7, 5, + 3, 3, 0xF7, 0xF7, 5, + 4, 2, 0xF7, 5 }; const byte MOUSE1[] = { - 7, 0, 7, 0, 6, 1, 0xF7, 4, 5, 0xFF, 0xFF, 0, 0xFF, 0xFF, - 3, 7, 0xFF, 0, 0, 0, 0, 0, 0xFF, 2, 9, 0xFF, 0, 0, 0, - 0xF7, 0, 0, 0, 0xFF, 1, 11, 0xFF, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0xFF, 1, 11, 0xFF, 0, 0, 0, 0, 0xF7, 0, 0, - 0, 0, 0xFF, 0, 13, 0xF7, 0, 0, 0xF7, 0, 0xF7, 0, 0xF7, - 0, 0xF7, 0, 0, 0xF7, 1, 11, 0xFF, 0, 0, 0, 0, 0xF7, - 0, 0, 0, 0, 0xFF, 1, 11, 0xFF, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0xFF, 2, 9, 0xFF, 0, 0, 0, 0xF7, 0, 0, 0, 0xFF, - 3, 7, 0xFF, 0, 0, 0, 0, 0, 0xFF, 4, 5, 0xFF, 0xFF, 0, - 0xFF, 0xFF, 6, 1, 0xF7, 0, 0, 0, 0, 0, 0 + // hotspot x and y, uint16 LE + 7, 0, 7, 0, + // byte 1: number of skipped pixels + // byte 2: number of plotted pixels + // then, pixels + 6, 1, 0xF7, + 4, 5, 0xFF, 0xFF, 0, 0xFF, 0xFF, + 3, 7, 0xFF, 0, 0, 0, 0, 0, 0xFF, + 2, 9, 0xFF, 0, 0, 0, 0xF7, 0, 0, 0, 0xFF, + 1, 11, 0xFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, + 1, 11, 0xFF, 0, 0, 0, 0, 0xF7, 0, 0, 0, 0, 0xFF, + 0, 13, 0xF7, 0, 0, 0xF7, 0, 0xF7, 0, 0xF7, 0, 0xF7, 0, 0, 0xF7, + 1, 11, 0xFF, 0, 0, 0, 0, 0xF7, 0, 0, 0, 0, 0xFF, + 1, 11, 0xFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, + 2, 9, 0xFF, 0, 0, 0, 0xF7, 0, 0, 0, 0xFF, + 3, 7, 0xFF, 0, 0, 0, 0, 0, 0xFF, + 4, 5, 0xFF, 0xFF, 0, 0xFF, 0xFF, + 6, 1, 0xF7, + 0, 0, + 0, 0, + 0, 0 }; const byte MOUSE2[] = { - 8, 0, 8, 0, 0, 0, 0, 0, 7, 2, 4, 5, 7, 2, 4, 5, 7, 2, - 4, 5, 7, 2, 4, 5, 7, 2, 4, 5, 2, 12, 4, 4, 4, 4, 4, - 0, 4, 4, 4, 4, 4, 5, 7, 2, 4, 5, 7, 2, 4, 5, 7, 2, 4, - 5, 7, 2, 4, 5, 7, 2, 4, 5, 0, 0, 0, 0, 0, 0 + // hotspot x and y, uint16 LE + 8, 0, 8, 0, + // byte 1: number of skipped pixels + // byte 2: number of plotted pixels + // then, pixels + 0, 0, + 0, 0, + 7, 2, 4, 5, + 7, 2, 4, 5, + 7, 2, 4, 5, + 7, 2, 4, 5, + 7, 2, 4, 5, + 2, 12, 4, 4, 4, 4, 4, 0, 4, 4, 4, 4, 4, 5, + 7, 2, 4, 5, + 7, 2, 4, 5, + 7, 2, 4, 5, + 7, 2, 4, 5, + 7, 2, 4, 5, + 0, 0, + 0, 0, + 0, 0 }; const byte MOUSE3[] = { - 0, 0, 0, 0, 0, 11, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 0, 12, 6, 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 5, 0, 12, - 6, 7, 7, 7, 7, 7, 7, 7, 7, 6, 5, 5, 0, 12, 6, 6, 6, - 6, 6, 6, 6, 6, 6, 6, 6, 5, 0, 12, 6, 6, 6, 6, 6, 5, - 6, 6, 6, 6, 6, 5, 0, 12, 6, 6, 6, 6, 5, 0, 0, 6, 6, - 6, 6, 5, 0, 12, 6, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 5, - 0, 12, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 0, 12, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 0, 12, 6, 6, 6, - 6, 6, 5, 6, 6, 6, 6, 6, 5, 0, 12, 6, 6, 6, 6, 6, 5, - 6, 6, 6, 6, 6, 5, 0, 12, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 6, 6, 5, 1, 11, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, - 0, 0, 0, 0, 0 + // hotspot x and y, uint16 LE + 0, 0, 0, 0, + // byte 1: number of skipped pixels + // byte 2: number of plotted pixels + // then, pixels + 0, 11, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 0, 12, 6, 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 5, + 0, 12, 6, 7, 7, 7, 7, 7, 7, 7, 7, 6, 5, 5, + 0, 12, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, + 0, 12, 6, 6, 6, 6, 6, 5, 6, 6, 6, 6, 6, 5, + 0, 12, 6, 6, 6, 6, 5, 0, 0, 6, 6, 6, 6, 5, + 0, 12, 6, 6, 6, 6, 6, 0, 6, 6, 6, 6, 6, 5, + 0, 12, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, + 0, 12, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, + 0, 12, 6, 6, 6, 6, 6, 5, 6, 6, 6, 6, 6, 5, + 0, 12, 6, 6, 6, 6, 6, 5, 6, 6, 6, 6, 6, 5, + 0, 12, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, + 1, 11, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 0, 0, + 0, 0, + 0, 0 }; const byte *const CURSORS[4] = { MOUSE0, MOUSE1, MOUSE2, MOUSE3 }; -const int TRAVEL_POS[][2] = { +const int _travelPos[][2] = { { -1, 0 }, { 228, 117 }, { 28, 98 }, @@ -139,17 +188,19 @@ const int TRAVEL_POS[][2] = { { -1, 21 } }; +const int INVENTORY_SIZE = 55; const char *const INVENTORY_NAMES[] = { - "CAMERA", "LENS", "PHOTOS", "MAIL", "GUN", "CASH", "COMLINK", "AMMO", - "LOCKPICK KIT", "EARRING", "RECIEPTS", "PAPER", "LADDER", "BOOTS", - "DOCUMENTS", "KNIFE", "DAGGER", "KEYS", "ROCK", "LOG", "SHOVEL", - "STONE", "REMOTE CONTROL", "FOOD AND WATER", "DOOR CARD KEY", + "CAMERA", "LENS", "PHOTOS", "MAIL", "GUN", + "CASH", "COMLINK", "AMMO", "LOCKPICK KIT", "EARRING", + "RECIEPTS", "PAPER", "LADDER", "BOOTS", "DOCUMENTS", + "KNIFE", "DAGGER", "KEYS", "ROCK", "LOG", + "SHOVEL", "STONE", "REMOTE CONTROL", "FOOD AND WATER", "DOOR CARD KEY", "FLASHLIGHT", "INTERLOCK KEY", "TOOLS", "REBREATHER", "JET PACK", - "ROD", "HCL2", "SAFE CARD KEY", "TUNING FORK", "STONE", "ROSE", - "KEY", "NOTE", "ALLEN WRENCH", "HOVER BOARD", "BLUE PRINTS", - "LETTER", "MEMORANDUM", "MARKERS", "FILM", "ANDRETTI FILM", - "GLASSES", "AMULET", "FACIAL KIT", "CAT FOOD", "MONKEY WRENCH", - "BIG DICK CARD", "BRA", "BOLT" + "ROD", "HCL2", "SAFE CARD KEY", "TUNING FORK", "STONE", + "ROSE", "KEY", "NOTE", "ALLEN WRENCH", "HOVER BOARD", + "BLUE PRINTS", "LETTER", "MEMORANDUM", "MARKERS", "FILM", + "ANDRETTI FILM", "GLASSES", "AMULET", "FACIAL KIT", "CAT FOOD", + "MONKEY WRENCH", "BIG DICK CARD", "BRA", "BOLT", nullptr }; const byte ROOM_TABLE1[] = { @@ -436,12 +487,12 @@ const char *const ROOM_DESCR[] = { const int ROOM_NUMB = 48; -const byte CHAR_TABLE0[] = { +const byte MMCHAR_0[] = { 0x02, 0x31, 0x00, 0x08, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, }; -const byte CHAR_TABLE2[] = { +const byte MMCHAR_2[] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x32, 0x33, 0x00, 0x01, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x33, 0x00, 0x00, 0x00, 0x33, @@ -454,13 +505,13 @@ const byte CHAR_TABLE2[] = { 0x00, 0x12, 0x00, 0x33, 0x00, 0x0a, 0x00, 0x33, 0x00, 0x13, 0x00, 0xff, 0xff, }; -const byte CHAR_TABLE3[] = { +const byte MMCHAR_3[] = { 0x02, 0x31, 0x00, 0x03, 0x00, 0x35, 0x00, 0x37, 0x00, 0x02, 0x00, 0x80, 0x00, 0xf7, 0x00, 0x4b, 0x37, 0x00, 0x01, 0x00, 0xff, 0x37, 0x00, 0x03, 0x00, 0x37, 0x00, 0x00, 0x00, 0xff, 0xff, }; -const byte CHAR_TABLE4[] = { +const byte MMCHAR_4[] = { 0x01, 0x31, 0x00, 0x0a, 0x00, 0x36, 0x00, 0x35, 0x00, 0x02, 0x00, 0x80, 0x00, 0xf7, 0x00, 0x49, 0x35, 0x00, 0x01, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x35, 0x00, 0x00, 0x00, 0x35, @@ -473,7 +524,7 @@ const byte CHAR_TABLE4[] = { 0x00, 0x13, 0x00, 0x35, 0x00, 0x0b, 0x00, 0x35, 0x00, 0x14, 0x00, 0xff, 0xff, }; -const byte CHAR_TABLE5[] = { +const byte MMCHAR_5[] = { 0x01, 0x31, 0x00, 0x08, 0x00, 0x37, 0x00, 0x34, 0x00, 0x02, 0x00, 0x80, 0x00, 0xf7, 0x00, 0x48, 0x34, 0x00, 0x01, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x34, 0x00, 0x00, 0x00, 0x43, @@ -490,31 +541,31 @@ const byte CHAR_TABLE5[] = { 0x00, 0x0f, 0x00, 0x43, 0x00, 0x0d, 0x00, 0x34, 0x00, 0x10, 0x00, 0xff, 0xff, }; -const byte CHAR_TABLE6[] = { +const byte MMCHAR_6[] = { 0x02, 0x31, 0x00, 0x03, 0x00, 0x38, 0x00, 0x44, 0x00, 0x03, 0x00, 0x80, 0x00, 0xf7, 0x00, 0x4e, 0x44, 0x00, 0x01, 0x00, 0xff, 0x44, 0x00, 0x02, 0x00, 0x44, 0x00, 0x00, 0x00, 0xff, 0xff, }; -const byte CHAR_TABLE7[] = { +const byte MMCHAR_7[] = { 0x02, 0x31, 0x00, 0x01, 0x00, 0x39, 0x00, 0x38, 0x00, 0x02, 0x00, 0x80, 0x00, 0xf7, 0x00, 0x4c, 0x38, 0x00, 0x01, 0x00, 0xff, 0x38, 0x00, 0x03, 0x00, 0x38, 0x00, 0x00, 0x00, 0xff, 0xff, }; -const byte CHAR_TABLE8[] = { +const byte MMCHAR_8[] = { 0x03, 0xff, 0xff, 0xff, 0xff, 0x3a, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x4d, 0x3b, 0x00, 0x01, 0x00, 0xff, 0x3b, 0x00, 0x02, 0x00, 0x3b, 0x00, 0x00, 0x00, 0xff, 0xff, }; -const byte CHAR_TABLE9[] = { +const byte MMCHAR_9[] = { 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x59, 0x4a, 0x00, 0x01, 0x00, 0xff, 0x4a, 0x00, 0x02, 0x00, 0x4a, 0x00, 0x00, 0x00, 0xff, 0xff, }; -const byte CHAR_TABLE10[] = { +const byte MMCHAR_10[] = { 0x01, 0x31, 0x00, 0x0a, 0x00, 0x3c, 0x00, 0x36, 0x00, 0x02, 0x00, 0x80, 0x00, 0xf7, 0x00, 0x4a, 0x36, 0x00, 0x01, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x36, 0x00, 0x00, 0x00, 0x36, @@ -532,19 +583,19 @@ const byte CHAR_TABLE10[] = { 0x00, 0x36, 0x00, 0x11, 0x00, 0x36, 0x00, 0x21, 0x00, 0x36, 0x00, 0x12, 0x00, 0x36, 0x00, 0x22, 0x00, 0xff, 0xff, }; -const byte CHAR_TABLE11[] = { +const byte MMCHAR_11[] = { 0x03, 0xff, 0xff, 0xff, 0xff, 0x3d, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x55, 0x45, 0x00, 0x01, 0x00, 0xff, 0x45, 0x00, 0x02, 0x00, 0x45, 0x00, 0x00, 0x00, 0xff, 0xff, }; -const byte CHAR_TABLE12[] = { +const byte MMCHAR_12[] = { 0x03, 0xff, 0xff, 0xff, 0xff, 0x3e, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x4f, 0x40, 0x00, 0x01, 0x00, 0xff, 0x40, 0x00, 0x02, 0x00, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, }; -const byte CHAR_TABLE13[] = { +const byte MMCHAR_13[] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0x3f, 0x00, 0x46, 0x00, 0x02, 0x00, 0x80, 0x00, 0xf7, 0x00, 0x56, 0x46, 0x00, 0x01, 0x00, 0xff, 0x46, 0x00, 0x03, 0x00, 0x46, 0x00, 0x00, 0x00, 0x46, @@ -557,7 +608,7 @@ const byte CHAR_TABLE13[] = { 0x00, 0x14, 0x00, 0x46, 0x00, 0x0c, 0x00, 0x46, 0x00, 0x15, 0x00, 0xff, 0xff, }; -const byte CHAR_TABLE15[] = { +const byte MMCHAR_15[] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0x41, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x57, 0x47, 0x00, 0x01, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x47, 0x00, 0x00, 0x00, 0x47, @@ -565,43 +616,43 @@ const byte CHAR_TABLE15[] = { 0x00, 0x47, 0x00, 0x06, 0x00, 0x47, 0x00, 0x04, 0x00, 0x47, 0x00, 0x07, 0x00, 0xff, 0xff, }; -const byte CHAR_TABLE16[] = { +const byte MMCHAR_16[] = { 0x03, 0xff, 0xff, 0xff, 0xff, 0x42, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x54, 0x41, 0x00, 0x01, 0x00, 0xff, 0x41, 0x00, 0x02, 0x00, 0x41, 0x00, 0x00, 0x00, 0xff, 0xff, }; -const byte CHAR_TABLE18[] = { +const byte MMCHAR_18[] = { 0x02, 0x31, 0x00, 0x07, 0x00, 0x44, 0x00, 0x3c, 0x00, 0x03, 0x00, 0x80, 0x00, 0xf7, 0x00, 0x50, 0x3c, 0x00, 0x01, 0x00, 0xff, 0x3c, 0x00, 0x02, 0x00, 0x3c, 0x00, 0x00, 0x00, 0xff, 0xff, }; -const byte CHAR_TABLE19[] = { +const byte MMCHAR_19[] = { 0x02, 0x31, 0x00, 0x07, 0x00, 0x45, 0x00, 0x3d, 0x00, 0x03, 0x00, 0x80, 0x00, 0xf7, 0x00, 0x51, 0x3d, 0x00, 0x01, 0x00, 0xff, 0x3d, 0x00, 0x02, 0x00, 0x3d, 0x00, 0x00, 0x00, 0xff, 0xff, }; -const byte CHAR_TABLE20[] = { +const byte MMCHAR_20[] = { 0x02, 0x31, 0x00, 0x02, 0x00, 0x46, 0x00, 0x48, 0x00, 0x02, 0x00, 0x80, 0x00, 0xf7, 0x00, 0x58, 0x48, 0x00, 0x01, 0x00, 0xff, 0x48, 0x00, 0x03, 0x00, 0x48, 0x00, 0x00, 0x00, 0xff, 0xff, }; -const byte CHAR_TABLE21[] = { +const byte MMCHAR_21[] = { 0x02, 0x31, 0x00, 0x07, 0x00, 0x47, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x80, 0x00, 0xf7, 0x00, 0x52, 0x3e, 0x00, 0x01, 0x00, 0xff, 0x3e, 0x00, 0x02, 0x00, 0x3e, 0x00, 0x00, 0x00, 0xff, 0xff, }; -const byte CHAR_TABLE23[] = { +const byte MMCHAR_23[] = { 0x02, 0x31, 0x00, 0x08, 0x00, 0x49, 0x00, 0x3f, 0x00, 0x03, 0x00, 0x80, 0x00, 0xf7, 0x00, 0x53, 0x3f, 0x00, 0x01, 0x00, 0xff, 0x3f, 0x00, 0x02, 0x00, 0x3f, 0x00, 0x00, 0x00, 0xff, 0xff, }; -const byte CHAR_TABLE24[] = { +const byte MMCHAR_24[] = { 0x02, 0x32, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x47, 0x32, 0x00, 0x02, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x32, 0x00, 0x01, 0x00, 0x32, @@ -612,13 +663,13 @@ const byte CHAR_TABLE24[] = { 0x00, 0x08, 0x00, 0x32, 0x00, 0x0f, 0x00, 0x32, 0x00, 0x09, 0x00, 0x32, 0x00, 0x10, 0x00, 0xff, 0xff }; -const byte CHAR_TABLE25[] = { +const byte MMCHAR_25[] = { 0x02, 0x39, 0x00, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x39, 0x00, 0x00, 0x00, 0x39, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF }; -const byte CHAR_TABLE26[] = { +const byte MMCHAR_26[] = { 0x01, 0x3a, 0x00, 0x01, 0x00, 0x0a, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x3a, 0x00, 0x02, 0x00, 0xff, 0x3a, 0x00, 0x03, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x42, @@ -638,7 +689,7 @@ const byte CHAR_TABLE26[] = { 0x00, 0x3a, 0x00, 0x14, 0x00, 0x42, 0x00, 0x11, 0x00, 0x3a, 0x00, 0x15, 0x00, 0xff, 0xff }; -const byte CHAR_TABLE27[] = { +const byte MMCHAR_27[] = { 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x58, 0x49, 0x00, 0x01, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x49, 0x00, 0x00, 0x00, 0x49, @@ -650,73 +701,144 @@ const byte CHAR_TABLE27[] = { 0x00, 0x49, 0x00, 0x10, 0x00, 0x49, 0x00, 0x09, 0x00, 0x49, 0x00, 0x11, 0x00, 0xff, 0xff, }; -const byte *const CHAR_TABLE[] = { - CHAR_TABLE0, nullptr, CHAR_TABLE2, CHAR_TABLE3, CHAR_TABLE4, CHAR_TABLE5, - CHAR_TABLE6, CHAR_TABLE7, CHAR_TABLE8, CHAR_TABLE9, CHAR_TABLE10, - CHAR_TABLE11, CHAR_TABLE12, CHAR_TABLE13, nullptr, CHAR_TABLE15, - CHAR_TABLE16, nullptr, CHAR_TABLE18, CHAR_TABLE19, CHAR_TABLE20, - CHAR_TABLE21, nullptr, CHAR_TABLE23, CHAR_TABLE24, CHAR_TABLE25, - CHAR_TABLE26, CHAR_TABLE27 + +// HACK: MMCHAR_0 has been used to replace the missing CHAR: 1, 14, 17 and 22 +const byte *const CHARTBL_MM[] = { + MMCHAR_0, MMCHAR_0, MMCHAR_2, MMCHAR_3, MMCHAR_4, + MMCHAR_5, MMCHAR_6, MMCHAR_7, MMCHAR_8, MMCHAR_9, + MMCHAR_10, MMCHAR_11, MMCHAR_12, MMCHAR_13, MMCHAR_0, + MMCHAR_15, MMCHAR_16, MMCHAR_0, MMCHAR_18, MMCHAR_19, + MMCHAR_20, MMCHAR_21, MMCHAR_0, MMCHAR_23, MMCHAR_24, + MMCHAR_25, MMCHAR_26, MMCHAR_27 }; -// TODO: Fix that array -const int COMBO_TABLE[54][4] = { - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 }, - { -1, -1, -1, -1 } +const int SIDEOFFR[] = { 4, 0, 7, 10, 3, 1, 2, 13, 0, 0, 0, 0 }; +const int SIDEOFFL[] = { 11, 6, 1, 4, 10, 6, 1, 4, 0, 0, 0, 0 }; +const int SIDEOFFU[] = { 1, 2, 0, 2, 2, 1, 1, 0, 0, 0, 0, 0 }; +const int SIDEOFFD[] = { 2, 0, 1, 1, 0, 1, 1, 1, 2, 0, 0, 0 }; + +const byte CREDIT_DATA[] = { + 0x1F, 0x00, 0x49, 0x00, 0x00, 0x00, 0xB7, 0x00, 0x49, 0x00, + 0x01, 0x00, 0x79, 0x00, 0x6F, 0x00, 0x02, 0x00, 0xFF, 0xFF, + 0xEA, 0x01, 0x75, 0x00, 0x46, 0x00, 0x03, 0x00, 0x46, 0x00, + 0x5E, 0x00, 0x04, 0x00, 0xFF, 0xFF, 0xEA, 0x01, 0x72, 0x00, + 0x3E, 0x00, 0x05, 0x00, 0x46, 0x00, 0x57, 0x00, 0x04, 0x00, + 0x5C, 0x00, 0x6E, 0x00, 0x06, 0x00, 0xFF, 0xFF, 0xEA, 0x01, + 0x63, 0x00, 0x48, 0x00, 0x07, 0x00, 0x2A, 0x00, 0x65, 0x00, + 0x08, 0x00, 0xFF, 0xFF, 0xEA, 0x01, 0x7E, 0x00, 0x39, 0x00, + 0x09, 0x00, 0x5C, 0x00, 0x57, 0x00, 0x06, 0x00, 0x45, 0x00, + 0x6B, 0x00, 0x04, 0x00, 0xFF, 0xFF, 0xEA, 0x01, 0x5F, 0x00, + 0x46, 0x00, 0x0A, 0x00, 0x67, 0x00, 0x62, 0x00, 0x0B, 0x00, + 0x47, 0x00, 0x76, 0x00, 0x0C, 0x00, 0xFF, 0xFF, 0xEA, 0x01, + 0x62, 0x00, 0x38, 0x00, 0x0D, 0x00, 0x47, 0x00, 0x55, 0x00, + 0x0E, 0x00, 0x49, 0x00, 0x6A, 0x00, 0x0F, 0x00, 0xFF, 0xFF, + 0xEA, 0x01, 0x18, 0x00, 0x22, 0x00, 0x10, 0x00, 0x17, 0x00, + 0x3E, 0x00, 0x11, 0x00, 0x16, 0x00, 0x52, 0x00, 0x12, 0x00, + 0xEE, 0x00, 0x7B, 0x00, 0x13, 0x00, 0xB5, 0x00, 0x93, 0x00, + 0x0B, 0x00, 0xFF, 0xFF, 0xF4, 0x01, 0xFF, 0xFF, 0xFF, 0xFF }; -} // End of namespace Martian +const byte ICON_PALETTE[] = { + 0x3F, 0x3F, 0x00, 0x00, 0x07, 0x16, + 0x00, 0x0A, 0x1A, 0x00, 0x0D, 0x1F, + 0x00, 0x11, 0x28, 0x00, 0x15, 0x30, + 0x00, 0x19, 0x39, 0x00, 0x1B, 0x3F, + 0x00, 0x2D, 0x3A +}; + +const int RMOUSE[10][2] = { + { 7, 36 }, { 38, 68 }, { 70, 99 }, { 102, 125 }, { 128, 152 }, + { 155, 185 }, { 188, 216 }, { 219, 260 }, { 263, 293 }, { 295, 314 } +}; + +const char *const TRAVDATA[] = { + "GALACTIC PICTURES", "TERRAFORM", "WASHROOM", "MR. BIG", "ALEXIS' HOME", + "JOHNNY FEDORA", "JUNKYARD IN", "TEX'S OFFICE", "MURDER SCENE", "PLAZA HOTEL", + "RESTAURANT", "GIFT SHOP", "LOVE SCENE", "RICK LOGAN", "HUT", + "SMUGGLERS BASE", "PYRAMID", "CASINO", "CAS LOBBY", "BAR", + "DUCTWORK", "RESTROOM", "OFFICE", "SAFE", "ALLEY", + "POWER PLANT", "PLANT OFFICE", "PLANT ROOM", "TEMPLE", "IN TEMPLE", + "JANE MANSFIELD'S HOME", "AEROBICS ACADEMY", "DR. LAWRENCE BARKLEY", "COLONISTS CAMP", "IN SLUM", + "REMOTE OUTPOST", "WALK", "CAVE", "PRISON", "ORACLE", + "JOCQUES SPARROW", "MAC MALDEN", "CHANTAL VARGAS", "GUY CALLABERO", "ROCKWELL BACHE", + "FERRIS COLLETTE", "NORA DESMOND ALEXANDER", "LOWELL PERCIVAL", "MICHELE BLOODWORTH", "BRADLEY ERICSON", + "COOPER BRADBURY", nullptr +}; + +const char *const _askTBL[] = { + "NONE", "MARSHALL ALEXANDER", "TERRAFORM CORP.", "COLLIER STANTON", "ROCKWELL BACHE", + "JOCQUES SPARROW", "NORA DESMOND ALEXANDER", "GALACTIC PICTURES", "LAWRENCE BARKLEY", "TMS", + "MAC MALDEN", "STANTON EXPEDITION", "LOWELL PERCIVAL", "CHANTAL VARGAS", "RICK LOGAN", + "ALEXIS ALEXANDER", "FERRIS COLLETT", "GUY CALLABERO", "ORACLE STONE", "THOMAS DANGERFIELD", + "JANE MANSFIELD", "STACY CRAWFORD", "DICK CASTRO", "ROCKY BULLWINKEL", "DEACON HAWKE", + "NATHAN BLOODWORTH", "MICHELLE BLOODWORTH", "BRADLEY ERICSON", "COOPER BRADBURY", "MARTIAN MEMORANDUM", + "JOHNNY FEDORA", "RHONDA FOXWORTH", "ANGELO ANDRETTI", "TEX MURPHY", "ROBERT BLOODWORTH", + "LARRRY HAMMOND", nullptr +}; + +byte HELP[] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 +}; +const byte DEATH_SCREENS[] = { + 5, 5, 3, 3, 7, 4, 6, 2, 2, 2, 1, 5, 3, 5, 2, 8, 5, 3, 8, 5 +}; + +const char *const DEATHMESSAGE[] = { + "A VICIOUS THUG PULLS OUT HIS GUN AND AIR CONDITIONS YOUR BRAIN.", + "BIG DICK COMES BACK AND ANNOUNCES YOUR TIME IS UP. ONE OF HIS BOYS PROCEEDS TO PART YOUR EYEBROWS.", + "ALTHOUGH HIS FIRST SHOT MISSED, THE PUNK FINDS YOU AND TURNS YOU INTO A DOUGHNUT.", + "THE CREEP SPOTS YOU. HE TURNS AND FIRES HIS WEAPON. IT BURNS A HOLE A BUZZARD CAN FLY THROUGH.", + "OBVIOUSLY RICK LOGAN HAS A FEW TRICK UP HIS SLEEVE. A TREMENDOUS WEIGHT HITS YOUR HEAD. YOU MUMBLE; WATCH OUT FOR THAT TREE...", + "SLOWLY SINKING IN THE SLIMY OOZE, YOU THINK OF SEVERAL JELLO WRESTLING MATCHES YOU'VE ATTENDED. BUT NO MORE...", + "THE PATH SUDDENLY GIVES WAY AND YOU FEEL MANY STAKES TEAR THROUGH YOUR FLESH. HOW DO YOU LIKE YOUR STAKE", + "THE SNAKE SINKS ITS FANGS INTO YOU LEG. THE POISON WORKS QUICKLY. THE SNAKE THEN SWALLOWS YOU WHOLE.", + "YOU FADE AWAY, GLOWING LIKE A LIGHTBULB.", + "YOU TOUCH THE BUBBLING RADIOACTIVE SELTZER. IT IMMEDIATELY CAUSES VITAL ORGANS TO ELONGATE AND EXPLODE. YOU DIE WITH AN ABSURD AND FOOLISH LOOK ON YOUR FACE.", + "THE DOGS PRETTY HUNGRY. IT WON'T TAKE HIM LONG TO FINISH SO SIT BACK AND ENJOY IT.", + "ROCKY DOESN'T LIKE BEING FOLLOWED. HE DECIDES TO BEAT YOU. WITHIN AND INCH OF YOUR LIFE. UNFORTUNATELY, HE MISJUDGED THE DISTANCE", + "YOU STUMBLE INTO DEADLY LASER FIRE.", + "THE OUTPOST AND YOUR BODY PARTS ARE BLOWN TO KINGDOM COME.", + "YOU REACH THE TOP, BUT YOUR AIR SOON RUNS OUT LEAVING YOU BREATHLESS.", + "YOU DIE IN THE FIERY EXPLOSION.", + "YOU FALL HUNDREDS OF FEET TO YOUR DEATH.", + "YOU WALK ONTO A PRESSURE SENSITIVE SECURITY PAD. A LASER ZEROS IN AND BLOWS A HOLE THE SIZE OF A SUBARU TIRE THROUGH YOU.", + "DANGERFIELD'S EXPERIMENT BACKFIRES. IT RELEASES A DEMON FROM HIS SUBCONSCIOUS WHICH DESTROYS THE ENTIRE PLANET.", + "ONCE DANGERFIELD GETS OUT OF HIS CHAMBER, HE PULLS OUT A WEAPON AND LETS YOU HAVE IT." +}; + +const char *const SPEC7MESSAGE = { + "THOMAS DANGERFIELD'S MAD EXPERIMENT OF ATTEMPTING TO HARNESS THE STONE'S POWER, ENDED HIS LIFE. DANGERFIELD WAS A DECENT HUMAN " \ + "BEING ONCE, BUT WAS DRIVEN INSANE BY HIS QUEST FOR THE ULTIMATE POWER. ALEXIS AND I DECIDE THAT DEACON HAWKE IS THE ONLY " \ + "LOGICAL CHOICE FOR THE STONE. WE ARRIVE AT THE TEMPLE WHERE SHE IS WAITING FOR US. SHE TURNS AND WHISPERS; 'YOU HAVE RETURNED " \ + "THE STONE TO THE MISTRESS OF THE LIGHT. YOU HAVE SURELY SAVED THE WORLD FROM ANNIHILATION BUT YOU MUST CONTINUE TO BE DILIGENT. " \ + "MANKIND MAY YET PROVE TO BE THE AUTHOR OF HIS OWN DEMISE. REVERENCE LIFE. PROTECT THE LIVING THINGS AND RECYCLE. AND NOW FOR " \ + "THE SAFETY OF MANKIND, I MUST TAKE THE STONE. PERHAPS SOMEDAY, WHEN THE HUMAN RACE IS READY, IT WILL BE RETURNED. UNTIL THEN "\ + "FAREWELL...'" +}; + +const byte _byte1EEB5[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, + 1 +}; + +const int PICTURERANGE[][2] = { +// { min X, max X}, {min Y, max Y} + { 20, 30 }, { 82, 87 }, + { 20, 30 }, { 105, 110 }, + { 0, 8 }, { 92, 100 }, + { 42, 46 }, { 92, 100 }, + { 9, 41 }, { 88, 104 }, + { 9, 41 }, { 117, 133 }, + { -1, -1 } +}; + +} // End of namespace Martian } // End of namespace Access diff --git a/engines/access/martian/martian_resources.h b/engines/access/martian/martian_resources.h index a52967d42a..2eb810ac80 100644 --- a/engines/access/martian/martian_resources.h +++ b/engines/access/martian/martian_resources.h @@ -31,22 +31,46 @@ namespace Martian { extern const char *const FILENAMES[]; +extern const int SIDEOFFR[]; +extern const int SIDEOFFL[]; +extern const int SIDEOFFU[]; +extern const int SIDEOFFD[]; + extern const byte *const CURSORS[4]; -extern const int TRAVEL_POS[][2]; +extern const int _travelPos[][2]; +extern const int INVENTORY_SIZE; extern const char *const INVENTORY_NAMES[]; extern const byte *const ROOM_TABLE[]; extern const char *const ROOM_DESCR[]; extern const int ROOM_NUMB; -extern const byte *const CHAR_TABLE[]; +extern const byte *const CHARTBL_MM[]; -extern const int COMBO_TABLE[54][4]; +extern const int SIDEOFFR[]; +extern const int SIDEOFFL[]; +extern const int SIDEOFFU[]; +extern const int SIDEOFFD[]; -} // End of namespace Martian +extern const byte CREDIT_DATA[]; +extern const byte ICON_PALETTE[]; + +extern const int RMOUSE[10][2]; + +extern byte HELP[]; +extern const char *const _askTBL[]; +extern const char *const TRAVDATA[]; +extern const byte DEATH_SCREENS[]; +extern const char *const DEATHMESSAGE[]; +extern const char *const SPEC7MESSAGE; + +extern const byte _byte1EEB5[]; +extern const int PICTURERANGE[][2]; + +} // End of namespace Martian } // End of namespace Access #endif /* ACCESS_MARTIAN_RESOURCES_H */ diff --git a/engines/access/martian/martian_room.cpp b/engines/access/martian/martian_room.cpp index e9d1b9d8cf..d5b03db246 100644 --- a/engines/access/martian/martian_room.cpp +++ b/engines/access/martian/martian_room.cpp @@ -43,72 +43,47 @@ void MartianRoom::loadRoom(int roomNumber) { } void MartianRoom::reloadRoom() { - loadRoom(_vm->_player->_roomNumber); - - if (_roomFlag != 1) { - _vm->_currentMan = _roomFlag; - _vm->_currentManOld = _roomFlag; - _vm->_manScaleOff = 0; - - switch (_vm->_currentMan) { - case 0: - _vm->_player->loadSprites("MAN.LZ"); - break; - - case 2: - _vm->_player->loadSprites("JMAN.LZ"); - break; +// _vm->_currentMan = _roomFlag; +// _vm->_currentManOld = _roomFlag; +// _vm->_manScaleOff = 0; - case 3: - _vm->_player->loadSprites("OVERHEAD.LZ"); - _vm->_manScaleOff = 1; - break; + _vm->_player->loadTexPalette(); + _vm->_player->loadSprites("TEX.LZ"); - default: - break; - } - } + loadRoom(_vm->_player->_roomNumber); reloadRoom1(); } void MartianRoom::reloadRoom1() { - if (_vm->_player->_roomNumber == 29 || _vm->_player->_roomNumber == 31 - || _vm->_player->_roomNumber == 42 || _vm->_player->_roomNumber == 44) { - //Resource *spriteData = _vm->_files->loadFile("MAYA.LZ"); - //_vm->_inactive._spritesPtr = new SpriteResource(_vm, spriteData); - //delete spriteData; - _vm->_currentCharFlag = false; - } - _selectCommand = -1; - _vm->_events->setNormalCursor(CURSOR_CROSSHAIRS); - _vm->_mouseMode = 0; - _vm->_boxSelect = true; + _vm->_boxSelect = false; //-1 _vm->_player->_playerOff = false; - _vm->_screen->fadeOut(); + _vm->_screen->forceFadeOut(); + _vm->_events->hideCursor(); _vm->_screen->clearScreen(); + _vm->_events->showCursor(); roomSet(); + _vm->_player->load(); - // TODO: Refactor + if (_vm->_player->_roomNumber != 47) + _vm->_player->calcManScale(); + _vm->_events->hideCursor(); + roomMenu(); _vm->_screen->setBufferScan(); setupRoom(); setWallCodes(); buildScreen(); + _vm->copyBF2Vid(); - if (!_vm->_screen->_vesaMode) { - _vm->copyBF2Vid(); - } else if (_vm->_player->_roomNumber != 20 && _vm->_player->_roomNumber != 24 - && _vm->_player->_roomNumber != 33) { - _vm->_screen->setPalette(); - _vm->copyBF2Vid(); - } - + _vm->_screen->setManPalette(); + _vm->_events->showCursor(); _vm->_player->_frame = 0; _vm->_oldRects.clear(); _vm->_newRects.clear(); + _vm->_events->clearEvents(); } void MartianRoom::roomSet() { @@ -116,6 +91,12 @@ void MartianRoom::roomSet() { _vm->_scripts->_sequence = 1000; _vm->_scripts->searchForSequence(); _vm->_scripts->executeScript(); + + for (int i = 0; i < 30; i++) + _byte26CD2[i] = 0; + + for (int i = 0; i < 10; i++) + _byte26CBC[i] = 0; } void MartianRoom::roomMenu() { @@ -126,16 +107,31 @@ void MartianRoom::roomMenu() { _vm->_screen->saveScreen(); _vm->_screen->setDisplayScan(); _vm->_destIn = _vm->_screen; // TODO: Redundant - _vm->_screen->plotImage(spr, 0, Common::Point(0, 177)); - _vm->_screen->plotImage(spr, 1, Common::Point(143, 177)); + _vm->_screen->plotImage(spr, 0, Common::Point(5, 184)); + _vm->_screen->plotImage(spr, 1, Common::Point(155, 184)); _vm->_screen->restoreScreen(); delete spr; } void MartianRoom::mainAreaClick() { + Common::Point &mousePos = _vm->_events->_mousePos; + Common::Point pt = _vm->_events->calcRawMouse(); + Screen &screen = *_vm->_screen; + Player &player = *_vm->_player; + + if (_selectCommand == -1) { + player._moveTo = pt; + player._playerMove = true; + } else if (mousePos.x >= screen._windowXAdd && + mousePos.x <= (screen._windowXAdd + screen._vWindowBytesWide) && + mousePos.y >= screen._windowYAdd && + mousePos.y <= (screen._windowYAdd + screen._vWindowLinesTall)) { + if (checkBoxes1(pt) >= 0) { + checkBoxes3(); + } + } } } // End of namespace Martian - } // End of namespace Access diff --git a/engines/access/martian/martian_room.h b/engines/access/martian/martian_room.h index 85529ce8f0..11501b6e57 100644 --- a/engines/access/martian/martian_room.h +++ b/engines/access/martian/martian_room.h @@ -39,6 +39,9 @@ private: MartianEngine *_game; void roomSet(); + + int _byte26CD2[30]; + int _byte26CBC[10]; protected: virtual void loadRoom(int roomNumber); @@ -52,8 +55,6 @@ public: virtual ~MartianRoom(); - virtual void loadRoomData(const byte *roomData) { warning("TODO - loadRoomData"); } - virtual void init4Quads() { } virtual void roomMenu(); diff --git a/engines/access/martian/martian_scripts.cpp b/engines/access/martian/martian_scripts.cpp index 0578872092..a9b5de5597 100644 --- a/engines/access/martian/martian_scripts.cpp +++ b/engines/access/martian/martian_scripts.cpp @@ -34,7 +34,300 @@ MartianScripts::MartianScripts(AccessEngine *vm) : Scripts(vm) { _game = (MartianEngine *)_vm; } +void MartianScripts::cmdSpecial0() { + _vm->_sound->stopSound(); + _vm->_midi->stopSong(); + + _vm->_midi->loadMusic(47, 1); + _vm->_midi->midiPlay(); + _vm->_midi->setLoop(true); + + _vm->_events->_vbCount = 300; + while (!_vm->shouldQuit() && _vm->_events->_vbCount > 0) + _vm->_events->pollEventsAndWait(); + + _vm->_screen->forceFadeOut(); + _vm->_files->loadScreen("HOUSE.SC"); + + _vm->_video->setVideo(_vm->_screen, Common::Point(46, 30), "HVID.VID", 20); + + do { + _vm->_video->playVideo(); + if (_vm->_video->_videoFrame == 4) { + _vm->_screen->flashPalette(16); + _vm->_sound->playSound(4); + do { + _vm->_events->pollEvents(); + } while (!_vm->shouldQuit() && _vm->_sound->_playingSound); + _vm->_timers[31]._timer = _vm->_timers[31]._initTm = 40; + } + } while (!_vm->_video->_videoEnd && !_vm->shouldQuit()); + + if (_vm->_video->_videoEnd) { + _vm->_screen->flashPalette(12); + _vm->_sound->playSound(4); + do { + _vm->_events->pollEvents(); + } while (!_vm->shouldQuit() && _vm->_sound->_playingSound); + _vm->_midi->stopSong(); + _vm->_midi->freeMusic(); + warning("TODO: Pop Midi"); + } +} + +void MartianScripts::cmdSpecial1(int param1) { + _vm->_events->hideCursor(); + + if (param1 != -1) { + _vm->_files->loadScreen(49, param1); + _vm->_buffer2.copyBuffer(_vm->_screen); + } + + _vm->_screen->setIconPalette(); + _vm->_screen->forceFadeIn(); + _vm->_events->showCursor(); +} + +void MartianScripts::cmdSpecial3() { + _vm->_screen->forceFadeOut(); + _vm->_events->hideCursor(); + _vm->_files->loadScreen(57, 3); + _vm->_buffer2.copyFrom(*_vm->_screen); + + _vm->_screen->setIconPalette(); + _vm->_events->showCursor(); + _vm->_screen->forceFadeIn(); +} + +void MartianScripts::doIntro(int param1) { + _game->doSpecial5(param1); +} + +void MartianScripts::cmdSpecial6() { + _vm->_midi->stopSong(); + _vm->_screen->setDisplayScan(); + _vm->_events->clearEvents(); + _vm->_screen->forceFadeOut(); + _vm->_events->hideCursor(); + _vm->_files->loadScreen(49, 9); + _vm->_events->showCursor(); + _vm->_screen->setIconPalette(); + _vm->_screen->forceFadeIn(); + + Resource *cellsRes = _vm->_files->loadFile("CELLS00.LZ"); + _vm->_objectsTable[0] = new SpriteResource(_vm, cellsRes); + delete cellsRes; + + _vm->_timers[20]._timer = _vm->_timers[20]._initTm = 30; + _vm->_fonts._charSet._lo = 1; + _vm->_fonts._charSet._hi = 10; + _vm->_fonts._charFor._lo = 1; + _vm->_fonts._charFor._hi = 255; + + _vm->_screen->_maxChars = 50; + _vm->_screen->_printOrg = _vm->_screen->_printStart = Common::Point(24, 18); + + Resource *notesRes = _vm->_files->loadFile("ETEXT.DAT"); + notesRes->_stream->seek(72); + + // Read the message + Common::String msg = ""; + byte c; + while ((c = (char)notesRes->_stream->readByte()) != '\0') + msg += c; + + //display the message + _game->showDeathText(msg); + + delete notesRes; + delete _vm->_objectsTable[0]; + _vm->_objectsTable[0] = nullptr; + _vm->_midi->stopSong(); +} + +void MartianScripts::cmdSpecial7() { + _vm->_room->clearRoom(); + _vm->_midi->loadMusic(47, 8); + + _vm->_sound->freeSounds(); + Resource *sound = _vm->_sound->loadSound(46, 14); + _vm->_sound->_soundTable.push_back(SoundEntry(sound, 1)); + + _vm->_screen->setDisplayScan(); + _vm->_screen->forceFadeOut(); + _vm->_events->hideCursor(); + + _vm->_files->loadScreen(40, 3); + _vm->_buffer1.copyBuffer(_vm->_screen); + _vm->_buffer2.copyBuffer(_vm->_screen); + + _vm->_events->showCursor(); + _vm->_screen->setIconPalette(); + _vm->_screen->forceFadeIn(); + + // Load objects specific to this special scene + Resource *data = _vm->_files->loadFile(40, 2); + _game->_spec7Objects = new SpriteResource(_vm, data); + delete data; + + // Load animation data + _vm->_animation->freeAnimationData(); + Resource *animResource = _vm->_files->loadFile(40, 1); + _vm->_animation->loadAnimations(animResource); + delete animResource; + + // Load script + Resource *newScript = _vm->_files->loadFile(40, 0); + _vm->_scripts->setScript(newScript); + + _vm->_images.clear(); + _vm->_oldRects.clear(); + _vm->_scripts->_sequence = 0; + + _vm->_sound->playSound(0); + + do { + charLoop(); + } while (_vm->_flags[134] != 1); + + do { + _vm->_events->pollEvents(); + } while (!_vm->shouldQuit() && _vm->_sound->_playingSound); + + _game->_numAnimTimers = 0; + _vm->_animation->freeAnimationData(); + _vm->_scripts->freeScriptData(); + _vm->_sound->freeSounds(); + + _vm->_screen->forceFadeOut(); + _vm->_midi->midiPlay(); + _vm->_midi->setLoop(true); + _vm->_events->hideCursor(); + + _vm->_files->loadScreen(40, 4); + _vm->_buffer1.copyBuffer(_vm->_screen); + _vm->_buffer2.copyBuffer(_vm->_screen); + + _vm->_events->showCursor(); + _vm->_screen->setIconPalette(); + _vm->_screen->forceFadeIn(); + + // Setup fonts + _vm->_fonts._charSet._hi = 10; + _vm->_fonts._charSet._lo = 1; + _vm->_fonts._charFor._lo = 247; + _vm->_fonts._charFor._hi = 255; + _vm->_screen->_maxChars = 50; + _vm->_screen->_printOrg = Common::Point(24, 18); + _vm->_screen->_printStart = Common::Point(24, 18); + + // Display death message + _game->showDeathText(Common::String(SPEC7MESSAGE)); + + _vm->_events->showCursor(); + _vm->_screen->copyBuffer(&_vm->_buffer1); + _vm->_events->hideCursor(); + + _vm->_video->setVideo(_vm->_screen, Common::Point(120, 16), FileIdent(40, 5), 10); + + while (!_vm->shouldQuit() && !_vm->_video->_videoEnd) { + _vm->_video->playVideo(); + _vm->_events->pollEventsAndWait(); + } + + _vm->_sound->freeSounds(); + sound = _vm->_sound->loadSound(40, 8); + _vm->_sound->_soundTable.push_back(SoundEntry(sound, 1)); + sound = _vm->_sound->loadSound(40, 9); + _vm->_sound->_soundTable.push_back(SoundEntry(sound, 1)); + sound = _vm->_sound->loadSound(40, 10); + _vm->_sound->_soundTable.push_back(SoundEntry(sound, 1)); + + _vm->_screen->forceFadeOut(); + _vm->_files->loadScreen(40, 7); + _vm->_destIn = _vm->_screen; + + _vm->_screen->plotImage(_game->_spec7Objects, 8, Common::Point(104, 176)); + _vm->_screen->plotImage(_game->_spec7Objects, 7, Common::Point(102, 160)); + _vm->_events->showCursor(); + _vm->_screen->forceFadeIn(); + + _vm->_events->_vbCount = 100; + while (!_vm->shouldQuit() && _vm->_events->_vbCount > 0) + _vm->_events->pollEventsAndWait(); + + _vm->_sound->playSound(0); + do { + _vm->_events->pollEvents(); + } while (!_vm->shouldQuit() && _vm->_sound->_playingSound); + + _vm->_events->_vbCount = 80; + while (!_vm->shouldQuit() && _vm->_events->_vbCount > 0) + _vm->_events->pollEventsAndWait(); + + _vm->_sound->playSound(1); + do { + _vm->_events->pollEvents(); + } while (!_vm->shouldQuit() && _vm->_sound->_playingSound); + + _vm->_events->_vbCount = 80; + while (!_vm->shouldQuit() && _vm->_events->_vbCount > 0) + _vm->_events->pollEventsAndWait(); + + _vm->_sound->playSound(2); + do { + _vm->_events->pollEvents(); + } while (!_vm->shouldQuit() && _vm->_sound->_playingSound); + + _vm->_sound->freeSounds(); + + delete _game->_spec7Objects; + _game->_spec7Objects = nullptr; + + _vm->_events->hideCursor(); + _vm->_screen->forceFadeOut(); + _vm->_files->loadScreen(40, 6); + _vm->_events->showCursor(); + _vm->_screen->forceFadeIn(); + + _vm->_events->waitKeyMouse(); + _vm->_midi->stopSong(); + _vm->_midi->freeMusic(); + + // The original was jumping to the restart label in main + _vm->_restartFl = true; + _vm->_events->pollEvents(); +} + void MartianScripts::executeSpecial(int commandIndex, int param1, int param2) { + switch (commandIndex) { + case 0: + cmdSpecial0(); + break; + case 1: + cmdSpecial1(param1); + break; + case 2: + warning("TODO: cmdSpecial2"); + break; + case 3: + cmdSpecial3(); + break; + case 4: + warning("TODO: cmdSpecial4"); + break; + case 5: + doIntro(param1); + break; + case 6: + cmdSpecial6(); + break; + case 7: + cmdSpecial7(); + break; + default: + warning("Unexpected Special code %d - Skipped", commandIndex); + } } typedef void(MartianScripts::*MartianScriptMethodPtr)(); diff --git a/engines/access/martian/martian_scripts.h b/engines/access/martian/martian_scripts.h index fc7495fc47..9c2141276e 100644 --- a/engines/access/martian/martian_scripts.h +++ b/engines/access/martian/martian_scripts.h @@ -35,9 +35,18 @@ class MartianEngine; class MartianScripts : public Scripts { private: MartianEngine *_game; + + void cmdSpecial0(); + void cmdSpecial1(int param1); + void cmdSpecial3(); + void doIntro(int param1); + void cmdSpecial6(); + void cmdSpecial7(); + protected: virtual void executeSpecial(int commandIndex, int param1, int param2); virtual void executeCommand(int commandIndex); + public: MartianScripts(AccessEngine *vm); }; diff --git a/engines/access/module.mk b/engines/access/module.mk index b6961aeca9..f7cf7f2261 100644 --- a/engines/access/module.mk +++ b/engines/access/module.mk @@ -28,6 +28,7 @@ MODULE_OBJS := \ amazon/amazon_room.o \ amazon/amazon_scripts.o \ martian/martian_game.o \ + martian/martian_player.o \ martian/martian_resources.o \ martian/martian_room.o \ martian/martian_scripts.o diff --git a/engines/access/player.cpp b/engines/access/player.cpp index 5a2b98293f..0162491aee 100644 --- a/engines/access/player.cpp +++ b/engines/access/player.cpp @@ -26,24 +26,25 @@ #include "access/access.h" #include "access/resources.h" #include "access/amazon/amazon_player.h" +#include "access/martian/martian_player.h" namespace Access { Player *Player::init(AccessEngine *vm) { switch (vm->getGameID()) { case GType_Amazon: + vm->_playerDataCount = 8; return new Amazon::AmazonPlayer(vm); + case GType_MartianMemorandum: + vm->_playerDataCount = 10; + return new Martian::MartianPlayer(vm); default: + vm->_playerDataCount = 8; return new Player(vm); } } Player::Player(AccessEngine *vm) : Manager(vm), ImageEntry() { - Common::fill(&_walkOffRight[0], &_walkOffRight[PLAYER_DATA_COUNT], 0); - Common::fill(&_walkOffLeft[0], &_walkOffLeft[PLAYER_DATA_COUNT], 0); - Common::fill(&_walkOffUp[0], &_walkOffUp[PLAYER_DATA_COUNT], 0); - Common::fill(&_walkOffDown[0], &_walkOffDown[PLAYER_DATA_COUNT], 0); - _playerSprites = nullptr; _playerSprites1 = nullptr; _manPal1 = nullptr; @@ -78,14 +79,36 @@ Player::Player(AccessEngine *vm) : Manager(vm), ImageEntry() { _downWalkMin = _downWalkMax = 0; _diagUpWalkMin = _diagUpWalkMax = 0; _diagDownWalkMin = _diagDownWalkMax = 0; + _walkOffRight = _walkOffLeft = nullptr; + _walkOffUp = _walkOffDown = nullptr; + _walkOffUR = _walkOffDR = nullptr; + _walkOffUL = _walkOffDL = nullptr; } Player::~Player() { delete _playerSprites; delete[] _manPal1; + delete[] _walkOffRight; + delete[] _walkOffLeft; + delete[] _walkOffUp; + delete[] _walkOffDown; + delete[] _walkOffUR; + delete[] _walkOffDR; + delete[] _walkOffUL; + delete[] _walkOffDL; } void Player::load() { + int dataCount = _vm->_playerDataCount; + _walkOffRight = new int[dataCount]; + _walkOffLeft = new int[dataCount]; + _walkOffUp = new int[dataCount]; + _walkOffDown = new int[dataCount]; + _walkOffUR = new Common::Point[dataCount]; + _walkOffDR = new Common::Point[dataCount]; + _walkOffUL = new Common::Point[dataCount]; + _walkOffDL = new Common::Point[dataCount]; + _playerOffset.x = _vm->_screen->_scaleTable1[25]; _playerOffset.y = _vm->_screen->_scaleTable1[67]; _leftDelta = -3; @@ -94,21 +117,6 @@ void Player::load() { _downDelta = -10; _scrollConst = 5; - for (int i = 0; i < PLAYER_DATA_COUNT; ++i) { - _walkOffRight[i] = SIDEOFFR[i]; - _walkOffLeft[i] = SIDEOFFL[i]; - _walkOffUp[i] = SIDEOFFU[i]; - _walkOffDown[i] = SIDEOFFD[i]; - _walkOffUR[i].x = DIAGOFFURX[i]; - _walkOffUR[i].y = DIAGOFFURY[i]; - _walkOffDR[i].x = DIAGOFFDRX[i]; - _walkOffDR[i].y = DIAGOFFDRY[i]; - _walkOffUL[i].x = DIAGOFFULX[i]; - _walkOffUL[i].y = DIAGOFFULY[i]; - _walkOffDL[i].x = DIAGOFFDLX[i]; - _walkOffDL[i].y = DIAGOFFDLY[i]; - } - _sideWalkMin = 0; _sideWalkMax = 7; _upWalkMin = 16; @@ -122,16 +130,35 @@ void Player::load() { _playerSprites = _playerSprites1; if (_manPal1) { - Common::copy(_manPal1 + 0x270, _manPal1 + 0x270 + 0x60, _vm->_screen->_manPal); + // Those values are from MM as Amazon doesn't use it + Common::copy(_manPal1 + 0x2A0, _manPal1 + 0x2A0 + 0x42, _vm->_screen->_manPal); } else { Common::fill(_vm->_screen->_manPal, _vm->_screen->_manPal + 0x60, 0); } } +void Player::loadTexPalette() { + Resource *texPal = _vm->_files->loadFile("TEXPAL.COL"); + int size = texPal->_size; + assert(size == 768); + _manPal1 = new byte[size]; + memcpy(_manPal1, texPal->data(), size); +} + void Player::loadSprites(const Common::String &name) { freeSprites(); Resource *data = _vm->_files->loadFile(name); + +#if 0 + Common::DumpFile *outFile = new Common::DumpFile(); + Common::String outName = name + ".dump"; + outFile->open(outName); + outFile->write(data->data(), data->_size); + outFile->finalize(); + outFile->close(); +#endif + _playerSprites1 = new SpriteResource(_vm, data); delete data; } @@ -357,7 +384,7 @@ void Player::walkRight() { if (_frame > _sideWalkMax) _frame = _sideWalkMin; - plotCom(0); + plotCom0(); } } @@ -634,15 +661,19 @@ void Player::checkMove() { } void Player::plotCom(int flags) { - _flags &= ~2; - _flags &= ~8; + _flags &= ~IMGFLAG_BACKWARDS; + _flags &= ~IMGFLAG_UNSCALED; _flags |= flags; plotCom3(); } +void Player::plotCom0() { + plotCom(_vm->getGameID() == GType_Amazon ? 0 : IMGFLAG_BACKWARDS); +} + void Player::plotCom1() { - plotCom(2); + plotCom(_vm->getGameID() == GType_Amazon ? IMGFLAG_BACKWARDS : 0); } void Player::plotCom2() { @@ -709,8 +740,12 @@ void Player::checkScroll() { } } -bool Player::scrollUp() { - _scrollAmount = -(_vm->_screen->_clipHeight - _playerY - _scrollThreshold); +bool Player::scrollUp(int forcedAmount) { + if (forcedAmount == -1) + _scrollAmount = -(_vm->_screen->_clipHeight - _playerY - _scrollThreshold); + else + _scrollAmount = forcedAmount; + if ((_vm->_scrollRow + _vm->_screen->_vWindowHeight) >= _vm->_room->_playFieldHeight) return true; @@ -737,8 +772,12 @@ bool Player::scrollUp() { return false; } -bool Player::scrollDown() { - _scrollAmount = -(_playerY - _scrollThreshold); +bool Player::scrollDown(int forcedAmount) { + if (forcedAmount == -1) + _scrollAmount = -(_playerY - _scrollThreshold); + else + _scrollAmount = forcedAmount; + _scrollFlag = true; _vm->_scrollY -= _scrollAmount; if (_vm->_scrollY >= 0) @@ -762,9 +801,13 @@ bool Player::scrollDown() { return true; } -bool Player::scrollLeft() { +bool Player::scrollLeft(int forcedAmount) { Screen &screen = *_vm->_screen; - _scrollAmount = -(_vm->_screen->_clipWidth - _playerX - _scrollThreshold); + if (forcedAmount == -1) + _scrollAmount = -(_vm->_screen->_clipWidth - _playerX - _scrollThreshold); + else + _scrollAmount = forcedAmount; + if ((_vm->_scrollCol + screen._vWindowWidth) == _vm->_room->_playFieldWidth) { _scrollEnd = 2; _vm->_scrollX = 0; @@ -789,8 +832,12 @@ bool Player::scrollLeft() { } } -bool Player::scrollRight() { - _scrollAmount = -(_playerX - _scrollThreshold); +bool Player::scrollRight(int forcedAmount) { + if (forcedAmount == -1) + _scrollAmount = -(_playerX - _scrollThreshold); + else + _scrollAmount = forcedAmount; + _scrollFlag = true; _vm->_scrollX -= _scrollAmount; diff --git a/engines/access/player.h b/engines/access/player.h index 329cc15ed2..3c554556dd 100644 --- a/engines/access/player.h +++ b/engines/access/player.h @@ -31,8 +31,6 @@ namespace Access { -#define PLAYER_DATA_COUNT 8 - enum Direction { NONE = 0, UP = 1, @@ -58,11 +56,11 @@ protected: int _diagUpWalkMin, _diagUpWalkMax; int _diagDownWalkMin, _diagDownWalkMax; SpriteResource *_playerSprites1; - byte *_manPal1; int _scrollEnd; int _inactiveYOff; void plotCom(int v1); + void plotCom0(); void plotCom1(); void plotCom2(); void plotCom3(); @@ -76,22 +74,19 @@ protected: void walkUpRight(); void walkDownRight(); void checkScrollUp(); - bool scrollUp(); - bool scrollDown(); - bool scrollLeft(); - bool scrollRight(); public: Direction _playerDirection; SpriteResource *_playerSprites; // Fields in original Player structure - int _walkOffRight[PLAYER_DATA_COUNT]; - int _walkOffLeft[PLAYER_DATA_COUNT]; - int _walkOffUp[PLAYER_DATA_COUNT]; - int _walkOffDown[PLAYER_DATA_COUNT]; - Common::Point _walkOffUR[PLAYER_DATA_COUNT]; - Common::Point _walkOffDR[PLAYER_DATA_COUNT]; - Common::Point _walkOffUL[PLAYER_DATA_COUNT]; - Common::Point _walkOffDL[PLAYER_DATA_COUNT]; + byte *_manPal1; + int *_walkOffRight; + int *_walkOffLeft; + int *_walkOffUp; + int *_walkOffDown; + Common::Point *_walkOffUR; + Common::Point *_walkOffDR; + Common::Point *_walkOffUL; + Common::Point *_walkOffDL; byte _rawTempL; int _rawXTemp; byte _rawYTempL; @@ -125,6 +120,8 @@ public: virtual void load(); + void loadTexPalette(); + void loadSprites(const Common::String &name); void freeSprites(); @@ -137,6 +134,10 @@ public: void calcPlayer(); + bool scrollUp(int forcedAmount = -1); + bool scrollDown(int forcedAmount = -1); + bool scrollLeft(int forcedAmount = -1); + bool scrollRight(int forcedAmount = -1); void checkScroll(); void checkMove(); diff --git a/engines/access/resources.cpp b/engines/access/resources.cpp index 4157cdfc0d..8699a4a82f 100644 --- a/engines/access/resources.cpp +++ b/engines/access/resources.cpp @@ -46,35 +46,17 @@ const byte INITIAL_PALETTE[18 * 3] = { 0x00, 0x00, 0x00 }; -const int SIDEOFFR[] = { 5, 5, 5, 5, 5, 5, 5, 5, 0 }; -const int SIDEOFFL[] = { 5, 5, 5, 5, 5, 5, 5, 5, 0 }; -const int SIDEOFFU[] = { 2, 2, 2, 2, 2, 2, 2, 2, 0 }; -const int SIDEOFFD[] = { 2, 2, 2, 2, 2, 2, 2, 2, 0 }; -const int DIAGOFFURX[] = { 4, 5, 2, 2, 3, 4, 2, 2, 0 }; -const int DIAGOFFURY[] = { 2, 3, 2, 2, 2, 3, 1, 1, 0 }; -const int DIAGOFFDRX[] = { 4, 5, 4, 3, 5, 4, 5, 1, 0 }; -const int DIAGOFFDRY[] = { 3, 2, 1, 2, 2, 1, 2, 1, 0 }; -const int DIAGOFFULX[] = { 4, 5, 4, 3, 3, 2, 2, 2, 0 }; -const int DIAGOFFULY[] = { 3, 3, 1, 2, 2, 1, 1, 1, 0 }; -const int DIAGOFFDLX[] = { 4, 5, 3, 3, 5, 4, 6, 1, 0 }; -const int DIAGOFFDLY[] = { 2, 2, 1, 2, 3, 1, 2, 1, 0 }; - -const int RMOUSE[10][2] = { - { 0, 35 }, { 0, 0 }, { 36, 70 }, { 71, 106 }, { 107, 141 }, - { 142, 177 }, { 178, 212 }, { 213, 248 }, { 249, 283 }, { 284, 318 } -}; - -const char *const LOOK_MESSAGE = "LOOKING THERE REVEALS NOTHING OF INTEREST."; -const char *const GET_MESSAGE = "YOU CAN'T TAKE THAT."; -const char *const OPEN_MESSAGE = "THAT DOESN'T OPEN."; -const char *const MOVE_MESSAGE = "THAT WON'T MOVE."; -const char *const USE_MESSAGE = "THAT DOESN'T SEEM TO WORK."; -const char *const GO_MESSAGE = "YOU CAN'T CLIMB THAT."; -const char *const HELP_MESSAGE = "THIS OBJECT REQUIRES NO HINTS"; -const char *const TALK_MESSAGE = "THERE SEEMS TO BE NO RESPONSE."; const char *const GENERAL_MESSAGES[] = { - LOOK_MESSAGE, OPEN_MESSAGE, MOVE_MESSAGE, GET_MESSAGE, USE_MESSAGE, - GO_MESSAGE, TALK_MESSAGE, HELP_MESSAGE, HELP_MESSAGE, USE_MESSAGE + "LOOKING THERE REVEALS NOTHING OF INTEREST.", // LOOK_MESSAGE + "THAT DOESN'T OPEN.", // OPEN_MESSAGE + "THAT WON'T MOVE." // MOVE_MESSAGE + "YOU CAN'T TAKE THAT.", // GET_MESSAGE + "THAT DOESN'T SEEM TO WORK.", // USE_MESSAGE + "YOU CAN'T CLIMB THAT.", // GO_MESSAGE + "THERE SEEMS TO BE NO RESPONSE.", // TALK_MESSAGE + "THIS OBJECT REQUIRES NO HINTS", // HELP_MESSAGE + "THIS OBJECT REQUIRES NO HINTS", // HELP_MESSAGE + "THAT DOESN'T SEEM TO WORK.", // USE_MESSAGE }; const int INVCOORDS[][4] = { diff --git a/engines/access/resources.h b/engines/access/resources.h index 8d59b1b1f1..07b8e5ada9 100644 --- a/engines/access/resources.h +++ b/engines/access/resources.h @@ -29,21 +29,6 @@ namespace Access { extern const byte INITIAL_PALETTE[18 * 3]; -extern const int SIDEOFFR[]; -extern const int SIDEOFFL[]; -extern const int SIDEOFFU[]; -extern const int SIDEOFFD[]; -extern const int DIAGOFFURX[]; -extern const int DIAGOFFURY[]; -extern const int DIAGOFFDRX[]; -extern const int DIAGOFFDRY[]; -extern const int DIAGOFFULX[]; -extern const int DIAGOFFULY[]; -extern const int DIAGOFFDLX[]; -extern const int DIAGOFFDLY[]; - -extern const int RMOUSE[10][2]; - extern const char *const GENERAL_MESSAGES[]; extern const int INVCOORDS[][4]; diff --git a/engines/access/room.cpp b/engines/access/room.cpp index 607259ec6f..c91b37c65d 100644 --- a/engines/access/room.cpp +++ b/engines/access/room.cpp @@ -38,6 +38,23 @@ Room::Room(AccessEngine *vm) : Manager(vm) { _selectCommand = 0; _conFlag = false; _selectCommand = -1; + + switch (vm->getGameID()) { + case GType_Amazon: + for (int i = 0; i < 10; i++) { + _rMouse[i][0] = Amazon::RMOUSE[i][0]; + _rMouse[i][1] = Amazon::RMOUSE[i][1]; + } + break; + case GType_MartianMemorandum: + for (int i = 0; i < 10; i++) { + _rMouse[i][0] = Martian::RMOUSE[i][0]; + _rMouse[i][1] = Martian::RMOUSE[i][1]; + } + break; + default: + error("Game not supported"); + } } Room::~Room() { @@ -55,6 +72,91 @@ void Room::freeTileData() { _tile = nullptr; } +void Room::clearCamera() { + _vm->_player->_scrollFlag = true; + _vm->_events->hideCursor(); + + _vm->_screen->_orgX1 = 48; + _vm->_screen->_orgY1 = 24; + _vm->_screen->_orgX2 = 274; + _vm->_screen->_orgY2 = 152; + _vm->_screen->_lColor = 0; + _vm->_screen->drawRect(); + + _vm->_events->showCursor(); + + _vm->_events->_vbCount = 4; + while (!_vm->shouldQuit() && _vm->_events->_vbCount > 0) + _vm->_events->pollEventsAndWait(); +} + +void Room::takePicture() { + _vm->_events->pollEvents(); + if (!_vm->_events->_leftButton) + return; + + Common::Array<Common::Rect> pictureCoords; + for (int i = 0; Martian::PICTURERANGE[i][0] != -1; i += 2) { + pictureCoords.push_back(Common::Rect(Martian::PICTURERANGE[i][0], Martian::PICTURERANGE[i + 1][0], + Martian::PICTURERANGE[i][1], Martian::PICTURERANGE[i + 1][1])); + } + + int result = _vm->_events->checkMouseBox1(pictureCoords); + + if (result == 4) { + _vm->_events->debounceLeft(); + if (_vm->_inventory->_inv[44]._value != ITEM_IN_INVENTORY) { + Common::String msg = "YOU HAVE NO MORE FILM."; + _vm->_scripts->doCmdPrint_v1(msg); + return; + } + + // TODO: simplify the second part of the test when tested + if ((_vm->_scrollCol < 35) || ((_vm->_scrollRow >= 10) && (_vm->_scrollRow >= 20))){ + Common::String msg = "THAT ISN'T INTERESTING ENOUGH TO WASTE FILM ON."; + _vm->_scripts->doCmdPrint_v1(msg); + return; + } + + if (_vm->_inventory->_inv[26]._value != ITEM_USED) { + Common::String msg = "ALTHOUGH IT WOULD MAKE A NICE PICTURE, YOU MAY FIND SOMETHING MORE INTERESTING TO USE YOUR FILM ON."; + _vm->_scripts->doCmdPrint_v1(msg); + return; + } + + Common::String msg = "THAT PHOTO MAY COME IN HANDY SOME DAY."; + _vm->_scripts->doCmdPrint_v1(msg); + _vm->_inventory->_inv[8]._value = ITEM_IN_INVENTORY; + _vm->_pictureTaken++; + if (_vm->_pictureTaken == 16) + _vm->_inventory->_inv[44]._value = ITEM_USED; + + _vm->_events->debounceLeft(); + _vm->_sound->playSound(0); + clearCamera(); + return; + } else if (result == 5) { + if (_vm->_flags[26] != 2) { + _vm->_video->closeVideo(); + _vm->_video->_videoEnd = true; + } + _vm->_player->_roomNumber = 7; + _vm->_room->_function = FN_CLEAR1; + return; + } else if (result >= 0) + _vm->_player->_move = (Direction)(result + 1); + + _vm->_player->_scrollFlag = false; + if (_vm->_player->_move == UP) + _vm->_player->scrollDown(2); + else if (_vm->_player->_move == DOWN) + _vm->_player->scrollUp(2); + else if (_vm->_player->_move == LEFT) + _vm->_player->scrollRight(2); + else if (_vm->_player->_move == RIGHT) + _vm->_player->scrollLeft(2); +} + void Room::doRoom() { bool reloadFlag = false; @@ -84,9 +186,13 @@ void Room::doRoom() { _vm->_events->pollEventsAndWait(); _vm->_canSaveLoad = false; - _vm->_player->walk(); - _vm->_midi->midiRepeat(); - _vm->_player->checkScroll(); + if ((_vm->getGameID() == GType_MartianMemorandum) && (_vm->_player->_roomNumber == 47)) { + takePicture(); + } else { + _vm->_player->walk(); + _vm->_midi->midiRepeat(); + _vm->_player->checkScroll(); + } doCommands(); if (_vm->shouldQuitOrRestart()) @@ -136,7 +242,8 @@ void Room::doRoom() { } else { _vm->plotList(); - if (_vm->_events->_mousePos.y < 177) + if (((_vm->getGameID() == GType_MartianMemorandum) && (_vm->_events->_mousePos.y < 184)) || + ((_vm->getGameID() == GType_Amazon) && (_vm->_events->_mousePos.y < 177))) _vm->_events->setCursor(_vm->_events->_normalMouse); else _vm->_events->setCursor(CURSOR_ARROW); @@ -173,8 +280,8 @@ void Room::loadRoomData(const byte *roomData) { _vm->_establishFlag = false; if (roomInfo._estIndex != -1) { _vm->_establishFlag = true; - if (_vm->_establishTable[roomInfo._estIndex] != 1) { - _vm->_establishTable[roomInfo._estIndex] = 1; + if (!_vm->_establishTable[roomInfo._estIndex]) { + _vm->_establishTable[roomInfo._estIndex] = true; _vm->establish(0, roomInfo._estIndex); } } @@ -455,8 +562,8 @@ void Room::doCommands() { if (_vm->_events->_mouseRow >= 22) { // Mouse in user interface area for (commandId = 0; commandId < 10; ++commandId) { - if (_vm->_events->_mousePos.x >= RMOUSE[commandId][0] && - _vm->_events->_mousePos.x < RMOUSE[commandId][1]) + if (_vm->_events->_mousePos.x >= _rMouse[commandId][0] && + _vm->_events->_mousePos.x < _rMouse[commandId][1]) break; } if (commandId < 10) @@ -489,9 +596,6 @@ void Room::cycleCommand(int incr) { } void Room::handleCommand(int commandId) { - if (commandId == 1) - --commandId; - if (commandId == 9) { _vm->_events->debounceLeft(); _vm->_canSaveLoad = true; @@ -510,41 +614,90 @@ void Room::executeCommand(int commandId) { EventsManager &events = *_vm->_events; _selectCommand = commandId; - switch (commandId) { - case 0: - events.forceSetCursor(CURSOR_LOOK); - break; - case 2: - events.forceSetCursor(CURSOR_USE); - break; - case 3: - events.forceSetCursor(CURSOR_TAKE); - break; - case 4: - events.setCursor(CURSOR_ARROW); - if (_vm->_inventory->newDisplayInv() == 2) { - commandOff(); + if (_vm->getGameID() == GType_MartianMemorandum) { + switch (commandId) { + case 4: + events.setCursor(CURSOR_ARROW); + if (_vm->_inventory->displayInv() == 2) { + commandOff(); + return; + } + if (_vm->_useItem == 39) { + if (_vm->_player->_roomNumber == 23) + _vm->_currentMan = 1; + commandOff(); + return; + } else if (_vm->_useItem == 6) { + _vm->_flags[3] = 2; + _vm->_scripts->converse1(24); + + _conFlag = true; + while (_conFlag && !_vm->shouldQuitOrRestart()) { + _conFlag = false; + _vm->_scripts->executeScript(); + } + + _vm->_boxSelect = true; + return; + } + break; + case 7: + walkCursor(); return; + case 8: { + events.forceSetCursor(CURSOR_CROSSHAIRS); + _vm->_scripts->_sequence = 10000; + _vm->_scripts->searchForSequence(); + + _conFlag = true; + while (_conFlag && !_vm->shouldQuitOrRestart()) { + _conFlag = false; + _vm->_scripts->executeScript(); + } + + _vm->_boxSelect = true; + return; + } + default: + // No set cursor in MM. Forcing to CROSSHAIRS + events.setCursor(CURSOR_CROSSHAIRS); + break; + } + } else { + switch (commandId) { + case 0: + case 1: + events.forceSetCursor(CURSOR_LOOK); + break; + case 2: + events.forceSetCursor(CURSOR_USE); + break; + case 3: + events.forceSetCursor(CURSOR_TAKE); + break; + case 4: + events.setCursor(CURSOR_ARROW); + if (_vm->_inventory->newDisplayInv() == 2) { + commandOff(); + return; + } + break; + case 5: + events.forceSetCursor(CURSOR_CLIMB); + break; + case 6: + events.forceSetCursor(CURSOR_TALK); + break; + case 7: + walkCursor(); + return; + case 8: + events.forceSetCursor(CURSOR_HELP); + break; + default: + break; } - break; - case 5: - events.forceSetCursor(CURSOR_CLIMB); - break; - case 6: - events.forceSetCursor(CURSOR_TALK); - break; - case 7: - walkCursor(); - return; - case 8: - events.forceSetCursor(CURSOR_HELP); - break; - default: - break; } - - // Draw the default toolbar menu at the bottom of the screen - roomMenu(); _vm->_screen->saveScreen(); _vm->_screen->setDisplayScan(); @@ -555,7 +708,7 @@ void Room::executeCommand(int commandId) { // Draw the button as selected _vm->_screen->plotImage(spr, _selectCommand + 2, - Common::Point(RMOUSE[_selectCommand][0], 176)); + Common::Point(_rMouse[_selectCommand][0], (_vm->getGameID() == GType_MartianMemorandum) ? 184 : 176)); _vm->_screen->restoreScreen(); _vm->_boxSelect = true; diff --git a/engines/access/room.h b/engines/access/room.h index eec273e3f4..12e7375428 100644 --- a/engines/access/room.h +++ b/engines/access/room.h @@ -72,6 +72,8 @@ private: int calcLR(int yp); int calcUD(int xp); + void takePicture(); + /** * Cycles forwards or backwards through the list of commands */ @@ -103,6 +105,8 @@ protected: */ void executeCommand(int commandId); + void clearCamera(); + virtual void reloadRoom() = 0; virtual void reloadRoom1() = 0; @@ -126,6 +130,7 @@ public: byte *_tile; int _selectCommand; bool _conFlag; + int _rMouse[10][2]; public: Room(AccessEngine *vm); diff --git a/engines/access/screen.cpp b/engines/access/screen.cpp index 970a8f3079..41f6194238 100644 --- a/engines/access/screen.cpp +++ b/engines/access/screen.cpp @@ -113,6 +113,20 @@ void Screen::setInitialPalettte() { g_system->getPaletteManager()->setPalette(INITIAL_PALETTE, 0, 18); } +void Screen::setManPalette() { + for (int i = 0; i < 0x42; i++) { + _rawPalette[672 + i] = VGA_COLOR_TRANS(_manPal[i]); + } +} + +void Screen::setIconPalette() { + if (_vm->getGameID() == GType_MartianMemorandum) { + for (int i = 0; i < 0x1B; i++) { + _rawPalette[741 + i] = VGA_COLOR_TRANS(Martian::ICON_PALETTE[i]); + } + } +} + void Screen::loadPalette(int fileNum, int subfile) { Resource *res = _vm->_files->loadFile(fileNum, subfile); byte *palette = res->data(); @@ -189,7 +203,7 @@ void Screen::forceFadeIn() { for (int idx = 0; idx < PALETTE_SIZE; ++idx, ++srcP, ++destP) { if (*destP != *srcP) { repeatFlag = true; - *destP = MAX((int)*destP + FADE_AMOUNT, (int)*srcP); + *destP = MIN((int)*destP + FADE_AMOUNT, (int)*srcP); } } @@ -267,6 +281,11 @@ void Screen::drawRect() { ASurface::drawRect(); } +void Screen::drawBox() { + addDirtyRect(Common::Rect(_orgX1, _orgY1, _orgX2, _orgY2)); + ASurface::drawBox(); +} + void Screen::transBlitFrom(ASurface *src, const Common::Point &destPos) { addDirtyRect(Common::Rect(destPos.x, destPos.y, destPos.x + src->w, destPos.y + src->h)); ASurface::transBlitFrom(src, destPos); @@ -322,6 +341,10 @@ void Screen::cyclePaletteBackwards() { } } +void Screen::flashPalette(int count) { + warning("TODO: Implement flashPalette"); +} + void Screen::addDirtyRect(const Common::Rect &r) { _dirtyRects.push_back(r); assert(r.isValidRect() && r.width() > 0 && r.height() > 0); diff --git a/engines/access/screen.h b/engines/access/screen.h index d45a533f9a..52485e5c7c 100644 --- a/engines/access/screen.h +++ b/engines/access/screen.h @@ -92,6 +92,8 @@ public: virtual void drawRect(); + virtual void drawBox(); + virtual void transBlitFrom(ASurface *src, const Common::Point &destPos); virtual void transBlitFrom(ASurface *src, const Common::Rect &bounds); @@ -137,7 +139,12 @@ public: /** * Set icon palette */ - void setIconPalette() {} + void setIconPalette(); + + /** + * Set Tex palette (Martian Memorandum) + */ + void setManPalette(); void loadPalette(int fileNum, int subfile); @@ -151,6 +158,8 @@ public: void getPalette(byte *pal); + void flashPalette(int count); + /** * Copy a buffer to the screen */ diff --git a/engines/access/scripts.cpp b/engines/access/scripts.cpp index 1bd24894d7..41dd5cc0d0 100644 --- a/engines/access/scripts.cpp +++ b/engines/access/scripts.cpp @@ -39,12 +39,101 @@ Scripts::Scripts(AccessEngine *vm) : Manager(vm) { _choiceStart = 0; _charsOrg = Common::Point(0, 0); _texsOrg = Common::Point(0, 0); + setOpcodes(); } Scripts::~Scripts() { freeScriptData(); } +void Scripts::setOpcodes() { + COMMAND_LIST[0] = &Scripts::cmdObject; + COMMAND_LIST[1] = &Scripts::cmdEndObject; + COMMAND_LIST[2] = &Scripts::cmdJumpLook; + COMMAND_LIST[3] = &Scripts::cmdJumpHelp; + COMMAND_LIST[4] = &Scripts::cmdJumpGet; + COMMAND_LIST[5] = &Scripts::cmdJumpMove; + COMMAND_LIST[6] = &Scripts::cmdJumpUse; + COMMAND_LIST[7] = &Scripts::cmdJumpTalk; + COMMAND_LIST[8] = &Scripts::cmdNull; + COMMAND_LIST[9] = &Scripts::cmdPrint_v1; + COMMAND_LIST[10] = &Scripts::cmdRetPos; + COMMAND_LIST[11] = &Scripts::cmdAnim; + COMMAND_LIST[12] = &Scripts::cmdSetFlag; + COMMAND_LIST[13] = &Scripts::cmdCheckFlag; + COMMAND_LIST[14] = &Scripts::cmdGoto; + COMMAND_LIST[15] = &Scripts::cmdAddScore; + COMMAND_LIST[16] = &Scripts::cmdSetInventory; + COMMAND_LIST[17] = &Scripts::cmdCheckInventory; + COMMAND_LIST[18] = &Scripts::cmdSetTex; + COMMAND_LIST[19] = &Scripts::cmdNewRoom; + COMMAND_LIST[20] = &Scripts::cmdConverse; + COMMAND_LIST[21] = &Scripts::cmdCheckFrame; + COMMAND_LIST[22] = &Scripts::cmdCheckAnim; + COMMAND_LIST[23] = &Scripts::cmdSnd; + COMMAND_LIST[24] = &Scripts::cmdRetNeg; + COMMAND_LIST[25] = &Scripts::cmdRetPos; + COMMAND_LIST[26] = &Scripts::cmdCheckLoc; + COMMAND_LIST[27] = &Scripts::cmdSetAnim; + COMMAND_LIST[28] = &Scripts::cmdDispInv_v1; + COMMAND_LIST[29] = &Scripts::cmdSetAbout; + COMMAND_LIST[30] = &Scripts::cmdSetTimer; + COMMAND_LIST[31] = &Scripts::cmdCheckTimer; + COMMAND_LIST[32] = &Scripts::cmdSetTravel; + COMMAND_LIST[33] = &Scripts::cmdJumpGoto; + COMMAND_LIST[34] = &Scripts::cmdSetVideo; + COMMAND_LIST[35] = &Scripts::cmdPlayVideo; + COMMAND_LIST[36] = &Scripts::cmdPlotImage; + COMMAND_LIST[37] = &Scripts::cmdSetDisplay; + COMMAND_LIST[38] = &Scripts::cmdSetBuffer; + COMMAND_LIST[39] = &Scripts::cmdSetScroll; + COMMAND_LIST[40] = &Scripts::cmdSaveRect; + COMMAND_LIST[41] = &Scripts::cmdVideoEnded; + COMMAND_LIST[42] = &Scripts::cmdSetBufVid; + COMMAND_LIST[43] = &Scripts::cmdPlayBufVid; + COMMAND_LIST[44] = &Scripts::cmdRemoveLast; + COMMAND_LIST[45] = &Scripts::cmdDoTravel; + COMMAND_LIST[46] = &Scripts::cmdCheckAbout; + COMMAND_LIST[47] = &Scripts::cmdSpecial; + COMMAND_LIST[48] = &Scripts::cmdSetCycle; + COMMAND_LIST[49] = &Scripts::cmdCycle; + COMMAND_LIST[50] = &Scripts::cmdCharSpeak; + COMMAND_LIST[51] = &Scripts::cmdTexSpeak; + COMMAND_LIST[52] = &Scripts::cmdTexChoice; + COMMAND_LIST[53] = &Scripts::cmdWait; + COMMAND_LIST[54] = &Scripts::cmdSetConPos; + COMMAND_LIST[55] = &Scripts::cmdCheckVFrame; + COMMAND_LIST[56] = &Scripts::cmdJumpChoice; + COMMAND_LIST[57] = &Scripts::cmdReturnChoice; + COMMAND_LIST[58] = &Scripts::cmdClearBlock; + COMMAND_LIST[59] = &Scripts::cmdLoadSound; + COMMAND_LIST[60] = &Scripts::cmdFreeSound; + COMMAND_LIST[61] = &Scripts::cmdSetVideoSound; + COMMAND_LIST[62] = &Scripts::cmdPlayVideoSound; + COMMAND_LIST[63] = &Scripts::cmdPrintWatch; + COMMAND_LIST[64] = &Scripts::cmdDispAbout; + COMMAND_LIST[65] = &Scripts::cmdPushLocation; + COMMAND_LIST[66] = &Scripts::cmdCheckTravel; + COMMAND_LIST[67] = &Scripts::cmdBlock; + COMMAND_LIST[68] = &Scripts::cmdPlayerOff; + COMMAND_LIST[69] = &Scripts::cmdPlayerOn; + COMMAND_LIST[70] = &Scripts::cmdDead; + COMMAND_LIST[71] = &Scripts::cmdFadeOut; + COMMAND_LIST[72] = &Scripts::cmdEndVideo; + COMMAND_LIST[73] = &Scripts::cmdHelp_v1; +} + +void Scripts::setOpcodes_v2() { + COMMAND_LIST[9] = &Scripts::cmdPrint_v2; + COMMAND_LIST[15] = &Scripts::cmdSetInventory; + COMMAND_LIST[28] = &Scripts::cmdDispInv_v2; + COMMAND_LIST[29] = &Scripts::cmdSetTimer; + COMMAND_LIST[32] = &Scripts::cmdJumpGoto; + COMMAND_LIST[40] = &Scripts::cmdVideoEnded; + COMMAND_LIST[45] = COMMAND_LIST[46] = &Scripts::cmdSpecial; + COMMAND_LIST[63] = COMMAND_LIST[64] = COMMAND_LIST[66] = COMMAND_LIST[67] = &Scripts::cmdPushLocation; +} + void Scripts::setScript(Resource *res, bool restartFlag) { _resource = res; _data = res->_stream; @@ -86,6 +175,50 @@ void Scripts::charLoop() { _endFlag = endFlag; } +void Scripts::clearWatch() { + _vm->_events->hideCursor(); + _vm->_screen->_orgX1 = 128; + _vm->_screen->_orgY1 = 57; + _vm->_screen->_orgX2 = 228; + _vm->_screen->_orgY2 = 106; + _vm->_screen->_lColor = 0; + _vm->_screen->drawRect(); + + _vm->_events->showCursor(); +} + +void Scripts::printWatch() { + _vm->_fonts._charSet._lo = 8; + _vm->_fonts._charSet._hi = 2; + _vm->_fonts._charFor._lo = 2; + _vm->_fonts._charFor._hi = 255; + + _vm->_screen->_maxChars = 19; + _vm->_screen->_printOrg = Common::Point(128, 58); + _vm->_screen->_printStart = Common::Point(128, 58); + clearWatch(); + + Common::String msg = readString(); + Common::String line = ""; + int width = 0; + bool lastLine; + do { + lastLine = _vm->_fonts._font2.getLine(msg, _vm->_screen->_maxChars * 6, line, width); + // Draw the text + _vm->_bubbleBox->printString(line); + + _vm->_screen->_printOrg.y += 6; + _vm->_screen->_printOrg.x = _vm->_screen->_printStart.x; + + if (_vm->_screen->_printOrg.y == 106) { + _vm->_events->waitKeyMouse(); + clearWatch(); + _vm->_screen->_printOrg.y = _vm->_screen->_printStart.y; + } + } while (!lastLine); + _vm->_events->waitKeyMouse(); +} + void Scripts::findNull() { // No implementation required in ScummVM, the strings in the script files are already skipped by the use of readByte() } @@ -109,37 +242,7 @@ int Scripts::executeScript() { return _returnCode; } -typedef void(Scripts::*ScriptMethodPtr)(); - void Scripts::executeCommand(int commandIndex) { - static const ScriptMethodPtr COMMAND_LIST[] = { - &Scripts::cmdObject, &Scripts::cmdEndObject, &Scripts::cmdJumpLook, - &Scripts::cmdJumpHelp, &Scripts::cmdJumpGet, &Scripts::cmdJumpMove, - &Scripts::cmdJumpUse, &Scripts::cmdJumpTalk, &Scripts::cmdNull, - &Scripts::cmdPrint, &Scripts::cmdRetPos, &Scripts::cmdAnim, - &Scripts::cmdSetFlag, &Scripts::cmdCheckFlag, &Scripts::cmdGoto, - &Scripts::cmdAddScore, &Scripts::cmdSetInventory, &Scripts::cmdCheckInventory, - &Scripts::cmdSetTex, &Scripts::cmdNewRoom, &Scripts::cmdConverse, - &Scripts::cmdCheckFrame, &Scripts::cmdCheckAnim, &Scripts::cmdSnd, - &Scripts::cmdRetNeg, &Scripts::cmdRetPos, &Scripts::cmdCheckLoc, - &Scripts::cmdSetAnim, &Scripts::cmdDispInv, &Scripts::cmdSetAbout, - &Scripts::cmdSetTimer, &Scripts::cmdCheckTimer, &Scripts::cmdSetTravel, - &Scripts::cmdJumpGoto, &Scripts::cmdSetVideo, &Scripts::cmdPlayVideo, - &Scripts::cmdPlotImage, &Scripts::cmdSetDisplay, &Scripts::cmdSetBuffer, - &Scripts::cmdSetScroll, &Scripts::cmdSaveRect, &Scripts::cmdVideoEnded, - &Scripts::cmdSetBufVid, &Scripts::cmdPlayBufVid, &Scripts::cmdRemoveLast, - &Scripts::cmdDoTravel, &Scripts::cmdCheckAbout, &Scripts::cmdSpecial, - &Scripts::cmdSetCycle, &Scripts::cmdCycle, &Scripts::cmdCharSpeak, - &Scripts::cmdTexSpeak, &Scripts::cmdTexChoice, &Scripts::cmdWait, - &Scripts::cmdSetConPos, &Scripts::cmdCheckVFrame, &Scripts::cmdJumpChoice, - &Scripts::cmdReturnChoice, &Scripts::cmdClearBlock, &Scripts::cmdLoadSound, - &Scripts::cmdFreeSound, &Scripts::cmdSetVideoSound, &Scripts::cmdPlayVideoSound, - &Scripts::cmdPrintWatch, &Scripts::cmdDispAbout, &Scripts::cmdPushLocation, - &Scripts::cmdCheckTravel, &Scripts::cmdBlock, &Scripts::cmdPlayerOff, - &Scripts::cmdPlayerOn, &Scripts::cmdDead, &Scripts::cmdFadeOut, - &Scripts::cmdEndVideo - }; - (this->*COMMAND_LIST[commandIndex])(); } @@ -198,18 +301,36 @@ void Scripts::cmdNull() { #define PRINT_TIMER 25 -void Scripts::cmdPrint() { +void Scripts::cmdPrint_v2() { // Get a text line for display Common::String msg = readString(); printString(msg); } -void Scripts::printString(const Common::String &msg) { +void Scripts::doCmdPrint_v1(Common::String msg) { _vm->_screen->_printOrg = Common::Point(20, 42); - _vm->_screen->_printStart = Common::Point(20, 42); - _vm->_timers[PRINT_TIMER]._timer = 50; - _vm->_timers[PRINT_TIMER]._initTm = 50; - ++_vm->_timers[PRINT_TIMER]._flag; + _vm->_screen->_printStart = Common::Point(20, 32); + _vm->_bubbleBox->placeBubble(msg); + _vm->_events->waitKeyMouse(); + _vm->_events->hideCursor(); + _vm->_screen->restoreBlock(); + _vm->_events->showCursor(); + findNull(); +} + +void Scripts::cmdPrint_v1() { + Common::String msg = readString(); + doCmdPrint_v1(msg); +} + +void Scripts::printString(const Common::String &msg) { + if (_vm->getGameID() != GType_MartianMemorandum) { + _vm->_screen->_printOrg = Common::Point(20, 42); + _vm->_screen->_printStart = Common::Point(20, 42); + _vm->_timers[PRINT_TIMER]._timer = 50; + _vm->_timers[PRINT_TIMER]._initTm = 50; + ++_vm->_timers[PRINT_TIMER]._flag; + } // Display the text in a bubble, and wait for a keypress or mouse click _vm->_bubbleBox->placeBubble(msg); @@ -268,11 +389,6 @@ void Scripts::cmdGoto() { } void Scripts::cmdAddScore() { - if (!_vm->isDemo()) { - cmdSetInventory(); - return; - } - _data->skip(1); } @@ -338,8 +454,8 @@ void Scripts::cmdNewRoom() { cmdRetPos(); } -void Scripts::cmdConverse() { - _vm->_conversation = _data->readUint16LE(); +void Scripts::converse1(int val) { + _vm->_conversation = val; _vm->_room->clearRoom(); _vm->freeChar(); _vm->_char->loadChar(_vm->_conversation); @@ -355,6 +471,11 @@ void Scripts::cmdConverse() { } } +void Scripts::cmdConverse() { + int val = _data->readUint16LE(); + converse1(val); +} + void Scripts::cmdCheckFrame() { int id = _data->readUint16LE(); Animation *anim = _vm->_animation->findAnimation(id); @@ -409,17 +530,19 @@ void Scripts::cmdSetAnim() { _vm->_animation->setAnimTimer(anim); } -void Scripts::cmdDispInv() { +void Scripts::cmdDispInv_v1() { + _vm->_inventory->displayInv(); +} + +void Scripts::cmdDispInv_v2() { _vm->_inventory->newDisplayInv(); } void Scripts::cmdSetAbout() { - if (!_vm->isDemo()) { - cmdSetTimer(); - return; - } - - error("TODO: DEMO - cmdSetAbout"); + int idx = _data->readByte(); + int val = _data->readByte(); + _vm->_ask[idx] = val; + _vm->_startAboutBox = _vm->_startAboutItem = 0; } void Scripts::cmdSetTimer() { @@ -461,11 +584,10 @@ void Scripts::cmdCheckTimer() { } void Scripts::cmdSetTravel() { - if (!_vm->isDemo()) { - cmdJumpGoto(); - return; - } - error("TODO: DEMO - cmdSetTravel"); + int idx = _data->readByte(); + int dest = _data->readByte(); + _vm->_travel[idx] = dest; + _vm->_startTravelItem = _vm->_startTravelBox = 0; } void Scripts::cmdJumpGoto() { @@ -517,11 +639,11 @@ void Scripts::cmdSetScroll() { } void Scripts::cmdSaveRect() { - if (!_vm->isDemo()) { - cmdVideoEnded(); - return; - } - error("TODO: DEMO - cmdSaveRect"); + int x = _vm->_screen->_lastBoundsX; + int y = _vm->_screen->_lastBoundsY; + int w = _vm->_screen->_lastBoundsW; + int h = _vm->_screen->_lastBoundsH; + _vm->_newRects.push_back(Common::Rect(x, y, x + w, x + h)); } void Scripts::cmdVideoEnded() { @@ -553,19 +675,81 @@ void Scripts::cmdRemoveLast() { } void Scripts::cmdDoTravel() { - if (!_vm->isDemo()) { - cmdSpecial(); + while (true) { + _vm->_travelBox->getList(Martian::TRAVDATA, _vm->_travel); + int btnSelected = 0; + int boxX = _vm->_travelBox->doBox_v1(_vm->_startTravelItem, _vm->_startTravelBox, btnSelected); + _vm->_startTravelItem = _vm->_boxDataStart; + _vm->_startTravelBox = _vm->_boxSelectY; + + if (boxX == -1) + btnSelected = 2; + + if (btnSelected != 2) { + int idx = _vm->_travelBox->_tempListIdx[boxX]; + if (Martian::_byte1EEB5[idx] != _vm->_byte26CB5) { + _vm->_bubbleBox->_bubbleTitle = "_travel"; + _vm->_bubbleBox->printString("YOU CAN'T GET THERE FROM HERE."); + continue; + } + if (_vm->_player->_roomNumber != idx) { + _vm->_player->_roomNumber = idx; + _vm->_room->_function = FN_CLEAR1; + if (Martian::_travelPos[idx][0] == -1) { + _vm->_player->_roomNumber = idx; + _vm->_room->_conFlag = true; + _vm->_scripts->converse1(Martian::_travelPos[idx][1]); + return; + } + _vm->_player->_rawPlayer = Common::Point(Martian::_travelPos[idx][0], Martian::_travelPos[idx][1]); + cmdRetPos(); + return; + } + } + + if (_vm->_player->_roomNumber == -1) + continue; + return; } - error("TODO: DEMO - cmdDoTravel"); } -void Scripts::cmdCheckAbout() { - if (!_vm->isDemo()) { - cmdSpecial(); - return; +void Scripts::cmdHelp_v1() { + int idx = 0; + for (int i = 0; i < 40; i++) { + byte c = _data->readByte(); + if (c != 0xFF) { + Common::String tmpStr = c + readString(); + if (Martian::HELP[i]) { + _vm->_helpBox->_tempList[idx] = tmpStr; + _vm->_helpBox->_tempListIdx[idx] = i; + ++idx; + } + } else + break; } - error("TODO: DEMO - cmdCheckAbout"); + _vm->_helpBox->_tempList[idx] = ""; + + int btnSelected = 0; + int boxX = _vm->_helpBox->doBox_v1(0, 0, btnSelected); + + if (boxX == -1) + btnSelected = 2; + + if (btnSelected != 2) + _vm->_useItem = _vm->_helpBox->_tempListIdx[boxX]; + else + _vm->_useItem = -1; +} + +void Scripts::cmdCheckAbout() { + int idx = _data->readSint16LE(); + int val = _data->readSint16LE(); + + if (_vm->_ask[idx] == val) + cmdGoto(); + else + _data->skip(2); } void Scripts::cmdSpecial() { @@ -574,7 +758,7 @@ void Scripts::cmdSpecial() { int p2 = _data->readUint16LE(); if (_specialFunction == 1) { - if (_vm->_establishTable[p2] == 1) + if (_vm->_establishTable[p2]) return; _vm->_screen->savePalette(); @@ -616,33 +800,48 @@ void Scripts::cmdCharSpeak() { void Scripts::cmdTexSpeak() { _vm->_screen->_printOrg = _texsOrg; _vm->_screen->_printStart = _texsOrg; - _vm->_screen->_maxChars = 20; + _vm->_screen->_maxChars = (_vm->getGameID() == GType_MartianMemorandum) ? 23 : 20; byte v; Common::String tmpStr = ""; while ((v = _data->readByte()) != 0) tmpStr += (char)v; - _vm->_bubbleBox->_bubbleDisplStr = Common::String("JASON"); + if (_vm->getGameID() == GType_MartianMemorandum) + _vm->_bubbleBox->_bubbleDisplStr = Common::String("TEX"); + else + _vm->_bubbleBox->_bubbleDisplStr = Common::String("JASON"); + _vm->_bubbleBox->placeBubble1(tmpStr); findNull(); } #define BTN_COUNT 6 void Scripts::cmdTexChoice() { - static const int BTN_RANGES[BTN_COUNT][2] = { - { 0, 76 }, { 77, 154 }, { 155, 232 }, { 233, 276 }, { 0, 0 }, - { 277, 319 } + // MM is defining 2 times the last range in the original. + static const int BTN_RANGES_v1[BTN_COUNT][2] = { + { 0, 60 }, { 64, 124 }, { 129, 192 }, { 194, 227 }, { 233, 292 }, { 297, 319 } + }; + + static const int BTN_RANGES_v2[BTN_COUNT][2] = { + { 0, 76 }, { 77, 154 }, { 155, 232 }, { 233, 276 }, { 0, 0 }, { 277, 319 } }; + _vm->_oldRects.clear(); _choiceStart = _data->pos() - 1; _vm->_fonts._charSet._lo = 1; _vm->_fonts._charSet._hi = 8; - _vm->_fonts._charFor._lo = 55; _vm->_fonts._charFor._hi = 255; + + if (_vm->getGameID() == GType_MartianMemorandum) { + _vm->_fonts._charFor._lo = 247; + _vm->_screen->_maxChars = 23; + } else { + _vm->_fonts._charFor._lo = 55; + _vm->_screen->_maxChars = 20; + } - _vm->_screen->_maxChars = 20; _vm->_screen->_printOrg = _texsOrg; _vm->_screen->_printStart = _texsOrg; @@ -659,7 +858,7 @@ void Scripts::cmdTexChoice() { Common::Array<Common::Rect> responseCoords; responseCoords.push_back(_vm->_bubbleBox->_bounds); - _vm->_screen->_printOrg.y = _vm->_bubbleBox->_bounds.bottom + 11; + _vm->_screen->_printOrg.y = _vm->_bubbleBox->_bounds.bottom + ((_vm->getGameID() == GType_MartianMemorandum) ? 20 : 11); findNull(); @@ -672,7 +871,7 @@ void Scripts::cmdTexChoice() { _vm->_bubbleBox->calcBubble(tmpStr); _vm->_bubbleBox->printBubble(tmpStr); responseCoords.push_back(_vm->_bubbleBox->_bounds); - _vm->_screen->_printOrg.y = _vm->_bubbleBox->_bounds.bottom + 11; + _vm->_screen->_printOrg.y = _vm->_bubbleBox->_bounds.bottom + ((_vm->getGameID() == GType_MartianMemorandum) ? 20 : 11); } findNull(); @@ -687,7 +886,7 @@ void Scripts::cmdTexChoice() { _vm->_bubbleBox->calcBubble(tmpStr); _vm->_bubbleBox->printBubble(tmpStr); responseCoords.push_back(_vm->_bubbleBox->_bounds); - _vm->_screen->_printOrg.y = _vm->_bubbleBox->_bounds.bottom + 11; + _vm->_screen->_printOrg.y = _vm->_bubbleBox->_bounds.bottom + ((_vm->getGameID() == GType_MartianMemorandum) ? 20 : 11); } findNull(); @@ -702,11 +901,13 @@ void Scripts::cmdTexChoice() { _vm->_bubbleBox->_bubbleDisplStr = _vm->_bubbleBox->_bubbleTitle; if (_vm->_events->_leftButton) { - if (_vm->_events->_mouseRow >= 22) { + if (_vm->_events->_mouseRow >= ((_vm->getGameID() == GType_MartianMemorandum) ? 23 : 22)) { _vm->_events->debounceLeft(); int x = _vm->_events->_mousePos.x; for (int i = 0; i < BTN_COUNT; i++) { - if ((x >= BTN_RANGES[i][0]) && (x < BTN_RANGES[i][1])) { + if (((_vm->getGameID() == GType_MartianMemorandum) && (x >= BTN_RANGES_v1[i][0]) && (x < BTN_RANGES_v1[i][1])) + || ((_vm->getGameID() == GType_Amazon) && (x >= BTN_RANGES_v2[i][0]) && (x < BTN_RANGES_v2[i][1]))) { + choice = i; break; } @@ -827,39 +1028,46 @@ void Scripts::cmdPlayVideoSound() { } void Scripts::cmdPrintWatch() { - if (!_vm->isDemo()) { - cmdPushLocation(); - return; - } - error("TODO: DEMO - cmdPrintWatch"); + printWatch(); + findNull(); } void Scripts::cmdDispAbout() { - if (!_vm->isDemo()) { - cmdPushLocation(); - return; - } - error("TODO: DEMO - cmdDispAbout"); + _vm->_travelBox->getList(Martian::_askTBL, _vm->_ask); + int btnSelected = 0; + int boxX = _vm->_aboutBox->doBox_v1(_vm->_startAboutItem, _vm->_startAboutBox, btnSelected); + _vm->_startAboutItem = _vm->_boxDataStart; + _vm->_startAboutBox = _vm->_boxSelectY; + + if (boxX == -1) + btnSelected = 2; + + if (btnSelected == 2) + _vm->_useItem = -1; + else + _vm->_useItem = _vm->_travelBox->_tempListIdx[boxX]; } void Scripts::cmdPushLocation() { - error("TODO cmdPushLocation"); + _choiceStart = _data->pos() - 1; } void Scripts::cmdCheckTravel() { - if (!_vm->isDemo()) { - cmdPushLocation(); - return; - } - error("TODO: DEMO - cmdCheckTravel"); + int idx = _data->readSint16LE(); + int val = _data->readUint16LE(); + + if (_vm->_travel[idx] == val) + cmdGoto(); + else + _data->skip(2); } void Scripts::cmdBlock() { - if (!_vm->isDemo()) { - cmdPushLocation(); - return; - } - error("TODO: DEMO - cmdBlock"); + error("TODO: cmdBlock"); + /*int val1 = */_data->readSint16LE(); + /*int val2 = */_data->readUint16LE(); + /*int val3 = */_data->readSint16LE(); + /*int val4 = */_data->readUint16LE(); } void Scripts::cmdPlayerOff() { diff --git a/engines/access/scripts.h b/engines/access/scripts.h index cfadf6d901..07fd6acfb1 100644 --- a/engines/access/scripts.h +++ b/engines/access/scripts.h @@ -35,18 +35,25 @@ class Scripts; #define SCRIPT_START_BYTE 0xE0 #define ROOM_SCRIPT 2000 +typedef void(Scripts::*ScriptMethodPtr)(); + class Scripts : public Manager { private: Resource *_resource; int _specialFunction; - void charLoop(); + void clearWatch(); + void printWatch(); + protected: Common::SeekableReadStream *_data; + ScriptMethodPtr COMMAND_LIST[100]; virtual void executeSpecial(int commandIndex, int param1, int param2) = 0; virtual void executeCommand(int commandIndex); + void charLoop(); + /** * Read a null terminated string from the script */ @@ -61,7 +68,8 @@ protected: void cmdJumpUse(); void cmdJumpTalk(); void cmdNull(); - void cmdPrint(); + void cmdPrint_v1(); + void cmdPrint_v2(); void cmdAnim(); void cmdSetFlag(); void cmdCheckFlag(); @@ -83,7 +91,8 @@ protected: void cmdRetNeg(); void cmdCheckLoc(); void cmdSetAnim(); - void cmdDispInv(); + void cmdDispInv_v1(); + void cmdDispInv_v2(); void cmdSetAbout(); void cmdSetTimer(); void cmdCheckTimer(); @@ -127,7 +136,8 @@ protected: void cmdDead(); void cmdFadeOut(); void cmdEndVideo(); - void cmdHelp(); + void cmdHelp_v1(); + void cmdHelp_v2(); void cmdCycleBack(); void cmdSetHelp(); public: @@ -138,11 +148,15 @@ public: int _choice; int32 _choiceStart; Common::Point _charsOrg, _texsOrg; + public: Scripts(AccessEngine *vm); virtual ~Scripts(); + void setOpcodes(); + void setOpcodes_v2(); + void setScript(Resource *data, bool restartFlag = false); void freeScriptData(); @@ -152,6 +166,7 @@ public: int executeScript(); void findNull(); + void doCmdPrint_v1(Common::String msg); /** * Print a given message to the screen in a bubble box @@ -161,6 +176,7 @@ public: // Script commands that need to be public void cmdFreeSound(); void cmdRetPos(); + void converse1(int val); }; } // End of namespace Access diff --git a/engines/access/sound.cpp b/engines/access/sound.cpp index da267bdc4c..51ffb88f37 100644 --- a/engines/access/sound.cpp +++ b/engines/access/sound.cpp @@ -21,9 +21,12 @@ */ #include "common/algorithm.h" +#include "common/config-manager.h" #include "audio/mixer.h" #include "audio/decoders/raw.h" #include "audio/decoders/wave.h" +// Miles Audio +#include "audio/miles.h" #include "access/access.h" #include "access/sound.h" @@ -47,9 +50,6 @@ void SoundManager::clearSounds() { if (_mixer->isSoundHandleActive(_effectsHandle)) _mixer->stopHandle(_effectsHandle); - if (_queue.size()) - _queue.remove_at(0); - while (_queue.size()) { delete _queue[0]; _queue.remove_at(0); @@ -75,26 +75,26 @@ Resource *SoundManager::loadSound(int fileNum, int subfile) { return _vm->_files->loadFile(fileNum, subfile); } -void SoundManager::playSound(int soundIndex) { - debugC(1, kDebugSound, "playSound(%d)", soundIndex); +void SoundManager::playSound(int soundIndex, bool loop) { + debugC(1, kDebugSound, "playSound(%d, %d)", soundIndex, loop); int priority = _soundTable[soundIndex]._priority; - playSound(_soundTable[soundIndex]._res, priority); + playSound(_soundTable[soundIndex]._res, priority, loop); } -void SoundManager::playSound(Resource *res, int priority) { +void SoundManager::playSound(Resource *res, int priority, bool loop) { debugC(1, kDebugSound, "playSound"); byte *resourceData = res->data(); assert(res->_size >= 32); + Audio::RewindableAudioStream *audioStream; + if (READ_BE_UINT32(resourceData) == MKTAG('R','I','F','F')) { // CD version uses WAVE-files Common::SeekableReadStream *waveStream = new Common::MemoryReadStream(resourceData, res->_size, DisposeAfterUse::NO); - Audio::RewindableAudioStream *audioStream = Audio::makeWAVStream(waveStream, DisposeAfterUse::YES); - _queue.push_back(audioStream); - + audioStream = Audio::makeWAVStream(waveStream, DisposeAfterUse::YES); } else if (READ_BE_UINT32(resourceData) == MKTAG('S', 'T', 'E', 'V')) { // sound files have a fixed header of 32 bytes in total // header content: @@ -134,16 +134,20 @@ void SoundManager::playSound(Resource *res, int priority) { return; } - Audio::RewindableAudioStream *audioStream = Audio::makeRawStream(resourceData + 32, sampleSize, sampleRate, 0, DisposeAfterUse::NO); - _queue.push_back(audioStream); - + audioStream = Audio::makeRawStream(resourceData + 32, sampleSize, sampleRate, 0, DisposeAfterUse::NO); } else error("Unknown format"); + if (loop) { + _queue.push_back(new Audio::LoopingAudioStream(audioStream, 0, DisposeAfterUse::NO)); + } else { + _queue.push_back(audioStream); + } + if (!_mixer->isSoundHandleActive(_effectsHandle)) _mixer->playStream(Audio::Mixer::kSFXSoundType, &_effectsHandle, _queue[0], -1, _mixer->kMaxChannelVolume, 0, - DisposeAfterUse::YES); + DisposeAfterUse::NO); } void SoundManager::checkSoundQueue() { @@ -152,12 +156,13 @@ void SoundManager::checkSoundQueue() { if (_queue.empty() || _mixer->isSoundHandleActive(_effectsHandle)) return; + delete _queue[0]; _queue.remove_at(0); - if (_queue.size()) + if (_queue.size() && _queue[0]) _mixer->playStream(Audio::Mixer::kSFXSoundType, &_effectsHandle, _queue[0], -1, _mixer->kMaxChannelVolume, 0, - DisposeAfterUse::YES); + DisposeAfterUse::NO); } bool SoundManager::isSFXPlaying() { @@ -178,7 +183,7 @@ void SoundManager::loadSounds(Common::Array<RoomInfo::SoundIdent> &sounds) { void SoundManager::stopSound() { debugC(3, kDebugSound, "stopSound"); - _mixer->stopHandle(Audio::SoundHandle()); + _mixer->stopHandle(_effectsHandle); } void SoundManager::freeSounds() { @@ -194,18 +199,63 @@ MusicManager::MusicManager(AccessEngine *vm) : _vm(vm) { _music = nullptr; _tempMusic = nullptr; _isLooping = false; + _driver = nullptr; + _byte1F781 = false; + + MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MT32); + MusicType musicType = MidiDriver::getMusicType(dev); + + // Amazon Guardians of Eden uses MIDPAK inside MIDIDRV.AP + // AdLib patches are inside MIDIDRV.AP too, 2nd resource file + // + // Amazon Guardians of Eden (demo) seems to use another type of driver, possibly written by Access themselves + // Martian Memorandum uses this other type of driver as well, which means it makes sense to reverse engineer it. + // + switch (musicType) { + case MT_ADLIB: { + if (_vm->getGameID() == GType_Amazon) { + Resource *midiDrvResource = _vm->_files->loadFile(92, 1); + Common::MemoryReadStream *adLibInstrumentStream = new Common::MemoryReadStream(midiDrvResource->data(), midiDrvResource->_size); + + _driver = Audio::MidiDriver_Miles_AdLib_create("", "", adLibInstrumentStream); + + delete midiDrvResource; + delete adLibInstrumentStream; + } else { + MidiPlayer::createDriver(); + } + break; + } + case MT_MT32: + _driver = Audio::MidiDriver_Miles_MT32_create(""); + _nativeMT32 = true; + break; + case MT_GM: + if (ConfMan.getBool("native_mt32")) { + _driver = Audio::MidiDriver_Miles_MT32_create(""); + _nativeMT32 = true; + } + break; + + default: + break; + } +#if 0 MidiPlayer::createDriver(); MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM); +#endif - int retValue = _driver->open(); - if (retValue == 0) { - if (_nativeMT32) - _driver->sendMT32Reset(); - else - _driver->sendGMReset(); + if (_driver) { + int retValue = _driver->open(); + if (retValue == 0) { + if (_nativeMT32) + _driver->sendMT32Reset(); + else + _driver->sendGMReset(); - _driver->setTimerCallback(this, &timerCallback); + _driver->setTimerCallback(this, &timerCallback); + } } } @@ -215,16 +265,23 @@ MusicManager::~MusicManager() { } void MusicManager::send(uint32 b) { + // Pass data directly to driver + _driver->send(b); +#if 0 if ((b & 0xF0) == 0xC0 && !_nativeMT32) { b = (b & 0xFFFF00FF) | MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8; } Audio::MidiPlayer::send(b); +#endif } void MusicManager::midiPlay() { debugC(1, kDebugSound, "midiPlay"); + if (!_driver) + return; + if (_music->_size < 4) { error("midiPlay() wrong music resource size"); } @@ -262,6 +319,8 @@ bool MusicManager::checkMidiDone() { void MusicManager::midiRepeat() { debugC(1, kDebugSound, "midiRepeat"); + if (!_driver) + return; if (!_parser) return; @@ -274,6 +333,9 @@ void MusicManager::midiRepeat() { void MusicManager::stopSong() { debugC(1, kDebugSound, "stopSong"); + if (!_driver) + return; + stop(); } @@ -292,6 +354,9 @@ void MusicManager::loadMusic(FileIdent file) { void MusicManager::newMusic(int musicId, int mode) { debugC(1, kDebugSound, "newMusic(%d, %d)", musicId, mode); + if (!_driver) + return; + if (mode == 1) { stopSong(); freeMusic(); diff --git a/engines/access/sound.h b/engines/access/sound.h index 90f6656e26..e11a6b9730 100644 --- a/engines/access/sound.h +++ b/engines/access/sound.h @@ -49,20 +49,21 @@ private: AccessEngine *_vm; Audio::Mixer *_mixer; Audio::SoundHandle _effectsHandle; - Common::Array<Audio::RewindableAudioStream *> _queue; + Common::Array<Audio::AudioStream *> _queue; void clearSounds(); - void playSound(Resource *res, int priority); + void playSound(Resource *res, int priority, bool loop); public: Common::Array<SoundEntry> _soundTable; + bool _playingSound; public: SoundManager(AccessEngine *vm, Audio::Mixer *mixer); ~SoundManager(); void loadSoundTable(int idx, int fileNum, int subfile, int priority = 1); - void playSound(int soundIndex); + void playSound(int soundIndex, bool loop = false); void checkSoundQueue(); bool isSFXPlaying(); @@ -84,6 +85,7 @@ private: public: Resource *_music; + bool _byte1F781; public: MusicManager(AccessEngine *vm); diff --git a/engines/access/video.cpp b/engines/access/video.cpp index 63d0aa5c89..5fc5f6762c 100644 --- a/engines/access/video.cpp +++ b/engines/access/video.cpp @@ -48,17 +48,13 @@ VideoPlayer::~VideoPlayer() { closeVideo(); } - -void VideoPlayer::setVideo(ASurface *vidSurface, const Common::Point &pt, const FileIdent &videoFile, int rate) { +void VideoPlayer::setVideo(ASurface *vidSurface, const Common::Point &pt, int rate) { _vidSurface = vidSurface; vidSurface->_orgX1 = pt.x; vidSurface->_orgY1 = pt.y; _vm->_timers[31]._timer = rate; _vm->_timers[31]._initTm = rate; - // Open up video stream - _videoData = _vm->_files->loadFile(videoFile); - // Load in header _header._frameCount = _videoData->_stream->readUint16LE(); _header._width = _videoData->_stream->readUint16LE(); @@ -91,6 +87,20 @@ void VideoPlayer::setVideo(ASurface *vidSurface, const Common::Point &pt, const _videoEnd = false; } +void VideoPlayer::setVideo(ASurface *vidSurface, const Common::Point &pt, const Common::String filename, int rate) { + // Open up video stream + _videoData = _vm->_files->loadFile(filename); + + setVideo(vidSurface, pt, rate); +} + +void VideoPlayer::setVideo(ASurface *vidSurface, const Common::Point &pt, const FileIdent &videoFile, int rate) { + // Open up video stream + _videoData = _vm->_files->loadFile(videoFile); + + setVideo(vidSurface, pt, rate); +} + void VideoPlayer::closeVideo() { delete _videoData; _videoData = nullptr; diff --git a/engines/access/video.h b/engines/access/video.h index 17825db367..83c8995d3e 100644 --- a/engines/access/video.h +++ b/engines/access/video.h @@ -51,6 +51,7 @@ private: Common::Rect _videoBounds; void getFrame(); + void setVideo(ASurface *vidSurface, const Common::Point &pt, int rate); public: int _videoFrame; bool _soundFlag; @@ -64,6 +65,7 @@ public: * Start up a video */ void setVideo(ASurface *vidSurface, const Common::Point &pt, const FileIdent &videoFile, int rate); + void setVideo(ASurface *vidSurface, const Common::Point &pt, const Common::String filename, int rate); /** * Decodes a frame of the video diff --git a/engines/agi/detection_tables.h b/engines/agi/detection_tables.h index 0ae822a538..af3d93288e 100644 --- a/engines/agi/detection_tables.h +++ b/engines/agi/detection_tables.h @@ -850,6 +850,7 @@ static const AGIGameDescription gameDescriptions[] = { FANMADE("Tex McPhilip 3 - A Destiny of Sin (Demo v0.25)", "992d12031a486ad84e592ff5d7c9d782"), FANMADE("The 13th Disciple (v1.00)", "887719ad59afce9a41ec057dbb73ad73"), FANMADE("The Adventures of a Crazed Hermit", "6e3086cbb794d3299a9c5a9792295511"), + FANMADE("The Gourd of the Beans", "246f4d94946afb547482d44a53616d06"), FANMADE("The Grateful Dead", "c2146631afacf8cb455ce24f3d2d46e7"), FANMADE("The Legend of Shay-Larah 1 - The Lost Prince", "04e720c8e30c9cf12db22ea14a24a3dd"), FANMADE("The Legend of Zelda: The Fungus of Time (Demo v1.00)", "dcaf8166ceb62a3d9b9aea7f3b197c09"), diff --git a/engines/agi/graphics.cpp b/engines/agi/graphics.cpp index 32d0fdc06a..c5cede71ef 100644 --- a/engines/agi/graphics.cpp +++ b/engines/agi/graphics.cpp @@ -72,6 +72,7 @@ static const uint8 egaPalette[16 * 3] = { * from Donald Duck's Playground (1986) to Manhunter II (1989). * 16 RGB colors. 3 bits per color component. */ +#if 0 static const uint8 atariStAgiPalette[16 * 3] = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, @@ -90,6 +91,7 @@ static const uint8 atariStAgiPalette[16 * 3] = { 0x7, 0x7, 0x4, 0x7, 0x7, 0x7 }; +#endif /** * Second generation Apple IIGS AGI palette. @@ -109,6 +111,8 @@ static const uint8 atariStAgiPalette[16 * 3] = { * 3.001 (Black Cauldron v1.0O 1989-02-24 (CE)) * 3.003 (Gold Rush! v1.0M 1989-02-28 (CE)) */ +#if 0 +// FIXME: Identical to amigaAgiPaletteV2 static const uint8 appleIIgsAgiPaletteV2[16 * 3] = { 0x0, 0x0, 0x0, 0x0, 0x0, 0xF, @@ -127,6 +131,7 @@ static const uint8 appleIIgsAgiPaletteV2[16 * 3] = { 0xE, 0xE, 0x0, 0xF, 0xF, 0xF }; +#endif /** * First generation Amiga & Apple IIGS AGI palette. diff --git a/engines/agi/preagi_mickey.cpp b/engines/agi/preagi_mickey.cpp index a1572d7f1f..883107a957 100644 --- a/engines/agi/preagi_mickey.cpp +++ b/engines/agi/preagi_mickey.cpp @@ -2299,6 +2299,8 @@ void MickeyEngine::init() { _gameStateMickey.fItemUsed[IDI_MSA_ITEM_LETTER] = true; #endif + + setflag(fSoundOn, true); // enable sound } Common::Error MickeyEngine::go() { diff --git a/engines/agos/agos.cpp b/engines/agos/agos.cpp index 6eda2eb9aa..8952e649fd 100644 --- a/engines/agos/agos.cpp +++ b/engines/agos/agos.cpp @@ -585,7 +585,9 @@ Common::Error AGOSEngine::init() { ((getFeatures() & GF_TALKIE) && getPlatform() == Common::kPlatformAcorn) || (getPlatform() == Common::kPlatformDOS)) { - int ret = _midi->open(getGameType()); + bool isDemo = (getFeatures() & GF_DEMO) ? true : false; + + int ret = _midi->open(getGameType(), isDemo); if (ret) warning("MIDI Player init failed: \"%s\"", MidiDriver::getErrorName(ret)); diff --git a/engines/agos/detection_tables.h b/engines/agos/detection_tables.h index 2f4709c49e..793d4081cf 100644 --- a/engines/agos/detection_tables.h +++ b/engines/agos/detection_tables.h @@ -1385,7 +1385,7 @@ static const AGOSGameDescription gameDescriptions[] = { { { "simon1", - "Floppy", + "Infocom Floppy", { { "gamepc", GAME_BASEFILE, "9f93d27432ce44a787eef10adb640870", 37070}, @@ -1409,7 +1409,7 @@ static const AGOSGameDescription gameDescriptions[] = { { { "simon1", - "Floppy", + "Infocom Floppy", { { "gamepc", GAME_BASEFILE, "62de24fc579b94fac7d3d23201b65b14", -1}, @@ -1599,11 +1599,11 @@ static const AGOSGameDescription gameDescriptions[] = { GF_TALKIE }, - // Simon the Sorcerer 1 - English DOS CD alternate? + // Simon the Sorcerer 1 - English DOS CD (Infocom) { { "simon1", - "CD", + "Infocom CD", { { "gamepc", GAME_BASEFILE, "c0b948b6821d2140f8b977144f21027a", -1}, diff --git a/engines/agos/drivers/accolade/adlib.cpp b/engines/agos/drivers/accolade/adlib.cpp new file mode 100644 index 0000000000..294be2b8a7 --- /dev/null +++ b/engines/agos/drivers/accolade/adlib.cpp @@ -0,0 +1,883 @@ +/* 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 "agos/agos.h" +#include "agos/drivers/accolade/mididriver.h" + +#include "common/file.h" +#include "common/mutex.h" +#include "common/system.h" +#include "common/textconsole.h" + +#include "audio/fmopl.h" +#include "audio/softsynth/emumidi.h" + +namespace AGOS { + +#define AGOS_ADLIB_VOICES_COUNT 11 +#define AGOS_ADLIB_VOICES_MELODIC_COUNT 6 +#define AGOS_ADLIB_VOICES_PERCUSSION_START 6 +#define AGOS_ADLIB_VOICES_PERCUSSION_COUNT 5 +#define AGOS_ADLIB_VOICES_PERCUSSION_CYMBAL 9 + +// 5 instruments on top of the regular MIDI ones +// used by the MUSIC.DRV variant for percussion instruments +#define AGOS_ADLIB_EXTRA_INSTRUMENT_COUNT 5 + +const byte operator1Register[AGOS_ADLIB_VOICES_COUNT] = { + 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x14, 0x12, 0x15, 0x11 +}; + +const byte operator2Register[AGOS_ADLIB_VOICES_COUNT] = { + 0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0xFF, 0xFF, 0xFF, 0xFF +}; + +// percussion: +// voice 6 - base drum - also uses operator 13h +// voice 7 - snare drum +// voice 8 - tom tom +// voice 9 - cymbal +// voice 10 - hi hat +const byte percussionBits[AGOS_ADLIB_VOICES_PERCUSSION_COUNT] = { + 0x10, 0x08, 0x04, 0x02, 0x01 +}; + +// hardcoded, dumped from Accolade music system +// same for INSTR.DAT + MUSIC.DRV, except that MUSIC.DRV does the lookup differently +const byte percussionKeyNoteChannelTable[] = { + 0x06, 0x07, 0x07, 0x07, 0x07, 0x08, 0x0A, 0x08, 0x0A, 0x08, + 0x0A, 0x08, 0x08, 0x09, 0x08, 0x09, 0x0F, 0x0F, 0x0A, 0x0F, + 0x0A, 0x0F, 0x0F, 0x0F, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x0A, 0x0F, 0x0F, 0x08, 0x0F, 0x08 +}; + +struct InstrumentEntry { + byte reg20op1; // Amplitude Modulation / Vibrato / Envelope Generator Type / Keyboard Scaling Rate / Modulator Frequency Multiple + byte reg40op1; // Level Key Scaling / Total Level + byte reg60op1; // Attack Rate / Decay Rate + byte reg80op1; // Sustain Level / Release Rate + byte reg20op2; // Amplitude Modulation / Vibrato / Envelope Generator Type / Keyboard Scaling Rate / Modulator Frequency Multiple + byte reg40op2; // Level Key Scaling / Total Level + byte reg60op2; // Attack Rate / Decay Rate + byte reg80op2; // Sustain Level / Release Rate + byte regC0; // Feedback / Algorithm, bit 0 - set -> both operators in use +}; + +// hardcoded, dumped from Accolade music system (INSTR.DAT variant) +const uint16 frequencyLookUpTable[12] = { + 0x02B2, 0x02DB, 0x0306, 0x0334, 0x0365, 0x0399, 0x03CF, + 0xFE05, 0xFE23, 0xFE44, 0xFE67, 0xFE8B +}; + +// hardcoded, dumped from Accolade music system (MUSIC.DRV variant) +const uint16 frequencyLookUpTableMusicDrv[12] = { + 0x0205, 0x0223, 0x0244, 0x0267, 0x028B, 0x02B2, 0x02DB, + 0x0306, 0x0334, 0x0365, 0x0399, 0x03CF +}; + +// +// Accolade adlib music driver +// +// Remarks: +// +// There are at least 2 variants of this sound system. +// One for the games Elvira 1 + Elvira 2 +// It seems it was also used for the game "Altered Destiny" +// Another one for the games Waxworks + Simon, the Sorcerer 1 Demo +// +// First one uses the file INSTR.DAT for instrument data, channel mapping etc. +// Second one uses the file MUSIC.DRV, which actually contains driver code + instrument data + channel mapping, etc. +// +// The second variant supported dynamic channel allocation for the FM voice channels, but this +// feature was at least definitely disabled for Simon, the Sorcerer 1 demo and for the Waxworks demo too. +// +// I have currently not implemented dynamic channel allocation. + +class MidiDriver_Accolade_AdLib : public MidiDriver { +public: + MidiDriver_Accolade_AdLib(); + virtual ~MidiDriver_Accolade_AdLib(); + + // MidiDriver + int open(); + void close(); + void send(uint32 b); + MidiChannel *allocateChannel() { return NULL; } + MidiChannel *getPercussionChannel() { return NULL; } + + bool isOpen() const { return _isOpen; } + uint32 getBaseTempo() { return 1000000 / OPL::OPL::kDefaultCallbackFrequency; } + + void setVolume(byte volume); + virtual uint32 property(int prop, uint32 param); + + bool setupInstruments(byte *instrumentData, uint16 instrumentDataSize, bool useMusicDrvFile); + + void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc); + +private: + bool _musicDrvMode; + + // from INSTR.DAT/MUSIC.DRV - simple mapping between MIDI channel and MT32 channel + byte _channelMapping[AGOS_MIDI_CHANNEL_COUNT]; + // from INSTR.DAT/MUSIC.DRV - simple mapping between MIDI instruments and MT32 instruments + byte _instrumentMapping[AGOS_MIDI_INSTRUMENT_COUNT]; + // from INSTR.DAT/MUSIC.DRV - volume adjustment per instrument + signed char _instrumentVolumeAdjust[AGOS_MIDI_INSTRUMENT_COUNT]; + // simple mapping between MIDI key notes and MT32 key notes + byte _percussionKeyNoteMapping[AGOS_MIDI_KEYNOTE_COUNT]; + + // from INSTR.DAT/MUSIC.DRV - adlib instrument data + InstrumentEntry *_instrumentTable; + byte _instrumentCount; + + struct ChannelEntry { + const InstrumentEntry *currentInstrumentPtr; + byte currentNote; + byte currentA0hReg; + byte currentB0hReg; + int16 volumeAdjust; + + ChannelEntry() : currentInstrumentPtr(NULL), currentNote(0), + currentA0hReg(0), currentB0hReg(0), volumeAdjust(0) { } + }; + + byte _percussionReg; + + OPL::OPL *_opl; + int _masterVolume; + + Common::TimerManager::TimerProc _adlibTimerProc; + void *_adlibTimerParam; + + bool _isOpen; + + // stores information about all FM voice channels + ChannelEntry _channels[AGOS_ADLIB_VOICES_COUNT]; + + void onTimer(); + + void resetAdLib(); + void resetAdLibOperatorRegisters(byte baseRegister, byte value); + void resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value); + + void programChange(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr); + void programChangeSetInstrument(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr); + void setRegister(int reg, int value); + void noteOn(byte FMvoiceChannel, byte note, byte velocity); + void noteOnSetVolume(byte FMvoiceChannel, byte operatorReg, byte adjustedVelocity); + void noteOff(byte FMvoiceChannel, byte note, bool dontCheckNote); +}; + +MidiDriver_Accolade_AdLib::MidiDriver_Accolade_AdLib() + : _masterVolume(15), _opl(0), + _adlibTimerProc(0), _adlibTimerParam(0), _isOpen(false) { + memset(_channelMapping, 0, sizeof(_channelMapping)); + memset(_instrumentMapping, 0, sizeof(_instrumentMapping)); + memset(_instrumentVolumeAdjust, 0, sizeof(_instrumentVolumeAdjust)); + memset(_percussionKeyNoteMapping, 0, sizeof(_percussionKeyNoteMapping)); + + _instrumentTable = NULL; + _instrumentCount = 0; + _musicDrvMode = false; + _percussionReg = 0x20; +} + +MidiDriver_Accolade_AdLib::~MidiDriver_Accolade_AdLib() { + if (_instrumentTable) { + delete[] _instrumentTable; + _instrumentCount = 0; + } +} + +int MidiDriver_Accolade_AdLib::open() { +// debugC(kDebugLevelAdLibDriver, "AdLib: starting driver"); + + _opl = OPL::Config::create(OPL::Config::kOpl2); + + if (!_opl) + return -1; + + _opl->init(); + + _isOpen = true; + + _opl->start(new Common::Functor0Mem<void, MidiDriver_Accolade_AdLib>(this, &MidiDriver_Accolade_AdLib::onTimer)); + + resetAdLib(); + + // Finally set up default instruments + for (byte FMvoiceNr = 0; FMvoiceNr < AGOS_ADLIB_VOICES_COUNT; FMvoiceNr++) { + if (FMvoiceNr < AGOS_ADLIB_VOICES_PERCUSSION_START) { + // Regular FM voices with instrument 0 + programChangeSetInstrument(FMvoiceNr, 0, 0); + } else { + byte percussionInstrument; + if (!_musicDrvMode) { + // INSTR.DAT: percussion voices with instrument 1, 2, 3, 4 and 5 + percussionInstrument = FMvoiceNr - AGOS_ADLIB_VOICES_PERCUSSION_START + 1; + } else { + // MUSIC.DRV: percussion voices with instrument 0x80, 0x81, 0x82, 0x83 and 0x84 + percussionInstrument = FMvoiceNr - AGOS_ADLIB_VOICES_PERCUSSION_START + 0x80; + } + programChangeSetInstrument(FMvoiceNr, percussionInstrument, percussionInstrument); + } + } + + // driver initialization does this here: + // INSTR.DAT + // noteOn(9, 0x29, 0); + // noteOff(9, 0x26, false); + // MUSIC.DRV + // noteOn(9, 0x26, 0); + // noteOff(9, 0x26, false); + + return 0; +} + +void MidiDriver_Accolade_AdLib::close() { + delete _opl; + _isOpen = false; +} + +void MidiDriver_Accolade_AdLib::setVolume(byte volume) { + _masterVolume = volume; + //renewNotes(-1, true); +} + +void MidiDriver_Accolade_AdLib::onTimer() { + if (_adlibTimerProc) + (*_adlibTimerProc)(_adlibTimerParam); +} + +void MidiDriver_Accolade_AdLib::resetAdLib() { + // The original driver sent 0x00 to register 0x00 up to 0xF5 + setRegister(0xBD, 0x00); // Disable rhythm + + // reset FM voice instrument data + resetAdLibOperatorRegisters(0x20, 0); + resetAdLibOperatorRegisters(0x60, 0); + resetAdLibOperatorRegisters(0x80, 0); + resetAdLibFMVoiceChannelRegisters(0xA0, 0); + resetAdLibFMVoiceChannelRegisters(0xB0, 0); + resetAdLibFMVoiceChannelRegisters(0xC0, 0); + resetAdLibOperatorRegisters(0xE0, 0); + resetAdLibOperatorRegisters(0x40, 0x3F); // original driver sent 0x00 + + setRegister(0x01, 0x20); // enable waveform control on both operators + setRegister(0x04, 0x60); // Timer control + + setRegister(0x08, 0); // select FM music mode + setRegister(0xBD, 0x20); // Enable rhythm + + // reset our percussion register + _percussionReg = 0x20; +} + +void MidiDriver_Accolade_AdLib::resetAdLibOperatorRegisters(byte baseRegister, byte value) { + byte operatorIndex; + + for (operatorIndex = 0; operatorIndex < 0x16; operatorIndex++) { + switch (operatorIndex) { + case 0x06: + case 0x07: + case 0x0E: + case 0x0F: + break; + default: + setRegister(baseRegister + operatorIndex, value); + } + } +} + +void MidiDriver_Accolade_AdLib::resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value) { + byte FMvoiceChannel; + + for (FMvoiceChannel = 0; FMvoiceChannel < AGOS_ADLIB_VOICES_COUNT; FMvoiceChannel++) { + setRegister(baseRegister + FMvoiceChannel, value); + } +} + +// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php +void MidiDriver_Accolade_AdLib::send(uint32 b) { + byte command = b & 0xf0; + byte channel = b & 0xf; + byte op1 = (b >> 8) & 0xff; + byte op2 = (b >> 16) & 0xff; + + byte mappedChannel = _channelMapping[channel]; + byte mappedInstrument = 0; + + // Ignore everything that is outside of our channel range + if (mappedChannel >= AGOS_ADLIB_VOICES_COUNT) + return; + + switch (command) { + case 0x80: + noteOff(mappedChannel, op1, false); + break; + case 0x90: + // Convert noteOn with velocity 0 to a noteOff + if (op2 == 0) + return noteOff(mappedChannel, op1, false); + + noteOn(mappedChannel, op1, op2); + break; + case 0xb0: // Control change + // Doesn't seem to be implemented + break; + case 0xc0: // Program Change + mappedInstrument = _instrumentMapping[op1]; + programChange(mappedChannel, mappedInstrument, op1); + break; + case 0xa0: // Polyphonic key pressure (aftertouch) + case 0xd0: // Channel pressure (aftertouch) + // Aftertouch doesn't seem to be implemented + break; + case 0xe0: + // No pitch bend change + break; + case 0xf0: // SysEx + warning("ADLIB: SysEx: %x", b); + break; + default: + warning("ADLIB: Unknown event %02x", command); + } +} + +void MidiDriver_Accolade_AdLib::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) { + _adlibTimerProc = timerProc; + _adlibTimerParam = timerParam; +} + +void MidiDriver_Accolade_AdLib::noteOn(byte FMvoiceChannel, byte note, byte velocity) { + byte adjustedNote = note; + byte adjustedVelocity = velocity; + byte regValueA0h = 0; + byte regValueB0h = 0; + + // adjust velocity + int16 channelVolumeAdjust = _channels[FMvoiceChannel].volumeAdjust; + channelVolumeAdjust += adjustedVelocity; + channelVolumeAdjust = CLIP<int16>(channelVolumeAdjust, 0, 0x7F); + + // TODO: adjust to global volume + // original drivers had a global volume variable, which was 0 for full volume, -64 for half volume + // and -128 for mute + + adjustedVelocity = channelVolumeAdjust; + + if (!_musicDrvMode) { + // INSTR.DAT + // force note-off + noteOff(FMvoiceChannel, note, true); + + } else { + // MUSIC.DRV + if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) { + // force note-off, but only for actual FM voice channels + noteOff(FMvoiceChannel, note, true); + } + } + + if (FMvoiceChannel != 9) { + // regular FM voice + + if (!_musicDrvMode) { + // INSTR.DAT: adjust key note + while (adjustedNote < 24) + adjustedNote += 12; + adjustedNote -= 12; + } + + } else { + // percussion channel + // MUSIC.DRV variant didn't do this adjustment, it directly used a pointer + adjustedNote -= 36; + if (adjustedNote > 40) { // Security check + warning("ADLIB: bad percussion channel note"); + return; + } + + byte percussionChannel = percussionKeyNoteChannelTable[adjustedNote]; + if (percussionChannel >= AGOS_ADLIB_VOICES_COUNT) + return; // INSTR.DAT variant checked for ">" instead of ">=", which seems to have been a bug + + // Map the keynote accordingly + adjustedNote = _percussionKeyNoteMapping[adjustedNote]; + // Now overwrite the FM voice channel + FMvoiceChannel = percussionChannel; + } + + if (!_musicDrvMode) { + // INSTR.DAT + + // Save this key note + _channels[FMvoiceChannel].currentNote = adjustedNote; + + adjustedVelocity += 24; + if (adjustedVelocity > 120) + adjustedVelocity = 120; + adjustedVelocity = adjustedVelocity >> 1; // divide by 2 + + } else { + // MUSIC.DRV + adjustedVelocity = adjustedVelocity >> 1; // divide by 2 + } + + // Set volume of voice channel + noteOnSetVolume(FMvoiceChannel, 1, adjustedVelocity); + if (FMvoiceChannel <= AGOS_ADLIB_VOICES_PERCUSSION_START) { + // Set second operator for FM voices + first percussion + noteOnSetVolume(FMvoiceChannel, 2, adjustedVelocity); + } + + if (FMvoiceChannel >= AGOS_ADLIB_VOICES_PERCUSSION_START) { + // Percussion + byte percussionIdx = FMvoiceChannel - AGOS_ADLIB_VOICES_PERCUSSION_START; + + // Enable bit of the requested percussion type + assert(percussionIdx < AGOS_ADLIB_VOICES_PERCUSSION_COUNT); + _percussionReg |= percussionBits[percussionIdx]; + setRegister(0xBD, _percussionReg); + } + + if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_CYMBAL) { + // FM voice, Base Drum, Snare Drum + Tom Tom + byte adlibNote = adjustedNote; + byte adlibOctave = 0; + byte adlibFrequencyIdx = 0; + uint16 adlibFrequency = 0; + + if (!_musicDrvMode) { + // INSTR.DAT + if (adlibNote >= 0x60) + adlibNote = 0x5F; + + adlibOctave = (adlibNote / 12) - 1; + adlibFrequencyIdx = adlibNote % 12; + adlibFrequency = frequencyLookUpTable[adlibFrequencyIdx]; + + if (adlibFrequency & 0x8000) + adlibOctave++; + if (adlibOctave & 0x80) { + adlibOctave++; + adlibFrequency = adlibFrequency >> 1; + } + + } else { + // MUSIC.DRV variant + if (adlibNote >= 19) + adlibNote -= 19; + + adlibOctave = (adlibNote / 12); + adlibFrequencyIdx = adlibNote % 12; + // additional code, that will lookup octave and do a multiplication with it + // noteOn however calls the frequency calculation in a way that it multiplies with 0 + adlibFrequency = frequencyLookUpTableMusicDrv[adlibFrequencyIdx]; + } + + regValueA0h = adlibFrequency & 0xFF; + regValueB0h = ((adlibFrequency & 0x300) >> 8) | (adlibOctave << 2); + if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) { + // set Key-On flag for regular FM voices, but not for percussion + regValueB0h |= 0x20; + } + + setRegister(0xA0 + FMvoiceChannel, regValueA0h); + setRegister(0xB0 + FMvoiceChannel, regValueB0h); + _channels[FMvoiceChannel].currentA0hReg = regValueA0h; + _channels[FMvoiceChannel].currentB0hReg = regValueB0h; + + if (_musicDrvMode) { + // MUSIC.DRV + if (FMvoiceChannel < AGOS_ADLIB_VOICES_MELODIC_COUNT) { + _channels[FMvoiceChannel].currentNote = adjustedNote; + } + } + } +} + +// 100% the same for INSTR.DAT and MUSIC.DRV variants +// except for a bug, that was introduced for MUSIC.DRV +void MidiDriver_Accolade_AdLib::noteOnSetVolume(byte FMvoiceChannel, byte operatorNr, byte adjustedVelocity) { + byte operatorReg = 0; + byte regValue40h = 0; + const InstrumentEntry *curInstrument = NULL; + + regValue40h = (63 - adjustedVelocity) & 0x3F; + + if ((operatorNr == 1) && (FMvoiceChannel <= AGOS_ADLIB_VOICES_PERCUSSION_START)) { + // first operator of FM voice channels or first percussion channel + curInstrument = _channels[FMvoiceChannel].currentInstrumentPtr; + if (!(curInstrument->regC0 & 0x01)) { // check, if both operators produce sound + // only one does, instrument wants fixed volume + if (operatorNr == 1) { + regValue40h = curInstrument->reg40op1; + } else { + regValue40h = curInstrument->reg40op2; + } + + // not sure, if we are supposed to implement these bugs, or not +#if 0 + if (!_musicDrvMode) { + // Table is 16 bytes instead of 18 bytes + if ((FMvoiceChannel == 7) || (FMvoiceChannel == 9)) { + regValue40h = 0; + warning("volume set bug (original)"); + } + } + if (_musicDrvMode) { + // MUSIC.DRV variant has a bug, which will overwrite these registers + // for all operators above 11 / 0Bh, which means percussion will always + // get a value of 0 (the table holding those bytes was 12 bytes instead of 18 + if (FMvoiceChannel >= AGOS_ADLIB_VOICES_PERCUSSION_START) { + regValue40h = 0; + warning("volume set bug (original)"); + } + } +#endif + } + } + + if (operatorNr == 1) { + operatorReg = operator1Register[FMvoiceChannel]; + } else { + operatorReg = operator2Register[FMvoiceChannel]; + } + assert(operatorReg != 0xFF); // Security check + setRegister(0x40 + operatorReg, regValue40h); +} + +void MidiDriver_Accolade_AdLib::noteOff(byte FMvoiceChannel, byte note, bool dontCheckNote) { + byte adjustedNote = note; + byte regValueB0h = 0; + + if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) { + // regular FM voice + + if (!_musicDrvMode) { + // INSTR.DAT: adjust key note + while (adjustedNote < 24) + adjustedNote += 12; + adjustedNote -= 12; + } + + if (!dontCheckNote) { + // check, if current note is also the current actually playing channel note + if (_channels[FMvoiceChannel].currentNote != adjustedNote) + return; // not the same -> ignore this note off command + } + + regValueB0h = _channels[FMvoiceChannel].currentB0hReg & 0xDF; // Remove "key on" bit + setRegister(0xB0 + FMvoiceChannel, regValueB0h); + + } else { + // percussion + adjustedNote -= 36; + if (adjustedNote > 40) { // Security check + warning("ADLIB: bad percussion channel note"); + return; + } + + byte percussionChannel = percussionKeyNoteChannelTable[adjustedNote]; + if (percussionChannel > AGOS_ADLIB_VOICES_COUNT) + return; + + byte percussionIdx = percussionChannel - AGOS_ADLIB_VOICES_PERCUSSION_START; + + // Disable bit of the requested percussion type + assert(percussionIdx < AGOS_ADLIB_VOICES_PERCUSSION_COUNT); + _percussionReg &= ~percussionBits[percussionIdx]; + setRegister(0xBD, _percussionReg); + } +} + +void MidiDriver_Accolade_AdLib::programChange(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr) { + if (mappedInstrumentNr >= _instrumentCount) { + warning("ADLIB: tried to set non-existent instrument"); + return; // out of range + } + + // setup instrument + //warning("ADLIB: program change for FM voice channel %d, instrument id %d", FMvoiceChannel, mappedInstrumentNr); + + if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) { + // Regular FM voice + programChangeSetInstrument(FMvoiceChannel, mappedInstrumentNr, MIDIinstrumentNr); + + } else { + // Percussion + // set default instrument (again) + byte percussionInstrumentNr = 0; + const InstrumentEntry *instrumentPtr; + + if (!_musicDrvMode) { + // INSTR.DAT: percussion default instruments start at instrument 1 + percussionInstrumentNr = FMvoiceChannel - AGOS_ADLIB_VOICES_PERCUSSION_START + 1; + } else { + // MUSIC.DRV: percussion default instruments start at instrument 0x80 + percussionInstrumentNr = FMvoiceChannel - AGOS_ADLIB_VOICES_PERCUSSION_START + 0x80; + } + if (percussionInstrumentNr >= _instrumentCount) { + warning("ADLIB: tried to set non-existent instrument"); + return; + } + instrumentPtr = &_instrumentTable[percussionInstrumentNr]; + _channels[FMvoiceChannel].currentInstrumentPtr = instrumentPtr; + _channels[FMvoiceChannel].volumeAdjust = _instrumentVolumeAdjust[percussionInstrumentNr]; + } +} + +void MidiDriver_Accolade_AdLib::programChangeSetInstrument(byte FMvoiceChannel, byte mappedInstrumentNr, byte MIDIinstrumentNr) { + const InstrumentEntry *instrumentPtr; + byte op1Reg = 0; + byte op2Reg = 0; + + if (mappedInstrumentNr >= _instrumentCount) { + warning("ADLIB: tried to set non-existent instrument"); + return; // out of range + } + + // setup instrument + instrumentPtr = &_instrumentTable[mappedInstrumentNr]; + //warning("set instrument for FM voice channel %d, instrument id %d", FMvoiceChannel, mappedInstrumentNr); + + op1Reg = operator1Register[FMvoiceChannel]; + op2Reg = operator2Register[FMvoiceChannel]; + + setRegister(0x20 + op1Reg, instrumentPtr->reg20op1); + setRegister(0x40 + op1Reg, instrumentPtr->reg40op1); + setRegister(0x60 + op1Reg, instrumentPtr->reg60op1); + setRegister(0x80 + op1Reg, instrumentPtr->reg80op1); + + if (FMvoiceChannel <= AGOS_ADLIB_VOICES_PERCUSSION_START) { + // set 2nd operator as well for FM voices and first percussion voice + setRegister(0x20 + op2Reg, instrumentPtr->reg20op2); + setRegister(0x40 + op2Reg, instrumentPtr->reg40op2); + setRegister(0x60 + op2Reg, instrumentPtr->reg60op2); + setRegister(0x80 + op2Reg, instrumentPtr->reg80op2); + + if (!_musicDrvMode) { + // set Feedback / Algorithm as well + setRegister(0xC0 + FMvoiceChannel, instrumentPtr->regC0); + } else { + if (FMvoiceChannel < AGOS_ADLIB_VOICES_PERCUSSION_START) { + // set Feedback / Algorithm as well for regular FM voices only + setRegister(0xC0 + FMvoiceChannel, instrumentPtr->regC0); + } + } + } + + // Remember instrument + _channels[FMvoiceChannel].currentInstrumentPtr = instrumentPtr; + _channels[FMvoiceChannel].volumeAdjust = _instrumentVolumeAdjust[MIDIinstrumentNr]; +} + +void MidiDriver_Accolade_AdLib::setRegister(int reg, int value) { + _opl->writeReg(reg, value); + //warning("OPL %x %x (%d)", reg, value, value); +} + +uint32 MidiDriver_Accolade_AdLib::property(int prop, uint32 param) { + return 0; +} + +// Called right at the start, we get an INSTR.DAT entry +bool MidiDriver_Accolade_AdLib::setupInstruments(byte *driverData, uint16 driverDataSize, bool useMusicDrvFile) { + uint16 channelMappingOffset = 0; + uint16 channelMappingSize = 0; + uint16 instrumentMappingOffset = 0; + uint16 instrumentMappingSize = 0; + uint16 instrumentVolumeAdjustOffset = 0; + uint16 instrumentVolumeAdjustSize = 0; + uint16 keyNoteMappingOffset = 0; + uint16 keyNoteMappingSize = 0; + uint16 instrumentCount = 0; + uint16 instrumentDataOffset = 0; + uint16 instrumentDataSize = 0; + uint16 instrumentEntrySize = 0; + + if (!useMusicDrvFile) { + // INSTR.DAT: we expect at least 354 bytes + if (driverDataSize < 354) + return false; + + // Data is like this: + // 128 bytes instrument mapping + // 128 bytes instrument volume adjust (signed!) + // 16 bytes unknown + // 16 bytes channel mapping + // 64 bytes key note mapping (not used for MT32) + // 1 byte instrument count + // 1 byte bytes per instrument + // x bytes no instruments used for MT32 + + channelMappingOffset = 256 + 16; + channelMappingSize = 16; + instrumentMappingOffset = 0; + instrumentMappingSize = 128; + instrumentVolumeAdjustOffset = 128; + instrumentVolumeAdjustSize = 128; + keyNoteMappingOffset = 256 + 16 + 16; + keyNoteMappingSize = 64; + + byte instrDatInstrumentCount = driverData[256 + 16 + 16 + 64]; + byte instrDatBytesPerInstrument = driverData[256 + 16 + 16 + 64 + 1]; + + // We expect 9 bytes per instrument + if (instrDatBytesPerInstrument != 9) + return false; + // And we also expect at least one adlib instrument + if (!instrDatInstrumentCount) + return false; + + instrumentCount = instrDatInstrumentCount; + instrumentDataOffset = 256 + 16 + 16 + 64 + 2; + instrumentDataSize = instrDatBytesPerInstrument * instrDatInstrumentCount; + instrumentEntrySize = instrDatBytesPerInstrument; + + } else { + // MUSIC.DRV: we expect at least 468 bytes + if (driverDataSize < 468) + return false; + + // music.drv is basically a driver, but with a few fixed locations for certain data + + channelMappingOffset = 396; + channelMappingSize = 16; + instrumentMappingOffset = 140; + instrumentMappingSize = 128; + instrumentVolumeAdjustOffset = 140 + 128; + instrumentVolumeAdjustSize = 128; + keyNoteMappingOffset = 376 + 36; // adjust by 36, because we adjust keyNote before mapping (see noteOn) + keyNoteMappingSize = 64; + + // seems to have used 128 + 5 instruments + // 128 regular ones and an additional 5 for percussion + instrumentCount = 128 + AGOS_ADLIB_EXTRA_INSTRUMENT_COUNT; + instrumentDataOffset = 722; + instrumentEntrySize = 9; + instrumentDataSize = instrumentCount * instrumentEntrySize; + } + + // Channel mapping + if (channelMappingSize) { + // Get these 16 bytes for MIDI channel mapping + if (channelMappingSize != sizeof(_channelMapping)) + return false; + + memcpy(_channelMapping, driverData + channelMappingOffset, sizeof(_channelMapping)); + } else { + // Set up straight mapping + for (uint16 channelNr = 0; channelNr < sizeof(_channelMapping); channelNr++) { + _channelMapping[channelNr] = channelNr; + } + } + + if (instrumentMappingSize) { + // And these for instrument mapping + if (instrumentMappingSize > sizeof(_instrumentMapping)) + return false; + + memcpy(_instrumentMapping, driverData + instrumentMappingOffset, instrumentMappingSize); + } + // Set up straight mapping for the remaining data + for (uint16 instrumentNr = instrumentMappingSize; instrumentNr < sizeof(_instrumentMapping); instrumentNr++) { + _instrumentMapping[instrumentNr] = instrumentNr; + } + + if (instrumentVolumeAdjustSize) { + if (instrumentVolumeAdjustSize != sizeof(_instrumentVolumeAdjust)) + return false; + + memcpy(_instrumentVolumeAdjust, driverData + instrumentVolumeAdjustOffset, instrumentVolumeAdjustSize); + } + + // Get key note mapping, if available + if (keyNoteMappingSize) { + if (keyNoteMappingSize != sizeof(_percussionKeyNoteMapping)) + return false; + + memcpy(_percussionKeyNoteMapping, driverData + keyNoteMappingOffset, keyNoteMappingSize); + } + + // Check, if there are enough bytes left to hold all instrument data + if (driverDataSize < (instrumentDataOffset + instrumentDataSize)) + return false; + + // We release previous instrument data, just in case + if (_instrumentTable) + delete[] _instrumentTable; + + _instrumentTable = new InstrumentEntry[instrumentCount]; + _instrumentCount = instrumentCount; + + byte *instrDATReadPtr = driverData + instrumentDataOffset; + InstrumentEntry *instrumentWritePtr = _instrumentTable; + + for (uint16 instrumentNr = 0; instrumentNr < _instrumentCount; instrumentNr++) { + memcpy(instrumentWritePtr, instrDATReadPtr, sizeof(InstrumentEntry)); + instrDATReadPtr += instrumentEntrySize; + instrumentWritePtr++; + } + + // Enable MUSIC.DRV-Mode (slightly different behaviour) + if (useMusicDrvFile) + _musicDrvMode = true; + + if (_musicDrvMode) { + // Extra code for MUSIC.DRV + + // This was done during "programChange" in the original driver + instrumentWritePtr = _instrumentTable; + for (uint16 instrumentNr = 0; instrumentNr < _instrumentCount; instrumentNr++) { + instrumentWritePtr->reg80op1 |= 0x03; // set release rate + instrumentWritePtr->reg80op2 |= 0x03; + instrumentWritePtr++; + } + } + return true; +} + +MidiDriver *MidiDriver_Accolade_AdLib_create(Common::String driverFilename) { + byte *driverData = NULL; + uint16 driverDataSize = 0; + bool isMusicDrvFile = false; + + MidiDriver_Accolade_readDriver(driverFilename, MT_ADLIB, driverData, driverDataSize, isMusicDrvFile); + if (!driverData) + error("ACCOLADE-ADLIB: error during readDriver()"); + + MidiDriver_Accolade_AdLib *driver = new MidiDriver_Accolade_AdLib(); + if (driver) { + if (!driver->setupInstruments(driverData, driverDataSize, isMusicDrvFile)) { + delete driver; + driver = nullptr; + } + } + + delete[] driverData; + return driver; +} + +} // End of namespace AGOS diff --git a/engines/agos/drivers/accolade/driverfile.cpp b/engines/agos/drivers/accolade/driverfile.cpp new file mode 100644 index 0000000000..4ff2fd550f --- /dev/null +++ b/engines/agos/drivers/accolade/driverfile.cpp @@ -0,0 +1,166 @@ +/* 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 "agos/agos.h" +#include "audio/mididrv.h" +#include "common/error.h" +#include "common/file.h" + +namespace AGOS { + +// this reads and gets Accolade driver data +// we need it for channel mapping, instrument mapping and other things +// this driver data chunk gets passed to the actual music driver (MT32 / AdLib) +void MidiDriver_Accolade_readDriver(Common::String filename, MusicType requestedDriverType, byte *&driverData, uint16 &driverDataSize, bool &isMusicDrvFile) { + Common::File *driverStream = new Common::File(); + + isMusicDrvFile = false; + + if (!driverStream->open(filename)) { + error("%s: unable to open file", filename.c_str()); + } + + if (filename == "INSTR.DAT") { + // INSTR.DAT: used by Elvira 1 + uint32 streamSize = driverStream->size(); + uint32 streamLeft = streamSize; + uint16 skipChunks = 0; // 1 for MT32, 0 for AdLib + uint16 chunkSize = 0; + + switch (requestedDriverType) { + case MT_ADLIB: + skipChunks = 0; + break; + case MT_MT32: + skipChunks = 1; // Skip one entry for MT32 + break; + default: + assert(0); + break; + } + + do { + if (streamLeft < 2) + error("%s: unexpected EOF", filename.c_str()); + + chunkSize = driverStream->readUint16LE(); + streamLeft -= 2; + + if (streamLeft < chunkSize) + error("%s: unexpected EOF", filename.c_str()); + + if (skipChunks) { + // Skip the chunk + driverStream->skip(chunkSize); + streamLeft -= chunkSize; + + skipChunks--; + } + } while (skipChunks); + + // Seek over the ASCII string until there is a NUL terminator + byte curByte = 0; + + do { + if (chunkSize == 0) + error("%s: no actual instrument data found", filename.c_str()); + + curByte = driverStream->readByte(); + chunkSize--; + } while (curByte); + + driverDataSize = chunkSize; + + // Read the requested instrument data entry + driverData = new byte[driverDataSize]; + driverStream->read(driverData, driverDataSize); + + } else if (filename == "MUSIC.DRV") { + // MUSIC.DRV / used by Elvira 2 / Waxworks / Simon 1 demo + uint32 streamSize = driverStream->size(); + uint32 streamLeft = streamSize; + uint16 getChunk = 0; // 4 for MT32, 2 for AdLib + + switch (requestedDriverType) { + case MT_ADLIB: + getChunk = 2; + break; + case MT_MT32: + getChunk = 4; + break; + default: + assert(0); + break; + } + + if (streamLeft < 2) + error("%s: unexpected EOF", filename.c_str()); + + uint16 chunkCount = driverStream->readUint16LE(); + streamLeft -= 2; + + if (getChunk >= chunkCount) + error("%s: required chunk not available", filename.c_str()); + + uint16 headerOffset = 2 + (28 * getChunk); + streamLeft -= (28 * getChunk); + + if (streamLeft < 28) + error("%s: unexpected EOF", filename.c_str()); + + // Seek to required chunk + driverStream->seek(headerOffset); + driverStream->skip(20); // skip over name + streamLeft -= 20; + + uint16 musicDrvSignature = driverStream->readUint16LE(); + uint16 musicDrvType = driverStream->readUint16LE(); + uint16 chunkOffset = driverStream->readUint16LE(); + uint16 chunkSize = driverStream->readUint16LE(); + + // Security checks + if (musicDrvSignature != 0xFEDC) + error("%s: chunk signature mismatch", filename.c_str()); + if (musicDrvType != 1) + error("%s: not a music driver", filename.c_str()); + if (chunkOffset >= streamSize) + error("%s: driver chunk points outside of file", filename.c_str()); + + streamLeft = streamSize - chunkOffset; + if (streamLeft < chunkSize) + error("%s: driver chunk is larger than file", filename.c_str()); + + driverDataSize = chunkSize; + + // Read the requested instrument data entry + driverData = new byte[driverDataSize]; + + driverStream->seek(chunkOffset); + driverStream->read(driverData, driverDataSize); + isMusicDrvFile = true; + } + + driverStream->close(); + delete driverStream; +} + +} // End of namespace AGOS diff --git a/engines/agos/drivers/accolade/mididriver.h b/engines/agos/drivers/accolade/mididriver.h new file mode 100644 index 0000000000..253fb6b736 --- /dev/null +++ b/engines/agos/drivers/accolade/mididriver.h @@ -0,0 +1,44 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef AGOS_DRIVERS_ACCOLADE_MIDIDRIVER_H +#define AGOS_DRIVERS_ACCOLADE_MIDIDRIVER_H + +#include "agos/agos.h" +#include "audio/mididrv.h" +#include "common/error.h" + +namespace AGOS { + +#define AGOS_MIDI_CHANNEL_COUNT 16 +#define AGOS_MIDI_INSTRUMENT_COUNT 128 + +#define AGOS_MIDI_KEYNOTE_COUNT 64 + +extern void MidiDriver_Accolade_readDriver(Common::String filename, MusicType requestedDriverType, byte *&driverData, uint16 &driverDataSize, bool &isMusicDrvFile); + +extern MidiDriver *MidiDriver_Accolade_AdLib_create(Common::String driverFilename); +extern MidiDriver *MidiDriver_Accolade_MT32_create(Common::String driverFilename); + +} // End of namespace AGOS + +#endif // AGOS_DRIVERS_ACCOLADE_MIDIDRIVER_H diff --git a/engines/agos/drivers/accolade/mt32.cpp b/engines/agos/drivers/accolade/mt32.cpp new file mode 100644 index 0000000000..319e0ebf56 --- /dev/null +++ b/engines/agos/drivers/accolade/mt32.cpp @@ -0,0 +1,278 @@ +/* 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 "agos/agos.h" +#include "agos/drivers/accolade/mididriver.h" + +#include "audio/mididrv.h" + +#include "common/config-manager.h" +#include "common/file.h" +#include "common/mutex.h" +#include "common/system.h" +#include "common/textconsole.h" + +namespace AGOS { + +class MidiDriver_Accolade_MT32 : public MidiDriver { +public: + MidiDriver_Accolade_MT32(); + virtual ~MidiDriver_Accolade_MT32(); + + // MidiDriver + int open(); + void close(); + bool isOpen() const { return _isOpen; } + + void send(uint32 b); + + MidiChannel *allocateChannel() { + if (_driver) + return _driver->allocateChannel(); + return NULL; + } + MidiChannel *getPercussionChannel() { + if (_driver) + return _driver->getPercussionChannel(); + return NULL; + } + + void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) { + if (_driver) + _driver->setTimerCallback(timer_param, timer_proc); + } + + uint32 getBaseTempo() { + if (_driver) { + return _driver->getBaseTempo(); + } + return 1000000 / _baseFreq; + } + +protected: + Common::Mutex _mutex; + MidiDriver *_driver; + bool _nativeMT32; // native MT32, may also be our MUNT, or MUNT over MIDI + + bool _isOpen; + int _baseFreq; + +private: + // simple mapping between MIDI channel and MT32 channel + byte _channelMapping[AGOS_MIDI_CHANNEL_COUNT]; + // simple mapping between MIDI instruments and MT32 instruments + byte _instrumentMapping[AGOS_MIDI_INSTRUMENT_COUNT]; + +public: + bool setupInstruments(byte *instrumentData, uint16 instrumentDataSize, bool useMusicDrvFile); +}; + +MidiDriver_Accolade_MT32::MidiDriver_Accolade_MT32() { + _driver = NULL; + _isOpen = false; + _nativeMT32 = false; + _baseFreq = 250; + + memset(_channelMapping, 0, sizeof(_channelMapping)); + memset(_instrumentMapping, 0, sizeof(_instrumentMapping)); +} + +MidiDriver_Accolade_MT32::~MidiDriver_Accolade_MT32() { + Common::StackLock lock(_mutex); + if (_driver) { + _driver->setTimerCallback(0, 0); + _driver->close(); + delete _driver; + } + _driver = NULL; +} + +int MidiDriver_Accolade_MT32::open() { + assert(!_driver); + +// debugC(kDebugLevelMT32Driver, "MT32: starting driver"); + + // Setup midi driver + MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_PREFER_MT32); + MusicType musicType = MidiDriver::getMusicType(dev); + + // check, if we got a real MT32 (or MUNT, or MUNT over MIDI) + switch (musicType) { + case MT_MT32: + _nativeMT32 = true; + break; + case MT_GM: + if (ConfMan.getBool("native_mt32")) { + _nativeMT32 = true; + } + break; + default: + break; + } + + _driver = MidiDriver::createMidi(dev); + if (!_driver) + return 255; + + int ret = _driver->open(); + if (ret) + return ret; + + if (_nativeMT32) + _driver->sendMT32Reset(); + else + _driver->sendGMReset(); + + return 0; +} + +void MidiDriver_Accolade_MT32::close() { + if (_driver) { + _driver->close(); + } +} + +// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php +void MidiDriver_Accolade_MT32::send(uint32 b) { + byte command = b & 0xf0; + byte channel = b & 0xf; + + if (command == 0xF0) { + if (_driver) { + _driver->send(b); + } + return; + } + + byte mappedChannel = _channelMapping[channel]; + + if (mappedChannel < AGOS_MIDI_CHANNEL_COUNT) { + // channel mapped to an actual MIDI channel, so use that one + b = (b & 0xFFFFFFF0) | mappedChannel; + if (command == 0xC0) { + // Program change + // Figure out the requested instrument + byte midiInstrument = (b >> 8) & 0xFF; + byte mappedInstrument = _instrumentMapping[midiInstrument]; + + // If there is no actual MT32 (or MUNT), we make a second mapping to General MIDI instruments + if (!_nativeMT32) { + mappedInstrument = (MidiDriver::_mt32ToGm[mappedInstrument]); + } + // And replace it + b = (b & 0xFFFF00FF) | (mappedInstrument << 8); + } + + if (_driver) { + _driver->send(b); + } + } +} + +// Called right at the start, we get an INSTR.DAT entry +bool MidiDriver_Accolade_MT32::setupInstruments(byte *driverData, uint16 driverDataSize, bool useMusicDrvFile) { + uint16 channelMappingOffset = 0; + uint16 channelMappingSize = 0; + uint16 instrumentMappingOffset = 0; + uint16 instrumentMappingSize = 0; + + if (!useMusicDrvFile) { + // INSTR.DAT: we expect at least 354 bytes + if (driverDataSize < 354) + return false; + + // Data is like this: + // 128 bytes instrument mapping + // 128 bytes instrument volume adjust (signed!) (not used for MT32) + // 16 bytes unknown + // 16 bytes channel mapping + // 64 bytes key note mapping (not really used for MT32) + // 1 byte instrument count + // 1 byte bytes per instrument + // x bytes no instruments used for MT32 + + channelMappingOffset = 256 + 16; + channelMappingSize = 16; + instrumentMappingOffset = 0; + instrumentMappingSize = 128; + + } else { + // MUSIC.DRV: we expect at least 468 bytes + if (driverDataSize < 468) + return false; + + channelMappingOffset = 396; + channelMappingSize = 16; + instrumentMappingOffset = 140; + instrumentMappingSize = 128; + } + + // Channel mapping + if (channelMappingSize) { + // Get these 16 bytes for MIDI channel mapping + if (channelMappingSize != sizeof(_channelMapping)) + return false; + + memcpy(_channelMapping, driverData + channelMappingOffset, sizeof(_channelMapping)); + } else { + // Set up straight mapping + for (uint16 channelNr = 0; channelNr < sizeof(_channelMapping); channelNr++) { + _channelMapping[channelNr] = channelNr; + } + } + + if (instrumentMappingSize) { + // And these for instrument mapping + if (instrumentMappingSize > sizeof(_instrumentMapping)) + return false; + + memcpy(_instrumentMapping, driverData + instrumentMappingOffset, instrumentMappingSize); + } + // Set up straight mapping for the remaining data + for (uint16 instrumentNr = instrumentMappingSize; instrumentNr < sizeof(_instrumentMapping); instrumentNr++) { + _instrumentMapping[instrumentNr] = instrumentNr; + } + return true; +} + +MidiDriver *MidiDriver_Accolade_MT32_create(Common::String driverFilename) { + byte *driverData = NULL; + uint16 driverDataSize = 0; + bool isMusicDrvFile = false; + + MidiDriver_Accolade_readDriver(driverFilename, MT_MT32, driverData, driverDataSize, isMusicDrvFile); + if (!driverData) + error("ACCOLADE-ADLIB: error during readDriver()"); + + MidiDriver_Accolade_MT32 *driver = new MidiDriver_Accolade_MT32(); + if (driver) { + if (!driver->setupInstruments(driverData, driverDataSize, isMusicDrvFile)) { + delete driver; + driver = nullptr; + } + } + + delete[] driverData; + return driver; +} + +} // End of namespace AGOS diff --git a/engines/agos/drivers/simon1/adlib.cpp b/engines/agos/drivers/simon1/adlib.cpp new file mode 100644 index 0000000000..7f1370e8bd --- /dev/null +++ b/engines/agos/drivers/simon1/adlib.cpp @@ -0,0 +1,516 @@ +/* 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 "agos/drivers/simon1/adlib.h" + +#include "common/textconsole.h" +#include "common/util.h" +#include "common/file.h" + +namespace AGOS { + +enum { + kChannelUnused = 0xFF, + kChannelOrphanedFlag = 0x80, + + kOPLVoicesCount = 9 +}; + +MidiDriver_Simon1_AdLib::Voice::Voice() + : channel(kChannelUnused), note(0), instrTotalLevel(0), instrScalingLevel(0), frequency(0) { +} + +MidiDriver_Simon1_AdLib::MidiDriver_Simon1_AdLib(const byte *instrumentData) + : _isOpen(false), _opl(nullptr), _timerProc(nullptr), _timerParam(nullptr), + _melodyVoices(0), _amvdrBits(0), _rhythmEnabled(false), _voices(), _midiPrograms(), + _instruments(instrumentData) { +} + +MidiDriver_Simon1_AdLib::~MidiDriver_Simon1_AdLib() { + close(); + delete[] _instruments; +} + +int MidiDriver_Simon1_AdLib::open() { + if (_isOpen) { + return MERR_ALREADY_OPEN; + } + + _opl = OPL::Config::create(); + if (!_opl) { + return MERR_DEVICE_NOT_AVAILABLE; + } + + if (!_opl->init()) { + delete _opl; + _opl = nullptr; + + return MERR_CANNOT_CONNECT; + } + + _opl->start(new Common::Functor0Mem<void, MidiDriver_Simon1_AdLib>(this, &MidiDriver_Simon1_AdLib::onTimer)); + + _opl->writeReg(0x01, 0x20); + _opl->writeReg(0x08, 0x40); + _opl->writeReg(0xBD, 0xC0); + reset(); + + _isOpen = true; + return 0; +} + +bool MidiDriver_Simon1_AdLib::isOpen() const { + return _isOpen; +} + +void MidiDriver_Simon1_AdLib::close() { + setTimerCallback(nullptr, nullptr); + + if (_isOpen) { + _opl->stop(); + delete _opl; + _opl = nullptr; + + _isOpen = false; + } +} + +void MidiDriver_Simon1_AdLib::send(uint32 b) { + int channel = b & 0x0F; + int command = b & 0xF0; + int param1 = (b >> 8) & 0xFF; + int param2 = (b >> 16) & 0xFF; + + // The percussion channel is handled specially. The AdLib output uses + // channels 11 to 15 for percussions. For this, the original converted + // note on on the percussion channel to note on channels 11 to 15 before + // giving it to the AdLib output. We do this in here for simplicity. + if (command == 0x90 && channel == 9) { + param1 -= 36; + if (param1 < 0 || param1 >= ARRAYSIZE(_rhythmMap)) { + return; + } + + channel = _rhythmMap[param1].channel; + MidiDriver::send(0xC0 | channel, _rhythmMap[param1].program, 0); + + param1 = _rhythmMap[param1].note; + MidiDriver::send(0x80 | channel, param1, param2); + + param2 >>= 1; + } + + switch (command) { + case 0x80: // note OFF + noteOff(channel, param1); + break; + + case 0x90: // note ON + if (param2 == 0) { + noteOff(channel, param1); + } else { + noteOn(channel, param1, param2); + } + break; + + case 0xB0: // control change + controlChange(channel, param1, param2); + break; + + case 0xC0: // program change + programChange(channel, param1); + break; + + default: + break; + } +} + +void MidiDriver_Simon1_AdLib::setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) { + _timerParam = timer_param; + _timerProc = timer_proc; +} + +uint32 MidiDriver_Simon1_AdLib::getBaseTempo() { + return 1000000 / OPL::OPL::kDefaultCallbackFrequency; +} + +void MidiDriver_Simon1_AdLib::onTimer() { + if (_timerProc) { + (*_timerProc)(_timerParam); + } +} + +void MidiDriver_Simon1_AdLib::reset() { + resetOPLVoices(); + resetRhythm(); + for (int i = 0; i < kNumberOfVoices; ++i) { + _voices[i].channel = kChannelUnused; + } + resetVoices(); +} + +void MidiDriver_Simon1_AdLib::resetOPLVoices() { + _amvdrBits &= 0xE0; + _opl->writeReg(0xBD, _amvdrBits); + for (int i = 8; i >= 0; --i) { + _opl->writeReg(0xB0 + i, 0); + } +} + +void MidiDriver_Simon1_AdLib::resetRhythm() { + _melodyVoices = 9; + _amvdrBits = 0xC0; + _opl->writeReg(0xBD, _amvdrBits); +} + +void MidiDriver_Simon1_AdLib::resetVoices() { + memset(_midiPrograms, 0, sizeof(_midiPrograms)); + for (int i = 0; i < kNumberOfVoices; ++i) { + _voices[i].channel = kChannelUnused; + } + + for (int i = 0; i < kOPLVoicesCount; ++i) { + resetRhythm(); + _opl->writeReg(0x08, 0x00); + + int oplRegister = _operatorMap[i]; + for (int j = 0; j < 4; ++j) { + oplRegister += 0x20; + + _opl->writeReg(oplRegister + 0, _operatorDefaults[2 * j + 0]); + _opl->writeReg(oplRegister + 3, _operatorDefaults[2 * j + 1]); + } + + _opl->writeReg(oplRegister + 0x60, 0x00); + _opl->writeReg(oplRegister + 0x63, 0x00); + + // This seems to be serious bug but the original does it the same way. + _opl->writeReg(_operatorMap[i] + i, 0x08); + } +} + +int MidiDriver_Simon1_AdLib::allocateVoice(uint channel) { + for (int i = 0; i < _melodyVoices; ++i) { + if (_voices[i].channel == (channel | kChannelOrphanedFlag)) { + return i; + } + } + + for (int i = 0; i < _melodyVoices; ++i) { + if (_voices[i].channel == kChannelUnused) { + return i; + } + } + + for (int i = 0; i < _melodyVoices; ++i) { + if (_voices[i].channel > 0x7F) { + return i; + } + } + + // The original had some logic for a priority based reuse of channels. + // However, the priority value is always 0, which causes the first channel + // to be picked all the time. + const int voice = 0; + _opl->writeReg(0xA0 + voice, (_voices[voice].frequency ) & 0xFF); + _opl->writeReg(0xB0 + voice, (_voices[voice].frequency >> 8) & 0xFF); + return voice; +} + +void MidiDriver_Simon1_AdLib::noteOff(uint channel, uint note) { + if (_melodyVoices <= 6 && channel >= 11) { + _amvdrBits &= ~(_rhythmInstrumentMask[channel - 11]); + _opl->writeReg(0xBD, _amvdrBits); + } else { + for (int i = 0; i < _melodyVoices; ++i) { + if (_voices[i].note == note && _voices[i].channel == channel) { + _voices[i].channel |= kChannelOrphanedFlag; + _opl->writeReg(0xA0 + i, (_voices[i].frequency ) & 0xFF); + _opl->writeReg(0xB0 + i, (_voices[i].frequency >> 8) & 0xFF); + return; + } + } + } +} + +void MidiDriver_Simon1_AdLib::noteOn(uint channel, uint note, uint velocity) { + if (_rhythmEnabled && channel >= 11) { + noteOnRhythm(channel, note, velocity); + return; + } + + const int voiceNum = allocateVoice(channel); + Voice &voice = _voices[voiceNum]; + + if ((voice.channel & 0x7F) != channel) { + setupInstrument(voiceNum, _midiPrograms[channel]); + } + voice.channel = channel; + + _opl->writeReg(0x43 + _operatorMap[voiceNum], (0x3F - (((velocity | 0x80) * voice.instrTotalLevel) >> 8)) | voice.instrScalingLevel); + + voice.note = note; + if (note >= 0x80) { + note = 0; + } + + const int frequencyAndOctave = _frequencyIndexAndOctaveTable[note]; + const uint frequency = _frequencyTable[frequencyAndOctave & 0x0F]; + + uint highByte = ((frequency & 0xFF00) >> 8) | ((frequencyAndOctave & 0x70) >> 2); + uint lowByte = frequency & 0x00FF; + voice.frequency = (highByte << 8) | lowByte; + + _opl->writeReg(0xA0 + voiceNum, lowByte); + _opl->writeReg(0xB0 + voiceNum, highByte | 0x20); +} + +void MidiDriver_Simon1_AdLib::noteOnRhythm(uint channel, uint note, uint velocity) { + const uint voiceNum = channel - 5; + Voice &voice = _voices[voiceNum]; + + _amvdrBits |= _rhythmInstrumentMask[voiceNum - 6]; + + const uint level = (0x3F - (((velocity | 0x80) * voice.instrTotalLevel) >> 8)) | voice.instrScalingLevel; + if (voiceNum == 6) { + _opl->writeReg(0x43 + _rhythmOperatorMap[voiceNum - 6], level); + } else { + _opl->writeReg(0x40 + _rhythmOperatorMap[voiceNum - 6], level); + } + + voice.note = note; + if (note >= 0x80) { + note = 0; + } + + const int frequencyAndOctave = _frequencyIndexAndOctaveTable[note]; + const uint frequency = _frequencyTable[frequencyAndOctave & 0x0F]; + + uint highByte = ((frequency & 0xFF00) >> 8) | ((frequencyAndOctave & 0x70) >> 2); + uint lowByte = frequency & 0x00FF; + voice.frequency = (highByte << 8) | lowByte; + + const uint oplOperator = _rhythmVoiceMap[voiceNum - 6]; + _opl->writeReg(0xA0 + oplOperator, lowByte); + _opl->writeReg(0xB0 + oplOperator, highByte); + + _opl->writeReg(0xBD, _amvdrBits); +} + +void MidiDriver_Simon1_AdLib::controlChange(uint channel, uint controller, uint value) { + // Enable/Disable Rhythm Section + if (controller == 0x67) { + resetVoices(); + _rhythmEnabled = (value != 0); + + if (_rhythmEnabled) { + _melodyVoices = 6; + _amvdrBits = 0xE0; + } else { + _melodyVoices = 9; + _amvdrBits = 0xC0; + } + + _voices[6].channel = kChannelUnused; + _voices[7].channel = kChannelUnused; + _voices[8].channel = kChannelUnused; + + _opl->writeReg(0xBD, _amvdrBits); + } +} + +void MidiDriver_Simon1_AdLib::programChange(uint channel, uint program) { + _midiPrograms[channel] = program; + + if (_rhythmEnabled && channel >= 11) { + setupInstrument(channel - 5, program); + } else { + // Fully unallocate all previously allocated but now unused voices for + // this MIDI channel. + for (uint i = 0; i < kOPLVoicesCount; ++i) { + if (_voices[i].channel == (channel | kChannelOrphanedFlag)) { + _voices[i].channel = kChannelUnused; + } + } + + // Set the program for all voices allocted for this MIDI channel. + for (uint i = 0; i < kOPLVoicesCount; ++i) { + if (_voices[i].channel == channel) { + setupInstrument(i, program); + } + } + } +} + +void MidiDriver_Simon1_AdLib::setupInstrument(uint voice, uint instrument) { + const byte *instrumentData = _instruments + instrument * 16; + + int scaling = instrumentData[3]; + if (_rhythmEnabled && voice >= 7) { + scaling = instrumentData[2]; + } + + const int scalingLevel = scaling & 0xC0; + const int totalLevel = scaling & 0x3F; + + _voices[voice].instrScalingLevel = scalingLevel; + _voices[voice].instrTotalLevel = (-(totalLevel - 0x3F)) & 0xFF; + + if (!_rhythmEnabled || voice <= 6) { + int oplRegister = _operatorMap[voice]; + for (int j = 0; j < 4; ++j) { + oplRegister += 0x20; + _opl->writeReg(oplRegister + 0, *instrumentData++); + _opl->writeReg(oplRegister + 3, *instrumentData++); + } + oplRegister += 0x60; + _opl->writeReg(oplRegister + 0, *instrumentData++); + _opl->writeReg(oplRegister + 3, *instrumentData++); + + _opl->writeReg(0xC0 + voice, *instrumentData++); + } else { + voice -= 7; + + int oplRegister = _rhythmOperatorMap[voice + 1]; + for (int j = 0; j < 4; ++j) { + oplRegister += 0x20; + _opl->writeReg(oplRegister + 0, *instrumentData++); + ++instrumentData; + } + oplRegister += 0x60; + _opl->writeReg(oplRegister + 0, *instrumentData++); + ++instrumentData; + + _opl->writeReg(0xC0 + _rhythmVoiceMap[voice + 1], *instrumentData++); + } +} + +const int MidiDriver_Simon1_AdLib::_operatorMap[9] = { + 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, + 0x12 +}; + +const int MidiDriver_Simon1_AdLib::_operatorDefaults[8] = { + 0x01, 0x11, 0x4F, 0x00, 0xF1, 0xF2, 0x53, 0x74 +}; + +const int MidiDriver_Simon1_AdLib::_rhythmOperatorMap[5] = { + 0x10, 0x14, 0x12, 0x15, 0x11 +}; + +const uint MidiDriver_Simon1_AdLib::_rhythmInstrumentMask[5] = { + 0x10, 0x08, 0x04, 0x02, 0x01 +}; + +const int MidiDriver_Simon1_AdLib::_rhythmVoiceMap[5] = { + 6, 7, 8, 8, 7 +}; + +const int MidiDriver_Simon1_AdLib::_frequencyIndexAndOctaveTable[128] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0A, 0x0B, 0x00, 0x01, 0x02, 0x03, + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1A, 0x1B, 0x20, 0x21, 0x22, 0x23, + 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3A, 0x3B, 0x40, 0x41, 0x42, 0x43, + 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5A, 0x5B, 0x60, 0x61, 0x62, 0x63, + 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7A, 0x7B, 0x70, 0x71, 0x72, 0x73, + 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, + 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B, 0x7B +}; + +const int MidiDriver_Simon1_AdLib::_frequencyTable[16] = { + 0x0157, 0x016B, 0x0181, 0x0198, 0x01B0, 0x01CA, 0x01E5, 0x0202, + 0x0220, 0x0241, 0x0263, 0x0287, 0x2100, 0xD121, 0xA307, 0x46A4 +}; + +const MidiDriver_Simon1_AdLib::RhythmMap MidiDriver_Simon1_AdLib::_rhythmMap[39] = { + { 11, 123, 40 }, + { 12, 127, 50 }, + { 12, 124, 1 }, + { 12, 124, 90 }, + { 13, 125, 50 }, + { 13, 125, 25 }, + { 15, 127, 80 }, + { 13, 125, 25 }, + { 15, 127, 40 }, + { 13, 125, 35 }, + { 15, 127, 90 }, + { 13, 125, 35 }, + { 13, 125, 45 }, + { 14, 126, 90 }, + { 13, 125, 45 }, + { 15, 127, 90 }, + { 0, 0, 0 }, + { 15, 127, 60 }, + { 0, 0, 0 }, + { 13, 125, 60 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { 13, 125, 45 }, + { 13, 125, 40 }, + { 13, 125, 35 }, + { 13, 125, 30 }, + { 13, 125, 25 }, + { 13, 125, 80 }, + { 13, 125, 40 }, + { 13, 125, 80 }, + { 13, 125, 40 }, + { 14, 126, 40 }, + { 15, 127, 60 }, + { 0, 0, 0 }, + { 0, 0, 0 }, + { 14, 126, 80 }, + { 0, 0, 0 }, + { 13, 125, 100 } +}; + +MidiDriver *createMidiDriverSimon1AdLib(const char *instrumentFilename) { + // Load instrument data. + Common::File ibk; + + if (!ibk.open(instrumentFilename)) { + return nullptr; + } + + if (ibk.readUint32BE() != 0x49424b1a) { + return nullptr; + } + + byte *instrumentData = new byte[128 * 16]; + if (ibk.read(instrumentData, 128 * 16) != 128 * 16) { + delete[] instrumentData; + return nullptr; + } + + return new MidiDriver_Simon1_AdLib(instrumentData); +} + +} // End of namespace AGOS diff --git a/engines/agos/drivers/simon1/adlib.h b/engines/agos/drivers/simon1/adlib.h new file mode 100644 index 0000000000..6057bf1b16 --- /dev/null +++ b/engines/agos/drivers/simon1/adlib.h @@ -0,0 +1,118 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef AGOS_SIMON1_ADLIB_H +#define AGOS_SIMON1_ADLIB_H + +#include "audio/mididrv.h" +#include "audio/fmopl.h" + +namespace AGOS { + +class MidiDriver_Simon1_AdLib : public MidiDriver { +public: + MidiDriver_Simon1_AdLib(const byte *instrumentData); + virtual ~MidiDriver_Simon1_AdLib(); + + // MidiDriver API + virtual int open(); + virtual bool isOpen() const; + virtual void close(); + + virtual void send(uint32 b); + + virtual void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc); + virtual uint32 getBaseTempo(); + + virtual MidiChannel *allocateChannel() { return 0; } + virtual MidiChannel *getPercussionChannel() { return 0; } +private: + bool _isOpen; + + OPL::OPL *_opl; + + Common::TimerManager::TimerProc _timerProc; + void *_timerParam; + void onTimer(); + + void reset(); + void resetOPLVoices(); + + void resetRhythm(); + int _melodyVoices; + uint8 _amvdrBits; + bool _rhythmEnabled; + + enum { + kNumberOfVoices = 11, + kNumberOfMidiChannels = 16 + }; + + struct Voice { + Voice(); + + uint channel; + uint note; + uint instrTotalLevel; + uint instrScalingLevel; + uint frequency; + }; + + void resetVoices(); + int allocateVoice(uint channel); + + Voice _voices[kNumberOfVoices]; + uint _midiPrograms[kNumberOfMidiChannels]; + + void noteOff(uint channel, uint note); + void noteOn(uint channel, uint note, uint velocity); + void noteOnRhythm(uint channel, uint note, uint velocity); + void controlChange(uint channel, uint controller, uint value); + void programChange(uint channel, uint program); + + void setupInstrument(uint voice, uint instrument); + const byte *_instruments; + + static const int _operatorMap[9]; + static const int _operatorDefaults[8]; + + static const int _rhythmOperatorMap[5]; + static const uint _rhythmInstrumentMask[5]; + static const int _rhythmVoiceMap[5]; + + static const int _frequencyIndexAndOctaveTable[128]; + static const int _frequencyTable[16]; + + struct RhythmMap { + int channel; + int program; + int note; + }; + + static const RhythmMap _rhythmMap[39]; +}; + +MidiDriver *createMidiDriverSimon1AdLib(const char *instrumentFilename); + +} // End of namespace AGOS + +#endif diff --git a/engines/agos/gfx.cpp b/engines/agos/gfx.cpp index 5a1f9f1917..867842276a 100644 --- a/engines/agos/gfx.cpp +++ b/engines/agos/gfx.cpp @@ -1303,6 +1303,13 @@ void AGOSEngine::setWindowImageEx(uint16 mode, uint16 vgaSpriteId) { } else { setWindowImage(mode, vgaSpriteId); } + + // Amiga versions wait for verb area to be displayed. + if (getGameType() == GType_SIMON1 && getPlatform() == Common::kPlatformAmiga && vgaSpriteId == 1) { + _copyScnFlag = 5; + while (_copyScnFlag && !shouldQuit()) + delay(1); + } } void AGOSEngine::setWindowImage(uint16 mode, uint16 vgaSpriteId, bool specialCase) { diff --git a/engines/agos/input.cpp b/engines/agos/input.cpp index 687a8ef1cf..686b1c35b2 100644 --- a/engines/agos/input.cpp +++ b/engines/agos/input.cpp @@ -707,6 +707,7 @@ bool AGOSEngine::processSpecialKeys() { if (_midiEnabled) { _midi->pause(_musicPaused); } + _mixer->pauseHandle(_modHandle, _musicPaused); syncSoundSettings(); break; case 's': diff --git a/engines/agos/midi.cpp b/engines/agos/midi.cpp index e5875a8353..d11ff201ea 100644 --- a/engines/agos/midi.cpp +++ b/engines/agos/midi.cpp @@ -23,10 +23,21 @@ #include "common/config-manager.h" #include "common/file.h" #include "common/textconsole.h" +#include "common/memstream.h" #include "agos/agos.h" #include "agos/midi.h" +#include "agos/drivers/accolade/mididriver.h" +#include "agos/drivers/simon1/adlib.h" +// Miles Audio for Simon 2 +#include "audio/miles.h" + +// PKWARE data compression library decompressor required for Simon 2 +#include "common/dcl.h" + +#include "gui/message.h" + namespace AGOS { @@ -42,8 +53,7 @@ MidiPlayer::MidiPlayer() { _driver = 0; _map_mt32_to_gm = false; - _adlibPatches = NULL; - + _adLibMusic = false; _enable_sfx = true; _current = 0; @@ -54,9 +64,11 @@ MidiPlayer::MidiPlayer() { _paused = false; _currentTrack = 255; - _loopTrackDefault = false; + _loopTrack = 0; _queuedTrack = 255; _loopQueuedTrack = 0; + + _musicMode = kMusicModeDisabled; } MidiPlayer::~MidiPlayer() { @@ -70,15 +82,183 @@ MidiPlayer::~MidiPlayer() { } _driver = NULL; clearConstructs(); - unloadAdlibPatches(); } -int MidiPlayer::open(int gameType) { +int MidiPlayer::open(int gameType, bool isDemo) { // Don't call open() twice! assert(!_driver); - // Setup midi driver - MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_ADLIB | MDT_MIDI | (gameType == GType_SIMON1 ? MDT_PREFER_MT32 : MDT_PREFER_GM)); + Common::String accoladeDriverFilename; + MusicType musicType = MT_INVALID; + + switch (gameType) { + case GType_ELVIRA1: + _musicMode = kMusicModeAccolade; + accoladeDriverFilename = "INSTR.DAT"; + break; + case GType_ELVIRA2: + case GType_WW: + // Attention: Elvira 2 shipped with INSTR.DAT and MUSIC.DRV + // MUSIC.DRV is the correct one. INSTR.DAT seems to be a left-over + _musicMode = kMusicModeAccolade; + accoladeDriverFilename = "MUSIC.DRV"; + break; + case GType_SIMON1: + if (isDemo) { + _musicMode = kMusicModeAccolade; + accoladeDriverFilename = "MUSIC.DRV"; + } else if (Common::File::exists("MT_FM.IBK")) { + _musicMode = kMusicModeSimon1; + } + break; + case GType_SIMON2: + //_musicMode = kMusicModeMilesAudio; + // currently disabled, because there are a few issues + // MT32 seems to work fine now, AdLib seems to use bad instruments and is also outputting music on + // the right speaker only. The original driver did initialize the panning to 0 and the Simon2 XMIDI + // tracks don't set panning at all. We can reset panning to be centered, which would solve this + // issue, but we still don't know who's setting it in the original interpreter. + break; + default: + break; + } + + MidiDriver::DeviceHandle dev; + int ret = 0; + + if (_musicMode != kMusicModeDisabled) { + dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MT32); + musicType = MidiDriver::getMusicType(dev); + + switch (musicType) { + case MT_ADLIB: + case MT_MT32: + break; + case MT_GM: + if (!ConfMan.getBool("native_mt32")) { + // Not a real MT32 / no MUNT + ::GUI::MessageDialog dialog(("You appear to be using a General MIDI device,\n" + "but your game only supports Roland MT32 MIDI.\n" + "We try to map the Roland MT32 instruments to\n" + "General MIDI ones. It is still possible that\n" + "some tracks sound incorrect.")); + dialog.runModal(); + } + // Switch to MT32 driver in any case + musicType = MT_MT32; + break; + default: + _musicMode = kMusicModeDisabled; + break; + } + } + + switch (_musicMode) { + case kMusicModeAccolade: { + // Setup midi driver + switch (musicType) { + case MT_ADLIB: + _driver = MidiDriver_Accolade_AdLib_create(accoladeDriverFilename); + break; + case MT_MT32: + _driver = MidiDriver_Accolade_MT32_create(accoladeDriverFilename); + break; + default: + assert(0); + break; + } + if (!_driver) + return 255; + + ret = _driver->open(); + if (ret == 0) { + // Reset is done inside our MIDI driver + _driver->setTimerCallback(this, &onTimer); + } + + //setTimerRate(_driver->getBaseTempo()); + return 0; + } + + case kMusicModeMilesAudio: { + switch (musicType) { + case MT_ADLIB: { + Common::File instrumentDataFile; + if (instrumentDataFile.exists("MIDPAK.AD")) { + // if there is a file called MIDPAK.AD, use it directly + warning("SIMON 2: using MIDPAK.AD"); + _driver = Audio::MidiDriver_Miles_AdLib_create("MIDPAK.AD", "MIDPAK.AD"); + } else { + // if there is no file called MIDPAK.AD, try to extract it from the file SETUP.SHR + // if we didn't do this, the user would be forced to "install" the game instead of simply + // copying all files from CD-ROM. + Common::SeekableReadStream *midpakAdLibStream; + midpakAdLibStream = simon2SetupExtractFile("MIDPAK.AD"); + if (!midpakAdLibStream) + error("MidiPlayer: could not extract MIDPAK.AD from SETUP.SHR"); + + // Pass this extracted data to the driver + warning("SIMON 2: using MIDPAK.AD extracted from SETUP.SHR"); + _driver = Audio::MidiDriver_Miles_AdLib_create("", "", midpakAdLibStream); + delete midpakAdLibStream; + } + // TODO: not sure what's going wrong with AdLib + // it doesn't seem to matter if we use the regular XMIDI tracks or the 2nd set meant for MT32 + break; + } + case MT_MT32: + _driver = Audio::MidiDriver_Miles_MT32_create(""); + _nativeMT32 = true; // use 2nd set of XMIDI tracks + break; + case MT_GM: + if (ConfMan.getBool("native_mt32")) { + _driver = Audio::MidiDriver_Miles_MT32_create(""); + _nativeMT32 = true; // use 2nd set of XMIDI tracks + } + break; + + default: + break; + } + if (!_driver) + return 255; + + ret = _driver->open(); + if (ret == 0) { + // Reset is done inside our MIDI driver + _driver->setTimerCallback(this, &onTimer); + } + return 0; + } + + case kMusicModeSimon1: { + // This only handles the original AdLib driver of Simon1. + if (musicType == MT_ADLIB) { + _adLibMusic = true; + _map_mt32_to_gm = false; + _nativeMT32 = false; + + _driver = createMidiDriverSimon1AdLib("MT_FM.IBK"); + if (_driver && _driver->open() == 0) { + _driver->setTimerCallback(this, &onTimer); + // Like the original, we enable the rhythm support by default. + _driver->send(0xB0, 0x67, 0x01); + return 0; + } + + delete _driver; + _driver = nullptr; + } + + _musicMode = kMusicModeDisabled; + } + + default: + break; + } + + dev = MidiDriver::detectDevice(MDT_ADLIB | MDT_MIDI | (gameType == GType_SIMON1 ? MDT_PREFER_MT32 : MDT_PREFER_GM)); + _adLibMusic = (MidiDriver::getMusicType(dev) == MT_ADLIB); _nativeMT32 = ((MidiDriver::getMusicType(dev) == MT_MT32) || ConfMan.getBool("native_mt32")); _driver = MidiDriver::createMidi(dev); @@ -88,15 +268,9 @@ int MidiPlayer::open(int gameType) { if (_nativeMT32) _driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); - /* Disabled due to not sounding right, and low volume level - if (gameType == GType_SIMON1 && MidiDriver::getMusicType(dev) == MT_ADLIB) { - loadAdlibPatches(); - } - */ - _map_mt32_to_gm = (gameType != GType_SIMON2 && !_nativeMT32); - int ret = _driver->open(); + ret = _driver->open(); if (ret) return ret; _driver->setTimerCallback(this, &onTimer); @@ -113,6 +287,33 @@ void MidiPlayer::send(uint32 b) { if (!_current) return; + if (_musicMode != kMusicModeDisabled) { + // Handle volume control for Simon1 output. + if (_musicMode == kMusicModeSimon1) { + // The driver does not support any volume control, thus we simply + // scale the velocities on note on for now. + // TODO: We should probably handle this at output level at some + // point. Then we can allow volume changes to affect already + // playing notes too. For now this simple change allows us to + // have some simple volume control though. + if ((b & 0xF0) == 0x90) { + byte volume = (b >> 16) & 0x7F; + + if (_current == &_sfx) { + volume = volume * _sfxVolume / 255; + } else if (_current == &_music) { + volume = volume * _musicVolume / 255; + } + + b = (b & 0xFF00FFFF) | (volume << 16); + } + } + + // Send directly to Accolade/Miles/Simon1 Audio driver + _driver->send(b); + return; + } + byte channel = (byte)(b & 0x0F); if ((b & 0xFFF0) == 0x07B0) { // Adjust volume changes by master music and master sfx volume. @@ -123,10 +324,8 @@ void MidiPlayer::send(uint32 b) { else if (_current == &_music) volume = volume * _musicVolume / 255; b = (b & 0xFF00FFFF) | (volume << 16); - } else if ((b & 0xF0) == 0xC0) { - if (_map_mt32_to_gm && !_adlibPatches) { - b = (b & 0xFFFF00FF) | (MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8); - } + } else if ((b & 0xF0) == 0xC0 && _map_mt32_to_gm) { + b = (b & 0xFFFF00FF) | (MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8); } else if ((b & 0xFFF0) == 0x007BB0) { // Only respond to an All Notes Off if this channel // has already been allocated. @@ -146,8 +345,10 @@ void MidiPlayer::send(uint32 b) { _current->volume[channel] = 127; } + // Allocate channels if needed if (!_current->channel[channel]) _current->channel[channel] = (channel == 9) ? _driver->getPercussionChannel() : _driver->allocateChannel(); + if (_current->channel[channel]) { if (channel == 9) { if (_current == &_sfx) @@ -155,16 +356,7 @@ void MidiPlayer::send(uint32 b) { else if (_current == &_music) _current->channel[9]->volume(_current->volume[9] * _musicVolume / 255); } - - if ((b & 0xF0) == 0xC0 && _adlibPatches) { - // NOTE: In the percussion channel, this function is a - // no-op. Any percussion instruments you hear may - // be the stock ones from adlib.cpp. - _driver->sysEx_customInstrument(_current->channel[channel]->getNumber(), 'ADL ', _adlibPatches + 30 * ((b >> 8) & 0xFF)); - } else { - _current->channel[channel]->send(b); - } - + _current->channel[channel]->send(b); if ((b & 0xFFF0) == 0x79B0) { // We have received a "Reset All Controllers" message // and passed it on to the MIDI driver. This may or may @@ -186,13 +378,13 @@ void MidiPlayer::metaEvent(byte type, byte *data, uint16 length) { return; } else if (_current == &_sfx) { clearConstructs(_sfx); - } else if (_current->loopTrack) { + } else if (_loopTrack) { _current->parser->jumpToTick(0); } else if (_queuedTrack != 255) { _currentTrack = 255; byte destination = _queuedTrack; _queuedTrack = 255; - _current->loopTrack = _loopQueuedTrack; + _loopTrack = _loopQueuedTrack; _loopQueuedTrack = false; // Remember, we're still inside the locked mutex. @@ -320,7 +512,7 @@ void MidiPlayer::setVolume(int musicVol, int sfxVol) { void MidiPlayer::setLoop(bool loop) { Common::StackLock lock(_mutex); - _loopTrackDefault = loop; + _loopTrack = loop; } void MidiPlayer::queueTrack(int track, bool loop) { @@ -375,47 +567,6 @@ void MidiPlayer::resetVolumeTable() { } } -void MidiPlayer::loadAdlibPatches() { - Common::File ibk; - - if (!ibk.open("mt_fm.ibk")) - return; - - if (ibk.readUint32BE() == 0x49424b1a) { - _adlibPatches = new byte[128 * 30]; - byte *ptr = _adlibPatches; - - memset(_adlibPatches, 0, 128 * 30); - - for (int i = 0; i < 128; i++) { - byte instr[16]; - - ibk.read(instr, 16); - - ptr[0] = instr[0]; // Modulator Sound Characteristics - ptr[1] = instr[2]; // Modulator Scaling/Output Level - ptr[2] = ~instr[4]; // Modulator Attack/Decay - ptr[3] = ~instr[6]; // Modulator Sustain/Release - ptr[4] = instr[8]; // Modulator Wave Select - ptr[5] = instr[1]; // Carrier Sound Characteristics - ptr[6] = instr[3]; // Carrier Scaling/Output Level - ptr[7] = ~instr[5]; // Carrier Attack/Delay - ptr[8] = ~instr[7]; // Carrier Sustain/Release - ptr[9] = instr[9]; // Carrier Wave Select - ptr[10] = instr[10]; // Feedback/Connection - - // The remaining six bytes are reserved for future use - - ptr += 30; - } - } -} - -void MidiPlayer::unloadAdlibPatches() { - delete[] _adlibPatches; - _adlibPatches = NULL; -} - static const int simon1_gmf_size[] = { 8900, 12166, 2848, 3442, 4034, 4508, 7064, 9730, 6014, 4742, 3138, 6570, 5384, 8909, 6457, 16321, 2742, 8968, 4804, 8442, 7717, @@ -472,24 +623,27 @@ void MidiPlayer::loadSMF(Common::File *in, int song, bool sfx) { // 1 BYTE : Major version // 1 BYTE : Minor version // 1 BYTE : Ticks (Ranges from 2 - 8, always 2 for SFX) - // 1 BYTE : Loop control. 0 = no loop, 1 = loop - - // In the original, the ticks value indicated how many - // times the music timer was called before it actually - // did something. The larger the value the slower the - // music. - // - // We, on the other hand, have a timer rate which is - // used to control by how much the music advances on - // each onTimer() call. The larger the value, the - // faster the music. - // - // It seems that 4 corresponds to our base tempo, so - // this should be the right way to calculate it. - timerRate = (4 * _driver->getBaseTempo()) / p->data[5]; - p->loopTrack = (p->data[6] != 0); - } else { - p->loopTrack = _loopTrackDefault; + // 1 BYTE : Loop control. 0 = no loop, 1 = loop (Music only) + if (!sfx) { + // In the original, the ticks value indicated how many + // times the music timer was called before it actually + // did something. The larger the value the slower the + // music. + // + // We, on the other hand, have a timer rate which is + // used to control by how much the music advances on + // each onTimer() call. The larger the value, the + // faster the music. + // + // It seems that 4 corresponds to our base tempo, so + // this should be the right way to calculate it. + timerRate = (4 * _driver->getBaseTempo()) / p->data[5]; + + // According to bug #1004919 calling setLoop() from + // within a lock causes a lockup, though I have no + // idea when this actually happens. + _loopTrack = (p->data[6] != 0); + } } MidiParser *parser = MidiParser::createParser_SMF(); @@ -559,8 +713,6 @@ void MidiPlayer::loadMultipleSMF(Common::File *in, bool sfx) { p->song_sizes[i] = size; } - p->loopTrack = _loopTrackDefault; - if (!sfx) { _currentTrack = 255; resetVolumeTable(); @@ -592,7 +744,6 @@ void MidiPlayer::loadXMIDI(Common::File *in, bool sfx) { in->seek(pos, 0); p->data = (byte *)calloc(size, 1); in->read(p->data, size); - p->loopTrack = _loopTrackDefault; } else { error("Expected 'FORM' tag but found '%c%c%c%c' instead", buf[0], buf[1], buf[2], buf[3]); } @@ -637,8 +788,105 @@ void MidiPlayer::loadS1D(Common::File *in, bool sfx) { _currentTrack = 255; resetVolumeTable(); } - p->loopTrack = _loopTrackDefault; p->parser = parser; // That plugs the power cord into the wall } +#define MIDI_SETUP_BUNDLE_HEADER_SIZE 56 +#define MIDI_SETUP_BUNDLE_FILEHEADER_SIZE 48 +#define MIDI_SETUP_BUNDLE_FILENAME_MAX_SIZE 12 + +// PKWARE data compression library (called "DCL" in ScummVM) was used for storing files within SETUP.SHR +// we need it to be able to get the file MIDPAK.AD, otherwise we would have to require the user +// to "install" the game before being able to actually play it, when using AdLib. +// +// SETUP.SHR file format: +// [bundle file header] +// [compressed file header] [compressed file data] +// * compressed file count +Common::SeekableReadStream *MidiPlayer::simon2SetupExtractFile(const Common::String &requestedFileName) { + Common::File *setupBundleStream = new Common::File(); + uint32 bundleSize = 0; + uint32 bundleBytesLeft = 0; + byte bundleHeader[MIDI_SETUP_BUNDLE_HEADER_SIZE]; + byte bundleFileHeader[MIDI_SETUP_BUNDLE_FILEHEADER_SIZE]; + uint16 bundleFileCount = 0; + uint16 bundleFileNr = 0; + + Common::String fileName; + uint32 fileCompressedSize = 0; + byte *fileCompressedDataPtr = nullptr; + + Common::SeekableReadStream *extractedStream = nullptr; + + if (!setupBundleStream->open("setup.shr")) + error("MidiPlayer: could not open setup.shr"); + + bundleSize = setupBundleStream->size(); + bundleBytesLeft = bundleSize; + + if (bundleSize < MIDI_SETUP_BUNDLE_HEADER_SIZE) + error("MidiPlayer: unexpected EOF in setup.shr"); + + if (setupBundleStream->read(bundleHeader, MIDI_SETUP_BUNDLE_HEADER_SIZE) != MIDI_SETUP_BUNDLE_HEADER_SIZE) + error("MidiPlayer: setup.shr read error"); + bundleBytesLeft -= MIDI_SETUP_BUNDLE_HEADER_SIZE; + + // Verify header byte + if (bundleHeader[13] != 't') + error("MidiPlayer: setup.shr bundle header data mismatch"); + + bundleFileCount = READ_LE_UINT16(&bundleHeader[14]); + + // Search for requested file + while (bundleFileNr < bundleFileCount) { + if (bundleBytesLeft < sizeof(bundleFileHeader)) + error("MidiPlayer: unexpected EOF in setup.shr"); + + if (setupBundleStream->read(bundleFileHeader, sizeof(bundleFileHeader)) != sizeof(bundleFileHeader)) + error("MidiPlayer: setup.shr read error"); + bundleBytesLeft -= MIDI_SETUP_BUNDLE_FILEHEADER_SIZE; + + // Extract filename from file-header + fileName.clear(); + for (byte curPos = 0; curPos < MIDI_SETUP_BUNDLE_FILENAME_MAX_SIZE; curPos++) { + if (!bundleFileHeader[curPos]) // terminating NUL + break; + fileName.insertChar(bundleFileHeader[curPos], curPos); + } + + // Get compressed + fileCompressedSize = READ_LE_UINT32(&bundleFileHeader[20]); + if (!fileCompressedSize) + error("MidiPlayer: compressed file is 0 bytes, data corruption?"); + if (bundleBytesLeft < fileCompressedSize) + error("MidiPlayer: unexpected EOF in setup.shr"); + + if (fileName == requestedFileName) { + // requested file found + fileCompressedDataPtr = new byte[fileCompressedSize]; + + if (setupBundleStream->read(fileCompressedDataPtr, fileCompressedSize) != fileCompressedSize) + error("MidiPlayer: setup.shr read error"); + + Common::MemoryReadStream *compressedStream = nullptr; + + compressedStream = new Common::MemoryReadStream(fileCompressedDataPtr, fileCompressedSize); + // we don't know the unpacked size, let decompressor figure it out + extractedStream = Common::decompressDCL(compressedStream); + delete compressedStream; + break; + } + + // skip compressed size + setupBundleStream->skip(fileCompressedSize); + bundleBytesLeft -= fileCompressedSize; + + bundleFileNr++; + } + setupBundleStream->close(); + delete setupBundleStream; + + return extractedStream; +} + } // End of namespace AGOS diff --git a/engines/agos/midi.h b/engines/agos/midi.h index 7e78bfef28..fb987fddcf 100644 --- a/engines/agos/midi.h +++ b/engines/agos/midi.h @@ -33,10 +33,16 @@ class File; namespace AGOS { +enum kMusicMode { + kMusicModeDisabled = 0, + kMusicModeAccolade = 1, + kMusicModeMilesAudio = 2, + kMusicModeSimon1 = 3 +}; + struct MusicInfo { MidiParser *parser; byte *data; - bool loopTrack; byte num_songs; // For Type 1 SMF resources byte *songs[16]; // For Type 1 SMF resources uint32 song_sizes[16]; // For Type 1 SMF resources @@ -47,7 +53,6 @@ struct MusicInfo { MusicInfo() { clear(); } void clear() { parser = 0; data = 0; num_songs = 0; - loopTrack = false; memset(songs, 0, sizeof(songs)); memset(song_sizes, 0, sizeof(song_sizes)); memset(channel, 0, sizeof(channel)); @@ -73,21 +78,18 @@ protected: // These are only used for music. byte _currentTrack; - bool _loopTrackDefault; + bool _loopTrack; byte _queuedTrack; bool _loopQueuedTrack; - byte *_adlibPatches; - protected: static void onTimer(void *data); void clearConstructs(); void clearConstructs(MusicInfo &info); void resetVolumeTable(); - void loadAdlibPatches(); - void unloadAdlibPatches(); public: + bool _adLibMusic; bool _enable_sfx; public: @@ -113,12 +115,17 @@ public: void setVolume(int musicVol, int sfxVol); public: - int open(int gameType); + int open(int gameType, bool isDemo); // MidiDriver_BASE interface implementation virtual void send(uint32 b); virtual void metaEvent(byte type, byte *data, uint16 length); +private: + kMusicMode _musicMode; + +private: + Common::SeekableReadStream *simon2SetupExtractFile(const Common::String &requestedFileName); }; } // End of namespace AGOS diff --git a/engines/agos/midiparser_s1d.cpp b/engines/agos/midiparser_s1d.cpp index c2c08bf451..7b9a058efc 100644 --- a/engines/agos/midiparser_s1d.cpp +++ b/engines/agos/midiparser_s1d.cpp @@ -179,12 +179,43 @@ void MidiParser_S1D::parseNextEvent(EventInfo &info) { bool MidiParser_S1D::loadMusic(byte *data, uint32 size) { unloadMusic(); + if (!size) + return false; + // The original actually just ignores the first two bytes. byte *pos = data; - if (*(pos++) != 0xFC) - debug(1, "Expected 0xFC header but found 0x%02X instead", (int) *pos); - - pos += 1; + if (*pos == 0xFC) { + // SysEx found right at the start + // this seems to happen since Elvira 2, we ignore it + // 3rd byte after the SysEx seems to be saved into a global + + // We expect at least 4 bytes in total + if (size < 4) + return false; + + byte skipOffset = pos[2]; // get second byte after the SysEx + // pos[1] seems to have been ignored + // pos[3] is saved into a global inside the original interpreters + + // Waxworks + Simon 1 demo typical header is: + // 0xFC 0x29 0x07 0x01 [0x00/0x01] + // Elvira 2 typical header is: + // 0xFC 0x04 0x06 0x06 + + if (skipOffset >= 6) { + // should be at least 6, so that we skip over the 2 size bytes and the + // smallest SysEx possible + skipOffset -= 2; // 2 size bytes were already read by previous code outside of this method + + if (size <= skipOffset) // Skip to the end of file? -> something is not correct + return false; + + // Do skip over the bytes + pos += skipOffset; + } else { + warning("MidiParser_S1D: unexpected skip offset in music file"); + } + } // And now we're at the actual data. Only one track. _numTracks = 1; diff --git a/engines/agos/module.mk b/engines/agos/module.mk index 7069d8005b..e7b773d76f 100644 --- a/engines/agos/module.mk +++ b/engines/agos/module.mk @@ -1,6 +1,10 @@ MODULE := engines/agos MODULE_OBJS := \ + drivers/accolade/adlib.o \ + drivers/accolade/driverfile.o \ + drivers/accolade/mt32.o \ + drivers/simon1/adlib.o \ agos.o \ charset.o \ charset-fontdata.o \ diff --git a/engines/agos/res_snd.cpp b/engines/agos/res_snd.cpp index 5d6ab60c8b..d04f1735d6 100644 --- a/engines/agos/res_snd.cpp +++ b/engines/agos/res_snd.cpp @@ -220,6 +220,7 @@ void AGOSEngine::playModule(uint16 music) { } _mixer->playStream(Audio::Mixer::kMusicSoundType, &_modHandle, audioStream); + _mixer->pauseHandle(_modHandle, _musicPaused); } void AGOSEngine_Simon1::playMusic(uint16 music, uint16 track) { @@ -309,7 +310,9 @@ void AGOSEngine::stopMusic() { } void AGOSEngine::playSting(uint16 soundId) { - if (!_midi->_enable_sfx) + // The sound effects in floppy disk version of + // Simon the Sorcerer 1 are only meant for AdLib + if (!_midi->_adLibMusic || !_midi->_enable_sfx) return; char filename[15]; diff --git a/engines/agos/sound.cpp b/engines/agos/sound.cpp index 812f46504f..762f60bd91 100644 --- a/engines/agos/sound.cpp +++ b/engines/agos/sound.cpp @@ -515,7 +515,7 @@ void Sound::readSfxFile(const Common::String &filename) { // This method is only used by Simon2 void Sound::loadSfxTable(const char *gameFilename, uint32 base) { - stopAll(); + stopAllSfx(); delete _effects; const bool dataIsUnsigned = true; @@ -684,7 +684,7 @@ void Sound::playRawData(byte *soundData, uint sound, uint size, uint freq) { memcpy(buffer, soundData, size); byte flags = 0; - if (_vm->getPlatform() == Common::kPlatformDOS) + if (_vm->getPlatform() == Common::kPlatformDOS && _vm->getGameId() != GID_ELVIRA2) flags = Audio::FLAG_UNSIGNED; Audio::AudioStream *stream = Audio::makeRawStream(buffer, size, freq, flags); diff --git a/engines/agos/zones.cpp b/engines/agos/zones.cpp index 1644213579..5a753d9b4b 100644 --- a/engines/agos/zones.cpp +++ b/engines/agos/zones.cpp @@ -94,8 +94,7 @@ void AGOSEngine::loadZone(uint16 zoneNum, bool useError) { vpe->sfxFile = NULL; - if ((getPlatform() == Common::kPlatformAmiga || getPlatform() == Common::kPlatformAtariST) && - getGameType() == GType_ELVIRA2) { + if (getGameType() == GType_ELVIRA2) { // A singe sound file is used for Amiga and AtariST versions if (loadVGASoundFile(1, 3)) { vpe->sfxFile = _block; diff --git a/engines/bbvs/sound.h b/engines/bbvs/sound.h index 4e44c2b962..4d3253c48e 100644 --- a/engines/bbvs/sound.h +++ b/engines/bbvs/sound.h @@ -38,7 +38,7 @@ public: void stop(); bool isPlaying(); protected: - Audio::SeekableAudioStream *_stream; + Audio::RewindableAudioStream *_stream; Audio::SoundHandle _handle; // Keep the filename for debugging purposes Common::String _filename; diff --git a/engines/cine/sound.cpp b/engines/cine/sound.cpp index 069a4787ac..0c788b816c 100644 --- a/engines/cine/sound.cpp +++ b/engines/cine/sound.cpp @@ -101,7 +101,7 @@ struct AdLibSoundInstrument { byte amDepth; }; -class AdLibSoundDriver : public PCSoundDriver, Audio::AudioStream { +class AdLibSoundDriver : public PCSoundDriver { public: AdLibSoundDriver(Audio::Mixer *mixer); virtual ~AdLibSoundDriver(); @@ -112,14 +112,8 @@ public: virtual void stopChannel(int channel); virtual void stopAll(); - // AudioStream interface - virtual int readBuffer(int16 *buffer, const int numSamples); - virtual bool isStereo() const { return false; } - virtual bool endOfData() const { return false; } - virtual int getRate() const { return _sampleRate; } - void initCard(); - void update(int16 *buf, int len); + void onTimer(); void setupInstrument(const byte *data, int channel); void loadRegisterInstrument(const byte *data, AdLibRegisterSoundInstrument *reg); virtual void loadInstrument(const byte *data, AdLibSoundInstrument *asi) = 0; @@ -128,10 +122,8 @@ protected: UpdateCallback _upCb; void *_upRef; - FM_OPL *_opl; - int _sampleRate; + OPL::OPL *_opl; Audio::Mixer *_mixer; - Audio::SoundHandle _soundHandle; byte _vibrato; int _channelsVolumeTable[4]; @@ -282,17 +274,19 @@ void PCSoundDriver::resetChannel(int channel) { AdLibSoundDriver::AdLibSoundDriver(Audio::Mixer *mixer) : _upCb(0), _upRef(0), _mixer(mixer) { - _sampleRate = _mixer->getOutputRate(); - _opl = makeAdLibOPL(_sampleRate); + + _opl = OPL::Config::create(); + if (!_opl || !_opl->init()) + error("Failed to create OPL"); + memset(_channelsVolumeTable, 0, sizeof(_channelsVolumeTable)); memset(_instrumentsTable, 0, sizeof(_instrumentsTable)); initCard(); - _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); + _opl->start(new Common::Functor0Mem<void, AdLibSoundDriver>(this, &AdLibSoundDriver::onTimer), 50); } AdLibSoundDriver::~AdLibSoundDriver() { - _mixer->stopHandle(_soundHandle); - OPLDestroy(_opl); + delete _opl; } void AdLibSoundDriver::setUpdateCallback(UpdateCallback upCb, void *ref) { @@ -322,71 +316,52 @@ void AdLibSoundDriver::stopChannel(int channel) { channel = 6; } if (ins->mode == 0 || channel == 6) { - OPLWriteReg(_opl, 0xB0 | channel, 0); + _opl->writeReg(0xB0 | channel, 0); } if (ins->mode != 0) { _vibrato &= ~(1 << (10 - ins->channel)); - OPLWriteReg(_opl, 0xBD, _vibrato); + _opl->writeReg(0xBD, _vibrato); } } void AdLibSoundDriver::stopAll() { int i; for (i = 0; i < 18; ++i) { - OPLWriteReg(_opl, 0x40 | _operatorsTable[i], 63); + _opl->writeReg(0x40 | _operatorsTable[i], 63); } for (i = 0; i < 9; ++i) { - OPLWriteReg(_opl, 0xB0 | i, 0); + _opl->writeReg(0xB0 | i, 0); } - OPLWriteReg(_opl, 0xBD, 0); -} - -int AdLibSoundDriver::readBuffer(int16 *buffer, const int numSamples) { - update(buffer, numSamples); - return numSamples; + _opl->writeReg(0xBD, 0); } void AdLibSoundDriver::initCard() { _vibrato = 0x20; - OPLWriteReg(_opl, 0xBD, _vibrato); - OPLWriteReg(_opl, 0x08, 0x40); + _opl->writeReg(0xBD, _vibrato); + _opl->writeReg(0x08, 0x40); static const int oplRegs[] = { 0x40, 0x60, 0x80, 0x20, 0xE0 }; for (int i = 0; i < 9; ++i) { - OPLWriteReg(_opl, 0xB0 | i, 0); + _opl->writeReg(0xB0 | i, 0); } for (int i = 0; i < 9; ++i) { - OPLWriteReg(_opl, 0xC0 | i, 0); + _opl->writeReg(0xC0 | i, 0); } for (int j = 0; j < 5; j++) { for (int i = 0; i < 18; ++i) { - OPLWriteReg(_opl, oplRegs[j] | _operatorsTable[i], 0); + _opl->writeReg(oplRegs[j] | _operatorsTable[i], 0); } } - OPLWriteReg(_opl, 1, 0x20); - OPLWriteReg(_opl, 1, 0); + _opl->writeReg(1, 0x20); + _opl->writeReg(1, 0); } -void AdLibSoundDriver::update(int16 *buf, int len) { - static int samplesLeft = 0; - while (len != 0) { - int count = samplesLeft; - if (count > len) { - count = len; - } - samplesLeft -= count; - len -= count; - YM3812UpdateOne(_opl, buf, count); - if (samplesLeft == 0) { - if (_upCb) { - (*_upCb)(_upRef); - } - samplesLeft = _sampleRate / 50; - } - buf += count; +void AdLibSoundDriver::onTimer() { + if (_upCb) { + (*_upCb)(_upRef); } } @@ -408,32 +383,32 @@ void AdLibSoundDriver::setupInstrument(const byte *data, int channel) { if (ins->mode == 0 || ins->channel == 6) { reg = &ins->regMod; - OPLWriteReg(_opl, 0x20 | mod, reg->vibrato); + _opl->writeReg(0x20 | mod, reg->vibrato); if (reg->freqMod) { tmp = reg->outputLevel & 0x3F; } else { tmp = (63 - (reg->outputLevel & 0x3F)) * _channelsVolumeTable[channel]; tmp = 63 - (2 * tmp + 127) / (2 * 127); } - OPLWriteReg(_opl, 0x40 | mod, tmp | (reg->keyScaling << 6)); - OPLWriteReg(_opl, 0x60 | mod, reg->attackDecay); - OPLWriteReg(_opl, 0x80 | mod, reg->sustainRelease); + _opl->writeReg(0x40 | mod, tmp | (reg->keyScaling << 6)); + _opl->writeReg(0x60 | mod, reg->attackDecay); + _opl->writeReg(0x80 | mod, reg->sustainRelease); if (ins->mode != 0) { - OPLWriteReg(_opl, 0xC0 | ins->channel, reg->feedbackStrength); + _opl->writeReg(0xC0 | ins->channel, reg->feedbackStrength); } else { - OPLWriteReg(_opl, 0xC0 | channel, reg->feedbackStrength); + _opl->writeReg(0xC0 | channel, reg->feedbackStrength); } - OPLWriteReg(_opl, 0xE0 | mod, ins->waveSelectMod); + _opl->writeReg(0xE0 | mod, ins->waveSelectMod); } reg = &ins->regCar; - OPLWriteReg(_opl, 0x20 | car, reg->vibrato); + _opl->writeReg(0x20 | car, reg->vibrato); tmp = (63 - (reg->outputLevel & 0x3F)) * _channelsVolumeTable[channel]; tmp = 63 - (2 * tmp + 127) / (2 * 127); - OPLWriteReg(_opl, 0x40 | car, tmp | (reg->keyScaling << 6)); - OPLWriteReg(_opl, 0x60 | car, reg->attackDecay); - OPLWriteReg(_opl, 0x80 | car, reg->sustainRelease); - OPLWriteReg(_opl, 0xE0 | car, ins->waveSelectCar); + _opl->writeReg(0x40 | car, tmp | (reg->keyScaling << 6)); + _opl->writeReg(0x60 | car, reg->attackDecay); + _opl->writeReg(0x80 | car, reg->sustainRelease); + _opl->writeReg(0xE0 | car, ins->waveSelectCar); } void AdLibSoundDriver::loadRegisterInstrument(const byte *data, AdLibRegisterSoundInstrument *reg) { @@ -490,16 +465,16 @@ void AdLibSoundDriverINS::setChannelFrequency(int channel, int frequency) { if (channel == 6) oct = 0; freq = _freqTable[note % 12]; - OPLWriteReg(_opl, 0xA0 | channel, freq); + _opl->writeReg(0xA0 | channel, freq); freq = (oct << 2) | ((freq & 0x300) >> 8); if (ins->mode == 0) { freq |= 0x20; } - OPLWriteReg(_opl, 0xB0 | channel, freq); + _opl->writeReg(0xB0 | channel, freq); } if (ins->mode != 0) { _vibrato |= 1 << (10 - ins->channel); - OPLWriteReg(_opl, 0xBD, _vibrato); + _opl->writeReg(0xBD, _vibrato); } } @@ -515,16 +490,16 @@ void AdLibSoundDriverINS::playSample(const byte *data, int size, int channel, in if (ins->mode == 0 || channel == 6) { uint16 note = 12; int freq = _freqTable[note % 12]; - OPLWriteReg(_opl, 0xA0 | channel, freq); + _opl->writeReg(0xA0 | channel, freq); freq = ((note / 12) << 2) | ((freq & 0x300) >> 8); if (ins->mode == 0) { freq |= 0x20; } - OPLWriteReg(_opl, 0xB0 | channel, freq); + _opl->writeReg(0xB0 | channel, freq); } if (ins->mode != 0) { _vibrato |= 1 << (10 - ins->channel); - OPLWriteReg(_opl, 0xBD, _vibrato); + _opl->writeReg(0xBD, _vibrato); } } @@ -562,15 +537,15 @@ void AdLibSoundDriverADL::setChannelFrequency(int channel, int frequency) { } freq = _freqTable[note % 12]; - OPLWriteReg(_opl, 0xA0 | channel, freq); + _opl->writeReg(0xA0 | channel, freq); freq = (oct << 2) | ((freq & 0x300) >> 8); if (ins->mode == 0) { freq |= 0x20; } - OPLWriteReg(_opl, 0xB0 | channel, freq); + _opl->writeReg(0xB0 | channel, freq); if (ins->mode != 0) { _vibrato |= 1 << (10 - channel); - OPLWriteReg(_opl, 0xBD, _vibrato); + _opl->writeReg(0xBD, _vibrato); } } @@ -580,11 +555,11 @@ void AdLibSoundDriverADL::playSample(const byte *data, int size, int channel, in setupInstrument(data, channel); AdLibSoundInstrument *ins = &_instrumentsTable[channel]; if (ins->mode != 0 && ins->channel == 6) { - OPLWriteReg(_opl, 0xB0 | channel, 0); + _opl->writeReg(0xB0 | channel, 0); } if (ins->mode != 0) { _vibrato &= ~(1 << (10 - ins->channel)); - OPLWriteReg(_opl, 0xBD, _vibrato); + _opl->writeReg(0xBD, _vibrato); } if (ins->mode != 0) { channel = ins->channel; @@ -599,15 +574,15 @@ void AdLibSoundDriverADL::playSample(const byte *data, int size, int channel, in note = ins->amDepth; } int freq = _freqTable[note % 12]; - OPLWriteReg(_opl, 0xA0 | channel, freq); + _opl->writeReg(0xA0 | channel, freq); freq = ((note / 12) << 2) | ((freq & 0x300) >> 8); if (ins->mode == 0) { freq |= 0x20; } - OPLWriteReg(_opl, 0xB0 | channel, freq); + _opl->writeReg(0xB0 | channel, freq); if (ins->mode != 0) { _vibrato |= 1 << (10 - channel); - OPLWriteReg(_opl, 0xBD, _vibrato); + _opl->writeReg(0xBD, _vibrato); } } diff --git a/engines/cruise/sound.cpp b/engines/cruise/sound.cpp index 0b0fab8c4a..f57435f4f7 100644 --- a/engines/cruise/sound.cpp +++ b/engines/cruise/sound.cpp @@ -108,7 +108,7 @@ struct VolumeEntry { int adjusted; }; -class AdLibSoundDriver : public PCSoundDriver, Audio::AudioStream { +class AdLibSoundDriver : public PCSoundDriver { public: AdLibSoundDriver(Audio::Mixer *mixer); virtual ~AdLibSoundDriver(); @@ -118,14 +118,8 @@ public: virtual void stopChannel(int channel); virtual void stopAll(); - // AudioStream interface - virtual int readBuffer(int16 *buffer, const int numSamples); - virtual bool isStereo() const { return false; } - virtual bool endOfData() const { return false; } - virtual int getRate() const { return _sampleRate; } - void initCard(); - void update(int16 *buf, int len); + void onTimer(); void setupInstrument(const byte *data, int channel); void setupInstrument(const AdLibSoundInstrument *ins, int channel); void loadRegisterInstrument(const byte *data, AdLibRegisterSoundInstrument *reg); @@ -135,10 +129,8 @@ public: void adjustVolume(int channel, int volume); protected: - FM_OPL *_opl; - int _sampleRate; + OPL::OPL *_opl; Audio::Mixer *_mixer; - Audio::SoundHandle _soundHandle; byte _vibrato; VolumeEntry _channelsVolumeTable[5]; @@ -302,8 +294,9 @@ void PCSoundDriver::syncSounds() { AdLibSoundDriver::AdLibSoundDriver(Audio::Mixer *mixer) : _mixer(mixer) { - _sampleRate = _mixer->getOutputRate(); - _opl = makeAdLibOPL(_sampleRate); + _opl = OPL::Config::create(); + if (!_opl || !_opl->init()) + error("Failed to create OPL"); for (int i = 0; i < 5; ++i) { _channelsVolumeTable[i].original = 0; @@ -311,15 +304,15 @@ AdLibSoundDriver::AdLibSoundDriver(Audio::Mixer *mixer) } memset(_instrumentsTable, 0, sizeof(_instrumentsTable)); initCard(); - _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); _musicVolume = ConfMan.getBool("music_mute") ? 0 : MIN(255, ConfMan.getInt("music_volume")); _sfxVolume = ConfMan.getBool("sfx_mute") ? 0 : MIN(255, ConfMan.getInt("sfx_volume")); + + _opl->start(new Common::Functor0Mem<void, AdLibSoundDriver>(this, &AdLibSoundDriver::onTimer), 50); } AdLibSoundDriver::~AdLibSoundDriver() { - _mixer->stopHandle(_soundHandle); - OPLDestroy(_opl); + delete _opl; } void AdLibSoundDriver::syncSounds() { @@ -368,70 +361,51 @@ void AdLibSoundDriver::stopChannel(int channel) { channel = 6; } if (ins->mode == 0 || channel == 6) { - OPLWriteReg(_opl, 0xB0 | channel, 0); + _opl->writeReg(0xB0 | channel, 0); } if (ins->mode != 0) { _vibrato &= ~(1 << (10 - ins->channel)); - OPLWriteReg(_opl, 0xBD, _vibrato); + _opl->writeReg(0xBD, _vibrato); } } void AdLibSoundDriver::stopAll() { for (int i = 0; i < 18; ++i) - OPLWriteReg(_opl, 0x40 | _operatorsTable[i], 63); + _opl->writeReg(0x40 | _operatorsTable[i], 63); for (int i = 0; i < 9; ++i) - OPLWriteReg(_opl, 0xB0 | i, 0); - - OPLWriteReg(_opl, 0xBD, 0); -} + _opl->writeReg(0xB0 | i, 0); -int AdLibSoundDriver::readBuffer(int16 *buffer, const int numSamples) { - update(buffer, numSamples); - return numSamples; + _opl->writeReg(0xBD, 0); } void AdLibSoundDriver::initCard() { _vibrato = 0x20; - OPLWriteReg(_opl, 0xBD, _vibrato); - OPLWriteReg(_opl, 0x08, 0x40); + _opl->writeReg(0xBD, _vibrato); + _opl->writeReg(0x08, 0x40); static const int oplRegs[] = { 0x40, 0x60, 0x80, 0x20, 0xE0 }; for (int i = 0; i < 9; ++i) { - OPLWriteReg(_opl, 0xB0 | i, 0); + _opl->writeReg(0xB0 | i, 0); } for (int i = 0; i < 9; ++i) { - OPLWriteReg(_opl, 0xC0 | i, 0); + _opl->writeReg(0xC0 | i, 0); } for (int j = 0; j < 5; j++) { for (int i = 0; i < 18; ++i) { - OPLWriteReg(_opl, oplRegs[j] | _operatorsTable[i], 0); + _opl->writeReg(oplRegs[j] | _operatorsTable[i], 0); } } - OPLWriteReg(_opl, 1, 0x20); - OPLWriteReg(_opl, 1, 0); + _opl->writeReg(1, 0x20); + _opl->writeReg(1, 0); } -void AdLibSoundDriver::update(int16 *buf, int len) { - static int samplesLeft = 0; - while (len != 0) { - int count = samplesLeft; - if (count > len) { - count = len; - } - samplesLeft -= count; - len -= count; - YM3812UpdateOne(_opl, buf, count); - if (samplesLeft == 0) { - if (_upCb) { - (*_upCb)(_upRef); - } - samplesLeft = _sampleRate / 50; - } - buf += count; +void AdLibSoundDriver::onTimer() { + if (_upCb) { + (*_upCb)(_upRef); } } @@ -457,32 +431,32 @@ void AdLibSoundDriver::setupInstrument(const AdLibSoundInstrument *ins, int chan if (ins->mode == 0 || ins->channel == 6) { reg = &ins->regMod; - OPLWriteReg(_opl, 0x20 | mod, reg->vibrato); + _opl->writeReg(0x20 | mod, reg->vibrato); if (reg->freqMod) { tmp = reg->outputLevel & 0x3F; } else { tmp = (63 - (reg->outputLevel & 0x3F)) * _channelsVolumeTable[channel].adjusted; tmp = 63 - (2 * tmp + 127) / (2 * 127); } - OPLWriteReg(_opl, 0x40 | mod, tmp | (reg->keyScaling << 6)); - OPLWriteReg(_opl, 0x60 | mod, reg->attackDecay); - OPLWriteReg(_opl, 0x80 | mod, reg->sustainRelease); + _opl->writeReg(0x40 | mod, tmp | (reg->keyScaling << 6)); + _opl->writeReg(0x60 | mod, reg->attackDecay); + _opl->writeReg(0x80 | mod, reg->sustainRelease); if (ins->mode != 0) { - OPLWriteReg(_opl, 0xC0 | ins->channel, reg->feedbackStrength); + _opl->writeReg(0xC0 | ins->channel, reg->feedbackStrength); } else { - OPLWriteReg(_opl, 0xC0 | channel, reg->feedbackStrength); + _opl->writeReg(0xC0 | channel, reg->feedbackStrength); } - OPLWriteReg(_opl, 0xE0 | mod, ins->waveSelectMod); + _opl->writeReg(0xE0 | mod, ins->waveSelectMod); } reg = &ins->regCar; - OPLWriteReg(_opl, 0x20 | car, reg->vibrato); + _opl->writeReg(0x20 | car, reg->vibrato); tmp = (63 - (reg->outputLevel & 0x3F)) * _channelsVolumeTable[channel].adjusted; tmp = 63 - (2 * tmp + 127) / (2 * 127); - OPLWriteReg(_opl, 0x40 | car, tmp | (reg->keyScaling << 6)); - OPLWriteReg(_opl, 0x60 | car, reg->attackDecay); - OPLWriteReg(_opl, 0x80 | car, reg->sustainRelease); - OPLWriteReg(_opl, 0xE0 | car, ins->waveSelectCar); + _opl->writeReg(0x40 | car, tmp | (reg->keyScaling << 6)); + _opl->writeReg(0x60 | car, reg->attackDecay); + _opl->writeReg(0x80 | car, reg->sustainRelease); + _opl->writeReg(0xE0 | car, ins->waveSelectCar); } void AdLibSoundDriver::loadRegisterInstrument(const byte *data, AdLibRegisterSoundInstrument *reg) { @@ -551,15 +525,15 @@ void AdLibSoundDriverADL::setChannelFrequency(int channel, int frequency) { } freq = _freqTable[note % 12]; - OPLWriteReg(_opl, 0xA0 | channel, freq); + _opl->writeReg(0xA0 | channel, freq); freq = ((note / 12) << 2) | ((freq & 0x300) >> 8); if (ins->mode == 0) { freq |= 0x20; } - OPLWriteReg(_opl, 0xB0 | channel, freq); + _opl->writeReg(0xB0 | channel, freq); if (ins->mode != 0) { _vibrato |= 1 << (10 - channel); - OPLWriteReg(_opl, 0xBD, _vibrato); + _opl->writeReg(0xBD, _vibrato); } } @@ -570,11 +544,11 @@ void AdLibSoundDriverADL::playSample(const byte *data, int size, int channel, in setupInstrument(data, channel); AdLibSoundInstrument *ins = &_instrumentsTable[channel]; if (ins->mode != 0 && ins->channel == 6) { - OPLWriteReg(_opl, 0xB0 | channel, 0); + _opl->writeReg(0xB0 | channel, 0); } if (ins->mode != 0) { _vibrato &= ~(1 << (10 - ins->channel)); - OPLWriteReg(_opl, 0xBD, _vibrato); + _opl->writeReg(0xBD, _vibrato); } if (ins->mode != 0) { channel = ins->channel; @@ -589,15 +563,15 @@ void AdLibSoundDriverADL::playSample(const byte *data, int size, int channel, in note = ins->amDepth; } int freq = _freqTable[note % 12]; - OPLWriteReg(_opl, 0xA0 | channel, freq); + _opl->writeReg(0xA0 | channel, freq); freq = ((note / 12) << 2) | ((freq & 0x300) >> 8); if (ins->mode == 0) { freq |= 0x20; } - OPLWriteReg(_opl, 0xB0 | channel, freq); + _opl->writeReg(0xB0 | channel, freq); if (ins->mode != 0) { _vibrato |= 1 << (10 - channel); - OPLWriteReg(_opl, 0xBD, _vibrato); + _opl->writeReg(0xBD, _vibrato); } } diff --git a/engines/fullpipe/motion.cpp b/engines/fullpipe/motion.cpp index 9573e0517b..5845ad1501 100644 --- a/engines/fullpipe/motion.cpp +++ b/engines/fullpipe/motion.cpp @@ -1232,7 +1232,7 @@ MessageQueue *MovGraph::method50(StaticANIObject *ani, MovArr *movarr, int stati return 0; uint idx; - int movidx; + int movidx = 0; bool done = false; for (idx = 0; idx <= _items.size() && !done; idx++) { diff --git a/engines/gob/gob.cpp b/engines/gob/gob.cpp index 5ab3271a8f..24bdb858d8 100644 --- a/engines/gob/gob.cpp +++ b/engines/gob/gob.cpp @@ -389,6 +389,9 @@ void GobEngine::syncSoundSettings() { Engine::syncSoundSettings(); _init->updateConfig(); + + if (_sound) + _sound->adlibSyncVolume(); } void GobEngine::pauseGame() { diff --git a/engines/gob/map_v2.cpp b/engines/gob/map_v2.cpp index cb5abe9644..370e62c88f 100644 --- a/engines/gob/map_v2.cpp +++ b/engines/gob/map_v2.cpp @@ -163,11 +163,9 @@ void Map_v2::loadMapObjects(const char *avjFile) { mapHeight = _screenHeight / _tilesHeight; mapWidth = _screenWidth / _tilesWidth; - for (int i = 0; i < mapHeight; i++) { + for (int i = 0; i < mapHeight; i++) for (int j = 0; j < mapWidth; j++) setPass(j, i, mapData.readSByte()); - _vm->_inter->_variables->getAddressOff8(var + i * _passWidth); - } } mapData.seek(tmpPos); diff --git a/engines/gob/sound/adlib.cpp b/engines/gob/sound/adlib.cpp index 65b43cae7a..1e024d5a50 100644 --- a/engines/gob/sound/adlib.cpp +++ b/engines/gob/sound/adlib.cpp @@ -37,6 +37,28 @@ static const int kPitchTomToSnare = 7; static const int kPitchSnareDrum = kPitchTom + kPitchTomToSnare; +// Attenuation map for GUI volume slider +// Note: no volume control in the original engine +const uint8 AdLib::kVolumeTable[Audio::Mixer::kMaxMixerVolume + 1] = { + 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 62, 61, 59, 57, 56, 55, + 53, 52, 51, 50, 49, 48, 47, 46, 46, 45, 44, 43, 43, 42, 41, 41, + 40, 39, 39, 38, 38, 37, 37, 36, 36, 35, 35, 34, 34, 33, 33, 33, + 32, 32, 31, 31, 31, 30, 30, 30, 29, 29, 29, 28, 28, 28, 27, 27, + 27, 26, 26, 26, 26, 25, 25, 25, 24, 24, 24, 24, 23, 23, 23, 23, + 22, 22, 22, 22, 21, 21, 21, 21, 21, 20, 20, 20, 20, 19, 19, 19, + 19, 19, 18, 18, 18, 18, 18, 18, 17, 17, 17, 17, 17, 16, 16, 16, + 16, 16, 16, 15, 15, 15, 15, 15, 15, 14, 14, 14, 14, 14, 14, 13, + 13, 13, 13, 13, 13, 13, 12, 12, 12, 12, 12, 12, 12, 11, 11, 11, + 11, 11, 11, 11, 11, 10, 10, 10, 10, 10, 10, 10, 10, 9, 9, 9, + 9, 9, 9, 9, 9, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0 +}; + // Is the operator a modulator (0) or a carrier (1)? const uint8 AdLib::kOperatorType[kOperatorCount] = { 0, 0, 0, 1, 1, 1, @@ -93,23 +115,20 @@ const uint16 AdLib::kHihatParams [kParamCount] = { 0, 1, 0, 15, 11, 0, 7, 5, 0, 0, 0, 0, 0, 0 }; -AdLib::AdLib(Audio::Mixer &mixer) : _mixer(&mixer), _opl(0), - _toPoll(0), _repCount(0), _first(true), _playing(false), _ended(true) { - - _rate = _mixer->getOutputRate(); +AdLib::AdLib(int callbackFreq) : _opl(0), + _toPoll(0), _repCount(0), _first(true), _playing(false), _ended(true), _volume(0) { initFreqs(); createOPL(); initOPL(); - _mixer->playStream(Audio::Mixer::kMusicSoundType, &_handle, - this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); + syncVolume(); + + _opl->start(new Common::Functor0Mem<void, AdLib>(this, &AdLib::onTimer), callbackFreq); } AdLib::~AdLib() { - _mixer->stopHandle(_handle); - delete _opl; } @@ -136,46 +155,38 @@ void AdLib::createOPL() { } _opl = OPL::Config::create(OPL::Config::parse(oplDriver), OPL::Config::kOpl2); - if (!_opl || !_opl->init(_rate)) { + if (!_opl || !_opl->init()) { delete _opl; error("Could not create an AdLib emulator"); } } -int AdLib::readBuffer(int16 *buffer, const int numSamples) { +void AdLib::onTimer() { Common::StackLock slock(_mutex); - // Nothing to do, fill with silence - if (!_playing) { - memset(buffer, 0, numSamples * sizeof(int16)); - return numSamples; + // Nothing to do + if (!_playing) + return; + + // Check if there's anything to do on this step + // If not, decrease the poll number and move on + if (_toPoll > 0) { + _toPoll--; + return; } - // Read samples from the OPL, polling in more music when necessary - uint32 samples = numSamples; - while (samples && _playing) { - if (_toPoll) { - const uint32 render = MIN(samples, _toPoll); - - _opl->readBuffer(buffer, render); - - buffer += render; - samples -= render; - _toPoll -= render; - - } else { - // Song ended, fill the rest with silence - if (_ended) { - memset(buffer, 0, samples * sizeof(int16)); - samples = 0; - break; - } - - // Poll more music - _toPoll = pollMusic(_first); - _first = false; + // Poll until we have to delay until the next poll + while (_toPoll == 0 && _playing) { + // Song ended, break out + if (_ended) { + _toPoll = 0; + break; } + + // Poll more music + _toPoll = pollMusic(_first); + _first = false; } // Song ended, loop if requested @@ -195,24 +206,6 @@ int AdLib::readBuffer(int16 *buffer, const int numSamples) { } else _playing = false; } - - return numSamples; -} - -bool AdLib::isStereo() const { - return _opl->isStereo(); -} - -bool AdLib::endOfData() const { - return !_playing; -} - -bool AdLib::endOfStream() const { - return false; -} - -int AdLib::getRate() const { - return _rate; } bool AdLib::isPlaying() const { @@ -231,10 +224,6 @@ void AdLib::setRepeating(int32 repCount) { _repCount = repCount; } -uint32 AdLib::getSamplesPerSecond() const { - return _rate * (isStereo() ? 2 : 1); -} - void AdLib::startPlay() { Common::StackLock slock(_mutex); @@ -442,6 +431,13 @@ void AdLib::writeKeyScaleLevelVolume(uint8 oper) { volume = (63 - (_operatorParams[oper][kParamLevel] & 0x3F)) * _operatorVolume[oper]; volume = 63 - ((2 * volume + kMaxVolume) / (2 * kMaxVolume)); + // Adjust carriers for GUI volume slider + if (kOperatorType[oper] == 1) { + volume += kVolumeTable[_volume]; + if (volume > 63) + volume = 63; + } + uint8 keyScale = _operatorParams[oper][kParamKeyScaleLevel] << 6; writeOPL(0x40 + kOperatorOffset[oper], volume | keyScale); @@ -639,4 +635,23 @@ void AdLib::setFreq(uint8 voice, uint16 note, bool on) { writeOPL(0xB0 + voice, value); } +void AdLib::setTimerFrequency(int timerFrequency) { + _opl->setCallbackFrequency(timerFrequency); +} + +void AdLib::syncVolume() { + Common::StackLock slock(_mutex); + + bool mute = false; + if (ConfMan.hasKey("mute")) + mute = ConfMan.getBool("mute"); + + _volume = (mute ? 0 : ConfMan.getInt("music_volume")); + + if (_playing) { + for(int i = 0; i < kOperatorCount; i++) + writeKeyScaleLevelVolume(i); + } +} + } // End of namespace Gob diff --git a/engines/gob/sound/adlib.h b/engines/gob/sound/adlib.h index 8071249374..d60458295c 100644 --- a/engines/gob/sound/adlib.h +++ b/engines/gob/sound/adlib.h @@ -35,9 +35,9 @@ namespace OPL { namespace Gob { /** Base class for a player of an AdLib music format. */ -class AdLib : public Audio::AudioStream { +class AdLib { public: - AdLib(Audio::Mixer &mixer); + AdLib(int callbackFrequency); virtual ~AdLib(); bool isPlaying() const; ///< Are we currently playing? @@ -53,13 +53,7 @@ public: void startPlay(); void stopPlay(); - -// AudioStream API - int readBuffer(int16 *buffer, const int numSamples); - bool isStereo() const; - bool endOfData() const; - bool endOfStream() const; - int getRate() const; + void syncVolume(); protected: enum kVoice { @@ -120,8 +114,6 @@ protected: static const int kOPLMidC = 48; ///< A mid C for the OPL. - /** Return the number of samples per second. */ - uint32 getSamplesPerSecond() const; /** Write a value into an OPL register. */ void writeOPL(byte reg, byte val); @@ -135,7 +127,7 @@ protected: /** The callback function that's called for polling more AdLib commands. * * @param first Is this the first poll since the start of the song? - * @return The number of samples until the next poll. + * @return The number of ticks until the next poll. */ virtual uint32 pollMusic(bool first) = 0; @@ -207,7 +199,14 @@ protected: /** Switch a voice off. */ void noteOff(uint8 voice); + /** + * Set the OPL timer frequency + */ + void setTimerFrequency(int timerFrequency); + private: + static const uint8 kVolumeTable[Audio::Mixer::kMaxMixerVolume + 1]; + static const uint8 kOperatorType [kOperatorCount]; static const uint8 kOperatorOffset[kOperatorCount]; static const uint8 kOperatorVoice [kOperatorCount]; @@ -226,13 +225,11 @@ private: static const uint16 kHihatParams [kParamCount]; - Audio::Mixer *_mixer; - Audio::SoundHandle _handle; OPL::OPL *_opl; Common::Mutex _mutex; - uint32 _rate; + int _volume; uint32 _toPoll; @@ -300,6 +297,11 @@ private: void changePitch(uint8 voice, uint16 pitchBend); void setFreq(uint8 voice, uint16 note, bool on); + + /** + * Callback function for OPL + */ + void onTimer(); }; } // End of namespace Gob diff --git a/engines/gob/sound/adlplayer.cpp b/engines/gob/sound/adlplayer.cpp index 384a928360..6354d8c37f 100644 --- a/engines/gob/sound/adlplayer.cpp +++ b/engines/gob/sound/adlplayer.cpp @@ -28,7 +28,7 @@ namespace Gob { -ADLPlayer::ADLPlayer(Audio::Mixer &mixer) : AdLib(mixer), +ADLPlayer::ADLPlayer() : AdLib(1000), _songData(0), _songDataSize(0), _playPos(0) { } @@ -135,14 +135,7 @@ uint32 ADLPlayer::pollMusic(bool first) { if (delay & 0x80) delay = ((delay & 3) << 8) | *_playPos++; - return getSampleDelay(delay); -} - -uint32 ADLPlayer::getSampleDelay(uint16 delay) const { - if (delay == 0) - return 0; - - return ((uint32)delay * getSamplesPerSecond()) / 1000; + return delay; } void ADLPlayer::rewind() { diff --git a/engines/gob/sound/adlplayer.h b/engines/gob/sound/adlplayer.h index 3edd238343..50e1db5b70 100644 --- a/engines/gob/sound/adlplayer.h +++ b/engines/gob/sound/adlplayer.h @@ -36,7 +36,7 @@ namespace Gob { /** A player for Coktel Vision's ADL music format. */ class ADLPlayer : public AdLib { public: - ADLPlayer(Audio::Mixer &mixer); + ADLPlayer(); ~ADLPlayer(); bool load(Common::SeekableReadStream &adl); @@ -76,8 +76,6 @@ private: bool readHeader (Common::SeekableReadStream &adl, int &timbreCount); bool readTimbres (Common::SeekableReadStream &adl, int timbreCount); bool readSongData(Common::SeekableReadStream &adl); - - uint32 getSampleDelay(uint16 delay) const; }; } // End of namespace Gob diff --git a/engines/gob/sound/musplayer.cpp b/engines/gob/sound/musplayer.cpp index 7001a5724b..dcbb712b56 100644 --- a/engines/gob/sound/musplayer.cpp +++ b/engines/gob/sound/musplayer.cpp @@ -27,7 +27,7 @@ namespace Gob { -MUSPlayer::MUSPlayer(Audio::Mixer &mixer) : AdLib(mixer), +MUSPlayer::MUSPlayer() : AdLib(60), _songData(0), _songDataSize(0), _playPos(0), _songID(0) { } @@ -43,15 +43,6 @@ void MUSPlayer::unload() { unloadMUS(); } -uint32 MUSPlayer::getSampleDelay(uint16 delay) const { - if (delay == 0) - return 0; - - uint32 freq = (_ticksPerBeat * _tempo) / 60; - - return ((uint32)delay * getSamplesPerSecond()) / freq; -} - void MUSPlayer::skipToTiming() { while (*_playPos < 0x80) _playPos++; @@ -66,8 +57,12 @@ uint32 MUSPlayer::pollMusic(bool first) { return 0; } - if (first) - return getSampleDelay(*_playPos++); + if (first) { + // Set the timer frequency on first run. + // Do not set it in rewind() for thread safety reasons. + setTimerFrequency((_ticksPerBeat * _tempo) / 60); + return *_playPos++; + } uint16 delay = 0; while (delay == 0) { @@ -100,6 +95,7 @@ uint32 MUSPlayer::pollMusic(bool first) { uint32 denom = *_playPos++; _tempo = _baseTempo * num + ((_baseTempo * denom) >> 7); + setTimerFrequency((_ticksPerBeat * _tempo) / 60); _playPos++; } else { @@ -182,7 +178,7 @@ uint32 MUSPlayer::pollMusic(bool first) { delay += *_playPos++; } - return getSampleDelay(delay); + return delay; } void MUSPlayer::rewind() { diff --git a/engines/gob/sound/musplayer.h b/engines/gob/sound/musplayer.h index c76c5aab38..973192ffd9 100644 --- a/engines/gob/sound/musplayer.h +++ b/engines/gob/sound/musplayer.h @@ -40,7 +40,7 @@ namespace Gob { */ class MUSPlayer : public AdLib { public: - MUSPlayer(Audio::Mixer &mixer); + MUSPlayer(); ~MUSPlayer(); /** Load the instruments (.SND or .TBR) */ @@ -97,7 +97,6 @@ private: bool readMUSHeader(Common::SeekableReadStream &mus); bool readMUSSong (Common::SeekableReadStream &mus); - uint32 getSampleDelay(uint16 delay) const; void setInstrument(uint8 voice, uint8 instrument); void skipToTiming(); diff --git a/engines/gob/sound/sound.cpp b/engines/gob/sound/sound.cpp index d2b2d3d6e8..22dfe9d3c3 100644 --- a/engines/gob/sound/sound.cpp +++ b/engines/gob/sound/sound.cpp @@ -234,7 +234,7 @@ bool Sound::adlibLoadADL(const char *fileName) { return false; if (!_adlPlayer) - _adlPlayer = new ADLPlayer(*_vm->_mixer); + _adlPlayer = new ADLPlayer(); debugC(1, kDebugSound, "AdLib: Loading ADL data (\"%s\")", fileName); @@ -256,7 +256,7 @@ bool Sound::adlibLoadADL(byte *data, uint32 size, int index) { return false; if (!_adlPlayer) - _adlPlayer = new ADLPlayer(*_vm->_mixer); + _adlPlayer = new ADLPlayer(); debugC(1, kDebugSound, "AdLib: Loading ADL data (%d)", index); @@ -425,6 +425,16 @@ int32 Sound::adlibGetRepeating() const { return false; } +void Sound::adlibSyncVolume() { + if (!_hasAdLib) + return; + + if (_adlPlayer) + _adlPlayer->syncVolume(); + if (_mdyPlayer) + _mdyPlayer->syncVolume(); +} + void Sound::adlibSetRepeating(int32 repCount) { if (!_hasAdLib) return; @@ -739,7 +749,7 @@ void Sound::createMDYPlayer() { delete _adlPlayer; _adlPlayer = 0; - _mdyPlayer = new MUSPlayer(*_vm->_mixer); + _mdyPlayer = new MUSPlayer(); } void Sound::createADLPlayer() { @@ -749,7 +759,7 @@ void Sound::createADLPlayer() { delete _mdyPlayer; _mdyPlayer= 0; - _adlPlayer = new ADLPlayer(*_vm->_mixer); + _adlPlayer = new ADLPlayer(); } } // End of namespace Gob diff --git a/engines/gob/sound/sound.h b/engines/gob/sound/sound.h index c959959755..6ebc323b18 100644 --- a/engines/gob/sound/sound.h +++ b/engines/gob/sound/sound.h @@ -96,6 +96,7 @@ public: int32 adlibGetRepeating() const; void adlibSetRepeating(int32 repCount); + void adlibSyncVolume(); // Infogrames diff --git a/engines/groovie/music.cpp b/engines/groovie/music.cpp index 2c164e020c..c00290b155 100644 --- a/engines/groovie/music.cpp +++ b/engines/groovie/music.cpp @@ -36,6 +36,7 @@ #include "common/textconsole.h" #include "audio/audiostream.h" #include "audio/midiparser.h" +#include "audio/miles.h" namespace Groovie { @@ -379,14 +380,56 @@ bool MusicPlayerMidi::loadParser(Common::SeekableReadStream *stream, bool loop) MusicPlayerXMI::MusicPlayerXMI(GroovieEngine *vm, const Common::String >lName) : MusicPlayerMidi(vm) { - // Create the parser - _midiParser = MidiParser::createParser_XMIDI(); // Create the driver MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM); - _driver = MidiDriver::createMidi(dev); + MusicType musicType = MidiDriver::getMusicType(dev); + _driver = NULL; + + // new Miles Audio support, to disable set milesAudioEnabled to false + _milesAudioMode = false; + bool milesAudioEnabled = true; + MidiParser::XMidiNewTimbreListProc newTimbreListProc = NULL; + + if (milesAudioEnabled) { + // 7th Guest uses FAT.AD/FAT.OPL/FAT.MT + // 11th Hour uses SAMPLE.AD/SAMPLE.OPL/SAMPLE.MT + switch (musicType) { + case MT_ADLIB: + _driver = Audio::MidiDriver_Miles_AdLib_create(gtlName + ".AD", gtlName + ".OPL"); + break; + case MT_MT32: + _driver = Audio::MidiDriver_Miles_MT32_create(gtlName + ".MT"); + break; + case MT_GM: + if (ConfMan.getBool("native_mt32")) { + _driver = Audio::MidiDriver_Miles_MT32_create(gtlName + ".MT"); + musicType = MT_MT32; + } + break; + default: + break; + } + + if (musicType == MT_MT32) { + newTimbreListProc = Audio::MidiDriver_Miles_MT32_processXMIDITimbreChunk; + } + } + + if (_driver) { + _milesAudioMode = true; + } + + if (!_driver) { + // No driver yet? create a generic one + _driver = MidiDriver::createMidi(dev); + } + assert(_driver); + // Create the parser + _midiParser = MidiParser::createParser_XMIDI(NULL, NULL, newTimbreListProc, _driver); + _driver->open(); // TODO: Handle return value != 0 (indicating an error) // Set the parser's driver @@ -400,6 +443,9 @@ MusicPlayerXMI::MusicPlayerXMI(GroovieEngine *vm, const Common::String >lName) _chanBanks[i] = 0; } + if (_milesAudioMode) + return; + // Load the Global Timbre Library if (MidiDriver::getMusicType(dev) == MT_ADLIB) { // MIDI through AdLib @@ -433,6 +479,11 @@ MusicPlayerXMI::~MusicPlayerXMI() { } void MusicPlayerXMI::send(uint32 b) { + if (_milesAudioMode) { + MusicPlayerMidi::send(b); + return; + } + if ((b & 0xFFF0) == 0x72B0) { // XMIDI Patch Bank Select 114 // From AIL2's documentation: XMIDI Patch Bank Select controller (114) // selects a bank to be used when searching the next patches diff --git a/engines/groovie/music.h b/engines/groovie/music.h index 4853840673..dcb91d42a8 100644 --- a/engines/groovie/music.h +++ b/engines/groovie/music.h @@ -134,6 +134,8 @@ private: // Output music type uint8 _musicType; + bool _milesAudioMode; + // Timbres class Timbre { public: diff --git a/engines/kyra/sound_adlib.cpp b/engines/kyra/sound_adlib.cpp index c0e0f67b8e..1d741d8bd0 100644 --- a/engines/kyra/sound_adlib.cpp +++ b/engines/kyra/sound_adlib.cpp @@ -55,7 +55,7 @@ namespace Kyra { -class AdLibDriver : public Audio::AudioStream { +class AdLibDriver { public: AdLibDriver(Audio::Mixer *mixer, int version); ~AdLibDriver(); @@ -70,34 +70,6 @@ public: void callback(); - // AudioStream API - int readBuffer(int16 *buffer, const int numSamples) { - int32 samplesLeft = numSamples; - memset(buffer, 0, sizeof(int16) * numSamples); - while (samplesLeft) { - if (!_samplesTillCallback) { - callback(); - _samplesTillCallback = _samplesPerCallback; - _samplesTillCallbackRemainder += _samplesPerCallbackRemainder; - if (_samplesTillCallbackRemainder >= CALLBACKS_PER_SECOND) { - _samplesTillCallback++; - _samplesTillCallbackRemainder -= CALLBACKS_PER_SECOND; - } - } - - int32 render = MIN(samplesLeft, _samplesTillCallback); - samplesLeft -= render; - _samplesTillCallback -= render; - YM3812UpdateOne(_adlib, buffer, render); - buffer += render; - } - return numSamples; - } - - bool isStereo() const { return false; } - bool endOfData() const { return false; } - int getRate() const { return _mixer->getOutputRate(); } - void setSyncJumpMask(uint16 mask) { _syncJumpMask = mask; } void setMusicVolume(uint8 volume); @@ -334,11 +306,6 @@ private: // _unkTable2_2[] - One of the tables in _unkTable2[] // _unkTable2_3[] - One of the tables in _unkTable2[] - int32 _samplesPerCallback; - int32 _samplesPerCallbackRemainder; - int32 _samplesTillCallback; - int32 _samplesTillCallbackRemainder; - int _curChannel; uint8 _soundTrigger; @@ -365,7 +332,7 @@ private: uint8 _unkValue19; uint8 _unkValue20; - FM_OPL *_adlib; + OPL::OPL *_adlib; uint8 *_soundData; uint32 _soundDataSize; @@ -411,7 +378,6 @@ private: Common::Mutex _mutex; Audio::Mixer *_mixer; - Audio::SoundHandle _soundHandle; uint8 _musicVolume, _sfxVolume; @@ -427,8 +393,9 @@ AdLibDriver::AdLibDriver(Audio::Mixer *mixer, int version) { _mixer = mixer; - _adlib = makeAdLibOPL(getRate()); - assert(_adlib); + _adlib = OPL::Config::create(); + if (!_adlib || !_adlib->init()) + error("Failed to create OPL"); memset(_channels, 0, sizeof(_channels)); _soundData = 0; @@ -451,13 +418,6 @@ AdLibDriver::AdLibDriver(Audio::Mixer *mixer, int version) { _tablePtr1 = _tablePtr2 = 0; - _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); - - _samplesPerCallback = getRate() / CALLBACKS_PER_SECOND; - _samplesPerCallbackRemainder = getRate() % CALLBACKS_PER_SECOND; - _samplesTillCallback = 0; - _samplesTillCallbackRemainder = 0; - _syncJumpMask = 0; _musicVolume = 0; @@ -467,11 +427,12 @@ AdLibDriver::AdLibDriver(Audio::Mixer *mixer, int version) { _programQueueStart = _programQueueEnd = 0; _retrySounds = false; + + _adlib->start(new Common::Functor0Mem<void, AdLibDriver>(this, &AdLibDriver::callback), CALLBACKS_PER_SECOND); } AdLibDriver::~AdLibDriver() { - _mixer->stopHandle(_soundHandle); - OPLDestroy(_adlib); + delete _adlib; _adlib = 0; } @@ -877,7 +838,7 @@ void AdLibDriver::resetAdLibState() { // New calling style: writeOPL(0xAB, 0xCD) void AdLibDriver::writeOPL(byte reg, byte val) { - OPLWriteReg(_adlib, reg, val); + _adlib->writeReg(reg, val); } void AdLibDriver::initChannel(Channel &channel) { diff --git a/engines/lure/res.h b/engines/lure/res.h index 9002ca3056..19fbde1aad 100644 --- a/engines/lure/res.h +++ b/engines/lure/res.h @@ -100,7 +100,6 @@ public: static Resources &getReference(); void reset(); - byte *getResource(uint16 resId); RoomDataList &roomData() { return _roomData; } RoomData *getRoom(uint16 roomNumber); bool checkHotspotExtent(HotspotData *hotspot); diff --git a/engines/made/made.cpp b/engines/made/made.cpp index af8156fc3e..ab07ef757b 100644 --- a/engines/made/made.cpp +++ b/engines/made/made.cpp @@ -275,7 +275,7 @@ void MadeEngine::handleEvents() { } Common::Error MadeEngine::run() { - _music = new MusicPlayer(); + _music = new MusicPlayer(getGameID() == GID_RTZ); syncSoundSettings(); // Initialize backend diff --git a/engines/made/music.cpp b/engines/made/music.cpp index b2917b58ed..f57da833c2 100644 --- a/engines/made/music.cpp +++ b/engines/made/music.cpp @@ -25,27 +25,67 @@ // MIDI and digital music class #include "made/music.h" +#include "made/redreader.h" #include "made/resource.h" #include "audio/midiparser.h" +#include "audio/miles.h" + +#include "common/file.h" +#include "common/stream.h" namespace Made { -MusicPlayer::MusicPlayer() : _isGM(false) { - MidiPlayer::createDriver(); +MusicPlayer::MusicPlayer(bool milesAudio) : _isGM(false),_milesAudioMode(false) { + MusicType musicType = MT_INVALID; + if (milesAudio) { + MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MT32); + musicType = MidiDriver::getMusicType(dev); + Common::SeekableReadStream *adLibInstrumentStream = nullptr; + switch (musicType) { + case MT_ADLIB: + _milesAudioMode = true; + if (Common::File::exists("rtzcd.red")) { + // Installing Return to Zork produces both a SAMPLE.AD and + // a SAMPLE.OPL file, but they are identical. The resource + // file appears to only contain SAMPLE.AD. + adLibInstrumentStream = RedReader::loadFromRed("rtzcd.red", "SAMPLE.AD"); + } + _driver = Audio::MidiDriver_Miles_AdLib_create("SAMPLE.AD", "SAMPLE.OPL", adLibInstrumentStream); + delete adLibInstrumentStream; + break; + case MT_MT32: + _milesAudioMode = true; + _driver = Audio::MidiDriver_Miles_MT32_create(""); + break; + default: + _milesAudioMode = false; + MidiPlayer::createDriver(); + break; + } + } else { + MidiPlayer::createDriver(); + } int ret = _driver->open(); if (ret == 0) { - if (_nativeMT32) - _driver->sendMT32Reset(); - else - _driver->sendGMReset(); + if (musicType != MT_ADLIB) { + if (_nativeMT32) + _driver->sendMT32Reset(); + else + _driver->sendGMReset(); + } _driver->setTimerCallback(this, &timerCallback); } } void MusicPlayer::send(uint32 b) { + if (_milesAudioMode) { + _driver->send(b); + return; + } + if ((b & 0xF0) == 0xC0 && !_isGM && !_nativeMT32) { b = (b & 0xFFFF00FF) | MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8; } diff --git a/engines/made/music.h b/engines/made/music.h index 95d1bd35c1..558b41c2e2 100644 --- a/engines/made/music.h +++ b/engines/made/music.h @@ -38,7 +38,7 @@ enum MusicFlags { class MusicPlayer : public Audio::MidiPlayer { public: - MusicPlayer(); + MusicPlayer(bool milesAudio); void playXMIDI(GenericResource *midiResource, MusicFlags flags = MUSIC_NORMAL); void playSMF(GenericResource *midiResource, MusicFlags flags = MUSIC_NORMAL); @@ -51,6 +51,7 @@ public: protected: bool _isGM; + bool _milesAudioMode; }; } // End of namespace Made diff --git a/engines/made/pmvplayer.cpp b/engines/made/pmvplayer.cpp index 6ea0dc24d0..453e2a4872 100644 --- a/engines/made/pmvplayer.cpp +++ b/engines/made/pmvplayer.cpp @@ -118,6 +118,8 @@ bool PmvPlayer::play(const char *filename) { // get it to work well? _audioStream = Audio::makeQueuingAudioStream(soundFreq, false); + SoundDecoderData *soundDecoderData = new SoundDecoderData(); + while (!_vm->shouldQuit() && !_aborted && !_fd->eos() && frameNumber < frameCount) { int32 frameTime = _vm->_system->getMillis(); @@ -153,7 +155,7 @@ bool PmvPlayer::play(const char *filename) { soundSize = chunkCount * chunkSize; soundData = (byte *)malloc(soundSize); - decompressSound(audioData + 8, soundData, chunkSize, chunkCount); + decompressSound(audioData + 8, soundData, chunkSize, chunkCount, NULL, soundDecoderData); _audioStream->queueBuffer(soundData, soundSize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED); } @@ -213,6 +215,7 @@ bool PmvPlayer::play(const char *filename) { } + delete soundDecoderData; delete[] frameData; _audioStream->finish(); diff --git a/engines/made/screenfx.cpp b/engines/made/screenfx.cpp index 3f98cbb9ab..bae59f05cc 100644 --- a/engines/made/screenfx.cpp +++ b/engines/made/screenfx.cpp @@ -201,7 +201,7 @@ void ScreenEffects::startBlendedPalette(byte *palette, byte *newPalette, int col } void ScreenEffects::stepBlendedPalette() { - if (_blendedPaletteStatus._active && _blendedPaletteStatus._value < _blendedPaletteStatus._maxValue) { + if (_blendedPaletteStatus._active && _blendedPaletteStatus._value <= _blendedPaletteStatus._maxValue) { setBlendedPalette(_blendedPaletteStatus._palette, _blendedPaletteStatus._newPalette, _blendedPaletteStatus._colorCount, _blendedPaletteStatus._value, _blendedPaletteStatus._maxValue); if (_blendedPaletteStatus._value == _blendedPaletteStatus._maxValue) diff --git a/engines/made/sound.cpp b/engines/made/sound.cpp index 91e855cbf5..ad49031e7b 100644 --- a/engines/made/sound.cpp +++ b/engines/made/sound.cpp @@ -133,10 +133,10 @@ void ManholeEgaSoundDecompressor::update3() { _sample2 += _sample1; } -void decompressSound(byte *source, byte *dest, uint16 chunkSize, uint16 chunkCount, SoundEnergyArray *soundEnergyArray) { +void decompressSound(byte *source, byte *dest, uint16 chunkSize, uint16 chunkCount, SoundEnergyArray *soundEnergyArray, SoundDecoderData *soundDecoderData) { - int16 prevSample = 0, workSample = 0; - byte soundBuffer[1025]; + int16 prevSample, workSample; + byte* soundBuffer; byte deltaSoundBuffer[1024]; int16 soundBuffer2[16]; byte deltaType, type; @@ -159,6 +159,15 @@ void decompressSound(byte *source, byte *dest, uint16 chunkSize, uint16 chunkCou if (soundEnergyArray) soundEnergyArray->clear(); + if (soundDecoderData) { + soundBuffer = soundDecoderData->_soundBuffer; + prevSample = soundDecoderData->_prevSample; + } else { + soundBuffer = new byte[1025]; + memset(soundBuffer, 0x80, 1025); + prevSample = 0; + } + while (chunkCount--) { deltaType = (*source) >> 6; workChunkSize = chunkSize; @@ -233,6 +242,11 @@ void decompressSound(byte *source, byte *dest, uint16 chunkSize, uint16 chunkCou } if (deltaType > 0) { + // NB: The original did not add this extra value at the end (as far + // as I can tell), and so technically read past the filled part of + // soundBuffer. + soundBuffer[workChunkSize] = soundBuffer[workChunkSize - 1]; + if (deltaType == 1) { for (i = 0; i < chunkSize - 1; i += 2) { l = i / 2; @@ -255,9 +269,13 @@ void decompressSound(byte *source, byte *dest, uint16 chunkSize, uint16 chunkCou prevSample = workSample; memcpy(dest, soundBuffer, chunkSize); dest += chunkSize; - } + if (soundDecoderData) { + soundDecoderData->_prevSample = prevSample; + } else { + delete[] soundBuffer; + } } } // End of namespace Made diff --git a/engines/made/sound.h b/engines/made/sound.h index 6ffca13aaa..72537322f9 100644 --- a/engines/made/sound.h +++ b/engines/made/sound.h @@ -53,7 +53,22 @@ struct SoundEnergyItem { typedef Common::Array<SoundEnergyItem> SoundEnergyArray; -void decompressSound(byte *source, byte *dest, uint16 chunkSize, uint16 chunkCount, SoundEnergyArray *soundEnergyArray = NULL); + +// Persistent data for decompressSound(). When calling decompressSound() +// repeatedly (for the same stream), pass the same SoundDecoderData object to +// ensure decoding properly resumes. +class SoundDecoderData { +public: + SoundDecoderData() { + memset(_soundBuffer, 0x80, sizeof(_soundBuffer)); + _prevSample = 0; + } + + byte _soundBuffer[1025]; + int16 _prevSample; +}; + +void decompressSound(byte *source, byte *dest, uint16 chunkSize, uint16 chunkCount, SoundEnergyArray *soundEnergyArray = NULL, SoundDecoderData *decoderData = NULL); } // End of namespace Made diff --git a/engines/mads/hotspots.cpp b/engines/mads/hotspots.cpp index bd28645504..8afef2e524 100644 --- a/engines/mads/hotspots.cpp +++ b/engines/mads/hotspots.cpp @@ -193,9 +193,8 @@ Hotspot::Hotspot(Common::SeekableReadStream &f, bool isV2) { _active = f.readByte() != 0; _cursor = (CursorType)f.readByte(); if (isV2) { - // This looks to be some sort of bitmask. Perhaps it signifies - // the valid verbs for this hotspot - f.skip(2); // unknown + f.skip(1); // cursor + f.skip(1); // syntax } _vocabId = f.readUint16LE(); _verbId = f.readUint16LE(); diff --git a/engines/mads/nebular/sound_nebular.cpp b/engines/mads/nebular/sound_nebular.cpp index 240c18f6dc..711f82a05b 100644 --- a/engines/mads/nebular/sound_nebular.cpp +++ b/engines/mads/nebular/sound_nebular.cpp @@ -21,6 +21,7 @@ */ #include "audio/audiostream.h" +#include "audio/fmopl.h" #include "audio/decoders/raw.h" #include "common/algorithm.h" #include "common/debug.h" @@ -156,7 +157,7 @@ AdlibSample::AdlibSample(Common::SeekableReadStream &s) { /*-----------------------------------------------------------------------*/ -ASound::ASound(Audio::Mixer *mixer, FM_OPL *opl, const Common::String &filename, int dataOffset) { +ASound::ASound(Audio::Mixer *mixer, OPL::OPL *opl, const Common::String &filename, int dataOffset) { // Open up the appropriate sound file if (!_soundFile.open(filename)) error("Could not open file - %s", filename.c_str()); @@ -188,11 +189,6 @@ ASound::ASound(Audio::Mixer *mixer, FM_OPL *opl, const Common::String &filename, _randomSeed = 1234; _amDep = _vibDep = _splitPoint = true; - _samplesTillCallback = 0; - _samplesTillCallbackRemainder = 0; - _samplesPerCallback = getRate() / CALLBACKS_PER_SECOND; - _samplesPerCallbackRemainder = getRate() % CALLBACKS_PER_SECOND; - for (int i = 0; i < 11; ++i) { _channelData[i]._field0 = 0; _channelData[i]._freqMask = 0; @@ -210,23 +206,19 @@ ASound::ASound(Audio::Mixer *mixer, FM_OPL *opl, const Common::String &filename, _mixer = mixer; _opl = opl; - _opl->init(getRate()); - _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, - Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); - // Initialize the Adlib adlibInit(); // Reset the adlib command0(); + + _opl->start(new Common::Functor0Mem<void, ASound>(this, &ASound::onTimer), CALLBACKS_PER_SECOND); } ASound::~ASound() { Common::List<CachedDataEntry>::iterator i; for (i = _dataCache.begin(); i != _dataCache.end(); ++i) delete[] (*i)._data; - - _mixer->stopHandle(_soundHandle); } void ASound::validate() { @@ -832,32 +824,10 @@ void ASound::updateFNumber() { write2(8, hiReg, val2); } -int ASound::readBuffer(int16 *buffer, const int numSamples) { +void ASound::onTimer() { Common::StackLock slock(_driverMutex); - - int32 samplesLeft = numSamples; - memset(buffer, 0, sizeof(int16) * numSamples); - while (samplesLeft) { - if (!_samplesTillCallback) { - poll(); - flush(); - - _samplesTillCallback = _samplesPerCallback; - _samplesTillCallbackRemainder += _samplesPerCallbackRemainder; - if (_samplesTillCallbackRemainder >= CALLBACKS_PER_SECOND) { - _samplesTillCallback++; - _samplesTillCallbackRemainder -= CALLBACKS_PER_SECOND; - } - } - - int32 render = MIN<int>(samplesLeft, _samplesTillCallback); - samplesLeft -= render; - _samplesTillCallback -= render; - - _opl->readBuffer(buffer, render); - buffer += render; - } - return numSamples; + poll(); + flush(); } void ASound::setVolume(int volume) { @@ -984,7 +954,7 @@ const ASound1::CommandPtr ASound1::_commandList[42] = { &ASound1::command40, &ASound1::command41 }; -ASound1::ASound1(Audio::Mixer *mixer, FM_OPL *opl) +ASound1::ASound1(Audio::Mixer *mixer, OPL::OPL *opl) : ASound(mixer, opl, "asound.001", 0x1520) { _cmd23Toggle = false; @@ -1285,7 +1255,7 @@ const ASound2::CommandPtr ASound2::_commandList[44] = { &ASound2::command40, &ASound2::command41, &ASound2::command42, &ASound2::command43 }; -ASound2::ASound2(Audio::Mixer *mixer, FM_OPL *opl) : ASound(mixer, opl, "asound.002", 0x15E0) { +ASound2::ASound2(Audio::Mixer *mixer, OPL::OPL *opl) : ASound(mixer, opl, "asound.002", 0x15E0) { _command12Param = 0xFD; // Load sound samples @@ -1656,7 +1626,7 @@ const ASound3::CommandPtr ASound3::_commandList[61] = { &ASound3::command60 }; -ASound3::ASound3(Audio::Mixer *mixer, FM_OPL *opl) : ASound(mixer, opl, "asound.003", 0x15B0) { +ASound3::ASound3(Audio::Mixer *mixer, OPL::OPL *opl) : ASound(mixer, opl, "asound.003", 0x15B0) { _command39Flag = false; // Load sound samples @@ -2060,7 +2030,7 @@ const ASound4::CommandPtr ASound4::_commandList[61] = { &ASound4::command60 }; -ASound4::ASound4(Audio::Mixer *mixer, FM_OPL *opl) : ASound(mixer, opl, "asound.004", 0x14F0) { +ASound4::ASound4(Audio::Mixer *mixer, OPL::OPL *opl) : ASound(mixer, opl, "asound.004", 0x14F0) { // Load sound samples _soundFile.seek(_dataOffset + 0x122); for (int i = 0; i < 210; ++i) @@ -2316,7 +2286,7 @@ const ASound5::CommandPtr ASound5::_commandList[42] = { &ASound5::command40, &ASound5::command41 }; -ASound5::ASound5(Audio::Mixer *mixer, FM_OPL *opl) : ASound(mixer, opl, "asound.002", 0x15E0) { +ASound5::ASound5(Audio::Mixer *mixer, OPL::OPL *opl) : ASound(mixer, opl, "asound.002", 0x15E0) { // Load sound samples _soundFile.seek(_dataOffset + 0x144); for (int i = 0; i < 164; ++i) @@ -2557,7 +2527,7 @@ const ASound6::CommandPtr ASound6::_commandList[30] = { &ASound6::nullCommand, &ASound6::command29 }; -ASound6::ASound6(Audio::Mixer *mixer, FM_OPL *opl) : ASound(mixer, opl, "asound.006", 0x1390) { +ASound6::ASound6(Audio::Mixer *mixer, OPL::OPL *opl) : ASound(mixer, opl, "asound.006", 0x1390) { // Load sound samples _soundFile.seek(_dataOffset + 0x122); for (int i = 0; i < 200; ++i) @@ -2713,7 +2683,7 @@ const ASound7::CommandPtr ASound7::_commandList[38] = { &ASound7::command36, &ASound7::command37 }; -ASound7::ASound7(Audio::Mixer *mixer, FM_OPL *opl) : ASound(mixer, opl, "asound.007", 0x1460) { +ASound7::ASound7(Audio::Mixer *mixer, OPL::OPL *opl) : ASound(mixer, opl, "asound.007", 0x1460) { // Load sound samples _soundFile.seek(_dataOffset + 0x122); for (int i = 0; i < 214; ++i) @@ -2919,7 +2889,7 @@ const ASound8::CommandPtr ASound8::_commandList[38] = { &ASound8::command36, &ASound8::command37 }; -ASound8::ASound8(Audio::Mixer *mixer, FM_OPL *opl) : ASound(mixer, opl, "asound.008", 0x1490) { +ASound8::ASound8(Audio::Mixer *mixer, OPL::OPL *opl) : ASound(mixer, opl, "asound.008", 0x1490) { // Load sound samples _soundFile.seek(_dataOffset + 0x122); for (int i = 0; i < 174; ++i) @@ -3175,7 +3145,7 @@ const ASound9::CommandPtr ASound9::_commandList[52] = { &ASound9::command48, &ASound9::command49, &ASound9::command50, &ASound9::command51 }; -ASound9::ASound9(Audio::Mixer *mixer, FM_OPL *opl) : ASound(mixer, opl, "asound.009", 0x16F0) { +ASound9::ASound9(Audio::Mixer *mixer, OPL::OPL *opl) : ASound(mixer, opl, "asound.009", 0x16F0) { _v1 = _v2 = 0; _soundPtr = nullptr; diff --git a/engines/mads/nebular/sound_nebular.h b/engines/mads/nebular/sound_nebular.h index 9bc1a49458..2b80b08d89 100644 --- a/engines/mads/nebular/sound_nebular.h +++ b/engines/mads/nebular/sound_nebular.h @@ -28,9 +28,12 @@ #include "common/mutex.h" #include "common/queue.h" #include "audio/audiostream.h" -#include "audio/fmopl.h" #include "audio/mixer.h" +namespace OPL { +class OPL; +} + namespace MADS { class SoundManager; @@ -142,7 +145,7 @@ struct CachedDataEntry { /** * Base class for the sound player resource files */ -class ASound : public Audio::AudioStream { +class ASound { private: Common::List<CachedDataEntry> _dataCache; uint16 _randomSeed; @@ -192,6 +195,11 @@ private: void processSample(); void updateFNumber(); + + /** + * Timer function for OPL + */ + void onTimer(); protected: int _commandParam; @@ -273,8 +281,7 @@ protected: int nullCommand() { return 0; } public: Audio::Mixer *_mixer; - FM_OPL *_opl; - Audio::SoundHandle _soundHandle; + OPL::OPL *_opl; AdlibChannel _channels[ADLIB_CHANNEL_COUNT]; AdlibChannel *_activeChannelPtr; AdlibChannelData _channelData[11]; @@ -306,10 +313,6 @@ public: int _activeChannelReg; int _v11; bool _amDep, _vibDep, _splitPoint; - int _samplesPerCallback; - int _samplesPerCallbackRemainder; - int _samplesTillCallback; - int _samplesTillCallbackRemainder; public: /** * Constructor @@ -318,7 +321,7 @@ public: * @param filename Specifies the adlib sound player file to use * @param dataOffset Offset in the file of the data segment */ - ASound(Audio::Mixer *mixer, FM_OPL *opl, const Common::String &filename, int dataOffset); + ASound(Audio::Mixer *mixer, OPL::OPL *opl, const Common::String &filename, int dataOffset); /** * Destructor @@ -363,27 +366,6 @@ public: */ CachedDataEntry &getCachedData(byte *pData); - // AudioStream interface - /** - * Main buffer read - */ - virtual int readBuffer(int16 *buffer, const int numSamples); - - /** - * Mono sound only - */ - virtual bool isStereo() const { return false; } - - /** - * Data is continuously pushed, so definitive end - */ - virtual bool endOfData() const { return false; } - - /** - * Return sample rate - */ - virtual int getRate() const { return 11025; } - /** * Set the volume */ @@ -433,7 +415,7 @@ private: void command111213(); int command2627293032(); public: - ASound1(Audio::Mixer *mixer, FM_OPL *opl); + ASound1(Audio::Mixer *mixer, OPL::OPL *opl); virtual int command(int commandId, int param); }; @@ -485,7 +467,7 @@ private: void command9Randomize(); void command9Apply(byte *data, int val, int incr); public: - ASound2(Audio::Mixer *mixer, FM_OPL *opl); + ASound2(Audio::Mixer *mixer, OPL::OPL *opl); virtual int command(int commandId, int param); }; @@ -545,7 +527,7 @@ private: void command9Randomize(); void command9Apply(byte *data, int val, int incr); public: - ASound3(Audio::Mixer *mixer, FM_OPL *opl); + ASound3(Audio::Mixer *mixer, OPL::OPL *opl); virtual int command(int commandId, int param); }; @@ -583,7 +565,7 @@ private: void method1(); public: - ASound4(Audio::Mixer *mixer, FM_OPL *opl); + ASound4(Audio::Mixer *mixer, OPL::OPL *opl); virtual int command(int commandId, int param); }; @@ -629,7 +611,7 @@ private: int command42(); int command43(); public: - ASound5(Audio::Mixer *mixer, FM_OPL *opl); + ASound5(Audio::Mixer *mixer, OPL::OPL *opl); virtual int command(int commandId, int param); }; @@ -658,7 +640,7 @@ private: int command25(); int command29(); public: - ASound6(Audio::Mixer *mixer, FM_OPL *opl); + ASound6(Audio::Mixer *mixer, OPL::OPL *opl); virtual int command(int commandId, int param); }; @@ -690,7 +672,7 @@ private: int command36(); int command37(); public: - ASound7(Audio::Mixer *mixer, FM_OPL *opl); + ASound7(Audio::Mixer *mixer, OPL::OPL *opl); virtual int command(int commandId, int param); }; @@ -733,7 +715,7 @@ private: void method1(byte *pData); void adjustRange(byte *pData, byte v, int incr); public: - ASound8(Audio::Mixer *mixer, FM_OPL *opl); + ASound8(Audio::Mixer *mixer, OPL::OPL *opl); virtual int command(int commandId, int param); }; @@ -792,7 +774,7 @@ private: int command59(); int command60(); public: - ASound9(Audio::Mixer *mixer, FM_OPL *opl); + ASound9(Audio::Mixer *mixer, OPL::OPL *opl); virtual int command(int commandId, int param); }; diff --git a/engines/mads/phantom/game_phantom.cpp b/engines/mads/phantom/game_phantom.cpp index 959a726edf..592a108aea 100644 --- a/engines/mads/phantom/game_phantom.cpp +++ b/engines/mads/phantom/game_phantom.cpp @@ -52,31 +52,74 @@ void GamePhantom::startGame() { void GamePhantom::initializeGlobals() { _globals.reset(); - warning("TODO: sub_316DA()"); + // TODO: Catacombs setup _player._facing = FACING_NORTH; _player._turnToFacing = FACING_NORTH; - _globals[kCurrentYear] = 1993; - - /* Section #1 variables */ - // TODO - - /* Section #2 variables */ - // TODO - - /* Section #3 variables */ - // TODO - - /* Section #4 variables */ - // TODO - - /* Section #5 variables */ - // TODO - - /* Section #9 variables */ - // TODO - + _globals[kTempVar] = false; + _globals[kRoom103104Transition] = 1; // new room + _globals[kCurrentYear] = 1993; + _globals[kTrapDoorStatus] = 0; // open + _globals[kChristineDoorStatus] = 0; // Christine is in her room + _globals[kSandbagStatus] = 0; // sandbag is secure + _globals[kJacquesStatus] = 0; // alive + _globals[kChrisFStatus] = 1; // Christine F. is alive in 1993 + _globals[kBrieTalkStatus] = 0; // before Brie motions + _globals[kPanelIn206] = 0; // not discovered + _globals[kFightStatus] = 0; + _globals[kJuliesDoor] = 1; // cracked open + _globals[kPrompterStandStatus] = 0; + _globals[kChrisDStatus] = 0; // before love + _globals[kJulieNameIsKnown] = 0; + _globals[kDoorsIn205] = 0; // both locked + _globals[kMadameGiryLocation] = 1; // middle + _globals[kTicketPeoplePresent] = 0; + _globals[kCoffinStatus] = 0; // closed and locked + _globals[kDoneBrieConv203] = 0; + _globals[kFlorentNameIsKnown] = 0; + _globals[kDegasNameIsKnown] = 0; + _globals[kMadameGiryShowsUp] = false; + _globals[kJacquesNameIsKnown] = 0; + _globals[kCharlesNameIsKnown] = false; + _globals[kTopFloorLocked] = true; + _globals[kMadameNameIsKnown] = 0; + _globals[kChrisKickedRaoulOut] = false; + _globals[kLookedAtCase] = false; + _globals[kRingIsOnFinger] = false; + _globals[kHeListened] = false; + _globals[kKnockedOverHead] = false; + _globals[kObservedPhan104] = false; + _globals[kReadBook] = false; + _globals[kCanFindBookInLibrary] = false; + _globals[kLookedAtSkullFace] = false; + _globals[kScannedBookcase] = false; + _globals[kRanConvIn205] = false; + _globals[kDoneRichConv203] = false; + _globals[kHintThatDaaeIsHome1] = false; + _globals[kHintThatDaaeIsHome2] = false; + _globals[kMakeBrieLeave203] = false; + _globals[kMakeRichLeave203] = false; + _globals[kCameFromFade] = false; + _globals[kChristineToldEnvelope] = false; + _globals[kLeaveAngelMusicOn] = false; + _globals[kDoorIn409IsOpen] = false; + _globals[kUnknown] = false; + _globals[kCobwebIsCut] = false; + _globals[kChristineIsInBoat] = false; + _globals[kRightDoorIsOpen504] = false; + _globals[kChrisLeft505] = false; + _globals[kChrisWillTakeSeat] = true; + _globals[kFlickedLever1] = 0; + _globals[kFlickedLever2] = 0; + _globals[kFlickedLever3] = 0; + _globals[kFlickedLever4] = 0; + _globals[kPlayerScore] = 0; + _globals[kPlayerScoreFlags] = 0; + + _globals[kMusicSelected] = _vm->getRandomNumber(1, 4); + + _player._spritesPrefix = "RAL"; // Fixed prefix Player::preloadSequences("RAL", 1); } @@ -108,6 +151,11 @@ void GamePhantom::checkShowDialog() { // TODO: Copied from Nebular if (_vm->_dialogs->_pendingDialog && _player._stepEnabled && !_globals[5]) { _player.releasePlayerSprites(); + + // HACK: Skip the main menu (since it'll then try to show Rex's main menu) + if (_vm->_dialogs->_pendingDialog == DIALOG_MAIN_MENU) + _vm->_dialogs->_pendingDialog = DIALOG_NONE; + _vm->_dialogs->showDialog(); _vm->_dialogs->_pendingDialog = DIALOG_NONE; } diff --git a/engines/mads/phantom/globals_phantom.h b/engines/mads/phantom/globals_phantom.h index 44d8c9e4bc..c23b53cdf5 100644 --- a/engines/mads/phantom/globals_phantom.h +++ b/engines/mads/phantom/globals_phantom.h @@ -33,23 +33,91 @@ namespace MADS { namespace Phantom { enum GlobalId { + // Global variables + kWalkerTiming = 0, -// kWalkerTiming0 = 1, + kWalkerTiming2 = 1, + kStopWalkerDisabled = 2, // disable walker idle animations + kTempInterface = 3, + kWalkerConverse = 4, // conversation started with an NPC + kWalkerConverseState = 5, + kWalkerConverseNow = 6, kCurrentYear = 10, // current year (1881 or 1993) - - //kTalkInanimateCount = 4, - - /* Section #1 variables */ - - /* Section #2 variables */ - - /* Section #3 Variables */ - - /* Section #4 Variables */ - - /* Section #5 Variables */ - + kMusicSelected = 11, + kPlayerScore = 12, + kPlayerScoreFlags = 13, + kDoneBrieConv203 = 14, + kLanternStatus = 15, + + // Section #1 variables + kLeaveAngelMusicOn = 19, + kTrapDoorStatus = 20, + kChristineDoorStatus = 21, + kSandbagStatus = 22, + kChrisFStatus = 23, + kBrieTalkStatus = 24, + kJuliesDoor = 25, + kPrompterStandStatus = 26, + kChrisDStatus = 27, + kJulieNameIsKnown = 28, + kChrisKickedRaoulOut = 29, + kJacquesNameIsKnown = 30, + kJacquesStatus = 31, + kFlorentNameIsKnown = 32, + kCharlesNameIsKnown = 33, + kRoom103104Transition = 34, + kObservedPhan104 = 35, + kDeathLocation = 36, + kMakeBrieLeave203 = 37, + kHintThatDaaeIsHome1 = 38, + kHintThatDaaeIsHome2 = 39, + + // Section #2 variables + kChristineToldEnvelope = 40, + kReadBook = 41, + kScannedBookcase = 42, + kRanConvIn205 = 43, + kDoorsIn205 = 44, + kPanelIn206 = 45, + kMadameNameIsKnown = 46, + kMadameGiryLocation = 47, + kLookedAtCase = 48, + kMadameGiryShowsUp = 49, + kDoneRichConv203 = 50, + kCameFromFade = 51, + kTicketPeoplePresent = 52, + kDegasNameIsKnown = 53, + kTempVar = 54, + kFlickedLever1 = 55, + kFlickedLever2 = 56, + kFlickedLever3 = 57, + kFlickedLever4 = 58, + + // Section #3 Variables + kTopFloorLocked = 60, + + // Section #4 Variables + kCatacombsRoom = 80, + // TODO + kDoorIn409IsOpen = 93, + kUnknown = 94, // TODO + kCobwebIsCut = 95, + + // Section #5 Variables + kChristineIsInBoat = 100, + kChrisWillTakeSeat = 101, + kRightDoorIsOpen504 = 102, + kCoffinStatus = 103, + kChrisLeft505 = 104, + kKnockedOverHead = 105, + kFightStatus = 106, + kHeListened = 107, + kCanFindBookInLibrary = 108, + kRingIsOnFinger = 109, + kLookedAtSkullFace = 110, + kCableHookWasSeparate = 111, + kMakeRichLeave203 = 112 }; class PhantomGlobals : public Globals { diff --git a/engines/mads/phantom/phantom_scenes.cpp b/engines/mads/phantom/phantom_scenes.cpp index 57c2bb2c9b..f7f4d154df 100644 --- a/engines/mads/phantom/phantom_scenes.cpp +++ b/engines/mads/phantom/phantom_scenes.cpp @@ -43,7 +43,7 @@ SceneLogic *SceneFactory::createScene(MADSEngine *vm) { switch (scene._nextSceneId) { // Scene group #1 (theater, stage and dressing rooms) case 101: // seats - return new DummyScene(vm); // TODO + return new Scene101(vm); case 102: // music stands return new Scene102(vm); case 103: // below stage diff --git a/engines/mads/phantom/phantom_scenes.h b/engines/mads/phantom/phantom_scenes.h index 55218f219a..c0a823ae06 100644 --- a/engines/mads/phantom/phantom_scenes.h +++ b/engines/mads/phantom/phantom_scenes.h @@ -35,7 +35,24 @@ namespace MADS { namespace Phantom { enum Verb { + VERB_LOOK = 0x3, + VERB_TAKE = 0x4, + VERB_PUSH = 0x5, + VERB_OPEN = 0x6, + VERB_PUT = 0x7, + VERB_TALK_TO = 0x8, + VERB_GIVE = 0x9, + VERB_PULL = 0xA, + VERB_CLOSE = 0xB, + VERB_THROW = 0xC, + VERB_WALK_TO = 0xD, + VERB_CLIMB_DOWN = 0x21, + VERB_CLIMB_INTO = 0x22, + VERB_CLIMB_THROUGH = 0x23, + VERB_EXIT_TO = 0x37, + VERB_JUMP_INTO = 0x53, VERB_LOOK_AT = 0x60, + VERB_LOOK_THROUGH = 0x61, VERB_TURN_OFF = 0x95, VERB_TURN_ON = 0x96, VERB_UNLOCK = 0x97, @@ -43,22 +60,14 @@ enum Verb { VERB_WALK_DOWN = 0x9A, VERB_WALK_THROUGH = 0x9B, VERB_WALK_UP = 0x9C, + VERB_CLIMB_UP = 0xA5, + VERB_WALK_ONTO = 0xA6, + VERB_WALK = 0xA7 }; enum Noun { NOUN_GAME = 0x1, NOUN_QSAVE = 0x2, - NOUN_LOOK = 0x3, - NOUN_TAKE = 0x4, - NOUN_PUSH = 0x5, - NOUN_OPEN = 0x6, - NOUN_PUT = 0x7, - NOUN_TALK_TO = 0x8, - NOUN_GIVE = 0x9, - NOUN_PULL = 0xA, - NOUN_CLOSE = 0xB, - NOUN_THROW = 0xC, - NOUN_WALK_TO = 0xD, NOUN_ = 0xE, NOUN_IN_ONE = 0xF, NOUN_IN_TWO = 0x10, @@ -78,9 +87,6 @@ enum Noun { NOUN_CEILING = 0x1E, NOUN_CHAIR = 0x1F, NOUN_CIRCULAR_STAIRCASE = 0x20, - NOUN_CLIMB_DOWN = 0x21, - NOUN_CLIMB_INTO = 0x22, - NOUN_CLIMB_THROUGH = 0x23, NOUN_COLUMN_PROP = 0x24, NOUN_CONDUCTORS_STAND = 0x25, NOUN_CORRIDOR = 0x26, @@ -100,7 +106,6 @@ enum Noun { NOUN_EXIT = 0x34, NOUN_EXIT_DOWN = 0x35, NOUN_EXIT_SIGN = 0x36, - NOUN_EXIT_TO = 0x37, NOUN_EXIT_TO_BACKSTAGE = 0x38, NOUN_EXIT_TO_CELLAR = 0x39, NOUN_EXIT_TO_CORRIDOR = 0x3A, @@ -128,7 +133,6 @@ enum Noun { NOUN_HOUSE = 0x50, NOUN_IN_ONE2 = 0x51, NOUN_IN_TWO2 = 0x52, - NOUN_JUMP_INTO = 0x53, NOUN_JUNK = 0x54, NOUN_KEY = 0x55, NOUN_LAMP = 0x56, @@ -141,7 +145,6 @@ enum Noun { NOUN_LOCK = 0x5D, NOUN_LOCKING_RAIL = 0x5E, NOUN_LOCKRAIL = 0x5F, - NOUN_LOOK_THROUGH = 0x61, NOUN_MANNEQUINS = 0x62, NOUN_MIRROR = 0x63, NOUN_MUMMY_PROP = 0x64, @@ -202,9 +205,6 @@ enum Noun { NOUN_WEDDING_RING = 0xA2, NOUN_YELLOW_FRAME = 0xA3, NOUN_PROP = 0xA4, - NOUN_CLIMB_UP = 0xA5, - NOUN_WALK_ONTO = 0xA6, - NOUN_WALK = 0xA7, NOUN_LEFT_DOOR = 0xA8, NOUN_RIGHT_DOOR = 0xA9, NOUN_DOOR_TO_PIT = 0xAA, diff --git a/engines/mads/phantom/phantom_scenes1.cpp b/engines/mads/phantom/phantom_scenes1.cpp index b4035dc68b..2d991fd3bc 100644 --- a/engines/mads/phantom/phantom_scenes1.cpp +++ b/engines/mads/phantom/phantom_scenes1.cpp @@ -42,6 +42,92 @@ void Scene1xx::sceneEntrySound() { /*------------------------------------------------------------------------*/ +Scene101::Scene101(MADSEngine *vm) : Scene1xx(vm) { + +} + +void Scene101::synchronize(Common::Serializer &s) { + Scene1xx::synchronize(s); + +} + +void Scene101::setup() { + //setPlayerSpritesPrefix(); + setAAName(); +} + +void Scene101::enter() { + // TODO + + if (_globals[kCurrentYear] == 1993) { + _globals._spriteIndexes[0] = _scene->_sprites.addSprites(formAnimName('z', -1)); + // TODO + //_scene->_sequences.setDepth(_globals._sequenceIndexes[0], 14); + } else { + // TODO + } + + // TODO +} + +void Scene101::step() { + // TODO +} + +void Scene101::preActions() { + if (_action.isAction(VERB_EXIT_TO, NOUN_ORCHESTRA_PIT)) { + // TODO: Handle Brie + _game._player._walkOffScreenSceneId = 102; + } else if (_action.isAction(VERB_EXIT_TO, NOUN_GRAND_FOYER)) { + // TODO: Handle Brie + _game._player._walkOffScreenSceneId = 202; + } else if (_action.isAction(VERB_TAKE, NOUN_MONSIEUR_BRIE)) { + _vm->_dialogs->show(10121); + } else if (_action.isAction(VERB_TALK_TO, NOUN_MONSIEUR_BRIE)) { + if (_globals[kBrieTalkStatus] == 2) + _game._player._needToWalk = false; + } + + // TODO +} + +void Scene101::actions() { + // TODO: Brie conversation + + // TODO: Look around + + if (_action.isAction(VERB_LOOK) || _action.isAction(VERB_LOOK_AT)) { + if (_action.isObject(NOUN_AISLE)) { + _vm->_dialogs->show(10112); + } else if (_action.isObject(NOUN_CHANDELIER)) { + _vm->_dialogs->show(10113); + } else if (_action.isObject(NOUN_BACK_WALL)) { + _vm->_dialogs->show(10114); + } else if (_action.isObject(NOUN_SIDE_WALL)) { + _vm->_dialogs->show(10115); + } else if (_action.isObject(NOUN_SEATS)) { + // TODO: Finish this + _vm->_dialogs->show(10116); + } else if (_action.isObject(NOUN_GRAND_FOYER)) { + _vm->_dialogs->show(10117); + } else if (_action.isObject(NOUN_ORCHESTRA_PIT)) { + _vm->_dialogs->show(10118); + } else if (_action.isObject(NOUN_MONSIEUR_BRIE)) { + _vm->_dialogs->show(10120); + } + + _game._player._stepEnabled = true; + } else if (_action.isAction(VERB_TALK_TO, NOUN_MONSIEUR_BRIE)) { + if (_globals[kBrieTalkStatus] == 2) + _vm->_dialogs->show(10122); + _game._player._stepEnabled = true; + } else if (_action.isAction(VERB_TAKE, NOUN_MONSIEUR_BRIE)) { + _game._player._stepEnabled = true; + } +} + +/*------------------------------------------------------------------------*/ + Scene102::Scene102(MADSEngine *vm) : Scene1xx(vm) { _animRunningFl = false; } @@ -60,22 +146,34 @@ void Scene102::setup() { void Scene102::enter() { _animRunningFl = false; - // TODO: Load sprite series + _globals._spriteIndexes[2] = _scene->_sprites.addSprites(formAnimName('x', 0)); + _globals._spriteIndexes[3] = _scene->_sprites.addSprites("*RAL86"); + + if (_globals[kCurrentYear] == 1993) { + _globals._spriteIndexes[0] = _scene->_sprites.addSprites(formAnimName('z', -1)); + // TODO + //_scene->_sequences.setDepth(_globals._sequenceIndexes[0], 14); + } else { + // TODO + } if (_scene->_priorSceneId == 101) { _game._player._playerPos = Common::Point(97, 79); _game._player._facing = FACING_SOUTHEAST; // TODO + _game._player.walk(Common::Point(83, 87), FACING_SOUTHEAST); + _scene->_sequences.setDepth(_globals._sequenceIndexes[2], 14); } else if (_scene->_priorSceneId == 104) { // Player fell from pit -> death // TODO } else if (_scene->_priorSceneId == 103 || _scene->_priorSceneId != -1) { _game._player._playerPos = Common::Point(282, 145); _game._player._facing = FACING_WEST; - // TODO: Door closing animation _animRunningFl = true; + // TODO: Door closing animation } else if (_scene->_priorSceneId == -1) { // TODO + _scene->_sequences.setDepth(_globals._sequenceIndexes[2], 14); } sceneEntrySound(); @@ -109,6 +207,8 @@ void Scene102::actions() { if (_animRunningFl) { // TODO } else { + _scene->_nextSceneId = 103; // FIXME: temporary HACK - remove! + switch (_game._trigger) { case 70: // try again case 0: diff --git a/engines/mads/phantom/phantom_scenes1.h b/engines/mads/phantom/phantom_scenes1.h index ef2cdb7aa0..0f5f56a4cf 100644 --- a/engines/mads/phantom/phantom_scenes1.h +++ b/engines/mads/phantom/phantom_scenes1.h @@ -53,6 +53,21 @@ public: Scene1xx(MADSEngine *vm) : PhantomScene(vm) {} }; +class Scene101 : public Scene1xx { +private: + // TODO + +public: + Scene101(MADSEngine *vm); + virtual void synchronize(Common::Serializer &s); + + virtual void setup(); + virtual void enter(); + virtual void step(); + virtual void preActions(); + virtual void actions(); +}; + class Scene102 : public Scene1xx { private: bool _animRunningFl; diff --git a/engines/mads/sound.cpp b/engines/mads/sound.cpp index 7b9388eee3..4a35edb80f 100644 --- a/engines/mads/sound.cpp +++ b/engines/mads/sound.cpp @@ -21,6 +21,7 @@ */ #include "audio/audiostream.h" +#include "audio/fmopl.h" #include "audio/decoders/raw.h" #include "common/memstream.h" #include "mads/sound.h" @@ -39,7 +40,7 @@ SoundManager::SoundManager(MADSEngine *vm, Audio::Mixer *mixer) { _masterVolume = 255; _opl = OPL::Config::create(); - _opl->init(11025); + _opl->init(); // Validate sound files switch (_vm->getGameID()) { diff --git a/engines/mads/sound.h b/engines/mads/sound.h index 16128f8284..9882f65e5a 100644 --- a/engines/mads/sound.h +++ b/engines/mads/sound.h @@ -37,7 +37,7 @@ class SoundManager { private: MADSEngine *_vm; Audio::Mixer *_mixer; - FM_OPL *_opl; + OPL::OPL *_opl; Nebular::ASound *_driver; bool _pollSoundEnabled; bool _soundPollFlag; diff --git a/engines/mohawk/console.cpp b/engines/mohawk/console.cpp index d49f3e8637..9b5bae78be 100644 --- a/engines/mohawk/console.cpp +++ b/engines/mohawk/console.cpp @@ -248,9 +248,9 @@ bool MystConsole::Cmd_PlayMovie(int argc, const char **argv) { return true; } - int8 stackNum = 0; - + Common::String fileName; if (argc == 3 || argc > 4) { + int8 stackNum = 0; for (byte i = 1; i <= ARRAYSIZE(mystStackNames); i++) if (!scumm_stricmp(argv[2], mystStackNames[i - 1])) { stackNum = i; @@ -261,16 +261,27 @@ bool MystConsole::Cmd_PlayMovie(int argc, const char **argv) { debugPrintf("\'%s\' is not a stack name!\n", argv[2]); return true; } + + fileName = _vm->wrapMovieFilename(argv[1], stackNum - 1); + } else { + fileName = argv[1]; } - if (argc == 2) - _vm->_video->playMovie(argv[1], 0, 0); - else if (argc == 3) - _vm->_video->playMovie(_vm->wrapMovieFilename(argv[1], stackNum - 1), 0, 0); - else if (argc == 4) - _vm->_video->playMovie(argv[1], atoi(argv[2]), atoi(argv[3])); - else - _vm->_video->playMovie(_vm->wrapMovieFilename(argv[1], stackNum - 1), atoi(argv[3]), atoi(argv[4])); + VideoHandle handle = _vm->_video->playMovie(fileName); + if (!handle) { + debugPrintf("Failed to open movie '%s'\n", fileName.c_str()); + return true; + } + + if (argc == 4) { + handle->setX(atoi(argv[2])); + handle->setY(atoi(argv[3])); + } else if (argc > 4) { + handle->setX(atoi(argv[3])); + handle->setY(atoi(argv[4])); + } else { + handle->center(); + } return false; } diff --git a/engines/mohawk/detection_tables.h b/engines/mohawk/detection_tables.h index 7632cde294..6bb836b5b8 100644 --- a/engines/mohawk/detection_tables.h +++ b/engines/mohawk/detection_tables.h @@ -1379,6 +1379,22 @@ static const MohawkGameDescription gameDescriptions[] = { "GRANDMA.EXE" }, + // Just Grandma and Me 1.0, Macintosh + { + { + "grandma", + "v1.0", + AD_ENTRY1("BookOutline", "9162483da06179e76f4a082412245efa"), + Common::EN_ANY, + Common::kPlatformMacintosh, + ADGF_NO_FLAGS, + GUIO1(GUIO_NOASPECT) + }, + GType_LIVINGBOOKSV1, + GF_LB_10, + 0 + }, + // Just Grandma and Me 1.1 Mac // From eisnerguy1 in bug#3610725 { diff --git a/engines/mohawk/livingbooks.cpp b/engines/mohawk/livingbooks.cpp index 998ef048f6..5af8fac901 100644 --- a/engines/mohawk/livingbooks.cpp +++ b/engines/mohawk/livingbooks.cpp @@ -363,6 +363,44 @@ void MohawkEngine_LivingBooks::destroyPage() { _focus = NULL; } +// Replace any colons (originally a slash) with another character +static Common::String replaceColons(const Common::String &in, char replace) { + Common::String out; + + for (uint32 i = 0; i < in.size(); i++) { + if (in[i] == ':') + out += replace; + else + out += in[i]; + } + + return out; +} + +// Helper function to assist in opening pages +static bool tryOpenPage(Archive *archive, const Common::String &fileName) { + // Try the plain file name first + if (archive->openFile(fileName)) + return true; + + // No colons, then bail out + if (!fileName.contains(':')) + return false; + + // Try replacing colons with underscores (in case the original was + // a Mac version and had slashes not as a separator). + if (archive->openFile(replaceColons(fileName, '_'))) + return true; + + // Try replacing colons with slashes (in case the original was a Mac + // version and had slashes as a separator). + if (archive->openFile(replaceColons(fileName, '/'))) + return true; + + // Failed to open the archive + return false; +} + bool MohawkEngine_LivingBooks::loadPage(LBMode mode, uint page, uint subpage) { destroyPage(); @@ -410,7 +448,7 @@ bool MohawkEngine_LivingBooks::loadPage(LBMode mode, uint page, uint subpage) { } Archive *pageArchive = createArchive(); - if (!filename.empty() && pageArchive->openFile(filename)) { + if (!filename.empty() && tryOpenPage(pageArchive, filename)) { _page = new LBPage(this); _page->open(pageArchive, 1000); } else { @@ -824,18 +862,18 @@ int MohawkEngine_LivingBooks::getIntFromConfig(const Common::String §ion, co Common::String MohawkEngine_LivingBooks::getFileNameFromConfig(const Common::String §ion, const Common::String &key, Common::String &leftover) { Common::String string = getStringFromConfig(section, key, leftover); - Common::String x; - uint32 i = 0; if (string.hasPrefix("//")) { // skip "//CD-ROM Title/" prefixes which we don't care about - i = 3; + uint i = 3; while (i < string.size() && string[i - 1] != '/') i++; + + // Already uses slashes, no need to convert further + return string.c_str() + i; } - x = string.c_str() + i; - return (getPlatform() == Common::kPlatformMacintosh) ? convertMacFileName(x) : convertWinFileName(x); + return (getPlatform() == Common::kPlatformMacintosh) ? convertMacFileName(string) : convertWinFileName(string); } Common::String MohawkEngine_LivingBooks::removeQuotesFromString(const Common::String &string, Common::String &leftover) { @@ -866,8 +904,10 @@ Common::String MohawkEngine_LivingBooks::convertMacFileName(const Common::String for (uint32 i = 0; i < string.size(); i++) { if (i == 0 && string[i] == ':') // First character should be ignored (another colon) continue; - if (string[i] == ':') + if (string[i] == ':') // Directory separator filename += '/'; + else if (string[i] == '/') // Literal slash + filename += ':'; // Replace by colon, as used by Mac OS X for slash else filename += string[i]; } @@ -3772,7 +3812,7 @@ LBMovieItem::~LBMovieItem() { void LBMovieItem::update() { if (_playing) { VideoHandle videoHandle = _vm->_video->findVideoHandle(_resourceId); - if (videoHandle == NULL_VID_HANDLE || _vm->_video->endOfVideo(videoHandle)) + if (!videoHandle || videoHandle->endOfVideo()) done(true); } @@ -3783,8 +3823,11 @@ bool LBMovieItem::togglePlaying(bool playing, bool restart) { if (playing) { if ((_loaded && _enabled && _globalEnabled) || _phase == kLBPhaseNone) { debug("toggled video for phase %d", _phase); - _vm->_video->playMovie(_resourceId, _rect.left, _rect.top); + VideoHandle handle = _vm->_video->playMovie(_resourceId); + if (!handle) + error("Failed to open tMOV %d", _resourceId); + handle->moveTo(_rect.left, _rect.top); return true; } } @@ -3808,9 +3851,9 @@ bool LBMiniGameItem::togglePlaying(bool playing, bool restart) { // just skip to the most logical page. For optional minigames, this // will return the player to the previous page. For mandatory minigames, // this will send the player to the next page. - // TODO: Document mini games from Arthur's Reading Race - uint16 destPage; + uint16 destPage = 0; + bool returnToMenu = false; // Figure out what minigame we have and bring us back to a page where // the player can continue @@ -3820,13 +3863,31 @@ bool LBMiniGameItem::togglePlaying(bool playing, bool restart) { destPage = 5; else if (_desc == "Fall") // Green Eggs and Ham: Fall minigame destPage = 13; + else if (_desc == "MagicWrite3") // Arthur's Reading Race: "Let Me Write" minigame (Page 3) + destPage = 3; + else if (_desc == "MagicWrite4") // Arthur's Reading Race: "Let Me Write" minigame (Page 4) + destPage = 4; + else if (_desc == "MagicSpy5") // Arthur's Reading Race: "I Spy" minigame (Page 5) + destPage = 5; + else if (_desc == "MagicSpy6") // Arthur's Reading Race: "I Spy" minigame (Page 6) + destPage = 6; + else if (_desc == "MagicWrite7") // Arthur's Reading Race: "Let Me Write" minigame (Page 7) + destPage = 7; + else if (_desc == "MagicSpy8") // Arthur's Reading Race: "I Spy" minigame (Page 8) + destPage = 8; + else if (_desc == "MagicRace") // Arthur's Reading Race: Race minigame + returnToMenu = true; else error("Unknown minigame '%s'", _desc.c_str()); GUI::MessageDialog dialog(Common::String::format("The '%s' minigame is not supported yet.", _desc.c_str())); dialog.runModal(); - _vm->addNotifyEvent(NotifyEvent(kLBNotifyChangePage, destPage)); + // Go back to the menu if requested, otherwise go to the requested page + if (returnToMenu) + _vm->addNotifyEvent(NotifyEvent(kLBNotifyGoToControls, 1)); + else + _vm->addNotifyEvent(NotifyEvent(kLBNotifyChangePage, destPage)); return false; } @@ -3863,7 +3924,7 @@ void LBProxyItem::load() { debug(1, "LBProxyItem loading archive '%s' with id %d", filename.c_str(), baseId); Archive *pageArchive = _vm->createArchive(); - if (!pageArchive->openFile(filename)) + if (!tryOpenPage(pageArchive, filename)) error("failed to open archive '%s' (for proxy '%s')", filename.c_str(), _desc.c_str()); _page = new LBPage(_vm); _page->open(pageArchive, baseId); diff --git a/engines/mohawk/livingbooks_code.cpp b/engines/mohawk/livingbooks_code.cpp index 6dcd8c3ce7..b5ea547414 100644 --- a/engines/mohawk/livingbooks_code.cpp +++ b/engines/mohawk/livingbooks_code.cpp @@ -842,8 +842,8 @@ CodeCommandInfo generalCommandInfo[NUM_GENERAL_COMMANDS] = { { "bottom", &LBCode::cmdBottom }, // 0x10 { "right", &LBCode::cmdRight }, - { "xpos", 0 }, - { "ypos", 0 }, + { "xpos", &LBCode::cmdXPos }, + { "ypos", &LBCode::cmdYPos }, { "playFrom", 0 }, { "move", &LBCode::cmdMove }, { 0, 0 }, @@ -852,13 +852,13 @@ CodeCommandInfo generalCommandInfo[NUM_GENERAL_COMMANDS] = { { "resetDragParams", 0 }, { "enableRollover", &LBCode::cmdUnimplemented /* FIXME */ }, { "setCursor", 0 }, - { "width", 0 }, - { "height", 0 }, + { "width", &LBCode::cmdWidth }, + { "height", &LBCode::cmdHeight }, { "getFrameBounds", 0 }, // also "getFrameRect" { "traceRect", 0 }, { "sqrt", 0 }, // 0x20 - { "deleteVar", 0 }, + { "deleteVar", &LBCode::cmdDeleteVar }, { "saveVars", 0 }, { "scriptLink", 0 }, { "setViewOrigin", &LBCode::cmdUnimplemented }, @@ -1131,6 +1131,38 @@ void LBCode::cmdRight(const Common::Array<LBValue> ¶ms) { _stack.push(rect.right); } +void LBCode::cmdXPos(const Common::Array<LBValue> ¶ms) { + if (params.size() != 1) + error("too many parameters (%d) to xpos", params.size()); + + Common::Point point = params[0].toPoint(); + _stack.push(point.x); +} + +void LBCode::cmdYPos(const Common::Array<LBValue> ¶ms) { + if (params.size() != 1) + error("too many parameters (%d) to ypos", params.size()); + + Common::Point point = params[0].toPoint(); + _stack.push(point.y); +} + +void LBCode::cmdWidth(const Common::Array<LBValue> ¶ms) { + if (params.size() > 1) + error("too many parameters (%d) to width", params.size()); + + Common::Rect rect = getRectFromParams(params); + _stack.push(rect.width()); +} + +void LBCode::cmdHeight(const Common::Array<LBValue> ¶ms) { + if (params.size() > 1) + error("too many parameters (%d) to height", params.size()); + + Common::Rect rect = getRectFromParams(params); + _stack.push(rect.height()); +} + void LBCode::cmdMove(const Common::Array<LBValue> ¶ms) { if (params.size() != 1 && params.size() != 2) error("incorrect number of parameters (%d) to move", params.size()); @@ -1263,6 +1295,14 @@ void LBCode::cmdGetProperty(const Common::Array<LBValue> ¶ms) { _stack.push(target->_variables[name]); } +void LBCode::cmdDeleteVar(const Common::Array<LBValue> ¶ms) { + if (params.size() != 1) + error("incorrect number of parameters (%d) to deleteVar", params.size()); + + const Common::String &string = params[0].toString(); + _vm->_variables.erase(string); +} + void LBCode::cmdExec(const Common::Array<LBValue> ¶ms) { if (params.size() != 1) error("incorrect number of parameters (%d) to exec", params.size()); @@ -1706,6 +1746,10 @@ uint LBCode::parseCode(const Common::String &source) { if (token != ' ' && token != '(' && wasFunction) error("while parsing script '%s', encountered incomplete function call", source.c_str()); + // Skip C++-style comments + if (token == '/' && lookahead == '/') + break; + // First, we check for simple operators. for (uint i = 0; i < NUM_LB_OPERATORS; i++) { if (token != operators[i].token) @@ -1736,6 +1780,7 @@ uint LBCode::parseCode(const Common::String &source) { switch (token) { // whitespace case ' ': + case '\t': // ignore break; // literal string diff --git a/engines/mohawk/livingbooks_code.h b/engines/mohawk/livingbooks_code.h index 080377ce99..6f6297d592 100644 --- a/engines/mohawk/livingbooks_code.h +++ b/engines/mohawk/livingbooks_code.h @@ -263,6 +263,10 @@ public: void cmdLeft(const Common::Array<LBValue> ¶ms); void cmdBottom(const Common::Array<LBValue> ¶ms); void cmdRight(const Common::Array<LBValue> ¶ms); + void cmdXPos(const Common::Array<LBValue> ¶ms); + void cmdYPos(const Common::Array<LBValue> ¶ms); + void cmdWidth(const Common::Array<LBValue> ¶ms); + void cmdHeight(const Common::Array<LBValue> ¶ms); void cmdMove(const Common::Array<LBValue> ¶ms); void cmdSetDragParams(const Common::Array<LBValue> ¶ms); void cmdNewList(const Common::Array<LBValue> ¶ms); @@ -273,6 +277,7 @@ public: void cmdDeleteAt(const Common::Array<LBValue> ¶ms); void cmdSetProperty(const Common::Array<LBValue> ¶ms); void cmdGetProperty(const Common::Array<LBValue> ¶ms); + void cmdDeleteVar(const Common::Array<LBValue> ¶ms); void cmdExec(const Common::Array<LBValue> ¶ms); void cmdReturn(const Common::Array<LBValue> ¶ms); void cmdSetPlayParams(const Common::Array<LBValue> ¶ms); diff --git a/engines/mohawk/myst_areas.cpp b/engines/mohawk/myst_areas.cpp index 4a3001774a..7a9596d8e0 100644 --- a/engines/mohawk/myst_areas.cpp +++ b/engines/mohawk/myst_areas.cpp @@ -223,20 +223,26 @@ VideoHandle MystResourceType6::playMovie() { VideoHandle handle = _vm->_video->findVideoHandle(_videoFile); // If the video is not running, play it - if (handle == NULL_VID_HANDLE || _vm->_video->endOfVideo(handle)) { - handle = _vm->_video->playMovie(_videoFile, _left, _top, _loop); + if (!handle || handle->endOfVideo()) { + handle = _vm->_video->playMovie(_videoFile); + if (!handle) + error("Failed to open '%s'", _videoFile.c_str()); + + handle->moveTo(_left, _top); + handle->setLooping(_loop != 0); + if (_direction == -1) { - _vm->_video->seekToTime(handle, _vm->_video->getDuration(handle)); - _vm->_video->setVideoRate(handle, -1); + handle->seek(handle->getDuration()); + handle->setRate(-1); } } else { // Resume the video - _vm->_video->pauseMovie(handle, false); + handle->pause(false); } if (_playBlocking) { _vm->_video->waitUntilMovieEnds(handle); - handle = NULL_VID_HANDLE; + return VideoHandle(); } return handle; @@ -249,13 +255,13 @@ void MystResourceType6::handleCardChange() { bool MystResourceType6::isPlaying() { VideoHandle handle = _vm->_video->findVideoHandle(_videoFile); - return handle != NULL_VID_HANDLE && !_vm->_video->endOfVideo(handle); + return handle && !handle->endOfVideo(); } void MystResourceType6::pauseMovie(bool pause) { VideoHandle handle = _vm->_video->findVideoHandle(_videoFile); - if (handle != NULL_VID_HANDLE && !_vm->_video->endOfVideo(handle)) - _vm->_video->pauseMovie(handle, pause); + if (handle && !handle->endOfVideo()) + handle->pause(pause); } MystResourceType7::MystResourceType7(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent) : MystResource(vm, rlstStream, parent) { diff --git a/engines/mohawk/myst_stacks/channelwood.cpp b/engines/mohawk/myst_stacks/channelwood.cpp index 2dd5745550..dfa15a9b6c 100644 --- a/engines/mohawk/myst_stacks/channelwood.cpp +++ b/engines/mohawk/myst_stacks/channelwood.cpp @@ -299,13 +299,17 @@ bool Channelwood::pipeChangeValve(bool open, uint16 mask) { void Channelwood::o_bridgeToggle(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Bridge rise / skink video", op); - VideoHandle bridge = _vm->_video->playMovie(_vm->wrapMovieFilename("bridge", kChannelwoodStack), 292, 203); + VideoHandle bridge = _vm->_video->playMovie(_vm->wrapMovieFilename("bridge", kChannelwoodStack)); + if (!bridge) + error("Failed to open 'bridge' movie"); + + bridge->moveTo(292, 203); // Toggle bridge state if (_state.waterPumpBridgeState) - _vm->_video->setVideoBounds(bridge, Audio::Timestamp(0, 3050, 600), Audio::Timestamp(0, 6100, 600)); + bridge->setBounds(Audio::Timestamp(0, 3050, 600), Audio::Timestamp(0, 6100, 600)); else - _vm->_video->setVideoBounds(bridge, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 3050, 600)); + bridge->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 3050, 600)); _vm->_video->waitUntilMovieEnds(bridge); } @@ -317,13 +321,17 @@ void Channelwood::o_pipeExtend(uint16 op, uint16 var, uint16 argc, uint16 *argv) debugC(kDebugScript, "\tsoundId: %d", soundId); _vm->_sound->replaceSoundMyst(soundId); - VideoHandle pipe = _vm->_video->playMovie(_vm->wrapMovieFilename("pipebrid", kChannelwoodStack), 267, 170); + VideoHandle pipe = _vm->_video->playMovie(_vm->wrapMovieFilename("pipebrid", kChannelwoodStack)); + if (!pipe) + error("Failed to open 'pipebrid' movie"); + + pipe->moveTo(267, 170); // Toggle pipe state if (_state.pipeState) - _vm->_video->setVideoBounds(pipe, Audio::Timestamp(0, 3040, 600), Audio::Timestamp(0, 6080, 600)); + pipe->setBounds(Audio::Timestamp(0, 3040, 600), Audio::Timestamp(0, 6080, 600)); else - _vm->_video->setVideoBounds(pipe, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 3040, 600)); + pipe->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 3040, 600)); _vm->_video->waitUntilMovieEnds(pipe); _vm->_sound->resumeBackgroundMyst(); @@ -605,23 +613,29 @@ void Channelwood::o_hologramMonitor(uint16 op, uint16 var, uint16 argc, uint16 * _vm->_video->stopVideos(); + VideoHandle handle; + switch (button) { case 0: - _vm->_video->playMovie(_vm->wrapMovieFilename("monalgh", kChannelwoodStack), 227, 70); + handle = _vm->_video->playMovie(_vm->wrapMovieFilename("monalgh", kChannelwoodStack)); break; case 1: - _vm->_video->playMovie(_vm->wrapMovieFilename("monamth", kChannelwoodStack), 227, 70); + handle = _vm->_video->playMovie(_vm->wrapMovieFilename("monamth", kChannelwoodStack)); break; case 2: - _vm->_video->playMovie(_vm->wrapMovieFilename("monasirs", kChannelwoodStack), 227, 70); + handle = _vm->_video->playMovie(_vm->wrapMovieFilename("monasirs", kChannelwoodStack)); break; case 3: - _vm->_video->playMovie(_vm->wrapMovieFilename("monsmsg", kChannelwoodStack), 227, 70); + handle = _vm->_video->playMovie(_vm->wrapMovieFilename("monsmsg", kChannelwoodStack)); break; default: warning("Opcode %d Control Variable Out of Range", op); break; } + + // Move the video to the right location + if (handle) + handle->moveTo(227, 70); } } diff --git a/engines/mohawk/myst_stacks/dni.cpp b/engines/mohawk/myst_stacks/dni.cpp index 3eb3c40cbb..6ba0b63423 100644 --- a/engines/mohawk/myst_stacks/dni.cpp +++ b/engines/mohawk/myst_stacks/dni.cpp @@ -103,14 +103,14 @@ void Dni::o_handPage(uint16 op, uint16 var, uint16 argc, uint16 *argv) { VideoHandle atrus = _vm->_video->findVideoHandle(_video); // Good ending and Atrus asked to give page - if (_globals.ending == 1 && _vm->_video->getTime(atrus) > (uint)Audio::Timestamp(0, 6801, 600).msecs()) { + if (_globals.ending == 1 && atrus && atrus->getTime() > (uint)Audio::Timestamp(0, 6801, 600).msecs()) { _globals.ending = 2; _globals.heldPage = 0; _vm->setMainCursor(kDefaultMystCursor); // Play movie end (atrus leaving) - _vm->_video->setVideoBounds(atrus, Audio::Timestamp(0, 14813, 600), _vm->_video->getDuration(atrus)); - _vm->_video->setVideoLooping(atrus, false); + atrus->setBounds(Audio::Timestamp(0, 14813, 600), atrus->getDuration()); + atrus->setLooping(false); _atrusLeft = true; _waitForLoop = false; @@ -121,8 +121,12 @@ void Dni::o_handPage(uint16 op, uint16 var, uint16 argc, uint16 *argv) { void Dni::atrusLeft_run() { if (_vm->_system->getMillis() > _atrusLeftTime + 63333) { _video = _vm->wrapMovieFilename("atrus2", kDniStack); - VideoHandle atrus = _vm->_video->playMovie(_video, 215, 77); - _vm->_video->setVideoBounds(atrus, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 98000, 600)); + VideoHandle atrus = _vm->_video->playMovie(_video); + if (!atrus) + error("Failed to open '%s'", _video.c_str()); + + atrus->moveTo(215, 77); + atrus->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 98000, 600)); _waitForLoop = true; _loopStart = 73095; @@ -139,9 +143,13 @@ void Dni::atrusLeft_run() { void Dni::loopVideo_run() { if (!_vm->_video->isVideoPlaying()) { - VideoHandle atrus = _vm->_video->playMovie(_video, 215, 77); - _vm->_video->setVideoBounds(atrus, Audio::Timestamp(0, _loopStart, 600), Audio::Timestamp(0, _loopEnd, 600)); - _vm->_video->setVideoLooping(atrus, true); + VideoHandle atrus = _vm->_video->playMovie(_video); + if (!atrus) + error("Failed to open '%s'", _video.c_str()); + + atrus->moveTo(215, 77); + atrus->setBounds(Audio::Timestamp(0, _loopStart, 600), Audio::Timestamp(0, _loopEnd, 600)); + atrus->setLooping(true); _waitForLoop = false; } @@ -155,14 +163,23 @@ void Dni::atrus_run() { // Atrus asking for page if (!_vm->_video->isVideoPlaying()) { _video = _vm->wrapMovieFilename("atr1page", kDniStack); - VideoHandle atrus = _vm->_video->playMovie(_video, 215, 77, true); - _vm->_video->setVideoBounds(atrus, Audio::Timestamp(0, 7388, 600), Audio::Timestamp(0, 14700, 600)); + VideoHandle atrus = _vm->_video->playMovie(_video); + if (!atrus) + error("Failed to open '%s'", _video.c_str()); + + atrus->moveTo(215, 77); + atrus->setLooping(true); + atrus->setBounds(Audio::Timestamp(0, 7388, 600), Audio::Timestamp(0, 14700, 600)); } } else if (_globals.ending != 3 && _globals.ending != 4) { if (_globals.heldPage == 13) { _video = _vm->wrapMovieFilename("atr1page", kDniStack); - VideoHandle atrus = _vm->_video->playMovie(_video, 215, 77); - _vm->_video->setVideoBounds(atrus, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 14700, 600)); + VideoHandle atrus = _vm->_video->playMovie(_video); + if (!atrus) + error("Failed to open '%s'", _video.c_str()); + + atrus->moveTo(215, 77); + atrus->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 14700, 600)); _waitForLoop = true; _loopStart = 7388; @@ -173,8 +190,12 @@ void Dni::atrus_run() { } else { _video = _vm->wrapMovieFilename("atr1nopg", kDniStack); - VideoHandle atrus = _vm->_video->playMovie(_video, 215, 77); - _vm->_video->setVideoBounds(atrus, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 46175, 600)); + VideoHandle atrus = _vm->_video->playMovie(_video); + if (!atrus) + error("Failed to open '%s'", _video.c_str()); + + atrus->moveTo(215, 77); + atrus->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 46175, 600)); _waitForLoop = true; _loopStart = 30656; @@ -184,7 +205,12 @@ void Dni::atrus_run() { _globals.ending = 3; } } else if (!_vm->_video->isVideoPlaying()) { - _vm->_video->playMovie(_vm->wrapMovieFilename("atrwrite", kDniStack), 215, 77, true); + VideoHandle handle = _vm->_video->playMovie(_vm->wrapMovieFilename("atrwrite", kDniStack)); + if (!handle) + error("Failed to open atrwrite movie"); + + handle->moveTo(215, 77); + handle->setLooping(true); } } diff --git a/engines/mohawk/myst_stacks/intro.cpp b/engines/mohawk/myst_stacks/intro.cpp index 2a33379198..dc66984398 100644 --- a/engines/mohawk/myst_stacks/intro.cpp +++ b/engines/mohawk/myst_stacks/intro.cpp @@ -98,10 +98,16 @@ void Intro::introMovies_run() { // Play Intro Movies // This is all quite messy... + VideoHandle handle; + switch (_introStep) { case 0: _introStep = 1; - _vm->_video->playMovie(_vm->wrapMovieFilename("broder", kIntroStack)); + handle = _vm->_video->playMovie(_vm->wrapMovieFilename("broder", kIntroStack)); + if (!handle) + error("Failed to open broder movie"); + + handle->center(); break; case 1: if (!_vm->_video->isVideoPlaying()) @@ -109,7 +115,11 @@ void Intro::introMovies_run() { break; case 2: _introStep = 3; - _vm->_video->playMovie(_vm->wrapMovieFilename("cyanlogo", kIntroStack)); + handle = _vm->_video->playMovie(_vm->wrapMovieFilename("cyanlogo", kIntroStack)); + if (!handle) + error("Failed to open cyanlogo movie"); + + handle->center(); break; case 3: if (!_vm->_video->isVideoPlaying()) @@ -118,8 +128,13 @@ void Intro::introMovies_run() { case 4: _introStep = 5; - if (!(_vm->getFeatures() & GF_DEMO)) // The demo doesn't have the intro video - _vm->_video->playMovie(_vm->wrapMovieFilename("intro", kIntroStack)); + if (!(_vm->getFeatures() & GF_DEMO)) { // The demo doesn't have the intro video + handle = _vm->_video->playMovie(_vm->wrapMovieFilename("intro", kIntroStack)); + if (!handle) + error("Failed to open intro movie"); + + handle->center(); + } break; case 5: if (!_vm->_video->isVideoPlaying()) diff --git a/engines/mohawk/myst_stacks/mechanical.cpp b/engines/mohawk/myst_stacks/mechanical.cpp index b5d1285435..ffcaa226c6 100644 --- a/engines/mohawk/myst_stacks/mechanical.cpp +++ b/engines/mohawk/myst_stacks/mechanical.cpp @@ -316,12 +316,16 @@ void Mechanical::o_snakeBoxTrigger(uint16 op, uint16 var, uint16 argc, uint16 *a void Mechanical::o_fortressStaircaseMovie(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Play Stairs Movement Movie", op); - VideoHandle staircase = _vm->_video->playMovie(_vm->wrapMovieFilename("hhstairs", kMechanicalStack), 174, 222); + VideoHandle staircase = _vm->_video->playMovie(_vm->wrapMovieFilename("hhstairs", kMechanicalStack)); + if (!staircase) + error("Failed to open hhstairs movie"); + + staircase->moveTo(174, 222); if (_state.staircaseState) { - _vm->_video->setVideoBounds(staircase, Audio::Timestamp(0, 840, 600), Audio::Timestamp(0, 1680, 600)); + staircase->setBounds(Audio::Timestamp(0, 840, 600), Audio::Timestamp(0, 1680, 600)); } else { - _vm->_video->setVideoBounds(staircase, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 840, 600)); + staircase->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 840, 600)); } _vm->_video->waitUntilMovieEnds(staircase); @@ -571,8 +575,12 @@ void Mechanical::o_elevatorWindowMovie(uint16 op, uint16 var, uint16 argc, uint1 debugC(kDebugScript, "Opcode %d Movie Time Index %d to %d", op, startTime, endTime); - VideoHandle window = _vm->_video->playMovie(_vm->wrapMovieFilename("ewindow", kMechanicalStack), 253, 0); - _vm->_video->setVideoBounds(window, Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, endTime, 600)); + VideoHandle window = _vm->_video->playMovie(_vm->wrapMovieFilename("ewindow", kMechanicalStack)); + if (!window) + error("Failed to open ewindow movie"); + + window->moveTo(253, 0); + window->setBounds(Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, endTime, 600)); _vm->_video->waitUntilMovieEnds(window); } @@ -644,8 +652,12 @@ void Mechanical::o_elevatorTopMovie(uint16 op, uint16 var, uint16 argc, uint16 * debugC(kDebugScript, "Opcode %d Movie Time Index %d to %d", op, startTime, endTime); - VideoHandle window = _vm->_video->playMovie(_vm->wrapMovieFilename("hcelev", kMechanicalStack), 206, 38); - _vm->_video->setVideoBounds(window, Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, endTime, 600)); + VideoHandle window = _vm->_video->playMovie(_vm->wrapMovieFilename("hcelev", kMechanicalStack)); + if (!window) + error("Failed to open hcelev movie"); + + window->moveTo(206, 38); + window->setBounds(Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, endTime, 600)); _vm->_video->waitUntilMovieEnds(window); } @@ -653,7 +665,7 @@ void Mechanical::o_fortressRotationSetPosition(uint16 op, uint16 var, uint16 arg debugC(kDebugScript, "Opcode %d: Set fortress position", op); VideoHandle gears = _fortressRotationGears->playMovie(); - uint32 moviePosition = Audio::Timestamp(_vm->_video->getTime(gears), 600).totalNumberOfFrames(); + uint32 moviePosition = Audio::Timestamp(gears->getTime(), 600).totalNumberOfFrames(); // Myst ME short movie workaround, explained in o_fortressRotation_init if (_fortressRotationShortMovieWorkaround) { @@ -788,9 +800,8 @@ void Mechanical::o_elevatorRotation_init(uint16 op, uint16 var, uint16 argc, uin void Mechanical::fortressRotation_run() { VideoHandle gears = _fortressRotationGears->playMovie(); - double oldRate = _vm->_video->getVideoRate(gears).toDouble(); - - uint32 moviePosition = Audio::Timestamp(_vm->_video->getTime(gears), 600).totalNumberOfFrames(); + double oldRate = gears->getRate().toDouble(); + uint32 moviePosition = Audio::Timestamp(gears->getTime(), 600).totalNumberOfFrames(); // Myst ME short movie workaround, explained in o_fortressRotation_init if (_fortressRotationShortMovieWorkaround) { @@ -837,19 +848,19 @@ void Mechanical::fortressRotation_run() { newRate = CLIP<double>(newRate, -2.5, 2.5); - _vm->_video->setVideoRate(gears, Common::Rational((int)(newRate * 1000.0), 1000)); + gears->setRate(Common::Rational((int)(newRate * 1000.0), 1000)); _gearsWereRunning = true; } else if (_gearsWereRunning) { // The fortress has stopped. Set its new position _fortressPosition = (moviePosition + 900) / 1800 % 4; - _vm->_video->setVideoRate(gears, 0); + gears->setRate(0); if (!_fortressRotationShortMovieWorkaround) { - _vm->_video->seekToTime(gears, Audio::Timestamp(0, 1800 * _fortressPosition, 600)); + gears->seek(Audio::Timestamp(0, 1800 * _fortressPosition, 600)); } else { - _vm->_video->seekToTime(gears, Audio::Timestamp(0, 1800 * (_fortressPosition % 2), 600)); + gears->seek(Audio::Timestamp(0, 1800 * (_fortressPosition % 2), 600)); } _vm->_sound->playSoundBlocking(_fortressRotationSounds[_fortressPosition]); @@ -864,9 +875,9 @@ void Mechanical::o_fortressRotation_init(uint16 op, uint16 var, uint16 argc, uin _fortressRotationGears = static_cast<MystResourceType6 *>(_invokingResource); VideoHandle gears = _fortressRotationGears->playMovie(); - _vm->_video->setVideoLooping(gears, true); - _vm->_video->seekToTime(gears, Audio::Timestamp(0, 1800 * _fortressPosition, 600)); - _vm->_video->setVideoRate(gears, 0); + gears->setLooping(true); + gears->seek(Audio::Timestamp(0, 1800 * _fortressPosition, 600)); + gears->setRate(0); _fortressRotationSounds[0] = argv[0]; _fortressRotationSounds[1] = argv[1]; @@ -884,7 +895,7 @@ void Mechanical::o_fortressRotation_init(uint16 op, uint16 var, uint16 argc, uin // ScummVM simulates a longer movie by counting the number of times the movie // looped and adding that time to the current movie position. // Hence allowing the fortress position to be properly computed. - uint32 movieDuration = _vm->_video->getDuration(gears).convertToFramerate(600).totalNumberOfFrames(); + uint32 movieDuration = gears->getDuration().convertToFramerate(600).totalNumberOfFrames(); if (movieDuration == 3680) { _fortressRotationShortMovieWorkaround = true; _fortressRotationShortMovieCount = 0; @@ -924,8 +935,8 @@ void Mechanical::fortressSimulation_run() { _fortressSimulationStartup->pauseMovie(true); VideoHandle holo = _fortressSimulationHolo->playMovie(); - _vm->_video->setVideoLooping(holo, true); - _vm->_video->setVideoRate(holo, 0); + holo->setLooping(true); + holo->setRate(0); _vm->_cursor->showCursor(); @@ -933,9 +944,8 @@ void Mechanical::fortressSimulation_run() { } else { VideoHandle holo = _fortressSimulationHolo->playMovie(); - double oldRate = _vm->_video->getVideoRate(holo).toDouble(); - - uint32 moviePosition = Audio::Timestamp(_vm->_video->getTime(holo), 600).totalNumberOfFrames(); + double oldRate = holo->getRate().toDouble(); + uint32 moviePosition = Audio::Timestamp(holo->getTime(), 600).totalNumberOfFrames(); int32 positionInQuarter = 900 - (moviePosition + 900) % 1800; @@ -968,15 +978,15 @@ void Mechanical::fortressSimulation_run() { newRate = CLIP<double>(newRate, -2.5, 2.5); - _vm->_video->setVideoRate(holo, Common::Rational((int)(newRate * 1000.0), 1000)); + holo->setRate(Common::Rational((int)(newRate * 1000.0), 1000)); _gearsWereRunning = true; } else if (_gearsWereRunning) { // The fortress has stopped. Set its new position uint16 simulationPosition = (moviePosition + 900) / 1800 % 4; - _vm->_video->setVideoRate(holo, 0); - _vm->_video->seekToTime(holo, Audio::Timestamp(0, 1800 * simulationPosition, 600)); + holo->setRate(0); + holo->seek(Audio::Timestamp(0, 1800 * simulationPosition, 600)); _vm->_sound->playSoundBlocking( _fortressRotationSounds[simulationPosition]); _gearsWereRunning = false; diff --git a/engines/mohawk/myst_stacks/myst.cpp b/engines/mohawk/myst_stacks/myst.cpp index ca6e7c0ee5..98f0aa5349 100644 --- a/engines/mohawk/myst_stacks/myst.cpp +++ b/engines/mohawk/myst_stacks/myst.cpp @@ -51,8 +51,6 @@ Myst::Myst(MohawkEngine_Myst *vm) : _dockVaultState = 0; _cabinDoorOpened = 0; _cabinMatchState = 2; - _cabinGaugeMovie = NULL_VID_HANDLE; - _cabinFireMovie = NULL_VID_HANDLE; _matchBurning = false; _tree = 0; _treeAlcove = 0; @@ -1135,10 +1133,13 @@ void Myst::o_clockWheelsExecute(uint16 op, uint16 var, uint16 argc, uint16 *argv _vm->_system->delayMillis(500); // Gears rise up - VideoHandle gears = _vm->_video->playMovie(_vm->wrapMovieFilename("gears", kMystStack), 305, 33); - _vm->_video->setVideoBounds(gears, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 650, 600)); - _vm->_video->waitUntilMovieEnds(gears); + VideoHandle gears = _vm->_video->playMovie(_vm->wrapMovieFilename("gears", kMystStack)); + if (!gears) + error("Failed to open gears movie"); + gears->moveTo(305, 33); + gears->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 650, 600)); + _vm->_video->waitUntilMovieEnds(gears); _state.clockTowerBridgeOpen = 1; _vm->redrawArea(12); @@ -1147,8 +1148,12 @@ void Myst::o_clockWheelsExecute(uint16 op, uint16 var, uint16 argc, uint16 *argv _vm->_system->delayMillis(500); // Gears sink down - VideoHandle gears = _vm->_video->playMovie(_vm->wrapMovieFilename("gears", kMystStack), 305, 33); - _vm->_video->setVideoBounds(gears, Audio::Timestamp(0, 700, 600), Audio::Timestamp(0, 1300, 600)); + VideoHandle gears = _vm->_video->playMovie(_vm->wrapMovieFilename("gears", kMystStack)); + if (!gears) + error("Failed to open gears movie"); + + gears->moveTo(305, 33); + gears->setBounds(Audio::Timestamp(0, 700, 600), Audio::Timestamp(0, 1300, 600)); _vm->_video->waitUntilMovieEnds(gears); _state.clockTowerBridgeOpen = 0; @@ -1191,15 +1196,23 @@ void Myst::o_imagerPlayButton(uint16 op, uint16 var, uint16 argc, uint16 *argv) if (_state.imagerActive) { // Mountains disappearing Common::String file = _vm->wrapMovieFilename("vltmntn", kMystStack); - VideoHandle mountain = _vm->_video->playMovie(file, 159, 96, false); - _vm->_video->setVideoBounds(mountain, Audio::Timestamp(0, 11180, 600), Audio::Timestamp(0, 16800, 600)); + VideoHandle mountain = _vm->_video->playMovie(file); + if (!mountain) + error("Failed to open '%s'", file.c_str()); + + mountain->moveTo(159, 96); + mountain->setBounds(Audio::Timestamp(0, 11180, 600), Audio::Timestamp(0, 16800, 600)); _state.imagerActive = 0; } else { // Mountains appearing Common::String file = _vm->wrapMovieFilename("vltmntn", kMystStack); - VideoHandle mountain = _vm->_video->playMovie(file, 159, 96, false); - _vm->_video->setVideoBounds(mountain, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 11180, 600)); + VideoHandle mountain = _vm->_video->playMovie(file); + if (!mountain) + error("Failed to open '%s'", file.c_str()); + + mountain->moveTo(159, 96); + mountain->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 11180, 600)); _state.imagerActive = 1; } @@ -1212,20 +1225,20 @@ void Myst::o_imagerPlayButton(uint16 op, uint16 var, uint16 argc, uint16 *argv) // Water disappearing VideoHandle water = _imagerMovie->playMovie(); - _vm->_video->setVideoBounds(water, Audio::Timestamp(0, 4204, 600), Audio::Timestamp(0, 6040, 600)); - _vm->_video->setVideoLooping(water, false); + water->setBounds(Audio::Timestamp(0, 4204, 600), Audio::Timestamp(0, 6040, 600)); + water->setLooping(false); _state.imagerActive = 0; } else { // Water appearing VideoHandle water = _imagerMovie->playMovie(); - _vm->_video->setVideoBounds(water, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 1814, 600)); + water->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 1814, 600)); _vm->_video->waitUntilMovieEnds(water); // Water looping water = _imagerMovie->playMovie(); - _vm->_video->setVideoBounds(water, Audio::Timestamp(0, 1814, 600), Audio::Timestamp(0, 4204, 600)); - _vm->_video->setVideoLooping(water, true); + water->setBounds(Audio::Timestamp(0, 1814, 600), Audio::Timestamp(0, 4204, 600)); + water->setLooping(true); _state.imagerActive = 1; } @@ -1902,11 +1915,19 @@ Common::Rational Myst::boilerComputeGaugeRate(uint16 pressure, uint32 delay) { } void Myst::boilerResetGauge(const Common::Rational &rate) { - if (_vm->_video->endOfVideo(_cabinGaugeMovie)) { + if (!_cabinGaugeMovie || _cabinGaugeMovie->endOfVideo()) { if (_vm->getCurCard() == 4098) { - _cabinGaugeMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabingau", kMystStack), 243, 96); + _cabinGaugeMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabingau", kMystStack)); + if (!_cabinGaugeMovie) + error("Failed to open cabingau movie"); + + _cabinGaugeMovie->moveTo(243, 96); } else { - _cabinGaugeMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabcgfar", kMystStack), 254, 136); + _cabinGaugeMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabcgfar", kMystStack)); + if (!_cabinGaugeMovie) + error("Failed to open cabingau movie"); + + _cabinGaugeMovie->moveTo(254, 136); } } @@ -1914,10 +1935,10 @@ void Myst::boilerResetGauge(const Common::Rational &rate) { if (rate > 0) goTo = Audio::Timestamp(0, 0, 600); else - goTo = _vm->_video->getDuration(_cabinGaugeMovie); + goTo = _cabinGaugeMovie->getDuration(); - _vm->_video->seekToTime(_cabinGaugeMovie, goTo); - _vm->_video->setVideoRate(_cabinGaugeMovie, rate); + _cabinGaugeMovie->seek(goTo); + _cabinGaugeMovie->setRate(rate); } void Myst::o_boilerIncreasePressureStop(uint16 op, uint16 var, uint16 argc, uint16 *argv) { @@ -1931,10 +1952,10 @@ void Myst::o_boilerIncreasePressureStop(uint16 op, uint16 var, uint16 argc, uint if (_state.cabinValvePosition > 0) _vm->_sound->replaceBackgroundMyst(8098, 49152); - if (!_vm->_video->endOfVideo(_cabinGaugeMovie)) { + if (_cabinGaugeMovie && !_cabinGaugeMovie->endOfVideo()) { uint16 delay = treeNextMoveDelay(_state.cabinValvePosition); Common::Rational rate = boilerComputeGaugeRate(_state.cabinValvePosition, delay); - _vm->_video->setVideoRate(_cabinGaugeMovie, rate); + _cabinGaugeMovie->setRate(rate); } } else if (_state.cabinValvePosition > 0) @@ -2006,10 +2027,10 @@ void Myst::o_boilerDecreasePressureStop(uint16 op, uint16 var, uint16 argc, uint if (_state.cabinValvePosition > 0) _vm->_sound->replaceBackgroundMyst(8098, 49152); - if (!_vm->_video->endOfVideo(_cabinGaugeMovie)) { + if (_cabinGaugeMovie && !_cabinGaugeMovie->endOfVideo()) { uint16 delay = treeNextMoveDelay(_state.cabinValvePosition); Common::Rational rate = boilerComputeGaugeRate(_state.cabinValvePosition, delay); - _vm->_video->setVideoRate(_cabinGaugeMovie, rate); + _cabinGaugeMovie->setRate(rate); } } else { @@ -2117,7 +2138,7 @@ void Myst::tree_run() { // Check if alcove is accessible treeSetAlcoveAccessible(); - if (_cabinGaugeMovie != NULL_VID_HANDLE) { + if (_cabinGaugeMovie) { Common::Rational rate = boilerComputeGaugeRate(pressure, delay); boilerResetGauge(rate); } @@ -2246,13 +2267,22 @@ void Myst::rocketCheckSolution() { // Book appearing Common::String movieFile = _vm->wrapMovieFilename("selenbok", kMystStack); - _rocketLinkBook = _vm->_video->playMovie(movieFile, 224, 41); - _vm->_video->setVideoBounds(_rocketLinkBook, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 660, 600)); + _rocketLinkBook = _vm->_video->playMovie(movieFile); + if (!_rocketLinkBook) + error("Failed to open '%s'", movieFile.c_str()); + + _rocketLinkBook->moveTo(224, 41); + _rocketLinkBook->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 660, 600)); _vm->_video->waitUntilMovieEnds(_rocketLinkBook); // Book looping closed - _rocketLinkBook = _vm->_video->playMovie(movieFile, 224, 41, true); - _vm->_video->setVideoBounds(_rocketLinkBook, Audio::Timestamp(0, 660, 600), Audio::Timestamp(0, 3500, 600)); + _rocketLinkBook = _vm->_video->playMovie(movieFile); + if (!_rocketLinkBook) + error("Failed to open '%s'", movieFile.c_str()); + + _rocketLinkBook->moveTo(224, 41); + _rocketLinkBook->setLooping(true); + _rocketLinkBook->setBounds(Audio::Timestamp(0, 660, 600), Audio::Timestamp(0, 3500, 600)); _tempVar = 1; } @@ -2367,7 +2397,7 @@ void Myst::o_rocketOpenBook(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Rocket open link book", op); // Flyby movie - _vm->_video->setVideoBounds(_rocketLinkBook, Audio::Timestamp(0, 3500, 600), Audio::Timestamp(0, 13100, 600)); + _rocketLinkBook->setBounds(Audio::Timestamp(0, 3500, 600), Audio::Timestamp(0, 13100, 600)); // Set linkable _tempVar = 2; @@ -2889,8 +2919,12 @@ void Myst::clockGearForwardOneStep(uint16 gear) { // Set video bounds uint16 gearPosition = _clockGearsPositions[gear] - 1; - _clockGearsVideos[gear] = _vm->_video->playMovie(_vm->wrapMovieFilename(videos[gear], kMystStack), x[gear], y[gear]); - _vm->_video->setVideoBounds(_clockGearsVideos[gear], + _clockGearsVideos[gear] = _vm->_video->playMovie(_vm->wrapMovieFilename(videos[gear], kMystStack)); + if (!_clockGearsVideos[gear]) + error("Failed to open %s movie", videos[gear]); + + _clockGearsVideos[gear]->moveTo(x[gear], y[gear]); + _clockGearsVideos[gear]->setBounds( Audio::Timestamp(0, startTime[gearPosition], 600), Audio::Timestamp(0, endTime[gearPosition], 600)); } @@ -2902,8 +2936,12 @@ void Myst::clockWeightDownOneStep() { // Set video bounds if (updateVideo) { - _clockWeightVideo = _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wlfch", kMystStack) , 124, 0); - _vm->_video->setVideoBounds(_clockWeightVideo, + _clockWeightVideo = _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wlfch", kMystStack)); + if (!_clockWeightVideo) + error("Failed to open cl1wlfch movie"); + + _clockWeightVideo->moveTo(124, 0); + _clockWeightVideo->setBounds( Audio::Timestamp(0, _clockWeightPosition, 600), Audio::Timestamp(0, _clockWeightPosition + 246, 600)); } @@ -2931,7 +2969,7 @@ void Myst::o_clockLeverEndMove(uint16 op, uint16 var, uint16 argc, uint16 *argv) // Let movies stop playing for (uint i = 0; i < ARRAYSIZE(videos); i++) { VideoHandle handle = _vm->_video->findVideoHandle(_vm->wrapMovieFilename(videos[i], kMystStack)); - if (handle != NULL_VID_HANDLE) + if (handle) _vm->_video->delayUntilMovieEnds(handle); } @@ -2956,8 +2994,12 @@ void Myst::clockGearsCheckSolution() { // Make weight go down _vm->_sound->replaceSoundMyst(9113); - _clockWeightVideo = _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wlfch", kMystStack) , 124, 0); - _vm->_video->setVideoBounds(_clockWeightVideo, + _clockWeightVideo = _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wlfch", kMystStack)); + if (!_clockWeightVideo) + error("Failed to open cl1wlfch movie"); + + _clockWeightVideo->moveTo(124, 0); + _clockWeightVideo->setBounds( Audio::Timestamp(0, _clockWeightPosition, 600), Audio::Timestamp(0, 2214, 600)); _vm->_video->waitUntilMovieEnds(_clockWeightVideo); @@ -2968,7 +3010,7 @@ void Myst::clockGearsCheckSolution() { _vm->_sound->replaceSoundMyst(7113); // Gear opening video - _vm->_video->playMovieBlocking(_vm->wrapMovieFilename("cl1wggat", kMystStack) , 195, 225); + _vm->_video->playMovieBlocking(_vm->wrapMovieFilename("cl1wggat", kMystStack), 195, 225); _state.gearsOpen = 1; _vm->redrawArea(40); @@ -3011,7 +3053,7 @@ void Myst::clockReset() { // Let movies stop playing for (uint i = 0; i < ARRAYSIZE(videos); i++) { VideoHandle handle = _vm->_video->findVideoHandle(_vm->wrapMovieFilename(videos[i], kMystStack)); - if (handle != NULL_VID_HANDLE) + if (handle) _vm->_video->delayUntilMovieEnds(handle); } @@ -3024,9 +3066,13 @@ void Myst::clockReset() { _vm->_sound->replaceSoundMyst(7113); // Gear closing movie - VideoHandle handle = _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wggat", kMystStack) , 195, 225); - _vm->_video->seekToTime(handle, _vm->_video->getDuration(handle)); - _vm->_video->setVideoRate(handle, -1); + VideoHandle handle = _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wggat", kMystStack)); + if (!handle) + error("Failed to open cl1wggat movie"); + + handle->moveTo(195, 225); + handle->seek(handle->getDuration()); + handle->setRate(-1); _vm->_video->waitUntilMovieEnds(handle); // Redraw gear @@ -3038,11 +3084,15 @@ void Myst::clockReset() { } void Myst::clockResetWeight() { - _clockWeightVideo = _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wlfch", kMystStack) , 124, 0); + _clockWeightVideo = _vm->_video->playMovie(_vm->wrapMovieFilename("cl1wlfch", kMystStack)); + if (!_clockWeightVideo) + error("Failed to open cl1wlfch movie"); + + _clockWeightVideo->moveTo(124, 0); // Play the movie backwards, weight going up - _vm->_video->seekToTime(_clockWeightVideo, Audio::Timestamp(0, _clockWeightPosition, 600)); - _vm->_video->setVideoRate(_clockWeightVideo, -1); + _clockWeightVideo->seek(Audio::Timestamp(0, _clockWeightPosition, 600)); + _clockWeightVideo->setRate(-1); // Reset position _clockWeightPosition = 0; @@ -3057,8 +3107,12 @@ void Myst::clockResetGear(uint16 gear) { // Set video bounds, gears going to 3 uint16 gearPosition = _clockGearsPositions[gear] - 1; if (gearPosition != 2) { - _clockGearsVideos[gear] = _vm->_video->playMovie(_vm->wrapMovieFilename(videos[gear], kMystStack), x[gear], y[gear]); - _vm->_video->setVideoBounds(_clockGearsVideos[gear], + _clockGearsVideos[gear] = _vm->_video->playMovie(_vm->wrapMovieFilename(videos[gear], kMystStack)); + if (!_clockGearsVideos[gear]) + error("Failed to open gears movie"); + + _clockGearsVideos[gear]->moveTo(x[gear], y[gear]); + _clockGearsVideos[gear]->setBounds( Audio::Timestamp(0, time[gearPosition], 600), Audio::Timestamp(0, time[2], 600)); } @@ -3289,8 +3343,8 @@ void Myst::imager_run() { if (_state.imagerActive && _state.imagerSelection == 67) { VideoHandle water = _imagerMovie->playMovie(); - _vm->_video->setVideoBounds(water, Audio::Timestamp(0, 1814, 600), Audio::Timestamp(0, 4204, 600)); - _vm->_video->setVideoLooping(water, true); + water->setBounds(Audio::Timestamp(0, 1814, 600), Audio::Timestamp(0, 4204, 600)); + water->setLooping(true); } } @@ -3402,8 +3456,11 @@ void Myst::gullsFly1_run() { else x = _vm->_rnd->getRandomNumber(160) + 260; - _vm->_video->playMovie(_vm->wrapMovieFilename(gulls[video], kMystStack), x, 0); + VideoHandle handle = _vm->_video->playMovie(_vm->wrapMovieFilename(gulls[video], kMystStack)); + if (!handle) + error("Failed to open gulls movie"); + handle->moveTo(x, 0); _gullsNextTime = time + _vm->_rnd->getRandomNumber(16667) + 13334; } } @@ -3548,8 +3605,11 @@ void Myst::gullsFly2_run() { if (time > _gullsNextTime) { uint16 video = _vm->_rnd->getRandomNumber(3); if (video != 3) { - _vm->_video->playMovie(_vm->wrapMovieFilename(gulls[video], kMystStack), 424, 0); - + VideoHandle handle = _vm->_video->playMovie(_vm->wrapMovieFilename(gulls[video], kMystStack)); + if (!handle) + error("Failed to open gulls movie"); + + handle->moveTo(424, 0); _gullsNextTime = time + _vm->_rnd->getRandomNumber(16667) + 13334; } } @@ -3580,31 +3640,41 @@ void Myst::o_boilerMovies_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) void Myst::boilerFireInit() { if (_vm->getCurCard() == 4098) { - _cabinFireMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabfire", kMystStack), 240, 279, true); - _vm->_video->pauseMovie(_cabinFireMovie, true); + _cabinFireMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabfire", kMystStack)); + if (!_cabinFireMovie) + error("Failed to open cabfire movie"); + + _cabinFireMovie->moveTo(240, 279); + _cabinFireMovie->setLooping(true); + _cabinFireMovie->pause(true); _vm->redrawArea(305); boilerFireUpdate(true); } else { if (_state.cabinPilotLightLit == 1 && _state.cabinValvePosition >= 1) { - _cabinFireMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabfirfr", kMystStack), 254, 244, true); + _cabinFireMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabfirfr", kMystStack)); + if (!_cabinFireMovie) + error("Failed to open cabfirfr movie"); + + _cabinFireMovie->moveTo(254, 244); + _cabinFireMovie->setLooping(true); } } } void Myst::boilerFireUpdate(bool init) { - uint position = _vm->_video->getTime(_cabinFireMovie); + uint position = _cabinFireMovie->getTime(); if (_state.cabinPilotLightLit == 1) { if (_state.cabinValvePosition == 0) { if (position > (uint)Audio::Timestamp(0, 200, 600).msecs() || init) { - _vm->_video->setVideoBounds(_cabinFireMovie, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 100, 600)); - _vm->_video->pauseMovie(_cabinFireMovie, false); + _cabinFireMovie->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 100, 600)); + _cabinFireMovie->pause(false); } } else { if (position < (uint)Audio::Timestamp(0, 200, 600).msecs() || init) { - _vm->_video->setVideoBounds(_cabinFireMovie, Audio::Timestamp(0, 201, 600), Audio::Timestamp(0, 1900, 600)); - _vm->_video->pauseMovie(_cabinFireMovie, false); + _cabinFireMovie->setBounds(Audio::Timestamp(0, 201, 600), Audio::Timestamp(0, 1900, 600)); + _cabinFireMovie->pause(false); } } } @@ -3612,15 +3682,23 @@ void Myst::boilerFireUpdate(bool init) { void Myst::boilerGaugeInit() { if (_vm->getCurCard() == 4098) { - _cabinGaugeMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabingau", kMystStack), 243, 96); + _cabinGaugeMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabingau", kMystStack)); + if (!_cabinFireMovie) + error("Failed to open cabingau movie"); + + _cabinFireMovie->moveTo(243, 96); } else { - _cabinGaugeMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabcgfar", kMystStack), 254, 136); + _cabinGaugeMovie = _vm->_video->playMovie(_vm->wrapMovieFilename("cabcgfar", kMystStack)); + if (!_cabinFireMovie) + error("Failed to open cabcgfar movie"); + + _cabinFireMovie->moveTo(254, 136); } Audio::Timestamp frame; if (_state.cabinPilotLightLit == 1 && _state.cabinValvePosition > 12) - frame = _vm->_video->getDuration(_cabinGaugeMovie); + frame = _cabinGaugeMovie->getDuration(); else frame = Audio::Timestamp(0, 0, 600); @@ -3680,18 +3758,27 @@ void Myst::greenBook_run() { _vm->_sound->stopSound(); _vm->_sound->pauseBackgroundMyst(); + VideoHandle book = _vm->_video->playMovie(file); + if (!book) + error("Failed to open '%s'", file.c_str()); + + book->moveTo(314, 76); + if (_globals.ending != 4) { _tempVar = 2; - _vm->_video->playMovie(file, 314, 76); } else { - VideoHandle book = _vm->_video->playMovie(file, 314, 76, true); - _vm->_video->setVideoBounds(book, Audio::Timestamp(0, loopStart, 600), Audio::Timestamp(0, loopEnd, 600)); + book->setBounds(Audio::Timestamp(0, loopStart, 600), Audio::Timestamp(0, loopEnd, 600)); + book->setLooping(true); _tempVar = 0; } } else if (_tempVar == 2 && !_vm->_video->isVideoPlaying()) { - VideoHandle book = _vm->_video->playMovie(file, 314, 76); - _vm->_video->setVideoBounds(book, Audio::Timestamp(0, loopStart, 600), Audio::Timestamp(0, loopEnd, 600)); - _vm->_video->setVideoLooping(book, true); + VideoHandle book = _vm->_video->playMovie(file); + if (!book) + error("Failed to open '%s'", file.c_str()); + + book->moveTo(314, 76); + book->setBounds(Audio::Timestamp(0, loopStart, 600), Audio::Timestamp(0, loopEnd, 600)); + book->setLooping(true); _tempVar = 0; } } @@ -3714,8 +3801,11 @@ void Myst::gullsFly3_run() { if (video != 3) { uint16 x = _vm->_rnd->getRandomNumber(280) + 135; - _vm->_video->playMovie(_vm->wrapMovieFilename(gulls[video], kMystStack), x, 0); + VideoHandle handle = _vm->_video->playMovie(_vm->wrapMovieFilename(gulls[video], kMystStack)); + if (!handle) + error("Failed to open gulls movie"); + handle->moveTo(x, 0); _gullsNextTime = time + _vm->_rnd->getRandomNumber(16667) + 13334; } } @@ -3750,8 +3840,8 @@ void Myst::o_treeEntry_exit(uint16 op, uint16 var, uint16 argc, uint16 *argv) { void Myst::o_boiler_exit(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Exit boiler card", op); - _cabinGaugeMovie = NULL_VID_HANDLE; - _cabinFireMovie = NULL_VID_HANDLE; + _cabinGaugeMovie = VideoHandle(); + _cabinFireMovie = VideoHandle(); } void Myst::o_generatorControlRoom_exit(uint16 op, uint16 var, uint16 argc, uint16 *argv) { diff --git a/engines/mohawk/myst_stacks/stoneship.cpp b/engines/mohawk/myst_stacks/stoneship.cpp index d8dbeef641..1113ceeac9 100644 --- a/engines/mohawk/myst_stacks/stoneship.cpp +++ b/engines/mohawk/myst_stacks/stoneship.cpp @@ -425,8 +425,12 @@ void Stoneship::o_cabinBookMovie(uint16 op, uint16 var, uint16 argc, uint16 *arg uint16 startTime = argv[0]; uint16 endTime = argv[1]; - VideoHandle book = _vm->_video->playMovie(_vm->wrapMovieFilename("bkroom", kStoneshipStack), 159, 99); - _vm->_video->setVideoBounds(book, Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, endTime, 600)); + VideoHandle book = _vm->_video->playMovie(_vm->wrapMovieFilename("bkroom", kStoneshipStack)); + if (!book) + error("Failed to open bkroom movie"); + + book->moveTo(159, 99); + book->setBounds(Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, endTime, 600)); _vm->_video->waitUntilMovieEnds(book); } @@ -597,9 +601,9 @@ void Stoneship::o_hologramPlayback(uint16 op, uint16 var, uint16 argc, uint16 *a if (_hologramTurnedOn) { if (_hologramDisplayPos) endPoint = _hologramDisplayPos; - _vm->_video->setVideoBounds(displayMovie, Audio::Timestamp(0, startPoint, 600), Audio::Timestamp(0, endPoint, 600)); + displayMovie->setBounds(Audio::Timestamp(0, startPoint, 600), Audio::Timestamp(0, endPoint, 600)); } else { - _vm->_video->setVideoBounds(displayMovie, Audio::Timestamp(0, startPoint, 600), Audio::Timestamp(0, endPoint, 600)); + displayMovie->setBounds(Audio::Timestamp(0, startPoint, 600), Audio::Timestamp(0, endPoint, 600)); } _vm->_video->delayUntilMovieEnds(displayMovie); @@ -673,29 +677,45 @@ void Stoneship::o_chestValveVideos(uint16 op, uint16 var, uint16 argc, uint16 *a if (_state.chestValveState) { // Valve closing - VideoHandle valve = _vm->_video->playMovie(movie, 97, 267); - _vm->_video->setVideoBounds(valve, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 350, 600)); + VideoHandle valve = _vm->_video->playMovie(movie); + if (!valve) + error("Failed to open '%s'", movie.c_str()); + + valve->moveTo(97, 267); + valve->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 350, 600)); _vm->_video->waitUntilMovieEnds(valve); } else if (_state.chestWaterState) { // Valve opening, spilling water - VideoHandle valve = _vm->_video->playMovie(movie, 97, 267); - _vm->_video->setVideoBounds(valve, Audio::Timestamp(0, 350, 600), Audio::Timestamp(0, 650, 600)); + VideoHandle valve = _vm->_video->playMovie(movie); + if (!valve) + error("Failed to open '%s'", movie.c_str()); + + valve->moveTo(97, 267); + valve->setBounds(Audio::Timestamp(0, 350, 600), Audio::Timestamp(0, 650, 600)); _vm->_video->waitUntilMovieEnds(valve); _vm->_sound->playSound(3132); for (uint i = 0; i < 25; i++) { - valve = _vm->_video->playMovie(movie, 97, 267); - _vm->_video->setVideoBounds(valve, Audio::Timestamp(0, 650, 600), Audio::Timestamp(0, 750, 600)); + valve = _vm->_video->playMovie(movie); + if (!valve) + error("Failed to open '%s'", movie.c_str()); + + valve->moveTo(97, 267); + valve->setBounds(Audio::Timestamp(0, 650, 600), Audio::Timestamp(0, 750, 600)); _vm->_video->waitUntilMovieEnds(valve); } _vm->_sound->resumeBackgroundMyst(); } else { // Valve opening - VideoHandle valve = _vm->_video->playMovie(movie, 97, 267); - _vm->_video->seekToTime(valve, Audio::Timestamp(0, 350, 600)); - _vm->_video->setVideoRate(valve, -1); + VideoHandle valve = _vm->_video->playMovie(movie); + if (!valve) + error("Failed to open '%s'", movie.c_str()); + + valve->moveTo(97, 267); + valve->seek(Audio::Timestamp(0, 350, 600)); + valve->setRate(-1); _vm->_video->waitUntilMovieEnds(valve); } } @@ -716,14 +736,22 @@ void Stoneship::o_trapLockOpen(uint16 op, uint16 var, uint16 argc, uint16 *argv) Common::String movie = _vm->wrapMovieFilename("openloc", kStoneshipStack); - VideoHandle lock = _vm->_video->playMovie(movie, 187, 71); - _vm->_video->setVideoBounds(lock, Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 750, 600)); + VideoHandle lock = _vm->_video->playMovie(movie); + if (!lock) + error("Failed to open '%s'", movie.c_str()); + + lock->moveTo(187, 71); + lock->setBounds(Audio::Timestamp(0, 0, 600), Audio::Timestamp(0, 750, 600)); _vm->_video->waitUntilMovieEnds(lock); _vm->_sound->playSound(2143); - lock = _vm->_video->playMovie(movie, 187, 71); - _vm->_video->setVideoBounds(lock, Audio::Timestamp(0, 750, 600), Audio::Timestamp(0, 10000, 600)); + lock = _vm->_video->playMovie(movie); + if (!lock) + error("Failed to open '%s'", movie.c_str()); + + lock->moveTo(187, 71); + lock->setBounds(Audio::Timestamp(0, 750, 600), Audio::Timestamp(0, 10000, 600)); _vm->_video->waitUntilMovieEnds(lock); if (_state.pumpState != 4) diff --git a/engines/mohawk/riven.cpp b/engines/mohawk/riven.cpp index a7fe12b9e1..898f68c581 100644 --- a/engines/mohawk/riven.cpp +++ b/engines/mohawk/riven.cpp @@ -828,7 +828,7 @@ static void sunnersTopStairsTimer(MohawkEngine_Riven *vm) { VideoHandle oldHandle = vm->_video->findVideoHandleRiven(1); uint32 timerTime = 500; - if (oldHandle == NULL_VID_HANDLE || vm->_video->endOfVideo(oldHandle)) { + if (!oldHandle || oldHandle->endOfVideo()) { uint32 &sunnerTime = vm->_vars["jsunnertime"]; if (sunnerTime == 0) { @@ -836,7 +836,7 @@ static void sunnersTopStairsTimer(MohawkEngine_Riven *vm) { } else if (sunnerTime < vm->getTotalPlayTime()) { VideoHandle handle = vm->_video->playMovieRiven(vm->_rnd->getRandomNumberRng(1, 3)); - timerTime = vm->_video->getDuration(handle).msecs() + vm->_rnd->getRandomNumberRng(2, 15) * 1000; + timerTime = handle->getDuration().msecs() + vm->_rnd->getRandomNumberRng(2, 15) * 1000; } sunnerTime = timerTime + vm->getTotalPlayTime(); @@ -858,7 +858,7 @@ static void sunnersMidStairsTimer(MohawkEngine_Riven *vm) { VideoHandle oldHandle = vm->_video->findVideoHandleRiven(1); uint32 timerTime = 500; - if (oldHandle == NULL_VID_HANDLE || vm->_video->endOfVideo(oldHandle)) { + if (!oldHandle || oldHandle->endOfVideo()) { uint32 &sunnerTime = vm->_vars["jsunnertime"]; if (sunnerTime == 0) { @@ -874,7 +874,7 @@ static void sunnersMidStairsTimer(MohawkEngine_Riven *vm) { VideoHandle handle = vm->_video->playMovieRiven(movie); - timerTime = vm->_video->getDuration(handle).msecs() + vm->_rnd->getRandomNumberRng(1, 10) * 1000; + timerTime = handle->getDuration().msecs() + vm->_rnd->getRandomNumberRng(1, 10) * 1000; } sunnerTime = timerTime + vm->getTotalPlayTime(); @@ -896,7 +896,7 @@ static void sunnersLowerStairsTimer(MohawkEngine_Riven *vm) { VideoHandle oldHandle = vm->_video->findVideoHandleRiven(1); uint32 timerTime = 500; - if (oldHandle == NULL_VID_HANDLE || vm->_video->endOfVideo(oldHandle)) { + if (!oldHandle || oldHandle->endOfVideo()) { uint32 &sunnerTime = vm->_vars["jsunnertime"]; if (sunnerTime == 0) { @@ -904,7 +904,7 @@ static void sunnersLowerStairsTimer(MohawkEngine_Riven *vm) { } else if (sunnerTime < vm->getTotalPlayTime()) { VideoHandle handle = vm->_video->playMovieRiven(vm->_rnd->getRandomNumberRng(3, 5)); - timerTime = vm->_video->getDuration(handle).msecs() + vm->_rnd->getRandomNumberRng(1, 30) * 1000; + timerTime = handle->getDuration().msecs() + vm->_rnd->getRandomNumberRng(1, 30) * 1000; } sunnerTime = timerTime + vm->getTotalPlayTime(); @@ -926,7 +926,7 @@ static void sunnersBeachTimer(MohawkEngine_Riven *vm) { VideoHandle oldHandle = vm->_video->findVideoHandleRiven(3); uint32 timerTime = 500; - if (oldHandle == NULL_VID_HANDLE || vm->_video->endOfVideo(oldHandle)) { + if (!oldHandle || oldHandle->endOfVideo()) { uint32 &sunnerTime = vm->_vars["jsunnertime"]; if (sunnerTime == 0) { @@ -938,7 +938,7 @@ static void sunnersBeachTimer(MohawkEngine_Riven *vm) { vm->_video->activateMLST(mlstID, vm->getCurCard()); VideoHandle handle = vm->_video->playMovieRiven(mlstID); - timerTime = vm->_video->getDuration(handle).msecs() + vm->_rnd->getRandomNumberRng(1, 30) * 1000; + timerTime = handle->getDuration().msecs() + vm->_rnd->getRandomNumberRng(1, 30) * 1000; } sunnerTime = timerTime + vm->getTotalPlayTime(); @@ -969,7 +969,7 @@ void MohawkEngine_Riven::installCardTimer() { } void MohawkEngine_Riven::doVideoTimer(VideoHandle handle, bool force) { - assert(handle != NULL_VID_HANDLE); + assert(handle); uint16 id = _scriptMan->getStoredMovieOpcodeID(); @@ -977,7 +977,7 @@ void MohawkEngine_Riven::doVideoTimer(VideoHandle handle, bool force) { return; // Run the opcode if we can at this point - if (force || _video->getTime(handle) >= _scriptMan->getStoredMovieOpcodeTime()) + if (force || handle->getTime() >= _scriptMan->getStoredMovieOpcodeTime()) _scriptMan->runStoredMovieOpcode(); } @@ -1003,7 +1003,7 @@ void MohawkEngine_Riven::checkSunnerAlertClick() { // If the alert video is no longer playing, we have nothing left to do VideoHandle handle = _video->findVideoHandleRiven(1); - if (handle == NULL_VID_HANDLE || _video->endOfVideo(handle)) + if (!handle || handle->endOfVideo()) return; sunners = 1; diff --git a/engines/mohawk/riven_external.cpp b/engines/mohawk/riven_external.cpp index cda0683028..00075039fe 100644 --- a/engines/mohawk/riven_external.cpp +++ b/engines/mohawk/riven_external.cpp @@ -229,7 +229,7 @@ void RivenExternal::runCredits(uint16 video, uint32 delay) { VideoHandle videoHandle = _vm->_video->findVideoHandleRiven(video); while (!_vm->shouldQuit() && _vm->_gfx->getCurCreditsImage() <= 320) { - if (_vm->_video->getCurFrame(videoHandle) >= (int32)_vm->_video->getFrameCount(videoHandle) - 1) { + if (videoHandle->getCurFrame() >= (int32)videoHandle->getFrameCount() - 1) { if (nextCreditsFrameStart == 0) { // Set us up to start after delay ms nextCreditsFrameStart = _vm->_system->getMillis() + delay; @@ -265,10 +265,10 @@ void RivenExternal::runDomeCheck() { // Check if we clicked while the golden frame was showing VideoHandle video = _vm->_video->findVideoHandleRiven(1); - assert(video != NULL_VID_HANDLE); + assert(video); - int32 curFrame = _vm->_video->getCurFrame(video); - int32 frameCount = _vm->_video->getFrameCount(video); + int32 curFrame = video->getCurFrame(); + int32 frameCount = video->getFrameCount(); // The final frame of the video is the 'golden' frame (double meaning: the // frame that is the magic one is the one with the golden symbol) but we @@ -857,8 +857,12 @@ void RivenExternal::xbupdateboiler(uint16 argc, uint16 *argv) { _vm->_video->playMovieRiven(7); } } else { - _vm->_video->disableMovieRiven(7); - _vm->_video->disableMovieRiven(8); + VideoHandle handle = _vm->_video->findVideoHandleRiven(7); + if (handle) + handle->setEnabled(false); + handle = _vm->_video->findVideoHandleRiven(8); + if (handle) + handle->setEnabled(false); } } @@ -1149,8 +1153,8 @@ void RivenExternal::lowerPins() { // Play the video of the pins going down VideoHandle handle = _vm->_video->playMovieRiven(upMovie); - assert(handle != NULL_VID_HANDLE); - _vm->_video->setVideoBounds(handle, Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, startTime + 550, 600)); + assert(handle); + handle->setBounds(Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, startTime + 550, 600)); _vm->_video->waitUntilMovieEnds(handle); upMovie = 0; @@ -1181,8 +1185,8 @@ void RivenExternal::xgrotatepins(uint16 argc, uint16 *argv) { // Play the video of the pins rotating VideoHandle handle = _vm->_video->playMovieRiven(_vm->_vars["gupmoov"]); - assert(handle != NULL_VID_HANDLE); - _vm->_video->setVideoBounds(handle, Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, startTime + 1215, 600)); + assert(handle); + handle->setBounds(Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, startTime + 1215, 600)); _vm->_video->waitUntilMovieEnds(handle); } @@ -1265,9 +1269,9 @@ void RivenExternal::xgpincontrols(uint16 argc, uint16 *argv) { // Actually play the movie VideoHandle handle = _vm->_video->playMovieRiven(pinMovieCodes[imagePos - 1]); - assert(handle != NULL_VID_HANDLE); + assert(handle); uint32 startTime = 9630 - pinPos * 600; - _vm->_video->setVideoBounds(handle, Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, startTime + 550, 600)); + handle->setBounds(Audio::Timestamp(0, startTime, 600), Audio::Timestamp(0, startTime + 550, 600)); _vm->_video->waitUntilMovieEnds(handle); // Update the relevant variables @@ -1343,8 +1347,8 @@ void RivenExternal::xgrviewer(uint16 argc, uint16 *argv) { // Now play the movie VideoHandle handle = _vm->_video->playMovieRiven(1); - assert(handle != NULL_VID_HANDLE); - _vm->_video->setVideoBounds(handle, Audio::Timestamp(0, s_viewerTimeIntervals[curPos], 600), Audio::Timestamp(0, s_viewerTimeIntervals[newPos], 600)); + assert(handle); + handle->setBounds(Audio::Timestamp(0, s_viewerTimeIntervals[curPos], 600), Audio::Timestamp(0, s_viewerTimeIntervals[newPos], 600)); _vm->_video->waitUntilMovieEnds(handle); // Set the new position and let the card's scripts take over again @@ -1412,8 +1416,8 @@ void RivenExternal::xglviewer(uint16 argc, uint16 *argv) { // Now play the movie VideoHandle handle = _vm->_video->playMovieRiven(1); - assert(handle != NULL_VID_HANDLE); - _vm->_video->setVideoBounds(handle, Audio::Timestamp(0, s_viewerTimeIntervals[curPos], 600), Audio::Timestamp(0, s_viewerTimeIntervals[newPos], 600)); + assert(handle); + handle->setBounds(Audio::Timestamp(0, s_viewerTimeIntervals[curPos], 600), Audio::Timestamp(0, s_viewerTimeIntervals[newPos], 600)); _vm->_video->waitUntilMovieEnds(handle); // Set the new position to the variable @@ -1467,7 +1471,7 @@ static void catherineViewerIdleTimer(MohawkEngine_Riven *vm) { VideoHandle videoHandle = vm->_video->playMovieRiven(30); // Reset the timer - vm->installTimer(&catherineViewerIdleTimer, vm->_video->getDuration(videoHandle).msecs() + vm->_rnd->getRandomNumber(60) * 1000); + vm->installTimer(&catherineViewerIdleTimer, videoHandle->getDuration().msecs() + vm->_rnd->getRandomNumber(60) * 1000); } void RivenExternal::xglview_prisonon(uint16 argc, uint16 *argv) { @@ -1507,7 +1511,7 @@ void RivenExternal::xglview_prisonon(uint16 argc, uint16 *argv) { _vm->_video->activateMLST(cathMovie, _vm->getCurCard()); VideoHandle videoHandle = _vm->_video->playMovieRiven(30); - timeUntilNextMovie = _vm->_video->getDuration(videoHandle).msecs() + _vm->_rnd->getRandomNumber(60) * 1000; + timeUntilNextMovie = videoHandle->getDuration().msecs() + _vm->_rnd->getRandomNumber(60) * 1000; } else { // Otherwise, just redraw the imager timeUntilNextMovie = _vm->_rnd->getRandomNumberRng(10, 20) * 1000; @@ -1986,7 +1990,7 @@ void RivenExternal::xschool280_playwhark(uint16 argc, uint16 *argv) { Audio::Timestamp startTime = Audio::Timestamp(0, (11560 / 19) * (*posVar), 600); *posVar += number; // Adjust to the end Audio::Timestamp endTime = Audio::Timestamp(0, (11560 / 19) * (*posVar), 600); - _vm->_video->setVideoBounds(handle, startTime, endTime); + handle->setBounds(startTime, endTime); _vm->_video->waitUntilMovieEnds(handle); if (*posVar > 19) { @@ -2059,7 +2063,7 @@ void RivenExternal::xbookclick(uint16 argc, uint16 *argv) { debug(0, "\tHotspot = %d -> %d", argv[3], hotspotMap[argv[3] - 1]); // Just let the video play while we wait until Gehn opens the trap book for us - while (_vm->_video->getTime(video) < startTime && !_vm->shouldQuit()) { + while (video->getTime() < startTime && !_vm->shouldQuit()) { if (_vm->_video->updateMovies()) _vm->_system->updateScreen(); @@ -2084,7 +2088,7 @@ void RivenExternal::xbookclick(uint16 argc, uint16 *argv) { // OK, Gehn has opened the trap book and has asked us to go in. Let's watch // and see what the player will do... - while (_vm->_video->getTime(video) < endTime && !_vm->shouldQuit()) { + while (video->getTime() < endTime && !_vm->shouldQuit()) { bool updateScreen = _vm->_video->updateMovies(); Common::Event event; @@ -2335,7 +2339,7 @@ static void rebelPrisonWindowTimer(MohawkEngine_Riven *vm) { VideoHandle handle = vm->_video->playMovieRiven(movie); // Ensure the next video starts after this one ends - uint32 timeUntilNextVideo = vm->_video->getDuration(handle).msecs() + vm->_rnd->getRandomNumberRng(38, 58) * 1000; + uint32 timeUntilNextVideo = handle->getDuration().msecs() + vm->_rnd->getRandomNumberRng(38, 58) * 1000; // Save the time in case we leave the card and return vm->_vars["rvillagetime"] = timeUntilNextVideo + vm->getTotalPlayTime(); @@ -2434,7 +2438,7 @@ void RivenExternal::xtexterior300_telescopedown(uint16 argc, uint16 *argv) { static const uint32 timeIntervals[] = { 4320, 3440, 2560, 1760, 880, 0 }; uint16 movieCode = telescopeCover ? 1 : 2; VideoHandle handle = _vm->_video->playMovieRiven(movieCode); - _vm->_video->setVideoBounds(handle, Audio::Timestamp(0, timeIntervals[telescopePos], 600), Audio::Timestamp(0, timeIntervals[telescopePos - 1], 600)); + handle->setBounds(Audio::Timestamp(0, timeIntervals[telescopePos], 600), Audio::Timestamp(0, timeIntervals[telescopePos - 1], 600)); _vm->_sound->playSound(14); // Play the moving sound _vm->_video->waitUntilMovieEnds(handle); @@ -2467,7 +2471,7 @@ void RivenExternal::xtexterior300_telescopeup(uint16 argc, uint16 *argv) { static const uint32 timeIntervals[] = { 0, 800, 1680, 2560, 3440, 4320 }; uint16 movieCode = _vm->_vars["ttelecover"] ? 4 : 5; VideoHandle handle = _vm->_video->playMovieRiven(movieCode); - _vm->_video->setVideoBounds(handle, Audio::Timestamp(0, timeIntervals[telescopePos - 1], 600), Audio::Timestamp(0, timeIntervals[telescopePos], 600)); + handle->setBounds(Audio::Timestamp(0, timeIntervals[telescopePos - 1], 600), Audio::Timestamp(0, timeIntervals[telescopePos], 600)); _vm->_sound->playSound(14); // Play the moving sound _vm->_video->waitUntilMovieEnds(handle); @@ -2569,26 +2573,47 @@ void RivenExternal::xt7500_checkmarbles(uint16 argc, uint16 *argv) { void RivenExternal::xt7600_setupmarbles(uint16 argc, uint16 *argv) { // Draw the small marbles when we're a step away from the waffle - uint16 baseBitmapId = _vm->findResourceID(ID_TBMP, "*tsmallred"); + + // Convert from marble X coordinate to screen X coordinate + static const uint16 xPosOffsets[] = { + 246, 245, 244, 243, 243, 241, 240, 240, 239, 238, 237, 237, 236, 235, 234, 233, 232, 231, 230, 229, 228, 227, 226, 226, 225 + }; + + // Convert from marble Y coordinate to screen Y coordinate + static const uint16 yPosOffsets[] = { + 261, 263, 265, 267, 268, 270, 272, 274, 276, 278, 281, 284, 285, 288, 290, 293, 295, 298, 300, 303, 306, 309, 311, 314, 316 + }; + + // Handle spacing for y coordinates due to the angle + static const double yAdjusts[] = { + 4.56, 4.68, 4.76, 4.84, 4.84, 4.96, 5.04, 5.04, 5.12, 5.2, 5.28, 5.28, 5.36, 5.44, 5.4, 5.6, 5.72, 5.8, 5.88, 5.96, 6.04, 6.12, 6.2, 6.2, 6.28 + }; + + // Waffle state of 0 is up, 1 down bool waffleDown = _vm->_vars["twaffle"] != 0; // Note that each of the small marble images is exactly 4x2 + // The original seems to scale the marble images from extras.mhk, but + // we're using the pre-scaled images in the stack. + uint16 baseBitmapId = _vm->findResourceID(ID_TBMP, "*tsmallred"); for (uint16 i = 0; i < kMarbleCount; i++) { - uint32 &var = _vm->_vars[s_marbleNames[i]]; + uint32 var = _vm->_vars[s_marbleNames[i]]; if (var == 0) { // The marble is still in its initial place // (Note that this is still drawn even if the waffle is down) - int marbleX = 376 + i * 2; - int marbleY = 253 + i * 4; - _vm->_gfx->copyImageToScreen(baseBitmapId + i, marbleX, marbleY, marbleX + kSmallMarbleWidth, marbleY + kSmallMarbleHeight); + static const uint16 defaultX[] = { 375, 377, 379, 381, 383, 385 }; + static const uint16 defaultY[] = { 253, 257, 261, 265, 268, 273 }; + _vm->_gfx->copyImageToScreen(baseBitmapId + i, defaultX[i], defaultY[i], defaultX[i] + kSmallMarbleWidth, defaultY[i] + kSmallMarbleHeight); } else if (waffleDown) { // The marble is on the grid and the waffle is down // (Nothing to draw here) } else { // The marble is on the grid and the waffle is up - // TODO: Draw them onto the grid + int marbleX = (int)floor(getMarbleX(var) * yAdjusts[getMarbleY(var)] + xPosOffsets[getMarbleY(var)] + 0.5); + int marbleY = yPosOffsets[getMarbleY(var)]; + _vm->_gfx->copyImageToScreen(baseBitmapId + i, marbleX, marbleY, marbleX + kSmallMarbleWidth, marbleY + kSmallMarbleHeight); } } } diff --git a/engines/mohawk/riven_scripts.cpp b/engines/mohawk/riven_scripts.cpp index 29ee5cd50b..caa235ec8b 100644 --- a/engines/mohawk/riven_scripts.cpp +++ b/engines/mohawk/riven_scripts.cpp @@ -493,7 +493,9 @@ void RivenScript::changeStack(uint16 op, uint16 argc, uint16 *argv) { // Command 28: disable a movie void RivenScript::disableMovie(uint16 op, uint16 argc, uint16 *argv) { - _vm->_video->disableMovieRiven(argv[0]); + VideoHandle handle = _vm->_video->findVideoHandleRiven(argv[0]); + if (handle) + handle->setEnabled(false); } // Command 29: disable all movies @@ -503,7 +505,9 @@ void RivenScript::disableAllMovies(uint16 op, uint16 argc, uint16 *argv) { // Command 31: enable a movie void RivenScript::enableMovie(uint16 op, uint16 argc, uint16 *argv) { - _vm->_video->enableMovieRiven(argv[0]); + VideoHandle handle = _vm->_video->findVideoHandleRiven(argv[0]); + if (handle) + handle->setEnabled(true); } // Command 32: play foreground movie - blocking (movie_id) diff --git a/engines/mohawk/video.cpp b/engines/mohawk/video.cpp index 3f27e4a1a4..ff4a69cd28 100644 --- a/engines/mohawk/video.cpp +++ b/engines/mohawk/video.cpp @@ -24,6 +24,7 @@ #include "mohawk/resource.h" #include "mohawk/video.h" +#include "common/algorithm.h" #include "common/debug.h" #include "common/events.h" #include "common/textconsole.h" @@ -37,19 +38,110 @@ namespace Mohawk { -void VideoEntry::clear() { - video = 0; - x = 0; - y = 0; - loop = false; - enabled = false; - start = Audio::Timestamp(0, 1); - filename.clear(); - id = -1; +VideoEntry::VideoEntry() : _video(0), _id(-1), _x(0), _y(0), _loop(false), _enabled(true) { } -bool VideoEntry::endOfVideo() { - return !video || video->endOfVideo(); +VideoEntry::VideoEntry(Video::VideoDecoder *video, const Common::String &fileName) : _video(video), _fileName(fileName), _id(-1), _x(0), _y(0), _loop(false), _enabled(true) { +} + +VideoEntry::VideoEntry(Video::VideoDecoder *video, int id) : _video(video), _id(id), _x(0), _y(0), _loop(false), _enabled(true) { +} + +VideoEntry::~VideoEntry() { + close(); +} + +void VideoEntry::close() { + delete _video; + _video = 0; +} + +bool VideoEntry::endOfVideo() const { + return !isOpen() || _video->endOfVideo(); +} + +int VideoEntry::getCurFrame() const { + assert(_video); + return _video->getCurFrame(); +} + +uint32 VideoEntry::getFrameCount() const { + assert(_video); + return _video->getFrameCount(); +} + +uint32 VideoEntry::getTime() const { + assert(_video); + return _video->getTime(); +} + +Audio::Timestamp VideoEntry::getDuration() const { + assert(_video); + return _video->getDuration(); +} + +Common::Rational VideoEntry::getRate() const { + assert(_video); + return _video->getRate(); +} + +void VideoEntry::center() { + assert(_video); + _x = (g_system->getWidth() - _video->getWidth()) / 2; + _y = (g_system->getHeight() - _video->getHeight()) / 2; +} + +void VideoEntry::setBounds(const Audio::Timestamp &startTime, const Audio::Timestamp &endTime) { + assert(_video); + _start = startTime; + _video->setEndTime(endTime); + _video->seek(startTime); +} + +void VideoEntry::seek(const Audio::Timestamp &time) { + assert(_video); + _video->seek(time); +} + +void VideoEntry::setRate(const Common::Rational &rate) { + assert(_video); + _video->setRate(rate); +} + +void VideoEntry::pause(bool isPaused) { + assert(_video); + _video->pauseVideo(isPaused); +} + +void VideoEntry::start() { + assert(_video); + _video->start(); +} + +void VideoEntry::stop() { + assert(_video); + _video->stop(); +} + +bool VideoEntry::isPlaying() const { + assert(_video); + return _video->isPlaying(); +} + +int VideoEntry::getVolume() const { + assert(_video); + return _video->getVolume(); +} + +void VideoEntry::setVolume(int volume) { + assert(_video); + _video->setVolume(CLIP(volume, 0, 255)); +} + +VideoHandle::VideoHandle(VideoEntryPtr ptr) : _ptr(ptr) { +} + +VideoHandle::VideoHandle(const VideoHandle &handle) : _ptr(handle._ptr) { } VideoManager::VideoManager(MohawkEngine* vm) : _vm(vm) { @@ -62,41 +154,42 @@ VideoManager::~VideoManager() { } void VideoManager::pauseVideos() { - for (uint16 i = 0; i < _videoStreams.size(); i++) - if (_videoStreams[i].video) - _videoStreams[i]->pauseVideo(true); + for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++) + (*it)->pause(true); } void VideoManager::resumeVideos() { - for (uint16 i = 0; i < _videoStreams.size(); i++) - if (_videoStreams[i].video) - _videoStreams[i]->pauseVideo(false); + for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++) + (*it)->pause(false); } void VideoManager::stopVideos() { - for (uint16 i = 0; i < _videoStreams.size(); i++) - delete _videoStreams[i].video; + for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++) + (*it)->close(); - _videoStreams.clear(); + _videos.clear(); } -void VideoManager::playMovieBlocking(const Common::String &filename, uint16 x, uint16 y, bool clearScreen) { - VideoHandle videoHandle = createVideoHandle(filename, x, y, false); - if (videoHandle == NULL_VID_HANDLE) +void VideoManager::playMovieBlocking(const Common::String &fileName, uint16 x, uint16 y, bool clearScreen) { + VideoEntryPtr ptr = open(fileName); + if (!ptr) return; + ptr->moveTo(x, y); + // Clear screen if requested if (clearScreen) { _vm->_system->fillScreen(_vm->_system->getScreenFormat().RGBToColor(0, 0, 0)); _vm->_system->updateScreen(); } - waitUntilMovieEnds(videoHandle); + ptr->start(); + waitUntilMovieEnds(ptr); } -void VideoManager::playMovieBlockingCentered(const Common::String &filename, bool clearScreen) { - VideoHandle videoHandle = createVideoHandle(filename, 0, 0, false); - if (videoHandle == NULL_VID_HANDLE) +void VideoManager::playMovieBlockingCentered(const Common::String &fileName, bool clearScreen) { + VideoEntryPtr ptr = open(fileName); + if (!ptr) return; // Clear screen if requested @@ -105,19 +198,22 @@ void VideoManager::playMovieBlockingCentered(const Common::String &filename, boo _vm->_system->updateScreen(); } - _videoStreams[videoHandle].x = (_vm->_system->getWidth() - _videoStreams[videoHandle]->getWidth()) / 2; - _videoStreams[videoHandle].y = (_vm->_system->getHeight() - _videoStreams[videoHandle]->getHeight()) / 2; - - waitUntilMovieEnds(videoHandle); + ptr->center(); + ptr->start(); + waitUntilMovieEnds(ptr); } void VideoManager::waitUntilMovieEnds(VideoHandle videoHandle) { - if (videoHandle == NULL_VID_HANDLE) + if (!videoHandle) return; + // Sanity check + if (videoHandle._ptr->isLooping()) + error("Called waitUntilMovieEnds() on a looping video"); + bool continuePlaying = true; - while (!_videoStreams[videoHandle].endOfVideo() && !_vm->shouldQuit() && continuePlaying) { + while (!videoHandle->endOfVideo() && !_vm->shouldQuit() && continuePlaying) { if (updateMovies()) _vm->_system->updateScreen(); @@ -149,12 +245,22 @@ void VideoManager::waitUntilMovieEnds(VideoHandle videoHandle) { _vm->_system->delayMillis(10); } - delete _videoStreams[videoHandle].video; - _videoStreams[videoHandle].clear(); + // Ensure it's removed + removeEntry(videoHandle._ptr); } void VideoManager::delayUntilMovieEnds(VideoHandle videoHandle) { - while (!_videoStreams[videoHandle].endOfVideo() && !_vm->shouldQuit()) { + // FIXME: Why is this separate from waitUntilMovieEnds? + // It seems to only cut out the event loop (which is bad). + + if (!videoHandle) + return; + + // Sanity check + if (videoHandle._ptr->isLooping()) + error("Called delayUntilMovieEnds() on a looping video"); + + while (!videoHandle->endOfVideo() && !_vm->shouldQuit()) { if (updateMovies()) _vm->_system->updateScreen(); @@ -162,73 +268,59 @@ void VideoManager::delayUntilMovieEnds(VideoHandle videoHandle) { _vm->_system->delayMillis(10); } - delete _videoStreams[videoHandle].video; - _videoStreams[videoHandle].clear(); + // Ensure it's removed + removeEntry(videoHandle._ptr); } -VideoHandle VideoManager::playMovie(const Common::String &filename, int16 x, int16 y, bool loop) { - VideoHandle videoHandle = createVideoHandle(filename, x, y, loop); - if (videoHandle == NULL_VID_HANDLE) - return NULL_VID_HANDLE; - - // Center x if requested - if (x < 0) - _videoStreams[videoHandle].x = (_vm->_system->getWidth() - _videoStreams[videoHandle]->getWidth()) / 2; - - // Center y if requested - if (y < 0) - _videoStreams[videoHandle].y = (_vm->_system->getHeight() - _videoStreams[videoHandle]->getHeight()) / 2; +VideoHandle VideoManager::playMovie(const Common::String &fileName) { + VideoEntryPtr ptr = open(fileName); + if (!ptr) + return VideoHandle(); - return videoHandle; + ptr->start(); + return ptr; } -VideoHandle VideoManager::playMovie(uint16 id, int16 x, int16 y, bool loop) { - VideoHandle videoHandle = createVideoHandle(id, x, y, loop); - if (videoHandle == NULL_VID_HANDLE) - return NULL_VID_HANDLE; +VideoHandle VideoManager::playMovie(uint16 id) { + VideoEntryPtr ptr = open(id); + if (!ptr) + return VideoHandle(); - // Center x if requested - if (x < 0) - _videoStreams[videoHandle].x = (_vm->_system->getWidth() - _videoStreams[videoHandle]->getWidth()) / 2; - - // Center y if requested - if (y < 0) - _videoStreams[videoHandle].y = (_vm->_system->getHeight() - _videoStreams[videoHandle]->getHeight()) / 2; - - return videoHandle; + ptr->start(); + return ptr; } bool VideoManager::updateMovies() { bool updateScreen = false; - for (uint32 i = 0; i < _videoStreams.size() && !_vm->shouldQuit(); i++) { - // Skip deleted videos - if (!_videoStreams[i].video) - continue; - - // Remove any videos that are over - if (_videoStreams[i].endOfVideo()) { - if (_videoStreams[i].loop) { - _videoStreams[i]->seek(_videoStreams[i].start); + for (VideoList::iterator it = _videos.begin(); it != _videos.end(); ) { + // Check of the video has reached the end + if ((*it)->endOfVideo()) { + if ((*it)->isLooping()) { + // Seek back if looping + (*it)->seek((*it)->getStart()); } else { - // Check the video time one last time before deleting it - _vm->doVideoTimer(i, true); - delete _videoStreams[i].video; - _videoStreams[i].clear(); + // Done; close and continue on + (*it)->close(); + it = _videos.erase(it); continue; } } - // Nothing more to do if we're paused - if (_videoStreams[i]->isPaused()) + Video::VideoDecoder *video = (*it)->_video; + + // Ignore paused videos + if (video->isPaused()) { + it++; continue; + } // Check if we need to draw a frame - if (_videoStreams[i]->needsUpdate()) { - const Graphics::Surface *frame = _videoStreams[i]->decodeNextFrame(); + if (video->needsUpdate()) { + const Graphics::Surface *frame = video->decodeNextFrame(); Graphics::Surface *convertedFrame = 0; - if (frame && _videoStreams[i].enabled) { + if (frame && (*it)->isEnabled()) { Graphics::PixelFormat pixelFormat = _vm->_system->getScreenFormat(); if (frame->format != pixelFormat) { @@ -236,25 +328,25 @@ bool VideoManager::updateMovies() { // support in the codec. Set _enableDither if shows up. if (pixelFormat.bytesPerPixel == 1) { warning("Cannot convert high color video frame to 8bpp"); - delete _videoStreams[i].video; - _videoStreams[i].clear(); + (*it)->close(); + it = _videos.erase(it); continue; } // Convert to the current screen format - convertedFrame = frame->convertTo(pixelFormat, _videoStreams[i]->getPalette()); + convertedFrame = frame->convertTo(pixelFormat, video->getPalette()); frame = convertedFrame; - } else if (pixelFormat.bytesPerPixel == 1 && _videoStreams[i]->hasDirtyPalette()) { + } else if (pixelFormat.bytesPerPixel == 1 && video->hasDirtyPalette()) { // Set the palette when running in 8bpp mode only // Don't do this for Myst, which has its own per-stack handling if (_vm->getGameType() != GType_MYST) - _vm->_system->getPaletteManager()->setPalette(_videoStreams[i]->getPalette(), 0, 256); + _vm->_system->getPaletteManager()->setPalette(video->getPalette(), 0, 256); } // Clip the width/height to make sure we stay on the screen (Myst does this a few times) - uint16 width = MIN<int32>(_videoStreams[i]->getWidth(), _vm->_system->getWidth() - _videoStreams[i].x); - uint16 height = MIN<int32>(_videoStreams[i]->getHeight(), _vm->_system->getHeight() - _videoStreams[i].y); - _vm->_system->copyRectToScreen(frame->getPixels(), frame->pitch, _videoStreams[i].x, _videoStreams[i].y, width, height); + uint16 width = MIN<int32>(video->getWidth(), _vm->_system->getWidth() - (*it)->getX()); + uint16 height = MIN<int32>(video->getHeight(), _vm->_system->getHeight() - (*it)->getY()); + _vm->_system->copyRectToScreen(frame->getPixels(), frame->pitch, (*it)->getX(), (*it)->getY(), width, height); // We've drawn something to the screen, make sure we update it updateScreen = true; @@ -268,7 +360,10 @@ bool VideoManager::updateMovies() { } // Check the video time - _vm->doVideoTimer(i, false); + _vm->doVideoTimer(*it, false); + + // Remember to increase the iterator + it++; } // Return true if we need to update the screen @@ -323,251 +418,165 @@ void VideoManager::clearMLST() { } VideoHandle VideoManager::playMovieRiven(uint16 id) { - for (uint16 i = 0; i < _mlstRecords.size(); i++) + for (uint16 i = 0; i < _mlstRecords.size(); i++) { if (_mlstRecords[i].code == id) { debug(1, "Play tMOV %d (non-blocking) at (%d, %d) %s, Volume = %d", _mlstRecords[i].movieID, _mlstRecords[i].left, _mlstRecords[i].top, _mlstRecords[i].loop != 0 ? "looping" : "non-looping", _mlstRecords[i].volume); - return createVideoHandle(_mlstRecords[i].movieID, _mlstRecords[i].left, _mlstRecords[i].top, _mlstRecords[i].loop != 0, _mlstRecords[i].volume); + + VideoEntryPtr ptr = open(_mlstRecords[i].movieID); + if (ptr) { + ptr->moveTo(_mlstRecords[i].left, _mlstRecords[i].top); + ptr->setLooping(_mlstRecords[i].loop != 0); + ptr->setVolume(_mlstRecords[i].volume); + ptr->start(); + } + + return ptr; } + } - return NULL_VID_HANDLE; + return VideoHandle(); } void VideoManager::playMovieBlockingRiven(uint16 id) { - for (uint16 i = 0; i < _mlstRecords.size(); i++) + for (uint16 i = 0; i < _mlstRecords.size(); i++) { if (_mlstRecords[i].code == id) { debug(1, "Play tMOV %d (blocking) at (%d, %d), Volume = %d", _mlstRecords[i].movieID, _mlstRecords[i].left, _mlstRecords[i].top, _mlstRecords[i].volume); - VideoHandle videoHandle = createVideoHandle(_mlstRecords[i].movieID, _mlstRecords[i].left, _mlstRecords[i].top, false); - waitUntilMovieEnds(videoHandle); + VideoEntryPtr ptr = open(_mlstRecords[i].movieID); + ptr->moveTo(_mlstRecords[i].left, _mlstRecords[i].top); + ptr->setVolume(_mlstRecords[i].volume); + ptr->start(); + waitUntilMovieEnds(ptr); return; } + } } void VideoManager::stopMovieRiven(uint16 id) { debug(2, "Stopping movie %d", id); - for (uint16 i = 0; i < _mlstRecords.size(); i++) - if (_mlstRecords[i].code == id) - for (uint16 j = 0; j < _videoStreams.size(); j++) - if (_mlstRecords[i].movieID == _videoStreams[j].id) { - delete _videoStreams[j].video; - _videoStreams[j].clear(); - return; - } -} - -void VideoManager::enableMovieRiven(uint16 id) { - debug(2, "Enabling movie %d", id); - for (uint16 i = 0; i < _mlstRecords.size(); i++) - if (_mlstRecords[i].code == id) - for (uint16 j = 0; j < _videoStreams.size(); j++) - if (_mlstRecords[i].movieID == _videoStreams[j].id) { - _videoStreams[j].enabled = true; - return; - } -} - -void VideoManager::disableMovieRiven(uint16 id) { - debug(2, "Disabling movie %d", id); - for (uint16 i = 0; i < _mlstRecords.size(); i++) - if (_mlstRecords[i].code == id) - for (uint16 j = 0; j < _videoStreams.size(); j++) - if (_mlstRecords[i].movieID == _videoStreams[j].id) { - _videoStreams[j].enabled = false; - return; - } + VideoHandle handle = findVideoHandleRiven(id); + if (handle) + removeEntry(handle._ptr); } void VideoManager::disableAllMovies() { debug(2, "Disabling all movies"); - for (uint16 i = 0; i < _videoStreams.size(); i++) - _videoStreams[i].enabled = false; + for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++) + (*it)->setEnabled(false); } -VideoHandle VideoManager::createVideoHandle(uint16 id, uint16 x, uint16 y, bool loop, uint16 volume) { - // First, check to see if that video is already playing - for (uint32 i = 0; i < _videoStreams.size(); i++) - if (_videoStreams[i].id == id) - return i; +VideoEntryPtr VideoManager::open(uint16 id) { + // If this video is already playing, return that handle + VideoHandle oldHandle = findVideoHandle(id); + if (oldHandle._ptr) + return oldHandle._ptr; // Otherwise, create a new entry - Video::QuickTimeDecoder *decoder = new Video::QuickTimeDecoder(); - decoder->setChunkBeginOffset(_vm->getResourceOffset(ID_TMOV, id)); - decoder->loadStream(_vm->getResource(ID_TMOV, id)); - decoder->setVolume((volume >= 256) ? 255 : volume); - - VideoEntry entry; - entry.clear(); - entry.video = decoder; - entry.x = x; - entry.y = y; - entry.id = id; - entry.loop = loop; - entry.enabled = true; + Video::QuickTimeDecoder *video = new Video::QuickTimeDecoder(); + video->setChunkBeginOffset(_vm->getResourceOffset(ID_TMOV, id)); + video->loadStream(_vm->getResource(ID_TMOV, id)); + + // Create the entry + VideoEntryPtr entry(new VideoEntry(video, id)); // Enable dither if necessary checkEnableDither(entry); - entry->start(); + // Add it to the video list + _videos.push_back(entry); - // Search for any deleted videos so we can take a formerly used slot - for (uint32 i = 0; i < _videoStreams.size(); i++) - if (!_videoStreams[i].video) { - _videoStreams[i] = entry; - return i; - } - - // Otherwise, just add it to the list - _videoStreams.push_back(entry); - return _videoStreams.size() - 1; + return entry; } -VideoHandle VideoManager::createVideoHandle(const Common::String &filename, uint16 x, uint16 y, bool loop, byte volume) { - // First, check to see if that video is already playing - for (uint32 i = 0; i < _videoStreams.size(); i++) - if (_videoStreams[i].filename == filename) - return i; +VideoEntryPtr VideoManager::open(const Common::String &fileName) { + // If this video is already playing, return that entry + VideoHandle oldHandle = findVideoHandle(fileName); + if (oldHandle._ptr) + return oldHandle._ptr; // Otherwise, create a new entry - VideoEntry entry; - entry.clear(); - entry.video = new Video::QuickTimeDecoder(); - entry.x = x; - entry.y = y; - entry.filename = filename; - entry.loop = loop; - entry.enabled = true; - - Common::File *file = new Common::File(); - if (!file->open(filename)) { - delete file; - return NULL_VID_HANDLE; + Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(fileName); + if (!stream) + return VideoEntryPtr(); + + Video::VideoDecoder *video = new Video::QuickTimeDecoder(); + if (!video->loadStream(stream)) { + // FIXME: Better error handling + delete video; + return VideoEntryPtr(); } - entry->loadStream(file); + // Create the entry + VideoEntryPtr entry(new VideoEntry(video, fileName)); // Enable dither if necessary checkEnableDither(entry); - entry->setVolume(volume); - entry->start(); - - // Search for any deleted videos so we can take a formerly used slot - for (uint32 i = 0; i < _videoStreams.size(); i++) - if (!_videoStreams[i].video) { - _videoStreams[i] = entry; - return i; - } + // Add it to the video list + _videos.push_back(entry); - // Otherwise, just add it to the list - _videoStreams.push_back(entry); - return _videoStreams.size() - 1; + return entry; } VideoHandle VideoManager::findVideoHandleRiven(uint16 id) { for (uint16 i = 0; i < _mlstRecords.size(); i++) if (_mlstRecords[i].code == id) - for (uint32 j = 0; j < _videoStreams.size(); j++) - if (_videoStreams[j].video && _mlstRecords[i].movieID == _videoStreams[j].id) - return j; + for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++) + if ((*it)->getID() == _mlstRecords[i].movieID) + return *it; - return NULL_VID_HANDLE; + return VideoHandle(); } VideoHandle VideoManager::findVideoHandle(uint16 id) { - if (!id) - return NULL_VID_HANDLE; + if (id == 0) + return VideoHandle(); - for (uint32 i = 0; i < _videoStreams.size(); i++) - if (_videoStreams[i].video && _videoStreams[i].id == id) - return i; + for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++) + if ((*it)->getID() == id) + return *it; - return NULL_VID_HANDLE; + return VideoHandle(); } -VideoHandle VideoManager::findVideoHandle(const Common::String &filename) { - if (filename.empty()) - return NULL_VID_HANDLE; +VideoHandle VideoManager::findVideoHandle(const Common::String &fileName) { + if (fileName.empty()) + return VideoHandle(); - for (uint32 i = 0; i < _videoStreams.size(); i++) - if (_videoStreams[i].video && _videoStreams[i].filename.equalsIgnoreCase(filename)) - return i; - - return NULL_VID_HANDLE; -} - -int VideoManager::getCurFrame(VideoHandle handle) { - assert(handle != NULL_VID_HANDLE); - return _videoStreams[handle]->getCurFrame(); -} - -uint32 VideoManager::getFrameCount(VideoHandle handle) { - assert(handle != NULL_VID_HANDLE); - return _videoStreams[handle]->getFrameCount(); -} + for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++) + if ((*it)->getFileName().equalsIgnoreCase(fileName)) + return *it; -uint32 VideoManager::getTime(VideoHandle handle) { - assert(handle != NULL_VID_HANDLE); - return _videoStreams[handle]->getTime(); -} - -Audio::Timestamp VideoManager::getDuration(VideoHandle handle) { - assert(handle != NULL_VID_HANDLE); - return _videoStreams[handle]->getDuration(); -} - -bool VideoManager::endOfVideo(VideoHandle handle) { - assert(handle != NULL_VID_HANDLE); - return _videoStreams[handle].endOfVideo(); + return VideoHandle(); } bool VideoManager::isVideoPlaying() { - for (uint32 i = 0; i < _videoStreams.size(); i++) - if (!_videoStreams[i].endOfVideo()) + for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++) + if (!(*it)->endOfVideo()) return true; return false; } -void VideoManager::setVideoBounds(VideoHandle handle, Audio::Timestamp start, Audio::Timestamp end) { - assert(handle != NULL_VID_HANDLE); - _videoStreams[handle].start = start; - _videoStreams[handle]->setEndTime(end); - _videoStreams[handle]->seek(start); -} - -void VideoManager::drawVideoFrame(VideoHandle handle, Audio::Timestamp time) { - assert(handle != NULL_VID_HANDLE); - _videoStreams[handle]->seek(time); +void VideoManager::drawVideoFrame(VideoHandle handle, const Audio::Timestamp &time) { + // FIXME: This should be done separately from the "playing" + // videos eventually. + assert(handle); + handle->seek(time); updateMovies(); - delete _videoStreams[handle].video; - _videoStreams[handle].clear(); -} - -void VideoManager::seekToTime(VideoHandle handle, Audio::Timestamp time) { - assert(handle != NULL_VID_HANDLE); - _videoStreams[handle]->seek(time); -} - -void VideoManager::setVideoLooping(VideoHandle handle, bool loop) { - assert(handle != NULL_VID_HANDLE); - _videoStreams[handle].loop = loop; -} - -Common::Rational VideoManager::getVideoRate(VideoHandle handle) const { - assert(handle != NULL_VID_HANDLE); - return _videoStreams[handle]->getRate(); + handle->close(); } -void VideoManager::setVideoRate(VideoHandle handle, const Common::Rational &rate) { - assert(handle != NULL_VID_HANDLE); - _videoStreams[handle]->setRate(rate); +VideoManager::VideoList::iterator VideoManager::findEntry(VideoEntryPtr ptr) { + return Common::find(_videos.begin(), _videos.end(), ptr); } -void VideoManager::pauseMovie(VideoHandle handle, bool pause) { - assert(handle != NULL_VID_HANDLE); - _videoStreams[handle]->pauseVideo(pause); +void VideoManager::removeEntry(VideoEntryPtr ptr) { + VideoManager::VideoList::iterator it = findEntry(ptr); + if (it != _videos.end()) + _videos.erase(it); } -void VideoManager::checkEnableDither(VideoEntry &entry) { +void VideoManager::checkEnableDither(VideoEntryPtr &entry) { // If we're not dithering, bail out if (!_enableDither) return; @@ -575,13 +584,13 @@ void VideoManager::checkEnableDither(VideoEntry &entry) { // Set the palette byte palette[256 * 3]; g_system->getPaletteManager()->grabPalette(palette, 0, 256); - entry->setDitheringPalette(palette); + entry->_video->setDitheringPalette(palette); - if (entry->getPixelFormat().bytesPerPixel != 1) { - if (entry.filename.empty()) - error("Failed to set dither for video %d", entry.id); + if (entry->_video->getPixelFormat().bytesPerPixel != 1) { + if (entry->getFileName().empty()) + error("Failed to set dither for video tMOV %d", entry->getID()); else - error("Failed to set dither for video %s", entry.filename.c_str()); + error("Failed to set dither for video %s", entry->getFileName().c_str()); } } diff --git a/engines/mohawk/video.h b/engines/mohawk/video.h index deb09afe6b..106a32f8e2 100644 --- a/engines/mohawk/video.h +++ b/engines/mohawk/video.h @@ -23,9 +23,17 @@ #ifndef MOHAWK_VIDEO_H #define MOHAWK_VIDEO_H +#include "audio/timestamp.h" #include "common/array.h" +#include "common/list.h" +#include "common/noncopyable.h" +#include "common/ptr.h" +#include "common/rational.h" #include "graphics/pixelformat.h" -#include "video/video_decoder.h" + +namespace Video { +class VideoDecoder; +} namespace Mohawk { @@ -43,29 +51,254 @@ struct MLSTRecord { uint16 u1; }; -struct VideoEntry { +/** + * A video monitored by the VideoManager + */ +class VideoEntry : private Common::NonCopyable { + // The private members should be able to be manipulated by VideoManager + friend class VideoManager; + +private: + // Hide the destructor/constructor + // Only VideoManager should be allowed + VideoEntry(); + VideoEntry(Video::VideoDecoder *video, const Common::String &fileName); + VideoEntry(Video::VideoDecoder *video, int id); + +public: + ~VideoEntry(); + + /** + * Convenience implicit cast to bool + */ + operator bool() const { return isOpen(); } + + /** + * Is the video open? + */ + bool isOpen() const { return _video != 0; } + + /** + * Close the video + */ + void close(); + + /** + * Has the video reached its end? + */ + bool endOfVideo() const; + + /** + * Get the X position of where the video is displayed + */ + uint16 getX() const { return _x; } + + /** + * Get the Y position of where the video is displayed + */ + uint16 getY() const { return _y; } + + /** + * Is the video looping? + */ + bool isLooping() const { return _loop; } + + /** + * Is the video enabled? (Drawing to the screen) + */ + bool isEnabled() const { return _enabled; } + + /** + * Get the start time of the video bounds + */ + const Audio::Timestamp &getStart() const { return _start; } + + /** + * Get the file name of the video, or empty if by ID + */ + const Common::String &getFileName() const { return _fileName; } + + /** + * Get the ID of the video, or -1 if by file name + */ + int getID() const { return _id; } + + /** + * Get the current frame of the video + */ + int getCurFrame() const; + + /** + * Get the frame count of the video + */ + uint32 getFrameCount() const; + + /** + * Get the current time position of the video + */ + uint32 getTime() const; + + /** + * Get the duration of the video + */ + Audio::Timestamp getDuration() const; + + /** + * Get the current playback rate of the videos + */ + Common::Rational getRate() const; + + /** + * Move the x position of the video + */ + void setX(uint16 x) { _x = x; } + + /** + * Move the y position of the video + */ + void setY(uint16 y) { _y = y; } + + /** + * Move the video to the specified coordinates + */ + void moveTo(uint16 x, uint16 y) { setX(x); setY(y); } + + /** + * Center the video on the screen + */ + void center(); + + /** + * Set the start time when using video bounds + */ + void setStart(const Audio::Timestamp &time) { _start = time; } + + /** + * Set the video to loop (true) or not (false) + */ + void setLooping(bool loop) { _loop = loop; } + + /** + * Set the video's enabled status + */ + void setEnabled(bool enabled) { _enabled = enabled; } + + /** + * Set the bounds of the video + * + * This automatically seeks to the start time + */ + void setBounds(const Audio::Timestamp &startTime, const Audio::Timestamp &endTime); + + /** + * Seek to the given time + */ + void seek(const Audio::Timestamp &time); + + /** + * Set the playback rate + */ + void setRate(const Common::Rational &rate); + + /** + * Pause the video + */ + void pause(bool isPaused); + + /** + * Start playing the video + */ + void start(); + + /** + * Stop playing the video + */ + void stop(); + + /** + * Is the video playing? + */ + bool isPlaying() const; + + /** + * Get the volume of the video + */ + int getVolume() const; + + /** + * Set the volume of the video + */ + void setVolume(int volume); + +private: + // Non-changing variables + Video::VideoDecoder *_video; + Common::String _fileName; // External video files + int _id; // Internal Mohawk files + // Playback variables - Video::VideoDecoder *video; - uint16 x; - uint16 y; - bool loop; - bool enabled; - Audio::Timestamp start; - - // Identification - Common::String filename; // External video files - int id; // Internal Mohawk files - - // Helper functions - Video::VideoDecoder *operator->() const { assert(video); return video; } // TODO: Remove this eventually - void clear(); - bool endOfVideo(); + uint16 _x; + uint16 _y; + bool _loop; + bool _enabled; + Audio::Timestamp _start; }; -typedef int32 VideoHandle; +typedef Common::SharedPtr<VideoEntry> VideoEntryPtr; -enum { - NULL_VID_HANDLE = -1 +/** + * A handle for manipulating a video + */ +class VideoHandle { + // The private members should be able to be manipulated by VideoManager + friend class VideoManager; + +public: + /** + * Default constructor + */ + VideoHandle() {} + + /** + * Copy constructor + */ + VideoHandle(const VideoHandle &handle); + + /** + * Is this handle pointing to a valid video entry? + */ + bool isValid() const { return _ptr && _ptr->isOpen(); } + + /** + * Convenience implicit cast to bool + */ + operator bool() const { return isValid(); } + + /** + * Simple equality operator + */ + bool operator==(const VideoHandle &other) const { return _ptr.get() == other._ptr.get(); } + + /** + * Simple inequality operator + */ + bool operator!=(const VideoHandle &other) const { return !(*this == other); } + + /** + * Convenience operator-> override to give direct access to the VideoEntry + */ + VideoEntryPtr operator->() const { return _ptr; } + +private: + /** + * Constructor for internal VideoManager use + */ + VideoHandle(VideoEntryPtr ptr); + + /** + * The video entry this is associated with + */ + VideoEntryPtr _ptr; }; class VideoManager { @@ -76,8 +309,8 @@ public: // Generic movie functions void playMovieBlocking(const Common::String &filename, uint16 x = 0, uint16 y = 0, bool clearScreen = false); void playMovieBlockingCentered(const Common::String &filename, bool clearScreen = true); - VideoHandle playMovie(const Common::String &filename, int16 x = -1, int16 y = -1, bool loop = false); - VideoHandle playMovie(uint16 id, int16 x = -1, int16 y = -1, bool loop = false); + VideoHandle playMovie(const Common::String &filename); + VideoHandle playMovie(uint16 id); bool updateMovies(); void pauseVideos(); void resumeVideos(); @@ -87,31 +320,18 @@ public: // Riven-related functions void activateMLST(uint16 mlstId, uint16 card); void clearMLST(); - void enableMovieRiven(uint16 id); - void disableMovieRiven(uint16 id); void disableAllMovies(); VideoHandle playMovieRiven(uint16 id); - void stopMovieRiven(uint16 id); void playMovieBlockingRiven(uint16 id); VideoHandle findVideoHandleRiven(uint16 id); + void stopMovieRiven(uint16 id); // Handle functions VideoHandle findVideoHandle(uint16 id); - VideoHandle findVideoHandle(const Common::String &filename); - int getCurFrame(VideoHandle handle); - uint32 getFrameCount(VideoHandle handle); - uint32 getTime(VideoHandle handle); - Audio::Timestamp getDuration(VideoHandle videoHandle); - bool endOfVideo(VideoHandle handle); - void setVideoBounds(VideoHandle handle, Audio::Timestamp start, Audio::Timestamp end); - void drawVideoFrame(VideoHandle handle, Audio::Timestamp time); - void seekToTime(VideoHandle handle, Audio::Timestamp time); - void setVideoLooping(VideoHandle handle, bool loop); - Common::Rational getVideoRate(VideoHandle handle) const; - void setVideoRate(VideoHandle handle, const Common::Rational &rate); - void waitUntilMovieEnds(VideoHandle videoHandle); - void delayUntilMovieEnds(VideoHandle videoHandle); - void pauseMovie(VideoHandle videoHandle, bool pause); + VideoHandle findVideoHandle(const Common::String &fileName); + void waitUntilMovieEnds(VideoHandle handle); + void delayUntilMovieEnds(VideoHandle handle); + void drawVideoFrame(VideoHandle handle, const Audio::Timestamp &time); private: MohawkEngine *_vm; @@ -120,14 +340,19 @@ private: Common::Array<MLSTRecord> _mlstRecords; // Keep tabs on any videos playing - Common::Array<VideoEntry> _videoStreams; + typedef Common::List<VideoEntryPtr> VideoList; + VideoList _videos; - VideoHandle createVideoHandle(uint16 id, uint16 x, uint16 y, bool loop, uint16 volume = 0xff); - VideoHandle createVideoHandle(const Common::String &filename, uint16 x, uint16 y, bool loop, byte volume = 0xff); + // Utility functions for managing entries + VideoEntryPtr open(uint16 id); + VideoEntryPtr open(const Common::String &fileName); + + VideoList::iterator findEntry(VideoEntryPtr ptr); + void removeEntry(VideoEntryPtr ptr); // Dithering control bool _enableDither; - void checkEnableDither(VideoEntry &entry); + void checkEnableDither(VideoEntryPtr &entry); }; } // End of namespace Mohawk diff --git a/engines/mortevielle/detection_tables.h b/engines/mortevielle/detection_tables.h index d244d15365..26611d4271 100644 --- a/engines/mortevielle/detection_tables.h +++ b/engines/mortevielle/detection_tables.h @@ -110,6 +110,23 @@ static const MortevielleGameDescription MortevielleGameDescriptions[] = { }, Common::DE_DEU, kUseEngineDataFile }, + // French, provided by ultrapingu in bug ref #6575 + { + { + "mortevielle", + "", + { + {"menu.mor", 0, "3fef0a3f8fca99fdcb6dbca8cbcef46f", 160}, + {"dxx.mor", 0, "949e68e829ecd5ad29e36a00347a9e7e", 207744}, + AD_LISTEND + }, + Common::FR_FRA, + Common::kPlatformDOS, + ADGF_NO_FLAGS, + GUIO0() + }, Common::FR_FRA, kUseOriginalData + }, + { AD_TABLE_END_MARKER , Common::EN_ANY, kUseEngineDataFile} }; diff --git a/engines/mortevielle/menu.cpp b/engines/mortevielle/menu.cpp index c0b81b252a..b788ce9a71 100644 --- a/engines/mortevielle/menu.cpp +++ b/engines/mortevielle/menu.cpp @@ -682,8 +682,13 @@ void Menu::initMenu() { if (!menuLoaded) { // Load menu from game data using the original language if (_vm->getOriginalLanguage() == Common::FR_FRA) { - if (!f.open("menufr.mor")) - error("Missing file - menufr.mor"); + if (f.exists("menufr.mor")) { + if (!f.open("menufr.mor")) + error("Missing file - menufr.mor"); + } else { + if (!f.open("menu.mor")) + error("Missing file - menu.mor"); + } } else { // Common::DE_DEU if (!f.open("menual.mor")) error("Missing file - menual.mor"); diff --git a/engines/parallaction/adlib.cpp b/engines/parallaction/adlib.cpp index 7c1dd1681f..568ad190aa 100644 --- a/engines/parallaction/adlib.cpp +++ b/engines/parallaction/adlib.cpp @@ -25,7 +25,7 @@ #include "audio/fmopl.h" #include "audio/mpu401.h" -#include "audio/softsynth/emumidi.h" +#include "audio/mididrv.h" namespace Parallaction { @@ -270,11 +270,13 @@ struct MelodicVoice { int8 _octave; }; -class AdLibDriver : public MidiDriver_Emulated { +class AdLibDriver : public MidiDriver { public: - AdLibDriver(Audio::Mixer *mixer) : MidiDriver_Emulated(mixer) { + AdLibDriver(Audio::Mixer *mixer) { for (uint i = 0; i < 16; ++i) _channels[i].init(this, i); + + _isOpen = false; } int open(); @@ -282,11 +284,13 @@ public: void send(uint32 b); MidiChannel *allocateChannel(); MidiChannel *getPercussionChannel() { return &_channels[9]; } + bool isOpen() const { return _isOpen; } + uint32 getBaseTempo() { return 1000000 / OPL::OPL::kDefaultCallbackFrequency; } - bool isStereo() const { return false; } - int getRate() const { return _mixer->getOutputRate(); } - - void generateSamples(int16 *buf, int len); + virtual void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) { + _adlibTimerProc = timerProc; + _adlibTimerParam = timerParam; + } protected: OPL::OPL *_opl; @@ -320,6 +324,13 @@ protected: void muteMelodicVoice(uint8 voice); void initVoices(); + +private: + void onTimer(); + + Common::TimerManager::TimerProc _adlibTimerProc; + void *_adlibTimerParam; + bool _isOpen; }; MidiDriver *createAdLibDriver() { @@ -348,10 +359,10 @@ int AdLibDriver::open() { if (_isOpen) return MERR_ALREADY_OPEN; - MidiDriver_Emulated::open(); + _isOpen = true; _opl = OPL::Config::create(); - _opl->init(getRate()); + _opl->init(); _opl->writeReg(0x1, 0x20); // set bit 5 (enable all waveforms) // Reset the OPL registers. @@ -364,7 +375,7 @@ int AdLibDriver::open() { initVoices(); - _mixer->playStream(Audio::Mixer::kMusicSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); + _opl->start(new Common::Functor0Mem<void, AdLibDriver>(this, &AdLibDriver::onTimer)); return 0; } @@ -373,7 +384,6 @@ void AdLibDriver::close() { return; _isOpen = false; - _mixer->stopHandle(_mixerSoundHandle); delete _opl; } @@ -777,9 +787,9 @@ MidiChannel *AdLibDriver::allocateChannel() { return NULL; } -void AdLibDriver::generateSamples(int16 *buf, int len) { - memset(buf, 0, sizeof(int16) * len); - _opl->readBuffer(buf, len); +void AdLibDriver::onTimer() { + if (_adlibTimerProc) + (*_adlibTimerProc)(_adlibTimerParam); } void AdLibDriver::initVoices() { diff --git a/engines/pegasus/sound.cpp b/engines/pegasus/sound.cpp index 5b437b81d4..ddcb2be010 100644 --- a/engines/pegasus/sound.cpp +++ b/engines/pegasus/sound.cpp @@ -59,7 +59,15 @@ void Sound::initFromAIFFFile(const Common::String &fileName) { return; } - _stream = Audio::makeAIFFStream(file, DisposeAfterUse::YES); + Audio::RewindableAudioStream *stream = Audio::makeAIFFStream(file, DisposeAfterUse::YES); + + _stream = dynamic_cast<Audio::SeekableAudioStream *>(stream); + + if (!_stream) { + delete stream; + warning("AIFF stream '%s' is not seekable", fileName.c_str()); + return; + } } void Sound::initFromQuickTime(const Common::String &fileName) { diff --git a/engines/queen/midiadlib.cpp b/engines/queen/midiadlib.cpp index 25175c21d7..f5bc0f4d58 100644 --- a/engines/queen/midiadlib.cpp +++ b/engines/queen/midiadlib.cpp @@ -23,118 +23,29 @@ #include "common/endian.h" #include "common/textconsole.h" -#include "audio/fmopl.h" -#include "audio/softsynth/emumidi.h" +#include "engines/queen/midiadlib.h" namespace Queen { -class AdLibMidiDriver : public MidiDriver_Emulated { -public: - - AdLibMidiDriver(Audio::Mixer *mixer) : MidiDriver_Emulated(mixer) { _adlibWaveformSelect = 0; } - ~AdLibMidiDriver() {} - - // MidiDriver - int open(); - void close(); - void send(uint32 b); - void metaEvent(byte type, byte *data, uint16 length); - MidiChannel *allocateChannel() { return 0; } - MidiChannel *getPercussionChannel() { return 0; } - - // AudioStream - bool isStereo() const { return false; } - int getRate() const { return _mixer->getOutputRate(); } - - // MidiDriver_Emulated - void generateSamples(int16 *buf, int len); - -private: - - void handleMidiEvent0x90_NoteOn(int channel, int param1, int param2); - void handleSequencerSpecificMetaEvent1(int channel, const uint8 *data); - void handleSequencerSpecificMetaEvent2(uint8 value); - void handleSequencerSpecificMetaEvent3(uint8 value); - - void adlibWrite(uint8 port, uint8 value); - void adlibSetupCard(); - void adlibSetupChannels(int fl); - void adlibResetAmpVibratoRhythm(int am, int vib, int kso); - void adlibResetChannels(); - void adlibSetAmpVibratoRhythm(); - void adlibSetCSMKeyboardSplit(); - void adlibSetNoteMul(int mul); - void adlibSetWaveformSelect(int fl); - void adlibSetPitchBend(int channel, int range); - void adlibPlayNote(int channel); - uint8 adlibPlayNoteHelper(int channel, int note1, int note2, int oct); - void adlibTurnNoteOff(int channel); - void adlibTurnNoteOn(int channel, int note); - void adlibSetupChannelFromSequence(int channel, const uint8 *src, int fl); - void adlibSetupChannel(int channel, const uint16 *src, int fl); - void adlibSetNoteVolume(int channel, int volume); - void adlibSetupChannelHelper(int channel); - void adlibSetChannel0x40(int channel); - void adlibSetChannel0xC0(int channel); - void adlibSetChannel0x60(int channel); - void adlibSetChannel0x80(int channel); - void adlibSetChannel0x20(int channel); - void adlibSetChannel0xE0(int channel); - - FM_OPL *_opl; - int _midiNumberOfChannels; - int _adlibNoteMul; - int _adlibWaveformSelect; - int _adlibAMDepthEq48; - int _adlibVibratoDepthEq14; - int _adlibRhythmEnabled; - int _adlibKeyboardSplitOn; - int _adlibVibratoRhythm; - uint8 _midiChannelsFreqTable[9]; - uint8 _adlibChannelsLevelKeyScalingTable[11]; - uint8 _adlibSetupChannelSequence1[14 * 18]; - uint16 _adlibSetupChannelSequence2[14]; - int16 _midiChannelsNote2Table[9]; - uint8 _midiChannelsNote1Table[9]; - uint8 _midiChannelsOctTable[9]; - uint16 _adlibChannelsVolume[11]; - uint16 _adlibMetaSequenceData[28]; - - static const uint8 _adlibChannelsMappingTable1[]; - static const uint8 _adlibChannelsNoFeedback[]; - static const uint8 _adlibChannelsMappingTable2[]; - static const uint8 _adlibChannelsMappingTable3[]; - static const uint8 _adlibChannelsKeyScalingTable1[]; - static const uint8 _adlibChannelsKeyScalingTable2[]; - static const uint8 _adlibChannelsVolumeTable[]; - static const uint8 _adlibInitSequenceData1[]; - static const uint8 _adlibInitSequenceData2[]; - static const uint8 _adlibInitSequenceData3[]; - static const uint8 _adlibInitSequenceData4[]; - static const uint8 _adlibInitSequenceData5[]; - static const uint8 _adlibInitSequenceData6[]; - static const uint8 _adlibInitSequenceData7[]; - static const uint8 _adlibInitSequenceData8[]; - static const int16 _midiChannelsNoteTable[]; - static const int16 _midiNoteFreqTable[]; -}; - int AdLibMidiDriver::open() { - MidiDriver_Emulated::open(); - _opl = makeAdLibOPL(getRate()); + _isOpen = true; + _opl = OPL::Config::create(); + if (!_opl || !_opl->init()) + error("Failed to create OPL"); + adlibSetupCard(); for (int i = 0; i < 11; ++i) { _adlibChannelsVolume[i] = 0; adlibSetNoteVolume(i, 0); adlibTurnNoteOff(i); } - _mixer->playStream(Audio::Mixer::kMusicSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); + + _opl->start(new Common::Functor0Mem<void, AdLibMidiDriver>(this, &AdLibMidiDriver::onTimer)); return 0; } void AdLibMidiDriver::close() { - _mixer->stopHandle(_mixerSoundHandle); - OPLDestroy(_opl); + delete _opl; } void AdLibMidiDriver::send(uint32 b) { @@ -164,6 +75,11 @@ void AdLibMidiDriver::send(uint32 b) { } } +void AdLibMidiDriver::setVolume(uint32 volume) { + for (int i = 0; i < _midiNumberOfChannels; ++i) + adlibSetChannelVolume(i, volume * 64 / 256 + 64); +} + void AdLibMidiDriver::metaEvent(byte type, byte *data, uint16 length) { int event = 0; if (length > 4 && READ_BE_UINT32(data) == 0x3F00) { @@ -192,9 +108,14 @@ void AdLibMidiDriver::metaEvent(byte type, byte *data, uint16 length) { warning("Unhandled meta event %d len %d", event, length); } -void AdLibMidiDriver::generateSamples(int16 *data, int len) { - memset(data, 0, sizeof(int16) * len); - YM3812UpdateOne(_opl, data, len); +void AdLibMidiDriver::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) { + _adlibTimerProc = timerProc; + _adlibTimerParam = timerParam; +} + +void AdLibMidiDriver::onTimer() { + if (_adlibTimerProc) + (*_adlibTimerProc)(_adlibTimerParam); } void AdLibMidiDriver::handleSequencerSpecificMetaEvent1(int channel, const uint8 *data) { @@ -238,7 +159,7 @@ void AdLibMidiDriver::handleMidiEvent0x90_NoteOn(int channel, int param1, int pa } void AdLibMidiDriver::adlibWrite(uint8 port, uint8 value) { - OPLWriteReg(_opl, port, value); + _opl->writeReg(port, value); } void AdLibMidiDriver::adlibSetupCard() { @@ -253,6 +174,7 @@ void AdLibMidiDriver::adlibSetupCard() { _midiChannelsFreqTable[i] = 0; } memset(_adlibChannelsLevelKeyScalingTable, 127, 11); + memset(_adlibChannelsVolumeTable, 128, 11); adlibSetupChannels(0); adlibResetAmpVibratoRhythm(0, 0, 0); adlibSetNoteMul(1); @@ -448,6 +370,11 @@ void AdLibMidiDriver::adlibSetNoteVolume(int channel, int volume) { } } +void AdLibMidiDriver::adlibSetChannelVolume(int channel, uint8 volume) { + if (channel < (_adlibRhythmEnabled ? 11 : 9)) + _adlibChannelsVolumeTable[channel] = volume; +} + void AdLibMidiDriver::adlibSetupChannelHelper(int channel) { adlibSetAmpVibratoRhythm(); adlibSetCSMKeyboardSplit(); @@ -558,10 +485,6 @@ const uint8 AdLibMidiDriver::_adlibChannelsKeyScalingTable2[] = { 0, 3, 1, 4, 2, 5, 6, 9, 7, 10, 8, 11, 12, 15, 16, 255, 14, 255, 17, 255, 13, 255 }; -const uint8 AdLibMidiDriver::_adlibChannelsVolumeTable[] = { - 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 -}; - const uint8 AdLibMidiDriver::_adlibInitSequenceData1[] = { 1, 1, 3, 15, 5, 0, 1, 3, 15, 0, 0, 0, 1, 0 }; @@ -617,8 +540,4 @@ const int16 AdLibMidiDriver::_midiNoteFreqTable[] = { -363, -361, -359, -356, -354, -351, -349, -347, -344, -342, -339, -337 }; -MidiDriver *C_Player_CreateAdLibMidiDriver(Audio::Mixer *mixer) { - return new AdLibMidiDriver(mixer); -} - } // End of namespace Queen diff --git a/engines/queen/midiadlib.h b/engines/queen/midiadlib.h new file mode 100644 index 0000000000..8692e51840 --- /dev/null +++ b/engines/queen/midiadlib.h @@ -0,0 +1,128 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "audio/fmopl.h" +#include "audio/mididrv.h" + +namespace Queen { + +class AdLibMidiDriver : public MidiDriver { +public: + + AdLibMidiDriver() { + _adlibWaveformSelect = 0; + _isOpen = false; + } + + ~AdLibMidiDriver() {} + + // MidiDriver + int open(); + void close(); + void send(uint32 b); + void metaEvent(byte type, byte *data, uint16 length); + MidiChannel *allocateChannel() { return 0; } + MidiChannel *getPercussionChannel() { return 0; } + void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc); + bool isOpen() const { return _isOpen; } + uint32 getBaseTempo() { return 1000000 / OPL::OPL::kDefaultCallbackFrequency; } + + void setVolume(uint32 volume); + +private: + + void handleMidiEvent0x90_NoteOn(int channel, int param1, int param2); + void handleSequencerSpecificMetaEvent1(int channel, const uint8 *data); + void handleSequencerSpecificMetaEvent2(uint8 value); + void handleSequencerSpecificMetaEvent3(uint8 value); + + void adlibWrite(uint8 port, uint8 value); + void adlibSetupCard(); + void adlibSetupChannels(int fl); + void adlibResetAmpVibratoRhythm(int am, int vib, int kso); + void adlibResetChannels(); + void adlibSetAmpVibratoRhythm(); + void adlibSetCSMKeyboardSplit(); + void adlibSetNoteMul(int mul); + void adlibSetWaveformSelect(int fl); + void adlibSetPitchBend(int channel, int range); + void adlibPlayNote(int channel); + uint8 adlibPlayNoteHelper(int channel, int note1, int note2, int oct); + void adlibTurnNoteOff(int channel); + void adlibTurnNoteOn(int channel, int note); + void adlibSetupChannelFromSequence(int channel, const uint8 *src, int fl); + void adlibSetupChannel(int channel, const uint16 *src, int fl); + void adlibSetNoteVolume(int channel, int volume); + void adlibSetChannelVolume(int channel, uint8 volume); + void adlibSetupChannelHelper(int channel); + void adlibSetChannel0x40(int channel); + void adlibSetChannel0xC0(int channel); + void adlibSetChannel0x60(int channel); + void adlibSetChannel0x80(int channel); + void adlibSetChannel0x20(int channel); + void adlibSetChannel0xE0(int channel); + + void onTimer(); + + OPL::OPL *_opl; + int _midiNumberOfChannels; + int _adlibNoteMul; + int _adlibWaveformSelect; + int _adlibAMDepthEq48; + int _adlibVibratoDepthEq14; + int _adlibRhythmEnabled; + int _adlibKeyboardSplitOn; + int _adlibVibratoRhythm; + uint8 _midiChannelsFreqTable[9]; + uint8 _adlibChannelsLevelKeyScalingTable[11]; + uint8 _adlibSetupChannelSequence1[14 * 18]; + uint16 _adlibSetupChannelSequence2[14]; + int16 _midiChannelsNote2Table[9]; + uint8 _midiChannelsNote1Table[9]; + uint8 _midiChannelsOctTable[9]; + uint16 _adlibChannelsVolume[11]; + uint16 _adlibMetaSequenceData[28]; + uint8 _adlibChannelsVolumeTable[11]; + + bool _isOpen; + Common::TimerManager::TimerProc _adlibTimerProc; + void *_adlibTimerParam; + + static const uint8 _adlibChannelsMappingTable1[]; + static const uint8 _adlibChannelsNoFeedback[]; + static const uint8 _adlibChannelsMappingTable2[]; + static const uint8 _adlibChannelsMappingTable3[]; + static const uint8 _adlibChannelsKeyScalingTable1[]; + static const uint8 _adlibChannelsKeyScalingTable2[]; + static const uint8 _adlibInitSequenceData1[]; + static const uint8 _adlibInitSequenceData2[]; + static const uint8 _adlibInitSequenceData3[]; + static const uint8 _adlibInitSequenceData4[]; + static const uint8 _adlibInitSequenceData5[]; + static const uint8 _adlibInitSequenceData6[]; + static const uint8 _adlibInitSequenceData7[]; + static const uint8 _adlibInitSequenceData8[]; + static const int16 _midiChannelsNoteTable[]; + static const int16 _midiNoteFreqTable[]; +}; + +} // End of namespace Queen diff --git a/engines/queen/music.cpp b/engines/queen/music.cpp index 93d6527622..9f74aab915 100644 --- a/engines/queen/music.cpp +++ b/engines/queen/music.cpp @@ -23,6 +23,7 @@ #include "common/config-manager.h" #include "common/events.h" +#include "queen/midiadlib.h" #include "queen/music.h" #include "queen/queen.h" #include "queen/resource.h" @@ -33,8 +34,6 @@ namespace Queen { -extern MidiDriver *C_Player_CreateAdLibMidiDriver(Audio::Mixer *); - MidiMusic::MidiMusic(QueenEngine *vm) : _isPlaying(false), _isLooping(false), _randomLoop(false), _masterVolume(192), @@ -69,7 +68,7 @@ MidiMusic::MidiMusic(QueenEngine *vm) // if (READ_LE_UINT16(_musicData + 2) != infoOffset) { // defaultAdLibVolume = _musicData[infoOffset]; // } - _driver = C_Player_CreateAdLibMidiDriver(vm->_mixer); + _driver = new AdLibMidiDriver(); } else { _driver = MidiDriver::createMidi(dev); if (_nativeMT32) { @@ -117,6 +116,9 @@ void MidiMusic::setVolume(int volume) { if (_channelsTable[i]) _channelsTable[i]->volume(_channelsVolume[i] * _masterVolume / 255); } + + if (_adlib) + static_cast<AdLibMidiDriver*>(_driver)->setVolume(volume); } void MidiMusic::playSong(uint16 songNum) { diff --git a/engines/saga/detection_tables.h b/engines/saga/detection_tables.h index 2f72e7a13c..98009326ae 100644 --- a/engines/saga/detection_tables.h +++ b/engines/saga/detection_tables.h @@ -192,9 +192,9 @@ static const SAGAGameDescription gameDescriptions[] = { ADGF_DEMO, GUIO1(GUIO_NOSPEECH) }, - GID_ITE, // Game id - GF_OLD_ITE_DOS, // features - ITE_DEFAULT_SCENE, // Starting scene number + GID_ITE, + GF_ITE_DOS_DEMO, + ITE_DEFAULT_SCENE, &ITEDemo_Resources, ARRAYSIZE(ITEDEMO_GameFonts), ITEDEMO_GameFonts, @@ -393,6 +393,33 @@ static const SAGAGameDescription gameDescriptions[] = { NULL, }, + // Inherit the earth - German Wyrmkeep combined Windows/Mac/Linux CD + + // Supplied by user nicode in bug #6428. + // Contains voices.rsc instead of "Inherit the Earth Voices". + { + { + "ite", + "Multi-OS CD Version", + { + {"ite.rsc", GAME_RESOURCEFILE, "420e09cfdbb4db12baefd4bc81d8e154", 8925349}, + {"scripts.rsc", GAME_SCRIPTFILE, "a891405405edefc69c9d6c420c868b84", -1}, + { NULL, 0, NULL, 0} + }, + Common::DE_DEU, + Common::kPlatformUnknown, + ADGF_CD, + GUIO0() + }, + GID_ITE, + 0, + ITE_DEFAULT_SCENE, + &ITE_Resources, + ARRAYSIZE(ITE_GameFonts), + ITE_GameFonts, + NULL, + }, + // Inherit the earth - Italian Wyrmkeep combined Windows/Mac/Linux CD (fan translation) // version is different from the other Wyrmkeep re-releases in that it does diff --git a/engines/saga/introproc_ihnm.cpp b/engines/saga/introproc_ihnm.cpp index fc28d2372f..dc3f55e8c1 100644 --- a/engines/saga/introproc_ihnm.cpp +++ b/engines/saga/introproc_ihnm.cpp @@ -68,7 +68,7 @@ int Scene::IHNMStartProc() { // Play the title music _vm->_music->play(1, MUSIC_NORMAL); // Play title screen - playTitle(2, 17); + playTitle(2, _vm->_music->isAdlib() ? 20 : 27); } } } else { @@ -150,7 +150,7 @@ bool Scene::checkKey() { break; case Common::EVENT_KEYDOWN: // Don't react to modifier keys alone. The original did - // non, and the user may want to change scaler without + // not, and the user may want to change scaler without // terminating the intro. if (event.kbd.ascii) res = true; diff --git a/engines/saga/introproc_ite.cpp b/engines/saga/introproc_ite.cpp index 0b129dbcc0..3c10cbe1dd 100644 --- a/engines/saga/introproc_ite.cpp +++ b/engines/saga/introproc_ite.cpp @@ -59,6 +59,11 @@ namespace Saga { #define RID_ITE_FAIREPATH_SCENE 1564 #define RID_ITE_FAIRETENT_SCENE 1567 +// Intro scenes - DOS demo +#define RID_ITE_INTRO_ANIM_SCENE_DOS_DEMO 298 +#define RID_ITE_CAVE_SCENE_DOS_DEMO 302 +#define RID_ITE_VALLEY_SCENE_DOS_DEMO 310 + // ITE intro music #define MUSIC_INTRO 9 #define MUSIC_TITLE_THEME 10 @@ -75,22 +80,24 @@ LoadSceneParams ITE_IntroList[] = { {RID_ITE_FAIRETENT_SCENE, kLoadByResourceId, Scene::SC_ITEIntroFaireTentProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE} }; -int Scene::ITEStartProc() { - size_t scenesCount; - size_t i; +LoadSceneParams ITE_DOS_Demo_IntroList[] = { + {RID_ITE_INTRO_ANIM_SCENE_DOS_DEMO, kLoadByResourceId, Scene::SC_ITEIntroAnimProc, false, kTransitionNoFade, 0, NO_CHAPTER_CHANGE}, + {RID_ITE_CAVE_SCENE_DOS_DEMO, kLoadByResourceId, Scene::SC_ITEIntroCaveDemoProc, false, kTransitionFade, 0, NO_CHAPTER_CHANGE}, + {RID_ITE_VALLEY_SCENE_DOS_DEMO, kLoadByResourceId, Scene::SC_ITEIntroValleyProc, false, kTransitionFade, 0, NO_CHAPTER_CHANGE}, +}; +int Scene::ITEStartProc() { LoadSceneParams firstScene; LoadSceneParams tempScene; + bool dosDemo = (_vm->getFeatures() & GF_ITE_DOS_DEMO); + int scenesCount = (!dosDemo) ? ARRAYSIZE(ITE_IntroList) : ARRAYSIZE(ITE_DOS_Demo_IntroList); - scenesCount = ARRAYSIZE(ITE_IntroList); - - for (i = 0; i < scenesCount; i++) { - tempScene = ITE_IntroList[i]; + for (int i = 0; i < scenesCount; i++) { + tempScene = (!dosDemo) ? ITE_IntroList[i] : ITE_DOS_Demo_IntroList[i]; tempScene.sceneDescriptor = _vm->_resource->convertResourceId(tempScene.sceneDescriptor); _vm->_scene->queueScene(tempScene); } - firstScene.loadFlag = kLoadBySceneNumber; firstScene.sceneDescriptor = _vm->getStartSceneNumber(); firstScene.sceneSkipTarget = true; @@ -437,6 +444,53 @@ int Scene::ITEIntroCaveCommonProc(int param, int caveScene) { return 0; } +int Scene::ITEIntroCaveDemoProc(int param) { + Event event; + EventColumns *eventColumns = NULL; + + switch (param) { + case SCENE_BEGIN: + // Begin palette cycling animation for candles + event.type = kEvTOneshot; + event.code = kPalAnimEvent; + event.op = kEventCycleStart; + event.time = 0; + eventColumns = _vm->_events->chain(eventColumns, event); + + // Queue narrator dialogue list + for (int i = 0; i < 11; i++) { + // Play voice + event.type = kEvTOneshot; + event.code = kVoiceEvent; + event.op = kEventPlay; + event.param = i; + event.time = _vm->_sndRes->getVoiceLength(i); + _vm->_events->chain(eventColumns, event); + } + + // End scene after last dialogue over + event.type = kEvTOneshot; + event.code = kSceneEvent; + event.op = kEventEnd; + event.time = INTRO_VOICE_PAD; + _vm->_events->chain(eventColumns, event); + + break; + case SCENE_END: + break; + + default: + warning("Illegal scene procedure parameter"); + break; + } + + return 0; +} + +int Scene::SC_ITEIntroCaveDemoProc(int param, void *refCon) { + return ((Scene *)refCon)->ITEIntroCaveDemoProc(param); +} + // Handles first introductory cave painting scene int Scene::SC_ITEIntroCave1Proc(int param, void *refCon) { return ((Scene *)refCon)->ITEIntroCaveCommonProc(param, 1); diff --git a/engines/saga/music.cpp b/engines/saga/music.cpp index d20882ca26..663f5991d0 100644 --- a/engines/saga/music.cpp +++ b/engines/saga/music.cpp @@ -31,6 +31,7 @@ #include "audio/mididrv.h" #include "audio/midiparser.h" #include "audio/midiparser_qt.h" +#include "audio/miles.h" #include "audio/decoders/raw.h" #include "common/config-manager.h" #include "common/file.h" @@ -42,24 +43,51 @@ namespace Saga { #define MUSIC_SUNSPOT 26 MusicDriver::MusicDriver() : _isGM(false) { - - MidiPlayer::createDriver(); - MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM); _driverType = MidiDriver::getMusicType(dev); + switch (_driverType) { + case MT_ADLIB: + if (Common::File::exists("INSTR.AD") && Common::File::exists("INSTR.OPL")) { + _milesAudioMode = true; + _driver = Audio::MidiDriver_Miles_AdLib_create("INSTR.AD", "INSTR.OPL"); + } else if (Common::File::exists("SAMPLE.AD") && Common::File::exists("SAMPLE.OPL")) { + _milesAudioMode = true; + _driver = Audio::MidiDriver_Miles_AdLib_create("SAMPLE.AD", "SAMPLE.OPL"); + } else { + _milesAudioMode = false; + MidiPlayer::createDriver(); + } + break; + case MT_MT32: + _milesAudioMode = true; + _driver = Audio::MidiDriver_Miles_MT32_create(""); + break; + default: + _milesAudioMode = false; + MidiPlayer::createDriver(); + break; + } + int retValue = _driver->open(); if (retValue == 0) { - if (_nativeMT32) - _driver->sendMT32Reset(); - else - _driver->sendGMReset(); + if (_driverType != MT_ADLIB) { + if (_driverType == MT_MT32 || _nativeMT32) + _driver->sendMT32Reset(); + else + _driver->sendGMReset(); + } _driver->setTimerCallback(this, &timerCallback); } } void MusicDriver::send(uint32 b) { + if (_milesAudioMode) { + _driver->send(b); + return; + } + if ((b & 0xF0) == 0xC0 && !_isGM && !_nativeMT32) { // Remap MT32 instruments to General Midi b = (b & 0xFFFF00FF) | MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8; @@ -249,7 +277,11 @@ void Music::play(uint32 resourceId, MusicFlags flags) { debug(2, "Music::play %d, %d", resourceId, flags); - if (isPlaying() && _trackNumber == resourceId) { + if (isPlaying() && _trackNumber == resourceId) + return; + + if (_vm->getFeatures() & GF_ITE_DOS_DEMO) { + warning("TODO: Music::play %d, %d for ITE DOS demo", resourceId, flags); return; } diff --git a/engines/saga/music.h b/engines/saga/music.h index 2106fb6fa6..2e7cc4c5ec 100644 --- a/engines/saga/music.h +++ b/engines/saga/music.h @@ -61,6 +61,7 @@ public: protected: MusicType _driverType; bool _isGM; + bool _milesAudioMode; }; class Music { @@ -79,6 +80,8 @@ public: void setVolume(int volume, int time = 1); int getVolume() { return _currentVolume; } + bool isAdlib() const { return _player->isAdlib(); } + Common::Array<int32> _songTable; private: diff --git a/engines/saga/saga.cpp b/engines/saga/saga.cpp index 3d38b3ea52..b94bb66bb4 100644 --- a/engines/saga/saga.cpp +++ b/engines/saga/saga.cpp @@ -117,6 +117,9 @@ SagaEngine::SagaEngine(OSystem *syst, const SAGAGameDescription *gameDesc) SearchMan.addSubDirectoryMatching(gameDataDir, "music"); SearchMan.addSubDirectoryMatching(gameDataDir, "sound"); + // Location of Miles audio files (sample.ad and sample.opl) in IHNM + SearchMan.addSubDirectoryMatching(gameDataDir, "drivers"); + // The Multi-OS version puts the voices file in the root directory of // the CD. The rest of the data files are in game/itedata SearchMan.addSubDirectoryMatching(gameDataDir, "game/itedata"); @@ -634,6 +637,9 @@ void SagaEngine::syncSoundSettings() { } void SagaEngine::pauseEngineIntern(bool pause) { + if (!_render || !_music) + return; + bool engineIsPaused = (_render->getFlags() & RF_RENDERPAUSE); if (engineIsPaused == pause) return; diff --git a/engines/saga/saga.h b/engines/saga/saga.h index 6077e55094..9c7b2f5295 100644 --- a/engines/saga/saga.h +++ b/engines/saga/saga.h @@ -137,9 +137,7 @@ enum GameFileTypes { enum GameFeatures { GF_ITE_FLOPPY = 1 << 0, -#if 0 - GF_OLD_ITE_DOS = 1 << 1, // Currently unused -#endif + GF_ITE_DOS_DEMO = 1 << 1, GF_EXTRA_ITE_CREDITS = 1 << 2, GF_8BIT_UNSIGNED_PCM = 1 << 3 }; diff --git a/engines/saga/scene.cpp b/engines/saga/scene.cpp index 4fa15d09e5..efd4c371b1 100644 --- a/engines/saga/scene.cpp +++ b/engines/saga/scene.cpp @@ -866,15 +866,13 @@ void Scene::loadSceneDescriptor(uint32 resourceId) { _sceneDescription.reset(); - if (resourceId == 0) { + if (resourceId == 0) return; - } _vm->_resource->loadResource(_sceneContext, resourceId, sceneDescriptorData); + ByteArrayReadStreamEndian readS(sceneDescriptorData, _sceneContext->isBigEndian()); - if (sceneDescriptorData.size() == 16) { - ByteArrayReadStreamEndian readS(sceneDescriptorData, _sceneContext->isBigEndian()); - + if (sceneDescriptorData.size() == 14 || sceneDescriptorData.size() == 16) { _sceneDescription.flags = readS.readSint16(); _sceneDescription.resourceListResourceId = readS.readSint16(); _sceneDescription.endSlope = readS.readSint16(); @@ -882,7 +880,10 @@ void Scene::loadSceneDescriptor(uint32 resourceId) { _sceneDescription.scriptModuleNumber = readS.readUint16(); _sceneDescription.sceneScriptEntrypointNumber = readS.readUint16(); _sceneDescription.startScriptEntrypointNumber = readS.readUint16(); - _sceneDescription.musicResourceId = readS.readSint16(); + if (sceneDescriptorData.size() == 16) + _sceneDescription.musicResourceId = readS.readSint16(); + } else { + warning("Scene::loadSceneDescriptor: Unknown scene descriptor data size (%d)", sceneDescriptorData.size()); } } diff --git a/engines/saga/scene.h b/engines/saga/scene.h index 410713c5d5..1a710cfe9c 100644 --- a/engines/saga/scene.h +++ b/engines/saga/scene.h @@ -400,12 +400,14 @@ class Scene { static int SC_ITEIntroTreeHouseProc(int param, void *refCon); static int SC_ITEIntroFairePathProc(int param, void *refCon); static int SC_ITEIntroFaireTentProc(int param, void *refCon); + static int SC_ITEIntroCaveDemoProc(int param, void *refCon); private: EventColumns *queueIntroDialogue(EventColumns *eventColumns, int n_dialogues, const IntroDialogue dialogue[]); EventColumns *queueCredits(int delta_time, int duration, int n_credits, const IntroCredit credits[]); int ITEIntroAnimProc(int param); int ITEIntroCaveCommonProc(int param, int caveScene); + int ITEIntroCaveDemoProc(int param); int ITEIntroValleyProc(int param); int ITEIntroTreeHouseProc(int param); int ITEIntroFairePathProc(int param); diff --git a/engines/saga/script.cpp b/engines/saga/script.cpp index 94b26c8da3..3cc6586432 100644 --- a/engines/saga/script.cpp +++ b/engines/saga/script.cpp @@ -977,19 +977,15 @@ void Script::opSpeak(SCRIPTOP_PARAMS) { // now data contains last string index -#if 0 - if (_vm->getFeatures() & GF_OLD_ITE_DOS) { // special ITE dos + if (_vm->getFeatures() & GF_ITE_DOS_DEMO) { if ((_vm->_scene->currentSceneNumber() == ITE_DEFAULT_SCENE) && (iparam1 >= 288) && (iparam1 <= (RID_SCENE1_VOICE_END - RID_SCENE1_VOICE_START + 288))) { sampleResourceId = RID_SCENE1_VOICE_START + iparam1 - 288; } } else { -#endif if (thread->_voiceLUT->size() > uint16(first)) sampleResourceId = (*thread->_voiceLUT)[uint16(first)]; -#if 0 } -#endif if (sampleResourceId < 0 || sampleResourceId > 4000) sampleResourceId = -1; diff --git a/engines/saga/sndres.cpp b/engines/saga/sndres.cpp index 39578e96f0..b8d03c9c08 100644 --- a/engines/saga/sndres.cpp +++ b/engines/saga/sndres.cpp @@ -327,9 +327,18 @@ bool SndRes::load(ResourceContext *context, uint32 resourceId, SoundBuffer &buff result = true; } break; case kSoundAIFF: { - Audio::SeekableAudioStream *audStream = Audio::makeAIFFStream(READ_STREAM(soundResourceLength), DisposeAfterUse::YES); - buffer.stream = audStream; - buffer.streamLength = audStream->getLength(); + Audio::RewindableAudioStream *audStream = Audio::makeAIFFStream(READ_STREAM(soundResourceLength), DisposeAfterUse::YES); + Audio::SeekableAudioStream *seekStream = dynamic_cast<Audio::SeekableAudioStream *>(audStream); + + if (!seekStream) { + warning("AIFF file is not seekable"); + delete audStream; + result = false; + break; + } + + buffer.stream = seekStream; + buffer.streamLength = seekStream->getLength(); result = true; } break; case kSoundVOC: { diff --git a/engines/sci/detection.cpp b/engines/sci/detection.cpp index 9a41127f6d..bac9b3467a 100644 --- a/engines/sci/detection.cpp +++ b/engines/sci/detection.cpp @@ -371,8 +371,8 @@ static const ADExtraGuiOptionsMap optionsList[] = { { GAMEOPTION_EGA_UNDITHER, { - _s("EGA undithering"), - _s("Enable undithering in EGA games"), + _s("Skip EGA dithering pass (full color backgrounds)"), + _s("Skip dithering pass in EGA games, graphics are shown with full colors"), "disable_dithering", false } diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h index 7cadcfc27e..55305c4b42 100644 --- a/engines/sci/detection_tables.h +++ b/engines/sci/detection_tables.h @@ -2445,6 +2445,16 @@ static const struct ADGameDescription SciGameDescriptions[] = { AD_LISTEND}, Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // Lighthouse - Japanese DOS (from m_kiewitz) + // Executable scanning reports "3.000.000", VERSION file reports "1.0C" + {"lighthouse", "", { + {"resmap.001", 0, "18e0ac1597fe1cf6dc663118fe983e3b", 7885}, + {"ressci.001", 0, "14e922c47b92156377cb49e241691792", 99573473}, + {"resmap.002", 0, "723fc742c623d8933e5753a264324cb0", 7657}, + {"ressci.002", 0, "175468431a979b9f317c294ce3bc1430", 94627469}, + AD_LISTEND}, + Common::JA_JPN, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // Lighthouse - Spanish DOS (from jvprat) // Executable scanning reports "3.000.000", VERSION file reports "1.1" {"lighthouse", "", { diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp index c56eb09482..61ac76d0a7 100644 --- a/engines/sci/engine/kfile.cpp +++ b/engines/sci/engine/kfile.cpp @@ -933,6 +933,11 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) { g_sci->_gfxMenu->kernelSetAttribute(1025 >> 8, 1025 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Status -> Statistics g_sci->_gfxMenu->kernelSetAttribute(1026 >> 8, 1026 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Status -> Goals break; + case GID_PQ2: + // HACK: Same as above - enable the save game menu option when loading in PQ2 (bug #6875). + // It gets disabled in the game's death screen. + g_sci->_gfxMenu->kernelSetAttribute(2, 1, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Game -> Save Game + break; default: break; } diff --git a/engines/sci/engine/script.cpp b/engines/sci/engine/script.cpp index 6034378ef6..36e33ccfa6 100644 --- a/engines/sci/engine/script.cpp +++ b/engines/sci/engine/script.cpp @@ -121,8 +121,8 @@ void Script::load(int script_nr, ResourceManager *resMan, ScriptPatcher *scriptP // // TODO: Remove this once such a mechanism is in place if (script->size > 65535) - error("TODO: SCI script %d is over 64KB - it's %d bytes long. This can't " - "be handled at the moment, thus stopping", script_nr, script->size); + warning("TODO: SCI script %d is over 64KB - it's %d bytes long. This can't " + "be fully handled at the moment", script_nr, script->size); } uint extraLocalsWorkaround = 0; @@ -1086,9 +1086,14 @@ void Script::initializeObjectsSci3(SegManager *segMan, SegmentId segmentId) { const byte *seeker = getSci3ObjectsPointer(); while (READ_SCI11ENDIAN_UINT16(seeker) == SCRIPT_OBJECT_MAGIC_NUMBER) { - reg_t reg = make_reg(segmentId, seeker - _buf); - Object *obj = scriptObjInit(reg); + // We call setSegment and setOffset directly here, instead of using + // make_reg, as in large scripts, seeker - _buf can be larger than + // a 16-bit integer + reg_t reg; + reg.setSegment(segmentId); + reg.setOffset(seeker - _buf); + Object *obj = scriptObjInit(reg); obj->setSuperClassSelector(segMan->getClassAddress(obj->getSuperClassSelector().getOffset(), SCRIPT_GET_LOCK, 0)); seeker += READ_SCI11ENDIAN_UINT16(seeker + 2); } diff --git a/engines/sci/graphics/transitions.cpp b/engines/sci/graphics/transitions.cpp index ccc7a4389a..c75580a077 100644 --- a/engines/sci/graphics/transitions.cpp +++ b/engines/sci/graphics/transitions.cpp @@ -124,6 +124,10 @@ void GfxTransitions::setup(int16 number, bool blackoutFlag) { } } +// Checks, if current time is lower than expected time of the current frame +// If current time is higher, then we have to assume that the current system isn't capable +// of either rendering frames that fast or has 60hz V'Sync enabled, which is why we drop frames +// in those cases, so that transitions work as fast as expected. bool GfxTransitions::doCreateFrame(uint32 shouldBeAtMsec) { uint32 msecPos = g_system->getMillis() - _transitionStartTime; @@ -132,12 +136,16 @@ bool GfxTransitions::doCreateFrame(uint32 shouldBeAtMsec) { return false; } -void GfxTransitions::updateScreenAndWait(uint32 shouldBeAtMsec) { +void GfxTransitions::updateScreen() { Common::Event ev; while (g_system->getEventManager()->pollEvent(ev)) {} // discard all events g_system->updateScreen(); +} + +void GfxTransitions::updateScreenAndWait(uint32 shouldBeAtMsec) { + updateScreen(); // if we have still some time left, delay accordingly uint32 msecPos = g_system->getMillis() - _transitionStartTime; if (shouldBeAtMsec > msecPos) @@ -257,6 +265,9 @@ void GfxTransitions::doTransition(int16 number, bool blackoutFlag) { warning("Transitions: ID %d not implemented", number); setNewScreen(blackoutFlag); } + // Just to make sure that the current frame is shown in case we skipped the last update-call b/c of timing + updateScreen(); + debugC(kDebugLevelGraphics, "Transition took %d milliseconds", g_system->getMillis() - _transitionStartTime); } void GfxTransitions::setNewPalette(bool blackoutFlag) { @@ -348,7 +359,9 @@ void GfxTransitions::pixelation(bool blackoutFlag) { copyRectToScreen(pixelRect, blackoutFlag); if ((stepNr & 0x3FF) == 0) { msecCount += 9; - updateScreenAndWait(msecCount); + if (doCreateFrame(msecCount)) { + updateScreenAndWait(msecCount); + } } stepNr++; } while (mask != 0x40); @@ -372,7 +385,9 @@ void GfxTransitions::blocks(bool blackoutFlag) { copyRectToScreen(blockRect, blackoutFlag); if ((stepNr & 7) == 0) { msecCount += 5; - updateScreenAndWait(msecCount); + if (doCreateFrame(msecCount)) { + updateScreenAndWait(msecCount); + } } stepNr++; } while (mask != 0x40); @@ -392,7 +407,9 @@ void GfxTransitions::straight(int16 number, bool blackoutFlag) { copyRectToScreen(newScreenRect, blackoutFlag); if ((stepNr & 1) == 0) { msecCount += 2; - updateScreenAndWait(msecCount); + if (doCreateFrame(msecCount)) { + updateScreenAndWait(msecCount); + } } stepNr++; newScreenRect.translate(-1, 0); @@ -405,7 +422,9 @@ void GfxTransitions::straight(int16 number, bool blackoutFlag) { copyRectToScreen(newScreenRect, blackoutFlag); if ((stepNr & 1) == 0) { msecCount += 2; - updateScreenAndWait(msecCount); + if (doCreateFrame(msecCount)) { + updateScreenAndWait(msecCount); + } } stepNr++; newScreenRect.translate(1, 0); @@ -417,7 +436,9 @@ void GfxTransitions::straight(int16 number, bool blackoutFlag) { while (newScreenRect.top >= _picRect.top) { copyRectToScreen(newScreenRect, blackoutFlag); msecCount += 4; - updateScreenAndWait(msecCount); + if (doCreateFrame(msecCount)) { + updateScreenAndWait(msecCount); + } stepNr++; newScreenRect.translate(0, -1); } @@ -428,7 +449,9 @@ void GfxTransitions::straight(int16 number, bool blackoutFlag) { while (newScreenRect.bottom <= _picRect.bottom) { copyRectToScreen(newScreenRect, blackoutFlag); msecCount += 4; - updateScreenAndWait(msecCount); + if (doCreateFrame(msecCount)) { + updateScreenAndWait(msecCount); + } stepNr++; newScreenRect.translate(0, 1); } @@ -534,7 +557,6 @@ void GfxTransitions::scroll(int16 number) { // Copy over final position just in case _screen->copyRectToScreen(newScreenRect); - g_system->updateScreen(); } // Vertically displays new screen starting from center - works on _picRect area @@ -552,7 +574,9 @@ void GfxTransitions::verticalRollFromCenter(bool blackoutFlag) { copyRectToScreen(leftRect, blackoutFlag); leftRect.translate(-1, 0); copyRectToScreen(rightRect, blackoutFlag); rightRect.translate(1, 0); msecCount += 3; - updateScreenAndWait(msecCount); + if (doCreateFrame(msecCount)) { + updateScreenAndWait(msecCount); + } } } @@ -567,7 +591,9 @@ void GfxTransitions::verticalRollToCenter(bool blackoutFlag) { copyRectToScreen(leftRect, blackoutFlag); leftRect.translate(1, 0); copyRectToScreen(rightRect, blackoutFlag); rightRect.translate(-1, 0); msecCount += 3; - updateScreenAndWait(msecCount); + if (doCreateFrame(msecCount)) { + updateScreenAndWait(msecCount); + } } } @@ -586,7 +612,9 @@ void GfxTransitions::horizontalRollFromCenter(bool blackoutFlag) { copyRectToScreen(upperRect, blackoutFlag); upperRect.translate(0, -1); copyRectToScreen(lowerRect, blackoutFlag); lowerRect.translate(0, 1); msecCount += 4; - updateScreenAndWait(msecCount); + if (doCreateFrame(msecCount)) { + updateScreenAndWait(msecCount); + } } } @@ -601,7 +629,9 @@ void GfxTransitions::horizontalRollToCenter(bool blackoutFlag) { copyRectToScreen(upperRect, blackoutFlag); upperRect.translate(0, 1); copyRectToScreen(lowerRect, blackoutFlag); lowerRect.translate(0, -1); msecCount += 4; - updateScreenAndWait(msecCount); + if (doCreateFrame(msecCount)) { + updateScreenAndWait(msecCount); + } } } @@ -633,7 +663,9 @@ void GfxTransitions::diagonalRollFromCenter(bool blackoutFlag) { copyRectToScreen(leftRect, blackoutFlag); leftRect.translate(-1, 0); leftRect.top--; leftRect.bottom++; copyRectToScreen(rightRect, blackoutFlag); rightRect.translate(1, 0); rightRect.top--; rightRect.bottom++; msecCount += 4; - updateScreenAndWait(msecCount); + if (doCreateFrame(msecCount)) { + updateScreenAndWait(msecCount); + } } } @@ -652,7 +684,9 @@ void GfxTransitions::diagonalRollToCenter(bool blackoutFlag) { copyRectToScreen(leftRect, blackoutFlag); leftRect.translate(1, 0); copyRectToScreen(rightRect, blackoutFlag); rightRect.translate(-1, 0); msecCount += 4; - updateScreenAndWait(msecCount); + if (doCreateFrame(msecCount)) { + updateScreenAndWait(msecCount); + } } } diff --git a/engines/sci/graphics/transitions.h b/engines/sci/graphics/transitions.h index ae9ca4b48a..05842a4d2a 100644 --- a/engines/sci/graphics/transitions.h +++ b/engines/sci/graphics/transitions.h @@ -89,6 +89,7 @@ private: void diagonalRollFromCenter(bool blackoutFlag); void diagonalRollToCenter(bool blackoutFlag); bool doCreateFrame(uint32 shouldBeAtMsec); + void updateScreen(); void updateScreenAndWait(uint32 shouldBeAtMsec); GfxScreen *_screen; diff --git a/engines/sci/sound/audio.cpp b/engines/sci/sound/audio.cpp index 8e35d6b055..fb9a3f17b9 100644 --- a/engines/sci/sound/audio.cpp +++ b/engines/sci/sound/audio.cpp @@ -391,18 +391,13 @@ Audio::RewindableAudioStream *AudioPlayer::getAudioStream(uint32 number, uint32 } else if (audioRes->size > 4 && READ_BE_UINT32(audioRes->data) == MKTAG('F','O','R','M')) { // AIFF detected Common::SeekableReadStream *waveStream = new Common::MemoryReadStream(audioRes->data, audioRes->size, DisposeAfterUse::NO); + Audio::RewindableAudioStream *rewindStream = Audio::makeAIFFStream(waveStream, DisposeAfterUse::YES); + audioSeekStream = dynamic_cast<Audio::SeekableAudioStream *>(rewindStream); - // Calculate samplelen from AIFF header - int waveSize = 0, waveRate = 0; - byte waveFlags = 0; - bool ret = Audio::loadAIFFFromStream(*waveStream, waveSize, waveRate, waveFlags); - if (!ret) - error("Failed to load AIFF from stream"); - - *sampleLen = (waveFlags & Audio::FLAG_16BITS ? waveSize >> 1 : waveSize) * 60 / waveRate; - - waveStream->seek(0, SEEK_SET); - audioStream = Audio::makeAIFFStream(waveStream, DisposeAfterUse::YES); + if (!audioSeekStream) { + warning("AIFF file is not seekable"); + delete rewindStream; + } } else if (audioRes->size > 14 && READ_BE_UINT16(audioRes->data) == 1 && READ_BE_UINT16(audioRes->data + 2) == 1 && READ_BE_UINT16(audioRes->data + 4) == 5 && READ_BE_UINT32(audioRes->data + 10) == 0x00018051) { // Mac snd detected diff --git a/engines/sci/sound/drivers/adlib.cpp b/engines/sci/sound/drivers/adlib.cpp index fcfda2f532..4f557be95e 100644 --- a/engines/sci/sound/drivers/adlib.cpp +++ b/engines/sci/sound/drivers/adlib.cpp @@ -27,7 +27,7 @@ #include "common/textconsole.h" #include "audio/fmopl.h" -#include "audio/softsynth/emumidi.h" +#include "audio/mididrv.h" #include "sci/resource.h" #include "sci/sound/drivers/mididriver.h" @@ -43,29 +43,30 @@ namespace Sci { // FIXME: We don't seem to be sending the polyphony init data, so disable this for now #define ADLIB_DISABLE_VOICE_MAPPING -class MidiDriver_AdLib : public MidiDriver_Emulated { +class MidiDriver_AdLib : public MidiDriver { public: enum { kVoices = 9, kRhythmKeys = 62 }; - MidiDriver_AdLib(Audio::Mixer *mixer) : MidiDriver_Emulated(mixer), _playSwitch(true), _masterVolume(15), _rhythmKeyMap(0), _opl(0) { } + MidiDriver_AdLib(Audio::Mixer *mixer) :_playSwitch(true), _masterVolume(15), _rhythmKeyMap(0), _opl(0), _isOpen(false) { } virtual ~MidiDriver_AdLib() { } // MidiDriver + int open() { return -1; } // Dummy implementation (use openAdLib) int openAdLib(bool isSCI0); void close(); void send(uint32 b); MidiChannel *allocateChannel() { return NULL; } MidiChannel *getPercussionChannel() { return NULL; } + bool isOpen() const { return _isOpen; } + uint32 getBaseTempo() { return 1000000 / OPL::OPL::kDefaultCallbackFrequency; } - // AudioStream - bool isStereo() const { return _stereo; } - int getRate() const { return _mixer->getOutputRate(); } + // MidiDriver + void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc); - // MidiDriver_Emulated - void generateSamples(int16 *buf, int len); + void onTimer(); void setVolume(byte volume); void playSwitch(bool play); @@ -133,6 +134,7 @@ private: bool _stereo; bool _isSCI0; OPL::OPL *_opl; + bool _isOpen; bool _playSwitch; int _masterVolume; Channel _channels[MIDI_CHANNELS]; @@ -140,6 +142,9 @@ private: byte *_rhythmKeyMap; Common::Array<AdLibPatch> _patches; + Common::TimerManager::TimerProc _adlibTimerProc; + void *_adlibTimerParam; + void loadInstrument(const byte *ins); void voiceOn(int voice, int note, int velocity); void voiceOff(int voice); @@ -215,14 +220,12 @@ static const int ym3812_note[13] = { }; int MidiDriver_AdLib::openAdLib(bool isSCI0) { - int rate = _mixer->getOutputRate(); - _stereo = STEREO; debug(3, "ADLIB: Starting driver in %s mode", (isSCI0 ? "SCI0" : "SCI1")); _isSCI0 = isSCI0; - _opl = OPL::Config::create(isStereo() ? OPL::Config::kDualOpl2 : OPL::Config::kOpl2); + _opl = OPL::Config::create(_stereo ? OPL::Config::kDualOpl2 : OPL::Config::kOpl2); // Try falling back to mono, thus plain OPL2 emualtor, when no Dual OPL2 is available. if (!_opl && _stereo) { @@ -233,22 +236,24 @@ int MidiDriver_AdLib::openAdLib(bool isSCI0) { if (!_opl) return -1; - _opl->init(rate); + if (!_opl->init()) { + delete _opl; + _opl = nullptr; + return -1; + } setRegister(0xBD, 0); setRegister(0x08, 0); setRegister(0x01, 0x20); - MidiDriver_Emulated::open(); + _isOpen = true; - _mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, DisposeAfterUse::NO); + _opl->start(new Common::Functor0Mem<void, MidiDriver_AdLib>(this, &MidiDriver_AdLib::onTimer)); return 0; } void MidiDriver_AdLib::close() { - _mixer->stopHandle(_mixerSoundHandle); - delete _opl; delete[] _rhythmKeyMap; } @@ -325,10 +330,14 @@ void MidiDriver_AdLib::send(uint32 b) { } } -void MidiDriver_AdLib::generateSamples(int16 *data, int len) { - if (isStereo()) - len <<= 1; - _opl->readBuffer(data, len); +void MidiDriver_AdLib::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) { + _adlibTimerProc = timerProc; + _adlibTimerParam = timerParam; +} + +void MidiDriver_AdLib::onTimer() { + if (_adlibTimerProc) + (*_adlibTimerProc)(_adlibTimerParam); // Increase the age of the notes for (int i = 0; i < kVoices; i++) { @@ -684,7 +693,7 @@ void MidiDriver_AdLib::setVelocityReg(int regOffset, int velocity, int kbScaleLe if (!_playSwitch) velocity = 0; - if (isStereo()) { + if (_stereo) { int velLeft = velocity; int velRight = velocity; @@ -734,7 +743,7 @@ void MidiDriver_AdLib::setRegister(int reg, int value, int channels) { _opl->write(0x221, value); } - if (isStereo()) { + if (_stereo) { if (channels & kRightChannel) { _opl->write(0x222, reg); _opl->write(0x223, value); diff --git a/engines/scumm/detection_tables.h b/engines/scumm/detection_tables.h index d42a7251d9..5a994cb699 100644 --- a/engines/scumm/detection_tables.h +++ b/engines/scumm/detection_tables.h @@ -306,9 +306,9 @@ static const GameSettings gameVariantsTable[] = { // Humongous Entertainment Scumm Version 7.2 {"airport", "", 0, GID_HEGAME, 6, 72, MDT_NONE, GF_USE_KEY, UNK, GUIO3(GUIO_NOLAUNCHLOAD, GUIO_NOMIDI, GUIO_NOASPECT)}, + {"farm", "", 0, GID_HEGAME, 6, 72, MDT_NONE, GF_USE_KEY, UNK, GUIO3(GUIO_NOLAUNCHLOAD, GUIO_NOMIDI, GUIO_NOASPECT)}, // Changed o_getResourceSize to cover all resource types - {"farm", "", 0, GID_HEGAME, 6, 73, MDT_NONE, GF_USE_KEY, UNK, GUIO3(GUIO_NOLAUNCHLOAD, GUIO_NOMIDI, GUIO_NOASPECT)}, {"puttzoo", "", 0, GID_PUTTZOO, 6, 73, MDT_NONE, GF_USE_KEY, UNK, GUIO3(GUIO_NOLAUNCHLOAD, GUIO_NOMIDI, GUIO_NOASPECT)}, {"puttzoo", "HE 72", 0, GID_PUTTZOO, 6, 72, MDT_NONE, GF_USE_KEY, UNK, GUIO3(GUIO_NOLAUNCHLOAD, GUIO_NOMIDI, GUIO_NOASPECT)}, {"puttzoo", "HE 98.5", 0, GID_PUTTZOO, 6, 98, MDT_NONE, GF_USE_KEY | GF_HE_985, UNK, GUIO3(GUIO_NOLAUNCHLOAD, GUIO_NOMIDI, GUIO_NOASPECT)}, diff --git a/engines/scumm/players/player_ad.cpp b/engines/scumm/players/player_ad.cpp index adcda68e10..4d4be2c3c2 100644 --- a/engines/scumm/players/player_ad.cpp +++ b/engines/scumm/players/player_ad.cpp @@ -27,6 +27,7 @@ #include "scumm/saveload.h" #include "audio/fmopl.h" +#include "audio/mixer.h" #include "common/textconsole.h" #include "common/config-manager.h" @@ -35,26 +36,19 @@ namespace Scumm { #define AD_CALLBACK_FREQUENCY 472 -Player_AD::Player_AD(ScummEngine *scumm, Audio::Mixer *mixer) - : _vm(scumm), _mixer(mixer), _rate(mixer->getOutputRate()) { +Player_AD::Player_AD(ScummEngine *scumm) + : _vm(scumm) { _opl2 = OPL::Config::create(); - if (!_opl2->init(_rate)) { + if (!_opl2->init()) { error("Could not initialize OPL2 emulator"); } - _samplesPerCallback = _rate / AD_CALLBACK_FREQUENCY; - _samplesPerCallbackRemainder = _rate % AD_CALLBACK_FREQUENCY; - _samplesTillCallback = 0; - _samplesTillCallbackRemainder = 0; - memset(_registerBackUpTable, 0, sizeof(_registerBackUpTable)); writeReg(0x01, 0x00); writeReg(0xBD, 0x00); writeReg(0x08, 0x00); writeReg(0x01, 0x20); - _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); - _engineMusicTimer = 0; _soundPlaying = -1; @@ -78,11 +72,11 @@ Player_AD::Player_AD(ScummEngine *scumm, Audio::Mixer *mixer) _musicVolume = _sfxVolume = 255; _isSeeking = false; + + _opl2->start(new Common::Functor0Mem<void, Player_AD>(this, &Player_AD::onTimer), AD_CALLBACK_FREQUENCY); } Player_AD::~Player_AD() { - _mixer->stopHandle(_soundHandle); - stopAllSounds(); Common::StackLock lock(_mutex); delete _opl2; @@ -244,36 +238,14 @@ void Player_AD::saveLoadWithSerializer(Serializer *ser) { } } -int Player_AD::readBuffer(int16 *buffer, const int numSamples) { +void Player_AD::onTimer() { Common::StackLock lock(_mutex); - int len = numSamples; - - while (len > 0) { - if (!_samplesTillCallback) { - if (_curOffset) { - updateMusic(); - } - - updateSfx(); - - _samplesTillCallback = _samplesPerCallback; - _samplesTillCallbackRemainder += _samplesPerCallbackRemainder; - if (_samplesTillCallbackRemainder >= AD_CALLBACK_FREQUENCY) { - ++_samplesTillCallback; - _samplesTillCallbackRemainder -= AD_CALLBACK_FREQUENCY; - } - } - - const int samplesToRead = MIN(len, _samplesTillCallback); - _opl2->readBuffer(buffer, samplesToRead); - - buffer += samplesToRead; - len -= samplesToRead; - _samplesTillCallback -= samplesToRead; + if (_curOffset) { + updateMusic(); } - return numSamples; + updateSfx(); } void Player_AD::setupVolume() { diff --git a/engines/scumm/players/player_ad.h b/engines/scumm/players/player_ad.h index 63a8503f47..63fda3cc7c 100644 --- a/engines/scumm/players/player_ad.h +++ b/engines/scumm/players/player_ad.h @@ -26,7 +26,6 @@ #include "scumm/music.h" #include "audio/audiostream.h" -#include "audio/mixer.h" #include "common/mutex.h" @@ -41,9 +40,9 @@ class ScummEngine; /** * Sound output for v3/v4 AdLib data. */ -class Player_AD : public MusicEngine, public Audio::AudioStream { +class Player_AD : public MusicEngine { public: - Player_AD(ScummEngine *scumm, Audio::Mixer *mixer); + Player_AD(ScummEngine *scumm); virtual ~Player_AD(); // MusicEngine API @@ -56,18 +55,12 @@ public: virtual void saveLoadWithSerializer(Serializer *ser); - // AudioStream API - virtual int readBuffer(int16 *buffer, const int numSamples); - virtual bool isStereo() const { return false; } - virtual bool endOfData() const { return false; } - virtual int getRate() const { return _rate; } + // Timer callback + void onTimer(); private: ScummEngine *const _vm; Common::Mutex _mutex; - Audio::Mixer *const _mixer; - const int _rate; - Audio::SoundHandle _soundHandle; void setupVolume(); int _musicVolume; @@ -75,11 +68,6 @@ private: OPL::OPL *_opl2; - int _samplesPerCallback; - int _samplesPerCallbackRemainder; - int _samplesTillCallback; - int _samplesTillCallbackRemainder; - int _soundPlaying; int32 _engineMusicTimer; diff --git a/engines/scumm/scumm-md5.h b/engines/scumm/scumm-md5.h index 1cbefee105..a836cf12bc 100644 --- a/engines/scumm/scumm-md5.h +++ b/engines/scumm/scumm-md5.h @@ -1,5 +1,5 @@ /* - This file was generated by the md5table tool on Sun May 10 14:23:43 2015 + This file was generated by the md5table tool on Sun Jun 28 03:19:52 2015 DO NOT EDIT MANUALLY! */ @@ -272,7 +272,7 @@ static const MD5Table md5table[] = { { "5c21fc49aee8f46e58fef21579e614a1", "thinker1", "", "", -1, Common::EN_USA, Common::kPlatformUnknown }, { "5c9cecbd2952ccec14c9ecebf5822a34", "puttzoo", "HE 100", "", -1, Common::EN_ANY, Common::kPlatformIOS }, { "5d88b9d6a88e6f8e90cded9d01b7f082", "loom", "VGA", "VGA", 8307, Common::EN_ANY, Common::kPlatformDOS }, - { "5dda73606533d66a4c3f4f9ea6e842af", "farm", "", "", 87061, Common::RU_RUS, Common::kPlatformWindows }, + { "5dda73606533d66a4c3f4f9ea6e842af", "farm", "HE 73", "", 87061, Common::RU_RUS, Common::kPlatformWindows }, { "5e8fb66971a60e523e5afbc4c129c0e8", "socks", "HE 85", "", -1, Common::EN_USA, Common::kPlatformUnknown }, { "5ebb57234b2fe5c5dff641e00184ad81", "freddi", "HE 73", "", -1, Common::FR_FRA, Common::kPlatformWindows }, { "5fbe557049892eb4b709d90916ec97ca", "indy3", "EGA", "EGA", 5361, Common::EN_ANY, Common::kPlatformDOS }, @@ -465,7 +465,7 @@ static const MD5Table md5table[] = { { "a194f15f51ee62badab74b9e7da97693", "baseball2001", "", "Demo", 20507, Common::EN_ANY, Common::kPlatformUnknown }, { "a197a87ae77f3b3333f09a7a2c448fe2", "freddi", "HE 99", "Updated", -1, Common::EN_ANY, Common::kPlatformWindows }, { "a22af0ad0e3126d19d22707b0267a37d", "balloon", "HE 80", "", -1, Common::NL_NLD, Common::kPlatformWindows }, - { "a2386da005672cbd5136f4f27a626c5f", "farm", "", "", 87061, Common::NL_NLD, Common::kPlatformWindows }, + { "a2386da005672cbd5136f4f27a626c5f", "farm", "HE 73", "", 87061, Common::NL_NLD, Common::kPlatformWindows }, { "a28135a7ade38cc0208b04507c46efd1", "spyfox", "HE 99", "", -1, Common::DE_DEU, Common::kPlatformUnknown }, { "a2bb6aa0537402c1b3c2ea899ccef64b", "lost", "HE 99", "Demo", 15540, Common::EN_ANY, Common::kPlatformWindows }, { "a3036878840720fbefa41e6965fa4a0a", "samnmax", "Floppy", "Floppy", -1, Common::EN_ANY, Common::kPlatformDOS }, @@ -480,7 +480,7 @@ static const MD5Table md5table[] = { { "a654fb60c3b67d6317a7894ffd9f25c5", "pajama3", "", "Demo", -1, Common::EN_USA, Common::kPlatformUnknown }, { "a71014c53a6d18c66ef2ea0ee42328e9", "PuttTime", "HE 99", "Mini Game", 18458, Common::NL_NLD, Common::kPlatformWindows }, { "a7cacad9c40c4dc9e1812abf6c8af9d5", "puttcircus", "", "Demo", -1, Common::EN_ANY, Common::kPlatformUnknown }, - { "a85856675429fe88051744f755b72f93", "farm", "", "", -1, Common::EN_ANY, Common::kPlatformWindows }, + { "a85856675429fe88051744f755b72f93", "farm", "HE 73", "", -1, Common::EN_ANY, Common::kPlatformWindows }, { "a86f9c49355579c30d4a55b477c0d869", "baseball2001", "", "", -1, Common::EN_ANY, Common::kPlatformUnknown }, { "a8fcc3084ad5e3e569722755f205b1ef", "pajama3", "", "Mini Game", 13911, Common::DE_DEU, Common::kPlatformWindows }, { "a9543ef0d79bcb47cd76ec197ad0a967", "puttmoon", "", "", -1, Common::EN_ANY, Common::kPlatform3DO }, @@ -650,7 +650,7 @@ static const MD5Table md5table[] = { { "ee785fe2569bc9965526e774f7ab86f1", "spyfox", "HE 99", "", -1, Common::FR_FRA, Common::kPlatformMacintosh }, { "ee8cfeb76e55d43a01c25e0865a9db76", "puttrace", "HE 98", "Demo", 13135, Common::NL_NLD, Common::kPlatformMacintosh }, { "eea4d9ac2fb6f145945a308e8866915b", "maniac", "C64", "", -1, Common::EN_ANY, Common::kPlatformC64 }, - { "eeb606c2d2ec877a712a9f20c10bcdda", "farm", "", "", 87034, Common::NL_NLD, Common::kPlatformMacintosh }, + { "eeb606c2d2ec877a712a9f20c10bcdda", "farm", "HE 73", "", 87034, Common::NL_NLD, Common::kPlatformMacintosh }, { "ef347474f3c7be3b29584eaa133cca05", "samnmax", "Floppy", "Floppy", -1, Common::FR_FRA, Common::kPlatformDOS }, { "ef71a322b6530ac45b1a070f7c0795f7", "moonbase", "Demo", "Demo", -1, Common::EN_ANY, Common::kPlatformWindows }, { "ef74d9071d4e564b037cb44bd6774de7", "fbear", "HE 62", "", -1, Common::HE_ISR, Common::kPlatformDOS }, @@ -679,7 +679,7 @@ static const MD5Table md5table[] = { { "faa89ab5e67ba4eebb4399f584f7490c", "pajama3", "", "Mini Game", 13911, Common::FR_FRA, Common::kPlatformWindows }, { "fb66aa42de21675116346213f176a366", "monkey", "VGA", "VGA", -1, Common::IT_ITA, Common::kPlatformAmiga }, { "fbb697d89d2beca87360a145f467bdae", "PuttTime", "HE 90", "Demo", -1, Common::DE_DEU, Common::kPlatformUnknown }, - { "fbbbb38a81fc9d6a61d509278390a290", "farm", "", "", -1, Common::EN_ANY, Common::kPlatformMacintosh }, + { "fbbbb38a81fc9d6a61d509278390a290", "farm", "HE 73", "", -1, Common::EN_ANY, Common::kPlatformMacintosh }, { "fbdd947d21e8f5bac6d6f7a316af1c5a", "spyfox", "", "Demo", 15693, Common::EN_ANY, Common::kPlatformUnknown }, { "fc53ce0e5f6562b1c1e1b4b8203acafb", "samnmax", "Floppy", "Floppy", -1, Common::ES_ESP, Common::kPlatformDOS }, { "fc6b6148e80d67939d9a18697c0f626a", "monkey", "EGA", "EGA", 8367, Common::DE_DEU, Common::kPlatformDOS }, diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp index 99b4e695bb..24d676a1ff 100644 --- a/engines/scumm/scumm.cpp +++ b/engines/scumm/scumm.cpp @@ -316,6 +316,7 @@ ScummEngine::ScummEngine(OSystem *syst, const DetectorResult &dr) _NES_lastTalkingActor = 0; _NES_talkColor = 0; _keepText = false; + _msgCount = 0; _costumeLoader = NULL; _costumeRenderer = NULL; _2byteFontPtr = 0; @@ -1904,7 +1905,7 @@ void ScummEngine::setupMusic(int midi) { // EGA/VGA. However, we support multi MIDI for that game and we cannot // support this with the Player_AD code at the moment. The reason here // is that multi MIDI is supported internally by our iMuse output. - _musicEngine = new Player_AD(this, _mixer); + _musicEngine = new Player_AD(this); } else if (_game.version >= 3 && _game.heversion <= 62) { MidiDriver *nativeMidiDriver = 0; MidiDriver *adlibMidiDriver = 0; diff --git a/engines/scumm/scumm.h b/engines/scumm/scumm.h index 30b4d61880..6e0adc3ff3 100644 --- a/engines/scumm/scumm.h +++ b/engines/scumm/scumm.h @@ -1182,6 +1182,7 @@ protected: byte _charsetBuffer[512]; bool _keepText; + byte _msgCount; int _nextLeft, _nextTop; diff --git a/engines/scumm/string.cpp b/engines/scumm/string.cpp index d60c4c6a50..3049fbcf62 100644 --- a/engines/scumm/string.cpp +++ b/engines/scumm/string.cpp @@ -283,6 +283,7 @@ bool ScummEngine::handleNextCharsetCode(Actor *a, int *code) { switch (c) { case 1: c = 13; // new line + _msgCount = _screenWidth; endLoop = true; break; case 2: @@ -293,6 +294,7 @@ bool ScummEngine::handleNextCharsetCode(Actor *a, int *code) { case 3: _haveMsg = (_game.version >= 7) ? 1 : 0xFF; _keepText = false; + _msgCount = 0; endLoop = true; break; case 8: @@ -573,6 +575,9 @@ void ScummEngine::CHARSET_1() { #endif restoreCharsetBg(); } + _msgCount = 0; + } else if (_game.version <= 2) { + _talkDelay += _msgCount * _defaultTalkDelay; } if (_game.version > 3) { @@ -600,6 +605,7 @@ void ScummEngine::CHARSET_1() { // End of text reached, set _haveMsg accordingly _haveMsg = (_game.version >= 7) ? 2 : 1; _keepText = false; + _msgCount = 0; break; } @@ -648,6 +654,7 @@ void ScummEngine::CHARSET_1() { } if (_game.version <= 3) { _charset->printChar(c, false); + _msgCount += 1; } else { if (_game.features & GF_16BIT_COLOR) { // HE games which use sprites for subtitles diff --git a/engines/sherlock/animation.cpp b/engines/sherlock/animation.cpp index 21d63633d3..bbf7c913b7 100644 --- a/engines/sherlock/animation.cpp +++ b/engines/sherlock/animation.cpp @@ -31,7 +31,7 @@ static const int NO_FRAMES = FRAMES_END; Animation::Animation(SherlockEngine *vm) : _vm(vm) { } -bool Animation::play(const Common::String &filename, int minDelay, int fade, +bool Animation::play(const Common::String &filename, bool intro, int minDelay, int fade, bool setPalette, int speed) { Events &events = *_vm->_events; Screen &screen = *_vm->_screen; @@ -39,7 +39,7 @@ bool Animation::play(const Common::String &filename, int minDelay, int fade, int soundNumber = 0; // Check for any any sound frames for the given animation - const int *soundFrames = checkForSoundFrames(filename); + const int *soundFrames = checkForSoundFrames(filename, intro); // Add on the VDX extension Common::String vdxName = filename + ".vdx"; @@ -102,12 +102,19 @@ bool Animation::play(const Common::String &filename, int minDelay, int fade, if (frameNumber++ == *soundFrames) { ++soundNumber; ++soundFrames; - Common::String fname = _soundLibraryFilename.empty() ? - Common::String::format("%s%01d", filename.c_str(), soundNumber) : - Common::String::format("%s%02d", filename.c_str(), soundNumber); + + Common::String sampleFilename; + + if (!intro) { + // regular animation, append 1-digit number + sampleFilename = Common::String::format("%s%01d", filename.c_str(), soundNumber); + } else { + // intro animation, append 2-digit number + sampleFilename = Common::String::format("%s%02d", filename.c_str(), soundNumber); + } if (sound._voices) - sound.playSound(fname, WAIT_RETURN_IMMEDIATELY, 100, _soundLibraryFilename.c_str()); + sound.playSound(sampleFilename, WAIT_RETURN_IMMEDIATELY, 100, _soundLibraryFilename.c_str()); } events.wait(speed * 3); @@ -133,6 +140,133 @@ bool Animation::play(const Common::String &filename, int minDelay, int fade, return !skipped && !_vm->shouldQuit(); } +bool Animation::play3DO(const Common::String &filename, bool intro, int minDelay, bool fadeFromGrey, + int speed) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + Sound &sound = *_vm->_sound; + int soundNumber = 0; + + bool fadeActive = false; + uint16 fadeLimitColor = 0; + uint16 fadeLimitColorRed = 0; + uint16 fadeLimitColorGreen = 0; + uint16 fadeLimitColorBlue = 0; + + // Check for any any sound frames for the given animation + const int *soundFrames = checkForSoundFrames(filename, intro); + + // Add the VDX extension + Common::String indexName = "prologue/" + filename + ".3dx"; + + // Load the animation + Common::File *indexStream = new Common::File(); + + if (!indexStream->open(indexName)) { + warning("unable to open %s\n", indexName.c_str()); + return false; + } + + // Load initial image + Common::String graphicsName = "prologue/" + filename + ".3da"; + ImageFile3DO images(graphicsName, kImageFile3DOType_Animation); + + events.wait(minDelay); + + if (fadeFromGrey) { + fadeActive = true; + fadeLimitColor = 0xCE59; // RGB565: 25, 50, 25 -> "grey" + } + + int frameNumber = 0; + Common::Point pt; + bool skipped = false; + while (!_vm->shouldQuit()) { + // Get the next sprite to display + int imageFrame = indexStream->readSint16BE(); + + if (imageFrame == -2) { + // End of animation reached + break; + } else if (imageFrame != -1) { + // Read position from either animation stream or the sprite frame itself + if (imageFrame < 0) { + imageFrame += 32768; + pt.x = indexStream->readUint16BE(); + pt.y = indexStream->readUint16BE(); + } else { + pt = images[imageFrame]._offset; + } + + // Draw the sprite. Note that we explicitly use the raw frame below, rather than the ImageFrame, + // since we don't want the offsets in the image file to be used, just the explicit position we specify + if (!fadeActive) { + screen.transBlitFrom(images[imageFrame]._frame, pt); + } else { + // Fade active, blit to backbuffer1 + screen._backBuffer1.transBlitFrom(images[imageFrame]._frame, pt); + } + } else { + // At this point, either the sprites for the frame has been complete, or there weren't any sprites + // at all to draw for the frame + + if (fadeActive) { + // process fading + screen.blitFrom3DOcolorLimit(fadeLimitColor); + + if (!fadeLimitColor) { + // we are at the end, so stop + fadeActive = false; + } else { + // decrease limit color + fadeLimitColorRed = fadeLimitColor & 0xF800; + fadeLimitColorGreen = fadeLimitColor & 0x07E0; + fadeLimitColorBlue = fadeLimitColor & 0x001F; + if (fadeLimitColorRed) + fadeLimitColor -= 0x0800; + if (fadeLimitColorGreen) + fadeLimitColor -= 0x0040; // -2 because we are using RGB565, sherlock uses RGB555 + if (fadeLimitColorBlue) + fadeLimitColor -= 0x0001; + } + } + + // Check if we've reached a frame with sound + if (frameNumber++ == *soundFrames) { + ++soundNumber; + ++soundFrames; + + Common::String sampleFilename; + + // append 1-digit number + sampleFilename = Common::String::format("prologue/sounds/%s%01d", filename.c_str(), soundNumber); + + if (sound._voices) + sound.playSound(sampleFilename, WAIT_RETURN_IMMEDIATELY, 100); // no sound library + } + events.wait(speed * 3); + } + + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + if (keyState.keycode == Common::KEYCODE_ESCAPE || + keyState.keycode == Common::KEYCODE_SPACE) { + skipped = true; + break; + } + } else if (events._pressed) { + skipped = true; + break; + } + } + + events.clearEvents(); + sound.stopSound(); + delete indexStream; + + return !skipped && !_vm->shouldQuit(); +} + void Animation::setPrologueNames(const char *const *names, int count) { for (int idx = 0; idx < count; ++idx, ++names) { _prologueNames.push_back(*names); @@ -163,10 +297,11 @@ void Animation::setTitleFrames(const int *frames, int count, int maxFrames) { } } -const int *Animation::checkForSoundFrames(const Common::String &filename) { +const int *Animation::checkForSoundFrames(const Common::String &filename, bool intro) { const int *frames = &NO_FRAMES; - if (_soundLibraryFilename.empty()) { + if (!intro) { + // regular animation is playing for (uint idx = 0; idx < _prologueNames.size(); ++idx) { if (filename.equalsIgnoreCase(_prologueNames[idx])) { frames = &_prologueFrames[idx][0]; @@ -174,6 +309,7 @@ const int *Animation::checkForSoundFrames(const Common::String &filename) { } } } else { + // intro-animation is playing for (uint idx = 0; idx < _titleNames.size(); ++idx) { if (filename.equalsIgnoreCase(_titleNames[idx])) { frames = &_titleFrames[idx][0]; diff --git a/engines/sherlock/animation.h b/engines/sherlock/animation.h index b7811d3fa8..f3c95d4027 100644 --- a/engines/sherlock/animation.h +++ b/engines/sherlock/animation.h @@ -45,10 +45,11 @@ private: /** * Checks for whether an animation is being played that has associated sound */ - const int *checkForSoundFrames(const Common::String &filename); + const int *checkForSoundFrames(const Common::String &filename, bool intro); public: Common::String _soundLibraryFilename; Common::String _gfxLibraryFilename; + public: Animation(SherlockEngine *vm); @@ -75,7 +76,9 @@ public: /** * Play a full-screen animation */ - bool play(const Common::String &filename, int minDelay, int fade, bool setPalette, int speed); + bool play(const Common::String &filename, bool intro, int minDelay, int fade, bool setPalette, int speed); + + bool play3DO(const Common::String &filename, bool intro, int minDelay, bool fadeFromGrey, int speed); }; } // End of namespace Sherlock diff --git a/engines/sherlock/debugger.cpp b/engines/sherlock/debugger.cpp index cfbea2bc24..50293db648 100644 --- a/engines/sherlock/debugger.cpp +++ b/engines/sherlock/debugger.cpp @@ -22,12 +22,44 @@ #include "sherlock/debugger.h" #include "sherlock/sherlock.h" +#include "sherlock/music.h" +#include "sherlock/scalpel/3do/movie_decoder.h" +#include "sherlock/scalpel/scalpel_debugger.h" +#include "sherlock/tattoo/tattoo_debugger.h" +#include "audio/mixer.h" +#include "audio/decoders/aiff.h" +#include "audio/decoders/wave.h" +#include "common/str-array.h" namespace Sherlock { +Debugger *Debugger::init(SherlockEngine *vm) { + if (vm->getGameID() == GType_RoseTattoo) + return new Tattoo::TattooDebugger(vm); + else + return new Scalpel::ScalpelDebugger(vm); +} + Debugger::Debugger(SherlockEngine *vm) : GUI::Debugger(), _vm(vm) { - registerCmd("continue", WRAP_METHOD(Debugger, cmdExit)); - registerCmd("scene", WRAP_METHOD(Debugger, cmdScene)); + _showAllLocations = LOC_DISABLED; + + registerCmd("continue", WRAP_METHOD(Debugger, cmdExit)); + registerCmd("scene", WRAP_METHOD(Debugger, cmdScene)); + registerCmd("song", WRAP_METHOD(Debugger, cmdSong)); + registerCmd("songs", WRAP_METHOD(Debugger, cmdListSongs)); + registerCmd("listfiles", WRAP_METHOD(Debugger, cmdListFiles)); + registerCmd("dumpfile", WRAP_METHOD(Debugger, cmdDumpFile)); + registerCmd("locations", WRAP_METHOD(Debugger, cmdLocations)); +} + +void Debugger::postEnter() { + if (!_3doPlayMovieFile.empty()) { + Scalpel3DOMoviePlay(_3doPlayMovieFile.c_str(), Common::Point(0, 0)); + + _3doPlayMovieFile.clear(); + } + + _vm->pauseEngine(false); } int Debugger::strToInt(const char *s) { @@ -56,4 +88,79 @@ bool Debugger::cmdScene(int argc, const char **argv) { } } +bool Debugger::cmdSong(int argc, const char **argv) { + if (argc != 2) { + debugPrintf("Format: song <name>\n"); + return true; + } + + Common::StringArray songs; + _vm->_music->getSongNames(songs); + + for (uint i = 0; i < songs.size(); i++) { + if (songs[i].equalsIgnoreCase(argv[1])) { + _vm->_music->loadSong(songs[i]); + return false; + } + } + + debugPrintf("Invalid song. Use the 'songs' command to see which ones are available.\n"); + return true; +} + +bool Debugger::cmdListSongs(int argc, const char **argv) { + Common::StringArray songs; + _vm->_music->getSongNames(songs); + debugPrintColumns(songs); + return true; +} + +bool Debugger::cmdListFiles(int argc, const char **argv) { + if (argc != 2) { + debugPrintf("Format: listfiles <resource file>\n"); + return true; + } + Common::StringArray files; + _vm->_res->getResourceNames(Common::String(argv[1]), files); + debugPrintColumns(files); + return true; +} + +bool Debugger::cmdDumpFile(int argc, const char **argv) { + if (argc != 2) { + debugPrintf("Format: dumpfile <resource name>\n"); + return true; + } + + Common::SeekableReadStream *s = _vm->_res->load(argv[1]); + if (!s) { + debugPrintf("Invalid resource.\n"); + return true; + } + + byte *buffer = new byte[s->size()]; + s->read(buffer, s->size()); + + Common::DumpFile dumpFile; + dumpFile.open(argv[1]); + + dumpFile.write(buffer, s->size()); + dumpFile.flush(); + dumpFile.close(); + + delete[] buffer; + + debugPrintf("Resource %s has been dumped to disk.\n", argv[1]); + + return true; +} + +bool Debugger::cmdLocations(int argc, const char **argv) { + _showAllLocations = LOC_REFRESH; + + debugPrintf("Now showing all map locations\n"); + return false; +} + + } // End of namespace Sherlock diff --git a/engines/sherlock/debugger.h b/engines/sherlock/debugger.h index e6a3aba828..bcc4448c32 100644 --- a/engines/sherlock/debugger.h +++ b/engines/sherlock/debugger.h @@ -30,10 +30,10 @@ namespace Sherlock { class SherlockEngine; +enum AllLocations { LOC_REFRESH = -1, LOC_DISABLED = 0, LOC_ALL = 1 }; + class Debugger : public GUI::Debugger { private: - SherlockEngine *_vm; - /** * Converts a decimal or hexadecimal string into a number */ @@ -43,9 +43,42 @@ private: * Switch to another scene */ bool cmdScene(int argc, const char **argv); + + /** + * Plays a song + */ + bool cmdSong(int argc, const char **argv); + + /** + * Lists all available songs + */ + bool cmdListSongs(int argc, const char **argv); + + /** + * Lists all files in a library (use at your own risk) + */ + bool cmdListFiles(int argc, const char **argv); + + /** + * Dumps a file to disk + */ + bool cmdDumpFile(int argc, const char **argv); + + /** + * Show all locations on the map + */ + bool cmdLocations(int argc, const char **argv); +protected: + SherlockEngine *_vm; + Common::String _3doPlayMovieFile; +public: + AllLocations _showAllLocations; public: Debugger(SherlockEngine *vm); virtual ~Debugger() {} + static Debugger *init(SherlockEngine *vm); + + void postEnter(); }; } // End of namespace Sherlock diff --git a/engines/sherlock/detection.cpp b/engines/sherlock/detection.cpp index ea68d79a1b..5a94b3485f 100644 --- a/engines/sherlock/detection.cpp +++ b/engines/sherlock/detection.cpp @@ -44,6 +44,10 @@ Common::Platform SherlockEngine::getPlatform() const { return _gameDescription->desc.platform; } +Common::Language SherlockEngine::getLanguage() const { + return _gameDescription->desc.language; +} + } // End of namespace Sherlock static const PlainGameDescriptor sherlockGames[] = { @@ -58,6 +62,7 @@ static const PlainGameDescriptor sherlockGames[] = { #define GAMEOPTION_HELP_STYLE GUIO_GAMEOPTIONS3 #define GAMEOPTION_PORTRAITS_ON GUIO_GAMEOPTIONS4 #define GAMEOPTION_WINDOW_STYLE GUIO_GAMEOPTIONS5 +#define GAMEOPTION_TRANSPARENT_WINDOWS GUIO_GAMEOPTIONS6 static const ADExtraGuiOptionsMap optionsList[] = { { @@ -110,6 +115,16 @@ static const ADExtraGuiOptionsMap optionsList[] = { } }, + { + GAMEOPTION_TRANSPARENT_WINDOWS, + { + _s("Transparent windows"), + _s("Show windows with a partially transparent background"), + "transparent_windows", + true + } + }, + AD_EXTRA_GUI_OPTIONS_TERMINATOR }; diff --git a/engines/sherlock/detection_tables.h b/engines/sherlock/detection_tables.h index 208b6710af..e2b5a3dce9 100644 --- a/engines/sherlock/detection_tables.h +++ b/engines/sherlock/detection_tables.h @@ -40,6 +40,75 @@ static const SherlockGameDescription gameDescriptions[] = { }, { + // Case of the Serrated Scalpel - German CD (from multilingual CD) + // Provided by m_kiewitz + { + "scalpel", + 0, { + {"talk.lib", 0, "40a5f9f37c0e0d2ad48d8f44d8e393c9", 284278}, + {"music.lib", 0, "68ae2f7684ecf903bd60a00bb6bae195", 366465}, + AD_LISTEND}, + Common::DE_DEU, + Common::kPlatformDOS, + ADGF_UNSTABLE, + GUIO6(GUIO_NOSPEECH, GAMEOPTION_ORIGINAL_SAVES, GAMEOPTION_FADE_STYLE, GAMEOPTION_HELP_STYLE, + GAMEOPTION_PORTRAITS_ON, GAMEOPTION_WINDOW_STYLE) + }, + GType_SerratedScalpel, + }, + + { + // Case of the Serrated Scalpel - German + // Provided by mgerhardy + { + "scalpel", + 0, { + {"talk.lib", 0, "44652e54172e13b1b075b1ef7d89de24", 284043}, + {"music.lib", 0, "68ae2f7684ecf903bd60a00bb6bae195", 366465}, + AD_LISTEND}, + Common::DE_DEU, + Common::kPlatformDOS, + ADGF_UNSTABLE, + GUIO6(GUIO_NOSPEECH, GAMEOPTION_ORIGINAL_SAVES, GAMEOPTION_FADE_STYLE, GAMEOPTION_HELP_STYLE, + GAMEOPTION_PORTRAITS_ON, GAMEOPTION_WINDOW_STYLE) + }, + GType_SerratedScalpel, + }, + + { + // Case of the Serrated Scalpel - Spanish CD (from multilingual CD) + // Provided by m_kiewitz + { + "scalpel", + 0, { + {"talk.lib", 0, "27697804b637a7f3b77234bf16f15dce", 171419}, + {"music.lib", 0, "68ae2f7684ecf903bd60a00bb6bae195", 366465}, + AD_LISTEND}, + Common::ES_ESP, + Common::kPlatformDOS, + ADGF_UNSTABLE, + GUIO6(GUIO_NOSPEECH, GAMEOPTION_ORIGINAL_SAVES, GAMEOPTION_FADE_STYLE, GAMEOPTION_HELP_STYLE, + GAMEOPTION_PORTRAITS_ON, GAMEOPTION_WINDOW_STYLE) + }, + GType_SerratedScalpel, + }, + + { + // Case of the Serrated Scalpel - English 3DO + { + "scalpel", + 0, + AD_ENTRY1s("talk.lib", "20f74a29f2db6475e85b029ac9fc03bc", 240610), + Common::EN_ANY, + Common::kPlatform3DO, + ADGF_UNSTABLE, + GUIO6(GUIO_NOSPEECH, GAMEOPTION_ORIGINAL_SAVES, GAMEOPTION_FADE_STYLE, GAMEOPTION_HELP_STYLE, + GAMEOPTION_PORTRAITS_ON, GAMEOPTION_WINDOW_STYLE) + }, + GType_SerratedScalpel, + }, + + { // Case of the Serrated Scalpel - Interactive English Demo // Provided by Strangerke { @@ -79,7 +148,7 @@ static const SherlockGameDescription gameDescriptions[] = { Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, - GUIO0() + GUIO3(GAMEOPTION_ORIGINAL_SAVES, GAMEOPTION_HELP_STYLE, GAMEOPTION_TRANSPARENT_WINDOWS) }, GType_RoseTattoo }, @@ -94,7 +163,7 @@ static const SherlockGameDescription gameDescriptions[] = { Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, - GUIO0() + GUIO3(GAMEOPTION_ORIGINAL_SAVES, GAMEOPTION_HELP_STYLE, GAMEOPTION_TRANSPARENT_WINDOWS) }, GType_RoseTattoo, }, @@ -109,7 +178,7 @@ static const SherlockGameDescription gameDescriptions[] = { Common::DE_DEU, Common::kPlatformDOS, ADGF_UNSTABLE, - GUIO0() + GUIO3(GAMEOPTION_ORIGINAL_SAVES, GAMEOPTION_HELP_STYLE, GAMEOPTION_TRANSPARENT_WINDOWS) }, GType_RoseTattoo, }, diff --git a/engines/sherlock/events.cpp b/engines/sherlock/events.cpp index b238605e13..079ea0e663 100644 --- a/engines/sherlock/events.cpp +++ b/engines/sherlock/events.cpp @@ -27,6 +27,7 @@ #include "graphics/cursorman.h" #include "sherlock/sherlock.h" #include "sherlock/events.h" +#include "sherlock/surface.h" namespace Sherlock { @@ -41,6 +42,9 @@ Events::Events(SherlockEngine *vm): _vm(vm) { _pressed = _released = false; _rightPressed = _rightReleased = false; _oldButtons = _oldRightButton = false; + _firstPress = false; + _waitCounter = 0; + _frameRate = 0; if (_vm->_interactiveFl) loadCursors("rmouse.vgs"); @@ -54,35 +58,102 @@ void Events::loadCursors(const Common::String &filename) { hideCursor(); delete _cursorImages; - _cursorImages = new ImageFile(filename); + if (!IS_3DO) { + // PC + _cursorImages = new ImageFile(filename); + } else { + // 3DO + _cursorImages = new ImageFile3DO(filename, kImageFile3DOType_RoomFormat); + } _cursorId = INVALID_CURSOR; } void Events::setCursor(CursorId cursorId) { - if (cursorId == _cursorId) + if (cursorId == _cursorId || _waitCounter > 0) return; - _cursorId = cursorId; + int hotspotX, hotspotY; + + if (cursorId == MAGNIFY) { + hotspotX = 8; + hotspotY = 8; + } else { + hotspotX = 0; + hotspotY = 0; + } // Set the cursor data Graphics::Surface &s = (*_cursorImages)[cursorId]._frame; - setCursor(s); + setCursor(s, hotspotX, hotspotY); + + _cursorId = cursorId; } -void Events::setCursor(const Graphics::Surface &src) { - CursorMan.replaceCursor(src.getPixels(), src.w, src.h, 0, 0, 0xff); +void Events::setCursor(const Graphics::Surface &src, int hotspotX, int hotspotY) { + _cursorId = INVALID_CURSOR; + _hotspotPos = Common::Point(hotspotX, hotspotY); + + if (!IS_3DO) { + // PC 8-bit palettized + CursorMan.replaceCursor(src.getPixels(), src.w, src.h, hotspotX, hotspotY, 0xff); + } else { + // 3DO RGB565 + CursorMan.replaceCursor(src.getPixels(), src.w, src.h, hotspotX, hotspotY, 0x0000, false, &src.format); + } showCursor(); } +void Events::setCursor(CursorId cursorId, const Common::Point &cursorPos, const Graphics::Surface &surface) { + _cursorId = cursorId; + + // Get the standard cursor frame + Graphics::Surface &cursorImg = (*_cursorImages)[cursorId]._frame; + + // If the X pos for the cursor image is -100, this is a special value to indicate + // the cursor should be horizontally centered + Common::Point cursorPt = cursorPos; + if (cursorPos.x == -100) + cursorPt.x = (surface.w - cursorImg.w) / 2; + + // Figure total bounds needed for cursor image and passed image + Common::Rect bounds(surface.w, surface.h); + bounds.extend(Common::Rect(cursorPt.x, cursorPt.y, cursorPt.x + cursorImg.w, cursorPt.y + cursorImg.h)); + Common::Rect r = bounds; + r.moveTo(0, 0); + + // Form a single surface containing both frames + Surface s(r.width(), r.height()); + s.fill(TRANSPARENCY); + + // Draw the passed image + Common::Point drawPos; + if (cursorPt.x < 0) + drawPos.x = -cursorPt.x; + if (cursorPt.y < 0) + drawPos.y = -cursorPt.y; + s.blitFrom(surface, Common::Point(drawPos.x, drawPos.y)); + + // Draw the cursor image + drawPos = Common::Point(MAX(cursorPt.x, (int16)0), MAX(cursorPt.y, (int16)0)); + s.transBlitFrom(cursorImg, Common::Point(drawPos.x, drawPos.y)); + + // Set up hotspot position for cursor, adjusting for cursor image's position within the surface + Common::Point hotspot; + if (cursorId == MAGNIFY) + hotspot = Common::Point(8, 8); + hotspot += drawPos; + // Set the cursor + setCursor(s.getRawSurface(), hotspot.x, hotspot.y); +} + void Events::animateCursorIfNeeded() { if (_cursorId >= WAIT && _cursorId < (WAIT + 3)) { - CursorId newId = (WAIT + 2) ? WAIT : (CursorId)((int)_cursorId + 1); + CursorId newId = (_cursorId == WAIT + 2) ? WAIT : (CursorId)((int)_cursorId + 1); setCursor(newId); } } - void Events::showCursor() { CursorMan.showMouse(true); } @@ -99,16 +170,14 @@ bool Events::isCursorVisible() const { return CursorMan.isVisible(); } -void Events::moveMouse(const Common::Point &pt) { - g_system->warpMouse(pt.x, pt.y); -} - void Events::pollEvents() { checkForNextFrameCounter(); Common::Event event; while (g_system->getEventManager()->pollEvent(event)) { - // Handle keypress + _mousePos = event.mouse; + + // Handle events switch (event.type) { case Common::EVENT_QUIT: case Common::EVENT_RTL: @@ -149,10 +218,29 @@ void Events::pollEventsAndWait() { g_system->delayMillis(10); } +void Events::warpMouse(const Common::Point &pt) { + _mousePos = pt - _vm->_screen->_currentScroll; + g_system->warpMouse(_mousePos.x, _mousePos.y); +} + +void Events::warpMouse() { + Screen &screen = *_vm->_screen; + warpMouse(Common::Point(screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH / 2, + screen._currentScroll.y + SHERLOCK_SCREEN_HEIGHT / 2)); +} + +Common::Point Events::mousePos() const { + return _vm->_screen->_currentScroll + _mousePos; +} + +void Events::setFrameRate(int newRate) { + _frameRate = newRate; +} + bool Events::checkForNextFrameCounter() { // Check for next game frame uint32 milli = g_system->getMillis(); - if ((milli - _priorFrameTime) >= GAME_FRAME_TIME) { + if ((milli - _priorFrameTime) >= (1000 / _frameRate)) { ++_frameCounter; _priorFrameTime = milli; @@ -168,12 +256,42 @@ bool Events::checkForNextFrameCounter() { return false; } -Common::Point Events::mousePos() const { - return g_system->getEventManager()->getMousePos(); -} - Common::KeyState Events::getKey() { - return _pendingKeys.pop(); + Common::KeyState keyState = _pendingKeys.pop(); + + switch (keyState.keycode) { + case Common::KEYCODE_KP1: + keyState.keycode = Common::KEYCODE_END; + break; + case Common::KEYCODE_KP2: + keyState.keycode = Common::KEYCODE_DOWN; + break; + case Common::KEYCODE_KP3: + keyState.keycode = Common::KEYCODE_PAGEDOWN; + break; + case Common::KEYCODE_KP4: + keyState.keycode = Common::KEYCODE_LEFT; + break; + case Common::KEYCODE_KP6: + keyState.keycode = Common::KEYCODE_RIGHT; + break; + case Common::KEYCODE_KP7: + keyState.keycode = Common::KEYCODE_HOME; + break; + case Common::KEYCODE_KP8: + keyState.keycode = Common::KEYCODE_UP; + break; + case Common::KEYCODE_KP9: + keyState.keycode = Common::KEYCODE_PAGEUP; + break; + case Common::KEYCODE_KP_ENTER: + keyState.keycode = Common::KEYCODE_RETURN; + break; + default: + break; + } + + return keyState; } void Events::clearEvents() { @@ -182,6 +300,7 @@ void Events::clearEvents() { _pressed = _released = false; _rightPressed = _rightReleased = false; _oldButtons = _oldRightButton = false; + _firstPress = false; } void Events::clearKeyboard() { @@ -189,7 +308,7 @@ void Events::clearKeyboard() { } void Events::wait(int numFrames) { - uint32 totalMilli = numFrames * 1000 / GAME_FRAME_RATE; + uint32 totalMilli = numFrames * 1000 / _frameRate; delay(totalMilli); } @@ -199,7 +318,7 @@ bool Events::delay(uint32 time, bool interruptable) { // For really short periods, simply delay by the desired amount pollEvents(); g_system->delayMillis(time); - bool result = !(interruptable && (kbHit() || _pressed)); + bool result = !(interruptable && (kbHit() || _pressed || _vm->shouldQuit())); clearEvents(); return result; @@ -212,17 +331,19 @@ bool Events::delay(uint32 time, bool interruptable) { while (!_vm->shouldQuit() && g_system->getMillis() < delayEnd) { pollEventsAndWait(); - if (interruptable && (kbHit() || _pressed)) { + if (interruptable && (kbHit() || _mouseButtons)) { clearEvents(); return false; } } - return true; + return !_vm->shouldQuit(); } } void Events::setButtonState() { + _firstPress = ((_mouseButtons & 1) && !_pressed) || ((_mouseButtons & 2) && !_rightPressed); + _released = _rightReleased = false; if (_mouseButtons & LEFT_BUTTON) _pressed = _oldButtons = true; @@ -246,4 +367,14 @@ bool Events::checkInput() { return kbHit() || _pressed || _released || _rightPressed || _rightReleased; } +void Events::incWaitCounter() { + setCursor(WAIT); + ++_waitCounter; +} + +void Events::decWaitCounter() { + assert(_waitCounter > 0); + --_waitCounter; +} + } // End of namespace Sherlock diff --git a/engines/sherlock/events.h b/engines/sherlock/events.h index b35109fefe..e13ef18822 100644 --- a/engines/sherlock/events.h +++ b/engines/sherlock/events.h @@ -26,11 +26,11 @@ #include "common/scummsys.h" #include "common/events.h" #include "common/stack.h" -#include "sherlock/resources.h" +#include "sherlock/image_file.h" namespace Sherlock { -#define GAME_FRAME_RATE 60 +#define GAME_FRAME_RATE 30 #define GAME_FRAME_TIME (1000 / GAME_FRAME_RATE) enum CursorId { ARROW = 0, MAGNIFY = 1, WAIT = 2, EXIT_ZONES_START = 5, INVALID_CURSOR = -1 }; @@ -44,6 +44,9 @@ private: uint32 _priorFrameTime; ImageFile *_cursorImages; int _mouseButtons; + Common::Point _mousePos; + int _waitCounter; + uint _frameRate; /** * Check whether it's time to display the next screen frame @@ -57,7 +60,9 @@ public: bool _rightReleased; bool _oldButtons; bool _oldRightButton; + bool _firstPress; Common::Stack<Common::KeyState> _pendingKeys; + Common::Point _hotspotPos; public: Events(SherlockEngine *vm); ~Events(); @@ -75,7 +80,12 @@ public: /** * Set the cursor to show from a passed frame */ - void setCursor(const Graphics::Surface &src); + void setCursor(const Graphics::Surface &src, int hotspotX = 0, int hotspotY = 0); + + /** + * Set both a standard cursor as well as an inventory item above it + */ + void setCursor(CursorId cursorId, const Common::Point &cursorPos, const Graphics::Surface &surface); /** * Animates the mouse cursor if the Wait cursor is showing @@ -103,11 +113,6 @@ public: bool isCursorVisible() const; /** - * Move the mouse - */ - void moveMouse(const Common::Point &pt); - - /** * Check for any pending events */ void pollEvents(); @@ -119,12 +124,38 @@ public: void pollEventsAndWait(); /** + * Move the mouse cursor + */ + void warpMouse(const Common::Point &pt); + + /** + * Move the mouse cursor to the center of the screen + */ + void warpMouse(); + + /** * Get the current mouse position */ + Common::Point screenMousePos() const { return _mousePos; } + + /** + * Get the current mouse position within the scene, adjusted by the scroll position + */ Common::Point mousePos() const; + /** + * Override the default frame rate + */ + void setFrameRate(int newRate); + + /** + * Return the current game frame number + */ uint32 getFrameCounter() const { return _frameCounter; } + /** + * Returns true if there's a pending keyboard key + */ bool kbHit() const { return !_pendingKeys.empty(); } /** @@ -164,6 +195,16 @@ public: * Checks to see to see if a key or a mouse button is pressed. */ bool checkInput(); + + /** + * Increment the wait counter + */ + void incWaitCounter(); + + /** + * Decrement the wait counter + */ + void decWaitCounter(); }; } // End of namespace Sherlock diff --git a/engines/sherlock/fixed_text.cpp b/engines/sherlock/fixed_text.cpp new file mode 100644 index 0000000000..cbee944120 --- /dev/null +++ b/engines/sherlock/fixed_text.cpp @@ -0,0 +1,38 @@ +/* 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 "sherlock/sherlock.h" +#include "sherlock/fixed_text.h" +#include "sherlock/scalpel/scalpel_fixed_text.h" +#include "sherlock/tattoo/tattoo_fixed_text.h" + +namespace Sherlock { + +FixedText *FixedText::init(SherlockEngine *vm) { + if (vm->getGameID() == GType_SerratedScalpel) + return new Scalpel::ScalpelFixedText(vm); + else + return new Tattoo::TattooFixedText(vm); +} + + +} // End of namespace Sherlock diff --git a/engines/sherlock/fixed_text.h b/engines/sherlock/fixed_text.h new file mode 100644 index 0000000000..40444f4052 --- /dev/null +++ b/engines/sherlock/fixed_text.h @@ -0,0 +1,66 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_FIXED_TEXT_H +#define SHERLOCK_FIXED_TEXT_H + +#include "common/scummsys.h" +#include "common/language.h" + +namespace Sherlock { + +#define FIXED(MSG) _vm->_fixedText->getText(kFixedText_##MSG) + +enum FixedTextActionId { + kFixedTextAction_Invalid = -1, + kFixedTextAction_Open = 0, + kFixedTextAction_Close, + kFixedTextAction_Move, + kFixedTextAction_Pick, + kFixedTextAction_Use +}; + +class SherlockEngine; + +class FixedText { +protected: + SherlockEngine *_vm; + + FixedText(SherlockEngine *vm) : _vm(vm) {} +public: + static FixedText *init(SherlockEngine *vm); + virtual ~FixedText() {} + + /** + * Gets text + */ + virtual const char *getText(int fixedTextId) = 0; + + /** + * Get action message + */ + virtual const Common::String getActionMessage(FixedTextActionId actionId, int messageIndex) = 0; +}; + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/fonts.cpp b/engines/sherlock/fonts.cpp new file mode 100644 index 0000000000..482e795b6d --- /dev/null +++ b/engines/sherlock/fonts.cpp @@ -0,0 +1,211 @@ +/* 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 "common/system.h" +#include "common/platform.h" +#include "sherlock/fonts.h" +#include "sherlock/image_file.h" +#include "sherlock/surface.h" +#include "sherlock/sherlock.h" + +namespace Sherlock { + +SherlockEngine *Fonts::_vm; +ImageFile *Fonts::_font; +int Fonts::_fontNumber; +int Fonts::_fontHeight; +int Fonts::_widestChar; +uint16 Fonts::_charCount; +byte Fonts::_yOffsets[255]; + +void Fonts::setVm(SherlockEngine *vm) { + _vm = vm; + _font = nullptr; + _charCount = 0; +} + +void Fonts::free() { + delete _font; +} + +void Fonts::setFont(int fontNum) { + _fontNumber = fontNum; + + // Discard previous font + delete _font; + + Common::String fontFilename; + + if (_vm->getPlatform() != Common::kPlatform3DO) { + // PC + // use FONT[number].VGS, which is a regular sherlock graphic file + fontFilename = Common::String::format("FONT%d.VGS", fontNum + 1); + + // load font data + _font = new ImageFile(fontFilename); + } else { + // 3DO + switch (fontNum) { + case 0: + case 1: + fontFilename = "helvetica14.font"; + break; + case 2: + fontFilename = "darts.font"; + break; + default: + error("setFont(): unsupported 3DO font number"); + } + + // load font data + _font = new ImageFile3DO(fontFilename, kImageFile3DOType_Font); + } + + _charCount = _font->size(); + + // Iterate through the frames to find the widest and tallest font characters + _fontHeight = _widestChar = 0; + for (uint idx = 0; idx < _charCount; ++idx) { + _fontHeight = MAX((uint16)_fontHeight, (*_font)[idx]._frame.h); + _widestChar = MAX((uint16)_widestChar, (*_font)[idx]._frame.w); + } + + // Initialize the Y offset table for the extended character set + for (int idx = 0; idx < 255; ++idx) { + _yOffsets[idx] = 0; + + if (IS_ROSE_TATTOO) { + if ((idx >= 129 && idx < 135) || (idx >= 136 && idx < 143) || (idx >= 147 && idx < 155) || + (idx >= 156 && idx < 165)) + _yOffsets[idx] = 1; + else if ((idx >= 143 && idx < 146) || idx == 165) + _yOffsets[idx] = 2; + } + } +} + +inline byte Fonts::translateChar(byte c) { + switch (c) { + case ' ': + return 0; // translate to first actual character + case 225: + // This was done in the German interpreter + // SH1: happens, when talking to the kid in the 2nd room + // SH2: happens, when looking at the newspaper right at the start in the backalley + // Special handling for 0xE1 (German Sharp-S character) + if (IS_ROSE_TATTOO) { + return 136; // it got translated to this for SH2 + } + return 135; // and this for SH1 + default: + if (IS_SERRATED_SCALPEL) { + if (c >= 0x80) { // German SH1 version did this, but not German SH2 + c--; + } + // Spanish SH1 did this (reverse engineered code) + //if ((c >= 0xA0) && (c <= 0xAD) || (c == 0x82)) { + // c--; + //} + } + assert(c > 32); // anything above space is allowed + return c - 33; + } +} + +void Fonts::writeString(Surface *surface, const Common::String &str, + const Common::Point &pt, int overrideColor) { + Common::Point charPos = pt; + + if (!_font) + return; + + for (const char *curCharPtr = str.c_str(); *curCharPtr; ++curCharPtr) { + byte curChar = *curCharPtr; + + if (curChar == ' ') { + charPos.x += 5; // hardcoded space + continue; + } + curChar = translateChar(curChar); + + assert(curChar < _charCount); + ImageFrame &frame = (*_font)[curChar]; + surface->transBlitFrom(frame, Common::Point(charPos.x, charPos.y + _yOffsets[curChar]), false, overrideColor); + charPos.x += frame._frame.w + 1; + } +} + +int Fonts::stringWidth(const Common::String &str) { + int width = 0; + + if (!_font) + return 0; + + for (const char *c = str.c_str(); *c; ++c) + width += charWidth(*c); + + return width; +} + +int Fonts::stringHeight(const Common::String &str) { + int height = 0; + + if (!_font) + return 0; + + for (const char *c = str.c_str(); *c; ++c) + height = MAX(height, charHeight(*c)); + + return height; +} + +int Fonts::charWidth(unsigned char c) { + byte curChar; + + if (!_font) + return 0; + + if (c == ' ') { + return 5; // hardcoded space + } + curChar = translateChar(c); + + if (curChar < _charCount) + return (*_font)[curChar]._frame.w + 1; + return 0; +} + +int Fonts::charHeight(unsigned char c) { + byte curChar; + + if (!_font) + return 0; + + // Space is supposed to be handled like the first actual character (which is decimal 33) + curChar = translateChar(c); + + assert(curChar < _charCount); + const ImageFrame &img = (*_font)[curChar]; + return img._height + img._offset.y + 1; +} + +} // End of namespace Sherlock diff --git a/engines/sherlock/fonts.h b/engines/sherlock/fonts.h new file mode 100644 index 0000000000..a527cc73c0 --- /dev/null +++ b/engines/sherlock/fonts.h @@ -0,0 +1,105 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_FONTS_H +#define SHERLOCK_FONTS_H + +#include "common/rect.h" +#include "common/platform.h" +#include "graphics/surface.h" + +namespace Sherlock { + +class SherlockEngine; +class ImageFile; +class Surface; + +class Fonts { +private: + static ImageFile *_font; + static byte _yOffsets[255]; +protected: + static SherlockEngine *_vm; + static int _fontNumber; + static int _fontHeight; + static int _widestChar; + static uint16 _charCount; + + static void writeString(Surface *surface, const Common::String &str, + const Common::Point &pt, int overrideColor = 0); + + static inline byte translateChar(byte c); +public: + /** + * Initialise the font manager + */ + static void setVm(SherlockEngine *vm); + + /** + * Frees the font manager + */ + static void free(); + + /** + * Set the font to use for writing text on the screen + */ + void setFont(int fontNum); + + /** + * Returns the width of a string in pixels + */ + int stringWidth(const Common::String &str); + + /** + * Returns the height of a string in pixels (i.e. the tallest displayed character) + */ + int stringHeight(const Common::String &str); + + /** + * Returns the width of a character in pixels + */ + int charWidth(unsigned char c); + + /** + * Returns the width of a character in pixels + */ + int charHeight(unsigned char c); + + /** + * Return the font height + */ + int fontHeight() const { return _fontHeight; } + + /** + * Return the width of the widest character in the font + */ + int widestChar() const { return _widestChar; } + + /** + * Return the currently active font number + */ + int fontNumber() const { return _fontNumber; } +}; + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/image_file.cpp b/engines/sherlock/image_file.cpp new file mode 100644 index 0000000000..3d881eb655 --- /dev/null +++ b/engines/sherlock/image_file.cpp @@ -0,0 +1,1098 @@ +/* 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 "sherlock/image_file.h" +#include "sherlock/screen.h" +#include "sherlock/sherlock.h" +#include "common/debug.h" +#include "common/memstream.h" + +namespace Sherlock { + +SherlockEngine *ImageFile::_vm; + +void ImageFile::setVm(SherlockEngine *vm) { + _vm = vm; +} + +ImageFile::ImageFile() { +} + +ImageFile::ImageFile(const Common::String &name, bool skipPal, bool animImages) { + Common::SeekableReadStream *stream = _vm->_res->load(name); + + Common::fill(&_palette[0], &_palette[PALETTE_SIZE], 0); + load(*stream, skipPal, animImages); + + delete stream; +} + +ImageFile::ImageFile(Common::SeekableReadStream &stream, bool skipPal) { + Common::fill(&_palette[0], &_palette[PALETTE_SIZE], 0); + load(stream, skipPal, false); +} + +ImageFile::~ImageFile() { + for (uint idx = 0; idx < size(); ++idx) + (*this)[idx]._frame.free(); +} + +void ImageFile::load(Common::SeekableReadStream &stream, bool skipPalette, bool animImages) { + loadPalette(stream); + + int streamSize = stream.size(); + while (stream.pos() < streamSize) { + ImageFrame frame; + frame._width = stream.readUint16LE() + 1; + frame._height = stream.readUint16LE() + 1; + frame._paletteBase = stream.readByte(); + + if (animImages) { + // Animation cutscene image files use a 16-bit x offset + frame._offset.x = stream.readUint16LE(); + frame._rleEncoded = (frame._offset.x & 0xff) == 1; + frame._offset.y = stream.readByte(); + } else { + // Standard image files have a separate byte for the RLE flag, and an 8-bit X offset + frame._rleEncoded = stream.readByte() == 1; + frame._offset.x = stream.readByte(); + frame._offset.y = stream.readByte(); + } + + frame._rleEncoded = !skipPalette && frame._rleEncoded; + + if (frame._paletteBase) { + // Nibble packed frame data + frame._size = (frame._width * frame._height) / 2; + } else if (frame._rleEncoded) { + // This size includes the header size, which we subtract + frame._size = stream.readUint16LE() - 11; + frame._rleMarker = stream.readByte(); + } else { + // Uncompressed data + frame._size = frame._width * frame._height; + } + + // Load data for frame and decompress it + byte *data = new byte[frame._size + 4]; + stream.read(data, frame._size); + Common::fill(data + frame._size, data + frame._size + 4, 0); + frame.decompressFrame(data, IS_ROSE_TATTOO); + delete[] data; + + push_back(frame); + } +} + +void ImageFile::loadPalette(Common::SeekableReadStream &stream) { + // Check for palette + uint16 width = stream.readUint16LE() + 1; + uint16 height = stream.readUint16LE() + 1; + byte paletteBase = stream.readByte(); + byte rleEncoded = stream.readByte(); + byte offsetX = stream.readByte(); + byte offsetY = stream.readByte(); + uint32 palSignature = 0; + + if ((width == 390) && (height == 2) && (!paletteBase) && (!rleEncoded) && (!offsetX) && (!offsetY)) { + // We check for these specific values + // We can't do "width * height", because at least the first German+Spanish menu bar is 60 x 13 + // which is 780, which is the size of the palette. We obviously don't want to detect it as palette. + + // As another security measure, we also check for the signature text + palSignature = stream.readUint32BE(); + if (palSignature != MKTAG('V', 'G', 'A', ' ')) { + // signature mismatch, rewind + stream.seek(-12, SEEK_CUR); + return; + } + // Found palette, so read it in + stream.seek(8, SEEK_CUR); // Skip over the rest of the signature text "VGA palette" + for (int idx = 0; idx < PALETTE_SIZE; ++idx) + _palette[idx] = VGA_COLOR_TRANS(stream.readByte()); + } else { + // Not a palette, so rewind to start of frame data for normal frame processing + stream.seek(-8, SEEK_CUR); + } +} + +void ImageFrame::decompressFrame(const byte *src, bool isRoseTattoo) { + _frame.create(_width, _height, Graphics::PixelFormat::createFormatCLUT8()); + byte *dest = (byte *)_frame.getPixels(); + Common::fill(dest, dest + _width * _height, 0xff); + + if (_paletteBase) { + // Nibble-packed + for (uint idx = 0; idx < _size; ++idx, ++src) { + *dest++ = *src & 0xF; + *dest++ = (*src >> 4); + } + } else if (_rleEncoded && isRoseTattoo) { + // Rose Tattoo run length encoding doesn't use the RLE marker byte + for (int yp = 0; yp < _height; ++yp) { + int xSize = _width; + while (xSize > 0) { + // Skip a given number of pixels + byte skip = *src++; + dest += skip; + xSize -= skip; + if (!xSize) + break; + + // Get a run length, and copy the following number of pixels + int rleCount = *src++; + xSize -= rleCount; + while (rleCount-- > 0) + *dest++ = *src++; + } + assert(xSize == 0); + } + } else if (_rleEncoded) { + // RLE encoded + int frameSize = _width * _height; + while (frameSize > 0) { + if (*src == _rleMarker) { + byte rleColor = src[1]; + byte rleCount = src[2]; + src += 3; + frameSize -= rleCount; + while (rleCount--) + *dest++ = rleColor; + } else { + *dest++ = *src++; + --frameSize; + } + } + assert(frameSize == 0); + } else { + // Uncompressed frame + Common::copy(src, src + _width * _height, dest); + } +} + +/*----------------------------------------------------------------*/ + +int ImageFrame::sDrawXSize(int scaleVal) const { + int width = _width; + int scale = scaleVal == 0 ? 1 : scaleVal; + + if (scaleVal >= SCALE_THRESHOLD) + --width; + + int result = width * SCALE_THRESHOLD / scale; + if (scaleVal >= SCALE_THRESHOLD) + ++result; + + return result; +} + +int ImageFrame::sDrawYSize(int scaleVal) const { + int height = _height; + int scale = scaleVal == 0 ? 1 : scaleVal; + + if (scaleVal >= SCALE_THRESHOLD) + --height; + + int result = height * SCALE_THRESHOLD / scale; + if (scaleVal >= SCALE_THRESHOLD) + ++result; + + return result; +} + +int ImageFrame::sDrawXOffset(int scaleVal) const { + if (scaleVal == SCALE_THRESHOLD) + return _offset.x; + + int width = _offset.x; + int scale = scaleVal == 0 ? 1 : scaleVal; + + if (scaleVal >= SCALE_THRESHOLD) + --width; + + int result = width * SCALE_THRESHOLD / scale; + if (scaleVal > SCALE_THRESHOLD) + ++result; + + return result; +} + +int ImageFrame::sDrawYOffset(int scaleVal) const { + if (scaleVal == SCALE_THRESHOLD) + return _offset.y; + + int height = _offset.y; + int scale = scaleVal == 0 ? 1 : scaleVal; + + if (scaleVal >= SCALE_THRESHOLD) + --height; + + int result = height * SCALE_THRESHOLD / scale; + if (scaleVal > SCALE_THRESHOLD) + ++result; + + return result; +} + +// ******************************************************* + +/*----------------------------------------------------------------*/ + +SherlockEngine *ImageFile3DO::_vm; + +void ImageFile3DO::setVm(SherlockEngine *vm) { + _vm = vm; +} + +ImageFile3DO::ImageFile3DO(const Common::String &name, ImageFile3DOType imageFile3DOType) { +#if 0 + Common::File *dataStream = new Common::File(); + + if (!dataStream->open(name)) { + error("unable to open %s\n", name.c_str()); + } +#endif + Common::SeekableReadStream *dataStream = _vm->_res->load(name); + + switch(imageFile3DOType) { + case kImageFile3DOType_Animation: + loadAnimationFile(*dataStream); + break; + case kImageFile3DOType_Cel: + case kImageFile3DOType_CelAnimation: + load3DOCelFile(*dataStream); + break; + case kImageFile3DOType_RoomFormat: + load3DOCelRoomData(*dataStream); + break; + case kImageFile3DOType_Font: + loadFont(*dataStream); + break; + default: + error("unknown Imagefile-3DO-Type"); + break; + } + + delete dataStream; +} + +ImageFile3DO::ImageFile3DO(Common::SeekableReadStream &stream, bool isRoomData) { + if (!isRoomData) { + load(stream, isRoomData); + } else { + load3DOCelRoomData(stream); + } +} + +ImageFile3DO::~ImageFile3DO() { + // already done in ImageFile destructor + //for (uint idx = 0; idx < size(); ++idx) + // (*this)[idx]._frame.free(); +} + +void ImageFile3DO::load(Common::SeekableReadStream &stream, bool isRoomData) { + uint32 headerId = 0; + + if (isRoomData) { + load3DOCelRoomData(stream); + return; + } + + headerId = stream.readUint32BE(); + assert(!stream.eos()); + + // Seek back to the start + stream.seek(-4, SEEK_CUR); + + // Identify type of file + switch (headerId) { + case MKTAG('C', 'C', 'B', ' '): + case MKTAG('A', 'N', 'I', 'M'): + case MKTAG('O', 'F', 'S', 'T'): // 3DOSplash.cel + // 3DO .cel (title1a.cel, etc.) or animation file (walk.anim) + load3DOCelFile(stream); + break; + + default: + // Sherlock animation file (.3da files) + loadAnimationFile(stream); + break; + } +} + +// 3DO uses RGB555, we use RGB565 internally so that more platforms are able to run us +inline uint16 ImageFile3DO::convertPixel(uint16 pixel3DO) { + byte red = (pixel3DO >> 10) & 0x1F; + byte green = (pixel3DO >> 5) & 0x1F; + byte blue = pixel3DO & 0x1F; + + return ((red << 11) | (green << 6) | (blue)); +} + +void ImageFile3DO::loadAnimationFile(Common::SeekableReadStream &stream) { + uint32 streamLeft = stream.size() - stream.pos(); + uint32 celDataSize = 0; + + while (streamLeft > 0) { + ImageFrame frame; + + // We expect a basic header of 8 bytes + if (streamLeft < 8) + error("load3DOAnimationFile: expected animation header, not enough bytes"); + + celDataSize = stream.readUint16BE(); + + frame._width = stream.readUint16BE() + 1; // 2 bytes BE width + frame._height = stream.readByte() + 1; // 1 byte BE height + frame._paletteBase = 0; + + frame._rleEncoded = true; // always compressed + if (frame._width & 0x8000) { + frame._width &= 0x7FFF; + celDataSize += 0x10000; + } + + frame._offset.x = stream.readUint16BE(); + frame._offset.y = stream.readByte(); + frame._size = 0; + // Got header + streamLeft -= 8; + + // cel data follows + if (streamLeft < celDataSize) + error("load3DOAnimationFile: expected cel data, not enough bytes"); + + // + // Load data for frame and decompress it + byte *data = new byte[celDataSize]; + stream.read(data, celDataSize); + streamLeft -= celDataSize; + + // always 16 bits per pixel (RGB555) + decompress3DOCelFrame(frame, data, celDataSize, 16, NULL); + + delete[] data; + + push_back(frame); + } +} + +static byte imagefile3DO_cel_bitsPerPixelLookupTable[8] = { + 0, 1, 2, 4, 6, 8, 16, 0 +}; + +// Reads a 3DO .cel/.anim file +void ImageFile3DO::load3DOCelFile(Common::SeekableReadStream &stream) { + int32 streamSize = stream.size(); + int32 chunkStartPos = 0; + uint32 chunkTag = 0; + uint32 chunkSize = 0; + byte *chunkDataPtr = NULL; + + // ANIM chunk (animation header for animation files) + bool animFound = false; + uint32 animVersion = 0; + uint32 animType = 0; + uint32 animFrameCount = 1; // we expect 1 frame without an ANIM header + // CCB chunk (cel control block) + bool ccbFound = false; + uint32 ccbVersion = 0; + uint32 ccbFlags = 0; + bool ccbFlags_compressed = false; + uint16 ccbPPMP0 = 0; + uint16 ccbPPMP1 = 0; + uint32 ccbPRE0 = 0; + uint16 ccbPRE0_height = 0; + byte ccbPRE0_bitsPerPixel = 0; + uint32 ccbPRE1 = 0; + uint16 ccbPRE1_width = 0; + uint32 ccbWidth = 0; + uint32 ccbHeight = 0; + // pixel lookup table + bool plutFound = false; + uint32 plutCount = 0; + ImageFile3DOPixelLookupTable plutRGBlookupTable; + + memset(&plutRGBlookupTable, 0, sizeof(plutRGBlookupTable)); + + while (!stream.err() && (stream.pos() < streamSize)) { + chunkStartPos = stream.pos(); + chunkTag = stream.readUint32BE(); + chunkSize = stream.readUint32BE(); + + if (stream.eos() || stream.err()) + break; + + if (chunkSize < 8) + error("load3DOCelFile: Invalid chunk size"); + + uint32 dataSize = chunkSize - 8; + + switch (chunkTag) { + case MKTAG('A', 'N', 'I', 'M'): + // animation header + assert(dataSize >= 24); + + if (animFound) + error("load3DOCelFile: multiple ANIM chunks not supported"); + + animFound = true; + animVersion = stream.readUint32BE(); + animType = stream.readUint32BE(); + animFrameCount = stream.readUint32BE(); + // UINT32 - framerate (0x2000 in walk.anim???) + // UINT32 - starting frame (0 for walk.anim) + // UINT32 - number of loops (0 for walk.anim) + + if (animVersion != 0) + error("load3DOCelFile: Unsupported animation file version"); + if (animType != 1) + error("load3DOCelFile: Only single CCB animation files are supported"); + break; + + case MKTAG('C', 'C', 'B', ' '): + // CEL control block + assert(dataSize >= 72); + + if (ccbFound) + error("load3DOCelFile: multiple CCB chunks not supported"); + + ccbFound = true; + ccbVersion = stream.readUint32BE(); + ccbFlags = stream.readUint32BE(); + stream.skip(3 * 4); // skip over 3 pointer fields, which are used in memory only by 3DO hardware + stream.skip(8 * 4); // skip over 8 offset fields + ccbPPMP0 = stream.readUint16BE(); + ccbPPMP1 = stream.readUint16BE(); + ccbPRE0 = stream.readUint32BE(); + ccbPRE1 = stream.readUint32BE(); + ccbWidth = stream.readUint32BE(); + ccbHeight = stream.readUint32BE(); + + if (ccbVersion != 0) + error("load3DOCelFile: Unsupported CCB version"); + + if (ccbFlags & 0x200) // bit 9 + ccbFlags_compressed = true; + + // bit 5 of ccbFlags defines how RGB-black (0, 0, 0) will get treated + // = false -> RGB-black is treated as transparent + // = true -> RGB-black is treated as actual black + // atm we are always treating it as transparent + // it seems this bit is not set for any data of Sherlock Holmes + + // PRE0 first 3 bits define how many bits per encoded pixel are used + ccbPRE0_bitsPerPixel = imagefile3DO_cel_bitsPerPixelLookupTable[ccbPRE0 & 0x07]; + if (!ccbPRE0_bitsPerPixel) + error("load3DOCelFile: Invalid CCB PRE0 bits per pixel"); + + ccbPRE0_height = ((ccbPRE0 >> 6) & 0x03FF) + 1; + ccbPRE1_width = (ccbPRE1 & 0x03FF) + 1; + assert(ccbPRE0_height == ccbHeight); + assert(ccbPRE1_width == ccbWidth); + break; + + case MKTAG('P', 'L', 'U', 'T'): + // pixel lookup table + // optional, not required for at least 16-bit pixel data + assert(dataSize >= 6); + + if (!ccbFound) + error("load3DOCelFile: PLUT chunk found without CCB chunk"); + if (plutFound) + error("load3DOCelFile: multiple PLUT chunks currently not supported"); + + plutFound = true; + plutCount = stream.readUint32BE(); + // table follows, each entry is 16bit RGB555 + assert(dataSize >= 4 + (plutCount * 2)); // security check + assert(plutCount <= 256); // security check + + assert(plutCount <= 32); // PLUT should never contain more than 32 entries + + for (uint32 plutColorNr = 0; plutColorNr < plutCount; plutColorNr++) { + plutRGBlookupTable.pixelColor[plutColorNr] = stream.readUint16BE(); + } + + if (ccbPRE0_bitsPerPixel == 8) { + // In case we are getting 8-bits per pixel, we calculate the shades accordingly + // I'm not 100% sure if the calculation is correct. It's difficult to find information + // on this topic. + // The map uses this type of cel + assert(plutCount == 32); // And we expect 32 entries inside PLUT chunk + + uint16 plutColorRGB = 0; + for (uint32 plutColorNr = 0; plutColorNr < plutCount; plutColorNr++) { + plutColorRGB = plutRGBlookupTable.pixelColor[plutColorNr]; + + // Extract RGB values + byte plutColorRed = (plutColorRGB >> 10) & 0x1F; + byte plutColorGreen = (plutColorRGB >> 5) & 0x1F; + byte plutColorBlue = plutColorRGB & 0x1F; + + byte shadeMultiplier = 2; + for (uint32 plutShadeNr = 1; plutShadeNr < 8; plutShadeNr++) { + uint16 shadedColorRGB; + byte shadedColorRed = (plutColorRed * shadeMultiplier) >> 3; + byte shadedColorGreen = (plutColorGreen * shadeMultiplier) >> 3; + byte shadedColorBlue = (plutColorBlue * shadeMultiplier) >> 3; + + shadedColorRed = CLIP<byte>(shadedColorRed, 0, 0x1F); + shadedColorGreen = CLIP<byte>(shadedColorGreen, 0, 0x1F); + shadedColorBlue = CLIP<byte>(shadedColorBlue, 0, 0x1F); + shadedColorRGB = (shadedColorRed << 10) | (shadedColorGreen << 5) | shadedColorBlue; + + plutRGBlookupTable.pixelColor[plutColorNr + (plutShadeNr << 5)] = shadedColorRGB; + shadeMultiplier++; + } + } + } + break; + + case MKTAG('X', 'T', 'R', 'A'): + // Unknown contents, occurs right before PDAT + break; + + case MKTAG('P', 'D', 'A', 'T'): { + // pixel data for one frame + // may be compressed or uncompressed pixels + + if (ccbPRE0_bitsPerPixel != 16) { + // We require a pixel lookup table in case bits-per-pixel is lower than 16 + if (!plutFound) + error("load3DOCelFile: bits per pixel < 16, but no pixel lookup table was found"); + } else { + // But we don't like it in case bits-per-pixel is 16 and we find one + if (plutFound) + error("load3DOCelFile: bits per pixel == 16, but pixel lookup table was found as well"); + } + // read data into memory + chunkDataPtr = new byte[dataSize]; + + stream.read(chunkDataPtr, dataSize); + + // Set up frame + ImageFrame imageFrame; + + imageFrame._width = ccbWidth; + imageFrame._height = ccbHeight; + imageFrame._paletteBase = 0; + imageFrame._offset.x = 0; + imageFrame._offset.y = 0; + imageFrame._rleEncoded = ccbFlags_compressed; + imageFrame._size = 0; + + // Decompress/copy this frame + if (!plutFound) { + decompress3DOCelFrame(imageFrame, chunkDataPtr, dataSize, ccbPRE0_bitsPerPixel, NULL); + } else { + decompress3DOCelFrame(imageFrame, chunkDataPtr, dataSize, ccbPRE0_bitsPerPixel, &plutRGBlookupTable); + } + + delete[] chunkDataPtr; + + push_back(imageFrame); + break; + } + + case MKTAG('O', 'F', 'S', 'T'): // 3DOSplash.cel + // unknown contents + break; + + default: + error("Unsupported '%s' chunk in 3DO cel file", tag2str(chunkTag)); + } + + // Seek to end of chunk + stream.seek(chunkStartPos + chunkSize); + } + + // Warning below being used to silence unused variable warnings for now + warning("TODO: Remove %d %d %d", animFrameCount, ccbPPMP0, ccbPPMP1); +} + +// Reads 3DO .cel data (room file format) +void ImageFile3DO::load3DOCelRoomData(Common::SeekableReadStream &stream) { + uint32 streamLeft = stream.size() - stream.pos(); + uint16 roomDataHeader_size = 0; + byte roomDataHeader_offsetX = 0; + byte roomDataHeader_offsetY = 0; + + // CCB chunk (cel control block) + uint32 ccbFlags = 0; + bool ccbFlags_compressed = false; + uint16 ccbPPMP0 = 0; + uint16 ccbPPMP1 = 0; + uint32 ccbPRE0 = 0; + uint16 ccbPRE0_height = 0; + byte ccbPRE0_bitsPerPixel = 0; + uint32 ccbPRE1 = 0; + uint16 ccbPRE1_width = 0; + uint32 ccbWidth = 0; + uint32 ccbHeight = 0; + // cel data + uint32 celDataSize = 0; + + while (streamLeft > 0) { + // We expect at least 8 bytes basic header + if (streamLeft < 8) + error("load3DOCelRoomData: expected room data header, not enough bytes"); + + // 3DO sherlock holmes room data header + stream.skip(4); // Possibly UINT16 width, UINT16 height?!?! + roomDataHeader_size = stream.readUint16BE(); + roomDataHeader_offsetX = stream.readByte(); + roomDataHeader_offsetY = stream.readByte(); + streamLeft -= 8; + + // We expect the header size specified in the basic header to be at least a raw CCB + if (roomDataHeader_size < 68) + error("load3DOCelRoomData: header size is too small"); + // Check, that enough bytes for CCB are available + if (streamLeft < 68) + error("load3DOCelRoomData: expected raw cel control block, not enough bytes"); + + // 3DO raw cel control block + ccbFlags = stream.readUint32BE(); + stream.skip(3 * 4); // skip over 3 pointer fields, which are used in memory only by 3DO hardware + stream.skip(8 * 4); // skip over 8 offset fields + ccbPPMP0 = stream.readUint16BE(); + ccbPPMP1 = stream.readUint16BE(); + ccbPRE0 = stream.readUint32BE(); + ccbPRE1 = stream.readUint32BE(); + ccbWidth = stream.readUint32BE(); + ccbHeight = stream.readUint32BE(); + + if (ccbFlags & 0x200) // bit 9 + ccbFlags_compressed = true; + + // PRE0 first 3 bits define how many bits per encoded pixel are used + ccbPRE0_bitsPerPixel = imagefile3DO_cel_bitsPerPixelLookupTable[ccbPRE0 & 0x07]; + if (!ccbPRE0_bitsPerPixel) + error("load3DOCelRoomData: Invalid CCB PRE0 bits per pixel"); + + ccbPRE0_height = ((ccbPRE0 >> 6) & 0x03FF) + 1; + ccbPRE1_width = (ccbPRE1 & 0x03FF) + 1; + assert(ccbPRE0_height == ccbHeight); + assert(ccbPRE1_width == ccbWidth); + + if (ccbPRE0_bitsPerPixel != 16) { + // We currently support 16-bits per pixel in here + error("load3DOCelRoomData: bits per pixel < 16?!?!?"); + } + // Got the raw CCB + streamLeft -= 68; + + // cel data follows + // size field does not include the 8 byte header + celDataSize = roomDataHeader_size - 68; + + if (streamLeft < celDataSize) + error("load3DOCelRoomData: expected cel data, not enough bytes"); + + // read data into memory + byte *celDataPtr = new byte[celDataSize]; + + stream.read(celDataPtr, celDataSize); + streamLeft -= celDataSize; + + // Set up frame + { + ImageFrame imageFrame; + + imageFrame._width = ccbWidth; + imageFrame._height = ccbHeight; + imageFrame._paletteBase = 0; + imageFrame._offset.x = roomDataHeader_offsetX; + imageFrame._offset.y = roomDataHeader_offsetY; + imageFrame._rleEncoded = ccbFlags_compressed; + imageFrame._size = 0; + + // Decompress/copy this frame + decompress3DOCelFrame(imageFrame, celDataPtr, celDataSize, ccbPRE0_bitsPerPixel, NULL); + + delete[] celDataPtr; + + push_back(imageFrame); + } + } + + // Suppress compiler warning + warning("ccbPPMP0 = %d, ccbPPMP1 = %d", ccbPPMP0, ccbPPMP1); +} + +static uint16 imagefile3DO_cel_bitsMask[17] = { + 0, + 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, + 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF +}; + +// gets [bitCount] bits from dataPtr, going from MSB to LSB +inline uint16 ImageFile3DO::celGetBits(const byte *&dataPtr, byte bitCount, byte &dataBitsLeft) { + byte resultBitsLeft = bitCount; + uint16 result = 0; + byte currentByte = *dataPtr; + + // Get bits of current byte + while (resultBitsLeft) { + if (resultBitsLeft < dataBitsLeft) { + // we need less than we have left + result |= (currentByte >> (dataBitsLeft - resultBitsLeft)) & imagefile3DO_cel_bitsMask[resultBitsLeft]; + dataBitsLeft -= resultBitsLeft; + resultBitsLeft = 0; + + } else { + // we need as much as we have left or more + resultBitsLeft -= dataBitsLeft; + result |= (currentByte & imagefile3DO_cel_bitsMask[dataBitsLeft]) << resultBitsLeft; + + // Go to next byte + dataPtr++; + dataBitsLeft = 8; + if (resultBitsLeft) { + currentByte = *dataPtr; + } + } + } + return result; +} + +// decompress/copy 3DO cel data +void ImageFile3DO::decompress3DOCelFrame(ImageFrame &frame, const byte *dataPtr, uint32 dataSize, byte bitsPerPixel, ImageFile3DOPixelLookupTable *pixelLookupTable) { + frame._frame.create(frame._width, frame._height, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)); + uint16 *dest = (uint16 *)frame._frame.getPixels(); + Common::fill(dest, dest + frame._width * frame._height, 0); + + int frameHeightLeft = frame._height; + int frameWidthLeft = frame._width; + uint16 pixelCount = 0; + uint16 pixel = 0; + + const byte *srcLineStart = dataPtr; + const byte *srcLineData = dataPtr; + byte srcLineDataBitsLeft = 0; + uint16 lineDWordSize = 0; + uint16 lineByteSize = 0; + + if (bitsPerPixel == 16) { + // Must not use pixel lookup table on 16-bits-per-pixel data + assert(!pixelLookupTable); + } + + if (frame._rleEncoded) { + // compressed + byte compressionType = 0; + byte compressionPixels = 0; + + while (frameHeightLeft > 0) { + frameWidthLeft = frame._width; + + if (bitsPerPixel >= 8) { + lineDWordSize = READ_BE_UINT16(srcLineStart); + srcLineData = srcLineStart + 2; + } else { + lineDWordSize = *srcLineStart; + srcLineData = srcLineStart + 1; + } + srcLineDataBitsLeft = 8; + + lineDWordSize += 2; + lineByteSize = lineDWordSize * 4; // calculate compressed data size in bytes for current line + + // debug + //warning("offset %d: decoding line, size %d, bytesize %d", srcSeeker - src, dwordSize, lineByteSize); + + while (frameWidthLeft > 0) { + // get 2 bits -> compressionType + // get 6 bits -> pixel count (0 = 1 pixel) + compressionType = celGetBits(srcLineData, 2, srcLineDataBitsLeft); + // 6 bits == length (0 = 1 pixel) + compressionPixels = celGetBits(srcLineData, 6, srcLineDataBitsLeft) + 1; + + if (!compressionType) // end of line + break; + + switch(compressionType) { + case 1: // simple copy + for (pixelCount = 0; pixelCount < compressionPixels; pixelCount++) { + pixel = celGetBits(srcLineData, bitsPerPixel, srcLineDataBitsLeft); + if (pixelLookupTable) { + pixel = pixelLookupTable->pixelColor[pixel]; + } + *dest++ = convertPixel(pixel); + } + break; + case 2: // transparent + for (pixelCount = 0; pixelCount < compressionPixels; pixelCount++) { + *dest++ = 0; + } + break; + case 3: // duplicate pixels + pixel = celGetBits(srcLineData, bitsPerPixel, srcLineDataBitsLeft); + if (pixelLookupTable) { + pixel = pixelLookupTable->pixelColor[pixel]; + } + pixel = convertPixel(pixel); + for (pixelCount = 0; pixelCount < compressionPixels; pixelCount++) { + *dest++ = pixel; + } + break; + default: + break; + } + frameWidthLeft -= compressionPixels; + } + + assert(frameWidthLeft >= 0); + + if (frameWidthLeft > 0) { + // still pixels left? skip them + dest += frameWidthLeft; + } + + frameHeightLeft--; + + // Seek to next line start + srcLineStart += lineByteSize; + } + } else { + // uncompressed + srcLineDataBitsLeft = 8; + lineDWordSize = ((frame._width * bitsPerPixel) + 31) >> 5; + lineByteSize = lineDWordSize * 4; + uint32 totalExpectedSize = lineByteSize * frame._height; + + assert(totalExpectedSize <= dataSize); // security check + + while (frameHeightLeft > 0) { + srcLineData = srcLineStart; + frameWidthLeft = frame._width; + + while (frameWidthLeft > 0) { + pixel = celGetBits(srcLineData, bitsPerPixel, srcLineDataBitsLeft); + if (pixelLookupTable) { + pixel = pixelLookupTable->pixelColor[pixel]; + } + *dest++ = convertPixel(pixel); + + frameWidthLeft--; + } + frameHeightLeft--; + + // Seek to next line start + srcLineStart += lineByteSize; + } + } +} + +// Reads Sherlock Holmes 3DO font file +void ImageFile3DO::loadFont(Common::SeekableReadStream &stream) { + uint32 streamSize = stream.size(); + uint32 header_offsetWidthTable = 0; + uint32 header_offsetBitsTable = 0; + uint32 header_fontHeight = 0; + uint32 header_bytesPerLine = 0; + uint32 header_maxChar = 0; + uint32 header_charCount = 0; + + byte *widthTablePtr = NULL; + uint32 bitsTableSize = 0; + byte *bitsTablePtr = NULL; + + stream.skip(2); // Unknown bytes + stream.skip(2); // Unknown bytes (0x000E) + header_offsetWidthTable = stream.readUint32BE(); + header_offsetBitsTable = stream.readUint32BE(); + stream.skip(4); // Unknown bytes (0x00000004) + header_fontHeight = stream.readUint32BE(); + header_bytesPerLine = stream.readUint32BE(); + header_maxChar = stream.readUint32BE(); + + assert(header_maxChar <= 255); + header_charCount = header_maxChar + 1; + + // Allocate memory for width table + widthTablePtr = new byte[header_charCount]; + + stream.seek(header_offsetWidthTable); + stream.read(widthTablePtr, header_charCount); + + // Allocate memory for the bits + assert(header_offsetBitsTable < streamSize); // Security check + bitsTableSize = streamSize - header_offsetBitsTable; + bitsTablePtr = new byte[bitsTableSize]; + stream.read(bitsTablePtr, bitsTableSize); + + // Now extract all characters + uint16 curChar = 0; + const byte *curBitsLinePtr = bitsTablePtr; + const byte *curBitsPtr = NULL; + byte curBitsLeft = 0; + uint32 curCharHeightLeft = 0; + uint32 curCharWidthLeft = 0; + byte curBits = 0; + byte curBitsReversed = 0; + byte curPosX = 0; + + assert(bitsTableSize >= (header_maxChar * header_fontHeight * header_bytesPerLine)); // Security + + // first frame needs to be "!" (33 decimal) + // our font code is subtracting 33 from the actual character code + curBitsLinePtr += (33 * (header_fontHeight * header_bytesPerLine)); + + for (curChar = 33; curChar < header_charCount; curChar++) { + // create frame + { + ImageFrame imageFrame; + + imageFrame._width = widthTablePtr[curChar]; + imageFrame._height = header_fontHeight; + imageFrame._paletteBase = 0; + imageFrame._offset.x = 0; + imageFrame._offset.y = 0; + imageFrame._rleEncoded = false; + imageFrame._size = 0; + + // Extract pixels + imageFrame._frame.create(imageFrame._width, imageFrame._height, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)); + uint16 *dest = (uint16 *)imageFrame._frame.getPixels(); + Common::fill(dest, dest + imageFrame._width * imageFrame._height, 0); + + curCharHeightLeft = header_fontHeight; + while (curCharHeightLeft) { + curCharWidthLeft = widthTablePtr[curChar]; + curBitsPtr = curBitsLinePtr; + curBitsLeft = 8; + curPosX = 0; + + while (curCharWidthLeft) { + if (!(curPosX & 1)) { + curBits = *curBitsPtr >> 4; + } else { + curBits = *curBitsPtr & 0x0F; + curBitsPtr++; + } + // doing this properly is complicated + // the 3DO has built-in anti-aliasing + // this here at least results in somewhat readable text + // TODO: make it better + if (curBits) { + curBitsReversed = (curBits >> 3) | ((curBits & 0x04) >> 1) | ((curBits & 0x02) << 1) | ((curBits & 0x01) << 3); + curBits = 20 - curBits; + } + + byte curIntensity = curBits; + *dest = (curIntensity << 11) | (curIntensity << 6) | curIntensity; + dest++; + + curCharWidthLeft--; + curPosX++; + } + + curCharHeightLeft--; + curBitsLinePtr += header_bytesPerLine; + } + + push_back(imageFrame); + } + } + + // Warning below being used to silence unused variable warnings for now + warning("TODO: Remove %d %d", curBitsLeft, curBitsReversed); + + delete[] bitsTablePtr; + delete[] widthTablePtr; +} + +/*----------------------------------------------------------------*/ + +StreamingImageFile::StreamingImageFile() { + _frameNumber = 0; + _stream = nullptr; + _flags = 0; + _scaleVal = 0; + _zPlacement = 0; + _compressed = false; + _active = false; +} + +StreamingImageFile::~StreamingImageFile() { + close(); +} + +void StreamingImageFile::load(Common::SeekableReadStream *stream, bool compressed) { + _stream = stream; + _compressed = compressed; + _frameNumber = -1; + _active = true; +} + +void StreamingImageFile::close() { + delete _stream; + _stream = nullptr; + _frameNumber = -1; + _active = false; +} + +bool StreamingImageFile::getNextFrame() { + // Don't proceed if we're already at the end of the stream + if (_stream->pos() >= _stream->size()) { + _active = false; + return false; + } + + // Increment frame number + ++_frameNumber; + + // If necessary, decompress the next frame + Common::SeekableReadStream *frameStream = _stream; + if (_compressed) { + uint32 inSize = _stream->readUint32LE(); + Resources::decompressLZ(*_stream, _buffer, STREAMING_BUFFER_SIZE, inSize); + frameStream = new Common::MemoryReadStream(_buffer, 11, DisposeAfterUse::NO); + } + + // Load the data for the frame + _imageFrame._width = frameStream->readUint16LE() + 1; + _imageFrame._height = frameStream->readUint16LE() + 1; + _imageFrame._paletteBase = frameStream->readByte(); + _imageFrame._rleEncoded = frameStream->readByte() == 1; + _imageFrame._offset.x = frameStream->readByte(); + _imageFrame._offset.y = frameStream->readByte(); + _imageFrame._size = frameStream->readUint16LE() - 11; + _imageFrame._rleMarker = frameStream->readByte(); + + // Decode the frame + if (_compressed) { + delete frameStream; + _imageFrame.decompressFrame(_buffer + 11, true); + } else { + byte *data = new byte[_imageFrame._size]; + _stream->read(data, _imageFrame._size); + _imageFrame.decompressFrame(_buffer + 11, true); + delete[] data; + } + + return true; +} + +} // End of namespace Sherlock diff --git a/engines/sherlock/image_file.h b/engines/sherlock/image_file.h new file mode 100644 index 0000000000..da260ab30b --- /dev/null +++ b/engines/sherlock/image_file.h @@ -0,0 +1,208 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_IMAGE_FILE_H +#define SHERLOCK_IMAGE_FILE_H + +#include "common/array.h" +#include "common/file.h" +#include "common/hashmap.h" +#include "common/hash-str.h" +#include "common/rect.h" +#include "common/str.h" +#include "common/stream.h" +#include "graphics/surface.h" + +namespace Sherlock { + +class SherlockEngine; + +struct ImageFrame { + uint32 _size; + uint16 _width, _height; + int _paletteBase; + bool _rleEncoded; + Common::Point _offset; + byte _rleMarker; + Graphics::Surface _frame; + + /** + * Decompress a single frame for the sprite + */ + void decompressFrame(const byte *src, bool isRoseTattoo); + + /** + * Return the frame width adjusted by a specified scale amount + */ + int sDrawXSize(int scaleVal) const; + + /** + * Return the frame height adjusted by a specified scale amount + */ + int sDrawYSize(int scaleVal) const; + + /** + * Return the frame offset x adjusted by a specified scale amount + */ + int sDrawXOffset(int scaleVal) const; + + /** + * Return the frame offset y adjusted by a specified scale amount + */ + int sDrawYOffset(int scaleVal) const; +}; + +class ImageFile : public Common::Array<ImageFrame> { +private: + static SherlockEngine *_vm; + + /** + * Load the data of the sprite + */ + void load(Common::SeekableReadStream &stream, bool skipPalette, bool animImages); + + /** + * Gets the palette at the start of the sprite file + */ + void loadPalette(Common::SeekableReadStream &stream); +public: + byte _palette[256 * 3]; +public: + ImageFile(); + ImageFile(const Common::String &name, bool skipPal = false, bool animImages = false); + ImageFile(Common::SeekableReadStream &stream, bool skipPal = false); + ~ImageFile(); + static void setVm(SherlockEngine *vm); +}; + +enum ImageFile3DOType { + kImageFile3DOType_Animation = 0, + kImageFile3DOType_Cel = 1, + kImageFile3DOType_CelAnimation = 2, + kImageFile3DOType_RoomFormat = 3, + kImageFile3DOType_Font = 4 +}; + +struct ImageFile3DOPixelLookupTable { + uint16 pixelColor[256]; +}; + +class ImageFile3DO : public ImageFile { // Common::Array<ImageFrame> { +private: + static SherlockEngine *_vm; + + /** + * Load the data of the sprite + */ + void load(Common::SeekableReadStream &stream, bool isRoomData); + + /** + * convert pixel RGB values from RGB555 to RGB565 + */ + inline uint16 convertPixel(uint16 pixel3DO); + + /** + * Load 3DO cel file + */ + void load3DOCelFile(Common::SeekableReadStream &stream); + + /** + * Load 3DO cel data (room file format) + */ + void load3DOCelRoomData(Common::SeekableReadStream &stream); + + inline uint16 celGetBits(const byte *&dataPtr, byte bitCount, byte &dataBitsLeft); + + /** + * Decompress a single frame of a 3DO cel file + */ + void decompress3DOCelFrame(ImageFrame &frame, const byte *dataPtr, uint32 dataSize, byte bitsPerPixel, ImageFile3DOPixelLookupTable *pixelLookupTable); + + /** + * Load animation graphics file + */ + void loadAnimationFile(Common::SeekableReadStream &stream); + + /** + * Load Sherlock Holmes 3DO font file + */ + void loadFont(Common::SeekableReadStream &stream); + +public: + ImageFile3DO(const Common::String &name, ImageFile3DOType imageFile3DOType); + ImageFile3DO(Common::SeekableReadStream &stream, bool isRoomData = false); + ~ImageFile3DO(); + static void setVm(SherlockEngine *vm); +}; + +#define STREAMING_BUFFER_SIZE 65536 + +class StreamingImageFile { +private: + int _frameNumber; + Common::SeekableReadStream *_stream; + byte _buffer[STREAMING_BUFFER_SIZE]; + bool _compressed; + bool _active; +public: + ImageFrame _imageFrame; + + Common::Point _position; // Animation position + Common::Rect _oldBounds; // Bounds of previous frame + Common::Rect _removeBounds; // Remove area for just drawn frame + + int _flags; // Flags + int _scaleVal; // Specifies the scale amount + int _zPlacement; // Used by doBgAnim for determining Z order +public: + StreamingImageFile(); + ~StreamingImageFile(); + + /** + * Initialize reading of the specified stream + */ + void load(Common::SeekableReadStream *stream, bool compressed); + + /** + * Close the streamining image file + */ + void close(); + + /** + * Get the next frame of the file + */ + bool getNextFrame(); + + /** + * Returns whether there are any remaining frames or not + */ + bool active() const { return _active; } + + /** + * Return the current frame number + */ + int frameNumber() const { return _frameNumber; } +}; + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/inventory.cpp b/engines/sherlock/inventory.cpp index a8ecb64102..d0982542f2 100644 --- a/engines/sherlock/inventory.cpp +++ b/engines/sherlock/inventory.cpp @@ -22,34 +22,48 @@ #include "sherlock/inventory.h" #include "sherlock/sherlock.h" +#include "sherlock/scalpel/scalpel_inventory.h" #include "sherlock/scalpel/scalpel_user_interface.h" +#include "sherlock/tattoo/tattoo_inventory.h" namespace Sherlock { InventoryItem::InventoryItem(int requiredFlag, const Common::String &name, const Common::String &description, const Common::String &examine) : - _requiredFlag(requiredFlag), _name(name), _description(description), + _requiredFlag(requiredFlag), _requiredFlag1(0), _name(name), _description(description), _examine(examine), _lookFlag(0) { } -void InventoryItem::synchronize(Common::Serializer &s) { +InventoryItem::InventoryItem(int requiredFlag, const Common::String &name, + const Common::String &description, const Common::String &examine, const Common::String &verbName) : + _requiredFlag(requiredFlag), _requiredFlag1(0), _name(name), _description(description), + _examine(examine), _lookFlag(0) { + _verb._verb = verbName; +} + +void InventoryItem::synchronize(Serializer &s) { s.syncAsSint16LE(_requiredFlag); s.syncAsSint16LE(_lookFlag); s.syncString(_name); s.syncString(_description); s.syncString(_examine); + _verb.synchronize(s); } /*----------------------------------------------------------------*/ +Inventory *Inventory::init(SherlockEngine *vm) { + if (vm->getGameID() == GType_SerratedScalpel) + return new Scalpel::ScalpelInventory(vm); + else + return new Tattoo::TattooInventory(vm); +} + Inventory::Inventory(SherlockEngine *vm) : Common::Array<InventoryItem>(), _vm(vm) { - Common::fill(&_invShapes[0], &_invShapes[MAX_VISIBLE_INVENTORY], (ImageFile *)nullptr); _invGraphicsLoaded = false; _invIndex = 0; _holdings = 0; _invMode = INVMODE_EXIT; - for (int i = 0; i < 6; ++i) - _invShapes[i] = nullptr; } Inventory::~Inventory() { @@ -64,50 +78,31 @@ void Inventory::freeInv() { } void Inventory::freeGraphics() { - for (uint idx = 0; idx < MAX_VISIBLE_INVENTORY; ++idx) + int count = _invShapes.size(); + for (int idx = 0; idx < count; ++idx) delete _invShapes[idx]; + _invShapes.clear(); + _invShapes.resize(count); - Common::fill(&_invShapes[0], &_invShapes[MAX_VISIBLE_INVENTORY], (ImageFile *)nullptr); _invGraphicsLoaded = false; } -void Inventory::loadInv() { - // Exit if the inventory names are already loaded - if (_names.size() > 0) - return; - - // Load the inventory names - Common::SeekableReadStream *stream = _vm->_res->load("invent.txt"); - - int streamSize = stream->size(); - while (stream->pos() < streamSize) { - Common::String name; - char c; - while ((c = stream->readByte()) != 0) - name += c; - - _names.push_back(name); - } - - delete stream; - - loadGraphics(); -} - void Inventory::loadGraphics() { if (_invGraphicsLoaded) return; - // Default all inventory slots to empty - Common::fill(&_invShapes[0], &_invShapes[MAX_VISIBLE_INVENTORY], (ImageFile *)nullptr); - - for (int idx = _invIndex; (idx < _holdings) && (idx - _invIndex) < MAX_VISIBLE_INVENTORY; ++idx) { + for (int idx = _invIndex; (idx < _holdings) && (idx - _invIndex) < (int)_invShapes.size(); ++idx) { // Get the name of the item to be displayed, figure out its accompanying // .VGS file with its picture, and then load it int invNum = findInv((*this)[idx]._name); - Common::String fName = Common::String::format("item%02d.vgs", invNum + 1); + Common::String filename = Common::String::format("item%02d.vgs", invNum + 1); - _invShapes[idx - _invIndex] = new ImageFile(fName); + if (!IS_3DO) { + // PC + _invShapes[idx - _invIndex] = new ImageFile(filename); + } else { + _invShapes[idx - _invIndex] = new ImageFile3DO(filename, kImageFile3DOType_RoomFormat); + } } _invGraphicsLoaded = true; @@ -123,226 +118,6 @@ int Inventory::findInv(const Common::String &name) { error("Couldn't find inventory item - %s", name.c_str()); } -void Inventory::putInv(InvSlamMode slamIt) { - Screen &screen = *_vm->_screen; - UserInterface &ui = *_vm->_ui; - - // If an inventory item has disappeared (due to using it or giving it), - // a blank space slot may have appeared. If so, adjust the inventory - if (_invIndex > 0 && _invIndex > (_holdings - 6)) { - --_invIndex; - freeGraphics(); - loadGraphics(); - } - - if (slamIt != SLAM_SECONDARY_BUFFER) { - screen.makePanel(Common::Rect(6, 163, 54, 197)); - screen.makePanel(Common::Rect(58, 163, 106, 197)); - screen.makePanel(Common::Rect(110, 163, 158, 197)); - screen.makePanel(Common::Rect(162, 163, 210, 197)); - screen.makePanel(Common::Rect(214, 163, 262, 197)); - screen.makePanel(Common::Rect(266, 163, 314, 197)); - } - - // Iterate through displaying up to 6 objects at a time - for (int idx = _invIndex; idx < _holdings && (idx - _invIndex) < MAX_VISIBLE_INVENTORY; ++idx) { - int itemNum = idx - _invIndex; - Surface &bb = slamIt == SLAM_SECONDARY_BUFFER ? screen._backBuffer2 : screen._backBuffer1; - Common::Rect r(8 + itemNum * 52, 165, 51 + itemNum * 52, 194); - - // Draw the background - if (idx == ui._selector) { - bb.fillRect(r, 235); - } else if (slamIt == SLAM_SECONDARY_BUFFER) { - bb.fillRect(r, BUTTON_MIDDLE); - } - - // Draw the item image - ImageFrame &frame = (*_invShapes[itemNum])[0]; - bb.transBlitFrom(frame, Common::Point(6 + itemNum * 52 + ((47 - frame._width) / 2), - 163 + ((33 - frame._height) / 2))); - } - - if (slamIt == SLAM_DISPLAY) - screen.slamArea(6, 163, 308, 34); - - if (slamIt != SLAM_SECONDARY_BUFFER) - ui.clearInfo(); - - if (slamIt == 0) { - invCommands(0); - } else if (slamIt == SLAM_SECONDARY_BUFFER) { - screen._backBuffer = &screen._backBuffer2; - invCommands(0); - screen._backBuffer = &screen._backBuffer1; - } -} - -void Inventory::drawInventory(InvNewMode mode) { - Screen &screen = *_vm->_screen; - UserInterface &ui = *_vm->_ui; - InvNewMode tempMode = mode; - - loadInv(); - - if (mode == INVENTORY_DONT_DISPLAY) { - screen._backBuffer = &screen._backBuffer2; - } - - // Draw the window background - Surface &bb = *screen._backBuffer; - bb.fillRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y1 + 10), BORDER_COLOR); - bb.fillRect(Common::Rect(0, CONTROLS_Y1 + 10, 2, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); - bb.fillRect(Common::Rect(SHERLOCK_SCREEN_WIDTH - 2, CONTROLS_Y1 + 10, - SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); - bb.fillRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - 2, SHERLOCK_SCREEN_WIDTH, - SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); - bb.fillRect(Common::Rect(2, CONTROLS_Y1 + 10, SHERLOCK_SCREEN_WIDTH - 2, SHERLOCK_SCREEN_HEIGHT - 2), - INV_BACKGROUND); - - // Draw the buttons - screen.makeButton(Common::Rect(Scalpel::INVENTORY_POINTS[0][0], CONTROLS_Y1, Scalpel::INVENTORY_POINTS[0][1], - CONTROLS_Y1 + 10), Scalpel::INVENTORY_POINTS[0][2] - screen.stringWidth("Exit") / 2, "Exit"); - screen.makeButton(Common::Rect(Scalpel::INVENTORY_POINTS[1][0], CONTROLS_Y1, Scalpel::INVENTORY_POINTS[1][1], - CONTROLS_Y1 + 10), Scalpel::INVENTORY_POINTS[1][2] - screen.stringWidth("Look") / 2, "Look"); - screen.makeButton(Common::Rect(Scalpel::INVENTORY_POINTS[2][0], CONTROLS_Y1, Scalpel::INVENTORY_POINTS[2][1], - CONTROLS_Y1 + 10), Scalpel::INVENTORY_POINTS[2][2] - screen.stringWidth("Use") / 2, "Use"); - screen.makeButton(Common::Rect(Scalpel::INVENTORY_POINTS[3][0], CONTROLS_Y1, Scalpel::INVENTORY_POINTS[3][1], - CONTROLS_Y1 + 10), Scalpel::INVENTORY_POINTS[3][2] - screen.stringWidth("Give") / 2, "Give"); - screen.makeButton(Common::Rect(Scalpel::INVENTORY_POINTS[4][0], CONTROLS_Y1, Scalpel::INVENTORY_POINTS[4][1], - CONTROLS_Y1 + 10), Scalpel::INVENTORY_POINTS[4][2], "^^"); - screen.makeButton(Common::Rect(Scalpel::INVENTORY_POINTS[5][0], CONTROLS_Y1, Scalpel::INVENTORY_POINTS[5][1], - CONTROLS_Y1 + 10), Scalpel::INVENTORY_POINTS[5][2], "^"); - screen.makeButton(Common::Rect(Scalpel::INVENTORY_POINTS[6][0], CONTROLS_Y1, Scalpel::INVENTORY_POINTS[6][1], - CONTROLS_Y1 + 10), Scalpel::INVENTORY_POINTS[6][2], "_"); - screen.makeButton(Common::Rect(Scalpel::INVENTORY_POINTS[7][0], CONTROLS_Y1, Scalpel::INVENTORY_POINTS[7][1], - CONTROLS_Y1 + 10), Scalpel::INVENTORY_POINTS[7][2], "__"); - - if (tempMode == INVENTORY_DONT_DISPLAY) - mode = LOOK_INVENTORY_MODE; - _invMode = (InvMode)mode; - - if (mode != PLAIN_INVENTORY) { - ui._oldKey = Scalpel::INVENTORY_COMMANDS[(int)mode]; - } else { - ui._oldKey = -1; - } - - invCommands(0); - putInv(SLAM_DONT_DISPLAY); - - if (tempMode != INVENTORY_DONT_DISPLAY) { - if (!ui._slideWindows) { - screen.slamRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); - } else { - ui.summonWindow(false, CONTROLS_Y1); - } - - ui._windowOpen = true; - } else { - // Reset the screen back buffer to the first buffer now that drawing is done - screen._backBuffer = &screen._backBuffer1; - } - - assert(IS_SERRATED_SCALPEL); - ((Scalpel::ScalpelUserInterface *)_vm->_ui)->_oldUse = -1; -} - -void Inventory::invCommands(bool slamIt) { - Screen &screen = *_vm->_screen; - UserInterface &ui = *_vm->_ui; - - if (slamIt) { - screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[0][2], CONTROLS_Y1), - _invMode == INVMODE_EXIT ? COMMAND_HIGHLIGHTED :COMMAND_FOREGROUND, - true, "Exit"); - screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[1][2], CONTROLS_Y1), - _invMode == INVMODE_LOOK ? COMMAND_HIGHLIGHTED :COMMAND_FOREGROUND, - true, "Look"); - screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[2][2], CONTROLS_Y1), - _invMode == INVMODE_USE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND, - true, "Use"); - screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[3][2], CONTROLS_Y1), - _invMode == INVMODE_GIVE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND, - true, "Give"); - screen.print(Common::Point(Scalpel::INVENTORY_POINTS[4][2], CONTROLS_Y1 + 1), - _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND, - "^^"); - screen.print(Common::Point(Scalpel::INVENTORY_POINTS[5][2], CONTROLS_Y1 + 1), - _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND, - "^"); - screen.print(Common::Point(Scalpel::INVENTORY_POINTS[6][2], CONTROLS_Y1 + 1), - (_holdings - _invIndex <= 6) ? COMMAND_NULL : COMMAND_FOREGROUND, - "_"); - screen.print(Common::Point(Scalpel::INVENTORY_POINTS[7][2], CONTROLS_Y1 + 1), - (_holdings - _invIndex <= 6) ? COMMAND_NULL : COMMAND_FOREGROUND, - "__"); - if (_invMode != INVMODE_LOOK) - ui.clearInfo(); - } else { - screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[0][2], CONTROLS_Y1), - _invMode == INVMODE_EXIT ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND, - false, "Exit"); - screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[1][2], CONTROLS_Y1), - _invMode == INVMODE_LOOK ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND, - false, "Look"); - screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[2][2], CONTROLS_Y1), - _invMode == INVMODE_USE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND, - false, "Use"); - screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[3][2], CONTROLS_Y1), - _invMode == INVMODE_GIVE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND, - false, "Give"); - screen.gPrint(Common::Point(Scalpel::INVENTORY_POINTS[4][2], CONTROLS_Y1), - _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND, - "^^"); - screen.gPrint(Common::Point(Scalpel::INVENTORY_POINTS[5][2], CONTROLS_Y1), - _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND, - "^"); - screen.gPrint(Common::Point(Scalpel::INVENTORY_POINTS[6][2], CONTROLS_Y1), - (_holdings - _invIndex < 7) ? COMMAND_NULL : COMMAND_FOREGROUND, - "_"); - screen.gPrint(Common::Point(Scalpel::INVENTORY_POINTS[7][2], CONTROLS_Y1), - (_holdings - _invIndex < 7) ? COMMAND_NULL : COMMAND_FOREGROUND, - "__"); - } -} - -void Inventory::highlight(int index, byte color) { - Screen &screen = *_vm->_screen; - Surface &bb = *screen._backBuffer; - int slot = index - _invIndex; - ImageFrame &frame = (*_invShapes[slot])[0]; - - bb.fillRect(Common::Rect(8 + slot * 52, 165, (slot + 1) * 52, 194), color); - bb.transBlitFrom(frame, Common::Point(6 + slot * 52 + ((47 - frame._width) / 2), - 163 + ((33 - frame._height) / 2))); - screen.slamArea(8 + slot * 52, 165, 44, 30); -} - -void Inventory::refreshInv() { - if (IS_ROSE_TATTOO) - return; - - Screen &screen = *_vm->_screen; - Talk &talk = *_vm->_talk; - Scalpel::ScalpelUserInterface &ui = *(Scalpel::ScalpelUserInterface *)_vm->_ui; - - ui._invLookFlag = true; - freeInv(); - - ui._infoFlag = true; - ui.clearInfo(); - - screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(0, CONTROLS_Y), - Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); - ui.examine(); - - if (!talk._talkToAbort) { - screen._backBuffer2.blitFrom((*ui._controlPanel)[0], Common::Point(0, CONTROLS_Y)); - loadInv(); - } -} - int Inventory::putNameInInventory(const Common::String &name) { Scene &scene = *_vm->_scene; int matches = 0; @@ -425,7 +200,7 @@ void Inventory::copyToInventory(Object &obj) { invItem._description = obj._description; invItem._examine = obj._examine; invItem._lookFlag = obj._lookFlag; - invItem._requiredFlag = obj._requiredFlag; + invItem._requiredFlag = obj._requiredFlag[0]; insert_at(_holdings, invItem); ++_holdings; @@ -450,7 +225,7 @@ int Inventory::deleteItemFromInventory(const Common::String &name) { return 1; } -void Inventory::synchronize(Common::Serializer &s) { +void Inventory::synchronize(Serializer &s) { s.syncAsSint16LE(_holdings); uint count = size(); diff --git a/engines/sherlock/inventory.h b/engines/sherlock/inventory.h index 02f570f5da..057e923e5d 100644 --- a/engines/sherlock/inventory.h +++ b/engines/sherlock/inventory.h @@ -25,15 +25,13 @@ #include "common/scummsys.h" #include "common/array.h" -#include "common/serializer.h" #include "common/str-array.h" #include "sherlock/objects.h" #include "sherlock/resources.h" +#include "sherlock/saveload.h" namespace Sherlock { -#define MAX_VISIBLE_INVENTORY 6 - enum InvMode { INVMODE_EXIT = 0, INVMODE_LOOK = 1, @@ -62,18 +60,24 @@ struct InventoryItem { Common::String _examine; int _lookFlag; - InventoryItem() : _requiredFlag(0), _lookFlag(0) {} + // Rose Tattoo fields + int _requiredFlag1; + UseType _verb; + + InventoryItem() : _requiredFlag(0), _lookFlag(0), _requiredFlag1(0) {} InventoryItem(int requiredFlag, const Common::String &name, const Common::String &description, const Common::String &examine); + InventoryItem(int requiredFlag, const Common::String &name, + const Common::String &description, const Common::String &examine, const Common::String &verbName); /** * Synchronize the data for an inventory item */ - void synchronize(Common::Serializer &s); + void synchronize(Serializer &s); }; class Inventory : public Common::Array<InventoryItem> { -private: +protected: SherlockEngine *_vm; Common::StringArray _names; @@ -82,7 +86,7 @@ private: */ void copyToInventory(Object &obj); public: - ImageFile *_invShapes[MAX_VISIBLE_INVENTORY]; + Common::Array<ImageFile *> _invShapes; bool _invGraphicsLoaded; InvMode _invMode; int _invIndex; @@ -93,8 +97,9 @@ public: */ void freeGraphics(); public: + static Inventory *init(SherlockEngine *vm); Inventory(SherlockEngine *vm); - ~Inventory(); + virtual ~Inventory(); /** * Free inventory data @@ -102,12 +107,6 @@ public: void freeInv(); /** - * Load the list of names the inventory items correspond to, if not already loaded, - * and then calls loadGraphics to load the associated graphics - */ - void loadInv(); - - /** * Load the list of names of graphics for the inventory */ void loadGraphics(); @@ -119,32 +118,6 @@ public: int findInv(const Common::String &name); /** - * Display the character's inventory. The slamIt parameter specifies: - */ - void putInv(InvSlamMode slamIt); - - /** - * Put the game into inventory mode and open the interface window. - */ - void drawInventory(InvNewMode flag); - - /** - * Prints the line of inventory commands at the top of an inventory window with - * the correct highlighting - */ - void invCommands(bool slamIt); - - /** - * Set the highlighting color of a given inventory item - */ - void highlight(int index, byte color); - - /** - * Support method for refreshing the display of the inventory - */ - void refreshInv(); - - /** * Adds a shape from the scene to the player's inventory */ int putNameInInventory(const Common::String &name); @@ -163,7 +136,13 @@ public: /** * Synchronize the data for a savegame */ - void synchronize(Common::Serializer &s); + void synchronize(Serializer &s); + + /** + * Load the list of names the inventory items correspond to, if not already loaded, + * and then calls loadGraphics to load the associated graphics + */ + virtual void loadInv() = 0; }; } // End of namespace Sherlock diff --git a/engines/sherlock/journal.cpp b/engines/sherlock/journal.cpp index b4b05da991..334cc05abf 100644 --- a/engines/sherlock/journal.cpp +++ b/engines/sherlock/journal.cpp @@ -21,113 +21,280 @@ */ #include "sherlock/journal.h" -#include "sherlock/sherlock.h" +#include "sherlock/scalpel/scalpel.h" +#include "sherlock/scalpel/scalpel_fixed_text.h" +#include "sherlock/scalpel/scalpel_journal.h" +#include "sherlock/tattoo/tattoo.h" +#include "sherlock/tattoo/tattoo_fixed_text.h" +#include "sherlock/tattoo/tattoo_journal.h" namespace Sherlock { -#define JOURNAL_BUTTONS_Y 178 -#define LINES_PER_PAGE 11 -#define JOURNAL_SEARCH_LEFT 15 -#define JOURNAL_SEARCH_TOP 186 -#define JOURNAL_SEARCH_RIGHT 296 -#define JOURNAL_SEACRH_MAX_CHARS 50 - -// Positioning of buttons in the journal view -static const int JOURNAL_POINTS[9][3] = { - { 6, 68, 37 }, - { 69, 131, 100 }, - { 132, 192, 162 }, - { 193, 250, 221 }, - { 251, 313, 281 }, - { 6, 82, 44 }, - { 83, 159, 121 }, - { 160, 236, 198 }, - { 237, 313, 275 } -}; - -static const int SEARCH_POINTS[3][3] = { - { 51, 123, 86 }, - { 124, 196, 159 }, - { 197, 269, 232 } +static const int TATTOO_LINE_SPACING[17] = { + 21, 21, 20, 21, 20, 21, 20, 21, 20, 21, 20, 21, 20, 20, 20, 20, 21 }; /*----------------------------------------------------------------*/ +Journal *Journal::init(SherlockEngine *vm) { + if (vm->getGameID() == GType_SerratedScalpel) + return new Scalpel::ScalpelJournal(vm); + else + return new Tattoo::TattooJournal(vm); +} + Journal::Journal(SherlockEngine *vm) : _vm(vm) { - // Initialize fields - _maxPage = 0; - _index = 0; - _sub = 0; _up = _down = false; + _index = 0; _page = 1; - - if (_vm->_interactiveFl) { - // Load the journal directory and location names - loadJournalLocations(); - } + _maxPage = 0; + _sub = 0; } -void Journal::record(int converseNum, int statementNum, bool replyOnly) { - int saveIndex = _index; - int saveSub = _sub; +bool Journal::drawJournal(int direction, int howFar) { + Events &events = *_vm->_events; + FixedText &fixedText = *_vm->_fixedText; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + int topLineY = IS_SERRATED_SCALPEL ? 37 : 103 - screen.charHeight('A'); + int yp = topLineY; + int startPage = _page; + bool endJournal = false; + bool firstOccurance = true; + bool searchSuccessful = false; + bool endFlag = false; + int lineNum = 0; + int savedIndex; + int temp; + const char *matchP; + int width; - // Record the entry into the list - _journal.push_back(JournalEntry(converseNum, statementNum, replyOnly)); - _index = _journal.size() - 1; + talk._converseNum = -1; + _down = true; - // Load the text for the new entry to get the number of lines it will have - loadJournalFile(true); + do { + // Get the number of lines for the current journal entry + loadJournalFile(false); + if (_lines.empty()) { + // Entry has no text, so it must be a stealth eny. Move onto further journal entries + // until an entry with text is found + if (++_index == (int)_journal.size()) { + endJournal = true; + } else { + _sub = 0; + loadJournalFile(false); + } + } + } while (!endJournal && _lines.empty()); - // Restore old state - _index = saveIndex; - _sub = saveSub; + // Check if there no further pages with text until the end of the journal + if (endJournal) { + // If moving forward or backwards, clear the page before printing + if (direction) + drawFrame(); - // If new lines were added to the ournal, update the total number of lines - // the journal continues - if (!_lines.empty()) { - _maxPage += _lines.size(); - } else { - // No lines in entry, so remove the new entry from the journal - _journal.remove_at(_journal.size() - 1); + if (IS_SERRATED_SCALPEL) + screen.gPrint(Common::Point(235, 21), COL_PEN_COLOR, fixedText.getText(Scalpel::kFixedText_Journal_Page), _page); + else + screen.gPrint(Common::Point(530, 72), COL_PEN_COLOR, fixedText.getText(Tattoo::kFixedText_Page), _page); + + return false; } -} -void Journal::loadJournalLocations() { - Resources &res = *_vm->_res; + // If the journal page is being changed, set the wait cursor + if (direction) + events.setCursor(WAIT); + + switch (direction) { + case 1: + case 4: + // Move backwards howFar number of lines unless either the start of the journal is reached, + // or a searched for keyword is found + do { + // Move backwards through the journal file a line at a time + if (--_sub < 0) { + do { + if (--_index < 0) { + _index = 0; + _sub = 0; + endJournal = true; + } + else { + loadJournalFile(false); + _sub = _lines.size() - 1; + } + } while (!endJournal && _lines.empty()); + } - _directory.clear(); + // If it's search mode, check each line for the given keyword + if (direction >= 3 && !_lines.empty() && !endJournal && !searchSuccessful) { + Common::String line = _lines[_sub]; + line.toUppercase(); + if (strstr(line.c_str(), _find.c_str()) != nullptr) { + // Found a match. Reset howFar so that the start of page that the match + // was found on will be displayed + searchSuccessful = true; + howFar = ((lineNum / LINES_PER_PAGE) + 1) * LINES_PER_PAGE; + } + } - Common::SeekableReadStream *dir = res.load("talk.lib"); - dir->skip(4); // Skip header + ++lineNum; + } while (lineNum < howFar && !endJournal); - // Get the numer of entries - _directory.resize(dir->readUint16LE()); + if (!_index && !_sub) + _page = 1; + else + _page -= howFar / LINES_PER_PAGE; + break; - // Read in each entry - char buffer[17]; - for (uint idx = 0; idx < _directory.size(); ++idx) { - dir->read(buffer, 17); - buffer[16] = '\0'; + case 2: + case 3: + // Move howFar lines ahead unless the end of the journal is reached, + // or a searched for keyword is found + for (temp = 0; (temp < (howFar / LINES_PER_PAGE)) && !endJournal && !searchSuccessful; ++temp) { + // Handle animating mouse cursor + events.animateCursorIfNeeded(); - _directory[idx] = Common::String(buffer); - } + lineNum = 0; + savedIndex = _index; + int savedSub = _sub; - delete dir; + // Move a single page ahead + do { + // If in search mode, check for keyword + if (direction >= 3 && _page != startPage) { + Common::String line = _lines[_sub]; + line.toUppercase(); + if (strstr(line.c_str(), _find.c_str()) != nullptr) + searchSuccessful = true; + } - // Load in the locations stored in journal.txt - Common::SeekableReadStream *loc = res.load("journal.txt"); + // Move forwards a line at a time, unless search word was found + if (!searchSuccessful) { + if (++_sub == (int)_lines.size()) { + // Reached end of page + do { + if (++_index == (int)_journal.size()) { + _index = savedIndex; + _sub = savedSub; + loadJournalFile(false); + endJournal = true; + } else { + _sub = 0; + loadJournalFile(false); + } + } while (!endJournal && _lines.empty()); + } - _locations.clear(); - while (loc->pos() < loc->size()) { - Common::String line; - char c; - while ((c = loc->readByte()) != 0) - line += c; + ++lineNum; + } + } while ((lineNum < LINES_PER_PAGE) && !endJournal && !searchSuccessful); - _locations.push_back(line); + if (!endJournal && !searchSuccessful) + // Move to next page + ++_page; + + if (searchSuccessful) { + // Search found, so show top of the page it was found on + _index = savedIndex; + _sub = savedSub; + loadJournalFile(false); + } + } + break; + + default: + break; } - delete loc; + if (direction) { + events.setCursor(ARROW); + drawFrame(); + } + + if (IS_SERRATED_SCALPEL) + screen.gPrint(Common::Point(235, 21), COL_PEN_COLOR, fixedText.getText(Scalpel::kFixedText_Journal_Page), _page); + else + screen.gPrint(Common::Point(530, 72), COL_PEN_COLOR, fixedText.getText(Tattoo::kFixedText_Page), _page); + + temp = _sub; + savedIndex = _index; + lineNum = 0; + + do { + bool inc = true; + + // If there wasn't any line to print at the top of the page, we won't need to + // increment the y position + if (_lines[temp].empty() && yp == topLineY) + inc = false; + + // If there's a searched for keyword in the line, it will need to be highlighted + if (searchSuccessful && firstOccurance) { + // Check if line has the keyword + Common::String line = _lines[temp]; + line.toUppercase(); + if ((matchP = strstr(line.c_str(), _find.c_str())) != nullptr) { + matchP = _lines[temp].c_str() + (matchP - line.c_str()); + firstOccurance = false; + + // Print out the start of the line before the matching keyword + Common::String lineStart(_lines[temp].c_str(), matchP); + if (lineStart.hasPrefix("@")) { + width = screen.stringWidth(lineStart.c_str() + 1); + screen.gPrint(Common::Point(JOURNAL_LEFT_X, yp), COL_PEN_HIGHLIGHT, "%s", lineStart.c_str() + 1); + } else { + width = screen.stringWidth(lineStart.c_str()); + screen.gPrint(Common::Point(JOURNAL_LEFT_X, yp), COL_PEN_COLOR, "%s", lineStart.c_str()); + } + + // Print out the found keyword + Common::String lineMatch(matchP, matchP + _find.size()); + byte fgColor = IS_SERRATED_SCALPEL ? (byte)Scalpel::INV_FOREGROUND : (byte)Tattoo::INV_FOREGROUND; + screen.gPrint(Common::Point(JOURNAL_LEFT_X + width, yp), fgColor, "%s", lineMatch.c_str()); + width += screen.stringWidth(lineMatch.c_str()); + + // Print remainder of line + screen.gPrint(Common::Point(JOURNAL_LEFT_X + width, yp), COL_PEN_COLOR, "%s", matchP + _find.size()); + } else if (_lines[temp].hasPrefix("@")) { + screen.gPrint(Common::Point(JOURNAL_LEFT_X, yp), COL_PEN_HIGHLIGHT, "%s", _lines[temp].c_str() + 1); + } else { + screen.gPrint(Common::Point(JOURNAL_LEFT_X, yp), COL_PEN_COLOR, "%s", _lines[temp].c_str()); + } + } else { + if (_lines[temp].hasPrefix("@")) { + screen.gPrint(Common::Point(JOURNAL_LEFT_X, yp), COL_PEN_HIGHLIGHT, "%s", _lines[temp].c_str() + 1); + } else { + screen.gPrint(Common::Point(JOURNAL_LEFT_X, yp), COL_PEN_COLOR, "%s", _lines[temp].c_str()); + } + } + + if (++temp == (int)_lines.size()) { + // Move to next page + do { + if (_index < ((int)_journal.size() - 1) && lineNum < (LINES_PER_PAGE - 1)) { + ++_index; + loadJournalFile(false); + temp = 0; + } else { + if (_index == ((int)_journal.size() - 1)) + _down = false; + endFlag = true; + } + } while (!endFlag && _lines.empty()); + } + + if (inc) { + // Move to next line + yp += IS_SERRATED_SCALPEL ? 13 : TATTOO_LINE_SPACING[lineNum]; + ++lineNum; + } + } while (lineNum < LINES_PER_PAGE && !endFlag); + + _index = savedIndex; + _up = _index || _sub; + + return direction >= 3 && searchSuccessful; } void Journal::loadJournalFile(bool alreadyLoaded) { @@ -144,7 +311,7 @@ void Journal::loadJournalFile(bool alreadyLoaded) { Common::String locStr(dirFilename.c_str() + 4, dirFilename.c_str() + 6); int newLocation = atoi(locStr.c_str()); - // If not flagged as alrady loaded, load the conversation into script variables + // If not flagged as already loaded, load the conversation into script variables if (!alreadyLoaded) { // See if the file to be used is already loaded if (journalEntry._converseNum != talk._converseNum) { @@ -154,7 +321,7 @@ void Journal::loadJournalFile(bool alreadyLoaded) { // Find the person being referred to talk._talkTo = -1; for (int idx = 0; idx < (int)people._characters.size(); ++idx) { - Common::String portrait = people[idx]._portrait; + Common::String portrait = people._characters[idx]._portrait; Common::String numStr(portrait.c_str(), portrait.c_str() + 4); if (locStr == numStr) { @@ -186,7 +353,10 @@ void Journal::loadJournalFile(bool alreadyLoaded) { if (newLocation != oldLocation) { // Add in scene title - journalString = "@" + _locations[newLocation - 1] + ":"; + journalString = "@"; + if (IS_SERRATED_SCALPEL || newLocation - 1 < 100) + journalString += _locations[newLocation - 1]; + journalString += ":"; // See if title can fit into a single line, or requires splitting on 2 lines int width = screen.stringWidth(journalString.c_str() + 1); @@ -216,16 +386,12 @@ void Journal::loadJournalFile(bool alreadyLoaded) { else journalString += "said to "; - switch (talk._talkTo) { - case 1: + if (talk._talkTo == 1) { journalString += "me"; - break; - case 2: + } else if ((talk._talkTo == 2 && IS_SERRATED_SCALPEL) || (talk._talkTo == 18 && IS_ROSE_TATTOO)) { journalString += "the Inspector"; - break; - default: + } else { journalString += people._characters[talk._talkTo]._name; - break; } journalString += ", \""; @@ -239,14 +405,28 @@ void Journal::loadJournalFile(bool alreadyLoaded) { bool commentFlag = false; bool commentJustPrinted = false; const byte *replyP = (const byte *)statement._reply.c_str(); + const int inspectorId = (IS_SERRATED_SCALPEL) ? 2 : 18; + int beforeLastSpeakerChange = journalString.size(); + bool justChangedSpeaker = true; while (*replyP) { byte c = *replyP++; + if (IS_ROSE_TATTOO) { + // Ignore commented out data + if (c == '/' && *(replyP + 1) == '*') { + replyP++; // skip * + while (*replyP++ != '*') {} // empty loop on purpose + replyP++; // skip / + c = *replyP; + } + } + // Is it a control character? if (c < opcodes[0]) { // Nope. Set flag for allowing control codes to insert spaces ctrlSpace = true; + justChangedSpeaker = false; assert(c >= ' '); // Check for embedded comments @@ -282,7 +462,7 @@ void Journal::loadJournalFile(bool alreadyLoaded) { } else { if (talk._talkTo == 1) journalString += "I"; - else if (talk._talkTo == 2) + else if (talk._talkTo == inspectorId) journalString += "The Inspector"; else journalString += people._characters[talk._talkTo]._name; @@ -312,6 +492,15 @@ void Journal::loadJournalFile(bool alreadyLoaded) { commentJustPrinted = false; } } else if (c == opcodes[OP_SWITCH_SPEAKER]) { + if (IS_ROSE_TATTOO) { + // If the speaker has just changed, then no text has just been added + // from the last speaker, so remove the initial "Person said" text + if (justChangedSpeaker) + journalString = Common::String(journalString.c_str(), journalString.c_str() + beforeLastSpeakerChange); + + justChangedSpeaker = true; + } + if (!startOfReply) { if (!commentFlag && !commentJustPrinted) journalString += "\"\n"; @@ -324,12 +513,14 @@ void Journal::loadJournalFile(bool alreadyLoaded) { startOfReply = false; c = *replyP++ - 1; + if (IS_ROSE_TATTOO) + replyP++; if (c == 0) journalString += "Holmes"; else if (c == 1) journalString += "I"; - else if (c == 2) + else if (c == inspectorId) journalString += "the Inspector"; else journalString += people._characters[c]._name; @@ -345,42 +536,119 @@ void Journal::loadJournalFile(bool alreadyLoaded) { else journalString += " said, \""; } else { - // Control code, so move past it and any parameters - if (c == opcodes[OP_RUN_CANIMATION] || c == opcodes[OP_ASSIGN_PORTRAIT_LOCATION] || - c == opcodes[OP_PAUSE] || c == opcodes[OP_PAUSE_WITHOUT_CONTROL] || + if (IS_SERRATED_SCALPEL) { + // Control code, so move past it and any parameters + if (c == opcodes[OP_RUN_CANIMATION] || + c == opcodes[OP_ASSIGN_PORTRAIT_LOCATION] || + c == opcodes[OP_PAUSE] || + c == opcodes[OP_PAUSE_WITHOUT_CONTROL] || c == opcodes[OP_WALK_TO_CANIMATION]) { - // These commands have a single parameter - ++replyP; - - } else if (c == opcodes[OP_ADJUST_OBJ_SEQUENCE]) { - replyP += (replyP[0] & 127) + replyP[1] + 2; - - } else if (c == opcodes[OP_WALK_TO_COORDS] || c == opcodes[OP_MOVE_MOUSE]) { - replyP += 4; - - } else if (c == opcodes[OP_SET_FLAG] || c == opcodes[OP_IF_STATEMENT]) { - replyP += 2; - - } else if (c == opcodes[OP_SFX_COMMAND] || c == opcodes[OP_PLAY_PROLOGUE] || - c == opcodes[OP_CALL_TALK_FILE]) { - replyP += 8; - break; - - } else if (c == opcodes[OP_TOGGLE_OBJECT] || c == opcodes[OP_ADD_ITEM_TO_INVENTORY] || - c == opcodes[OP_SET_OBJECT] || c == opcodes[OP_DISPLAY_INFO_LINE] || - c == opcodes[OP_REMOVE_ITEM_FROM_INVENTORY]) { - replyP += (*replyP & 127) + 1; - - } else if (c == opcodes[OP_GOTO_SCENE]) { - replyP += 5; - - } else if (c == opcodes[OP_CARRIAGE_RETURN]) { - journalString += "\n"; + // These commands have a single parameter + ++replyP; + } else if (c == opcodes[OP_ADJUST_OBJ_SEQUENCE]) { + replyP += (replyP[0] & 127) + replyP[1] + 2; + } else if (c == opcodes[OP_WALK_TO_COORDS] || c == opcodes[OP_MOVE_MOUSE]) { + replyP += 4; + } else if (c == opcodes[OP_SET_FLAG] || c == opcodes[OP_IF_STATEMENT]) { + replyP += 2; + } else if (c == opcodes[OP_SFX_COMMAND] || c == opcodes[OP_PLAY_PROLOGUE] || + c == opcodes[OP_CALL_TALK_FILE]) { + replyP += 8; + break; + } else if ( + c == opcodes[OP_TOGGLE_OBJECT] || + c == opcodes[OP_ADD_ITEM_TO_INVENTORY] || + c == opcodes[OP_SET_OBJECT] || + c == opcodes[OP_DISPLAY_INFO_LINE] || + c == opcodes[OP_REMOVE_ITEM_FROM_INVENTORY]) { + replyP += (*replyP & 127) + 1; + } else if (c == opcodes[OP_GOTO_SCENE]) { + replyP += 5; + } else if (c == opcodes[OP_END_TEXT_WINDOW]) { + journalString += "\n"; + } + } else { + if (c == opcodes[OP_RUN_CANIMATION] || + c == opcodes[OP_PAUSE] || + c == opcodes[OP_MOUSE_OFF_ON] || + c == opcodes[OP_SET_WALK_CONTROL] || + c == opcodes[OP_PAUSE_WITHOUT_CONTROL] || + c == opcodes[OP_WALK_TO_CANIMATION] || + c == opcodes[OP_TURN_NPC_OFF] || + c == opcodes[OP_TURN_NPC_ON] || + c == opcodes[OP_RESTORE_PEOPLE_SEQUENCE]) + ++replyP; + else if ( + c == opcodes[OP_SET_TALK_SEQUENCE] || + c == opcodes[OP_SET_FLAG] || + c == opcodes[OP_WALK_NPC_TO_CANIM] || + c == opcodes[OP_WALK_HOLMES_AND_NPC_TO_CANIM] || + c == opcodes[OP_NPC_PATH_LABEL] || + c == opcodes[OP_PATH_GOTO_LABEL]) + replyP += 2; + else if ( + c == opcodes[OP_SET_NPC_PATH_PAUSE] || + c == opcodes[OP_NPC_PATH_PAUSE_TAKING_NOTES] || + c == opcodes[OP_NPC_PATH_PAUSE_LOOKING_HOLMES] || + c == opcodes[OP_NPC_VERB_CANIM]) + replyP += 3; + else if ( + c == opcodes[OP_SET_SCENE_ENTRY_FLAG] || + c == opcodes[OP_PATH_IF_FLAG_GOTO_LABEL]) + replyP += 4; + else if ( + c == opcodes[OP_WALK_TO_COORDS]) + replyP += 5; + else if ( + c == opcodes[OP_WALK_NPC_TO_COORDS] || + c == opcodes[OP_GOTO_SCENE] || + c == opcodes[OP_SET_NPC_PATH_DEST] || + c == opcodes[OP_SET_NPC_POSITION]) + replyP += 6; + else if ( + c == opcodes[OP_PLAY_SONG] || + c == opcodes[OP_NEXT_SONG]) + replyP += 8; + else if ( + c == opcodes[OP_CALL_TALK_FILE] || + c == opcodes[OP_SET_NPC_TALK_FILE] || + c == opcodes[OP_NPC_WALK_GRAPHICS]) + replyP += 9; + else if ( + c == opcodes[OP_NPC_VERB_SCRIPT]) + replyP += 10; + else if ( + c == opcodes[OP_WALK_HOLMES_AND_NPC_TO_COORDS]) + replyP += 11; + else if ( + c == opcodes[OP_NPC_VERB] || + c == opcodes[OP_NPC_VERB_TARGET]) + replyP += 14; + else if ( + c == opcodes[OP_ADJUST_OBJ_SEQUENCE]) + replyP += (replyP[0] & 127) + replyP[1] + 2; + else if ( + c == opcodes[OP_TOGGLE_OBJECT] || + c == opcodes[OP_ADD_ITEM_TO_INVENTORY] || + c == opcodes[OP_SET_OBJECT] || + c == opcodes[OP_REMOVE_ITEM_FROM_INVENTORY]) + replyP += (*replyP & 127) + 1; + else if ( + c == opcodes[OP_END_TEXT_WINDOW]) { + journalString += '\n'; + } else if ( + c == opcodes[OP_NPC_DESC_ON_OFF]) { + replyP++; + while (replyP[0] && replyP[0] != opcodes[OP_NPC_DESC_ON_OFF]) + replyP++; + } else if ( + c == opcodes[OP_SET_NPC_INFO_LINE]) + replyP += replyP[1] + 2; } // Put a space in the output for a control character, unless it's // immediately coming after another control character - if (ctrlSpace && c != opcodes[OP_ASSIGN_PORTRAIT_LOCATION] && c != opcodes[OP_CARRIAGE_RETURN] && + if (ctrlSpace && c != opcodes[OP_ASSIGN_PORTRAIT_LOCATION] && c != opcodes[OP_END_TEXT_WINDOW] && !commentJustPrinted) { journalString += " "; ctrlSpace = false; @@ -391,6 +659,9 @@ void Journal::loadJournalFile(bool alreadyLoaded) { if (!startOfReply && !commentJustPrinted) journalString += '"'; + if (IS_ROSE_TATTOO && justChangedSpeaker) + journalString = Common::String(journalString.c_str(), journalString.c_str() + beforeLastSpeakerChange); + // Finally finished building the journal text. Need to process the text to // word wrap it to fit on-screen. The resulting lines are stored in the // _lines array @@ -431,727 +702,38 @@ void Journal::loadJournalFile(bool alreadyLoaded) { } } -void Journal::drawJournalFrame() { - Resources &res = *_vm->_res; - Screen &screen = *_vm->_screen; - byte palette[PALETTE_SIZE]; - - // Load in the journal background - Common::SeekableReadStream *bg = res.load("journal.lbv"); - bg->read(screen._backBuffer1.getPixels(), SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCREEN_HEIGHT); - bg->read(palette, PALETTE_SIZE); - delete bg; - - // Translate the palette for display - for (int idx = 0; idx < PALETTE_SIZE; ++idx) - palette[idx] = VGA_COLOR_TRANS(palette[idx]); - - // Set the palette and print the title - screen.setPalette(palette); - screen.gPrint(Common::Point(111, 18), BUTTON_BOTTOM, "Watson's Journal"); - screen.gPrint(Common::Point(110, 17), INV_FOREGROUND, "Watson's Journal"); - - // Draw the buttons - screen.makeButton(Common::Rect(JOURNAL_POINTS[0][0], JOURNAL_BUTTONS_Y, - JOURNAL_POINTS[0][1], JOURNAL_BUTTONS_Y + 10), - JOURNAL_POINTS[0][2] - screen.stringWidth("Exit") / 2, "Exit"); - screen.makeButton(Common::Rect(JOURNAL_POINTS[1][0], JOURNAL_BUTTONS_Y, - JOURNAL_POINTS[1][1], JOURNAL_BUTTONS_Y + 10), - JOURNAL_POINTS[1][2] - screen.stringWidth("Back 10") / 2, "Back 10"); - screen.makeButton(Common::Rect(JOURNAL_POINTS[2][0], JOURNAL_BUTTONS_Y, - JOURNAL_POINTS[2][1], JOURNAL_BUTTONS_Y + 10), - JOURNAL_POINTS[2][2] - screen.stringWidth("Up") / 2, "Up"); - screen.makeButton(Common::Rect(JOURNAL_POINTS[3][0], JOURNAL_BUTTONS_Y, - JOURNAL_POINTS[3][1], JOURNAL_BUTTONS_Y + 10), - JOURNAL_POINTS[3][2] - screen.stringWidth("Down") / 2, "Down"); - screen.makeButton(Common::Rect(JOURNAL_POINTS[4][0], JOURNAL_BUTTONS_Y, - JOURNAL_POINTS[4][1], JOURNAL_BUTTONS_Y + 10), - JOURNAL_POINTS[4][2] - screen.stringWidth("Ahead 10") / 2, "Ahead 10"); - screen.makeButton(Common::Rect(JOURNAL_POINTS[5][0], JOURNAL_BUTTONS_Y + 11, - JOURNAL_POINTS[5][1], JOURNAL_BUTTONS_Y + 21), - JOURNAL_POINTS[5][2] - screen.stringWidth("Search") / 2, "Search"); - screen.makeButton(Common::Rect(JOURNAL_POINTS[6][0], JOURNAL_BUTTONS_Y + 11, - JOURNAL_POINTS[6][1], JOURNAL_BUTTONS_Y + 21), - JOURNAL_POINTS[6][2] - screen.stringWidth("First Page") / 2, "First Page"); - screen.makeButton(Common::Rect(JOURNAL_POINTS[7][0], JOURNAL_BUTTONS_Y + 11, - JOURNAL_POINTS[7][1], JOURNAL_BUTTONS_Y + 21), - JOURNAL_POINTS[7][2] - screen.stringWidth("Last Page") / 2, "Last Page"); - - // WORKAROUND: Draw Print Text button as disabled, since we don't support it in ScummVM - screen.makeButton(Common::Rect(JOURNAL_POINTS[8][0], JOURNAL_BUTTONS_Y + 11, - JOURNAL_POINTS[8][1], JOURNAL_BUTTONS_Y + 21), - JOURNAL_POINTS[8][2] - screen.stringWidth("Print Text") / 2, "Print Text"); - screen.buttonPrint(Common::Point(JOURNAL_POINTS[8][2], JOURNAL_BUTTONS_Y + 11), - COMMAND_NULL, false, "Print Text"); -} - -void Journal::drawInterface() { - Screen &screen = *_vm->_screen; - - drawJournalFrame(); - - if (_journal.empty()) { - _up = _down = 0; - } else { - drawJournal(0, 0); - } - - doArrows(); - - // Show the entire screen - screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); -} - -void Journal::doArrows() { - Screen &screen = *_vm->_screen; - byte color; - - color = (_page > 1) ? COMMAND_FOREGROUND : COMMAND_NULL; - screen.buttonPrint(Common::Point(JOURNAL_POINTS[1][2], JOURNAL_BUTTONS_Y), color, false, "Back 10"); - screen.buttonPrint(Common::Point(JOURNAL_POINTS[2][2], JOURNAL_BUTTONS_Y), color, false, "Up"); - - color = _down ? COMMAND_FOREGROUND : COMMAND_NULL; - screen.buttonPrint(Common::Point(JOURNAL_POINTS[3][2], JOURNAL_BUTTONS_Y), color, false, "Down"); - screen.buttonPrint(Common::Point(JOURNAL_POINTS[4][2], JOURNAL_BUTTONS_Y), color, false, "Ahead 10"); - screen.buttonPrint(Common::Point(JOURNAL_POINTS[7][2], JOURNAL_BUTTONS_Y + 11), color, false, "Last Page"); - - color = _journal.size() > 0 ? COMMAND_FOREGROUND : COMMAND_NULL; - screen.buttonPrint(Common::Point(JOURNAL_POINTS[5][2], JOURNAL_BUTTONS_Y + 11), color, false, "Search"); - screen.buttonPrint(Common::Point(JOURNAL_POINTS[8][2], JOURNAL_BUTTONS_Y + 11), COMMAND_NULL, false, "Print Text"); - - color = _page > 1 ? COMMAND_FOREGROUND : COMMAND_NULL; - screen.buttonPrint(Common::Point(JOURNAL_POINTS[6][2], JOURNAL_BUTTONS_Y + 11), color, false, "First Page"); -} - -bool Journal::drawJournal(int direction, int howFar) { - Events &events = *_vm->_events; - Screen &screen = *_vm->_screen; - Talk &talk = *_vm->_talk; - int yp = 37; - int startPage = _page; - bool endJournal = false; - bool firstOccurance = true; - bool searchSuccessful = false; - bool endFlag = false; - int lineNum = 0; - int savedIndex; - int temp; - const char *matchP; - int width; - - talk._converseNum = -1; - _down = true; - - do { - // Get the number of lines for the current journal entry - loadJournalFile(false); - if (_lines.empty()) { - // Entry has no text, so it must be a stealth eny. Move onto further journal entries - // until an entry with text is found - if (++_index == (int)_journal.size()) { - endJournal = true; - } else { - _sub = 0; - loadJournalFile(false); - } - } - } while (!endJournal && _lines.empty()); - - // Check if there no further pages with text until the end of the journal - if (endJournal) { - // If moving forward or backwards, clear the page before printing - if (direction) - drawJournalFrame(); - - screen.gPrint(Common::Point(235, 21), PEN_COLOR, "Page %d", _page); - return false; - } - - // If the journal page is being changed, set the wait cursor - if (direction) - events.setCursor(WAIT); - - switch (direction) { - case 1: - case 4: - // Move backwards howFar number of lines unless either the start of the journal is reached, - // or a searched for keyword is found - do { - // Animate the glass mouse cursor - int cursorNum = (int)events.getCursor() + 1; - if (cursorNum > (WAIT + 2)) - cursorNum = WAIT; - events.setCursor((CursorId)cursorNum); - - // Move backwards through the journal file a line at a time - if (--_sub < 0) { - do { - if (--_index < 0) { - _index = 0; - _sub = 0; - endJournal = true; - } - else { - loadJournalFile(false); - _sub = _lines.size() - 1; - } - } while (!endJournal && _lines.empty()); - } - - // If it's search mode, check each line for the given keyword - if (direction >= 3 && !_lines.empty() && !endJournal && !searchSuccessful) { - Common::String line = _lines[_sub]; - line.toUppercase(); - if (strstr(line.c_str(), _find.c_str()) != nullptr) { - // Found a match. Reset howFar so that the start of page that the match - // was found on will be displayed - searchSuccessful = true; - howFar = ((lineNum / LINES_PER_PAGE) + 1) * LINES_PER_PAGE; - } - } - - ++lineNum; - } while (lineNum < howFar && !endJournal); - - if (!_index && !_sub) - _page = 1; - else - _page -= howFar / LINES_PER_PAGE; - break; - - case 2: - case 3: - // Move howFar lines ahead unless the end of the journal is reached, - // or a searched for keyword is found - for (temp = 0; (temp < (howFar / LINES_PER_PAGE)) && !endJournal && !searchSuccessful; ++temp) { - // Handle animating mouse cursor - int cursorNum = (int)events.getCursor() + 1; - if (cursorNum >(WAIT + 2)) - cursorNum = WAIT; - events.setCursor((CursorId)cursorNum); - - lineNum = 0; - savedIndex = _index; - int savedSub = _sub; - - // Move a single page ahead - do { - // If in search mode, check for keyword - if (direction >= 3 && _page != startPage) { - Common::String line = _lines[_sub]; - line.toUppercase(); - if (strstr(line.c_str(), _find.c_str()) != nullptr) - searchSuccessful = true; - } - - // Move forwards a line at a time, unless search word was found - if (!searchSuccessful) { - if (++_sub == (int)_lines.size()) { - // Reached end of page - do { - if (++_index == (int)_journal.size()) { - _index = savedIndex; - _sub = savedSub; - loadJournalFile(false); - endJournal = true; - } else { - _sub = 0; - loadJournalFile(false); - } - } while (!endJournal && _lines.empty()); - } - - ++lineNum; - } - } while ((lineNum < LINES_PER_PAGE) && !endJournal && !searchSuccessful); - - if (!endJournal && !searchSuccessful) - // Move to next page - ++_page; - - if (searchSuccessful) { - // Search found, so show top of the page it was found on - _index = savedIndex; - _sub = savedSub; - loadJournalFile(false); - } - } - break; - - default: - break; - } - - if (direction) { - events.setCursor(ARROW); - drawJournalFrame(); - } - - screen.gPrint(Common::Point(235, 21), PEN_COLOR, "Page %d", _page); - - temp = _sub; - savedIndex = _index; - lineNum = 0; - - do { - bool inc = true; - - // If there wasn't any line to print at the top of the page, we won't need to - // increment the y position - if (_lines[temp].empty() && yp == 37) - inc = false; - - // If there's a searched for keyword in the line, it will need to be highlighted - if (searchSuccessful && firstOccurance) { - // Check if line has the keyword - Common::String line = _lines[temp]; - line.toUppercase(); - if ((matchP = strstr(line.c_str(), _find.c_str())) != nullptr) { - matchP = _lines[temp].c_str() + (matchP - line.c_str()); - firstOccurance = false; - - // Print out the start of the line before the matching keyword - Common::String lineStart(_lines[temp].c_str(), matchP); - if (lineStart.hasPrefix("@")) { - width = screen.stringWidth(lineStart.c_str() + 1); - screen.gPrint(Common::Point(53, yp), 15, "%s", lineStart.c_str() + 1); - } else { - width = screen.stringWidth(lineStart.c_str()); - screen.gPrint(Common::Point(53, yp), PEN_COLOR, "%s", lineStart.c_str()); - } - - // Print out the found keyword - Common::String lineMatch(matchP, matchP + _find.size()); - screen.gPrint(Common::Point(53 + width, yp), INV_FOREGROUND, "%s", lineMatch.c_str()); - width += screen.stringWidth(lineMatch.c_str()); - - // Print remainder of line - screen.gPrint(Common::Point(53 + width, yp), PEN_COLOR, "%s", matchP + _find.size()); - } else if (_lines[temp].hasPrefix("@")) { - screen.gPrint(Common::Point(53, yp), 15, "%s", _lines[temp].c_str() + 1); - } else { - screen.gPrint(Common::Point(53, yp), PEN_COLOR, "%s", _lines[temp].c_str()); - } - } else { - if (_lines[temp].hasPrefix("@")) { - screen.gPrint(Common::Point(53, yp), 15, "%s", _lines[temp].c_str() + 1); - } else { - screen.gPrint(Common::Point(53, yp), PEN_COLOR, "%s", _lines[temp].c_str()); - } - } - - if (++temp == (int)_lines.size()) { - // Move to next page - do { - if (_index < ((int)_journal.size() - 1) && lineNum < (LINES_PER_PAGE - 1)) { - ++_index; - loadJournalFile(false); - temp = 0; - } else { - if (_index == ((int)_journal.size() - 1)) - _down = false; - endFlag = true; - } - } while (!endFlag && _lines.empty()); - } - - if (inc) { - // Move to next line - ++lineNum; - yp += 13; - } - } while (lineNum < LINES_PER_PAGE && !endFlag); - - _index = savedIndex; - _up = _index || _sub; - - return direction >= 3 && searchSuccessful; -} - -JournalButton Journal::getHighlightedButton(const Common::Point &pt) { - if (pt.x > JOURNAL_POINTS[0][0] && pt.x < JOURNAL_POINTS[0][1] && pt.y >= JOURNAL_BUTTONS_Y && - pt.y < (JOURNAL_BUTTONS_Y + 10)) - return BTN_EXIT; - - if (pt.x > JOURNAL_POINTS[1][0] && pt.x < JOURNAL_POINTS[1][1] && pt.y >= JOURNAL_BUTTONS_Y && - pt.y < (JOURNAL_BUTTONS_Y + 10) && _page > 1) - return BTN_BACK10; - - if (pt.x > JOURNAL_POINTS[2][0] && pt.x < JOURNAL_POINTS[2][1] && pt.y >= JOURNAL_BUTTONS_Y && - pt.y < (JOURNAL_BUTTONS_Y + 10) && _up) - return BTN_UP; - - if (pt.x > JOURNAL_POINTS[3][0] && pt.x < JOURNAL_POINTS[3][1] && pt.y >= JOURNAL_BUTTONS_Y && - pt.y < (JOURNAL_BUTTONS_Y + 10) && _down) - return BTN_DOWN; - - if (pt.x > JOURNAL_POINTS[4][0] && pt.x < JOURNAL_POINTS[4][1] && pt.y >= JOURNAL_BUTTONS_Y && - pt.y < (JOURNAL_BUTTONS_Y + 10) && _down) - return BTN_AHEAD110; - - if (pt.x > JOURNAL_POINTS[5][0] && pt.x < JOURNAL_POINTS[5][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) && - pt.y < (JOURNAL_BUTTONS_Y + 20) && !_journal.empty()) - return BTN_SEARCH; - - if (pt.x > JOURNAL_POINTS[6][0] && pt.x < JOURNAL_POINTS[6][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) && - pt.y < (JOURNAL_BUTTONS_Y + 20) && _up) - return BTN_FIRST_PAGE; - - if (pt.x > JOURNAL_POINTS[7][0] && pt.x < JOURNAL_POINTS[7][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) && - pt.y < (JOURNAL_BUTTONS_Y + 20) && _down) - return BTN_LAST_PAGE; - - if (pt.x > JOURNAL_POINTS[8][0] && pt.x < JOURNAL_POINTS[8][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) && - pt.y < (JOURNAL_BUTTONS_Y + 20) && !_journal.empty()) - return BTN_PRINT_TEXT; - - return BTN_NONE; -} - -bool Journal::handleEvents(int key) { - Events &events = *_vm->_events; - Screen &screen = *_vm->_screen; - bool doneFlag = false; - - Common::Point pt = events.mousePos(); - JournalButton btn = getHighlightedButton(pt); - byte color; - - if (events._pressed || events._released) { - // Exit button - color = (btn == BTN_EXIT) ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND; - screen.buttonPrint(Common::Point(JOURNAL_POINTS[0][2], JOURNAL_BUTTONS_Y), color, true, "Exit"); - - // Back 10 button - if (btn == BTN_BACK10) { - screen.buttonPrint(Common::Point(JOURNAL_POINTS[1][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, "Back 10"); - } else if (_page > 1) { - screen.buttonPrint(Common::Point(JOURNAL_POINTS[1][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, "Back 10"); - } - - // Up button - if (btn == BTN_UP) { - screen.buttonPrint(Common::Point(JOURNAL_POINTS[2][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, "Up"); - } else if (_up) { - screen.buttonPrint(Common::Point(JOURNAL_POINTS[2][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, "Up"); - } - - // Down button - if (btn == BTN_DOWN) { - screen.buttonPrint(Common::Point(JOURNAL_POINTS[3][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, "Down"); - } else if (_down) { - screen.buttonPrint(Common::Point(JOURNAL_POINTS[3][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, "Down"); - } - - // Ahead 10 button - if (btn == BTN_AHEAD110) { - screen.buttonPrint(Common::Point(JOURNAL_POINTS[4][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, "Ahead 10"); - } else if (_down) { - screen.buttonPrint(Common::Point(JOURNAL_POINTS[4][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, "Ahead 10"); - } - - // Search button - if (btn == BTN_SEARCH) { - color = COMMAND_HIGHLIGHTED; - } else if (_journal.empty()) { - color = COMMAND_NULL; - } else { - color = COMMAND_FOREGROUND; - } - screen.buttonPrint(Common::Point(JOURNAL_POINTS[5][2], JOURNAL_BUTTONS_Y + 11), color, true, "Search"); - - // First Page button - if (btn == BTN_FIRST_PAGE) { - color = COMMAND_HIGHLIGHTED; - } else if (_up) { - color = COMMAND_FOREGROUND; - } else { - color = COMMAND_NULL; - } - screen.buttonPrint(Common::Point(JOURNAL_POINTS[6][2], JOURNAL_BUTTONS_Y + 11), color, true, "First Page"); - - // Last Page button - if (btn == BTN_LAST_PAGE) { - color = COMMAND_HIGHLIGHTED; - } else if (_down) { - color = COMMAND_FOREGROUND; - } else { - color = COMMAND_NULL; - } - screen.buttonPrint(Common::Point(JOURNAL_POINTS[7][2], JOURNAL_BUTTONS_Y + 11), color, true, "Last Page"); - - // Print Text button - screen.buttonPrint(Common::Point(JOURNAL_POINTS[8][2], JOURNAL_BUTTONS_Y + 11), COMMAND_NULL, true, "Print Text"); - } - - if (btn == BTN_EXIT && events._released) { - // Exit button pressed - doneFlag = true; - - } else if (((btn == BTN_BACK10 && events._released) || key == 'B') && (_page > 1)) { - // Scrolll up 10 pages - if (_page < 11) - drawJournal(1, (_page - 1) * LINES_PER_PAGE); - else - drawJournal(1, 10 * LINES_PER_PAGE); - - doArrows(); - screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); - - } else if (((btn == BTN_UP && events._released) || key == 'U') && _up) { - // Scroll up - drawJournal(1, LINES_PER_PAGE); - doArrows(); - screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); - - } else if (((btn == BTN_DOWN && events._released) || key == 'D') && _down) { - // Scroll down - drawJournal(2, LINES_PER_PAGE); - doArrows(); - screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); - - } else if (((btn == BTN_AHEAD110 && events._released) || key == 'A') && _down) { - // Scroll down 10 pages - if ((_page + 10) > _maxPage) - drawJournal(2, (_maxPage - _page) * LINES_PER_PAGE); - else - drawJournal(2, 10 * LINES_PER_PAGE); - - doArrows(); - screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); - - } else if (((btn == BTN_SEARCH && events._released) || key == 'S') && !_journal.empty()) { - screen.buttonPrint(Common::Point(JOURNAL_POINTS[5][2], JOURNAL_BUTTONS_Y + 11), COMMAND_FOREGROUND, true, "Search"); - bool notFound = false; - - do { - int dir; - if ((dir = getSearchString(notFound)) != 0) { - int savedIndex = _index; - int savedSub = _sub; - int savedPage = _page; - - if (drawJournal(dir + 2, 1000 * LINES_PER_PAGE) == 0) { - _index = savedIndex; - _sub = savedSub; - _page = savedPage; - - drawJournalFrame(); - drawJournal(0, 0); - notFound = true; - } else { - doneFlag = true; - } - - doArrows(); - screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); - } else { - doneFlag = true; - } - } while (!doneFlag); - doneFlag = false; - - } else if (((btn == BTN_FIRST_PAGE && events._released) || key == 'F') && _up) { - // First page - _index = _sub = 0; - _up = _down = false; - _page = 1; - - drawJournalFrame(); - drawJournal(0, 0); - doArrows(); - screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); - - } else if (((btn == BTN_LAST_PAGE && events._released) || key == 'L') && _down) { - // Last page - if ((_page + 10) > _maxPage) - drawJournal(2, (_maxPage - _page) * LINES_PER_PAGE); - else - drawJournal(2, 1000 * LINES_PER_PAGE); - - doArrows(); - screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); - } - - events.wait(2); - - return doneFlag; -} - -int Journal::getSearchString(bool printError) { - enum Button { BTN_NONE, BTN_EXIT, BTN_BACKWARD, BTN_FORWARD }; - - Events &events = *_vm->_events; - Screen &screen = *_vm->_screen; - Talk &talk = *_vm->_talk; - int xp; - int yp = 174; - bool flag = false; - Common::String name; - int done = 0; - byte color; - - // Draw search panel - screen.makePanel(Common::Rect(6, 171, 313, 199)); - screen.makeButton(Common::Rect(SEARCH_POINTS[0][0], yp, SEARCH_POINTS[0][1], yp + 10), - SEARCH_POINTS[0][2] - screen.stringWidth("Exit") / 2, "Exit"); - screen.makeButton(Common::Rect(SEARCH_POINTS[1][0], yp, SEARCH_POINTS[1][1], yp + 10), - SEARCH_POINTS[1][2] - screen.stringWidth("Backward") / 2, "Backward"); - screen.makeButton(Common::Rect(SEARCH_POINTS[2][0], yp, SEARCH_POINTS[2][1], yp + 10), - SEARCH_POINTS[2][2] - screen.stringWidth("Forward") / 2, "Forward"); - screen.gPrint(Common::Point(SEARCH_POINTS[0][2] - screen.stringWidth("Exit") / 2, yp), - COMMAND_FOREGROUND, "E"); - screen.gPrint(Common::Point(SEARCH_POINTS[1][2] - screen.stringWidth("Backward") / 2, yp), - COMMAND_FOREGROUND, "B"); - screen.gPrint(Common::Point(SEARCH_POINTS[2][2] - screen.stringWidth("Forward") / 2, yp), - COMMAND_FOREGROUND, "F"); - - screen.makeField(Common::Rect(12, 185, 307, 196)); - - screen.fillRect(Common::Rect(12, 185, 307, 186), BUTTON_BOTTOM); - screen.vLine(12, 185, 195, BUTTON_BOTTOM); - screen.hLine(13, 195, 306, BUTTON_TOP); - screen.hLine(306, 186, 195, BUTTON_TOP); - - if (printError) { - screen.gPrint(Common::Point((SHERLOCK_SCREEN_WIDTH - screen.stringWidth("Text Not Found !")) / 2, 185), - INV_FOREGROUND, "Text Not Found !"); - } else if (!_find.empty()) { - // There's already a search term, display it already - screen.gPrint(Common::Point(15, 185), TALK_FOREGROUND, "%s", _find.c_str()); - name = _find; - } - - screen.slamArea(6, 171, 307, 28); - - if (printError) { - // Give time for user to see the message - events.setButtonState(); - for (int idx = 0; idx < 40 && !_vm->shouldQuit() && !events.kbHit() && !events._released; ++idx) { - events.pollEvents(); - events.setButtonState(); - events.wait(2); - } - - events.clearKeyboard(); - screen._backBuffer1.fillRect(Common::Rect(13, 186, 306, 195), BUTTON_MIDDLE); - - if (!_find.empty()) { - screen.gPrint(Common::Point(15, 185), TALK_FOREGROUND, "%s", _find.c_str()); - name = _find; - } +void Journal::record(int converseNum, int statementNum, bool replyOnly) { + int saveIndex = _index; + int saveSub = _sub; - screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + if (IS_3DO) { + // there seems to be no journal in the 3DO version + return; } - xp = JOURNAL_SEARCH_LEFT + screen.stringWidth(name); - yp = JOURNAL_SEARCH_TOP; - - do { - events._released = false; - Button found = BTN_NONE; - - while (!_vm->shouldQuit() && !events.kbHit() && !events._released) { - found = BTN_NONE; - if (talk._talkToAbort) - return 0; - - // Check if key or mouse button press has occurred - events.setButtonState(); - Common::Point pt = events.mousePos(); - - flag = !flag; - screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), flag ? INV_FOREGROUND : BUTTON_MIDDLE); - - if (events._pressed || events._released) { - if (pt.x > SEARCH_POINTS[0][0] && pt.x < SEARCH_POINTS[0][1] && pt.y > 174 && pt.y < 183) { - found = BTN_EXIT; - color = COMMAND_HIGHLIGHTED; - } else { - color = COMMAND_FOREGROUND; - } - screen.print(Common::Point(SEARCH_POINTS[0][2] - screen.stringWidth("Exit") / 2, 175), color, "Exit"); - - if (pt.x > SEARCH_POINTS[1][0] && pt.x < SEARCH_POINTS[1][1] && pt.y > 174 && pt.y < 183) { - found = BTN_BACKWARD; - color = COMMAND_HIGHLIGHTED; - } else { - color = COMMAND_FOREGROUND; - } - screen.print(Common::Point(SEARCH_POINTS[1][2] - screen.stringWidth("Backward") / 2, 175), color, "Backward"); - - if (pt.x > SEARCH_POINTS[2][0] && pt.x < SEARCH_POINTS[2][1] && pt.y > 174 && pt.y < 183) { - found = BTN_FORWARD; - color = COMMAND_HIGHLIGHTED; - } else { - color = COMMAND_FOREGROUND; - } - screen.print(Common::Point(SEARCH_POINTS[2][2] - screen.stringWidth("Forward") / 2, 175), color, "Forward"); - } - - events.wait(2); - } + // Record the entry into the list + _journal.push_back(JournalEntry(converseNum, statementNum, replyOnly)); + _index = _journal.size() - 1; - if (events.kbHit()) { - Common::KeyState keyState = events.getKey(); - - if ((keyState.keycode == Common::KEYCODE_BACKSPACE) && (name.size() > 0)) { - screen.vgaBar(Common::Rect(xp - screen.charWidth(name.lastChar()), yp, xp + 8, yp + 9), BUTTON_MIDDLE); - xp -= screen.charWidth(name.lastChar()); - screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), INV_FOREGROUND); - name.deleteLastChar(); - - } else if (keyState.keycode == Common::KEYCODE_RETURN) { - done = 1; - - } else if (keyState.keycode == Common::KEYCODE_ESCAPE) { - screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), BUTTON_MIDDLE); - done = -1; - - } else if (keyState.ascii >= ' ' && keyState.ascii <= 'z' && keyState.keycode != Common::KEYCODE_AT && - name.size() < JOURNAL_SEACRH_MAX_CHARS && (xp + screen.charWidth(keyState.ascii)) < JOURNAL_SEARCH_RIGHT) { - char ch = toupper(keyState.ascii); - screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), BUTTON_MIDDLE); - screen.print(Common::Point(xp, yp), TALK_FOREGROUND, "%c", ch); - xp += screen.charWidth(ch); - name += ch; - } - } + // Load the text for the new entry to get the number of lines it will have + loadJournalFile(true); - if (events._released) { - switch (found) { - case BTN_EXIT: - done = -1; break; - case BTN_BACKWARD: - done = 2; break; - case BTN_FORWARD: - done = 1; break; - default: - break; - } - } - } while (!done && !_vm->shouldQuit()); + // Restore old state + _index = saveIndex; + _sub = saveSub; - if (done != -1) { - _find = name; + // If new lines were added to the ournal, update the total number of lines + // the journal continues + if (!_lines.empty()) { + _maxPage += _lines.size(); } else { - done = 0; + // No lines in entry, so remove the new entry from the journal + _journal.remove_at(_journal.size() - 1); } - - // Redisplay the journal screen - drawJournalFrame(); - drawJournal(0, 0); - screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); - - return done; } -void Journal::resetPosition() { - _index = _sub = _up = _down = 0; - _page = 1; -} -void Journal::synchronize(Common::Serializer &s) { +void Journal::synchronize(Serializer &s) { s.syncAsSint16LE(_index); s.syncAsSint16LE(_sub); s.syncAsSint16LE(_page); diff --git a/engines/sherlock/journal.h b/engines/sherlock/journal.h index d62b8338c0..a8fec104f4 100644 --- a/engines/sherlock/journal.h +++ b/engines/sherlock/journal.h @@ -26,20 +26,18 @@ #include "common/scummsys.h" #include "common/array.h" #include "common/rect.h" -#include "common/serializer.h" #include "common/str-array.h" #include "common/stream.h" +#include "sherlock/saveload.h" namespace Sherlock { -#define JOURNAL_MAX_WIDTH 230 +#define LINES_PER_PAGE (IS_SERRATED_SCALPEL ? 11 : 17) +#define JOURNAL_MAX_WIDTH (IS_SERRATED_SCALPEL ? 230 : 422) #define JOURNAL_MAX_CHARS 80 +#define JOURNAL_LEFT_X (IS_SERRATED_SCALPEL ? 53 : 156) -enum JournalButton { - BTN_NONE, BTN_EXIT, BTN_BACK10, BTN_UP, BTN_DOWN, BTN_AHEAD110, BTN_SEARCH, - BTN_FIRST_PAGE, BTN_LAST_PAGE, BTN_PRINT_TEXT -}; - +class SherlockEngine; struct JournalEntry { int _converseNum; @@ -51,26 +49,21 @@ struct JournalEntry { _converseNum(converseNum), _statementNum(statementNum), _replyOnly(replyOnly) {} }; -class SherlockEngine; - class Journal { -private: +protected: SherlockEngine *_vm; - Common::Array<JournalEntry> _journal; Common::StringArray _directory; Common::StringArray _locations; + Common::Array<JournalEntry> _journal; Common::StringArray _lines; - int _maxPage; - int _index; - int _sub; bool _up, _down; + int _index; int _page; + int _maxPage; + int _sub; Common::String _find; - /** - * Load the list of location names that the journal will make reference to - */ - void loadJournalLocations(); + Journal(SherlockEngine *vm); /** * Loads the description for the current display index in the journal, and then @@ -79,59 +72,35 @@ private: * first time, or being reloaded */ void loadJournalFile(bool alreadyLoaded); +public: + static Journal *init(SherlockEngine *vm); + virtual ~Journal() {} /** - * Display the arrows that can be used to scroll up and down pages - */ - void doArrows(); - - /** - * Displays a page of the journal at the current index - */ + * Displays a page of the journal at the current index + */ bool drawJournal(int direction, int howFar); /** - * Show the search submenu and allow the player to enter a search string + * Synchronize the data for a savegame */ - int getSearchString(bool printError); - + void synchronize(Serializer &s); +public: /** * Draw the journal background, frame, and interface buttons */ - void drawJournalFrame(); + virtual void drawFrame() = 0; /** - * Returns the button, if any, that is under the specified position + * Reset viewing position to the start of the journal */ - JournalButton getHighlightedButton(const Common::Point &pt); -public: - Journal(SherlockEngine *vm); + virtual void resetPosition() {} /** * Records statements that are said, in the order which they are said. The player * can then read the journal to review them */ - void record(int converseNum, int statementNum, bool replyOnly = false); - - /** - * Display the journal - */ - void drawInterface(); - - /** - * Handle events whilst the journal is being displayed - */ - bool handleEvents(int key); - - /** - * Reset viewing position to the start of the journal - */ - void resetPosition(); - - /** - * Synchronize the data for a savegame - */ - void synchronize(Common::Serializer &s); + virtual void record(int converseNum, int statementNum, bool replyOnly = false); }; } // End of namespace Sherlock diff --git a/engines/sherlock/map.cpp b/engines/sherlock/map.cpp index ffbca3fb42..fbccaf244f 100644 --- a/engines/sherlock/map.cpp +++ b/engines/sherlock/map.cpp @@ -20,546 +20,33 @@ * */ +#include "common/system.h" #include "sherlock/map.h" #include "sherlock/sherlock.h" -#include "common/system.h" +#include "sherlock/scalpel/scalpel_map.h" +#include "sherlock/tattoo/tattoo_map.h" namespace Sherlock { -MapPaths::MapPaths() { - _numLocations = 0; -} - -void MapPaths::load(int numLocations, Common::SeekableReadStream &s) { - _numLocations = numLocations; - _paths.resize(_numLocations * _numLocations); - - for (int idx = 0; idx < (numLocations * numLocations); ++idx) { - Common::Array<byte> &path = _paths[idx]; - int v; - - do { - v = s.readByte(); - path.push_back(v); - } while (v && v < 254); - } -} - -const byte *MapPaths::getPath(int srcLocation, int destLocation) { - return &_paths[srcLocation * _numLocations + destLocation][0]; -} - -/*----------------------------------------------------------------*/ - -Map::Map(SherlockEngine *vm): _vm(vm), _topLine(g_system->getWidth(), 12) { - _active = false; - _mapCursors = nullptr; - _shapes = nullptr; - _iconShapes = nullptr; - _point = 0; - _placesShown = false; - _cursorIndex = -1; - _drawMap = false; - _overPos = Common::Point(13000, 12600); - _charPoint = 0; - _oldCharPoint = 0; - _frameChangeFlag = false; - - // Initialise the initial walk sequence set - _walkSequences.resize(MAX_HOLMES_SEQUENCE); - for (int idx = 0; idx < MAX_HOLMES_SEQUENCE; ++idx) { - _walkSequences[idx]._sequences.resize(MAX_FRAME); - Common::fill(&_walkSequences[idx]._sequences[0], &_walkSequences[idx]._sequences[0] + MAX_FRAME, 0); - } - - if (!_vm->isDemo()) - loadData(); -} - -void Map::loadPoints(int count, const int *xList, const int *yList, const int *transList) { - for (int idx = 0; idx < count; ++idx, ++xList, ++yList, ++transList) { - _points.push_back(MapEntry(*xList, *yList, *transList)); - } -} - -void Map::loadSequences(int count, const byte *seq) { - for (int idx = 0; idx < count; ++idx, seq += MAX_FRAME) - Common::copy(seq, seq + MAX_FRAME, &_walkSequences[idx]._sequences[0]); -} - -void Map::loadData() { - // TODO: Remove this - if (_vm->getGameID() == GType_RoseTattoo) - return; - - // Load the list of location names - Common::SeekableReadStream *txtStream = _vm->_res->load( - _vm->getGameID() == GType_SerratedScalpel ? "chess.txt" : "map.txt"); - - int streamSize = txtStream->size(); - while (txtStream->pos() < streamSize) { - Common::String line; - char c; - while ((c = txtStream->readByte()) != '\0') - line += c; - - _locationNames.push_back(line); - } - - delete txtStream; - - // Load the path data - Common::SeekableReadStream *pathStream = _vm->_res->load("chess.pth"); - - // Get routes between different locations on the map - _paths.load(31, *pathStream); - - // Load in the co-ordinates that the paths refer to - _pathPoints.resize(208); - for (uint idx = 0; idx < _pathPoints.size(); ++idx) { - _pathPoints[idx].x = pathStream->readSint16LE(); - _pathPoints[idx].y = pathStream->readSint16LE(); - } - - delete pathStream; -} - -int Map::show() { - Events &events = *_vm->_events; - People &people = *_vm->_people; - Screen &screen = *_vm->_screen; - Common::Point lDrawn(-1, -1); - bool changed = false, exitFlag = false; - _active = true; - - // Set font and custom cursor for the map - int oldFont = screen.fontNumber(); - screen.setFont(0); - - // Initial screen clear - screen._backBuffer1.clear(); - screen.clear(); - - // Load the entire map - ImageFile bigMap("bigmap.vgs"); - screen.setPalette(bigMap._palette); - - // Load need sprites - setupSprites(); - - screen._backBuffer1.blitFrom(bigMap[0], Common::Point(-_bigPos.x, -_bigPos.y)); - screen._backBuffer1.blitFrom(bigMap[1], Common::Point(-_bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y)); - screen._backBuffer1.blitFrom(bigMap[2], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, -_bigPos.y)); - screen._backBuffer1.blitFrom(bigMap[3], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y)); - - _drawMap = true; - _charPoint = -1; - _point = -1; - people[AL]._position = _lDrawnPos = _overPos; - - // Show place icons - showPlaces(); - saveTopLine(); - _placesShown = true; - - // Keep looping until either a location is picked, or the game is ended - while (!_vm->shouldQuit() && !exitFlag) { - events.pollEventsAndWait(); - events.setButtonState(); - - // Keyboard handling - if (events.kbHit()) { - Common::KeyState keyState = events.getKey(); - - if (keyState.keycode == Common::KEYCODE_RETURN || keyState.keycode == Common::KEYCODE_SPACE) { - // Both space and enter simulate a mouse release - events._pressed = false; - events._released = true; - events._oldButtons = 0; - } - } - - // Ignore scrolling attempts until the screen is drawn - if (!_drawMap) { - Common::Point pt = events.mousePos(); - - // Check for vertical map scrolling - if ((pt.y > (SHERLOCK_SCREEN_HEIGHT - 10) && _bigPos.y < 200) || (pt.y < 10 && _bigPos.y > 0)) { - if (pt.y > (SHERLOCK_SCREEN_HEIGHT - 10)) - _bigPos.y += 10; - else - _bigPos.y -= 10; - - changed = true; - } - - // Check for horizontal map scrolling - if ((pt.x > (SHERLOCK_SCREEN_WIDTH - 10) && _bigPos.x < 315) || (pt.x < 10 && _bigPos.x > 0)) { - if (pt.x > (SHERLOCK_SCREEN_WIDTH - 10)) - _bigPos.x += 15; - else - _bigPos.x -= 15; - - changed = true; - } - } - - if (changed) { - // Map has scrolled, so redraw new map view - changed = false; - - screen._backBuffer1.blitFrom(bigMap[0], Common::Point(-_bigPos.x, -_bigPos.y)); - screen._backBuffer1.blitFrom(bigMap[1], Common::Point(-_bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y)); - screen._backBuffer1.blitFrom(bigMap[2], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, -_bigPos.y)); - screen._backBuffer1.blitFrom(bigMap[3], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y)); - - showPlaces(); - _placesShown = false; - - saveTopLine(); - _savedPos.x = -1; - updateMap(true); - } else if (!_drawMap) { - if (!_placesShown) { - showPlaces(); - _placesShown = true; - } - - if (_cursorIndex == 0) { - Common::Point pt = events.mousePos(); - highlightIcon(Common::Point(pt.x - 4 + _bigPos.x, pt.y + _bigPos.y)); - } - updateMap(false); - } - - if ((events._released || events._rightReleased) && _point != -1) { - if (people[AL]._walkCount == 0) { - people._walkDest = _points[_point] + Common::Point(4, 9); - _charPoint = _point; - - // Start walking to selected location - walkTheStreets(); - - // Show wait cursor - _cursorIndex = 1; - events.setCursor((*_mapCursors)[_cursorIndex]._frame); - } - } - - // Check if a scene has beeen selected and we've finished "moving" to it - if (people[AL]._walkCount == 0) { - if (_charPoint >= 1 && _charPoint < (int)_points.size()) - exitFlag = true; - } - - if (_drawMap) { - _drawMap = false; - - if (screen._fadeStyle) - screen.randomTransition(); - else - screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); - } - - // Wait for a frame - events.wait(1); - } - - freeSprites(); - _overPos = people[AL]._position; - - // Reset font - screen.setFont(oldFont); - - _active = false; - return _charPoint; -} - -void Map::setupSprites() { - Events &events = *_vm->_events; - People &people = *_vm->_people; - Scene &scene = *_vm->_scene; - _savedPos.x = -1; - - _mapCursors = new ImageFile("omouse.vgs"); - _cursorIndex = 0; - events.setCursor((*_mapCursors)[_cursorIndex]._frame); - - _shapes = new ImageFile("mapicon.vgs"); - _iconShapes = new ImageFile("overicon.vgs"); - _iconSave.create((*_shapes)[4]._width, (*_shapes)[4]._height); - Person &p = people[AL]; - p._description = " "; - p._type = CHARACTER; - p._position = Common::Point(12400, 5000); - p._sequenceNumber = 0; - p._images = _shapes; - p._imageFrame = nullptr; - p._frameNumber = 0; - p._delta = Common::Point(0, 0); - p._oldSize = Common::Point(0, 0); - p._oldSize = Common::Point(0, 0); - p._misc = 0; - p._walkCount = 0; - p._allow = 0; - p._noShapeSize = Common::Point(0, 0); - p._goto = Common::Point(28000, 15000); - p._status = 0; - p._walkSequences = _walkSequences; - p.setImageFrame(); - scene._bgShapes.clear(); -} - -void Map::freeSprites() { - delete _mapCursors; - delete _shapes; - delete _iconShapes; - _iconSave.free(); -} - -void Map::showPlaces() { - Screen &screen = *_vm->_screen; - - for (uint idx = 0; idx < _points.size(); ++idx) { - const MapEntry &pt = _points[idx]; - - if (pt.x != 0 && pt.y != 0) { - if (pt.x >= _bigPos.x && (pt.x - _bigPos.x) < SHERLOCK_SCREEN_WIDTH - && pt.y >= _bigPos.y && (pt.y - _bigPos.y) < SHERLOCK_SCREEN_HEIGHT) { - if (_vm->readFlags(idx)) { - screen._backBuffer1.transBlitFrom((*_iconShapes)[pt._translate], - Common::Point(pt.x - _bigPos.x - 6, pt.y - _bigPos.y - 12)); - } - } - } - } -} - -void Map::saveTopLine() { - _topLine.blitFrom(_vm->_screen->_backBuffer1, Common::Point(0, 0), Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, 12)); -} - -void Map::eraseTopLine() { - Screen &screen = *_vm->_screen; - screen._backBuffer1.blitFrom(_topLine, Common::Point(0, 0)); - screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, _topLine.h()); -} - -void Map::showPlaceName(int idx, bool highlighted) { - People &people = *_vm->_people; - Screen &screen = *_vm->_screen; - - Common::String name = _locationNames[idx]; - int width = screen.stringWidth(name); - - if (!_cursorIndex) { - saveIcon(people[AL]._imageFrame, _lDrawnPos); - - bool flipped = people[AL]._sequenceNumber == MAP_DOWNLEFT || people[AL]._sequenceNumber == MAP_LEFT - || people[AL]._sequenceNumber == MAP_UPLEFT; - screen._backBuffer1.transBlitFrom(*people[AL]._imageFrame, _lDrawnPos, flipped); - } - - if (highlighted) { - int xp = (SHERLOCK_SCREEN_WIDTH - screen.stringWidth(name)) / 2; - screen.gPrint(Common::Point(xp + 2, 2), 0, "%s", name.c_str()); - screen.gPrint(Common::Point(xp + 1, 1), 0, "%s", name.c_str()); - screen.gPrint(Common::Point(xp, 0), 12, "%s", name.c_str()); - - screen.slamArea(xp, 0, width + 2, 15); - } -} - -void Map::updateMap(bool flushScreen) { - Events &events = *_vm->_events; - People &people = *_vm->_people; - Screen &screen = *_vm->_screen; - Common::Point osPos = _savedPos; - Common::Point osSize = _savedSize; - Common::Point hPos; - - if (_cursorIndex >= 1) { - if (++_cursorIndex > (1 + 8)) - _cursorIndex = 1; - - events.setCursor((*_mapCursors)[(_cursorIndex + 1) / 2]._frame); - } - - if (!_drawMap && !flushScreen) - restoreIcon(); +Map *Map::init(SherlockEngine *vm) { + if (vm->getGameID() == GType_SerratedScalpel) + return new Scalpel::ScalpelMap(vm); else - _savedPos.x = -1; - - people[AL].adjustSprite(); - - _lDrawnPos.x = hPos.x = people[AL]._position.x / 100 - _bigPos.x; - _lDrawnPos.y = hPos.y = people[AL]._position.y / 100 - people[AL].frameHeight() - _bigPos.y; - - // Draw the person icon - saveIcon(people[AL]._imageFrame, hPos); - if (people[AL]._sequenceNumber == MAP_DOWNLEFT || people[AL]._sequenceNumber == MAP_LEFT - || people[AL]._sequenceNumber == MAP_UPLEFT) - screen._backBuffer1.transBlitFrom(*people[AL]._imageFrame, hPos, true); - else - screen._backBuffer1.transBlitFrom(*people[AL]._imageFrame, hPos, false); - - if (flushScreen) { - screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); - } else if (!_drawMap) { - if (hPos.x > 0 && hPos.y >= 0 && hPos.x < SHERLOCK_SCREEN_WIDTH && hPos.y < SHERLOCK_SCREEN_HEIGHT) - screen.flushImage(people[AL]._imageFrame, Common::Point(people[AL]._position.x / 100 - _bigPos.x, - people[AL]._position.y / 100 - people[AL].frameHeight() - _bigPos.y), - &people[AL]._oldPosition.x, &people[AL]._oldPosition.y, &people[AL]._oldSize.x, &people[AL]._oldSize.y); - - if (osPos.x != -1) - screen.slamArea(osPos.x, osPos.y, osSize.x, osSize.y); - } + return new Tattoo::TattooMap(vm); } -void Map::walkTheStreets() { - People &people = *_vm->_people; - Common::Array<Common::Point> tempPath; - - // Get indexes into the path lists for the start and destination scenes - int start = _points[_oldCharPoint]._translate; - int dest = _points[_charPoint]._translate; - - // Get pointer to start of path - const byte *path = _paths.getPath(start, dest); - - // Add in destination position - people._walkTo.clear(); - Common::Point destPos = people._walkDest; - - // Check for any intermediate points between the two locations - if (path[0] || _charPoint > 50 || _oldCharPoint > 50) { - people[AL]._sequenceNumber = -1; - - if (_charPoint == 51 || _oldCharPoint == 51) { - people.setWalking(); - } else { - bool reversePath = false; - - // Check for moving the path backwards or forwards - if (path[0] == 255) { - reversePath = true; - SWAP(start, dest); - path = _paths.getPath(start, dest); - } - - do { - int idx = *path++; - tempPath.push_back(_pathPoints[idx - 1] + Common::Point(4, 4)); - } while (*path != 254); - - // Load up the path to use - people._walkTo.clear(); - - if (reversePath) { - for (int idx = (int)tempPath.size() - 1; idx >= 0; --idx) - people._walkTo.push(tempPath[idx]); - } else { - for (int idx = 0; idx < (int)tempPath.size(); ++idx) - people._walkTo.push(tempPath[idx]); - } - - people._walkDest = people._walkTo.pop() + Common::Point(12, 6); - people.setWalking(); - } - } else { - people[AL]._walkCount = 0; - } - - // Store the final destination icon position - people._walkTo.push(destPos); -} - -void Map::saveIcon(ImageFrame *src, const Common::Point &pt) { - Screen &screen = *_vm->_screen; - Common::Point size(src->_width, src->_height); - Common::Point pos = pt; - - if (pos.x < 0) { - size.x += pos.x; - pos.x = 0; - } - - if (pos.y < 0) { - size.y += pos.y; - pos.y = 0; - } - - if ((pos.x + size.x) > SHERLOCK_SCREEN_WIDTH) - size.x -= (pos.x + size.x) - SHERLOCK_SCREEN_WIDTH; - - if ((pos.y + size.y) > SHERLOCK_SCREEN_HEIGHT) - size.y -= (pos.y + size.y) - SHERLOCK_SCREEN_HEIGHT; - - if (size.x < 1 || size.y < 1 || pos.x >= SHERLOCK_SCREEN_WIDTH || pos.y >= SHERLOCK_SCREEN_HEIGHT || _drawMap) { - // Flag as the area not needing to be saved - _savedPos.x = -1; - return; - } - - assert(size.x <= _iconSave.w() && size.y <= _iconSave.h()); - _iconSave.blitFrom(screen._backBuffer1, Common::Point(0, 0), - Common::Rect(pos.x, pos.y, pos.x + size.x, pos.y + size.y)); - _savedPos = pos; - _savedSize = size; +Map::Map(SherlockEngine *vm) : _vm(vm) { + _charPoint = _oldCharPoint = 0; + _active = _frameChangeFlag = false; } -void Map::restoreIcon() { - Screen &screen = *_vm->_screen; - - if (_savedPos.x >= 0 && _savedPos.y >= 0 && _savedPos.x <= SHERLOCK_SCREEN_WIDTH - && _savedPos.y < SHERLOCK_SCREEN_HEIGHT) - screen._backBuffer1.blitFrom(_iconSave, _savedPos, Common::Rect(0, 0, _savedSize.x, _savedSize.y)); -} - -void Map::highlightIcon(const Common::Point &pt) { - int oldPoint = _point; - - // Iterate through the icon list - bool done = false; - for (int idx = 0; idx < (int)_points.size(); ++idx) { - const MapEntry &entry = _points[idx]; - - // Check whether the mouse is over a given icon - if (entry.x != 0 && entry.y != 0) { - if (Common::Rect(entry.x - 8, entry.y - 8, entry.x + 9, entry.y + 9).contains(pt)) { - done = true; - - if (_point != idx && _vm->readFlags(idx)) { - // Changed to a new valid (visible) location - eraseTopLine(); - showPlaceName(idx, true); - _point = idx; - } - } - } - } - - if (!done) { - // No icon was highlighted - if (_point != -1) { - // No longer highlighting previously highlighted icon, so erase it - showPlaceName(_point, false); - eraseTopLine(); - } - - _point = -1; - } else if (oldPoint != -1 && oldPoint != _point) { - showPlaceName(oldPoint, false); - eraseTopLine(); - } -} - -void Map::synchronize(Common::Serializer &s) { - s.syncAsSint16LE(_bigPos.x); - s.syncAsSint16LE(_bigPos.y); - s.syncAsSint16LE(_overPos.x); +void Map::synchronize(Serializer &s) { + s.syncAsSint32LE(_bigPos.x); + s.syncAsSint32LE(_bigPos.y); + s.syncAsSint32LE(_overPos.x); s.syncAsSint16LE(_overPos.y); s.syncAsSint16LE(_oldCharPoint); } + } // End of namespace Sherlock diff --git a/engines/sherlock/map.h b/engines/sherlock/map.h index e0c7d038c4..104f5e9c8a 100644 --- a/engines/sherlock/map.h +++ b/engines/sherlock/map.h @@ -23,156 +23,37 @@ #ifndef SHERLOCK_MAP_H #define SHERLOCK_MAP_H -#include "common/scummsys.h" -#include "common/array.h" -#include "common/rect.h" -#include "common/serializer.h" -#include "common/str.h" -#include "common/str-array.h" -#include "sherlock/surface.h" #include "sherlock/objects.h" +#include "sherlock/saveload.h" namespace Sherlock { class SherlockEngine; -struct MapEntry : Common::Point { - int _translate; - - MapEntry() : Common::Point(), _translate(-1) {} - - MapEntry(int posX, int posY, int translate) : Common::Point(posX, posY), _translate(translate) {} -}; - -class MapPaths { -private: - int _numLocations; - Common::Array< Common::Array<byte> > _paths; - -public: - MapPaths(); - - /** - * Load the data for the paths between locations on the map - */ - void load(int numLocations, Common::SeekableReadStream &s); - - /** - * Get the path between two locations on the map - */ - const byte *getPath(int srcLocation, int destLocation); -}; - class Map { -private: +protected: SherlockEngine *_vm; - Common::Array<MapEntry> _points; // Map locations for each scene - Common::StringArray _locationNames; - MapPaths _paths; - Common::Array<Common::Point> _pathPoints; - Common::Point _savedPos; - Common::Point _savedSize; - Surface _topLine; - ImageFile *_mapCursors; - ImageFile *_shapes; - ImageFile *_iconShapes; - WalkSequences _walkSequences; - Point32 _lDrawnPos; - int _point; - bool _placesShown; - int _cursorIndex; - bool _drawMap; - Surface _iconSave; -private: - /** - * Load data needed for the map - */ - void loadData(); - - /** - * Load and initialize all the sprites that are needed for the map display - */ - void setupSprites(); - /** - * Free the sprites and data used by the map - */ - void freeSprites(); - - /** - * Draws an icon for every place that's currently known - */ - void showPlaces(); - - /** - * Makes a copy of the top rows of the screen that are used to display location names - */ - void saveTopLine(); - - /** - * Erases anything shown in the top line by restoring the previously saved original map background - */ - void eraseTopLine(); - - /** - * Prints the name of the specified icon - */ - void showPlaceName(int idx, bool highlighted); - - /** - * Update all on-screen sprites to account for any scrolling of the map - */ - void updateMap(bool flushScreen); - - /** - * Handle moving icon for player from their previous location on the map to a destination location - */ - void walkTheStreets(); - - /** - * Save the area under the player's icon - */ - void saveIcon(ImageFrame *src, const Common::Point &pt); - - /** - * Restore the area under the player's icon - */ - void restoreIcon(); - - /** - * Handles highlighting map icons, showing their names - */ - void highlightIcon(const Common::Point &pt); + Map(SherlockEngine *vm); public: - bool _active; Point32 _overPos; Point32 _bigPos; int _charPoint, _oldCharPoint; + bool _active; bool _frameChangeFlag; public: - Map(SherlockEngine *vm); - - const MapEntry &operator[](int idx) { return _points[idx]; } - - /** - * Loads the list of points for locations on the map for each scene - */ - void loadPoints(int count, const int *xList, const int *yList, const int *transList); - - /** - * Load the sequence data for player icon animations - */ - void loadSequences(int count, const byte *seq); + static Map *init(SherlockEngine *vm); + virtual ~Map() {} /** * Show the map */ - int show(); + virtual int show() = 0; /** * Synchronize the data for a savegame */ - void synchronize(Common::Serializer &s); + void synchronize(Serializer &s); }; } // End of namespace Sherlock diff --git a/engines/sherlock/module.mk b/engines/sherlock/module.mk index 1a38d56511..7fa7896691 100644 --- a/engines/sherlock/module.mk +++ b/engines/sherlock/module.mk @@ -1,21 +1,57 @@ MODULE := engines/sherlock MODULE_OBJS = \ - scalpel/darts.o \ scalpel/scalpel.o \ + scalpel/3do/movie_decoder.o \ scalpel/drivers/adlib.o \ + scalpel/drivers/mt32.o \ scalpel/tsage/logo.o \ scalpel/tsage/resources.o \ + scalpel/scalpel_darts.o \ + scalpel/scalpel_debugger.o \ + scalpel/scalpel_fixed_text.o \ + scalpel/scalpel_inventory.o \ + scalpel/scalpel_journal.o \ + scalpel/scalpel_map.o \ + scalpel/scalpel_people.o \ + scalpel/scalpel_saveload.o \ scalpel/scalpel_scene.o \ + scalpel/scalpel_screen.o \ + scalpel/scalpel_talk.o \ scalpel/scalpel_user_interface.o \ scalpel/settings.o \ tattoo/tattoo.o \ + tattoo/tattoo_darts.o \ + tattoo/tattoo_debugger.o \ + tattoo/tattoo_fixed_text.o \ + tattoo/tattoo_inventory.o \ + tattoo/tattoo_journal.o \ + tattoo/tattoo_map.o \ + tattoo/tattoo_people.o \ + tattoo/tattoo_resources.o \ tattoo/tattoo_scene.o \ + tattoo/tattoo_talk.o \ tattoo/tattoo_user_interface.o \ + tattoo/widget_base.o \ + tattoo/widget_credits.o \ + tattoo/widget_files.o \ + tattoo/widget_foolscap.o \ + tattoo/widget_inventory.o \ + tattoo/widget_lab.o \ + tattoo/widget_options.o \ + tattoo/widget_password.o \ + tattoo/widget_quit.o \ + tattoo/widget_talk.o \ + tattoo/widget_text.o \ + tattoo/widget_tooltip.o \ + tattoo/widget_verbs.o \ animation.o \ debugger.o \ detection.o \ events.o \ + fixed_text.o \ + fonts.o \ + image_file.o \ inventory.o \ journal.o \ map.o \ diff --git a/engines/sherlock/music.cpp b/engines/sherlock/music.cpp index 0735f41d9a..7802bf5eeb 100644 --- a/engines/sherlock/music.cpp +++ b/engines/sherlock/music.cpp @@ -20,10 +20,16 @@ * */ +#include "common/algorithm.h" #include "common/config-manager.h" +#include "common/mutex.h" #include "sherlock/sherlock.h" #include "sherlock/music.h" #include "sherlock/scalpel/drivers/mididriver.h" +// for Miles Audio (Sherlock Holmes 2) +#include "audio/miles.h" +// for 3DO digital music +#include "audio/decoders/aiff.h" namespace Sherlock { @@ -57,32 +63,21 @@ MidiParser_SH::MidiParser_SH() { _beats = 0; _lastEvent = 0; _trackEnd = nullptr; + + _musData = nullptr; + _musDataSize = 0; +} + +MidiParser_SH::~MidiParser_SH() { + Common::StackLock lock(_mutex); + unloadMusic(); + _driver = NULL; } void MidiParser_SH::parseNextEvent(EventInfo &info) { -// warning("parseNextEvent"); + Common::StackLock lock(_mutex); - // An attempt to remap MT32 instruments to GMIDI. Only partially successful, it still - // does not sound even close to the real MT32. Oddly enough, on the actual hardware MT32 - // and SB sound very differently. - static const byte mt32Map[128] = { - 0, 1, 0, 2, 4, 4, 5, 3, /* 0-7 */ - 16, 17, 18, 16, 16, 19, 20, 21, /* 8-15 */ - 6, 6, 6, 7, 7, 7, 8, 112, /* 16-23 */ - 62, 62, 63, 63 , 38, 38, 39, 39, /* 24-31 */ - 88, 95, 52, 98, 97, 99, 14, 54, /* 32-39 */ - 102, 96, 53, 102, 81, 100, 14, 80, /* 40-47 */ - 48, 48, 49, 45, 41, 40, 42, 42, /* 48-55 */ - 43, 46, 45, 24, 25, 28, 27, 104, /* 56-63 */ - 32, 32, 34, 33, 36, 37, 35, 35, /* 64-71 */ - 79, 73, 72, 72, 74, 75, 64, 65, /* 72-79 */ - 66, 67, 71, 71, 68, 69, 70, 22, /* 80-87 */ - 56, 59, 57, 57, 60, 60, 58, 61, /* 88-95 */ - 61, 11, 11, 98, 14, 9, 14, 13, /* 96-103 */ - 12, 107, 107, 77, 78, 78, 76, 76, /* 104-111 */ - 47, 117, 127, 118, 118, 116, 115, 119, /* 112-119 */ - 115, 112, 55, 124, 123, 0, 14, 117 /* 120-127 */ - }; +// warning("parseNextEvent"); // there is no delta right at the start of the music data // this order is essential, otherwise notes will get delayed or even go missing @@ -102,8 +97,6 @@ void MidiParser_SH::parseNextEvent(EventInfo &info) { case 0xC: { // program change int idx = *_position._playPos++; info.basic.param1 = idx & 0x7f; - // don't do this here, it breaks adlib - //info.basic.param1 = mt32Map[idx & 0x7f]; // remap MT32 to GM info.basic.param2 = 0; } break; @@ -151,16 +144,16 @@ void MidiParser_SH::parseNextEvent(EventInfo &info) { } } else if (info.event == 0xFC) { // Official End-Of-Track signal - warning("System META event 0xFC"); + debugC(kDebugLevelMusic, "Music: System META event 0xFC"); byte type = *(_position._playPos++); switch (type) { case 0x80: // end of track, triggers looping - warning("META event triggered looping"); + debugC(kDebugLevelMusic, "Music: META event triggered looping"); jumpToTick(0, true, true, false); break; case 0x81: // end of track, stop playing - warning("META event triggered music stop"); + debugC(kDebugLevelMusic, "Music: META event triggered music stop"); stopPlaying(); unloadMusic(); break; @@ -179,20 +172,26 @@ void MidiParser_SH::parseNextEvent(EventInfo &info) { }// switch (info.command()) } -bool MidiParser_SH::loadMusic(byte *data, uint32 size) { - warning("loadMusic"); +bool MidiParser_SH::loadMusic(byte *musData, uint32 musDataSize) { + Common::StackLock lock(_mutex); + + debugC(kDebugLevelMusic, "Music: loadMusic()"); unloadMusic(); - byte *headerPtr = data; - byte *pos = data; + _musData = musData; + _musDataSize = musDataSize; + + byte *headerPtr = _musData + 12; // skip over the already checked SPACE header + byte *pos = headerPtr; + uint16 headerSize = READ_LE_UINT16(headerPtr); - assert(headerSize == 0x7F); + assert(headerSize == 0x7F); // Security check // Skip over header pos += headerSize; _lastEvent = 0; - _trackEnd = data + size; + _trackEnd = _musData + _musDataSize; _numTracks = 1; _tracks[0] = pos; @@ -204,45 +203,152 @@ bool MidiParser_SH::loadMusic(byte *data, uint32 size) { return true; } +void MidiParser_SH::unloadMusic() { + Common::StackLock lock(_mutex); + + if (_musData) { + delete[] _musData; + _musData = NULL; + _musDataSize = 0; + } + + MidiParser::unloadMusic(); +} + /*----------------------------------------------------------------*/ Music::Music(SherlockEngine *vm, Audio::Mixer *mixer) : _vm(vm), _mixer(mixer) { + _midiDriver = NULL; + _midiParser = NULL; + _musicType = MT_NULL; + _musicPlaying = false; + _musicOn = false; + _midiOption = false; + _musicVolume = 0; + + if (IS_3DO) { + // 3DO - uses digital samples for music + _musicOn = true; + return; + } + if (_vm->_interactiveFl) _vm->_res->addToCache("MUSIC.LIB"); - MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM); + MidiDriver::DeviceHandle dev; - _musicType = MidiDriver::getMusicType(dev); + if (IS_SERRATED_SCALPEL) { + // Serrated Scalpel: used an internal Electronic Arts .MUS music engine + _midiParser = new MidiParser_SH(); + dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MT32); + _musicType = MidiDriver::getMusicType(dev); - _driver = NULL; - - switch (_musicType) { - case MT_ADLIB: - _driver = MidiDriver_AdLib_create(); - break; - default: - _driver = MidiDriver::createMidi(dev); - break; + switch (_musicType) { + case MT_ADLIB: + _midiDriver = MidiDriver_SH_AdLib_create(); + break; + case MT_MT32: + _midiDriver = MidiDriver_MT32_create(); + break; + case MT_GM: + if (ConfMan.getBool("native_mt32")) { + _midiDriver = MidiDriver_MT32_create(); + _musicType = MT_MT32; + } + break; + default: + // Create default one + // I guess we shouldn't do this anymore + //_midiDriver = MidiDriver::createMidi(dev); + break; + } + } else { + // Rose Tattooo: seems to use Miles Audio 3 + _midiParser = MidiParser::createParser_XMIDI(); + dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM); + _musicType = MidiDriver::getMusicType(dev); + + switch (_musicType) { + case MT_ADLIB: + // SAMPLE.AD -> regular AdLib instrument data + // SAMPLE.OPL -> OPL-3 instrument data + // although in case of Rose Tattoo both files are exactly the same + _midiDriver = Audio::MidiDriver_Miles_AdLib_create("SAMPLE.AD", "SAMPLE.OPL"); + break; + case MT_MT32: + // Sherlock Holmes 2 does not have a MT32 timbre file + _midiDriver = Audio::MidiDriver_Miles_MT32_create(""); + break; + case MT_GM: + if (ConfMan.getBool("native_mt32")) { + _midiDriver = Audio::MidiDriver_Miles_MT32_create(""); + _musicType = MT_MT32; + } else { + _midiDriver = MidiDriver::createMidi(dev); + _musicType = MT_GM; + } + break; + default: + // Do not create anything + break; + } } - if (_driver) { - assert(_driver); - - int ret = _driver->open(); + if (_midiDriver) { + int ret = _midiDriver->open(); if (ret == 0) { - _driver->sendGMReset(); - _driver->setTimerCallback(&_midiParser, &_midiParser.timerCallback); + // Reset is done inside our MIDI driver + _midiDriver->setTimerCallback(_midiParser, &_midiParser->timerCallback); + } + _midiParser->setMidiDriver(_midiDriver); + _midiParser->setTimerRate(_midiDriver->getBaseTempo()); + + if (IS_SERRATED_SCALPEL) { + if (_musicType == MT_MT32) { + // Upload patches + Common::SeekableReadStream *MT32driverStream = _vm->_res->load("MTHOM.DRV", "MUSIC.LIB"); + + if (!MT32driverStream) + error("Music: could not load MTHOM.DRV, critical"); + + byte *MT32driverData = new byte[MT32driverStream->size()]; + int32 MT32driverDataSize = MT32driverStream->size(); + assert(MT32driverData); + + MT32driverStream->read(MT32driverData, MT32driverDataSize); + delete MT32driverStream; + + assert(MT32driverDataSize > 12); + byte *MT32driverDataPtr = MT32driverData + 12; + MT32driverDataSize -= 12; + + MidiDriver_MT32_uploadPatches(_midiDriver, MT32driverDataPtr, MT32driverDataSize); + delete[] MT32driverData; + } } - _midiParser.setMidiDriver(_driver); - _midiParser.setTimerRate(_driver->getBaseTempo()); + + _musicOn = true; } +} - _musicPlaying = false; - _musicOn = true; +Music::~Music() { + stopMusic(); + if (_midiDriver) { + _midiDriver->setTimerCallback(this, NULL); + } + if (_midiParser) { + _midiParser->stopPlaying(); + delete _midiParser; + _midiParser = nullptr; + } + if (_midiDriver) { + _midiDriver->close(); + delete _midiDriver; + } } bool Music::loadSong(int songNumber) { - warning("loadSong"); + debugC(kDebugLevelMusic, "Music: loadSong()"); if(songNumber == 100) songNumber = 55; @@ -260,21 +366,27 @@ bool Music::loadSong(int songNumber) { if((songNumber > NUM_SONGS) || (songNumber < 1)) return false; - Common::String songName = Common::String(SONG_NAMES[songNumber - 1]) + ".MUS"; + Common::String songName = Common::String(SONG_NAMES[songNumber - 1]); freeSong(); // free any song that is currently loaded - + stopMusic(); + if (!playMusic(songName)) return false; - stopMusic(); startSong(); return true; } bool Music::loadSong(const Common::String &songName) { - warning("TODO: Music::loadSong"); - return false; + freeSong(); // free any song that is currently loaded + stopMusic(); + + if (!playMusic(songName)) + return false; + + startSong(); + return true; } void Music::syncMusicSettings() { @@ -285,76 +397,220 @@ bool Music::playMusic(const Common::String &name) { if (!_musicOn) return false; - warning("Sound::playMusic %s", name.c_str()); - Common::SeekableReadStream *stream = _vm->_res->load(name, "MUSIC.LIB"); + debugC(kDebugLevelMusic, "Music: playMusic('%s')", name.c_str()); - byte *data = new byte[stream->size()]; - int32 dataSize = stream->size(); - assert(data); + if (!IS_3DO) { + // MIDI based + if (!_midiDriver) + return false; - stream->read(data, dataSize); - delete stream; + Common::String midiMusicName = (IS_SERRATED_SCALPEL) ? name + ".MUS" : name + ".XMI"; + Common::SeekableReadStream *stream = _vm->_res->load(midiMusicName, "MUSIC.LIB"); - // for dumping the music tracks -#if 0 - Common::DumpFile outFile; - outFile.open(name + ".RAW"); - outFile.write(data, stream->size()); - outFile.flush(); - outFile.close(); -#endif + byte *midiMusicData = new byte[stream->size()]; + int32 midiMusicDataSize = stream->size(); - if (dataSize < 14) { - warning("not enough data in music file"); - return false; - } + stream->read(midiMusicData, midiMusicDataSize); + delete stream; - byte *dataPos = data; - if (memcmp(" ", dataPos, 12)) { - warning("Expected header not found in music file"); - return false; - } - dataPos += 12; - dataSize -= 12; + if (midiMusicDataSize < 14) { + warning("Music: not enough data in music file"); + delete[] midiMusicData; + return false; + } - uint16 headerSize = READ_LE_UINT16(dataPos); - if (headerSize != 0x7F) { - warning("music header is not as expected"); - return false; - } + byte *dataPos = midiMusicData; + uint32 dataSize = midiMusicDataSize; + + if (IS_SERRATED_SCALPEL) { + if (memcmp(" ", dataPos, 12)) { + warning("Music: expected header not found in music file"); + delete[] midiMusicData; + return false; + } + dataPos += 12; + dataSize -= 12; + + if (dataSize < 0x7F) { + warning("Music: expected music header not found in music file"); + delete[] midiMusicData; + return false; + } + + uint16 headerSize = READ_LE_UINT16(dataPos); + if (headerSize != 0x7F) { + warning("Music: header is not as expected"); + delete[] midiMusicData; + return false; + } + } else { + if (memcmp("FORM", dataPos, 4)) { + warning("Music: expected header not found in music file"); + delete[] midiMusicData; + return false; + } + } + + if (IS_SERRATED_SCALPEL) { + // Pass the music data to the driver as well + // because channel mapping and a few other things inside the header + switch (_musicType) { + case MT_ADLIB: + MidiDriver_SH_AdLib_newMusicData(_midiDriver, dataPos, dataSize); + break; - if (_musicType == MT_ADLIB) { - if (_driver) - MidiDriver_AdLib_newMusicData(_driver, dataPos, dataSize); + case MT_MT32: + MidiDriver_MT32_newMusicData(_midiDriver, dataPos, dataSize); + break; + + default: + // should never happen + break; + } + } + + _midiParser->loadMusic(midiMusicData, midiMusicDataSize); + + } else { + // 3DO: sample based + Audio::AudioStream *musicStream; + Common::String digitalMusicName = "music/" + name + "_MW22.aifc"; + + if (isPlaying()) { + _mixer->stopHandle(_digitalMusicHandle); + } + + Common::File *digitalMusicFile = new Common::File(); + if (!digitalMusicFile->open(digitalMusicName)) { + warning("playMusic: can not open 3DO music '%s'", digitalMusicName.c_str()); + return false; + } + + // Try to load the given file as AIFF/AIFC + musicStream = Audio::makeAIFFStream(digitalMusicFile, DisposeAfterUse::YES); + if (!musicStream) { + warning("playMusic: can not load 3DO music '%s'", digitalMusicName.c_str()); + return false; + } + _mixer->playStream(Audio::Mixer::kMusicSoundType, &_digitalMusicHandle, musicStream); } - _midiParser.loadMusic(dataPos, dataSize); + _musicPlaying = true; return true; } void Music::stopMusic() { - // TODO - warning("TODO: Sound::stopMusic"); + freeSong(); +} + +void Music::startSong() { + // No implementation needed for ScummVM +} + +void Music::freeSong() { + if (!IS_3DO) { + if (_midiParser->isPlaying()) + _midiParser->stopPlaying(); + + // Free the MIDI MUS data buffer + _midiParser->unloadMusic(); + } _musicPlaying = false; } -void Music::startSong() { - if (!_musicOn) - return; +bool Music::isPlaying() { + if (!IS_3DO) { + // MIDI based + return _midiParser->isPlaying(); + } else { + // 3DO: sample based + return _mixer->isSoundHandleActive(_digitalMusicHandle); + } +} - // TODO - warning("TODO: Sound::startSong"); - _musicPlaying = true; +// Returns the current music position in milliseconds +uint32 Music::getCurrentPosition() { + if (!IS_3DO) { + // MIDI based + return (_midiParser->getTick() * 1000) / 60; // translate tick to millisecond + } else { + // 3DO: sample based + return _mixer->getSoundElapsedTime(_digitalMusicHandle); + } } -void Music::freeSong() { - // TODO - warning("TODO: Sound::freeSong"); +// This is used to wait for the music in certain situations like especially the intro +// Note: the original game didn't do this, instead it just waited for certain amounts of time +// We do this, so that the intro graphics + music work together even on faster/slower hardware. +bool Music::waitUntilMSec(uint32 msecTarget, uint32 msecMax, uint32 additionalDelay, uint32 noMusicDelay) { + uint32 msecCurrent = 0; + + if (!isPlaying()) { + return _vm->_events->delay(noMusicDelay, true); + } + while (1) { + if (!isPlaying()) { // Music is not playing anymore -> we are done + if (additionalDelay > 0) { + if (!_vm->_events->delay(additionalDelay, true)) + return false; + } + return true; + } + + msecCurrent = getCurrentPosition(); + //warning("waitUntilMSec: %lx", msecCurrent); + + if ((!msecMax) || (msecCurrent <= msecMax)) { + if (msecCurrent >= msecTarget) { + if (additionalDelay > 0) { + if (!_vm->_events->delay(additionalDelay, true)) + return false; + } + return true; + } + } + if (!_vm->_events->delay(10, true)) + return false; + } } -void Music::waitTimerRoland(uint time) { - // TODO - warning("TODO: Sound::waitTimerRoland"); -}} // End of namespace Sherlock +void Music::setMusicVolume(int volume) { + _musicVolume = volume; + _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, volume); +} + +void Music::getSongNames(Common::StringArray &songs) { + songs.clear(); + if (IS_SERRATED_SCALPEL) { + if (IS_3DO) { + Common::FSDirectory gameDirectory(ConfMan.get("path")); + Common::FSDirectory *musicDirectory = gameDirectory.getSubDirectory("music"); + Common::ArchiveMemberList files; + + musicDirectory->listMatchingMembers(files, "*_mw22.aifc"); + + for (Common::ArchiveMemberList::iterator i = files.begin(); i != files.end(); ++i) { + Common::String name = (*i)->getName(); + name.erase(name.size() - 10); + songs.push_back(name); + } + } else { + for (int i = 0; i < ARRAYSIZE(SONG_NAMES); i++) { + songs.push_back(SONG_NAMES[i]); + } + } + } else { + Common::StringArray fileList; + _vm->_res->getResourceNames("music.lib", fileList); + for (Common::StringArray::iterator i = fileList.begin(); i != fileList.end(); ++i) { + if ((*i).matchString("*.XMI", true)) { + (*i).erase((*i).size() - 4); + songs.push_back(*i); + } + } + } + Common::sort(songs.begin(), songs.end()); +} +} // End of namespace Sherlock diff --git a/engines/sherlock/music.h b/engines/sherlock/music.h index 0ebc3e94e2..afd3a429be 100644 --- a/engines/sherlock/music.h +++ b/engines/sherlock/music.h @@ -27,40 +27,61 @@ #include "audio/midiparser.h" //#include "audio/mididrv.h" #include "sherlock/scalpel/drivers/mididriver.h" +// for 3DO digital music +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "common/mutex.h" +#include "common/str-array.h" namespace Sherlock { class SherlockEngine; class MidiParser_SH : public MidiParser { +public: + MidiParser_SH(); + ~MidiParser_SH(); + protected: - virtual void parseNextEvent(EventInfo &info); + Common::Mutex _mutex; + void parseNextEvent(EventInfo &info); uint8 _beats; uint8 _lastEvent; byte *_data; byte *_trackEnd; + public: - MidiParser_SH(); - virtual bool loadMusic(byte *data, uint32 size); + bool loadMusic(byte *musData, uint32 musSize); + virtual void unloadMusic(); + +private: + byte *_musData; + uint32 _musDataSize; }; class Music { private: SherlockEngine *_vm; Audio::Mixer *_mixer; - MidiParser_SH _midiParser; - MidiDriver *_driver; - + MidiParser *_midiParser; + MidiDriver *_midiDriver; + Audio::SoundHandle _digitalMusicHandle; + MusicType _musicType; + + /** + * Play the specified music resource + */ + bool playMusic(const Common::String &name); public: bool _musicPlaying; bool _musicOn; - -private: - MusicType _musicType; - + int _musicVolume; + bool _midiOption; + Common::String _currentSongName, _nextSongName; public: Music(SherlockEngine *vm, Audio::Mixer *mixer); + ~Music(); /** * Saves sound-related settings @@ -86,21 +107,28 @@ public: * Free any currently loaded song */ void freeSong(); - - /** - * Play the specified music resource - */ - bool playMusic(const Common::String &name); /** * Stop playing the music */ void stopMusic(); - void waitTimerRoland(uint time); + bool isPlaying(); + uint32 getCurrentPosition(); + + bool waitUntilMSec(uint32 msecTarget, uint32 maxMSec, uint32 additionalDelay, uint32 noMusicDelay); + + /** + * Sets the volume of the MIDI music with a value ranging from 0 to 127 + */ + void setMusicVolume(int volume); + + /** + * Gets the names of all the songs in the game. Used by the debugger. + */ + void getSongNames(Common::StringArray &songs); }; } // End of namespace Sherlock #endif - diff --git a/engines/sherlock/objects.cpp b/engines/sherlock/objects.cpp index 8818f805a5..093f666a46 100644 --- a/engines/sherlock/objects.cpp +++ b/engines/sherlock/objects.cpp @@ -20,608 +20,135 @@ * */ +#include "common/util.h" #include "sherlock/objects.h" -#include "sherlock/sherlock.h" #include "sherlock/people.h" #include "sherlock/scene.h" -#include "common/util.h" +#include "sherlock/scalpel/scalpel.h" +#include "sherlock/scalpel/scalpel_map.h" +#include "sherlock/scalpel/scalpel_people.h" +#include "sherlock/tattoo/tattoo.h" namespace Sherlock { #define START_FRAME 0 -#define UPPER_LIMIT 0 -#define LOWER_LIMIT CONTROLS_Y -#define LEFT_LIMIT 0 -#define RIGHT_LIMIT SHERLOCK_SCREEN_WIDTH +#define NUM_ADJUSTED_WALKS 21 // Distance to walk around WALK_AROUND boxes #define CLEAR_DIST_X 5 #define CLEAR_DIST_Y 0 -SherlockEngine *Sprite::_vm; - -void Sprite::clear() { - _name = ""; - _description = ""; - _examine.clear(); - _pickUp = ""; - _walkSequences.clear(); - _images = nullptr; - _imageFrame = nullptr; - _walkCount = 0; - _allow = 0; - _frameNumber = _sequenceNumber = 0; - _position.x = _position.y = 0; - _delta.x = _delta.y = 0; - _oldPosition.x = _oldPosition.y = 0; - _oldSize.x = _oldSize.y = 0; - _goto.x = _goto.y = 0; - _type = INVALID; - _pickUp.clear(); - _noShapeSize.x = _noShapeSize.y = 0; - _status = 0; - _misc = 0; - _numFrames = 0; - _altImages = nullptr; - _altSequences = false; - Common::fill(&_stopFrames[0], &_stopFrames[8], (ImageFrame *)nullptr); -} - -void Sprite::setImageFrame() { - int frameNum = MAX(_frameNumber, 0); - int imageNumber = _walkSequences[_sequenceNumber][frameNum]; - - if (IS_SERRATED_SCALPEL) - imageNumber = imageNumber + _walkSequences[_sequenceNumber][0] - 2; - else if (imageNumber > _numFrames) - imageNumber = 1; - - // Get the images to use - ImageFile *images = _altSequences ? _altImages : _images; - assert(images); - - // Set the frame pointer - _imageFrame = &(*images)[imageNumber]; -} - -void Sprite::adjustSprite() { - Map &map = *_vm->_map; - People &people = *_vm->_people; - Scene &scene = *_vm->_scene; - Talk &talk = *_vm->_talk; - - if (_type == INVALID || (_type == CHARACTER && scene._animating)) - return; - - if (!talk._talkCounter && _type == CHARACTER && _walkCount) { - // Handle active movement for the sprite - _position += _delta; - --_walkCount; - - if (!_walkCount) { - // If there any points left for the character to walk to along the - // route to a destination, then move to the next point - if (!people._walkTo.empty()) { - people._walkDest = people._walkTo.pop(); - people.setWalking(); - } else { - people.gotoStand(*this); - } - } - } - - if (_type == CHARACTER && !map._active) { - if ((_position.y / 100) > LOWER_LIMIT) { - _position.y = LOWER_LIMIT * 100; - people.gotoStand(*this); - } - - if ((_position.y / 100) < UPPER_LIMIT) { - _position.y = UPPER_LIMIT * 100; - people.gotoStand(*this); - } - - if ((_position.x / 100) < LEFT_LIMIT) { - _position.x = LEFT_LIMIT * 100; - people.gotoStand(*this); - } - } else if (!map._active) { - _position.y = CLIP((int)_position.y, (int)UPPER_LIMIT, (int)LOWER_LIMIT); - _position.x = CLIP((int)_position.x, (int)LEFT_LIMIT, (int)RIGHT_LIMIT); - } - - if (!map._active || (map._frameChangeFlag = !map._frameChangeFlag)) - ++_frameNumber; - - if (_walkSequences[_sequenceNumber][_frameNumber] == 0) { - switch (_sequenceNumber) { - case STOP_UP: - case STOP_DOWN: - case STOP_LEFT: - case STOP_RIGHT: - case STOP_UPRIGHT: - case STOP_UPLEFT: - case STOP_DOWNRIGHT: - case STOP_DOWNLEFT: - // We're in a stop sequence, so reset back to the last frame, so - // the character is shown as standing still - --_frameNumber; - break; - - default: - // Move 1 past the first frame - we need to compensate, since we - // already passed the frame increment - _frameNumber = 1; - break; - } - } - - // Update the _imageFrame to point to the new frame's image - setImageFrame(); +#define ADJUST_COORD(COORD) \ + if (COORD.x != -1) \ + COORD.x *= FIXED_INT_MULTIPLIER; \ + if (COORD.y != -1) \ + COORD.y *= FIXED_INT_MULTIPLIER - // Check to see if character has entered an exit zone - if (!_walkCount && scene._walkedInScene && scene._goToScene == -1) { - Common::Rect charRect(_position.x / 100 - 5, _position.y / 100 - 2, - _position.x / 100 + 5, _position.y / 100 + 2); - Exit *exit = scene.checkForExit(charRect); - - if (exit) { - scene._goToScene = exit->_scene; - - if (exit->_people.x != 0) { - people._hSavedPos = exit->_people; - people._hSavedFacing = exit->_peopleDir; - - if (people._hSavedFacing > 100 && people._hSavedPos.x < 1) - people._hSavedPos.x = 100; - } - } - } -} - -void Sprite::checkSprite() { - Events &events = *_vm->_events; - People &people = *_vm->_people; - Scene &scene = *_vm->_scene; - Screen &screen = *_vm->_screen; - Talk &talk = *_vm->_talk; - Point32 pt; - Common::Rect objBounds; - Common::Point spritePt(_position.x / 100, _position.y / 100); - - if (!talk._talkCounter && _type == CHARACTER) { - pt = _walkCount ? _position + _delta : _position; - pt.x /= 100; - pt.y /= 100; - - for (uint idx = 0; idx < scene._bgShapes.size() && !talk._talkToAbort; ++idx) { - Object &obj = scene._bgShapes[idx]; - if (obj._aType <= PERSON || obj._type == INVALID || obj._type == HIDDEN) - continue; - - if (obj._type == NO_SHAPE) { - objBounds = Common::Rect(obj._position.x, obj._position.y, - obj._position.x + obj._noShapeSize.x + 1, obj._position.y + obj._noShapeSize.y + 1); - } else { - int xp = obj._position.x + obj._imageFrame->_offset.x; - int yp = obj._position.y + obj._imageFrame->_offset.y; - objBounds = Common::Rect(xp, yp, - xp + obj._imageFrame->_frame.w + 1, yp + obj._imageFrame->_frame.h + 1); - } - - if (objBounds.contains(pt)) { - if (objBounds.contains(spritePt)) { - // Current point is already inside the the bounds, so impact occurred - // on a previous call. So simply do nothing until we're clear of the box - switch (obj._aType) { - case TALK_MOVE: - if (_walkCount) { - // Holmes is moving - obj._type = HIDDEN; - obj.setFlagsAndToggles(); - talk.talkTo(obj._use[0]._target); - } - break; - - case PAL_CHANGE: - case PAL_CHANGE2: - if (_walkCount) { - int palStart = atoi(obj._use[0]._names[0].c_str()) * 3; - int palLength = atoi(obj._use[0]._names[1].c_str()) * 3; - int templ = atoi(obj._use[0]._names[2].c_str()) * 3; - if (templ == 0) - templ = 100; - - // Ensure only valid palette change data found - if (palLength > 0) { - // Figure out how far into the shape Holmes is so that we - // can figure out what percentage of the original palette - // to set the current palette to - int palPercent = (pt.x - objBounds.left) * 100 / objBounds.width(); - palPercent = palPercent * templ / 100; - if (obj._aType == PAL_CHANGE) - // Invert percentage - palPercent = 100 - palPercent; - - for (int i = palStart; i < (palStart + palLength); ++i) - screen._sMap[i] = screen._cMap[i] * palPercent / 100; - - events.pollEvents(); - screen.setPalette(screen._sMap); - } - } - break; - - case TALK: - case TALK_EVERY: - obj._type = HIDDEN; - obj.setFlagsAndToggles(); - talk.talkTo(obj._use[0]._target); - break; - - default: - break; - } - } else { - // New impact just occurred - switch (obj._aType) { - case BLANK_ZONE: - // A blank zone masks out all other remaining zones underneath it. - // If this zone is hit, exit the outer loop so we do not check anymore - return; - - case SOLID: - case TALK: - // Stop walking - if (obj._aType == TALK) { - obj.setFlagsAndToggles(); - talk.talkTo(obj._use[0]._target); - } else { - people.gotoStand(*this); - } - break; - - case TALK_EVERY: - if (obj._aType == TALK_EVERY) { - obj._type = HIDDEN; - obj.setFlagsAndToggles(); - talk.talkTo(obj._use[0]._target); - } else { - people.gotoStand(*this); - } - break; - - case FLAG_SET: - obj.setFlagsAndToggles(); - obj._type = HIDDEN; - break; - - case WALK_AROUND: - if (objBounds.contains(people._walkTo.front())) { - // Reached zone - people.gotoStand(*this); - } else { - // Destination not within box, walk to best corner - Common::Point walkPos; - - if (spritePt.x >= objBounds.left && spritePt.x < objBounds.right) { - // Impact occurred due to vertical movement. Determine whether to - // travel to the left or right side - if (_delta.x > 0) - // Go to right side - walkPos.x = objBounds.right + CLEAR_DIST_X; - else if (_delta.x < 0) { - // Go to left side - walkPos.x = objBounds.left - CLEAR_DIST_X; - } else { - // Going straight up or down. So choose best side - if (spritePt.x >= (objBounds.left + objBounds.width() / 2)) - walkPos.x = objBounds.right + CLEAR_DIST_X; - else - walkPos.x = objBounds.left - CLEAR_DIST_X; - } - - walkPos.y = (_delta.y >= 0) ? objBounds.top - CLEAR_DIST_Y : - objBounds.bottom + CLEAR_DIST_Y; - } else { - // Impact occurred due to horizontal movement - if (_delta.y > 0) - // Go to bottom of box - walkPos.y = objBounds.bottom + CLEAR_DIST_Y; - else if (_delta.y < 0) - // Go to top of box - walkPos.y = objBounds.top - CLEAR_DIST_Y; - else { - // Going straight horizontal, so choose best side - if (spritePt.y >= (objBounds.top + objBounds.height() / 2)) - walkPos.y = objBounds.bottom + CLEAR_DIST_Y; - else - walkPos.y = objBounds.top - CLEAR_DIST_Y; - } - - walkPos.x = (_delta.x >= 0) ? objBounds.left - CLEAR_DIST_X : - objBounds.right + CLEAR_DIST_X; - } - - walkPos.x += people[AL]._imageFrame->_frame.w / 2; - people._walkDest = walkPos; - people._walkTo.push(walkPos); - people.setWalking(); - } - break; - - case DELTA: - _position.x += 200; - break; - - default: - break; - } - } - } - } - } -} +SherlockEngine *BaseObject::_vm; +bool BaseObject::_countCAnimFrames; /*----------------------------------------------------------------*/ -void WalkSequence::load(Common::SeekableReadStream &s) { - char buffer[9]; - s.read(buffer, 9); - _vgsName = Common::String(buffer); - _horizFlip = s.readByte() != 0; - - _sequences.resize(s.readUint16LE()); - s.read(&_sequences[0], _sequences.size()); -} - -/*----------------------------------------------------------------*/ - -WalkSequences &WalkSequences::operator=(const WalkSequences &src) { - resize(src.size()); - for (uint idx = 0; idx < size(); ++idx) { - const WalkSequence &wSrc = src[idx]; - WalkSequence &wDest = (*this)[idx]; - wDest._horizFlip = wSrc._horizFlip; - - wDest._sequences.resize(wSrc._sequences.size()); - Common::copy(&wSrc._sequences[0], &wSrc._sequences[0] + wSrc._sequences.size(), &wDest._sequences[0]); - } - - return *this; -} - -/*----------------------------------------------------------------*/ - -void ActionType::load(Common::SeekableReadStream &s) { - char buffer[12]; - - _cAnimNum = s.readByte(); - _cAnimSpeed = s.readByte(); - if (_cAnimSpeed & 0x80) - _cAnimSpeed = -(_cAnimSpeed & 0x7f); - - for (int idx = 0; idx < NAMES_COUNT; ++idx) { - s.read(buffer, 12); - _names[idx] = Common::String(buffer); - } -} - -/*----------------------------------------------------------------*/ - -UseType::UseType() { - _cAnimNum = _cAnimSpeed = 0; - _useFlag = 0; -} - -void UseType::load(Common::SeekableReadStream &s, bool isRoseTattoo) { - char buffer[12]; - - if (isRoseTattoo) { - s.read(buffer, 12); - _verb = Common::String(buffer); - } - - _cAnimNum = s.readByte(); - _cAnimSpeed = s.readByte(); - if (_cAnimSpeed & 0x80) - _cAnimSpeed = -(_cAnimSpeed & 0x7f); - - for (int idx = 0; idx < NAMES_COUNT; ++idx) { - s.read(buffer, 12); - _names[idx] = Common::String(buffer); - } - - _useFlag = s.readSint16LE(); - - if (!isRoseTattoo) - s.skip(6); - - s.read(buffer, 12); - _target = Common::String(buffer); -} - -/*----------------------------------------------------------------*/ - -SherlockEngine *Object::_vm; -bool Object::_countCAnimFrames; - -void Object::setVm(SherlockEngine *vm) { +void BaseObject::setVm(SherlockEngine *vm) { _vm = vm; _countCAnimFrames = false; } -Object::Object() { - _sequenceOffset = 0; +BaseObject::BaseObject() { + _type = INVALID; _sequences = nullptr; _images = nullptr; _imageFrame = nullptr; + _sequenceNumber = 0; + _startSeq = 0; _walkCount = 0; _allow = 0; _frameNumber = 0; - _sequenceNumber = 0; - _type = INVALID; - _pickup = 0; - _defaultCommand = 0; _lookFlag = 0; - _pickupFlag = 0; - _requiredFlag = 0; + _requiredFlag[0] = _requiredFlag[1] = 0; _status = 0; _misc = 0; _maxFrames = 0; _flags = 0; - _aOpen._cAnimNum = 0; - _aOpen._cAnimSpeed = 0; _aType = OBJECT; _lookFrames = 0; _seqCounter = 0; - _lookFacing = 0; _lookcAnim = 0; _seqStack = 0; _seqTo = 0; _descOffset = 0; _seqCounter2 = 0; _seqSize = 0; - _quickDraw = 0; _scaleVal = 0; - _requiredFlag1 = 0; _gotoSeq = 0; _talkSeq = 0; _restoreSlot = 0; } -void Object::load(Common::SeekableReadStream &s, bool isRoseTattoo) { - char buffer[41]; - s.read(buffer, 12); - _name = Common::String(buffer); - s.read(buffer, 41); - _description = Common::String(buffer); +bool BaseObject::hasAborts() const { + int seqNum = _talkSeq; - _examine.clear(); - _sequences = nullptr; - _images = nullptr; - _imageFrame = nullptr; - - s.skip(4); - _sequenceOffset = s.readUint16LE(); - s.seek(10, SEEK_CUR); - - _walkCount = s.readByte(); - _allow = s.readByte(); - _frameNumber = s.readSint16LE(); - _sequenceNumber = s.readSint16LE(); - _position.x = s.readSint16LE(); - _position.y = s.readSint16LE(); - _delta.x = s.readSint16LE(); - _delta.y = s.readSint16LE(); - _type = (SpriteType)s.readUint16LE(); - _oldPosition.x = s.readSint16LE(); - _oldPosition.y = s.readSint16LE(); - _oldSize.x = s.readUint16LE(); - _oldSize.y = s.readUint16LE(); - _goto.x = s.readSint16LE(); - _goto.y = s.readSint16LE(); + // See if the object is in its regular sequence + bool startChecking = !seqNum || _type == CHARACTER; - _pickup = isRoseTattoo ? 0 : s.readByte(); - _defaultCommand = isRoseTattoo ? 0 : s.readByte(); - _lookFlag = s.readSint16LE(); - _pickupFlag = isRoseTattoo ? 0 : s.readSint16LE(); - _requiredFlag = s.readSint16LE(); - _noShapeSize.x = s.readUint16LE(); - _noShapeSize.y = s.readUint16LE(); - _status = s.readUint16LE(); - _misc = s.readByte(); - _maxFrames = s.readUint16LE(); - _flags = s.readByte(); - - if (!isRoseTattoo) - _aOpen.load(s); - - _aType = (AType)s.readByte(); - _lookFrames = s.readByte(); - _seqCounter = s.readByte(); - _lookPosition.x = s.readUint16LE(); - _lookPosition.y = isRoseTattoo ? s.readSint16LE() : s.readByte(); - _lookFacing = s.readByte(); - _lookcAnim = s.readByte(); - - if (!isRoseTattoo) - _aClose.load(s); - - _seqStack = s.readByte(); - _seqTo = s.readByte(); - _descOffset = s.readUint16LE(); - _seqCounter2 = s.readByte(); - _seqSize = s.readUint16LE(); - - if (isRoseTattoo) { - for (int idx = 0; idx < 6; ++idx) - _use[idx].load(s, true); - - _quickDraw = s.readByte(); - _scaleVal = s.readUint16LE(); - _requiredFlag1 = s.readSint16LE(); - _gotoSeq = s.readByte(); - _talkSeq = s.readByte(); - _restoreSlot = s.readByte(); - } else { - s.skip(1); - _aMove.load(s); - s.skip(8); - - for (int idx = 0; idx < 4; ++idx) - _use[idx].load(s, false); - } -} - -void Object::toggleHidden() { - if (_type != HIDDEN && _type != HIDE_SHAPE && _type != INVALID) { - if (_seqTo != 0) - _sequences[_frameNumber] = _seqTo + SEQ_TO_CODE + 128; - _seqTo = 0; + uint idx = 0; + do + { + // Get the Frame value + int v = _sequences[idx++]; - if (_images == nullptr || _images->size() == 0) - // No shape to erase, so flag as hidden - _type = HIDDEN; - else - // Otherwise, flag it to be hidden after it gets erased - _type = HIDE_SHAPE; - } else if (_type != INVALID) { - if (_seqTo != 0) - _sequences[_frameNumber] = _seqTo + SEQ_TO_CODE + 128; - _seqTo = 0; + // See if we found an Allow Talk Interrupt Code + if (startChecking && v == ALLOW_TALK_CODE) + return true; - _seqCounter = _seqCounter2 = 0; - _seqStack = 0; - _frameNumber = -1; + // If we've started checking and we've encountered another Talk or Listen Sequence Code, + // then we're done checking this sequence because this is where it would repeat + if (startChecking && (v == TALK_SEQ_CODE || v == TALK_LISTEN_CODE)) + break; - if (_images == nullptr || _images->size() == 0) { - _type = NO_SHAPE; + // See if we've found the beginning of a Talk Sequence + if ((v == TALK_SEQ_CODE && seqNum < 128) || (v == TALK_LISTEN_CODE && seqNum >= 128)) { + // If checking was already on and we came across one of these codes, then there couldn't + // have been an Allow Talk Interrupt code in the sequence we were checking, so we're done. + if (startChecking) + break; + + seqNum--; + // See if we're at the correct Talk Sequence Number + if (!(seqNum & 127)) + { + // Correct Sequence, Start Checking Now + startChecking = true; + } } else { - _type = ACTIVE_BG_SHAPE; - int idx = _sequences[0]; - if (idx >= _maxFrames) - // Turn on: set up first frame - idx = 0; - - _imageFrame = &(*_images)[idx]; + // Move ahead any extra because of special control codes + switch (v) { + case 0: idx++; break; + case MOVE_CODE: + case TELEPORT_CODE: idx += 4; break; + case CALL_TALK_CODE:idx += 8; break; + case HIDE_CODE: idx += 2; break; + } } - } + } while (idx < _seqSize); + + return false; } -void Object::checkObject() { +void BaseObject::checkObject() { Scene &scene = *_vm->_scene; Sound &sound = *_vm->_sound; + Talk &talk = *_vm->_talk; int checkFrame = _allow ? MAX_FRAME : FRAMES_END; bool codeFound; if (_seqTo) { byte *ptr = &_sequences[_frameNumber]; if (*ptr == _seqTo) { - // The sequence is completed - *ptr = _seqTo + SEQ_TO_CODE + 128; // Reset to normal + // The sequence is completed. Reset to normal + *ptr = _seqTo + (IS_ROSE_TATTOO ? 0 : SEQ_TO_CODE + 128); _seqTo = 0; } else { // Continue doing sequence @@ -637,6 +164,11 @@ void Object::checkObject() { ++_frameNumber; do { + if (!_sequences) { + warning("checkObject: _sequences is not set"); + break; + } + // Check for end of sequence codeFound = checkEndOfSequence(); @@ -644,20 +176,33 @@ void Object::checkObject() { codeFound = true; int v = _sequences[_frameNumber]; - if (v >= 228) { + // Check for a Talk or Listen Sequence + if (IS_ROSE_TATTOO && v == ALLOW_TALK_CODE) { + if (_gotoSeq) { + setObjTalkSequence(_gotoSeq); + _gotoSeq = 0; + } else { + ++_frameNumber; + } + } else if (IS_ROSE_TATTOO && (v == TALK_SEQ_CODE || v == TALK_LISTEN_CODE)) { + if (_talkSeq) + setObjTalkSequence(_talkSeq); + else + setObjSequence(0, false); + } else if (v >= GOTO_CODE) { // Goto code found - v -= 228; + v -= GOTO_CODE; _seqCounter2 = _seqCounter; _seqStack = _frameNumber + 1; setObjSequence(v, false); } else if (v >= SOUND_CODE && (v < (SOUND_CODE + 30))) { codeFound = true; ++_frameNumber; - v -= SOUND_CODE; + v -= SOUND_CODE + (IS_SERRATED_SCALPEL ? 1 : 0); if (sound._soundOn && !_countCAnimFrames) { - if (!scene._sounds[v - 1]._name.empty() && sound._digitized) - sound.playLoadedSound(v - 1, WAIT_RETURN_IMMEDIATELY); + if (!scene._sounds[v]._name.empty() && sound._digitized) + sound.playLoadedSound(v, WAIT_RETURN_IMMEDIATELY); } } else if (v >= FLIP_CODE && v < (FLIP_CODE + 3)) { // Flip code @@ -682,28 +227,90 @@ void Object::checkObject() { default: break; } + } else if (IS_ROSE_TATTOO && v == TELEPORT_CODE) { + _position.x = READ_LE_UINT16(&_sequences[_frameNumber + 1]); + _position.y = READ_LE_UINT16(&_sequences[_frameNumber + 3]); + + _frameNumber += 5; + } else if (IS_ROSE_TATTOO && v == CALL_TALK_CODE) { + Common::String filename; + for (int idx = 0; idx < 8; ++idx) { + if (_sequences[_frameNumber + 1 + idx] != 1) + filename += (char)_sequences[_frameNumber + 1 + idx]; + else + break; + } + + _frameNumber += 8; + talk.talkTo(filename); + + } else if (IS_ROSE_TATTOO && v == HIDE_CODE) { + switch (_sequences[_frameNumber + 2]) { + case 1: + // Hide Object + if (scene._bgShapes[_sequences[_frameNumber + 1] - 1]._type != HIDDEN) + scene._bgShapes[_sequences[_frameNumber + 1] - 1].toggleHidden(); + break; + + case 2: + // Activate Object + if (scene._bgShapes[_sequences[_frameNumber + 1] - 1]._type == HIDDEN) + scene._bgShapes[_sequences[_frameNumber + 1] - 1].toggleHidden(); + break; + + case 3: + // Toggle Object + scene._bgShapes[_sequences[_frameNumber + 1] - 1].toggleHidden(); + break; + + default: + break; + } + _frameNumber += 3; + } else { v -= 128; - // 68-99 is a squence code + // 68-99 is a sequence code if (v > SEQ_TO_CODE) { - byte *p = &_sequences[_frameNumber]; - v -= SEQ_TO_CODE; // # from 1-32 - _seqTo = v; - *p = *(p - 1); - - if (*p > 128) - // If the high bit is set, convert to a real frame - *p -= (byte)(SEQ_TO_CODE - 128); - - if (*p > _seqTo) - *p -= 1; - else - *p += 1; + if (IS_ROSE_TATTOO) { + ++_frameNumber; + byte *p = &_sequences[_frameNumber]; + _seqTo = *p; + *p = *(p - 2); + + if (*p > _seqTo) + *p -= 1; + else + *p += 1; + + --_frameNumber; + } else { + byte *p = &_sequences[_frameNumber]; + v -= SEQ_TO_CODE; // # from 1-32 + _seqTo = v; + *p = *(p - 1); + + if (*p > 128) + // If the high bit is set, convert to a real frame + *p -= (byte)(SEQ_TO_CODE - 128); + + if (*p > _seqTo) + *p -= 1; + else + *p += 1; + + // Will be incremented below to return back to original value + --_frameNumber; + v = 0; + } + } else if (IS_ROSE_TATTOO && v == 10) { + // Set delta for objects + _delta = Common::Point(READ_LE_UINT16(&_sequences[_frameNumber + 1]), + READ_LE_UINT16(&_sequences[_frameNumber + 3])); + _noShapeSize = Common::Point(0, 0); + _frameNumber += 4; - // Will be incremented below to return back to original value - --_frameNumber; - v = 0; } else if (v == 10) { // Set delta for objects Common::Point pt(_sequences[_frameNumber + 1], _sequences[_frameNumber + 2]); @@ -719,9 +326,10 @@ void Object::checkObject() { _delta = pt; _frameNumber += 2; + } else if (v < USE_COUNT) { for (int idx = 0; idx < NAMES_COUNT; ++idx) { - checkNameForCodes(_use[v]._names[idx], nullptr); + checkNameForCodes(_use[v]._names[idx]); } if (_use[v]._useFlag) @@ -734,7 +342,7 @@ void Object::checkObject() { } while (codeFound); } -bool Object::checkEndOfSequence() { +bool BaseObject::checkEndOfSequence() { Screen &screen = *_vm->_screen; int checkFrame = _allow ? MAX_FRAME : FRAMES_END; bool result = false; @@ -742,20 +350,26 @@ bool Object::checkEndOfSequence() { if (_type == REMOVE || _type == INVALID) return false; - if (_sequences[_frameNumber] == 0 || _frameNumber >= checkFrame) { + if (_frameNumber < 0 || _frameNumber >= checkFrame || _sequences[_frameNumber] == 0) { result = true; - if (_frameNumber >= (checkFrame - 1)) { + if (_frameNumber < 0 || _frameNumber >= (checkFrame - 1)) { _frameNumber = START_FRAME; } else { // Determine next sequence to use int seq = _sequences[_frameNumber + 1]; + // If the object has been turned off, we're going nowhere + if (IS_ROSE_TATTOO && (_type == HIDE_SHAPE || _type == HIDDEN || _type == REMOVE)) + return false; + if (seq == 99) { --_frameNumber; screen._backBuffer1.transBlitFrom(*_imageFrame, _position); screen._backBuffer2.transBlitFrom(*_imageFrame, _position); _type = INVALID; + } else if (IS_ROSE_TATTOO && _talkSeq && seq == 0) { + setObjTalkSequence(_talkSeq); } else { setObjSequence(seq, false); } @@ -786,10 +400,14 @@ bool Object::checkEndOfSequence() { return result; } -void Object::setObjSequence(int seq, bool wait) { +void BaseObject::setObjSequence(int seq, bool wait) { Scene &scene = *_vm->_scene; int checkFrame = _allow ? MAX_FRAME : FRAMES_END; + if (IS_ROSE_TATTOO && (seq == -1 || seq == 255)) + // This means goto beginning + seq = 0; + if (seq >= 128) { // Loop the sequence until the count exceeded seq -= 128; @@ -812,6 +430,10 @@ void Object::setObjSequence(int seq, bool wait) { if (_frameNumber >= checkFrame) _frameNumber = 0; + // For Rose Tattoo, save the starting frame for new sequences + if (IS_ROSE_TATTOO) + _startSeq = _frameNumber; + _seqCounter = 0; if (_sequences[_frameNumber] == 0) seq = _sequences[_frameNumber + 1]; @@ -819,12 +441,18 @@ void Object::setObjSequence(int seq, bool wait) { return; } else { // Find beginning of sequence - do { - --_frameNumber; - } while (_frameNumber > 0 && _sequences[_frameNumber] != 0); + if (IS_ROSE_TATTOO) { + // Use the saved start of the sequence to reset the frame + _frameNumber = _startSeq; + } else { + // For Scalpel, scan backwards from the end of the sequence to find its start + do { + --_frameNumber; + } while (_frameNumber > 0 && _sequences[_frameNumber] != 0); - if (_frameNumber != 0) - _frameNumber += 2; + if (_frameNumber != 0) + _frameNumber += 2; + } return; } @@ -837,16 +465,35 @@ void Object::setObjSequence(int seq, bool wait) { int seqCc = 0; while (seqCc < seq && idx < checkFrame) { - ++idx; - if (_sequences[idx] == 0) { - ++seqCc; - idx += 2; + if (IS_SERRATED_SCALPEL) { + ++idx; + + if (_sequences[idx] == 0) { + ++seqCc; + idx += 2; + } + } else { + byte s = _sequences[idx]; + + if (s == 0) { + ++seqCc; + ++idx; + } else if (s == MOVE_CODE || s == TELEPORT_CODE) { + idx += 4; + } else if (s == CALL_TALK_CODE) { + idx += 8; + } else if (s == HIDE_CODE) { + idx += 2; + } + + ++idx; } } if (idx >= checkFrame) idx = 0; _frameNumber = idx; + _startSeq = idx; if (wait) { seqCc = idx; @@ -859,8 +506,8 @@ void Object::setObjSequence(int seq, bool wait) { } } -int Object::checkNameForCodes(const Common::String &name, const char *const messages[]) { - Map &map = *_vm->_map; +int BaseObject::checkNameForCodes(const Common::String &name, FixedTextActionId fixedTextActionId) { + FixedText &fixedText = *_vm->_fixedText; People &people = *_vm->_people; Scene &scene = *_vm->_scene; Screen &screen = *_vm->_screen; @@ -901,13 +548,20 @@ int Object::checkNameForCodes(const Common::String &name, const char *const mess break; } + case 'V': + // Do nothing for Verb codes. This is only a flag for Inventory syntax + break; + default: if (ch >= '0' && ch <= '9') { scene._goToScene = atoi(name.c_str() + 1); - if (scene._goToScene < 97 && map[scene._goToScene].x) { - map._overPos.x = map[scene._goToScene].x * 100 - 600; - map._overPos.y = map[scene._goToScene].y * 100 + 900; + if (IS_SERRATED_SCALPEL && scene._goToScene < 97) { + Scalpel::ScalpelMap &map = *(Scalpel::ScalpelMap *)_vm->_map; + if (map[scene._goToScene].x) { + map._overPos.x = (map[scene._goToScene].x - 6) * FIXED_INT_MULTIPLIER; + map._overPos.y = (map[scene._goToScene].y + 9) * FIXED_INT_MULTIPLIER; + } } const char *p; @@ -915,24 +569,23 @@ int Object::checkNameForCodes(const Common::String &name, const char *const mess ++p; Common::String s(p, p + 3); - people._hSavedPos.x = atoi(s.c_str()); + people._savedPos.x = atoi(s.c_str()); s = Common::String(p + 3, p + 6); - people._hSavedPos.y = atoi(s.c_str()); + people._savedPos.y = atoi(s.c_str()); s = Common::String(p + 6, p + 9); - people._hSavedFacing = atoi(s.c_str()); - if (people._hSavedFacing == 0) - people._hSavedFacing = 10; + people._savedPos._facing = atoi(s.c_str()); + if (people._savedPos._facing == 0) + people._savedPos._facing = 10; } else if ((p = strchr(name.c_str(), '/')) != nullptr) { - people._hSavedPos = Common::Point(1, 0); - people._hSavedFacing = 100 + atoi(p + 1); + people._savedPos = PositionFacing(1, 0, 100 + atoi(p + 1)); } } else { scene._goToScene = 100; } - people[AL]._position = Common::Point(0, 0); + people[HOLMES]._position = Point32(0, 0); break; } } else if (name.hasPrefix("!")) { @@ -940,13 +593,14 @@ int Object::checkNameForCodes(const Common::String &name, const char *const mess int messageNum = atoi(name.c_str() + 1); ui._infoFlag = true; ui.clearInfo(); - screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", messages[messageNum]); + Common::String errorMessage = fixedText.getActionMessage(fixedTextActionId, messageNum); + screen.print(Common::Point(0, INFO_LINE + 1), COL_INFO_FOREGROUND, "%s", errorMessage.c_str()); ui._menuCounter = 25; } else if (name.hasPrefix("@")) { // Message attached to canimation ui._infoFlag = true; ui.clearInfo(); - screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", name.c_str() + 1); + screen.print(Common::Point(0, INFO_LINE + 1), COL_INFO_FOREGROUND, "%s", name.c_str() + 1); printed = true; ui._menuCounter = 25; } @@ -954,6 +608,679 @@ int Object::checkNameForCodes(const Common::String &name, const char *const mess return printed; } +/*----------------------------------------------------------------*/ + +void Sprite::clear() { + _name = ""; + _description = ""; + _examine.clear(); + _pickUp = ""; + _walkSequences.clear(); + _sequences = nullptr; + _images = nullptr; + _imageFrame = nullptr; + _walkCount = 0; + _allow = 0; + _frameNumber = 0; + _position.x = _position.y = 0; + _delta.x = _delta.y = 0; + _oldPosition.x = _oldPosition.y = 0; + _oldSize.x = _oldSize.y = 0; + _goto.x = _goto.y = 0; + _type = INVALID; + _pickUp.clear(); + _noShapeSize.x = _noShapeSize.y = 0; + _status = 0; + _misc = 0; + _altImages = nullptr; + _altSeq = 0; + Common::fill(&_stopFrames[0], &_stopFrames[8], (ImageFrame *)nullptr); +} + +void Sprite::setImageFrame() { + int frameNum = MAX(_frameNumber, 0); + int imageNumber = _walkSequences[_sequenceNumber][frameNum]; + + if (IS_SERRATED_SCALPEL) + imageNumber = imageNumber + _walkSequences[_sequenceNumber][0] - 2; + else if (imageNumber > _maxFrames) + imageNumber = 1; + + // Get the images to use + ImageFile *images = _altSeq ? _altImages : _images; + assert(images); + + if (IS_3DO) { + // only do this to the image-array with 110 entries + // map uses another image-array and this code + if (images->size() == 110) { + // 3DO has 110 animation frames inside walk.anim + // PC has 55 + // this adjusts the framenumber accordingly + // sort of HACK + imageNumber *= 2; + } + } else if (IS_ROSE_TATTOO) { + --imageNumber; + } + + // Set the frame pointer + _imageFrame = &(*images)[imageNumber]; +} + +void Sprite::checkSprite() { + Events &events = *_vm->_events; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + Point32 pt; + Common::Rect objBounds; + Common::Point spritePt(_position.x / FIXED_INT_MULTIPLIER, _position.y / FIXED_INT_MULTIPLIER); + + if (_type != CHARACTER || (IS_SERRATED_SCALPEL && talk._talkCounter)) + return; + + pt = _walkCount ? _position + _delta : _position; + pt.x /= FIXED_INT_MULTIPLIER; + pt.y /= FIXED_INT_MULTIPLIER; + + if (IS_ROSE_TATTOO) { + checkObject(); + + // For Rose Tattoo, we only do the further processing for Sherlock + if (this != &people[HOLMES]) + return; + } + + for (uint idx = 0; idx < scene._bgShapes.size() && !talk._talkToAbort; ++idx) { + Object &obj = scene._bgShapes[idx]; + if (obj._aType <= PERSON || obj._type == INVALID || obj._type == HIDDEN) + continue; + + if (obj._type == NO_SHAPE) { + objBounds = Common::Rect(obj._position.x, obj._position.y, + obj._position.x + obj._noShapeSize.x + 1, obj._position.y + obj._noShapeSize.y + 1); + } else { + int xp = obj._position.x + obj._imageFrame->_offset.x; + int yp = obj._position.y + obj._imageFrame->_offset.y; + objBounds = Common::Rect(xp, yp, + xp + obj._imageFrame->_frame.w + 1, yp + obj._imageFrame->_frame.h + 1); + } + + if (objBounds.contains(pt)) { + if (objBounds.contains(spritePt)) { + // Current point is already inside the the bounds, so impact occurred + // on a previous call. So simply do nothing until we're clear of the box + switch (obj._aType) { + case TALK_MOVE: + if (_walkCount) { + // Holmes is moving + obj._type = HIDDEN; + obj.setFlagsAndToggles(); + talk.talkTo(obj._use[0]._target); + } + break; + + case PAL_CHANGE: + case PAL_CHANGE2: + if (_walkCount) { + int palStart = atoi(obj._use[0]._names[0].c_str()) * 3; + int palLength = atoi(obj._use[0]._names[1].c_str()) * 3; + int templ = atoi(obj._use[0]._names[2].c_str()) * 3; + if (templ == 0) + templ = 100; + + // Ensure only valid palette change data found + if (palLength > 0) { + // Figure out how far into the shape Holmes is so that we + // can figure out what percentage of the original palette + // to set the current palette to + int palPercent = (pt.x - objBounds.left) * 100 / objBounds.width(); + palPercent = palPercent * templ / 100; + if (obj._aType == PAL_CHANGE) + // Invert percentage + palPercent = 100 - palPercent; + + for (int i = palStart; i < (palStart + palLength); ++i) + screen._sMap[i] = screen._cMap[i] * palPercent / 100; + + events.pollEvents(); + screen.setPalette(screen._sMap); + } + } + break; + + case TALK: + case TALK_EVERY: + obj._type = HIDDEN; + obj.setFlagsAndToggles(); + talk.talkTo(obj._use[0]._target); + break; + + default: + break; + } + } else { + // New impact just occurred + switch (obj._aType) { + case BLANK_ZONE: + // A blank zone masks out all other remaining zones underneath it. + // If this zone is hit, exit the outer loop so we do not check anymore + return; + + case SOLID: + case TALK: + // Stop walking + if (obj._aType == TALK) { + obj.setFlagsAndToggles(); + talk.talkTo(obj._use[0]._target); + } else { + gotoStand(); + } + break; + + case TALK_EVERY: + if (obj._aType == TALK_EVERY) { + obj._type = HIDDEN; + obj.setFlagsAndToggles(); + talk.talkTo(obj._use[0]._target); + } else { + gotoStand(); + } + break; + + case FLAG_SET: + obj.setFlagsAndToggles(); + obj._type = HIDDEN; + break; + + case WALK_AROUND: + if (objBounds.contains(people[HOLMES]._walkTo.front())) { + // Reached zone + gotoStand(); + } else { + // Destination not within box, walk to best corner + Common::Point walkPos; + + if (spritePt.x >= objBounds.left && spritePt.x < objBounds.right) { + // Impact occurred due to vertical movement. Determine whether to + // travel to the left or right side + if (_delta.x > 0) + // Go to right side + walkPos.x = objBounds.right + CLEAR_DIST_X; + else if (_delta.x < 0) { + // Go to left side + walkPos.x = objBounds.left - CLEAR_DIST_X; + } else { + // Going straight up or down. So choose best side + if (spritePt.x >= (objBounds.left + objBounds.width() / 2)) + walkPos.x = objBounds.right + CLEAR_DIST_X; + else + walkPos.x = objBounds.left - CLEAR_DIST_X; + } + + walkPos.y = (_delta.y >= 0) ? objBounds.top - CLEAR_DIST_Y : + objBounds.bottom + CLEAR_DIST_Y; + } else { + // Impact occurred due to horizontal movement + if (_delta.y > 0) + // Go to bottom of box + walkPos.y = objBounds.bottom + CLEAR_DIST_Y; + else if (_delta.y < 0) + // Go to top of box + walkPos.y = objBounds.top - CLEAR_DIST_Y; + else { + // Going straight horizontal, so choose best side + if (spritePt.y >= (objBounds.top + objBounds.height() / 2)) + walkPos.y = objBounds.bottom + CLEAR_DIST_Y; + else + walkPos.y = objBounds.top - CLEAR_DIST_Y; + } + + walkPos.x = (_delta.x >= 0) ? objBounds.left - CLEAR_DIST_X : + objBounds.right + CLEAR_DIST_X; + } + + walkPos.x += people[HOLMES]._imageFrame->_frame.w / 2; + people[HOLMES]._walkDest = walkPos; + people[HOLMES]._walkTo.push(walkPos); + people[HOLMES].setWalking(); + } + break; + + case DELTA: + _position.x += 200; + break; + + default: + break; + } + } + } + } +} + +const Common::Rect Sprite::getOldBounds() const { + return Common::Rect(_oldPosition.x, _oldPosition.y, _oldPosition.x + _oldSize.x, _oldPosition.y + _oldSize.y); +} + +/*----------------------------------------------------------------*/ + +void WalkSequence::load(Common::SeekableReadStream &s) { + char buffer[9]; + s.read(buffer, 9); + _vgsName = Common::String(buffer); + _horizFlip = s.readByte() != 0; + + _sequences.resize(s.readUint16LE()); + s.skip(4); // Skip over pointer field of structure + + s.read(&_sequences[0], _sequences.size()); +} + +/*----------------------------------------------------------------*/ + +WalkSequences &WalkSequences::operator=(const WalkSequences &src) { + resize(src.size()); + for (uint idx = 0; idx < size(); ++idx) { + const WalkSequence &wSrc = src[idx]; + WalkSequence &wDest = (*this)[idx]; + wDest._horizFlip = wSrc._horizFlip; + + wDest._sequences.resize(wSrc._sequences.size()); + Common::copy(&wSrc._sequences[0], &wSrc._sequences[0] + wSrc._sequences.size(), &wDest._sequences[0]); + } + + return *this; +} + +/*----------------------------------------------------------------*/ + +ActionType::ActionType() { + _cAnimNum = _cAnimSpeed = 0; + _useFlag = 0; +} + +void ActionType::load(Common::SeekableReadStream &s) { + char buffer[12]; + + _cAnimNum = s.readByte(); + _cAnimSpeed = s.readByte(); + if (_cAnimSpeed & 0x80) + _cAnimSpeed = -(_cAnimSpeed & 0x7f); + + for (int idx = 0; idx < NAMES_COUNT; ++idx) { + s.read(buffer, 12); + _names[idx] = Common::String(buffer); + } +} + +/*----------------------------------------------------------------*/ + +UseType::UseType(): ActionType() { +} + +void UseType::load(Common::SeekableReadStream &s, bool isRoseTattoo) { + char buffer[12]; + + if (isRoseTattoo) { + s.read(buffer, 12); + _verb = Common::String(buffer); + } + + ActionType::load(s); + + _useFlag = s.readSint16LE(); + + if (!isRoseTattoo) + s.skip(6); + + s.read(buffer, 12); + _target = Common::String(buffer); +} + +void UseType::load3DO(Common::SeekableReadStream &s) { + char buffer[12]; + + _cAnimNum = s.readByte(); + _cAnimSpeed = s.readByte(); + if (_cAnimSpeed & 0x80) + _cAnimSpeed = -(_cAnimSpeed & 0x7f); + + for (int idx = 0; idx < NAMES_COUNT; ++idx) { + s.read(buffer, 12); + _names[idx] = Common::String(buffer); + } + + _useFlag = s.readSint16BE(); + + s.skip(6); + + s.read(buffer, 12); + _target = Common::String(buffer); +} + +void UseType::synchronize(Serializer &s) { + s.syncString(_verb); + s.syncAsSint16LE(_cAnimNum); + s.syncAsSint16LE(_cAnimSpeed); + s.syncAsSint16LE(_useFlag); + + for (int idx = 0; idx < 4; ++idx) + s.syncString(_names[idx]); + s.syncString(_target); +} + +/*----------------------------------------------------------------*/ + +Object::Object(): BaseObject() { + _sequenceOffset = 0; + _pickup = 0; + _defaultCommand = 0; + _pickupFlag = 0; +} + +void Object::load(Common::SeekableReadStream &s, bool isRoseTattoo) { + char buffer[41]; + s.read(buffer, 12); + _name = Common::String(buffer); + s.read(buffer, 41); + _description = Common::String(buffer); + + _examine.clear(); + _sequences = nullptr; + _images = nullptr; + _imageFrame = nullptr; + + s.skip(4); + _sequenceOffset = s.readUint16LE(); + s.seek(10, SEEK_CUR); + + _walkCount = s.readByte(); + _allow = s.readByte(); + _frameNumber = s.readSint16LE(); + _sequenceNumber = s.readSint16LE(); + _position.x = s.readSint16LE(); + _position.y = s.readSint16LE(); + _delta.x = s.readSint16LE(); + _delta.y = s.readSint16LE(); + _type = (SpriteType)s.readUint16LE(); + _oldPosition.x = s.readSint16LE(); + _oldPosition.y = s.readSint16LE(); + _oldSize.x = s.readUint16LE(); + _oldSize.y = s.readUint16LE(); + + _goto.x = s.readSint16LE(); + _goto.y = s.readSint16LE(); + if (!isRoseTattoo) { + _goto.x = _goto.x * FIXED_INT_MULTIPLIER / 100; + _goto.y = _goto.y * FIXED_INT_MULTIPLIER / 100; + } + + _pickup = isRoseTattoo ? 0 : s.readByte(); + _defaultCommand = isRoseTattoo ? 0 : s.readByte(); + _lookFlag = s.readSint16LE(); + _pickupFlag = isRoseTattoo ? 0 : s.readSint16LE(); + _requiredFlag[0] = s.readSint16LE(); + _noShapeSize.x = s.readUint16LE(); + _noShapeSize.y = s.readUint16LE(); + _status = s.readUint16LE(); + _misc = s.readByte(); + _maxFrames = s.readUint16LE(); + _flags = s.readByte(); + + if (!isRoseTattoo) + _aOpen.load(s); + + _aType = (AType)s.readByte(); + _lookFrames = s.readByte(); + _seqCounter = s.readByte(); + if (isRoseTattoo) { + _lookPosition.x = s.readUint16LE() * FIXED_INT_MULTIPLIER; + _lookPosition.y = s.readSint16LE() * FIXED_INT_MULTIPLIER; + } else { + _lookPosition.x = s.readUint16LE() * FIXED_INT_MULTIPLIER / 100; + _lookPosition.y = s.readByte() * FIXED_INT_MULTIPLIER; + } + _lookPosition._facing = s.readByte(); + _lookcAnim = s.readByte(); + + if (!isRoseTattoo) + _aClose.load(s); + + _seqStack = s.readByte(); + _seqTo = s.readByte(); + _descOffset = s.readUint16LE(); + _seqCounter2 = s.readByte(); + _seqSize = s.readUint16LE(); + + if (isRoseTattoo) { + for (int idx = 0; idx < 6; ++idx) + _use[idx].load(s, true); + + _quickDraw = s.readByte(); + _scaleVal = s.readUint16LE(); + _requiredFlag[1] = s.readSint16LE(); + _gotoSeq = s.readByte(); + _talkSeq = s.readByte(); + _restoreSlot = s.readByte(); + } else { + s.skip(1); + _aMove.load(s); + s.skip(8); + + for (int idx = 0; idx < 4; ++idx) + _use[idx].load(s, false); + } + //warning("object %s, useAnim %d", _name.c_str(), _use[0]._cAnimNum); +} + +void Object::load3DO(Common::SeekableReadStream &s) { + int32 streamStartPos = s.pos(); + char buffer[41]; + + _examine.clear(); + _sequences = nullptr; + _images = nullptr; + _imageFrame = nullptr; + + // on 3DO all of this data is reordered!!! + // it seems that possibly the 3DO compiler reordered the global struct + // 3DO size for 1 object is 588 bytes + s.skip(4); + _sequenceOffset = s.readUint16LE(); // weird that this seems to be LE + s.seek(10, SEEK_CUR); + + // Offset 16 + _frameNumber = s.readSint16BE(); + _sequenceNumber = s.readSint16BE(); + _position.x = s.readSint16BE(); + _position.y = s.readSint16BE(); + _delta.x = s.readSint16BE(); + _delta.y = s.readSint16BE(); + _type = (SpriteType)s.readUint16BE(); + _oldPosition.x = s.readSint16BE(); + _oldPosition.y = s.readSint16BE(); + _oldSize.x = s.readUint16BE(); + _oldSize.y = s.readUint16BE(); + + _goto.x = s.readSint16BE(); + _goto.y = s.readSint16BE(); + _goto.x = _goto.x * FIXED_INT_MULTIPLIER / 100; + _goto.y = _goto.y * FIXED_INT_MULTIPLIER / 100; + + // Offset 42 + warning("pos %d", s.pos()); + + // Unverified + _lookFlag = s.readSint16BE(); + _pickupFlag = s.readSint16BE(); + _requiredFlag[0] = s.readSint16BE(); + _noShapeSize.x = s.readUint16BE(); + _noShapeSize.y = s.readUint16BE(); + _status = s.readUint16BE(); + // Unverified END + + _maxFrames = s.readUint16BE(); + // offset 56 + _lookPosition.x = s.readUint16BE() * FIXED_INT_MULTIPLIER / 100; + // offset 58 + _descOffset = s.readUint16BE(); + _seqSize = s.readUint16BE(); + + s.skip(2); // boundary filler + + // 288 bytes + for (int idx = 0; idx < 4; ++idx) { + _use[idx].load3DO(s); + s.skip(2); // Filler + } + + // 158 bytes + _aOpen.load(s); // 2 + 12*4 bytes = 50 bytes + s.skip(2); // Boundary filler + _aClose.load(s); + s.skip(2); // Filler + _aMove.load(s); + s.skip(2); // Filler + + // offset 508 + // 3DO: name is at the end + s.read(buffer, 12); + _name = Common::String(buffer); + s.read(buffer, 41); + _description = Common::String(buffer); + + // Unverified + _walkCount = s.readByte(); + _allow = s.readByte(); + _pickup = s.readByte(); + _defaultCommand = s.readByte(); + // Unverified END + + // Probably those here?!?! + _misc = s.readByte(); + _flags = s.readByte(); + + // Unverified + _aType = (AType)s.readByte(); + _lookFrames = s.readByte(); + _seqCounter = s.readByte(); + // Unverified END + + _lookPosition.y = s.readByte() * FIXED_INT_MULTIPLIER; + _lookPosition._facing = s.readByte(); + + // Unverified + _lookcAnim = s.readByte(); + _seqStack = s.readByte(); + _seqTo = s.readByte(); + _seqCounter2 = s.readByte(); + // Unverified END + + s.skip(12); // Unknown + + //warning("object %s, offset %d", _name.c_str(), streamPos); + //warning("object %s, lookPosX %d, lookPosY %d", _name.c_str(), _lookPosition.x, _lookPosition.y); + //warning("object %s, defCmd %d", _name.c_str(), _defaultCommand); + int32 dataSize = s.pos() - streamStartPos; + assert(dataSize == 588); +} + +void Object::toggleHidden() { + if (_type != HIDDEN && _type != HIDE_SHAPE && _type != INVALID) { + if (_seqTo != 0) + _sequences[_frameNumber] = _seqTo + SEQ_TO_CODE + 128; + _seqTo = 0; + + if (_images == nullptr || _images->size() == 0) + // No shape to erase, so flag as hidden + _type = HIDDEN; + else + // Otherwise, flag it to be hidden after it gets erased + _type = HIDE_SHAPE; + } else if (_type != INVALID) { + if (_seqTo != 0) + _sequences[_frameNumber] = _seqTo + SEQ_TO_CODE + 128; + _seqTo = 0; + + _seqCounter = _seqCounter2 = 0; + _seqStack = 0; + _frameNumber = -1; + + if (_images == nullptr || _images->size() == 0) { + _type = NO_SHAPE; + } else { + _type = ACTIVE_BG_SHAPE; + int idx = _sequences[0]; + if (idx >= _maxFrames) + // Turn on: set up first frame + idx = 0; + + _imageFrame = &(*_images)[idx]; + } + } +} + +void Object::setObjTalkSequence(int seq) { + Talk &talk = *_vm->_talk; + + // See if we're supposed to restore the object's sequence from the talk sequence stack + if (seq == -1) { + if (_seqTo != 0) + _sequences[_frameNumber] = _seqTo; + + talk.pullSequence(_restoreSlot); + return; + } + + assert(_type != CHARACTER); + + talk.pushSequenceEntry(this); + int talkSeqNum = seq; + + // Find where the talk sequence data begins in the object + int idx = 0; + for (;;) { + // Get the Frame value + byte f = _sequences[idx++]; + + // See if we've found the beginning of a Talk Sequence + if ((f == TALK_SEQ_CODE && seq < 128) || (f == TALK_LISTEN_CODE && seq > 128)) { + --seq; + + // See if we're at the correct Talk Sequence Number + if (!(seq & 127)) + { + // Correct Sequence, Start Talking Here + if (_seqTo != 0) + _sequences[_frameNumber] = _seqTo; + _frameNumber = idx; + _seqTo = 0; + _seqStack = 0; + _seqCounter = 0; + _seqCounter2 = 0; + _talkSeq = talkSeqNum; + break; + } + } else { + // Move ahead any extra because of special control codes + switch (f) { + case 0: idx++; break; + case MOVE_CODE: + case TELEPORT_CODE: idx += 4; break; + case CALL_TALK_CODE: idx += 8; break; + case HIDE_CODE: idx += 2; break; + } + } + + // See if we're out of sequence data + if (idx >= (int)_seqSize) + break; + } +} + void Object::setFlagsAndToggles() { Scene &scene = *_vm->_scene; Talk &talk = *_vm->_talk; @@ -983,7 +1310,22 @@ void Object::adjustObject() { if (_type == REMOVE) return; - _position += _delta; + if (IS_ROSE_TATTOO && (_delta.x || _delta.y)) { + // The shape position is in pixels, and the delta is in fixed integer amounts + int t; + _noShapeSize.x += _delta.x; + t = _noShapeSize.x / (FIXED_INT_MULTIPLIER / 10); + _noShapeSize.x -= t * (FIXED_INT_MULTIPLIER / 10); + _position.x += t; + + _noShapeSize.y += _delta.y; + t = _noShapeSize.y / (FIXED_INT_MULTIPLIER / 10); + _noShapeSize.y -= t * (FIXED_INT_MULTIPLIER / 10); + _position.y += t; + } else if (IS_SERRATED_SCALPEL) { + // The delta is in whole pixels, so simply adjust the position with it + _position += _delta; + } if (_position.y > LOWER_LIMIT) _position.y = LOWER_LIMIT; @@ -1001,7 +1343,8 @@ void Object::adjustObject() { } } -int Object::pickUpObject(const char *const messages[]) { +int Object::pickUpObject(FixedTextActionId fixedTextActionId) { + FixedText &fixedText = *_vm->_fixedText; Inventory &inv = *_vm->_inventory; People &people = *_vm->_people; Scene &scene = *_vm->_scene; @@ -1014,7 +1357,7 @@ int Object::pickUpObject(const char *const messages[]) { if (pickup == 99) { for (int idx = 0; idx < NAMES_COUNT && !talk._talkToAbort; ++idx) { - if (checkNameForCodes(_use[0]._names[idx], nullptr)) { + if (checkNameForCodes(_use[0]._names[idx], kFixedTextAction_Invalid)) { if (!talk._talkToAbort) printed = true; } @@ -1030,7 +1373,8 @@ int Object::pickUpObject(const char *const messages[]) { ui._infoFlag = true; ui.clearInfo(); - screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", messages[message]); + Common::String errorMessage = fixedText.getActionMessage(fixedTextActionId, message); + screen.print(Common::Point(0, INFO_LINE + 1), COL_INFO_FOREGROUND, "%s", errorMessage.c_str()); ui._menuCounter = 30; } else { // Pick it up @@ -1055,13 +1399,13 @@ int Object::pickUpObject(const char *const messages[]) { } else { // Play generic pickup sequence // Original moved cursor position here - people.goAllTheWay(); + people[HOLMES].goAllTheWay(); ui._menuCounter = 25; ui._temp1 = 1; } for (int idx = 0; idx < NAMES_COUNT && !talk._talkToAbort; ++idx) { - if (checkNameForCodes(_use[0]._names[idx], nullptr)) { + if (checkNameForCodes(_use[0]._names[idx], kFixedTextAction_Invalid)) { if (!talk._talkToAbort) printed = true; } @@ -1079,7 +1423,7 @@ int Object::pickUpObject(const char *const messages[]) { Common::String itemName = _description; itemName.setChar(tolower(itemName[0]), 0); - screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "Picked up %s", itemName.c_str()); + screen.print(Common::Point(0, INFO_LINE + 1), COL_INFO_FOREGROUND, "Picked up %s", itemName.c_str()); ui._menuCounter = 25; } } @@ -1088,7 +1432,7 @@ int Object::pickUpObject(const char *const messages[]) { } const Common::Rect Object::getNewBounds() const { - Common::Point pt = _position; + Point32 pt = _position; if (_imageFrame) pt += _imageFrame->_offset; @@ -1107,14 +1451,14 @@ const Common::Rect Object::getOldBounds() const { /*----------------------------------------------------------------*/ -void CAnim::load(Common::SeekableReadStream &s, bool isRoseTattoo) { +void CAnim::load(Common::SeekableReadStream &s, bool isRoseTattoo, uint32 dataOffset) { char buffer[12]; s.read(buffer, 12); _name = Common::String(buffer); if (isRoseTattoo) { Common::fill(&_sequences[0], &_sequences[30], 0); - _size = s.readUint32LE(); + _dataSize = s.readUint32LE(); } else { s.read(_sequences, 30); } @@ -1126,33 +1470,85 @@ void CAnim::load(Common::SeekableReadStream &s, bool isRoseTattoo) { _flags = s.readByte(); _scaleVal = s.readSint16LE(); } else { - _size = s.readUint32LE(); + _dataSize = s.readUint32LE(); _type = (SpriteType)s.readUint16LE(); _flags = s.readByte(); } - _goto.x = s.readSint16LE(); - _goto.y = s.readSint16LE(); - _gotoDir = s.readSint16LE(); - _teleportPos.x = s.readSint16LE(); - _teleportPos.y = s.readSint16LE(); - _teleportDir = s.readSint16LE(); -} + _goto[0].x = s.readSint16LE(); + _goto[0].y = s.readSint16LE(); + _goto[0]._facing = s.readSint16LE(); + ADJUST_COORD(_goto[0]); -/*----------------------------------------------------------------*/ + if (isRoseTattoo) { + // Get Goto position and facing for second NPC + _goto[1].x = s.readSint16LE(); + _goto[1].y = s.readSint16LE(); + _goto[1]._facing = s.readSint16LE(); + ADJUST_COORD(_goto[1]); + } else if (_goto[0].x != -1) { + // For Serrated Scalpel, adjust the loaded co-ordinates + _goto[0].x = _goto[0].x / 100; + _goto[0].y = _goto[0].y / 100; + } -CAnimStream::CAnimStream() { - _stream = nullptr; - _frameSize = 0; - _images = nullptr; - _imageFrame = nullptr; - _flags = 0; - _scaleVal = 0; - _zPlacement = 0; + _teleport[0].x = s.readSint16LE(); + _teleport[0].y = s.readSint16LE(); + _teleport[0]._facing = s.readSint16LE(); + ADJUST_COORD(_teleport[0]); + + if (isRoseTattoo) { + // Get Teleport position and facing for second NPC + _teleport[1].x = s.readSint16LE(); + _teleport[1].y = s.readSint16LE(); + _teleport[1]._facing = s.readSint16LE(); + ADJUST_COORD(_teleport[1]); + } else if (_teleport[0].x != -1) { + // For Serrated Scalpel, adjust the loaded co-ordinates + _teleport[0].x = _teleport[0].x / 100; + _teleport[0].y = _teleport[0].y / 100; + } + + // Save offset of data, which is actually inside another table inside the room data file + // This table is at offset 44 for Serrated Scalpel + // TODO: find it for the other game + _dataOffset = dataOffset; } -void CAnimStream::getNextFrame() { - // TODO +void CAnim::load3DO(Common::SeekableReadStream &s, uint32 dataOffset) { + // this got reordered on 3DO + // maybe it was the 3DO compiler + + _dataSize = s.readUint32BE(); + // Save offset of data, which is inside another table inside the room data file + _dataOffset = dataOffset; + + _position.x = s.readSint16BE(); + _position.y = s.readSint16BE(); + + _type = (SpriteType)s.readUint16BE(); + + _goto[0].x = s.readSint16BE(); + _goto[0].y = s.readSint16BE(); + _goto[0]._facing = s.readSint16BE(); + + _teleport[0].x = s.readSint16BE(); + _teleport[0].y = s.readSint16BE(); + _teleport[0]._facing = s.readSint16BE(); + + char buffer[12]; + s.read(buffer, 12); + _name = Common::String(buffer); + + s.read(_sequences, 30); + _flags = s.readByte(); + + s.skip(3); // Filler + + _goto[0].x = _goto[0].x * FIXED_INT_MULTIPLIER / 100; + _goto[0].y = _goto[0].y * FIXED_INT_MULTIPLIER / 100; + _teleport[0].x = _teleport[0].x * FIXED_INT_MULTIPLIER / 100; + _teleport[0].y = _teleport[0].y * FIXED_INT_MULTIPLIER / 100; } /*----------------------------------------------------------------*/ diff --git a/engines/sherlock/objects.h b/engines/sherlock/objects.h index d671066a23..38b02aeae8 100644 --- a/engines/sherlock/objects.h +++ b/engines/sherlock/objects.h @@ -27,7 +27,9 @@ #include "common/rect.h" #include "common/str-array.h" #include "common/str.h" -#include "sherlock/resources.h" +#include "sherlock/image_file.h" +#include "sherlock/fixed_text.h" +#include "sherlock/saveload.h" namespace Sherlock { @@ -76,12 +78,26 @@ enum { #define MAX_HOLMES_SEQUENCE 16 #define MAX_FRAME 30 -#define FIXED_INT_MULTIPLIER 100 +#define FIXED_INT_MULTIPLIER 1000 // code put into sequences to defines 1-10 type seqs #define SEQ_TO_CODE 67 #define FLIP_CODE (64 + 128) #define SOUND_CODE (34 + 128) +#define HIDE_CODE (7+128) // Code for hiding/unhiding an object from a Sequence +#define CALL_TALK_CODE (8+128) // Code for call a Talk File from a Sequence +#define TELEPORT_CODE (9+128) // Code for setting Teleport Data (X,Y) +#define MOVE_CODE (10+128) // Code for setting Movement Delta (X,Y) + +#define GOTO_CODE 228 +#define TALK_SEQ_CODE 252 // Code specifying start of talk sequence frames in a Sequence +#define TALK_LISTEN_CODE 251 // Code specifying start of talk listen frames in a Sequence +#define ALLOW_TALK_CODE 250 + +#define UPPER_LIMIT 0 +#define LOWER_LIMIT (IS_SERRATED_SCALPEL ? CONTROLS_Y : SHERLOCK_SCREEN_HEIGHT) +#define LEFT_LIMIT 0 +#define RIGHT_LIMIT SHERLOCK_SCREEN_WIDTH class Point32 { public: @@ -102,6 +118,17 @@ public: void operator-=(const Point32 &delta) { x -= delta.x; y -= delta.y; } }; +class PositionFacing : public Point32 { +public: + int _facing; + + PositionFacing() : Point32(), _facing(0) {} + PositionFacing(int xp, int yp, int theFacing) : Point32(xp, yp), _facing(theFacing) {} + PositionFacing &operator=(const Point32 &pt) { + x = pt.x; y = pt.y; + return *this; + } +}; struct WalkSequence { Common::String _vgsName; @@ -129,6 +156,9 @@ struct ActionType { int _cAnimNum; int _cAnimSpeed; Common::String _names[NAMES_COUNT]; + int _useFlag; // Which flag USE will set (if any) + + ActionType(); /** * Load the data for the action @@ -136,11 +166,7 @@ struct ActionType { void load(Common::SeekableReadStream &s); }; -struct UseType { - int _cAnimNum; - int _cAnimSpeed; - Common::String _names[NAMES_COUNT]; - int _useFlag; // Which flag USE will set (if any) +struct UseType: public ActionType { Common::String _target; Common::String _verb; @@ -150,66 +176,129 @@ struct UseType { * Load the data for the UseType */ void load(Common::SeekableReadStream &s, bool isRoseTattoo); -}; + void load3DO(Common::SeekableReadStream &s); + /** + * Synchronize the data for a savegame + */ + void synchronize(Serializer &s); +}; -class Sprite { -private: +class BaseObject { +protected: static SherlockEngine *_vm; +protected: + /** + * This will check to see if the object has reached the end of a sequence. + * If it has, it switch to whichever next sequence should be started. + * @returns true if the end of a sequence was reached + */ + bool checkEndOfSequence(); + + /** + * Scans through the sequences array and finds the designated sequence. + * It then sets the frame number of the start of that sequence + */ + void setObjSequence(int seq, bool wait); +public: + static bool _countCAnimFrames; +public: + SpriteType _type; // Type of object/sprite + Common::String _description; // Description lines + byte *_sequences; // Holds animation sequences + ImageFile *_images; // Sprite images + ImageFrame *_imageFrame; // Pointer to shape in the images + int _sequenceNumber; // Sequence being used + int _startSeq; // Frame sequence starts at + int _walkCount; // Walk counter + int _allow; // Allowed UI commands + int _frameNumber; // Frame number in rame sequence to draw + Point32 _position; // Current position + Point32 _delta; // Momvement amount + Common::Point _oldPosition; // Old position + Common::Point _oldSize; // Image's old size + Point32 _goto; // Walk destination + + int _lookFlag; // Which flag LOOK will set (if any) + int _requiredFlag[2]; // Object will be hidden if not set + Common::Point _noShapeSize; // Size of a NO_SHAPE + int _status; // Status (open/closed, moved/not) + int8 _misc; // Misc field -- use varies with type + int _maxFrames; // Number of frames + int _flags; // Tells if object can be walked behind + AType _aType; // Tells if this is an object, person, talk, etc. + int _lookFrames; // How many frames to play of the look anim before pausing + int _seqCounter; // How many times this sequence has been executed + PositionFacing _lookPosition; // Where to walk when examining object + int _lookcAnim; + int _seqStack; // Allows gosubs to return to calling frame + int _seqTo; // Allows 1-5, 8-3 type sequences encoded in 2 bytes + uint _descOffset; // Tells where description starts in DescText + int _seqCounter2; // Counter of calling frame sequence + uint _seqSize; // Tells where description starts + UseType _use[6]; // Serrated Scalpel uses 4, Rose Tattoo 6 + int _quickDraw; // Flag telling whether to use quick draw routine or not + int _scaleVal; // Tells how to scale the sprite + int _gotoSeq; // Used by Talk to tell which sequence to goto when able + int _talkSeq; // Tells which talk sequence currently in use (Talk or Listen) + int _restoreSlot; // Used when talk returns to the previous sequence +public: + BaseObject(); + virtual ~BaseObject() {} + static void setVm(SherlockEngine *vm); + + /** + * Returns true if the the object has an Allow Talk Code in the sequence that it's + * currently running, specified by the _talkSeq field of the object. If it's 0, + * then it's a regular sequence. If it's not 0 but below 128, then it's a Talk Sequence. + * If it's above 128, then it's one of the Listen sequences. + */ + bool hasAborts() const; + + /** + * Check the state of the object + */ + void checkObject(); + + /** + * Checks for codes + * @param name The name to check for codes + * @param messages Provides a lookup list of messages that can be printed + * @returns 0 if no codes are found, 1 if codes were found + */ + int checkNameForCodes(const Common::String &name, FixedTextActionId fixedTextActionId = kFixedTextAction_Invalid); + + /** + * Adjusts the frame and sequence variables of a sprite that corresponds to the current speaker + * so that it points to the beginning of the sequence number's talk sequence in the object's + * sequence buffer + * @param seq Which sequence to use (if there's more than 1) + * @remarks 1: First talk seq, 2: second talk seq, etc. + */ + virtual void setObjTalkSequence(int seq) {} +}; + +class Sprite: public BaseObject { public: Common::String _name; - Common::String _description; Common::String _examine; // Examine in-depth description Common::String _pickUp; // Message for if you can't pick up object WalkSequences _walkSequences; // Holds animation sequences - ImageFile *_images; // Sprite images - ImageFrame *_imageFrame; // Pointer to shape in the images - int _walkCount; // Character walk counter - int _allow; // Allowed menu commands - ObjectAllow - int _frameNumber; // Frame number in rame sequence to draw - int _sequenceNumber; // Sequence being used - Point32 _position; // Current position - Point32 _delta; // Momvement delta - Common::Point _oldPosition; // Old position - Common::Point _oldSize; // Image's old size - Common::Point _goto; // Walk destination - SpriteType _type; // Type of object Common::Point _noShapeSize; // Size of a NO_SHAPE int _status; // Status: open/closed, moved/not moved int8 _misc; // Miscellaneous use - int _numFrames; // How many frames the object has // Rose Tattoo fields - int _startSeq; // Frame sequence starts at - int _flags; // Flags for the sprite - int _aType; // Tells if this is an object, person, talk, etc. - int _lookFrames; // How many frames to play of a canim before pausing - int _seqCounter; // How many times the sequence has been run - Common::Point _lookPosition; // Where to look when examining object - int _lookFacing; // Direction to face when examining object - int _lookCAnim; - int _seqStack; // Allow gosubs to return to calling frame - int _seqTo; // Allows 1-5, 8-3 type sequences encoded in 2 bytes - uint _descOffset; // Tells where description starts in description text for scene - int _seqCounter2; // Counter of calling frame sequence - uint _seqSize; // Size of sequence - UseType _use[6]; - int _quickDraw; // Flag telling whether to use quick draw routine or not - int _scaleVal; // Tells how to scale the sprite - int _requiredFlags1; // This flag must also be set, or the sprite is hidden - int _gotoSeq; // Used by Talk to tell which sequence to goto when able - int _talkSeq; // Tells which talk sequence currently in use (Talk or Listen) - int _restoreSlot; // Used when talk returns to the previous sequence - ImageFrame *_stopFrames[8]; // Stop/rest frame for each direction ImageFile *_altImages; // Images used for alternate NPC sequences - bool _altSequences; // Which of the sequences the alt graphics apply to (0: main, 1=NPC seq) + int _altSeq; // Which of the sequences the alt graphics apply to (0: main, 1=NPC seq) int _centerWalk; // Flag telling the walk code to offset the walk destination Common::Point _adjust; // Fine tuning adjustment to position when drawn int _oldWalkSequence; public: - Sprite() { clear(); } + Sprite(): BaseObject() { clear(); } + virtual ~Sprite() {} static void setVm(SherlockEngine *vm) { _vm = vm; } @@ -224,16 +313,20 @@ public: void setImageFrame(); /** - * This adjusts the sprites position, as well as it's animation sequence: - */ - void adjustSprite(); - - /** * Checks the sprite's position to see if it's collided with any special objects */ void checkSprite(); /** + * Adjusts the frame and sequence variables of a sprite that corresponds to the current speaker + * so that it points to the beginning of the sequence number's talk sequence in the object's + * sequence buffer + * @param seq Which sequence to use (if there's more than 1) + * @remarks 1: First talk seq, 2: second talk seq, etc. + */ + virtual void setObjTalkSequence(int seq) {} + + /** * Return frame width */ int frameWidth() const { return _imageFrame ? _imageFrame->_frame.w : 0; } @@ -242,71 +335,39 @@ public: * Return frame height */ int frameHeight() const { return _imageFrame ? _imageFrame->_frame.h : 0; } -}; -enum { OBJ_BEHIND = 1, OBJ_FLIPPED = 2, OBJ_FORWARD = 4, TURNON_OBJ = 0x20, TURNOFF_OBJ = 0x40 }; -#define USE_COUNT 4 + /** + * Returns the old bounsd for the sprite from the previous frame + */ + const Common::Rect getOldBounds() const; -class Object { -private: - static SherlockEngine *_vm; + /** + * This adjusts the sprites position, as well as it's animation sequence: + */ + virtual void adjustSprite() = 0; /** - * This will check to see if the object has reached the end of a sequence. - * If it has, it switch to whichever next sequence should be started. - * @returns true if the end of a sequence was reached + * Bring a moving character using the sprite to a standing position */ - bool checkEndOfSequence(); + virtual void gotoStand() = 0; /** - * Scans through the sequences array and finds the designated sequence. - * It then sets the frame number of the start of that sequence + * Set the variables for moving a character from one poisition to another + * in a straight line */ - void setObjSequence(int seq, bool wait); -public: - static bool _countCAnimFrames; + virtual void setWalking() = 0; +}; - static void setVm(SherlockEngine *vm); +enum { OBJ_BEHIND = 1, OBJ_FLIPPED = 2, OBJ_FORWARD = 4, TURNON_OBJ = 0x20, TURNOFF_OBJ = 0x40 }; +#define USE_COUNT 4 + +class Object: public BaseObject { public: Common::String _name; // Name - Common::String _description; // Description lines Common::String _examine; // Examine in-depth description int _sequenceOffset; - uint8 *_sequences; // Holds animation sequences - ImageFile *_images; // Sprite images - ImageFrame *_imageFrame; // Pointer to shape in the images - int _walkCount; // Character walk counter - int _allow; // Allowed menu commands - ObjectAllow - int _frameNumber; // Frame number in rame sequence to draw - int _sequenceNumber; // Sequence being used - SpriteType _type; // Object type - Common::Point _position; // Current position - Common::Point _delta; // Momvement amount - Common::Point _oldPosition; // Old position - Common::Point _oldSize; // Image's old size - Common::Point _goto; // Walk destination - int _pickup; int _defaultCommand; // Default right-click command - int _lookFlag; // Which flag LOOK will set (if any) - int _requiredFlag; // Object will be hidden if not set - Common::Point _noShapeSize; // Size of a NO_SHAPE - int _status; // Status (open/closed, moved/not) - int8 _misc; // Misc field -- use varies with type - int _maxFrames; // Number of frames - int _flags; // Tells if object can be walked behind - AType _aType; // Tells if this is an object, person, talk, etc. - int _lookFrames; // How many frames to play of the look anim before pausing - int _seqCounter; // How many times this sequence has been executed - Common::Point _lookPosition; // Where to walk when examining object - int _lookFacing; // Direction to face when examining object - int _lookcAnim; - int _seqStack; // Allows gosubs to return to calling frame - int _seqTo; // Allows 1-5, 8-3 type sequences encoded in 2 bytes - uint _descOffset; // Tells where description starts in DescText - int _seqCounter2; // Counter of calling frame sequence - uint _seqSize; // Tells where description starts - UseType _use[6]; // Serrated Scalpel uses 4, Rose Tattoo 6 // Serrated Scalpel fields int _pickupFlag; // Which flag PICKUP will set (if any) @@ -314,20 +375,14 @@ public: ActionType _aClose; ActionType _aMove; - // Rose Tattoo fields - int _quickDraw; - int _scaleVal; - int _requiredFlag1; - int _gotoSeq; - int _talkSeq; - int _restoreSlot; - Object(); + virtual ~Object() {} /** * Load the data for the object */ void load(Common::SeekableReadStream &s, bool isRoseTattoo); + void load3DO(Common::SeekableReadStream &s); /** * Toggle the type of an object between hidden and active @@ -335,19 +390,6 @@ public: void toggleHidden(); /** - * Check the state of the object - */ - void checkObject(); - - /** - * Checks for codes - * @param name The name to check for codes - * @param messages Provides a lookup list of messages that can be printed - * @returns 0 if no codes are found, 1 if codes were found - */ - int checkNameForCodes(const Common::String &name, const char *const messages[]); - - /** * Handle setting any flags associated with the object */ void setFlagsAndToggles(); @@ -362,13 +404,13 @@ public: * Handles trying to pick up an object. If allowed, plays an y necessary animation for picking * up the item, and then adds it to the player's inventory */ - int pickUpObject(const char *const messages[]); + int pickUpObject(FixedTextActionId fixedTextActionId = kFixedTextAction_Invalid); /** * Return the frame width */ int frameWidth() const { return _imageFrame ? _imageFrame->_frame.w : 0; } - + /** * Return the frame height */ @@ -388,17 +430,25 @@ public: * Returns the old bounsd for the sprite from the previous frame */ const Common::Rect getOldBounds() const; + + /** + * Adjusts the frame and sequence variables of a sprite that corresponds to the current speaker + * so that it points to the beginning of the sequence number's talk sequence in the object's + * sequence buffer + * @param seq Which sequence to use (if there's more than 1) + * @remarks 1: First talk seq, 2: second talk seq, etc. + */ + virtual void setObjTalkSequence(int seq); }; struct CAnim { Common::String _name; // Name Common::Point _position; // Position - int _size; // Size of uncompressed animation + int _dataSize; // Size of uncompressed animation data + uint32 _dataOffset; // offset within room file of animation data int _flags; // Tells if can be walked behind - Common::Point _goto; // coords holmes should walk to before starting canim - int _gotoDir; - Common::Point _teleportPos; // Location Holmes shoul teleport to after - int _teleportDir; // playing canim + PositionFacing _goto[2]; // Position Holmes (and NPC in Rose Tattoo) should walk to before anim starts + PositionFacing _teleport[2]; // Location Holmes (and NPC) shoul teleport to after playing canim // Scalpel specific byte _sequences[MAX_FRAME]; // Animation sequences @@ -410,27 +460,8 @@ struct CAnim { /** * Load the data for the animation */ - void load(Common::SeekableReadStream &s, bool isRoseTattoo); -}; - -struct CAnimStream { - Common::SeekableReadStream *_stream; // Stream to read frames from - int _frameSize; // Temporary used to store the frame size - - void *_images; // TOOD: FIgure out hwo to hook up ImageFile with streaming support - ImageFrame *_imageFrame; - - Common::Point _position; // Animation position - Common::Rect _oldBounds; // Bounds of previous frame - Common::Rect _removeBounds; // Remove area for just drawn frame - - int _flags; // Flags - int _scaleVal; // Specifies the scale amount - int _zPlacement; // Used by doBgAnim for determining Z order - - CAnimStream(); - - void getNextFrame(); + void load(Common::SeekableReadStream &s, bool isRoseTattoo, uint32 dataOffset); + void load3DO(Common::SeekableReadStream &s, uint32 dataOffset); }; struct SceneImage { diff --git a/engines/sherlock/people.cpp b/engines/sherlock/people.cpp index 0ef49ffefb..b1f4abba47 100644 --- a/engines/sherlock/people.cpp +++ b/engines/sherlock/people.cpp @@ -22,14 +22,11 @@ #include "sherlock/people.h" #include "sherlock/sherlock.h" +#include "sherlock/scalpel/scalpel_people.h" +#include "sherlock/tattoo/tattoo_people.h" namespace Sherlock { -// Walk speeds -#define MWALK_SPEED 2 -#define XWALK_SPEED 4 -#define YWALK_SPEED 1 - // Characer animation sequences static const uint8 CHARACTER_SEQUENCES[MAX_HOLMES_SEQUENCE][MAX_FRAME] = { { 29, 1, 2, 3, 4, 5, 6, 7, 0 }, // Walk Right @@ -52,8 +49,7 @@ static const uint8 CHARACTER_SEQUENCES[MAX_HOLMES_SEQUENCE][MAX_FRAME] = { // Rose Tattoo walk image libraries // Walk resources within WALK.LIB -#define NUM_IN_WALK_LIB 10 -const char *const WALK_LIB_NAMES[10] = { +const char *const WALK_LIB_NAMES[NUM_IN_WALK_LIB] = { "SVGAWALK.VGS", "COATWALK.VGS", "WATSON.VGS", @@ -68,34 +64,97 @@ const char *const WALK_LIB_NAMES[10] = { /*----------------------------------------------------------------*/ -Person::Person() : Sprite(), _walkLoaded(false), _npcIndex(0), _npcStack(0), _npcPause(false) { - Common::fill(&_npcPath[0], &_npcPath[MAX_NPC_PATH], 0); - _tempX = _tempScaleVal = 0; +Person::Person() : Sprite() { + _walkLoaded = false; + _oldWalkSequence = -1; + _srcZone = _destZone = 0; } -void Person::clearNPC() { - Common::fill(&_npcPath[0], &_npcPath[MAX_NPC_PATH], 0); - _npcIndex = _npcStack = 0; - _npcName = ""; +void Person::goAllTheWay() { + Scene &scene = *_vm->_scene; + Common::Point srcPt = getSourcePoint(); + + // Get the zone the player is currently in + _srcZone = scene.whichZone(srcPt); + if (_srcZone == -1) + _srcZone = scene.closestZone(srcPt); + + // Get the zone of the destination + _destZone = scene.whichZone(_walkDest); + if (_destZone == -1) { + _destZone = scene.closestZone(_walkDest); + + // Check for any restriction of final destination position + _walkDest = _vm->_people->restrictToZone(_destZone, _walkDest); + } + + // Only do a walk if both zones are acceptable + if (_srcZone == -2 || _destZone == -2) + return; + + // If the start and dest zones are the same, walk directly to the dest point + if (_srcZone == _destZone) { + setWalking(); + } else { + // Otherwise a path needs to be formed from the path information + int i = scene._walkDirectory[_srcZone][_destZone]; + + // See if we need to use a reverse path + if (i == -1) + i = scene._walkDirectory[_destZone][_srcZone]; + + const WalkArray &points = scene._walkPoints[i]; + + // See how many points there are between the src and dest zones + if (!points._pointsCount || points._pointsCount == -1) { + // There are none, so just walk to the new zone + setWalking(); + } else { + // There are points, so set up a multi-step path between points + // to reach the given destination + _walkTo.clear(); + + if (scene._walkDirectory[_srcZone][_destZone] != -1) { + for (int idx = (int)points.size() - 1; idx >= 0; --idx) + _walkTo.push(points[idx]); + } else { + for (int idx = 0; idx < (int)points.size(); ++idx) { + _walkTo.push(points[idx]); + } + } + + // Final position + _walkTo.push(_walkDest); + + // Start walking + _walkDest = _walkTo.pop(); + setWalking(); + } + } } /*----------------------------------------------------------------*/ -People::People(SherlockEngine *vm) : _vm(vm), _player(_data[0]) { +People *People::init(SherlockEngine *vm) { + if (vm->getGameID() == GType_SerratedScalpel) + return new Scalpel::ScalpelPeople(vm); + else + return new Tattoo::TattooPeople(vm); +} + +People::People(SherlockEngine *vm) : _vm(vm) { _holmesOn = true; - _oldWalkSequence = -1; - _allowWalkAbort = false; + _allowWalkAbort = true; _portraitLoaded = false; _portraitsOn = true; _clearingThePortrait = false; - _srcZone = _destZone = 0; _talkPics = nullptr; _portraitSide = 0; _speakerFlip = false; _holmesFlip = false; _holmesQuotient = 0; - _hSavedPos = Common::Point(-1, -1); - _hSavedFacing = -1; + _savedPos = Point32(-1, -1); + _savedPos._facing = -1; _forceWalkReload = false; _useWalkLib = false; _walkControl = 0; @@ -104,9 +163,10 @@ People::People(SherlockEngine *vm) : _vm(vm), _player(_data[0]) { } People::~People() { - for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { - if (_data[idx]._walkLoaded) - delete _data[PLAYER]._images; + for (uint idx = 0; idx < _data.size(); ++idx) { + if (_data[idx]->_walkLoaded) + delete _data[idx]->_images; + delete _data[idx]; } delete _talkPics; @@ -114,27 +174,33 @@ People::~People() { } void People::reset() { - _data[0]._description = "Sherlock Holmes!"; + SaveManager &saves = *_vm->_saves; + Talk &talk = *_vm->_talk; + _data[HOLMES]->_description = "Sherlock Holmes!"; // Note: Serrated Scalpel only uses a single Person slot for Sherlock.. Watson is handled by scene sprites int count = IS_SERRATED_SCALPEL ? 1 : MAX_CHARACTERS; for (int idx = 0; idx < count; ++idx) { - Sprite &p = _data[idx]; + Person &p = *_data[idx]; - p._type = (idx == 0) ? CHARACTER : INVALID; - if (IS_SERRATED_SCALPEL) - p._position = Point32(10000, 11000); - else - p._position = Point32(36000, 29000); + if (IS_SERRATED_SCALPEL) { + p._type = CHARACTER; + p._position = Point32(100 * FIXED_INT_MULTIPLIER, 110 * FIXED_INT_MULTIPLIER); + } else if (!talk._scriptMoreFlag && !saves._justLoaded) { + p._type = (idx == 0) ? CHARACTER : INVALID; + p._position = Point32(36 * FIXED_INT_MULTIPLIER, 29 * FIXED_INT_MULTIPLIER); + p._use[0]._verb = ""; + p._use[1]._verb = ""; + } - p._sequenceNumber = STOP_DOWNRIGHT; + p._sequenceNumber = IS_SERRATED_SCALPEL ? (int)Scalpel::STOP_DOWNRIGHT : (int)Tattoo::STOP_DOWNRIGHT; p._imageFrame = nullptr; p._frameNumber = 1; - p._delta = Common::Point(0, 0); + p._startSeq = 0; + p._delta = Point32(0, 0); p._oldPosition = Common::Point(0, 0); p._oldSize = Common::Point(0, 0); p._misc = 0; - p._walkCount = 0; p._pickUp = ""; p._allow = 0; p._noShapeSize = Common::Point(0, 0); @@ -147,11 +213,13 @@ void People::reset() { p._restoreSlot = 0; p._startSeq = 0; p._altImages = nullptr; - p._altSequences = 0; + p._altSeq = 0; p._centerWalk = true; p._adjust = Common::Point(0, 0); // Load the default walk sequences + p._walkCount = 0; + p._walkTo.clear(); p._oldWalkSequence = -1; p._walkSequences.clear(); if (IS_SERRATED_SCALPEL) { @@ -166,93 +234,17 @@ void People::reset() { } } } - - // Reset any walk path in progress when Sherlock leaves scenes - _walkTo.clear(); -} - -bool People::loadWalk() { - Resources &res = *_vm->_res; - bool result = false; - - if (IS_SERRATED_SCALPEL) { - if (_data[PLAYER]._walkLoaded) { - return false; - } else { - _data[PLAYER]._images = new ImageFile("walk.vgs"); - _data[PLAYER].setImageFrame(); - _data[PLAYER]._walkLoaded = true; - - result = true; - } - } else { - for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { - if (!_data[idx]._walkLoaded && (_data[idx]._type == CHARACTER || _data[idx]._type == HIDDEN_CHARACTER)) { - if (_data[idx]._type == HIDDEN_CHARACTER) - _data[idx]._type = INVALID; - - // See if this is one of the more used Walk Graphics stored in WALK.LIB - for (int libNum = 0; libNum < NUM_IN_WALK_LIB; ++libNum) { - if (!_data[0]._walkVGSName.compareToIgnoreCase(WALK_LIB_NAMES[libNum])) { - _useWalkLib = true; - break; - } - } - - // Load the images for the character - _data[idx]._images = new ImageFile(_data[idx]._walkVGSName, false); - _data[idx]._numFrames = _data[idx]._images->size(); - - // Load walk sequence data - Common::String fname = Common::String(_data[idx]._walkVGSName.c_str(), strchr(_data[idx]._walkVGSName.c_str(), '.')); - fname += ".SEQ"; - - // Load the walk sequence data - Common::SeekableReadStream *stream = res.load(fname, _useWalkLib ? "walk.lib" : "vgs.lib"); - - _data[idx]._walkSequences.resize(stream->readByte()); - - for (uint seqNum = 0; seqNum < _data[idx]._walkSequences.size(); ++seqNum) - _data[idx]._walkSequences[seqNum].load(*stream); - - // Close the sequences resource - delete stream; - _useWalkLib = false; - - _data[idx]._frameNumber = 0; - _data[idx].setImageFrame(); - - // Set the stop Frames pointers - for (int dirNum = 0; dirNum < 8; ++dirNum) { - int count = 0; - while (_data[idx]._walkSequences[dirNum + 8][count] != 0) - ++count; - count += 2; - count = _data[idx]._walkSequences[dirNum + 8][count] - 1; - _data[idx]._stopFrames[dirNum] = &(*_data[idx]._images)[count]; - } - - result = true; - _data[idx]._walkLoaded = true; - } else if (_data[idx]._type != CHARACTER) { - _data[idx]._walkLoaded = false; - } - } - } - - _forceWalkReload = false; - return result; } bool People::freeWalk() { bool result = false; for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { - if (_data[idx]._walkLoaded) { - delete _data[idx]._images; - _data[idx]._images = nullptr; + if (_data[idx]->_walkLoaded) { + delete _data[idx]->_images; + _data[idx]->_images = nullptr; - _data[idx]._walkLoaded = false; + _data[idx]->_walkLoaded = false; result = true; } } @@ -260,322 +252,9 @@ bool People::freeWalk() { return result; } -void People::setWalking() { - Map &map = *_vm->_map; - Scene &scene = *_vm->_scene; - int oldDirection, oldFrame; - Common::Point speed, delta; - - // Flag that player has now walked in the scene - scene._walkedInScene = true; - - // Stop any previous walking, since a new dest is being set - _player._walkCount = 0; - oldDirection = _player._sequenceNumber; - oldFrame = _player._frameNumber; - - // Set speed to use horizontal and vertical movement - if (map._active) { - speed = Common::Point(MWALK_SPEED, MWALK_SPEED); - } else { - speed = Common::Point(XWALK_SPEED, YWALK_SPEED); - } - - // If the player is already close to the given destination that no - // walking is needed, move to the next straight line segment in the - // overall walking route, if there is one - for (;;) { - // Since we want the player to be centered on the destination they - // clicked, but characters draw positions start at their left, move - // the destination half the character width to draw him centered - int temp; - if (_walkDest.x >= (temp = _player._imageFrame->_frame.w / 2)) - _walkDest.x -= temp; - - delta = Common::Point( - ABS(_player._position.x / 100 - _walkDest.x), - ABS(_player._position.y / 100 - _walkDest.y) - ); - - // If we're ready to move a sufficient distance, that's it. Otherwise, - // move onto the next portion of the walk path, if there is one - if ((delta.x > 3 || delta.y > 0) || _walkTo.empty()) - break; - - // Pop next walk segment off the walk route stack - _walkDest = _walkTo.pop(); - } - - // If a sufficient move is being done, then start the move - if (delta.x > 3 || delta.y) { - // See whether the major movement is horizontal or vertical - if (delta.x >= delta.y) { - // Set the initial frame sequence for the left and right, as well - // as setting the delta x depending on direction - if (_walkDest.x < (_player._position.x / 100)) { - _player._sequenceNumber = (map._active ? (int)MAP_LEFT : (int)WALK_LEFT); - _player._delta.x = speed.x * -100; - } else { - _player._sequenceNumber = (map._active ? (int)MAP_RIGHT : (int)WALK_RIGHT); - _player._delta.x = speed.x * 100; - } - - // See if the x delta is too small to be divided by the speed, since - // this would cause a divide by zero error - if (delta.x >= speed.x) { - // Det the delta y - _player._delta.y = (delta.y * 100) / (delta.x / speed.x); - if (_walkDest.y < (_player._position.y / 100)) - _player._delta.y = -_player._delta.y; - - // Set how many times we should add the delta to the player's position - _player._walkCount = delta.x / speed.x; - } else { - // The delta x was less than the speed (ie. we're really close to - // the destination). So set delta to 0 so the player won't move - _player._delta = Common::Point(0, 0); - _player._position = Common::Point(_walkDest.x * 100, _walkDest.y * 100); - _player._walkCount = 1; - } - - // See if the sequence needs to be changed for diagonal walking - if (_player._delta.y > 150) { - if (!map._active) { - switch (_player._sequenceNumber) { - case WALK_LEFT: - _player._sequenceNumber = WALK_DOWNLEFT; - break; - case WALK_RIGHT: - _player._sequenceNumber = WALK_DOWNRIGHT; - break; - } - } - } else if (_player._delta.y < -150) { - if (!map._active) { - switch (_player._sequenceNumber) { - case WALK_LEFT: - _player._sequenceNumber = WALK_UPLEFT; - break; - case WALK_RIGHT: - _player._sequenceNumber = WALK_UPRIGHT; - break; - } - } - } - } else { - // Major movement is vertical, so set the sequence for up and down, - // and set the delta Y depending on the direction - if (_walkDest.y < (_player._position.y / 100)) { - _player._sequenceNumber = WALK_UP; - _player._delta.y = speed.y * -100; - } else { - _player._sequenceNumber = WALK_DOWN; - _player._delta.y = speed.y * 100; - } - - // If we're on the overhead map, set the sequence so we keep moving - // in the same direction - if (map._active) - _player._sequenceNumber = (oldDirection == -1) ? MAP_RIGHT : oldDirection; - - // Set the delta x - _player._delta.x = (delta.x * 100) / (delta.y / speed.y); - if (_walkDest.x < (_player._position.x / 100)) - _player._delta.x = -_player._delta.x; - - _player._walkCount = delta.y / speed.y; - } - } - - // See if the new walk sequence is the same as the old. If it's a new one, - // we need to reset the frame number to zero so it's animation starts at - // it's beginning. Otherwise, if it's the same sequence, we can leave it - // as is, so it keeps the animation going at wherever it was up to - if (_player._sequenceNumber != _oldWalkSequence) - _player._frameNumber = 0; - _oldWalkSequence = _player._sequenceNumber; - - if (!_player._walkCount) - gotoStand(_player); - - // If the sequence is the same as when we started, then Holmes was - // standing still and we're trying to re-stand him, so reset Holmes' - // rame to the old frame number from before it was reset to 0 - if (_player._sequenceNumber == oldDirection) - _player._frameNumber = oldFrame; -} - -void People::gotoStand(Sprite &sprite) { - Map &map = *_vm->_map; - _walkTo.clear(); - sprite._walkCount = 0; - - switch (sprite._sequenceNumber) { - case WALK_UP: - sprite._sequenceNumber = STOP_UP; - break; - case WALK_DOWN: - sprite._sequenceNumber = STOP_DOWN; - break; - case TALK_LEFT: - case WALK_LEFT: - sprite._sequenceNumber = STOP_LEFT; - break; - case TALK_RIGHT: - case WALK_RIGHT: - sprite._sequenceNumber = STOP_RIGHT; - break; - case WALK_UPRIGHT: - sprite._sequenceNumber = STOP_UPRIGHT; - break; - case WALK_UPLEFT: - sprite._sequenceNumber = STOP_UPLEFT; - break; - case WALK_DOWNRIGHT: - sprite._sequenceNumber = STOP_DOWNRIGHT; - break; - case WALK_DOWNLEFT: - sprite._sequenceNumber = STOP_DOWNLEFT; - break; - default: - break; - } - - // Only restart frame at 0 if the sequence number has changed - if (_oldWalkSequence != -1 || sprite._sequenceNumber == STOP_UP) - sprite._frameNumber = 0; - - if (map._active) { - sprite._sequenceNumber = 0; - _player._position.x = (map[map._charPoint].x - 6) * 100; - _player._position.y = (map[map._charPoint].x + 10) * 100; - } - - _oldWalkSequence = -1; - _allowWalkAbort = true; -} - -void People::walkToCoords(const Common::Point &destPos, int destDir) { - Events &events = *_vm->_events; - Scene &scene = *_vm->_scene; - Talk &talk = *_vm->_talk; - - CursorId oldCursor = events.getCursor(); - events.setCursor(WAIT); - - _walkDest = Common::Point(destPos.x / 100 + 10, destPos.y / 100); - _allowWalkAbort = true; - goAllTheWay(); - - // Keep calling doBgAnim until the walk is done - do { - events.pollEventsAndWait(); - scene.doBgAnim(); - } while (!_vm->shouldQuit() && _player._walkCount); - - if (!talk._talkToAbort) { - // Put player exactly on destination position, and set direction - _player._position = destPos; - _player._sequenceNumber = destDir; - gotoStand(_player); - - // Draw Holmes facing the new direction - scene.doBgAnim(); - - if (!talk._talkToAbort) - events.setCursor(oldCursor); - } -} - -void People::goAllTheWay() { - Scene &scene = *_vm->_scene; - Common::Point srcPt(_player._position.x / 100 + _player.frameWidth() / 2, - _player._position.y / 100); - - // Get the zone the player is currently in - _srcZone = scene.whichZone(srcPt); - if (_srcZone == -1) - _srcZone = scene.closestZone(srcPt); - - // Get the zone of the destination - _destZone = scene.whichZone(_walkDest); - if (_destZone == -1) { - _destZone = scene.closestZone(_walkDest); - - // The destination isn't in a zone - if (_walkDest.x >= (SHERLOCK_SCREEN_WIDTH - 1)) - _walkDest.x = SHERLOCK_SCREEN_WIDTH - 2; - - // Trace a line between the centroid of the found closest zone to - // the destination, to find the point at which the zone will be left - const Common::Rect &destRect = scene._zones[_destZone]; - const Common::Point destCenter((destRect.left + destRect.right) / 2, - (destRect.top + destRect.bottom) / 2); - const Common::Point delta = _walkDest - destCenter; - Common::Point pt(destCenter.x * 100, destCenter.y * 100); - - // Move along the line until the zone is left - do { - pt += delta; - } while (destRect.contains(pt.x / 100, pt.y / 100)); - - // Set the new walk destination to the last point that was in the - // zone just before it was left - _walkDest = Common::Point((pt.x - delta.x * 2) / 100, - (pt.y - delta.y * 2) / 100); - } - - // Only do a walk if both zones are acceptable - if (_srcZone == -2 || _destZone == -2) - return; - - // If the start and dest zones are the same, walk directly to the dest point - if (_srcZone == _destZone) { - setWalking(); - } else { - // Otherwise a path needs to be formed from the path information - int i = scene._walkDirectory[_srcZone][_destZone]; - - // See if we need to use a reverse path - if (i == -1) - i = scene._walkDirectory[_destZone][_srcZone]; - - int count = scene._walkData[i]; - ++i; - - // See how many points there are between the src and dest zones - if (!count || count == -1) { - // There are none, so just walk to the new zone - setWalking(); - } else { - // There are points, so set up a multi-step path between points - // to reach the given destination - _walkTo.clear(); - - if (scene._walkDirectory[_srcZone][_destZone] != -1) { - i += 3 * (count - 1); - for (int idx = 0; idx < count; ++idx, i -= 3) { - _walkTo.push(Common::Point(READ_LE_UINT16(&scene._walkData[i]), - scene._walkData[i + 2])); - } - } else { - for (int idx = 0; idx < count; ++idx, i += 3) { - _walkTo.push(Common::Point(READ_LE_UINT16(&scene._walkData[i]), scene._walkData[i + 2])); - } - } - - // Final position - _walkTo.push(_walkDest); - - // Start walking - _walkDest = _walkTo.pop(); - setWalking(); - } - } -} - int People::findSpeaker(int speaker) { Scene &scene = *_vm->_scene; + const char *portrait = _characters[speaker]._portrait; for (int idx = 0; idx < (int)scene._bgShapes.size(); ++idx) { Object &obj = scene._bgShapes[idx]; @@ -583,7 +262,7 @@ int People::findSpeaker(int speaker) { if (obj._type == ACTIVE_BG_SHAPE) { Common::String name(obj._name.c_str(), obj._name.c_str() + 4); - if (name.equalsIgnoreCase(_characters[speaker]._portrait) + if (name.equalsIgnoreCase(portrait) && obj._name[4] >= '0' && obj._name[4] <= '9') return idx; } @@ -622,91 +301,4 @@ void People::clearTalking() { } } -void People::setTalking(int speaker) { - Resources &res = *_vm->_res; - - // If no speaker is specified, then we can exit immediately - if (speaker == -1) - return; - - if (_portraitsOn) { - delete _talkPics; - Common::String filename = Common::String::format("%s.vgs", _characters[speaker]._portrait); - _talkPics = new ImageFile(filename); - - // Load portrait sequences - Common::SeekableReadStream *stream = res.load("sequence.txt"); - stream->seek(speaker * MAX_FRAME); - - int idx = 0; - do { - _portrait._sequences[idx] = stream->readByte(); - ++idx; - } while (idx < 2 || _portrait._sequences[idx - 2] || _portrait._sequences[idx - 1]); - - delete stream; - - _portrait._maxFrames = idx; - _portrait._frameNumber = 0; - _portrait._sequenceNumber = 0; - _portrait._images = _talkPics; - _portrait._imageFrame = &(*_talkPics)[0]; - _portrait._position = Common::Point(_portraitSide, 10); - _portrait._delta = Common::Point(0, 0); - _portrait._oldPosition = Common::Point(0, 0); - _portrait._goto = Common::Point(0, 0); - _portrait._flags = 5; - _portrait._status = 0; - _portrait._misc = 0; - _portrait._allow = 0; - _portrait._type = ACTIVE_BG_SHAPE; - _portrait._name = " "; - _portrait._description = " "; - _portrait._examine = " "; - _portrait._walkCount = 0; - - if (_holmesFlip || _speakerFlip) { - _portrait._flags |= 2; - - _holmesFlip = false; - _speakerFlip = false; - } - - if (_portraitSide == 20) - _portraitSide = 220; - else - _portraitSide = 20; - - _portraitLoaded = true; - } -} - -void People::synchronize(Common::Serializer &s) { - s.syncAsByte(_holmesOn); - - if (IS_SERRATED_SCALPEL) { - s.syncAsSint16LE(_player._position.x); - s.syncAsSint16LE(_player._position.y); - s.syncAsSint16LE(_player._sequenceNumber); - } else { - for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { - Person &p = _data[idx]; - s.syncAsSint16LE(p._position.x); - s.syncAsSint16LE(p._position.y); - s.syncAsSint16LE(p._sequenceNumber); - s.syncAsSint16LE(p._type); - s.syncString(p._walkVGSName); - s.syncString(p._description); - s.syncString(p._examine); - } - } - - s.syncAsSint16LE(_holmesQuotient); - - if (s.isLoading()) { - _hSavedPos = _player._position; - _hSavedFacing = _player._sequenceNumber; - } -} - } // End of namespace Sherlock diff --git a/engines/sherlock/people.h b/engines/sherlock/people.h index 013727d8ba..c47d45145a 100644 --- a/engines/sherlock/people.h +++ b/engines/sherlock/people.h @@ -24,35 +24,28 @@ #define SHERLOCK_PEOPLE_H #include "common/scummsys.h" -#include "common/serializer.h" #include "common/queue.h" #include "sherlock/objects.h" +#include "sherlock/saveload.h" namespace Sherlock { enum PeopleId { - PLAYER = 0, - AL = 0, - PEG = 1, - MAX_CHARACTERS = 6, - MAX_NPC = 5, + HOLMES = 0, + WATSON = 1, MAX_NPC_PATH = 200 }; -// Animation sequence identifiers for characters -enum { - WALK_RIGHT = 0, WALK_DOWN = 1, WALK_LEFT = 2, WALK_UP = 3, STOP_LEFT = 4, - STOP_DOWN = 5, STOP_RIGHT = 6, STOP_UP = 7, WALK_UPRIGHT = 8, - WALK_DOWNRIGHT = 9, WALK_UPLEFT = 10, WALK_DOWNLEFT = 11, - STOP_UPRIGHT = 12, STOP_UPLEFT = 13, STOP_DOWNRIGHT = 14, - STOP_DOWNLEFT = 15, TALK_RIGHT = 6, TALK_LEFT = 4 -}; - enum { MAP_UP = 1, MAP_UPRIGHT = 2, MAP_RIGHT = 1, MAP_DOWNRIGHT = 4, MAP_DOWN = 5, MAP_DOWNLEFT = 6, MAP_LEFT = 2, MAP_UPLEFT = 8 }; +#define NUM_IN_WALK_LIB 10 +extern const char *const WALK_LIB_NAMES[10]; + +#define MAX_CHARACTERS (IS_SERRATED_SCALPEL ? 1 : 6) + struct PersonData { const char *_name; const char *_portrait; @@ -64,46 +57,55 @@ struct PersonData { }; class Person : public Sprite { +protected: + /** + * Get the source position for a character potentially affected by scaling + */ + virtual Common::Point getSourcePoint() const = 0; public: + Common::Queue<Common::Point> _walkTo; + int _srcZone, _destZone; bool _walkLoaded; Common::String _portrait; - - // NPC related fields - int _npcIndex; - int _npcStack; - bool _npcPause; - byte _npcPath[MAX_NPC_PATH]; + Common::Point _walkDest; Common::String _npcName; - int _tempX; - int _tempScaleVal; // Rose Tattoo fields Common::String _walkVGSName; // Name of walk library person is using public: Person(); + virtual ~Person() {} + + /** + * Called to set the character walking to the current cursor location. + * It uses the zones and the inter-zone points to determine a series + * of steps to walk to get to that position. + */ + void goAllTheWay(); /** - * Clear the NPC related data + * Walk to the co-ordinates passed, and then face the given direction + */ + virtual void walkToCoords(const Point32 &destPos, int destDir) = 0; + + /** + * Center the visible screen so that the person is in the center of the screen */ - void clearNPC(); + virtual void centerScreenOnPerson() {} }; class SherlockEngine; class People { -private: +protected: SherlockEngine *_vm; - Person _data[MAX_CHARACTERS]; - int _oldWalkSequence; - int _srcZone, _destZone; + Common::Array<Person *> _data; + + People(SherlockEngine *vm); public: Common::Array<PersonData> _characters; ImageFile *_talkPics; - Common::Point _walkDest; - Common::Point _hSavedPos; - int _hSavedFacing; - Common::Queue<Common::Point> _walkTo; - Person &_player; + PositionFacing _savedPos; bool _holmesOn; bool _portraitLoaded; bool _portraitsOn; @@ -119,17 +121,11 @@ public: int _walkControl; public: - People(SherlockEngine *vm); - ~People(); + static People *init(SherlockEngine *vm); + virtual ~People(); - Person &operator[](PeopleId id) { - assert(id < MAX_CHARACTERS); - return _data[id]; - } - Person &operator[](int idx) { - assert(idx < MAX_CHARACTERS); - return _data[idx]; - } + Person &operator[](PeopleId id) { return *_data[id]; } + Person &operator[](int idx) { return *_data[idx]; } /** * Reset the player data @@ -137,59 +133,50 @@ public: void reset(); /** - * Load the walking images for Sherlock - */ - bool loadWalk(); - - /** * If the walk data has been loaded, then it will be freed */ bool freeWalk(); /** - * Set the variables for moving a character from one poisition to another - * in a straight line - goAllTheWay must have been previously called to - * check for any obstacles in the path. - */ - void setWalking(); - - /** - * Bring a moving character to a standing position. If the Scalpel chessboard - * is being displayed, then the chraracter will always face down. + * Turn off any currently active portraits, and removes them from being drawn */ - void gotoStand(Sprite &sprite); + void clearTalking(); /** - * Walk to the co-ordinates passed, and then face the given direction - */ - void walkToCoords(const Common::Point &destPos, int destDir); + * Finds the scene background object corresponding to a specified speaker + */ + virtual int findSpeaker(int speaker); /** - * Called to set the character walking to the current cursor location. - * It uses the zones and the inter-zone points to determine a series - * of steps to walk to get to that position. + * Synchronize the data for a savegame */ - void goAllTheWay(); + virtual void synchronize(Serializer &s) = 0; /** - * Finds the scene background object corresponding to a specified speaker + * Change the sequence of the scene background object associated with the current speaker. */ - int findSpeaker(int speaker); + virtual void setTalkSequence(int speaker, int sequenceNum = 1) = 0; /** - * Turn off any currently active portraits, and removes them from being drawn + * Load the walking images for Sherlock */ - void clearTalking(); + virtual bool loadWalk() = 0; /** - * Setup the data for an animating speaker portrait at the top of the screen + * Restrict passed point to zone using Sherlock's positioning rules */ - void setTalking(int speaker); + virtual const Common::Point restrictToZone(int zoneId, const Common::Point &destPos) = 0; /** - * Synchronize the data for a savegame + * If the specified speaker is a background object, it will set it so that it uses + * the Listen Sequence (specified by the sequence number). If the current sequence + * has an Allow Talk Code in it, the _gotoSeq field will be set so that the object + * begins listening as soon as it hits the Allow Talk Code. If there is no Abort Code, + * the Listen Sequence will begin immediately. + * @param speaker Who is speaking + * @param sequenceNum Which listen sequence to use */ - void synchronize(Common::Serializer &s); + virtual void setListenSequence(int speaker, int sequenceNum = 1) = 0; }; } // End of namespace Sherlock diff --git a/engines/sherlock/resources.cpp b/engines/sherlock/resources.cpp index 2e6a0c2d7c..c4093048bd 100644 --- a/engines/sherlock/resources.cpp +++ b/engines/sherlock/resources.cpp @@ -64,7 +64,7 @@ void Cache::load(const Common::String &name, Common::SeekableReadStream &stream) // Check whether the file is compressed if (signature == MKTAG('L', 'Z', 'V', 26)) { - // It's compressed, so decompress the file and store it's data in the cache entry + // It's compressed, so decompress the file and store its data in the cache entry Common::SeekableReadStream *decompressed = _vm->_res->decompress(stream); cacheEntry.resize(decompressed->size()); decompressed->read(&cacheEntry[0], decompressed->size()); @@ -89,18 +89,37 @@ Resources::Resources(SherlockEngine *vm) : _vm(vm), _cache(vm) { _resourceIndex = -1; if (_vm->_interactiveFl) { - addToCache("vgs.lib"); - addToCache("talk.lib"); - addToCache("journal.txt"); + if (!IS_3DO) { + addToCache("vgs.lib"); + addToCache("talk.lib"); + addToCache("journal.txt"); + + if (IS_SERRATED_SCALPEL) { + addToCache("sequence.txt"); + addToCache("portrait.lib"); + } else { + addToCache("walk.lib"); + } + } else { + // 3DO - if (IS_SERRATED_SCALPEL) { - addToCache("sequence.txt"); - addToCache("portrait.lib"); + // ITEM data from VGS.LIB is in ITEM.LIB + addToCache("item.lib"); + + // talk.lib - resources themselves seem to be the same, although a few texts were slightly changed + addToCache("talk.lib"); + + // remaining files are missing + // portraits were replaced with FMV } } } void Resources::addToCache(const Common::String &filename) { + // Return immediately if the library has already been loaded + if (_indexes.contains(filename)) + return; + _cache.load(filename); // Check to see if the file is a library @@ -176,16 +195,27 @@ void Resources::decompressIfNecessary(Common::SeekableReadStream *&stream) { } } -Common::SeekableReadStream *Resources::load(const Common::String &filename, const Common::String &libraryFile) { +Common::SeekableReadStream *Resources::load(const Common::String &filename, const Common::String &libraryFile, + bool suppressErrors) { // Open up the library for access Common::SeekableReadStream *libStream = load(libraryFile); - // Check if the library has already had it's index read, and if not, load it + // Check if the library has already had its index read, and if not, load it if (!_indexes.contains(libraryFile)) loadLibraryIndex(libraryFile, libStream, false); + LibraryIndex &libIndex = _indexes[libraryFile]; + + // Handle if resource is not present + if (!libIndex.contains(filename)) { + if (!suppressErrors) + error("Could not find resource - %s", filename.c_str()); + + delete libStream; + return nullptr; + } // Extract the data for the specified resource and return it - LibraryEntry &entry = _indexes[libraryFile][filename]; + LibraryEntry &entry = libIndex[filename]; libStream->seek(entry._offset); Common::SeekableReadStream *stream = libStream->readStream(entry._size); decompressIfNecessary(stream); @@ -203,38 +233,80 @@ void Resources::loadLibraryIndex(const Common::String &libFilename, Common::SeekableReadStream *stream, bool isNewStyle) { uint32 offset, nextOffset; + // Return immediately if the library has already been loaded + if (_indexes.contains(libFilename)) + return; + // Create an index entry _indexes[libFilename] = LibraryIndex(); LibraryIndex &index = _indexes[libFilename]; // Read in the number of resources stream->seek(4); - int count = stream->readUint16LE(); + int count = 0; - if (isNewStyle) - stream->seek((count + 1) * 8, SEEK_CUR); + if (!IS_3DO) { + // PC + count = stream->readUint16LE(); - // Loop through reading in the entries - for (int idx = 0; idx < count; ++idx) { - // Read the name of the resource - char resName[13]; - stream->read(resName, 13); - resName[12] = '\0'; + if (isNewStyle) + stream->seek((count + 1) * 8, SEEK_CUR); - // Read the offset - offset = stream->readUint32LE(); + // Loop through reading in the entries + for (int idx = 0; idx < count; ++idx) { + // Read the name of the resource + char resName[13]; + stream->read(resName, 13); + resName[12] = '\0'; - if (idx == (count - 1)) { - nextOffset = stream->size(); - } else { - // Read the size by jumping forward to read the next entry's offset - stream->seek(13, SEEK_CUR); - nextOffset = stream->readUint32LE(); - stream->seek(-17, SEEK_CUR); + // Read the offset + offset = stream->readUint32LE(); + + if (idx == (count - 1)) { + nextOffset = stream->size(); + } else { + // Read the size by jumping forward to read the next entry's offset + stream->seek(13, SEEK_CUR); + nextOffset = stream->readUint32LE(); + stream->seek(-17, SEEK_CUR); + } + + // Add the entry to the index + index[resName] = LibraryEntry(idx, offset, nextOffset - offset); } - // Add the entry to the index - index[resName] = LibraryEntry(idx, offset, nextOffset - offset); + } else { + // 3DO + count = stream->readUint16BE(); + + // 3DO header + // Loop through reading in the entries + + // Read offset of first entry + offset = stream->readUint32BE(); + + for (int idx = 0; idx < count; ++idx) { + + // Read the name of the resource + char resName[13]; + stream->read(resName, 13); + resName[12] = '\0'; + + stream->skip(3); // filler + + if (idx == (count - 1)) { + nextOffset = stream->size(); + } else { + // Read the offset of the next entry + nextOffset = stream->readUint32BE(); + } + + // Add the entry to the index + index[resName] = LibraryEntry(idx, offset, nextOffset - offset); + + // use next offset as current offset + offset = nextOffset; + } } } @@ -242,10 +314,18 @@ int Resources::resourceIndex() const { return _resourceIndex; } +void Resources::getResourceNames(const Common::String &libraryFile, Common::StringArray &names) { + addToCache(libraryFile); + LibraryIndex &libIndex = _indexes[libraryFile]; + for (LibraryIndex::iterator i = libIndex.begin(); i != libIndex.end(); ++i) { + names.push_back(i->_key); + } +} + Common::SeekableReadStream *Resources::decompress(Common::SeekableReadStream &source) { // This variation can't be used by Rose Tattoo, since compressed resources include the input size, // not the output size. Which means their decompression has to be done via passed buffers - assert(_vm->getGameID() == GType_SerratedScalpel); + assert(IS_SERRATED_SCALPEL); uint32 id = source.readUint32BE(); assert(id == MKTAG('L', 'Z', 'V', 0x1A)); @@ -255,7 +335,7 @@ Common::SeekableReadStream *Resources::decompress(Common::SeekableReadStream &so } Common::SeekableReadStream *Resources::decompress(Common::SeekableReadStream &source, uint32 outSize) { - int inSize = (_vm->getGameID() == GType_RoseTattoo) ? source.readSint32LE() : -1; + int inSize = IS_ROSE_TATTOO ? source.readSint32LE() : -1; byte *outBuffer = (byte *)malloc(outSize); Common::MemoryReadStream *outStream = new Common::MemoryReadStream(outBuffer, outSize, DisposeAfterUse::YES); @@ -265,7 +345,7 @@ Common::SeekableReadStream *Resources::decompress(Common::SeekableReadStream &so } void Resources::decompress(Common::SeekableReadStream &source, byte *buffer, uint32 outSize) { - int inputSize = (_vm->getGameID() == GType_RoseTattoo) ? source.readSint32LE() : -1; + int inputSize = IS_ROSE_TATTOO ? source.readSint32LE() : -1; decompressLZ(source, buffer, outSize, inputSize); } @@ -316,184 +396,4 @@ void Resources::decompressLZ(Common::SeekableReadStream &source, byte *outBuffer } while ((outSize == -1 || outBuffer < outBufferEnd) && (inSize == -1 || source.pos() < endPos)); } -/*----------------------------------------------------------------*/ - -SherlockEngine *ImageFile::_vm; - -void ImageFile::setVm(SherlockEngine *vm) { - _vm = vm; -} - -ImageFile::ImageFile(const Common::String &name, bool skipPal, bool animImages) { - Common::SeekableReadStream *stream = _vm->_res->load(name); - - Common::fill(&_palette[0], &_palette[PALETTE_SIZE], 0); - load(*stream, skipPal, animImages); - - delete stream; -} - -ImageFile::ImageFile(Common::SeekableReadStream &stream, bool skipPal) { - Common::fill(&_palette[0], &_palette[PALETTE_SIZE], 0); - load(stream, skipPal, false); -} - -ImageFile::~ImageFile() { - for (uint idx = 0; idx < size(); ++idx) - (*this)[idx]._frame.free(); -} - -void ImageFile::load(Common::SeekableReadStream &stream, bool skipPalette, bool animImages) { - loadPalette(stream); - - int streamSize = stream.size(); - while (stream.pos() < streamSize) { - ImageFrame frame; - frame._width = stream.readUint16LE() + 1; - frame._height = stream.readUint16LE() + 1; - frame._paletteBase = stream.readByte(); - - if (animImages) { - // Animation cutscene image files use a 16-bit x offset - frame._offset.x = stream.readUint16LE(); - frame._rleEncoded = (frame._offset.x & 0xff) == 1; - frame._offset.y = stream.readByte(); - } else { - // Standard image files have a separate byte for the RLE flag, and an 8-bit X offset - frame._rleEncoded = stream.readByte() == 1; - frame._offset.x = stream.readByte(); - frame._offset.y = stream.readByte(); - } - - frame._rleEncoded = !skipPalette && frame._rleEncoded; - - if (frame._paletteBase) { - // Nibble packed frame data - frame._size = (frame._width * frame._height) / 2; - } else if (frame._rleEncoded) { - // This size includes the header size, which we subtract - frame._size = stream.readUint16LE() - 11; - frame._rleMarker = stream.readByte(); - } else { - // Uncompressed data - frame._size = frame._width * frame._height; - } - - // Load data for frame and decompress it - byte *data = new byte[frame._size]; - stream.read(data, frame._size); - decompressFrame(frame, data); - delete[] data; - - push_back(frame); - } -} - -void ImageFile::loadPalette(Common::SeekableReadStream &stream) { - // Check for palette - int v1 = stream.readUint16LE() + 1; - int v2 = stream.readUint16LE() + 1; - stream.skip(1); // Skip paletteBase byte - bool rleEncoded = stream.readByte() == 1; - int palSize = v1 * v2; - - if ((palSize - 12) == PALETTE_SIZE && !rleEncoded) { - // Found palette, so read it in - stream.seek(2 + 12, SEEK_CUR); - for (int idx = 0; idx < PALETTE_SIZE; ++idx) - _palette[idx] = VGA_COLOR_TRANS(stream.readByte()); - } else { - // Not a palette, so rewind to start of frame data for normal frame processing - stream.seek(-6, SEEK_CUR); - } -} - -void ImageFile::decompressFrame(ImageFrame &frame, const byte *src) { - frame._frame.create(frame._width, frame._height, Graphics::PixelFormat::createFormatCLUT8()); - - if (frame._paletteBase) { - // Nibble-packed - byte *pDest = (byte *)frame._frame.getPixels(); - for (uint idx = 0; idx < frame._size; ++idx, ++src) { - *pDest++ = *src & 0xF; - *pDest++ = (*src >> 4); - } - } else if (frame._rleEncoded && _vm->getGameID() == GType_RoseTattoo) { - // Rose Tattoo run length encoding doesn't use the RLE marker byte - byte *dst = (byte *)frame._frame.getPixels(); - - for (int yp = 0; yp < frame._height; ++yp) { - int xSize = frame._width; - while (xSize > 0) { - // Skip a given number of pixels - byte skip = *src++; - dst += skip; - xSize -= skip; - if (!xSize) - break; - - // Get a run length, and copy the following number of pixels - int rleCount = *src++; - xSize -= rleCount; - while (rleCount-- > 0) - *dst++ = *src++; - } - assert(xSize == 0); - } - } else if (frame._rleEncoded) { - // RLE encoded - byte *dst = (byte *)frame._frame.getPixels(); - - int frameSize = frame._width * frame._height; - while (frameSize > 0) { - if (*src == frame._rleMarker) { - byte rleColor = src[1]; - byte rleCount = src[2]; - src += 3; - frameSize -= rleCount; - while (rleCount--) - *dst++ = rleColor; - } else { - *dst++ = *src++; - --frameSize; - } - } - assert(frameSize == 0); - } else { - // Uncompressed frame - Common::copy(src, src + frame._width * frame._height, - (byte *)frame._frame.getPixels()); - } -} - -/*----------------------------------------------------------------*/ - -int ImageFrame::sDrawXSize(int scaleVal) const { - int width = _width; - int scale = scaleVal == 0 ? 1 : scaleVal; - - if (scaleVal >= 256) - --width; - - int result = width * 256 / scale; - if (scaleVal >= 256) - ++result; - - return result; -} - -int ImageFrame::sDrawYSize(int scaleVal) const { - int height = _height; - int scale = scaleVal == 0 ? 1 : scaleVal; - - if (scaleVal >= 256) - --height; - - int result = height * 256 / scale; - if (scaleVal >= 256) - ++result; - - return result; -} - } // End of namespace Sherlock diff --git a/engines/sherlock/resources.h b/engines/sherlock/resources.h index 5c071e3922..99d58a51b1 100644 --- a/engines/sherlock/resources.h +++ b/engines/sherlock/resources.h @@ -29,6 +29,7 @@ #include "common/hash-str.h" #include "common/rect.h" #include "common/str.h" +#include "common/str-array.h" #include "common/stream.h" #include "graphics/surface.h" @@ -88,7 +89,7 @@ private: int _resourceIndex; /** - * Reads in the index from a library file, and caches it's index for later use + * Reads in the index from a library file, and caches its index for later use */ void loadLibraryIndex(const Common::String &libFilename, Common::SeekableReadStream *stream, bool isNewStyle); public: @@ -96,7 +97,7 @@ public: /** * Adds the specified file to the cache. If it's a library file, takes care of - * loading it's index for future use + * loading its index for future use */ void addToCache(const Common::String &filename); @@ -113,7 +114,7 @@ public: bool isInCache(const Common::String &filename) const { return _cache.isCached(filename); } /** - * Checks the passed stream, and if is compressed, deletes it and replaces it with it's uncompressed data + * Checks the passed stream, and if is compressed, deletes it and replaces it with its uncompressed data */ void decompressIfNecessary(Common::SeekableReadStream *&stream); @@ -125,7 +126,7 @@ public: /** * Loads a specific resource from a given library file */ - Common::SeekableReadStream *load(const Common::String &filename, const Common::String &libraryFile); + Common::SeekableReadStream *load(const Common::String &filename, const Common::String &libraryFile, bool suppressErrors = false); /** * Returns true if the given file exists on disk or in the cache @@ -133,13 +134,18 @@ public: bool exists(const Common::String &filename) const; /** - * Returns the index of the last loaded resource in it's given library file. + * Returns the index of the last loaded resource in its given library file. * This will be used primarily when loading talk files, so the engine can * update the given conversation number in the journal */ int resourceIndex() const; /** + * Produces a list of all resource names within a file. Used by the debugger. + */ + void getResourceNames(const Common::String &libraryFile, Common::StringArray &names); + + /** * Decompresses LZW compressed data */ Common::SeekableReadStream *decompress(Common::SeekableReadStream &source); @@ -165,53 +171,6 @@ public: static void decompressLZ(Common::SeekableReadStream &source, byte *outBuffer, int32 outSize, int32 inSize); }; -struct ImageFrame { - uint32 _size; - uint16 _width, _height; - int _paletteBase; - bool _rleEncoded; - Common::Point _offset; - byte _rleMarker; - Graphics::Surface _frame; - - /** - * Return the frame width adjusted by a specified scale amount - */ - int sDrawXSize(int scaleVal) const; - - /** - * Return the frame height adjusted by a specified scale amount - */ - int sDrawYSize(int scaleVal) const; -}; - -class ImageFile : public Common::Array<ImageFrame> { -private: - static SherlockEngine *_vm; - - /** - * Load the data of the sprite - */ - void load(Common::SeekableReadStream &stream, bool skipPalette, bool animImages); - - /** - * Gets the palette at the start of the sprite file - */ - void loadPalette(Common::SeekableReadStream &stream); - - /** - * Decompress a single frame for the sprite - */ - void decompressFrame(ImageFrame &frame, const byte *src); -public: - byte _palette[256 * 3]; -public: - ImageFile(const Common::String &name, bool skipPal = false, bool animImages = false); - ImageFile(Common::SeekableReadStream &stream, bool skipPal = false); - ~ImageFile(); - static void setVm(SherlockEngine *vm); -}; - } // End of namespace Sherlock #endif diff --git a/engines/sherlock/saveload.cpp b/engines/sherlock/saveload.cpp index ddf4917a34..f32552c7ea 100644 --- a/engines/sherlock/saveload.cpp +++ b/engines/sherlock/saveload.cpp @@ -23,31 +23,30 @@ #include "sherlock/saveload.h" #include "sherlock/surface.h" #include "sherlock/sherlock.h" +#include "sherlock/scalpel/scalpel_saveload.h" +#include "sherlock/tattoo/widget_files.h" #include "common/system.h" #include "graphics/scaler.h" #include "graphics/thumbnail.h" namespace Sherlock { -const int ENV_POINTS[6][3] = { - { 41, 80, 61 }, // Exit - { 81, 120, 101 }, // Load - { 121, 160, 141 }, // Save - { 161, 200, 181 }, // Up - { 201, 240, 221 }, // Down - { 241, 280, 261 } // Quit -}; - -static const char *const EMPTY_SAVEGAME_SLOT = "-EMPTY-"; +const char *const EMPTY_SAVEGAME_SLOT = "-EMPTY-"; static const char *const SAVEGAME_STR = "SHLK"; #define SAVEGAME_STR_SIZE 4 /*----------------------------------------------------------------*/ +SaveManager *SaveManager::init(SherlockEngine *vm, const Common::String &target) { + if (vm->getGameID() == GType_SerratedScalpel) + return new Scalpel::ScalpelSaveManager(vm, target); + else + return new Tattoo::WidgetFiles(vm, target); +} + SaveManager::SaveManager(SherlockEngine *vm, const Common::String &target) : _vm(vm), _target(target) { _saveThumb = nullptr; - _envMode = SAVEMODE_NONE; _justLoaded = false; _savegameIndex = 0; } @@ -59,54 +58,6 @@ SaveManager::~SaveManager() { } } -void SaveManager::drawInterface() { - Screen &screen = *_vm->_screen; - UserInterface &ui = *_vm->_ui; - - // Create a list of savegame slots - createSavegameList(); - - screen._backBuffer1.fillRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y + 10), BORDER_COLOR); - screen._backBuffer1.fillRect(Common::Rect(0, CONTROLS_Y + 10, 2, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); - screen._backBuffer1.fillRect(Common::Rect(318, CONTROLS_Y + 10, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); - screen._backBuffer1.fillRect(Common::Rect(0, 199, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); - screen._backBuffer1.fillRect(Common::Rect(2, CONTROLS_Y + 10, SHERLOCK_SCREEN_WIDTH - 2, SHERLOCK_SCREEN_HEIGHT - 2), INV_BACKGROUND); - - screen.makeButton(Common::Rect(ENV_POINTS[0][0], CONTROLS_Y, ENV_POINTS[0][1], CONTROLS_Y + 10), - ENV_POINTS[0][2] - screen.stringWidth("Exit") / 2, "Exit"); - screen.makeButton(Common::Rect(ENV_POINTS[1][0], CONTROLS_Y, ENV_POINTS[1][1], CONTROLS_Y + 10), - ENV_POINTS[1][2] - screen.stringWidth("Load") / 2, "Load"); - screen.makeButton(Common::Rect(ENV_POINTS[2][0], CONTROLS_Y, ENV_POINTS[2][1], CONTROLS_Y + 10), - ENV_POINTS[2][2] - screen.stringWidth("Save") / 2, "Save"); - screen.makeButton(Common::Rect(ENV_POINTS[3][0], CONTROLS_Y, ENV_POINTS[3][1], CONTROLS_Y + 10), - ENV_POINTS[3][2] - screen.stringWidth("Up") / 2, "Up"); - screen.makeButton(Common::Rect(ENV_POINTS[4][0], CONTROLS_Y, ENV_POINTS[4][1], CONTROLS_Y + 10), - ENV_POINTS[4][2] - screen.stringWidth("Down") / 2, "Down"); - screen.makeButton(Common::Rect(ENV_POINTS[5][0], CONTROLS_Y, ENV_POINTS[5][1], CONTROLS_Y + 10), - ENV_POINTS[5][2] - screen.stringWidth("Quit") / 2, "Quit"); - - if (!_savegameIndex) - screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_NULL, 0, "Up"); - - if (_savegameIndex == MAX_SAVEGAME_SLOTS - ONSCREEN_FILES_COUNT) - screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_NULL, 0, "Down"); - - for (int idx = _savegameIndex; idx < _savegameIndex + ONSCREEN_FILES_COUNT; ++idx) { - screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10), - INV_FOREGROUND, "%d.", idx + 1); - screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10), - INV_FOREGROUND, "%s", _savegames[idx].c_str()); - } - - if (!ui._slideWindows) { - screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); - } else { - ui.summonWindow(); - } - - _envMode = SAVEMODE_NONE; -} - void SaveManager::createSavegameList() { Screen &screen = *_vm->_screen; @@ -116,7 +67,7 @@ void SaveManager::createSavegameList() { SaveStateList saveList = getSavegameList(_target); for (uint idx = 0; idx < saveList.size(); ++idx) { - int slot = saveList[idx].getSaveSlot() - 1; + int slot = saveList[idx].getSaveSlot(); if (slot >= 0 && slot < MAX_SAVEGAME_SLOTS) _savegames[slot] = saveList[idx].getDescription(); } @@ -153,7 +104,9 @@ SaveStateList SaveManager::getSavegameList(const Common::String &target) { Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(*file); if (in) { - readSavegameHeader(in, header); + if (!readSavegameHeader(in, header)) + continue; + saveList.push_back(SaveStateDescriptor(slot, header._saveName)); header._thumbnail->free(); @@ -176,7 +129,7 @@ bool SaveManager::readSavegameHeader(Common::InSaveFile *in, SherlockSavegameHea return false; header._version = in->readByte(); - if (header._version > SHERLOCK_SAVEGAME_VERSION) + if (header._version < MINIMUM_SAVEGAME_VERSION || header._version > CURRENT_SAVEGAME_VERSION) return false; // Read in the string @@ -204,7 +157,7 @@ void SaveManager::writeSavegameHeader(Common::OutSaveFile *out, SherlockSavegame // Write out a savegame header out->write(SAVEGAME_STR, SAVEGAME_STR_SIZE + 1); - out->writeByte(SHERLOCK_SAVEGAME_VERSION); + out->writeByte(CURRENT_SAVEGAME_VERSION); // Write savegame name out->write(header._saveName.c_str(), header._saveName.size()); @@ -236,56 +189,19 @@ void SaveManager::createThumbnail() { delete _saveThumb; } - uint8 thumbPalette[PALETTE_SIZE]; - _vm->_screen->getPalette(thumbPalette); _saveThumb = new Graphics::Surface(); - ::createThumbnail(_saveThumb, (const byte *)_vm->_screen->getPixels(), SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT, thumbPalette); -} -int SaveManager::getHighlightedButton() const { - Common::Point pt = _vm->_events->mousePos(); - - for (int idx = 0; idx < 6; ++idx) { - if (pt.x > ENV_POINTS[idx][0] && pt.x < ENV_POINTS[idx][1] && pt.y > CONTROLS_Y - && pt.y < (CONTROLS_Y + 10)) - return idx; + if (!IS_3DO) { + uint8 thumbPalette[PALETTE_SIZE]; + _vm->_screen->getPalette(thumbPalette); + ::createThumbnail(_saveThumb, (const byte *)_vm->_screen->getPixels(), SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT, thumbPalette); + } else { + ::createThumbnailFromScreen(_saveThumb); } - - return -1; -} - -void SaveManager::highlightButtons(int btnIndex) { - Screen &screen = *_vm->_screen; - byte color = (btnIndex == 0) ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND; - - screen.buttonPrint(Common::Point(ENV_POINTS[0][2], CONTROLS_Y), color, 1, "Exit"); - - if ((btnIndex == 1) || ((_envMode == SAVEMODE_LOAD) && (btnIndex != 2))) - screen.buttonPrint(Common::Point(ENV_POINTS[1][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Load"); - else - screen.buttonPrint(Common::Point(ENV_POINTS[1][2], CONTROLS_Y), COMMAND_FOREGROUND, true, "Load"); - - if ((btnIndex == 2) || ((_envMode == SAVEMODE_SAVE) && (btnIndex != 1))) - screen.buttonPrint(Common::Point(ENV_POINTS[2][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Save"); - else - screen.buttonPrint(Common::Point(ENV_POINTS[2][2], CONTROLS_Y), COMMAND_FOREGROUND, true, "Save"); - - if (btnIndex == 3 && _savegameIndex) - screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Up"); - else - if (_savegameIndex) - screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_FOREGROUND, true, "Up"); - - if ((btnIndex == 4) && (_savegameIndex < MAX_SAVEGAME_SLOTS - 5)) - screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Down"); - else if (_savegameIndex < (MAX_SAVEGAME_SLOTS - 5)) - screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_FOREGROUND, true, "Down"); - - color = (btnIndex == 5) ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND; - screen.buttonPrint(Common::Point(ENV_POINTS[5][2], CONTROLS_Y), color, 1, "Quit"); } void SaveManager::loadGame(int slot) { + Events &events = *_vm->_events; Common::InSaveFile *saveFile = g_system->getSavefileManager()->openForLoading( generateSaveName(slot)); if (!saveFile) @@ -302,13 +218,16 @@ void SaveManager::loadGame(int slot) { } // Synchronize the savegame data - Common::Serializer s(saveFile, nullptr); + Serializer s(saveFile, nullptr); + s.setSaveVersion(header._version); synchronize(s); delete saveFile; + events.clearEvents(); } void SaveManager::saveGame(int slot, const Common::String &name) { + Events &events = *_vm->_events; Common::OutSaveFile *out = g_system->getSavefileManager()->openForSaving( generateSaveName(slot)); @@ -317,18 +236,20 @@ void SaveManager::saveGame(int slot, const Common::String &name) { writeSavegameHeader(out, header); // Synchronize the savegame data - Common::Serializer s(nullptr, out); + Serializer s(nullptr, out); + s.setSaveVersion(CURRENT_SAVEGAME_VERSION); synchronize(s); out->finalize(); delete out; + events.clearEvents(); } Common::String SaveManager::generateSaveName(int slot) { return Common::String::format("%s.%03d", _target.c_str(), slot); } -void SaveManager::synchronize(Common::Serializer &s) { +void SaveManager::synchronize(Serializer &s) { Inventory &inv = *_vm->_inventory; Journal &journal = *_vm->_journal; Map &map = *_vm->_map; @@ -351,130 +272,7 @@ void SaveManager::synchronize(Common::Serializer &s) { if (screen.fontNumber() != oldFont) journal.resetPosition(); - _justLoaded = true; -} - -bool SaveManager::checkGameOnScreen(int slot) { - Screen &screen = *_vm->_screen; - - // Check if it's already on-screen - if (slot != -1 && (slot < _savegameIndex || slot >= (_savegameIndex + ONSCREEN_FILES_COUNT))) { - _savegameIndex = slot; - - screen._backBuffer1.fillRect(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2, - SHERLOCK_SCREEN_HEIGHT - 1), INV_BACKGROUND); - - for (int idx = _savegameIndex; idx < (_savegameIndex + 5); ++idx) { - screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10), - INV_FOREGROUND, "%d.", idx + 1); - screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10), - INV_FOREGROUND, "%s", _savegames[idx].c_str()); - } - - screen.slamRect(Common::Rect(3, CONTROLS_Y + 11, 318, SHERLOCK_SCREEN_HEIGHT)); - - byte color = !_savegameIndex ? COMMAND_NULL : COMMAND_FOREGROUND; - screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), color, 1, "Up"); - - color = (_savegameIndex == (MAX_SAVEGAME_SLOTS - 5)) ? COMMAND_NULL : COMMAND_FOREGROUND; - screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), color, 1, "Down"); - - return true; - } - - return false; -} - -bool SaveManager::promptForDescription(int slot) { - Events &events = *_vm->_events; - Scene &scene = *_vm->_scene; - Screen &screen = *_vm->_screen; - Talk &talk = *_vm->_talk; - int xp, yp; - bool flag = false; - - screen.buttonPrint(Common::Point(ENV_POINTS[0][2], CONTROLS_Y), COMMAND_NULL, true, "Exit"); - screen.buttonPrint(Common::Point(ENV_POINTS[1][2], CONTROLS_Y), COMMAND_NULL, true, "Load"); - screen.buttonPrint(Common::Point(ENV_POINTS[2][2], CONTROLS_Y), COMMAND_NULL, true, "Save"); - screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_NULL, true, "Up"); - screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_NULL, true, "Down"); - screen.buttonPrint(Common::Point(ENV_POINTS[5][2], CONTROLS_Y), COMMAND_NULL, true, "Quit"); - - Common::String saveName = _savegames[slot]; - if (isSlotEmpty(slot)) { - // It's an empty slot, so start off with an empty save name - saveName = ""; - - yp = CONTROLS_Y + 12 + (slot - _savegameIndex) * 10; - screen.vgaBar(Common::Rect(24, yp, 85, yp + 9), INV_BACKGROUND); - } - - screen.print(Common::Point(6, CONTROLS_Y + 12 + (slot - _savegameIndex) * 10), TALK_FOREGROUND, "%d.", slot + 1); - screen.print(Common::Point(24, CONTROLS_Y + 12 + (slot - _savegameIndex) * 10), TALK_FOREGROUND, "%s", saveName.c_str()); - xp = 24 + screen.stringWidth(saveName); - yp = CONTROLS_Y + 12 + (slot - _savegameIndex) * 10; - - int done = 0; - do { - while (!_vm->shouldQuit() && !events.kbHit()) { - scene.doBgAnim(); - - if (talk._talkToAbort) - return false; - - // Allow event processing - events.pollEventsAndWait(); - events.setButtonState(); - - flag = !flag; - if (flag) - screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_FOREGROUND); - else - screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_BACKGROUND); - } - if (_vm->shouldQuit()) - return false; - - // Get the next keypress - Common::KeyState keyState = events.getKey(); - - if (keyState.keycode == Common::KEYCODE_BACKSPACE && saveName.size() > 0) { - // Delete character of save name - screen.vgaBar(Common::Rect(xp - screen.charWidth(saveName.lastChar()), yp - 1, - xp + 8, yp + 9), INV_BACKGROUND); - xp -= screen.charWidth(saveName.lastChar()); - screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_FOREGROUND); - saveName.deleteLastChar(); - - } else if (keyState.keycode == Common::KEYCODE_RETURN && saveName.compareToIgnoreCase(EMPTY_SAVEGAME_SLOT)) { - done = 1; - - } else if (keyState.keycode == Common::KEYCODE_ESCAPE) { - screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_BACKGROUND); - done = -1; - - } else if (keyState.ascii >= ' ' && keyState.ascii <= 'z' && saveName.size() < 50 - && (xp + screen.charWidth(keyState.ascii)) < 308) { - char c = (char)keyState.ascii; - - screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_BACKGROUND); - screen.print(Common::Point(xp, yp), TALK_FOREGROUND, "%c", c); - xp += screen.charWidth(c); - screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_FOREGROUND); - saveName += c; - } - } while (!done); - - if (done == 1) { - // Enter key perssed - _savegames[slot] = saveName; - } else { - done = 0; - _envMode = SAVEMODE_NONE; - highlightButtons(-1); - } - - return done == 1; + _justLoaded = s.isLoading(); } bool SaveManager::isSlotEmpty(int slot) const { diff --git a/engines/sherlock/saveload.h b/engines/sherlock/saveload.h index a7ed852a5f..f4f3e7cfd9 100644 --- a/engines/sherlock/saveload.h +++ b/engines/sherlock/saveload.h @@ -34,11 +34,15 @@ namespace Sherlock { #define MAX_SAVEGAME_SLOTS 99 #define ONSCREEN_FILES_COUNT 5 -#define SHERLOCK_SAVEGAME_VERSION 1 + +enum { + CURRENT_SAVEGAME_VERSION = 4, + MINIMUM_SAVEGAME_VERSION = 4 +}; enum SaveMode { SAVEMODE_NONE = 0, SAVEMODE_LOAD = 1, SAVEMODE_SAVE = 2 }; -extern const int ENV_POINTS[6][3]; +extern const char *const EMPTY_SAVEGAME_SLOT; struct SherlockSavegameHeader { uint8 _version; @@ -51,8 +55,22 @@ struct SherlockSavegameHeader { class SherlockEngine; + +/** + * Derived serializer class with extra synchronization types + */ +class Serializer : public Common::Serializer { +public: + Serializer(Common::SeekableReadStream *in, Common::WriteStream *out) : Common::Serializer(in, out) {} + + /** + * New method to allow setting the version + */ + void setSaveVersion(byte version) { _version = version; } +}; + class SaveManager { -private: +protected: SherlockEngine *_vm; Common::String _target; Graphics::Surface *_saveThumb; @@ -65,20 +83,15 @@ private: /** * Synchronize the data for a savegame */ - void synchronize(Common::Serializer &s); + void synchronize(Serializer &s); public: Common::StringArray _savegames; int _savegameIndex; - SaveMode _envMode; bool _justLoaded; public: + static SaveManager *init(SherlockEngine *vm, const Common::String &target); SaveManager(SherlockEngine *vm, const Common::String &target); - ~SaveManager(); - - /** - * Shows the in-game dialog interface for loading and saving games - */ - void drawInterface(); + virtual ~SaveManager(); /** * Creates a thumbnail for the current on-screen contents @@ -127,16 +140,6 @@ public: void saveGame(int slot, const Common::String &name); /** - * Make sure that the selected savegame is on-screen - */ - bool checkGameOnScreen(int slot); - - /** - * Prompts the user to enter a description in a given slot - */ - bool promptForDescription(int slot); - - /** * Returns true if the given save slot is empty */ bool isSlotEmpty(int slot) const; diff --git a/engines/sherlock/scalpel/3do/movie_decoder.cpp b/engines/sherlock/scalpel/3do/movie_decoder.cpp new file mode 100644 index 0000000000..8e8f99bc19 --- /dev/null +++ b/engines/sherlock/scalpel/3do/movie_decoder.cpp @@ -0,0 +1,510 @@ +/* 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 "common/scummsys.h" +#include "common/stream.h" +#include "common/textconsole.h" + +#include "audio/audiostream.h" +#include "audio/decoders/raw.h" +#include "audio/decoders/3do.h" + +#include "sherlock/scalpel/3do/movie_decoder.h" +#include "image/codecs/cinepak.h" + +// for Test-Code +#include "common/system.h" +#include "common/events.h" +#include "common/keyboard.h" +#include "engines/engine.h" +#include "engines/util.h" +#include "graphics/palette.h" +#include "graphics/pixelformat.h" +#include "graphics/surface.h" + +namespace Sherlock { + +Scalpel3DOMovieDecoder::Scalpel3DOMovieDecoder() + : _stream(0), _videoTrack(0), _audioTrack(0) { + _streamVideoOffset = 0; + _streamAudioOffset = 0; +} + +Scalpel3DOMovieDecoder::~Scalpel3DOMovieDecoder() { + close(); +} + +bool Scalpel3DOMovieDecoder::loadStream(Common::SeekableReadStream *stream) { + uint32 videoSubType = 0; + uint32 videoCodecTag = 0; + uint32 videoHeight = 0; + uint32 videoWidth = 0; + uint32 videoFrameCount = 0; + uint32 audioSubType = 0; + uint32 audioCodecTag = 0; + uint32 audioChannels = 0; + uint32 audioSampleRate = 0; + + close(); + + _stream = stream; + _streamVideoOffset = 0; + _streamAudioOffset = 0; + + // Look for packets that we care about + static const int maxPacketCheckCount = 20; + for (int i = 0; i < maxPacketCheckCount; i++) { + uint32 chunkTag = _stream->readUint32BE(); + uint32 chunkSize = _stream->readUint32BE() - 8; + + // Bail out if done + if (_stream->eos()) + break; + + uint32 dataStartOffset = _stream->pos(); + + switch (chunkTag) { + case MKTAG('F','I','L','M'): { + // See if this is a FILM header + _stream->skip(4); // time stamp (based on 240 per second) + _stream->skip(4); // Unknown 0x00000000 + videoSubType = _stream->readUint32BE(); + + switch (videoSubType) { + case MKTAG('F', 'H', 'D', 'R'): + // FILM header found + if (_videoTrack) { + warning("Sherlock 3DO movie: Multiple FILM headers found"); + close(); + return false; + } + _stream->readUint32BE(); + videoCodecTag = _stream->readUint32BE(); + videoHeight = _stream->readUint32BE(); + videoWidth = _stream->readUint32BE(); + _stream->skip(4); // time scale + videoFrameCount = _stream->readUint32BE(); + + _videoTrack = new StreamVideoTrack(videoWidth, videoHeight, videoCodecTag, videoFrameCount); + addTrack(_videoTrack); + break; + + case MKTAG('F', 'R', 'M', 'E'): + break; + + default: + warning("Sherlock 3DO movie: Unknown subtype inside FILM packet"); + close(); + return false; + } + break; + } + + case MKTAG('S','N','D','S'): { + _stream->skip(8); + audioSubType = _stream->readUint32BE(); + + switch (audioSubType) { + case MKTAG('S', 'H', 'D', 'R'): + // Audio header + + // Bail if we already have a track + if (_audioTrack) { + warning("Sherlock 3DO movie: Multiple SNDS headers found"); + close(); + return false; + } + + // OK, this is the start of a audio stream + _stream->readUint32BE(); // Version, always 0x00000000 + _stream->readUint32BE(); // Unknown 0x00000008 ?! + _stream->readUint32BE(); // Unknown 0x00007500 + _stream->readUint32BE(); // Unknown 0x00004000 + _stream->readUint32BE(); // Unknown 0x00000000 + _stream->readUint32BE(); // Unknown 0x00000010 + audioSampleRate = _stream->readUint32BE(); + audioChannels = _stream->readUint32BE(); + audioCodecTag = _stream->readUint32BE(); + _stream->readUint32BE(); // Unknown 0x00000004 compression ratio? + _stream->readUint32BE(); // Unknown 0x00000A2C + + _audioTrack = new StreamAudioTrack(audioCodecTag, audioSampleRate, audioChannels); + addTrack(_audioTrack); + break; + + case MKTAG('S', 'S', 'M', 'P'): + // Audio data + break; + default: + warning("Sherlock 3DO movie: Unknown subtype inside FILM packet"); + close(); + return false; + } + break; + } + + case MKTAG('C','T','R','L'): + case MKTAG('F','I','L','L'): // filler chunk, fills to certain boundary + case MKTAG('D','A','C','Q'): + case MKTAG('J','O','I','N'): // add cel data (not used in sherlock) + // Ignore these chunks + break; + + case MKTAG('S','H','D','R'): + // Happens for EA logo, seems to be garbage data right at the start of the file + break; + + default: + warning("Unknown chunk-tag '%s' inside Sherlock 3DO movie", tag2str(chunkTag)); + close(); + return false; + } + + if ((_videoTrack) && (_audioTrack)) + break; + + // Seek to next chunk + _stream->seek(dataStartOffset + chunkSize); + } + + // Bail if we didn't find video + audio + if ((!_videoTrack) || (!_audioTrack)) { + close(); + return false; + } + + // Rewind back to the beginning + _stream->seek(0); + + return true; +} + +void Scalpel3DOMovieDecoder::close() { + Video::VideoDecoder::close(); + + delete _stream; _stream = 0; + _videoTrack = 0; +} + +// We try to at least decode 1 frame +// and also try to get at least 0.5 seconds of audio queued up +void Scalpel3DOMovieDecoder::readNextPacket() { + uint32 currentMovieTime = getTime(); + uint32 wantedAudioQueued = currentMovieTime + 500; // always try to be 0.500 seconds in front of movie time + + int32 chunkOffset = 0; + int32 dataStartOffset = 0; + int32 nextChunkOffset = 0; + uint32 chunkTag = 0; + uint32 chunkSize = 0; + + uint32 videoSubType = 0; + uint32 videoTimeStamp = 0; + uint32 videoFrameSize = 0; + uint32 audioSubType = 0; + uint32 audioBytes = 0; + bool videoGotFrame = false; + bool videoDone = false; + bool audioDone = false; + + // Seek to smallest stream offset + if (_streamVideoOffset <= _streamAudioOffset) { + _stream->seek(_streamVideoOffset); + } else { + _stream->seek(_streamAudioOffset); + } + + if (wantedAudioQueued <= _audioTrack->getTotalAudioQueued()) { + // already got enough audio queued up + audioDone = true; + } + + while (1) { + chunkOffset = _stream->pos(); + assert(chunkOffset >= 0); + + // Read chunk header + chunkTag = _stream->readUint32BE(); + chunkSize = _stream->readUint32BE() - 8; + + // Calculate offsets + dataStartOffset = _stream->pos(); + assert(dataStartOffset >= 0); + nextChunkOffset = dataStartOffset + chunkSize; + + //warning("offset %lx - tag %lx", dataStartOffset, tag); + + if (_stream->eos()) + break; + + switch (chunkTag) { + case MKTAG('F','I','L','M'): + videoTimeStamp = _stream->readUint32BE(); + _stream->skip(4); // Unknown + videoSubType = _stream->readUint32BE(); + + switch (videoSubType) { + case MKTAG('F', 'H', 'D', 'R'): + // Ignore video header + break; + + case MKTAG('F', 'R', 'M', 'E'): + // Found frame data + if (_streamVideoOffset <= chunkOffset) { + // We are at an offset that is still relevant to video decoding + if (!videoDone) { + if (!videoGotFrame) { + // We haven't decoded any frame yet, so do so now + _stream->readUint32BE(); + videoFrameSize = _stream->readUint32BE(); + _videoTrack->decodeFrame(_stream->readStream(videoFrameSize), videoTimeStamp); + + _streamVideoOffset = nextChunkOffset; + videoGotFrame = true; + + } else { + // Already decoded a frame, so get timestamp of follow-up frame + // and then we are done with video + + // Calculate next frame time + // 3DO clock time for movies runs at 240Hh, that's why timestamps are based on 240. + uint32 currentFrameStartTime = _videoTrack->getNextFrameStartTime(); + uint32 nextFrameStartTime = videoTimeStamp * 1000 / 240; + assert(currentFrameStartTime <= nextFrameStartTime); + _videoTrack->setNextFrameStartTime(nextFrameStartTime); + + // next time we want to start at the current chunk + _streamVideoOffset = chunkOffset; + videoDone = true; + } + } + } + break; + + default: + error("Sherlock 3DO movie: Unknown subtype inside FILM packet"); + break; + } + break; + + case MKTAG('S','N','D','S'): + _stream->skip(8); + audioSubType = _stream->readUint32BE(); + + switch (audioSubType) { + case MKTAG('S', 'H', 'D', 'R'): + // Ignore the audio header + break; + + case MKTAG('S', 'S', 'M', 'P'): + // Got audio chunk + if (_streamAudioOffset <= chunkOffset) { + // We are at an offset that is still relevant to audio decoding + if (!audioDone) { + audioBytes = _stream->readUint32BE(); + _audioTrack->queueAudio(_stream, audioBytes); + + _streamAudioOffset = nextChunkOffset; + if (wantedAudioQueued <= _audioTrack->getTotalAudioQueued()) { + // Got enough audio + audioDone = true; + } + } + } + break; + + default: + error("Sherlock 3DO movie: Unknown subtype inside SNDS packet"); + break; + } + break; + + case MKTAG('C','T','R','L'): + case MKTAG('F','I','L','L'): // filler chunk, fills to certain boundary + case MKTAG('D','A','C','Q'): + case MKTAG('J','O','I','N'): // add cel data (not used in sherlock) + // Ignore these chunks + break; + + case MKTAG('S','H','D','R'): + // Happens for EA logo, seems to be garbage data right at the start of the file + break; + + default: + error("Unknown chunk-tag '%s' inside Sherlock 3DO movie", tag2str(chunkTag)); + } + + // Always seek to end of chunk + // Sometimes not all of the chunk is filled with audio + _stream->seek(nextChunkOffset); + + if ((videoDone) && (audioDone)) { + return; + } + } +} + +Scalpel3DOMovieDecoder::StreamVideoTrack::StreamVideoTrack(uint32 width, uint32 height, uint32 codecTag, uint32 frameCount) { + _width = width; + _height = height; + _frameCount = frameCount; + _curFrame = -1; + _nextFrameStartTime = 0; + + // Create the Cinepak decoder, if we're using it + if (codecTag == MKTAG('c', 'v', 'i', 'd')) + _codec = new Image::CinepakDecoder(); + else + error("Unsupported Sherlock 3DO movie video codec tag '%s'", tag2str(codecTag)); +} + +Scalpel3DOMovieDecoder::StreamVideoTrack::~StreamVideoTrack() { + delete _codec; +} + +bool Scalpel3DOMovieDecoder::StreamVideoTrack::endOfTrack() const { + return getCurFrame() >= getFrameCount() - 1; +} + +Graphics::PixelFormat Scalpel3DOMovieDecoder::StreamVideoTrack::getPixelFormat() const { + return _codec->getPixelFormat(); +} + +void Scalpel3DOMovieDecoder::StreamVideoTrack::decodeFrame(Common::SeekableReadStream *stream, uint32 videoTimeStamp) { + _surface = _codec->decodeFrame(*stream); + _curFrame++; +} + +Scalpel3DOMovieDecoder::StreamAudioTrack::StreamAudioTrack(uint32 codecTag, uint32 sampleRate, uint32 channels) { + switch (codecTag) { + case MKTAG('A','D','P','4'): + case MKTAG('S','D','X','2'): + // ADP4 + SDX2 are both allowed + break; + + default: + error("Unsupported Sherlock 3DO movie audio codec tag '%s'", tag2str(codecTag)); + } + + _totalAudioQueued = 0; // currently 0 milliseconds queued + + _codecTag = codecTag; + _sampleRate = sampleRate; + switch (channels) { + case 1: + _stereo = false; + break; + case 2: + _stereo = true; + break; + default: + error("Unsupported Sherlock 3DO movie audio channels %d", channels); + } + + _audioStream = Audio::makeQueuingAudioStream(sampleRate, _stereo); + + // reset audio decoder persistent spaces + memset(&_ADP4_PersistentSpace, 0, sizeof(_ADP4_PersistentSpace)); + memset(&_SDX2_PersistentSpace, 0, sizeof(_SDX2_PersistentSpace)); +} + +Scalpel3DOMovieDecoder::StreamAudioTrack::~StreamAudioTrack() { + delete _audioStream; +// free(_ADP4_PersistentSpace); +// free(_SDX2_PersistentSpace); +} + +void Scalpel3DOMovieDecoder::StreamAudioTrack::queueAudio(Common::SeekableReadStream *stream, uint32 size) { + Common::SeekableReadStream *compressedAudioStream = 0; + Audio::RewindableAudioStream *audioStream = 0; + uint32 audioLengthMSecs = 0; + + // Read the specified chunk into memory + compressedAudioStream = stream->readStream(size); + + switch(_codecTag) { + case MKTAG('A','D','P','4'): + audioStream = Audio::make3DO_ADP4AudioStream(compressedAudioStream, _sampleRate, _stereo, &audioLengthMSecs, DisposeAfterUse::YES, &_ADP4_PersistentSpace); + break; + case MKTAG('S','D','X','2'): + audioStream = Audio::make3DO_SDX2AudioStream(compressedAudioStream, _sampleRate, _stereo, &audioLengthMSecs, DisposeAfterUse::YES, &_SDX2_PersistentSpace); + break; + default: + break; + } + if (audioStream) { + _totalAudioQueued += audioLengthMSecs; + _audioStream->queueAudioStream(audioStream, DisposeAfterUse::YES); + } else { + // in case there was an error + delete compressedAudioStream; + } +} + +Audio::AudioStream *Scalpel3DOMovieDecoder::StreamAudioTrack::getAudioStream() const { + return _audioStream; +} + +// Test-code + +// Code for showing a movie. Only meant for testing/debug purposes +bool Scalpel3DOMoviePlay(const char *filename, Common::Point pos) { + Scalpel3DOMovieDecoder *videoDecoder = new Scalpel3DOMovieDecoder(); + + if (!videoDecoder->loadFile(filename)) { + warning("Scalpel3DOMoviePlay: could not open '%s'", filename); + return false; + } + + bool skipVideo = false; + //byte bytesPerPixel = videoDecoder->getPixelFormat().bytesPerPixel; + uint16 width = videoDecoder->getWidth(); + uint16 height = videoDecoder->getHeight(); + //uint16 pitch = videoDecoder->getWidth() * bytesPerPixel; + + videoDecoder->start(); + + while (!g_engine->shouldQuit() && !videoDecoder->endOfVideo() && (!skipVideo)) { + if (videoDecoder->needsUpdate()) { + const Graphics::Surface *frame = videoDecoder->decodeNextFrame(); + + if (frame) { + g_system->copyRectToScreen(frame->getPixels(), frame->pitch, pos.x, pos.y, width, height); + g_system->updateScreen(); + } + } + + Common::Event event; + while (g_system->getEventManager()->pollEvent(event)) { + if ((event.type == Common::EVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_ESCAPE)) + skipVideo = true; + } + + g_system->delayMillis(10); + } + videoDecoder->close(); + delete videoDecoder; + + return !skipVideo; +} + +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/3do/movie_decoder.h b/engines/sherlock/scalpel/3do/movie_decoder.h new file mode 100644 index 0000000000..9f1670fc6c --- /dev/null +++ b/engines/sherlock/scalpel/3do/movie_decoder.h @@ -0,0 +1,127 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_SCALPEL_3DO_MOVIE_DECODER_H +#define SHERLOCK_SCALPEL_3DO_MOVIE_DECODER_H + +#include "common/rect.h" +#include "video/video_decoder.h" +#include "audio/decoders/3do.h" + +namespace Audio { +class QueuingAudioStream; +} + +namespace Common { +class SeekableReadStream; +} + +namespace Image { +class Codec; +} + +namespace Sherlock { + +class Scalpel3DOMovieDecoder : public Video::VideoDecoder { +public: + Scalpel3DOMovieDecoder(); + ~Scalpel3DOMovieDecoder(); + + bool loadStream(Common::SeekableReadStream *stream); + void close(); + +protected: + void readNextPacket(); + +private: + int32 _streamVideoOffset; /* current stream offset for video decoding */ + int32 _streamAudioOffset; /* current stream offset for audio decoding */ + +private: + class StreamVideoTrack : public VideoTrack { + public: + StreamVideoTrack(uint32 width, uint32 height, uint32 codecTag, uint32 frameCount); + ~StreamVideoTrack(); + + bool endOfTrack() const; + + uint16 getWidth() const { return _width; } + uint16 getHeight() const { return _height; } + Graphics::PixelFormat getPixelFormat() const; + int getCurFrame() const { return _curFrame; } + int getFrameCount() const { return _frameCount; } + void setNextFrameStartTime(uint32 nextFrameStartTime) { _nextFrameStartTime = nextFrameStartTime; } + uint32 getNextFrameStartTime() const { return _nextFrameStartTime; } + const Graphics::Surface *decodeNextFrame() { return _surface; } + + void decodeFrame(Common::SeekableReadStream *stream, uint32 videoTimeStamp); + + private: + const Graphics::Surface *_surface; + + int _curFrame; + uint32 _frameCount; + uint32 _nextFrameStartTime; + + Image::Codec *_codec; + uint16 _width, _height; + }; + + class StreamAudioTrack : public AudioTrack { + public: + StreamAudioTrack(uint32 codecTag, uint32 sampleRate, uint32 channels); + ~StreamAudioTrack(); + + void queueAudio(Common::SeekableReadStream *stream, uint32 size); + + protected: + Audio::AudioStream *getAudioStream() const; + + private: + Audio::QueuingAudioStream *_audioStream; + uint32 _totalAudioQueued; /* total amount of milliseconds of audio, that we queued up already */ + + public: + uint32 getTotalAudioQueued() const { return _totalAudioQueued; } + + private: + int16 decodeSample(uint8 dataNibble); + + uint32 _codecTag; + uint16 _sampleRate; + bool _stereo; + + Audio::audio_3DO_ADP4_PersistentSpace _ADP4_PersistentSpace; + Audio::audio_3DO_SDX2_PersistentSpace _SDX2_PersistentSpace; + }; + + Common::SeekableReadStream *_stream; + StreamVideoTrack *_videoTrack; + StreamAudioTrack *_audioTrack; +}; + +// Testing +extern bool Scalpel3DOMoviePlay(const char *filename, Common::Point pos); + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/scalpel/drivers/adlib.cpp b/engines/sherlock/scalpel/drivers/adlib.cpp index db1151c841..29a39f0c39 100644 --- a/engines/sherlock/scalpel/drivers/adlib.cpp +++ b/engines/sherlock/scalpel/drivers/adlib.cpp @@ -35,21 +35,21 @@ namespace Sherlock { #define SHERLOCK_ADLIB_VOICES_COUNT 9 #define SHERLOCK_ADLIB_NOTES_COUNT 96 -byte adlib_Operator1Register[SHERLOCK_ADLIB_VOICES_COUNT] = { +byte operator1Register[SHERLOCK_ADLIB_VOICES_COUNT] = { 0x00, 0x01, 0x02, 0x08, 0x09, 0x0A, 0x10, 0x11, 0x12 }; -byte adlib_Operator2Register[SHERLOCK_ADLIB_VOICES_COUNT] = { +byte operator2Register[SHERLOCK_ADLIB_VOICES_COUNT] = { 0x03, 0x04, 0x05, 0x0B, 0x0C, 0x0D, 0x13, 0x14, 0x15 }; -struct adlib_percussionChannelEntry { +struct percussionChannelEntry { byte requiredNote; byte replacementNote; }; // hardcoded, dumped from ADHOM.DRV -const adlib_percussionChannelEntry adlib_percussionChannelTable[SHERLOCK_ADLIB_VOICES_COUNT] = { +const percussionChannelEntry percussionChannelTable[SHERLOCK_ADLIB_VOICES_COUNT] = { { 0x00, 0x00 }, { 0x00, 0x00 }, { 0x00, 0x00 }, @@ -61,7 +61,7 @@ const adlib_percussionChannelEntry adlib_percussionChannelTable[SHERLOCK_ADLIB_V { 0x26, 0x1E } }; -struct adlib_InstrumentEntry { +struct InstrumentEntry { byte reg20op1; byte reg40op1; byte reg60op1; @@ -77,7 +77,7 @@ struct adlib_InstrumentEntry { }; // hardcoded, dumped from ADHOM.DRV -const adlib_InstrumentEntry adlib_instrumentTable[] = { +const InstrumentEntry instrumentTable[] = { { 0x71, 0x89, 0x51, 0x11, 0x00, 0x61, 0x23, 0x42, 0x15, 0x01, 0x02, 0xF4 }, { 0x22, 0x20, 0x97, 0x89, 0x00, 0xA2, 0x1F, 0x70, 0x07, 0x00, 0x0A, 0xF4 }, { 0x70, 0x1A, 0x64, 0x13, 0x00, 0x20, 0x1F, 0x53, 0x46, 0x00, 0x0E, 0xF4 }, @@ -203,7 +203,7 @@ const adlib_InstrumentEntry adlib_instrumentTable[] = { }; // hardcoded, dumped from ADHOM.DRV -uint16 adlib_FrequencyLookUpTable[SHERLOCK_ADLIB_NOTES_COUNT] = { +uint16 frequencyLookUpTable[SHERLOCK_ADLIB_NOTES_COUNT] = { 0x0158, 0x016C, 0x0182, 0x0199, 0x01B1, 0x01CB, 0x01E6, 0x0203, 0x0222, 0x0242, 0x0265, 0x0289, 0x0558, 0x056C, 0x0582, 0x0599, 0x05B1, 0x05CB, 0x05E6, 0x0603, 0x0622, 0x0642, 0x0665, 0x0689, 0x0958, 0x096C, 0x0982, 0x0999, 0x09B1, 0x09CB, @@ -216,13 +216,14 @@ uint16 adlib_FrequencyLookUpTable[SHERLOCK_ADLIB_NOTES_COUNT] = { 0x1DE6, 0x1E03, 0x1E22, 0x1E42, 0x1E65, 0x1E89 }; -class MidiDriver_AdLib : public MidiDriver_Emulated { +class MidiDriver_SH_AdLib : public MidiDriver { public: - MidiDriver_AdLib(Audio::Mixer *mixer) - : MidiDriver_Emulated(mixer), _masterVolume(15), _opl(0) { + MidiDriver_SH_AdLib(Audio::Mixer *mixer) + : _masterVolume(15), _opl(0), + _adlibTimerProc(0), _adlibTimerParam(0), _isOpen(false) { memset(_voiceChannelMapping, 0, sizeof(_voiceChannelMapping)); } - virtual ~MidiDriver_AdLib() { } + virtual ~MidiDriver_SH_AdLib() { } // MidiDriver int open(); @@ -230,15 +231,13 @@ public: void send(uint32 b); MidiChannel *allocateChannel() { return NULL; } MidiChannel *getPercussionChannel() { return NULL; } + bool isOpen() const { return _isOpen; } + uint32 getBaseTempo() { return 1000000 / OPL::OPL::kDefaultCallbackFrequency; } - // AudioStream - bool isStereo() const { return false; } - int getRate() const { return _mixer->getOutputRate(); } int getPolyphony() const { return SHERLOCK_ADLIB_VOICES_COUNT; } bool hasRhythmChannel() const { return false; } - // MidiDriver_Emulated - void generateSamples(int16 *buf, int len); + virtual void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc); void setVolume(byte volume); virtual uint32 property(int prop, uint32 param); @@ -249,7 +248,7 @@ private: struct adlib_ChannelEntry { bool inUse; uint16 inUseTimer; - const adlib_InstrumentEntry *currentInstrumentPtr; + const InstrumentEntry *currentInstrumentPtr; byte currentNote; byte currentA0hReg; byte currentB0hReg; @@ -261,19 +260,22 @@ private: OPL::OPL *_opl; int _masterVolume; + Common::TimerManager::TimerProc _adlibTimerProc; + void *_adlibTimerParam; + + bool _isOpen; + // points to a MIDI channel for each of the new voice channels byte _voiceChannelMapping[SHERLOCK_ADLIB_VOICES_COUNT]; // stores information about all FM voice channels adlib_ChannelEntry _channels[SHERLOCK_ADLIB_VOICES_COUNT]; -protected: void onTimer(); -private: void resetAdLib(); - void resetAdLib_OperatorRegisters(byte baseRegister, byte value); - void resetAdLib_FMVoiceChannelRegisters(byte baseRegister, byte value); + void resetAdLibOperatorRegisters(byte baseRegister, byte value); + void resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value); void programChange(byte MIDIchannel, byte parameter); void setRegister(int reg, int value); @@ -284,32 +286,31 @@ private: void pitchBendChange(byte MIDIchannel, byte parameter1, byte parameter2); }; -int MidiDriver_AdLib::open() { - int rate = _mixer->getOutputRate(); - - debug(3, "ADLIB: Starting driver"); +int MidiDriver_SH_AdLib::open() { + debugC(kDebugLevelAdLibDriver, "AdLib: starting driver"); _opl = OPL::Config::create(OPL::Config::kOpl2); if (!_opl) return -1; - _opl->init(rate); + _opl->init(); - MidiDriver_Emulated::open(); + _isOpen = true; - _mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, _mixer->kMaxChannelVolume, 0, DisposeAfterUse::NO); + _opl->start(new Common::Functor0Mem<void, MidiDriver_SH_AdLib>(this, &MidiDriver_SH_AdLib::onTimer)); return 0; } -void MidiDriver_AdLib::close() { - _mixer->stopHandle(_mixerSoundHandle); +void MidiDriver_SH_AdLib::close() { + // Stop the OPL timer + _opl->stop(); delete _opl; } -void MidiDriver_AdLib::setVolume(byte volume) { +void MidiDriver_SH_AdLib::setVolume(byte volume) { _masterVolume = volume; //renewNotes(-1, true); } @@ -317,7 +318,13 @@ void MidiDriver_AdLib::setVolume(byte volume) { // this should/must get called per tick // original driver did this before MIDI data processing on each tick // we do it atm after MIDI data processing -void MidiDriver_AdLib::onTimer() { +void MidiDriver_SH_AdLib::onTimer() { + if (_adlibTimerProc) + (*_adlibTimerProc)(_adlibTimerParam); + + // this should/must get called per tick + // original driver did this before MIDI data processing on each tick + // we do it atm after MIDI data processing for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) { if (_channels[FMvoiceChannel].inUse) { _channels[FMvoiceChannel].inUseTimer++; @@ -326,7 +333,7 @@ void MidiDriver_AdLib::onTimer() { } // Called when a music track got loaded into memory -void MidiDriver_AdLib::newMusicData(byte *musicData, int32 musicDataSize) { +void MidiDriver_SH_AdLib::newMusicData(byte *musicData, int32 musicDataSize) { assert(musicDataSize >= 0x7F); // MIDI Channel <-> FM Voice Channel mapping at offset 0x22 of music data memcpy(&_voiceChannelMapping, musicData + 0x22, 9); @@ -338,7 +345,7 @@ void MidiDriver_AdLib::newMusicData(byte *musicData, int32 musicDataSize) { memset(&_channels, 0, sizeof(_channels)); } -void MidiDriver_AdLib::resetAdLib() { +void MidiDriver_SH_AdLib::resetAdLib() { setRegister(0x01, 0x20); // enable waveform control on both operators setRegister(0x04, 0xE0); // Timer control @@ -347,17 +354,17 @@ void MidiDriver_AdLib::resetAdLib() { setRegister(0xBD, 0); // disable Rhythm // reset FM voice instrument data - resetAdLib_OperatorRegisters(0x20, 0); - resetAdLib_OperatorRegisters(0x60, 0); - resetAdLib_OperatorRegisters(0x80, 0); - resetAdLib_FMVoiceChannelRegisters(0xA0, 0); - resetAdLib_FMVoiceChannelRegisters(0xB0, 0); - resetAdLib_FMVoiceChannelRegisters(0xC0, 0); - resetAdLib_OperatorRegisters(0xE0, 0); - resetAdLib_OperatorRegisters(0x40, 0x3F); + resetAdLibOperatorRegisters(0x20, 0); + resetAdLibOperatorRegisters(0x60, 0); + resetAdLibOperatorRegisters(0x80, 0); + resetAdLibFMVoiceChannelRegisters(0xA0, 0); + resetAdLibFMVoiceChannelRegisters(0xB0, 0); + resetAdLibFMVoiceChannelRegisters(0xC0, 0); + resetAdLibOperatorRegisters(0xE0, 0); + resetAdLibOperatorRegisters(0x40, 0x3F); } -void MidiDriver_AdLib::resetAdLib_OperatorRegisters(byte baseRegister, byte value) { +void MidiDriver_SH_AdLib::resetAdLibOperatorRegisters(byte baseRegister, byte value) { byte operatorIndex; for (operatorIndex = 0; operatorIndex < 0x16; operatorIndex++) { @@ -373,7 +380,7 @@ void MidiDriver_AdLib::resetAdLib_OperatorRegisters(byte baseRegister, byte valu } } -void MidiDriver_AdLib::resetAdLib_FMVoiceChannelRegisters(byte baseRegister, byte value) { +void MidiDriver_SH_AdLib::resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value) { byte FMvoiceChannel; for (FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) { @@ -382,7 +389,7 @@ void MidiDriver_AdLib::resetAdLib_FMVoiceChannelRegisters(byte baseRegister, byt } // MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php -void MidiDriver_AdLib::send(uint32 b) { +void MidiDriver_SH_AdLib::send(uint32 b) { byte command = b & 0xf0; byte channel = b & 0xf; byte op1 = (b >> 8) & 0xff; @@ -406,22 +413,18 @@ void MidiDriver_AdLib::send(uint32 b) { // Aftertouch doesn't seem to be implemented in the Sherlock Holmes adlib driver break; case 0xe0: - warning("pitch bend change"); + debugC(kDebugLevelAdLibDriver, "AdLib: pitch bend change"); pitchBendChange(channel, op1, op2); break; case 0xf0: // SysEx - warning("SysEx: %x", b); + warning("ADLIB: SysEx: %x", b); break; default: warning("ADLIB: Unknown event %02x", command); } } -void MidiDriver_AdLib::generateSamples(int16 *data, int len) { - _opl->readBuffer(data, len); -} - -void MidiDriver_AdLib::noteOn(byte MIDIchannel, byte note, byte velocity) { +void MidiDriver_SH_AdLib::noteOn(byte MIDIchannel, byte note, byte velocity) { int16 oldestInUseChannel = -1; uint16 oldestInUseTimer = 0; @@ -453,7 +456,7 @@ void MidiDriver_AdLib::noteOn(byte MIDIchannel, byte note, byte velocity) { } if (oldestInUseChannel >= 0) { // channel found - warning("used In-Use channel"); + debugC(kDebugLevelAdLibDriver, "AdLib: used In-Use channel"); // original driver used note 0, we use the current note // because using note 0 could create a bad note (out of index) and we check that. Original driver didn't. voiceOnOff(oldestInUseChannel, false, _channels[oldestInUseChannel].currentNote, 0); @@ -464,27 +467,26 @@ void MidiDriver_AdLib::noteOn(byte MIDIchannel, byte note, byte velocity) { voiceOnOff(oldestInUseChannel, true, note, velocity); return; } - warning("MIDI channel not mapped/all FM voice channels busy %d", MIDIchannel); + debugC(kDebugLevelAdLibDriver, "AdLib: MIDI channel not mapped/all FM voice channels busy %d", MIDIchannel); } else { // Percussion channel - warning("percussion!"); for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) { if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) { - if (note == adlib_percussionChannelTable[FMvoiceChannel].requiredNote) { + if (note == percussionChannelTable[FMvoiceChannel].requiredNote) { _channels[FMvoiceChannel].inUse = true; _channels[FMvoiceChannel].currentNote = note; - voiceOnOff(FMvoiceChannel, true, adlib_percussionChannelTable[FMvoiceChannel].replacementNote, velocity); + voiceOnOff(FMvoiceChannel, true, percussionChannelTable[FMvoiceChannel].replacementNote, velocity); return; } } } - warning("percussion MIDI channel not mapped/all FM voice channels busy"); + debugC(kDebugLevelAdLibDriver, "AdLib: percussion MIDI channel not mapped/all FM voice channels busy"); } } -void MidiDriver_AdLib::noteOff(byte MIDIchannel, byte note) { +void MidiDriver_SH_AdLib::noteOff(byte MIDIchannel, byte note) { for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) { if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) { if (_channels[FMvoiceChannel].currentNote == note) { @@ -496,7 +498,7 @@ void MidiDriver_AdLib::noteOff(byte MIDIchannel, byte note) { // not-percussion voiceOnOff(FMvoiceChannel, false, note, 0); } else { - voiceOnOff(FMvoiceChannel, false, adlib_percussionChannelTable[FMvoiceChannel].replacementNote, 0); + voiceOnOff(FMvoiceChannel, false, percussionChannelTable[FMvoiceChannel].replacementNote, 0); } return; } @@ -504,7 +506,7 @@ void MidiDriver_AdLib::noteOff(byte MIDIchannel, byte note) { } } -void MidiDriver_AdLib::voiceOnOff(byte FMvoiceChannel, bool keyOn, byte note, byte velocity) { +void MidiDriver_SH_AdLib::voiceOnOff(byte FMvoiceChannel, bool keyOn, byte note, byte velocity) { byte frequencyOffset = 0; uint16 frequency = 0; byte op2RegAdjust = 0; @@ -519,10 +521,10 @@ void MidiDriver_AdLib::voiceOnOff(byte FMvoiceChannel, bool keyOn, byte note, by frequencyOffset = note; } if (frequencyOffset >= SHERLOCK_ADLIB_NOTES_COUNT) { - warning("CRITICAL - bad note!!!"); + warning("CRITICAL - AdLib driver: bad note!!!"); return; } - frequency = adlib_FrequencyLookUpTable[frequencyOffset]; + frequency = frequencyLookUpTable[frequencyOffset]; if (keyOn) { // adjust register 40h @@ -530,7 +532,7 @@ void MidiDriver_AdLib::voiceOnOff(byte FMvoiceChannel, bool keyOn, byte note, by regValue40h = _channels[FMvoiceChannel].currentInstrumentPtr->reg40op2; } regValue40h = regValue40h - (velocity >> 3); - op2RegAdjust = adlib_Operator2Register[FMvoiceChannel]; + op2RegAdjust = operator2Register[FMvoiceChannel]; setRegister(0x40 + op2RegAdjust, regValue40h); } @@ -546,7 +548,7 @@ void MidiDriver_AdLib::voiceOnOff(byte FMvoiceChannel, bool keyOn, byte note, by _channels[FMvoiceChannel].currentB0hReg = regValueB0h; } -void MidiDriver_AdLib::pitchBendChange(byte MIDIchannel, byte parameter1, byte parameter2) { +void MidiDriver_SH_AdLib::pitchBendChange(byte MIDIchannel, byte parameter1, byte parameter2) { uint16 channelFrequency = 0; byte channelRegB0hWithoutFrequency = 0; uint16 parameter = 0; @@ -585,20 +587,20 @@ void MidiDriver_AdLib::pitchBendChange(byte MIDIchannel, byte parameter1, byte p } } -void MidiDriver_AdLib::programChange(byte MIDIchannel, byte op1) { - const adlib_InstrumentEntry *instrumentPtr; +void MidiDriver_SH_AdLib::programChange(byte MIDIchannel, byte op1) { + const InstrumentEntry *instrumentPtr; byte op1Reg = 0; byte op2Reg = 0; // setup instrument - instrumentPtr = &adlib_instrumentTable[op1]; + instrumentPtr = &instrumentTable[op1]; //warning("program change for MIDI channel %d, instrument id %d", MIDIchannel, op1); for (byte FMvoiceChannel = 0; FMvoiceChannel < SHERLOCK_ADLIB_VOICES_COUNT; FMvoiceChannel++) { if (_voiceChannelMapping[FMvoiceChannel] == MIDIchannel) { - op1Reg = adlib_Operator1Register[FMvoiceChannel]; - op2Reg = adlib_Operator2Register[FMvoiceChannel]; + op1Reg = operator1Register[FMvoiceChannel]; + op2Reg = operator2Register[FMvoiceChannel]; setRegister(0x20 + op1Reg, instrumentPtr->reg20op1); setRegister(0x40 + op1Reg, instrumentPtr->reg40op1); @@ -619,21 +621,26 @@ void MidiDriver_AdLib::programChange(byte MIDIchannel, byte op1) { } } } -void MidiDriver_AdLib::setRegister(int reg, int value) { +void MidiDriver_SH_AdLib::setRegister(int reg, int value) { _opl->write(0x220, reg); _opl->write(0x221, value); } -uint32 MidiDriver_AdLib::property(int prop, uint32 param) { +uint32 MidiDriver_SH_AdLib::property(int prop, uint32 param) { return 0; } -MidiDriver *MidiDriver_AdLib_create() { - return new MidiDriver_AdLib(g_system->getMixer()); +void MidiDriver_SH_AdLib::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) { + _adlibTimerProc = timerProc; + _adlibTimerParam = timerParam; +} + +MidiDriver *MidiDriver_SH_AdLib_create() { + return new MidiDriver_SH_AdLib(g_system->getMixer()); } -void MidiDriver_AdLib_newMusicData(MidiDriver *driver, byte *musicData, int32 musicDataSize) { - static_cast<MidiDriver_AdLib *>(driver)->newMusicData(musicData, musicDataSize); +void MidiDriver_SH_AdLib_newMusicData(MidiDriver *driver, byte *musicData, int32 musicDataSize) { + static_cast<MidiDriver_SH_AdLib *>(driver)->newMusicData(musicData, musicDataSize); } -} // End of namespace Sci +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/drivers/mididriver.h b/engines/sherlock/scalpel/drivers/mididriver.h index 64213315e8..1b8ceeda3d 100644 --- a/engines/sherlock/scalpel/drivers/mididriver.h +++ b/engines/sherlock/scalpel/drivers/mididriver.h @@ -20,8 +20,8 @@ * */ -#ifndef SHERLOCK_SOFTSEQ_MIDIDRIVER_H -#define SHERLOCK_SOFTSEQ_MIDIDRIVER_H +#ifndef SHERLOCK_SCALPEL_DRIVERS_MIDIDRIVER_H +#define SHERLOCK_SCALPEL_DRIVERS_MIDIDRIVER_H #include "sherlock/sherlock.h" #include "audio/mididrv.h" @@ -29,9 +29,13 @@ namespace Sherlock { -extern MidiDriver *MidiDriver_AdLib_create(); -extern void MidiDriver_AdLib_newMusicData(MidiDriver *driver, byte *musicData, int32 musicDataSize); +extern MidiDriver *MidiDriver_SH_AdLib_create(); +extern void MidiDriver_SH_AdLib_newMusicData(MidiDriver *driver, byte *musicData, int32 musicDataSize); -} // End of namespace Sci +extern MidiDriver *MidiDriver_MT32_create(); +extern void MidiDriver_MT32_uploadPatches(MidiDriver *driver, byte *driverData, int32 driverSize); +extern void MidiDriver_MT32_newMusicData(MidiDriver *driver, byte *musicData, int32 musicDataSize); -#endif // SHERLOCK_SOFTSEQ_MIDIDRIVER_H +} // End of namespace Sherlock + +#endif // SHERLOCK_SCALPEL_DRIVERS_MIDIDRIVER_H diff --git a/engines/sherlock/scalpel/drivers/mt32.cpp b/engines/sherlock/scalpel/drivers/mt32.cpp new file mode 100644 index 0000000000..33e7671719 --- /dev/null +++ b/engines/sherlock/scalpel/drivers/mt32.cpp @@ -0,0 +1,282 @@ +/* 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 "sherlock/sherlock.h" +#include "sherlock/scalpel/drivers/mididriver.h" + +#include "common/config-manager.h" +#include "common/file.h" +#include "common/system.h" +#include "common/textconsole.h" + +//#include "audio/mididrv.h" + +namespace Sherlock { + +#define SHERLOCK_MT32_CHANNEL_COUNT 16 + +const byte mt32ReverbDataSysEx[] = { + 0x10, 0x00, 0x01, 0x01, 0x05, 0x05, 0xFF +}; + +class MidiDriver_MT32 : public MidiDriver { +public: + MidiDriver_MT32() { + _driver = NULL; + _isOpen = false; + _nativeMT32 = false; + _baseFreq = 250; + + memset(_MIDIchannelActive, 1, sizeof(_MIDIchannelActive)); + } + virtual ~MidiDriver_MT32(); + + // MidiDriver + int open(); + void close(); + bool isOpen() const { return _isOpen; } + + void send(uint32 b); + + void newMusicData(byte *musicData, int32 musicDataSize); + + MidiChannel *allocateChannel() { + if (_driver) + return _driver->allocateChannel(); + return NULL; + } + MidiChannel *getPercussionChannel() { + if (_driver) + return _driver->getPercussionChannel(); + return NULL; + } + + void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) { + if (_driver) + _driver->setTimerCallback(timer_param, timer_proc); + } + + uint32 getBaseTempo() { + if (_driver) { + return _driver->getBaseTempo(); + } + return 1000000 / _baseFreq; + } + +protected: + Common::Mutex _mutex; + MidiDriver *_driver; + bool _nativeMT32; + + bool _isOpen; + int _baseFreq; + +private: + // points to a MIDI channel for each of the new voice channels + byte _MIDIchannelActive[SHERLOCK_MT32_CHANNEL_COUNT]; + +public: + void uploadMT32Patches(byte *driverData, int32 driverSize); + + void mt32SysEx(const byte *&dataPtr, int32 &bytesLeft); +}; + +MidiDriver_MT32::~MidiDriver_MT32() { + Common::StackLock lock(_mutex); + if (_driver) { + _driver->setTimerCallback(0, 0); + _driver->close(); + delete _driver; + } + _driver = NULL; +} + +int MidiDriver_MT32::open() { + assert(!_driver); + + debugC(kDebugLevelMT32Driver, "MT32: starting driver"); + + // Setup midi driver + MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_PREFER_MT32); + MusicType musicType = MidiDriver::getMusicType(dev); + + switch (musicType) { + case MT_MT32: + _nativeMT32 = true; + break; + case MT_GM: + if (ConfMan.getBool("native_mt32")) { + _nativeMT32 = true; + } + break; + default: + break; + } + + _driver = MidiDriver::createMidi(dev); + if (!_driver) + return 255; + + if (_nativeMT32) + _driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); + + int ret = _driver->open(); + if (ret) + return ret; + + if (_nativeMT32) + _driver->sendMT32Reset(); + else + _driver->sendGMReset(); + + return 0; +} + +void MidiDriver_MT32::close() { + if (_driver) { + _driver->close(); + } +} + +// Called when a music track got loaded into memory +void MidiDriver_MT32::newMusicData(byte *musicData, int32 musicDataSize) { + assert(musicDataSize >= 0x7F); // Security check + + // MIDI Channel Enable/Disable bytes at offset 0x2 of music data + memcpy(&_MIDIchannelActive, musicData + 0x2, SHERLOCK_MT32_CHANNEL_COUNT); + + // Send 16 bytes from offset 0x12 to MT32 + // All the music tracks of Sherlock seem to contain dummy data + // probably a feature, that was used in the game "Ski or Die" + // that's why we don't implement this + + // Also send these bytes to MT32 (SysEx) - seems to be reverb configuration + if (_nativeMT32) { + const byte *reverbData = mt32ReverbDataSysEx; + int32 reverbDataSize = sizeof(mt32ReverbDataSysEx); + mt32SysEx(reverbData, reverbDataSize); + } +} + +void MidiDriver_MT32::uploadMT32Patches(byte *driverData, int32 driverSize) { + if (!_driver) + return; + + if (!_nativeMT32) + return; + + // patch data starts at offset 0x863 + assert(driverSize == 0x13B9); // Security check + assert(driverData[0x863] == 0x7F); // another security check + + const byte *patchPtr = driverData + 0x863; + int32 bytesLeft = driverSize - 0x863; + + while(1) { + mt32SysEx(patchPtr, bytesLeft); + + assert(bytesLeft); + if (*patchPtr == 0x80) // List terminator + break; + } +} + +void MidiDriver_MT32::mt32SysEx(const byte *&dataPtr, int32 &bytesLeft) { + byte sysExMessage[270]; + uint16 sysExPos = 0; + byte sysExByte = 0; + uint16 sysExChecksum = 0; + + memset(&sysExMessage, 0, sizeof(sysExMessage)); + + sysExMessage[0] = 0x41; // Roland + sysExMessage[1] = 0x10; + sysExMessage[2] = 0x16; // Model MT32 + sysExMessage[3] = 0x12; // Command DT1 + + sysExPos = 4; + sysExChecksum = 0; + while (1) { + assert(bytesLeft); + + sysExByte = *dataPtr++; + bytesLeft--; + if (sysExByte == 0xff) + break; // Message done + + assert(sysExPos < sizeof(sysExMessage)); + sysExMessage[sysExPos++] = sysExByte; + sysExChecksum -= sysExByte; + } + + // Calculate checksum + assert(sysExPos < sizeof(sysExMessage)); + sysExMessage[sysExPos++] = sysExChecksum & 0x7f; + + debugC(kDebugLevelMT32Driver, "MT32: uploading patch data, size %d", sysExPos); + + // Send SysEx + _driver->sysEx(sysExMessage, sysExPos); + + // Wait the time it takes to send the SysEx data + uint32 delay = (sysExPos + 2) * 1000 / 3125; + + // Plus an additional delay for the MT-32 rev00 + if (_nativeMT32) + delay += 40; + + g_system->delayMillis(delay); +} + +// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php +void MidiDriver_MT32::send(uint32 b) { + byte command = b & 0xf0; + byte channel = b & 0xf; + + if (command == 0xF0) { + if (_driver) { + _driver->send(b); + } + return; + } + + if (_MIDIchannelActive[channel]) { + // Only forward MIDI-data in case the channel is currently enabled via music-data + if (_driver) { + _driver->send(b); + } + } +} + +MidiDriver *MidiDriver_MT32_create() { + return new MidiDriver_MT32(); +} + +void MidiDriver_MT32_newMusicData(MidiDriver *driver, byte *musicData, int32 musicDataSize) { + static_cast<MidiDriver_MT32 *>(driver)->newMusicData(musicData, musicDataSize); +} + +void MidiDriver_MT32_uploadPatches(MidiDriver *driver, byte *driverData, int32 driverSize) { + static_cast<MidiDriver_MT32 *>(driver)->uploadMT32Patches(driverData, driverSize); +} + +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/scalpel.cpp b/engines/sherlock/scalpel/scalpel.cpp index 43b69068ab..c3915a1cf2 100644 --- a/engines/sherlock/scalpel/scalpel.cpp +++ b/engines/sherlock/scalpel/scalpel.cpp @@ -22,10 +22,16 @@ #include "engines/util.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/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 { @@ -92,280 +98,80 @@ static const byte MAP_SEQUENCES[3][MAX_FRAME] = { #define MAX_PEOPLE 66 -const char PEOPLE_PORTRAITS[MAX_PEOPLE][5] = { - { "HOLM" }, // Sherlock Holmes - { "WATS" }, // Dr. Watson - { "LEST" }, // Inspector Lestrade - { "CON1" }, // Constable O'Brien - { "CON2" }, // Constable Lewis - { "SHEI" }, // Sheila Parker - { "HENR" }, // Henry Carruthers - { "LESL" }, // Lesley (flower girl) - { "USH1" }, // Usher #1 - { "USH2" }, // Usher #2 - { "FRED" }, // Fredrick Epstein - { "WORT" }, // Mrs. Worthington - { "COAC" }, // Coach - { "PLAY" }, // Player - { "WBOY" }, // Tim (Waterboy) - { "JAME" }, // James Sanders - { "BELL" }, // Belle (perfumerie) - { "GIRL" }, // Cleaning Girl (perfumerie) - { "EPST" }, // Epstien in the Opera Balcony - { "WIGG" }, // Wiggins - { "PAUL" }, // Paul (Brumwell / Carroway) - { "BART" }, // Bartender - { "DIRT" }, // Dirty Drunk - { "SHOU" }, // Shouting Drunk - { "STAG" }, // Staggering Drunk - { "BOUN" }, // Bouncer - { "SAND" }, // James Sanders - At Home - { "CORO" }, // The Coroner - { "EQUE" }, // The Equestrian Shop Keeper - { "GEOR" }, // George Blackwood - { "LARS" }, // Lars - { "PARK" }, // Sheila Parker (happy) - { "CHEM" }, // Chemist - { "GREG" }, // Inspector Gregson - { "LAWY" }, // Jacob Farthington Lawyer - { "MYCR" }, // Mycroft - { "SHER" }, // Old Sherman - { "CHMB" }, // Richard Chemist Stock boy - { "BARM" }, // Barman - { "DAND" }, // Dandy Player - { "ROUG" }, // Rough-looking Player - { "SPEC" }, // Spectator - { "HUNT" }, // Robert Hunt - { "VIOL" }, // Violet Secretary - { "PETT" }, // Pettigrew - { "APPL" }, // Augie (apple seller) - { "ANNA" }, // Anna Carroway - { "GUAR" }, // Guard - { "ANTO" }, // Antonio Caruso - { "TOBY" }, // Toby the Dog - { "KING" }, // Simon Kingsley - { "ALFR" }, // Alfred Tobacco Clerk - { "LADY" }, // Lady Brumwell - { "ROSA" }, // Madame Rosa - { "LADB" }, // Lady Brumwell - { "MOOR" }, // Joseph Moorehead - { "BEAL" }, // Mrs. Beale - { "LION" }, // Felix the Lion - { "HOLL" }, // Hollingston - { "CALL" }, // Constable Callaghan - { "JERE" }, // Sergeant Jeremy Duncan - { "LORD" }, // Lord Brumwell - { "NIGE" }, // Nigel Jameson - { "JONA" }, // Jonas (newspaper seller) - { "DUGA" }, // Constable Dugan - { "INSP" } // Inspector Lestrade (Scotland Yard) +struct PeopleData { + const char *portrait; + const char *name; + byte stillSequences[MAX_TALK_SEQUENCES]; + byte talkSequences[MAX_TALK_SEQUENCES]; }; -const char *const PEOPLE_NAMES[MAX_PEOPLE] = { - "Sherlock Holmes", - "Dr. Watson", - "Inspector Lestrade", - "Constable O'Brien", - "Constable Lewis", - "Sheila Parker", - "Henry Carruthers", - "Lesley", - "An Usher", - "An Usher", - "Fredrick Epstein", - "Mrs. Worthington", - "The Coach", - "A Player", - "Tim", - "James Sanders", - "Belle", - "Cleaning Girl", - "Fredrick Epstein", - "Wiggins", - "Paul", - "The Bartender", - "A Dirty Drunk", - "A Shouting Drunk", - "A Staggering Drunk", - "The Bouncer", - "James Sanders", - "The Coroner", - "Reginald Snipes", - "George Blackwood", - "Lars", - "Sheila Parker", - "The Chemist", - "Inspector Gregson", - "Jacob Farthington", - "Mycroft", - "Old Sherman", - "Richard", - "The Barman", - "A Dandy Player", - "A Rough-looking Player", - "A Spectator", - "Robert Hunt", - "Violet", - "Pettigrew", - "Augie", - "Anna Carroway", - "A Guard", - "Antonio Caruso", - "Toby the Dog", - "Simon Kingsley", - "Alfred", - "Lady Brumwell", - "Madame Rosa", - "Lady Brumwell", - "Joseph Moorehead", - "Mrs. Beale", - "Felix", - "Hollingston", - "Constable Callaghan", - "Sergeant Duncan", - "Lord Brumwell", - "Nigel Jaimeson", - "Jonas", - "Constable Dugan", - "Inspector Lestrade" -}; - -static const byte PEOPLE_STILL_SEQUENCES[MAX_PEOPLE][MAX_TALK_SEQUENCES] = { - { 1, 0, 0 }, // Sherlock Holmes - { 6, 0, 0 }, // Dr. Watson - { 4, 0, 0 }, // Inspector Lestrade - { 2, 0, 0 }, // Constable #1 - { 2, 0, 0 }, // Constable #2 - { 2, 0, 0 }, // Sheila Parker - { 3, 0, 0 }, // Henry Carruthers - { 9, 0, 0 }, // Lesly (flower girl) - { 13, 0, 0 }, // Usher #1 - { 2, 0, 0 }, // Usher #2 - { 4, 0, 0 }, // Fredrick Epstein - { 9, 0, 0 }, // Mrs.Worthington - { 2, 0, 0 }, // Coach - { 8, 0, 0 }, // Player - { 13, 0, 0 }, // Waterboy - { 6, 0, 0 }, // James Sanders - { 1, 0, 0 }, // Belle (perfumerie) - { 20, 0, 0 }, // Cleaning Girl (perfumerie) - { 17, 0, 0 }, // Epstien in the Opera Balcony - { 3, 0, 0 }, // Wiggins - { 2, 0, 0 }, // Paul (Brumwell/Carroway) - { 1, 0, 0 }, // Bartender - { 1, 0, 0 }, // Dirty Drunk - { 1, 0, 0 }, // Shouting Drunk - { 1, 0, 0 }, // Staggering Drunk - { 1, 0, 0 }, // Bouncer - { 6, 0, 0 }, // James Sanders - At Home - { 6, 0, 0 }, // The Coroner - { 1, 0, 0 }, // The Equestrian Shop Keeper - { 1, 0, 0 }, // George Blackwood - { 7, 0, 0 }, // Lars - { 1, 0, 0 }, // Sheila Parker - { 8, 0, 0 }, // Chemist - { 6, 0, 0 }, // Inspector Gregson - { 1, 0, 0 }, // Lawyer - { 1, 0, 0 }, // Mycroft - { 7, 0, 0 }, // Old Sherman - { 1, 0, 0 }, // Stock Boy in Chemist Shop - { 1, 0, 0 }, // Barman - { 1, 0, 0 }, // Dandy Player - { 1, 0, 0 }, // Rough-looking Player - { 1, 0, 0 }, // Spectator - { 1, 0, 0 }, // Robert Hunt - { 3, 0, 0 }, // Violet Secretary - { 1, 0, 0 }, // Pettigrew - { 8, 0, 0 }, // Augie (apple seller) - { 16, 0, 0 }, // Anna Carroway - { 1, 0, 0 }, // Guard - { 8, 0, 0 }, // Antonio Caruso - { 1, 0, 0 }, // Toby the Dog - { 13, 0, 0 }, // Simon Kingsley - { 2, 0, 0 }, // Alfred Tobacco Clerk - { 1, 0, 0 }, // Lady Brumwell - { 1, 0, 0 }, // Madame Rosa - { 1, 0, 0 }, // Lady Brumwell - { 1, 0, 0 }, // Joseph Moorehead - { 5, 0, 0 }, // Mrs. Beale - { 1, 0, 0 }, // Felix the Lion - { 1, 0, 0 }, // Hollingston - { 1, 0, 0 }, // Constable Callaghan - { 2, 0, 0 }, // Sergeant Jeremy Duncan - { 1, 0, 0 }, // Lord Brumwell - { 1, 0, 0 }, // Nigel Jameson - { 1, 0, 0 }, // Jonas (newspaper seller) - { 1, 0, 0 }, // Constable Dugan - { 4, 0, 0 } // Inspector Lestrade (Yard) -}; - -static const byte PEOPLE_TALK_SEQUENCES[MAX_PEOPLE][MAX_TALK_SEQUENCES] = { - { 1, 0, 0 }, // Sherlock Holmes - { 5, 5, 6, 7, 8, 7, 8, 6, 0, 0 }, // Dr. Watson - { 2, 0, 0 }, // Inspector Lestrade - { 1, 0, 0 }, // Constable #1 - { 1, 0, 0 }, // Constable #2 - { 2, 3, 0, 0 }, // Sheila Parker - { 3, 0, 0 }, // Henry Carruthers - { 1, 2, 3, 2, 1, 2, 3, 0, 0 }, // Lesly (flower girl) - { 13, 14, 0, 0 }, // Usher #1 - { 2, 0, 0 }, // Usher #2 - { 1, 2, 3, 4, 3, 4, 3, 2, 0, 0 }, // Fredrick Epstein - { 8, 0, 0 }, // Mrs.Worthington - { 1, 2, 3, 4, 5, 4, 3, 2, 0, 0 }, // Coach - { 7, 8, 0, 0 }, // Player - { 12, 13, 0, 0 }, // Waterboy - { 3, 4, 0, 0 }, // James Sanders - { 4, 5, 0, 0 }, // Belle (perfumerie) - { 14, 15, 16, 17, 18, 19, 20, 20, 20, 0, 0 }, // Cleaning Girl (perfumerie) - { 16, 17, 18, 18, 18, 17, 17, 0, 0 }, // Epstien in the Opera Balcony - { 2, 3, 0, 0 }, // Wiggins - { 1, 2, 0, 0 }, // Paul (Brumwell/Carroway) - { 1, 0, 0 }, // Bartender - { 1, 0, 0 }, // Dirty Drunk - { 1, 0, 0 }, // Shouting Drunk - { 1, 0, 0 }, // Staggering Drunk - { 1, 0, 0 }, // Bouncer - { 5, 6, 0, 0 }, // James Sanders - At Home - { 4, 5, 0, 0 }, // The Coroner - { 1, 0, 0 }, // The Equestrian Shop Keeper - { 1, 0, 0 }, // George Blackwood - { 5, 6, 0, 0 }, // Lars - { 1, 0, 0 }, // Sheila Parker - { 8, 9, 0, 0 }, // Chemist - { 5, 6, 0, 0 }, // Inspector Gregson - { 1, 0, 0 }, // Lawyer - { 1, 0, 0 }, // Mycroft - { 7, 8, 0, 0 }, // Old Sherman - { 1, 0, 0 }, // Stock Boy in Chemist Shop - { 1, 0, 0 }, // Barman - { 1, 0, 0 }, // Dandy Player - { 1, 0, 0 }, // Rough-looking Player - { 1, 0, 0 }, // Spectator - { 1, 0, 0 }, // Robert Hunt - { 3, 4, 0, 0 }, // Violet Secretary - { 1, 0, 0 }, // Pettigrew - { 14, 15, 0, 0 }, // Augie (apple seller) - { 3, 4, 5, 6, 0, 0 }, // Anna Carroway - { 4, 5, 6, 0, 0 }, // Guard - { 7, 8, 0, 0 }, // Antonio Caruso - { 1, 0, 0 }, // Toby the Dog - { 13, 14, 0, 0 }, // Simon Kingsley - { 2, 3, 0, 0 }, // Alfred Tobacco Clerk - { 3, 4, 0, 0 }, // Lady Brumwell - { 1, 30, 0, 0 }, // Madame Rosa - { 3, 4, 0, 0 }, // Lady Brumwell - { 1, 0, 0 }, // Joseph Moorehead - { 14, 15, 16, 17, 18, 19, 20, 0, 0 }, // Mrs. Beale - { 1, 0, 0 }, // Felix the Lion - { 1, 0, 0 }, // Hollingston - { 1, 0, 0 }, // Constable Callaghan - { 1, 1, 2, 2, 0, 0 }, // Sergeant Jeremy Duncan - { 9, 10, 0, 0 }, // Lord Brumwell - { 1, 2, 0, 138, 3, 4, 0, 138, 0, 0 }, // Nigel Jameson - { 1, 8, 0, 0 }, // Jonas (newspaper seller) - { 1, 0, 0 }, // Constable Dugan - { 2, 0, 0 } // Inspector Lestrade (Yard) +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 } } }; /*----------------------------------------------------------------*/ @@ -381,7 +187,16 @@ ScalpelEngine::~ScalpelEngine() { } void ScalpelEngine::initialize() { - initGraphics(320, 200, false); + // 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(); @@ -394,9 +209,10 @@ void ScalpelEngine::initialize() { if (!isDemo()) { // Load the map co-ordinates for each scene and sequence data - _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; + 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 @@ -404,8 +220,8 @@ void ScalpelEngine::initialize() { // Set up list of people for (int idx = 0; idx < MAX_PEOPLE; ++idx) - _people->_characters.push_back(PersonData(PEOPLE_NAMES[idx], PEOPLE_PORTRAITS[idx], - PEOPLE_STILL_SEQUENCES[idx], PEOPLE_TALK_SEQUENCES[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); @@ -421,45 +237,81 @@ void ScalpelEngine::initialize() { } void ScalpelEngine::showOpening() { + bool finished = true; + if (isDemo() && _interactiveFl) return; - if (!TsAGE::Logo::show(this)) - return; - if (!showCityCutscene()) - return; - if (!showAlleyCutscene()) - return; - if (!showStreetCutscene()) - return; - if (!showOfficeCutscene()) - 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->clearEvents(); - _music->stopMusic(); + _events->setFrameRate(GAME_FRAME_RATE); } bool ScalpelEngine::showCityCutscene() { + byte greyPalette[PALETTE_SIZE]; byte palette[PALETTE_SIZE]; - _music->playMusic("prolog1.mus"); + // 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", 1, 255, true, 2); + bool finished = _animation->play("26open1", true, 1, 255, true, 2); if (finished) { - ImageFile titleImages("title2.vgs", true); + 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[0], Common::Point(10, 11)); + _screen->_backBuffer1.transBlitFrom(titleImages_LondonNovember[0], londonPosition); _screen->randomTransition(); finished = _events->delay(1000, true); // November, 1888 if (finished) { - _screen->_backBuffer1.transBlitFrom(titleImages[1], Common::Point(101, 102)); + _screen->_backBuffer1.transBlitFrom(titleImages_LondonNovember[1], Common::Point(100, 100)); _screen->randomTransition(); finished = _events->delay(5000, true); } @@ -470,19 +322,35 @@ bool ScalpelEngine::showCityCutscene() { } if (finished) - finished = _animation->play("26open2", 1, 0, false, 2); + finished = _animation->play("26open2", true, 1, 0, false, 2); if (finished) { - ImageFile titleImages("title.vgs", true); + 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[0], Common::Point(75, 6)); + _screen->_backBuffer1.transBlitFrom(titleImages_SherlockHolmesTitle[0], lostFilesPosition); // Sherlock Holmes - _screen->_backBuffer1.transBlitFrom(titleImages[1], Common::Point(34, 21)); + _screen->_backBuffer1.transBlitFrom(titleImages_SherlockHolmesTitle[1], sherlockHolmesPosition); // copyright - _screen->_backBuffer1.transBlitFrom(titleImages[2], Common::Point(4, 190)); + _screen->_backBuffer1.transBlitFrom(titleImages_SherlockHolmesTitle[2], copyrightPosition); _screen->verticalTransition(); finished = _events->delay(4000, true); @@ -500,9 +368,23 @@ bool ScalpelEngine::showCityCutscene() { if (finished) { // In the alley... - _screen->transBlitFrom(titleImages[3], Common::Point(72, 51)); + 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); - finished = _events->delay(3000, true); + + // Wait until the track got looped and the first few notes were played + finished = _music->waitUntilMSec(4300, 21300, 0, 2500); // ticks 0x104 / ticks 0x500 } } @@ -513,22 +395,45 @@ bool ScalpelEngine::showCityCutscene() { bool ScalpelEngine::showAlleyCutscene() { byte palette[PALETTE_SIZE]; - _music->playMusic("prolog2.mus"); + _music->loadSong("prolog2"); _animation->_gfxLibraryFilename = "TITLE.LIB"; _animation->_soundLibraryFilename = "TITLE.SND"; - bool finished = _animation->play("27PRO1", 1, 3, true, 2); - if (finished) - finished = _animation->play("27PRO2", 1, 0, false, 2); + // 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"); - finished = _events->delay(6000); + + // 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", 1, 0, true, 2); + finished = _animation->play("27PRO3", true, 1, 0, true, 2); if (finished) { _screen->getPalette(palette); @@ -536,11 +441,28 @@ bool ScalpelEngine::showAlleyCutscene() { } if (finished) { - ImageFile titleImages("title3.vgs", true); + ImageFile titleImages_EarlyTheFollowingMorning("title3.vgs", true); // "Early the following morning on Baker Street..." - _screen->_backBuffer1.transBlitFrom(titleImages[0], Common::Point(35, 51), false, 0); - _screen->fadeIn(palette, 3); - finished = _events->delay(1000); + 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 = ""; @@ -552,24 +474,89 @@ bool ScalpelEngine::showStreetCutscene() { _animation->_gfxLibraryFilename = "TITLE.LIB"; _animation->_soundLibraryFilename = "TITLE.SND"; - _music->playMusic("PROLOG3.MUS"); + _music->loadSong("prolog3"); + + // wait a bit + bool finished = _events->delay(500); - bool finished = _animation->play("14KICK", 1, 3, true, 2); + 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("14NOTE", 1, 0, false, 2); + 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); - _screen->setPalette(creditsImages._palette); + + // 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 @@ -597,70 +584,327 @@ bool ScalpelEngine::scrollCredits() { return true; } -bool ScalpelEngine::showOfficeCutscene() { - _music->playMusic("PROLOG4.MUS"); - _animation->_gfxLibraryFilename = "TITLE2.LIB"; +// 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"; - bool finished = _animation->play("COFF1", 1, 3, true, 3); + _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->loadSong("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->play("COFF2", 1, 0, false, 3); + finished = _animation->play3DO("26open2", true, 1, false, 2); + if (finished) { - showLBV("note.lbv"); + _screen->_backBuffer1.blitFrom(*_screen); // save into backbuffer 1, used for fade - 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); + // "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) { - _events->clearEvents(); - finished = _events->delay(500); + 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 = _animation->play("COFF3", 1, 0, true, 3); + 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->play("COFF4", 1, 0, false, 3); + 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 = scrollCredits(); + finished = _animation->play3DO("27PRO2", true, 1, false, 2); if (finished) - _screen->fadeToBlack(3); + 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; + } + } - _animation->_gfxLibraryFilename = ""; - _animation->_soundLibraryFilename = ""; 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", "A message requesting help", "_ITEM03A")); - inv.push_back(InventoryItem(0, "Holmes Card", "A number of business cards", "_ITEM07A")); + 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", "Opera Tickets", "_ITEM10A")); - inv.push_back(InventoryItem(138, "Cuff Link", "Cuff Link", "_ITEM04A")); - inv.push_back(InventoryItem(138, "Wire Hook", "Wire Hook", "_ITEM06A")); - inv.push_back(InventoryItem(150, "Note", "Note", "_ITEM13A")); - inv.push_back(InventoryItem(481, "Open Watch", "An open pocket watch", "_ITEM62A")); - inv.push_back(InventoryItem(481, "Paper", "A piece of paper with numbers on it", "_ITEM44A")); - inv.push_back(InventoryItem(532, "Letter", "A letter folded many times", "_ITEM68A")); - inv.push_back(InventoryItem(544, "Tarot", "Tarot Cards", "_ITEM71A")); - inv.push_back(InventoryItem(544, "Ornate Key", "An ornate key", "_ITEM70A")); - inv.push_back(InventoryItem(586, "Pawn ticket", "A pawn ticket", "_ITEM16A")); + 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) { @@ -682,8 +926,8 @@ void ScalpelEngine::startScene() { _scene->_goToScene = _map->show(); _music->freeSong(); - _people->_hSavedPos = Common::Point(-1, -1); - _people->_hSavedFacing = -1; + _people->_savedPos = Common::Point(-1, -1); + _people->_savedPos._facing = -1; } // Some rooms are prologue cutscenes, rather than normal game scenes. These are: @@ -705,8 +949,8 @@ void ScalpelEngine::startScene() { // Blackwood's capture _res->addToCache("final2.vda", "epilogue.lib"); _res->addToCache("final2.vdx", "epilogue.lib"); - _animation->play("final1", 1, 3, true, 4); - _animation->play("final2", 1, 0, false, 4); + _animation->play("final1", false, 1, 3, true, 4); + _animation->play("final2", false, 1, 0, false, 4); break; case RESCUE_ANNA: @@ -722,8 +966,8 @@ void ScalpelEngine::startScene() { _res->addToCache("finale4.vda", "EPILOG2.lib"); _res->addToCache("finale4.vdx", "EPILOG2.lib"); - _animation->play("finalr1", 1, 3, true, 4); - _animation->play("finalr2", 1, 0, false, 4); + _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 @@ -735,12 +979,12 @@ void ScalpelEngine::startScene() { _res->addToCache("finale4.vdx", "EPILOG2.lib"); } - _animation->play("finale1", 1, 0, false, 4); - _animation->play("finale2", 1, 0, false, 4); - _animation->play("finale3", 1, 0, false, 4); + _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", 1, 0, false, 4); + _animation->play("finale4", false, 1, 0, false, 4); _useEpilogue2 = false; break; @@ -751,9 +995,9 @@ void ScalpelEngine::startScene() { _res->addToCache("SUBWAY3.vda", "epilogue.lib"); _res->addToCache("SUBWAY3.vdx", "epilogue.lib"); - _animation->play("SUBWAY1", 1, 3, true, 4); - _animation->play("SUBWAY2", 1, 0, false, 4); - _animation->play("SUBWAY3", 1, 0, false, 4); + _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; @@ -762,7 +1006,7 @@ void ScalpelEngine::startScene() { case BRUMWELL_SUICIDE: // Brumwell suicide - _animation->play("suicid", 1, 3, true, 4); + _animation->play("suicid", false, 1, 3, true, 4); break; default: break; @@ -822,8 +1066,8 @@ void ScalpelEngine::startScene() { _mapResult = _scene->_goToScene; } -void ScalpelEngine::eraseMirror12() { - Common::Point pt((*_people)[AL]._position.x / 100, (*_people)[AL]._position.y / 100); +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)) { @@ -832,15 +1076,15 @@ void ScalpelEngine::eraseMirror12() { } } -void ScalpelEngine::doMirror12() { +void ScalpelEngine::doBrumwellMirror() { People &people = *_people; - Person &player = people._player; + Person &player = people[HOLMES]; - Common::Point pt((*_people)[AL]._position.x / 100, (*_people)[AL]._position.y / 100); + 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)[AL]._sequenceNumber) { + switch ((*_people)[HOLMES]._sequenceNumber) { case WALK_DOWN: frameNum -= 7; break; @@ -883,12 +1127,12 @@ void ScalpelEngine::doMirror12() { if (Common::Rect(80, 100, 145, 138).contains(pt)) { // Get the frame of Sherlock to draw - ImageFrame &imageFrame = (*people[AL]._images)[frameNum]; + ImageFrame &imageFrame = (*people[HOLMES]._images)[frameNum]; // Draw the mirror image of Holmes - bool flipped = people[AL]._sequenceNumber == WALK_LEFT || people[AL]._sequenceNumber == STOP_LEFT - || people[AL]._sequenceNumber == WALK_UPRIGHT || people[AL]._sequenceNumber == STOP_UPRIGHT - || people[AL]._sequenceNumber == WALK_DOWNLEFT || people[AL]._sequenceNumber == STOP_DOWNLEFT; + 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 @@ -907,8 +1151,8 @@ void ScalpelEngine::doMirror12() { } } -void ScalpelEngine::flushMirror12() { - Common::Point pt((*_people)[AL]._position.x / 100, (*_people)[AL]._position.y / 100); +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)) @@ -917,4 +1161,4 @@ void ScalpelEngine::flushMirror12() { } // End of namespace Scalpel -} // End of namespace Scalpel +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/scalpel.h b/engines/sherlock/scalpel/scalpel.h index 8743bfb7a9..cb1cb20492 100644 --- a/engines/sherlock/scalpel/scalpel.h +++ b/engines/sherlock/scalpel/scalpel.h @@ -24,40 +24,56 @@ #define SHERLOCK_SCALPEL_H #include "sherlock/sherlock.h" -#include "sherlock/scalpel/darts.h" +#include "sherlock/scalpel/scalpel_darts.h" namespace Sherlock { namespace Scalpel { -enum { BLACKWOOD_CAPTURE = 2, BAKER_STREET = 4, DRAWING_ROOM = 12, STATION = 17, PUB_INTERIOR = 19, - LAWYER_OFFICE = 27, BAKER_ST_EXTERIOR = 39, RESCUE_ANNA = 52, MOOREHEAD_DEATH = 53, EXIT_GAME = 55, - BRUMWELL_SUICIDE = 70, OVERHEAD_MAP2 = 98, DARTS_GAME = 99, OVERHEAD_MAP = 100 }; +enum { + 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 +}; class ScalpelEngine : public SherlockEngine { private: Darts *_darts; int _mapResult; + bool show3DOSplash(); + /** * Show the starting city cutscene which shows the game title */ bool showCityCutscene(); + bool showCityCutscene3DO(); /** * Show the back alley where the initial murder takes place */ bool showAlleyCutscene(); + bool showAlleyCutscene3DO(); /** * Show the Baker Street outside cutscene */ bool showStreetCutscene(); + bool showStreetCutscene3DO(); /** * Show Holmes and Watson at the breakfast table, lestrade's note, and then the scrolling credits */ bool showOfficeCutscene(); + bool showOfficeCutscene3DO(); /** * Show the game credits @@ -97,17 +113,17 @@ public: /** * Takes care of clearing the mirror in scene 12 (mansion drawing room), in case anything drew over it */ - void eraseMirror12(); + void eraseBrumwellMirror(); /** * Takes care of drawing Holme's reflection onto the mirror in scene 12 (mansion drawing room) */ - void doMirror12(); + void doBrumwellMirror(); /** * This clears the mirror in scene 12 (mansion drawing room) in case anything messed draw over it */ - void flushMirror12(); + void flushBrumwellMirror(); }; } // End of namespace Scalpel diff --git a/engines/sherlock/scalpel/darts.cpp b/engines/sherlock/scalpel/scalpel_darts.cpp index 8d78335a55..87f4566837 100644 --- a/engines/sherlock/scalpel/darts.cpp +++ b/engines/sherlock/scalpel/scalpel_darts.cpp @@ -20,7 +20,7 @@ * */ -#include "sherlock/scalpel/darts.h" +#include "sherlock/scalpel/scalpel_darts.h" #include "sherlock/scalpel/scalpel.h" namespace Sherlock { @@ -117,7 +117,7 @@ void Darts::playDarts() { if (playerNumber == 0) { screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 30), PLAYER_COLOR, "Holmes Wins!"); if (_level < OPPONENTS_COUNT) - setFlagsForDarts(318 + _level); + _vm->setFlagsDirect(318 + _level); } else { screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 30), PLAYER_COLOR, "%s Wins!", _opponent.c_str()); } @@ -366,8 +366,8 @@ void Darts::drawDartThrow(const Common::Point &pt) { void Darts::erasePowerBars() { Screen &screen = *_vm->_screen; - screen._backBuffer1.fillRect(Common::Rect(DARTBARHX, DARTHORIZY, DARTBARHX + DARTBARSIZE, DARTHORIZY + 10), 0); - screen._backBuffer1.fillRect(Common::Rect(DARTBARVX, DARTHEIGHTY, DARTBARVX + 10, DARTHEIGHTY + DARTBARSIZE), 0); + screen._backBuffer1.fillRect(Common::Rect(DARTBARHX, DARTHORIZY, DARTBARHX + DARTBARSIZE, DARTHORIZY + 10), BLACK); + screen._backBuffer1.fillRect(Common::Rect(DARTBARVX, DARTHEIGHTY, DARTBARVX + 10, DARTHEIGHTY + DARTBARSIZE), BLACK); screen._backBuffer1.transBlitFrom((*_dartImages)[2], Common::Point(DARTBARHX - 1, DARTHORIZY - 1)); screen._backBuffer1.transBlitFrom((*_dartImages)[3], Common::Point(DARTBARVX - 1, DARTHEIGHTY - 1)); screen.slamArea(DARTBARHX - 1, DARTHORIZY - 1, DARTBARSIZE + 3, 11); @@ -377,15 +377,11 @@ void Darts::erasePowerBars() { int Darts::doPowerBar(const Common::Point &pt, byte color, int goToPower, bool isVertical) { Events &events = *_vm->_events; Screen &screen = *_vm->_screen; - Music &music = *_vm->_music; bool done; int idx = 0; events.clearEvents(); - if (music._musicOn) - music.waitTimerRoland(10); - else - events.delay(100); + events.delay(100); // Display loop do { @@ -395,7 +391,7 @@ int Darts::doPowerBar(const Common::Point &pt, byte color, int goToPower, bool i // Reached target power for a computer player done = true; else if (goToPower == 0) { - // Check for pres + // Check for press if (dartHit()) done = true; } @@ -410,10 +406,7 @@ int Darts::doPowerBar(const Common::Point &pt, byte color, int goToPower, bool i screen.slamArea(pt.x + idx, pt.y, 1, 8); } - if (music._musicOn) { - if (!(idx % 3)) - music.waitTimerRoland(1); - } else if (!(idx % 8)) + if (!(idx % 8)) events.wait(1); ++idx; @@ -422,16 +415,16 @@ int Darts::doPowerBar(const Common::Point &pt, byte color, int goToPower, bool i return MIN(idx * 100 / DARTBARSIZE, 100); } -bool Darts::dartHit() { +int Darts::dartHit() { Events &events = *_vm->_events; // Process pending events events.pollEventsAndWait(); if (events.kbHit()) { - // Key was pressed, so discard it and return true - events.clearKeyboard(); - return true; + // Key was pressed, so return it + Common::KeyState keyState = events.getKey(); + return keyState.keycode; } _oldDartButtons = events._pressed; @@ -548,10 +541,6 @@ bool Darts::findNumberOnBoard(int aim, Common::Point &pt) { return done; } -void Darts::setFlagsForDarts(int flagNum) { - _vm->_flags[ABS(flagNum)] = flagNum >= 0; -} - } // End of namespace Scalpel -} // End of namespace Scalpel +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/darts.h b/engines/sherlock/scalpel/scalpel_darts.h index 42990f8056..483a163510 100644 --- a/engines/sherlock/scalpel/darts.h +++ b/engines/sherlock/scalpel/scalpel_darts.h @@ -20,10 +20,10 @@ * */ -#ifndef SHERLOCK_DARTS_H -#define SHERLOCK_DARTS_H +#ifndef SHERLOCK_SCALPEL_DARTS_H +#define SHERLOCK_SCALPEL_DARTS_H -#include "sherlock/resources.h" +#include "sherlock/image_file.h" namespace Sherlock { @@ -97,7 +97,7 @@ private: /** * Returns true if a mouse button or key is pressed. */ - bool dartHit(); + int dartHit(); /** * Return the score of the given location on the dart-board @@ -114,12 +114,6 @@ private: * Returns the center position for the area of the dartboard with a given number */ bool findNumberOnBoard(int aim, Common::Point &pt); - - /** - * Set a global flag to 0 or 1 depending on whether the passed flag is negative or positive. - * @remarks We don't use the global setFlags method because we don't want to check scene flags - */ - void setFlagsForDarts(int flagNum); public: Darts(ScalpelEngine *vm); diff --git a/engines/sherlock/scalpel/scalpel_debugger.cpp b/engines/sherlock/scalpel/scalpel_debugger.cpp new file mode 100644 index 0000000000..7f5e1efa69 --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_debugger.cpp @@ -0,0 +1,91 @@ +/* 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 "sherlock/scalpel/scalpel_debugger.h" +#include "sherlock/sherlock.h" +#include "audio/mixer.h" +#include "audio/decoders/3do.h" +#include "audio/decoders/aiff.h" +#include "audio/decoders/wave.h" + +namespace Sherlock { + +namespace Scalpel { + +ScalpelDebugger::ScalpelDebugger(SherlockEngine *vm) : Debugger(vm) { + registerCmd("3do_playmovie", WRAP_METHOD(ScalpelDebugger, cmd3DO_PlayMovie)); + registerCmd("3do_playaudio", WRAP_METHOD(ScalpelDebugger, cmd3DO_PlayAudio)); +} + +bool ScalpelDebugger::cmd3DO_PlayMovie(int argc, const char **argv) { + if (argc != 2) { + debugPrintf("Format: 3do_playmovie <3do-movie-file>\n"); + return true; + } + + // play gets postboned until debugger is closed + Common::String filename = argv[1]; + _3doPlayMovieFile = filename; + + return cmdExit(0, 0); +} + +bool ScalpelDebugger::cmd3DO_PlayAudio(int argc, const char **argv) { + if (argc != 2) { + debugPrintf("Format: 3do_playaudio <3do-audio-file>\n"); + return true; + } + + Common::File *file = new Common::File(); + if (!file->open(argv[1])) { + debugPrintf("can not open specified audio file\n"); + return true; + } + + Audio::AudioStream *testStream; + Audio::SoundHandle testHandle; + + // Try to load the given file as AIFF/AIFC + testStream = Audio::makeAIFFStream(file, DisposeAfterUse::YES); + + if (testStream) { + g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, &testHandle, testStream); + _vm->_events->clearEvents(); + + while ((!_vm->shouldQuit()) && g_system->getMixer()->isSoundHandleActive(testHandle)) { + _vm->_events->pollEvents(); + g_system->delayMillis(10); + if (_vm->_events->kbHit()) { + break; + } + } + + debugPrintf("playing completed\n"); + g_system->getMixer()->stopHandle(testHandle); + } + + return true; +} + +} // End of namespace Scalpel + +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/scalpel_debugger.h b/engines/sherlock/scalpel/scalpel_debugger.h new file mode 100644 index 0000000000..17a84779f0 --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_debugger.h @@ -0,0 +1,54 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_SCALPEL_DEBUGGER_H +#define SHERLOCK_SCALPEL_DEBUGGER_H + +#include "sherlock/debugger.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Scalpel { + +class ScalpelDebugger : public Debugger { +private: + /** + * Plays a 3DO movie + */ + bool cmd3DO_PlayMovie(int argc, const char **argv); + + /** + * Plays a 3DO audio + */ + bool cmd3DO_PlayAudio(int argc, const char **argv); +public: + ScalpelDebugger(SherlockEngine *vm); + virtual ~ScalpelDebugger() {} +}; + +} // End of namespace Scalpel + +} // End of namespace Sherlock + +#endif /* SHERLOCK_DEBUGGER_H */ diff --git a/engines/sherlock/scalpel/scalpel_fixed_text.cpp b/engines/sherlock/scalpel/scalpel_fixed_text.cpp new file mode 100644 index 0000000000..63f84d68c6 --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_fixed_text.cpp @@ -0,0 +1,377 @@ +/* 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 "sherlock/scalpel/scalpel_fixed_text.h" +#include "sherlock/sherlock.h" + +namespace Sherlock { + +namespace Scalpel { + +static const char *const fixedTextEN[] = { + // SH1: Window buttons + "Exit", + "Up", + "Down", + // SH1: Inventory buttons + "Exit", + "Look", + "Use", + "Give", + // SH1: Journal text + "Watson's Journal", + "Page %d", + // SH1: Journal buttons + "Exit", + "Back 10", + "Up", + "Down", + "Ahead 10", + "Search", + "First Page", + "Last Page", + "Print Text", + // SH1: Journal search + "Exit", + "Backward", + "Forward", + "Text Not Found !", + // SH1: Initial Inventory + "A message requesting help", + "A number of business cards", + "Opera Tickets", + "Cuff Link", + "Wire Hook", + "Note", + "An open pocket watch", + "A piece of paper with numbers on it", + "A letter folded many times", + "Tarot Cards", + "An ornate key", + "A pawn ticket", + // SH2: Verbs + "Open", + "Look", + "Talk", + "Journal" +}; + +// sharp-s : 0xE1 / octal 341 +// small a-umlaut: 0x84 / octal 204 +// small o-umlaut: 0x94 / octal 224 +// small u-umlaut: 0x81 / octal 201 +static const char *const fixedTextDE[] = { + // SH1: Window buttons + "Zur\201ck", + "Hoch", + "Runter", + // SH1: Inventory buttons + "Zur\201ck", + "Schau", + "Benutze", + "Gib", + // SH1: Journal text + "Watsons Tagebuch", + "Seite %d", + // SH1: Journal buttons + "Zur\201ck", + "10 hoch", + "Hoch", + "Runter", + "10 runter", + "Suche", + "Erste Seite", + "Letzte Seite", + "Drucke Text", + // SH1: Journal search + "Zur\201ck", + "R\201ckw\204rts", // original: "Backward" + "Vorw\204rts", // original: "Forward" + "Text nicht gefunden!", + // SH1: Initial Inventory + "Ein Hilferuf von Lestrade", + "Holmes' Visitenkarten", + "Karten f\201rs Opernhaus", + "Manschettenkn\224pfe", + "Zum Haken verbogener Drahtkorb", + "Mitteilung am Epstein", + "Eine offene Taschenuhr", + "Ein Zettel mit Zahlen drauf", + "Ein mehrfach gefalteter Briefbogen", + "Ein Tarock-Kartenspiel", // [sic] + "Ein verzierter Schl\201ssel", + "Ein Pfandschein", + // SH2: Verbs + "\231ffne", + "Schau", + "Rede", + "Tagebuch" +}; + +// up-side down exclamation mark - 0xAD / octal 255 +// up-side down question mark - 0xA8 / octal 250 +// n with a wave on top - 0xA4 / octal 244 +static const char *const fixedTextES[] = { + // SH1: Window buttons + "Exit", + "Subir", + "Bajar", + // SH1: Inventory buttons + "Exit", + "Mirar", + "Usar", + "Dar", + // SH1: Journal text + "Diario de Watson", + "Pagina %d", + // SH1: Journal buttons + "Exit", + "Retroceder", + "Subir", + "baJar", + "Adelante", + "Buscar", + "1a pagina", + "Ult pagina", + "Imprimir", + // SH1: Journal search + "Exit", + "Retroceder", + "Avanzar", + "Texto no encontrado!", + // SH1: Initial Inventory + "Un mensaje solicitando ayuda", + "Unas cuantas tarjetas de visita", + "Entradas para la opera", + "Unos gemelos", + "Un gancho de alambre", + "Una nota", + "Un reloj de bolsillo abierto", + "Un trozo de papel con unos numeros", + "Un carta muy plegada", + "Unas cartas de Tarot", + "Una llave muy vistosa", + "Una papeleta de empe\244o", +}; + +// ========================================= + +// === Sherlock Holmes 1: Serrated Scalpel === +static const char *const fixedTextEN_ActionOpen[] = { + "This cannot be opened", + "It is already open", + "It is locked", + "Wait for Watson", + " ", + "." +}; + +static const char *const fixedTextDE_ActionOpen[] = { + "Das kann man nicht \224ffnen", + "Ist doch schon offen!", + "Leider verschlossen", + "Warte auf Watson", + " ", + "." +}; + +static const char *const fixedTextES_ActionOpen[] = { + "No puede ser abierto", + "Ya esta abierto", + "Esta cerrado", + "Espera a Watson", + " ", + "." +}; + +static const char *const fixedTextEN_ActionClose[] = { + "This cannot be closed", + "It is already closed", + "The safe door is in the way" +}; + +static const char *const fixedTextDE_ActionClose[] = { + "Das kann man nicht schlie\341en", + "Ist doch schon zu!", + "Die safet\201r ist Weg" +}; + +static const char *const fixedTextES_ActionClose[] = { + "No puede ser cerrado", + "Ya esta cerrado", + "La puerta de seguridad esta entre medias" +}; + +static const char *const fixedTextEN_ActionMove[] = { + "This cannot be moved", + "It is bolted to the floor", + "It is too heavy", + "The other crate is in the way" +}; + + +static const char *const fixedTextDE_ActionMove[] = { + "L\204\341t sich nicht bewegen", + "Festged\201belt in der Erde...", + "Oha, VIEL zu schwer", + "Der andere Kiste ist im Weg" // [sic] +}; + +static const char *const fixedTextES_ActionMove[] = { + "No puede moverse", + "Esta sujeto a la pared", + "Es demasiado pesado", + "El otro cajon esta en mitad" +}; + +static const char *const fixedTextEN_ActionPick[] = { + "Nothing of interest here", + "It is bolted down", + "It is too big to carry", + "It is too heavy", + "I think a girl would be more your type", + "Those flowers belong to Penny", + "She's far too young for you!", + "I think a girl would be more your type!", + "Government property for official use only" +}; + +static const char *const fixedTextDE_ActionPick[] = { + "Nichts Interessantes da", + "Zu gut befestigt", + "Ist ja wohl ein bi\341chen zu gro\341, oder ?", + "Oha, VIEL zu schwer", + "Ich denke, Du stehst mehr auf M\204dchen ?", + "Diese Blumen geh\224ren Penny", + "Sie ist doch viel zu jung f\201r Dich!", + "Ich denke, Du stehst mehr auf M\204dchen ?", + "Staatseigentum - Nur f\201r den Dienstgebrauch !" +}; + +static const char *const fixedTextES_ActionPick[] = { + "No hay nada interesante", + "Esta anclado al suelo", + "Es muy grande para llevarlo", + "Pesa demasiado", + "Creo que una chica sera mas tu tipo", + "Esas flores pertenecen a Penny", + "\255Es demasiado joven para ti!", + "\255Creo que una chica sera mas tu tipo!", + "Propiedad del gobierno para uso oficial" +}; + +static const char *const fixedTextEN_ActionUse[] = { + "You can't do that", + "It had no effect", + "You can't reach it", + "OK, the door looks bigger! Happy?", + "Doors don't smoke" +}; + +static const char *const fixedTextDE_ActionUse[] = { + "Nein, das geht wirklich nicht", + "Tja keinerlei Wirkung", + "Da kommst du nicht dran", + "Na gut, die T\201r sieht jetzt gr\224\341er aus. Zufrieden?", + "T\201ren sind Nichtraucher!" +}; + +static const char *const fixedTextES_ActionUse[] = { + "No puedes hacerlo", + "No tuvo ningun efecto", + "No puedes alcanzarlo", + "Bien, \255es enorme! \250Feliz?", + "Las puertas no fuman" +}; + +#define FIXEDTEXT_GETCOUNT(_name_) sizeof(_name_) / sizeof(byte *) +#define FIXEDTEXT_ENTRY(_name_) _name_, FIXEDTEXT_GETCOUNT(_name_) + +static const FixedTextActionEntry fixedTextEN_Actions[] = { + { FIXEDTEXT_ENTRY(fixedTextEN_ActionOpen) }, + { FIXEDTEXT_ENTRY(fixedTextEN_ActionClose) }, + { FIXEDTEXT_ENTRY(fixedTextEN_ActionMove) }, + { FIXEDTEXT_ENTRY(fixedTextEN_ActionPick) }, + { FIXEDTEXT_ENTRY(fixedTextEN_ActionUse) } +}; + +static const FixedTextActionEntry fixedTextDE_Actions[] = { + { FIXEDTEXT_ENTRY(fixedTextDE_ActionOpen) }, + { FIXEDTEXT_ENTRY(fixedTextDE_ActionClose) }, + { FIXEDTEXT_ENTRY(fixedTextDE_ActionMove) }, + { FIXEDTEXT_ENTRY(fixedTextDE_ActionPick) }, + { FIXEDTEXT_ENTRY(fixedTextDE_ActionUse) } +}; + +static const FixedTextActionEntry fixedTextES_Actions[] = { + { FIXEDTEXT_ENTRY(fixedTextES_ActionOpen) }, + { FIXEDTEXT_ENTRY(fixedTextES_ActionClose) }, + { FIXEDTEXT_ENTRY(fixedTextES_ActionMove) }, + { FIXEDTEXT_ENTRY(fixedTextES_ActionPick) }, + { FIXEDTEXT_ENTRY(fixedTextES_ActionUse) } +}; + +// ========================================= + +// TODO: +// It seems there was a French version of Sherlock Holmes 2 +static const FixedTextLanguageEntry fixedTextLanguages[] = { + { Common::DE_DEU, fixedTextDE, fixedTextDE_Actions }, + { Common::ES_ESP, fixedTextES, fixedTextES_Actions }, + { Common::EN_ANY, fixedTextEN, fixedTextEN_Actions }, + { Common::UNK_LANG, fixedTextEN, fixedTextEN_Actions } +}; + +// ========================================= + +// ========================================= + +ScalpelFixedText::ScalpelFixedText(SherlockEngine *vm) : FixedText(vm) { + // Figure out which fixed texts to use + Common::Language curLanguage = _vm->getLanguage(); + + const FixedTextLanguageEntry *curLanguageEntry = fixedTextLanguages; + + while (curLanguageEntry->language != Common::UNK_LANG) { + if (curLanguageEntry->language == curLanguage) + break; // found current language + curLanguageEntry++; + } + _curLanguageEntry = curLanguageEntry; +} + +const char *ScalpelFixedText::getText(int fixedTextId) { + return _curLanguageEntry->fixedTextArray[fixedTextId]; +} + +const Common::String ScalpelFixedText::getActionMessage(FixedTextActionId actionId, int messageIndex) { + assert(actionId >= 0); + assert(messageIndex >= 0); + const FixedTextActionEntry *curActionEntry = &_curLanguageEntry->actionArray[actionId]; + + assert(messageIndex < curActionEntry->fixedTextArrayCount); + return Common::String(curActionEntry->fixedTextArray[messageIndex]); +} + +} // End of namespace Scalpel + +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/scalpel_fixed_text.h b/engines/sherlock/scalpel/scalpel_fixed_text.h new file mode 100644 index 0000000000..eae86b8f27 --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_fixed_text.h @@ -0,0 +1,108 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_SCALPEL_FIXED_TEXT_H +#define SHERLOCK_SCALPEL_FIXED_TEXT_H + +#include "sherlock/fixed_text.h" + +namespace Sherlock { + +namespace Scalpel { + +enum FixedTextId { + // Window buttons + kFixedText_Window_Exit = 0, + kFixedText_Window_Up, + kFixedText_Window_Down, + // Inventory buttons + kFixedText_Inventory_Exit, + kFixedText_Inventory_Look, + kFixedText_Inventory_Use, + kFixedText_Inventory_Give, + // Journal text + kFixedText_Journal_WatsonsJournal, + kFixedText_Journal_Page, + // Journal buttons + kFixedText_Journal_Exit, + kFixedText_Journal_Back10, + kFixedText_Journal_Up, + kFixedText_Journal_Down, + kFixedText_Journal_Ahead10, + kFixedText_Journal_Search, + kFixedText_Journal_FirstPage, + kFixedText_Journal_LastPage, + kFixedText_Journal_PrintText, + // Journal search + kFixedText_JournalSearch_Exit, + kFixedText_JournalSearch_Backward, + kFixedText_JournalSearch_Forward, + kFixedText_JournalSearch_NotFound, + // Initial inventory + kFixedText_InitInventory_Message, + kFixedText_InitInventory_HolmesCard, + kFixedText_InitInventory_Tickets, + kFixedText_InitInventory_CuffLink, + kFixedText_InitInventory_WireHook, + kFixedText_InitInventory_Note, + kFixedText_InitInventory_OpenWatch, + kFixedText_InitInventory_Paper, + kFixedText_InitInventory_Letter, + kFixedText_InitInventory_Tarot, + kFixedText_InitInventory_OrnateKey, + kFixedText_InitInventory_PawnTicket +}; + +struct FixedTextActionEntry { + const char *const *fixedTextArray; + int fixedTextArrayCount; +}; + +struct FixedTextLanguageEntry { + Common::Language language; + const char *const *fixedTextArray; + const FixedTextActionEntry *actionArray; +}; + +class ScalpelFixedText: public FixedText { +private: + const FixedTextLanguageEntry *_curLanguageEntry; +public: + ScalpelFixedText(SherlockEngine *vm); + virtual ~ScalpelFixedText() {} + + /** + * Gets text + */ + virtual const char *getText(int fixedTextId); + + /** + * Get action message + */ + virtual const Common::String getActionMessage(FixedTextActionId actionId, int messageIndex); +}; + +} // End of namespace Scalpel + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/scalpel/scalpel_inventory.cpp b/engines/sherlock/scalpel/scalpel_inventory.cpp new file mode 100644 index 0000000000..e19a43238c --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_inventory.cpp @@ -0,0 +1,296 @@ +/* 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 "sherlock/scalpel/scalpel_inventory.h" +#include "sherlock/scalpel/scalpel_fixed_text.h" +#include "sherlock/scalpel/scalpel_screen.h" +#include "sherlock/scalpel/scalpel_user_interface.h" +#include "sherlock/scalpel/scalpel.h" + +namespace Sherlock { + +namespace Scalpel { + +ScalpelInventory::ScalpelInventory(SherlockEngine *vm) : Inventory(vm) { + _invShapes.resize(6); +} + +ScalpelInventory::~ScalpelInventory() { +} + +void ScalpelInventory::drawInventory(InvNewMode mode) { + FixedText &fixedText = *_vm->_fixedText; + ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen; + UserInterface &ui = *_vm->_ui; + InvNewMode tempMode = mode; + + loadInv(); + + if (mode == INVENTORY_DONT_DISPLAY) { + screen._backBuffer = &screen._backBuffer2; + } + + // Draw the window background + Surface &bb = *screen._backBuffer; + bb.fillRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y1 + 10), BORDER_COLOR); + bb.fillRect(Common::Rect(0, CONTROLS_Y1 + 10, 2, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); + bb.fillRect(Common::Rect(SHERLOCK_SCREEN_WIDTH - 2, CONTROLS_Y1 + 10, + SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); + bb.fillRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - 2, SHERLOCK_SCREEN_WIDTH, + SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); + bb.fillRect(Common::Rect(2, CONTROLS_Y1 + 10, SHERLOCK_SCREEN_WIDTH - 2, SHERLOCK_SCREEN_HEIGHT - 2), + INV_BACKGROUND); + + // Draw the buttons + Common::String fixedText_Exit = fixedText.getText(kFixedText_Inventory_Exit); + Common::String fixedText_Look = fixedText.getText(kFixedText_Inventory_Look); + Common::String fixedText_Use = fixedText.getText(kFixedText_Inventory_Use); + Common::String fixedText_Give = fixedText.getText(kFixedText_Inventory_Give); + + screen.makeButton(Common::Rect(INVENTORY_POINTS[0][0], CONTROLS_Y1, INVENTORY_POINTS[0][1], + CONTROLS_Y1 + 10), INVENTORY_POINTS[0][2] - screen.stringWidth(fixedText_Exit) / 2, fixedText_Exit); + screen.makeButton(Common::Rect(INVENTORY_POINTS[1][0], CONTROLS_Y1, INVENTORY_POINTS[1][1], + CONTROLS_Y1 + 10), INVENTORY_POINTS[1][2] - screen.stringWidth(fixedText_Look) / 2, fixedText_Look); + screen.makeButton(Common::Rect(INVENTORY_POINTS[2][0], CONTROLS_Y1, INVENTORY_POINTS[2][1], + CONTROLS_Y1 + 10), INVENTORY_POINTS[2][2] - screen.stringWidth(fixedText_Use) / 2, fixedText_Use); + screen.makeButton(Common::Rect(INVENTORY_POINTS[3][0], CONTROLS_Y1, INVENTORY_POINTS[3][1], + CONTROLS_Y1 + 10), INVENTORY_POINTS[3][2] - screen.stringWidth(fixedText_Give) / 2, fixedText_Give); + screen.makeButton(Common::Rect(INVENTORY_POINTS[4][0], CONTROLS_Y1, INVENTORY_POINTS[4][1], + CONTROLS_Y1 + 10), INVENTORY_POINTS[4][2], "^^"); // 2 arrows pointing to the left + screen.makeButton(Common::Rect(INVENTORY_POINTS[5][0], CONTROLS_Y1, INVENTORY_POINTS[5][1], + CONTROLS_Y1 + 10), INVENTORY_POINTS[5][2], "^"); // 1 arrow pointing to the left + screen.makeButton(Common::Rect(INVENTORY_POINTS[6][0], CONTROLS_Y1, INVENTORY_POINTS[6][1], + CONTROLS_Y1 + 10), INVENTORY_POINTS[6][2], "_"); // 1 arrow pointing to the right + screen.makeButton(Common::Rect(INVENTORY_POINTS[7][0], CONTROLS_Y1, INVENTORY_POINTS[7][1], + CONTROLS_Y1 + 10), INVENTORY_POINTS[7][2], "__"); // 2 arrows pointing to the right + + if (tempMode == INVENTORY_DONT_DISPLAY) + mode = LOOK_INVENTORY_MODE; + _invMode = (InvMode)((int)mode); + + if (mode != PLAIN_INVENTORY) { + ui._oldKey = INVENTORY_COMMANDS[(int)mode]; + } else { + ui._oldKey = -1; + } + + invCommands(0); + putInv(SLAM_DONT_DISPLAY); + + if (tempMode != INVENTORY_DONT_DISPLAY) { + if (!ui._slideWindows) { + screen.slamRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + } else { + ui.summonWindow(false, CONTROLS_Y1); + } + + ui._windowOpen = true; + } else { + // Reset the screen back buffer to the first buffer now that drawing is done + screen._backBuffer = &screen._backBuffer1; + } + + assert(IS_SERRATED_SCALPEL); + ((ScalpelUserInterface *)_vm->_ui)->_oldUse = -1; +} + +void ScalpelInventory::invCommands(bool slamIt) { + FixedText &fixedText = *_vm->_fixedText; + ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen; + UserInterface &ui = *_vm->_ui; + + Common::String fixedText_Exit = fixedText.getText(kFixedText_Inventory_Exit); + Common::String fixedText_Look = fixedText.getText(kFixedText_Inventory_Look); + Common::String fixedText_Use = fixedText.getText(kFixedText_Inventory_Use); + Common::String fixedText_Give = fixedText.getText(kFixedText_Inventory_Give); + + if (slamIt) { + screen.buttonPrint(Common::Point(INVENTORY_POINTS[0][2], CONTROLS_Y1), + _invMode == INVMODE_EXIT ? COMMAND_HIGHLIGHTED :COMMAND_FOREGROUND, + true, fixedText_Exit); + screen.buttonPrint(Common::Point(INVENTORY_POINTS[1][2], CONTROLS_Y1), + _invMode == INVMODE_LOOK ? COMMAND_HIGHLIGHTED :COMMAND_FOREGROUND, + true, fixedText_Look); + screen.buttonPrint(Common::Point(INVENTORY_POINTS[2][2], CONTROLS_Y1), + _invMode == INVMODE_USE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND, + true, fixedText_Use); + screen.buttonPrint(Common::Point(INVENTORY_POINTS[3][2], CONTROLS_Y1), + _invMode == INVMODE_GIVE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND, + true, fixedText_Give); + screen.print(Common::Point(INVENTORY_POINTS[4][2], CONTROLS_Y1 + 1), + _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND, + "^^"); // 2 arrows pointing to the left + screen.print(Common::Point(INVENTORY_POINTS[5][2], CONTROLS_Y1 + 1), + _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND, + "^"); // 2 arrows pointing to the left + screen.print(Common::Point(INVENTORY_POINTS[6][2], CONTROLS_Y1 + 1), + (_holdings - _invIndex <= 6) ? COMMAND_NULL : COMMAND_FOREGROUND, + "_"); // 1 arrow pointing to the right + screen.print(Common::Point(INVENTORY_POINTS[7][2], CONTROLS_Y1 + 1), + (_holdings - _invIndex <= 6) ? COMMAND_NULL : COMMAND_FOREGROUND, + "__"); // 2 arrows pointing to the right + if (_invMode != INVMODE_LOOK) + ui.clearInfo(); + } else { + screen.buttonPrint(Common::Point(INVENTORY_POINTS[0][2], CONTROLS_Y1), + _invMode == INVMODE_EXIT ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND, + false, fixedText_Exit); + screen.buttonPrint(Common::Point(INVENTORY_POINTS[1][2], CONTROLS_Y1), + _invMode == INVMODE_LOOK ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND, + false, fixedText_Look); + screen.buttonPrint(Common::Point(INVENTORY_POINTS[2][2], CONTROLS_Y1), + _invMode == INVMODE_USE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND, + false, fixedText_Use); + screen.buttonPrint(Common::Point(INVENTORY_POINTS[3][2], CONTROLS_Y1), + _invMode == INVMODE_GIVE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND, + false, fixedText_Give); + screen.gPrint(Common::Point(INVENTORY_POINTS[4][2], CONTROLS_Y1), + _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND, + "^^"); // 2 arrows pointing to the left + screen.gPrint(Common::Point(INVENTORY_POINTS[5][2], CONTROLS_Y1), + _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND, + "^"); // 1 arrow pointing to the left + screen.gPrint(Common::Point(INVENTORY_POINTS[6][2], CONTROLS_Y1), + (_holdings - _invIndex < 7) ? COMMAND_NULL : COMMAND_FOREGROUND, + "_"); // 1 arrow pointing to the right + screen.gPrint(Common::Point(INVENTORY_POINTS[7][2], CONTROLS_Y1), + (_holdings - _invIndex < 7) ? COMMAND_NULL : COMMAND_FOREGROUND, + "__"); // 2 arrows pointing to the right + } +} + +void ScalpelInventory::highlight(int index, byte color) { + Screen &screen = *_vm->_screen; + Surface &bb = *screen._backBuffer; + int slot = index - _invIndex; + ImageFrame &frame = (*_invShapes[slot])[0]; + + bb.fillRect(Common::Rect(8 + slot * 52, 165, (slot + 1) * 52, 194), color); + bb.transBlitFrom(frame, Common::Point(6 + slot * 52 + ((47 - frame._width) / 2), + 163 + ((33 - frame._height) / 2))); + screen.slamArea(8 + slot * 52, 165, 44, 30); +} + +void ScalpelInventory::refreshInv() { + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + ScalpelUserInterface &ui = *(ScalpelUserInterface *)_vm->_ui; + + ui._invLookFlag = true; + freeInv(); + + ui._infoFlag = true; + ui.clearInfo(); + + screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(0, CONTROLS_Y), + Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + ui.examine(); + + if (!talk._talkToAbort) { + screen._backBuffer2.blitFrom((*ui._controlPanel)[0], Common::Point(0, CONTROLS_Y)); + loadInv(); + } +} + +void ScalpelInventory::putInv(InvSlamMode slamIt) { + ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen; + UserInterface &ui = *_vm->_ui; + + // If an inventory item has disappeared (due to using it or giving it), + // a blank space slot may have appeared. If so, adjust the inventory + if (_invIndex > 0 && _invIndex > (_holdings - (int)_invShapes.size())) { + --_invIndex; + freeGraphics(); + loadGraphics(); + } + + if (slamIt != SLAM_SECONDARY_BUFFER) { + screen.makePanel(Common::Rect(6, 163, 54, 197)); + screen.makePanel(Common::Rect(58, 163, 106, 197)); + screen.makePanel(Common::Rect(110, 163, 158, 197)); + screen.makePanel(Common::Rect(162, 163, 210, 197)); + screen.makePanel(Common::Rect(214, 163, 262, 197)); + screen.makePanel(Common::Rect(266, 163, 314, 197)); + } + + // Iterate through displaying up to 6 objects at a time + for (int idx = _invIndex; idx < _holdings && (idx - _invIndex) < (int)_invShapes.size(); ++idx) { + int itemNum = idx - _invIndex; + Surface &bb = slamIt == SLAM_SECONDARY_BUFFER ? screen._backBuffer2 : screen._backBuffer1; + Common::Rect r(8 + itemNum * 52, 165, 51 + itemNum * 52, 194); + + // Draw the background + if (idx == ui._selector) { + bb.fillRect(r, BUTTON_BACKGROUND); + } + else if (slamIt == SLAM_SECONDARY_BUFFER) { + bb.fillRect(r, BUTTON_MIDDLE); + } + + // Draw the item image + ImageFrame &frame = (*_invShapes[itemNum])[0]; + bb.transBlitFrom(frame, Common::Point(6 + itemNum * 52 + ((47 - frame._width) / 2), + 163 + ((33 - frame._height) / 2))); + } + + if (slamIt == SLAM_DISPLAY) + screen.slamArea(6, 163, 308, 34); + + if (slamIt != SLAM_SECONDARY_BUFFER) + ui.clearInfo(); + + if (slamIt == 0) { + invCommands(0); + } + else if (slamIt == SLAM_SECONDARY_BUFFER) { + screen._backBuffer = &screen._backBuffer2; + invCommands(0); + screen._backBuffer = &screen._backBuffer1; + } +} + +void ScalpelInventory::loadInv() { + // Exit if the inventory names are already loaded + if (_names.size() > 0) + return; + + // Load the inventory names + Common::SeekableReadStream *stream = _vm->_res->load("invent.txt"); + + int streamSize = stream->size(); + while (stream->pos() < streamSize) { + Common::String name; + char c; + while ((c = stream->readByte()) != 0) + name += c; + + _names.push_back(name); + } + + delete stream; + + loadGraphics(); +} + +} // End of namespace Scalpel + +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/scalpel_inventory.h b/engines/sherlock/scalpel/scalpel_inventory.h new file mode 100644 index 0000000000..afafb0b94a --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_inventory.h @@ -0,0 +1,74 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_SCALPEL_INVENTORY_H +#define SHERLOCK_SCALPEL_INVENTORY_H + +#include "sherlock/inventory.h" + +namespace Sherlock { + +namespace Scalpel { + +class ScalpelInventory : public Inventory { +public: + ScalpelInventory(SherlockEngine *vm); + ~ScalpelInventory(); + + /** + * Put the game into inventory mode and open the interface window. + */ + void drawInventory(InvNewMode flag); + + /** + * Prints the line of inventory commands at the top of an inventory window with + * the correct highlighting + */ + void invCommands(bool slamIt); + + /** + * Set the highlighting color of a given inventory item + */ + void highlight(int index, byte color); + + /** + * Support method for refreshing the display of the inventory + */ + void refreshInv(); + + /** + * Display the character's inventory. The slamIt parameter specifies: + */ + void putInv(InvSlamMode slamIt); + + /** + * Load the list of names the inventory items correspond to, if not already loaded, + * and then calls loadGraphics to load the associated graphics + */ + virtual void loadInv(); +}; + +} // End of namespace Scalpel + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/scalpel/scalpel_journal.cpp b/engines/sherlock/scalpel/scalpel_journal.cpp new file mode 100644 index 0000000000..787d899aee --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_journal.cpp @@ -0,0 +1,636 @@ +/* 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 "sherlock/journal.h" +#include "sherlock/sherlock.h" +#include "sherlock/scalpel/scalpel_fixed_text.h" +#include "sherlock/scalpel/scalpel_journal.h" +#include "sherlock/scalpel/scalpel_screen.h" +#include "sherlock/scalpel/scalpel.h" +#include "sherlock/tattoo/tattoo_journal.h" + +namespace Sherlock { + +namespace Scalpel { + +#define JOURNAL_BUTTONS_Y 178 +#define JOURNAL_SEARCH_LEFT 15 +#define JOURNAL_SEARCH_TOP 186 +#define JOURNAL_SEARCH_RIGHT 296 +#define JOURNAL_SEACRH_MAX_CHARS 50 + +// Positioning of buttons in the journal view +static const int JOURNAL_POINTS[9][3] = { + { 6, 68, 37 }, + { 69, 131, 100 }, + { 132, 192, 162 }, + { 193, 250, 221 }, + { 251, 313, 281 }, + { 6, 82, 44 }, + { 83, 159, 121 }, + { 160, 236, 198 }, + { 237, 313, 275 } +}; + +static const int SEARCH_POINTS[3][3] = { + { 51, 123, 86 }, + { 124, 196, 159 }, + { 197, 269, 232 } +}; + +/*----------------------------------------------------------------*/ + +ScalpelJournal::ScalpelJournal(SherlockEngine *vm) : Journal(vm) { + if (_vm->_interactiveFl) { + // Load the journal directory and location names + loadLocations(); + } +} + +void ScalpelJournal::loadLocations() { + Resources &res = *_vm->_res; + + _directory.clear(); + _locations.clear(); + + + Common::SeekableReadStream *dir = res.load("talk.lib"); + dir->skip(4); // Skip header + + // Get the numer of entries + _directory.resize(dir->readUint16LE()); + + // Read in each entry + char buffer[17]; + for (uint idx = 0; idx < _directory.size(); ++idx) { + dir->read(buffer, 17); + buffer[16] = '\0'; + + _directory[idx] = Common::String(buffer); + } + + delete dir; + + if (IS_3DO) { + // 3DO: storage of locations is currently unknown TODO + return; + } + + // Load in the locations stored in journal.txt + Common::SeekableReadStream *loc = res.load("journal.txt"); + + while (loc->pos() < loc->size()) { + Common::String line; + char c; + while ((c = loc->readByte()) != 0) + line += c; + + _locations.push_back(line); + } + + delete loc; +} + +void ScalpelJournal::drawFrame() { + FixedText &fixedText = *_vm->_fixedText; + Resources &res = *_vm->_res; + ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen; + byte palette[PALETTE_SIZE]; + + // Load in the journal background + Common::SeekableReadStream *bg = res.load("journal.lbv"); + bg->read(screen._backBuffer1.getPixels(), SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCREEN_HEIGHT); + bg->read(palette, PALETTE_SIZE); + delete bg; + + // Translate the palette for display + for (int idx = 0; idx < PALETTE_SIZE; ++idx) + palette[idx] = VGA_COLOR_TRANS(palette[idx]); + + Common::String fixedText_WatsonsJournal = fixedText.getText(kFixedText_Journal_WatsonsJournal); + Common::String fixedText_Exit = fixedText.getText(kFixedText_Journal_Exit); + Common::String fixedText_Back10 = fixedText.getText(kFixedText_Journal_Back10); + Common::String fixedText_Up = fixedText.getText(kFixedText_Journal_Up); + Common::String fixedText_Down = fixedText.getText(kFixedText_Journal_Down); + Common::String fixedText_Ahead10 = fixedText.getText(kFixedText_Journal_Ahead10); + Common::String fixedText_Search = fixedText.getText(kFixedText_Journal_Search); + Common::String fixedText_FirstPage = fixedText.getText(kFixedText_Journal_FirstPage); + Common::String fixedText_LastPage = fixedText.getText(kFixedText_Journal_LastPage); + Common::String fixedText_PrintText = fixedText.getText(kFixedText_Journal_PrintText); + + // Set the palette and print the title + screen.setPalette(palette); + screen.gPrint(Common::Point(111, 18), BUTTON_BOTTOM, "%s", fixedText_WatsonsJournal.c_str()); + screen.gPrint(Common::Point(110, 17), INV_FOREGROUND, "%s", fixedText_WatsonsJournal.c_str()); + + // Draw the buttons + screen.makeButton(Common::Rect(JOURNAL_POINTS[0][0], JOURNAL_BUTTONS_Y, + JOURNAL_POINTS[0][1], JOURNAL_BUTTONS_Y + 10), + JOURNAL_POINTS[0][2] - screen.stringWidth(fixedText_Exit) / 2, fixedText_Exit); + screen.makeButton(Common::Rect(JOURNAL_POINTS[1][0], JOURNAL_BUTTONS_Y, + JOURNAL_POINTS[1][1], JOURNAL_BUTTONS_Y + 10), + JOURNAL_POINTS[1][2] - screen.stringWidth(fixedText_Back10) / 2, fixedText_Back10); + screen.makeButton(Common::Rect(JOURNAL_POINTS[2][0], JOURNAL_BUTTONS_Y, + JOURNAL_POINTS[2][1], JOURNAL_BUTTONS_Y + 10), + JOURNAL_POINTS[2][2] - screen.stringWidth(fixedText_Up) / 2, fixedText_Up); + screen.makeButton(Common::Rect(JOURNAL_POINTS[3][0], JOURNAL_BUTTONS_Y, + JOURNAL_POINTS[3][1], JOURNAL_BUTTONS_Y + 10), + JOURNAL_POINTS[3][2] - screen.stringWidth(fixedText_Down) / 2, fixedText_Down); + screen.makeButton(Common::Rect(JOURNAL_POINTS[4][0], JOURNAL_BUTTONS_Y, + JOURNAL_POINTS[4][1], JOURNAL_BUTTONS_Y + 10), + JOURNAL_POINTS[4][2] - screen.stringWidth(fixedText_Ahead10) / 2, fixedText_Ahead10); + screen.makeButton(Common::Rect(JOURNAL_POINTS[5][0], JOURNAL_BUTTONS_Y + 11, + JOURNAL_POINTS[5][1], JOURNAL_BUTTONS_Y + 21), + JOURNAL_POINTS[5][2] - screen.stringWidth(fixedText_Search) / 2, fixedText_Search); + screen.makeButton(Common::Rect(JOURNAL_POINTS[6][0], JOURNAL_BUTTONS_Y + 11, + JOURNAL_POINTS[6][1], JOURNAL_BUTTONS_Y + 21), + JOURNAL_POINTS[6][2] - screen.stringWidth(fixedText_FirstPage) / 2, fixedText_FirstPage); + screen.makeButton(Common::Rect(JOURNAL_POINTS[7][0], JOURNAL_BUTTONS_Y + 11, + JOURNAL_POINTS[7][1], JOURNAL_BUTTONS_Y + 21), + JOURNAL_POINTS[7][2] - screen.stringWidth(fixedText_LastPage) / 2, fixedText_LastPage); + + // WORKAROUND: Draw Print Text button as disabled, since we don't support it in ScummVM + screen.makeButton(Common::Rect(JOURNAL_POINTS[8][0], JOURNAL_BUTTONS_Y + 11, + JOURNAL_POINTS[8][1], JOURNAL_BUTTONS_Y + 21), + JOURNAL_POINTS[8][2] - screen.stringWidth(fixedText_PrintText) / 2, fixedText_PrintText); + screen.buttonPrint(Common::Point(JOURNAL_POINTS[8][2], JOURNAL_BUTTONS_Y + 11), + COMMAND_NULL, false, fixedText_PrintText); +} + +void ScalpelJournal::drawInterface() { + ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen; + + drawFrame(); + + if (_journal.empty()) { + _up = _down = 0; + } else { + drawJournal(0, 0); + } + + doArrows(); + + // Show the entire screen + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); +} + +void ScalpelJournal::doArrows() { + FixedText &fixedText = *_vm->_fixedText; + ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen; + byte color; + + Common::String fixedText_Back10 = fixedText.getText(kFixedText_Journal_Back10); + Common::String fixedText_Up = fixedText.getText(kFixedText_Journal_Up); + Common::String fixedText_Down = fixedText.getText(kFixedText_Journal_Down); + Common::String fixedText_Ahead10 = fixedText.getText(kFixedText_Journal_Ahead10); + Common::String fixedText_Search = fixedText.getText(kFixedText_Journal_Search); + Common::String fixedText_FirstPage = fixedText.getText(kFixedText_Journal_FirstPage); + Common::String fixedText_LastPage = fixedText.getText(kFixedText_Journal_LastPage); + Common::String fixedText_PrintText = fixedText.getText(kFixedText_Journal_PrintText); + + color = (_page > 1) ? COMMAND_FOREGROUND : COMMAND_NULL; + screen.buttonPrint(Common::Point(JOURNAL_POINTS[1][2], JOURNAL_BUTTONS_Y), color, false, fixedText_Back10); + screen.buttonPrint(Common::Point(JOURNAL_POINTS[2][2], JOURNAL_BUTTONS_Y), color, false, fixedText_Up); + + color = _down ? COMMAND_FOREGROUND : COMMAND_NULL; + screen.buttonPrint(Common::Point(JOURNAL_POINTS[3][2], JOURNAL_BUTTONS_Y), color, false, fixedText_Down); + screen.buttonPrint(Common::Point(JOURNAL_POINTS[4][2], JOURNAL_BUTTONS_Y), color, false, fixedText_Ahead10); + screen.buttonPrint(Common::Point(JOURNAL_POINTS[7][2], JOURNAL_BUTTONS_Y + 11), color, false, fixedText_LastPage); + + color = _journal.size() > 0 ? COMMAND_FOREGROUND : COMMAND_NULL; + screen.buttonPrint(Common::Point(JOURNAL_POINTS[5][2], JOURNAL_BUTTONS_Y + 11), color, false, fixedText_Search); + screen.buttonPrint(Common::Point(JOURNAL_POINTS[8][2], JOURNAL_BUTTONS_Y + 11), COMMAND_NULL, false, fixedText_PrintText); + + color = _page > 1 ? COMMAND_FOREGROUND : COMMAND_NULL; + screen.buttonPrint(Common::Point(JOURNAL_POINTS[6][2], JOURNAL_BUTTONS_Y + 11), color, false, fixedText_FirstPage); +} + +JournalButton ScalpelJournal::getHighlightedButton(const Common::Point &pt) { + if (pt.x > JOURNAL_POINTS[0][0] && pt.x < JOURNAL_POINTS[0][1] && pt.y >= JOURNAL_BUTTONS_Y && + pt.y < (JOURNAL_BUTTONS_Y + 10)) + return BTN_EXIT; + + if (pt.x > JOURNAL_POINTS[1][0] && pt.x < JOURNAL_POINTS[1][1] && pt.y >= JOURNAL_BUTTONS_Y && + pt.y < (JOURNAL_BUTTONS_Y + 10) && _page > 1) + return BTN_BACK10; + + if (pt.x > JOURNAL_POINTS[2][0] && pt.x < JOURNAL_POINTS[2][1] && pt.y >= JOURNAL_BUTTONS_Y && + pt.y < (JOURNAL_BUTTONS_Y + 10) && _up) + return BTN_UP; + + if (pt.x > JOURNAL_POINTS[3][0] && pt.x < JOURNAL_POINTS[3][1] && pt.y >= JOURNAL_BUTTONS_Y && + pt.y < (JOURNAL_BUTTONS_Y + 10) && _down) + return BTN_DOWN; + + if (pt.x > JOURNAL_POINTS[4][0] && pt.x < JOURNAL_POINTS[4][1] && pt.y >= JOURNAL_BUTTONS_Y && + pt.y < (JOURNAL_BUTTONS_Y + 10) && _down) + return BTN_AHEAD110; + + if (pt.x > JOURNAL_POINTS[5][0] && pt.x < JOURNAL_POINTS[5][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) && + pt.y < (JOURNAL_BUTTONS_Y + 20) && !_journal.empty()) + return BTN_SEARCH; + + if (pt.x > JOURNAL_POINTS[6][0] && pt.x < JOURNAL_POINTS[6][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) && + pt.y < (JOURNAL_BUTTONS_Y + 20) && _up) + return BTN_FIRST_PAGE; + + if (pt.x > JOURNAL_POINTS[7][0] && pt.x < JOURNAL_POINTS[7][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) && + pt.y < (JOURNAL_BUTTONS_Y + 20) && _down) + return BTN_LAST_PAGE; + + if (pt.x > JOURNAL_POINTS[8][0] && pt.x < JOURNAL_POINTS[8][1] && pt.y >= (JOURNAL_BUTTONS_Y + 11) && + pt.y < (JOURNAL_BUTTONS_Y + 20) && !_journal.empty()) + return BTN_PRINT_TEXT; + + return BTN_NONE; +} + +bool ScalpelJournal::handleEvents(int key) { + Events &events = *_vm->_events; + FixedText &fixedText = *_vm->_fixedText; + ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen; + bool doneFlag = false; + + Common::Point pt = events.mousePos(); + JournalButton btn = getHighlightedButton(pt); + byte color; + + if (events._pressed || events._released) { + Common::String fixedText_Exit = fixedText.getText(kFixedText_Journal_Exit); + Common::String fixedText_Back10 = fixedText.getText(kFixedText_Journal_Back10); + Common::String fixedText_Up = fixedText.getText(kFixedText_Journal_Up); + Common::String fixedText_Down = fixedText.getText(kFixedText_Journal_Down); + Common::String fixedText_Ahead10 = fixedText.getText(kFixedText_Journal_Ahead10); + Common::String fixedText_Search = fixedText.getText(kFixedText_Journal_Search); + Common::String fixedText_FirstPage = fixedText.getText(kFixedText_Journal_FirstPage); + Common::String fixedText_LastPage = fixedText.getText(kFixedText_Journal_LastPage); + Common::String fixedText_PrintText = fixedText.getText(kFixedText_Journal_PrintText); + + // Exit button + color = (btn == BTN_EXIT) ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND; + screen.buttonPrint(Common::Point(JOURNAL_POINTS[0][2], JOURNAL_BUTTONS_Y), color, true, fixedText_Exit); + + // Back 10 button + if (btn == BTN_BACK10) { + screen.buttonPrint(Common::Point(JOURNAL_POINTS[1][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Back10); + } else if (_page > 1) { + screen.buttonPrint(Common::Point(JOURNAL_POINTS[1][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, fixedText_Back10); + } + + // Up button + if (btn == BTN_UP) { + screen.buttonPrint(Common::Point(JOURNAL_POINTS[2][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Up); + } else if (_up) { + screen.buttonPrint(Common::Point(JOURNAL_POINTS[2][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, fixedText_Up); + } + + // Down button + if (btn == BTN_DOWN) { + screen.buttonPrint(Common::Point(JOURNAL_POINTS[3][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Down); + } else if (_down) { + screen.buttonPrint(Common::Point(JOURNAL_POINTS[3][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, fixedText_Down); + } + + // Ahead 10 button + if (btn == BTN_AHEAD110) { + screen.buttonPrint(Common::Point(JOURNAL_POINTS[4][2], JOURNAL_BUTTONS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Ahead10); + } else if (_down) { + screen.buttonPrint(Common::Point(JOURNAL_POINTS[4][2], JOURNAL_BUTTONS_Y), COMMAND_FOREGROUND, true, fixedText_Ahead10); + } + + // Search button + if (btn == BTN_SEARCH) { + color = COMMAND_HIGHLIGHTED; + } else if (_journal.empty()) { + color = COMMAND_NULL; + } else { + color = COMMAND_FOREGROUND; + } + screen.buttonPrint(Common::Point(JOURNAL_POINTS[5][2], JOURNAL_BUTTONS_Y + 11), color, true, fixedText_Search); + + // First Page button + if (btn == BTN_FIRST_PAGE) { + color = COMMAND_HIGHLIGHTED; + } else if (_up) { + color = COMMAND_FOREGROUND; + } else { + color = COMMAND_NULL; + } + screen.buttonPrint(Common::Point(JOURNAL_POINTS[6][2], JOURNAL_BUTTONS_Y + 11), color, true, fixedText_FirstPage); + + // Last Page button + if (btn == BTN_LAST_PAGE) { + color = COMMAND_HIGHLIGHTED; + } else if (_down) { + color = COMMAND_FOREGROUND; + } else { + color = COMMAND_NULL; + } + screen.buttonPrint(Common::Point(JOURNAL_POINTS[7][2], JOURNAL_BUTTONS_Y + 11), color, true, fixedText_LastPage); + + // Print Text button + screen.buttonPrint(Common::Point(JOURNAL_POINTS[8][2], JOURNAL_BUTTONS_Y + 11), COMMAND_NULL, true, fixedText_PrintText); + } + + if (btn == BTN_EXIT && events._released) { + // Exit button pressed + doneFlag = true; + + } else if (((btn == BTN_BACK10 && events._released) || key == 'B') && (_page > 1)) { + // Scrolll up 10 pages + if (_page < 11) + drawJournal(1, (_page - 1) * LINES_PER_PAGE); + else + drawJournal(1, 10 * LINES_PER_PAGE); + + doArrows(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + + } else if (((btn == BTN_UP && events._released) || key == 'U') && _up) { + // Scroll up + drawJournal(1, LINES_PER_PAGE); + doArrows(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + + } else if (((btn == BTN_DOWN && events._released) || key == 'D') && _down) { + // Scroll down + drawJournal(2, LINES_PER_PAGE); + doArrows(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + + } else if (((btn == BTN_AHEAD110 && events._released) || key == 'A') && _down) { + // Scroll down 10 pages + if ((_page + 10) > _maxPage) + drawJournal(2, (_maxPage - _page) * LINES_PER_PAGE); + else + drawJournal(2, 10 * LINES_PER_PAGE); + + doArrows(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + + } else if (((btn == BTN_SEARCH && events._released) || key == 'S') && !_journal.empty()) { + screen.buttonPrint(Common::Point(JOURNAL_POINTS[5][2], JOURNAL_BUTTONS_Y + 11), COMMAND_FOREGROUND, true, "Search"); + bool notFound = false; + + do { + int dir; + if ((dir = getSearchString(notFound)) != 0) { + int savedIndex = _index; + int savedSub = _sub; + int savedPage = _page; + + if (drawJournal(dir + 2, 1000 * LINES_PER_PAGE) == 0) { + _index = savedIndex; + _sub = savedSub; + _page = savedPage; + + drawFrame(); + drawJournal(0, 0); + notFound = true; + } else { + doneFlag = true; + } + + doArrows(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + } else { + doneFlag = true; + } + } while (!doneFlag); + doneFlag = false; + + } else if (((btn == BTN_FIRST_PAGE && events._released) || key == 'F') && _up) { + // First page + _index = _sub = 0; + _up = _down = false; + _page = 1; + + drawFrame(); + drawJournal(0, 0); + doArrows(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + + } else if (((btn == BTN_LAST_PAGE && events._released) || key == 'L') && _down) { + // Last page + if ((_page + 10) > _maxPage) + drawJournal(2, (_maxPage - _page) * LINES_PER_PAGE); + else + drawJournal(2, 1000 * LINES_PER_PAGE); + + doArrows(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + } + + events.wait(2); + + return doneFlag; +} + +int ScalpelJournal::getSearchString(bool printError) { + enum Button { BTN_NONE, BTN_EXIT, BTN_BACKWARD, BTN_FORWARD }; + + Events &events = *_vm->_events; + FixedText &fixedText = *_vm->_fixedText; + ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen; + Talk &talk = *_vm->_talk; + int xp; + int yp = 174; + bool flag = false; + Common::String name; + int done = 0; + byte color; + + Common::String fixedText_Exit = fixedText.getText(kFixedText_JournalSearch_Exit); + Common::String fixedText_Backward = fixedText.getText(kFixedText_JournalSearch_Backward); + Common::String fixedText_Forward = fixedText.getText(kFixedText_JournalSearch_Forward); + Common::String fixedText_NotFound = fixedText.getText(kFixedText_JournalSearch_NotFound); + + // Draw search panel + screen.makePanel(Common::Rect(6, 171, 313, 199)); + screen.makeButton(Common::Rect(SEARCH_POINTS[0][0], yp, SEARCH_POINTS[0][1], yp + 10), + SEARCH_POINTS[0][2] - screen.stringWidth(fixedText_Exit) / 2, fixedText_Exit); + screen.makeButton(Common::Rect(SEARCH_POINTS[1][0], yp, SEARCH_POINTS[1][1], yp + 10), + SEARCH_POINTS[1][2] - screen.stringWidth(fixedText_Backward) / 2, fixedText_Backward); + screen.makeButton(Common::Rect(SEARCH_POINTS[2][0], yp, SEARCH_POINTS[2][1], yp + 10), + SEARCH_POINTS[2][2] - screen.stringWidth(fixedText_Forward) / 2, fixedText_Forward); + + screen.gPrint(Common::Point(SEARCH_POINTS[0][2] - screen.stringWidth(fixedText_Exit) / 2, yp), + COMMAND_HIGHLIGHTED, "%c", fixedText_Exit[0]); + screen.gPrint(Common::Point(SEARCH_POINTS[1][2] - screen.stringWidth(fixedText_Backward) / 2, yp), + COMMAND_HIGHLIGHTED, "%c", fixedText_Backward[0]); + screen.gPrint(Common::Point(SEARCH_POINTS[2][2] - screen.stringWidth(fixedText_Forward) / 2, yp), + COMMAND_HIGHLIGHTED, "%c", fixedText_Forward[0]); + + screen.makeField(Common::Rect(12, 185, 307, 196)); + + screen.fillRect(Common::Rect(12, 185, 307, 186), BUTTON_BOTTOM); + screen.vLine(12, 185, 195, BUTTON_BOTTOM); + screen.hLine(13, 195, 306, BUTTON_TOP); + screen.hLine(306, 186, 195, BUTTON_TOP); + + if (printError) { + screen.gPrint(Common::Point((SHERLOCK_SCREEN_WIDTH - screen.stringWidth(fixedText_NotFound)) / 2, 185), + INV_FOREGROUND, "%s", fixedText_NotFound.c_str()); + } else if (!_find.empty()) { + // There's already a search term, display it already + screen.gPrint(Common::Point(15, 185), TALK_FOREGROUND, "%s", _find.c_str()); + name = _find; + } + + screen.slamArea(6, 171, 307, 28); + + if (printError) { + // Give time for user to see the message + events.setButtonState(); + for (int idx = 0; idx < 40 && !_vm->shouldQuit() && !events.kbHit() && !events._released; ++idx) { + events.pollEvents(); + events.setButtonState(); + events.wait(2); + } + + events.clearKeyboard(); + screen._backBuffer1.fillRect(Common::Rect(13, 186, 306, 195), BUTTON_MIDDLE); + + if (!_find.empty()) { + screen.gPrint(Common::Point(15, 185), TALK_FOREGROUND, "%s", _find.c_str()); + name = _find; + } + + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + } + + xp = JOURNAL_SEARCH_LEFT + screen.stringWidth(name); + yp = JOURNAL_SEARCH_TOP; + + do { + events._released = false; + Button found = BTN_NONE; + + while (!_vm->shouldQuit() && !events.kbHit() && !events._released) { + found = BTN_NONE; + if (talk._talkToAbort) + return 0; + + // Check if key or mouse button press has occurred + events.setButtonState(); + Common::Point pt = events.mousePos(); + + flag = !flag; + screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), flag ? INV_FOREGROUND : BUTTON_MIDDLE); + + if (events._pressed || events._released) { + if (pt.x > SEARCH_POINTS[0][0] && pt.x < SEARCH_POINTS[0][1] && pt.y > 174 && pt.y < 183) { + found = BTN_EXIT; + color = COMMAND_HIGHLIGHTED; + } else { + color = COMMAND_FOREGROUND; + } + screen.print(Common::Point(SEARCH_POINTS[0][2] - screen.stringWidth(fixedText_Exit) / 2, 175), color, "%s", fixedText_Exit.c_str()); + + if (pt.x > SEARCH_POINTS[1][0] && pt.x < SEARCH_POINTS[1][1] && pt.y > 174 && pt.y < 183) { + found = BTN_BACKWARD; + color = COMMAND_HIGHLIGHTED; + } else { + color = COMMAND_FOREGROUND; + } + screen.print(Common::Point(SEARCH_POINTS[1][2] - screen.stringWidth(fixedText_Backward) / 2, 175), color, "%s", fixedText_Backward.c_str()); + + if (pt.x > SEARCH_POINTS[2][0] && pt.x < SEARCH_POINTS[2][1] && pt.y > 174 && pt.y < 183) { + found = BTN_FORWARD; + color = COMMAND_HIGHLIGHTED; + } else { + color = COMMAND_FOREGROUND; + } + screen.print(Common::Point(SEARCH_POINTS[2][2] - screen.stringWidth(fixedText_Forward) / 2, 175), color, "%s", fixedText_Forward.c_str()); + } + + events.wait(2); + } + + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + + if ((keyState.keycode == Common::KEYCODE_BACKSPACE) && (name.size() > 0)) { + screen.vgaBar(Common::Rect(xp - screen.charWidth(name.lastChar()), yp, xp + 8, yp + 9), BUTTON_MIDDLE); + xp -= screen.charWidth(name.lastChar()); + screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), INV_FOREGROUND); + name.deleteLastChar(); + + } else if (keyState.keycode == Common::KEYCODE_RETURN) { + done = 1; + + } else if (keyState.keycode == Common::KEYCODE_ESCAPE) { + screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), BUTTON_MIDDLE); + done = -1; + + } else if (keyState.ascii >= ' ' && keyState.ascii <= 'z' && keyState.keycode != Common::KEYCODE_AT && + name.size() < JOURNAL_SEACRH_MAX_CHARS && (xp + screen.charWidth(keyState.ascii)) < JOURNAL_SEARCH_RIGHT) { + char ch = toupper(keyState.ascii); + screen.vgaBar(Common::Rect(xp, yp, xp + 8, yp + 9), BUTTON_MIDDLE); + screen.print(Common::Point(xp, yp), TALK_FOREGROUND, "%c", ch); + xp += screen.charWidth(ch); + name += ch; + } + } + + if (events._released) { + switch (found) { + case BTN_EXIT: + done = -1; break; + case BTN_BACKWARD: + done = 2; break; + case BTN_FORWARD: + done = 1; break; + default: + break; + } + } + } while (!done && !_vm->shouldQuit()); + + if (done != -1) { + _find = name; + } else { + done = 0; + } + + // Redisplay the journal screen + drawFrame(); + drawJournal(0, 0); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + + return done; +} + +void ScalpelJournal::resetPosition() { + _index = _sub = _up = _down = 0; + _page = 1; +} + +void ScalpelJournal::record(int converseNum, int statementNum, bool replyOnly) { + // there seems to be no journal in the 3DO version + if (!IS_3DO) + Journal::record(converseNum, statementNum, replyOnly); +} + +} // End of namespace Scalpel + +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/scalpel_journal.h b/engines/sherlock/scalpel/scalpel_journal.h new file mode 100644 index 0000000000..c8e9c01739 --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_journal.h @@ -0,0 +1,99 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_SCALPEL_JOURNAL_H +#define SHERLOCK_SCALPEL_JOURNAL_H + +#include "sherlock/journal.h" +#include "sherlock/saveload.h" +#include "common/scummsys.h" +#include "common/array.h" +#include "common/rect.h" +#include "common/str-array.h" +#include "common/stream.h" + +namespace Sherlock { + +namespace Scalpel { + +enum JournalButton { + BTN_NONE, BTN_EXIT, BTN_BACK10, BTN_UP, BTN_DOWN, BTN_AHEAD110, BTN_SEARCH, + BTN_FIRST_PAGE, BTN_LAST_PAGE, BTN_PRINT_TEXT +}; + +class ScalpelJournal: public Journal { +private: + /** + * Load the list of journal locations + */ + void loadLocations(); + + /** + * Display the arrows that can be used to scroll up and down pages + */ + void doArrows(); + + /** + * Show the search submenu and allow the player to enter a search string + */ + int getSearchString(bool printError); + + /** + * Returns the button, if any, that is under the specified position + */ + JournalButton getHighlightedButton(const Common::Point &pt); +public: + ScalpelJournal(SherlockEngine *vm); + virtual ~ScalpelJournal() {} + + /** + * Display the journal + */ + void drawInterface(); + + /** + * Handle events whilst the journal is being displayed + */ + bool handleEvents(int key); +public: + /** + * Draw the journal background, frame, and interface buttons + */ + virtual void drawFrame(); + + /** + * Reset viewing position to the start of the journal + */ + virtual void resetPosition(); + + /** + * Records statements that are said, in the order which they are said. The player + * can then read the journal to review them + */ + virtual void record(int converseNum, int statementNum, bool replyOnly = false); +}; + +} // End of namespace Scalpel + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/scalpel/scalpel_map.cpp b/engines/sherlock/scalpel/scalpel_map.cpp new file mode 100644 index 0000000000..369822ba02 --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_map.cpp @@ -0,0 +1,596 @@ +/* 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 "common/system.h" +#include "sherlock/scalpel/scalpel_map.h" +#include "sherlock/events.h" +#include "sherlock/people.h" +#include "sherlock/screen.h" +#include "sherlock/sherlock.h" + +namespace Sherlock { + +namespace Scalpel { + +MapPaths::MapPaths() { + _numLocations = 0; +} + +void MapPaths::load(int numLocations, Common::SeekableReadStream &s) { + _numLocations = numLocations; + _paths.resize(_numLocations * _numLocations); + + for (int idx = 0; idx < (numLocations * numLocations); ++idx) { + Common::Array<byte> &path = _paths[idx]; + int v; + + do { + v = s.readByte(); + path.push_back(v); + } while (v && v < 254); + } +} + +const byte *MapPaths::getPath(int srcLocation, int destLocation) { + return &_paths[srcLocation * _numLocations + destLocation][0]; +} + +/*----------------------------------------------------------------*/ + +ScalpelMap::ScalpelMap(SherlockEngine *vm): Map(vm), _topLine(g_system->getWidth(), 12) { + _mapCursors = nullptr; + _shapes = nullptr; + _iconShapes = nullptr; + _point = 0; + _placesShown = false; + _cursorIndex = -1; + _drawMap = false; + _overPos = Point32(130 * FIXED_INT_MULTIPLIER, 126 * FIXED_INT_MULTIPLIER); + _frameChangeFlag = false; + + // Initialise the initial walk sequence set + _walkSequences.resize(MAX_HOLMES_SEQUENCE); + for (int idx = 0; idx < MAX_HOLMES_SEQUENCE; ++idx) { + _walkSequences[idx]._sequences.resize(MAX_FRAME); + Common::fill(&_walkSequences[idx]._sequences[0], &_walkSequences[idx]._sequences[0] + MAX_FRAME, 0); + } + + if (!_vm->isDemo()) + loadData(); +} + +void ScalpelMap::loadPoints(int count, const int *xList, const int *yList, const int *transList) { + for (int idx = 0; idx < count; ++idx, ++xList, ++yList, ++transList) { + _points.push_back(MapEntry(*xList, *yList, *transList)); + } +} + +void ScalpelMap::loadSequences(int count, const byte *seq) { + for (int idx = 0; idx < count; ++idx, seq += MAX_FRAME) + Common::copy(seq, seq + MAX_FRAME, &_walkSequences[idx]._sequences[0]); +} + +void ScalpelMap::loadData() { + // Load the list of location names + Common::SeekableReadStream *txtStream = _vm->_res->load("chess.txt"); + + int streamSize = txtStream->size(); + while (txtStream->pos() < streamSize) { + Common::String line; + char c; + while ((c = txtStream->readByte()) != '\0') + line += c; + + _locationNames.push_back(line); + } + + delete txtStream; + + // Load the path data + Common::SeekableReadStream *pathStream = _vm->_res->load("chess.pth"); + + // Get routes between different locations on the map + _paths.load(31, *pathStream); + + // Load in the co-ordinates that the paths refer to + _pathPoints.resize(208); + for (uint idx = 0; idx < _pathPoints.size(); ++idx) { + _pathPoints[idx].x = pathStream->readSint16LE(); + _pathPoints[idx].y = pathStream->readSint16LE(); + } + + delete pathStream; +} + +int ScalpelMap::show() { + Debugger &debugger = *_vm->_debugger; + Events &events = *_vm->_events; + People &people = *_vm->_people; + Screen &screen = *_vm->_screen; + bool changed = false, exitFlag = false; + _active = true; + + // Set font and custom cursor for the map + int oldFont = screen.fontNumber(); + screen.setFont(0); + + // Initial screen clear + screen._backBuffer1.clear(); + screen.clear(); + + // Load the entire map + ImageFile *bigMap = NULL; + if (!IS_3DO) { + // PC + bigMap = new ImageFile("bigmap.vgs"); + screen.setPalette(bigMap->_palette); + } else { + // 3DO + bigMap = new ImageFile3DO("overland.cel", kImageFile3DOType_Cel); + } + + // Load need sprites + setupSprites(); + + if (!IS_3DO) { + screen._backBuffer1.blitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y)); + screen._backBuffer1.blitFrom((*bigMap)[1], Common::Point(-_bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y)); + screen._backBuffer1.blitFrom((*bigMap)[2], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, -_bigPos.y)); + screen._backBuffer1.blitFrom((*bigMap)[3], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y)); + } else { + screen._backBuffer1.blitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y)); + screen.blitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y)); + } + + _drawMap = true; + _charPoint = -1; + _point = -1; + people[HOLMES]._position = _lDrawnPos = _overPos; + + // Show place icons + showPlaces(); + saveTopLine(); + _placesShown = true; + + // Keep looping until either a location is picked, or the game is ended + while (!_vm->shouldQuit() && !exitFlag) { + events.pollEventsAndWait(); + events.setButtonState(); + + if (debugger._showAllLocations == LOC_REFRESH) { + showPlaces(); + screen.slamArea(screen._currentScroll.x, screen._currentScroll.y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_WIDTH); + } + + // Keyboard handling + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + + if (keyState.keycode == Common::KEYCODE_RETURN || keyState.keycode == Common::KEYCODE_SPACE) { + // Both space and enter simulate a mouse release + events._pressed = false; + events._released = true; + events._oldButtons = 0; + } + } + + // Ignore scrolling attempts until the screen is drawn + if (!_drawMap) { + Common::Point pt = events.mousePos(); + + // Check for vertical map scrolling + if ((pt.y > (SHERLOCK_SCREEN_HEIGHT - 10) && _bigPos.y < 200) || (pt.y < 10 && _bigPos.y > 0)) { + if (pt.y > (SHERLOCK_SCREEN_HEIGHT - 10)) + _bigPos.y += 10; + else + _bigPos.y -= 10; + + changed = true; + } + + // Check for horizontal map scrolling + if ((pt.x > (SHERLOCK_SCREEN_WIDTH - 10) && _bigPos.x < 315) || (pt.x < 10 && _bigPos.x > 0)) { + if (pt.x > (SHERLOCK_SCREEN_WIDTH - 10)) + _bigPos.x += 15; + else + _bigPos.x -= 15; + + changed = true; + } + } + + if (changed) { + // Map has scrolled, so redraw new map view + changed = false; + + if (!IS_3DO) { + screen._backBuffer1.blitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y)); + screen._backBuffer1.blitFrom((*bigMap)[1], Common::Point(-_bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y)); + screen._backBuffer1.blitFrom((*bigMap)[2], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, -_bigPos.y)); + screen._backBuffer1.blitFrom((*bigMap)[3], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y)); + } else { + screen._backBuffer1.blitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y)); + } + + showPlaces(); + _placesShown = false; + + saveTopLine(); + _savedPos.x = -1; + updateMap(true); + } else if (!_drawMap) { + if (!_placesShown) { + showPlaces(); + _placesShown = true; + } + + if (_cursorIndex == 0) { + Common::Point pt = events.mousePos(); + highlightIcon(Common::Point(pt.x - 4 + _bigPos.x, pt.y + _bigPos.y)); + } + updateMap(false); + } + + if ((events._released || events._rightReleased) && _point != -1) { + if (people[HOLMES]._walkCount == 0) { + people[HOLMES]._walkDest = _points[_point] + Common::Point(4, 9); + _charPoint = _point; + + // Start walking to selected location + walkTheStreets(); + + // Show wait cursor + _cursorIndex = 1; + events.setCursor((*_mapCursors)[_cursorIndex]._frame); + } + } + + // Check if a scene has beeen selected and we've finished "moving" to it + if (people[HOLMES]._walkCount == 0) { + if (_charPoint >= 1 && _charPoint < (int)_points.size()) + exitFlag = true; + } + + if (_drawMap) { + _drawMap = false; + + if (screen._fadeStyle) + screen.randomTransition(); + else + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + } + + // Wait for a frame + events.wait(1); + } + + freeSprites(); + _overPos = people[HOLMES]._position; + + // Reset font + screen.setFont(oldFont); + + // Free map graphic + delete bigMap; + + _active = false; + return _charPoint; +} + +void ScalpelMap::setupSprites() { + Events &events = *_vm->_events; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + _savedPos.x = -1; + + if (!IS_3DO) { + // PC + _mapCursors = new ImageFile("omouse.vgs"); + _shapes = new ImageFile("mapicon.vgs"); + _iconShapes = new ImageFile("overicon.vgs"); + } else { + // 3DO + _mapCursors = new ImageFile3DO("omouse.vgs", kImageFile3DOType_RoomFormat); + _shapes = new ImageFile3DO("mapicon.vgs", kImageFile3DOType_RoomFormat); + _iconShapes = new ImageFile3DO("overicon.vgs", kImageFile3DOType_RoomFormat); + } + + _cursorIndex = 0; + events.setCursor((*_mapCursors)[_cursorIndex]._frame); + + _iconSave.create((*_shapes)[4]._width, (*_shapes)[4]._height); + Person &p = people[HOLMES]; + p._description = " "; + p._type = CHARACTER; + p._position = Common::Point(12400, 5000); + p._sequenceNumber = 0; + p._images = _shapes; + p._imageFrame = nullptr; + p._frameNumber = 0; + p._delta = Common::Point(0, 0); + p._oldSize = Common::Point(0, 0); + p._oldSize = Common::Point(0, 0); + p._misc = 0; + p._walkCount = 0; + p._allow = 0; + p._noShapeSize = Common::Point(0, 0); + p._goto = Common::Point(28000, 15000); + p._status = 0; + p._walkSequences = _walkSequences; + p.setImageFrame(); + scene._bgShapes.clear(); +} + +void ScalpelMap::freeSprites() { + delete _mapCursors; + delete _shapes; + delete _iconShapes; + _iconSave.free(); +} + +void ScalpelMap::showPlaces() { + Debugger &debugger = *_vm->_debugger; + Screen &screen = *_vm->_screen; + + for (uint idx = 0; idx < _points.size(); ++idx) { + const MapEntry &pt = _points[idx]; + + if (pt.x != 0 && pt.y != 0) { + if (debugger._showAllLocations != LOC_DISABLED) + _vm->setFlagsDirect(idx); + + if (pt.x >= _bigPos.x && (pt.x - _bigPos.x) < SHERLOCK_SCREEN_WIDTH + && pt.y >= _bigPos.y && (pt.y - _bigPos.y) < SHERLOCK_SCREEN_HEIGHT) { + if (_vm->readFlags(idx)) { + screen._backBuffer1.transBlitFrom((*_iconShapes)[pt._translate], + Common::Point(pt.x - _bigPos.x - 6, pt.y - _bigPos.y - 12)); + } + } + } + } + + if (debugger._showAllLocations == LOC_REFRESH) + debugger._showAllLocations = LOC_ALL; +} + +void ScalpelMap::saveTopLine() { + _topLine.blitFrom(_vm->_screen->_backBuffer1, Common::Point(0, 0), Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, 12)); +} + +void ScalpelMap::eraseTopLine() { + Screen &screen = *_vm->_screen; + screen._backBuffer1.blitFrom(_topLine, Common::Point(0, 0)); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, _topLine.h()); +} + +void ScalpelMap::showPlaceName(int idx, bool highlighted) { + People &people = *_vm->_people; + Screen &screen = *_vm->_screen; + + Common::String name = _locationNames[idx]; + int width = screen.stringWidth(name); + + if (!_cursorIndex) { + saveIcon(people[HOLMES]._imageFrame, _lDrawnPos); + + bool flipped = people[HOLMES]._sequenceNumber == MAP_DOWNLEFT || people[HOLMES]._sequenceNumber == MAP_LEFT + || people[HOLMES]._sequenceNumber == MAP_UPLEFT; + screen._backBuffer1.transBlitFrom(*people[HOLMES]._imageFrame, _lDrawnPos, flipped); + } + + if (highlighted) { + int xp = (SHERLOCK_SCREEN_WIDTH - screen.stringWidth(name)) / 2; + screen.gPrint(Common::Point(xp + 2, 2), BLACK, "%s", name.c_str()); + screen.gPrint(Common::Point(xp + 1, 1), BLACK, "%s", name.c_str()); + screen.gPrint(Common::Point(xp, 0), 12, "%s", name.c_str()); + + screen.slamArea(xp, 0, width + 2, 15); + } +} + +void ScalpelMap::updateMap(bool flushScreen) { + Events &events = *_vm->_events; + People &people = *_vm->_people; + Screen &screen = *_vm->_screen; + Common::Point osPos = _savedPos; + Common::Point osSize = _savedSize; + Common::Point hPos; + + if (_cursorIndex >= 1) { + if (++_cursorIndex > (1 + 8)) + _cursorIndex = 1; + + events.setCursor((*_mapCursors)[(_cursorIndex + 1) / 2]._frame); + } + + if (!_drawMap && !flushScreen) + restoreIcon(); + else + _savedPos.x = -1; + + people[HOLMES].adjustSprite(); + + _lDrawnPos.x = hPos.x = people[HOLMES]._position.x / FIXED_INT_MULTIPLIER - _bigPos.x; + _lDrawnPos.y = hPos.y = people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES].frameHeight() - _bigPos.y; + + // Draw the person icon + saveIcon(people[HOLMES]._imageFrame, hPos); + if (people[HOLMES]._sequenceNumber == MAP_DOWNLEFT || people[HOLMES]._sequenceNumber == MAP_LEFT + || people[HOLMES]._sequenceNumber == MAP_UPLEFT) + screen._backBuffer1.transBlitFrom(*people[HOLMES]._imageFrame, hPos, true); + else + screen._backBuffer1.transBlitFrom(*people[HOLMES]._imageFrame, hPos, false); + + if (flushScreen) { + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + } else if (!_drawMap) { + if (hPos.x > 0 && hPos.y >= 0 && hPos.x < SHERLOCK_SCREEN_WIDTH && hPos.y < SHERLOCK_SCREEN_HEIGHT) + screen.flushImage(people[HOLMES]._imageFrame, Common::Point(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER - _bigPos.x, + people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES].frameHeight() - _bigPos.y), + &people[HOLMES]._oldPosition.x, &people[HOLMES]._oldPosition.y, &people[HOLMES]._oldSize.x, &people[HOLMES]._oldSize.y); + + if (osPos.x != -1) + screen.slamArea(osPos.x, osPos.y, osSize.x, osSize.y); + } +} + +void ScalpelMap::walkTheStreets() { + People &people = *_vm->_people; + Common::Array<Common::Point> tempPath; + + // Get indexes into the path lists for the start and destination scenes + int start = _points[_oldCharPoint]._translate; + int dest = _points[_charPoint]._translate; + + // Get pointer to start of path + const byte *path = _paths.getPath(start, dest); + + // Add in destination position + people[HOLMES]._walkTo.clear(); + Common::Point destPos = people[HOLMES]._walkDest; + + // Check for any intermediate points between the two locations + if (path[0] || _charPoint > 50 || _oldCharPoint > 50) { + people[HOLMES]._sequenceNumber = -1; + + if (_charPoint == 51 || _oldCharPoint == 51) { + people[HOLMES].setWalking(); + } else { + bool reversePath = false; + + // Check for moving the path backwards or forwards + if (path[0] == 255) { + reversePath = true; + SWAP(start, dest); + path = _paths.getPath(start, dest); + } + + do { + int idx = *path++; + tempPath.push_back(_pathPoints[idx - 1] + Common::Point(4, 4)); + } while (*path != 254); + + // Load up the path to use + people[HOLMES]._walkTo.clear(); + + if (reversePath) { + for (int idx = (int)tempPath.size() - 1; idx >= 0; --idx) + people[HOLMES]._walkTo.push(tempPath[idx]); + } else { + for (int idx = 0; idx < (int)tempPath.size(); ++idx) + people[HOLMES]._walkTo.push(tempPath[idx]); + } + + people[HOLMES]._walkDest = people[HOLMES]._walkTo.pop() + Common::Point(12, 6); + people[HOLMES].setWalking(); + } + } else { + people[HOLMES]._walkCount = 0; + } + + // Store the final destination icon position + people[HOLMES]._walkTo.push(destPos); +} + +void ScalpelMap::saveIcon(ImageFrame *src, const Common::Point &pt) { + Screen &screen = *_vm->_screen; + Common::Point size(src->_width, src->_height); + Common::Point pos = pt; + + if (pos.x < 0) { + size.x += pos.x; + pos.x = 0; + } + + if (pos.y < 0) { + size.y += pos.y; + pos.y = 0; + } + + if ((pos.x + size.x) > SHERLOCK_SCREEN_WIDTH) + size.x -= (pos.x + size.x) - SHERLOCK_SCREEN_WIDTH; + + if ((pos.y + size.y) > SHERLOCK_SCREEN_HEIGHT) + size.y -= (pos.y + size.y) - SHERLOCK_SCREEN_HEIGHT; + + if (size.x < 1 || size.y < 1 || pos.x >= SHERLOCK_SCREEN_WIDTH || pos.y >= SHERLOCK_SCREEN_HEIGHT || _drawMap) { + // Flag as the area not needing to be saved + _savedPos.x = -1; + return; + } + + assert(size.x <= _iconSave.w() && size.y <= _iconSave.h()); + _iconSave.blitFrom(screen._backBuffer1, Common::Point(0, 0), + Common::Rect(pos.x, pos.y, pos.x + size.x, pos.y + size.y)); + _savedPos = pos; + _savedSize = size; +} + +void ScalpelMap::restoreIcon() { + Screen &screen = *_vm->_screen; + + if (_savedPos.x >= 0 && _savedPos.y >= 0 && _savedPos.x <= SHERLOCK_SCREEN_WIDTH + && _savedPos.y < SHERLOCK_SCREEN_HEIGHT) + screen._backBuffer1.blitFrom(_iconSave, _savedPos, Common::Rect(0, 0, _savedSize.x, _savedSize.y)); +} + +void ScalpelMap::highlightIcon(const Common::Point &pt) { + int oldPoint = _point; + + // Iterate through the icon list + bool done = false; + for (int idx = 0; idx < (int)_points.size(); ++idx) { + const MapEntry &entry = _points[idx]; + + // Check whether the mouse is over a given icon + if (entry.x != 0 && entry.y != 0) { + if (Common::Rect(entry.x - 8, entry.y - 8, entry.x + 9, entry.y + 9).contains(pt)) { + done = true; + + if (_point != idx && _vm->readFlags(idx)) { + // Changed to a new valid (visible) location + eraseTopLine(); + showPlaceName(idx, true); + _point = idx; + } + } + } + } + + if (!done) { + // No icon was highlighted + if (_point != -1) { + // No longer highlighting previously highlighted icon, so erase it + showPlaceName(_point, false); + eraseTopLine(); + } + + _point = -1; + } else if (oldPoint != -1 && oldPoint != _point) { + showPlaceName(oldPoint, false); + eraseTopLine(); + } +} + +} // End of namespace Scalpel + +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/scalpel_map.h b/engines/sherlock/scalpel/scalpel_map.h new file mode 100644 index 0000000000..b17677725c --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_map.h @@ -0,0 +1,173 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_SCALPEL_MAP_H +#define SHERLOCK_SCALPEL_MAP_H + +#include "common/scummsys.h" +#include "common/array.h" +#include "common/rect.h" +#include "common/str-array.h" +#include "sherlock/surface.h" +#include "sherlock/map.h" +#include "sherlock/resources.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Scalpel { + + +struct MapEntry : Common::Point { + int _translate; + + MapEntry() : Common::Point(), _translate(-1) {} + + MapEntry(int posX, int posY, int translate) : Common::Point(posX, posY), _translate(translate) {} +}; + +class MapPaths { +private: + int _numLocations; + Common::Array< Common::Array<byte> > _paths; + +public: + MapPaths(); + + /** + * Load the data for the paths between locations on the map + */ + void load(int numLocations, Common::SeekableReadStream &s); + + /** + * Get the path between two locations on the map + */ + const byte *getPath(int srcLocation, int destLocation); +}; + +class ScalpelMap: public Map { +private: + Common::Array<MapEntry> _points; // Map locations for each scene + Common::StringArray _locationNames; + MapPaths _paths; + Common::Array<Common::Point> _pathPoints; + Common::Point _savedPos; + Common::Point _savedSize; + Surface _topLine; + ImageFile *_mapCursors; + ImageFile *_shapes; + ImageFile *_iconShapes; + WalkSequences _walkSequences; + Point32 _lDrawnPos; + int _point; + bool _placesShown; + int _cursorIndex; + bool _drawMap; + Surface _iconSave; +protected: + /** + * Load data needed for the map + */ + void loadData(); + + /** + * Load and initialize all the sprites that are needed for the map display + */ + void setupSprites(); + + /** + * Free the sprites and data used by the map + */ + void freeSprites(); + + /** + * Draws an icon for every place that's currently known + */ + void showPlaces(); + + /** + * Makes a copy of the top rows of the screen that are used to display location names + */ + void saveTopLine(); + + /** + * Erases anything shown in the top line by restoring the previously saved original map background + */ + void eraseTopLine(); + + /** + * Prints the name of the specified icon + */ + void showPlaceName(int idx, bool highlighted); + + /** + * Update all on-screen sprites to account for any scrolling of the map + */ + void updateMap(bool flushScreen); + + /** + * Handle moving icon for player from their previous location on the map to a destination location + */ + void walkTheStreets(); + + /** + * Save the area under the player's icon + */ + void saveIcon(ImageFrame *src, const Common::Point &pt); + + /** + * Restore the area under the player's icon + */ + void restoreIcon(); + + /** + * Handles highlighting map icons, showing their names + */ + void highlightIcon(const Common::Point &pt); +public: + ScalpelMap(SherlockEngine *vm); + virtual ~ScalpelMap() {} + + const MapEntry &operator[](int idx) { return _points[idx]; } + + /** + * Loads the list of points for locations on the map for each scene + */ + void loadPoints(int count, const int *xList, const int *yList, const int *transList); + + /** + * Load the sequence data for player icon animations + */ + void loadSequences(int count, const byte *seq); + + /** + * Show the map + */ + virtual int show(); +}; + +} // End of namespace Scalpel + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/scalpel/scalpel_people.cpp b/engines/sherlock/scalpel/scalpel_people.cpp new file mode 100644 index 0000000000..924095cd50 --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_people.cpp @@ -0,0 +1,567 @@ +/* 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 "sherlock/scalpel/scalpel_people.h" +#include "sherlock/scalpel/scalpel_map.h" +#include "sherlock/sherlock.h" + +namespace Sherlock { + +namespace Scalpel { + +// Walk speeds +#define MWALK_SPEED 2 +#define XWALK_SPEED 4 +#define YWALK_SPEED 1 + +/*----------------------------------------------------------------*/ + +void ScalpelPerson::adjustSprite() { + Map &map = *_vm->_map; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + Talk &talk = *_vm->_talk; + + if (_type == INVALID || (_type == CHARACTER && scene._animating)) + return; + + if (!talk._talkCounter && _type == CHARACTER && _walkCount) { + // Handle active movement for the sprite + _position += _delta; + --_walkCount; + + if (!_walkCount) { + // If there any points left for the character to walk to along the + // route to a destination, then move to the next point + if (!people[HOLMES]._walkTo.empty()) { + _walkDest = people[HOLMES]._walkTo.pop(); + setWalking(); + } else { + gotoStand(); + } + } + } + + if (_type == CHARACTER && !map._active) { + if ((_position.y / FIXED_INT_MULTIPLIER) > LOWER_LIMIT) { + _position.y = LOWER_LIMIT * FIXED_INT_MULTIPLIER; + gotoStand(); + } + + if ((_position.y / FIXED_INT_MULTIPLIER) < UPPER_LIMIT) { + _position.y = UPPER_LIMIT * FIXED_INT_MULTIPLIER; + gotoStand(); + } + + if ((_position.x / FIXED_INT_MULTIPLIER) < LEFT_LIMIT) { + _position.x = LEFT_LIMIT * FIXED_INT_MULTIPLIER; + gotoStand(); + } + + if ((_position.x / FIXED_INT_MULTIPLIER) > RIGHT_LIMIT) { + _position.x = RIGHT_LIMIT * FIXED_INT_MULTIPLIER; + gotoStand(); + } + } else if (!map._active) { + _position.y = CLIP((int)_position.y, (int)UPPER_LIMIT, (int)LOWER_LIMIT); + _position.x = CLIP((int)_position.x, (int)LEFT_LIMIT, (int)RIGHT_LIMIT); + } + + if (!map._active || (map._frameChangeFlag = !map._frameChangeFlag)) + ++_frameNumber; + + if (_frameNumber >= (int)_walkSequences[_sequenceNumber]._sequences.size() || + _walkSequences[_sequenceNumber][_frameNumber] == 0) { + switch (_sequenceNumber) { + case STOP_UP: + case STOP_DOWN: + case STOP_LEFT: + case STOP_RIGHT: + case STOP_UPRIGHT: + case STOP_UPLEFT: + case STOP_DOWNRIGHT: + case STOP_DOWNLEFT: + // We're in a stop sequence, so reset back to the last frame, so + // the character is shown as standing still + --_frameNumber; + break; + + default: + // Move 1 past the first frame - we need to compensate, since we + // already passed the frame increment + _frameNumber = 1; + break; + } + } + + // Update the _imageFrame to point to the new frame's image + setImageFrame(); + + // Check to see if character has entered an exit zone + if (!_walkCount && scene._walkedInScene && scene._goToScene == -1) { + Common::Rect charRect(_position.x / FIXED_INT_MULTIPLIER - 5, _position.y / FIXED_INT_MULTIPLIER - 2, + _position.x / FIXED_INT_MULTIPLIER + 5, _position.y / FIXED_INT_MULTIPLIER + 2); + Exit *exit = scene.checkForExit(charRect); + + if (exit) { + scene._goToScene = exit->_scene; + + if (exit->_newPosition.x != 0) { + people._savedPos = exit->_newPosition; + + if (people._savedPos._facing > 100 && people._savedPos.x < 1) + people._savedPos.x = 100; + } + } + } +} + +void ScalpelPerson::gotoStand() { + ScalpelMap &map = *(ScalpelMap *)_vm->_map; + People &people = *_vm->_people; + _walkTo.clear(); + _walkCount = 0; + + switch (_sequenceNumber) { + case Scalpel::WALK_UP: + _sequenceNumber = STOP_UP; + break; + case WALK_DOWN: + _sequenceNumber = STOP_DOWN; + break; + case TALK_LEFT: + case WALK_LEFT: + _sequenceNumber = STOP_LEFT; + break; + case TALK_RIGHT: + case WALK_RIGHT: + _sequenceNumber = STOP_RIGHT; + break; + case WALK_UPRIGHT: + _sequenceNumber = STOP_UPRIGHT; + break; + case WALK_UPLEFT: + _sequenceNumber = STOP_UPLEFT; + break; + case WALK_DOWNRIGHT: + _sequenceNumber = STOP_DOWNRIGHT; + break; + case WALK_DOWNLEFT: + _sequenceNumber = STOP_DOWNLEFT; + break; + default: + break; + } + + // Only restart frame at 0 if the sequence number has changed + if (_oldWalkSequence != -1 || _sequenceNumber == Scalpel::STOP_UP) + _frameNumber = 0; + + if (map._active) { + _sequenceNumber = 0; + people[HOLMES]._position.x = (map[map._charPoint].x - 6) * FIXED_INT_MULTIPLIER; + people[HOLMES]._position.y = (map[map._charPoint].y + 10) * FIXED_INT_MULTIPLIER; + } + + _oldWalkSequence = -1; + people._allowWalkAbort = true; +} + +void ScalpelPerson::setWalking() { + Map &map = *_vm->_map; + Scene &scene = *_vm->_scene; + int oldDirection, oldFrame; + Common::Point speed, delta; + + // Flag that player has now walked in the scene + scene._walkedInScene = true; + + // Stop any previous walking, since a new dest is being set + _walkCount = 0; + oldDirection = _sequenceNumber; + oldFrame = _frameNumber; + + // Set speed to use horizontal and vertical movement + if (map._active) { + speed = Common::Point(MWALK_SPEED, MWALK_SPEED); + } else { + speed = Common::Point(XWALK_SPEED, YWALK_SPEED); + } + + // If the player is already close to the given destination that no + // walking is needed, move to the next straight line segment in the + // overall walking route, if there is one + for (;;) { + // Since we want the player to be centered on the destination they + // clicked, but characters draw positions start at their left, move + // the destination half the character width to draw him centered + int temp; + if (_walkDest.x >= (temp = _imageFrame->_frame.w / 2)) + _walkDest.x -= temp; + + delta = Common::Point( + ABS(_position.x / FIXED_INT_MULTIPLIER - _walkDest.x), + ABS(_position.y / FIXED_INT_MULTIPLIER - _walkDest.y) + ); + + // If we're ready to move a sufficient distance, that's it. Otherwise, + // move onto the next portion of the walk path, if there is one + if ((delta.x > 3 || delta.y > 0) || _walkTo.empty()) + break; + + // Pop next walk segment off the walk route stack + _walkDest = _walkTo.pop(); + } + + // If a sufficient move is being done, then start the move + if (delta.x > 3 || delta.y) { + // See whether the major movement is horizontal or vertical + if (delta.x >= delta.y) { + // Set the initial frame sequence for the left and right, as well + // as setting the delta x depending on direction + if (_walkDest.x < (_position.x / FIXED_INT_MULTIPLIER)) { + _sequenceNumber = (map._active ? (int)MAP_LEFT : (int)WALK_LEFT); + _delta.x = speed.x * -FIXED_INT_MULTIPLIER; + } else { + _sequenceNumber = (map._active ? (int)MAP_RIGHT : (int)WALK_RIGHT); + _delta.x = speed.x * FIXED_INT_MULTIPLIER; + } + + // See if the x delta is too small to be divided by the speed, since + // this would cause a divide by zero error + if (delta.x >= speed.x) { + // Det the delta y + _delta.y = (delta.y * FIXED_INT_MULTIPLIER) / (delta.x / speed.x); + if (_walkDest.y < (_position.y / FIXED_INT_MULTIPLIER)) + _delta.y = -_delta.y; + + // Set how many times we should add the delta to the player's position + _walkCount = delta.x / speed.x; + } else { + // The delta x was less than the speed (ie. we're really close to + // the destination). So set delta to 0 so the player won't move + _delta = Point32(0, 0); + _position = Point32(_walkDest.x * FIXED_INT_MULTIPLIER, _walkDest.y * FIXED_INT_MULTIPLIER); + + _walkCount = 1; + } + + // See if the sequence needs to be changed for diagonal walking + if (_delta.y > 150) { + if (!map._active) { + switch (_sequenceNumber) { + case WALK_LEFT: + _sequenceNumber = WALK_DOWNLEFT; + break; + case WALK_RIGHT: + _sequenceNumber = WALK_DOWNRIGHT; + break; + } + } + } else if (_delta.y < -150) { + if (!map._active) { + switch (_sequenceNumber) { + case WALK_LEFT: + _sequenceNumber = WALK_UPLEFT; + break; + case WALK_RIGHT: + _sequenceNumber = WALK_UPRIGHT; + break; + } + } + } + } else { + // Major movement is vertical, so set the sequence for up and down, + // and set the delta Y depending on the direction + if (_walkDest.y < (_position.y / FIXED_INT_MULTIPLIER)) { + _sequenceNumber = WALK_UP; + _delta.y = speed.y * -FIXED_INT_MULTIPLIER; + } else { + _sequenceNumber = WALK_DOWN; + _delta.y = speed.y * FIXED_INT_MULTIPLIER; + } + + // If we're on the overhead map, set the sequence so we keep moving + // in the same direction + if (map._active) + _sequenceNumber = (oldDirection == -1) ? MAP_RIGHT : oldDirection; + + // Set the delta x + _delta.x = (delta.x * FIXED_INT_MULTIPLIER) / (delta.y / speed.y); + if (_walkDest.x < (_position.x / FIXED_INT_MULTIPLIER)) + _delta.x = -_delta.x; + + _walkCount = delta.y / speed.y; + } + } + + // See if the new walk sequence is the same as the old. If it's a new one, + // we need to reset the frame number to zero so its animation starts at + // its beginning. Otherwise, if it's the same sequence, we can leave it + // as is, so it keeps the animation going at wherever it was up to + if (_sequenceNumber != _oldWalkSequence) + _frameNumber = 0; + _oldWalkSequence = _sequenceNumber; + + if (!_walkCount) + gotoStand(); + + // If the sequence is the same as when we started, then Holmes was + // standing still and we're trying to re-stand him, so reset Holmes' + // rame to the old frame number from before it was reset to 0 + if (_sequenceNumber == oldDirection) + _frameNumber = oldFrame; +} + +void ScalpelPerson::walkToCoords(const Point32 &destPos, int destDir) { + Events &events = *_vm->_events; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + Talk &talk = *_vm->_talk; + + CursorId oldCursor = events.getCursor(); + events.setCursor(WAIT); + + _walkDest = Common::Point(destPos.x / FIXED_INT_MULTIPLIER + 10, destPos.y / FIXED_INT_MULTIPLIER); + people._allowWalkAbort = true; + goAllTheWay(); + + // Keep calling doBgAnim until the walk is done + do { + events.pollEventsAndWait(); + scene.doBgAnim(); + } while (!_vm->shouldQuit() && _walkCount); + + if (!talk._talkToAbort) { + // Put character exactly on destination position, and set direction + _position = destPos; + _sequenceNumber = destDir; + gotoStand(); + + // Draw Holmes facing the new direction + scene.doBgAnim(); + + if (!talk._talkToAbort) + events.setCursor(oldCursor); + } +} + +Common::Point ScalpelPerson::getSourcePoint() const { + return Common::Point(_position.x / FIXED_INT_MULTIPLIER + frameWidth() / 2, + _position.y / FIXED_INT_MULTIPLIER); +} + +void ScalpelPerson::synchronize(Serializer &s) { + if (_walkCount) + gotoStand(); + + s.syncAsSint32LE(_position.x); + s.syncAsSint32LE(_position.y); +} + +/*----------------------------------------------------------------*/ + +ScalpelPeople::ScalpelPeople(SherlockEngine *vm) : People(vm) { + _data.push_back(new ScalpelPerson()); +} + +void ScalpelPeople::setTalking(int speaker) { + Resources &res = *_vm->_res; + + // If no speaker is specified, then we can exit immediately + if (speaker == -1) + return; + + if (_portraitsOn) { + delete _talkPics; + Common::String filename = Common::String::format("%s.vgs", _characters[speaker]._portrait); + _talkPics = new ImageFile(filename); + + // Load portrait sequences + Common::SeekableReadStream *stream = res.load("sequence.txt"); + stream->seek(speaker * MAX_FRAME); + + int idx = 0; + do { + _portrait._sequences[idx] = stream->readByte(); + ++idx; + } while (idx < 2 || _portrait._sequences[idx - 2] || _portrait._sequences[idx - 1]); + + delete stream; + + _portrait._maxFrames = idx; + _portrait._frameNumber = 0; + _portrait._sequenceNumber = 0; + _portrait._images = _talkPics; + _portrait._imageFrame = &(*_talkPics)[0]; + _portrait._position = Common::Point(_portraitSide, 10); + _portrait._delta = Common::Point(0, 0); + _portrait._oldPosition = Common::Point(0, 0); + _portrait._goto = Common::Point(0, 0); + _portrait._flags = 5; + _portrait._status = 0; + _portrait._misc = 0; + _portrait._allow = 0; + _portrait._type = ACTIVE_BG_SHAPE; + _portrait._name = " "; + _portrait._description = " "; + _portrait._examine = " "; + _portrait._walkCount = 0; + + if (_holmesFlip || _speakerFlip) { + _portrait._flags |= 2; + + _holmesFlip = false; + _speakerFlip = false; + } + + if (_portraitSide == 20) + _portraitSide = 220; + else + _portraitSide = 20; + + _portraitLoaded = true; + } +} + +void ScalpelPeople::synchronize(Serializer &s) { + (*this)[HOLMES].synchronize(s); + s.syncAsSint16LE(_holmesQuotient); + s.syncAsByte(_holmesOn); + + if (s.isLoading()) { + _savedPos = _data[HOLMES]->_position; + _savedPos._facing = _data[HOLMES]->_sequenceNumber; + } +} + +void ScalpelPeople::setTalkSequence(int speaker, int sequenceNum) { + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + + // If no speaker is specified, then nothing needs to be done + if (speaker == -1) + return; + + if (speaker) { + int objNum = people.findSpeaker(speaker); + if (objNum != -1) { + Object &obj = scene._bgShapes[objNum]; + + if (obj._seqSize < MAX_TALK_SEQUENCES) { + warning("Tried to copy too many talk frames"); + } else { + for (int idx = 0; idx < MAX_TALK_SEQUENCES; ++idx) { + obj._sequences[idx] = people._characters[speaker]._talkSequences[idx]; + if (idx > 0 && !obj._sequences[idx] && !obj._sequences[idx - 1]) + return; + + obj._frameNumber = 0; + obj._sequenceNumber = 0; + } + } + } + } +} + +bool ScalpelPeople::loadWalk() { + bool result = false; + + if (_data[HOLMES]->_walkLoaded) { + return false; + } else { + if (!IS_3DO) { + _data[HOLMES]->_images = new ImageFile("walk.vgs"); + } else { + // Load walk.anim on 3DO, which is a cel animation file + _data[HOLMES]->_images = new ImageFile3DO("walk.anim", kImageFile3DOType_CelAnimation); + } + _data[HOLMES]->setImageFrame(); + _data[HOLMES]->_walkLoaded = true; + + result = true; + } + + _forceWalkReload = false; + return result; +} + +const Common::Point ScalpelPeople::restrictToZone(int zoneId, const Common::Point &destPos) { + Scene &scene = *_vm->_scene; + Common::Point walkDest = destPos; + + // The destination isn't in a zone + if (walkDest.x >= (SHERLOCK_SCREEN_WIDTH - 1)) + walkDest.x = SHERLOCK_SCREEN_WIDTH - 2; + + // Trace a line between the centroid of the found closest zone to + // the destination, to find the point at which the zone will be left + const Common::Rect &destRect = scene._zones[zoneId]; + const Common::Point destCenter((destRect.left + destRect.right) / 2, + (destRect.top + destRect.bottom) / 2); + const Common::Point delta = walkDest - destCenter; + Point32 pt(destCenter.x * FIXED_INT_MULTIPLIER, destCenter.y * FIXED_INT_MULTIPLIER); + + // Move along the line until the zone is left + do { + pt += delta; + } while (destRect.contains(pt.x / FIXED_INT_MULTIPLIER, pt.y / FIXED_INT_MULTIPLIER)); + + // Set the new walk destination to the last point that was in the + // zone just before it was left + return Common::Point((pt.x - delta.x * 2) / FIXED_INT_MULTIPLIER, + (pt.y - delta.y * 2) / FIXED_INT_MULTIPLIER); +} + +void ScalpelPeople::setListenSequence(int speaker, int sequenceNum) { + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + + // Don't bother doing anything if no specific speaker is specified + if (speaker == -1) + return; + + if (speaker) { + int objNum = people.findSpeaker(speaker); + if (objNum != -1) { + Object &obj = scene._bgShapes[objNum]; + + if (obj._seqSize < MAX_TALK_SEQUENCES) { + warning("Tried to copy too few still frames"); + } else { + for (uint idx = 0; idx < MAX_TALK_SEQUENCES; ++idx) { + obj._sequences[idx] = people._characters[speaker]._stillSequences[idx]; + if (idx > 0 && !people._characters[speaker]._talkSequences[idx] && + !people._characters[speaker]._talkSequences[idx - 1]) + break; + } + + obj._frameNumber = 0; + obj._seqTo = 0; + } + } + } +} + +} // End of namespace Scalpel + +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/scalpel_people.h b/engines/sherlock/scalpel/scalpel_people.h new file mode 100644 index 0000000000..2ab6f5bc7d --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_people.h @@ -0,0 +1,130 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_SCALPEL_PEOPLE_H +#define SHERLOCK_SCALPEL_PEOPLE_H + +#include "common/scummsys.h" +#include "sherlock/people.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Scalpel { + +// Animation sequence identifiers for characters +enum ScalpelSequences { + WALK_RIGHT = 0, WALK_DOWN = 1, WALK_LEFT = 2, WALK_UP = 3, STOP_LEFT = 4, + STOP_DOWN = 5, STOP_RIGHT = 6, STOP_UP = 7, WALK_UPRIGHT = 8, + WALK_DOWNRIGHT = 9, WALK_UPLEFT = 10, WALK_DOWNLEFT = 11, + STOP_UPRIGHT = 12, STOP_UPLEFT = 13, STOP_DOWNRIGHT = 14, + STOP_DOWNLEFT = 15, TALK_RIGHT = 6, TALK_LEFT = 4 +}; + +class ScalpelPerson : public Person { +public: + ScalpelPerson() : Person() {} + virtual ~ScalpelPerson() {} + + /** + * Synchronize the data for a savegame + */ + virtual void synchronize(Serializer &s); + + /** + * This adjusts the sprites position, as well as its animation sequence: + */ + virtual void adjustSprite(); + + /** + * Bring a moving character to a standing position + */ + virtual void gotoStand(); + + /** + * Set the variables for moving a character from one poisition to another + * in a straight line + */ + virtual void setWalking(); + + /** + * Walk to the co-ordinates passed, and then face the given direction + */ + virtual void walkToCoords(const Point32 &destPos, int destDir); + + /** + * Get the source position for a character potentially affected by scaling + */ + virtual Common::Point getSourcePoint() const; +}; + +class ScalpelPeople : public People { +public: + ScalpelPeople(SherlockEngine *vm); + virtual ~ScalpelPeople() {} + + ScalpelPerson &operator[](PeopleId id) { return *(ScalpelPerson *)_data[id]; } + ScalpelPerson &operator[](int idx) { return *(ScalpelPerson *)_data[idx]; } + + /** + * Setup the data for an animating speaker portrait at the top of the screen + */ + void setTalking(int speaker); + + /** + * Synchronize the data for a savegame + */ + virtual void synchronize(Serializer &s); + + /** + * Change the sequence of the scene background object associated with the specified speaker. + */ + virtual void setTalkSequence(int speaker, int sequenceNum = 1); + + /** + * Restrict passed point to zone using Sherlock's positioning rules + */ + virtual const Common::Point restrictToZone(int zoneId, const Common::Point &destPos); + + /** + * Load the walking images for Sherlock + */ + virtual bool loadWalk(); + + /** + * If the specified speaker is a background object, it will set it so that it uses + * the Listen Sequence (specified by the sequence number). If the current sequence + * has an Allow Talk Code in it, the _gotoSeq field will be set so that the object + * begins listening as soon as it hits the Allow Talk Code. If there is no Abort Code, + * the Listen Sequence will begin immediately. + * @param speaker Who is speaking + * @param sequenceNum Which listen sequence to use + */ + virtual void setListenSequence(int speaker, int sequenceNum = 1); +}; + +} // End of namespace Scalpel + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/scalpel/scalpel_saveload.cpp b/engines/sherlock/scalpel/scalpel_saveload.cpp new file mode 100644 index 0000000000..01ba149813 --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_saveload.cpp @@ -0,0 +1,261 @@ +/* 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 "sherlock/scalpel/scalpel_saveload.h" +#include "sherlock/scalpel/scalpel_screen.h" +#include "sherlock/scalpel/scalpel.h" + +namespace Sherlock { + +namespace Scalpel { + +const int ENV_POINTS[6][3] = { + { 41, 80, 61 }, // Exit + { 81, 120, 101 }, // Load + { 121, 160, 141 }, // Save + { 161, 200, 181 }, // Up + { 201, 240, 221 }, // Down + { 241, 280, 261 } // Quit +}; + +/*----------------------------------------------------------------*/ + +ScalpelSaveManager::ScalpelSaveManager(SherlockEngine *vm, const Common::String &target) : SaveManager(vm, target) { +} + +void ScalpelSaveManager::drawInterface() { + ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen; + UserInterface &ui = *_vm->_ui; + + // Create a list of savegame slots + createSavegameList(); + + screen._backBuffer1.fillRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y + 10), BORDER_COLOR); + screen._backBuffer1.fillRect(Common::Rect(0, CONTROLS_Y + 10, 2, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); + screen._backBuffer1.fillRect(Common::Rect(318, CONTROLS_Y + 10, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); + screen._backBuffer1.fillRect(Common::Rect(0, 199, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); + screen._backBuffer1.fillRect(Common::Rect(2, CONTROLS_Y + 10, SHERLOCK_SCREEN_WIDTH - 2, SHERLOCK_SCREEN_HEIGHT - 2), INV_BACKGROUND); + + screen.makeButton(Common::Rect(ENV_POINTS[0][0], CONTROLS_Y, ENV_POINTS[0][1], CONTROLS_Y + 10), + ENV_POINTS[0][2] - screen.stringWidth("Exit") / 2, "Exit"); + screen.makeButton(Common::Rect(ENV_POINTS[1][0], CONTROLS_Y, ENV_POINTS[1][1], CONTROLS_Y + 10), + ENV_POINTS[1][2] - screen.stringWidth("Load") / 2, "Load"); + screen.makeButton(Common::Rect(ENV_POINTS[2][0], CONTROLS_Y, ENV_POINTS[2][1], CONTROLS_Y + 10), + ENV_POINTS[2][2] - screen.stringWidth("Save") / 2, "Save"); + screen.makeButton(Common::Rect(ENV_POINTS[3][0], CONTROLS_Y, ENV_POINTS[3][1], CONTROLS_Y + 10), + ENV_POINTS[3][2] - screen.stringWidth("Up") / 2, "Up"); + screen.makeButton(Common::Rect(ENV_POINTS[4][0], CONTROLS_Y, ENV_POINTS[4][1], CONTROLS_Y + 10), + ENV_POINTS[4][2] - screen.stringWidth("Down") / 2, "Down"); + screen.makeButton(Common::Rect(ENV_POINTS[5][0], CONTROLS_Y, ENV_POINTS[5][1], CONTROLS_Y + 10), + ENV_POINTS[5][2] - screen.stringWidth("Quit") / 2, "Quit"); + + if (!_savegameIndex) + screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_NULL, 0, "Up"); + + if (_savegameIndex == MAX_SAVEGAME_SLOTS - ONSCREEN_FILES_COUNT) + screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_NULL, 0, "Down"); + + for (int idx = _savegameIndex; idx < _savegameIndex + ONSCREEN_FILES_COUNT; ++idx) { + screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10), + INV_FOREGROUND, "%d.", idx + 1); + screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10), + INV_FOREGROUND, "%s", _savegames[idx].c_str()); + } + + if (!ui._slideWindows) { + screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + } else { + ui.summonWindow(); + } + + _envMode = SAVEMODE_NONE; +} + +int ScalpelSaveManager::getHighlightedButton() const { + Common::Point pt = _vm->_events->mousePos(); + + for (int idx = 0; idx < 6; ++idx) { + if (pt.x > ENV_POINTS[idx][0] && pt.x < ENV_POINTS[idx][1] && pt.y > CONTROLS_Y + && pt.y < (CONTROLS_Y + 10)) + return idx; + } + + return -1; +} + +void ScalpelSaveManager::highlightButtons(int btnIndex) { + ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen; + byte color = (btnIndex == 0) ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND; + + screen.buttonPrint(Common::Point(ENV_POINTS[0][2], CONTROLS_Y), color, 1, "Exit"); + + if ((btnIndex == 1) || ((_envMode == SAVEMODE_LOAD) && (btnIndex != 2))) + screen.buttonPrint(Common::Point(ENV_POINTS[1][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Load"); + else + screen.buttonPrint(Common::Point(ENV_POINTS[1][2], CONTROLS_Y), COMMAND_FOREGROUND, true, "Load"); + + if ((btnIndex == 2) || ((_envMode == SAVEMODE_SAVE) && (btnIndex != 1))) + screen.buttonPrint(Common::Point(ENV_POINTS[2][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Save"); + else + screen.buttonPrint(Common::Point(ENV_POINTS[2][2], CONTROLS_Y), COMMAND_FOREGROUND, true, "Save"); + + if (btnIndex == 3 && _savegameIndex) + screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Up"); + else + if (_savegameIndex) + screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_FOREGROUND, true, "Up"); + + if ((btnIndex == 4) && (_savegameIndex < MAX_SAVEGAME_SLOTS - 5)) + screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Down"); + else if (_savegameIndex < (MAX_SAVEGAME_SLOTS - 5)) + screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_FOREGROUND, true, "Down"); + + color = (btnIndex == 5) ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND; + screen.buttonPrint(Common::Point(ENV_POINTS[5][2], CONTROLS_Y), color, 1, "Quit"); +} + +bool ScalpelSaveManager::checkGameOnScreen(int slot) { + ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen; + + // Check if it's already on-screen + if (slot != -1 && (slot < _savegameIndex || slot >= (_savegameIndex + ONSCREEN_FILES_COUNT))) { + _savegameIndex = slot; + + screen._backBuffer1.fillRect(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2, + SHERLOCK_SCREEN_HEIGHT - 1), INV_BACKGROUND); + + for (int idx = _savegameIndex; idx < (_savegameIndex + 5); ++idx) { + screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10), + INV_FOREGROUND, "%d.", idx + 1); + screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (idx - _savegameIndex) * 10), + INV_FOREGROUND, "%s", _savegames[idx].c_str()); + } + + screen.slamRect(Common::Rect(3, CONTROLS_Y + 11, 318, SHERLOCK_SCREEN_HEIGHT)); + + byte color = !_savegameIndex ? COMMAND_NULL : COMMAND_FOREGROUND; + screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), color, 1, "Up"); + + color = (_savegameIndex == (MAX_SAVEGAME_SLOTS - 5)) ? COMMAND_NULL : COMMAND_FOREGROUND; + screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), color, 1, "Down"); + + return true; + } + + return false; +} + +bool ScalpelSaveManager::promptForDescription(int slot) { + Events &events = *_vm->_events; + Scene &scene = *_vm->_scene; + ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen; + Talk &talk = *_vm->_talk; + int xp, yp; + bool flag = false; + + screen.buttonPrint(Common::Point(ENV_POINTS[0][2], CONTROLS_Y), COMMAND_NULL, true, "Exit"); + screen.buttonPrint(Common::Point(ENV_POINTS[1][2], CONTROLS_Y), COMMAND_NULL, true, "Load"); + screen.buttonPrint(Common::Point(ENV_POINTS[2][2], CONTROLS_Y), COMMAND_NULL, true, "Save"); + screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), COMMAND_NULL, true, "Up"); + screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), COMMAND_NULL, true, "Down"); + screen.buttonPrint(Common::Point(ENV_POINTS[5][2], CONTROLS_Y), COMMAND_NULL, true, "Quit"); + + Common::String saveName = _savegames[slot]; + if (isSlotEmpty(slot)) { + // It's an empty slot, so start off with an empty save name + saveName = ""; + + yp = CONTROLS_Y + 12 + (slot - _savegameIndex) * 10; + screen.vgaBar(Common::Rect(24, yp, 85, yp + 9), INV_BACKGROUND); + } + + screen.print(Common::Point(6, CONTROLS_Y + 12 + (slot - _savegameIndex) * 10), TALK_FOREGROUND, "%d.", slot + 1); + screen.print(Common::Point(24, CONTROLS_Y + 12 + (slot - _savegameIndex) * 10), TALK_FOREGROUND, "%s", saveName.c_str()); + xp = 24 + screen.stringWidth(saveName); + yp = CONTROLS_Y + 12 + (slot - _savegameIndex) * 10; + + int done = 0; + do { + while (!_vm->shouldQuit() && !events.kbHit()) { + scene.doBgAnim(); + + if (talk._talkToAbort) + return false; + + // Allow event processing + events.pollEventsAndWait(); + events.setButtonState(); + + flag = !flag; + if (flag) + screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_FOREGROUND); + else + screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_BACKGROUND); + } + if (_vm->shouldQuit()) + return false; + + // Get the next keypress + Common::KeyState keyState = events.getKey(); + + if (keyState.keycode == Common::KEYCODE_BACKSPACE && saveName.size() > 0) { + // Delete character of save name + screen.vgaBar(Common::Rect(xp - screen.charWidth(saveName.lastChar()), yp - 1, + xp + 8, yp + 9), INV_BACKGROUND); + xp -= screen.charWidth(saveName.lastChar()); + screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_FOREGROUND); + saveName.deleteLastChar(); + + } else if (keyState.keycode == Common::KEYCODE_RETURN && saveName.compareToIgnoreCase(EMPTY_SAVEGAME_SLOT)) { + done = 1; + + } else if (keyState.keycode == Common::KEYCODE_ESCAPE) { + screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_BACKGROUND); + done = -1; + + } else if (keyState.ascii >= ' ' && keyState.ascii <= 'z' && saveName.size() < 50 + && (xp + screen.charWidth(keyState.ascii)) < 308) { + char c = (char)keyState.ascii; + + screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_BACKGROUND); + screen.print(Common::Point(xp, yp), TALK_FOREGROUND, "%c", c); + xp += screen.charWidth(c); + screen.vgaBar(Common::Rect(xp, yp - 1, xp + 8, yp + 9), INV_FOREGROUND); + saveName += c; + } + } while (!done); + + if (done == 1) { + // Enter key perssed + _savegames[slot] = saveName; + } else { + done = 0; + _envMode = SAVEMODE_NONE; + highlightButtons(-1); + } + + return done == 1; +} + +} // End of namespace Scalpel + +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/scalpel_saveload.h b/engines/sherlock/scalpel/scalpel_saveload.h new file mode 100644 index 0000000000..6b035cace3 --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_saveload.h @@ -0,0 +1,71 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_SCALPEL_SAVELOAD_H +#define SHERLOCK_SCALPEL_SAVELOAD_H + +#include "sherlock/saveload.h" + +namespace Sherlock { + +namespace Scalpel { + +extern const int ENV_POINTS[6][3]; + +class ScalpelSaveManager: public SaveManager { +public: + SaveMode _envMode; +public: + ScalpelSaveManager(SherlockEngine *vm, const Common::String &target); + virtual ~ScalpelSaveManager() {} + + /** + * Shows the in-game dialog interface for loading and saving games + */ + void drawInterface(); + + /** + * Return the index of the button the mouse is over, if any + */ + int getHighlightedButton() const; + + /** + * Handle highlighting buttons + */ + void highlightButtons(int btnIndex); + + /** + * Make sure that the selected savegame is on-screen + */ + bool checkGameOnScreen(int slot); + + /** + * Prompts the user to enter a description in a given slot + */ + bool promptForDescription(int slot); +}; + +} // End of namespace Scalpel + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/scalpel/scalpel_scene.cpp b/engines/sherlock/scalpel/scalpel_scene.cpp index b6a42419d8..b2c7339363 100644 --- a/engines/sherlock/scalpel/scalpel_scene.cpp +++ b/engines/sherlock/scalpel/scalpel_scene.cpp @@ -21,18 +21,130 @@ */ #include "sherlock/scalpel/scalpel_scene.h" +#include "sherlock/scalpel/scalpel_map.h" +#include "sherlock/scalpel/scalpel_people.h" +#include "sherlock/scalpel/scalpel_user_interface.h" #include "sherlock/scalpel/scalpel.h" #include "sherlock/events.h" #include "sherlock/people.h" #include "sherlock/screen.h" +#include "sherlock/sherlock.h" namespace Sherlock { namespace Scalpel { +const int FS_TRANS[8] = { + STOP_UP, STOP_UPRIGHT, STOP_RIGHT, STOP_DOWNRIGHT, STOP_DOWN, STOP_DOWNLEFT, STOP_LEFT, STOP_UPLEFT +}; + +/*----------------------------------------------------------------*/ + +bool ScalpelScene::loadScene(const Common::String &filename) { + ScalpelMap &map = *(ScalpelMap *)_vm->_map; + bool result = Scene::loadScene(filename); + + if (!_vm->isDemo()) { + // Reset the previous map location and position on overhead map + map._oldCharPoint = _currentScene; + + map._overPos.x = (map[_currentScene].x - 6) * FIXED_INT_MULTIPLIER; + map._overPos.y = (map[_currentScene].y + 9) * FIXED_INT_MULTIPLIER; + + } + + return result; +} + +void ScalpelScene::drawAllShapes() { + People &people = *_vm->_people; + Screen &screen = *_vm->_screen; + + // Restrict drawing window + screen.setDisplayBounds(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT)); + + // Draw all active shapes which are behind the person + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE && _bgShapes[idx]._misc == BEHIND) + screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, _bgShapes[idx]._flags & OBJ_FLIPPED); + } + + // Draw all canimations which are behind the person + for (uint idx = 0; idx < _canimShapes.size(); ++idx) { + if (_canimShapes[idx]._type == ACTIVE_BG_SHAPE && _canimShapes[idx]._misc == BEHIND) + screen._backBuffer->transBlitFrom(*_canimShapes[idx]._imageFrame, + _canimShapes[idx]._position, _canimShapes[idx]._flags & OBJ_FLIPPED); + } + + // Draw all active shapes which are normal and behind the person + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE && _bgShapes[idx]._misc == NORMAL_BEHIND) + screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, _bgShapes[idx]._flags & OBJ_FLIPPED); + } + + // Draw all canimations which are normal and behind the person + for (uint idx = 0; idx < _canimShapes.size(); ++idx) { + if (_canimShapes[idx]._type == ACTIVE_BG_SHAPE && _canimShapes[idx]._misc == NORMAL_BEHIND) + screen._backBuffer->transBlitFrom(*_canimShapes[idx]._imageFrame, _canimShapes[idx]._position, + _canimShapes[idx]._flags & OBJ_FLIPPED); + } + + // Draw any active characters + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { + Person &p = people[idx]; + if (p._type == CHARACTER && p._walkLoaded) { + bool flipped = IS_SERRATED_SCALPEL && ( + p._sequenceNumber == WALK_LEFT || p._sequenceNumber == STOP_LEFT || + p._sequenceNumber == WALK_UPLEFT || p._sequenceNumber == STOP_UPLEFT || + p._sequenceNumber == WALK_DOWNRIGHT || p._sequenceNumber == STOP_DOWNRIGHT); + + screen._backBuffer->transBlitFrom(*p._imageFrame, Common::Point(p._position.x / FIXED_INT_MULTIPLIER, + p._position.y / FIXED_INT_MULTIPLIER - p.frameHeight()), flipped); + } + } + + // Draw all static and active shapes that are NORMAL and are in front of the player + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + if ((_bgShapes[idx]._type == ACTIVE_BG_SHAPE || _bgShapes[idx]._type == STATIC_BG_SHAPE) && + _bgShapes[idx]._misc == NORMAL_FORWARD) + screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, + _bgShapes[idx]._flags & OBJ_FLIPPED); + } + + // Draw all static and active canimations that are NORMAL and are in front of the player + for (uint idx = 0; idx < _canimShapes.size(); ++idx) { + if ((_canimShapes[idx]._type == ACTIVE_BG_SHAPE || _canimShapes[idx]._type == STATIC_BG_SHAPE) && + _canimShapes[idx]._misc == NORMAL_FORWARD) + screen._backBuffer->transBlitFrom(*_canimShapes[idx]._imageFrame, _canimShapes[idx]._position, + _canimShapes[idx]._flags & OBJ_FLIPPED); + } + + // Draw all static and active shapes that are FORWARD + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + _bgShapes[idx]._oldPosition = _bgShapes[idx]._position; + _bgShapes[idx]._oldSize = Common::Point(_bgShapes[idx].frameWidth(), + _bgShapes[idx].frameHeight()); + + if ((_bgShapes[idx]._type == ACTIVE_BG_SHAPE || _bgShapes[idx]._type == STATIC_BG_SHAPE) && + _bgShapes[idx]._misc == FORWARD) + screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, + _bgShapes[idx]._flags & OBJ_FLIPPED); + } + + // Draw all static and active canimations that are forward + for (uint idx = 0; idx < _canimShapes.size(); ++idx) { + if ((_canimShapes[idx]._type == ACTIVE_BG_SHAPE || _canimShapes[idx]._type == STATIC_BG_SHAPE) && + _canimShapes[idx]._misc == FORWARD) + screen._backBuffer->transBlitFrom(*_canimShapes[idx]._imageFrame, _canimShapes[idx]._position, + _canimShapes[idx]._flags & OBJ_FLIPPED); + } + + screen.resetDisplayBounds(); +} + void ScalpelScene::checkBgShapes() { People &people = *_vm->_people; - Person &holmes = people._player; + Person &holmes = people[HOLMES]; Common::Point pt(holmes._position.x / FIXED_INT_MULTIPLIER, holmes._position.y / FIXED_INT_MULTIPLIER); // Call the base scene method to handle bg shapes @@ -57,9 +169,9 @@ void ScalpelScene::checkBgShapes() { void ScalpelScene::doBgAnimCheckCursor() { Inventory &inv = *_vm->_inventory; Events &events = *_vm->_events; - Sound &sound = *_vm->_sound; UserInterface &ui = *_vm->_ui; Common::Point mousePos = events.mousePos(); + events.animateCursorIfNeeded(); if (ui._menuMode == LOOK_MODE) { if (mousePos.y > CONTROLS_Y1) @@ -80,11 +192,6 @@ void ScalpelScene::doBgAnimCheckCursor() { events.setCursor(ARROW); } } - - if (sound._diskSoundPlaying && !*sound._soundIsOn) { - // Loaded sound just finished playing - sound.freeDigiSound(); - } } void ScalpelScene::doBgAnim() { @@ -94,6 +201,8 @@ void ScalpelScene::doBgAnim() { Screen &screen = *_vm->_screen; Talk &talk = *_vm->_talk; + doBgAnimCheckCursor(); + screen.setDisplayBounds(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT)); talk._talkToAbort = false; @@ -116,18 +225,18 @@ void ScalpelScene::doBgAnim() { _canimShapes[idx].checkObject(); } - if (_currentScene == 12) - vm.eraseMirror12(); + if (_currentScene == DRAWING_ROOM) + vm.eraseBrumwellMirror(); // Restore the back buffer from the back buffer 2 in the changed area - Common::Rect bounds(people[AL]._oldPosition.x, people[AL]._oldPosition.y, - people[AL]._oldPosition.x + people[AL]._oldSize.x, - people[AL]._oldPosition.y + people[AL]._oldSize.y); + Common::Rect bounds(people[HOLMES]._oldPosition.x, people[HOLMES]._oldPosition.y, + people[HOLMES]._oldPosition.x + people[HOLMES]._oldSize.x, + people[HOLMES]._oldPosition.y + people[HOLMES]._oldSize.y); Common::Point pt(bounds.left, bounds.top); - if (people[AL]._type == CHARACTER) + if (people[HOLMES]._type == CHARACTER) screen.restoreBackground(bounds); - else if (people[AL]._type == REMOVE) + else if (people[HOLMES]._type == REMOVE) screen._backBuffer->blitFrom(screen._backBuffer2, pt, bounds); for (uint idx = 0; idx < _bgShapes.size(); ++idx) { @@ -182,14 +291,14 @@ void ScalpelScene::doBgAnim() { _canimShapes[idx].adjustObject(); } - if (people[AL]._type == CHARACTER && people._holmesOn) - people[AL].adjustSprite(); + if (people[HOLMES]._type == CHARACTER && people._holmesOn) + people[HOLMES].adjustSprite(); // Flag the bg shapes which need to be redrawn checkBgShapes(); - if (_currentScene == 12) - vm.doMirror12(); + if (_currentScene == DRAWING_ROOM) + vm.doBrumwellMirror(); // Draw all active shapes which are behind the person for (uint idx = 0; idx < _bgShapes.size(); ++idx) { @@ -222,16 +331,16 @@ void ScalpelScene::doBgAnim() { } // Draw the person if not animating - if (people[AL]._type == CHARACTER && people[AL]._walkLoaded) { + if (people[HOLMES]._type == CHARACTER && people[HOLMES]._walkLoaded) { // If Holmes is too far to the right, move him back so he's on-screen - int xRight = SHERLOCK_SCREEN_WIDTH - 2 - people[AL]._imageFrame->_frame.w; - int tempX = MIN(people[AL]._position.x / FIXED_INT_MULTIPLIER, xRight); - - bool flipped = people[AL]._sequenceNumber == WALK_LEFT || people[AL]._sequenceNumber == STOP_LEFT || - people[AL]._sequenceNumber == WALK_UPLEFT || people[AL]._sequenceNumber == STOP_UPLEFT || - people[AL]._sequenceNumber == WALK_DOWNRIGHT || people[AL]._sequenceNumber == STOP_DOWNRIGHT; - screen._backBuffer->transBlitFrom(*people[AL]._imageFrame, - Common::Point(tempX, people[AL]._position.y / FIXED_INT_MULTIPLIER - people[AL]._imageFrame->_frame.h), flipped); + int xRight = SHERLOCK_SCREEN_WIDTH - 2 - people[HOLMES]._imageFrame->_frame.w; + int tempX = MIN(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER, xRight); + + bool flipped = people[HOLMES]._sequenceNumber == WALK_LEFT || people[HOLMES]._sequenceNumber == STOP_LEFT || + people[HOLMES]._sequenceNumber == WALK_UPLEFT || people[HOLMES]._sequenceNumber == STOP_UPLEFT || + people[HOLMES]._sequenceNumber == WALK_DOWNRIGHT || people[HOLMES]._sequenceNumber == STOP_DOWNRIGHT; + screen._backBuffer->transBlitFrom(*people[HOLMES]._imageFrame, + Common::Point(tempX, people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES]._imageFrame->_frame.h), flipped); } // Draw all static and active shapes are NORMAL and are in front of the person @@ -281,25 +390,25 @@ void ScalpelScene::doBgAnim() { _animating = 0; screen.slamRect(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT)); } else { - if (people[AL]._type != INVALID && ((_goToScene == -1 || _canimShapes.empty()))) { - if (people[AL]._type == REMOVE) { + if (people[HOLMES]._type != INVALID && ((_goToScene == -1 || _canimShapes.empty()))) { + if (people[HOLMES]._type == REMOVE) { screen.slamRect(Common::Rect( - people[AL]._oldPosition.x, people[AL]._oldPosition.y, - people[AL]._oldPosition.x + people[AL]._oldSize.x, - people[AL]._oldPosition.y + people[AL]._oldSize.y + people[HOLMES]._oldPosition.x, people[HOLMES]._oldPosition.y, + people[HOLMES]._oldPosition.x + people[HOLMES]._oldSize.x, + people[HOLMES]._oldPosition.y + people[HOLMES]._oldSize.y )); - people[AL]._type = INVALID; + people[HOLMES]._type = INVALID; } else { - screen.flushImage(people[AL]._imageFrame, - Common::Point(people[AL]._position.x / FIXED_INT_MULTIPLIER, - people[AL]._position.y / FIXED_INT_MULTIPLIER - people[AL].frameHeight()), - &people[AL]._oldPosition.x, &people[AL]._oldPosition.y, - &people[AL]._oldSize.x, &people[AL]._oldSize.y); + screen.flushImage(people[HOLMES]._imageFrame, + Common::Point(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER, + people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES].frameHeight()), + &people[HOLMES]._oldPosition.x, &people[HOLMES]._oldPosition.y, + &people[HOLMES]._oldSize.x, &people[HOLMES]._oldSize.y); } } - if (_currentScene == 12) - vm.flushMirror12(); + if (_currentScene == DRAWING_ROOM) + vm.flushBrumwellMirror(); for (uint idx = 0; idx < _bgShapes.size(); ++idx) { Object &o = _bgShapes[idx]; @@ -376,6 +485,262 @@ void ScalpelScene::doBgAnim() { } } +int ScalpelScene::startCAnim(int cAnimNum, int playRate) { + Events &events = *_vm->_events; + ScalpelMap &map = *(ScalpelMap *)_vm->_map; + People &people = *_vm->_people; + Resources &res = *_vm->_res; + Talk &talk = *_vm->_talk; + ScalpelUserInterface &ui = *(ScalpelUserInterface *)_vm->_ui; + Point32 tpPos, walkPos; + int tpDir, walkDir; + int tFrames = 0; + int gotoCode = -1; + + // Validation + if (cAnimNum >= (int)_cAnim.size()) + // number out of bounds + return -1; + if (_canimShapes.size() >= 3 || playRate == 0) + // Too many active animations, or invalid play rate + return 0; + + CAnim &cAnim = _cAnim[cAnimNum]; + if (playRate < 0) { + // Reverse direction + walkPos = cAnim._teleport[0]; + walkDir = cAnim._teleport[0]._facing; + tpPos = cAnim._goto[0]; + tpDir = cAnim._goto[0]._facing; + } else { + // Forward direction + walkPos = cAnim._goto[0]; + walkDir = cAnim._goto[0]._facing; + tpPos = cAnim._teleport[0]; + tpDir = cAnim._teleport[0]._facing; + } + + CursorId oldCursor = events.getCursor(); + events.setCursor(WAIT); + + if (walkPos.x != -1) { + // Holmes must walk to the walk point before the cAnimation is started + if (people[HOLMES]._position != walkPos) + people[HOLMES].walkToCoords(walkPos, walkDir); + } + + if (talk._talkToAbort) + return 1; + + // Add new anim shape entry for displaying the animation + _canimShapes.push_back(Object()); + Object &cObj = _canimShapes[_canimShapes.size() - 1]; + + // Copy the canimation into the bgShapes type canimation structure so it can be played + cObj._allow = cAnimNum + 1; // Keep track of the parent structure + cObj._name = _cAnim[cAnimNum]._name; // Copy name + + // Remove any attempt to draw object frame + if (cAnim._type == NO_SHAPE && cAnim._sequences[0] < 100) + cAnim._sequences[0] = 0; + + cObj._sequences = cAnim._sequences; + cObj._images = nullptr; + cObj._position = cAnim._position; + cObj._delta = Common::Point(0, 0); + cObj._type = cAnim._type; + cObj._flags = cAnim._flags; + + cObj._maxFrames = 0; + cObj._frameNumber = -1; + cObj._sequenceNumber = cAnimNum; + cObj._oldPosition = Common::Point(0, 0); + cObj._oldSize = Common::Point(0, 0); + cObj._goto = Common::Point(0, 0); + cObj._status = 0; + cObj._misc = 0; + cObj._imageFrame = nullptr; + + if (cAnim._name.size() > 0 && cAnim._type != NO_SHAPE) { + if (tpPos.x != -1) + people[HOLMES]._type = REMOVE; + + Common::String fname = cAnim._name + ".vgs"; + if (!res.isInCache(fname)) { + // Set up RRM scene data + Common::SeekableReadStream *roomStream = res.load(_roomFilename); + roomStream->seek(cAnim._dataOffset); + //rrmStream->seek(44 + cAnimNum * 4); + //rrmStream->seek(rrmStream->readUint32LE()); + + // Load the canimation into the cache + Common::SeekableReadStream *imgStream = !_compressed ? roomStream->readStream(cAnim._dataSize) : + Resources::decompressLZ(*roomStream, cAnim._dataSize); + res.addToCache(fname, *imgStream); + + delete imgStream; + delete roomStream; + } + + // Now load the resource as an image + if (!IS_3DO) { + cObj._images = new ImageFile(fname); + } else { + cObj._images = new ImageFile3DO(fname, kImageFile3DOType_RoomFormat); + } + cObj._imageFrame = &(*cObj._images)[0]; + cObj._maxFrames = cObj._images->size(); + + int frames = 0; + if (playRate < 0) { + // Reverse direction + // Count number of frames + while (frames < MAX_FRAME && cObj._sequences[frames]) + ++frames; + } else { + // Forward direction + BaseObject::_countCAnimFrames = true; + + while (cObj._type == ACTIVE_BG_SHAPE) { + cObj.checkObject(); + ++frames; + + if (frames >= 1000) + error("CAnim has infinite loop sequence"); + } + + if (frames > 1) + --frames; + + BaseObject::_countCAnimFrames = false; + + cObj._type = cAnim._type; + cObj._frameNumber = -1; + cObj._position = cAnim._position; + cObj._delta = Common::Point(0, 0); + } + + // Return if animation has no frames in it + if (frames == 0) + return -2; + + ++frames; + int repeat = ABS(playRate); + int dir; + + if (playRate < 0) { + // Play in reverse + dir = -2; + cObj._frameNumber = frames - 3; + } else { + dir = 0; + } + + tFrames = frames - 1; + int pauseFrame = (_cAnimFramePause) ? frames - _cAnimFramePause : -1; + + while (--frames) { + if (frames == pauseFrame) + ui.printObjectDesc(); + + doBgAnim(); + + // Repeat same frame + int temp = repeat; + while (--temp > 0) { + cObj._frameNumber--; + doBgAnim(); + + if (_vm->shouldQuit()) + return 0; + } + + cObj._frameNumber += dir; + } + + people[HOLMES]._type = CHARACTER; + } + + // Teleport to ending coordinates if necessary + if (tpPos.x != -1) { + people[HOLMES]._position = tpPos; // Place the player + people[HOLMES]._sequenceNumber = tpDir; + people[HOLMES].gotoStand(); + } + + if (playRate < 0) + // Reverse direction - set to end sequence + cObj._frameNumber = tFrames - 1; + + if (cObj._frameNumber <= 26) + gotoCode = cObj._sequences[cObj._frameNumber + 3]; + + // Unless anim shape has already been freed, set it to REMOVE so doBgAnim can free it + if (_canimShapes.indexOf(cObj) != -1) + cObj.checkObject(); + + if (gotoCode > 0 && !talk._talkToAbort) { + _goToScene = gotoCode; + + if (_goToScene < 97 && map[_goToScene].x) { + map._overPos = map[_goToScene]; + } + } + + people.loadWalk(); + + if (tpPos.x != -1 && !talk._talkToAbort) { + // Teleport to ending coordinates + people[HOLMES]._position = tpPos; + people[HOLMES]._sequenceNumber = tpDir; + + people[HOLMES].gotoStand(); + } + + events.setCursor(oldCursor); + + return 1; +} + +int ScalpelScene::closestZone(const Common::Point &pt) { + int dist = 1000; + int zone = -1; + + for (uint idx = 0; idx < _zones.size(); ++idx) { + Common::Point zc((_zones[idx].left + _zones[idx].right) / 2, + (_zones[idx].top + _zones[idx].bottom) / 2); + int d = ABS(zc.x - pt.x) + ABS(zc.y - pt.y); + + if (d < dist) { + // Found a closer zone + dist = d; + zone = idx; + } + } + + return zone; +} + +int ScalpelScene::findBgShape(const Common::Point &pt) { + if (!_doBgAnimDone) + // New frame hasn't been drawn yet + return -1; + + for (int idx = (int)_bgShapes.size() - 1; idx >= 0; --idx) { + Object &o = _bgShapes[idx]; + if (o._type != INVALID && o._type != NO_SHAPE && o._type != HIDDEN + && o._aType <= PERSON) { + if (o.getNewBounds().contains(pt)) + return idx; + } else if (o._type == NO_SHAPE) { + if (o.getNoShapeBounds().contains(pt)) + return idx; + } + } + + return -1; +} + } // End of namespace Scalpel } // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/scalpel_scene.h b/engines/sherlock/scalpel/scalpel_scene.h index e5a442f44f..8fe3b66b38 100644 --- a/engines/sherlock/scalpel/scalpel_scene.h +++ b/engines/sherlock/scalpel/scalpel_scene.h @@ -35,16 +35,43 @@ namespace Sherlock { namespace Scalpel { +extern const int FS_TRANS[8]; + +enum { BLACKWOOD_CAPTURE = 2, BAKER_STREET = 4, DRAWING_ROOM = 12, STATION = 17, PUB_INTERIOR = 19, + LAWYER_OFFICE = 27, BAKER_ST_EXTERIOR = 39, RESCUE_ANNA = 52, MOOREHEAD_DEATH = 53, EXIT_GAME = 55, + BRUMWELL_SUICIDE = 70, OVERHEAD_MAP2 = 98, DARTS_GAME = 99, OVERHEAD_MAP = 100 }; + class ScalpelScene : public Scene { private: void doBgAnimCheckCursor(); protected: /** + * Loads the data associated for a given scene. The room resource file's format is: + * BGHEADER: Holds an index for the rest of the file + * STRUCTS: The objects for the scene + * IMAGES: The graphic information for the structures + * + * The _misc field of the structures contains the number of the graphic image + * that it should point to after loading; _misc is then set to 0. + */ + virtual bool loadScene(const Common::String &filename); + + /** * Checks all the background shapes. If a background shape is animating, * it will flag it as needing to be drawn. If a non-animating shape is * colliding with another shape, it will also flag it as needing drawing */ virtual void checkBgShapes(); + + /** + * Draw all the shapes, people and NPCs in the correct order + */ + virtual void drawAllShapes(); + + /** + * Returns the index of the closest zone to a given point. + */ + virtual int closestZone(const Common::Point &pt); public: ScalpelScene(SherlockEngine *vm) : Scene(vm) {} @@ -52,6 +79,22 @@ public: * Draw all objects and characters. */ virtual void doBgAnim(); + + /** + * Attempt to start a canimation sequence. It will load the requisite graphics, and + * then copy the canim object into the _canimShapes array to start the animation. + * + * @param cAnimNum The canim object within the current scene + * @param playRate Play rate. 0 is invalid; 1=normal speed, 2=1/2 speed, etc. + * A negative playRate can also be specified to play the animation in reverse + */ + virtual int startCAnim(int cAnimNum, int playRate = 1); + + /** + * Attempts to find a background shape within the passed bounds. If found, + * it will return the shape number, or -1 on failure. + */ + virtual int findBgShape(const Common::Point &pt); }; } // End of namespace Scalpel diff --git a/engines/sherlock/scalpel/scalpel_screen.cpp b/engines/sherlock/scalpel/scalpel_screen.cpp new file mode 100644 index 0000000000..2096dabcdf --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_screen.cpp @@ -0,0 +1,93 @@ +/* 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 "sherlock/scalpel/scalpel_screen.h" +#include "sherlock/scalpel/scalpel.h" + +namespace Sherlock { + +namespace Scalpel { + +ScalpelScreen::ScalpelScreen(SherlockEngine *vm) : Screen(vm) { +} + +void ScalpelScreen::makeButton(const Common::Rect &bounds, int textX, + const Common::String &str) { + + Surface &bb = *_backBuffer; + bb.fillRect(Common::Rect(bounds.left, bounds.top, bounds.right, bounds.top + 1), BUTTON_TOP); + bb.fillRect(Common::Rect(bounds.left, bounds.top, bounds.left + 1, bounds.bottom), BUTTON_TOP); + bb.fillRect(Common::Rect(bounds.right - 1, bounds.top, bounds.right, bounds.bottom), BUTTON_BOTTOM); + bb.fillRect(Common::Rect(bounds.left + 1, bounds.bottom - 1, bounds.right, bounds.bottom), BUTTON_BOTTOM); + bb.fillRect(Common::Rect(bounds.left + 1, bounds.top + 1, bounds.right - 1, bounds.bottom - 1), BUTTON_MIDDLE); + + gPrint(Common::Point(textX, bounds.top), COMMAND_HIGHLIGHTED, "%c", str[0]); + gPrint(Common::Point(textX + charWidth(str[0]), bounds.top), + COMMAND_FOREGROUND, "%s", str.c_str() + 1); +} + +void ScalpelScreen::buttonPrint(const Common::Point &pt, byte color, bool slamIt, + const Common::String &str) { + int xStart = pt.x - stringWidth(str) / 2; + + if (color == COMMAND_FOREGROUND) { + // First character needs to be highlighted + if (slamIt) { + print(Common::Point(xStart, pt.y + 1), COMMAND_HIGHLIGHTED, "%c", str[0]); + print(Common::Point(xStart + charWidth(str[0]), pt.y + 1), + COMMAND_FOREGROUND, "%s", str.c_str() + 1); + } else { + gPrint(Common::Point(xStart, pt.y), COMMAND_HIGHLIGHTED, "%c", str[0]); + gPrint(Common::Point(xStart + charWidth(str[0]), pt.y), + COMMAND_FOREGROUND, "%s", str.c_str() + 1); + } + } else if (slamIt) { + print(Common::Point(xStart, pt.y + 1), color, "%s", str.c_str()); + } else { + gPrint(Common::Point(xStart, pt.y), color, "%s", str.c_str()); + } +} + +void ScalpelScreen::makePanel(const Common::Rect &r) { + _backBuffer->fillRect(r, BUTTON_MIDDLE); + _backBuffer->hLine(r.left, r.top, r.right - 2, BUTTON_TOP); + _backBuffer->hLine(r.left + 1, r.top + 1, r.right - 3, BUTTON_TOP); + _backBuffer->vLine(r.left, r.top, r.bottom - 1, BUTTON_TOP); + _backBuffer->vLine(r.left + 1, r.top + 1, r.bottom - 2, BUTTON_TOP); + + _backBuffer->vLine(r.right - 1, r.top, r.bottom - 1, BUTTON_BOTTOM); + _backBuffer->vLine(r.right - 2, r.top + 1, r.bottom - 2, BUTTON_BOTTOM); + _backBuffer->hLine(r.left, r.bottom - 1, r.right - 1, BUTTON_BOTTOM); + _backBuffer->hLine(r.left + 1, r.bottom - 2, r.right - 1, BUTTON_BOTTOM); +} + +void ScalpelScreen::makeField(const Common::Rect &r) { + _backBuffer->fillRect(r, BUTTON_MIDDLE); + _backBuffer->hLine(r.left, r.top, r.right - 1, BUTTON_BOTTOM); + _backBuffer->hLine(r.left + 1, r.bottom - 1, r.right - 1, BUTTON_TOP); + _backBuffer->vLine(r.left, r.top + 1, r.bottom - 1, BUTTON_BOTTOM); + _backBuffer->vLine(r.right - 1, r.top + 1, r.bottom - 2, BUTTON_TOP); +} + +} // End of namespace Scalpel + +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/scalpel_screen.h b/engines/sherlock/scalpel/scalpel_screen.h new file mode 100644 index 0000000000..472fe9e220 --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_screen.h @@ -0,0 +1,66 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_SCALPEL_SCREEN_H +#define SHERLOCK_SCALPEL_SCREEN_H + +#include "sherlock/screen.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Scalpel { + +class ScalpelScreen : public Screen { +public: + ScalpelScreen(SherlockEngine *vm); + virtual ~ScalpelScreen() {} + + /** + * Draws a button for use in the inventory, talk, and examine dialogs. + */ + void makeButton(const Common::Rect &bounds, int textX, const Common::String &str); + + /** + * Prints an interface command with the first letter highlighted to indicate + * what keyboard shortcut is associated with it + */ + void buttonPrint(const Common::Point &pt, byte color, bool slamIt, const Common::String &str); + + /** + * Draw a panel in the back buffer with a raised area effect around the edges + */ + void makePanel(const Common::Rect &r); + + /** + * Draw a field in the back buffer with a raised area effect around the edges, + * suitable for text input. + */ + void makeField(const Common::Rect &r); +}; + +} // End of namespace Scalpel + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/scalpel/scalpel_talk.cpp b/engines/sherlock/scalpel/scalpel_talk.cpp new file mode 100644 index 0000000000..2dda817445 --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_talk.cpp @@ -0,0 +1,935 @@ +/* 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 "sherlock/scalpel/scalpel_talk.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/scalpel_user_interface.h" +#include "sherlock/scalpel/scalpel.h" +#include "sherlock/screen.h" +#include "sherlock/scalpel/3do/movie_decoder.h" + +namespace Sherlock { + +namespace Scalpel { + +const byte SCALPEL_OPCODES[] = { + 128, // OP_SWITCH_SPEAKER + 129, // OP_RUN_CANIMATION + 130, // OP_ASSIGN_PORTRAIT_LOCATION + 131, // OP_PAUSE + 132, // OP_REMOVE_PORTRAIT + 133, // OP_CLEAR_WINDOW + 134, // OP_ADJUST_OBJ_SEQUENCE + 135, // OP_WALK_TO_COORDS + 136, // OP_PAUSE_WITHOUT_CONTROL + 137, // OP_BANISH_WINDOW + 138, // OP_SUMMON_WINDOW + 139, // OP_SET_FLAG + 140, // OP_SFX_COMMAND + 141, // OP_TOGGLE_OBJECT + 142, // OP_STEALTH_MODE_ACTIVE + 143, // OP_IF_STATEMENT + 144, // OP_ELSE_STATEMENT + 145, // OP_END_IF_STATEMENT + 146, // OP_STEALTH_MODE_DEACTIVATE + 147, // OP_TURN_HOLMES_OFF + 148, // OP_TURN_HOLMES_ON + 149, // OP_GOTO_SCENE + 150, // OP_PLAY_PROLOGUE + 151, // OP_ADD_ITEM_TO_INVENTORY + 152, // OP_SET_OBJECT + 153, // OP_CALL_TALK_FILE + 143, // OP_MOVE_MOUSE + 155, // OP_DISPLAY_INFO_LINE + 156, // OP_CLEAR_INFO_LINE + 157, // OP_WALK_TO_CANIMATION + 158, // OP_REMOVE_ITEM_FROM_INVENTORY + 159, // OP_ENABLE_END_KEY + 160, // OP_DISABLE_END_KEY + 161, // OP_END_TEXT_WINDOW + 0, // OP_MOUSE_ON_OFF + 0, // OP_SET_WALK_CONTROL + 0, // OP_SET_TALK_SEQUENCE + 0, // OP_PLAY_SONG + 0, // OP_WALK_HOLMES_AND_NPC_TO_CANIM + 0, // OP_SET_NPC_PATH_DEST + 0, // OP_NEXT_SONG + 0, // OP_SET_NPC_PATH_PAUSE + 0, // OP_PASSWORD + 0, // OP_SET_SCENE_ENTRY_FLAG + 0, // OP_WALK_NPC_TO_CANIM + 0, // OP_WALK_HOLMES_AND_NPC_TO_COORDS + 0, // OP_WALK_HOLMES_AND_NPC_TO_COORDS + 0, // OP_SET_NPC_TALK_FILE + 0, // OP_TURN_NPC_OFF + 0, // OP_TURN_NPC_ON + 0, // OP_NPC_DESC_ON_OFF + 0, // OP_NPC_PATH_PAUSE_TAKING_NOTES + 0, // OP_NPC_PATH_PAUSE_LOOKING_HOLMES + 0, // OP_ENABLE_TALK_INTERRUPTS + 0, // OP_DISABLE_TALK_INTERRUPTS + 0, // OP_SET_NPC_INFO_LINE + 0, // OP_SET_NPC_POSITION + 0, // OP_NPC_PATH_LABEL + 0, // OP_PATH_GOTO_LABEL + 0, // OP_PATH_IF_FLAG_GOTO_LABEL + 0, // OP_NPC_WALK_GRAPHICS + 0, // OP_NPC_VERB + 0, // OP_NPC_VERB_CANIM + 0, // OP_NPC_VERB_SCRIPT + 0, // OP_RESTORE_PEOPLE_SEQUENCE + 0, // OP_NPC_VERB_TARGET + 0, // OP_TURN_SOUNDS_OFF + 0 // OP_NULL +}; + +/*----------------------------------------------------------------*/ + +ScalpelTalk::ScalpelTalk(SherlockEngine *vm) : Talk(vm) { + static OpcodeMethod OPCODE_METHODS[] = { + (OpcodeMethod)&ScalpelTalk::cmdSwitchSpeaker, + (OpcodeMethod)&ScalpelTalk::cmdRunCAnimation, + (OpcodeMethod)&ScalpelTalk::cmdAssignPortraitLocation, + + (OpcodeMethod)&ScalpelTalk::cmdPause, + (OpcodeMethod)&ScalpelTalk::cmdRemovePortrait, + (OpcodeMethod)&ScalpelTalk::cmdClearWindow, + (OpcodeMethod)&ScalpelTalk::cmdAdjustObjectSequence, + (OpcodeMethod)&ScalpelTalk::cmdWalkToCoords, + (OpcodeMethod)&ScalpelTalk::cmdPauseWithoutControl, + (OpcodeMethod)&ScalpelTalk::cmdBanishWindow, + (OpcodeMethod)&ScalpelTalk::cmdSummonWindow, + (OpcodeMethod)&ScalpelTalk::cmdSetFlag, + (OpcodeMethod)&ScalpelTalk::cmdSfxCommand, + + (OpcodeMethod)&ScalpelTalk::cmdToggleObject, + (OpcodeMethod)&ScalpelTalk::cmdStealthModeActivate, + (OpcodeMethod)&ScalpelTalk::cmdIf, + (OpcodeMethod)&ScalpelTalk::cmdElse, + nullptr, + (OpcodeMethod)&ScalpelTalk::cmdStealthModeDeactivate, + (OpcodeMethod)&ScalpelTalk::cmdHolmesOff, + (OpcodeMethod)&ScalpelTalk::cmdHolmesOn, + (OpcodeMethod)&ScalpelTalk::cmdGotoScene, + (OpcodeMethod)&ScalpelTalk::cmdPlayPrologue, + + (OpcodeMethod)&ScalpelTalk::cmdAddItemToInventory, + (OpcodeMethod)&ScalpelTalk::cmdSetObject, + (OpcodeMethod)&ScalpelTalk::cmdCallTalkFile, + (OpcodeMethod)&ScalpelTalk::cmdMoveMouse, + (OpcodeMethod)&ScalpelTalk::cmdDisplayInfoLine, + (OpcodeMethod)&ScalpelTalk::cmdClearInfoLine, + (OpcodeMethod)&ScalpelTalk::cmdWalkToCAnimation, + (OpcodeMethod)&ScalpelTalk::cmdRemoveItemFromInventory, + (OpcodeMethod)&ScalpelTalk::cmdEnableEndKey, + (OpcodeMethod)&ScalpelTalk::cmdDisableEndKey, + + (OpcodeMethod)&ScalpelTalk::cmdEndTextWindow, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr + }; + + _opcodeTable = OPCODE_METHODS; + _opcodes = SCALPEL_OPCODES; + + if (vm->getLanguage() == Common::DE_DEU || vm->getLanguage() == Common::ES_ESP) { + // The German and Spanish versions use a different opcode range + static byte opcodes[sizeof(SCALPEL_OPCODES)]; + for (uint idx = 0; idx < sizeof(SCALPEL_OPCODES); ++idx) + opcodes[idx] = SCALPEL_OPCODES[idx] ? SCALPEL_OPCODES[idx] + 47 : 0; + + _opcodes = opcodes; + } + +} + +void ScalpelTalk::talkTo(const Common::String filename) { + ScalpelUserInterface &ui = *(ScalpelUserInterface *)_vm->_ui; + + Talk::talkTo(filename); + + if (filename == "Tube59c") { + // WORKAROUND: Original game bug causes the results of testing the powdery substance + // to disappear too quickly. Introduce a delay to allow it to be properly displayed + ui._menuCounter = 30; + } +} + +void ScalpelTalk::talkInterface(const byte *&str) { + FixedText &fixedText = *_vm->_fixedText; + People &people = *_vm->_people; + ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen; + UserInterface &ui = *_vm->_ui; + + // If the window isn't yet open, draw the window before printing starts + if (!ui._windowOpen && _noTextYet) { + _noTextYet = false; + drawInterface(); + + if (_talkTo != -1) { + Common::String fixedText_Exit = fixedText.getText(kFixedText_Window_Exit); + Common::String fixedText_Up = fixedText.getText(kFixedText_Window_Up); + Common::String fixedText_Down = fixedText.getText(kFixedText_Window_Down); + screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, false, fixedText_Exit); + screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, false, fixedText_Up); + screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, false, fixedText_Down); + } + } + + // If it's the first line, display the speaker + if (!_line && _speaker >= 0 && _speaker < (int)people._characters.size()) { + // If the window is open, display the name directly on-screen. + // Otherwise, simply draw it on the back buffer + if (ui._windowOpen) { + screen.print(Common::Point(16, _yp), TALK_FOREGROUND, "%s", + people._characters[_speaker & 127]._name); + } else { + screen.gPrint(Common::Point(16, _yp - 1), TALK_FOREGROUND, "%s", + people._characters[_speaker & 127]._name); + _openTalkWindow = true; + } + + _yp += 9; + } + + // Find amount of text that will fit on the line + int width = 0, idx = 0; + do { + width += screen.charWidth(str[idx]); + ++idx; + ++_charCount; + } while (width < 298 && str[idx] && str[idx] != '{' && (!isOpcode(str[idx]))); + + if (str[idx] || width >= 298) { + if ((!isOpcode(str[idx])) && str[idx] != '{') { + --idx; + --_charCount; + } + } else { + _endStr = true; + } + + // If word wrap is needed, find the start of the current word + if (width >= 298) { + while (str[idx] != ' ') { + --idx; + --_charCount; + } + } + + // Print the line + Common::String lineStr((const char *)str, (const char *)str + idx); + + // If the speaker indicates a description file, print it in yellow + if (_speaker != -1) { + if (ui._windowOpen) { + screen.print(Common::Point(16, _yp), COMMAND_FOREGROUND, "%s", lineStr.c_str()); + } else { + screen.gPrint(Common::Point(16, _yp - 1), COMMAND_FOREGROUND, "%s", lineStr.c_str()); + _openTalkWindow = true; + } + } else { + if (ui._windowOpen) { + screen.print(Common::Point(16, _yp), COMMAND_FOREGROUND, "%s", lineStr.c_str()); + } else { + screen.gPrint(Common::Point(16, _yp - 1), COMMAND_FOREGROUND, "%s", lineStr.c_str()); + _openTalkWindow = true; + } + } + + // Move to end of displayed line + str += idx; + + // If line wrap occurred, then move to after the separating space between the words + if ((!isOpcode(str[0])) && str[0] != '{') + ++str; + + _yp += 9; + ++_line; + + // Certain different conditions require a wait + if ((_line == 4 && str < _scriptEnd && str[0] != _opcodes[OP_SFX_COMMAND] && str[0] != _opcodes[OP_PAUSE] && _speaker != -1) || + (_line == 5 && str < _scriptEnd && str[0] != _opcodes[OP_PAUSE] && _speaker == -1) || + _endStr) { + _wait = 1; + } + + byte v = (str >= _scriptEnd ? 0 : str[0]); + if (v == _opcodes[OP_SWITCH_SPEAKER] || v == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION] || + v == _opcodes[OP_BANISH_WINDOW] || v == _opcodes[OP_IF_STATEMENT] || + v == _opcodes[OP_ELSE_STATEMENT] || v == _opcodes[OP_END_IF_STATEMENT] || + v == _opcodes[OP_GOTO_SCENE] || v == _opcodes[OP_CALL_TALK_FILE]) { + _wait = 1; + } +} + +OpcodeReturn ScalpelTalk::cmdSwitchSpeaker(const byte *&str) { + ScalpelPeople &people = *(ScalpelPeople *)_vm->_people; + UserInterface &ui = *_vm->_ui; + + if (!(_speaker & SPEAKER_REMOVE)) + people.clearTalking(); + if (_talkToAbort) + return RET_EXIT; + + ui.clearWindow(); + _yp = CONTROLS_Y + 12; + _charCount = _line = 0; + + _speaker = *++str - 1; + people.setTalking(_speaker); + pullSequence(); + pushSequence(_speaker); + people.setTalkSequence(_speaker); + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdGotoScene(const byte *&str) { + ScalpelMap &map = *(ScalpelMap *)_vm->_map; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + scene._goToScene = str[1] - 1; + + if (scene._goToScene != OVERHEAD_MAP) { + // Not going to the map overview + map._oldCharPoint = scene._goToScene; + map._overPos.x = (map[scene._goToScene].x - 6) * FIXED_INT_MULTIPLIER; + map._overPos.y = (map[scene._goToScene].y + 9) * FIXED_INT_MULTIPLIER; + + // Run a canimation? + if (str[2] > 100) { + people._savedPos = PositionFacing(160, 100, str[2]); + } else { + int32 posX = (str[3] - 1) * 256 + str[4] - 1; + int32 posY = str[5] - 1; + people._savedPos = PositionFacing(posX, posY, str[2] - 1); + } + } + + str += 6; + + _scriptMoreFlag = (scene._goToScene == 100) ? 2 : 1; + _scriptSaveIndex = str - _scriptStart; + _endStr = true; + _wait = 0; + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdAssignPortraitLocation(const byte *&str) { + People &people = *_vm->_people; + + ++str; + switch (str[0] & 15) { + case 1: + people._portraitSide = 20; + break; + case 2: + people._portraitSide = 220; + break; + case 3: + people._portraitSide = 120; + break; + default: + break; + } + + if (str[0] > 15) + people._speakerFlip = true; + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdClearInfoLine(const byte *&str) { + UserInterface &ui = *_vm->_ui; + + ui._infoFlag = true; + ui.clearInfo(); + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdClearWindow(const byte *&str) { + UserInterface &ui = *_vm->_ui; + + ui.clearWindow(); + _yp = CONTROLS_Y + 12; + _charCount = _line = 0; + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdDisplayInfoLine(const byte *&str) { + Screen &screen = *_vm->_screen; + UserInterface &ui = *_vm->_ui; + Common::String tempString; + + ++str; + for (int idx = 0; idx < str[0]; ++idx) + tempString += str[idx + 1]; + str += str[0]; + + screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", tempString.c_str()); + ui._menuCounter = 30; + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdElse(const byte *&str) { + // If this is encountered here, it means that a preceeding IF statement was found, + // and evaluated to true. Now all the statements for the true block are finished, + // so skip over the block of code that would have executed if the result was false + _wait = 0; + do { + ++str; + } while (str[0] && str[0] != _opcodes[OP_END_IF_STATEMENT]); + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdIf(const byte *&str) { + ++str; + int flag = (str[0] - 1) * 256 + str[1] - 1 - (str[1] == 1 ? 1 : 0); + ++str; + _wait = 0; + + bool result = flag < 0x8000; + if (_vm->readFlags(flag & 0x7fff) != result) { + do { + ++str; + } while (str[0] && str[0] != _opcodes[OP_ELSE_STATEMENT] && str[0] != _opcodes[OP_END_IF_STATEMENT]); + + if (!str[0]) + _endStr = true; + } + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdMoveMouse(const byte *&str) { + Events &events = *_vm->_events; + + ++str; + events.warpMouse(Common::Point((str[0] - 1) * 256 + str[1] - 1, str[2])); + if (_talkToAbort) + return RET_EXIT; + str += 3; + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdPlayPrologue(const byte *&str) { + Animation &anim = *_vm->_animation; + Common::String tempString; + + ++str; + for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx) + tempString += str[idx]; + + anim.play(tempString, false, 1, 3, true, 4); + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdRemovePortrait(const byte *&str) { + People &people = *_vm->_people; + + if (_speaker >= 0 && _speaker < SPEAKER_REMOVE) + people.clearTalking(); + pullSequence(); + if (_talkToAbort) + return RET_EXIT; + + _speaker |= SPEAKER_REMOVE; + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdWalkToCoords(const byte *&str) { + People &people = *_vm->_people; + ++str; + + people[HOLMES].walkToCoords(Point32(((str[0] - 1) * 256 + str[1] - 1) * FIXED_INT_MULTIPLIER, + str[2] * FIXED_INT_MULTIPLIER), str[3] - 1); + if (_talkToAbort) + return RET_EXIT; + + str += 3; + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdSfxCommand(const byte *&str) { + Sound &sound = *_vm->_sound; + Common::String tempString; + + ++str; + if (sound._voices) { + for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx) + tempString += str[idx]; + sound.playSpeech(tempString); + + // Set voices to wait for more + sound._voices = 2; + } + + _wait = 1; + str += 7; + + return RET_SUCCESS; +} + +OpcodeReturn ScalpelTalk::cmdSummonWindow(const byte *&str) { + Events &events = *_vm->_events; + FixedText &fixedText = *_vm->_fixedText; + ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen; + + drawInterface(); + events._pressed = events._released = false; + events.clearKeyboard(); + _noTextYet = false; + + if (_speaker != -1) { + Common::String fixedText_Exit = fixedText.getText(kFixedText_Window_Exit); + Common::String fixedText_Up = fixedText.getText(kFixedText_Window_Up); + Common::String fixedText_Down = fixedText.getText(kFixedText_Window_Down); + screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, false, fixedText_Exit); + screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, false, fixedText_Up); + screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, false, fixedText_Down); + } + + return RET_SUCCESS; +} + +void ScalpelTalk::talkWait(const byte *&str) { + UserInterface &ui = *_vm->_ui; + bool pauseFlag = _pauseFlag; + + Talk::talkWait(str); + + // Clear the window unless the wait was due to a PAUSE command + if (!pauseFlag && _wait != -1 && str < _scriptEnd && str[0] != _opcodes[OP_SFX_COMMAND]) { + if (!_talkStealth) + ui.clearWindow(); + _yp = CONTROLS_Y + 12; + _charCount = _line = 0; + } +} + +void ScalpelTalk::nothingToSay() { + error("Character had no talk options available"); +} + +void ScalpelTalk::switchSpeaker() { + // If it's the 3DO, pass on to start the actor's conversation movie + if (IS_3DO) + talk3DOMovieTrigger(_3doSpeechIndex++); +} + +void ScalpelTalk::talk3DOMovieTrigger(int subIndex) { + // Find out a few things that we need + int userSelector = _vm->_ui->_selector; + int scriptSelector = _scriptSelect; + int selector = 0; + int roomNr = _vm->_scene->_currentScene; + + if (userSelector >= 0) { + // User-selected dialog + selector = userSelector; + } else { + if (scriptSelector >= 0) { + // Script-selected dialog + selector = scriptSelector; + subIndex--; // for scripts we adjust subIndex, b/c we won't get called from doTalkControl() + } else { + warning("talk3DOMovieTrigger: unable to find selector"); + return; + } + } + + // Make a quick update, so that current text is shown on screen + _vm->_screen->update(); + + // Figure out that movie filename + Common::String movieFilename; + + movieFilename = _scriptName; + movieFilename.deleteChar(1); // remove 2nd character of scriptname + // cut scriptname to 6 characters + while (movieFilename.size() > 6) { + movieFilename.deleteChar(6); + } + + movieFilename.insertChar(selector + 'a', movieFilename.size()); + movieFilename.insertChar(subIndex + 'a', movieFilename.size()); + movieFilename = Common::String::format("movies/%02d/%s.stream", roomNr, movieFilename.c_str()); + + warning("3DO movie player:"); + warning("room: %d", roomNr); + warning("script: %s", _scriptName.c_str()); + warning("selector: %d", selector); + warning("subindex: %d", subIndex); + + Scalpel3DOMoviePlay(movieFilename.c_str(), Common::Point(5, 5)); + + // Restore screen HACK + _vm->_screen->makeAllDirty(); +} + +void ScalpelTalk::drawInterface() { + FixedText &fixedText = *_vm->_fixedText; + ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen; + Surface &bb = *screen._backBuffer; + + bb.fillRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y1 + 10), BORDER_COLOR); + bb.fillRect(Common::Rect(0, CONTROLS_Y + 10, 2, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); + bb.fillRect(Common::Rect(SHERLOCK_SCREEN_WIDTH - 2, CONTROLS_Y + 10, + SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); + bb.fillRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - 1, SHERLOCK_SCREEN_WIDTH - 2, + SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); + bb.fillRect(Common::Rect(2, CONTROLS_Y + 10, SHERLOCK_SCREEN_WIDTH - 2, + SHERLOCK_SCREEN_HEIGHT - 2), INV_BACKGROUND); + + if (_talkTo != -1) { + Common::String fixedText_Exit = fixedText.getText(kFixedText_Window_Exit); + Common::String fixedText_Up = fixedText.getText(kFixedText_Window_Up); + Common::String fixedText_Down = fixedText.getText(kFixedText_Window_Down); + + screen.makeButton(Common::Rect(99, CONTROLS_Y, 139, CONTROLS_Y + 10), + 119 - screen.stringWidth(fixedText_Exit) / 2, fixedText_Exit); + screen.makeButton(Common::Rect(140, CONTROLS_Y, 180, CONTROLS_Y + 10), + 159 - screen.stringWidth(fixedText_Up) / 2, fixedText_Up); + screen.makeButton(Common::Rect(181, CONTROLS_Y, 221, CONTROLS_Y + 10), + 200 - screen.stringWidth(fixedText_Down) / 2, fixedText_Down); + } else { + int strWidth = screen.stringWidth(Scalpel::PRESS_KEY_TO_CONTINUE); + screen.makeButton(Common::Rect(46, CONTROLS_Y, 273, CONTROLS_Y + 10), + 160 - strWidth / 2, Scalpel::PRESS_KEY_TO_CONTINUE); + screen.gPrint(Common::Point(160 - strWidth / 2, CONTROLS_Y), COMMAND_FOREGROUND, "P"); + } +} + +bool ScalpelTalk::displayTalk(bool slamIt) { + FixedText &fixedText = *_vm->_fixedText; + ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen; + int yp = CONTROLS_Y + 14; + int lineY = -1; + _moreTalkDown = _moreTalkUp = false; + + for (uint idx = 0; idx < _statements.size(); ++idx) { + _statements[idx]._talkPos.top = _statements[idx]._talkPos.bottom = -1; + } + + if (_talkIndex) { + for (int idx = 0; idx < _talkIndex && !_moreTalkUp; ++idx) { + if (_statements[idx]._talkMap != -1) + _moreTalkUp = true; + } + } + + // Display the up arrow and enable Up button if the first option is scrolled off-screen + Common::String fixedText_Up = fixedText.getText(kFixedText_Window_Up); + Common::String fixedText_Down = fixedText.getText(kFixedText_Window_Down); + if (_moreTalkUp) { + if (slamIt) { + screen.print(Common::Point(5, CONTROLS_Y + 13), INV_FOREGROUND, "~"); + screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_FOREGROUND, true, fixedText_Up); + } else { + screen.gPrint(Common::Point(5, CONTROLS_Y + 12), INV_FOREGROUND, "~"); + screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_FOREGROUND, false, fixedText_Up); + } + } else { + if (slamIt) { + screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, true, fixedText_Up); + screen.vgaBar(Common::Rect(5, CONTROLS_Y + 11, 15, CONTROLS_Y + 22), INV_BACKGROUND); + } else { + screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, false, fixedText_Up); + screen._backBuffer1.fillRect(Common::Rect(5, CONTROLS_Y + 11, + 15, CONTROLS_Y + 22), INV_BACKGROUND); + } + } + + // Loop through the statements + bool done = false; + for (uint idx = _talkIndex; idx < _statements.size() && !done; ++idx) { + Statement &statement = _statements[idx]; + + if (statement._talkMap != -1) { + bool flag = _talkHistory[_converseNum][idx]; + lineY = talkLine(idx, statement._talkMap, flag ? (byte)TALK_NULL : (byte)INV_FOREGROUND, + yp, slamIt); + + if (lineY != -1) { + statement._talkPos.top = yp; + yp = lineY; + statement._talkPos.bottom = yp; + + if (yp == SHERLOCK_SCREEN_HEIGHT) + done = true; + } else { + done = true; + } + } + } + + // Display the down arrow and enable down button if there are more statements available down off-screen + if (lineY == -1 || lineY == SHERLOCK_SCREEN_HEIGHT) { + _moreTalkDown = true; + + if (slamIt) { + screen.print(Common::Point(5, 190), INV_FOREGROUND, "|"); + screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_FOREGROUND, true, fixedText_Down); + } else { + screen.gPrint(Common::Point(5, 189), INV_FOREGROUND, "|"); + screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_FOREGROUND, false, fixedText_Down); + } + } else { + if (slamIt) { + screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, true, fixedText_Down); + screen.vgaBar(Common::Rect(5, 189, 16, 199), INV_BACKGROUND); + } else { + screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, false, fixedText_Down); + screen._backBuffer1.fillRect(Common::Rect(5, 189, 16, 199), INV_BACKGROUND); + } + } + + return done; +} + +int ScalpelTalk::talkLine(int lineNum, int stateNum, byte color, int lineY, bool slamIt) { + Screen &screen = *_vm->_screen; + int idx = lineNum; + Common::String msg, number; + bool numberFlag = false; + + // Get the statement to display as well as optional number prefix + if (idx < SPEAKER_REMOVE) { + number = Common::String::format("%d.", stateNum + 1); + numberFlag = true; + } else { + idx -= SPEAKER_REMOVE; + } + msg = _statements[idx]._statement; + + // Handle potentially multiple lines needed to display entire statement + const char *lineStartP = msg.c_str(); + int maxWidth = 298 - (numberFlag ? 18 : 0); + for (;;) { + // Get as much of the statement as possible will fit on the + Common::String sLine; + const char *lineEndP = lineStartP; + int width = 0; + do { + width += screen.charWidth(*lineEndP); + } while (*++lineEndP && width < maxWidth); + + // Check if we need to wrap the line + if (width >= maxWidth) { + // Work backwards to the prior word's end + while (*--lineEndP != ' ') + ; + + sLine = Common::String(lineStartP, lineEndP++); + } else { + // Can display remainder of the statement on the current line + sLine = Common::String(lineStartP); + } + + + if (lineY <= (SHERLOCK_SCREEN_HEIGHT - 10)) { + // Need to directly display on-screen? + if (slamIt) { + // See if a numer prefix is needed or not + if (numberFlag) { + // Are we drawing the first line? + if (lineStartP == msg.c_str()) { + // We are, so print the number and then the text + screen.print(Common::Point(16, lineY), color, "%s", number.c_str()); + } + + // Draw the line with an indent + screen.print(Common::Point(30, lineY), color, "%s", sLine.c_str()); + } else { + screen.print(Common::Point(16, lineY), color, "%s", sLine.c_str()); + } + } else { + if (numberFlag) { + if (lineStartP == msg.c_str()) { + screen.gPrint(Common::Point(16, lineY - 1), color, "%s", number.c_str()); + } + + screen.gPrint(Common::Point(30, lineY - 1), color, "%s", sLine.c_str()); + } else { + screen.gPrint(Common::Point(16, lineY - 1), color, "%s", sLine.c_str()); + } + } + + // Move to next line, if any + lineY += 9; + lineStartP = lineEndP; + + if (!*lineEndP) + break; + } else { + // We're close to the bottom of the screen, so stop display + lineY = -1; + break; + } + } + + if (lineY == -1 && lineStartP != msg.c_str()) + lineY = SHERLOCK_SCREEN_HEIGHT; + + // Return the Y position of the next line to follow this one + return lineY; +} + +void ScalpelTalk::showTalk() { + FixedText &fixedText = *_vm->_fixedText; + People &people = *_vm->_people; + ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen; + ScalpelUserInterface &ui = *(ScalpelUserInterface *)_vm->_ui; + Common::String fixedText_Exit = fixedText.getText(kFixedText_Window_Exit); + byte color = ui._endKeyActive ? COMMAND_FOREGROUND : COMMAND_NULL; + + clearSequences(); + pushSequence(_talkTo); + people.setListenSequence(_talkTo); + + ui._selector = ui._oldSelector = -1; + + if (!ui._windowOpen) { + // Draw the talk interface on the back buffer + drawInterface(); + displayTalk(false); + } else { + displayTalk(true); + } + + // If the window is already open, simply draw. Otherwise, do it + // to the back buffer and then summon the window + if (ui._windowOpen) { + screen.buttonPrint(Common::Point(119, CONTROLS_Y), color, true, fixedText_Exit); + } else { + screen.buttonPrint(Common::Point(119, CONTROLS_Y), color, false, fixedText_Exit); + + if (!ui._slideWindows) { + screen.slamRect(Common::Rect(0, CONTROLS_Y, + SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + } else { + ui.summonWindow(); + } + + ui._windowOpen = true; + } +} + +OpcodeReturn ScalpelTalk::cmdCallTalkFile(const byte *&str) { + Common::String tempString; + + ++str; + for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx) + tempString += str[idx]; + str += 8; + + int scriptCurrentIndex = str - _scriptStart; + + // Save the current script position and new talk file + if (_scriptStack.size() < 9) { + ScriptStackEntry rec1; + rec1._name = _scriptName; + rec1._currentIndex = scriptCurrentIndex; + rec1._select = _scriptSelect; + _scriptStack.push(rec1); + + // Push the new talk file onto the stack + ScriptStackEntry rec2; + rec2._name = tempString; + rec2._currentIndex = 0; + rec2._select = 100; + _scriptStack.push(rec2); + } else { + error("Script stack overflow"); + } + + _scriptMoreFlag = 1; + _endStr = true; + _wait = 0; + + return RET_SUCCESS; +} + +void ScalpelTalk::pushSequenceEntry(Object *obj) { + Scene &scene = *_vm->_scene; + SequenceEntry seqEntry; + seqEntry._objNum = scene._bgShapes.indexOf(*obj); + + if (seqEntry._objNum != -1) { + for (uint idx = 0; idx < MAX_TALK_SEQUENCES; ++idx) + seqEntry._sequences.push_back(obj->_sequences[idx]); + + seqEntry._frameNumber = obj->_frameNumber; + seqEntry._seqTo = obj->_seqTo; + } + + _sequenceStack.push(seqEntry); + if (_scriptStack.size() >= 5) + error("script stack overflow"); +} + +void ScalpelTalk::pullSequence(int slot) { + Scene &scene = *_vm->_scene; + + if (_sequenceStack.empty()) + return; + + SequenceEntry seq = _sequenceStack.pop(); + if (seq._objNum != -1) { + Object &obj = scene._bgShapes[seq._objNum]; + + if (obj._seqSize < MAX_TALK_SEQUENCES) { + warning("Tried to restore too few frames"); + } else { + for (int idx = 0; idx < MAX_TALK_SEQUENCES; ++idx) + obj._sequences[idx] = seq._sequences[idx]; + + obj._frameNumber = seq._frameNumber; + obj._seqTo = seq._seqTo; + } + } +} + +void ScalpelTalk::clearSequences() { + _sequenceStack.clear(); +} + +} // End of namespace Scalpel + +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/scalpel_talk.h b/engines/sherlock/scalpel/scalpel_talk.h new file mode 100644 index 0000000000..31f78cbf85 --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_talk.h @@ -0,0 +1,143 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_SCALPEL_TALK_H +#define SHERLOCK_SCALPEL_TALK_H + +#include "common/scummsys.h" +#include "common/array.h" +#include "common/rect.h" +#include "common/serializer.h" +#include "common/stream.h" +#include "common/stack.h" +#include "sherlock/talk.h" + +namespace Sherlock { + +namespace Scalpel { + +class ScalpelTalk : public Talk { +private: + Common::Stack<SequenceEntry> _sequenceStack; + + OpcodeReturn cmdSwitchSpeaker(const byte *&str); + OpcodeReturn cmdAssignPortraitLocation(const byte *&str); + OpcodeReturn cmdGotoScene(const byte *&str); + OpcodeReturn cmdCallTalkFile(const byte *&str); + OpcodeReturn cmdClearInfoLine(const byte *&str); + OpcodeReturn cmdClearWindow(const byte *&str); + OpcodeReturn cmdDisplayInfoLine(const byte *&str); + OpcodeReturn cmdElse(const byte *&str); + OpcodeReturn cmdIf(const byte *&str); + OpcodeReturn cmdMoveMouse(const byte *&str); + OpcodeReturn cmdPlayPrologue(const byte *&str); + OpcodeReturn cmdRemovePortrait(const byte *&str); + OpcodeReturn cmdSfxCommand(const byte *&str); + OpcodeReturn cmdSummonWindow(const byte *&str); + OpcodeReturn cmdWalkToCoords(const byte *&str); +protected: + /** + * Display the talk interface window + */ + virtual void talkInterface(const byte *&str); + + /** + * Pause when displaying a talk dialog on-screen + */ + virtual void talkWait(const byte *&str); + + /** + * Called when the active speaker is switched + */ + virtual void switchSpeaker(); + + /** + * Called when a character being spoken to has no talk options to display + */ + virtual void nothingToSay(); + + /** + * Show the talk display + */ + virtual void showTalk(); +public: + ScalpelTalk(SherlockEngine *vm); + virtual ~ScalpelTalk() {} + + /** + * Called whenever a conversation or item script needs to be run. For standard conversations, + * it opens up a description window similar to how 'talk' does, but shows a 'reply' directly + * instead of waiting for a statement option. + * @remarks It seems that at some point, all item scripts were set up to use this as well. + * In their case, the conversation display is simply suppressed, and control is passed on to + * doScript to implement whatever action is required. + */ + virtual void talkTo(const Common::String filename); + + /** + * Draws the interface for conversation display + */ + void drawInterface(); + + /** + * Display a list of statements in a window at the bottom of the screen that the + * player can select from. + */ + bool displayTalk(bool slamIt); + + /** + * Prints a single conversation option in the interface window + */ + int talkLine(int lineNum, int stateNum, byte color, int lineY, bool slamIt); + + /** + * Trigger to play a 3DO talk dialog movie + */ + void talk3DOMovieTrigger(int subIndex); + + /** + * Push the details of a passed object onto the saved sequences stack + */ + virtual void pushSequenceEntry(Object *obj); + + /** + * Pulls a background object sequence from the sequence stack and restore's the + * object's sequence + */ + virtual void pullSequence(int slot = -1); + + /** + * Returns true if the script stack is empty + */ + virtual bool isSequencesEmpty() const { return _scriptStack.empty(); } + + /** + * Clears the stack of pending object sequences associated with speakers in the scene + */ + virtual void clearSequences(); +}; + +} // End of namespace Scalpel + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/scalpel/scalpel_user_interface.cpp b/engines/sherlock/scalpel/scalpel_user_interface.cpp index 295cddb3c9..a67d464a11 100644 --- a/engines/sherlock/scalpel/scalpel_user_interface.cpp +++ b/engines/sherlock/scalpel/scalpel_user_interface.cpp @@ -21,8 +21,16 @@ */ #include "sherlock/scalpel/scalpel_user_interface.h" -#include "sherlock/sherlock.h" +#include "sherlock/scalpel/scalpel_fixed_text.h" +#include "sherlock/scalpel/scalpel_inventory.h" +#include "sherlock/scalpel/scalpel_journal.h" +#include "sherlock/scalpel/scalpel_people.h" +#include "sherlock/scalpel/scalpel_saveload.h" +#include "sherlock/scalpel/scalpel_screen.h" +#include "sherlock/scalpel/scalpel_talk.h" #include "sherlock/scalpel/settings.h" +#include "sherlock/scalpel/scalpel.h" +#include "sherlock/sherlock.h" namespace Sherlock { @@ -60,33 +68,22 @@ const char COMMANDS[13] = "LMTPOCIUGJFS"; const char INVENTORY_COMMANDS[9] = { "ELUG-+,." }; const char *const PRESS_KEY_FOR_MORE = "Press any Key for More."; const char *const PRESS_KEY_TO_CONTINUE = "Press any Key to Continue."; - -const char *const MOPEN[] = { - "This cannot be opened", "It is already open", "It is locked", "Wait for Watson", " ", "." -}; -const char *const MCLOSE[] = { - "This cannot be closed", "It is already closed", "The safe door is in the way" -}; -const char *const MMOVE[] = { - "This cannot be moved", "It is bolted to the floor", "It is too heavy", "The other crate is in the way" -}; -const char *const MPICK[] = { - "Nothing of interest here", "It is bolted down", "It is too big to carry", "It is too heavy", - "I think a girl would be more your type", "Those flowers belong to Penny", "She's far too young for you!", - "I think a girl would be more your type!", "Government property for official use only" -}; -const char *const MUSE[] = { - "You can't do that", "It had no effect", "You can't reach it", "OK, the door looks bigger! Happy?", - "Doors don't smoke" -}; +const int UI_OFFSET_3DO = 16; // (320 - 288) / 2 /*----------------------------------------------------------------*/ ScalpelUserInterface::ScalpelUserInterface(SherlockEngine *vm): UserInterface(vm) { if (_vm->_interactiveFl) { - _controls = new ImageFile("menu.all"); - _controlPanel = new ImageFile("controls.vgs"); + if (!IS_3DO) { + // PC + _controls = new ImageFile("menu.all"); + _controlPanel = new ImageFile("controls.vgs"); + } else { + // 3DO + _controls = new ImageFile3DO("menu.all", kImageFile3DOType_RoomFormat); + _controlPanel = new ImageFile3DO("controls.vgs", kImageFile3DOType_RoomFormat); + } } else { _controls = nullptr; _controlPanel = nullptr; @@ -94,8 +91,6 @@ ScalpelUserInterface::ScalpelUserInterface(SherlockEngine *vm): UserInterface(vm _keyPress = '\0'; _lookHelp = 0; - _bgFound = 0; - _oldBgFound = -1; _help = _oldHelp = 0; _key = _oldKey = '\0'; _temp = _oldTemp = 0; @@ -113,18 +108,20 @@ ScalpelUserInterface::~ScalpelUserInterface() { } void ScalpelUserInterface::reset() { - _oldKey = -1; + UserInterface::reset(); _help = _oldHelp = -1; - _oldTemp = _temp = -1; } void ScalpelUserInterface::drawInterface(int bufferNum) { Screen &screen = *_vm->_screen; + const ImageFrame &src = (*_controlPanel)[0]; + int16 x = (!IS_3DO) ? 0 : UI_OFFSET_3DO; + if (bufferNum & 1) - screen._backBuffer1.transBlitFrom((*_controlPanel)[0], Common::Point(0, CONTROLS_Y)); + screen._backBuffer1.transBlitFrom(src, Common::Point(x, CONTROLS_Y)); if (bufferNum & 2) - screen._backBuffer2.transBlitFrom((*_controlPanel)[0], Common::Point(0, CONTROLS_Y)); + screen._backBuffer2.transBlitFrom(src, Common::Point(x, CONTROLS_Y)); if (bufferNum == 3) screen._backBuffer2.fillRect(0, INFO_LINE, SHERLOCK_SCREEN_WIDTH, INFO_LINE + 10, INFO_BLACK); } @@ -141,7 +138,7 @@ void ScalpelUserInterface::handleInput() { whileMenuCounter(); Common::Point pt = events.mousePos(); - _bgFound = scene.findBgShape(Common::Rect(pt.x, pt.y, pt.x + 1, pt.y + 1)); + _bgFound = scene.findBgShape(pt); _keyPress = '\0'; // Check kbd and set the mouse released flag if Enter or space is pressed. @@ -279,7 +276,7 @@ void ScalpelUserInterface::handleInput() { } if (events._released && personFound) - talk.talk(_bgFound); + talk.initTalk(_bgFound); else if (personFound) lookScreen(pt); else if (_bgFound < 1000) @@ -316,9 +313,9 @@ void ScalpelUserInterface::handleInput() { // Mouse clicked in script zone events._pressed = events._released = false; } else { - people._walkDest = pt; people._allowWalkAbort = false; - people.goAllTheWay(); + people[HOLMES]._walkDest = pt; + people[HOLMES].goAllTheWay(); } if (_oldKey != -1) { @@ -385,6 +382,7 @@ void ScalpelUserInterface::handleInput() { void ScalpelUserInterface::depressButton(int num) { Screen &screen = *_vm->_screen; Common::Point pt(MENU_POINTS[num][0], MENU_POINTS[num][1]); + offsetButton3DO(pt, num); ImageFrame &frame = (*_controls)[num]; screen._backBuffer1.transBlitFrom(frame, pt); @@ -392,10 +390,17 @@ void ScalpelUserInterface::depressButton(int num) { } void ScalpelUserInterface::restoreButton(int num) { + Events &events = *_vm->_events; Screen &screen = *_vm->_screen; Common::Point pt(MENU_POINTS[num][0], MENU_POINTS[num][1]); + offsetButton3DO(pt, num); + Graphics::Surface &frame = (*_controls)[num]._frame; + // Reset the cursor + events.setCursor(ARROW); + + // Restore the UI on the back buffer screen._backBuffer1.blitFrom(screen._backBuffer2, pt, Common::Rect(pt.x, pt.y, pt.x + 90, pt.y + 19)); screen.slamArea(pt.x, pt.y, pt.x + frame.w, pt.y + frame.h); @@ -441,6 +446,7 @@ void ScalpelUserInterface::toggleButton(int num) { ImageFrame &frame = (*_controls)[num]; Common::Point pt(MENU_POINTS[num][0], MENU_POINTS[num][1]); + offsetButton3DO(pt, num); screen._backBuffer1.transBlitFrom(frame, pt); screen.slamArea(pt.x, pt.y, pt.x + frame._width, pt.y + frame._height); } @@ -495,7 +501,7 @@ void ScalpelUserInterface::examine() { scene.startCAnim(_cNum, canimSpeed); } else if (obj._lookPosition.y != 0) { // Need to walk to the object to be examined - people.walkToCoords(Common::Point(obj._lookPosition.x, obj._lookPosition.y * 100), obj._lookFacing); + people[HOLMES].walkToCoords(obj._lookPosition, obj._lookPosition._facing); } if (!talk._talkToAbort) { @@ -536,7 +542,7 @@ void ScalpelUserInterface::lookScreen(const Common::Point &pt) { Common::String tempStr; // Don't display anything for right button command - if ((events._rightPressed || events._rightPressed) && !events._pressed) + if ((events._rightPressed || events._rightReleased) && !events._pressed) return; if (mousePos.y < CONTROLS_Y && (temp = _bgFound) != -1) { @@ -666,9 +672,9 @@ void ScalpelUserInterface::lookInv() { void ScalpelUserInterface::doEnvControl() { Events &events = *_vm->_events; - SaveManager &saves = *_vm->_saves; + ScalpelSaveManager &saves = *(ScalpelSaveManager *)_vm->_saves; Scene &scene = *_vm->_scene; - Screen &screen = *_vm->_screen; + ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen; Talk &talk = *_vm->_talk; Common::Point mousePos = events.mousePos(); static const char ENV_COMMANDS[7] = "ELSUDQ"; @@ -968,9 +974,10 @@ void ScalpelUserInterface::doEnvControl() { void ScalpelUserInterface::doInvControl() { Events &events = *_vm->_events; - Inventory &inv = *_vm->_inventory; + FixedText &fixedText = *_vm->_fixedText; + ScalpelInventory &inv = *(ScalpelInventory *)_vm->_inventory; Scene &scene = *_vm->_scene; - Screen &screen = *_vm->_screen; + ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen; Talk &talk = *_vm->_talk; int colors[8]; Common::Point mousePos = events.mousePos(); @@ -993,15 +1000,20 @@ void ScalpelUserInterface::doInvControl() { if (events._pressed || events._released) { events.clearKeyboard(); + Common::String fixedText_Exit = fixedText.getText(kFixedText_Inventory_Exit); + Common::String fixedText_Look = fixedText.getText(kFixedText_Inventory_Look); + Common::String fixedText_Use = fixedText.getText(kFixedText_Inventory_Use); + Common::String fixedText_Give = fixedText.getText(kFixedText_Inventory_Give); + if (found != -1) // If a slot highlighted, set its color colors[found] = COMMAND_HIGHLIGHTED; - screen.buttonPrint(Common::Point(INVENTORY_POINTS[0][2], CONTROLS_Y1), colors[0], true, "Exit"); + screen.buttonPrint(Common::Point(INVENTORY_POINTS[0][2], CONTROLS_Y1), colors[0], true, fixedText_Exit); if (found >= 0 && found <= 3) { - screen.buttonPrint(Common::Point(INVENTORY_POINTS[1][2], CONTROLS_Y1), colors[1], true, "Look"); - screen.buttonPrint(Common::Point(INVENTORY_POINTS[2][2], CONTROLS_Y1), colors[2], true, "Use"); - screen.buttonPrint(Common::Point(INVENTORY_POINTS[3][2], CONTROLS_Y1), colors[3], true, "Give"); + screen.buttonPrint(Common::Point(INVENTORY_POINTS[1][2], CONTROLS_Y1), colors[1], true, fixedText_Look); + screen.buttonPrint(Common::Point(INVENTORY_POINTS[2][2], CONTROLS_Y1), colors[2], true, fixedText_Use); + screen.buttonPrint(Common::Point(INVENTORY_POINTS[3][2], CONTROLS_Y1), colors[3], true, fixedText_Give); inv._invMode = (InvMode)found; _selector = -1; } @@ -1063,7 +1075,7 @@ void ScalpelUserInterface::doInvControl() { } if (_selector != -1) - inv.highlight(_selector, 235); + inv.highlight(_selector, BUTTON_BACKGROUND); _oldSelector = _selector; } @@ -1177,10 +1189,10 @@ void ScalpelUserInterface::doInvControl() { bool giveFl = (tempMode >= INVMODE_GIVE); if (_selector >= 0) // Use/Give inv object with scene object - checkUseAction(&scene._bgShapes[_find]._use[0], inv[_selector]._name, MUSE, _find, giveFl); + checkUseAction(&scene._bgShapes[_find]._use[0], inv[_selector]._name, kFixedTextAction_Use, _find, giveFl); else // Now inv object has been highlighted - checkUseAction(&scene._bgShapes[_find]._use[0], "*SELF*", MUSE, _find, giveFl); + checkUseAction(&scene._bgShapes[_find]._use[0], "*SELF*", kFixedTextAction_Use, _find, giveFl); _selector = _oldSelector = -1; } @@ -1191,7 +1203,7 @@ void ScalpelUserInterface::doInvControl() { void ScalpelUserInterface::doLookControl() { Events &events = *_vm->_events; - Inventory &inv = *_vm->_inventory; + ScalpelInventory &inv = *(ScalpelInventory *)_vm->_inventory; Screen &screen = *_vm->_screen; _key = _oldKey = -1; @@ -1206,6 +1218,7 @@ void ScalpelUserInterface::doLookControl() { } else if (!_lookHelp) { // Need to close the window and depress the Look button Common::Point pt(MENU_POINTS[0][0], MENU_POINTS[0][1]); + offsetButton3DO(pt, 0); screen._backBuffer2.blitFrom((*_controls)[0], pt); banishWindow(true); @@ -1252,8 +1265,8 @@ void ScalpelUserInterface::doLookControl() { void ScalpelUserInterface::doMainControl() { Events &events = *_vm->_events; - Inventory &inv = *_vm->_inventory; - SaveManager &saves = *_vm->_saves; + ScalpelInventory &inv = *(ScalpelInventory *)_vm->_inventory; + ScalpelSaveManager &saves = *(ScalpelSaveManager *)_vm->_saves; Common::Point pt = events.mousePos(); if ((events._pressed || events._released) && pt.y > CONTROLS_Y) { @@ -1264,6 +1277,10 @@ void ScalpelUserInterface::doMainControl() { for (_temp = 0; (_temp < 12) && (_key == -1); ++_temp) { Common::Rect r(MENU_POINTS[_temp][0], MENU_POINTS[_temp][1], MENU_POINTS[_temp][2], MENU_POINTS[_temp][3]); + if (IS_3DO && _temp >= 0 && _temp <= 2) { + r.left += UI_OFFSET_3DO - 1; + r.right += UI_OFFSET_3DO - 1; + } if (r.contains(pt)) _key = COMMANDS[_temp]; } @@ -1403,7 +1420,7 @@ void ScalpelUserInterface::doMiscControl(int allowed) { switch (allowed) { case ALLOW_OPEN: - checkAction(obj._aOpen, MOPEN, _temp); + checkAction(obj._aOpen, _temp, kFixedTextAction_Open); if (_menuMode != TALK_MODE && !talk._talkToAbort) { _menuMode = STD_MODE; restoreButton(OPEN_MODE - 1); @@ -1412,7 +1429,7 @@ void ScalpelUserInterface::doMiscControl(int allowed) { break; case ALLOW_CLOSE: - checkAction(obj._aClose, MCLOSE, _temp); + checkAction(obj._aClose, _temp, kFixedTextAction_Close); if (_menuMode != TALK_MODE && !talk._talkToAbort) { _menuMode = STD_MODE; restoreButton(CLOSE_MODE - 1); @@ -1421,7 +1438,7 @@ void ScalpelUserInterface::doMiscControl(int allowed) { break; case ALLOW_MOVE: - checkAction(obj._aMove, MMOVE, _temp); + checkAction(obj._aMove, _temp, kFixedTextAction_Move); if (_menuMode != TALK_MODE && !talk._talkToAbort) { _menuMode = STD_MODE; restoreButton(MOVE_MODE - 1); @@ -1448,7 +1465,7 @@ void ScalpelUserInterface::doPickControl() { // Don't allow characters to be picked up if (_bgFound < 1000) { - scene._bgShapes[_bgFound].pickUpObject(MPICK); + scene._bgShapes[_bgFound].pickUpObject(kFixedTextAction_Pick); if (!talk._talkToAbort && _menuMode != TALK_MODE) { _key = _oldKey = -1; @@ -1462,34 +1479,39 @@ void ScalpelUserInterface::doPickControl() { void ScalpelUserInterface::doTalkControl() { Events &events = *_vm->_events; - Journal &journal = *_vm->_journal; - People &people = *_vm->_people; - Screen &screen = *_vm->_screen; + FixedText &fixedText = *_vm->_fixedText; + ScalpelJournal &journal = *(ScalpelJournal *)_vm->_journal; + ScalpelPeople &people = *(ScalpelPeople *)_vm->_people; + ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen; Sound &sound = *_vm->_sound; - Talk &talk = *_vm->_talk; + ScalpelTalk &talk = *(ScalpelTalk *)_vm->_talk; Common::Point mousePos = events.mousePos(); _key = _oldKey = -1; _keyboardInput = false; + Common::String fixedText_Exit = fixedText.getText(kFixedText_Window_Exit); + Common::String fixedText_Up = fixedText.getText(kFixedText_Window_Up); + Common::String fixedText_Down = fixedText.getText(kFixedText_Window_Down); + if (events._pressed || events._released) { events.clearKeyboard(); // Handle button printing if (mousePos.x > 99 && mousePos.x < 138 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 10) && !_endKeyActive) - screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Exit"); + screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Exit); else if (_endKeyActive) - screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_FOREGROUND, true, "Exit"); + screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_FOREGROUND, true, fixedText_Exit); if (mousePos.x > 140 && mousePos.x < 170 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 10) && talk._moreTalkUp) - screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Up"); + screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Up); else if (talk._moreTalkUp) - screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_FOREGROUND, true, "Up"); + screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_FOREGROUND, true, fixedText_Up); if (mousePos.x > 181&& mousePos.x < 220 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 10) && talk._moreTalkDown) - screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "Down"); + screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_HIGHLIGHTED, true, fixedText_Down); else if (talk._moreTalkDown) - screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_FOREGROUND, true, "Down"); + screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_FOREGROUND, true, fixedText_Down); bool found = false; for (_selector = talk._talkIndex; _selector < (int)talk._statements.size() && !found; ++_selector) { @@ -1574,9 +1596,9 @@ void ScalpelUserInterface::doTalkControl() { screen.slamRect(Common::Rect(5, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH - 5, SHERLOCK_SCREEN_HEIGHT - 2)); } else if (_selector != -1) { - screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, true, "Exit"); - screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, true, "Up"); - screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, true, "Down"); + screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, true, fixedText_Exit); + screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, true, fixedText_Up); + screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, true, fixedText_Down); // If the reply is new, add it to the journal if (!talk._talkHistory[talk._converseNum][_selector]) { @@ -1615,15 +1637,18 @@ void ScalpelUserInterface::doTalkControl() { people.setTalking(0); if (!talk._statements[_selector]._voiceFile.empty() && sound._voices) { - sound.playSound(talk._statements[_selector]._voiceFile, WAIT_RETURN_IMMEDIATELY); + sound.playSpeech(talk._statements[_selector]._voiceFile); // Set voices as an indicator for waiting sound._voices = 2; - sound._speechOn = *sound._soundIsOn; } else { - sound._speechOn = false; + sound._speechPlaying = false; } + if (IS_3DO) + // Trigger to play 3DO movie + talk.talk3DOMovieTrigger(0); + talk.waitForMore(talk._statements[_selector]._statement.size()); if (talk._talkToAbort) return; @@ -1670,9 +1695,9 @@ void ScalpelUserInterface::doTalkControl() { !talk._statements[select]._statement.hasPrefix("^")) { // Not a reply first file, so display the new selections if (_endKeyActive) - screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_FOREGROUND, true, "Exit"); + screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_FOREGROUND, true, fixedText_Exit); else - screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, true, "Exit"); + screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, true, fixedText_Exit); talk.displayTalk(true); events.setCursor(ARROW); @@ -1714,7 +1739,7 @@ void ScalpelUserInterface::doTalkControl() { void ScalpelUserInterface::journalControl() { Events &events = *_vm->_events; - Journal &journal = *_vm->_journal; + ScalpelJournal &journal = *(ScalpelJournal *)_vm->_journal; Scene &scene = *_vm->_scene; Screen &screen = *_vm->_screen; bool doneFlag = false; @@ -1762,8 +1787,8 @@ void ScalpelUserInterface::journalControl() { void ScalpelUserInterface::printObjectDesc(const Common::String &str, bool firstTime) { Events &events = *_vm->_events; - Inventory &inv = *_vm->_inventory; - Screen &screen = *_vm->_screen; + ScalpelInventory &inv = *(ScalpelInventory *)_vm->_inventory; + ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen; Talk &talk = *_vm->_talk; if (str.hasPrefix("_")) { @@ -1787,6 +1812,7 @@ void ScalpelUserInterface::printObjectDesc(const Common::String &str, bool first // menu area, and draw the controls onto it Surface tempSurface((*_controls)[0]._frame.w, (*_controls)[0]._frame.h); Common::Point pt(MENU_POINTS[0][0], MENU_POINTS[0][1]); + offsetButton3DO(pt, 0); tempSurface.blitFrom(screen._backBuffer2, Common::Point(0, 0), Common::Rect(pt.x, pt.y, pt.x + tempSurface.w(), pt.y + tempSurface.h())); @@ -1975,8 +2001,7 @@ void ScalpelUserInterface::summonWindow(bool slideUp, int height) { Screen &screen = *_vm->_screen; // Extract the window that's been drawn on the back buffer - Surface tempSurface(SHERLOCK_SCREEN_WIDTH, - (SHERLOCK_SCREEN_HEIGHT - height)); + Surface tempSurface(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT - height); Common::Rect r(0, height, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); tempSurface.blitFrom(screen._backBuffer1, Common::Point(0, 0), r); @@ -2055,13 +2080,14 @@ void ScalpelUserInterface::banishWindow(bool slideUp) { } void ScalpelUserInterface::checkUseAction(const UseType *use, const Common::String &invName, - const char *const messages[], int objNum, bool giveMode) { + FixedTextActionId fixedTextActionId, int objNum, bool giveMode) { Events &events = *_vm->_events; + FixedText &fixedText = *_vm->_fixedText; Inventory &inv = *_vm->_inventory; Scene &scene = *_vm->_scene; Screen &screen = *_vm->_screen; Talk &talk = *_vm->_talk; - bool printed = messages == nullptr; + bool printed = fixedTextActionId == kFixedTextAction_Invalid; if (objNum >= 1000) { // Holmes was specified, so do nothing @@ -2113,7 +2139,7 @@ void ScalpelUserInterface::checkUseAction(const UseType *use, const Common::Stri if (!talk._talkToAbort) { Object &obj = scene._bgShapes[objNum]; for (int idx = 0; idx < NAMES_COUNT && !talk._talkToAbort; ++idx) { - if (obj.checkNameForCodes(action._names[idx], messages)) { + if (obj.checkNameForCodes(action._names[idx], fixedTextActionId)) { if (!talk._talkToAbort) printed = true; } @@ -2134,10 +2160,11 @@ void ScalpelUserInterface::checkUseAction(const UseType *use, const Common::Stri if (giveMode) { screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "No, thank you."); - } else if (messages == nullptr) { + } else if (fixedTextActionId == kFixedTextAction_Invalid) { screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "You can't do that."); } else { - screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", messages[0]); + Common::String errorMessage = fixedText.getActionMessage(fixedTextActionId, 0); + screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", errorMessage.c_str()); } _infoFlag = true; @@ -2147,136 +2174,15 @@ void ScalpelUserInterface::checkUseAction(const UseType *use, const Common::Stri events.setCursor(ARROW); } -void ScalpelUserInterface::checkAction(ActionType &action, const char *const messages[], int objNum) { - Events &events = *_vm->_events; - People &people = *_vm->_people; - Scene &scene = *_vm->_scene; - Screen &screen = *_vm->_screen; - Talk &talk = *_vm->_talk; - Common::Point pt(-1, -1); - - if (objNum >= 1000) - // Ignore actions done on characters - return; - - if (!action._cAnimSpeed) { - // Invalid action, to print error message - _infoFlag = true; - clearInfo(); - screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", messages[action._cAnimNum]); - _infoFlag = true; - - // Set how long to show the message - _menuCounter = 30; - } else { - Object &obj = scene._bgShapes[objNum]; - - int cAnimNum; - if (action._cAnimNum == 0) - // Really a 10 - cAnimNum = 9; - else - cAnimNum = action._cAnimNum - 1; - - int dir = -1; - if (action._cAnimNum != 99) { - CAnim &anim = scene._cAnim[cAnimNum]; - - if (action._cAnimNum != 99) { - if (action._cAnimSpeed & REVERSE_DIRECTION) { - pt = anim._teleportPos; - dir = anim._teleportDir; - } else { - pt = anim._goto; - dir = anim._gotoDir; - } - } - } else { - pt = Common::Point(-1, -1); - dir = -1; - } - - // Has a value, so do action - // Show wait cursor whilst walking to object and doing action - events.setCursor(WAIT); - bool printed = false; - - for (int nameIdx = 0; nameIdx < NAMES_COUNT; ++nameIdx) { - if (action._names[nameIdx].hasPrefix("*") && action._names[nameIdx].size() >= 2 - && toupper(action._names[nameIdx][1]) == 'W') { - if (obj.checkNameForCodes(Common::String(action._names[nameIdx].c_str() + 2), messages)) { - if (!talk._talkToAbort) - printed = true; - } - } - } - - bool doCAnim = true; - for (int nameIdx = 0; nameIdx < NAMES_COUNT; ++nameIdx) { - if (action._names[nameIdx].hasPrefix("*") && action._names[nameIdx].size() >= 2) { - char ch = toupper(action._names[nameIdx][1]); - - if (ch == 'T' || ch == 'B') { - printed = true; - if (pt.x != -1) - // Holmes needs to walk to object before the action is done - people.walkToCoords(pt, dir); - - if (!talk._talkToAbort) { - // Ensure Holmes is on the exact intended location - people[AL]._position = pt; - people[AL]._sequenceNumber = dir; - people.gotoStand(people[AL]); - - talk.talkTo(action._names[nameIdx].c_str() + 2); - if (ch == 'T') - doCAnim = false; - } - } - } - } - - if (doCAnim && !talk._talkToAbort) { - if (pt.x != -1) - // Holmes needs to walk to object before the action is done - people.walkToCoords(pt, dir); - } - - for (int nameIdx = 0; nameIdx < NAMES_COUNT; ++nameIdx) { - if (action._names[nameIdx].hasPrefix("*") && action._names[nameIdx].size() >= 2 - && toupper(action._names[nameIdx][1]) == 'F') { - if (obj.checkNameForCodes(action._names[nameIdx].c_str() + 2, messages)) { - if (!talk._talkToAbort) - printed = true; - } - } - } - - if (doCAnim && !talk._talkToAbort && action._cAnimNum != 99) - scene.startCAnim(cAnimNum, action._cAnimSpeed); - - if (!talk._talkToAbort) { - for (int nameIdx = 0; nameIdx < NAMES_COUNT && !talk._talkToAbort; ++nameIdx) { - if (obj.checkNameForCodes(action._names[nameIdx], messages)) { - if (!talk._talkToAbort) - printed = true; - } - } - - // Unless we're leaving the scene, print a "Done" message unless the printed flag has been set - if (scene._goToScene != 1 && !printed && !talk._talkToAbort) { - _infoFlag = true; - clearInfo(); - screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "Done..."); - - // Set how long to show the message - _menuCounter = 30; - } - } +void ScalpelUserInterface::offsetButton3DO(Common::Point &pt, int num) { + if (IS_3DO) { + if (num >= 0 && num <= 2) + pt.x += 15; + else if (num >= 6 && num <= 8) + pt.x -= 4; + else if (num >= 9 && num <= 11) + pt.x -= 8; } - - // Reset cursor back to arrow - events.setCursor(ARROW); } } // End of namespace Scalpel diff --git a/engines/sherlock/scalpel/scalpel_user_interface.h b/engines/sherlock/scalpel/scalpel_user_interface.h index 552bcf00c5..9a55189a66 100644 --- a/engines/sherlock/scalpel/scalpel_user_interface.h +++ b/engines/sherlock/scalpel/scalpel_user_interface.h @@ -49,9 +49,8 @@ class ScalpelUserInterface: public UserInterface { private: char _keyPress; int _lookHelp; - int _bgFound, _oldBgFound; int _help, _oldHelp; - char _key, _oldKey; + int _key, _oldKey; int _temp, _oldTemp; int _oldLook; bool _keyboardInput; @@ -85,7 +84,7 @@ private: void lookScreen(const Common::Point &pt); /** - * Gets the item in the inventory the mouse is on and display's it's description + * Gets the item in the inventory the mouse is on and display's its description */ void lookInv(); @@ -136,15 +135,10 @@ private: /** * Checks to see whether a USE action is valid on the given object */ - void checkUseAction(const UseType *use, const Common::String &invName, const char *const messages[], + void checkUseAction(const UseType *use, const Common::String &invName, FixedTextActionId fixedTextActionId, int objNum, bool giveMode); /** - * Called for OPEN, CLOSE, and MOVE actions are being done - */ - void checkAction(ActionType &action, const char *const messages[], int objNum); - - /** * Print the previously selected object's decription */ void printObjectDesc(const Common::String &str, bool firstTime); @@ -172,6 +166,8 @@ public: * of the highlighted object */ void examine(); + + void offsetButton3DO(Common::Point &pt, int num); public: /** * Resets the user interface diff --git a/engines/sherlock/scalpel/settings.cpp b/engines/sherlock/scalpel/settings.cpp index aa8033d25e..f6769a4b99 100644 --- a/engines/sherlock/scalpel/settings.cpp +++ b/engines/sherlock/scalpel/settings.cpp @@ -22,7 +22,9 @@ #include "sherlock/sherlock.h" #include "sherlock/scalpel/settings.h" +#include "sherlock/scalpel/scalpel_screen.h" #include "sherlock/scalpel/scalpel_user_interface.h" +#include "sherlock/scalpel/scalpel.h" namespace Sherlock { @@ -56,7 +58,7 @@ static const char *const SETUP_NAMES[12] = { void Settings::drawInteface(bool flag) { People &people = *_vm->_people; - Screen &screen = *_vm->_screen; + ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen; Sound &sound = *_vm->_sound; Music &music = *_vm->_music; UserInterface &ui = *_vm->_ui; @@ -138,7 +140,7 @@ void Settings::drawInteface(bool flag) { int Settings::drawButtons(const Common::Point &pt, int _key) { Events &events = *_vm->_events; People &people = *_vm->_people; - Screen &screen = *_vm->_screen; + ScalpelScreen &screen = *(ScalpelScreen *)_vm->_screen; Music &music = *_vm->_music; Sound &sound = *_vm->_sound; UserInterface &ui = *_vm->_ui; diff --git a/engines/sherlock/scalpel/tsage/logo.cpp b/engines/sherlock/scalpel/tsage/logo.cpp index 239543b017..64539b941a 100644 --- a/engines/sherlock/scalpel/tsage/logo.cpp +++ b/engines/sherlock/scalpel/tsage/logo.cpp @@ -156,11 +156,13 @@ ScalpelEngine *Object::_vm; Object::Object() { _vm = nullptr; - _isAnimating = false; + _isAnimating = _finished = false; _frame = 0; _numFrames = 0; _frameChange = 0; _angle = _changeCtr = 0; + _walkStartFrame = 0; + _majorDiff = _minorDiff = 0; } void Object::setVisage(int visage, int strip) { @@ -221,8 +223,15 @@ void Object::update() { Screen &screen = *_vm->_screen; if (_visage.isLoaded()) { - if (isMoving()) - move(); + if (isMoving()) { + uint32 currTime = _vm->_events->getFrameCounter(); + if (_walkStartFrame <= currTime) { + int moveRate = 10; + int frameInc = 60 / moveRate; + _walkStartFrame = currTime + frameInc; + move(); + } + } if (_isAnimating) { if (_frame < _visage.getFrameCount()) @@ -304,7 +313,6 @@ bool Object::isMoving() const { void Object::move() { Common::Point currPos = _position; Common::Point moveDiff(5, 3); - int yDiff = 0; int percent = 100; if (dontMove()) @@ -335,9 +343,7 @@ void Object::move() { currPos.y += ySign; _majorDiff -= ABS(xAmount); - - } - else { + } else { int yAmount = _moveSign.y * moveDiff.y * percent / 100; if (!yAmount) yAmount = _moveSign.y; @@ -387,15 +393,16 @@ bool Logo::show(ScalpelEngine *vm) { while (!logo->finished()) { logo->nextFrame(); - events.wait(2); - events.setButtonState(); - // Erase areas from previous frame, and update and re-draw objects for (int idx = 0; idx < 4; ++idx) logo->_objects[idx].erase(); for (int idx = 0; idx < 4; ++idx) logo->_objects[idx].update(); + events.delay(10); + events.setButtonState(); + ++logo->_frameCounter; + interrupted = vm->shouldQuit() || events.kbHit() || events._pressed; if (interrupted) { // Keyboard or mouse button pressed, so break out of logo display @@ -412,8 +419,21 @@ Logo::Logo(ScalpelEngine *vm) : _vm(vm), _lib("sf3.rlb") { Object::_vm = vm; Visage::_tLib = &_lib; + _finished = false; + // Initialize counter _counter = 0; + + // Initialize wait frame counters + _waitFrames = 0; + _waitStartFrame = 0; + + // Initialize animation counters + _animateObject = 0; + _animateStartFrame = 0; + _animateFrameDelay = 0; + _animateFrames = NULL; + _animateFrame = 0; // Save a copy of the original palette _vm->_screen->getPalette(_originalPalette); @@ -437,12 +457,55 @@ Logo::~Logo() { } bool Logo::finished() const { - return _counter >= 442; + return _finished; } +const AnimationFrame handFrames[] = { + { 1, 33, 91 }, { 2, 44, 124 }, { 3, 64, 153 }, { 4, 87, 174 }, + { 5, 114, 191 }, { 6, 125, 184 }, { 7, 154, 187 }, { 8, 181, 182 }, + { 9, 191, 167 }, { 10, 190, 150 }, { 11, 182, 139 }, { 11, 170, 130 }, + { 11, 158, 121 }, { 0, 0, 0 } +}; + +const AnimationFrame companyFrames[] = { + { 1, 155, 94 }, { 2, 155, 94 }, { 3, 155, 94 }, { 4, 155, 94 }, + { 5, 155, 94 }, { 6, 155, 94 }, { 7, 155, 94 }, { 8, 155, 94 }, + { 0, 0, 0 } +}; + void Logo::nextFrame() { Screen &screen = *_vm->_screen; + if (_waitFrames) { + uint32 currFrame = _frameCounter; + if (currFrame - _waitStartFrame < _waitFrames) { + return; + } + _waitStartFrame = 0; + _waitFrames = 0; + } + + if (_animateFrames) { + uint32 currFrame = _frameCounter; + if (currFrame > _animateStartFrame + _animateFrameDelay) { + AnimationFrame animationFrame = _animateFrames[_animateFrame]; + if (animationFrame.frame) { + _objects[_animateObject]._frame = animationFrame.frame; + _objects[_animateObject]._position = Common::Point(animationFrame.x, animationFrame.y); + _animateStartFrame += _animateFrameDelay; + _animateFrame++; + } else { + _animateObject = 0; + _animateFrameDelay = 0; + _animateFrames = NULL; + _animateStartFrame = 0; + _animateFrame = 0; + } + } + if (_animateFrames) + return; + } + switch (_counter++) { case 0: // Load the background and fade it in @@ -480,160 +543,87 @@ void Logo::nextFrame() { // Fade out the background but keep the shapes visible fade(_palette2); screen._backBuffer1.clear(); - screen.clear(); } + waitFrames(10); break; - case 13: { + case 4: // Load the new palette byte palette[PALETTE_SIZE]; Common::copy(&_palette2[0], &_palette2[PALETTE_SIZE], &palette[0]); _lib.getPalette(palette, 12); + screen.clear(); screen.setPalette(palette); - break; - } - case 14: + // Morph into the EA logo _objects[0].setVisage(12, 1); _objects[0]._frame = 1; _objects[0]._numFrames = 7; + _objects[0].setAnimMode(true); _objects[0]._position = Common::Point(170, 142); _objects[0].setDestination(Common::Point(158, 71)); break; - case 15: + case 5: // Wait until the logo has expanded upwards to form EA logo if (_objects[0].isMoving()) --_counter; break; - case 16: + case 6: fade(_palette3, 40); break; - case 20: + case 7: // Show the 'Electronic Arts' company name _objects[1].setVisage(14, 1); _objects[1]._frame = 1; _objects[1]._position = Common::Point(152, 98); + waitFrames(120); break; - case 140: + case 8: // Start sequence of positioning and size hand cursor in an arc _objects[2].setVisage(18, 1); - _objects[2]._frame = 1; - _objects[2]._position = Common::Point(33, 91); - break; - - case 145: - _objects[2]._frame = 2; - _objects[2]._position = Common::Point(44, 124); - break; - - case 150: - _objects[2]._frame = 3; - _objects[2]._position = Common::Point(64, 153); - break; - - case 155: - _objects[2]._frame = 4; - _objects[2]._position = Common::Point(87, 174); - break; - - case 160: - _objects[2]._frame = 5; - _objects[2]._position = Common::Point(114, 191); - break; - - case 165: - _objects[2]._frame = 6; - _objects[2]._position = Common::Point(125, 184); - break; - - case 170: - _objects[2]._frame = 7; - _objects[2]._position = Common::Point(154, 187); - break; - - case 175: - _objects[2]._frame = 8; - _objects[2]._position = Common::Point(181, 182); - break; - - case 180: - _objects[2]._frame = 9; - _objects[2]._position = Common::Point(191, 167); - break; - - case 185: - _objects[2]._frame = 10; - _objects[2]._position = Common::Point(190, 150); - break; - - case 190: - _objects[2]._frame = 11; - _objects[2]._position = Common::Point(182, 139); - break; - - case 195: - _objects[2]._frame = 11; - _objects[2]._position = Common::Point(170, 130); - break; - - case 200: - _objects[2]._frame = 11; - _objects[2]._position = Common::Point(158, 121); + startAnimation(2, 5, &handFrames[0]); break; - case 205: + case 9: // Show a highlighting of the company name + _objects[1].remove(); + _objects[2].erase(); _objects[2].remove(); _objects[3].setVisage(19, 1); - _objects[3]._position = Common::Point(155, 94); + startAnimation(3, 8, &companyFrames[0]); break; - case 213: - _objects[3]._frame = 2; - _objects[3]._position = Common::Point(155, 94); + case 10: + waitFrames(180); break; - case 221: - _objects[1].remove(); + case 11: + _finished = true; break; - case 222: - _objects[3]._frame = 3; - _objects[3]._position = Common::Point(155, 94); - break; - - case 230: - _objects[3]._frame = 4; - _objects[3]._position = Common::Point(155, 94); - break; - - case 238: - _objects[3]._frame = 5; - _objects[3]._position = Common::Point(155, 94); - break; - - case 246: - _objects[3]._frame = 6; - _objects[3]._position = Common::Point(155, 94); + default: break; + } +} - case 254: - _objects[3]._frame = 7; - _objects[3]._position = Common::Point(155, 94); - break; +void Logo::waitFrames(uint frames) { + _waitFrames = frames; + _waitStartFrame = _frameCounter; +} - case 262: - _objects[3]._frame = 8; - _objects[3]._position = Common::Point(155, 94); - break; +void Logo::startAnimation(uint object, uint frameDelay, const AnimationFrame *frames) { + _animateObject = object; + _animateFrameDelay = frameDelay; + _animateFrames = frames; + _animateStartFrame = _frameCounter; + _animateFrame = 1; - default: - break; - } + _objects[object]._frame = frames[0].frame; + _objects[object]._position = Common::Point(frames[0].x, frames[0].y); } void Logo::loadBackground() { diff --git a/engines/sherlock/scalpel/tsage/logo.h b/engines/sherlock/scalpel/tsage/logo.h index 68b7353a15..8e47ea42a1 100644 --- a/engines/sherlock/scalpel/tsage/logo.h +++ b/engines/sherlock/scalpel/tsage/logo.h @@ -185,16 +185,30 @@ public: void remove() { _visage.clear(); } }; +struct AnimationFrame { + int frame; + int x; + int y; +}; + class Logo { private: ScalpelEngine *_vm; TLib _lib; - int _counter; + int _counter, _frameCounter; + bool _finished; byte _originalPalette[PALETTE_SIZE]; byte _palette1[PALETTE_SIZE]; byte _palette2[PALETTE_SIZE]; byte _palette3[PALETTE_SIZE]; Object _objects[4]; + uint _waitFrames; + uint32 _waitStartFrame; + int _animateObject; + uint32 _animateStartFrame; + uint _animateFrameDelay; + const AnimationFrame *_animateFrames; + uint _animateFrame; Logo(ScalpelEngine *vm); ~Logo(); @@ -204,6 +218,19 @@ private: bool finished() const; /** + * Wait for a number of frames. Note that the frame count in _events is + * not the same as the number of calls to nextFrame(). + */ + void waitFrames(uint frames); + + /** + * Start an animation sequence. Used for sequences that are described + * one frame at a time because they do unusual things, or run at + * unusual rates. + */ + void startAnimation(uint object, uint frameDelay, const AnimationFrame *frames); + + /** * Load the background for the scene */ void loadBackground(); diff --git a/engines/sherlock/scene.cpp b/engines/sherlock/scene.cpp index 76fb7ae3a7..4e40032df9 100644 --- a/engines/sherlock/scene.cpp +++ b/engines/sherlock/scene.cpp @@ -22,21 +22,16 @@ #include "sherlock/scene.h" #include "sherlock/sherlock.h" +#include "sherlock/screen.h" #include "sherlock/scalpel/scalpel.h" +#include "sherlock/scalpel/scalpel_people.h" #include "sherlock/scalpel/scalpel_scene.h" -#include "sherlock/screen.h" #include "sherlock/tattoo/tattoo.h" #include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo_user_interface.h" namespace Sherlock { -static const int FS_TRANS[8] = { - STOP_UP, STOP_UPRIGHT, STOP_RIGHT, STOP_DOWNRIGHT, STOP_DOWN, - STOP_DOWNLEFT, STOP_LEFT, STOP_UPLEFT -}; - -/*----------------------------------------------------------------*/ - BgFileHeader::BgFileHeader() { _numStructs = -1; _numImages = -1; @@ -82,6 +77,16 @@ void BgFileHeaderInfo::load(Common::SeekableReadStream &s) { _filename = Common::String(buffer); } +void BgFileHeaderInfo::load3DO(Common::SeekableReadStream &s) { + _filesize = s.readUint32BE(); + _maxFrames = s.readByte(); + + char buffer[9]; + s.read(buffer, 9); + _filename = Common::String(buffer); + s.skip(2); // only on 3DO! +} + /*----------------------------------------------------------------*/ void Exit::load(Common::SeekableReadStream &s, bool isRoseTattoo) { @@ -102,14 +107,31 @@ void Exit::load(Common::SeekableReadStream &s, bool isRoseTattoo) { if (!isRoseTattoo) _allow = s.readSint16LE(); - _people.x = s.readSint16LE(); - _people.y = s.readSint16LE(); - _peopleDir = s.readUint16LE(); + _newPosition.x = s.readSint16LE(); + _newPosition.y = s.readSint16LE(); + _newPosition._facing = s.readUint16LE(); if (isRoseTattoo) _allow = s.readSint16LE(); } +void Exit::load3DO(Common::SeekableReadStream &s) { + left = s.readSint16BE(); + top = s.readSint16BE(); + setWidth(s.readUint16BE()); + setHeight(s.readUint16BE()); + + _image = 0; + _scene = s.readSint16BE(); + + _allow = s.readSint16BE(); + + _newPosition.x = s.readSint16BE(); + _newPosition.y = s.readSint16BE(); + _newPosition._facing = s.readUint16BE(); + s.skip(2); // Filler +} + /*----------------------------------------------------------------*/ void SceneEntry::load(Common::SeekableReadStream &s) { @@ -119,6 +141,13 @@ void SceneEntry::load(Common::SeekableReadStream &s) { _allow = s.readByte(); } +void SceneEntry::load3DO(Common::SeekableReadStream &s) { + _startPosition.x = s.readSint16BE(); + _startPosition.y = s.readSint16BE(); + _startDir = s.readByte(); + _allow = s.readByte(); +} + void SceneSound::load(Common::SeekableReadStream &s) { char buffer[9]; s.read(buffer, 8); @@ -128,6 +157,10 @@ void SceneSound::load(Common::SeekableReadStream &s) { _priority = s.readByte(); } +void SceneSound::load3DO(Common::SeekableReadStream &s) { + load(s); +} + /*----------------------------------------------------------------*/ int ObjectArray::indexOf(const Object &obj) const { @@ -153,6 +186,18 @@ void ScaleZone::load(Common::SeekableReadStream &s) { /*----------------------------------------------------------------*/ +void WalkArray::load(Common::SeekableReadStream &s, bool isRoseTattoo) { + _pointsCount = (int8)s.readByte(); + + for (int idx = 0; idx < _pointsCount; ++idx) { + int x = s.readSint16LE(); + int y = isRoseTattoo ? s.readSint16LE() : s.readByte(); + push_back(Common::Point(x, y)); + } +} + +/*----------------------------------------------------------------*/ + Scene *Scene::init(SherlockEngine *vm) { if (vm->getGameID() == GType_SerratedScalpel) return new Scalpel::ScalpelScene(vm); @@ -161,25 +206,32 @@ Scene *Scene::init(SherlockEngine *vm) { } Scene::Scene(SherlockEngine *vm): _vm(vm) { - for (int idx = 0; idx < SCENES_COUNT; ++idx) - Common::fill(&_sceneStats[idx][0], &_sceneStats[idx][65], false); + _sceneStats = new bool *[SCENES_COUNT]; + _sceneStats[0] = new bool[SCENES_COUNT * (MAX_BGSHAPES + 1)]; + Common::fill(&_sceneStats[0][0], &_sceneStats[0][SCENES_COUNT * (MAX_BGSHAPES + 1)], false); + for (int idx = 1; idx < SCENES_COUNT; ++idx) { + _sceneStats[idx] = _sceneStats[idx - 1] + (MAX_BGSHAPES + 1); + } + _currentScene = -1; _goToScene = -1; _loadingSavedGame = false; _walkedInScene = false; _version = 0; - _lzwMode = false; + _compressed = false; _invGraphicItems = 0; _cAnimFramePause = 0; _restoreFlag = false; _animating = 0; _doBgAnimDone = true; _tempFadeStyle = 0; - _exitZone = -1; + _doBgAnimDone = false; } Scene::~Scene() { freeScene(); + delete[] _sceneStats[0]; + delete[] _sceneStats; } void Scene::selectScene() { @@ -193,12 +245,10 @@ void Scene::selectScene() { ui._windowOpen = ui._infoFlag = false; ui._menuMode = STD_MODE; - // Free any previous scene - freeScene(); - // Load the scene Common::String sceneFile = Common::String::format("res%02d", _goToScene); - _rrmName = Common::String::format("res%02d.rrm", _goToScene); + // _rrmName gets set during loadScene() + // _rrmName is for ScalpelScene::startCAnim _currentScene = _goToScene; _goToScene = -1; @@ -210,8 +260,8 @@ void Scene::selectScene() { _tempFadeStyle = 0; } - people._walkDest = Common::Point(people[AL]._position.x / FIXED_INT_MULTIPLIER, - people[AL]._position.y / FIXED_INT_MULTIPLIER); + people[HOLMES]._walkDest = Common::Point(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER, + people[HOLMES]._position.y / FIXED_INT_MULTIPLIER); _restoreFlag = true; events.clearEvents(); @@ -226,7 +276,9 @@ void Scene::freeScene() { if (_currentScene == -1) return; + _vm->_ui->clearWindow(); _vm->_talk->freeTalkVars(); + _vm->_talk->clearSequences(); _vm->_inventory->freeInv(); _vm->_music->freeSong(); _vm->_sound->freeLoadedSounds(); @@ -238,7 +290,7 @@ void Scene::freeScene() { _sequenceBuffer.clear(); _descText.clear(); - _walkData.clear(); + _walkPoints.clear(); _cAnim.clear(); _bgShapes.clear(); _zones.clear(); @@ -253,14 +305,11 @@ void Scene::freeScene() { bool Scene::loadScene(const Common::String &filename) { Events &events = *_vm->_events; - Map &map = *_vm->_map; Music &music = *_vm->_music; People &people = *_vm->_people; Resources &res = *_vm->_res; SaveManager &saves = *_vm->_saves; Screen &screen = *_vm->_screen; - Sound &sound = *_vm->_sound; - Talk &talk = *_vm->_talk; UserInterface &ui = *_vm->_ui; bool flag; @@ -276,175 +325,492 @@ bool Scene::loadScene(const Common::String &filename) { _cAnim.clear(); _sequenceBuffer.clear(); - // Check if it's a scene we need to keep trakc track of how many times we've visited - for (int idx = (int)_sceneTripCounters.size() - 1; idx >= 0; --idx) { - if (_sceneTripCounters[idx]._sceneNumber == _currentScene) { - if (--_sceneTripCounters[idx]._numTimes == 0) { - _vm->setFlags(_sceneTripCounters[idx]._flag); - _sceneTripCounters.remove_at(idx); + // + // Load the room resource file for the scene + // + + if (!IS_3DO) { + // PC version + Common::String roomFilename = filename + ".rrm"; + _roomFilename = roomFilename; + + flag = _vm->_res->exists(roomFilename); + if (flag) { + Common::SeekableReadStream *rrmStream = _vm->_res->load(roomFilename); + + rrmStream->seek(39); + if (IS_SERRATED_SCALPEL) { + _version = rrmStream->readByte(); + _compressed = _version == 10; + } else { + _compressed = rrmStream->readByte() > 0; } - } - } - if (IS_ROSE_TATTOO) { - // Set the NPC paths for the scene - setNPCPath(0); - - // Handle loading music for the scene - if (sound._midiDrvLoaded) { - if (talk._scriptMoreFlag != 1 && talk._scriptMoreFlag != 3) - sound._nextSongName = Common::String::format("res%02d", _currentScene); - - // If it's a new song, then start it up - if (sound._currentSongName.compareToIgnoreCase(sound._nextSongName)) { - if (music.loadSong(sound._nextSongName)) { - sound.setMIDIVolume(sound._musicVolume); - if (music._musicOn) - music.startSong(); + // Go to header and read it in + rrmStream->seek(rrmStream->readUint32LE()); + + BgFileHeader bgHeader; + bgHeader.load(*rrmStream, IS_ROSE_TATTOO); + _invGraphicItems = bgHeader._numImages + 1; + + if (IS_ROSE_TATTOO) { + // Resize the screen if necessary + int fullWidth = SHERLOCK_SCREEN_WIDTH + bgHeader._scrollSize; + if (screen._backBuffer1.w() != fullWidth) { + screen._backBuffer1.create(fullWidth, SHERLOCK_SCREEN_HEIGHT); + screen._backBuffer2.create(fullWidth, SHERLOCK_SCREEN_HEIGHT); } - } - } - } - // - // Load the room resource file for the scene - // + // Handle initializing the palette + screen.initPaletteFade(bgHeader._bytesWritten); + rrmStream->read(screen._cMap, PALETTE_SIZE); + paletteLoaded(); + screen.translatePalette(screen._cMap); - Common::String rrmFile = filename + ".rrm"; - flag = _vm->_res->exists(rrmFile); - if (flag) { - Common::SeekableReadStream *rrmStream = _vm->_res->load(rrmFile); + // Read in background + if (_compressed) { + res.decompress(*rrmStream, (byte *)screen._backBuffer1.getPixels(), fullWidth * SHERLOCK_SCREEN_HEIGHT); + } else { + rrmStream->read(screen._backBuffer1.getPixels(), fullWidth * SHERLOCK_SCREEN_HEIGHT); + } + } - rrmStream->seek(39); - if (IS_SERRATED_SCALPEL) { - _version = rrmStream->readByte(); - _lzwMode = _version == 10; - } else { - _lzwMode = rrmStream->readByte() > 0; - } + // Read in the shapes header info + Common::Array<BgFileHeaderInfo> bgInfo; + bgInfo.resize(bgHeader._numStructs); - // Go to header and read it in - rrmStream->seek(rrmStream->readUint32LE()); + for (uint idx = 0; idx < bgInfo.size(); ++idx) + bgInfo[idx].load(*rrmStream); - BgFileHeader bgHeader; - bgHeader.load(*rrmStream, IS_ROSE_TATTOO); - _invGraphicItems = bgHeader._numImages + 1; + // Read information + if (IS_ROSE_TATTOO) { + // Load shapes + Common::SeekableReadStream *infoStream = !_compressed ? rrmStream : res.decompress(*rrmStream, bgHeader._numStructs * 625); - if (IS_ROSE_TATTOO) { - screen.initPaletteFade(bgHeader._bytesWritten); - rrmStream->read(screen._cMap, PALETTE_SIZE); - screen.translatePalette(screen._cMap); - screen.setupBGArea(screen._cMap); + _bgShapes.resize(bgHeader._numStructs); + for (int idx = 0; idx < bgHeader._numStructs; ++idx) + _bgShapes[idx].load(*infoStream, true); - screen.initScrollVars(); + if (_compressed) + delete infoStream; - // Read in background - if (_lzwMode) { - res.decompress(*rrmStream, (byte *)screen._backBuffer1.getPixels(), SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCREEN_HEIGHT); + // Load description text + _descText.resize(bgHeader._descSize); + if (_compressed) + res.decompress(*rrmStream, (byte *)&_descText[0], bgHeader._descSize); + else + rrmStream->read(&_descText[0], bgHeader._descSize); + + // Load sequences + _sequenceBuffer.resize(bgHeader._seqSize); + if (_compressed) + res.decompress(*rrmStream, &_sequenceBuffer[0], bgHeader._seqSize); + else + rrmStream->read(&_sequenceBuffer[0], bgHeader._seqSize); + } else if (!_compressed) { + // Serrated Scalpel uncompressed info + _bgShapes.resize(bgHeader._numStructs); + for (int idx = 0; idx < bgHeader._numStructs; ++idx) + _bgShapes[idx].load(*rrmStream, false); + + if (bgHeader._descSize) { + _descText.resize(bgHeader._descSize); + rrmStream->read(&_descText[0], bgHeader._descSize); + } + + if (bgHeader._seqSize) { + _sequenceBuffer.resize(bgHeader._seqSize); + rrmStream->read(&_sequenceBuffer[0], bgHeader._seqSize); + } } else { - rrmStream->read(screen._backBuffer1.getPixels(), SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCREEN_HEIGHT); + // Serrated Scalpel compressed info + Common::SeekableReadStream *infoStream; + + // Read shapes + infoStream = Resources::decompressLZ(*rrmStream, bgHeader._numStructs * 569); + + _bgShapes.resize(bgHeader._numStructs); + for (int idx = 0; idx < bgHeader._numStructs; ++idx) + _bgShapes[idx].load(*infoStream, false); + + delete infoStream; + + // Read description texts + if (bgHeader._descSize) { + infoStream = Resources::decompressLZ(*rrmStream, bgHeader._descSize); + + _descText.resize(bgHeader._descSize); + infoStream->read(&_descText[0], bgHeader._descSize); + + delete infoStream; + } + + // Read sequences + if (bgHeader._seqSize) { + infoStream = Resources::decompressLZ(*rrmStream, bgHeader._seqSize); + + _sequenceBuffer.resize(bgHeader._seqSize); + infoStream->read(&_sequenceBuffer[0], bgHeader._seqSize); + + delete infoStream; + } } - } - // Read in the shapes header info - Common::Array<BgFileHeaderInfo> bgInfo; - bgInfo.resize(bgHeader._numStructs); + // Set up the list of images used by the scene + _images.resize(bgHeader._numImages + 1); + for (int idx = 0; idx < bgHeader._numImages; ++idx) { + _images[idx + 1]._filesize = bgInfo[idx]._filesize; + _images[idx + 1]._maxFrames = bgInfo[idx]._maxFrames; - for (uint idx = 0; idx < bgInfo.size(); ++idx) - bgInfo[idx].load(*rrmStream); + // Read in the image data + Common::SeekableReadStream *imageStream = _compressed ? + res.decompress(*rrmStream, bgInfo[idx]._filesize) : + rrmStream->readStream(bgInfo[idx]._filesize); + + _images[idx + 1]._images = new ImageFile(*imageStream); - // Read information - if (IS_ROSE_TATTOO) { - // Load shapes - Common::SeekableReadStream *infoStream = !_lzwMode ? rrmStream : res.decompress(*rrmStream, bgHeader._numStructs * 625); + delete imageStream; + } - _bgShapes.resize(bgHeader._numStructs); - for (int idx = 0; idx < bgHeader._numStructs; ++idx) - _bgShapes[idx].load(*infoStream, _vm->getGameID() == GType_RoseTattoo); + // Set up the bgShapes + for (int idx = 0; idx < bgHeader._numStructs; ++idx) { + _bgShapes[idx]._images = _images[_bgShapes[idx]._misc]._images; + _bgShapes[idx]._imageFrame = !_bgShapes[idx]._images ? (ImageFrame *)nullptr : + &(*_bgShapes[idx]._images)[0]; + + _bgShapes[idx]._examine = Common::String(&_descText[_bgShapes[idx]._descOffset]); + _bgShapes[idx]._sequences = &_sequenceBuffer[_bgShapes[idx]._sequenceOffset]; + _bgShapes[idx]._misc = 0; + _bgShapes[idx]._seqCounter = 0; + _bgShapes[idx]._seqCounter2 = 0; + _bgShapes[idx]._seqStack = 0; + _bgShapes[idx]._frameNumber = -1; + _bgShapes[idx]._oldPosition = Common::Point(0, 0); + _bgShapes[idx]._oldSize = Common::Point(1, 1); + } - if (_lzwMode) - delete infoStream; + // Load in cAnim list + _cAnim.clear(); + if (bgHeader._numcAnimations) { + int animSize = IS_SERRATED_SCALPEL ? 65 : 47; + Common::SeekableReadStream *cAnimStream = _compressed ? + res.decompress(*rrmStream, animSize * bgHeader._numcAnimations) : + rrmStream->readStream(animSize * bgHeader._numcAnimations); + + // Load cAnim offset table as well + uint32 *cAnimOffsetTablePtr = new uint32[bgHeader._numcAnimations]; + uint32 *cAnimOffsetPtr = cAnimOffsetTablePtr; + memset(cAnimOffsetTablePtr, 0, bgHeader._numcAnimations * sizeof(uint32)); + if (IS_SERRATED_SCALPEL) { + // Save current stream offset + int32 curOffset = rrmStream->pos(); + rrmStream->seek(44); // Seek to cAnim-Offset-Table + for (uint16 curCAnim = 0; curCAnim < bgHeader._numcAnimations; curCAnim++) { + *cAnimOffsetPtr = rrmStream->readUint32LE(); + cAnimOffsetPtr++; + } + // Seek back to original stream offset + rrmStream->seek(curOffset); + } + // TODO: load offset table for Rose Tattoo as well - // Load description text - _descText.resize(bgHeader._descSize); - if (_lzwMode) - res.decompress(*rrmStream, (byte *)&_descText[0], bgHeader._descSize); - else - rrmStream->read(&_descText[0], bgHeader._descSize); - - // Load sequences - _sequenceBuffer.resize(bgHeader._seqSize); - if (_lzwMode) - res.decompress(*rrmStream, &_sequenceBuffer[0], bgHeader._seqSize); - else - rrmStream->read(&_sequenceBuffer[0], bgHeader._seqSize); - } else if (!_lzwMode) { - // Serrated Scalpel uncompressed info - _bgShapes.resize(bgHeader._numStructs); - for (int idx = 0; idx < bgHeader._numStructs; ++idx) - _bgShapes[idx].load(*rrmStream, false); - - if (bgHeader._descSize) { - _descText.resize(bgHeader._descSize); - rrmStream->read(&_descText[0], bgHeader._descSize); + // Go to the start of the cAnimOffsetTable + cAnimOffsetPtr = cAnimOffsetTablePtr; + + _cAnim.resize(bgHeader._numcAnimations); + for (uint idx = 0; idx < _cAnim.size(); ++idx) { + _cAnim[idx].load(*cAnimStream, IS_ROSE_TATTOO, *cAnimOffsetPtr); + cAnimOffsetPtr++; + } + + delete cAnimStream; + delete[] cAnimOffsetTablePtr; } - if (bgHeader._seqSize) { - _sequenceBuffer.resize(bgHeader._seqSize); - rrmStream->read(&_sequenceBuffer[0], bgHeader._seqSize); + + + // Read in the room bounding areas + int size = rrmStream->readUint16LE(); + Common::SeekableReadStream *boundsStream = !_compressed ? rrmStream : + res.decompress(*rrmStream, size); + + _zones.resize(size / 10); + for (uint idx = 0; idx < _zones.size(); ++idx) { + _zones[idx].left = boundsStream->readSint16LE(); + _zones[idx].top = boundsStream->readSint16LE(); + _zones[idx].setWidth(boundsStream->readSint16LE() + 1); + _zones[idx].setHeight(boundsStream->readSint16LE() + 1); + boundsStream->skip(2); // Skip unused scene number field } - } else { - // Serrated Scalpel compressed info - Common::SeekableReadStream *infoStream; - // Read shapes - infoStream = Resources::decompressLZ(*rrmStream, bgHeader._numStructs * 569); + if (_compressed) + delete boundsStream; - _bgShapes.resize(bgHeader._numStructs); - for (int idx = 0; idx < bgHeader._numStructs; ++idx) - _bgShapes[idx].load(*infoStream, false); + // Ensure we've reached the path version byte + if (rrmStream->readByte() != (IS_SERRATED_SCALPEL ? 254 : 251)) + error("Invalid scene path data"); - delete infoStream; + // Load the walk directory and walk data + assert(_zones.size() < MAX_ZONES); - // Read description texts - if (bgHeader._descSize) { - infoStream = Resources::decompressLZ(*rrmStream, bgHeader._descSize); - _descText.resize(bgHeader._descSize); - infoStream->read(&_descText[0], bgHeader._descSize); + for (uint idx1 = 0; idx1 < _zones.size(); ++idx1) { + Common::fill(&_walkDirectory[idx1][0], &_walkDirectory[idx1][MAX_ZONES], 0); + for (uint idx2 = 0; idx2 < _zones.size(); ++idx2) + _walkDirectory[idx1][idx2] = rrmStream->readSint16LE(); + } - delete infoStream; + // Read in the walk data + size = rrmStream->readUint16LE(); + Common::SeekableReadStream *walkStream = !_compressed ? rrmStream : + res.decompress(*rrmStream, size); + + int startPos = walkStream->pos(); + while ((walkStream->pos() - startPos) < size) { + _walkPoints.push_back(WalkArray()); + _walkPoints[_walkPoints.size() - 1]._fileOffset = walkStream->pos() - startPos; + _walkPoints[_walkPoints.size() - 1].load(*walkStream, IS_ROSE_TATTOO); } - // Read sequences - if (bgHeader._seqSize) { - infoStream = Resources::decompressLZ(*rrmStream, bgHeader._seqSize); + if (_compressed) + delete walkStream; + + // Translate the file offsets of the walk directory to indexes in the loaded walk data + for (uint idx1 = 0; idx1 < _zones.size(); ++idx1) { + for (uint idx2 = 0; idx2 < _zones.size(); ++idx2) { + int fileOffset = _walkDirectory[idx1][idx2]; + if (fileOffset == -1) + continue; + + uint dataIndex = 0; + while (dataIndex < _walkPoints.size() && _walkPoints[dataIndex]._fileOffset != fileOffset) + ++dataIndex; + assert(dataIndex < _walkPoints.size()); + _walkDirectory[idx1][idx2] = dataIndex; + } + } - _sequenceBuffer.resize(bgHeader._seqSize); - infoStream->read(&_sequenceBuffer[0], bgHeader._seqSize); + if (IS_ROSE_TATTOO) { + // Read in the entrance + _entrance.load(*rrmStream); - delete infoStream; + // Load scale zones + _scaleZones.resize(rrmStream->readByte()); + for (uint idx = 0; idx < _scaleZones.size(); ++idx) + _scaleZones[idx].load(*rrmStream); + } + + // Read in the exits + int numExits = rrmStream->readByte(); + _exits.resize(numExits); + + for (int idx = 0; idx < numExits; ++idx) + _exits[idx].load(*rrmStream, IS_ROSE_TATTOO); + + if (IS_SERRATED_SCALPEL) + // Read in the entrance + _entrance.load(*rrmStream); + + // Initialize sound list + int numSounds = rrmStream->readByte(); + _sounds.resize(numSounds); + + for (int idx = 0; idx < numSounds; ++idx) + _sounds[idx].load(*rrmStream); + + loadSceneSounds(); + + if (IS_ROSE_TATTOO) { + // Load the object sound list + char buffer[27]; + + _objSoundList.resize(rrmStream->readUint16LE()); + for (uint idx = 0; idx < _objSoundList.size(); ++idx) { + rrmStream->read(buffer, 27); + _objSoundList[idx] = Common::String(buffer); + } + } else { + // Read in palette + rrmStream->read(screen._cMap, PALETTE_SIZE); + screen.translatePalette(screen._cMap); + Common::copy(screen._cMap, screen._cMap + PALETTE_SIZE, screen._sMap); + + // Read in the background + Common::SeekableReadStream *bgStream = !_compressed ? rrmStream : + res.decompress(*rrmStream, SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCENE_HEIGHT); + + bgStream->read(screen._backBuffer1.getPixels(), SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCENE_HEIGHT); + + if (_compressed) + delete bgStream; } + + // Backup the image and set the palette + screen._backBuffer2.blitFrom(screen._backBuffer1); + screen.setPalette(screen._cMap); + + delete rrmStream; } - // Set up the list of images used by the scene - _images.resize(bgHeader._numImages + 1); - for (int idx = 0; idx < bgHeader._numImages; ++idx) { + } else { + // === 3DO version === + _roomFilename = "rooms/" + filename + ".rrm"; + flag = _vm->_res->exists(_roomFilename); + if (!flag) + error("loadScene: 3DO room data file not found"); + + Common::SeekableReadStream *roomStream = _vm->_res->load(_roomFilename); + uint32 roomStreamSize = roomStream->size(); + + // there should be at least all bytes of the header data + if (roomStreamSize < 128) + error("loadScene: 3DO room data file is too small"); + + // Read 3DO header + roomStream->skip(4); // UINT32: offset graphic data? + uint16 header3DO_numStructs = roomStream->readUint16BE(); + uint16 header3DO_numImages = roomStream->readUint16BE(); + uint16 header3DO_numAnimations = roomStream->readUint16BE(); + roomStream->skip(6); + + uint32 header3DO_bgInfo_offset = roomStream->readUint32BE() + 0x80; + uint32 header3DO_bgInfo_size = roomStream->readUint32BE(); + uint32 header3DO_bgShapes_offset = roomStream->readUint32BE() + 0x80; + uint32 header3DO_bgShapes_size = roomStream->readUint32BE(); + uint32 header3DO_descriptions_offset = roomStream->readUint32BE() + 0x80; + uint32 header3DO_descriptions_size = roomStream->readUint32BE(); + uint32 header3DO_sequence_offset = roomStream->readUint32BE() + 0x80; + uint32 header3DO_sequence_size = roomStream->readUint32BE(); + uint32 header3DO_cAnim_offset = roomStream->readUint32BE() + 0x80; + uint32 header3DO_cAnim_size = roomStream->readUint32BE(); + uint32 header3DO_roomBounding_offset = roomStream->readUint32BE() + 0x80; + uint32 header3DO_roomBounding_size = roomStream->readUint32BE(); + uint32 header3DO_walkDirectory_offset = roomStream->readUint32BE() + 0x80; + uint32 header3DO_walkDirectory_size = roomStream->readUint32BE(); + uint32 header3DO_walkData_offset = roomStream->readUint32BE() + 0x80; + uint32 header3DO_walkData_size = roomStream->readUint32BE(); + uint32 header3DO_exits_offset = roomStream->readUint32BE() + 0x80; + uint32 header3DO_exits_size = roomStream->readUint32BE(); + uint32 header3DO_entranceData_offset = roomStream->readUint32BE() + 0x80; + uint32 header3DO_entranceData_size = roomStream->readUint32BE(); + uint32 header3DO_soundList_offset = roomStream->readUint32BE() + 0x80; + uint32 header3DO_soundList_size = roomStream->readUint32BE(); + //uint32 header3DO_unknown_offset = roomStream->readUint32BE() + 0x80; + //uint32 header3DO_unknown_size = roomStream->readUint32BE(); + roomStream->skip(8); // Skip over unknown offset+size + uint32 header3DO_bgGraphicData_offset = roomStream->readUint32BE() + 0x80; + uint32 header3DO_bgGraphicData_size = roomStream->readUint32BE(); + + // Calculate amount of entries + int32 header3DO_soundList_count = header3DO_soundList_size / 9; + + _invGraphicItems = header3DO_numImages + 1; + + // Verify all offsets + if (header3DO_bgInfo_offset >= roomStreamSize) + error("loadScene: 3DO bgInfo offset points outside of room file"); + if (header3DO_bgInfo_size > (roomStreamSize - header3DO_bgInfo_offset)) + error("loadScene: 3DO bgInfo size goes beyond room file"); + if (header3DO_bgShapes_offset >= roomStreamSize) + error("loadScene: 3DO bgShapes offset points outside of room file"); + if (header3DO_bgShapes_size > (roomStreamSize - header3DO_bgShapes_offset)) + error("loadScene: 3DO bgShapes size goes beyond room file"); + if (header3DO_descriptions_offset >= roomStreamSize) + error("loadScene: 3DO descriptions offset points outside of room file"); + if (header3DO_descriptions_size > (roomStreamSize - header3DO_descriptions_offset)) + error("loadScene: 3DO descriptions size goes beyond room file"); + if (header3DO_sequence_offset >= roomStreamSize) + error("loadScene: 3DO sequence offset points outside of room file"); + if (header3DO_sequence_size > (roomStreamSize - header3DO_sequence_offset)) + error("loadScene: 3DO sequence size goes beyond room file"); + if (header3DO_cAnim_offset >= roomStreamSize) + error("loadScene: 3DO cAnim offset points outside of room file"); + if (header3DO_cAnim_size > (roomStreamSize - header3DO_cAnim_offset)) + error("loadScene: 3DO cAnim size goes beyond room file"); + if (header3DO_roomBounding_offset >= roomStreamSize) + error("loadScene: 3DO roomBounding offset points outside of room file"); + if (header3DO_roomBounding_size > (roomStreamSize - header3DO_roomBounding_offset)) + error("loadScene: 3DO roomBounding size goes beyond room file"); + if (header3DO_walkDirectory_offset >= roomStreamSize) + error("loadScene: 3DO walkDirectory offset points outside of room file"); + if (header3DO_walkDirectory_size > (roomStreamSize - header3DO_walkDirectory_offset)) + error("loadScene: 3DO walkDirectory size goes beyond room file"); + if (header3DO_walkData_offset >= roomStreamSize) + error("loadScene: 3DO walkData offset points outside of room file"); + if (header3DO_walkData_size > (roomStreamSize - header3DO_walkData_offset)) + error("loadScene: 3DO walkData size goes beyond room file"); + if (header3DO_exits_offset >= roomStreamSize) + error("loadScene: 3DO exits offset points outside of room file"); + if (header3DO_exits_size > (roomStreamSize - header3DO_exits_offset)) + error("loadScene: 3DO exits size goes beyond room file"); + if (header3DO_entranceData_offset >= roomStreamSize) + error("loadScene: 3DO entranceData offset points outside of room file"); + if (header3DO_entranceData_size > (roomStreamSize - header3DO_entranceData_offset)) + error("loadScene: 3DO entranceData size goes beyond room file"); + if (header3DO_soundList_offset >= roomStreamSize) + error("loadScene: 3DO soundList offset points outside of room file"); + if (header3DO_soundList_size > (roomStreamSize - header3DO_soundList_offset)) + error("loadScene: 3DO soundList size goes beyond room file"); + if (header3DO_bgGraphicData_offset >= roomStreamSize) + error("loadScene: 3DO bgGraphicData offset points outside of room file"); + if (header3DO_bgGraphicData_size > (roomStreamSize - header3DO_bgGraphicData_offset)) + error("loadScene: 3DO bgGraphicData size goes beyond room file"); + + // === BGINFO === read in the shapes header info + Common::Array<BgFileHeaderInfo> bgInfo; + + uint32 expected3DO_bgInfo_size = header3DO_numStructs * 16; + if (expected3DO_bgInfo_size != header3DO_bgInfo_size) // Security check + error("loadScene: 3DO bgInfo size mismatch"); + + roomStream->seek(header3DO_bgInfo_offset); + bgInfo.resize(header3DO_numStructs); + for (uint idx = 0; idx < bgInfo.size(); ++idx) + bgInfo[idx].load3DO(*roomStream); + + // === BGSHAPES === read in the shapes info + uint32 expected3DO_bgShapes_size = header3DO_numStructs * 588; + if (expected3DO_bgShapes_size != header3DO_bgShapes_size) // Security check + error("loadScene: 3DO bgShapes size mismatch"); + + roomStream->seek(header3DO_bgShapes_offset); + _bgShapes.resize(header3DO_numStructs); + for (int idx = 0; idx < header3DO_numStructs; ++idx) + _bgShapes[idx].load3DO(*roomStream); + + // === DESCRIPTION === read description text + if (header3DO_descriptions_size) { + roomStream->seek(header3DO_descriptions_offset); + _descText.resize(header3DO_descriptions_size); + roomStream->read(&_descText[0], header3DO_descriptions_size); + } + + // === SEQUENCE === read sequence buffer + if (header3DO_sequence_size) { + roomStream->seek(header3DO_sequence_offset); + _sequenceBuffer.resize(header3DO_sequence_size); + roomStream->read(&_sequenceBuffer[0], header3DO_sequence_size); + } + + // === IMAGES === set up the list of images used by the scene + roomStream->seek(header3DO_bgGraphicData_offset); + _images.resize(header3DO_numImages + 1); + for (int idx = 0; idx < header3DO_numImages; ++idx) { _images[idx + 1]._filesize = bgInfo[idx]._filesize; _images[idx + 1]._maxFrames = bgInfo[idx]._maxFrames; - // Read in the image data - Common::SeekableReadStream *imageStream = _lzwMode ? - res.decompress(*rrmStream, bgInfo[idx]._filesize) : - rrmStream->readStream(bgInfo[idx]._filesize); + // Read image data into memory + Common::SeekableReadStream *imageStream = roomStream->readStream(bgInfo[idx]._filesize); - _images[idx + 1]._images = new ImageFile(*imageStream); + // Load image data into an ImageFile array as room file data + // which is basically a fixed header, followed by a raw cel header, followed by regular cel data + _images[idx + 1]._images = new ImageFile3DO(*imageStream, true); delete imageStream; } - // Set up the bgShapes - for (int idx = 0; idx < bgHeader._numStructs; ++idx) { + // === BGSHAPES === Set up the bgShapes + for (int idx = 0; idx < header3DO_numStructs; ++idx) { _bgShapes[idx]._images = _images[_bgShapes[idx]._misc]._images; _bgShapes[idx]._imageFrame = !_bgShapes[idx]._images ? (ImageFrame *)nullptr : &(*_bgShapes[idx]._images)[0]; @@ -460,121 +826,169 @@ bool Scene::loadScene(const Common::String &filename) { _bgShapes[idx]._oldSize = Common::Point(1, 1); } - // Load in cAnim list + // === CANIM === read cAnim list _cAnim.clear(); - if (bgHeader._numcAnimations) { - int animSize = IS_SERRATED_SCALPEL ? 65 : 47; - Common::SeekableReadStream *canimStream = _lzwMode ? - res.decompress(*rrmStream, animSize * bgHeader._numcAnimations) : - rrmStream->readStream(animSize * bgHeader._numcAnimations); + if (header3DO_numAnimations) { + roomStream->seek(header3DO_cAnim_offset); + Common::SeekableReadStream *cAnimStream = roomStream->readStream(header3DO_cAnim_size); + + uint32 *cAnimOffsetTablePtr = new uint32[header3DO_numAnimations]; + uint32 *cAnimOffsetPtr = cAnimOffsetTablePtr; + uint32 cAnimOffset = 0; + memset(cAnimOffsetTablePtr, 0, header3DO_numAnimations * sizeof(uint32)); + + // Seek to end of graphics data and load cAnim offset table from there + roomStream->seek(header3DO_bgGraphicData_offset + header3DO_bgGraphicData_size); + for (uint16 curCAnim = 0; curCAnim < header3DO_numAnimations; curCAnim++) { + cAnimOffset = roomStream->readUint32BE(); + if (cAnimOffset >= roomStreamSize) + error("loadScene: 3DO cAnim entry offset points outside of room file"); + + *cAnimOffsetPtr = cAnimOffset; + cAnimOffsetPtr++; + } - _cAnim.resize(bgHeader._numcAnimations); - for (uint idx = 0; idx < _cAnim.size(); ++idx) - _cAnim[idx].load(*canimStream, IS_ROSE_TATTOO); + // Go to the start of the cAnimOffsetTable + cAnimOffsetPtr = cAnimOffsetTablePtr; - delete canimStream; + _cAnim.resize(header3DO_numAnimations); + for (uint idx = 0; idx < _cAnim.size(); ++idx) { + _cAnim[idx].load3DO(*cAnimStream, *cAnimOffsetPtr); + cAnimOffsetPtr++; + } + + delete cAnimStream; + delete[] cAnimOffsetTablePtr; } - // Read in the room bounding areas - int size = rrmStream->readUint16LE(); - Common::SeekableReadStream *boundsStream = !_lzwMode ? rrmStream : - res.decompress(*rrmStream, size); + // === BOUNDING AREAS === Read in the room bounding areas + int roomBoundingCount = header3DO_roomBounding_size / 12; + uint32 expected3DO_roomBounding_size = roomBoundingCount * 12; + if (expected3DO_roomBounding_size != header3DO_roomBounding_size) + error("loadScene: 3DO roomBounding size mismatch"); - _zones.resize(size / 10); + roomStream->seek(header3DO_roomBounding_offset); + _zones.resize(roomBoundingCount); for (uint idx = 0; idx < _zones.size(); ++idx) { - _zones[idx].left = boundsStream->readSint16LE(); - _zones[idx].top = boundsStream->readSint16LE(); - _zones[idx].setWidth(boundsStream->readSint16LE() + 1); - _zones[idx].setHeight(boundsStream->readSint16LE() + 1); - boundsStream->skip(2); // Skip unused scene number field + _zones[idx].left = roomStream->readSint16BE(); + _zones[idx].top = roomStream->readSint16BE(); + _zones[idx].setWidth(roomStream->readSint16BE() + 1); + _zones[idx].setHeight(roomStream->readSint16BE() + 1); + roomStream->skip(4); // skip UINT32 } - if (_lzwMode) - delete boundsStream; - - // Ensure we've reached the path version byte - if (rrmStream->readByte() != (IS_SERRATED_SCALPEL ? 254 : 251)) - error("Invalid scene path data"); + // === WALK DIRECTORY === Load the walk directory + uint32 expected3DO_walkDirectory_size = _zones.size() * _zones.size() * 2; + if (expected3DO_walkDirectory_size != header3DO_walkDirectory_size) + error("loadScene: 3DO walkDirectory size mismatch"); - // Load the walk directory + roomStream->seek(header3DO_walkDirectory_offset); assert(_zones.size() < MAX_ZONES); for (uint idx1 = 0; idx1 < _zones.size(); ++idx1) { for (uint idx2 = 0; idx2 < _zones.size(); ++idx2) - _walkDirectory[idx1][idx2] = rrmStream->readSint16LE(); + _walkDirectory[idx1][idx2] = roomStream->readSint16BE(); } - // Read in the walk data - size = rrmStream->readUint16LE(); - Common::SeekableReadStream *walkStream = !_lzwMode ? rrmStream : - res.decompress(*rrmStream, size); - - _walkData.resize(size); - walkStream->read(&_walkData[0], size); - - if (_lzwMode) - delete walkStream; + // === WALK DATA === Read in the walk data + roomStream->seek(header3DO_walkData_offset); - if (IS_ROSE_TATTOO) { - // Read in the entrance - _entrance.load(*rrmStream); - - // Load scale zones - _scaleZones.resize(rrmStream->readByte()); - for (uint idx = 0; idx < _scaleZones.size(); ++idx) - _scaleZones[idx].load(*rrmStream); + int startPos = roomStream->pos(); + while ((roomStream->pos() - startPos) < (int)header3DO_walkData_size) { + _walkPoints.push_back(WalkArray()); + _walkPoints[_walkPoints.size() - 1]._fileOffset = roomStream->pos() - startPos; + _walkPoints[_walkPoints.size() - 1].load(*roomStream, false); } - // Read in the exits - _exitZone = -1; - int numExits = rrmStream->readByte(); - _exits.resize(numExits); - - for (int idx = 0; idx < numExits; ++idx) - _exits[idx].load(*rrmStream, IS_ROSE_TATTOO); - - if (IS_SERRATED_SCALPEL) - // Read in the entrance - _entrance.load(*rrmStream); - - // Initialize sound list - int numSounds = rrmStream->readByte(); - _sounds.resize(numSounds); - - for (int idx = 0; idx < numSounds; ++idx) - _sounds[idx].load(*rrmStream); - - loadSceneSounds(); - - if (IS_ROSE_TATTOO) { - // Load the object sound list - char buffer[27]; - - _objSoundList.resize(rrmStream->readUint16LE()); - for (uint idx = 0; idx < _objSoundList.size(); ++idx) { - rrmStream->read(buffer, 27); - _objSoundList[idx] = Common::String(buffer); + // Translate the file offsets of the walk directory to indexes in the loaded walk data + for (uint idx1 = 0; idx1 < _zones.size(); ++idx1) { + for (uint idx2 = 0; idx2 < _zones.size(); ++idx2) { + int fileOffset = _walkDirectory[idx1][idx2]; + if (fileOffset == -1) + continue; + + uint dataIndex = 0; + while (dataIndex < _walkPoints.size() && _walkPoints[dataIndex]._fileOffset != fileOffset) + ++dataIndex; + assert(dataIndex < _walkPoints.size()); + _walkDirectory[idx1][idx2] = dataIndex; } - } else { - // Read in palette - rrmStream->read(screen._cMap, PALETTE_SIZE); - screen.translatePalette(screen._cMap); - Common::copy(screen._cMap, screen._cMap + PALETTE_SIZE, screen._sMap); + } - // Read in the background - Common::SeekableReadStream *bgStream = !_lzwMode ? rrmStream : - res.decompress(*rrmStream, SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCENE_HEIGHT); + // === EXITS === Read in the exits + roomStream->seek(header3DO_exits_offset); + + int exitsCount = header3DO_exits_size / 20; + + _exits.resize(exitsCount); + for (int idx = 0; idx < exitsCount; ++idx) + _exits[idx].load3DO(*roomStream); + + // === ENTRANCE === Read in the entrance + if (header3DO_entranceData_size != 8) + error("loadScene: 3DO entranceData size mismatch"); + + roomStream->seek(header3DO_entranceData_offset); + _entrance.load3DO(*roomStream); + + // === SOUND LIST === Initialize sound list + roomStream->seek(header3DO_soundList_offset); + _sounds.resize(header3DO_soundList_count); + for (int idx = 0; idx < header3DO_soundList_count; ++idx) + _sounds[idx].load3DO(*roomStream); + + delete roomStream; + + // === BACKGROUND PICTURE === + // load from file rooms\[filename].bg + // it's uncompressed 15-bit RGB555 data + + Common::String roomBackgroundFilename = "rooms/" + filename + ".bg"; + flag = _vm->_res->exists(roomBackgroundFilename); + if (!flag) + error("loadScene: 3DO room background file not found (%s)", roomBackgroundFilename.c_str()); + + Common::File roomBackgroundStream; + if (!roomBackgroundStream.open(roomBackgroundFilename)) + error("Could not open file - %s", roomBackgroundFilename.c_str()); + + int totalPixelCount = SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCENE_HEIGHT; + uint16 *roomBackgroundDataPtr = NULL; + uint16 *pixelSourcePtr = NULL; + uint16 *pixelDestPtr = (uint16 *)screen._backBuffer1.getPixels(); + uint16 curPixel = 0; + uint32 roomBackgroundStreamSize = roomBackgroundStream.size(); + uint32 expectedBackgroundSize = totalPixelCount * 2; + + // Verify file size of background file + if (expectedBackgroundSize != roomBackgroundStreamSize) + error("loadScene: 3DO room background file not expected size"); + + roomBackgroundDataPtr = new uint16[totalPixelCount]; + roomBackgroundStream.read(roomBackgroundDataPtr, roomBackgroundStreamSize); + roomBackgroundStream.close(); + + // Convert data from RGB555 to RGB565 + pixelSourcePtr = roomBackgroundDataPtr; + for (int pixels = 0; pixels < totalPixelCount; pixels++) { + curPixel = READ_BE_UINT16(pixelSourcePtr++); + + byte curPixelRed = (curPixel >> 10) & 0x1F; + byte curPixelGreen = (curPixel >> 5) & 0x1F; + byte curPixelBlue = curPixel & 0x1F; + *pixelDestPtr = ((curPixelRed << 11) | (curPixelGreen << 6) | (curPixelBlue)); + pixelDestPtr++; + } - bgStream->read(screen._backBuffer1.getPixels(), SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCENE_HEIGHT); + delete[] roomBackgroundDataPtr; - if (_lzwMode) - delete bgStream; - } +#if 0 + // code to show the background + screen.blitFrom(screen._backBuffer1); + _vm->_events->wait(10000); +#endif - // Backup the image and set the palette + // Backup the image screen._backBuffer2.blitFrom(screen._backBuffer1); - screen.setPalette(screen._cMap); - - delete rrmStream; } // Handle drawing any on-screen interface @@ -622,17 +1036,6 @@ bool Scene::loadScene(const Common::String &filename) { _walkedInScene = false; saves._justLoaded = false; - if (!_vm->isDemo()) { - // Reset the previous map location and position on overhead map - map._oldCharPoint = _currentScene; - - if (IS_SERRATED_SCALPEL) { - map._overPos.x = (map[_currentScene].x - 6) * FIXED_INT_MULTIPLIER; - map._overPos.y = (map[_currentScene].y + 9) * FIXED_INT_MULTIPLIER; - - } - } - events.clearEvents(); return flag; } @@ -645,11 +1048,11 @@ void Scene::loadSceneSounds() { } void Scene::checkSceneStatus() { - if (_sceneStats[_currentScene][64]) { - for (uint idx = 0; idx < 64; ++idx) { + if (_sceneStats[_currentScene][MAX_BGSHAPES]) { + for (int idx = 0; idx < MAX_BGSHAPES; ++idx) { bool flag = _sceneStats[_currentScene][idx]; - if (idx < _bgShapes.size()) { + if (idx < (int)_bgShapes.size()) { Object &obj = _bgShapes[idx]; if (flag) { @@ -671,7 +1074,7 @@ void Scene::checkSceneStatus() { void Scene::saveSceneStatus() { // Flag any objects for the scene that have been altered - int count = MIN((int)_bgShapes.size(), 64); + int count = MIN((int)_bgShapes.size(), MAX_BGSHAPES); for (int idx = 0; idx < count; ++idx) { Object &obj = _bgShapes[idx]; _sceneStats[_currentScene][idx] = obj._type == HIDDEN || obj._type == REMOVE @@ -679,7 +1082,7 @@ void Scene::saveSceneStatus() { } // Flag scene as having been visited - _sceneStats[_currentScene][64] = true; + _sceneStats[_currentScene][MAX_BGSHAPES] = true; } void Scene::checkSceneFlags(bool flag) { @@ -687,9 +1090,15 @@ void Scene::checkSceneFlags(bool flag) { for (uint idx = 0; idx < _bgShapes.size(); ++idx) { Object &o = _bgShapes[idx]; + bool objectFlag = true; - if (o._requiredFlag) { - if (!_vm->readFlags(_bgShapes[idx]._requiredFlag)) { + if (o._requiredFlag[0] || o._requiredFlag[1]) { + if (o._requiredFlag[0] != 0) + objectFlag = _vm->readFlags(o._requiredFlag[0]); + if (o._requiredFlag[1] != 0) + objectFlag &= _vm->readFlags(o._requiredFlag[1]); + + if (!objectFlag) { // Kill object if (o._type != HIDDEN && o._type != INVALID) { if (o._images == nullptr || o._images->size() == 0) @@ -699,7 +1108,7 @@ void Scene::checkSceneFlags(bool flag) { // Flag it as needing to be hidden after first erasing it o._type = mode; } - } else if (_bgShapes[idx]._requiredFlag > 0) { + } else if (IS_ROSE_TATTOO || o._requiredFlag[0] > 0) { // Restore object if (o._images == nullptr || o._images->size() == 0) o._type = NO_SHAPE; @@ -750,18 +1159,29 @@ void Scene::transitionToScene() { SaveManager &saves = *_vm->_saves; Screen &screen = *_vm->_screen; Talk &talk = *_vm->_talk; - Common::Point &hSavedPos = people._hSavedPos; - int &hSavedFacing = people._hSavedFacing; + Point32 &hSavedPos = people._savedPos; + int &hSavedFacing = people._savedPos._facing; if (hSavedPos.x < 1) { // No exit information from last scene-check entrance info if (_entrance._startPosition.x < 1) { // No entrance info either, so use defaults - hSavedPos = Common::Point(16000, 10000); - hSavedFacing = 4; + if (IS_SERRATED_SCALPEL) { + hSavedPos = Point32(160 * FIXED_INT_MULTIPLIER, 100 * FIXED_INT_MULTIPLIER); + hSavedFacing = 4; + } else { + hSavedPos = people[HOLMES]._position; + hSavedFacing = people[HOLMES]._sequenceNumber; + } } else { // setup entrance info - hSavedPos = _entrance._startPosition; + hSavedPos.x = _entrance._startPosition.x * FIXED_INT_MULTIPLIER; + hSavedPos.y = _entrance._startPosition.y * FIXED_INT_MULTIPLIER; + if (IS_SERRATED_SCALPEL) { + hSavedPos.x /= 100; + hSavedPos.y /= 100; + } + hSavedFacing = _entrance._startDir; } } else { @@ -769,7 +1189,11 @@ void Scene::transitionToScene() { // Note: If a savegame was just loaded, then the data is already correct. // Otherwise, this is a linked scene or entrance info, and must be translated if (hSavedFacing < 8 && !saves._justLoaded) { - hSavedFacing = FS_TRANS[hSavedFacing]; + if (IS_ROSE_TATTOO) + hSavedFacing = Tattoo::FS_TRANS[hSavedFacing]; + else + hSavedFacing = Scalpel::FS_TRANS[hSavedFacing]; + hSavedPos.x *= FIXED_INT_MULTIPLIER; hSavedPos.y *= FIXED_INT_MULTIPLIER; } @@ -777,13 +1201,15 @@ void Scene::transitionToScene() { int cAnimNum = -1; - if (hSavedFacing < 101) { - // Standard info, so set it - people[PLAYER]._position = hSavedPos; - people[PLAYER]._sequenceNumber = hSavedFacing; - } else { - // It's canimation information - cAnimNum = hSavedFacing - 101; + if (!saves._justLoaded) { + if (hSavedFacing < 101) { + // Standard info, so set it + people[HOLMES]._position = hSavedPos; + people[HOLMES]._sequenceNumber = hSavedFacing; + } else { + // It's canimation information + cAnimNum = hSavedFacing - 101; + } } // Reset positioning for next load @@ -792,9 +1218,14 @@ void Scene::transitionToScene() { if (cAnimNum != -1) { // Prevent Holmes from being drawn - people[PLAYER]._position = Common::Point(0, 0); + people[HOLMES]._position = Common::Point(0, 0); } + // If the scene is capable of scrolling, set the current scroll so that whoever has control + // of the scroll code is in the middle of the screen + if (screen._backBuffer1.w() > SHERLOCK_SCREEN_WIDTH) + people[people._walkControl].centerScreenOnPerson(); + for (uint objIdx = 0; objIdx < _bgShapes.size(); ++objIdx) { Object &obj = _bgShapes[objIdx]; @@ -811,8 +1242,8 @@ void Scene::transitionToScene() { } if (Common::Rect(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y).contains( - Common::Point(people[PLAYER]._position.x / FIXED_INT_MULTIPLIER, - people[PLAYER]._position.y / FIXED_INT_MULTIPLIER))) { + Common::Point(people[HOLMES]._position.x / FIXED_INT_MULTIPLIER, + people[HOLMES]._position.y / FIXED_INT_MULTIPLIER))) { // Current point is already inside box - impact occurred on // a previous call. So simply do nothing except talk until the // player is clear of the box @@ -844,22 +1275,30 @@ void Scene::transitionToScene() { updateBackground(); // Actually do the transition - if (screen._fadeStyle) - screen.randomTransition(); - else - screen.blitFrom(screen._backBuffer1); + if (screen._fadeStyle) { + if (!IS_3DO) { + // do pixel-transition for PC + screen.randomTransition(); + } else { + // fade in for 3DO + screen.clear(); + screen.fadeIntoScreen3DO(3); + } + } else { + screen.slamArea(screen._currentScroll.x, screen._currentScroll.y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + } screen.update(); // Start any initial animation for the scene if (cAnimNum != -1) { CAnim &c = _cAnim[cAnimNum]; - Common::Point pt = c._goto; + PositionFacing pt = c._goto[0]; - c._goto = Common::Point(-1, -1); - people[AL]._position = Common::Point(0, 0); + c._goto[0].x = c._goto[0].y = -1; + people[HOLMES]._position = Common::Point(0, 0); startCAnim(cAnimNum, 1); - c._goto = pt; + c._goto[0] = pt; } } @@ -892,92 +1331,6 @@ void Scene::updateBackground() { drawAllShapes(); } -void Scene::drawAllShapes() { - People &people = *_vm->_people; - Screen &screen = *_vm->_screen; - - // Restrict drawing window - screen.setDisplayBounds(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT)); - - // Draw all active shapes which are behind the person - for (uint idx = 0; idx < _bgShapes.size(); ++idx) { - if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE && _bgShapes[idx]._misc == BEHIND) - screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, _bgShapes[idx]._flags & OBJ_FLIPPED); - } - - // Draw all canimations which are behind the person - for (uint idx = 0; idx < _canimShapes.size(); ++idx) { - if (_canimShapes[idx]._type == ACTIVE_BG_SHAPE && _canimShapes[idx]._misc == BEHIND) - screen._backBuffer->transBlitFrom(*_canimShapes[idx]._imageFrame, - _canimShapes[idx]._position, _canimShapes[idx]._flags & OBJ_FLIPPED); - } - - // Draw all active shapes which are normal and behind the person - for (uint idx = 0; idx < _bgShapes.size(); ++idx) { - if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE && _bgShapes[idx]._misc == NORMAL_BEHIND) - screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, _bgShapes[idx]._flags & OBJ_FLIPPED); - } - - // Draw all canimations which are normal and behind the person - for (uint idx = 0; idx < _canimShapes.size(); ++idx) { - if (_canimShapes[idx]._type == ACTIVE_BG_SHAPE && _canimShapes[idx]._misc == NORMAL_BEHIND) - screen._backBuffer->transBlitFrom(*_canimShapes[idx]._imageFrame, _canimShapes[idx]._position, - _canimShapes[idx]._flags & OBJ_FLIPPED); - } - - // Draw any active characters - for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { - Person &p = people[idx]; - if (p._type == CHARACTER && p._walkLoaded) { - bool flipped = IS_SERRATED_SCALPEL && ( - p._sequenceNumber == WALK_LEFT || p._sequenceNumber == STOP_LEFT || - p._sequenceNumber == WALK_UPLEFT || p._sequenceNumber == STOP_UPLEFT || - p._sequenceNumber == WALK_DOWNRIGHT || p._sequenceNumber == STOP_DOWNRIGHT); - - screen._backBuffer->transBlitFrom(*p._imageFrame, Common::Point(p._position.x / FIXED_INT_MULTIPLIER, - p._position.y / FIXED_INT_MULTIPLIER - p.frameHeight()), flipped); - } - } - - // Draw all static and active shapes that are NORMAL and are in front of the player - for (uint idx = 0; idx < _bgShapes.size(); ++idx) { - if ((_bgShapes[idx]._type == ACTIVE_BG_SHAPE || _bgShapes[idx]._type == STATIC_BG_SHAPE) && - _bgShapes[idx]._misc == NORMAL_FORWARD) - screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, - _bgShapes[idx]._flags & OBJ_FLIPPED); - } - - // Draw all static and active canimations that are NORMAL and are in front of the player - for (uint idx = 0; idx < _canimShapes.size(); ++idx) { - if ((_canimShapes[idx]._type == ACTIVE_BG_SHAPE || _canimShapes[idx]._type == STATIC_BG_SHAPE) && - _canimShapes[idx]._misc == NORMAL_FORWARD) - screen._backBuffer->transBlitFrom(*_canimShapes[idx]._imageFrame, _canimShapes[idx]._position, - _canimShapes[idx]._flags & OBJ_FLIPPED); - } - - // Draw all static and active shapes that are FORWARD - for (uint idx = 0; idx < _bgShapes.size(); ++idx) { - _bgShapes[idx]._oldPosition = _bgShapes[idx]._position; - _bgShapes[idx]._oldSize = Common::Point(_bgShapes[idx].frameWidth(), - _bgShapes[idx].frameHeight()); - - if ((_bgShapes[idx]._type == ACTIVE_BG_SHAPE || _bgShapes[idx]._type == STATIC_BG_SHAPE) && - _bgShapes[idx]._misc == FORWARD) - screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, - _bgShapes[idx]._flags & OBJ_FLIPPED); - } - - // Draw all static and active canimations that are forward - for (uint idx = 0; idx < _canimShapes.size(); ++idx) { - if ((_canimShapes[idx]._type == ACTIVE_BG_SHAPE || _canimShapes[idx]._type == STATIC_BG_SHAPE) && - _canimShapes[idx]._misc == FORWARD) - screen._backBuffer->transBlitFrom(*_canimShapes[idx]._imageFrame, _canimShapes[idx]._position, - _canimShapes[idx]._flags & OBJ_FLIPPED); - } - - screen.resetDisplayBounds(); -} - Exit *Scene::checkForExit(const Common::Rect &r) { for (uint idx = 0; idx < _exits.size(); ++idx) { if (_exits[idx].intersects(r)) @@ -987,238 +1340,6 @@ Exit *Scene::checkForExit(const Common::Rect &r) { return nullptr; } -int Scene::startCAnim(int cAnimNum, int playRate) { - Events &events = *_vm->_events; - Map &map = *_vm->_map; - People &people = *_vm->_people; - Resources &res = *_vm->_res; - Talk &talk = *_vm->_talk; - UserInterface &ui = *_vm->_ui; - Common::Point tpPos, walkPos; - int tpDir, walkDir; - int tFrames = 0; - int gotoCode = -1; - - // Validation - if (cAnimNum >= (int)_cAnim.size()) - // number out of bounds - return -1; - if (_canimShapes.size() >= 3 || playRate == 0) - // Too many active animations, or invalid play rate - return 0; - - CAnim &cAnim = _cAnim[cAnimNum]; - if (playRate < 0) { - // Reverse direction - walkPos = cAnim._teleportPos; - walkDir = cAnim._teleportDir; - tpPos = cAnim._goto; - tpDir = cAnim._gotoDir; - } else { - // Forward direction - walkPos = cAnim._goto; - walkDir = cAnim._gotoDir; - tpPos = cAnim._teleportPos; - tpDir = cAnim._teleportDir; - } - - CursorId oldCursor = events.getCursor(); - events.setCursor(WAIT); - - if (walkPos.x != -1) { - // Holmes must walk to the walk point before the cAnimation is started - if (people[AL]._position != walkPos) - people.walkToCoords(walkPos, walkDir); - } - - if (talk._talkToAbort) - return 1; - - // Add new anim shape entry for displaying the animation - _canimShapes.push_back(Object()); - Object &cObj = _canimShapes[_canimShapes.size() - 1]; - - // Copy the canimation into the bgShapes type canimation structure so it can be played - cObj._allow = cAnimNum + 1; // Keep track of the parent structure - cObj._name = _cAnim[cAnimNum]._name; // Copy name - - // Remove any attempt to draw object frame - if (cAnim._type == NO_SHAPE && cAnim._sequences[0] < 100) - cAnim._sequences[0] = 0; - - cObj._sequences = cAnim._sequences; - cObj._images = nullptr; - cObj._position = cAnim._position; - cObj._delta = Common::Point(0, 0); - cObj._type = cAnim._type; - cObj._flags = cAnim._flags; - - cObj._maxFrames = 0; - cObj._frameNumber = -1; - cObj._sequenceNumber = cAnimNum; - cObj._oldPosition = Common::Point(0, 0); - cObj._oldSize = Common::Point(0, 0); - cObj._goto = Common::Point(0, 0); - cObj._status = 0; - cObj._misc = 0; - cObj._imageFrame = nullptr; - - if (cAnim._name.size() > 0 && cAnim._type != NO_SHAPE) { - if (tpPos.x != -1) - people[AL]._type = REMOVE; - - Common::String fname = cAnim._name + ".vgs"; - if (!res.isInCache(fname)) { - // Set up RRM scene data - Common::SeekableReadStream *rrmStream = res.load(_rrmName); - rrmStream->seek(44 + cAnimNum * 4); - rrmStream->seek(rrmStream->readUint32LE()); - - // Load the canimation into the cache - Common::SeekableReadStream *imgStream = !_lzwMode ? rrmStream->readStream(cAnim._size) : - Resources::decompressLZ(*rrmStream, cAnim._size); - res.addToCache(fname, *imgStream); - - delete imgStream; - delete rrmStream; - } - - // Now load the resource as an image - cObj._images = new ImageFile(fname); - cObj._imageFrame = &(*cObj._images)[0]; - cObj._maxFrames = cObj._images->size(); - - int frames = 0; - if (playRate < 0) { - // Reverse direction - // Count number of frames - while (cObj._sequences[frames] && frames < MAX_FRAME) - ++frames; - } else { - // Forward direction - Object::_countCAnimFrames = true; - - while (cObj._type == ACTIVE_BG_SHAPE) { - cObj.checkObject(); - ++frames; - - if (frames >= 1000) - error("CAnim has infinite loop sequence"); - } - - if (frames > 1) - --frames; - - Object::_countCAnimFrames = false; - - cObj._type = cAnim._type; - cObj._frameNumber = -1; - cObj._position = cAnim._position; - cObj._delta = Common::Point(0, 0); - } - - // Return if animation has no frames in it - if (frames == 0) - return -2; - - ++frames; - int repeat = ABS(playRate); - int dir; - - if (playRate < 0) { - // Play in reverse - dir = -2; - cObj._frameNumber = frames - 3; - } else { - dir = 0; - } - - tFrames = frames - 1; - int pauseFrame = (_cAnimFramePause) ? frames - _cAnimFramePause : -1; - - while (--frames) { - if (frames == pauseFrame) - ui.printObjectDesc(); - - doBgAnim(); - - // Repeat same frame - int temp = repeat; - while (--temp > 0) { - cObj._frameNumber--; - doBgAnim(); - - if (_vm->shouldQuit()) - return 0; - } - - cObj._frameNumber += dir; - } - - people[AL]._type = CHARACTER; - } - - // Teleport to ending coordinates if necessary - if (tpPos.x != -1) { - people[AL]._position = tpPos; // Place the player - people[AL]._sequenceNumber = tpDir; - people.gotoStand(people[AL]); - } - - if (playRate < 0) - // Reverse direction - set to end sequence - cObj._frameNumber = tFrames - 1; - - if (cObj._frameNumber <= 26) - gotoCode = cObj._sequences[cObj._frameNumber + 3]; - - // Unless anim shape has already been freed, set it to REMOVE so doBgAnim can free it - if (_canimShapes.indexOf(cObj) != -1) - cObj.checkObject(); - - if (gotoCode > 0 && !talk._talkToAbort) { - _goToScene = gotoCode; - - if (_goToScene < 97 && map[_goToScene].x) { - map._overPos = map[_goToScene]; - } - } - - people.loadWalk(); - - if (tpPos.x != -1 && !talk._talkToAbort) { - // Teleport to ending coordinates - people[AL]._position = tpPos; - people[AL]._sequenceNumber = tpDir; - - people.gotoStand(people[AL]); - } - - events.setCursor(oldCursor); - - return 1; -} - -int Scene::findBgShape(const Common::Rect &r) { - if (!_doBgAnimDone) - // New frame hasn't been drawn yet - return -1; - - for (int idx = (int)_bgShapes.size() - 1; idx >= 0; --idx) { - Object &o = _bgShapes[idx]; - if (o._type != INVALID && o._type != NO_SHAPE && o._type != HIDDEN - && o._aType <= PERSON) { - if (r.intersects(o.getNewBounds())) - return idx; - } else if (o._type == NO_SHAPE) { - if (r.intersects(o.getNoShapeBounds())) - return idx; - } - } - - return -1; -} - int Scene::checkForZones(const Common::Point &pt, int zoneType) { int matches = 0; @@ -1247,26 +1368,7 @@ int Scene::whichZone(const Common::Point &pt) { return -1; } -int Scene::closestZone(const Common::Point &pt) { - int dist = 1000; - int zone = -1; - - for (uint idx = 0; idx < _zones.size(); ++idx) { - Common::Point zc((_zones[idx].left + _zones[idx].right) / 2, - (_zones[idx].top + _zones[idx].bottom) / 2); - int d = ABS(zc.x - pt.x) + ABS(zc.y - pt.y); - - if (d < dist) { - // Found a closer zone - dist = d; - zone = idx; - } - } - - return zone; -} - -void Scene::synchronize(Common::Serializer &s) { +void Scene::synchronize(Serializer &s) { if (s.isSaving()) saveSceneStatus(); @@ -1277,37 +1379,16 @@ void Scene::synchronize(Common::Serializer &s) { _loadingSavedGame = true; } - for (int sceneNum = 0; sceneNum < SCENES_COUNT; ++sceneNum) { - for (int flag = 0; flag < 65; ++flag) { + for (int sceneNum = 1; sceneNum < SCENES_COUNT; ++sceneNum) { + for (int flag = 0; flag <= MAX_BGSHAPES; ++flag) { s.syncAsByte(_sceneStats[sceneNum][flag]); } } } -void Scene::setNPCPath(int npc) { - People &people = *_vm->_people; - Talk &talk = *_vm->_talk; - - people[npc].clearNPC(); - people[npc]._name = Common::String::format("WATS%.2dA", _currentScene); - - // If we're in the middle of a script that will continue once the scene is loaded, - // return without calling the path script - if (talk._scriptMoreFlag == 1 || talk._scriptMoreFlag == 3) - return; - - // Turn off all the NPCs, since the talk script will turn them back on as needed - for (uint idx = 0; idx < MAX_NPC; ++idx) - people[idx + 1]._type = INVALID; - - // Call the path script for the scene - Common::String pathFile = Common::String::format("PATH%.2dA", _currentScene); - talk.talkTo(pathFile); -} - void Scene::checkBgShapes() { People &people = *_vm->_people; - Person &holmes = people._player; + Person &holmes = people[HOLMES]; Common::Point pt(holmes._position.x / FIXED_INT_MULTIPLIER, holmes._position.y / FIXED_INT_MULTIPLIER); // Iterate through the shapes diff --git a/engines/sherlock/scene.h b/engines/sherlock/scene.h index 37a1b32740..f75dfb40cd 100644 --- a/engines/sherlock/scene.h +++ b/engines/sherlock/scene.h @@ -33,7 +33,6 @@ namespace Sherlock { -#define SCENES_COUNT 63 #define MAX_ZONES 40 #define INFO_LINE 140 @@ -73,14 +72,14 @@ struct BgFileHeaderInfo { * Load the data for the object */ void load(Common::SeekableReadStream &s); + void load3DO(Common::SeekableReadStream &s); }; class Exit: public Common::Rect { public: int _scene; int _allow; - Common::Point _people; - int _peopleDir; + PositionFacing _newPosition; Common::String _dest; int _image; // Arrow image to use @@ -89,6 +88,7 @@ public: * Load the data for the object */ void load(Common::SeekableReadStream &s, bool isRoseTattoo); + void load3DO(Common::SeekableReadStream &s); }; struct SceneEntry { @@ -100,6 +100,7 @@ struct SceneEntry { * Load the data for the object */ void load(Common::SeekableReadStream &s); + void load3DO(Common::SeekableReadStream &s); }; struct SceneSound { @@ -110,6 +111,7 @@ struct SceneSound { * Load the data for the object */ void load(Common::SeekableReadStream &s); + void load3DO(Common::SeekableReadStream &s); }; class ObjectArray : public Common::Array<Object> { @@ -128,33 +130,24 @@ public: void load(Common::SeekableReadStream &s); }; -struct SceneTripEntry { - bool _flag; - int _sceneNumber; - int _numTimes; +class WalkArray : public Common::Array < Common::Point > { +public: + int _pointsCount; + int _fileOffset; + + WalkArray() : _pointsCount(0), _fileOffset(-1) {} - SceneTripEntry() : _flag(false), _sceneNumber(0), _numTimes(0) {} - SceneTripEntry(bool flag, int sceneNumber, int numTimes) : _flag(flag), - _sceneNumber(sceneNumber), _numTimes(numTimes) {} + /** + * Load data for the walk array entry + */ + void load(Common::SeekableReadStream &s, bool isRoseTattoo); }; class Scene { private: - Common::String _rrmName; bool _loadingSavedGame; /** - * Loads the data associated for a given scene. The .BGD file's format is: - * BGHEADER: Holds an index for the rest of the file - * STRUCTS: The objects for the scene - * IMAGES: The graphic information for the structures - * - * The _misc field of the structures contains the number of the graphic image - * that it should point to after loading; _misc is then set to 0. - */ - bool loadScene(const Common::String &filename); - - /** * Loads sounds for the scene */ void loadSceneSounds(); @@ -185,6 +178,18 @@ private: void saveSceneStatus(); protected: SherlockEngine *_vm; + Common::String _roomFilename; + + /** + * Loads the data associated for a given scene. The room resource file's format is: + * BGHEADER: Holds an index for the rest of the file + * STRUCTS: The objects for the scene + * IMAGES: The graphic information for the structures + * + * The _misc field of the structures contains the number of the graphic image + * that it should point to after loading; _misc is then set to 0. + */ + virtual bool loadScene(const Common::String &filename); /** * Checks all the background shapes. If a background shape is animating, @@ -196,29 +201,32 @@ protected: /** * Draw all the shapes, people and NPCs in the correct order */ - void drawAllShapes(); + virtual void drawAllShapes() = 0; + + /** + * Called by loadScene when the palette is loaded for Rose Tattoo + */ + virtual void paletteLoaded() {} Scene(SherlockEngine *vm); public: int _currentScene; int _goToScene; - bool _sceneStats[SCENES_COUNT][65]; - bool _savedStats[SCENES_COUNT][9]; + bool **_sceneStats; bool _walkedInScene; int _version; - bool _lzwMode; + bool _compressed; int _invGraphicItems; Common::String _comments; Common::Array<char> _descText; Common::Array<Common::Rect> _zones; - Common::Array<Object> _bgShapes; + ObjectArray _bgShapes; Common::Array<CAnim> _cAnim; Common::Array<byte> _sequenceBuffer; Common::Array<SceneImage> _images; int _walkDirectory[MAX_ZONES][MAX_ZONES]; - Common::Array<byte> _walkData; + Common::Array<WalkArray> _walkPoints; Common::Array<Exit> _exits; - int _exitZone; SceneEntry _entrance; Common::Array<SceneSound> _sounds; ObjectArray _canimShapes; @@ -229,7 +237,6 @@ public: bool _doBgAnimDone; int _tempFadeStyle; int _cAnimFramePause; - Common::Array<SceneTripEntry> _sceneTripCounters; public: static Scene *init(SherlockEngine *vm); virtual ~Scene(); @@ -240,11 +247,6 @@ public: void selectScene(); /** - * Fres all the graphics and other dynamically allocated data for the scene - */ - void freeScene(); - - /** * Check the scene's objects against the game flags. If false is passed, * it means the scene has just been loaded. A value of true means that the scene * is in use (ie. not just loaded) @@ -257,28 +259,12 @@ public: Exit *checkForExit(const Common::Rect &r); /** - * Attempt to start a canimation sequence. It will load the requisite graphics, and - * then copy the canim object into the _canimShapes array to start the animation. - * - * @param cAnimNum The canim object within the current scene - * @param playRate Play rate. 0 is invalid; 1=normal speed, 2=1/2 speed, etc. - * A negative playRate can also be specified to play the animation in reverse - */ - int startCAnim(int cAnimNum, int playRate); - - /** * Scans through the object list to find one with a matching name, and will * call toggleHidden with all matches found. Returns the numer of matches found */ int toggleObject(const Common::String &name); /** - * Attempts to find a background shape within the passed bounds. If found, - * it will return the shape number, or -1 on failure. - */ - int findBgShape(const Common::Rect &r); - - /** * Checks to see if the given position in the scene belongs to a given zone type. * If it is, the zone is activated and used just like a TAKL zone or aFLAG_SET zone. */ @@ -290,21 +276,25 @@ public: int whichZone(const Common::Point &pt); /** + * Fres all the graphics and other dynamically allocated data for the scene + */ + virtual void freeScene(); + + /** * Returns the index of the closest zone to a given point. */ - int closestZone(const Common::Point &pt); + virtual int closestZone(const Common::Point &pt) = 0; /** - * Synchronize the data for a savegame + * Attempts to find a background shape within the passed bounds. If found, + * it will return the shape number, or -1 on failure. */ - void synchronize(Common::Serializer &s); + virtual int findBgShape(const Common::Point &pt) = 0; /** - * Resets the NPC path information when entering a new scene. - * @remarks The default talk file for the given NPC is set to WATS##A, where ## is - * the scene number being entered + * Synchronize the data for a savegame */ - void setNPCPath(int npc); + virtual void synchronize(Serializer &s); public: /** * Draw all objects and characters. @@ -316,6 +306,16 @@ public: * to be drawn */ virtual void updateBackground(); + + /** + * Attempt to start a canimation sequence. It will load the requisite graphics, and + * then copy the canim object into the _canimShapes array to start the animation. + * + * @param cAnimNum The canim object within the current scene + * @param playRate Play rate. 0 is invalid; 1=normal speed, 2=1/2 speed, etc. + * A negative playRate can also be specified to play the animation in reverse + */ + virtual int startCAnim(int cAnimNum, int playRate = 1) = 0; }; } // End of namespace Sherlock diff --git a/engines/sherlock/screen.cpp b/engines/sherlock/screen.cpp index a3af5559c7..4233bca0cb 100644 --- a/engines/sherlock/screen.cpp +++ b/engines/sherlock/screen.cpp @@ -22,55 +22,41 @@ #include "sherlock/screen.h" #include "sherlock/sherlock.h" +#include "sherlock/scalpel/scalpel_screen.h" #include "common/system.h" #include "common/util.h" #include "graphics/palette.h" namespace Sherlock { +Screen *Screen::init(SherlockEngine *vm) { + if (vm->getGameID() == GType_SerratedScalpel) + return new Scalpel::ScalpelScreen(vm); + else + return new Screen(vm); +} + Screen::Screen(SherlockEngine *vm) : Surface(g_system->getWidth(), g_system->getHeight()), _vm(vm), _backBuffer1(g_system->getWidth(), g_system->getHeight()), _backBuffer2(g_system->getWidth(), g_system->getHeight()), _backBuffer(&_backBuffer1) { _transitionSeed = 1; _fadeStyle = false; - _font = nullptr; - _fontHeight = 0; Common::fill(&_cMap[0], &_cMap[PALETTE_SIZE], 0); Common::fill(&_sMap[0], &_sMap[PALETTE_SIZE], 0); Common::fill(&_tMap[0], &_tMap[PALETTE_SIZE], 0); + + // Set up the initial font setFont(IS_SERRATED_SCALPEL ? 1 : 4); // Rose Tattoo specific fields _fadeBytesRead = _fadeBytesToRead = 0; _oldFadePercent = 0; - _scrollSize = 0; - _scrollSpeed = 0; - _currentScroll = 0; - _targetScroll = 0; _flushScreen = false; } Screen::~Screen() { - delete _font; -} - -void Screen::setFont(int fontNumb) { - // Interactive demo doesn't use fonts - if (!_vm->_interactiveFl) - return; - - _fontNumber = fontNumb; - Common::String fname = Common::String::format("FONT%d.VGS", fontNumb + 1); - - // Discard any previous font and read in new one - delete _font; - _font = new ImageFile(fname); - - // Iterate through the frames to find the tallest font character - _fontHeight = 0; - for (uint idx = 0; idx < _font->size(); ++idx) - _fontHeight = MAX((uint16)_fontHeight, (*_font)[idx]._frame.h); + Fonts::free(); } void Screen::update() { @@ -91,6 +77,10 @@ void Screen::update() { _dirtyRects.clear(); } +void Screen::makeAllDirty() { + addDirtyRect(Common::Rect(0, 0, this->w(), this->h())); +} + void Screen::getPalette(byte palette[PALETTE_SIZE]) { g_system->getPaletteManager()->grabPalette(palette, 0, PALETTE_COUNT); } @@ -229,14 +219,133 @@ void Screen::verticalTransition() { } } -void Screen::restoreBackground(const Common::Rect &r) { - if (r.width() > 0 && r.height() > 0) { - Common::Rect tempRect = r; - tempRect.clip(Common::Rect(0, 0, this->w(), SHERLOCK_SCENE_HEIGHT)); +void Screen::fadeIntoScreen3DO(int speed) { + Events &events = *_vm->_events; + uint16 *currentScreenBasePtr = (uint16 *)getPixels(); + uint16 *targetScreenBasePtr = (uint16 *)_backBuffer->getPixels(); + uint16 currentScreenPixel = 0; + uint16 targetScreenPixel = 0; + + uint16 currentScreenPixelRed = 0; + uint16 currentScreenPixelGreen = 0; + uint16 currentScreenPixelBlue = 0; + + uint16 targetScreenPixelRed = 0; + uint16 targetScreenPixelGreen = 0; + uint16 targetScreenPixelBlue = 0; + + uint16 screenWidth = this->w(); + uint16 screenHeight = this->h(); + uint16 screenX = 0; + uint16 screenY = 0; + uint16 pixelsChanged = 0; + + _dirtyRects.clear(); + + do { + pixelsChanged = 0; + uint16 *currentScreenPtr = currentScreenBasePtr; + uint16 *targetScreenPtr = targetScreenBasePtr; + + for (screenY = 0; screenY < screenHeight; screenY++) { + for (screenX = 0; screenX < screenWidth; screenX++) { + currentScreenPixel = *currentScreenPtr; + targetScreenPixel = *targetScreenPtr; + + if (currentScreenPixel != targetScreenPixel) { + // pixel doesn't match, adjust accordingly + currentScreenPixelRed = currentScreenPixel & 0xF800; + currentScreenPixelGreen = currentScreenPixel & 0x07E0; + currentScreenPixelBlue = currentScreenPixel & 0x001F; + targetScreenPixelRed = targetScreenPixel & 0xF800; + targetScreenPixelGreen = targetScreenPixel & 0x07E0; + targetScreenPixelBlue = targetScreenPixel & 0x001F; + + if (currentScreenPixelRed != targetScreenPixelRed) { + if (currentScreenPixelRed < targetScreenPixelRed) { + currentScreenPixelRed += 0x0800; + } else { + currentScreenPixelRed -= 0x0800; + } + } + if (currentScreenPixelGreen != targetScreenPixelGreen) { + // Adjust +2/-2 because we are running RGB555 at RGB565 + if (currentScreenPixelGreen < targetScreenPixelGreen) { + currentScreenPixelGreen += 0x0040; + } else { + currentScreenPixelGreen -= 0x0040; + } + } + if (currentScreenPixelBlue != targetScreenPixelBlue) { + if (currentScreenPixelBlue < targetScreenPixelBlue) { + currentScreenPixelBlue += 0x0001; + } else { + currentScreenPixelBlue -= 0x0001; + } + } + *currentScreenPtr = currentScreenPixelRed | currentScreenPixelGreen | currentScreenPixelBlue; + pixelsChanged++; + } + + currentScreenPtr++; + targetScreenPtr++; + } + } + + // Too much considered dirty at the moment + addDirtyRect(Common::Rect(0, 0, screenWidth, screenHeight)); - if (tempRect.isValidRect()) - _backBuffer1.blitFrom(_backBuffer2, Common::Point(tempRect.left, tempRect.top), tempRect); + events.pollEvents(); + events.delay(10 * speed); + } while ((pixelsChanged) && (!_vm->shouldQuit())); +} + +void Screen::blitFrom3DOcolorLimit(uint16 limitColor) { + uint16 *currentScreenPtr = (uint16 *)getPixels(); + uint16 *targetScreenPtr = (uint16 *)_backBuffer->getPixels(); + uint16 currentScreenPixel = 0; + + uint16 screenWidth = this->w(); + uint16 screenHeight = this->h(); + uint16 screenX = 0; + uint16 screenY = 0; + + uint16 currentScreenPixelRed = 0; + uint16 currentScreenPixelGreen = 0; + uint16 currentScreenPixelBlue = 0; + + uint16 limitPixelRed = limitColor & 0xF800; + uint16 limitPixelGreen = limitColor & 0x07E0; + uint16 limitPixelBlue = limitColor & 0x001F; + + for (screenY = 0; screenY < screenHeight; screenY++) { + for (screenX = 0; screenX < screenWidth; screenX++) { + currentScreenPixel = *targetScreenPtr; + + currentScreenPixelRed = currentScreenPixel & 0xF800; + currentScreenPixelGreen = currentScreenPixel & 0x07E0; + currentScreenPixelBlue = currentScreenPixel & 0x001F; + + if (currentScreenPixelRed < limitPixelRed) + currentScreenPixelRed = limitPixelRed; + if (currentScreenPixelGreen < limitPixelGreen) + currentScreenPixelGreen = limitPixelGreen; + if (currentScreenPixelBlue < limitPixelBlue) + currentScreenPixelBlue = limitPixelBlue; + + *currentScreenPtr = currentScreenPixelRed | currentScreenPixelGreen | currentScreenPixelBlue; + currentScreenPtr++; + targetScreenPtr++; + } } + + // Too much considered dirty at the moment + addDirtyRect(Common::Rect(0, 0, screenWidth, screenHeight)); +} + +void Screen::restoreBackground(const Common::Rect &r) { + if (r.width() > 0 && r.height() > 0) + _backBuffer1.blitFrom(_backBuffer2, Common::Point(r.left, r.top), r); } void Screen::slamArea(int16 xp, int16 yp, int16 width, int16 height) { @@ -245,14 +354,33 @@ void Screen::slamArea(int16 xp, int16 yp, int16 width, int16 height) { void Screen::slamRect(const Common::Rect &r) { if (r.width() && r.height() > 0) { - Common::Rect tempRect = r; - tempRect.clip(Common::Rect(0, 0, this->w(), this->h())); + Common::Rect srcRect = r, destRect = r; + + destRect.translate(-_currentScroll.x, -_currentScroll.y); + + if (destRect.left < 0) { + srcRect.left += -destRect.left; + destRect.left = 0; + } + if (destRect.top < 0) { + srcRect.top += -destRect.top; + destRect.top = 0; + } + if (destRect.right > SHERLOCK_SCREEN_WIDTH) { + srcRect.right -= (destRect.left - SHERLOCK_SCREEN_WIDTH); + destRect.right = SHERLOCK_SCREEN_WIDTH; + } + if (destRect.bottom > SHERLOCK_SCREEN_HEIGHT) { + srcRect.bottom -= (destRect.bottom - SHERLOCK_SCREEN_HEIGHT); + destRect.bottom = SHERLOCK_SCREEN_HEIGHT; + } - if (tempRect.isValidRect()) - blitFrom(*_backBuffer, Common::Point(tempRect.left, tempRect.top), tempRect); + if (srcRect.isValidRect()) + blitFrom(*_backBuffer, Common::Point(destRect.left, destRect.top), srcRect); } } + void Screen::flushImage(ImageFrame *frame, const Common::Point &pt, int16 *xp, int16 *yp, int16 *width, int16 *height) { Common::Point imgPos = pt + frame->_offset; @@ -283,7 +411,7 @@ void Screen::flushImage(ImageFrame *frame, const Common::Point &pt, int16 *xp, i void Screen::flushScaleImage(ImageFrame *frame, const Common::Point &pt, int16 *xp, int16 *yp, int16 *width, int16 *height, int scaleVal) { - Common::Point imgPos = pt + frame->_offset; + Common::Point imgPos(pt.x + frame->sDrawXOffset(scaleVal), pt.y + frame->sDrawYOffset(scaleVal)); Common::Rect newBounds(imgPos.x, imgPos.y, imgPos.x + frame->sDrawXSize(scaleVal), imgPos.y + frame->sDrawYSize(scaleVal)); Common::Rect oldBounds(*xp, *yp, *xp + *width, *yp + *height); @@ -310,6 +438,28 @@ void Screen::flushScaleImage(ImageFrame *frame, const Common::Point &pt, int16 * *height = newBounds.height(); } +void Screen::flushImage(ImageFrame *frame, const Common::Point &pt, Common::Rect &newBounds, int scaleVal) { + Common::Point newPos(newBounds.left, newBounds.top); + Common::Point newSize(newBounds.width(), newBounds.height()); + + if (scaleVal == SCALE_THRESHOLD) + flushImage(frame, pt, &newPos.x, &newPos.y, &newSize.x, &newSize.y); + else + flushScaleImage(frame, pt, &newPos.x, &newPos.y, &newSize.x, &newSize.y, scaleVal); + + // Transfer the pos and size amounts into a single bounds rect + newBounds = Common::Rect(newPos.x, newPos.y, newPos.x + newSize.x, newPos.y + newSize.y); +} + +void Screen::blockMove(const Common::Rect &r) { + Common::Rect bounds = r; + slamRect(bounds); +} + +void Screen::blockMove() { + blockMove(Common::Rect(0, 0, w(), h())); +} + void Screen::print(const Common::Point &pt, byte color, const char *formatStr, ...) { // Create the string to display va_list args; @@ -349,37 +499,8 @@ void Screen::gPrint(const Common::Point &pt, byte color, const char *formatStr, writeString(str, pt, color); } -int Screen::stringWidth(const Common::String &str) { - int width = 0; - - for (const char *c = str.c_str(); *c; ++c) - width += charWidth(*c); - - return width; -} - -int Screen::charWidth(char c) { - if (c == ' ') - return 5; - else if (Common::isPrint(c)) - return (*_font)[c - 33]._frame.w + 1; - else - return 0; -} - -void Screen::writeString(const Common::String &str, const Common::Point &pt, byte color) { - Common::Point charPos = pt; - - for (const char *c = str.c_str(); *c; ++c) { - if (*c == ' ') - charPos.x += 5; - else { - assert(Common::isPrint(*c)); - ImageFrame &frame = (*_font)[*c - 33]; - _backBuffer->transBlitFrom(frame, charPos, false, color); - charPos.x += frame._frame.w + 1; - } - } +void Screen::writeString(const Common::String &str, const Common::Point &pt, byte overrideColor) { + Fonts::writeString(_backBuffer, str, pt, overrideColor); } void Screen::vgaBar(const Common::Rect &r, int color) { @@ -387,67 +508,8 @@ void Screen::vgaBar(const Common::Rect &r, int color) { slamRect(r); } -void Screen::makeButton(const Common::Rect &bounds, int textX, - const Common::String &str) { - - Surface &bb = *_backBuffer; - bb.fillRect(Common::Rect(bounds.left, bounds.top, bounds.right, bounds.top + 1), BUTTON_TOP); - bb.fillRect(Common::Rect(bounds.left, bounds.top, bounds.left + 1, bounds.bottom), BUTTON_TOP); - bb.fillRect(Common::Rect(bounds.right - 1, bounds.top, bounds.right, bounds.bottom), BUTTON_BOTTOM); - bb.fillRect(Common::Rect(bounds.left + 1, bounds.bottom - 1, bounds.right, bounds.bottom), BUTTON_BOTTOM); - bb.fillRect(Common::Rect(bounds.left + 1, bounds.top + 1, bounds.right - 1, bounds.bottom - 1), BUTTON_MIDDLE); - - gPrint(Common::Point(textX, bounds.top), COMMAND_HIGHLIGHTED, "%c", str[0]); - gPrint(Common::Point(textX + charWidth(str[0]), bounds.top), - COMMAND_FOREGROUND, "%s", str.c_str() + 1); -} - -void Screen::buttonPrint(const Common::Point &pt, byte color, bool slamIt, - const Common::String &str) { - int xStart = pt.x - stringWidth(str) / 2; - - if (color == COMMAND_FOREGROUND) { - // First character needs to be highlighted - if (slamIt) { - print(Common::Point(xStart, pt.y + 1), COMMAND_HIGHLIGHTED, "%c", str[0]); - print(Common::Point(xStart + charWidth(str[0]), pt.y + 1), - COMMAND_FOREGROUND, "%s", str.c_str() + 1); - } else { - gPrint(Common::Point(xStart, pt.y), COMMAND_HIGHLIGHTED, "%c", str[0]); - gPrint(Common::Point(xStart + charWidth(str[0]), pt.y), - COMMAND_FOREGROUND, "%s", str.c_str() + 1); - } - } else if (slamIt) { - print(Common::Point(xStart, pt.y + 1), color, "%s", str.c_str()); - } else { - gPrint(Common::Point(xStart, pt.y), color, "%s", str.c_str()); - } -} - -void Screen::makePanel(const Common::Rect &r) { - _backBuffer->fillRect(r, BUTTON_MIDDLE); - _backBuffer->hLine(r.left, r.top, r.right - 2, BUTTON_TOP); - _backBuffer->hLine(r.left + 1, r.top + 1, r.right - 3, BUTTON_TOP); - _backBuffer->vLine(r.left, r.top, r.bottom - 1, BUTTON_TOP); - _backBuffer->vLine(r.left + 1, r.top + 1, r.bottom - 2, BUTTON_TOP); - - _backBuffer->vLine(r.right - 1, r.top, r.bottom - 1, BUTTON_BOTTOM); - _backBuffer->vLine(r.right - 2, r.top + 1, r.bottom - 2, BUTTON_BOTTOM); - _backBuffer->hLine(r.left, r.bottom - 1, r.right - 1, BUTTON_BOTTOM); - _backBuffer->hLine(r.left + 1, r.bottom - 2, r.right - 1, BUTTON_BOTTOM); -} - -void Screen::makeField(const Common::Rect &r) { - _backBuffer->fillRect(r, BUTTON_MIDDLE); - _backBuffer->hLine(r.left, r.top, r.right - 1, BUTTON_BOTTOM); - _backBuffer->hLine(r.left + 1, r.bottom - 1, r.right - 1, BUTTON_TOP); - _backBuffer->vLine(r.left, r.top + 1, r.bottom - 1, BUTTON_BOTTOM); - _backBuffer->vLine(r.right - 1, r.top + 1, r.bottom - 2, BUTTON_TOP); -} - void Screen::setDisplayBounds(const Common::Rect &r) { - assert(r.left == 0 && r.top == 0); - _sceneSurface.setPixels(_backBuffer1.getPixels(), r.width(), r.height()); + _sceneSurface.setPixels(_backBuffer1.getBasePtr(r.left, r.top), r.width(), r.height(), _backBuffer1.getPixelFormat()); _backBuffer = &_sceneSurface; } @@ -461,7 +523,7 @@ Common::Rect Screen::getDisplayBounds() { Common::Rect(0, 0, this->w(), this->h()); } -void Screen::synchronize(Common::Serializer &s) { +void Screen::synchronize(Serializer &s) { int fontNumb = _fontNumber; s.syncAsByte(fontNumb); if (s.isLoading()) @@ -484,22 +546,6 @@ int Screen::fadeRead(Common::SeekableReadStream &stream, byte *buf, int totalSiz return totalSize; } -/** - * Creates a grey-scale version of the passed palette - */ -void Screen::setupBGArea(const byte cMap[PALETTE_SIZE]) { - warning("TODO"); -} - -/** - * Initializes scroll variables - */ -void Screen::initScrollVars() { - _scrollSize = 0; - _currentScroll = 0; - _targetScroll = 0; -} - void Screen::translatePalette(byte palette[PALETTE_SIZE]) { for (int idx = 0; idx < PALETTE_SIZE; ++idx) palette[idx] = VGA_COLOR_TRANS(palette[idx]); diff --git a/engines/sherlock/screen.h b/engines/sherlock/screen.h index 8fda9cbb9c..2e0cef72ca 100644 --- a/engines/sherlock/screen.h +++ b/engines/sherlock/screen.h @@ -25,33 +25,25 @@ #include "common/list.h" #include "common/rect.h" -#include "common/serializer.h" #include "sherlock/surface.h" #include "sherlock/resources.h" +#include "sherlock/saveload.h" namespace Sherlock { #define PALETTE_SIZE 768 #define PALETTE_COUNT 256 #define VGA_COLOR_TRANS(x) ((x) * 255 / 63) +#define BG_GREYSCALE_RANGE_END 229 enum { + BLACK = 0, INFO_BLACK = 1, - INFO_FOREGROUND = 11, - INFO_BACKGROUND = 1, BORDER_COLOR = 237, - INV_FOREGROUND = 14, - INV_BACKGROUND = 1, - COMMAND_HIGHLIGHTED = 10, - COMMAND_FOREGROUND = 15, COMMAND_BACKGROUND = 4, - COMMAND_NULL = 248, - BUTTON_TOP = 233, - BUTTON_MIDDLE = 244, - BUTTON_BOTTOM = 248, + BUTTON_BACKGROUND = 235, TALK_FOREGROUND = 12, - TALK_NULL = 16, - PEN_COLOR = 250 + TALK_NULL = 16 }; class SherlockEngine; @@ -59,18 +51,13 @@ class SherlockEngine; class Screen : public Surface { private: SherlockEngine *_vm; - int _fontNumber; Common::List<Common::Rect> _dirtyRects; uint32 _transitionSeed; - ImageFile *_font; - int _fontHeight; Surface _sceneSurface; // Rose Tattoo fields int _fadeBytesRead, _fadeBytesToRead; int _oldFadePercent; - byte _lookupTable[PALETTE_COUNT]; - byte _lookupTable1[PALETTE_COUNT]; private: /** * Merges together overlapping dirty areas of the screen @@ -81,11 +68,6 @@ private: * Returns the union of two dirty area rectangles */ bool unionRectangle(Common::Rect &destRect, const Common::Rect &src1, const Common::Rect &src2); - - /** - * Draws the given string into the back buffer using the images stored in _font - */ - void writeString(const Common::String &str, const Common::Point &pt, byte color); protected: /** * Adds a rectangle to the list of modified areas of the screen during the @@ -99,22 +81,22 @@ public: byte _cMap[PALETTE_SIZE]; byte _sMap[PALETTE_SIZE]; byte _tMap[PALETTE_SIZE]; - int _currentScroll, _targetScroll; - int _scrollSize, _scrollSpeed; bool _flushScreen; + Common::Point _currentScroll; public: + static Screen *init(SherlockEngine *vm); Screen(SherlockEngine *vm); virtual ~Screen(); /** - * Set the font to use for writing text on the screen + * Handles updating any dirty areas of the screen Surface object to the physical screen */ - void setFont(int fontNumber); + void update(); /** - * Handles updating any dirty areas of the screen Surface object to the physical screen + * Makes the whole screen dirty, Hack for 3DO movie playing */ - void update(); + void makeAllDirty(); /** * Return the currently active palette @@ -152,6 +134,13 @@ public: void verticalTransition(); /** + * Fade backbuffer 1 into screen (3DO RGB!) + */ + void fadeIntoScreen3DO(int speed); + + void blitFrom3DOcolorLimit(uint16 color); + + /** * Prints the text passed onto the back buffer at the given position and color. * The string is then blitted to the screen */ @@ -192,41 +181,24 @@ public: int16 *width, int16 *height, int scaleVal); /** - * Returns the width of a string in pixels - */ - int stringWidth(const Common::String &str); - - /** - * Returns the width of a character in pixels + * Variation of flushImage/flushScaleImage that takes in and updates a rect */ - int charWidth(char c); + void flushImage(ImageFrame *frame, const Common::Point &pt, Common::Rect &newBounds, int scaleVal); /** - * Fills an area on the back buffer, and then copies it to the screen + * Copies data from the back buffer to the screen */ - void vgaBar(const Common::Rect &r, int color); + void blockMove(const Common::Rect &r); /** - * Draws a button for use in the inventory, talk, and examine dialogs. + * Copies the entire screen from the back buffer */ - void makeButton(const Common::Rect &bounds, int textX, const Common::String &str); + void blockMove(); /** - * Prints an interface command with the first letter highlighted to indicate - * what keyboard shortcut is associated with it - */ - void buttonPrint(const Common::Point &pt, byte color, bool slamIt, const Common::String &str); - - /** - * Draw a panel in the back buffer with a raised area effect around the edges - */ - void makePanel(const Common::Rect &r); - - /** - * Draw a field in the back buffer with a raised area effect around the edges, - * suitable for text input. + * Fills an area on the back buffer, and then copies it to the screen */ - void makeField(const Common::Rect &r); + void vgaBar(const Common::Rect &r, int color); /** * Sets the active back buffer pointer to a restricted sub-area of the first back buffer @@ -243,22 +215,22 @@ public: */ Common::Rect getDisplayBounds(); - int fontNumber() const { return _fontNumber; } - /** * Synchronize the data for a savegame */ - void synchronize(Common::Serializer &s); + void synchronize(Serializer &s); + + /** + * Draws the given string into the back buffer using the images stored in _font + */ + virtual void writeString(const Common::String &str, const Common::Point &pt, byte overrideColor); + // Rose Tattoo specific methods void initPaletteFade(int bytesToRead); int fadeRead(Common::SeekableReadStream &stream, byte *buf, int totalSize); - void setupBGArea(const byte cMap[PALETTE_SIZE]); - - void initScrollVars(); - /** * Translate a palette from 6-bit RGB values to full 8-bit values suitable for passing * to the underlying palette manager diff --git a/engines/sherlock/sherlock.cpp b/engines/sherlock/sherlock.cpp index d3a409a67b..ae77c91009 100644 --- a/engines/sherlock/sherlock.cpp +++ b/engines/sherlock/sherlock.cpp @@ -33,6 +33,7 @@ SherlockEngine::SherlockEngine(OSystem *syst, const SherlockGameDescription *gam _animation = nullptr; _debugger = nullptr; _events = nullptr; + _fixedText = nullptr; _inventory = nullptr; _journal = nullptr; _map = nullptr; @@ -56,13 +57,14 @@ SherlockEngine::~SherlockEngine() { delete _animation; delete _debugger; delete _events; + delete _fixedText; delete _journal; delete _map; - delete _music; delete _people; delete _saves; delete _scene; delete _screen; + delete _music; delete _sound; delete _talk; delete _ui; @@ -71,11 +73,15 @@ SherlockEngine::~SherlockEngine() { } void SherlockEngine::initialize() { - DebugMan.addDebugChannel(kDebugScript, "scripts", "Script debug level"); + DebugMan.addDebugChannel(kDebugLevelScript, "scripts", "Script debug level"); + DebugMan.addDebugChannel(kDebugLevelAdLibDriver, "AdLib", "AdLib driver debugging"); + DebugMan.addDebugChannel(kDebugLevelMT32Driver, "MT32", "MT32 driver debugging"); + DebugMan.addDebugChannel(kDebugLevelMusic, "Music", "Music debugging"); + Fonts::setVm(this); ImageFile::setVm(this); - Object::setVm(this); - Sprite::setVm(this); + ImageFile3DO::setVm(this); + BaseObject::setVm(this); if (isDemo()) { Common::File f; @@ -87,22 +93,29 @@ void SherlockEngine::initialize() { _res = new Resources(this); _animation = new Animation(this); - _debugger = new Debugger(this); + _debugger = Debugger::init(this); _events = new Events(this); - _inventory = new Inventory(this); - _map = new Map(this); + _fixedText = FixedText::init(this); + _inventory = Inventory::init(this); + _map = Map::init(this); _music = new Music(this, _mixer); - _journal = new Journal(this); - _people = new People(this); - _saves = new SaveManager(this, _targetName); + _journal = Journal::init(this); + _people = People::init(this); + _saves = SaveManager::init(this, _targetName); _scene = Scene::init(this); - _screen = new Screen(this); + _screen = Screen::init(this); _sound = new Sound(this, _mixer); _talk = Talk::init(this); _ui = UserInterface::init(this); // Load game settings loadConfig(); + + if (getPlatform() == Common::kPlatform3DO) { + // Disable portraits on 3DO + // 3DO does not include portrait data + _people->_portraitsOn = false; + } } Common::Error SherlockEngine::run() { @@ -115,7 +128,7 @@ Common::Error SherlockEngine::run() { // If requested, load a savegame instead of showing the intro if (ConfMan.hasKey("save_slot")) { int saveSlot = ConfMan.getInt("save_slot"); - if (saveSlot >= 1 && saveSlot <= MAX_SAVEGAME_SLOTS) + if (saveSlot >= 0 && saveSlot <= MAX_SAVEGAME_SLOTS) _loadGameSlot = saveSlot; } @@ -166,7 +179,7 @@ void SherlockEngine::sceneLoop() { // Handle any input from the keyboard or mouse handleInput(); - if (_people->_hSavedPos.x == -1) { + if (_people->_savedPos.x == -1) { _canLoadSave = true; _scene->doBgAnim(); _canLoadSave = false; @@ -175,11 +188,10 @@ void SherlockEngine::sceneLoop() { _scene->freeScene(); _people->freeWalk(); - } void SherlockEngine::handleInput() { - _canLoadSave = true; + _canLoadSave = _ui->_menuMode == STD_MODE || _ui->_menuMode == LAB_MODE; _events->pollEventsAndWait(); _canLoadSave = false; @@ -203,11 +215,15 @@ void SherlockEngine::setFlags(int flagNum) { _scene->checkSceneFlags(true); } +void SherlockEngine::setFlagsDirect(int flagNum) { + _flags[ABS(flagNum)] = flagNum >= 0; +} + void SherlockEngine::loadConfig() { // Load sound settings syncSoundSettings(); - ConfMan.registerDefault("font", 1); + ConfMan.registerDefault("font", getGameID() == GType_SerratedScalpel ? 1 : 4); _screen->setFont(ConfMan.getInt("font")); if (getGameID() == GType_SerratedScalpel) @@ -240,7 +256,7 @@ void SherlockEngine::syncSoundSettings() { _music->syncMusicSettings(); } -void SherlockEngine::synchronize(Common::Serializer &s) { +void SherlockEngine::synchronize(Serializer &s) { for (uint idx = 0; idx < _flags.size(); ++idx) s.syncAsByte(_flags[idx]); } @@ -263,4 +279,4 @@ Common::Error SherlockEngine::saveGameState(int slot, const Common::String &desc return Common::kNoError; } -} // End of namespace Comet +} // End of namespace Sherlock diff --git a/engines/sherlock/sherlock.h b/engines/sherlock/sherlock.h index e71c729893..c05680eb08 100644 --- a/engines/sherlock/sherlock.h +++ b/engines/sherlock/sherlock.h @@ -35,6 +35,7 @@ #include "sherlock/animation.h" #include "sherlock/debugger.h" #include "sherlock/events.h" +#include "sherlock/fixed_text.h" #include "sherlock/inventory.h" #include "sherlock/journal.h" #include "sherlock/map.h" @@ -51,7 +52,10 @@ namespace Sherlock { enum { - kDebugScript = 1 << 0 + kDebugLevelScript = 1 << 0, + kDebugLevelAdLibDriver = 2 << 0, + kDebugLevelMT32Driver = 3 << 0, + kDebugLevelMusic = 4 << 0 }; enum GameType { @@ -61,7 +65,14 @@ enum GameType { #define SHERLOCK_SCREEN_WIDTH _vm->_screen->w() #define SHERLOCK_SCREEN_HEIGHT _vm->_screen->h() +#define SHERLOCK_SCENE_WIDTH _vm->_screen->_backBuffer1.w() #define SHERLOCK_SCENE_HEIGHT (IS_SERRATED_SCALPEL ? 138 : 480) +#define SCENES_COUNT (IS_SERRATED_SCALPEL ? 63 : 101) +#define MAX_BGSHAPES (IS_SERRATED_SCALPEL ? 64 : 150) + +#define COL_INFO_FOREGROUND (IS_SERRATED_SCALPEL ? (byte)Scalpel::INFO_FOREGROUND : (byte)Tattoo::INFO_FOREGROUND) +#define COL_PEN_COLOR (IS_SERRATED_SCALPEL ? (byte)Scalpel::PEN_COLOR : (byte)Tattoo::PEN_COLOR) +#define COL_PEN_HIGHLIGHT (IS_SERRATED_SCALPEL ? 15 : 129) struct SherlockGameDescription; @@ -78,11 +89,6 @@ private: * Handle all player input */ void handleInput(); - - /** - * Load game configuration esttings - */ - void loadConfig(); protected: /** * Does basic initialization of the game engine @@ -97,11 +103,17 @@ protected: * Returns a list of features the game itself supports */ virtual bool hasFeature(EngineFeature f) const; + + /** + * Load game configuration esttings + */ + virtual void loadConfig(); public: const SherlockGameDescription *_gameDescription; Animation *_animation; Debugger *_debugger; Events *_events; + FixedText *_fixedText; Inventory *_inventory; Journal *_journal; Map *_map; @@ -156,6 +168,11 @@ public: virtual void syncSoundSettings(); /** + * Saves game configuration information + */ + virtual void saveConfig(); + + /** * Returns whether the version is a demo */ virtual bool isDemo() const; @@ -171,6 +188,11 @@ public: Common::Platform getPlatform() const; /** + * Return the game's language + */ + Common::Language getLanguage() const; + + /** * Return a random number */ int getRandomNumber(int limit) { return _randomSource.getRandomNumber(limit - 1); } @@ -189,18 +211,20 @@ public: void setFlags(int flagNum); /** - * Saves game configuration information + * Set a global flag to 0 or 1 depending on whether the passed flag is negative or positive. + * @remarks We don't use the global setFlags method because we don't want to check scene flags */ - void saveConfig(); + void setFlagsDirect(int flagNum); /** * Synchronize the data for a savegame */ - void synchronize(Common::Serializer &s); + void synchronize(Serializer &s); }; #define IS_ROSE_TATTOO (_vm->getGameID() == GType_RoseTattoo) #define IS_SERRATED_SCALPEL (_vm->getGameID() == GType_SerratedScalpel) +#define IS_3DO (_vm->getPlatform() == Common::kPlatform3DO) } // End of namespace Sherlock diff --git a/engines/sherlock/sound.cpp b/engines/sherlock/sound.cpp index 390576e98e..fd51462bc0 100644 --- a/engines/sherlock/sound.cpp +++ b/engines/sherlock/sound.cpp @@ -27,6 +27,8 @@ #include "common/algorithm.h" #include "audio/mixer.h" #include "audio/decoders/raw.h" +#include "audio/decoders/aiff.h" +#include "audio/decoders/wave.h" namespace Sherlock { @@ -53,17 +55,19 @@ static const uint8 creativeADPCM_AdjustMap[64] = { Sound::Sound(SherlockEngine *vm, Audio::Mixer *mixer) : _vm(vm), _mixer(mixer) { _digitized = false; _voices = 0; - _diskSoundPlaying = false; _soundPlaying = false; - _soundIsOn = &_soundPlaying; + _speechPlaying = false; _curPriority = 0; - _digiBuf = nullptr; - _midiDrvLoaded = false; - _musicVolume = 0; + _soundVolume = 255; _soundOn = true; _speechOn = true; + if (IS_3DO) { + // 3DO: we don't need to prepare anything for sound + return; + } + _vm->_res->addToCache("MUSIC.LIB"); if (!_vm->_interactiveFl) _vm->_res->addToCache("TITLE.SND"); @@ -85,12 +89,13 @@ Sound::Sound(SherlockEngine *vm, Audio::Mixer *mixer) : _vm(vm), _mixer(mixer) { void Sound::syncSoundSettings() { _digitized = !ConfMan.getBool("mute"); - _voices = !ConfMan.getBool("mute") && !ConfMan.getBool("speech_mute") ? 1 : 0; + _speechOn = !ConfMan.getBool("mute") && !ConfMan.getBool("speech_mute"); + _voices = _speechOn ? 1 : 0; } void Sound::loadSound(const Common::String &name, int priority) { // No implementation required in ScummVM - warning("loadSound"); + //warning("loadSound"); } byte Sound::decodeSample(byte sample, byte &reference, int16 &scale) { @@ -116,59 +121,34 @@ byte Sound::decodeSample(byte sample, byte &reference, int16 &scale) { } bool Sound::playSound(const Common::String &name, WaitType waitType, int priority, const char *libraryFilename) { - Resources &res = *_vm->_res; stopSound(); Common::String filename = name; - if (!filename.contains('.')) - filename += ".SND"; - - Common::String libFilename(libraryFilename); - Common::SeekableReadStream *stream = libFilename.empty() ? res.load(filename) : res.load(filename, libFilename); - - if (!stream) - error("Unable to find sound file '%s'", filename.c_str()); - - stream->skip(2); - int size = stream->readUint32BE(); - int rate = stream->readUint16BE(); - byte *data = (byte *)malloc(size); - byte *ptr = data; - stream->read(ptr, size); - delete stream; - - assert(size > 2); - - byte *decoded = (byte *)malloc((size - 1) * 2); - - // Holmes uses Creative ADPCM 4-bit data - int counter = 0; - byte reference = ptr[0]; - int16 scale = 0; - - for(int i = 1; i < size; i++) { - decoded[counter++] = decodeSample((ptr[i]>>4)&0x0f, reference, scale); - decoded[counter++] = decodeSample((ptr[i]>>0)&0x0f, reference, scale); + if (!filename.contains('.')) { + if (!IS_3DO) { + if (IS_SERRATED_SCALPEL) { + filename += ".SND"; + } else { + filename += ".WAV"; + } + } else { + // 3DO uses .aiff extension + filename += ".AIFF"; + if (!filename.contains('/')) { + // if no directory was given, use the room sounds directory + filename = "rooms/sounds/" + filename; + } + } } - free(data); - -#if 0 - // Debug : used to dump files - Common::DumpFile outFile; - outFile.open(filename); - outFile.write(decoded, (size - 2) * 2); - outFile.flush(); - outFile.close(); -#endif + Audio::SoundHandle soundHandle = (IS_SERRATED_SCALPEL) ? _scalpelEffectsHandle : getFreeSoundHandle(); + if (!playSoundResource(filename, libraryFilename, Audio::Mixer::kSFXSoundType, soundHandle)) + error("Could not find sound resource - %s", filename.c_str()); - Audio::AudioStream *audioStream = Audio::makeRawStream(decoded, (size - 2) * 2, rate, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES); - _mixer->playStream(Audio::Mixer::kPlainSoundType, &_effectsHandle, audioStream, -1, Audio::Mixer::kMaxChannelVolume); _soundPlaying = true; _curPriority = priority; if (waitType == WAIT_RETURN_IMMEDIATELY) { - _diskSoundPlaying = true; return true; } @@ -180,22 +160,23 @@ bool Sound::playSound(const Common::String &name, WaitType waitType, int priorit retval = false; break; } - } while (!_vm->shouldQuit() && _mixer->isSoundHandleActive(_effectsHandle)); + } while (!_vm->shouldQuit() && _mixer->isSoundHandleActive(soundHandle)); _soundPlaying = false; - _mixer->stopHandle(_effectsHandle); + _mixer->stopHandle(soundHandle); return retval; } void Sound::playLoadedSound(int bufNum, WaitType waitType) { - if (_mixer->isSoundHandleActive(_effectsHandle) && (_curPriority > _vm->_scene->_sounds[bufNum]._priority)) - return; + if (IS_SERRATED_SCALPEL) { + if (_mixer->isSoundHandleActive(_scalpelEffectsHandle) && (_curPriority > _vm->_scene->_sounds[bufNum]._priority)) + return; - stopSound(); - playSound(_vm->_scene->_sounds[bufNum]._name, waitType, _vm->_scene->_sounds[bufNum]._priority); + stopSound(); + } - return; + playSound(_vm->_scene->_sounds[bufNum]._name, waitType, _vm->_scene->_sounds[bufNum]._priority); } void Sound::freeLoadedSounds() { @@ -205,23 +186,117 @@ void Sound::freeLoadedSounds() { } void Sound::stopSound() { - _mixer->stopHandle(_effectsHandle); -} - -void Sound::stopSndFuncPtr(int v1, int v2) { - // TODO - warning("TODO: Sound::stopSndFuncPtr"); + if (IS_SERRATED_SCALPEL) { + _mixer->stopHandle(_scalpelEffectsHandle); + } else { + for (int i = 0; i < MAX_MIXER_CHANNELS; i++) + _mixer->stopHandle(_tattooEffectsHandle[i]); + } } void Sound::freeDigiSound() { - delete[] _digiBuf; - _digiBuf = nullptr; - _diskSoundPlaying = false; _soundPlaying = false; } -void Sound::setMIDIVolume(int volume) { - // TODO +Audio::SoundHandle Sound::getFreeSoundHandle() { + for (int i = 0; i < MAX_MIXER_CHANNELS; i++) { + if (!_mixer->isSoundHandleActive(_tattooEffectsHandle[i])) + return _tattooEffectsHandle[i]; + } + + error("getFreeSoundHandle: No sound handle found"); +} + +void Sound::setVolume(int volume) { + warning("TODO: setVolume - %d", volume); +} + +void Sound::playSpeech(const Common::String &name) { + Resources &res = *_vm->_res; + Scene &scene = *_vm->_scene; + stopSpeech(); + + // TODO: Technically Scalpel has an sfx command which I've set to call this method because it sets the + // _voice variable as if it were speech. Need to do a play-through of Scalpel and see if it's ever called. + // If so, will need to enhance this method to handle the Serrated Scalpel voice resources + assert(IS_ROSE_TATTOO); + + // Figure out which speech library to use + Common::String libraryName = Common::String::format("speech%02d.lib", scene._currentScene); + if ((!scumm_strnicmp(name.c_str(), "SLVE12S", 7)) || (!scumm_strnicmp(name.c_str(), "WATS12X", 7)) + || (!scumm_strnicmp(name.c_str(), "HOLM12X", 7))) + libraryName = "SPEECH12.LIB"; + + // If the speech library file doesn't even exist, then we can't play anything + Common::File f; + if (!f.exists(libraryName)) + return; + + // Ensure the given library is in the cache + res.addToCache(libraryName); + + if (playSoundResource(name, libraryName, Audio::Mixer::kSpeechSoundType, _speechHandle)) + _speechPlaying = true; +} + +void Sound::stopSpeech() { + _mixer->stopHandle(_speechHandle); + _speechPlaying = false; +} + +bool Sound::isSpeechPlaying() { + _speechPlaying = _mixer->isSoundHandleActive(_speechHandle); + return _speechPlaying; +} + +bool Sound::playSoundResource(const Common::String &name, const Common::String &libFilename, + Audio::Mixer::SoundType soundType, Audio::SoundHandle &handle) { + Resources &res = *_vm->_res; + Common::SeekableReadStream *stream = libFilename.empty() ? res.load(name) : res.load(name, libFilename, true); + if (!stream) + return false; + + Audio::AudioStream *audioStream; + if (IS_ROSE_TATTOO && soundType == Audio::Mixer::kSpeechSoundType) { + audioStream = Audio::makeRawStream(stream, 11025, Audio::FLAG_UNSIGNED); + } else if (IS_3DO) { + // 3DO: AIFF file + audioStream = Audio::makeAIFFStream(stream, DisposeAfterUse::YES); + } else if (IS_SERRATED_SCALPEL) { + stream->skip(2); + int size = stream->readUint32BE(); + int rate = stream->readUint16BE(); + byte *data = (byte *)malloc(size); + byte *ptr = data; + stream->read(ptr, size); + delete stream; + + assert(size > 2); + + byte *decoded = (byte *)malloc((size - 1) * 2); + + // Holmes uses Creative ADPCM 4-bit data + int counter = 0; + byte reference = ptr[0]; + int16 scale = 0; + + for (int i = 1; i < size; i++) { + decoded[counter++] = decodeSample((ptr[i] >> 4) & 0x0f, reference, scale); + decoded[counter++] = decodeSample((ptr[i] >> 0) & 0x0f, reference, scale); + } + + free(data); + + audioStream = Audio::makeRawStream(decoded, (size - 2) * 2, rate, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES); + } else { + audioStream = Audio::makeWAVStream(stream, DisposeAfterUse::YES); + } + + if (!audioStream) + return false; + + _mixer->playStream(soundType, &handle, audioStream, -1, Audio::Mixer::kMaxChannelVolume); + return true; } } // End of namespace Sherlock diff --git a/engines/sherlock/sound.h b/engines/sherlock/sound.h index e1c0777763..b2d1584e85 100644 --- a/engines/sherlock/sound.h +++ b/engines/sherlock/sound.h @@ -39,26 +39,37 @@ enum WaitType { WAIT_RETURN_IMMEDIATELY = 0, WAIT_FINISH = 1, WAIT_KBD_OR_FINISH = 2 }; +#define MAX_MIXER_CHANNELS 10 + class Sound { private: SherlockEngine *_vm; Audio::Mixer *_mixer; - Audio::SoundHandle _effectsHandle; + Audio::SoundHandle _scalpelEffectsHandle; + Audio::SoundHandle _tattooEffectsHandle[MAX_MIXER_CHANNELS]; + Audio::SoundHandle _speechHandle; int _curPriority; + /** + * Decode a sound sample + */ byte decodeSample(byte sample, byte& reference, int16& scale); + + /** + * Handle playing a sound or speech + */ + bool playSoundResource(const Common::String &name, const Common::String &libFilename, + Audio::Mixer::SoundType soundType, Audio::SoundHandle &handle); public: bool _digitized; int _voices; bool _soundOn; bool _speechOn; - bool _diskSoundPlaying; bool _soundPlaying; - bool *_soundIsOn; - byte *_digiBuf; - bool _midiDrvLoaded; - Common::String _currentSongName, _nextSongName; - int _musicVolume; + bool _speechPlaying; + int _soundVolume; + + Common::String _talkSoundFile; public: Sound(SherlockEngine *vm, Audio::Mixer *mixer); @@ -92,9 +103,26 @@ public: */ void stopSound(); - void stopSndFuncPtr(int v1, int v2); void freeDigiSound(); - void setMIDIVolume(int volume); + + Audio::SoundHandle getFreeSoundHandle(); + + void setVolume(int volume); + + /** + * Play a specified voice resource + */ + void playSpeech(const Common::String &name); + + /** + * Stop any currently playing speech + */ + void stopSpeech(); + + /** + * Returns true if speech is currently playing + */ + bool isSpeechPlaying(); }; } // End of namespace Sherlock diff --git a/engines/sherlock/surface.cpp b/engines/sherlock/surface.cpp index 5a9e59e01b..b56692c704 100644 --- a/engines/sherlock/surface.cpp +++ b/engines/sherlock/surface.cpp @@ -28,11 +28,11 @@ namespace Sherlock { -Surface::Surface(uint16 width, uint16 height) : _freePixels(true) { +Surface::Surface(uint16 width, uint16 height) : Fonts(), _freePixels(true) { create(width, height); } -Surface::Surface() : _freePixels(false) { +Surface::Surface() : Fonts(), _freePixels(false) { } Surface::~Surface() { @@ -44,10 +44,18 @@ void Surface::create(uint16 width, uint16 height) { if (_freePixels) _surface.free(); - _surface.create(width, height, Graphics::PixelFormat::createFormatCLUT8()); + if (_vm->getPlatform() == Common::kPlatform3DO) { + _surface.create(width, height, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)); + } else { + _surface.create(width, height, Graphics::PixelFormat::createFormatCLUT8()); + } _freePixels = true; } +Graphics::PixelFormat Surface::getPixelFormat() { + return _surface.format; +} + void Surface::blitFrom(const Surface &src) { blitFrom(src, Common::Point(0, 0)); } @@ -92,17 +100,51 @@ void Surface::blitFrom(const Surface &src, const Common::Point &pt, const Common } void Surface::transBlitFrom(const ImageFrame &src, const Common::Point &pt, - bool flipped, int overrideColor) { - transBlitFrom(src._frame, pt + src._offset, flipped, overrideColor); + bool flipped, int overrideColor, int scaleVal) { + Common::Point drawPt(pt.x + src.sDrawXOffset(scaleVal), pt.y + src.sDrawYOffset(scaleVal)); + transBlitFrom(src._frame, drawPt, flipped, overrideColor, scaleVal); } void Surface::transBlitFrom(const Surface &src, const Common::Point &pt, - bool flipped, int overrideColor) { + bool flipped, int overrideColor, int scaleVal) { const Graphics::Surface &s = src._surface; - transBlitFrom(s, pt, flipped, overrideColor); + transBlitFrom(s, pt, flipped, overrideColor, scaleVal); } void Surface::transBlitFrom(const Graphics::Surface &src, const Common::Point &pt, + bool flipped, int overrideColor, int scaleVal) { + if (scaleVal == SCALE_THRESHOLD) { + transBlitFromUnscaled(src, pt, flipped, overrideColor); + return; + } + + int destWidth = src.w * SCALE_THRESHOLD / scaleVal; + int destHeight = src.h * SCALE_THRESHOLD / scaleVal; + + // Loop through drawing output lines + for (int destY = pt.y, scaleYCtr = 0; destY < (pt.y + destHeight); ++destY, scaleYCtr += scaleVal) { + if (destY < 0 || destY >= this->h()) + continue; + const byte *srcLine = (const byte *)src.getBasePtr(0, scaleYCtr / SCALE_THRESHOLD); + byte *destLine = (byte *)getBasePtr(pt.x, destY); + + // Loop through drawing individual rows + for (int xCtr = 0, scaleXCtr = 0; xCtr < destWidth; ++xCtr, scaleXCtr += scaleVal) { + int destX = pt.x + xCtr; + if (destX < 0 || destX >= this->w()) + continue; + + byte srcVal = srcLine[flipped ? src.w - scaleXCtr / SCALE_THRESHOLD - 1 : scaleXCtr / SCALE_THRESHOLD]; + if (srcVal != TRANSPARENCY) + destLine[xCtr] = srcVal; + } + } + + // Mark the affected area + addDirtyRect(Common::Rect(pt.x, pt.y, pt.x + destWidth, pt.y + destHeight)); +} + +void Surface::transBlitFromUnscaled(const Graphics::Surface &src, const Common::Point &pt, bool flipped, int overrideColor) { Common::Rect drawRect(0, 0, src.w, src.h); Common::Rect destRect(pt.x, pt.y, pt.x + src.w, pt.y + src.h); @@ -120,19 +162,42 @@ void Surface::transBlitFrom(const Graphics::Surface &src, const Common::Point &p addDirtyRect(Common::Rect(destPt.x, destPt.y, destPt.x + drawRect.width(), destPt.y + drawRect.height())); - // Draw loop - const int TRANSPARENCY = 0xFF; - for (int yp = 0; yp < drawRect.height(); ++yp) { - const byte *srcP = (const byte *)src.getBasePtr( - flipped ? drawRect.right - 1 : drawRect.left, drawRect.top + yp); - byte *destP = (byte *)getBasePtr(destPt.x, destPt.y + yp); - - for (int xp = 0; xp < drawRect.width(); ++xp, ++destP) { - if (*srcP != TRANSPARENCY) - *destP = overrideColor ? overrideColor : *srcP; - - srcP = flipped ? srcP - 1 : srcP + 1; + switch (src.format.bytesPerPixel) { + case 1: + // 8-bit palettized: Draw loop + assert(_surface.format.bytesPerPixel == 1); // Security check + for (int yp = 0; yp < drawRect.height(); ++yp) { + const byte *srcP = (const byte *)src.getBasePtr( + flipped ? drawRect.right - 1 : drawRect.left, drawRect.top + yp); + byte *destP = (byte *)getBasePtr(destPt.x, destPt.y + yp); + + for (int xp = 0; xp < drawRect.width(); ++xp, ++destP) { + if (*srcP != TRANSPARENCY) + *destP = overrideColor ? overrideColor : *srcP; + + srcP = flipped ? srcP - 1 : srcP + 1; + } } + break; + case 2: + // 3DO 15-bit RGB565: Draw loop + assert(_surface.format.bytesPerPixel == 2); // Security check + for (int yp = 0; yp < drawRect.height(); ++yp) { + const uint16 *srcP = (const uint16 *)src.getBasePtr( + flipped ? drawRect.right - 1 : drawRect.left, drawRect.top + yp); + uint16 *destP = (uint16 *)getBasePtr(destPt.x, destPt.y + yp); + + for (int xp = 0; xp < drawRect.width(); ++xp, ++destP) { + if (*srcP) // RGB 0, 0, 0 -> transparent on 3DO + *destP = *srcP; // overrideColor ? overrideColor : *srcP; + + srcP = flipped ? srcP - 1 : srcP + 1; + } + } + break; + default: + error("Surface: unsupported bytesperpixel"); + break; } } @@ -145,6 +210,10 @@ void Surface::fillRect(const Common::Rect &r, byte color) { addDirtyRect(r); } +void Surface::fill(uint16 color) { + _surface.fillRect(Common::Rect(_surface.w, _surface.h), color); +} + bool Surface::clip(Common::Rect &srcBounds, Common::Rect &destBounds) { if (destBounds.left >= _surface.w || destBounds.top >= _surface.h || destBounds.right <= 0 || destBounds.bottom <= 0) @@ -185,17 +254,28 @@ void Surface::free() { } } -void Surface::setPixels(byte *pixels, int width, int height) { - _surface.format = Graphics::PixelFormat::createFormatCLUT8(); - _surface.w = _surface.pitch = width; +void Surface::setPixels(byte *pixels, int width, int height, Graphics::PixelFormat pixelFormat) { + _surface.format = pixelFormat; + _surface.w = width; _surface.h = height; + _surface.pitch = width * pixelFormat.bytesPerPixel; _surface.setPixels(pixels); } -void Surface::maskArea(const ImageFrame &src, const Common::Point &pt, int scrollX) { - // TODO - error("TODO: maskArea"); +void Surface::writeString(const Common::String &str, const Common::Point &pt, byte overrideColor) { + Fonts::writeString(this, str, pt, overrideColor); } +void Surface::writeFancyString(const Common::String &str, const Common::Point &pt, byte overrideColor1, byte overrideColor2) { + writeString(str, Common::Point(pt.x, pt.y), overrideColor1); + writeString(str, Common::Point(pt.x + 1, pt.y), overrideColor1); + writeString(str, Common::Point(pt.x + 2, pt.y), overrideColor1); + writeString(str, Common::Point(pt.x, pt.y + 1), overrideColor1); + writeString(str, Common::Point(pt.x + 2, pt.y + 1), overrideColor1); + writeString(str, Common::Point(pt.x, pt.y + 2), overrideColor1); + writeString(str, Common::Point(pt.x + 1, pt.y + 2), overrideColor1); + writeString(str, Common::Point(pt.x + 2, pt.y + 2), overrideColor1); + writeString(str, Common::Point(pt.x + 1, pt.y + 1), overrideColor2); +} } // End of namespace Sherlock diff --git a/engines/sherlock/surface.h b/engines/sherlock/surface.h index 663f87f37f..385fb1793e 100644 --- a/engines/sherlock/surface.h +++ b/engines/sherlock/surface.h @@ -24,13 +24,18 @@ #define SHERLOCK_GRAPHICS_H #include "common/rect.h" +#include "common/platform.h" #include "graphics/surface.h" +#include "sherlock/fonts.h" namespace Sherlock { +#define SCALE_THRESHOLD 0x100 +#define TRANSPARENCY 255 + struct ImageFrame; -class Surface { +class Surface: public Fonts { private: bool _freePixels; @@ -45,14 +50,16 @@ private: void blitFrom(const Graphics::Surface &src); /** - * Draws a surface at a given position within this surface + * Draws a sub-section of a surface at a given position within this surface */ - void blitFrom(const Graphics::Surface &src, const Common::Point &pt); + void blitFrom(const Graphics::Surface &src, const Common::Point &pt, const Common::Rect &srcBounds); /** - * Draws a sub-section of a surface at a given position within this surface + * Draws a surface at a given position within this surface with transparency */ - void blitFrom(const Graphics::Surface &src, const Common::Point &pt, const Common::Rect &srcBounds); + void transBlitFromUnscaled(const Graphics::Surface &src, const Common::Point &pt, bool flipped, + int overrideColor); + protected: Graphics::Surface _surface; @@ -68,6 +75,8 @@ public: */ void create(uint16 width, uint16 height); + Graphics::PixelFormat getPixelFormat(); + /** * Copy a surface into this one */ @@ -99,22 +108,27 @@ public: void blitFrom(const ImageFrame &src, const Common::Point &pt, const Common::Rect &srcBounds); /** + * Draws a surface at a given position within this surface + */ + void blitFrom(const Graphics::Surface &src, const Common::Point &pt); + + /** * Draws an image frame at a given position within this surface with transparency */ void transBlitFrom(const ImageFrame &src, const Common::Point &pt, - bool flipped = false, int overrideColor = 0); + bool flipped = false, int overrideColor = 0, int scaleVal = 256); /** * Draws a surface at a given position within this surface with transparency */ void transBlitFrom(const Surface &src, const Common::Point &pt, - bool flipped = false, int overrideColor = 0); + bool flipped = false, int overrideColor = 0, int scaleVal = 256); /** * Draws a surface at a given position within this surface with transparency */ void transBlitFrom(const Graphics::Surface &src, const Common::Point &pt, - bool flipped = false, int overrideColor = 0); + bool flipped = false, int overrideColor = 0, int scaleVal = 256); /** * Fill a given area of the surface with a given color @@ -126,10 +140,10 @@ public: */ void fillRect(const Common::Rect &r, byte color); - void maskArea(const ImageFrame &src, const Common::Point &pt, int scrollX); + void fill(uint16 color); /** - * Clear the screen + * Clear the surface */ void clear(); @@ -139,9 +153,20 @@ public: void free(); /** + * Returns true if the surface is empty + */ + bool empty() const { return _surface.getPixels() == nullptr; } + + /** * Set the pixels for the surface to an existing data block */ - void setPixels(byte *pixels, int width, int height); + void setPixels(byte *pixels, int width, int height, Graphics::PixelFormat format); + + /** + * Draws the given string into the back buffer using the images stored in _font + */ + virtual void writeString(const Common::String &str, const Common::Point &pt, byte overrideColor); + void writeFancyString(const Common::String &str, const Common::Point &pt, byte overrideColor1, byte overrideColor2); inline uint16 w() const { return _surface.w; } inline uint16 h() const { return _surface.h; } @@ -149,6 +174,7 @@ public: inline byte *getPixels() { return (byte *)_surface.getPixels(); } inline byte *getBasePtr(int x, int y) { return (byte *)_surface.getBasePtr(x, y); } inline const byte *getBasePtr(int x, int y) const { return (const byte *)_surface.getBasePtr(x, y); } + inline Graphics::Surface &getRawSurface() { return _surface; } inline void hLine(int x, int y, int x2, uint32 color) { _surface.hLine(x, y, x2, color); } inline void vLine(int x, int y, int y2, uint32 color) { _surface.vLine(x, y, y2, color); } }; diff --git a/engines/sherlock/talk.cpp b/engines/sherlock/talk.cpp index 59897e2c2a..fa00b9d715 100644 --- a/engines/sherlock/talk.cpp +++ b/engines/sherlock/talk.cpp @@ -23,162 +23,29 @@ #include "sherlock/talk.h" #include "sherlock/sherlock.h" #include "sherlock/screen.h" +#include "sherlock/scalpel/scalpel.h" +#include "sherlock/scalpel/scalpel_people.h" +#include "sherlock/scalpel/scalpel_talk.h" #include "sherlock/scalpel/scalpel_user_interface.h" +#include "sherlock/tattoo/tattoo.h" +#include "sherlock/tattoo/tattoo_people.h" +#include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo_talk.h" namespace Sherlock { -#define SPEAKER_REMOVE 0x80 - -const byte SCALPEL_OPCODES[] = { - 128, // OP_SWITCH_SPEAKER - 129, // OP_RUN_CANIMATION - 130, // OP_ASSIGN_PORTRAIT_LOCATION - 131, // OP_PAUSE - 132, // OP_REMOVE_PORTRAIT - 133, // OP_CLEAR_WINDOW - 134, // OP_ADJUST_OBJ_SEQUENCE - 135, // OP_WALK_TO_COORDS - 136, // OP_PAUSE_WITHOUT_CONTROL - 137, // OP_BANISH_WINDOW - 138, // OP_SUMMON_WINDOW - 139, // OP_SET_FLAG - 140, // OP_SFX_COMMAND - 141, // OP_TOGGLE_OBJECT - 142, // OP_STEALTH_MODE_ACTIVE - 143, // OP_IF_STATEMENT - 144, // OP_ELSE_STATEMENT - 145, // OP_END_IF_STATEMENT - 146, // OP_STEALTH_MODE_DEACTIVATE - 147, // OP_TURN_HOLMES_OFF - 148, // OP_TURN_HOLMES_ON - 149, // OP_GOTO_SCENE - 150, // OP_PLAY_PROLOGUE - 151, // OP_ADD_ITEM_TO_INVENTORY - 152, // OP_SET_OBJECT - 153, // OP_CALL_TALK_FILE - 143, // OP_MOVE_MOUSE - 155, // OP_DISPLAY_INFO_LINE - 156, // OP_CLEAR_INFO_LINE - 157, // OP_WALK_TO_CANIMATION - 158, // OP_REMOVE_ITEM_FROM_INVENTORY - 159, // OP_ENABLE_END_KEY - 160, // OP_DISABLE_END_KEY - 161, // OP_CARRIAGE_RETURN - 0, // OP_MOUSE_ON_OFF - 0, // OP_SET_WALK_CONTROL - 0, // OP_SET_TALK_SEQUENCE - 0, // OP_PLAY_SONG - 0, // OP_WALK_HOLMES_AND_NPC_TO_CANIM - 0, // OP_SET_NPC_PATH_DEST - 0, // OP_NEXT_SONG - 0, // OP_SET_NPC_PATH_PAUSE - 0, // OP_PASSWORD - 0, // OP_SET_SCENE_ENTRY_FLAG - 0, // OP_WALK_NPC_TO_CANIM - 0, // OP_WALK_HOLMES_AND_NPC_TO_COORDS - 0, // OP_WALK_HOLMES_AND_NPC_TO_COORDS - 0, // OP_SET_NPC_TALK_FILE - 0, // OP_TURN_NPC_OFF - 0, // OP_TURN_NPC_ON - 0, // OP_NPC_DESC_ON_OFF - 0, // OP_NPC_PATH_PAUSE_TAKING_NOTES - 0, // OP_NPC_PATH_PAUSE_LOOKING_HOLMES - 0, // OP_ENABLE_TALK_INTERRUPTS - 0, // OP_DISABLE_TALK_INTERRUPTS - 0, // OP_SET_NPC_INFO_LINE - 0, // OP_SET_NPC_POSITION - 0, // OP_NPC_PATH_LABEL - 0, // OP_PATH_GOTO_LABEL - 0, // OP_PATH_IF_FLAG_GOTO_LABEL - 0, // OP_NPC_WALK_GRAPHICS - 0, // OP_NPC_VERB - 0, // OP_NPC_VERB_CANIM - 0, // OP_NPC_VERB_SCRIPT - 0, // OP_RESTORE_PEOPLE_SEQUENCE - 0, // OP_NPC_VERB_TARGET - 0 // OP_TURN_SOUNDS_OFF -}; - -const byte TATTOO_OPCODES[] = { - 170, // OP_SWITCH_SPEAKER - 171, // OP_RUN_CANIMATION - 0, // OP_ASSIGN_PORTRAIT_LOCATION - 173, // OP_PAUSE - 0, // OP_REMOVE_PORTRAIT - 0, // OP_CLEAR_WINDOW - 176, // OP_ADJUST_OBJ_SEQUENCE - 177, // OP_WALK_TO_COORDS - 178, // OP_PAUSE_WITHOUT_CONTROL - 179, // OP_BANISH_WINDOW - 0, // OP_SUMMON_WINDOW - 181, // OP_SET_FLAG - 0, // OP_SFX_COMMAND - 183, // OP_TOGGLE_OBJECT - 184, // OP_STEALTH_MODE_ACTIVE - 0, // OP_IF_STATEMENT - 0, // OP_ELSE_STATEMENT - 0, // OP_END_IF_STATEMENT - 188, // OP_STEALTH_MODE_DEACTIVATE - 189, // OP_TURN_HOLMES_OFF - 190, // OP_TURN_HOLMES_ON - 191, // OP_GOTO_SCENE - 0, // OP_PLAY_PROLOGUE - 193, // OP_ADD_ITEM_TO_INVENTORY - 194, // OP_SET_OBJECT - 172, // OP_CALL_TALK_FILE - 0, // OP_MOVE_MOUSE - 0, // OP_DISPLAY_INFO_LINE - 0, // OP_CLEAR_INFO_LINE - 199, // OP_WALK_TO_CANIMATION - 200, // OP_REMOVE_ITEM_FROM_INVENTORY - 201, // OP_ENABLE_END_KEY - 202, // OP_DISABLE_END_KEY - 0, // OP_CARRIAGE_RETURN - 174, // OP_MOUSE_ON_OFF - 175, // OP_SET_WALK_CONTROL - 180, // OP_SET_TALK_SEQUENCE - 182, // OP_PLAY_SONG - 187, // OP_WALK_HOLMES_AND_NPC_TO_CANIM - 192, // OP_SET_NPC_PATH_DEST - 195, // OP_NEXT_SONG - 196, // OP_SET_NPC_PATH_PAUSE - 197, // OP_PASSWORD - 198, // OP_SET_SCENE_ENTRY_FLAG - 185, // OP_WALK_NPC_TO_CANIM - 204, // OP_WALK_HOLMES_AND_NPC_TO_COORDS - 205, // OP_SET_NPC_TALK_FILE - 206, // OP_TURN_NPC_OFF - 207, // OP_TURN_NPC_ON - 208, // OP_NPC_DESC_ON_OFF - 209, // OP_NPC_PATH_PAUSE_TAKING_NOTES - 210, // OP_NPC_PATH_PAUSE_LOOKING_HOLMES - 211, // OP_ENABLE_TALK_INTERRUPTS - 212, // OP_DISABLE_TALK_INTERRUPTS - 213, // OP_SET_NPC_INFO_LINE - 214, // OP_SET_NPC_POSITION - 215, // OP_NPC_PATH_LABEL - 216, // OP_PATH_GOTO_LABEL - 217, // OP_PATH_IF_FLAG_GOTO_LABEL - 218, // OP_NPC_WALK_GRAPHICS - 220, // OP_NPC_VERB - 221, // OP_NPC_VERB_CANIM - 222, // OP_NPC_VERB_SCRIPT - 224, // OP_RESTORE_PEOPLE_SEQUENCE - 226, // OP_NPC_VERB_TARGET - 227 // OP_TURN_SOUNDS_OFF -}; - -/*----------------------------------------------------------------*/ - SequenceEntry::SequenceEntry() { _objNum = 0; - _frameNumber = 0; + _obj = nullptr; + _seqStack = 0; _seqTo = 0; + _sequenceNumber = _frameNumber = 0; + _seqCounter = _seqCounter2 = 0; } /*----------------------------------------------------------------*/ -void Statement::synchronize(Common::SeekableReadStream &s) { +void Statement::load(Common::SeekableReadStream &s, bool isRoseTattoo) { int length; length = s.readUint16LE(); @@ -212,6 +79,7 @@ void Statement::synchronize(Common::SeekableReadStream &s) { _portraitSide = s.readByte(); _quotient = s.readUint16LE(); + _journal = isRoseTattoo ? s.readByte() : 0; } /*----------------------------------------------------------------*/ @@ -222,26 +90,17 @@ TalkHistoryEntry::TalkHistoryEntry() { /*----------------------------------------------------------------*/ -TalkSequences::TalkSequences(const byte *data) { - Common::copy(data, data + MAX_TALK_SEQUENCES, _data); -} - -void TalkSequences::clear() { - Common::fill(&_data[0], &_data[MAX_TALK_SEQUENCES], 0); -} - -/*----------------------------------------------------------------*/ - Talk *Talk::init(SherlockEngine *vm) { if (vm->getGameID() == GType_SerratedScalpel) - return new ScalpelTalk(vm); + return new Scalpel::ScalpelTalk(vm); else - return new TattooTalk(vm); + return new Tattoo::TattooTalk(vm); } Talk::Talk(SherlockEngine *vm) : _vm(vm) { _talkCounter = 0; _talkToAbort = false; + _openTalkWindow = false; _speaker = 0; _talkIndex = 0; _talkTo = 0; @@ -252,7 +111,9 @@ Talk::Talk(SherlockEngine *vm) : _vm(vm) { _moreTalkDown = _moreTalkUp = false; _scriptMoreFlag = 0; _scriptSaveIndex = -1; - _opcodes = IS_SERRATED_SCALPEL ? SCALPEL_OPCODES : TATTOO_OPCODES; + _opcodes = nullptr; + _opcodeTable = nullptr; + _3doSpeechIndex = -1; _charCount = 0; _line = 0; @@ -261,15 +122,19 @@ Talk::Talk(SherlockEngine *vm) : _vm(vm) { _pauseFlag = false; _seqCount = 0; _scriptStart = _scriptEnd = nullptr; + _endStr = _noTextYet = false; + + _talkHistory.resize(IS_ROSE_TATTOO ? 1500 : 500); } -void Talk::talkTo(const Common::String &filename) { +void Talk::talkTo(const Common::String filename) { Events &events = *_vm->_events; Inventory &inv = *_vm->_inventory; Journal &journal = *_vm->_journal; People &people = *_vm->_people; Scene &scene = *_vm->_scene; Screen &screen = *_vm->_screen; + Sound &sound = *_vm->_sound; UserInterface &ui = *_vm->_ui; Common::Rect savedBounds = screen.getDisplayBounds(); bool abortFlag = false; @@ -280,7 +145,11 @@ void Talk::talkTo(const Common::String &filename) { // If there any canimations currently running, or a portrait is being cleared, // save the filename for later executing when the canimation is done - if (scene._canimShapes.size() > 0 || people._clearingThePortrait) { + bool ongoingAnim = scene._canimShapes.size() > 0; + if (IS_ROSE_TATTOO) { + ongoingAnim = static_cast<Tattoo::TattooScene *>(_vm->_scene)->_activeCAnim.active(); + } + if (ongoingAnim || people._clearingThePortrait) { // Make sure we're not in the middle of a script if (!_scriptMoreFlag) { _scriptName = filename; @@ -301,13 +170,13 @@ void Talk::talkTo(const Common::String &filename) { // Turn on the Exit option ui._endKeyActive = true; - if (people[AL]._walkCount || people._walkTo.size() > 0) { - // Only interrupt if an action if trying to do an action, and not just - // if the player is walking around the scene + if (people[HOLMES]._walkCount || (!people[HOLMES]._walkTo.empty() && + (IS_SERRATED_SCALPEL || people._allowWalkAbort))) { + // Only interrupt if trying to do an action, and not just if player is walking around the scene if (people._allowWalkAbort) abortFlag = true; - people.gotoStand(people._player); + people[HOLMES].gotoStand(); } if (_talkToAbort) @@ -327,13 +196,19 @@ void Talk::talkTo(const Common::String &filename) { } } - while (!_sequenceStack.empty()) + if (IS_ROSE_TATTOO) { pullSequence(); + } else { + while (!isSequencesEmpty()) + pullSequence(); + } if (IS_SERRATED_SCALPEL) { // Restore any pressed button if (!ui._windowOpen && savedMode != STD_MODE) - ((Scalpel::ScalpelUserInterface *)_vm->_ui)->restoreButton((int)(savedMode - 1)); + static_cast<Scalpel::ScalpelUserInterface *>(_vm->_ui)->restoreButton((int)(savedMode - 1)); + } else { + static_cast<Tattoo::TattooPeople *>(_vm->_people)->pullNPCPaths(); } // Clear the ui counter so that anything displayed on the info line @@ -462,9 +337,19 @@ void Talk::talkTo(const Common::String &filename) { _scriptSelect = select; _speaker = _talkTo; - Statement &statement = _statements[select]; + // Set up the talk file extension + if (IS_ROSE_TATTOO && sound._speechOn && _scriptMoreFlag != 1) + sound._talkSoundFile += Common::String::format("%02dB", select + 1); + + // Make a copy of the statement (in case the script frees the statement list), and then execute it + Statement statement = _statements[select]; doScript(_statements[select]._reply); + if (IS_ROSE_TATTOO) { + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) + people[idx]._misc = 0; + } + if (_talkToAbort) return; @@ -502,40 +387,9 @@ void Talk::talkTo(const Common::String &filename) { // If the new conversion is a reply first, then we don't need // to display any choices, since the reply needs to be shown - if (!newStatement._statement.hasPrefix("*") && - !newStatement._statement.hasPrefix("^")) { - clearSequences(); - pushSequence(_talkTo); - setStillSeq(_talkTo); + if (!newStatement._statement.hasPrefix("*") && !newStatement._statement.hasPrefix("^")) { _talkIndex = select; - ui._selector = ui._oldSelector = -1; - - if (!ui._windowOpen) { - // Draw the talk interface on the back buffer - drawInterface(); - displayTalk(false); - } else { - displayTalk(true); - } - - byte color = ui._endKeyActive ? COMMAND_FOREGROUND : COMMAND_NULL; - - // If the window is already open, simply draw. Otherwise, do it - // to the back buffer and then summon the window - if (ui._windowOpen) { - screen.buttonPrint(Common::Point(119, CONTROLS_Y), color, true, "Exit"); - } else { - screen.buttonPrint(Common::Point(119, CONTROLS_Y), color, false, "Exit"); - - if (!ui._slideWindows) { - screen.slamRect(Common::Rect(0, CONTROLS_Y, - SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); - } else { - ui.summonWindow(); - } - - ui._windowOpen = true; - } + showTalk(); // Break out of loop now that we're waiting for player input events.setCursor(ARROW); @@ -545,7 +399,6 @@ void Talk::talkTo(const Common::String &filename) { if (_talkTo != -1 && !_talkHistory[_converseNum][select]) journal.record(_converseNum, select, true); _talkHistory[_converseNum][select] = true; - } ui._key = ui._oldKey = Scalpel::COMMANDS[TALK_MODE - 1]; @@ -555,13 +408,17 @@ void Talk::talkTo(const Common::String &filename) { } else { freeTalkVars(); - if (!ui._lookScriptFlag) { - ui.drawInterface(2); - ui.banishWindow(); - ui._windowBounds.top = CONTROLS_Y1; - ui._menuMode = STD_MODE; + if (IS_SERRATED_SCALPEL) { + if (!ui._lookScriptFlag) { + ui.drawInterface(2); + ui._menuMode = STD_MODE; + ui._windowBounds.top = CONTROLS_Y1; + } + } else { + ui._menuMode = static_cast<Tattoo::TattooScene *>(_vm->_scene)->_labTableScene ? LAB_MODE : STD_MODE; } + ui.banishWindow(); break; } } @@ -582,27 +439,21 @@ void Talk::talkTo(const Common::String &filename) { // previous script can continue popStack(); - if (_vm->getGameID() == GType_SerratedScalpel && filename == "Tube59c") { - // WORKAROUND: Original game bug causes the results of testing the powdery substance - // to disappear too quickly. Introduce a delay to allow it to be properly displayed - ui._menuCounter = 30; - } - events.setCursor(ARROW); } -void Talk::talk(int objNum) { +void Talk::initTalk(int objNum) { Events &events = *_vm->_events; People &people = *_vm->_people; Scene &scene = *_vm->_scene; - Screen &screen = *_vm->_screen; UserInterface &ui = *_vm->_ui; - Object &obj = scene._bgShapes[objNum]; ui._windowBounds.top = CONTROLS_Y; ui._infoFlag = true; _speaker = SPEAKER_REMOVE; - loadTalkFile(scene._bgShapes[objNum]._name); + + Common::String talkFilename = (objNum >= 1000) ? people[objNum - 1000]._npcName : scene._bgShapes[objNum]._name; + loadTalkFile(talkFilename); // Find the first statement with the correct flags int select = -1; @@ -612,8 +463,15 @@ void Talk::talk(int objNum) { break; } } - if (select == -1) - error("No entry matched all required flags"); + + if (select == -1) { + freeTalkVars(); + if (!scumm_strnicmp(talkFilename.c_str(), "PATH", 4)) + error("No entries found to execute in path file"); + + nothingToSay(); + return; + } // See if the statement is a stealth mode reply Statement &statement = _statements[select]; @@ -623,35 +481,44 @@ void Talk::talk(int objNum) { // Start talk in stealth mode _talkStealth = 2; - talkTo(obj._name); + talkTo(talkFilename); } else if (statement._statement.hasPrefix("*")) { // Character being spoken to will speak first - clearSequences(); - pushSequence(_talkTo); - setStillSeq(_talkTo); + if (objNum > 1000) { + (*static_cast<Tattoo::TattooPeople *>(_vm->_people))[objNum - 1000].walkHolmesToNPC(); + } else { + Object &obj = scene._bgShapes[objNum]; + clearSequences(); + pushSequence(_talkTo); + people.setListenSequence(_talkTo, 129); - events.setCursor(WAIT); - if (obj._lookPosition.y != 0) - // Need to walk to character first - people.walkToCoords(Common::Point(obj._lookPosition.x, obj._lookPosition.y * 100), - obj._lookFacing); - events.setCursor(ARROW); + events.setCursor(WAIT); + if (obj._lookPosition.y != 0) + // Need to walk to character first + people[HOLMES].walkToCoords(obj._lookPosition, obj._lookPosition._facing); + events.setCursor(ARROW); + } if (!_talkToAbort) - talkTo(obj._name); + talkTo(talkFilename); } else { // Holmes will be speaking first - clearSequences(); - pushSequence(_talkTo); - setStillSeq(_talkTo); - _talkToFlag = false; - events.setCursor(WAIT); - if (obj._lookPosition.y != 0) - // Walk over to person to talk to - people.walkToCoords(Common::Point(obj._lookPosition.x, obj._lookPosition.y * 100), - obj._lookFacing); - events.setCursor(ARROW); + + if (objNum > 1000) { + (*static_cast<Tattoo::TattooPeople *>(_vm->_people))[objNum - 1000].walkHolmesToNPC(); + } else { + Object &obj = scene._bgShapes[objNum]; + clearSequences(); + pushSequence(_talkTo); + people.setListenSequence(_talkTo, 129); + + events.setCursor(WAIT); + if (obj._lookPosition.y != 0) + // Walk over to person to talk to + people[HOLMES].walkToCoords(obj._lookPosition, obj._lookPosition._facing); + events.setCursor(ARROW); + } if (!_talkToAbort) { // See if walking over triggered a conversation @@ -662,21 +529,11 @@ void Talk::talk(int objNum) { pullSequence(); } } else { - drawInterface(); - - events._pressed = events._released = false; _talkIndex = select; - displayTalk(false); - ui._selector = ui._oldSelector = -1; + showTalk(); - if (!ui._slideWindows) { - screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, - SHERLOCK_SCREEN_HEIGHT)); - } else { - ui.summonWindow(); - } - - ui._windowOpen = true; + // Break out of loop now that we're waiting for player input + events.setCursor(ARROW); } _talkToFlag = -1; @@ -709,6 +566,12 @@ void Talk::loadTalkFile(const Common::String &filename) { Common::String talkFile = chP ? Common::String(filename.c_str(), chP) + ".tlk" : Common::String(filename.c_str(), filename.c_str() + 7) + ".tlk"; + // Create the base of the sound filename used for talking in Rose Tattoo + if (IS_ROSE_TATTOO && _scriptMoreFlag != 1) + sound._talkSoundFile = Common::String(filename.c_str(), filename.c_str() + 7) + "."; + else if (IS_3DO) + _3doSpeechIndex = 1; + // Open the talk file for reading Common::SeekableReadStream *talkStream = res.load(talkFile); _converseNum = res.resourceIndex(); @@ -716,7 +579,7 @@ void Talk::loadTalkFile(const Common::String &filename) { _statements.resize(talkStream->readByte()); for (uint idx = 0; idx < _statements.size(); ++idx) - _statements[idx].synchronize(*talkStream); + _statements[idx].load(*talkStream, IS_ROSE_TATTOO); delete talkStream; @@ -765,317 +628,15 @@ void Talk::setTalkMap() { } } -void Talk::drawInterface() { - Screen &screen = *_vm->_screen; - Surface &bb = *screen._backBuffer; - - bb.fillRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y1 + 10), BORDER_COLOR); - bb.fillRect(Common::Rect(0, CONTROLS_Y + 10, 2, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); - bb.fillRect(Common::Rect(SHERLOCK_SCREEN_WIDTH - 2, CONTROLS_Y + 10, - SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); - bb.fillRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - 1, SHERLOCK_SCREEN_WIDTH - 2, - SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); - bb.fillRect(Common::Rect(2, CONTROLS_Y + 10, SHERLOCK_SCREEN_WIDTH - 2, - SHERLOCK_SCREEN_HEIGHT - 2), INV_BACKGROUND); - - if (_talkTo != -1) { - screen.makeButton(Common::Rect(99, CONTROLS_Y, 139, CONTROLS_Y + 10), - 119 - screen.stringWidth("Exit") / 2, "Exit"); - screen.makeButton(Common::Rect(140, CONTROLS_Y, 180, CONTROLS_Y + 10), - 159 - screen.stringWidth("Up") / 2, "Up"); - screen.makeButton(Common::Rect(181, CONTROLS_Y, 221, CONTROLS_Y + 10), - 200 - screen.stringWidth("Down") / 2, "Down"); - } else { - int strWidth = screen.stringWidth(Scalpel::PRESS_KEY_TO_CONTINUE); - screen.makeButton(Common::Rect(46, CONTROLS_Y, 273, CONTROLS_Y + 10), - 160 - strWidth / 2, Scalpel::PRESS_KEY_TO_CONTINUE); - screen.gPrint(Common::Point(160 - strWidth / 2, CONTROLS_Y), COMMAND_FOREGROUND, "P"); - } -} - -bool Talk::displayTalk(bool slamIt) { - Screen &screen = *_vm->_screen; - int yp = CONTROLS_Y + 14; - int lineY = -1; - _moreTalkDown = _moreTalkUp = false; - - for (uint idx = 0; idx < _statements.size(); ++idx) { - _statements[idx]._talkPos.top = _statements[idx]._talkPos.bottom = -1; - } - - if (_talkIndex) { - for (int idx = 0; idx < _talkIndex && !_moreTalkUp; ++idx) { - if (_statements[idx]._talkMap != -1) - _moreTalkUp = true; - } - } - - // Display the up arrow and enable Up button if the first option is scrolled off-screen - if (_moreTalkUp) { - if (slamIt) { - screen.print(Common::Point(5, CONTROLS_Y + 13), INV_FOREGROUND, "~"); - screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_FOREGROUND, true, "Up"); - } else { - screen.gPrint(Common::Point(5, CONTROLS_Y + 12), INV_FOREGROUND, "~"); - screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_FOREGROUND, false, "Up"); - } - } else { - if (slamIt) { - screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, true, "Up"); - screen.vgaBar(Common::Rect(5, CONTROLS_Y + 11, 15, CONTROLS_Y + 22), INV_BACKGROUND); - } else { - screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, false, "Up"); - screen._backBuffer1.fillRect(Common::Rect(5, CONTROLS_Y + 11, - 15, CONTROLS_Y + 22), INV_BACKGROUND); - } - } - - // Loop through the statements - bool done = false; - for (uint idx = _talkIndex; idx < _statements.size() && !done; ++idx) { - Statement &statement = _statements[idx]; - - if (statement._talkMap != -1) { - bool flag = _talkHistory[_converseNum][idx]; - lineY = talkLine(idx, statement._talkMap, flag ? TALK_NULL : INV_FOREGROUND, - yp, slamIt); - - if (lineY != -1) { - statement._talkPos.top = yp; - yp = lineY; - statement._talkPos.bottom = yp; - - if (yp == SHERLOCK_SCREEN_HEIGHT) - done = true; - } else { - done = true; - } - } - } - - // Display the down arrow and enable down button if there are more statements available down off-screen - if (lineY == -1 || lineY == SHERLOCK_SCREEN_HEIGHT) { - _moreTalkDown = true; - - if (slamIt) { - screen.print(Common::Point(5, 190), INV_FOREGROUND, "|"); - screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_FOREGROUND, true, "Down"); - } else { - screen.gPrint(Common::Point(5, 189), INV_FOREGROUND, "|"); - screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_FOREGROUND, false, "Down"); - } - } else { - if (slamIt) { - screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, true, "Down"); - screen.vgaBar(Common::Rect(5, 189, 16, 199), INV_BACKGROUND); - } else { - screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, false, "Down"); - screen._backBuffer1.fillRect(Common::Rect(5, 189, 16, 199), INV_BACKGROUND); - } - } - - return done; -} - -int Talk::talkLine(int lineNum, int stateNum, byte color, int lineY, bool slamIt) { - Screen &screen = *_vm->_screen; - int idx = lineNum; - Common::String msg, number; - bool numberFlag = false; - - // Get the statement to display as well as optional number prefix - if (idx < SPEAKER_REMOVE) { - number = Common::String::format("%d.", stateNum + 1); - numberFlag = true; - } else { - idx -= SPEAKER_REMOVE; - } - msg = _statements[idx]._statement; - - // Handle potentially multiple lines needed to display entire statement - const char *lineStartP = msg.c_str(); - int maxWidth = 298 - (numberFlag ? 18 : 0); - for (;;) { - // Get as much of the statement as possible will fit on the - Common::String sLine; - const char *lineEndP = lineStartP; - int width = 0; - do { - width += screen.charWidth(*lineEndP); - } while (*++lineEndP && width < maxWidth); - - // Check if we need to wrap the line - if (width >= maxWidth) { - // Work backwards to the prior word's end - while (*--lineEndP != ' ') - ; - - sLine = Common::String(lineStartP, lineEndP++); - } else { - // Can display remainder of the statement on the current line - sLine = Common::String(lineStartP); - } - - - if (lineY <= (SHERLOCK_SCREEN_HEIGHT - 10)) { - // Need to directly display on-screen? - if (slamIt) { - // See if a numer prefix is needed or not - if (numberFlag) { - // Are we drawing the first line? - if (lineStartP == msg.c_str()) { - // We are, so print the number and then the text - screen.print(Common::Point(16, lineY), color, "%s", number.c_str()); - } - - // Draw the line with an indent - screen.print(Common::Point(30, lineY), color, "%s", sLine.c_str()); - } else { - screen.print(Common::Point(16, lineY), color, "%s", sLine.c_str()); - } - } else { - if (numberFlag) { - if (lineStartP == msg.c_str()) { - screen.gPrint(Common::Point(16, lineY - 1), color, "%s", number.c_str()); - } - - screen.gPrint(Common::Point(30, lineY - 1), color, "%s", sLine.c_str()); - } else { - screen.gPrint(Common::Point(16, lineY - 1), color, "%s", sLine.c_str()); - } - } - - // Move to next line, if any - lineY += 9; - lineStartP = lineEndP; - - if (!*lineEndP) - break; - } else { - // We're close to the bottom of the screen, so stop display - lineY = -1; - break; - } - } - - if (lineY == -1 && lineStartP != msg.c_str()) - lineY = SHERLOCK_SCREEN_HEIGHT; - - // Return the Y position of the next line to follow this one - return lineY; -} - -void Talk::clearSequences() { - _sequenceStack.clear(); -} - -void Talk::pullSequence() { - Scene &scene = *_vm->_scene; - - if (_sequenceStack.empty()) - return; - - SequenceEntry seq = _sequenceStack.pop(); - if (seq._objNum != -1) { - Object &obj = scene._bgShapes[seq._objNum]; - - if (obj._seqSize < MAX_TALK_SEQUENCES) { - warning("Tried to restore too few frames"); - } else { - for (int idx = 0; idx < MAX_TALK_SEQUENCES; ++idx) - obj._sequences[idx] = seq._sequences[idx]; - - obj._frameNumber = seq._frameNumber; - obj._seqTo = seq._seqTo; - } - } -} - void Talk::pushSequence(int speaker) { People &people = *_vm->_people; Scene &scene = *_vm->_scene; // Only proceed if a speaker is specified - if (speaker == -1) - return; - - SequenceEntry seqEntry; - if (!speaker) { - seqEntry._objNum = -1; - } else { - seqEntry._objNum = people.findSpeaker(speaker); - - if (seqEntry._objNum != -1) { - Object &obj = scene._bgShapes[seqEntry._objNum]; - for (uint idx = 0; idx < MAX_TALK_SEQUENCES; ++idx) - seqEntry._sequences.push_back(obj._sequences[idx]); - - seqEntry._frameNumber = obj._frameNumber; - seqEntry._seqTo = obj._seqTo; - } - } - - _sequenceStack.push(seqEntry); - if (_scriptStack.size() >= 5) - error("script stack overflow"); -} - -void Talk::setSequence(int speaker) { - People &people = *_vm->_people; - Scene &scene = *_vm->_scene; - - // If no speaker is specified, then nothing needs to be done - if (speaker == -1) - return; - - if (speaker) { + if (speaker != -1) { int objNum = people.findSpeaker(speaker); - if (objNum != -1) { - Object &obj = scene._bgShapes[objNum]; - - if (obj._seqSize < MAX_TALK_SEQUENCES) { - warning("Tried to copy too many talk frames"); - } else { - for (int idx = 0; idx < MAX_TALK_SEQUENCES; ++idx) { - obj._sequences[idx] = people._characters[speaker]._talkSequences[idx]; - if (idx > 0 && !obj._sequences[idx] && !obj._sequences[idx - 1]) - return; - - obj._frameNumber = 0; - obj._sequenceNumber = 0; - } - } - } - } -} - -void Talk::setStillSeq(int speaker) { - People &people = *_vm->_people; - Scene &scene = *_vm->_scene; - - // Don't bother doing anything if no specific speaker is specified - if (speaker == -1) - return; - - if (speaker) { - int objNum = people.findSpeaker(speaker); - if (objNum != -1) { - Object &obj = scene._bgShapes[objNum]; - - if (obj._seqSize < MAX_TALK_SEQUENCES) { - warning("Tried to copy too few still frames"); - } else { - for (uint idx = 0; idx < MAX_TALK_SEQUENCES; ++idx) { - obj._sequences[idx] = people._characters[speaker]._stillSequences[idx]; - if (idx > 0 && !people._characters[speaker]._talkSequences[idx] && - !people._characters[speaker]._talkSequences[idx - 1]) - break; - } - - obj._frameNumber = 0; - obj._seqTo = 0; - } - } + if (objNum != -1) + pushSequenceEntry(&scene._bgShapes[objNum]); } } @@ -1084,14 +645,12 @@ void Talk::doScript(const Common::String &script) { Scene &scene = *_vm->_scene; Screen &screen = *_vm->_screen; UserInterface &ui = *_vm->_ui; - bool openTalkWindow = false; _savedSequences.clear(); _scriptStart = (const byte *)script.c_str(); _scriptEnd = _scriptStart + script.size(); const byte *str = _scriptStart; - _yp = CONTROLS_Y + 12; _charCount = 0; _line = 0; _wait = 0; @@ -1099,6 +658,21 @@ void Talk::doScript(const Common::String &script) { _seqCount = 0; _noTextYet = true; _endStr = false; + _openTalkWindow = false; + + if (IS_SERRATED_SCALPEL) + _yp = CONTROLS_Y + 12; + else + _yp = (_talkTo == -1) ? 5 : screen.fontHeight() + 11; + + if (IS_ROSE_TATTOO) { + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { + Tattoo::TattooPerson &p = (*(Tattoo::TattooPeople *)_vm->_people)[idx]; + p._savedNpcSequence = p._sequenceNumber; + p._savedNpcFrame = p._frameNumber; + p._resetNPCPath = true; + } + } if (_scriptMoreFlag) { _scriptMoreFlag = 0; @@ -1110,48 +684,60 @@ void Talk::doScript(const Common::String &script) { _talkStealth = 2; _speaker |= SPEAKER_REMOVE; } else { - pushSequence(_speaker); - ui.clearWindow(); + if (IS_SERRATED_SCALPEL) + pushSequence(_speaker); + if (IS_SERRATED_SCALPEL || ui._windowOpen) + ui.clearWindow(); // Need to switch speakers? if (str[0] == _opcodes[OP_SWITCH_SPEAKER]) { _speaker = str[1] - 1; - str += 2; - pullSequence(); - pushSequence(_speaker); - setSequence(_speaker); + + if (IS_SERRATED_SCALPEL) { + str += 2; + pullSequence(); + pushSequence(_speaker); + } else { + str += 3; + } + + people.setTalkSequence(_speaker); } else { - setSequence(_speaker); + people.setTalkSequence(_speaker); } - // Assign portrait location? - if (str[0] == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION]) { - switch (str[1] & 15) { - case 1: - people._portraitSide = 20; - break; - case 2: - people._portraitSide = 220; - break; - case 3: - people._portraitSide = 120; - break; - default: - break; + if (IS_SERRATED_SCALPEL) { + // Assign portrait location? + if (str[0] == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION]) { + switch (str[1] & 15) { + case 1: + people._portraitSide = 20; + break; + case 2: + people._portraitSide = 220; + break; + case 3: + people._portraitSide = 120; + break; + default: + break; - } + } - if (str[1] > 15) - people._speakerFlip = true; - str += 2; - } + if (str[1] > 15) + people._speakerFlip = true; + str += 2; + } - // Remove portrait? - if (str[0] == _opcodes[OP_REMOVE_PORTRAIT]) { - _speaker = 255; - } else { - // Nope, so set the first speaker - people.setTalking(_speaker); + if (IS_SERRATED_SCALPEL) { + // Remove portrait? + if ( str[0] == _opcodes[OP_REMOVE_PORTRAIT]) { + _speaker = -1; + } else { + // Nope, so set the first speaker + ((Scalpel::ScalpelPeople *)_vm->_people)->setTalking(_speaker); + } + } } } @@ -1166,9 +752,9 @@ void Talk::doScript(const Common::String &script) { // Start of comment, so skip over it while (*str++ != '}') ; - } else if (c >= 128 && c <= 227 && _opcodeTable[c - 128]) { + } else if (isOpcode(c)) { // Handle control code - switch ((this->*_opcodeTable[c - 128])(str)) { + switch ((this->*_opcodeTable[c - _opcodes[0]])(str)) { case RET_EXIT: return; case RET_CONTINUE: @@ -1179,107 +765,12 @@ void Talk::doScript(const Common::String &script) { ++str; } else { - // If the window isn't yet open, draw the window before printing starts - if (!ui._windowOpen && _noTextYet) { - _noTextYet = false; - drawInterface(); - - if (_talkTo != -1) { - screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, false, "Exit"); - screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, false, "Up"); - screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, false, "Down"); - } - } - - // If it's the first line, display the speaker - if (!_line && _speaker >= 0 && _speaker < (int)people._characters.size()) { - // If the window is open, display the name directly on-screen. - // Otherwise, simply draw it on the back buffer - if (ui._windowOpen) { - screen.print(Common::Point(16, _yp), TALK_FOREGROUND, "%s", - people._characters[_speaker & 127]._name); - } else { - screen.gPrint(Common::Point(16, _yp - 1), TALK_FOREGROUND, "%s", - people._characters[_speaker & 127]._name); - openTalkWindow = true; - } - - _yp += 9; - } - - // Find amount of text that will fit on the line - int width = 0, idx = 0; - do { - width += screen.charWidth(str[idx]); - ++idx; - ++_charCount; - } while (width < 298 && str[idx] && str[idx] != '{' && str[idx] < _opcodes[0]); - - if (str[idx] || width >= 298) { - if (str[idx] < _opcodes[0] && str[idx] != '{') { - --idx; - --_charCount; - } - } else { - _endStr = true; - } - - // If word wrap is needed, find the start of the current word - if (width >= 298) { - while (str[idx] != ' ') { - --idx; - --_charCount; - } - } - - // Print the line - Common::String lineStr((const char *)str, (const char *)str + idx); - - // If the speaker indicates a description file, print it in yellow - if (_speaker != -1) { - if (ui._windowOpen) { - screen.print(Common::Point(16, _yp), COMMAND_FOREGROUND, "%s", lineStr.c_str()); - } else { - screen.gPrint(Common::Point(16, _yp - 1), COMMAND_FOREGROUND, "%s", lineStr.c_str()); - openTalkWindow = true; - } - } else { - if (ui._windowOpen) { - screen.print(Common::Point(16, _yp), COMMAND_FOREGROUND, "%s", lineStr.c_str()); - } else { - screen.gPrint(Common::Point(16, _yp - 1), COMMAND_FOREGROUND, "%s", lineStr.c_str()); - openTalkWindow = true; - } - } - - // Move to end of displayed line - str += idx; - - // If line wrap occurred, then move to after the separating space between the words - if (str[0] < _opcodes[0] && str[0] != '{') - ++str; - - _yp += 9; - ++_line; - - // Certain different conditions require a wait - if ((_line == 4 && str < _scriptEnd && str[0] != _opcodes[OP_SFX_COMMAND] && str[0] != _opcodes[OP_PAUSE] && _speaker != -1) || - (_line == 5 && str < _scriptEnd && str[0] != _opcodes[OP_PAUSE] && _speaker == -1) || - _endStr) { - _wait = 1; - } - - byte v = (str >= _scriptEnd ? 0 : str[0]); - if (v == _opcodes[OP_SWITCH_SPEAKER] || v == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION] || - v == _opcodes[OP_BANISH_WINDOW] || v == _opcodes[OP_IF_STATEMENT] || - v == _opcodes[OP_ELSE_STATEMENT] || v == _opcodes[OP_END_IF_STATEMENT] || - v == _opcodes[OP_GOTO_SCENE] || v == _opcodes[OP_CALL_TALK_FILE]) { - _wait = 1; - } + // Handle drawing the talk interface with the text + talkInterface(str); } // Open window if it wasn't already open, and text has already been printed - if ((openTalkWindow && _wait) || (openTalkWindow && str[0] >= _opcodes[0] && str[0] != _opcodes[OP_CARRIAGE_RETURN])) { + if ((_openTalkWindow && _wait) || (_openTalkWindow && str[0] >= _opcodes[0] && str[0] != _opcodes[OP_END_TEXT_WINDOW])) { if (!ui._slideWindows) { screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); } else { @@ -1287,34 +778,12 @@ void Talk::doScript(const Common::String &script) { } ui._windowOpen = true; - openTalkWindow = false; + _openTalkWindow = false; } - if (_wait) { + if (_wait) // Handling pausing - if (!_pauseFlag && _charCount < 160) - _charCount = 160; - - _wait = waitForMore(_charCount); - if (_wait == -1) - _endStr = true; - - // If a key was pressed to finish the window, see if further voice files should be skipped - if (_wait >= 0 && _wait < 254) { - if (str[0] == _opcodes[OP_SFX_COMMAND]) - str += 9; - } - - // Clear the window unless the wait was due to a PAUSE command - if (!_pauseFlag && _wait != -1 && str < _scriptEnd && str[0] != _opcodes[OP_SFX_COMMAND]) { - if (!_talkStealth) - ui.clearWindow(); - _yp = CONTROLS_Y + 12; - _charCount = _line = 0; - } - - _pauseFlag = false; - } + talkWait(str); } while (!_vm->shouldQuit() && !_endStr); if (_wait != -1) { @@ -1329,8 +798,13 @@ void Talk::doScript(const Common::String &script) { } pullSequence(); - if (_speaker >= 0 && _speaker < SPEAKER_REMOVE) - people.clearTalking(); + + if (IS_SERRATED_SCALPEL) { + if (_speaker >= 0 && _speaker < SPEAKER_REMOVE) + people.clearTalking(); + } else { + static_cast<Tattoo::TattooPeople *>(_vm->_people)->pullNPCPaths(); + } } } @@ -1342,14 +816,23 @@ int Talk::waitForMore(int delay) { UserInterface &ui = *_vm->_ui; CursorId oldCursor = events.getCursor(); int key2 = 254; + bool playingSpeech = false; // Unless we're in stealth mode, show the appropriate cursor if (!_talkStealth) { events.setCursor(ui._lookScriptFlag ? MAGNIFY : ARROW); } + // Handle playing any speech associated with the text being displayed + switchSpeaker(); + if (sound._speechOn && IS_ROSE_TATTOO) { + sound.playSpeech(sound._talkSoundFile); + sound._talkSoundFile.setChar(sound._talkSoundFile.lastChar() + 1, sound._talkSoundFile.size() - 1); + } + playingSpeech = sound.isSpeechPlaying(); + do { - if (sound._speechOn && !*sound._soundIsOn) + if (IS_SERRATED_SCALPEL && sound._speechOn && !sound.isSpeechPlaying()) people._portrait._frameNumber = -1; scene.doBgAnim(); @@ -1360,11 +843,21 @@ int Talk::waitForMore(int delay) { events._released = true; } else { // See if there's been a button press + events.pollEventsAndWait(); events.setButtonState(); if (events.kbHit()) { Common::KeyState keyState = events.getKey(); - if (Common::isPrint(keyState.ascii)) + if (keyState.keycode == Common::KEYCODE_ESCAPE) { + if (IS_ROSE_TATTOO && static_cast<Tattoo::TattooEngine *>(_vm)->_runningProlog) { + // Skip out of the introduction + _vm->setFlags(-76); + _vm->setFlags(396); + scene._goToScene = 1; + } + break; + + } else if (Common::isPrint(keyState.ascii)) key2 = keyState.keycode; } @@ -1378,18 +871,17 @@ int Talk::waitForMore(int delay) { if ((delay > 0 && !ui._invLookFlag && !ui._lookScriptFlag) || _talkStealth) --delay; - // If there are voices playing, reset delay so that they keep playing - if (sound._voices == 2 && *sound._soundIsOn) + if (playingSpeech && !sound.isSpeechPlaying()) delay = 0; - } while (!_vm->shouldQuit() && key2 == 254 && (delay || (sound._voices == 2 && *sound._soundIsOn)) + } while (!_vm->shouldQuit() && key2 == 254 && (delay || (playingSpeech && sound.isSpeechPlaying())) && !events._released && !events._rightReleased); // If voices was set 2 to indicate a voice file was place, then reset it back to 1 if (sound._voices == 2) sound._voices = 1; - if (delay > 0 && sound._diskSoundPlaying) - sound.stopSndFuncPtr(0, 0); + if (delay > 0 && sound.isSpeechPlaying()) + sound.stopSpeech(); // Adjust _talkStealth mode: // mode 1 - It was by a pause without stealth being on before the pause, so reset back to 0 @@ -1406,13 +898,22 @@ int Talk::waitForMore(int delay) { break; } - sound._speechOn = false; + + sound.stopSpeech(); events.setCursor(_talkToAbort ? ARROW : oldCursor); events._pressed = events._released = false; return key2; } +bool Talk::isOpcode(byte checkCharacter) { + if ((checkCharacter < _opcodes[0]) || (checkCharacter >= (_opcodes[0] + 99))) + return false; // outside of range + if (_opcodeTable[checkCharacter - _opcodes[0]]) + return true; + return false; +} + void Talk::popStack() { if (!_scriptStack.empty()) { ScriptStackEntry scriptEntry = _scriptStack.pop(); @@ -1423,8 +924,8 @@ void Talk::popStack() { } } -void Talk::synchronize(Common::Serializer &s) { - for (int idx = 0; idx < MAX_TALK_FILES; ++idx) { +void Talk::synchronize(Serializer &s) { + for (uint idx = 0; idx < _talkHistory.size(); ++idx) { TalkHistoryEntry &he = _talkHistory[idx]; for (int flag = 0; flag < 16; ++flag) @@ -1507,42 +1008,6 @@ OpcodeReturn Talk::cmdBanishWindow(const byte *&str) { return RET_SUCCESS; } -OpcodeReturn Talk::cmdCallTalkFile(const byte *&str) { - Common::String tempString; - - ++str; - for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx) - tempString += str[idx]; - str += 8; - - int scriptCurrentIndex = str - _scriptStart; - - // Save the current script position and new talk file - if (_scriptStack.size() < 9) { - ScriptStackEntry rec1; - rec1._name = _scriptName; - rec1._currentIndex = scriptCurrentIndex; - rec1._select = _scriptSelect; - _scriptStack.push(rec1); - - // Push the new talk file onto the stack - ScriptStackEntry rec2; - rec2._name = tempString; - rec2._currentIndex = 0; - rec2._select = 100; - _scriptStack.push(rec2); - } - else { - error("Script stack overflow"); - } - - _scriptMoreFlag = 1; - _endStr = true; - _wait = 0; - - return RET_SUCCESS; -} - OpcodeReturn Talk::cmdDisableEndKey(const byte *&str) { _vm->_ui->_endKeyActive = false; return RET_SUCCESS; @@ -1553,44 +1018,20 @@ OpcodeReturn Talk::cmdEnableEndKey(const byte *&str) { return RET_SUCCESS; } -OpcodeReturn Talk::cmdGotoScene(const byte *&str) { - Map &map = *_vm->_map; - People &people = *_vm->_people; - Scene &scene = *_vm->_scene; - scene._goToScene = str[1] - 1; - - if (scene._goToScene != 100) { - // Not going to the map overview - map._oldCharPoint = scene._goToScene; - map._overPos.x = map[scene._goToScene].x * 100 - 600; - map._overPos.y = map[scene._goToScene].y * 100 + 900; - - // Run a canimation? - if (str[2] > 100) { - people._hSavedFacing = str[2]; - people._hSavedPos = Common::Point(160, 100); - } - } - str += 6; - - _scriptMoreFlag = (scene._goToScene == 100) ? 2 : 1; - _scriptSaveIndex = str - _scriptStart; - _endStr = true; - _wait = 0; - +OpcodeReturn Talk::cmdEndTextWindow(const byte *&str) { return RET_SUCCESS; } OpcodeReturn Talk::cmdHolmesOff(const byte *&str) { People &people = *_vm->_people; - people[PLAYER]._type = REMOVE; + people[HOLMES]._type = REMOVE; return RET_SUCCESS; } OpcodeReturn Talk::cmdHolmesOn(const byte *&str) { People &people = *_vm->_people; - people[PLAYER]._type = CHARACTER; + people[HOLMES]._type = CHARACTER; return RET_SUCCESS; } @@ -1607,6 +1048,8 @@ OpcodeReturn Talk::cmdPauseWithoutControl(const byte *&str) { Scene &scene = *_vm->_scene; ++str; + events.incWaitCounter(); + for (int idx = 0; idx < (str[0] - 1); ++idx) { scene.doBgAnim(); if (_talkToAbort) @@ -1617,6 +1060,9 @@ OpcodeReturn Talk::cmdPauseWithoutControl(const byte *&str) { events.setButtonState(); } + events.decWaitCounter(); + + _endStr = false; return RET_SUCCESS; } @@ -1643,7 +1089,9 @@ OpcodeReturn Talk::cmdRunCAnimation(const byte *&str) { return RET_EXIT; // Check if next character is changing side or changing portrait - if (_charCount && (str[1] == _opcodes[OP_SWITCH_SPEAKER] || str[1] == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION])) + _wait = 0; + if (_charCount && (str[1] == _opcodes[OP_SWITCH_SPEAKER] || + (IS_SERRATED_SCALPEL && str[1] == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION]))) _wait = 1; return RET_SUCCESS; @@ -1697,28 +1145,6 @@ OpcodeReturn Talk::cmdStealthModeDeactivate(const byte *&str) { return RET_SUCCESS; } -OpcodeReturn Talk::cmdSwitchSpeaker(const byte *&str) { - People &people = *_vm->_people; - UserInterface &ui = *_vm->_ui; - - if (!(_speaker & SPEAKER_REMOVE)) - people.clearTalking(); - if (_talkToAbort) - return RET_EXIT; - - ui.clearWindow(); - _yp = CONTROLS_Y + 12; - _charCount = _line = 0; - - _speaker = *++str - 1; - people.setTalking(_speaker); - pullSequence(); - pushSequence(_speaker); - setSequence(_speaker); - - return RET_SUCCESS; -} - OpcodeReturn Talk::cmdToggleObject(const byte *&str) { Scene &scene = *_vm->_scene; Common::String tempString; @@ -1739,397 +1165,26 @@ OpcodeReturn Talk::cmdWalkToCAnimation(const byte *&str) { ++str; CAnim &animation = scene._cAnim[str[0] - 1]; - people.walkToCoords(animation._goto, animation._gotoDir); + people[HOLMES].walkToCoords(animation._goto[0], animation._goto[0]._facing); return _talkToAbort ? RET_EXIT : RET_SUCCESS; } -OpcodeReturn Talk::cmdWalkToCoords(const byte *&str) { - People &people = *_vm->_people; - ++str; - - people.walkToCoords(Common::Point(((str[0] - 1) * 256 + str[1] - 1) * 100, - str[2] * 100), str[3] - 1); - if (_talkToAbort) - return RET_EXIT; - - str += 3; - return RET_SUCCESS; -} - -/*----------------------------------------------------------------*/ - -ScalpelTalk::ScalpelTalk(SherlockEngine *vm) : Talk(vm) { - static OpcodeMethod OPCODE_METHODS[] = { - (OpcodeMethod)&ScalpelTalk::cmdSwitchSpeaker, - (OpcodeMethod)&ScalpelTalk::cmdRunCAnimation, - (OpcodeMethod)&ScalpelTalk::cmdAssignPortraitLocation, - - (OpcodeMethod)&ScalpelTalk::cmdPause, - (OpcodeMethod)&ScalpelTalk::cmdRemovePortrait, - (OpcodeMethod)&ScalpelTalk::cmdClearWindow, - (OpcodeMethod)&ScalpelTalk::cmdAdjustObjectSequence, - (OpcodeMethod)&ScalpelTalk::cmdWalkToCoords, - (OpcodeMethod)&ScalpelTalk::cmdPauseWithoutControl, - (OpcodeMethod)&ScalpelTalk::cmdBanishWindow, - (OpcodeMethod)&ScalpelTalk::cmdSummonWindow, - (OpcodeMethod)&ScalpelTalk::cmdSetFlag, - (OpcodeMethod)&ScalpelTalk::cmdSfxCommand, - - (OpcodeMethod)&ScalpelTalk::cmdToggleObject, - (OpcodeMethod)&ScalpelTalk::cmdStealthModeActivate, - (OpcodeMethod)&ScalpelTalk::cmdIf, - (OpcodeMethod)&ScalpelTalk::cmdElse, - nullptr, - (OpcodeMethod)&ScalpelTalk::cmdStealthModeDeactivate, - (OpcodeMethod)&ScalpelTalk::cmdHolmesOff, - (OpcodeMethod)&ScalpelTalk::cmdHolmesOn, - (OpcodeMethod)&ScalpelTalk::cmdGotoScene, - (OpcodeMethod)&ScalpelTalk::cmdPlayPrologue, - - (OpcodeMethod)&ScalpelTalk::cmdAddItemToInventory, - (OpcodeMethod)&ScalpelTalk::cmdSetObject, - (OpcodeMethod)&ScalpelTalk::cmdCallTalkFile, - (OpcodeMethod)&ScalpelTalk::cmdMoveMouse, - (OpcodeMethod)&ScalpelTalk::cmdDisplayInfoLine, - (OpcodeMethod)&ScalpelTalk::cmdClearInfoLine, - (OpcodeMethod)&ScalpelTalk::cmdWalkToCAnimation, - (OpcodeMethod)&ScalpelTalk::cmdRemoveItemFromInventory, - (OpcodeMethod)&ScalpelTalk::cmdEnableEndKey, - (OpcodeMethod)&ScalpelTalk::cmdDisableEndKey, - - (OpcodeMethod)&ScalpelTalk::cmdCarriageReturn, - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr - }; - - _opcodeTable = OPCODE_METHODS; -} - -OpcodeReturn ScalpelTalk::cmdAssignPortraitLocation(const byte *&str) { - People &people = *_vm->_people; - - ++str; - switch (str[0] & 15) { - case 1: - people._portraitSide = 20; - break; - case 2: - people._portraitSide = 220; - break; - case 3: - people._portraitSide = 120; - break; - default: - break; - } - - if (str[0] > 15) - people._speakerFlip = true; - - return RET_SUCCESS; -} - -OpcodeReturn ScalpelTalk::cmdClearInfoLine(const byte *&str) { - UserInterface &ui = *_vm->_ui; - - ui._infoFlag = true; - ui.clearInfo(); - - return RET_SUCCESS; -} - -OpcodeReturn ScalpelTalk::cmdClearWindow(const byte *&str) { - UserInterface &ui = *_vm->_ui; - - ui.clearWindow(); - _yp = CONTROLS_Y + 12; - _charCount = _line = 0; - - return RET_SUCCESS; -} - -OpcodeReturn ScalpelTalk::cmdDisplayInfoLine(const byte *&str) { - Screen &screen = *_vm->_screen; - UserInterface &ui = *_vm->_ui; - Common::String tempString; - - ++str; - for (int idx = 0; idx < str[0]; ++idx) - tempString += str[idx + 1]; - str += str[0]; - - screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", tempString.c_str()); - ui._menuCounter = 30; - - return RET_SUCCESS; -} - -OpcodeReturn ScalpelTalk::cmdElse(const byte *&str) { - // If this is encountered here, it means that a preceeding IF statement was found, - // and evaluated to true. Now all the statements for the true block are finished, - // so skip over the block of code that would have executed if the result was false - _wait = 0; - do { - ++str; - } while (str[0] && str[0] != _opcodes[OP_END_IF_STATEMENT]); - - return RET_SUCCESS; -} +void Talk::talkWait(const byte *&str) { + if (!_pauseFlag && _charCount < 160) + _charCount = 160; -OpcodeReturn ScalpelTalk::cmdIf(const byte *&str) { - ++str; - int flag = (str[0] - 1) * 256 + str[1] - 1 - (str[1] == 1 ? 1 : 0); - ++str; - _wait = 0; + _wait = waitForMore(_charCount); + if (_wait == -1) + _endStr = true; - bool result = flag < 0x8000; - if (_vm->readFlags(flag & 0x7fff) != result) { - do { - ++str; - } while (str[0] && str[0] != _opcodes[OP_ELSE_STATEMENT] && str[0] != _opcodes[OP_END_IF_STATEMENT]); - - if (!str[0]) - _endStr = true; + // If a key was pressed to finish the window, see if further voice files should be skipped + if (_wait >= 0 && _wait < 254) { + if (str[0] == _opcodes[OP_SFX_COMMAND]) + str += 9; } - return RET_SUCCESS; -} - -OpcodeReturn ScalpelTalk::cmdMoveMouse(const byte *&str) { - Events &events = *_vm->_events; - - ++str; - events.moveMouse(Common::Point((str[0] - 1) * 256 + str[1] - 1, str[2])); - if (_talkToAbort) - return RET_EXIT; - str += 3; - - return RET_SUCCESS; -} - -OpcodeReturn ScalpelTalk::cmdPlayPrologue(const byte *&str) { - Animation &anim = *_vm->_animation; - Common::String tempString; - - ++str; - for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx) - tempString += str[idx]; - - anim.play(tempString, 1, 3, true, 4); - - return RET_SUCCESS; -} - -OpcodeReturn ScalpelTalk::cmdRemovePortrait(const byte *&str) { - People &people = *_vm->_people; - - if (_speaker >= 0 && _speaker < SPEAKER_REMOVE) - people.clearTalking(); - pullSequence(); - if (_talkToAbort) - return RET_EXIT; - - _speaker |= SPEAKER_REMOVE; - return RET_SUCCESS; -} - -OpcodeReturn ScalpelTalk::cmdSfxCommand(const byte *&str) { - Sound &sound = *_vm->_sound; - Common::String tempString; - - ++str; - if (sound._voices) { - for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx) - tempString += str[idx]; - sound.playSound(tempString, WAIT_RETURN_IMMEDIATELY); - - // Set voices to wait for more - sound._voices = 2; - sound._speechOn = (*sound._soundIsOn); - } - - _wait = 1; - str += 7; - - return RET_SUCCESS; -} - -OpcodeReturn ScalpelTalk::cmdSummonWindow(const byte *&str) { - Events &events = *_vm->_events; - Screen &screen = *_vm->_screen; - - drawInterface(); - events._pressed = events._released = false; - events.clearKeyboard(); - _noTextYet = false; - - if (_speaker != -1) { - screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, false, "Exit"); - screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_NULL, false, "Up"); - screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_NULL, false, "Down"); - } - - return RET_SUCCESS; -} - -OpcodeReturn ScalpelTalk::cmdCarriageReturn(const byte *&str) { - return RET_SUCCESS; -} - -/*----------------------------------------------------------------*/ - -TattooTalk::TattooTalk(SherlockEngine *vm) : Talk(vm) { - static OpcodeMethod OPCODE_METHODS[] = { - nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - (OpcodeMethod)&TattooTalk::cmdSwitchSpeaker, - - (OpcodeMethod)&TattooTalk::cmdRunCAnimation, - (OpcodeMethod)&TattooTalk::cmdCallTalkFile, - (OpcodeMethod)&TattooTalk::cmdPause, - (OpcodeMethod)&TattooTalk::cmdMouseOnOff, - (OpcodeMethod)&TattooTalk::cmdSetWalkControl, - (OpcodeMethod)&TattooTalk::cmdAdjustObjectSequence, - (OpcodeMethod)&TattooTalk::cmdWalkToCoords, - (OpcodeMethod)&TattooTalk::cmdPauseWithoutControl, - (OpcodeMethod)&TattooTalk::cmdBanishWindow, - (OpcodeMethod)&TattooTalk::cmdSetTalkSequence, - - (OpcodeMethod)&TattooTalk::cmdSetFlag, - (OpcodeMethod)&TattooTalk::cmdPlaySong, - (OpcodeMethod)&TattooTalk::cmdToggleObject, - (OpcodeMethod)&TattooTalk::cmdStealthModeActivate, - (OpcodeMethod)&TattooTalk::cmdWalkNPCToCAnimation, - (OpcodeMethod)&TattooTalk::cmdWalkNPCToCoords, - (OpcodeMethod)&TattooTalk::cmdWalkHomesAndNPCToCoords, - (OpcodeMethod)&TattooTalk::cmdStealthModeDeactivate, - (OpcodeMethod)&TattooTalk::cmdHolmesOff, - (OpcodeMethod)&TattooTalk::cmdHolmesOn, - - (OpcodeMethod)&TattooTalk::cmdGotoScene, - (OpcodeMethod)&TattooTalk::cmdSetNPCPathDest, - (OpcodeMethod)&TattooTalk::cmdAddItemToInventory, - (OpcodeMethod)&TattooTalk::cmdSetObject, - (OpcodeMethod)&TattooTalk::cmdNextSong, - (OpcodeMethod)&TattooTalk::cmdSetNPCPathPause, - (OpcodeMethod)&TattooTalk::cmdPassword, - (OpcodeMethod)&TattooTalk::cmdSetSceneEntryFlag, - (OpcodeMethod)&TattooTalk::cmdWalkToCAnimation, - (OpcodeMethod)&TattooTalk::cmdRemoveItemFromInventory, - - (OpcodeMethod)&TattooTalk::cmdEnableEndKey, - (OpcodeMethod)&TattooTalk::cmdDisableEndKey, - nullptr, - (OpcodeMethod)&TattooTalk::cmdWalkHomesAndNPCToCoords, - (OpcodeMethod)&TattooTalk::cmdSetNPCTalkFile, - (OpcodeMethod)&TattooTalk::cmdSetNPCOff, - (OpcodeMethod)&TattooTalk::cmdSetNPCOn, - (OpcodeMethod)&TattooTalk::cmdSetNPCDescOnOff, - (OpcodeMethod)&TattooTalk::cmdSetNPCPathPauseTakingNotes, - (OpcodeMethod)&TattooTalk::cmdSetNPCPathPauseLookingHolmes, - - (OpcodeMethod)&TattooTalk::cmdTalkInterruptsEnable, - (OpcodeMethod)&TattooTalk::cmdTalkInterruptsDisable, - (OpcodeMethod)&TattooTalk::cmdSetNPCInfoLine, - (OpcodeMethod)&TattooTalk::cmdSetNPCPosition, - (OpcodeMethod)&TattooTalk::cmdNPCLabelSet, - (OpcodeMethod)&TattooTalk::cmdNPCLabelGoto, - (OpcodeMethod)&TattooTalk::cmdNPCLabelIfFlagGoto, - (OpcodeMethod)&TattooTalk::cmdSetNPCWalkGraphics, - nullptr, - (OpcodeMethod)&TattooTalk::cmdSetNPCVerb, - - (OpcodeMethod)&TattooTalk::cmdSetNPCVerbCAnimation, - (OpcodeMethod)&TattooTalk::cmdSetNPCVerbScript, - nullptr, - (OpcodeMethod)&TattooTalk::cmdRestorePeopleSequence, - (OpcodeMethod)&TattooTalk::cmdSetNPCVerbTarget, - (OpcodeMethod)&TattooTalk::cmdTurnSoundsOff - }; - - _opcodeTable = OPCODE_METHODS; -} - -OpcodeReturn TattooTalk::cmdMouseOnOff(const byte *&str) { error("TODO: script opcode"); } - -OpcodeReturn TattooTalk::cmdNextSong(const byte *&str) { - Sound &sound = *_vm->_sound; - - // Get the name of the next song to play - ++str; - sound._nextSongName = ""; - for (int idx = 0; idx < 8; ++idx) { - if (str[idx] != '~') - sound._nextSongName += str[idx]; - else - break; - } - str += 7; - - return RET_SUCCESS; -} - -OpcodeReturn TattooTalk::cmdNPCLabelGoto(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdNPCLabelIfFlagGoto(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdNPCLabelSet(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdPassword(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdPlaySong(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdRestorePeopleSequence(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdSetNPCDescOnOff(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdSetNPCInfoLine(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdSetNPCOff(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdSetNPCOn(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdSetNPCPathDest(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdSetNPCPathPause(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdSetNPCPathPauseTakingNotes(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdSetNPCPathPauseLookingHolmes(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdSetNPCPosition(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdSetNPCTalkFile(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdSetNPCVerb(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdSetNPCVerbCAnimation(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdSetNPCVerbScript(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdSetNPCVerbTarget(const byte *&str) { error("TODO: script opcode"); } - -OpcodeReturn TattooTalk::cmdSetNPCWalkGraphics(const byte *&str) { - ++str; - int npc = *str - 1; - People &people = *_vm->_people; - Person &person = people[npc]; - - // Build up walk library name for the given NPC - person._walkVGSName = ""; - for (int idx = 0; idx < 8; ++idx) { - if (str[idx + 1] != '~') - person._walkVGSName += str[idx + 1]; - else - break; - } - person._walkVGSName += ".VGS"; - - people._forceWalkReload = true; - str += 8; - - return RET_SUCCESS; + _pauseFlag = false; } -OpcodeReturn TattooTalk::cmdSetSceneEntryFlag(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdSetTalkSequence(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdSetWalkControl(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdTalkInterruptsDisable(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdTalkInterruptsEnable(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdTurnSoundsOff(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdWalkHolmesAndNPCToCAnimation(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdWalkNPCToCAnimation(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdWalkNPCToCoords(const byte *&str) { error("TODO: script opcode"); } -OpcodeReturn TattooTalk::cmdWalkHomesAndNPCToCoords(const byte *&str) { error("TODO: script opcode"); } - } // End of namespace Sherlock diff --git a/engines/sherlock/talk.h b/engines/sherlock/talk.h index ebfe8f1732..a22a39db94 100644 --- a/engines/sherlock/talk.h +++ b/engines/sherlock/talk.h @@ -26,14 +26,15 @@ #include "common/scummsys.h" #include "common/array.h" #include "common/rect.h" -#include "common/serializer.h" #include "common/stream.h" #include "common/stack.h" +#include "sherlock/objects.h" +#include "sherlock/saveload.h" namespace Sherlock { +#define SPEAKER_REMOVE 0x80 #define MAX_TALK_SEQUENCES 11 -#define MAX_TALK_FILES 500 enum { OP_SWITCH_SPEAKER = 0, @@ -69,7 +70,7 @@ enum { OP_REMOVE_ITEM_FROM_INVENTORY = 30, OP_ENABLE_END_KEY = 31, OP_DISABLE_END_KEY = 32, - OP_CARRIAGE_RETURN = 33, + OP_END_TEXT_WINDOW = 33, OP_MOUSE_OFF_ON = 34, OP_SET_WALK_CONTROL = 35, @@ -82,27 +83,29 @@ enum { OP_NEED_PASSWORD = 42, OP_SET_SCENE_ENTRY_FLAG = 43, OP_WALK_NPC_TO_CANIM = 44, - OP_WALK_HOLMES_AND_NPC_TO_COORDS = 45, - OP_SET_NPC_TALK_FILE = 46, - OP_TURN_NPC_OFF = 47, - OP_TURN_NPC_ON = 48, - OP_NPC_DESC_ON_OFF = 49, - OP_NPC_PATH_PAUSE_TAKING_NOTES = 50, - OP_NPC_PATH_PAUSE_LOOKING_HOLMES = 51, - OP_ENABLE_TALK_INTERRUPTS = 52, - OP_DISABLE_TALK_INTERRUPTS = 53, - OP_SET_NPC_INFO_LINE = 54, - OP_SET_NPC_POSITION = 54, - OP_NPC_PATH_LABEL = 55, - OP_PATH_GOTO_LABEL = 56, - OP_PATH_IF_FLAG_GOTO_LABEL = 57, - OP_NPC_WALK_GRAPHICS = 58, - OP_NPC_VERB = 59, - OP_NPC_VERB_CANIM = 60, - OP_NPC_VERB_SCRIPT = 61, - OP_RESTORE_PEOPLE_SEQUENCE = 62, - OP_NPC_VERB_TARGET = 63, - OP_TURN_SOUNDS_OFF = 64 + OP_WALK_NPC_TO_COORDS = 45, + OP_WALK_HOLMES_AND_NPC_TO_COORDS = 46, + OP_SET_NPC_TALK_FILE = 47, + OP_TURN_NPC_OFF = 48, + OP_TURN_NPC_ON = 49, + OP_NPC_DESC_ON_OFF = 50, + OP_NPC_PATH_PAUSE_TAKING_NOTES = 51, + OP_NPC_PATH_PAUSE_LOOKING_HOLMES = 52, + OP_ENABLE_TALK_INTERRUPTS = 53, + OP_DISABLE_TALK_INTERRUPTS = 54, + OP_SET_NPC_INFO_LINE = 55, + OP_SET_NPC_POSITION = 56, + OP_NPC_PATH_LABEL = 57, + OP_PATH_GOTO_LABEL = 58, + OP_PATH_IF_FLAG_GOTO_LABEL = 59, + OP_NPC_WALK_GRAPHICS = 60, + OP_NPC_VERB = 61, + OP_NPC_VERB_CANIM = 62, + OP_NPC_VERB_SCRIPT = 63, + OP_RESTORE_PEOPLE_SEQUENCE = 64, + OP_NPC_VERB_TARGET = 65, + OP_TURN_SOUNDS_OFF = 66, + OP_NULL = 67 }; enum OpcodeReturn { RET_EXIT = -1, RET_SUCCESS = 0, RET_CONTINUE = 1 }; @@ -116,8 +119,13 @@ typedef OpcodeReturn(Talk::*OpcodeMethod)(const byte *&str); struct SequenceEntry { int _objNum; Common::Array<byte> _sequences; - int _frameNumber; - int _seqTo; + Object *_obj; // Pointer to the bgshape that these values go to + short _frameNumber; // Frame number in frame sequence to draw + short _sequenceNumber; // Start frame of sequences that are repeated + int _seqStack; // Allows gosubs to return to calling frame + int _seqTo; // Allows 1-5, 8-3 type sequences encoded + int _seqCounter; // How many times this sequence has been executed + int _seqCounter2; SequenceEntry(); }; @@ -139,11 +147,12 @@ struct Statement { int _quotient; int _talkMap; Common::Rect _talkPos; + int _journal; /** * Load the data for a single statement within a talk file */ - void synchronize(Common::SeekableReadStream &s); + void load(Common::SeekableReadStream &s, bool isRoseTattoo); }; struct TalkHistoryEntry { @@ -153,16 +162,6 @@ struct TalkHistoryEntry { bool &operator[](int index) { return _data[index]; } }; -struct TalkSequences { - byte _data[MAX_TALK_SEQUENCES]; - - TalkSequences() { clear(); } - TalkSequences(const byte *data); - - byte &operator[](int idx) { return _data[idx]; } - void clear(); -}; - class Talk { friend class Scalpel::ScalpelUserInterface; private: @@ -170,48 +169,18 @@ private: * Remove any voice commands from a loaded statement list */ void stripVoiceCommands(); - - /** - * Form a table of the display indexes for statements - */ - void setTalkMap(); - - /** - * Display a list of statements in a window at the bottom of the screen that the - * player can select from. - */ - bool displayTalk(bool slamIt); - - /** - * Prints a single conversation option in the interface window - */ - int talkLine(int lineNum, int stateNum, byte color, int lineY, bool slamIt); - - /** - * Parses a reply for control codes and display text. The found text is printed within - * the text window, handles delays, animations, and animating portraits. - */ - void doScript(const Common::String &script); - - /** - * When the talk window has been displayed, waits a period of time proportional to - * the amount of text that's been displayed - */ - int waitForMore(int delay); protected: SherlockEngine *_vm; OpcodeMethod *_opcodeTable; Common::Stack<SequenceEntry> _savedSequences; - Common::Stack<SequenceEntry> _sequenceStack; Common::Stack<ScriptStackEntry> _scriptStack; - Common::Array<Statement> _statements; - TalkHistoryEntry _talkHistory[MAX_TALK_FILES]; - int _speaker; + Common::Array<TalkHistoryEntry> _talkHistory; int _talkIndex; int _scriptSelect; int _talkStealth; int _talkToFlag; int _scriptSaveIndex; + int _3doSpeechIndex; // These fields are used solely by doScript, but are fields because all the script opcodes are // separate methods now, and need access to these fields @@ -229,10 +198,9 @@ protected: OpcodeReturn cmdAddItemToInventory(const byte *&str); OpcodeReturn cmdAdjustObjectSequence(const byte *&str); OpcodeReturn cmdBanishWindow(const byte *&str); - OpcodeReturn cmdCallTalkFile(const byte *&str); OpcodeReturn cmdDisableEndKey(const byte *&str); OpcodeReturn cmdEnableEndKey(const byte *&str); - OpcodeReturn cmdGotoScene(const byte *&str); + OpcodeReturn cmdEndTextWindow(const byte *&str); OpcodeReturn cmdHolmesOff(const byte *&str); OpcodeReturn cmdHolmesOn(const byte *&str); OpcodeReturn cmdPause(const byte *&str); @@ -243,20 +211,61 @@ protected: OpcodeReturn cmdSetObject(const byte *&str); OpcodeReturn cmdStealthModeActivate(const byte *&str); OpcodeReturn cmdStealthModeDeactivate(const byte *&str); - OpcodeReturn cmdSwitchSpeaker(const byte *&str); OpcodeReturn cmdToggleObject(const byte *&str); OpcodeReturn cmdWalkToCAnimation(const byte *&str); - OpcodeReturn cmdWalkToCoords(const byte *&str); +protected: + /** + * Checks, if a character is an opcode + */ + bool isOpcode(byte checkCharacter); + + /** + * Form a table of the display indexes for statements + */ + void setTalkMap(); + + /** + * When the talk window has been displayed, waits a period of time proportional to + * the amount of text that's been displayed + */ + int waitForMore(int delay); + + /** + * Display the talk interface window + */ + virtual void talkInterface(const byte *&str) = 0; + + /** + * Pause when displaying a talk dialog on-screen + */ + virtual void talkWait(const byte *&str); + + /** + * Show the talk display + */ + virtual void showTalk() = 0; + + /** + * Called when a character being spoken to has no talk options to display + */ + virtual void nothingToSay() = 0; + + /** + * Called when the active speaker is switched + */ + virtual void switchSpeaker() {} public: + Common::Array<Statement> _statements; bool _talkToAbort; int _talkCounter; int _talkTo; int _scriptMoreFlag; + bool _openTalkWindow; Common::String _scriptName; bool _moreTalkUp, _moreTalkDown; int _converseNum; const byte *_opcodes; - + int _speaker; public: static Talk *init(SherlockEngine *vm); virtual ~Talk() {} @@ -274,7 +283,13 @@ public: * In their case, the conversation display is simply suppressed, and control is passed on to * doScript to implement whatever action is required. */ - void talkTo(const Common::String &filename); + virtual void talkTo(const Common::String filename); + + /** + * Parses a reply for control codes and display text. The found text is printed within + * the text window, handles delays, animations, and animating portraits. + */ + void doScript(const Common::String &script); /** * Main method for handling conversations when a character to talk to has been @@ -282,7 +297,7 @@ public: * interface window for the conversation and passes on control to give the * player a list of options to make a selection from */ - void talk(int objNum); + void initTalk(int objNum); /** * Clear loaded talk data @@ -290,48 +305,26 @@ public: void freeTalkVars(); /** - * Draws the interface for conversation display - */ - void drawInterface(); - - /** * Opens the talk file 'talk.tlk' and searches the index for the specified * conversation. If found, the data for that conversation is loaded */ void loadTalkFile(const Common::String &filename); /** - * Change the sequence of a background object corresponding to a given speaker. - * The new sequence will display the character as "listening" - */ - void setStillSeq(int speaker); - - /** - * Clears the stack of pending object sequences associated with speakers in the scene - */ - void clearSequences(); - - /** - * Pulls a background object sequence from the sequence stack and restore's the - * object's sequence - */ - void pullSequence(); - - /** * Push the sequence of a background object that's an NPC that needs to be * saved onto the sequence stack. */ void pushSequence(int speaker); /** - * Change the sequence of the scene background object associated with the current speaker. + * Push the details of a passed object onto the saved sequences stack */ - void setSequence(int speaker); + virtual void pushSequenceEntry(Object *obj) = 0; /** - * Returns true if the script stack is empty + * Clears the stack of pending object sequences associated with speakers in the scene */ - bool isSequencesEmpty() const { return _scriptStack.empty(); } + virtual void clearSequences() = 0; /** * Pops an entry off of the script stack @@ -341,66 +334,34 @@ public: /** * Synchronize the data for a savegame */ - void synchronize(Common::Serializer &s); -}; + void synchronize(Serializer &s); -class ScalpelTalk : public Talk { -protected: - OpcodeReturn cmdAssignPortraitLocation(const byte *&str); - OpcodeReturn cmdClearInfoLine(const byte *&str); - OpcodeReturn cmdClearWindow(const byte *&str); - OpcodeReturn cmdDisplayInfoLine(const byte *&str); - OpcodeReturn cmdElse(const byte *&str); - OpcodeReturn cmdIf(const byte *&str); - OpcodeReturn cmdMoveMouse(const byte *&str); - OpcodeReturn cmdPlayPrologue(const byte *&str); - OpcodeReturn cmdRemovePortrait(const byte *&str); - OpcodeReturn cmdSfxCommand(const byte *&str); - OpcodeReturn cmdSummonWindow(const byte *&str); - OpcodeReturn cmdCarriageReturn(const byte *&str); -public: - ScalpelTalk(SherlockEngine *vm); - virtual ~ScalpelTalk() {} -}; + /** + * Draws the interface for conversation display + */ + virtual void drawInterface() {} -class TattooTalk : public Talk { -protected: - OpcodeReturn cmdMouseOnOff(const byte *&str); - OpcodeReturn cmdNextSong(const byte *&str); - OpcodeReturn cmdPassword(const byte *&str); - OpcodeReturn cmdPlaySong(const byte *&str); - OpcodeReturn cmdRestorePeopleSequence(const byte *&str); - OpcodeReturn cmdSetNPCDescOnOff(const byte *&str); - OpcodeReturn cmdSetNPCInfoLine(const byte *&str); - OpcodeReturn cmdNPCLabelGoto(const byte *&str); - OpcodeReturn cmdNPCLabelIfFlagGoto(const byte *&str); - OpcodeReturn cmdNPCLabelSet(const byte *&str); - OpcodeReturn cmdSetNPCOff(const byte *&str); - OpcodeReturn cmdSetNPCOn(const byte *&str); - OpcodeReturn cmdSetNPCPathDest(const byte *&str); - OpcodeReturn cmdSetNPCPathPause(const byte *&str); - OpcodeReturn cmdSetNPCPathPauseTakingNotes(const byte *&str); - OpcodeReturn cmdSetNPCPathPauseLookingHolmes(const byte *&str); - OpcodeReturn cmdSetNPCPosition(const byte *&str); - OpcodeReturn cmdSetNPCTalkFile(const byte *&str); - OpcodeReturn cmdSetNPCVerb(const byte *&str); - OpcodeReturn cmdSetNPCVerbCAnimation(const byte *&str); - OpcodeReturn cmdSetNPCVerbScript(const byte *&str); - OpcodeReturn cmdSetNPCVerbTarget(const byte *&str); - OpcodeReturn cmdSetNPCWalkGraphics(const byte *&str); - OpcodeReturn cmdSetSceneEntryFlag(const byte *&str); - OpcodeReturn cmdSetTalkSequence(const byte *&str); - OpcodeReturn cmdSetWalkControl(const byte *&str); - OpcodeReturn cmdTalkInterruptsDisable(const byte *&str); - OpcodeReturn cmdTalkInterruptsEnable(const byte *&str); - OpcodeReturn cmdTurnSoundsOff(const byte *&str); - OpcodeReturn cmdWalkHolmesAndNPCToCAnimation(const byte *&str); - OpcodeReturn cmdWalkNPCToCAnimation(const byte *&str); - OpcodeReturn cmdWalkNPCToCoords(const byte *&str); - OpcodeReturn cmdWalkHomesAndNPCToCoords(const byte *&str); -public: - TattooTalk(SherlockEngine *vm); - virtual ~TattooTalk() {} + /** + * Display a list of statements in a window at the bottom of the screen that the + * player can select from. + */ + virtual bool displayTalk(bool slamIt) { return false; } + + /** + * Prints a single conversation option in the interface window + */ + virtual int talkLine(int lineNum, int stateNum, byte color, int lineY, bool slamIt) { return 0; } + + /** + * Pulls a background object sequence from the sequence stack and restore's the + * object's sequence + */ + virtual void pullSequence(int slot = -1) = 0; + + /** + * Returns true if the script stack is empty + */ + virtual bool isSequencesEmpty() const = 0; }; } // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/tattoo.cpp b/engines/sherlock/tattoo/tattoo.cpp index 368b24bfcd..bfb35565bc 100644 --- a/engines/sherlock/tattoo/tattoo.cpp +++ b/engines/sherlock/tattoo/tattoo.cpp @@ -20,20 +20,34 @@ * */ -#include "sherlock/tattoo/tattoo.h" +#include "common/config-manager.h" #include "engines/util.h" +#include "sherlock/tattoo/tattoo.h" +#include "sherlock/tattoo/tattoo_fixed_text.h" +#include "sherlock/tattoo/tattoo_resources.h" +#include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo_user_interface.h" +#include "sherlock/tattoo/widget_base.h" +#include "sherlock/people.h" namespace Sherlock { namespace Tattoo { TattooEngine::TattooEngine(OSystem *syst, const SherlockGameDescription *gameDesc) : - SherlockEngine(syst, gameDesc) { - _creditsActive = false; + SherlockEngine(syst, gameDesc), _darts(this), _foolscapWidget(this) { + _runningProlog = false; + _fastMode = false; + _allowFastMode = true; + _transparentMenus = true; + _textWindowsOn = true; +} + +TattooEngine::~TattooEngine() { } void TattooEngine::showOpening() { - // TODO + // No implementation - opening is done using in-game scenes } void TattooEngine::initialize() { @@ -42,20 +56,83 @@ void TattooEngine::initialize() { // Initialize the base engine SherlockEngine::initialize(); - _flags.resize(100 * 8); + // Initialise the global flags + _flags.resize(3200); + _flags[1] = _flags[4] = _flags[76] = true; + _runningProlog = true; // Add some more files to the cache _res->addToCache("walk.lib"); + + // Set up list of people + for (int idx = 0; idx < TATTOO_MAX_PEOPLE; ++idx) { + _people->_characters.push_back(PersonData( + getLanguage() == Common::FR_FRA ? FRENCH_NAMES[idx] : ENGLISH_NAMES[idx], + PORTRAITS[idx], nullptr, nullptr)); + } + + // Load the inventory + loadInventory(); // Starting scene - _scene->_goToScene = 91; + _scene->_goToScene = STARTING_INTRO_SCENE; // Load an initial palette loadInitialPalette(); } void TattooEngine::startScene() { - // TODO + TattooUserInterface &ui = *(TattooUserInterface *)_ui; + + switch (_scene->_goToScene) { + case 7: + case 8: + case 18: + case 53: + case 68: + // Load overlay mask(s) for the scene + ui._mask = _res->load(Common::String::format("res%02d.msk", _scene->_goToScene)); + if (_scene->_goToScene == 8) + ui._mask1 = _res->load("res08a.msk"); + else if (_scene->_goToScene == 18 || _scene->_goToScene == 68) + ui._mask1 = _res->load("res08a.msk"); + break; + + case STARTING_INTRO_SCENE: + // Disable input so that the intro can't be skipped until the game's logo has been shown + ui._lockoutTimer = STARTUP_KEYS_DISABLED_DELAY; + break; + + case OVERHEAD_MAP: + case OVERHEAD_MAP2: + // Show the map + _scene->_currentScene = OVERHEAD_MAP; + _scene->_goToScene = _map->show(); + + _people->_savedPos = Common::Point(-1, -1); + _people->_savedPos._facing = -1; + break; + + case 101: + // Darts Board minigame + _darts.playDarts(GAME_CRICKET); + break; + + case 102: + // Darts Board minigame + _darts.playDarts(GAME_301); + break; + + case 103: + // Darts Board minigame + _darts.playDarts(GAME_501); + break; + + default: + break; + } + + _events->setCursor(ARROW); } void TattooEngine::loadInitialPalette() { @@ -68,14 +145,65 @@ void TattooEngine::loadInitialPalette() { delete stream; } -void TattooEngine::drawCredits() { - // TODO +void TattooEngine::loadInventory() { + Inventory &inv = *_inventory; + + Common::String inv1 = _fixedText->getText(kFixedText_Inv1); + Common::String inv2 = _fixedText->getText(kFixedText_Inv2); + Common::String inv3 = _fixedText->getText(kFixedText_Inv3); + Common::String inv4 = _fixedText->getText(kFixedText_Inv4); + Common::String inv5 = _fixedText->getText(kFixedText_Inv5); + Common::String inv6 = _fixedText->getText(kFixedText_Inv6); + Common::String inv7 = _fixedText->getText(kFixedText_Inv7); + Common::String inv8 = _fixedText->getText(kFixedText_Inv8); + Common::String invDesc1 = _fixedText->getText(kFixedText_InvDesc1); + Common::String invDesc2 = _fixedText->getText(kFixedText_InvDesc2); + Common::String invDesc3 = _fixedText->getText(kFixedText_InvDesc3); + Common::String invDesc4 = _fixedText->getText(kFixedText_InvDesc4); + Common::String invDesc5 = _fixedText->getText(kFixedText_InvDesc5); + Common::String invDesc6 = _fixedText->getText(kFixedText_InvDesc6); + Common::String invDesc7 = _fixedText->getText(kFixedText_InvDesc7); + Common::String invDesc8 = _fixedText->getText(kFixedText_InvDesc8); + Common::String solve = _fixedText->getText(kFixedText_Solve); + + // Initial inventory + inv._holdings = 5; + inv.push_back(InventoryItem(0, inv1, invDesc1, "_ITEM01A")); + inv.push_back(InventoryItem(0, inv2, invDesc2, "_ITEM02A")); + inv.push_back(InventoryItem(0, inv3, invDesc3, "_ITEM03A")); + inv.push_back(InventoryItem(0, inv4, invDesc4, "_ITEM04A")); + inv.push_back(InventoryItem(0, inv5, invDesc5, "_ITEM05A")); + + // Hidden items + inv.push_back(InventoryItem(295, inv6, invDesc6, "_PAP212D", solve)); + inv.push_back(InventoryItem(294, inv7, invDesc7, "_PAP212I")); + inv.push_back(InventoryItem(818, inv8, invDesc8, "_LANT02I")); +} + +void TattooEngine::doFoolscapPuzzle() { + _foolscapWidget.show(); +} + +void TattooEngine::loadConfig() { + SherlockEngine::loadConfig(); + + _transparentMenus = ConfMan.getBool("transparent_windows"); + _textWindowsOn = ConfMan.getBool("subtitles") || !_sound->_speechOn; +} + +void TattooEngine::saveConfig() { + SherlockEngine::saveConfig(); + + ConfMan.setBool("transparent_windows", _transparentMenus); + ConfMan.setBool("subtitles", _textWindowsOn); + ConfMan.flushToDisk(); } -void TattooEngine::eraseCredits() { - // TODO +bool TattooEngine::canSaveGameStateCurrently() { + TattooUserInterface &ui = *(TattooUserInterface *)_ui; + return _canLoadSave && !ui._creditsWidget.active() && !_runningProlog; } } // End of namespace Tattoo -} // End of namespace Scalpel +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/tattoo.h b/engines/sherlock/tattoo/tattoo.h index bb6310dbe3..71872ab1b9 100644 --- a/engines/sherlock/tattoo/tattoo.h +++ b/engines/sherlock/tattoo/tattoo.h @@ -24,17 +24,48 @@ #define SHERLOCK_TATTOO_H #include "sherlock/sherlock.h" +#include "sherlock/tattoo/tattoo_darts.h" +#include "sherlock/tattoo/widget_foolscap.h" namespace Sherlock { namespace Tattoo { +enum { + INV_FOREGROUND = 167, + INV_BACKGROUND = 1, + INFO_FOREGROUND = 233, + INFO_BACKGROUND = 239, + INFO_TOP = 185, + INFO_MIDDLE = 186, + INFO_BOTTOM = 188, + MENU_BACKGROUND = 225, + COMMAND_FOREGROUND = 15, + COMMAND_HIGHLIGHTED = 254, + COMMAND_NULL = 193, + PEN_COLOR = 248, + PEN_HIGHLIGHT_COLOR = 129 +}; + +enum { + FLAG_PLAYER_IS_HOLMES = 76, + FLAG_ALT_MAP_MUSIC = 525 +}; + class TattooEngine : public SherlockEngine { private: + Darts _darts; + WidgetFoolscap _foolscapWidget; + /** * Loads the initial palette for the game */ void loadInitialPalette(); + + /** + * Load the initial inventory + */ + void loadInventory(); protected: /** * Initialize the engine @@ -47,21 +78,34 @@ protected: * Starting a scene within the game */ virtual void startScene(); + + /** + * Load configuration options + */ + virtual void loadConfig(); public: - bool _creditsActive; + bool _runningProlog; + bool _fastMode, _allowFastMode; + bool _transparentMenus; + bool _textWindowsOn; public: TattooEngine(OSystem *syst, const SherlockGameDescription *gameDesc); - virtual ~TattooEngine() {} + virtual ~TattooEngine(); + + /** + * Shows the foolscap puzzle + */ + void doFoolscapPuzzle(); /** - * Draw credits on the screen + * Save the game configuration */ - void drawCredits(); + virtual void saveConfig(); /** - * Erase any area of the screen covered by credits + * Returns true if the game can be saved */ - void eraseCredits(); + virtual bool canSaveGameStateCurrently(); }; } // End of namespace Tattoo diff --git a/engines/sherlock/tattoo/tattoo_darts.cpp b/engines/sherlock/tattoo/tattoo_darts.cpp new file mode 100644 index 0000000000..cb4b52b01a --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_darts.cpp @@ -0,0 +1,1000 @@ +/* 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 "sherlock/tattoo/tattoo_darts.h" +#include "sherlock/tattoo/tattoo_fixed_text.h" +#include "sherlock/tattoo/tattoo.h" + +namespace Sherlock { + +namespace Tattoo { + +enum { + DART_COLOR_FORE = 5, + PLAYER_COLOR = 11 +}; + +static const int STATUS_INFO_X = 430; +static const int STATUS_INFO_Y = 50; +static const int STATUS_INFO_WIDTH = 205; +static const int STATUS_INFO_HEIGHT = 330; +static const int STATUS2_INFO_X = 510; +static const int STATUS2_X_ADD = STATUS2_INFO_X - STATUS_INFO_X; +static const int DART_BAR_VX = 10; +static const int DART_HEIGHT_Y = 121; +static const int DART_BAR_SIZE = 150; +static const int DARTBOARD_LEFT = 73; +static const int DARTBOARD_TOP = 68; +static const int DARTBOARD_WIDTH = 257; +static const int DARTBOARD_HEIGHT = 256; +static const int DARTBOARD_TOTALX = DARTBOARD_WIDTH * 120 / 100; +static const int DARTBOARD_TOTALY = DARTBOARD_HEIGHT * 120 / 100; +static const int DARTBOARD_TOTALTOP = DARTBOARD_TOP - DARTBOARD_WIDTH / 10; +static const int DARTBOARD_TOTALLEFT = DARTBOARD_LEFT - DARTBOARD_HEIGHT / 10; +static const int CRICKET_VALUE[7] = { 20, 19, 18, 17, 16, 15, 25 }; + +Darts::Darts(SherlockEngine *vm) : _vm(vm) { + _gameType = GAME_301; + _hand1 = _hand2 = nullptr; + _dartGraphics = nullptr; + _dartsLeft = nullptr; + _dartMap = nullptr; + _dartBoard = nullptr; + Common::fill(&_cricketScore[0][0], &_cricketScore[0][7], 0); + Common::fill(&_cricketScore[1][0], &_cricketScore[1][7], 0); + _score1 = _score2 = 0; + _roundNum = 0; + _roundScore = 0; + _level = 0; + _oldDartButtons = false; + _handX = 0; + _compPlay = 1; + _escapePressed = false; +} + +void Darts::playDarts(GameType gameType) { + Events &events = *_vm->_events; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + int oldFontType = screen.fontNumber(); + int playerNum = 0; + int roundStart, score; + int lastDart; + int numHits = 0; + bool gameOver = false; + bool done = false; + const char *const NUM_HITS_STR[3] = { "a", FIXED(Double), FIXED(Triple) }; + + screen.setFont(7); + _spacing = screen.fontHeight() + 2; + + // Load dart graphics and initialize values + loadDarts(); + initDarts(); + + while (!done && !_vm->shouldQuit()) { + roundStart = score = (playerNum == 0) ? _score1 : _score2; + + showNames(playerNum); + showStatus(playerNum); + _roundScore = 0; + + for (int idx = 0; idx < 3 && !_vm->shouldQuit(); ++idx) { + if (_compPlay == 1) + lastDart = throwDart(idx + 1, playerNum * 2); /* Throw one dart */ + else + if (_compPlay == 2) + lastDart = throwDart(idx + 1, playerNum + 1); /* Throw one dart */ + else + lastDart = throwDart(idx + 1, 0); /* Throw one dart */ + + if (_gameType == GAME_301) { + score -= lastDart; + _roundScore += lastDart; + } else { + numHits = lastDart >> 16; + if (numHits == 0) + numHits = 1; + if (numHits > 3) + numHits = 3; + + lastDart = lastDart & 0xffff; + updateCricketScore(playerNum, lastDart, numHits); + score = (playerNum == 0) ? _score1 : _score2; + } + + // Special case for ScummVM: I'm making pressing Escape to exit out of the Darts game as a way to skip + // it entirely if you don't want to play all the way through it + if (_escapePressed) { + gameOver = true; + done = true; + playerNum = 0; + } + + + if (_gameType == GAME_301) { + if (playerNum == 0) + _score1 = score; + else + _score2 = score; + + if (score == 0) + // Someone won + gameOver = true; + } else { + // check for cricket game over + bool allClosed = true; + int nOtherScore; + + for (int y = 0; y < 7; y++) { + if (_cricketScore[playerNum][y] < 3) + allClosed = false; + } + + if (allClosed) { + nOtherScore = (playerNum == 0) ? _score2 : _score1; + if (score >= nOtherScore) + gameOver = true; + } + } + + // Show scores + showStatus(playerNum); + screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(_dartInfo.left, _dartInfo.top - 1), + Common::Rect(_dartInfo.left, _dartInfo.top - 1, _dartInfo.right, _dartInfo.bottom - 1)); + screen.print(Common::Point(_dartInfo.left, _dartInfo.top), 0, "%s # %d", FIXED(Dart), idx + 1); + + if (_gameType == GAME_301) { + if (_vm->getLanguage() == Common::FR_FRA) + screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing), 0, + "%s %s: %d", FIXED(Scored), FIXED(Points), lastDart); + else + screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing), 0, + "%s %d %s", FIXED(Scored), lastDart, FIXED(Points)); + } else { + if (lastDart != 25) + screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing), 0, + "%s %s %d", FIXED(Hit), NUM_HITS_STR[numHits - 1], lastDart); + else + screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing), 0, + "%s %s %s", FIXED(Hit), NUM_HITS_STR[numHits - 1], FIXED(Bullseye)); + } + + if (score != 0 && playerNum == 0 && !gameOver) + screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 3), 0, + "%s", FIXED(PressAKey)); + + if (gameOver) { + screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 3), + 0, "%s", FIXED(GameOver)); + if (playerNum == 0) { + screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 4), 0, + "%s %s", FIXED(Holmes), FIXED(Wins)); + _vm->setFlagsDirect(531); + } else { + screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 4), 0, + "%s %s!", _opponent.c_str(), FIXED(Wins)); + _vm->setFlagsDirect(530); + } + + screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 5), 0, + "%s", FIXED(PressAKey)); + + done = true; + idx = 10; + } else if (_gameType == GAME_301 && score < 0) { + screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 2), 0, + "%s!", FIXED(Busted)); + + // End turn + idx = 10; + score = roundStart; + if (playerNum == 0) + _score1 = score; + else + _score2 = score; + } + + // Clear keyboard events + events.clearEvents(); + + if ((playerNum == 0 && _compPlay == 1) || _compPlay == 0 || done) { + if (_escapePressed) { + done = true; + break; + } + } else { + events.wait(20); + } + + screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_dartInfo.left, _dartInfo.top - 1), + Common::Rect(_dartInfo.left, _dartInfo.top - 1, _dartInfo.right, _dartInfo.bottom - 1)); + screen.blitFrom(screen._backBuffer1); + } + + playerNum ^= 1; + if (!playerNum) + ++_roundNum; + + if (!done) { + screen._backBuffer2.blitFrom((*_dartBoard)[0], Common::Point(0, 0)); + screen._backBuffer1.blitFrom(screen._backBuffer2); + screen.blitFrom(screen._backBuffer2); + } + } + + // Wait for a keypress + do { + events.pollEventsAndWait(); + events.setButtonState(); + } while (!_vm->shouldQuit() && !events.kbHit() && !events._pressed); + events.clearEvents(); + + closeDarts(); + screen.fadeToBlack(); + screen.setFont(oldFontType); + + // Flag to return to the Billard's Academy scene + scene._goToScene = 26; +} + +void Darts::initDarts() { + _dartInfo = Common::Rect(430, 245, 430 + 205, 245 + 150); + _escapePressed = false; + + if (_gameType == GAME_CRICKET) { + _dartInfo = Common::Rect(430, 245, 430 + 205, 245 + 150); + } + + Common::fill(&_cricketScore[0][0], &_cricketScore[0][7], 0); + Common::fill(&_cricketScore[1][0], &_cricketScore[1][7], 0); + + switch (_gameType) { + case GAME_501: + _score1 = _score2 = 501; + _gameType = GAME_301; + break; + + case GAME_301: + _score1 = _score2 = 301; + break; + + default: + // Cricket + _score1 = _score2 = 0; + break; + } + + _roundNum = 1; + + if (_level == 9) { + // No computer players + _compPlay = 0; + _level = 0; + } else if (_level == 8) { + _level = _vm->getRandomNumber(3); + _compPlay = 2; + } else { + // Check for opponent flags + for (int idx = 0; idx < 4; ++idx) { + if (_vm->readFlags(314 + idx)) + _level = idx; + } + } + + _opponent = FIXED(Jock); +} + +void Darts::loadDarts() { + Resources &res = *_vm->_res; + Screen &screen = *_vm->_screen; + byte palette[PALETTE_SIZE]; + + // Load images + _hand1 = new ImageFile("hand1.vgs"); + _hand2 = new ImageFile("hand2.vgs"); + _dartGraphics = new ImageFile("darts.vgs"); + _dartsLeft = new ImageFile("DartsLft.vgs"); + _dartMap = new ImageFile("DartMap.vgs"); + _dartBoard = new ImageFile("DartBd.vgs"); + + // Load and set the palette + Common::SeekableReadStream *stream = res.load("DartBd.pal"); + stream->read(palette, PALETTE_SIZE); + screen.translatePalette(palette); + screen.setPalette(palette); + delete stream; + + // Load the initial background + screen._backBuffer1.blitFrom((*_dartBoard)[0], Common::Point(0, 0)); + screen._backBuffer2.blitFrom(screen._backBuffer1); + screen.blitFrom(screen._backBuffer1); +} + +void Darts::closeDarts() { + delete _dartBoard; + delete _dartsLeft; + delete _dartGraphics; + delete _dartMap; + delete _hand1; + delete _hand2; +} + +void Darts::showNames(int playerNum) { + Screen &screen = *_vm->_screen; + byte color; + + color = playerNum == 0 ? PLAYER_COLOR : DART_COLOR_FORE; + screen.print(Common::Point(STATUS_INFO_X, STATUS_INFO_Y), 0, "%s", FIXED(Holmes)); + screen._backBuffer1.fillRect(Common::Rect(STATUS_INFO_X, STATUS_INFO_Y + _spacing + 1, + STATUS_INFO_X + 50, STATUS_INFO_Y + _spacing + 3), color); + screen.fillRect(Common::Rect(STATUS_INFO_X, STATUS_INFO_Y + _spacing + 1, + STATUS_INFO_X + 50, STATUS_INFO_Y + _spacing + 3), color); + + color = playerNum == 1 ? PLAYER_COLOR : DART_COLOR_FORE; + screen.print(Common::Point(STATUS2_INFO_X, STATUS_INFO_Y), 0, "%s", _opponent.c_str()); + screen._backBuffer1.fillRect(Common::Rect(STATUS2_INFO_X, STATUS_INFO_Y + _spacing + 1, + STATUS2_INFO_X + 50, STATUS_INFO_Y + _spacing + 3), color); + screen.fillRect(Common::Rect(STATUS2_INFO_X, STATUS_INFO_Y + _spacing + 1, + STATUS2_INFO_X + 50, STATUS_INFO_Y + _spacing + 3), color); + + screen._backBuffer2.blitFrom(screen._backBuffer1); +} + +void Darts::showStatus(int playerNum) { + Screen &screen = *_vm->_screen; + const char *const CRICKET_SCORE_NAME[7] = { "20", "19", "18", "17", "16", "15", FIXED(Bull) }; + + screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 10), + Common::Rect(STATUS_INFO_X, STATUS_INFO_Y + 10, STATUS_INFO_X + STATUS_INFO_WIDTH, + STATUS_INFO_Y + STATUS_INFO_HEIGHT - 10)); + screen.print(Common::Point(STATUS_INFO_X + 30, STATUS_INFO_Y + _spacing + 4), 0, "%d", _score1); + + screen.print(Common::Point(STATUS2_INFO_X + 30, STATUS_INFO_Y + _spacing + 4), 0, "%d", _score2); + + int temp = (_gameType == GAME_CRICKET) ? STATUS_INFO_Y + 10 * _spacing + 5 : STATUS_INFO_Y + 55; + screen.print(Common::Point(STATUS_INFO_X, temp), 0, "%s: %d", FIXED(Round), _roundNum); + + if (_gameType == GAME_301) { + screen.print(Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 75), 0, "%s: %d", FIXED(TurnTotal), _roundScore); + } else { + // Show cricket scores + for (int x = 0; x < 7; ++x) { + screen.print(Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 40 + x * _spacing), 0, "%s:", CRICKET_SCORE_NAME[x]); + + for (int y = 0; y < 2; ++y) { + switch (CRICKET_SCORE_NAME[y][x]) { + case 1: + screen.print(Common::Point(STATUS_INFO_X + 38 + y*STATUS2_X_ADD, STATUS_INFO_Y + 40 + x * _spacing), 0, "/"); + break; + case 2: + screen.print(Common::Point(STATUS_INFO_X + 38 + y*STATUS2_X_ADD, STATUS_INFO_Y + 40 + x * _spacing), 0, "X"); + break; + case 3: + screen.print(Common::Point(STATUS_INFO_X + 38 + y * STATUS2_X_ADD - 1, STATUS_INFO_Y + 40 + x * _spacing), 0, "X"); + screen.print(Common::Point(STATUS_INFO_X + 37 + y * STATUS2_X_ADD, STATUS_INFO_Y + 40 + x * _spacing), 0, "O"); + break; + default: + break; + } + } + } + } + + screen.blitFrom(screen._backBuffer1, Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 10), + Common::Rect(STATUS_INFO_X, STATUS_INFO_Y + 10, STATUS_INFO_X + STATUS_INFO_WIDTH, + STATUS_INFO_Y + STATUS_INFO_HEIGHT - 10)); +} + +void Darts::erasePowerBars() { + Screen &screen = *_vm->_screen; + + // Erase the old power bars and replace them with empty ones + screen._backBuffer1.fillRect(Common::Rect(DART_BAR_VX, DART_HEIGHT_Y, DART_BAR_VX + 9, DART_HEIGHT_Y + DART_BAR_SIZE), 0); + screen._backBuffer1.transBlitFrom((*_dartGraphics)[0], Common::Point(DART_BAR_VX - 1, DART_HEIGHT_Y - 1)); + screen.slamArea(DART_BAR_VX - 1, DART_HEIGHT_Y - 1, 10, DART_BAR_SIZE + 2); +} + +bool Darts::dartHit() { + Events &events = *_vm->_events; + events.pollEventsAndWait(); + events.setButtonState(); + + // Keyboard check + if (events.kbHit()) { + if (events.getKey().keycode == Common::KEYCODE_ESCAPE) + _escapePressed = true; + + events.clearEvents(); + return true; + } + + bool result = events._pressed && !_oldDartButtons; + _oldDartButtons = events._pressed; + return result; +} + +int Darts::doPowerBar(const Common::Point &pt, byte color, int goToPower, int orientation) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + int idx = 0; + + events.clearEvents(); + events.delay(100); + + while (!_vm->shouldQuit()) { + if (idx >= DART_BAR_SIZE) + break; + + if ((goToPower - 1) == idx) + break; + else if (goToPower == 0) { + if (dartHit()) + break; + } + + screen._backBuffer1.hLine(pt.x, pt.y + DART_BAR_SIZE- 1 - idx, pt.x + 8, color); + screen._backBuffer1.transBlitFrom((*_dartGraphics)[0], Common::Point(pt.x - 1, pt.y - 1)); + screen.slamArea(pt.x, pt.y + DART_BAR_SIZE - 1 - idx, 8, 2); + + if (!(idx % 8)) + events.wait(1); + + ++idx; + } + + return MIN(idx * 100 / DART_BAR_SIZE, 100); +} + +int Darts::drawHand(int goToPower, int computer) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + const int HAND_OFFSET[2] = { 72, 44 }; + ImageFile *hands; + int hand; + + goToPower = (goToPower * DARTBOARD_WIDTH) / 150; + + if (!computer) { + hand = 0; + hands = _hand1; + } else { + hand = 1; + hands = _hand2; + } + + _handSize.x = (*hands)[0]._offset.x + (*hands)[0]._width; + _handSize.y = (*hands)[0]._offset.y + (*hands)[0]._height; + + // Clear keyboard buffer + events.clearEvents(); + events.delay(100); + + Common::Point pt(DARTBOARD_LEFT - HAND_OFFSET[hand], SHERLOCK_SCREEN_HEIGHT - _handSize.y); + int x = 0; + + while (!_vm->shouldQuit()) { + if (x >= DARTBOARD_WIDTH) + break; + + if ((goToPower - 1) == x) + break; + else if (goToPower == 0) { + if (dartHit()) + break; + } + + screen._backBuffer1.transBlitFrom((*hands)[0], pt); + screen.slamArea(pt.x - 1, pt.y, _handSize.x + 1, _handSize.y); + screen.restoreBackground(Common::Rect(pt.x, pt.y, pt.x + _handSize.x, pt.y + _handSize.y)); + + if (!(x % 8)) + events.wait(1); + + ++x; + ++pt.x; + } + + _handX = pt.x - 1; + + return MIN(x * 100 / DARTBOARD_WIDTH, 100); +} + +Common::Point Darts::convertFromScreenToScoreCoords(const Common::Point &pt) const { + return Common::Point(CLIP((int)pt.x, 0, DARTBOARD_WIDTH), CLIP((int)pt.y, 0, DARTBOARD_HEIGHT)); +} + +int Darts::dartScore(const Common::Point &pt) { + Common::Point pos(pt.x - DARTBOARD_LEFT, pt.y - DARTBOARD_TOP); + if (pos.x < 0 || pos.y < 0) + return 0; + int score; + + if (pos.x < DARTBOARD_WIDTH && pos.y < DARTBOARD_HEIGHT) { + pos = convertFromScreenToScoreCoords(pos); + score = *(const byte *)(*_dartMap)[0]._frame.getBasePtr(pos.x, pos.y); + + if (_gameType == GAME_301) { + if (score >= 100) { + if (score <= 120) + // Hit a double + score = (score - 100) * 2; + else + // Hit a triple + score = (score - 120) * 3; + } + } else if (score >= 100) { + if (score >= 120) + // Hit a double + score = (2 << 16) + (score - 100); + else + // Hit a triple + score = (3 << 16) + (score - 120); + } + } else { + score = 0; + } + + return score; +} + +void Darts::drawDartThrow(const Common::Point &dartPos, int computer) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + int cx, cy; + int handCy; + int drawX = 0, drawY = 0, oldDrawX = 0, oldDrawY = 0; + int xSize = 0, ySize = 0, oldxSize = 0, oldySize = 0; + int handOCx, handOCy; + int ocx, ocy; + int handOldxSize, handOldySize; + int delta = 9; + int dartNum; + int hddy; + + // Draw the animation of the hand throwing the dart first + // See which hand animation to use + ImageFile &hands = !computer ? *_hand1 : *_hand2; + int numFrames = !computer ? 14 : 13; + + ocx = ocy = handOCx = handOCy = 0; + oldxSize = oldySize = handOldxSize = handOldySize = 1; + cx = dartPos.x; + cy = SHERLOCK_SCREEN_HEIGHT - _handSize.y - 20; + + hddy = (cy - dartPos.y) / (numFrames - 7); + hddy += 2; + hddy = hddy * 10 / 8; + if (dartPos.y > 275) + hddy += 3; + + for (int idx = 0; idx < numFrames; ++idx) { + _handSize.x = hands[idx]._offset.x + hands[idx]._width; + _handSize.y = hands[idx]._offset.y + hands[idx]._height; + handCy = SHERLOCK_SCREEN_HEIGHT - _handSize.y; + + screen._backBuffer1.transBlitFrom(hands[idx], Common::Point(_handX, handCy)); + screen.slamArea(_handX, handCy, _handSize.x + 1, _handSize.y); + screen.slamArea(handOCx, handOCy, handOldxSize, handOldySize); + screen.restoreBackground(Common::Rect(_handX, handCy, _handX + _handSize.x, handCy + _handSize.y)); + + handOCx = _handX; + handOCy = handCy; + handOldxSize = _handSize.x; + handOldySize = _handSize.y; + + if (idx > 6) { + dartNum = idx - 6; + if (computer) + dartNum += 19; + + xSize = (*_dartGraphics)[dartNum]._width; + ySize = (*_dartGraphics)[dartNum]._height; + + ocx = drawX = cx - (*_dartGraphics)[dartNum]._width / 2; + ocy = drawY = cy - (*_dartGraphics)[dartNum]._height; + + // Draw dart + screen._backBuffer1.transBlitFrom((*_dartGraphics)[dartNum], dartPos); + + if (drawX < 0) { + xSize += drawX; + if (xSize < 0) + xSize = 1; + drawX = 0; + } + + if (drawY < 0) { + ySize += drawY; + if (ySize < 0) + ySize = 1; + drawY = 0; + } + + // Flush the drawn dart to the screen + screen.slamArea(drawX, drawY, xSize, ySize); + if (oldDrawX != -1) + // Flush the erased dart area + screen.slamArea(oldDrawX, oldDrawY, oldxSize, oldySize); + + screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(drawX, drawY), + Common::Rect(drawX, drawY, drawX + xSize, drawY + ySize)); + + oldDrawX = drawX; + oldDrawY = drawY; + oldxSize = xSize; + oldySize = ySize; + + cy -= hddy; + } + + events.wait(1); + } + + // Clear the last little bit of the hand from the screen + screen.slamArea(handOCx, handOCy, handOldxSize, handOldySize); + + // Erase the old dart + if (oldDrawX != -1) + screen.slamArea(oldDrawX, oldDrawY, oldxSize, oldySize); + + screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(drawX, drawY), + Common::Rect(drawX, drawY, drawX + xSize, drawY + ySize)); + + cx = dartPos.x; + cy = dartPos.y + 2; + oldDrawX = oldDrawY = -1; + + for (int idx = 5; idx <= 23; ++idx) { + dartNum = idx - 4; + if (computer) + dartNum += 19; + + if (idx < 14) + cy -= delta--; + else + if (idx == 14) + delta = 1; + if (idx > 14) + cy += delta++; + + xSize = (*_dartGraphics)[dartNum]._width; + ySize = (*_dartGraphics)[dartNum]._height; + + ocx = drawX = cx - (*_dartGraphics)[dartNum]._width / 2; + ocy = drawY = cy - (*_dartGraphics)[dartNum]._height; + + screen._backBuffer1.transBlitFrom((*_dartGraphics)[dartNum], Common::Point(drawX, drawY)); + + if (drawX < 0) { + xSize += drawX; + if (xSize < 0) + xSize = 1; + drawX = 0; + } + + if (drawY < 0) { + ySize += drawY; + if (ySize < 0) + ySize = 1; + drawY = 0; + } + + // flush the dart + screen.slamArea(drawX, drawY, xSize, ySize); + if (oldDrawX != -1) + screen.slamArea(oldDrawX, oldDrawY, oldxSize, oldySize); + + if (idx != 23) + screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(drawX, drawY), + Common::Rect(drawX, drawY, drawX + xSize, drawY + ySize)); // erase dart + + events.wait(1); + + oldDrawX = drawX; + oldDrawY = drawY; + oldxSize = xSize; + oldySize = ySize; + } + + dartNum = 19; + if (computer) + dartNum += 19; + xSize = (*_dartGraphics)[dartNum]._width; + ySize = (*_dartGraphics)[dartNum]._height; + + // Draw final dart on the board + screen._backBuffer1.transBlitFrom((*_dartGraphics)[dartNum], Common::Point(ocx, ocy)); + screen._backBuffer2.transBlitFrom((*_dartGraphics)[dartNum], Common::Point(ocx, ocy)); + screen.slamArea(ocx, ocy, xSize, ySize); +} + +int Darts::findNumberOnBoard(int aim, Common::Point &pt) { + ImageFrame &img = (*_dartMap)[0]; + + if ((aim > 20) && ((aim != 25) && (aim != 50))) { + if ((aim <= 40) && ((aim & 1) == 0)) { + aim /= 2; + aim += 100; + } else { + aim /= 3; + aim += 120; + } + } + + bool done = false; + for (int y = 0; y < img._width && !done; ++y) { + for (int x = 0; x < img._height && !done; ++x) { + byte score = *(const byte *)img._frame.getBasePtr(x, y); + + if (score == aim) { + // Found a match. Aim at non-double/triple numbers whenever possible. + // ie. Aim at 18 instead of triple 6 or double 9 + done = true; + + if (aim < 21) { + pt.x = x + 10; + pt.y = y + 10; + + score = *(const byte *)img._frame.getBasePtr(x, y); + if (score != aim) + done = false; + } else { + // Aiming at double or triple + pt.x = x + 3; + pt.y = y + 3; + } + } + } + } + + pt = convertFromScreenToScoreCoords(pt); + + if (aim == 3) + pt.y += 30; + if (aim == 17) + pt.y += 10; + + if (aim == 15) { + pt.y += 5; + pt.x += 5; + } + + pt.y = DARTBOARD_HEIGHT - pt.y; + return done; +} + +void Darts::getComputerNumber(int playerNum, Common::Point &targetPos) { + int score; + int aim = 0; + Common::Point pt; + bool done = false; + int cricketaimset = false; + bool shootBull = false; + + score = (playerNum == 0) ? _score1 : _score2; + + if (_gameType == GAME_301) { + // Try to hit number + aim = score; + if(score > 60) + shootBull = true; + } else { + if (_cricketScore[playerNum][6] < 3) { + // shoot at bull first + aim = CRICKET_VALUE[6]; + cricketaimset = true; + } else { + // Now check and shoot in this order: 20,19,18,17,16,15 + for (int idx = 0; idx < 7; ++idx) { + if (_cricketScore[playerNum][idx] < 3) { + aim = CRICKET_VALUE[idx]; + cricketaimset = true; + break; + } + } + } + + if (!cricketaimset) { + // Everything is closed + // just in case we don't get set in loop below, which should never happen + aim = 14; + for (int idx = 0; idx < 7; ++idx) { + if (_cricketScore[playerNum^1][idx] < 3) { + // Opponent has this open + aim = CRICKET_VALUE[idx]; + + if (idx == 6) + shootBull = true; + } + } + } + } + + if (shootBull) { + // Aim at bulls eye + targetPos.x = targetPos.y = 75; + + if (_level <= 1) { + if (_vm->getRandomNumber(1) == 1) { + targetPos.x += (_vm->getRandomNumber(20)-10); + targetPos.y += (_vm->getRandomNumber(20)-10); + } + } + } else { + // Loop in case number does not exist on board + do { + done = findNumberOnBoard(aim, pt); + --aim; + } while (!done); + + pt.x += DARTBOARD_TOTALLEFT * 70 / 100; + pt.y += DARTBOARD_TOTALTOP * 70 / 100; + + // old * 3/2 + targetPos.x = pt.x * 100 / DARTBOARD_TOTALX * 3 / 2; + targetPos.y = pt.y * 100 / DARTBOARD_TOTALY * 3 / 2; + } + + // the higher the level, the more accurate the throw + int v = _vm->getRandomNumber(9); + v += _level * 2; + + if (v <= 2) { + targetPos.x += _vm->getRandomNumber(70) - 35; + targetPos.y += _vm->getRandomNumber(70) - 35; + } else if (v <= 4) { + targetPos.x += _vm->getRandomNumber(50) - 25; + targetPos.y += _vm->getRandomNumber(50) - 25; + } else if (v <= 6) { + targetPos.x += _vm->getRandomNumber(30) - 15; + targetPos.y += _vm->getRandomNumber(30) - 15; + } else if (v <= 8) { + targetPos.x += _vm->getRandomNumber(20) -10; + targetPos.y += _vm->getRandomNumber(20) -10; + } else if (v <= 10) { + targetPos.x += _vm->getRandomNumber(11) - 5; + targetPos.y += _vm->getRandomNumber(11) - 5; + } + + if (targetPos.x < 1) + targetPos.x = 1; + if (targetPos.y < 1) + targetPos.y = 1; +} + +int Darts::throwDart(int dartNum, int computer) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + int height; + int horiz; + Common::Point targetPos; + Common::String temp; + + /* clear keyboard buffer */ + events.clearEvents(); + + erasePowerBars(); + screen.print(Common::Point(_dartInfo.left, _dartInfo.top), 0, "%s # %d", FIXED(Dart), dartNum); + + drawDartsLeft(dartNum, computer); + + if (!computer) { + screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing), 0, "%s", FIXED(HitAKey)); + screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 2), 0, "%s", FIXED(ToStart)); + } + + if (!computer) { + // Wait for a hit + while (!dartHit() && !_vm->shouldQuit()) + ; + if (_escapePressed) + return 0; + } else { + events.wait(1); + } + + drawDartsLeft(dartNum + 1, computer); + screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_dartInfo.left, _dartInfo.top - 1), + Common::Rect(_dartInfo.left, _dartInfo.top - 1, _dartInfo.right, _dartInfo.bottom - 1)); + screen.blitFrom(screen._backBuffer1, Common::Point(_dartInfo.left, _dartInfo.top - 1), + Common::Rect(_dartInfo.left, _dartInfo.top - 1, _dartInfo.right, _dartInfo.bottom - 1)); + + if (computer) { + getComputerNumber(computer - 1, targetPos); + } else { + // Keyboard control + targetPos = Common::Point(0, 0); + } + + horiz = drawHand(targetPos.x, computer); + if (_escapePressed) + return 0; + + height = doPowerBar(Common::Point(DART_BAR_VX, DART_HEIGHT_Y), DART_COLOR_FORE, targetPos.y, 1); + if (_escapePressed) + return 0; + + // Invert height + height = 101 - height; + + // Copy power bars to the secondary back buffer + screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(DART_BAR_VX - 1, DART_HEIGHT_Y - 1), + Common::Rect(DART_BAR_VX - 1, DART_HEIGHT_Y - 1, DART_BAR_VX - 1 + 10, + DART_HEIGHT_Y - 1 + DART_BAR_SIZE + 2)); + + Common::Point dartPos(DARTBOARD_TOTALLEFT + horiz*DARTBOARD_TOTALX / 100, + DARTBOARD_TOTALTOP + height * DARTBOARD_TOTALY / 100); + + dartPos.x += 2 - _vm->getRandomNumber(4); + dartPos.y += 2 - _vm->getRandomNumber(4); + + drawDartThrow(dartPos, computer); + return dartScore(dartPos); +} + +void Darts::doCricketScoreHits(int player, int scoreIndex, int numHits) { + while (numHits--) { + if (_cricketScore[player][scoreIndex] < 3) + _cricketScore[player][scoreIndex]++; + else if (_cricketScore[player ^ 1][scoreIndex] < 3) { + if (player == 0) + _score1 += CRICKET_VALUE[scoreIndex]; + else + _score2 += CRICKET_VALUE[scoreIndex]; + } + } +} + +void Darts::updateCricketScore(int player, int dartVal, int multiplier) { + if (dartVal < 15) + return; + + if (dartVal <= 20) + doCricketScoreHits(player, 20 - dartVal, multiplier); + else if (dartVal == 25) + doCricketScoreHits(player, 6, multiplier); +} + +void Darts::drawDartsLeft(int dartNum, int computer) { + Screen &screen = *_vm->_screen; + const int DART_X1[3] = { 391, 451, 507 }; + const int DART_Y1[3] = { 373, 373, 373 }; + const int DART_X2[3] = { 393, 441, 502 }; + const int DART_Y2[3] = { 373, 373, 373 }; + + screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(DART_X1[0], DART_Y1[0]), + Common::Rect(DART_X1[0], DART_Y1[0], SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + + for (int idx = 2; idx >= dartNum - 1; --idx) { + if (computer) + screen._backBuffer1.transBlitFrom((*_dartsLeft)[idx + 3], Common::Point(DART_X2[idx], DART_Y2[idx])); + else + screen._backBuffer1.transBlitFrom((*_dartsLeft)[idx], Common::Point(DART_X1[idx], DART_Y1[idx])); + } + + screen.slamArea(DART_X1[0], DART_Y1[0], SHERLOCK_SCREEN_WIDTH - DART_X1[0], SHERLOCK_SCREEN_HEIGHT - DART_Y1[0]); +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/tattoo_darts.h b/engines/sherlock/tattoo/tattoo_darts.h new file mode 100644 index 0000000000..ab6b1c8204 --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_darts.h @@ -0,0 +1,173 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_TATTOO_DARTS_H +#define SHERLOCK_TATTOO_DARTS_H + +#include "common/scummsys.h" +#include "sherlock/image_file.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Tattoo { + +enum GameType { GAME_301, GAME_CRICKET, GAME_501 }; + +class Darts { +private: + SherlockEngine *_vm; + GameType _gameType; + ImageFile *_hand1, *_hand2; + ImageFile *_dartGraphics; + ImageFile *_dartsLeft; + ImageFile *_dartMap; + ImageFile *_dartBoard; + Common::Rect _dartInfo; + int _cricketScore[2][7]; + int _score1, _score2; + int _roundNum; + int _roundScore; + int _level; + int _compPlay; + Common::String _opponent; + int _spacing; + bool _oldDartButtons; + int _handX; + Common::Point _handSize; + bool _escapePressed; + + /** + * Initialize game variables + */ + void initDarts(); + + /** + * Load dartboard graphics + */ + void loadDarts(); + + /** + * Free loaded dart images + */ + void closeDarts(); + + /** + * Show the player names + */ + void showNames(int playerNum); + + /** + * Show the current scores + */ + void showStatus(int playerNum); + + /** + * Erases the power bars + */ + void erasePowerBars(); + + /** + * Returns true if a mouse button or key is pressed + */ + bool dartHit(); + + /** + * Shows a power bar and increments it until a key or mouse button is pressed. If the bar + * reaches the end, it will also end. The reached power bar number is returned. + * @param pt Bar position + * @param color draw color + * @param goToPower If provided, input is ignored, and the bar is increased up to the specified level + * @param orientation 0=Horizontal, 1=Vertical + */ + int doPowerBar(const Common::Point &pt, byte color, int goToPower, int orientation); + + /** + * This is similar to doPowerBar, except it draws the player's hand moving across the + * bottom of the screen to indicate the positioning of the darts + */ + int drawHand(int goToPower, int computer); + + /** + * Converts a passed co-ordinates from screen co-ordinates to an offset within the dartboard + */ + Common::Point convertFromScreenToScoreCoords(const Common::Point &pt) const; + + /** + * Return the score a dart at the given position will get + */ + int dartScore(const Common::Point &pt); + + /** + * Draw a dart travelling to the board + */ + void drawDartThrow(const Common::Point &dartPos, int computer); + + /** + * Looks for the passed number on the dartboard. If it finds it, it will return + * the co-ordinates of the center of the number + */ + int findNumberOnBoard(int aim, Common::Point &pt); + + /** + * Calculates a position for the comptuer wants to throw, and then calculates where they + * actually did throw. The computer will not always hit what it's aiming it. + */ + void getComputerNumber(int playerNum, Common::Point &targetPos); + + /** + * Throw one dart. If computer is 1 or 2, the computer will throw the dart, and user input + * will be ignored. + * @param computer 1=1st computer player, 2=2nd computer player + */ + int throwDart(int dartNum, int computer); + + /** + * This will update the number of hits for the target score, as well as updating the + * score if it's closed + */ + void doCricketScoreHits(int player, int scoreIndex, int numHits); + + /** + * Updates the score based upon what the dart hit + */ + void updateCricketScore(int player, int dartVal, int multiplier); + + /** + * Draw the darts left + */ + void drawDartsLeft(int dartNum, int computer); +public: + Darts(SherlockEngine *vm); + + /** + * Play the darts game + */ + void playDarts(GameType gameType); +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/zvision/detection.h b/engines/sherlock/tattoo/tattoo_debugger.cpp index f80cac79ec..8d59b4c592 100644 --- a/engines/zvision/detection.h +++ b/engines/sherlock/tattoo/tattoo_debugger.cpp @@ -20,24 +20,16 @@ * */ -#ifndef ZVISION_DETECTION_H -#define ZVISION_DETECTION_H +#include "sherlock/tattoo/tattoo_debugger.h" +#include "sherlock/sherlock.h" -#include "engines/advancedDetector.h" +namespace Sherlock { -namespace ZVision { - -enum ZVisionGameId { - GID_NONE = 0, - GID_NEMESIS = 1, - GID_GRANDINQUISITOR = 2 -}; - -struct ZVisionGameDescription { - ADGameDescription desc; - ZVisionGameId gameId; -}; +namespace Tattoo { +TattooDebugger::TattooDebugger(SherlockEngine *vm) : Debugger(vm) { } -#endif +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/tattoo_debugger.h b/engines/sherlock/tattoo/tattoo_debugger.h new file mode 100644 index 0000000000..e729262b11 --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_debugger.h @@ -0,0 +1,44 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_TATTOO_DEBUGGER_H +#define SHERLOCK_TATTOO_DEBUGGER_H + +#include "sherlock/debugger.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Tattoo { + +class TattooDebugger : public Debugger { +public: + TattooDebugger(SherlockEngine *vm); + virtual ~TattooDebugger() {} +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif /* SHERLOCK_TATTOO_DEBUGGER_H */ diff --git a/engines/sherlock/tattoo/tattoo_fixed_text.cpp b/engines/sherlock/tattoo/tattoo_fixed_text.cpp new file mode 100644 index 0000000000..c9345e44d1 --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_fixed_text.cpp @@ -0,0 +1,211 @@ +/* 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 "sherlock/tattoo/tattoo_fixed_text.h" +#include "sherlock/sherlock.h" + +namespace Sherlock { + +namespace Tattoo { + +static const char *const FIXED_TEXT_ENGLISH[] = { + "Money", + "Card", + "Tobacco", + "Timetable", + "Summons", + "Foolscap", + "Damp Paper", + "Bull's Eye", + + "Money", + "Card", + "Tobacco", + "Timetable", + "Summons", + "Foolscap", + "Foolscap", + "Bull's Eye Lantern", + + "Open", + "Look", + "Talk", + "Use", + "Journal", + "Inventory", + "Options", + "Solve", + "with", + "No effect...", + "This person has nothing to say at the moment", + "Picked up", + + "Page %d", + "Close Journal", + "Search Journal", + "Save Journal", + "Abort Search", + "Search Backwards", + "Search Forwards", + "Text Not Found !", + + "Holmes", + "Jock", + "Bull", + "Round", + "Turn Total", + "Dart", + "to start", + "Hit a key", + "Press a key", + "bullseye", + "GAME OVER", + "BUSTED", + "Wins", + "Scored", + "points", + "Hit", + "Double", + "Triple", + + "Apply", + "Water", + "Heat", + "Load Game", + "Save Game", + "Music", + "Sound Effects", + "Voices", + "Text Windows", + "Transparent Menus", + "Change Font Style", + "Off", + "On", + "Quit", + "Are you sure you", + "wish to Quit ?", + "Yes", + "No", + "Enter Password", + "Going East" +}; + +static const char *const FIXED_TEXT_GERMAN[] = { + "Geld", + "S. Holmes", + "Tabak", + "Plan", + "Aufforderg.", + "Blatt pap.", + "Dunstig pap", + "Handlampe", + + "Geld", + "S. Holmes", + "Tabak", + "Plan", + "Aufforderg.", + "Pergament", + "Dunstig pap", + "Handlampe", + + "ffne", + "Schau", + "Rede", + "Benutze", + "Journal", + "Inventory", + "Options", + "Losen", + "mit", + "Keine Wirkung...", + "Diese Person weic im Augenblick nichts zu berichten.", + + "Seite %d", + "Schliecen", + "Lessen", + "In Datei sichern", + "Suche abbrechen", + "Rbckwarts suchen ", + "Vorwarts suchen ", + "Text nicht gefunden", + + "Holmes", + "Jock", + "Bull", + "Runde", + "Gesamt", + "Pfeil", + "zum Starten", + "Taste dracken", + "Taste dracken", + "Bullseye", + "SPIEL BEENDET", + "VERLOREN", + "Gewinnt", + "Erzielte", + "Punkte", + "Treffer", + "Doppel", + "Dreifach", + + "Benutze", + "Wasser", + "Erhitze", + "Spiel laden", + "Spiel sichern", + "Musik", + "Soundeffekte", + "Voices", + "Textfenster", + "Transparente Menbs", + "Schriftart andern", + "Aus", + "An", + "Ende", + "Spiel beenden? ", + "Sind Sie sicher ?", + "Ja", + "Nein", + "Pacwort eingeben", + "Going East" +}; + +TattooFixedText::TattooFixedText(SherlockEngine *vm) : FixedText(vm) { + if (vm->getLanguage() == Common::DE_DEU) + _fixedText = FIXED_TEXT_GERMAN; + else + _fixedText = FIXED_TEXT_ENGLISH; +} + +const char *TattooFixedText::getText(int fixedTextId) { + return _fixedText[fixedTextId]; +} + +const Common::String TattooFixedText::getActionMessage(FixedTextActionId actionId, int messageIndex) { + return Common::String(); +} + + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/tattoo_fixed_text.h b/engines/sherlock/tattoo/tattoo_fixed_text.h new file mode 100644 index 0000000000..3f43678ca2 --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_fixed_text.h @@ -0,0 +1,134 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_TATTOO_FIXED_TEXT_H +#define SHERLOCK_TATTOO_FIXED_TEXT_H + +#include "sherlock/fixed_text.h" + +namespace Sherlock { + +namespace Tattoo { + +enum FixedTextId { + kFixedText_Inv1, + kFixedText_Inv2, + kFixedText_Inv3, + kFixedText_Inv4, + kFixedText_Inv5, + kFixedText_Inv6, + kFixedText_Inv7, + kFixedText_Inv8, + kFixedText_InvDesc1, + kFixedText_InvDesc2, + kFixedText_InvDesc3, + kFixedText_InvDesc4, + kFixedText_InvDesc5, + kFixedText_InvDesc6, + kFixedText_InvDesc7, + kFixedText_InvDesc8, + kFixedText_Open, + kFixedText_Look, + kFixedText_Talk, + kFixedText_Use, + kFixedText_Journal, + kFixedText_Inventory, + kFixedText_Options, + kFixedText_Solve, + kFixedText_With, + kFixedText_NoEffect, + kFixedText_NothingToSay, + kFixedText_PickedUp, + + kFixedText_Page, + kFixedText_CloseJournal, + kFixedText_SearchJournal, + kFixedText_SaveJournal, + kFixedText_AbortSearch, + kFixedText_SearchBackwards, + kFixedText_SearchForwards, + kFixedText_TextNotFound, + + kFixedText_Holmes, + kFixedText_Jock, + kFixedText_Bull, + kFixedText_Round, + kFixedText_TurnTotal, + kFixedText_Dart, + kFixedText_ToStart, + kFixedText_HitAKey, + kFixedText_PressAKey, + kFixedText_Bullseye, + kFixedText_GameOver, + kFixedText_Busted, + kFixedText_Wins, + kFixedText_Scored, + kFixedText_Points, + kFixedText_Hit, + kFixedText_Double, + kFixedText_Triple, + + kFixedText_Apply, + kFixedText_Water, + kFixedText_Heat, + kFixedText_LoadGame, + kFixedText_SaveGame, + kFixedText_Music, + kFixedText_SoundEffects, + kFixedText_Voices, + kFixedText_TextWindows, + kFixedText_TransparentMenus, + kFixedText_ChangeFont, + kFixedText_Off, + kFixedText_On, + kFixedText_Quit, + kFixedText_AreYouSureYou, + kFixedText_WishToQuit, + kFixedText_Yes, + kFixedText_No, + kFixedText_EnterPassword, + kFixedText_CorrectPassword +}; + +class TattooFixedText: public FixedText { +private: + const char *const *_fixedText; +public: + TattooFixedText(SherlockEngine *vm); + virtual ~TattooFixedText() {} + + /** + * Gets text + */ + virtual const char *getText(int fixedTextId); + + /** + * Get action message + */ + virtual const Common::String getActionMessage(FixedTextActionId actionId, int messageIndex); +}; + +} // End of namespace Scalpel + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/tattoo_inventory.cpp b/engines/sherlock/tattoo/tattoo_inventory.cpp new file mode 100644 index 0000000000..6bd1822c10 --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_inventory.cpp @@ -0,0 +1,63 @@ +/* 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 "sherlock/tattoo/tattoo_inventory.h" +#include "sherlock/tattoo/tattoo.h" + +namespace Sherlock { + +namespace Tattoo { + +TattooInventory::TattooInventory(SherlockEngine *vm) : Inventory(vm) { + _invShapes.resize(8); +} + +TattooInventory::~TattooInventory() { +} + +void TattooInventory::loadInv() { + // Exit if the inventory names are already loaded + if (_names.size() > 0) + return; + + // Load the inventory names + Common::SeekableReadStream *stream = _vm->_res->load("invent.txt"); + + int count = stream->readByte(); + char c; + + for (int idx = 0; idx < count; ++idx) { + Common::String name; + while ((c = stream->readByte()) != 0) + name += c; + + _names.push_back(name); + } + + delete stream; + + loadGraphics(); +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/tattoo_inventory.h b/engines/sherlock/tattoo/tattoo_inventory.h new file mode 100644 index 0000000000..a18324b785 --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_inventory.h @@ -0,0 +1,48 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_TATTOO_INVENTORY_H +#define SHERLOCK_TATTOO_INVENTORY_H + +#include "sherlock/inventory.h" + +namespace Sherlock { + +namespace Tattoo { + +class TattooInventory : public Inventory { +public: + TattooInventory(SherlockEngine *vm); + ~TattooInventory(); + + /** + * Load the list of names the inventory items correspond to, if not already loaded, + * and then calls loadGraphics to load the associated graphics + */ + virtual void loadInv(); +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/tattoo_journal.cpp b/engines/sherlock/tattoo/tattoo_journal.cpp new file mode 100644 index 0000000000..29b40096cb --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_journal.cpp @@ -0,0 +1,939 @@ +/* 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 "sherlock/tattoo/tattoo_journal.h" +#include "sherlock/tattoo/tattoo_fixed_text.h" +#include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo_user_interface.h" +#include "sherlock/tattoo/tattoo.h" + +namespace Sherlock { + +namespace Tattoo { + +#define JOURNAL_BAR_WIDTH 450 + +TattooJournal::TattooJournal(SherlockEngine *vm) : Journal(vm) { + _journalImages = nullptr; + _selector = _oldSelector = JH_NONE; + _wait = false; + _exitJournal = false; + _scrollingTimer = 0; + _savedIndex = _savedSub = _savedPage = 0; + + loadLocations(); +} + +void TattooJournal::show() { + Events &events = *_vm->_events; + Resources &res = *_vm->_res; + Screen &screen = *_vm->_screen; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + byte palette[PALETTE_SIZE]; + + Common::Point oldScroll = screen._currentScroll; + screen._currentScroll = Common::Point(0, 0); + + // Load journal images + _journalImages = new ImageFile("journal.vgs"); + + // Load palette + Common::SeekableReadStream *stream = res.load("journal.pal"); + stream->read(palette, PALETTE_SIZE); + ui.setupBGArea(palette); + screen.translatePalette(palette); + delete stream; + + // Set screen to black, and set background + screen._backBuffer1.blitFrom((*_journalImages)[0], Common::Point(0, 0)); + screen.empty(); + screen.setPalette(palette); + + if (_journal.empty()) { + _up = _down = false; + } else { + drawJournal(0, 0); + } + drawControls(0); + screen.slamRect(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + + _exitJournal = false; + _scrollingTimer = 0; + + do { + events.pollEventsAndWait(); + events.setButtonState(); + _wait = true; + + handleKeyboardEvents(); + highlightJournalControls(true); + + handleButtons(); + + if (_wait) + events.wait(2); + + } while (!_vm->shouldQuit() && !_exitJournal); + + // Clear events + events.clearEvents(); + + // Free the images + delete _journalImages; + _journalImages = nullptr; + + // Reset back to whatever scroll was active for the screen + screen._currentScroll = oldScroll; +} + +void TattooJournal::handleKeyboardEvents() { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + Common::Point mousePos = events.mousePos(); + + if (!events.kbHit()) + return; + + Common::KeyState keyState = events.getKey(); + + if (keyState.keycode == Common::KEYCODE_TAB && (keyState.flags & Common::KBD_SHIFT)) { + // Shift tab + Common::Rect r(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13); + r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, SHERLOCK_SCREEN_HEIGHT - r.height()); + + // See if mouse is over any of the journal controls + _selector = JH_NONE; + if (Common::Rect(r.left + 3, r.top + 3, r.right - 3, r.top + screen.fontHeight() + 4).contains(mousePos)) + _selector = (mousePos.x - r.left) / (r.width() / 3); + + // If the mouse is not over an option, move the mouse to that it points to the first option + if (_selector == JH_NONE) { + events.warpMouse(Common::Point(r.left + r.width() / 3 - 10, r.top + screen.fontHeight() + 2)); + } else { + if (_selector == JH_CLOSE) + _selector = JH_PRINT; + else + --_selector; + + events.warpMouse(Common::Point(r.left + (r.width() / 3) * (_selector + 1) - 10, mousePos.y)); + } + + } else if (keyState.keycode == Common::KEYCODE_PAGEUP) { + // See if they have Shift held down to go forward 10 pages + if (keyState.flags & Common::KBD_SHIFT) { + if (_page > 1) { + // Scroll Up 10 pages if possible + if (_page < 11) + drawJournal(1, (_page - 1) * LINES_PER_PAGE); + else + drawJournal(1, 10 * LINES_PER_PAGE); + + drawScrollBar(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + _wait = false; + } + } else { + if (_page > 1) { + // Scroll Up 1 page + drawJournal(1, LINES_PER_PAGE); + drawScrollBar(); + drawJournal(0, 0); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + _wait = false; + } + } + + } else if (keyState.keycode == Common::KEYCODE_PAGEDOWN) { + if (keyState.flags & Common::KBD_SHIFT) { + if (_down) { + // Scroll down 10 Pages + if (_page + 10 > _maxPage) + drawJournal(2, (_maxPage - _page) * LINES_PER_PAGE); + else + drawJournal(2, 10 * LINES_PER_PAGE); + drawScrollBar(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + + _wait = false; + } + } else { + if (_down) { + // Scroll down 1 page + drawJournal(2, LINES_PER_PAGE); + drawScrollBar(); + drawJournal(0, 0); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + + _wait = false; + } + } + + } else if (keyState.keycode == Common::KEYCODE_HOME) { + // Scroll to start of journal + if (_page > 1) { + // Go to the beginning of the journal + _index = _sub = _up = _down = 0; + _page = 1; + + drawFrame(); + drawJournal(0, 0); + + drawScrollBar(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + + _wait = false; + } + + } else if (keyState.keycode == Common::KEYCODE_END) { + // Scroll to end of journal + if (_down) { + // Go to the end of the journal + drawJournal(2, 100000); + drawScrollBar(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + + _wait = false; + } + } else if (keyState.keycode == Common::KEYCODE_RETURN) { + events._pressed = false; + events._released = true; + events._oldButtons = 0; + } else if (keyState.keycode == Common::KEYCODE_ESCAPE) { + _exitJournal = true; + } else if (keyState.keycode == Common::KEYCODE_TAB) { + Common::Rect r(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13); + r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, SHERLOCK_SCENE_HEIGHT - r.height()); + + // See if the mouse is over any of the journal controls + _selector = JH_NONE; + if (Common::Rect(r.left + 3, r.top + 3, r.right - 3, r.top + screen.fontHeight() + 4).contains(mousePos)) + _selector = (mousePos.x - r.left) / (r.width() / 3); + + // If the mouse is not over any of the options, move the mouse so that it points to the first option + if (_selector == JH_NONE) { + events.warpMouse(Common::Point(r.left + r.width() / 3 - 10, r.top + screen.fontHeight() + 2)); + } else { + if (_selector == JH_PRINT) + _selector = JH_NONE; + else + ++_selector; + + events.warpMouse(Common::Point(r.left + (r.width() / 3) * (_selector + 1) - 10, mousePos.y)); + } + } +} + +void TattooJournal::handleButtons() { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + uint32 frameCounter = events.getFrameCounter(); + Common::Point mousePos = events.mousePos(); + + // If they're dragging the scrollbar thumb, keep it selected whilst the button is being held + if ((events._pressed || events._released) && _selector == JH_THUMBNAIL) { + // FIgure out the left of the scrollbar scroll area and paging data + const int scrollingWidth = JOURNAL_BAR_WIDTH - BUTTON_SIZE * 2 - 6; + const int scrollingLeft = (SHERLOCK_SCREEN_WIDTH - JOURNAL_BAR_WIDTH) / 2 + BUTTON_SIZE + 3; + const int numPages = (_maxPage + LINES_PER_PAGE - 1) / LINES_PER_PAGE - 1; + const int barWidth = CLIP(scrollingWidth / numPages, BUTTON_SIZE, JOURNAL_BAR_WIDTH - BUTTON_SIZE * 2 - 6); + + const int scrollOffset = mousePos.x - scrollingLeft; + const int page = scrollOffset * FIXED_INT_MULTIPLIER / ((scrollingWidth - barWidth) * (FIXED_INT_MULTIPLIER / (numPages - 1))) + 1; + + if (page != _page) { + if (page < _page) + drawJournal(1, (_page - page) * LINES_PER_PAGE); + else + drawJournal(2, (page - _page) * LINES_PER_PAGE); + drawScrollBar(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + _wait = false; + } + } else if (_selector != JH_NONE && events._pressed) { + if (frameCounter >= _scrollingTimer) { + // Set next scrolling time + _scrollingTimer = frameCounter + 6; + + // Handle different scrolling actions + switch (_selector) { + case JH_SCROLL_LEFT: + // Scroll left (1 page back) + if (_page > 1) { + // Scroll Up + drawJournal(1, LINES_PER_PAGE); + drawScrollBar(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + _wait = false; + } + break; + + case JH_PAGE_LEFT: + // Page left (10 pages back) + if (_page > 1) { + // Scroll Up 10 Pages if possible + if (_page < 11) + drawJournal(1, (_page - 1) * LINES_PER_PAGE); + else + drawJournal(1, 10 * LINES_PER_PAGE); + drawScrollBar(); + drawJournal(0, 0); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + _wait = false; + } + break; + + case JH_PAGE_RIGHT: + // Page right (10 pages ahead) + if (_down) { + // Scroll Down 10 Pages + if (_page + 10 > _maxPage) + drawJournal(2, (_maxPage - _page) * LINES_PER_PAGE); + else + drawJournal(2, 10 * LINES_PER_PAGE); + drawScrollBar(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + _wait = false; + } + break; + + case JH_SCROLL_RIGHT: + // Scroll right (1 Page Ahead) + if (_down) { + // Scroll Down + drawJournal(2, LINES_PER_PAGE); + drawScrollBar(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + _wait = false; + } + break; + + default: + break; + } + } + } + + if (events._released || events._rightReleased) { + _scrollingTimer = 0; + + switch (_selector) { + case JH_CLOSE: + _exitJournal = true; + break; + + case JH_SEARCH: { + // Search Journal + disableControls(); + + bool notFound = false; + int dir; + + do { + if ((dir = getFindName(notFound)) != 0) { + _savedIndex = _index; + _savedSub = _sub; + _savedPage = _page; + + if (drawJournal(dir + 2, 1000 * LINES_PER_PAGE) == 0) + { + _index = _savedIndex; + _sub = _savedSub; + _page = _savedPage; + + drawFrame(); + drawJournal(0, 0); + notFound = true; + } else { + break; + } + + highlightJournalControls(false); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + } else { + break; + } + } while (!_vm->shouldQuit()); + break; + } + + case JH_PRINT: + // Print Journal - not implemented in ScummVM + break; + + default: + break; + } + } +} + +void TattooJournal::loadLocations() { + Resources &res = *_vm->_res; + + _directory.clear(); + _locations.clear(); + + Common::SeekableReadStream *dir = res.load("talk.lib"); + dir->skip(4); // Skip header + + // Get the numer of entries + _directory.resize(dir->readUint16LE()); + dir->seek((_directory.size() + 1) * 8, SEEK_CUR); + + // Read in each entry + char buffer[17]; + for (uint idx = 0; idx < _directory.size(); ++idx) { + dir->read(buffer, 17); + buffer[16] = '\0'; + + _directory[idx] = Common::String(buffer); + } + + delete dir; + + // Load in the locations stored in journal.txt + Common::SeekableReadStream *loc = res.load("journal.txt"); + + // Initialize locations + _locations.resize(100); + for (int idx = 0; idx < 100; ++idx) + _locations[idx] = "No Description"; + + while (loc->pos() < loc->size()) { + // In Rose Tattoo, each location line starts with the location + // number, followed by a dot, some spaces and its description + // in quotes + Common::String line = loc->readLine(); + Common::String locNumStr; + int locNum = 0; + int i = 0; + Common::String locDesc; + + // Get the location + while (Common::isDigit(line[i])) { + locNumStr += line[i]; + i++; + } + locNum = atoi(locNumStr.c_str()) - 1; + + // Skip the dot, spaces and initial quotation mark + while (line[i] == ' ' || line[i] == '.' || line[i] == '\"') + i++; + + do { + locDesc += line[i]; + i++; + } while (line[i] != '\"'); + + _locations[locNum] = locDesc; + } + + delete loc; +} + +void TattooJournal::drawFrame() { + Screen &screen = *_vm->_screen; + + screen._backBuffer1.blitFrom((*_journalImages)[0], Common::Point(0, 0)); + drawControls(0); + +} + +void TattooJournal::drawControls(int mode) { + TattooEngine &vm = *(TattooEngine *)_vm; + Screen &screen = *_vm->_screen; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + ImageFile &images = *ui._interfaceImages; + + Common::Rect r(JOURNAL_BAR_WIDTH, !mode ? (BUTTON_SIZE + screen.fontHeight() + 13) : + (screen.fontHeight() + 4) * 2 + 9); + r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, !mode ? (SHERLOCK_SCREEN_HEIGHT - r.height()) : + (SHERLOCK_SCREEN_HEIGHT - r.height()) / 2); + + Common::Rect inner = r; + inner.grow(-3); + + if (vm._transparentMenus) + ui.makeBGArea(inner); + else + screen._backBuffer1.fillRect(inner, MENU_BACKGROUND); + + // Draw the four corners of the info box + screen._backBuffer1.transBlitFrom(images[0], Common::Point(r.left, r.top)); + screen._backBuffer1.transBlitFrom(images[1], Common::Point(r.right - images[1]._width, r.top)); + screen._backBuffer1.transBlitFrom(images[1], Common::Point(r.left, r.bottom - images[1]._height)); + screen._backBuffer1.transBlitFrom(images[1], Common::Point(r.right - images[1]._width, r.bottom - images[1]._height)); + + // Draw the top of the info box + screen._backBuffer1.hLine(r.left + images[0]._width, r.top, r.right - images[0]._height, INFO_TOP); + screen._backBuffer1.hLine(r.left + images[0]._width, r.top + 1, r.right - images[0]._height, INFO_MIDDLE); + screen._backBuffer1.hLine(r.left + images[0]._width, r.top + 2, r.right - images[0]._height, INFO_BOTTOM); + + // Draw the bottom of the info box + screen._backBuffer1.hLine(r.left + images[0]._width, r.bottom - 3, r.right - images[0]._height, INFO_TOP); + screen._backBuffer1.hLine(r.left + images[0]._width, r.bottom - 2, r.right - images[0]._height, INFO_MIDDLE); + screen._backBuffer1.hLine(r.left + images[0]._width, r.bottom - 1, r.right - images[0]._height, INFO_BOTTOM); + + // Draw the left side of the info box + screen._backBuffer1.vLine(r.left, r.top + images[0]._height, r.bottom - images[2]._height, INFO_TOP); + screen._backBuffer1.vLine(r.left + 1, r.top + images[0]._height, r.bottom - images[2]._height, INFO_MIDDLE); + screen._backBuffer1.vLine(r.left + 2, r.top + images[0]._height, r.bottom - images[2]._height, INFO_BOTTOM); + + // Draw the right side of the info box + screen._backBuffer1.vLine(r.right - 3, r.top + images[0]._height, r.bottom - images[2]._height, INFO_TOP); + screen._backBuffer1.vLine(r.right - 2, r.top + images[0]._height, r.bottom - images[2]._height, INFO_MIDDLE); + screen._backBuffer1.vLine(r.right - 1, r.top + images[0]._height, r.bottom - images[2]._height, INFO_BOTTOM); + + // Draw the sides of the separator bar above the scroll bar + int yp = r.top + screen.fontHeight() + 7; + screen._backBuffer1.transBlitFrom(images[4], Common::Point(r.left, yp - 1)); + screen._backBuffer1.transBlitFrom(images[5], Common::Point(r.right - images[5]._width, yp - 1)); + + // Draw the bar above the scroll bar + screen._backBuffer1.hLine(r.left + images[4]._width, yp, r.right - images[5]._width, INFO_TOP); + screen._backBuffer1.hLine(r.left + images[4]._width, yp + 1, r.right - images[5]._width, INFO_MIDDLE); + screen._backBuffer1.hLine(r.left + images[4]._width, yp + 2, r.right - images[5]._width, INFO_BOTTOM); + + if (mode != 2) { + // Draw the Bars separating the Journal Commands + int xp = r.right / 3; + for (int idx = 0; idx < 2; ++idx) { + screen._backBuffer1.transBlitFrom(images[6], Common::Point(xp - 2, r.top + 1)); + screen._backBuffer1.transBlitFrom(images[7], Common::Point(xp - 2, yp - 1)); + + screen._backBuffer1.hLine(xp - 1, r.top + 4, yp - 2, INFO_TOP); + screen._backBuffer1.hLine(xp, r.top + 4, yp - 2, INFO_MIDDLE); + screen._backBuffer1.hLine(xp + 1, r.top + 4, yp - 2, INFO_BOTTOM); + xp = r.right / 3 * 2; + } + } + + int savedSelector = _oldSelector; + _oldSelector = 100; + + switch (mode) { + case 0: + highlightJournalControls(false); + break; + case 1: + highlightSearchControls(false); + break; + default: + break; + } + + _oldSelector = savedSelector; +} + +void TattooJournal::highlightJournalControls(bool slamIt) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + Common::Point mousePos = events.mousePos(); + Common::Rect r(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13); + r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, SHERLOCK_SCREEN_HEIGHT - r.height()); + + if ((events._pressed || events._released) && _selector == JH_THUMBNAIL) { + if (events._released) + _selector = JH_NONE; + } else { + // Calculate the Scroll Position Bar + int numPages = (_maxPage + LINES_PER_PAGE - 1) / LINES_PER_PAGE - 1; + int barWidth = (r.width() - BUTTON_SIZE * 2 - 6) / numPages; + barWidth = CLIP(barWidth, BUTTON_SIZE, r.width() - BUTTON_SIZE * 2 - 6); + + int barX = (numPages <= 1) ? r.left + 3 + BUTTON_SIZE : (r.width() - BUTTON_SIZE * 2 - 6 - barWidth) + * FIXED_INT_MULTIPLIER / (numPages - 1) * (_page - 1) / FIXED_INT_MULTIPLIER + r.left + 3 + BUTTON_SIZE; + + // See if the mouse is over any of the Journal Controls + Common::Rect bounds(r.left, r.top, r.right - 3, r.top + screen.fontHeight() + 7); + _selector = JH_NONE; + if (bounds.contains(mousePos)) + _selector = (mousePos.x - r.left) / (r.width() / 3); + + else if (events._pressed && mousePos.y >= (r.top + screen.fontHeight() + 10) + && mousePos.y < (r.top + screen.fontHeight() + 10 + BUTTON_SIZE)) { + if (mousePos.x >= r.left && mousePos.x < (r.left + BUTTON_SIZE)) + // Press on the Scroll Left button + _selector = JH_SCROLL_LEFT; + else if (mousePos.x >= (r.left + BUTTON_SIZE + 3) && mousePos.x < barX) + // Press on area to the left of the thumb, for scrolling back 10 pages + _selector = JH_PAGE_LEFT; + else if (mousePos.x >= (barX + barWidth) && mousePos.x < (r.right - BUTTON_SIZE - 3)) + // Press on area to the right of the thumb, for scrolling forward 10 pages + _selector = JH_PAGE_RIGHT; + else if (mousePos.x >= (r.right - BUTTON_SIZE) && mousePos.x < r.right) + // Press of the Scroll Right button + _selector = JH_SCROLL_RIGHT; + else if (mousePos.x >= barX && mousePos.x < (barX + barWidth)) + // Mouse on thumbnail + _selector = JH_THUMBNAIL; + } + } + + // See if the Search was selected, but is not available + if (_journal.empty() && (_selector == JH_SEARCH || _selector == JH_PRINT)) + _selector = JH_NONE; + + if (_selector == JH_PAGE_LEFT && _oldSelector == JH_PAGE_RIGHT) + _selector = JH_PAGE_RIGHT; + else if (_selector == JH_PAGE_RIGHT && _oldSelector == JH_PAGE_LEFT) + _selector = JH_PAGE_LEFT; + + // See if they're pointing at a different control + if (_selector != _oldSelector) { + // Print the Journal commands + int xp = r.left + r.width() / 6; + byte color = (_selector == JH_CLOSE) ? COMMAND_HIGHLIGHTED : INFO_TOP; + + screen.gPrint(Common::Point(xp - screen.stringWidth(FIXED(CloseJournal)) / 2, r.top + 5), + color, "%s", FIXED(CloseJournal)); + xp += r.width() / 3; + + if (!_journal.empty()) + color = (_selector == JH_SEARCH) ? COMMAND_HIGHLIGHTED : INFO_TOP; + else + color = INFO_BOTTOM; + screen.gPrint(Common::Point(xp - screen.stringWidth(FIXED(SearchJournal)) / 2, r.top + 5), + color, "%s", FIXED(SearchJournal)); + xp += r.width() / 3; + + color = INFO_BOTTOM; + screen.gPrint(Common::Point(xp - screen.stringWidth(FIXED(SaveJournal)) / 2, r.top + 5), + color, "%s", FIXED(SaveJournal)); + + // Draw the horizontal scrollbar + drawScrollBar(); + + if (slamIt) + screen.slamRect(r); + + _oldSelector = _selector; + } +} + +void TattooJournal::highlightSearchControls(bool slamIt) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + Common::Point mousePos = events.mousePos(); + Common::Rect r(JOURNAL_BAR_WIDTH, (screen.fontHeight() + 4) * 2 + 9); + r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, (SHERLOCK_SCREEN_HEIGHT - r.height()) / 2); + const char *SEARCH_COMMANDS[3] = { FIXED(AbortSearch), FIXED(SearchBackwards), FIXED(SearchForwards) }; + + // See if the mouse is over any of the Journal Controls + _selector = JH_NONE; + if (Common::Rect(r.left + 3, r.top + 3, r.right - 3, r.top + 7 + screen.fontHeight()).contains(mousePos)) + _selector = (mousePos.x - r.left) / (r.width() / 3); + + // See if they're pointing at a different control + if (_selector != _oldSelector) { + // Print the search commands + int xp = r.left + r.width() / 6; + + for (int idx = 0; idx < 3; ++idx) { + byte color = (_selector == idx) ? COMMAND_HIGHLIGHTED : INFO_TOP; + screen.gPrint(Common::Point(xp - screen.stringWidth(SEARCH_COMMANDS[idx]) / 2, + r.top + 5), color, "%s", SEARCH_COMMANDS[idx]); + xp += r.width() / 3; + } + + if (slamIt) + screen.slamRect(r); + + _oldSelector = _selector; + } +} + +void TattooJournal::drawScrollBar() { + Screen &screen = *_vm->_screen; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + bool raised; + byte color; + + Common::Rect r(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13); + r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, SHERLOCK_SCREEN_HEIGHT - r.height()); + + // Calculate the Scroll Position Bar + int numPages = (_maxPage + LINES_PER_PAGE - 1) / LINES_PER_PAGE - 1; + int barWidth = (r.width() - BUTTON_SIZE * 2 - 6) / numPages; + barWidth = CLIP(barWidth, BUTTON_SIZE, r.width() - BUTTON_SIZE * 2 - 6); + int barX; + if (numPages <= 1) { + barX = r.left + 3 + BUTTON_SIZE; + } else { + barX = (r.width() - BUTTON_SIZE * 2 - 6 - barWidth) * FIXED_INT_MULTIPLIER / (numPages - 1) * + (_page - 1) / FIXED_INT_MULTIPLIER + r.left + 3 + BUTTON_SIZE; + if (barX + BUTTON_SIZE > r.left + r.width() - BUTTON_SIZE - 3) + barX = r.right - BUTTON_SIZE * 2 - 3; + } + + // Draw the scroll bar here + // Draw the Scroll Left button + raised = _selector != JH_SCROLL_LEFT; + screen._backBuffer1.fillRect(Common::Rect(r.left, r.top + screen.fontHeight() + 12, r.left + BUTTON_SIZE, + r.top + screen.fontHeight() + BUTTON_SIZE + 9), INFO_MIDDLE); + ui.drawDialogRect(screen._backBuffer1, Common::Rect(r.left + 3, r.top + screen.fontHeight() + 10, r.left + 3 + BUTTON_SIZE, + r.top + screen.fontHeight() + 10 + BUTTON_SIZE), raised); + + color = (_page > 1) ? INFO_BOTTOM + 2 : INFO_BOTTOM; + screen._backBuffer1.vLine(r.left + 1 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 10 + BUTTON_SIZE / 2, + r.top + screen.fontHeight() + 10 + BUTTON_SIZE / 2, color); + screen._backBuffer1.vLine(r.left + 2 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 9 + BUTTON_SIZE / 2, + r.top + screen.fontHeight() + 11 + BUTTON_SIZE / 2, color); + screen._backBuffer1.vLine(r.left + 3 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 8 + BUTTON_SIZE / 2, + r.top + screen.fontHeight() + 12 + BUTTON_SIZE / 2, color); + screen._backBuffer1.vLine(r.left + 4 + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 7 + BUTTON_SIZE / 2, + r.top + screen.fontHeight() + 13 + BUTTON_SIZE / 2, color); + + // Draw the Scroll Right button + raised = _selector != JH_SCROLL_RIGHT; + screen._backBuffer1.fillRect(Common::Rect(r.right - BUTTON_SIZE - 1, r.top + screen.fontHeight() + 12, + r.right - 5, r.top + screen.fontHeight() + BUTTON_SIZE + 9), INFO_MIDDLE); + ui.drawDialogRect(screen._backBuffer1, Common::Rect(r.right - BUTTON_SIZE - 3, r.top + screen.fontHeight() + 10, r.right - 3, + r.top + screen.fontHeight() + BUTTON_SIZE + 9), raised); + + color = _down ? INFO_BOTTOM + 2 : INFO_BOTTOM; + screen._backBuffer1.vLine(r.right - 1 - BUTTON_SIZE + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 10 + BUTTON_SIZE / 2, + r.top + screen.fontHeight() + 10 + BUTTON_SIZE / 2, color); + screen._backBuffer1.vLine(r.right - 2 - BUTTON_SIZE + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 9 + BUTTON_SIZE / 2, + r.top + screen.fontHeight() + 11 + BUTTON_SIZE / 2, color); + screen._backBuffer1.vLine(r.right - 3 - BUTTON_SIZE + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 8 + BUTTON_SIZE / 2, + r.top + screen.fontHeight() + 12 + BUTTON_SIZE / 2, color); + screen._backBuffer1.vLine(r.right - 4 - BUTTON_SIZE + BUTTON_SIZE / 2, r.top + screen.fontHeight() + 7 + BUTTON_SIZE / 2, + r.top + screen.fontHeight() + 13 + BUTTON_SIZE / 2, color); + + // Draw the scroll bar + screen._backBuffer1.fillRect(Common::Rect(barX + 2, r.top + screen.fontHeight() + 12, barX + barWidth - 3, + r.top + screen.fontHeight() + BUTTON_SIZE + 9), INFO_MIDDLE); + ui.drawDialogRect(screen._backBuffer1, Common::Rect(barX, r.top + screen.fontHeight() + 10, barX + barWidth, + r.top + screen.fontHeight() + 10 + BUTTON_SIZE), true); +} + +void TattooJournal::disableControls() { + Screen &screen = *_vm->_screen; + Common::Rect r(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13); + r.moveTo((SHERLOCK_SCREEN_HEIGHT - r.width()) / 2, SHERLOCK_SCREEN_HEIGHT - r.height()); + const char *JOURNAL_COMMANDS[3] = { FIXED(CloseJournal), FIXED(SearchJournal), FIXED(SaveJournal) }; + + // Print the Journal commands + int xp = r.left + r.width() / 6; + for (int idx = 0; idx < 2; ++idx) { + screen.gPrint(Common::Point(xp - screen.stringWidth(JOURNAL_COMMANDS[idx]) / 2, r.top + 5), + INFO_BOTTOM, "%s", JOURNAL_COMMANDS[idx]); + + xp += r.width() / 3; + } + + screen.slamRect(r); +} + +int TattooJournal::getFindName(bool printError) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + int result = 0; + int done = 0; + Common::String name; + int cursorX, cursorY; + bool flag = false; + + Common::Rect r(JOURNAL_BAR_WIDTH, (screen.fontHeight() + 4) * 2 + 9); + r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, (SHERLOCK_SCREEN_HEIGHT - r.height()) / 2); + + // Set the cursors Y position + cursorY = r.top + screen.fontHeight() + 12; + + drawControls(1); + + // Backup the area under the text entry + Surface bgSurface(r.width() - 6, screen.fontHeight()); + bgSurface.blitFrom(screen._backBuffer1, Common::Point(0, 0), Common::Rect(r.left + 3, cursorY, + r.right - 3, cursorY + screen.fontHeight())); + + if (printError) { + screen.gPrint(Common::Point(0, cursorY), INFO_TOP, "%s", FIXED(TextNotFound)); + } else { + // If there was a name already entered, copy it to name and display it + if (!_find.empty()) { + screen.gPrint(Common::Point(r.left + screen.widestChar() + 3, cursorY), COMMAND_HIGHLIGHTED, "%s", _find.c_str()); + name = _find; + } + } + + screen.slamRect(r); + + if (printError) { + // Pause to allow error to be shown + int timer = 0; + + do { + events.pollEvents(); + events.setButtonState(); + + ++timer; + events.wait(2); + } while (!_vm->shouldQuit() && !events.kbHit() && !events._released && !events._rightReleased && timer < 40); + + events.clearEvents(); + + // Restore the text background + screen._backBuffer1.blitFrom(bgSurface, Common::Point(r.left, cursorY)); + + // If there was a name already entered, copy it to name and display it + if (!_find.empty()) { + screen.gPrint(Common::Point(r.left + screen.widestChar() + 3, cursorY), COMMAND_HIGHLIGHTED, "%s", _find.c_str()); + name = _find; + } + + screen.slamArea(r.left + 3, cursorY, r.width() - 6, screen.fontHeight()); + } + + // Set the cursors X position + cursorX = r.left + screen.widestChar() + 3 + screen.stringWidth(name); + + do { + events._released = events._rightReleased = false; + + while (!events.kbHit() && !events._released && !events._rightReleased) { + if (talk._talkToAbort) + return 0; + + // See if a key or a mouse button is pressed + events.pollEventsAndWait(); + events.setButtonState(); + + // Handle blinking cursor + flag = !flag; + if (flag) { + // Draw cursor + screen._backBuffer1.fillRect(Common::Rect(cursorX, cursorY, cursorX + 7, cursorY + 8), COMMAND_HIGHLIGHTED); + screen.slamArea(cursorX, cursorY, 8, 9); + } else { + // Erase cursor by restoring background and writing current text + screen._backBuffer1.blitFrom(bgSurface, Common::Point(r.left + 3, cursorY)); + screen.gPrint(Common::Point(r.left + screen.widestChar() + 3, cursorY), COMMAND_HIGHLIGHTED, "%s", name.c_str()); + screen.slamArea(r.left + 3, r.top, r.width() - 3, screen.fontHeight()); + } + + highlightSearchControls(true); + + events.wait(2); + } + + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + Common::Point mousePos = events.mousePos(); + + if (keyState.keycode == Common::KEYCODE_BACKSPACE && !name.empty()) { + cursorX -= screen.charWidth(name.lastChar()); + name.deleteLastChar(); + } + + if (keyState.keycode == Common::KEYCODE_RETURN) + done = 1; + + else if (keyState.keycode == Common::KEYCODE_ESCAPE) + done = -1; + + if (keyState.keycode == Common::KEYCODE_TAB) { + r = Common::Rect(JOURNAL_BAR_WIDTH, BUTTON_SIZE + screen.fontHeight() + 13); + r.moveTo((SHERLOCK_SCREEN_WIDTH - r.width()) / 2, (SHERLOCK_SCREEN_HEIGHT - r.height()) / 2); + + // See if the mouse is over any of the journal controls + _selector = JH_NONE; + if (Common::Rect(r.left + 3, r.top + 3, r.right - 3, r.top + screen.fontHeight() + 4).contains(mousePos)) + _selector = (mousePos.x - r.left) / (r.width() / 3); + + // If the mouse is not over any of the options, move the mouse so that it points to the first option + if (_selector == JH_NONE) { + events.warpMouse(Common::Point(r.left + r.width() / 3, r.top + screen.fontHeight() + 2)); + } else { + if (keyState.keycode & Common::KBD_SHIFT) { + if (_selector == JH_CLOSE) + _selector = JH_PRINT; + else + --_selector; + } else { + if (_selector == JH_PRINT) + _selector = JH_CLOSE; + else + ++_selector; + } + + events.warpMouse(Common::Point(r.left + (r.width() / 3) * (_selector + 1) - 10, mousePos.y)); + } + } + + if (keyState.ascii && keyState.ascii != '@' && name.size() < 50) { + if ((cursorX + screen.charWidth(keyState.ascii)) < (r.right - screen.widestChar() * 3)) { + cursorX += screen.charWidth(keyState.ascii); + name += toupper(keyState.ascii); + } + } + + // Redraw the text + screen._backBuffer1.blitFrom(bgSurface, Common::Point(r.left + 3, cursorY)); + screen.gPrint(Common::Point(r.left + screen.widestChar() + 3, cursorY), COMMAND_HIGHLIGHTED, + "%s", name.c_str()); + screen.slamArea(r.left + 3, cursorY, r.right - 3, screen.fontHeight()); + } + + if (events._released || events._rightReleased) { + switch (_selector) { + case JH_CLOSE: + done = -1; + break; + case JH_SEARCH: + done = 2; + break; + case JH_PRINT: + done = 1; + break; + default: + break; + } + } + } while (!done); + + if (done != -1) { + _find = name; + result = done; + } else { + result = 0; + } + + drawFrame(); + drawJournal(0, 0); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + + return result; +} + +void TattooJournal::record(int converseNum, int statementNum, bool replyOnly) { + TattooEngine &vm = *(TattooEngine *)_vm; + + // Only record activity in the Journal if the player is Holmes (i.e. we're paast the prologoue) + if (_vm->readFlags(FLAG_PLAYER_IS_HOLMES) && !vm._runningProlog) + Journal::record(converseNum, statementNum, replyOnly); +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/tattoo_journal.h b/engines/sherlock/tattoo/tattoo_journal.h new file mode 100644 index 0000000000..96c1c6cab4 --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_journal.h @@ -0,0 +1,114 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +*/ + +#ifndef SHERLOCK_TATTOO_JOURNAL_H +#define SHERLOCK_TATTOO_JOURNAL_H + +#include "sherlock/journal.h" +#include "sherlock/image_file.h" + +namespace Sherlock { + +namespace Tattoo { + +enum JournalHighlight { + JH_NONE = -1, JH_CLOSE = 0, JH_SEARCH = 1, JH_PRINT = 2, + JH_SCROLL_LEFT = 3, JH_PAGE_LEFT = 4, JH_PAGE_RIGHT = 5, JH_SCROLL_RIGHT = 6, JH_THUMBNAIL = 7 +}; + +class TattooJournal : public Journal { +private: + ImageFile *_journalImages; + int _selector, _oldSelector; + bool _wait; + bool _exitJournal; + uint32 _scrollingTimer; + int _savedIndex, _savedSub, _savedPage; + + /** + * Load the list of journal locations + */ + void loadLocations(); + + /** + * Displays the controls used by the journal + * @param mode 0: Normal journal buttons, 1: Search interface + */ + void drawControls(int mode); + + /** + * Draw the journal controls used by the journal + */ + void highlightJournalControls(bool slamIt); + + /** + * Draw the journal controls used in search mode + */ + void highlightSearchControls(bool slamIt); + + void drawScrollBar(); + + /** + * Check for and handle any pending keyboard events + */ + void handleKeyboardEvents(); + + /** + * Handle mouse presses on interface buttons + */ + void handleButtons(); + + /** + * Disable the journal controls + */ + void disableControls(); + + /** + * Get in a name to search through the journal for + */ + int getFindName(bool printError); +public: + TattooJournal(SherlockEngine *vm); + virtual ~TattooJournal() {} + + /** + * Show the journal + */ + void show(); +public: + /** + * Draw the journal background, frame, and interface buttons + */ + virtual void drawFrame(); + + /** + * Records statements that are said, in the order which they are said. The player + * can then read the journal to review them + */ + virtual void record(int converseNum, int statementNum, bool replyOnly = false); +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/tattoo_map.cpp b/engines/sherlock/tattoo/tattoo_map.cpp new file mode 100644 index 0000000000..4bd85bd5c0 --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_map.cpp @@ -0,0 +1,434 @@ +/* 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 "sherlock/tattoo/tattoo_map.h" +#include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo.h" + +namespace Sherlock { + +namespace Tattoo { + +#define MAP_NAME_COLOR 131 +#define CLOSEUP_STEPS 30 +#define SCROLL_SPEED 16 + +/*-------------------------------------------------------------------------*/ + +void MapEntry::clear() { + _iconNum = -1; + _description = ""; +} + +/*-------------------------------------------------------------------------*/ + +TattooMap::TattooMap(SherlockEngine *vm) : Map(vm), _mapTooltip(vm) { + _iconImages = nullptr; + _bgFound = _oldBgFound = 0; + + loadData(); +} + +int TattooMap::show() { + Debugger &debugger = *_vm->_debugger; + Events &events = *_vm->_events; + Music &music = *_vm->_music; + Resources &res = *_vm->_res; + TattooScene &scene = *(TattooScene *)_vm->_scene; + Screen &screen = *_vm->_screen; + int result = 0; + + // Check if we need to keep track of how many times player has been to the map + for (uint idx = 0; idx < scene._sceneTripCounters.size(); ++idx) { + SceneTripEntry &entry = scene._sceneTripCounters[idx]; + + if (entry._sceneNumber == OVERHEAD_MAP || entry._sceneNumber == OVERHEAD_MAP2) { + if (--entry._numTimes == 0) { + _vm->setFlagsDirect(entry._flag); + scene._sceneTripCounters.remove_at(idx); + } + } + } + + if (music._musicOn) { + // See if Holmes or Watson is the active character + Common::String song; + if (_vm->readFlags(FLAG_PLAYER_IS_HOLMES)) + // Player is Holmes + song = "Cue9"; + else if (_vm->readFlags(FLAG_ALT_MAP_MUSIC)) + song = "Cue8"; + else + song = "Cue7"; + + if (music.loadSong(song)) { + music.startSong(); + } + } + + screen.initPaletteFade(1364485); + + // Load the custom mouse cursors for the map + ImageFile cursors("omouse.vgs"); + events.setCursor(cursors[0]._frame); + events.warpMouse(); + + // Load the data for the map + _iconImages = new ImageFile("mapicons.vgs"); + loadData(); + + // Load the palette + Common::SeekableReadStream *stream = res.load("map.pal"); + stream->read(screen._cMap, PALETTE_SIZE); + screen.translatePalette(screen._cMap); + delete stream; + + // Load the map image and draw it to the back buffer + ImageFile *map = new ImageFile("map.vgs"); + screen._backBuffer1.create(SHERLOCK_SCREEN_WIDTH * 2, SHERLOCK_SCREEN_HEIGHT * 2); + screen._backBuffer1.blitFrom((*map)[0], Common::Point(0, 0)); + delete map; + + screen.clear(); + screen.setPalette(screen._cMap); + drawMapIcons(); + + // Copy the map drawn in the back buffer to the secondary back buffer + screen._backBuffer2.create(SHERLOCK_SCREEN_WIDTH * 2, SHERLOCK_SCREEN_HEIGHT * 2); + screen._backBuffer2.blitFrom(screen._backBuffer1); + + // Display the built map to the screen + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + + // Set initial scroll position + _targetScroll = _bigPos; + screen._currentScroll = Common::Point(-1, -1); + + do { + // Allow for event processing and get the current mouse position + events.pollEventsAndWait(); + events.setButtonState(); + Common::Point mousePos = events.screenMousePos(); + + if (debugger._showAllLocations == LOC_REFRESH) { + drawMapIcons(); + screen.slamArea(screen._currentScroll.x, screen._currentScroll.y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_WIDTH); + } + + checkMapNames(true); + + if (mousePos.x < (SHERLOCK_SCREEN_WIDTH / 6)) + _targetScroll.x -= 2 * SCROLL_SPEED * (SHERLOCK_SCREEN_WIDTH / 6 - mousePos.x) / (SHERLOCK_SCREEN_WIDTH / 6); + if (mousePos.x > (SHERLOCK_SCREEN_WIDTH * 5 / 6)) + _targetScroll.x += 2 * SCROLL_SPEED * (mousePos.x - (SHERLOCK_SCREEN_WIDTH * 5 / 6)) / (SHERLOCK_SCREEN_WIDTH / 6); + if (mousePos.y < (SHERLOCK_SCREEN_HEIGHT / 6)) + _targetScroll.y -= 2 * SCROLL_SPEED * (SHERLOCK_SCREEN_HEIGHT / 6 - mousePos.y) / (SHERLOCK_SCREEN_HEIGHT / 6); + if (mousePos.y > (SHERLOCK_SCREEN_HEIGHT * 5 / 6)) + _targetScroll.y += 2 * SCROLL_SPEED * (mousePos.y - SHERLOCK_SCREEN_HEIGHT * 5 / 6) / (SHERLOCK_SCREEN_HEIGHT / 6); + + if (_targetScroll.x < 0) + _targetScroll.x = 0; + if ((_targetScroll.x + SHERLOCK_SCREEN_WIDTH) > screen._backBuffer1.w()) + _targetScroll.x = screen._backBuffer1.w() - SHERLOCK_SCREEN_WIDTH; + if (_targetScroll.y < 0) + _targetScroll.y = 0; + if ((_targetScroll.y + SHERLOCK_SCREEN_HEIGHT) > screen._backBuffer1.h()) + _targetScroll.y = screen._backBuffer1.h() - SHERLOCK_SCREEN_HEIGHT; + + // Check the keyboard + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + + switch (keyState.keycode) { + case Common::KEYCODE_HOME: + _targetScroll.x = 0; + _targetScroll.y = 0; + break; + + case Common::KEYCODE_END: + _targetScroll.x = screen._backBuffer1.w() - SHERLOCK_SCREEN_WIDTH; + _targetScroll.y = screen._backBuffer1.h() - SHERLOCK_SCREEN_HEIGHT; + break; + + case Common::KEYCODE_PAGEUP: + _targetScroll.y -= SHERLOCK_SCREEN_HEIGHT; + if (_targetScroll.y < 0) + _targetScroll.y = 0; + break; + + case Common::KEYCODE_PAGEDOWN: + _targetScroll.y += SHERLOCK_SCREEN_HEIGHT; + if (_targetScroll.y > (screen._backBuffer1.h() - SHERLOCK_SCREEN_HEIGHT)) + _targetScroll.y = screen._backBuffer1.h() - SHERLOCK_SCREEN_HEIGHT; + break; + + case Common::KEYCODE_SPACE: + events._pressed = false; + events._oldButtons = 0; + events._released = true; + break; + + default: + break; + } + } + + // Handle any scrolling of the map + if (screen._currentScroll != _targetScroll) { + // If there is a Text description being displayed, restore the area under it + _mapTooltip.erase(); + + screen._currentScroll = _targetScroll; + + checkMapNames(false); + screen.slamArea(_targetScroll.x, _targetScroll.y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + } + + // Handling if a location has been clicked on + if (events._released && _bgFound != -1) { + // If there is a Text description being displayed, restore the area under it + _mapTooltip.erase(); + + // Save the current scroll position on the map + _bigPos = screen._currentScroll; + + showCloseUp(_bgFound); + result = _bgFound + 1; + } + } while (!result && !_vm->shouldQuit()); + + music.stopMusic(); + events.clearEvents(); + _mapTooltip.banishWindow(); + + // Reset the back buffers back to standard size + screen._backBuffer1.create(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + screen._backBuffer2.create(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + + return result; +} + +void TattooMap::loadData() { + Resources &res = *_vm->_res; + char c; + + Common::SeekableReadStream *stream = res.load("map.txt"); + + _data.resize(100); + for (uint idx = 0; idx < _data.size(); ++idx) + _data[idx].clear(); + + do + { + // Find the start of the number + do { + c = stream->readByte(); + if (stream->pos() >= stream->size()) + break; + } while (c < '0' || c > '9'); + if (stream->pos() >= stream->size()) + break; + + // Get the scene number + Common::String locStr; + locStr += c; + while ((c = stream->readByte()) != '.') + locStr += c; + MapEntry &mapEntry = _data[atoi(locStr.c_str()) - 1]; + + // Get the location name + while (stream->readByte() != '"') + ; + + while ((c = stream->readByte()) != '"') + mapEntry._description += c; + + // Find the ( specifying the (X,Y) position of the Icon + while (stream->readByte() != '(') + ; + + // Get the X Position of the icon + Common::String numStr; + while ((c = stream->readByte()) != ',') + numStr += c; + mapEntry.x = atoi(numStr.c_str()); + + // Get the Y position of the icon + numStr = ""; + while ((c = stream->readByte()) != ')') + numStr += c; + mapEntry.y = atoi(numStr.c_str()); + + // Find and get the location's icon number + while (stream->readByte() != '#') + ; + + Common::String iconStr; + while (stream->pos() < stream->size() && (c = stream->readByte()) != '\r') + iconStr += c; + + mapEntry._iconNum = atoi(iconStr.c_str()) - 1; + } while (stream->pos() < stream->size()); + + delete stream; +} + +void TattooMap::drawMapIcons() { + Debugger &debugger = *_vm->_debugger; + Screen &screen = *_vm->_screen; + + for (uint idx = 0; idx < _data.size(); ++idx) { + if (debugger._showAllLocations != LOC_DISABLED) + _vm->setFlagsDirect(idx + 1); + + if (_data[idx]._iconNum != -1 && _vm->readFlags(idx + 1)) { + MapEntry &mapEntry = _data[idx]; + ImageFrame &img = (*_iconImages)[mapEntry._iconNum]; + screen._backBuffer1.transBlitFrom(img._frame, Common::Point(mapEntry.x - img._width / 2, + mapEntry.y - img._height / 2)); + } + } + + if (debugger._showAllLocations == LOC_REFRESH) + debugger._showAllLocations = LOC_ALL; +} + +void TattooMap::checkMapNames(bool slamIt) { + Events &events = *_vm->_events; + Common::Point mapPos = events.mousePos(); + + // See if the mouse is pointing at any of the map locations + _bgFound = -1; + + for (uint idx = 0; idx < _data.size(); ++idx) { + if (_data[idx]._iconNum != -1 && _vm->readFlags(idx + 1)) { + MapEntry &mapEntry = _data[idx]; + ImageFrame &img = (*_iconImages)[mapEntry._iconNum]; + Common::Rect r(mapEntry.x - img._width / 2, mapEntry.y - img._height / 2, + mapEntry.x + img._width / 2, mapEntry.y + img._height / 2); + + if (r.contains(mapPos)) { + _bgFound = idx; + break; + } + } + } + + // Handle updating the tooltip + if (_bgFound != _oldBgFound) { + if (_bgFound == -1) { + _mapTooltip.setText(""); + } else { + const Common::String &desc = _data[_bgFound]._description; + _mapTooltip.setText(desc); + } + + _oldBgFound = _bgFound; + } + + _mapTooltip.handleEvents(); + if (slamIt) + _mapTooltip.draw(); +} + +void TattooMap::restoreArea(const Common::Rect &bounds) { + Screen &screen = *_vm->_screen; + + Common::Rect r = bounds; + r.clip(Common::Rect(0, 0, screen._backBuffer1.w(), screen._backBuffer1.h())); + + if (!r.isEmpty()) + screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(r.left, r.top), r); +} + +void TattooMap::showCloseUp(int closeUpNum) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + + // Reset scroll position + screen._currentScroll = Common::Point(0, 0); + + // Get the closeup images + Common::String fname = Common::String::format("res%02d.vgs", closeUpNum + 1); + ImageFile pic(fname); + + Point32 closeUp(_data[closeUpNum].x * 100, _data[closeUpNum].y * 100); + Point32 delta((SHERLOCK_SCREEN_WIDTH / 2 - closeUp.x / 100) * 100 / CLOSEUP_STEPS, + (SHERLOCK_SCREEN_HEIGHT / 2 - closeUp.y / 100) * 100 / CLOSEUP_STEPS); + Common::Rect oldBounds(closeUp.x / 100, closeUp.y / 100, closeUp.x / 100 + 1, closeUp.y / 100 + 1); + int size = 64; + int n = 256; + int deltaVal = 512; + bool minimize = false; + int scaleVal, newSize; + + do { + scaleVal = n; + newSize = pic[0].sDrawXSize(n); + + if (newSize > size) { + if (minimize) + deltaVal /= 2; + n += deltaVal; + } else { + minimize = true; + deltaVal /= 2; + n -= deltaVal; + if (n < 1) + n = 1; + } + } while (deltaVal && size != newSize); + + int deltaScale = (SCALE_THRESHOLD - scaleVal) / CLOSEUP_STEPS; + + for (int step = 0; step < CLOSEUP_STEPS; ++step) { + Common::Point picSize(pic[0].sDrawXSize(scaleVal), pic[0].sDrawYSize(scaleVal)); + Common::Point pt(closeUp.x / 100 - picSize.x / 2, closeUp.y / 100 - picSize.y / 2); + + restoreArea(oldBounds); + screen._backBuffer1.transBlitFrom(pic[0], pt, false, 0, scaleVal); + + screen.slamRect(oldBounds); + screen.slamArea(pt.x, pt.y, picSize.x, picSize.y); + + oldBounds = Common::Rect(pt.x, pt.y, pt.x + picSize.x + 1, pt.y + picSize.y + 1); + closeUp += delta; + scaleVal += deltaScale; + + events.wait(1); + } + + // Handle final drawing of closeup + Common::Rect r(SHERLOCK_SCREEN_WIDTH / 2 - pic[0]._width / 2, SHERLOCK_SCREEN_HEIGHT / 2 - pic[0]._height / 2, + SHERLOCK_SCREEN_WIDTH / 2 - pic[0]._width / 2 + pic[0]._width, + SHERLOCK_SCREEN_HEIGHT / 2 - pic[0]._height / 2 + pic[0]._height); + + restoreArea(oldBounds); + screen._backBuffer1.transBlitFrom(pic[0], Common::Point(r.left, r.top)); + screen.slamRect(oldBounds); + screen.slamRect(r); + events.wait(60); +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/tattoo_map.h b/engines/sherlock/tattoo/tattoo_map.h new file mode 100644 index 0000000000..1c7173bdb0 --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_map.h @@ -0,0 +1,93 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_TATTOO_MAP_H +#define SHERLOCK_TATTOO_MAP_H + +#include "common/scummsys.h" +#include "sherlock/map.h" +#include "sherlock/resources.h" +#include "sherlock/surface.h" +#include "sherlock/tattoo/widget_tooltip.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Tattoo { + +struct MapEntry : Common::Point { + int _iconNum; + Common::String _description; + + MapEntry() : Common::Point(), _iconNum(-1) {} + MapEntry(int posX, int posY, int iconNum) : Common::Point(posX, posY), _iconNum(iconNum) {} + void clear(); +}; + +class TattooMap : public Map { +private: + Common::Array<MapEntry> _data; + ImageFile *_iconImages; + int _bgFound, _oldBgFound; + WidgetMapTooltip _mapTooltip; + Common::Point _targetScroll; + + /** + * Load data needed for the map + */ + void loadData(); + + /** + * Draws all available location icons onto the back buffer + */ + void drawMapIcons(); + + /** + * Draws the location names of whatever the mouse moves over on the map + */ + void checkMapNames(bool slamIt); + + /** + * Restores an area of the map background + */ + void restoreArea(const Common::Rect &bounds); + + /** + * This will load a specified close up and zoom it up to the middle of the screen + */ + void showCloseUp(int closeUpNum); +public: + TattooMap(SherlockEngine *vm); + virtual ~TattooMap() {} + + /** + * Show the map + */ + virtual int show(); +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/tattoo_people.cpp b/engines/sherlock/tattoo/tattoo_people.cpp new file mode 100644 index 0000000000..e3e957e35c --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_people.cpp @@ -0,0 +1,1503 @@ +/* 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 "sherlock/tattoo/tattoo_people.h" +#include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo_talk.h" +#include "sherlock/tattoo/tattoo_user_interface.h" +#include "sherlock/tattoo/tattoo.h" + +namespace Sherlock { + +namespace Tattoo { + +#define FACING_PLAYER 16 +#define NUM_ADJUSTED_WALKS 21 +#define CHARACTERS_INDEX 256 + +struct AdjustWalk { + char _vgsName[9]; + int _xAdjust; + int _flipXAdjust; + int _yAdjust; +} ; + +static const AdjustWalk ADJUST_WALKS[NUM_ADJUSTED_WALKS] = { + { "TUPRIGHT", -7, -19, 6 }, + { "TRIGHT", 8, -14, 0 }, + { "TDOWNRG", 14, -12, 0 }, + { "TWUPRIGH", 12, 4, 2 }, + { "TWRIGHT", 31, -14, 0 }, + { "TWDOWNRG", 6, -24, 0 }, + { "HTUPRIGH", 2, -20, 0 }, + { "HTRIGHT", 28, -20, 0 }, + { "HTDOWNRG", 8, -2, 0 }, + { "GTUPRIGH", 4, -12, 0 }, + { "GTRIGHT", 12, -16, 0 }, + { "GTDOWNRG", 10, -18, 0 }, + { "JTUPRIGH", 8, -10, 0 }, + { "JTRIGHT", 22, -6, 0 }, + { "JTDOWNRG", 4, -20, 0 }, + { "CTUPRIGH", 10, 0, 0 }, + { "CTRIGHT", 26, -22, 0 }, + { "CTDOWNRI", 16, 4, 0 }, + { "ITUPRIGH", 0, 0, 0 }, + { "ITRIGHT", 20, 0, 0 }, + { "ITDOWNRG", 8, 0, 0 } +}; + +static const int WALK_SPEED_X[99] = { + 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 98, 90, 90, 90, 90, 90, 91, 90, 90, + 90, 90,100, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90,100, 90, + 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90,103, 90, 90, 90, 90, 90, 90, 90, + 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, + 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90, 90 +}; + +static const int WALK_SPEED_Y[99] = { + 28, 28, 28, 28, 28, 28, 28, 28, 28, 32, 32, 32, 28, 28, 28, 28, 28, 26, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 32, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 31, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28 +}; + +static const int WALK_SPEED_DIAG_X[99] = { + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 90, 50, 50, 50, 50, 50, 50, 50, 50, 50, + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50 +}; + +/*----------------------------------------------------------------*/ + +SavedNPCPath::SavedNPCPath() { + Common::fill(&_path[0], &_path[MAX_NPC_PATH], 0); + _npcIndex = 0; + _npcPause = 0; + _npcFacing = 0; + _lookHolmes = false; +} + +SavedNPCPath::SavedNPCPath(byte path[MAX_NPC_PATH], int npcIndex, int npcPause, const Point32 &position, + int npcFacing, bool lookHolmes) : _npcIndex(npcIndex), _npcPause(npcPause), _position(position), + _npcFacing(npcFacing), _lookHolmes(lookHolmes) { + Common::copy(&path[0], &path[MAX_NPC_PATH], &_path[0]); +} + +/*----------------------------------------------------------------*/ + +TattooPerson::TattooPerson() : Person() { + Common::fill(&_npcPath[0], &_npcPath[MAX_NPC_PATH], 0); + _tempX = _tempScaleVal = 0; + _npcIndex = 0; + _npcMoved = false; + _npcFacing = -1; + _resetNPCPath = true; + _savedNpcSequence = 0; + _savedNpcFrame = 0; + _updateNPCPath = true; + _npcPause = 0; + _lookHolmes = false; +} + +void TattooPerson::freeAltGraphics() { + if (_altImages != nullptr) { + delete _altImages; + _altImages = nullptr; + } + + _altSeq = 0; +} + +void TattooPerson::adjustSprite() { + People &people = *_vm->_people; + TattooScene &scene = *(TattooScene *)_vm->_scene; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + + if (_type == INVALID) + return; + + if (_type == CHARACTER && _status) { + // Sprite waiting to move, so restart walk + _walkCount = _status; + _status = 0; + + _walkDest = _walkTo.front(); + setWalking(); + } else if (_type == CHARACTER && _walkCount) { + if (_walkCount > 10) { + _walkDest = _nextDest; + setWalking(); + } + + _position += _delta; + if (_walkCount) + --_walkCount; + + if (!_walkCount) { + // If there are remaining points to walk, move to the next one + if (!_walkTo.empty()) { + _walkDest = _walkTo.pop(); + setWalking(); + } else { + gotoStand(); + } + } + } + + if (_type != CHARACTER) { + if (_position.y > SHERLOCK_SCREEN_HEIGHT) + _position.y = SHERLOCK_SCREEN_HEIGHT; + + if (_position.y < UPPER_LIMIT) + _position.y = UPPER_LIMIT; + + if (_position.x < LEFT_LIMIT) + _position.x = LEFT_LIMIT; + + if (_position.x > RIGHT_LIMIT) + _position.x = RIGHT_LIMIT; + } + + int frameNum = _frameNumber; + if (frameNum == -1) + frameNum = 0; + int idx = _walkSequences[_sequenceNumber][frameNum]; + if (idx > _maxFrames) + idx = 1; + + // Set the image frame + if (_altSeq) + _imageFrame = &(*_altImages)[idx - 1]; + else + _imageFrame = &(*_images)[idx - 1]; + + // See if the player has come to a stop after clicking on an Arrow zone to leave the scene. + // If so, this will set up the exit information for the scene transition + if (!_walkCount && ui._exitZone != -1 && scene._walkedInScene && scene._goToScene == -1 && + !_description.compareToIgnoreCase(people[HOLMES]._description)) { + Exit &exit = scene._exits[ui._exitZone]; + scene._goToScene = exit._scene; + + if (exit._newPosition.x != 0) { + people._savedPos = exit._newPosition; + + if (people._savedPos._facing > 100 && people._savedPos.x < 1) + people._savedPos.x = 100; + } + } +} + +void TattooPerson::gotoStand() { + TattooPeople &people = *(TattooPeople *)_vm->_people; + + // If the misc field is set, then we're running a special talk sequence, so don't interrupt it. + if (_misc) + return; + + _walkTo.clear(); + _walkCount = 0; + int oldFacing = _sequenceNumber; + + // If the person was talking or listening, just return it to the standing sequence + // in the direction they were pointing + if (_sequenceNumber >= TALK_UPRIGHT && _sequenceNumber <= LISTEN_UPLEFT) { + switch (_sequenceNumber) { + case TALK_UPRIGHT: + case LISTEN_UPRIGHT: + _sequenceNumber = STOP_UPRIGHT; + break; + case TALK_RIGHT: + case LISTEN_RIGHT: + _sequenceNumber = STOP_RIGHT; + break; + case TALK_DOWNRIGHT: + case LISTEN_DOWNRIGHT: + _sequenceNumber = STOP_DOWNRIGHT; + break; + case TALK_DOWNLEFT: + case LISTEN_DOWNLEFT: + _sequenceNumber = STOP_DOWNLEFT; + break; + case TALK_LEFT: + case LISTEN_LEFT: + _sequenceNumber = STOP_LEFT; + break; + case TALK_UPLEFT: + case LISTEN_UPLEFT: + _sequenceNumber = STOP_UPLEFT; + break; + default: + break; + } + + if (_seqTo) { + // Reset to previous value + _walkSequences[oldFacing]._sequences[_frameNumber] = _seqTo; + _seqTo = 0; + } + + // Set the Frame number to the last frame so we don't move + _frameNumber = 0; + + checkWalkGraphics(); + + _oldWalkSequence = -1; + people._allowWalkAbort = true; + return; + } + + // If the sprite that is stopping is an NPC and he is supposed to face a certain direction + // when he stops, set that direction here + int npc = -1; + for (int idx = 1; idx < MAX_CHARACTERS; ++idx) { + if (_imageFrame == people[idx]._imageFrame) + npc = idx; + } + + if (npc != -1 && people[npc]._npcFacing != -1) { + if (people[npc]._npcFacing == FACING_PLAYER) { + // See where Holmes is with respect to the NPC (x coords) + if (people[HOLMES]._position.x < people[npc]._position.x) + people[npc]._npcFacing = STOP_LEFT; + else + people[npc]._npcFacing = STOP_RIGHT; + + // See where Holmes is with respect to the NPC (y coords) + if (people[HOLMES]._position.y < people[npc]._position.y - (10 * FIXED_INT_MULTIPLIER)) { + // Holmes is above the NPC so reset the facing to the diagonal ups + if (people[npc]._npcFacing == STOP_RIGHT) + people[npc]._npcFacing = STOP_UPRIGHT; + else + people[npc]._npcFacing = STOP_UPLEFT; + } else { + if (people[HOLMES]._position.y > people[npc]._position.y + (10 * FIXED_INT_MULTIPLIER)) { + // Holmes is below the NPC so reset the facing to the diagonal downs + if (people[npc]._npcFacing == STOP_RIGHT) + people[npc]._npcFacing = STOP_DOWNRIGHT; + else + people[npc]._npcFacing = STOP_DOWNLEFT; + } + } + } + + _sequenceNumber = people[npc]._npcFacing; + } else { + switch (_sequenceNumber) { + case WALK_UP: _sequenceNumber = STOP_UP; break; + case WALK_UPRIGHT: _sequenceNumber = STOP_UPRIGHT; break; + case WALK_RIGHT: _sequenceNumber = STOP_RIGHT; break; + case WALK_DOWNRIGHT: _sequenceNumber = STOP_DOWNRIGHT; break; + case WALK_DOWN: _sequenceNumber = STOP_DOWN; break; + case WALK_DOWNLEFT: _sequenceNumber = STOP_DOWNLEFT;break; + case WALK_LEFT: _sequenceNumber = STOP_LEFT; break; + case WALK_UPLEFT: _sequenceNumber = STOP_UPLEFT; break; + } + } + + // Only restart the frame number at 0 if the new sequence is different from the last sequence + // so we don't let Holmes repeat standing. + if (_oldWalkSequence != -1) { + if (_seqTo) { + // Reset to previous value + _walkSequences[oldFacing]._sequences[_frameNumber] = _seqTo; + _seqTo = 0; + } + + _frameNumber = 0; + } + + checkWalkGraphics(); + + _oldWalkSequence = -1; + people._allowWalkAbort = true; +} + +void TattooPerson::setWalking() { + TattooScene &scene = *(TattooScene *)_vm->_scene; + int oldDirection, oldFrame; + Common::Point delta; + _nextDest = _walkDest; + + // Flag that player has now walked in the scene + scene._walkedInScene = true; + + // Stop any previous walking, since a new dest is being set + _walkCount = 0; + oldDirection = _sequenceNumber; + oldFrame = _frameNumber; + + // Set speed to use horizontal and vertical movement + int scaleVal = scene.getScaleVal(_position); + Common::Point speed(MAX(WALK_SPEED_X[scene._currentScene - 1] * SCALE_THRESHOLD / scaleVal, 2), + MAX(WALK_SPEED_Y[scene._currentScene - 1] * SCALE_THRESHOLD / scaleVal, 2)); + Common::Point diagSpeed(MAX(WALK_SPEED_DIAG_X[scene._currentScene - 1] * SCALE_THRESHOLD / scaleVal, 2), + MAX((WALK_SPEED_Y[scene._currentScene - 1] - 2) * SCALE_THRESHOLD / scaleVal, 2)); + + // If the player is already close to the given destination that no walking is needed, + // move to the next straight line segment in the overall walking route, if there is one + for (;;) { + if (_centerWalk || !_walkTo.empty()) { + // Since we want the player to be centered on the ultimate destination, and the player + // is drawn from the left side, move the cursor half the width of the player to center it + delta = Common::Point(_position.x / FIXED_INT_MULTIPLIER - _walkDest.x, + _position.y / FIXED_INT_MULTIPLIER - _walkDest.y); + + int dir; + if (ABS(delta.x) > ABS(delta.y)) + dir = (delta.x < 0) ? WALK_LEFT : WALK_RIGHT; + else + dir = (delta.y < 0) ? WALK_UP : WALK_DOWN; + + scaleVal = scene.getScaleVal(Point32(_walkDest.x * FIXED_INT_MULTIPLIER, + _walkDest.y * FIXED_INT_MULTIPLIER)); + _walkDest.x -= _stopFrames[dir]->sDrawXSize(scaleVal) / 2; + } + + delta = Common::Point( + ABS(_position.x / FIXED_INT_MULTIPLIER - _walkDest.x), + ABS(_position.y / FIXED_INT_MULTIPLIER - _walkDest.y) + ); + + // If we're ready to move a sufficient distance, that's it. Otherwise, + // move onto the next portion of the walk path, if there is one + if ((delta.x > 3 || delta.y > 0) || _walkTo.empty()) + break; + + // Pop next walk segment off the walk route stack + _walkDest = _walkTo.pop(); + } + + // If a sufficient move is being done, then start the move + if (delta.x > 3 || delta.y) { + // See whether the major movement is horizontal or vertical + if (delta.x >= delta.y) { + // Set the initial frame sequence for the left and right, as well + // as setting the delta x depending on direction + if (_walkDest.x < (_position.x / FIXED_INT_MULTIPLIER)) { + _sequenceNumber = WALK_LEFT; + _delta.x = speed.x * -(FIXED_INT_MULTIPLIER / 10); + } else { + _sequenceNumber = WALK_RIGHT; + _delta.x = speed.x * (FIXED_INT_MULTIPLIER / 10); + } + + // See if the x delta is too small to be divided by the speed, since + // this would cause a divide by zero error + if ((delta.x * 10) >= speed.x) { + // Det the delta y + _delta.y = (delta.y * FIXED_INT_MULTIPLIER) / ((delta.x * 10) / speed.x); + if (_walkDest.y < (_position.y / FIXED_INT_MULTIPLIER)) + _delta.y = -_delta.y; + + // Set how many times we should add the delta to the player's position + _walkCount = (delta.x * 10) / speed.x; + } else { + // The delta x was less than the speed (ie. we're really close to + // the destination). So set delta to 0 so the player won't move + _delta = Point32(0, 0); + _position = Point32(_walkDest.x * FIXED_INT_MULTIPLIER, _walkDest.y * FIXED_INT_MULTIPLIER); + + _walkCount = 1; + } + + // See if the sequence needs to be changed for diagonal walking + if (_delta.y > 1500) { + if (_sequenceNumber == WALK_LEFT || _sequenceNumber == WALK_RIGHT) { + _delta.x = _delta.x / speed.x * diagSpeed.x; + _delta.y = (delta.y * FIXED_INT_MULTIPLIER) / (delta.x * 10 / diagSpeed.x); + + _walkCount = delta.x * 10 / diagSpeed.x; + } + + switch (_sequenceNumber) { + case WALK_LEFT: + _sequenceNumber = WALK_DOWNLEFT; + break; + case WALK_RIGHT: + _sequenceNumber = WALK_DOWNRIGHT; + break; + } + } else if (_delta.y < -1500) { + if (_sequenceNumber == WALK_LEFT || _sequenceNumber == WALK_RIGHT) { + _delta.x = _delta.x / speed.x * diagSpeed.x; + _delta.y = -1 * (delta.y * FIXED_INT_MULTIPLIER) / (delta.x * 10 / diagSpeed.x); + + _walkCount = (delta.x * 10) / diagSpeed.x; + } + + switch (_sequenceNumber) { + case WALK_LEFT: + _sequenceNumber = WALK_UPLEFT; + break; + case WALK_RIGHT: + _sequenceNumber = WALK_UPRIGHT; + break; + } + } + } else { + // Major movement is vertical, so set the sequence for up and down, + // and set the delta Y depending on the direction + if (_walkDest.y < (_position.y / FIXED_INT_MULTIPLIER)) { + _sequenceNumber = WALK_UP; + _delta.y = speed.y * -(FIXED_INT_MULTIPLIER / 10); + } else { + speed.y = diagSpeed.y; + _sequenceNumber = WALK_DOWN; + _delta.y = speed.y * (FIXED_INT_MULTIPLIER / 10); + } + + // Set the delta x + if (delta.y * 10 / speed.y) + _delta.x = (delta.x * FIXED_INT_MULTIPLIER) / (delta.y * 10 / speed.y); + else + _delta.x = (delta.x * FIXED_INT_MULTIPLIER) / delta.y; + + if (_walkDest.x < _position.y / FIXED_INT_MULTIPLIER) + _delta.x = -_delta.x; + + // Set how many times we should add the delta's to the players position + if (delta.y * 10 / speed.y) + _walkCount = delta.y * 10 / speed.y; + else + _walkCount = delta.y; + } + } + + // See if the new walk sequence is the same as the old. If it's a new one, + // we need to reset the frame number to zero so its animation starts at + // its beginning. Otherwise, if it's the same sequence, we can leave it + // as is, so it keeps the animation going at wherever it was up to + if (_sequenceNumber != _oldWalkSequence) { + if (_seqTo) { + // Reset to previous value + _walkSequences[oldDirection]._sequences[_frameNumber] = _seqTo; + _seqTo = 0; + } + _frameNumber = 0; + } + + checkWalkGraphics(); + _oldWalkSequence = _sequenceNumber; + + if (!_walkCount && _walkTo.empty()) + gotoStand(); + + // If the sequence is the same as when we started, then Holmes was standing still and we're trying + // to re-stand him, so reset Holmes' rame to the old frame number from before it was reset to 0 + if (_sequenceNumber == oldDirection) + _frameNumber = oldFrame; +} + +void TattooPerson::walkToCoords(const Point32 &destPos, int destDir) { + TattooEngine &vm = *(TattooEngine *)_vm; + Events &events = *_vm->_events; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooScene &scene = *(TattooScene *)_vm->_scene; + Talk &talk = *_vm->_talk; + + CursorId oldCursor = events.getCursor(); + events.setCursor(WAIT); + + _walkDest = Common::Point(destPos.x / FIXED_INT_MULTIPLIER, destPos.y / FIXED_INT_MULTIPLIER); + + bool isHolmes = this == &people[HOLMES]; + if (isHolmes) { + people._allowWalkAbort = true; + } else { + // Clear the path Variables + _npcIndex = _npcPause = 0; + Common::fill(_npcPath, _npcPath + 100, 0); + _npcFacing = destDir; + } + + _centerWalk = false; + + // Only move the person if they're going an appreciable distance + if (ABS(_walkDest.x - (_position.x / FIXED_INT_MULTIPLIER)) > 8 || + ABS(_walkDest.y - (_position.y / FIXED_INT_MULTIPLIER)) > 4) { + goAllTheWay(); + + do { + // Keep doing animations whilst walk is in progress + events.wait(1); + scene.doBgAnim(); + + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + + if (keyState.keycode == Common::KEYCODE_ESCAPE && vm._runningProlog) { + vm.setFlags(-76); + vm.setFlags(396); + scene._goToScene = 1; + talk._talkToAbort = true; + } + } + } while (!_vm->shouldQuit() && _walkCount && !talk._talkToAbort); + } + + _centerWalk = true; + if (!isHolmes) + _updateNPCPath = true; + + if (!talk._talkToAbort) { + // put character exactly on right spot + _position = destPos; + + if (_sequenceNumber != destDir) { + // Facing character to correct ending direction + _sequenceNumber = destDir; + gotoStand(); + } + + if (!isHolmes) + _updateNPCPath = false; + + // Secondary walking wait loop + bool done = false; + while (!done && !_vm->shouldQuit()) { + events.wait(1); + scene.doBgAnim(); + + // See if we're past the initial goto stand sequence + for (int idx = 0; idx < _frameNumber; ++idx) { + if (_walkSequences[_sequenceNumber][idx] == 0) { + done = true; + break; + } + } + + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + + if (keyState.keycode == Common::KEYCODE_ESCAPE && vm._runningProlog) { + vm.setFlags(-76); + vm.setFlags(396); + scene._goToScene = 1; + talk._talkToAbort = true; + } + } + } + + if (!isHolmes) + _updateNPCPath = true; + + if (!talk._talkToAbort) + events.setCursor(oldCursor); + } +} + +void TattooPerson::clearNPC() { + Common::fill(&_npcPath[0], &_npcPath[MAX_NPC_PATH], 0); + _npcIndex = 0; + _pathStack.clear(); + _npcName = ""; +} + +void TattooPerson::updateNPC() { + People &people = *_vm->_people; + Talk &talk = *_vm->_talk; + + // If the NPC isn't on, or it's in Talk or Listen Mode, then return without doing anything + if (_type != CHARACTER || _sequenceNumber >= TALK_UPRIGHT) + return; + + // If the NPC is paused, just decrement his pause counter and exit + if (_npcPause) { + // Decrement counter + --_npcPause; + + // Now see if we need to update the NPC's frame sequence so that he faces Holmes + if (_lookHolmes) { + // See where Holmes is with respect to the NPC (x coordinate) + _npcFacing = (people[HOLMES]._position.x < _position.x) ? STOP_LEFT : STOP_RIGHT; + + // See where Holmes is with respect to the NPC (y coordinate) + if (people[HOLMES]._position.y < (_position.y - 10 * FIXED_INT_MULTIPLIER)) { + // Holmes is above the NPC so reset the facing to a diagonal up + _npcFacing = (_npcFacing == STOP_RIGHT) ? STOP_UPRIGHT : STOP_UPLEFT; + } else if (people[HOLMES]._position.y > (_position.y + 10 * FIXED_INT_MULTIPLIER)) { + // Holmes is below the NPC so reset the facing to a diagonal down + _npcFacing = (_npcFacing == STOP_RIGHT) ? STOP_DOWNRIGHT : STOP_DOWNLEFT; + } + + // See if we need to set the old_walk_sequence so the NPC will put his arms + // up if he turns another way + if (_sequenceNumber != _npcFacing) + _oldWalkSequence = _sequenceNumber; + + gotoStand(); + } + } else { + // Reset the look flag so the NPC won't face Holmes anymore + _lookHolmes = false; + + // See if the NPC is stopped or not. Don't do anything if he's moving + if (!_walkCount) { + // If there is no new command, reset the path back to the beginning + if (!_npcPath[_npcIndex]) + _npcIndex = 0; + + // The NPC is stopped and any pause he was doing is done. We can now see what + // the next command in the NPC path is. + + // Scan Past any NPC Path Labels since they do nothing except mark places for If's and Goto's + while (_npcPath[_npcIndex] == NPCPATH_PATH_LABEL) + _npcIndex += 2; + + if (_npcPath[_npcIndex]) { + _npcFacing = -1; + + switch (_npcPath[_npcIndex]) { + case NPCPATH_SET_DEST: { + // Set the NPC's new destination + int xp = (_npcPath[_npcIndex + 1] - 1) * 256 + _npcPath[_npcIndex + 2] - 1; + if (xp > 16384) + xp = -1 * (xp - 16384); + _walkDest.x = xp; + _walkDest.y = (_npcPath[_npcIndex + 3] - 1) * 256 + _npcPath[_npcIndex + 4] - 1; + _npcFacing = _npcPath[_npcIndex + 5] - 1; + + goAllTheWay(); + _npcIndex += 6; + break; + } + + case NPCPATH_PAUSE: + // Set the NPC to pause where he is + _npcPause = (_npcPath[_npcIndex + 1] - 1) * 256 + _npcPath[_npcIndex + 2] - 1; + _npcIndex += 3; + break; + + case NPCPATH_SET_TALK_FILE: { + // Set the NPC's Talk File to use if Holmes talks to them + ++_npcIndex; + + _npcName = ""; + for (int idx = 0; idx < 8; ++idx) { + if (_npcPath[_npcIndex + idx] != '~') + _npcName += _npcPath[_npcIndex + idx]; + else + break; + } + + _npcIndex += 8; + break; + } + + case NPCPATH_CALL_TALK_FILE: { + // Call a Talk File + ++_npcIndex; + + Common::String name; + for (int idx = 0; idx < 8; ++idx) { + if (_npcPath[_npcIndex + idx] != '~') + name += _npcPath[_npcIndex + idx]; + else + break; + } + + _npcIndex += 8; + talk.talkTo(name); + break; + } + + case NPCPATH_TAKE_NOTES: + // Set the NPC to Pause where he is and set his frame sequences + // so he takes notes while he's paused + _npcPause = (_npcPath[_npcIndex + 1] - 1) * 256 + _npcPath[_npcIndex + 2] - 1; + _npcIndex += 3; + break; + + case NPCPATH_FACE_HOLMES: + // Set the NPC to Pause where he is and set his look flag so he will always face Holmes + // while he is paused + _npcPause = (_npcPath[_npcIndex + 1] - 1) * 256 + _npcPath[_npcIndex + 2] - 1; + _lookHolmes = true; + _npcIndex += 3; + break; + + //case NPCPATH_PATH_LABEL: // No implementation needed here + + case NPCPATH_GOTO_LABEL: { + // Goto NPC Path Label + int label = _npcPath[_npcIndex + 1]; + _npcIndex = 0; + + // Scan through NPC path data to find the label + bool found = false; + while (!found) { + switch (_npcPath[_npcIndex]) { + case NPCPATH_SET_DEST: + _npcIndex += 6; + break; + case NPCPATH_PAUSE: + case NPCPATH_TAKE_NOTES: + case NPCPATH_FACE_HOLMES: + _npcIndex += 3; + break; + case NPCPATH_SET_TALK_FILE: + case NPCPATH_CALL_TALK_FILE: + _npcIndex += 8; + break; + case NPCPATH_PATH_LABEL: + if (_npcPath[_npcIndex + 1] == label) + found = true; + _npcIndex += 2; + break; + case NPCPATH_GOTO_LABEL: + _npcIndex += 2; + break; + case NPCPATH_IFFLAG_GOTO_LABEL: + _npcIndex += 4; + break; + } + } + break; + } + + case NPCPATH_IFFLAG_GOTO_LABEL: { + // If FLAG then Goto Label + int flag = (_npcPath[_npcIndex + 1] - 1) * 256 + _npcPath[_npcIndex + 2] - 1 - (_npcPath[_npcIndex + 2] == 1 ? 1 : 0); + + // Set the value the flag should be for the if statement to succeed + bool flagVal = flag < 16384; + + int label = _npcPath[_npcIndex + 3]; + _npcIndex += 4; + + // If the flag is set Correctly, move the NPC Index to the given label + if (_vm->readFlags(flag & 16383) == flagVal) { + _npcIndex = 0; + bool found = false; + while (!found) + { + switch (_npcPath[_npcIndex]) + { + case NPCPATH_SET_DEST: + _npcIndex += 6; + break; + case NPCPATH_PAUSE: + case NPCPATH_TAKE_NOTES: + case NPCPATH_FACE_HOLMES: + _npcIndex += 3; + break; + case NPCPATH_SET_TALK_FILE: + case NPCPATH_CALL_TALK_FILE: + _npcIndex += 8; + break; + case NPCPATH_PATH_LABEL: + if (_npcPath[_npcIndex + 1] == label) + found = true; + _npcIndex += 2; + break; + case NPCPATH_GOTO_LABEL: + _npcIndex += 2; + break; + case NPCPATH_IFFLAG_GOTO_LABEL: + _npcIndex += 4; + break; + } + } + } + + break; + } + + default: + break; + } + } + } + } +} + +void TattooPerson::pushNPCPath() { + assert(_pathStack.size() < 2); + SavedNPCPath savedPath(_npcPath, _npcIndex, _npcPause, _position, _sequenceNumber, _lookHolmes); + _pathStack.push(savedPath); +} + +void TattooPerson::pullNPCPath() { + // Pop the stack entry and restore the fields + SavedNPCPath path = _pathStack.pop(); + Common::copy(&path._path[0], &path._path[MAX_NPC_PATH], &_npcPath[0]); + _npcIndex = path._npcIndex; + _npcPause = path._npcPause; + + // Handle the first case if the NPC was paused + if (_npcPause) { + _npcFacing = path._npcFacing; + _lookHolmes = path._lookHolmes; + + // See if the NPC has moved from where they originally were + if (path._position != _position) { + _walkDest = Point32(path._position.x / FIXED_INT_MULTIPLIER, path._position.y / FIXED_INT_MULTIPLIER); + goAllTheWay(); + _npcPause = 0; + _npcIndex -= 3; + } else { + // See if we need to set the old walk sequence so the NPC will put his arms up if he turns another way + if (_npcFacing != _sequenceNumber) + _oldWalkSequence = _sequenceNumber; + + gotoStand(); + } + } else { + // Handle the second case if the NPC was in motion + _npcIndex -= 6; + } +} + +Common::Point TattooPerson::getSourcePoint() const { + TattooScene &scene = *(TattooScene *)_vm->_scene; + int scaleVal = scene.getScaleVal(_position); + + return Common::Point(_position.x / FIXED_INT_MULTIPLIER + _imageFrame->sDrawXSize(scaleVal) / 2, + _position.y / FIXED_INT_MULTIPLIER); +} + +void TattooPerson::setObjTalkSequence(int seq) { + assert(seq != -1 && _type == CHARACTER); + + if (_seqTo) { + // reset to previous value + _walkSequences[_sequenceNumber]._sequences[_frameNumber] = _seqTo; + _seqTo = 0; + } + + _sequenceNumber = _gotoSeq; + _frameNumber = 0; + checkWalkGraphics(); +} + +void TattooPerson::checkWalkGraphics() { + People &people = *_vm->_people; + + if (_images == nullptr) { + freeAltGraphics(); + return; + } + + Common::String filename = Common::String::format("%s.vgs", _walkSequences[_sequenceNumber]._vgsName.c_str()); + + // Set the adjust depending on if we have to fine tune the x position of this particular graphic + _adjust.x = _adjust.y = 0; + + for (int idx = 0; idx < NUM_ADJUSTED_WALKS; ++idx) { + if (!scumm_strnicmp(_walkSequences[_sequenceNumber]._vgsName.c_str(), ADJUST_WALKS[idx]._vgsName, + strlen(ADJUST_WALKS[idx]._vgsName))) { + if (_walkSequences[_sequenceNumber]._horizFlip) + _adjust.x = ADJUST_WALKS[idx]._flipXAdjust; + else + _adjust.x = ADJUST_WALKS[idx]._xAdjust; + + _adjust.y = ADJUST_WALKS[idx]._yAdjust; + break; + } + } + + // See if we're already using Alternate Graphics + if (_altSeq) { + // See if the VGS file called for is different than the alternate graphics already loaded + if (_walkSequences[_sequenceNumber]._vgsName.compareToIgnoreCase(_walkSequences[_altSeq - 1]._vgsName)) { + // Different AltGraphics, Free the old ones + freeAltGraphics(); + } + } + + // If there is no Alternate Sequence set, see if we need to load a new one + if (!_altSeq) { + int npcNum = -1; + // Find which NPC this is so we can check the name of the graphics loaded + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { + if (this == &people[idx]) { + npcNum = idx; + break; + } + } + + if (npcNum != -1) { + // See if the VGS file called for is different than the main graphics which are already loaded + if (filename.compareToIgnoreCase(people[npcNum]._walkVGSName) != 0) { + // See if this is one of the more used Walk Graphics stored in WALK.LIB + for (int idx = 0; idx < NUM_IN_WALK_LIB; ++idx) { + if (!scumm_stricmp(filename.c_str(), WALK_LIB_NAMES[idx])) { + people._useWalkLib = true; + break; + } + } + + _altImages = new ImageFile(filename); + people._useWalkLib = false; + + _altSeq = _sequenceNumber + 1; + } + } + } + + // If this is a different seqeunce from the current sequence, reset the appropriate variables + if (_sequences != &_walkSequences[_sequenceNumber]._sequences[0]) { + _seqTo = _seqCounter = _seqCounter2 = _seqStack = _startSeq = 0; + _sequences = &_walkSequences[_sequenceNumber]._sequences[0]; + _seqSize = _walkSequences[_sequenceNumber]._sequences.size(); + + // WORKAROUND: Occassionally when switching to a new walk sequence the existing frame number may be outside + // the allowed range for the new sequence. In such cases, reset the frame number + if (_frameNumber < 0 || _frameNumber >= (int)_seqSize || _walkSequences[_sequenceNumber][_frameNumber] == 0) + _frameNumber = 0; + } + + setImageFrame(); +} + +void TattooPerson::synchronize(Serializer &s) { + if (s.isSaving()) { + SpriteType type = (_type == INVALID && _walkLoaded) ? HIDDEN_CHARACTER : _type; + s.syncAsSint16LE(type); + } else { + if (_walkCount) + gotoStand(); + + s.syncAsSint16LE(_type); + } + + s.syncAsSint32LE(_position.x); + s.syncAsSint32LE(_position.y); + s.syncString(_walkVGSName); + s.syncString(_description); + s.syncString(_examine); + + // NPC specific properties + s.syncBytes(&_npcPath[0], MAX_NPC_PATH); + s.syncString(_npcName); + s.syncAsSint32LE(_npcPause); + s.syncAsByte(_lookHolmes); + s.syncAsByte(_updateNPCPath); + if (s.isLoading()) + _npcIndex = 0; + + // Verbs + for (int idx = 0; idx < 2; ++idx) + _use[idx].synchronize(s); +} + +void TattooPerson::walkHolmesToNPC() { + Events &events = *_vm->_events; + TattooScene &scene = *(TattooScene *)_vm->_scene; + TattooPeople &people = *(TattooPeople *)_vm->_people; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + TattooPerson &holmes = people[HOLMES]; + int facing; + + // Save the character's details + pushNPCPath(); + + // If the NPC is moving, stop him at his current position + if (_walkCount) { + // Reset the facing so the NPC will stop facing the direction he was going, + // rather than the direction he was supposed to when he finished wlaking + _npcFacing = -1; + gotoStand(); + } + + int scaleVal = scene.getScaleVal(_position); + ImageFrame &imgFrame = (*holmes._images)[0]; + + // Clear the path variables + memset(_npcPath, 0, 100); + + // Set the NPC path so he pauses for 250 while looking at Holmes + _npcPath[0] = 6; + _npcPath[1] = 1; + _npcPath[2] = 251; + _npcIndex = 0; + _npcPause = 250; + _lookHolmes = true; + + // See where Holmes is with respect to the NPC (x coords) + if (holmes._position.x < _position.x) { + holmes._walkDest.x = MAX(_position.x / FIXED_INT_MULTIPLIER - imgFrame.sDrawXSize(scaleVal), 0); + } else { + holmes._walkDest.x = MIN(_position.x / FIXED_INT_MULTIPLIER + imgFrame.sDrawXSize(scaleVal) * 2, + screen._backBuffer1.w() - 1); + } + + // See where Holmes is with respect to the NPC (y coords) + if (holmes._position.y < (_position.y - imgFrame.sDrawXSize(scaleVal) * 500)) { + holmes._walkDest.y = MAX(_position.y / FIXED_INT_MULTIPLIER - imgFrame.sDrawXSize(scaleVal) / 2, 0); + } else { + if (holmes._position.y > (_position.y + imgFrame.sDrawXSize(scaleVal) * 500)) { + // Holmes is below the NPC + holmes._walkDest.y = MIN(_position.y / FIXED_INT_MULTIPLIER + imgFrame.sDrawXSize(scaleVal) / 2, + SHERLOCK_SCREEN_HEIGHT - 1); + } else { + // Holmes is roughly on the same Y as the NPC + holmes._walkDest.y = _position.y / FIXED_INT_MULTIPLIER; + } + } + + events.setCursor(WAIT); + + _walkDest.x += 10; + people._allowWalkAbort = true; + holmes.goAllTheWay(); + + // Do doBgAnim should be called over and over until walk is done + do { + events.wait(1); + scene.doBgAnim(); + } while (holmes._walkCount); + + if (!talk._talkToAbort) { + // Setup correct direction for Holmes to face + + // See where Holmes is with respect to the NPC (x coords) + facing = (holmes._position.x < _position.x) ? STOP_RIGHT : STOP_LEFT; + + // See where Holmes is with respect to the NPC (y coords) + if (holmes._position.y < (_position.y - (10 * FIXED_INT_MULTIPLIER))) { + // Holmes is above the NPC. Reset the facing to the diagonal downs + facing = (facing == STOP_RIGHT) ? STOP_DOWNRIGHT : STOP_DOWNLEFT; + } else { + if (holmes._position.y > (_position.y + 10 * FIXED_INT_MULTIPLIER)) { + // Holmes is below the NPC. Reset the facing to the diagonal ups + facing = (facing == STOP_RIGHT) ? STOP_UPRIGHT : STOP_UPLEFT; + } + } + + holmes._sequenceNumber = facing; + holmes.gotoStand(); + + events.setCursor(ARROW); + } +} + +void TattooPerson::walkBothToCoords(const PositionFacing &holmesDest, const PositionFacing &npcDest) { + Events &events = *_vm->_events; + TattooPeople &people = *(TattooPeople *)_vm->_people; + Scene &scene = *_vm->_scene; + Talk &talk = *_vm->_talk; + TattooPerson &holmes = people[HOLMES]; + bool holmesStopped = false, npcStopped = false; + + // Save the current cursor and change to the wait cursor + CursorId oldCursor = events.getCursor(); + events.setCursor(WAIT); + + holmes._centerWalk = false; + _centerWalk = false; + + // Start Holmes walking to his dest + holmes._walkDest = Common::Point(holmesDest.x / FIXED_INT_MULTIPLIER + 10, holmesDest.y / FIXED_INT_MULTIPLIER); + people._allowWalkAbort = true; + holmes.goAllTheWay(); + + // Start the NPC walking to their dest + _walkDest = Common::Point(npcDest.x / FIXED_INT_MULTIPLIER + 10, npcDest.y / FIXED_INT_MULTIPLIER); + goAllTheWay(); + + // Clear the path variables + _npcIndex = _npcPause = 0; + Common::fill(&_npcPath[0], &_npcPath[100], 0); + _npcFacing = npcDest._facing; + + // Now loop until both stop walking + do { + events.pollEvents(); + scene.doBgAnim(); + + if (!holmes._walkCount && !holmesStopped) { + // Holmes finished walking + holmesStopped = true; + + // Ensure Holmes is on the exact destination spot + holmes._position = holmesDest; + holmes._sequenceNumber = holmesDest._facing; + holmes.gotoStand(); + } + + if (!_walkCount && !npcStopped) { + // NPC finished walking + npcStopped = true; + + // Ensure NPC is on the exact destination spot + _position = npcDest; + _sequenceNumber = npcDest._facing; + gotoStand(); + } + + } while (!_vm->shouldQuit() && (holmes._walkCount || _walkCount)); + + holmes._centerWalk = true; + _centerWalk = true; + + // Do one last frame draw so that the lsat person to stop will be drawn in their final position + scene.doBgAnim(); + + _updateNPCPath = true; + + if (!talk._talkToAbort) + // Restore original mouse cursor + events.setCursor(oldCursor); +} + +void TattooPerson::centerScreenOnPerson() { + Screen &screen = *_vm->_screen; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + + ui._targetScroll.x = CLIP(_position.x / FIXED_INT_MULTIPLIER - SHERLOCK_SCREEN_WIDTH / 2, + 0, screen._backBuffer1.w() - SHERLOCK_SCREEN_WIDTH); + screen._currentScroll = ui._targetScroll; + + // Reset the default look position to the center of the screen + ui._lookPos = screen._currentScroll + Common::Point(SHERLOCK_SCREEN_WIDTH / 2, SHERLOCK_SCREEN_HEIGHT / 2); +} + +/*----------------------------------------------------------------*/ + +TattooPeople::TattooPeople(SherlockEngine *vm) : People(vm) { + for (int idx = 0; idx < 6; ++idx) + _data.push_back(new TattooPerson()); +} + +void TattooPeople::setListenSequence(int speaker, int sequenceNum) { + Scene &scene = *_vm->_scene; + + // If no speaker is specified, then nothing needs to be done + if (speaker == -1) + return; + + int objNum = findSpeaker(speaker); + if (objNum < CHARACTERS_INDEX && objNum != -1) { + // See if the Object has to wait for an Abort Talk Code + Object &obj = scene._bgShapes[objNum]; + if (obj.hasAborts()) + obj._gotoSeq = sequenceNum; + else + obj.setObjTalkSequence(sequenceNum); + } else if (objNum != -1) { + objNum -= CHARACTERS_INDEX; + TattooPerson &person = (*this)[objNum]; + + int newDir = person._sequenceNumber; + switch (person._sequenceNumber) { + case WALK_UP: + case STOP_UP: + case WALK_UPRIGHT: + case STOP_UPRIGHT: + case TALK_UPRIGHT: + case LISTEN_UPRIGHT: + newDir = LISTEN_UPRIGHT; + break; + case WALK_RIGHT: + case STOP_RIGHT: + case TALK_RIGHT: + case LISTEN_RIGHT: + newDir = LISTEN_RIGHT; + break; + case WALK_DOWNRIGHT: + case STOP_DOWNRIGHT: + case TALK_DOWNRIGHT: + case LISTEN_DOWNRIGHT: + newDir = LISTEN_DOWNRIGHT; + break; + case WALK_DOWN: + case STOP_DOWN: + case WALK_DOWNLEFT: + case STOP_DOWNLEFT: + case TALK_DOWNLEFT: + case LISTEN_DOWNLEFT: + newDir = LISTEN_DOWNLEFT; + break; + case WALK_LEFT: + case STOP_LEFT: + case TALK_LEFT: + case LISTEN_LEFT: + newDir = LISTEN_LEFT; + break; + case WALK_UPLEFT: + case STOP_UPLEFT: + case TALK_UPLEFT: + case LISTEN_UPLEFT: + newDir = LISTEN_UPLEFT; + break; + + default: + break; + } + + // See if the NPC's Seq has to wait for an Abort Talk Code + if (person.hasAborts()) { + person._gotoSeq = newDir; + } else { + if (person._seqTo) { + // Reset to previous value + person._walkSequences[person._sequenceNumber]._sequences[person._frameNumber] = person._seqTo; + person._seqTo = 0; + } + + person._sequenceNumber = newDir; + person._frameNumber = 0; + person.checkWalkGraphics(); + } + } +} + +void TattooPeople::setTalkSequence(int speaker, int sequenceNum) { + TattooPeople &people = *(TattooPeople *)_vm->_people; + Scene &scene = *_vm->_scene; + TattooTalk &talk = *(TattooTalk *)_vm->_talk; + + // If no speaker is specified, then nothing needs to be done + if (speaker == -1) + return; + + int objNum = people.findSpeaker(speaker); + if (objNum != -1 && objNum < CHARACTERS_INDEX) { + Object &obj = scene._bgShapes[objNum]; + + // See if the Object has to wait for an Abort Talk Code + if (obj.hasAborts()) { + talk.pushSequenceEntry(&obj); + obj._gotoSeq = sequenceNum; + } else { + obj.setObjTalkSequence(sequenceNum); + } + } else if (objNum != -1) { + objNum -= CHARACTERS_INDEX; + TattooPerson &person = people[objNum]; + int newDir = person._sequenceNumber; + + switch (newDir) { + case WALK_UP: + case STOP_UP: + case WALK_UPRIGHT: + case STOP_UPRIGHT: + case TALK_UPRIGHT: + case LISTEN_UPRIGHT: + newDir = TALK_UPRIGHT; + break; + case WALK_RIGHT: + case STOP_RIGHT: + case TALK_RIGHT: + case LISTEN_RIGHT: + newDir = TALK_RIGHT; + break; + case WALK_DOWNRIGHT: + case STOP_DOWNRIGHT: + case TALK_DOWNRIGHT: + case LISTEN_DOWNRIGHT: + newDir = TALK_DOWNRIGHT; + break; + case WALK_DOWN: + case STOP_DOWN: + case WALK_DOWNLEFT: + case STOP_DOWNLEFT: + case TALK_DOWNLEFT: + case LISTEN_DOWNLEFT: + newDir = TALK_DOWNLEFT; + break; + case WALK_LEFT: + case STOP_LEFT: + case TALK_LEFT: + case LISTEN_LEFT: + newDir = TALK_LEFT; + break; + case WALK_UPLEFT: + case STOP_UPLEFT: + case TALK_UPLEFT: + case LISTEN_UPLEFT: + newDir = TALK_UPLEFT; + break; + default: + break; + } + + // See if the NPC's sequence has to wait for an Abort Talk Code + if (person.hasAborts()) { + person._gotoSeq = newDir; + } else { + if (person._seqTo) { + // Reset to previous value + person._walkSequences[person._sequenceNumber]._sequences[person._frameNumber] = person._seqTo; + person._seqTo = 0; + } + + person._sequenceNumber = newDir; + person._frameNumber = 0; + person.checkWalkGraphics(); + } + } +} + + +int TattooPeople::findSpeaker(int speaker) { + int result = People::findSpeaker(speaker); + const char *portrait = _characters[speaker]._portrait; + + // Fallback that Rose Tattoo uses if no speaker was found + if (result == -1) { + bool flag = _vm->readFlags(FLAG_PLAYER_IS_HOLMES); + + if (_data[HOLMES]->_type == CHARACTER && ((speaker == HOLMES && flag) || (speaker == WATSON && !flag))) + // Return the offset index for the first character + return 0 + CHARACTERS_INDEX; + + // Otherwise, scan through the list of the remaining characters to find a name match + for (uint idx = 1; idx < _data.size(); ++idx) { + TattooPerson &p = (*this)[idx]; + + if (p._type == CHARACTER) { + Common::String name(p._name.c_str(), p._name.c_str() + 4); + + if (name.equalsIgnoreCase(portrait) && p._npcName[4] >= '0' && p._npcName[4] <= '9') + return idx + CHARACTERS_INDEX; + } + } + } + + return result; +} + +void TattooPeople::synchronize(Serializer &s) { + s.syncAsByte(_holmesOn); + + for (uint idx = 0; idx < _data.size(); ++idx) + (*this)[idx].synchronize(s); + + s.syncAsSint16LE(_holmesQuotient); + + if (s.isLoading()) { + _savedPos.x = _data[HOLMES]->_position.x; + _savedPos.y = _data[HOLMES]->_position.y; + _savedPos._facing = _data[HOLMES]->_sequenceNumber; + } +} + +bool TattooPeople::loadWalk() { + Resources &res = *_vm->_res; + bool result = false; + + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { + Person &person = *_data[idx]; + + if (!person._walkLoaded && (person._type == CHARACTER || person._type == HIDDEN_CHARACTER)) { + if (person._type == HIDDEN_CHARACTER) + person._type = INVALID; + + // See if this is one of the more used Walk Graphics stored in WALK.LIB + for (int libNum = 0; libNum < NUM_IN_WALK_LIB; ++libNum) { + if (!person._walkVGSName.compareToIgnoreCase(WALK_LIB_NAMES[libNum])) { + _useWalkLib = true; + break; + } + } + + // Load the images for the character + person._images = new ImageFile(person._walkVGSName, false); + person._maxFrames = person._images->size(); + + // Load walk sequence data + Common::String fname = Common::String(person._walkVGSName.c_str(), strchr(person._walkVGSName.c_str(), '.')); + fname += ".SEQ"; + + // Load the walk sequence data + Common::SeekableReadStream *stream = res.load(fname, _useWalkLib ? "walk.lib" : "vgs.lib"); + + person._walkSequences.resize(stream->readByte()); + + for (uint seqNum = 0; seqNum < person._walkSequences.size(); ++seqNum) + person._walkSequences[seqNum].load(*stream); + + // Close the sequences resource + delete stream; + _useWalkLib = false; + + person._sequences = &person._walkSequences[person._sequenceNumber]._sequences[0]; + person._seqSize = person._walkSequences[person._sequenceNumber]._sequences.size(); + person._frameNumber = 0; + person.setImageFrame(); + + // Set the stop Frames pointers + for (int dirNum = 0; dirNum < 8; ++dirNum) { + int count = 0; + while (person._walkSequences[dirNum + 8][count] != 0) + ++count; + count += 2; + count = person._walkSequences[dirNum + 8][count] - 1; + person._stopFrames[dirNum] = &(*person._images)[count]; + } + + result = true; + person._walkLoaded = true; + } else if (person._type != CHARACTER) { + person._walkLoaded = false; + } + } + + _forceWalkReload = false; + return result; +} + + +void TattooPeople::pullNPCPaths() { + for (int idx = 1; idx < MAX_CHARACTERS; ++idx) { + TattooPerson &p = (*this)[idx]; + if (p._npcMoved) { + while (!p._pathStack.empty()) + p.pullNPCPath(); + } + } +} + +const Common::Point TattooPeople::restrictToZone(int zoneId, const Common::Point &destPos) { + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + Common::Rect &r = scene._zones[zoneId]; + + if (destPos.x < 0 || destPos.x > screen._backBuffer1.w()) + return destPos; + else if (destPos.y < r.top && r.left < destPos.x && destPos.x < r.right) + return Common::Point(destPos.x, r.top); + else if (destPos.y > r.bottom && r.left < destPos.x && destPos.x < r.right) + return Common::Point(destPos.x, r.bottom); + else if (destPos.x < r.left && r.top < destPos.y && destPos.y < r.bottom) + return Common::Point(r.left, destPos.y); + else if (destPos.x > r.right && r.top < destPos.y && destPos.y < r.bottom) + return Common::Point(r.right, destPos.y); + + // Find which corner of the zone the point is closet to + if (destPos.x <= r.left) { + return Common::Point(r.left, (destPos.y <= r.top) ? r.top : r.bottom); + } else { + return Common::Point(r.right, (destPos.y <= r.top) ? r.top : r.bottom); + } +} + + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/tattoo_people.h b/engines/sherlock/tattoo/tattoo_people.h new file mode 100644 index 0000000000..722c4a9aaa --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_people.h @@ -0,0 +1,281 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_TATTOO_PEOPLE_H +#define SHERLOCK_TATTOO_PEOPLE_H + +#include "common/scummsys.h" +#include "common/stack.h" +#include "sherlock/people.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Tattoo { + +// Animation sequence identifiers for characters +enum TattooSequences { + // Walk Sequences Numbers for NPCs + WALK_UP = 0, + WALK_UPRIGHT = 1, + WALK_RIGHT = 2, + WALK_DOWNRIGHT = 3, + WALK_DOWN = 4, + WALK_DOWNLEFT = 5, + WALK_LEFT = 6, + WALK_UPLEFT = 7, + + // Stop Sequences Numbers for NPCs + STOP_UP = 8, + STOP_UPRIGHT = 9, + STOP_RIGHT = 10, + STOP_DOWNRIGHT = 11, + STOP_DOWN = 12, + STOP_DOWNLEFT = 13, + STOP_LEFT = 14, + STOP_UPLEFT = 15, + + // NPC Talk Sequence Numbers + TALK_UPRIGHT = 16, + TALK_RIGHT = 17, + TALK_DOWNRIGHT = 18, + TALK_DOWNLEFT = 19, + TALK_LEFT = 20, + TALK_UPLEFT = 21, + + // NPC Listen Sequence Numbers + LISTEN_UPRIGHT = 22, + LISTEN_RIGHT = 23, + LISTEN_DOWNRIGHT = 24, + LISTEN_DOWNLEFT = 25, + LISTEN_LEFT = 26, + LISTEN_UPLEFT = 27 +}; + +enum NpcPath { + NPCPATH_SET_DEST = 1, + NPCPATH_PAUSE = 2, + NPCPATH_SET_TALK_FILE = 3, + NPCPATH_CALL_TALK_FILE = 4, + NPCPATH_TAKE_NOTES = 5, + NPCPATH_FACE_HOLMES = 6, + NPCPATH_PATH_LABEL = 7, + NPCPATH_GOTO_LABEL = 8, + NPCPATH_IFFLAG_GOTO_LABEL = 9 +}; + +struct SavedNPCPath { + byte _path[MAX_NPC_PATH]; + int _npcIndex; + int _npcPause; + Point32 _position; + int _npcFacing; + bool _lookHolmes; + + SavedNPCPath(); + SavedNPCPath(byte path[MAX_NPC_PATH], int npcIndex, int npcPause, const Point32 &position, + int npcFacing, bool lookHolmes); +}; + +class TattooPerson: public Person { +private: + Point32 _nextDest; +private: + bool checkCollision() const; + + /** + * Free the alternate graphics used by NPCs + */ + void freeAltGraphics(); +protected: + /** + * Get the source position for a character potentially affected by scaling + */ + virtual Common::Point getSourcePoint() const; +public: + Common::Stack<SavedNPCPath> _pathStack; + int _npcIndex; + int _npcPause; + byte _npcPath[MAX_NPC_PATH]; + bool _npcMoved; + int _npcFacing; + bool _resetNPCPath; + int _savedNpcSequence; + int _savedNpcFrame; + int _tempX; + int _tempScaleVal; + bool _updateNPCPath; + bool _lookHolmes; +public: + TattooPerson(); + virtual ~TattooPerson() {} + + /** + * Clear the NPC related data + */ + void clearNPC(); + + /** + * Called from doBgAnim to move NPCs along any set paths. If an NPC is paused in his path, + * he will remain paused until his pause timer runs out. If he is walking somewhere, + * he will continue walking there until he reaches the dest position. When an NPC stops moving, + * the next element of his path is processed. + * + * The path is an array of bytes with control codes followed by their parameters as needed. + */ + void updateNPC(); + + /** + * Push the NPC's path data onto the path stack for when a talk file moves the NPC that + * has some control codes. + */ + void pushNPCPath(); + + /** + * Pull an NPC's path data that has been previously saved on the path stack for that character. + * There are two possibilities for when the NPC was interrupted, and both are handled differently: + * 1) The NPC was paused at a position + * If the NPC didn't move, we can just restore his pause counter and exit. But if he did move, + * he must return to that position, and the path index must be reset to the pause he was executing. + * This means that the index must be decremented by 3 + * 2) The NPC was in route to a position + * He must be set to walk to that position again. This is done by moving the path index + * so that it points to the code that set the NPC walking there in the first place. + * The regular calls to updateNPC will handle the rest + */ + void pullNPCPath(); + + /** + * Checks a sprite associated with an NPC to see if the frame sequence specified + * in the sequence number uses alternate graphics, and if so if they need to be loaded + */ + void checkWalkGraphics(); + + /** + * Synchronize the data for a savegame + */ + void synchronize(Serializer &s); + + + /** + * Walk Holmes to the NPC + */ + void walkHolmesToNPC(); + + /** + * Walk both the specified character and Holmes to specified destination positions + */ + void walkBothToCoords(const PositionFacing &holmesDest, const PositionFacing &npcDest); + + /** + * This adjusts the sprites position, as well as its animation sequence: + */ + virtual void adjustSprite(); + + /** + * Bring a moving character to a standing position + */ + virtual void gotoStand(); + + /** + * Set the variables for moving a character from one poisition to another + * in a straight line + */ + virtual void setWalking(); + + /** + * Walk to the co-ordinates passed, and then face the given direction + */ + virtual void walkToCoords(const Point32 &destPos, int destDir); + + /** + * Adjusts the frame and sequence variables of a sprite that corresponds to the current speaker + * so that it points to the beginning of the sequence number's talk sequence in the object's + * sequence buffer + * @param seq Which sequence to use (if there's more than 1) + * @remarks 1: First talk seq, 2: second talk seq, etc. + */ + virtual void setObjTalkSequence(int seq); + + /** + * Center the visible screen so that the person is in the center of the screen + */ + virtual void centerScreenOnPerson(); +}; + +class TattooPeople : public People { +public: + TattooPeople(SherlockEngine *vm); + virtual ~TattooPeople() {} + + TattooPerson &operator[](PeopleId id) { return *(TattooPerson *)_data[id]; } + TattooPerson &operator[](int idx) { return *(TattooPerson *)_data[idx]; } + + /** + * Restore any saved NPC walk path data from any of the NPCs + */ + void pullNPCPaths(); + + /** + * Finds the scene background object corresponding to a specified speaker + */ + virtual int findSpeaker(int speaker); + + /** + * Synchronize the data for a savegame + */ + virtual void synchronize(Serializer &s); + + /** + * Change the sequence of the scene background object associated with the specified speaker. + */ + virtual void setTalkSequence(int speaker, int sequenceNum = 1); + + /** + * Load the walking images for Sherlock + */ + virtual bool loadWalk(); + + /** + * Restrict passed point to zone using Sherlock's positioning rules + */ + virtual const Common::Point restrictToZone(int zoneId, const Common::Point &destPos); + + /** + * If the specified speaker is a background object, it will set it so that it uses + * the Listen Sequence (specified by the sequence number). If the current sequence + * has an Allow Talk Code in it, the _gotoSeq field will be set so that the object + * begins listening as soon as it hits the Allow Talk Code. If there is no Abort Code, + * the Listen Sequence will begin immediately. + * @param speaker Who is speaking + * @param sequenceNum Which listen sequence to use + */ + virtual void setListenSequence(int speaker, int sequenceNum = 1); +}; + +} // End of namespace Scalpel + +} // End of namespace Sherlock + + +#endif diff --git a/engines/sherlock/tattoo/tattoo_resources.cpp b/engines/sherlock/tattoo/tattoo_resources.cpp new file mode 100644 index 0000000000..3be41e2650 --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_resources.cpp @@ -0,0 +1,329 @@ +/* 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 "sherlock/tattoo/tattoo_resources.h" + +namespace Sherlock { + +namespace Tattoo { + +const char PORTRAITS[TATTOO_MAX_PEOPLE][5] = { + { "HOLM" }, // Sherlock Holmes + { "WATS" }, // Dr. Watson + { "HUDS" }, // Mrs. Hudson + { "FORB" }, // Stanley Forbes + { "MYCR" }, // Mycroft Holmes + { "WIGG" }, // Wiggins + { "BURN" }, // Police Constable Burns + { "TRIM" }, // Augustus Trimble + { "DALE" }, // Police Constable Daley + { "MATR" }, // Matron + { "GRAC" }, // Sister Grace + { "MCCA" }, // Preston McCabe + { "COLL" }, // Bob Colleran + { "JONA" }, // Jonas Rigby + { "ROAC" }, // Police Constable Roach + { "DEWA" }, // James Dewar + { "JERE" }, // Sergeant Jeremy Duncan + { "GREG" }, // Inspector Gregson + { "LEST" }, // Inspector Lestrade + { "NEED" }, // Jesse Needhem + { "FLEM" }, // Arthur Fleming + { "PRAT" }, // Mr. Thomas Pratt + { "TILL" }, // Mathilda (Tillie) Mason + { "RUSS" }, // Adrian Russell + { "WHIT" }, // Eldridge Whitney + { "HEPP" }, // Hepplethwaite + { "HORA" }, // Horace Silverbridge + { "SHER" }, // Old Sherman + { "VERN" }, // Maxwell Verner + { "REDD" }, // Millicent Redding + { "VIRG" }, // Virgil Silverbridge + { "GEOR" }, // George O'Keeffe + { "LAWT" }, // Lord Denys Lawton + { "JENK" }, // Jenkins + { "JOCK" }, // Jock Mahoney + { "BART" }, // Bartender + { "LADY" }, // Lady Cordelia Lockridge + { "PETT" }, // Pettigrew + { "FANS" }, // Sir Avery Fanshawe + { "HODG" }, // Hodgkins + { "WILB" }, // Wilbur "Birdy" Heywood + { "JACO" }, // Jacob Farthington + { "BLED" }, // Philip Bledsoe + { "FOWL" }, // Sidney Fowler + { "PROF" }, // Professor Theodore Totman + { "ROSE" }, // Rose Hinchem + { "TALL" }, // Tallboy + { "STIT" }, // Ethlebert "Stitch" Rumsey + { "FREE" }, // Charles Freedman + { "HEMM" }, // Nigel Hemmings + { "CART" }, // Fairfax Carter + { "WILH" }, // Wilhelm II + { "WACH" }, // Wachthund + { "WILS" }, // Jonathan Wilson + { "DAVE" }, // David Lloyd-Jones + { "HARG" }, // Edward Hargrove + { "MORI" }, // Professor James Moriarty + { "LASC" }, // The Lascar + { "PARR" }, // Parrot + { "SCAR" }, // Vincent Scarrett + { "ALEX" }, // Alexandra + { "QUEE" }, // Queen Victoria + { "JOHN" }, // John Brown + { "PAT1" }, // Patient #1 + { "PAT2" }, // Patient #2 + { "PATR" }, // Patron + { "QUEN" }, // Queen Victoria + { "WITE" }, // Patient in White + { "LUSH" }, // Lush + { "DRNK" }, // Drunk + { "PROS" }, // Prostitute + { "MUDL" }, // Mudlark + { "GRIN" }, // Grinder + { "BOUN" }, // Bouncer + { "RATC" }, // Agnes Ratchet + { "ALOY" }, // Aloysius Ratchet + { "REAL" }, // Real Estate Agent + { "CAND" }, // Candy Clerk + { "BEAD" }, // Beadle + { "PRUS" }, // Prussian + { "ROWB" }, // Mrs. Rowbottom + { "MSLJ" }, // Miss Lloyd-Jones + { "TPAT" }, // Tavern patron + { "USER" }, // User + { "TOBY" }, // Toby + { "STAT" }, // Stationer + { "CLRK" }, // Law Clerk + { "CLER" }, // Ministry Clerk + { "BATH" }, // Bather + { "MAID" }, // Maid + { "LADF" }, // Lady Fanshawe + { "SIDN" }, // Sidney Ratchet + { "BOYO" }, // Boy + { "PTR2" }, // Second Patron + { "BRIT" }, // Constable Brit + { "DROV" } // Wagon Driver +}; + +const char *const FRENCH_NAMES[TATTOO_MAX_PEOPLE] = { + "Sherlock Holmes", + "Dr. Watson", + "Mme. Hudson", + "Stanley Forbes", + "Mycroft Holmes", + "Wiggins", + "Sergent Burns", + "Augustus Trimble", + "Sergent Daley", + "Infirmi?re chef", + "Mme. Grace", + "Preston McCabe", + "Bob Colleran", + "Jonas Rigby", + "Sergent Roach", + "James Dewar", + "Sergent Jeremy Duncan", + "Inspecteur Gregson", + "Inspecteur Lestrade", + "Jesse Needhem", + "Arthur Fleming", + "M. Thomas Pratt", + "Mathilda (Tillie) Mason", + "Adrian Russell", + "Eldridge Whitney", + "Hepplethwaite", + "Horace Silverbridge", + "Sherman", + "Maxwell Verner", + "Millicent Redding", + "Virgil Silverbridge", + "George O'Keeffe", + "Lord Denys Lawton", + "Jenkins", + "Jock Mahoney", + "Serveur", + "Lady Cordelia Lockridge", + "Pettigrew", + "Sir Avery Fanshawe", + "Hodgkins", + "Wilbur \"Birdy\" Heywood", + "Jacob Farthington", + "Philip Bledsoe", + "Sidney Fowler", + "Professeur Theodore Totman", + "Rose Hinchem", + "Tallboy", + "Ethlebert \"Stitch\" Rumsey", + "Charles Freedman", + "Nigel Hemmings", + "Fairfax Carter", + "Wilhelm II", + "Wachthund", + "Jonathan Wilson", + "David Lloyd-Jones", + "Edward Hargrove", + "Misteray", + "Le Lascar", + "Oiseau", + "Vincent Scarrett", + "Alexandra", + "Queen Victoria", + "John Brown", + "Patient", + "Patient", + "Client", + "Queen Victoria", + "Patient en blanc", + "Ivrogne", + "Ivrogne", + "Belle femme", + "Mudlark", + "Broyeur", + "Videur", + "Agnes Ratchet", + "Aloysius Ratchet", + "Immobilier", + "Gar?on", + "Beadle", + "Prussian", + "Mme. Rowbottom", + "Mme Lloyd-Jones", + "Tavern Client", + "User", + "Toby", + "Papeterie", + "Law Clerc", + "Ministry Employ?", + "Clint du thermes", + "Bonne", + "Lady Fanshawe", + "Sidney Ratchet", + "Gar?on", + "Client", + "Sergent Brit", + "Wagon Driver" +}; + +const char *const ENGLISH_NAMES[TATTOO_MAX_PEOPLE] = { + "Sherlock Holmes", + "Dr. Watson", + "Mrs. Hudson", + "Stanley Forbes", + "Mycroft Holmes", + "Wiggins", + "Police Constable Burns", + "Augustus Trimble", + "Police Constable Daley", + "Matron", + "Sister Grace", + "Preston McCabe", + "Bob Colleran", + "Jonas Rigby", + "Police Constable Roach", + "James Dewar", + "Sergeant Jeremy Duncan", + "Inspector Gregson", + "Inspector Lestrade", + "Jesse Needhem", + "Arthur Fleming", + "Mr. Thomas Pratt", + "Mathilda (Tillie) Mason", + "Adrian Russell", + "Eldridge Whitney", + "Hepplethwaite", + "Horace Silverbridge", + "Old Sherman", + "Maxwell Verner", + "Millicent Redding", + "Virgil Silverbridge", + "George O'Keeffe", + "Lord Denys Lawton", + "Jenkins", + "Jock Mahoney", + "Bartender", + "Lady Cordelia Lockridge", + "Pettigrew", + "Sir Avery Fanshawe", + "Hodgkins", + "Wilbur \"Birdy\" Heywood", + "Jacob Farthington", + "Philip Bledsoe", + "Sidney Fowler", + "Professor Theodore Totman", + "Rose Hinchem", + "Tallboy", + "Ethlebert \"Stitch\" Rumsey", + "Charles Freedman", + "Nigel Hemmings", + "Fairfax Carter", + "Wilhelm II", + "Wachthund", + "Jonathan Wilson", + "David Lloyd-Jones", + "Edward Hargrove", + "Misteray", + "The Lascar", + "Parrot", + "Vincent Scarrett", + "Alexandra", + "Queen Victoria", + "John Brown", + "A Patient", + "A Patient", + "Patron", + "Queen Victoria", + "Patient in white", + "Lush", + "Drunk", + "Prostitute", + "Mudlark", + "Grinder", + "Bouncer", + "Agnes Ratchet", + "Aloysius Ratchet", + "Real Estate Agent", + "Candy Clerk", + "Beadle", + "Prussian", + "Mrs. Rowbottom", + "Miss Lloyd-Jones", + "Tavern patron", + "User", + "Toby", + "Stationer", + "Law Clerk", + "Ministry Clerk", + "Bather", + "Maid", + "Lady Fanshawe", + "Sidney Ratchet", + "Boy", + "Patron", + "Constable Brit", + "Wagon Driver" +}; + + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/tattoo_resources.h b/engines/sherlock/tattoo/tattoo_resources.h new file mode 100644 index 0000000000..b706d90f2d --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_resources.h @@ -0,0 +1,42 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_TATTOO_RESOURCES_H +#define SHERLOCK_TATTOO_RESOURCES_H + +#include "common/scummsys.h" + +namespace Sherlock { + +namespace Tattoo { + +#define TATTOO_MAX_PEOPLE 96 + +extern const char PORTRAITS[TATTOO_MAX_PEOPLE][5]; +extern const char *const FRENCH_NAMES[TATTOO_MAX_PEOPLE]; +extern const char *const ENGLISH_NAMES[TATTOO_MAX_PEOPLE]; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/tattoo_scene.cpp b/engines/sherlock/tattoo/tattoo_scene.cpp index 2a13b2a450..f13d0588df 100644 --- a/engines/sherlock/tattoo/tattoo_scene.cpp +++ b/engines/sherlock/tattoo/tattoo_scene.cpp @@ -21,8 +21,10 @@ */ #include "sherlock/tattoo/tattoo_scene.h" -#include "sherlock/tattoo/tattoo.h" +#include "sherlock/tattoo/tattoo_people.h" +#include "sherlock/tattoo/tattoo_talk.h" #include "sherlock/tattoo/tattoo_user_interface.h" +#include "sherlock/tattoo/tattoo.h" #include "sherlock/events.h" #include "sherlock/people.h" @@ -30,28 +32,262 @@ namespace Sherlock { namespace Tattoo { -TattooScene::TattooScene(SherlockEngine *vm) : Scene(vm) { - _arrowZone = -1; - _mask = _mask1 = nullptr; - _maskCounter = 0; +const int FS_TRANS[8] = { + STOP_UP, STOP_UPRIGHT, STOP_RIGHT, STOP_DOWNRIGHT, STOP_DOWN, STOP_DOWNLEFT, STOP_LEFT, STOP_UPLEFT +}; + +/*----------------------------------------------------------------*/ + +struct ShapeEntry { + Object *_shape; + TattooPerson *_person; + bool _isAnimation; + int _yp; + int _ordering; + + ShapeEntry(TattooPerson *person, int yp, int ordering) : + _shape(nullptr), _person(person), _yp(yp), _isAnimation(false), _ordering(ordering) {} + ShapeEntry(Object *shape, int yp, int ordering) : + _shape(shape), _person(nullptr), _yp(yp), _isAnimation(false), _ordering(ordering) {} + ShapeEntry(int yp, int ordering) : + _shape(nullptr), _person(nullptr), _yp(yp), _isAnimation(true), _ordering(ordering) {} +}; +typedef Common::List<ShapeEntry> ShapeList; + +static bool sortImagesY(const ShapeEntry &s1, const ShapeEntry &s2) { + // Objects are order by the calculated Y position first and then, when both are equal, + // by the order in which the entries were added + return s1._yp < s2._yp || (s1._yp == s2._yp && s1._ordering < s2._ordering); } -void TattooScene::checkBgShapes() { - People &people = *_vm->_people; - Person &holmes = people._player; - Common::Point pt(holmes._position.x / FIXED_INT_MULTIPLIER, holmes._position.y / FIXED_INT_MULTIPLIER); +/*----------------------------------------------------------------*/ + +TattooScene::TattooScene(SherlockEngine *vm) : Scene(vm), _labWidget(vm) { + _labTableScene = false; +} + +bool TattooScene::loadScene(const Common::String &filename) { + TattooEngine &vm = *(TattooEngine *)_vm; + Events &events = *_vm->_events; + Music &music = *_vm->_music; + Talk &talk = *_vm->_talk; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + + // If we're going to the first game scene after the intro sequence, flag it as finished + if (vm._runningProlog && _currentScene == STARTING_GAME_SCENE) { + vm._runningProlog = false; + events.showCursor(); + talk._talkToAbort = false; + } + + // Check if it's a scene we need to keep trakc track of how many times we've visited + for (int idx = (int)_sceneTripCounters.size() - 1; idx >= 0; --idx) { + if (_sceneTripCounters[idx]._sceneNumber == _currentScene) { + if (--_sceneTripCounters[idx]._numTimes == 0) { + _vm->setFlags(_sceneTripCounters[idx]._flag); + _sceneTripCounters.remove_at(idx); + } + } + } + + // Set the NPC paths for the scene + setNPCPath(WATSON); + + // Handle loading music for the scene + if (music._musicOn) { + if (talk._scriptMoreFlag != 1 && talk._scriptMoreFlag != 3) + music._nextSongName = Common::String::format("res%02d", _currentScene); + + // If it's a new song, then start it up + if (music._currentSongName.compareToIgnoreCase(music._nextSongName)) { + if (music.loadSong(music._nextSongName)) { + if (music._musicOn) + music.startSong(); + } + } + } + + bool result = Scene::loadScene(filename); + + if (_currentScene != STARTING_INTRO_SCENE) { + // Set the menu/ui mode and whether we're in a lab table close-up scene + _labTableScene = _currentScene > 91 && _currentScene < 100; + ui._menuMode = _labTableScene ? LAB_MODE : STD_MODE; + + if (_labTableScene) + ui.addFixedWidget(&_labWidget); + } + + return result; +} + +void TattooScene::drawAllShapes() { + TattooPeople &people = *(TattooPeople *)_vm->_people; + Screen &screen = *_vm->_screen; + ShapeList shapeList; + int ordering = 0; + + // Draw all objects and animations that are set to behind + screen.setDisplayBounds(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + + // Draw all active shapes which are behind the person + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &obj = _bgShapes[idx]; + + if (obj._type == ACTIVE_BG_SHAPE && obj._misc == BEHIND) { + if (obj._quickDraw && obj._scaleVal == SCALE_THRESHOLD) + screen._backBuffer1.blitFrom(*obj._imageFrame, obj._position); + else + screen._backBuffer1.transBlitFrom(*obj._imageFrame, obj._position, obj._flags & OBJ_FLIPPED, 0, obj._scaleVal); + } + } + + // Draw the animation if it is behind the person + if (_activeCAnim.active() && _activeCAnim._zPlacement == BEHIND) + screen._backBuffer1.transBlitFrom(_activeCAnim._imageFrame, _activeCAnim._position, + (_activeCAnim._flags & 4) >> 1, 0, _activeCAnim._scaleVal); + + screen.resetDisplayBounds(); + + // Queue drawing of all objects that are set to NORMAL. + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &obj = _bgShapes[idx]; + + if (obj._type == ACTIVE_BG_SHAPE && (obj._misc == NORMAL_BEHIND || obj._misc == NORMAL_FORWARD)) { + if (obj._scaleVal == SCALE_THRESHOLD) + shapeList.push_back(ShapeEntry(&obj, obj._position.y + obj._imageFrame->_offset.y + + obj._imageFrame->_height, ordering++)); + else + shapeList.push_back(ShapeEntry(&obj, obj._position.y + obj._imageFrame->sDrawYOffset(obj._scaleVal) + + obj._imageFrame->sDrawYSize(obj._scaleVal), ordering++)); + } + } + + // Queue drawing the animation if it is NORMAL and can fall in front of, or behind the people + if (_activeCAnim.active() && (_activeCAnim._zPlacement == NORMAL_BEHIND || _activeCAnim._zPlacement == NORMAL_FORWARD)) { + if (_activeCAnim._scaleVal == SCALE_THRESHOLD) + shapeList.push_back(ShapeEntry(_activeCAnim._position.y + _activeCAnim._imageFrame._offset.y + + _activeCAnim._imageFrame._height, ordering++)); + else + shapeList.push_back(ShapeEntry(_activeCAnim._position.y + _activeCAnim._imageFrame.sDrawYOffset(_activeCAnim._scaleVal) + + _activeCAnim._imageFrame.sDrawYSize(_activeCAnim._scaleVal), ordering++)); + } + + // Queue all active characters for drawing + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { + if (people[idx]._type == CHARACTER && people[idx]._walkLoaded) + shapeList.push_back(ShapeEntry(&people[idx], people[idx]._position.y / FIXED_INT_MULTIPLIER, ordering++)); + } + + // Sort the list + Common::sort(shapeList.begin(), shapeList.end(), sortImagesY); + + // Draw the list of shapes in order + for (ShapeList::iterator i = shapeList.begin(); i != shapeList.end(); ++i) { + ShapeEntry &se = *i; + + if (se._shape) { + // it's a bg shape + if (se._shape->_quickDraw && se._shape->_scaleVal == SCALE_THRESHOLD) + screen._backBuffer1.blitFrom(*se._shape->_imageFrame, se._shape->_position); + else + screen._backBuffer1.transBlitFrom(*se._shape->_imageFrame, se._shape->_position, + se._shape->_flags & OBJ_FLIPPED, 0, se._shape->_scaleVal); + } else if (se._isAnimation) { + // It's an active animation + screen._backBuffer1.transBlitFrom(_activeCAnim._imageFrame, _activeCAnim._position, + (_activeCAnim._flags & 4) >> 1, 0, _activeCAnim._scaleVal); + } else { + // Drawing person + TattooPerson &p = *se._person; + + p._tempX = p._position.x / FIXED_INT_MULTIPLIER; + p._tempScaleVal = getScaleVal(p._position); + Common::Point adjust = p._adjust; + + if (p._tempScaleVal == SCALE_THRESHOLD) { + p._tempX += adjust.x; + screen._backBuffer1.transBlitFrom(*p._imageFrame, Common::Point(p._tempX, p._position.y / FIXED_INT_MULTIPLIER + - p.frameHeight() - adjust.y), p._walkSequences[p._sequenceNumber]._horizFlip, 0, p._tempScaleVal); + } else { + if (adjust.x) { + if (!p._tempScaleVal) + ++p._tempScaleVal; + + if (p._tempScaleVal >= SCALE_THRESHOLD && adjust.x) + --adjust.x; + + adjust.x = adjust.x * SCALE_THRESHOLD / p._tempScaleVal; + + if (p._tempScaleVal >= SCALE_THRESHOLD) + ++adjust.x; + p._tempX += adjust.x; + } + + if (adjust.y) { + if (!p._tempScaleVal) + p._tempScaleVal++; + + if (p._tempScaleVal >= SCALE_THRESHOLD && adjust.y) + --adjust.y; + + adjust.y = adjust.y * SCALE_THRESHOLD / p._tempScaleVal; + + if (p._tempScaleVal >= SCALE_THRESHOLD) + ++adjust.y; + } + + screen._backBuffer1.transBlitFrom(*p._imageFrame, Common::Point(p._tempX, p._position.y / FIXED_INT_MULTIPLIER + - p._imageFrame->sDrawYSize(p._tempScaleVal) - adjust.y), p._walkSequences[p._sequenceNumber]._horizFlip, 0, p._tempScaleVal); + } + } + } + + // Draw all objects & canimations that are set to FORWARD. + // Draw all static and active shapes that are FORWARD + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &obj = _bgShapes[idx]; + + if (obj._type == ACTIVE_BG_SHAPE && obj._misc == FORWARD) { + if (obj._quickDraw && obj._scaleVal == SCALE_THRESHOLD) + screen._backBuffer1.blitFrom(*obj._imageFrame, obj._position); + else + screen._backBuffer1.transBlitFrom(*obj._imageFrame, obj._position, obj._flags & OBJ_FLIPPED, 0, obj._scaleVal); + } + } + + // Draw the canimation if it is set as FORWARD + if (_activeCAnim.active() && _activeCAnim._zPlacement == FORWARD) + screen._backBuffer1.transBlitFrom(_activeCAnim._imageFrame, _activeCAnim._position, (_activeCAnim._flags & 4) >> 1, 0, _activeCAnim._scaleVal); + + // Draw all NO_SHAPE shapes which have their flag bits clear + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &obj = _bgShapes[idx]; + if (obj._type == NO_SHAPE && (obj._flags & 1) == 0) + screen._backBuffer1.fillRect(obj.getNoShapeBounds(), 15); + } +} + +void TattooScene::paletteLoaded() { + Screen &screen = *_vm->_screen; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + ui.setupBGArea(screen._cMap); + ui.initScrollVars(); +} + +void TattooScene::checkBgShapes() { // Call the base scene method to handle bg shapes Scene::checkBgShapes(); // Check for any active playing animation - if (_activeCAnim._images && _activeCAnim._zPlacement != REMOVE) { + if (_activeCAnim.active() && _activeCAnim._zPlacement != REMOVE) { switch (_activeCAnim._flags & 3) { case 0: _activeCAnim._zPlacement = BEHIND; break; case 1: - _activeCAnim._zPlacement = ((_activeCAnim._position.y + _activeCAnim._imageFrame->_frame.h - 1)) ? + _activeCAnim._zPlacement = ((_activeCAnim._position.y + _activeCAnim._imageFrame._frame.h - 1)) ? NORMAL_FORWARD : NORMAL_BEHIND; break; case 2: @@ -63,9 +299,19 @@ void TattooScene::checkBgShapes() { } } +void TattooScene::freeScene() { + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + Scene::freeScene(); + + // Delete any scene overlays that were used by the scene + delete ui._mask; + delete ui._mask1; + ui._mask = ui._mask1 = nullptr; +} + void TattooScene::doBgAnimCheckCursor() { Events &events = *_vm->_events; - UserInterface &ui = *_vm->_ui; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; Common::Point mousePos = events.mousePos(); // If we're in Look Mode, make sure the cursor is the magnifying glass @@ -77,7 +323,7 @@ void TattooScene::doBgAnimCheckCursor() { if (events.getCursor() == ARROW || events.getCursor() >= EXIT_ZONES_START) { CursorId cursorId = ARROW; - if (ui._menuMode == STD_MODE && _arrowZone != -1 && _currentScene != 90) { + if (ui._menuMode == STD_MODE && ui._arrowZone != -1 && _currentScene != 90) { for (uint idx = 0; idx < _exits.size(); ++idx) { Exit &exit = _exits[idx]; if (exit.contains(mousePos)) @@ -86,151 +332,80 @@ void TattooScene::doBgAnimCheckCursor() { } events.setCursor(cursorId); - } -} - -void TattooScene::doBgAnimEraseBackground() { - TattooEngine &vm = *((TattooEngine *)_vm); - People &people = *_vm->_people; - Screen &screen = *_vm->_screen; - TattooUserInterface &ui = *((TattooUserInterface *)_vm->_ui); - - static const int16 OFFSETS[16] = { -1, -2, -3, -3, -2, -1, -1, 0, 1, 2, 3, 3, 2, 1, 0, 0 }; - - if (_mask != nullptr) { - if (screen._backBuffer1.w() > screen.w()) - screen.blitFrom(screen._backBuffer1, Common::Point(0, 0), Common::Rect(screen._currentScroll, 0, - screen._currentScroll + screen.w(), screen.h())); - else - screen.blitFrom(screen._backBuffer1); - - switch (_currentScene) { - case 7: - if (++_maskCounter == 2) { - _maskCounter = 0; - if (--_maskOffset.x < 0) - _maskOffset.x = SHERLOCK_SCREEN_WIDTH - 1; - } - break; - - case 8: - _maskOffset.x += 2; - if (_maskOffset.x >= SHERLOCK_SCREEN_WIDTH) - _maskOffset.x = 0; - break; - - case 18: - case 68: - ++_maskCounter; - if (_maskCounter / 4 >= 16) - _maskCounter = 0; - - _maskOffset.x = OFFSETS[_maskCounter / 4]; - break; - - case 53: - if (++_maskCounter == 2) { - _maskCounter = 0; - if (++_maskOffset.x == screen._backBuffer1.w()) - _maskOffset.x = 0; - } - break; - - default: - break; - } } else { - // Standard scene without mask, so call user interface to erase any UI elements as necessary - ui.doBgAnimRestoreUI(); - - // Restore background for any areas covered by characters and shapes - for (uint idx = 0; idx < MAX_CHARACTERS; ++idx) - screen.restoreBackground(Common::Rect(people[idx]._oldPosition.x, people[idx]._oldPosition.y, - people[idx]._oldPosition.x + people[idx]._oldSize.x, people[idx]._oldPosition.y + people[idx]._oldSize.y)); - - for (uint idx = 0; idx < _bgShapes.size(); ++idx) { - Object &obj = _bgShapes[idx]; - - if ((obj._type == ACTIVE_BG_SHAPE && (obj._maxFrames > 1 || obj._delta.x != 0 || obj._delta.y != 0)) || - obj._type == HIDE_SHAPE || obj._type == REMOVE) - screen._backBuffer1.blitFrom(*obj._imageFrame, obj._oldPosition, - Common::Rect(obj._oldPosition.x, obj._oldPosition.y, obj._oldPosition.x + obj._oldSize.x, - obj._oldPosition.y + obj._oldSize.y)); - } - - // If credits are active, erase the area they cover - if (vm._creditsActive) - vm.eraseCredits(); + events.animateCursorIfNeeded(); } - - for (uint idx = 0; idx < _bgShapes.size(); ++idx) { - Object &obj = _bgShapes[idx]; - - if (obj._type == NO_SHAPE && (obj._flags & 1) == 0) { - screen._backBuffer1.blitFrom(screen._backBuffer2, obj._position, obj.getNoShapeBounds()); - - obj._oldPosition = obj._position; - obj._oldSize = obj._noShapeSize; - } - } - - // Adjust the Target Scroll if needed - if ((people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER - screen._currentScroll) < - (SHERLOCK_SCREEN_WIDTH / 8) && people[people._walkControl]._delta.x < 0) { - - screen._targetScroll = (short)(people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER - - SHERLOCK_SCREEN_WIDTH / 8 - 250); - if (screen._targetScroll < 0) - screen._targetScroll = 0; - } - - if ((people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER - screen._currentScroll) > (SHERLOCK_SCREEN_WIDTH / 4 * 3) - && people[people._walkControl]._delta.x > 0) - screen._targetScroll = (short)(people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER - - SHERLOCK_SCREEN_WIDTH / 4 * 3 + 250); - - if (screen._targetScroll > screen._scrollSize) - screen._targetScroll = screen._scrollSize; - - ui.doScroll(); } void TattooScene::doBgAnim() { + TattooEngine &vm = *(TattooEngine *)_vm; + Events &events = *_vm->_events; + TattooPeople &people = *(TattooPeople *)_vm->_people; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; TattooUserInterface &ui = *((TattooUserInterface *)_vm->_ui); doBgAnimCheckCursor(); -// Events &events = *_vm->_events; - People &people = *_vm->_people; -// Scene &scene = *_vm->_scene; - Screen &screen = *_vm->_screen; - Talk &talk = *_vm->_talk; - - screen.setDisplayBounds(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT)); talk._talkToAbort = false; // Check the characters and sprites for updates - for (uint idx = 0; idx < MAX_CHARACTERS; ++idx) { + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { if (people[idx]._type == CHARACTER) people[idx].checkSprite(); } - + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE) _bgShapes[idx].checkObject(); } + // If one of the objects has signalled a call to a talk file, to go to another scene, exit immediately + if (_goToScene != -1) + return; + // Erase any affected background areas - doBgAnimEraseBackground(); + ui.doBgAnimEraseBackground(); doBgAnimUpdateBgObjectsAndAnim(); + doBgAnimDrawSprites(); + ui.drawInterface(); + + if (ui._creditsWidget.active()) + ui._creditsWidget.blitCredits(); + + if (screen._flushScreen) { + screen.slamArea(screen._currentScroll.x, screen._currentScroll.y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + screen._flushScreen = false; + } + + screen._flushScreen = false; + _doBgAnimDone = true; + ui._drawMenu = false; + + // Handle drawing tooltips + if (ui._menuMode == STD_MODE || ui._menuMode == LAB_MODE) + ui._tooltipWidget.draw(); + if (!ui._postRenderWidgets.empty()) { + for (WidgetList::iterator i = ui._postRenderWidgets.begin(); i != ui._postRenderWidgets.end(); ++i) + (*i)->draw(); + ui._postRenderWidgets.clear(); + } + + if (!vm._fastMode) + events.wait(3); + + for (int idx = 1; idx < MAX_CHARACTERS; ++idx) { + if (people[idx]._updateNPCPath) + people[idx].updateNPC(); + } } void TattooScene::doBgAnimUpdateBgObjectsAndAnim() { People &people = *_vm->_people; - Screen &screen = *_vm->_screen; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; for (uint idx = 0; idx < _bgShapes.size(); ++idx) { Object &obj = _bgShapes[idx]; @@ -238,102 +413,31 @@ void TattooScene::doBgAnimUpdateBgObjectsAndAnim() { obj.adjustObject(); } - for (uint idx = 0; idx < MAX_CHARACTERS; ++idx) { + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { if (people[idx]._type == CHARACTER) people[idx].adjustSprite(); } - if ((_activeCAnim._images != nullptr) && (_activeCAnim._zPlacement != REMOVE)) { - _activeCAnim.getNextFrame(); - } - // Flag the bg shapes which need to be redrawn checkBgShapes(); drawAllShapes(); - - if (_mask != nullptr) { - switch (_currentScene) { - case 7: - screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 110), screen._currentScroll); - screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x, 110), screen._currentScroll); - screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x + SHERLOCK_SCREEN_WIDTH, 110), screen._currentScroll); - break; - - case 8: - screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 180), screen._currentScroll); - screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x, 180), screen._currentScroll); - screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x + SHERLOCK_SCREEN_WIDTH, 180), screen._currentScroll); - if (!_vm->readFlags(880)) - screen._backBuffer1.maskArea((*_mask1)[0], Common::Point(940, 300), screen._currentScroll); - break; - - case 18: - screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x, 203), screen._currentScroll); - if (!_vm->readFlags(189)) - screen._backBuffer1.maskArea((*_mask1)[0], Common::Point(124 + _maskOffset.x, 239), screen._currentScroll); - break; - - case 53: - screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x, 110), screen._currentScroll); - screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 110), screen._currentScroll); - break; - - case 68: - screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x, 203), screen._currentScroll); - screen._backBuffer1.maskArea((*_mask1)[0], Common::Point(124 + _maskOffset.x, 239), screen._currentScroll); - break; - } - } + ui.drawMaskArea(true); } - void TattooScene::updateBackground() { - People &people = *_vm->_people; + TattooPeople &people = *(TattooPeople *)_vm->_people; Screen &screen = *_vm->_screen; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; Scene::updateBackground(); - if (_mask != nullptr) { - switch (_currentScene) { - case 7: - screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 110), screen._currentScroll); - screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x, 110), screen._currentScroll); - screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x + SHERLOCK_SCREEN_WIDTH, 110), screen._currentScroll); - break; - - case 8: - screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 180), screen._currentScroll); - screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x, 180), screen._currentScroll); - screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x + SHERLOCK_SCREEN_WIDTH, 180), screen._currentScroll); - if (!_vm->readFlags(880)) - screen._backBuffer1.maskArea((*_mask1)[0], Common::Point(940, 300), screen._currentScroll); - break; - - case 18: - screen._backBuffer1.maskArea((*_mask)[0], Common::Point(0, 203), screen._currentScroll); - if (!_vm->readFlags(189)) - screen._backBuffer1.maskArea((*_mask1)[0], Common::Point(124, 239), screen._currentScroll); - break; - - case 53: - screen._backBuffer1.maskArea((*_mask)[0], Common::Point(_maskOffset.x, 110), screen._currentScroll); - break; - - case 68: - screen._backBuffer1.maskArea((*_mask)[0], Common::Point(0, 203), screen._currentScroll); - screen._backBuffer1.maskArea((*_mask1)[0], Common::Point(124, 239), screen._currentScroll); - break; - - default: - break; - } - } + ui.drawMaskArea(false); screen._flushScreen = true; for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { - Person &p = people[idx]; + TattooPerson &p = people[idx]; if (p._type != INVALID) { if (_goToScene == -1 || _cAnim.size() == 0) { @@ -341,7 +445,7 @@ void TattooScene::updateBackground() { screen.slamArea(p._oldPosition.x, p._oldPosition.y, p._oldSize.x, p._oldSize.y); p._type = INVALID; } else { - if (p._tempScaleVal == 256) { + if (p._tempScaleVal == SCALE_THRESHOLD) { screen.flushImage(p._imageFrame, Common::Point(p._tempX, p._position.y / FIXED_INT_MULTIPLIER - p._imageFrame->_width), &p._oldPosition.x, &p._oldPosition.y, &p._oldSize.x, &p._oldSize.y); } else { @@ -360,7 +464,7 @@ void TattooScene::updateBackground() { if (obj._type == ACTIVE_BG_SHAPE || obj._type == REMOVE) { if (_goToScene == -1) { - if (obj._scaleVal == 256) + if (obj._scaleVal == SCALE_THRESHOLD) screen.flushImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y, &obj._oldSize.x, &obj._oldSize.y); else @@ -381,7 +485,7 @@ void TattooScene::updateBackground() { screen.slamRect(obj.getNoShapeBounds()); screen.slamRect(obj.getOldBounds()); } else if (obj._type == HIDE_SHAPE) { - if (obj._scaleVal == 256) + if (obj._scaleVal == SCALE_THRESHOLD) screen.flushImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y, &obj._oldSize.x, &obj._oldSize.y); else @@ -395,6 +499,360 @@ void TattooScene::updateBackground() { screen._flushScreen = false; } +void TattooScene::doBgAnimDrawSprites() { + TattooPeople &people = *(TattooPeople *)_vm->_people; + Screen &screen = *_vm->_screen; + + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { + TattooPerson &person = people[idx]; + + if (person._type != INVALID) { + if (_goToScene == -1 || _cAnim.size() == 0) { + if (person._type == REMOVE) { + screen.slamRect(person.getOldBounds()); + person._type = INVALID; + } else { + if (person._tempScaleVal == SCALE_THRESHOLD) { + screen.flushImage(person._imageFrame, Common::Point(person._tempX, person._position.y / FIXED_INT_MULTIPLIER + - person.frameHeight()), &person._oldPosition.x, &person._oldPosition.y, &person._oldSize.x, &person._oldSize.y); + } else { + int ts = person._imageFrame->sDrawYSize(person._tempScaleVal); + int ty = person._position.y / FIXED_INT_MULTIPLIER - ts; + screen.flushScaleImage(person._imageFrame, Common::Point(person._tempX, ty), + &person._oldPosition.x, &person._oldPosition.y, &person._oldSize.x, &person._oldSize.y, person._tempScaleVal); + } + } + } + } + } + + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &obj = _bgShapes[idx]; + + if (obj._type == ACTIVE_BG_SHAPE || obj._type == REMOVE) { + if (_goToScene == -1) { + if (obj._scaleVal == SCALE_THRESHOLD) + screen.flushImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y, + &obj._oldSize.x, &obj._oldSize.y); + else + screen.flushScaleImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y, + &obj._oldSize.x, &obj._oldSize.y, obj._scaleVal); + + if (obj._type == REMOVE) + obj._type = INVALID; + } + } + } + + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &obj = _bgShapes[idx]; + + if (_goToScene == -1) { + if (obj._type == NO_SHAPE && (obj._flags & 1) == 0) { + screen.slamRect(obj.getNoShapeBounds()); + screen.slamRect(obj.getOldBounds()); + } else if (obj._type == HIDE_SHAPE) { + if (obj._scaleVal == SCALE_THRESHOLD) + screen.flushImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y, + &obj._oldSize.x, &obj._oldSize.y); + else + screen.flushScaleImage(obj._imageFrame, obj._position, &obj._oldPosition.x, &obj._oldPosition.y, + &obj._oldSize.x, &obj._oldSize.y, obj._scaleVal); + obj._type = HIDDEN; + } + } + } + + if (_activeCAnim.active() || _activeCAnim._zPlacement == REMOVE) { + if (_activeCAnim._zPlacement != REMOVE) { + screen.flushImage(&_activeCAnim._imageFrame, _activeCAnim._position, _activeCAnim._oldBounds, _activeCAnim._scaleVal); + } else { + screen.slamRect(_activeCAnim._removeBounds); + _activeCAnim._removeBounds = Common::Rect(0, 0, 0, 0); + _activeCAnim._zPlacement = -1; // Reset _zPlacement so we don't REMOVE again + } + } +} + +int TattooScene::getScaleVal(const Point32 &pt) { + bool found = false; + int result = SCALE_THRESHOLD; + Common::Point pos(pt.x / FIXED_INT_MULTIPLIER, pt.y / FIXED_INT_MULTIPLIER); + + for (uint idx = 0; idx < _scaleZones.size() && !found; ++idx) { + ScaleZone &sz = _scaleZones[idx]; + if (sz.contains(pos)) { + int n = (sz._bottomNumber - sz._topNumber) * 100 / sz.height() * (pos.y - sz.top) / 100 + sz._topNumber; + result = 25600L / n; + // CHECKME: Shouldn't we set 'found' at this place? + } + } + + // If it wasn't found, we may be off screen to the left or right, so find the scale zone + // that would apply to the y val passed in disregarding the x + if (!found) { + for (uint idx = 0; idx < _scaleZones.size() && !found; ++idx) { + ScaleZone &sz = _scaleZones[idx]; + if (pos.y >= sz.top && pos.y < sz.bottom) { + int n = (sz._bottomNumber - sz._topNumber) * 100 / sz.height() * (pos.y - sz.top) / 100 + sz._topNumber; + result = 25600L / n; + } + } + } + + return result; +} + +int TattooScene::startCAnim(int cAnimNum, int playRate) { + TattooEngine &vm = *(TattooEngine *)_vm; + Events &events = *_vm->_events; + TattooPeople &people = *(TattooPeople *)_vm->_people; + Resources &res = *_vm->_res; + Talk &talk = *_vm->_talk; + UserInterface &ui = *_vm->_ui; + + // Exit immediately if the anim number is out of range, or the anim doesn't have a position specified + if (cAnimNum < 0 || cAnimNum >= (int)_cAnim.size() || _cAnim[cAnimNum]._position.x == -1) + // Return out of range error + return -1; + + // Get the co-ordinates that the Player & NPC #1 must walk to and end on + CAnim &cAnim = _cAnim[cAnimNum]; + PositionFacing goto1 = cAnim._goto[0]; + PositionFacing goto2 = cAnim._goto[1]; + PositionFacing teleport1 = cAnim._teleport[0]; + PositionFacing teleport2 = cAnim._teleport[1]; + + // See if the Player must walk to a position before the animation starts + SpriteType savedPlayerType = people[HOLMES]._type; + if (goto1.x != -1 && people[HOLMES]._type == CHARACTER) { + if (people[HOLMES]._position != goto1) + people[HOLMES].walkToCoords(goto1, goto1._facing); + } + + if (talk._talkToAbort) + return 1; + + // See if NPC #1 must walk to a position before the animation starts + SpriteType savedNPCType = people[WATSON]._type; + if (goto2.x != -1 && people[WATSON]._type == CHARACTER) { + if (people[WATSON]._position != goto2) + people[WATSON].walkToCoords(goto2, goto2._facing); + } + + if (talk._talkToAbort) + return 1; + + // Turn the player (and NPC #1 if neccessary) off before running the canimation + if (teleport1.x != -1 && savedPlayerType == CHARACTER) + people[HOLMES]._type = REMOVE; + + if (teleport2.x != -1 && savedNPCType == CHARACTER) + people[WATSON]._type = REMOVE; + + if (ui._windowOpen) + ui.banishWindow(); + + //_activeCAnim._filesize = cAnim._size; + + // Open up the room resource file and get the data for the animation + Common::SeekableReadStream *stream = res.load(_roomFilename); + stream->seek(44 + cAnimNum * 4); + stream->seek(stream->readUint32LE()); + Common::SeekableReadStream *animStream = stream->readStream(cAnim._dataSize); + delete stream; + + // Set up the active animation + _activeCAnim._position = cAnim._position; + _activeCAnim._oldBounds = Common::Rect(0, 0, 0, 0); + _activeCAnim._flags = cAnim._flags; + _activeCAnim._scaleVal = cAnim._scaleVal; + _activeCAnim._zPlacement = 0; + + _activeCAnim.load(animStream, _compressed); + + while (!_vm->shouldQuit()) { + // Get the next frame + if (!_activeCAnim.getNextFrame()) + break; + + // Draw the frame + doBgAnim(); + + // Check for Escape key being pressed to abort animation + events.pollEvents(); + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + + if (keyState.keycode == Common::KEYCODE_ESCAPE && vm._runningProlog) { + _vm->setFlags(-76); + _vm->setFlags(396); + _goToScene = STARTING_GAME_SCENE; + talk._talkToAbort = true; + _activeCAnim.close(); + } + } + } + + // Turn the people back on + people[HOLMES]._type = savedPlayerType; + if (teleport2.x != -1) + people[WATSON]._type = savedNPCType; + + // Teleport the Player to the ending coordinates if necessary + if (teleport1.x != -1 && savedPlayerType == CHARACTER) { + people[HOLMES]._position = teleport1; + people[HOLMES]._sequenceNumber = teleport1._facing; + people[HOLMES].gotoStand(); + } + + // Teleport Watson to the ending coordinates if necessary + if (teleport2.x != -1 && savedNPCType == CHARACTER) { + people[WATSON]._position = teleport2; + people[WATSON]._sequenceNumber = teleport2._facing; + people[WATSON].gotoStand(); + } + + // Flag the Canimation to be cleared + _activeCAnim._zPlacement = REMOVE; + _activeCAnim._removeBounds = _activeCAnim._oldBounds; + + // Free up the animation + _activeCAnim.close(); + + return 1; +} + +void TattooScene::setNPCPath(int npc) { + TattooPeople &people = *(TattooPeople *)_vm->_people; + SaveManager &saves = *_vm->_saves; + Talk &talk = *_vm->_talk; + + // Don't do initial scene setup if a savegame has just been loaded + if (saves._justLoaded) + return; + + people[npc].clearNPC(); + people[npc]._npcName = Common::String::format("WATS%.2dA", _currentScene); + + // If we're in the middle of a script that will continue once the scene is loaded, + // return without calling the path script + if (talk._scriptMoreFlag == 1 || talk._scriptMoreFlag == 3) + return; + + // Turn off all the NPCs, since the talk script will turn them back on as needed + for (int idx = 1; idx < MAX_CHARACTERS; ++idx) + people[idx]._type = INVALID; + + // Call the path script for the scene + Common::String pathFile = Common::String::format("PATH%.2dA", _currentScene); + talk.talkTo(pathFile); +} + +int TattooScene::findBgShape(const Common::Point &pt) { + People &people = *_vm->_people; + UserInterface &ui = *_vm->_ui; + + if (!_doBgAnimDone) + // New frame hasn't been drawn yet + return -1; + + int result = -1; + for (int idx = (int)_bgShapes.size() - 1; idx >= 0 && result == -1; --idx) { + Object &o = _bgShapes[idx]; + + if (o._type != INVALID && o._type != NO_SHAPE && o._type != HIDDEN && + (o._aType <= PERSON || (ui._menuMode == LAB_MODE && o._aType == SOLID))) { + if (o.getNewBounds().contains(pt)) + result = idx; + } else if (o._type == NO_SHAPE) { + if (o.getNoShapeBounds().contains(pt)) + result = idx; + } + } + + // Now check for the mouse being over an NPC. If so, it overrides any found bg object + for (int idx = 1; idx < MAX_CHARACTERS; ++idx) { + Person &person = people[idx]; + + if (person._type == CHARACTER) { + int scaleVal = getScaleVal(person._position); + Common::Rect charRect; + + if (scaleVal == SCALE_THRESHOLD) + charRect = Common::Rect(person.frameWidth(), person.frameHeight()); + else + charRect = Common::Rect(person._imageFrame->sDrawXSize(scaleVal), person._imageFrame->sDrawYSize(scaleVal)); + charRect.moveTo(person._position.x / FIXED_INT_MULTIPLIER, person._position.y / FIXED_INT_MULTIPLIER + - charRect.height()); + + if (charRect.contains(pt)) + result = 1000 + idx; + } + } + + return result; +} + +void TattooScene::synchronize(Serializer &s) { + TattooEngine &vm = *(TattooEngine *)_vm; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + Scene::synchronize(s); + + if (s.isLoading()) { + // In case we were showing the intro prologue or the ending credits, stop them + vm._runningProlog = false; + ui._creditsWidget.close(); + } +} + +int TattooScene::closestZone(const Common::Point &pt) { + int zone = -1; + int dist = 9999; + int d; + + for (uint idx = 0; idx < _zones.size(); ++idx) { + Common::Rect &r = _zones[idx]; + + // Check the distance from the point to the center of the zone + d = ABS(r.left + (r.width() / 2) - pt.x) + ABS(r.top + (r.height() / 2) - pt.y); + if (d < dist) { + dist = d; + zone = idx; + } + + // Check the distance from the point to the upper left of the zone + d = ABS((int)(r.left - pt.x)) + ABS((int)(r.top - pt.y)); + if (d < dist) + { + dist = d; + zone = idx; + } + + // Check the distance from the point to the upper right of the zone + d = ABS(r.left + r.width() - pt.x) + ABS(r.top - pt.y); + if (d < dist) { + dist = d; + zone = idx; + } + + // Check the distance from the point to the lower left of the zone + d = ABS(r.left - pt.x) + ABS(r.top + r.height() - pt.y); + if (d < dist) { + dist = d; + zone = idx; + } + + // Check the distance from the point to the lower right of the zone + d = ABS(r.left + r.width() - pt.x) + ABS(r.top + r.height() - pt.y); + if (d < dist) { + dist = d; + zone = idx; + } + } + + return zone; +} } // End of namespace Tattoo diff --git a/engines/sherlock/tattoo/tattoo_scene.h b/engines/sherlock/tattoo/tattoo_scene.h index de28306c1b..678330077c 100644 --- a/engines/sherlock/tattoo/tattoo_scene.h +++ b/engines/sherlock/tattoo/tattoo_scene.h @@ -25,39 +25,104 @@ #include "common/scummsys.h" #include "sherlock/scene.h" +#include "sherlock/tattoo/widget_lab.h" namespace Sherlock { namespace Tattoo { +extern const int FS_TRANS[8]; + +enum { + STARTING_GAME_SCENE = 1, WEARY_PUNT = 52, TRAIN_RIDE = 69, STARTING_INTRO_SCENE = 91, OVERHEAD_MAP2 = 90, OVERHEAD_MAP = 100 +}; + +struct SceneTripEntry { + int _flag; + int _sceneNumber; + int _numTimes; + + SceneTripEntry() : _flag(0), _sceneNumber(0), _numTimes(0) {} + SceneTripEntry(int flag, int sceneNumber, int numTimes) : _flag(flag), + _sceneNumber(sceneNumber), _numTimes(numTimes) {} +}; + class TattooScene : public Scene { private: - int _arrowZone; - int _maskCounter; - Common::Point _maskOffset; -private: - void doBgAnimCheckCursor(); + WidgetLab _labWidget; - void doBgAnimEraseBackground(); + void doBgAnimCheckCursor(); /** * Update the background objects and canimations as part of doBgAnim */ void doBgAnimUpdateBgObjectsAndAnim(); + + void doBgAnimDrawSprites(); + + /** + * Resets the NPC path information when entering a new scene. + * @remarks The default talk file for the given NPC is set to WATS##A, where ## is + * the scene number being entered + */ + void setNPCPath(int npc); protected: /** + * Loads the data associated for a given scene. The room resource file's format is: + * BGHEADER: Holds an index for the rest of the file + * STRUCTS: The objects for the scene + * IMAGES: The graphic information for the structures + * + * The _misc field of the structures contains the number of the graphic image + * that it should point to after loading; _misc is then set to 0. + */ + virtual bool loadScene(const Common::String &filename); + + /** * Checks all the background shapes. If a background shape is animating, * it will flag it as needing to be drawn. If a non-animating shape is * colliding with another shape, it will also flag it as needing drawing */ virtual void checkBgShapes(); + + /** + * Draw all the shapes, people and NPCs in the correct order + */ + virtual void drawAllShapes(); + + /** + * Called by loadScene when the palette is loaded for Rose Tattoo + */ + virtual void paletteLoaded(); + + /** + * Synchronize the data for a savegame + */ + virtual void synchronize(Serializer &s); + + /** + * Returns the index of the closest zone to a given point. + */ + virtual int closestZone(const Common::Point &pt); public: - ImageFile *_mask, *_mask1; - CAnimStream _activeCAnim; + StreamingImageFile _activeCAnim; + Common::Array<SceneTripEntry> _sceneTripCounters; + bool _labTableScene; public: TattooScene(SherlockEngine *vm); /** + * Returns the scale value for the passed co-ordinates. This is taken from the scene's + * scale zones, interpolating inbetween the top and bottom values of the zones as needed + */ + int getScaleVal(const Point32 &pt); + + /** + * Fres all the graphics and other dynamically allocated data for the scene + */ + virtual void freeScene(); + + /** * Draw all objects and characters. */ virtual void doBgAnim(); @@ -68,6 +133,21 @@ public: */ virtual void updateBackground(); + /** + * Attempt to start a canimation sequence. It will load the requisite graphics, and + * then copy the canim object into the _canimShapes array to start the animation. + * + * @param cAnimNum The canim object within the current scene + * @param playRate Play rate. 0 is invalid; 1=normal speed, 2=1/2 speed, etc. + * A negative playRate can also be specified to play the animation in reverse + */ + virtual int startCAnim(int cAnimNum, int playRate = 1); + + /** + * Attempts to find a background shape within the passed bounds. If found, + * it will return the shape number, or -1 on failure. + */ + virtual int findBgShape(const Common::Point &pt); }; } // End of namespace Tattoo diff --git a/engines/sherlock/tattoo/tattoo_talk.cpp b/engines/sherlock/tattoo/tattoo_talk.cpp new file mode 100644 index 0000000000..a4ceca042b --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_talk.cpp @@ -0,0 +1,1013 @@ +/* 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 "sherlock/tattoo/tattoo_talk.h" +#include "sherlock/tattoo/tattoo_fixed_text.h" +#include "sherlock/tattoo/tattoo_people.h" +#include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo_user_interface.h" +#include "sherlock/tattoo/tattoo.h" +#include "sherlock/screen.h" + +namespace Sherlock { + +namespace Tattoo { + +static const uint8 DIRECTION_CONVERSION[] = { + WALK_RIGHT, WALK_DOWN, WALK_LEFT, WALK_UP, STOP_RIGHT, STOP_DOWN, STOP_LEFT, STOP_UP, + WALK_UPRIGHT, WALK_DOWNRIGHT, WALK_UPLEFT, WALK_DOWNLEFT, STOP_UPRIGHT, STOP_UPLEFT, + STOP_DOWNRIGHT, STOP_DOWNLEFT +}; + +const byte TATTOO_OPCODES[] = { + 170, // OP_SWITCH_SPEAKER + 171, // OP_RUN_CANIMATION + 0, // OP_ASSIGN_PORTRAIT_LOCATION + 173, // OP_PAUSE + 0, // OP_REMOVE_PORTRAIT + 0, // OP_CLEAR_WINDOW + 176, // OP_ADJUST_OBJ_SEQUENCE + 177, // OP_WALK_HOlMES_TO_COORDS + 178, // OP_PAUSE_WITHOUT_CONTROL + 179, // OP_BANISH_WINDOW + 0, // OP_SUMMON_WINDOW + 181, // OP_SET_FLAG + 0, // OP_SFX_COMMAND + 183, // OP_TOGGLE_OBJECT + 184, // OP_STEALTH_MODE_ACTIVE + 0, // OP_IF_STATEMENT + 0, // OP_ELSE_STATEMENT + 0, // OP_END_IF_STATEMENT + 188, // OP_STEALTH_MODE_DEACTIVATE + 189, // OP_TURN_HOLMES_OFF + 190, // OP_TURN_HOLMES_ON + 191, // OP_GOTO_SCENE + 0, // OP_PLAY_PROLOGUE + 193, // OP_ADD_ITEM_TO_INVENTORY + 194, // OP_SET_OBJECT + 172, // OP_CALL_TALK_FILE + 0, // OP_MOVE_MOUSE + 0, // OP_DISPLAY_INFO_LINE + 0, // OP_CLEAR_INFO_LINE + 199, // OP_WALK_TO_CANIMATION + 200, // OP_REMOVE_ITEM_FROM_INVENTORY + 201, // OP_ENABLE_END_KEY + 202, // OP_DISABLE_END_KEY + 203, // OP_END_TEXT_WINDOW + 174, // OP_MOUSE_ON_OFF + 175, // OP_SET_WALK_CONTROL + 180, // OP_SET_TALK_SEQUENCE + 182, // OP_PLAY_SONG + 187, // OP_WALK_HOLMES_AND_NPC_TO_CANIM + 192, // OP_SET_NPC_PATH_DEST + 195, // OP_NEXT_SONG + 196, // OP_SET_NPC_PATH_PAUSE + 197, // OP_PASSWORD + 198, // OP_SET_SCENE_ENTRY_FLAG + 185, // OP_WALK_NPC_TO_CANIM + 186, // OP_WALK_NPC_TO_COORDS + 204, // OP_WALK_HOLMES_AND_NPC_TO_COORDS + 205, // OP_SET_NPC_TALK_FILE + 206, // OP_TURN_NPC_OFF + 207, // OP_TURN_NPC_ON + 208, // OP_NPC_DESC_ON_OFF + 209, // OP_NPC_PATH_PAUSE_TAKING_NOTES + 210, // OP_NPC_PATH_PAUSE_LOOKING_HOLMES + 211, // OP_ENABLE_TALK_INTERRUPTS + 212, // OP_DISABLE_TALK_INTERRUPTS + 213, // OP_SET_NPC_INFO_LINE + 214, // OP_SET_NPC_POSITION + 215, // OP_NPC_PATH_LABEL + 216, // OP_PATH_GOTO_LABEL + 217, // OP_PATH_IF_FLAG_GOTO_LABEL + 218, // OP_NPC_WALK_GRAPHICS + 220, // OP_NPC_VERB + 221, // OP_NPC_VERB_CANIM + 222, // OP_NPC_VERB_SCRIPT + 224, // OP_RESTORE_PEOPLE_SEQUENCE + 226, // OP_NPC_VERB_TARGET + 227, // OP_TURN_SOUNDS_OFF + 225 // OP_NULL +}; + +/*----------------------------------------------------------------*/ + +TattooTalk::TattooTalk(SherlockEngine *vm) : Talk(vm), _talkWidget(vm), _passwordWidget(vm) { + static OpcodeMethod OPCODE_METHODS[] = { + (OpcodeMethod)&TattooTalk::cmdSwitchSpeaker, + + (OpcodeMethod)&TattooTalk::cmdRunCAnimation, + (OpcodeMethod)&TattooTalk::cmdCallTalkFile, + (OpcodeMethod)&TattooTalk::cmdPause, + (OpcodeMethod)&TattooTalk::cmdMouseOnOff, + (OpcodeMethod)&TattooTalk::cmdSetWalkControl, + (OpcodeMethod)&TattooTalk::cmdAdjustObjectSequence, + (OpcodeMethod)&TattooTalk::cmdWalkHolmesToCoords, + (OpcodeMethod)&TattooTalk::cmdPauseWithoutControl, + (OpcodeMethod)&TattooTalk::cmdBanishWindow, + (OpcodeMethod)&TattooTalk::cmdSetTalkSequence, + + (OpcodeMethod)&TattooTalk::cmdSetFlag, + (OpcodeMethod)&TattooTalk::cmdPlaySong, + (OpcodeMethod)&TattooTalk::cmdToggleObject, + (OpcodeMethod)&TattooTalk::cmdStealthModeActivate, + (OpcodeMethod)&TattooTalk::cmdWalkNPCToCAnimation, + (OpcodeMethod)&TattooTalk::cmdWalkNPCToCoords, + (OpcodeMethod)&TattooTalk::cmdWalkHomesAndNPCToCoords, + (OpcodeMethod)&TattooTalk::cmdStealthModeDeactivate, + (OpcodeMethod)&TattooTalk::cmdHolmesOff, + (OpcodeMethod)&TattooTalk::cmdHolmesOn, + + (OpcodeMethod)&TattooTalk::cmdGotoScene, + (OpcodeMethod)&TattooTalk::cmdSetNPCPathDest, + (OpcodeMethod)&TattooTalk::cmdAddItemToInventory, + (OpcodeMethod)&TattooTalk::cmdSetObject, + (OpcodeMethod)&TattooTalk::cmdNextSong, + (OpcodeMethod)&TattooTalk::cmdSetNPCPathPause, + (OpcodeMethod)&TattooTalk::cmdPassword, + (OpcodeMethod)&TattooTalk::cmdSetSceneEntryFlag, + (OpcodeMethod)&TattooTalk::cmdWalkToCAnimation, + (OpcodeMethod)&TattooTalk::cmdRemoveItemFromInventory, + + (OpcodeMethod)&TattooTalk::cmdEnableEndKey, + (OpcodeMethod)&TattooTalk::cmdDisableEndKey, + (OpcodeMethod)&TattooTalk::cmdEndTextWindow, + (OpcodeMethod)&TattooTalk::cmdWalkHomesAndNPCToCoords, + (OpcodeMethod)&TattooTalk::cmdSetNPCTalkFile, + (OpcodeMethod)&TattooTalk::cmdSetNPCOff, + (OpcodeMethod)&TattooTalk::cmdSetNPCOn, + (OpcodeMethod)&TattooTalk::cmdSetNPCDescOnOff, + (OpcodeMethod)&TattooTalk::cmdSetNPCPathPauseTakingNotes, + (OpcodeMethod)&TattooTalk::cmdSetNPCPathPauseLookingHolmes, + + (OpcodeMethod)&TattooTalk::cmdTalkInterruptsEnable, + (OpcodeMethod)&TattooTalk::cmdTalkInterruptsDisable, + (OpcodeMethod)&TattooTalk::cmdSetNPCInfoLine, + (OpcodeMethod)&TattooTalk::cmdSetNPCPosition, + (OpcodeMethod)&TattooTalk::cmdNPCLabelSet, + (OpcodeMethod)&TattooTalk::cmdNPCLabelGoto, + (OpcodeMethod)&TattooTalk::cmdNPCLabelIfFlagGoto, + (OpcodeMethod)&TattooTalk::cmdSetNPCWalkGraphics, + nullptr, + (OpcodeMethod)&TattooTalk::cmdSetNPCVerb, + + (OpcodeMethod)&TattooTalk::cmdSetNPCVerbCAnimation, + (OpcodeMethod)&TattooTalk::cmdSetNPCVerbScript, + nullptr, + (OpcodeMethod)&TattooTalk::cmdRestorePeopleSequence, + nullptr, + (OpcodeMethod)&TattooTalk::cmdSetNPCVerbTarget, + (OpcodeMethod)&TattooTalk::cmdTurnSoundsOff + }; + + _opcodes = TATTOO_OPCODES; + _opcodeTable = OPCODE_METHODS; +} + +void TattooTalk::talkTo(const Common::String filename) { + Events &events = *_vm->_events; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + + // WORKAROUND: Keep wait cursor active until very end of the cutscene of the monkey + // stealing the cap, which is finished by calling the 30cuend script + if (filename == "wilb29a") + events.incWaitCounter(); + + Talk::talkTo(filename); + + if (filename == "wilb29a") + ui._menuMode = TALK_MODE; + if (filename == "30cuend") { + events.decWaitCounter(); + events.setCursor(ARROW); + } +} + +void TattooTalk::talkInterface(const byte *&str) { + TattooEngine &vm = *(TattooEngine *)_vm; + Sound &sound = *_vm->_sound; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + const byte *s = str; + + // Move to past the end of the text string + _wait = 1; + _charCount = 0; + while ((*str < TATTOO_OPCODES[0] || *str == TATTOO_OPCODES[OP_NULL]) && *str) { + ++_charCount; + ++str; + } + + // If speech is on, and text windows (subtitles) are off, then don't show the text window + if (!vm._textWindowsOn && sound._speechOn && _speaker != -1) + return; + + // Display the text window + ui.banishWindow(); + ui._textWidget.load(Common::String((const char *)s, (const char *)str), _speaker); + ui._textWidget.summonWindow(); +} + +void TattooTalk::nothingToSay() { + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + ui.putMessage("%s", FIXED(NothingToSay)); +} + +void TattooTalk::showTalk() { + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + + people.setListenSequence(_talkTo, 129); + + _talkWidget.load(); + _talkWidget.summonWindow(); + _talkWidget.refresh(); + + if (ui._menuMode != MESSAGE_MODE) + ui._menuMode = TALK_MODE; +} + +OpcodeReturn TattooTalk::cmdSwitchSpeaker(const byte *&str) { + TattooPeople &people = *(TattooPeople *)_vm->_people; + Screen &screen = *_vm->_screen; + UserInterface &ui = *_vm->_ui; + + if (_talkToAbort) + return RET_EXIT; + + ui.clearWindow(); + + _yp = screen.fontHeight() + 11; + _charCount = _line = 0; + + people.setListenSequence(_speaker, 129); + _speaker = *++str - 1; + ++str; + + people.setTalkSequence(_speaker, 1); + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdMouseOnOff(const byte *&str) { + Events &events = *_vm->_events; + bool mouseOn = *++str == 2; + if (mouseOn) + events.showCursor(); + else + events.hideCursor(); + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdWalkHolmesToCoords(const byte *&str) { + People &people = *_vm->_people; + ++str; + + int xp = (str[0] - 1) * 256 + str[1] - 1; + if (xp > 16384) + // Negative X + xp = -1 * (xp - 16384); + int yp = (str[2] - 1) * 256 + str[3] - 1; + + people[HOLMES].walkToCoords(Point32(xp * FIXED_INT_MULTIPLIER, yp * FIXED_INT_MULTIPLIER), + DIRECTION_CONVERSION[str[4] - 1]); + + if (_talkToAbort) + return RET_EXIT; + + str += 4; + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdGotoScene(const byte *&str) { + Map &map = *_vm->_map; + TattooPeople &people = *(TattooPeople *)_vm->_people; + Scene &scene = *_vm->_scene; + scene._goToScene = str[1] - 1; + + if (scene._goToScene != OVERHEAD_MAP) { + // Not going to the map overview + map._oldCharPoint = scene._goToScene; + + // Run a canimation? + if (str[2] > 100) { + people._savedPos = PositionFacing(160, 100, str[2]); + } else { + int posX = (str[3] - 1) * 256 + str[4] - 1; + if (posX > 16384) + posX = -1 * (posX - 16384); + int posY = (str[5] - 1) * 256 + str[6] - 1; + people._savedPos = PositionFacing(posX, posY, str[2] - 1); + } + + _scriptMoreFlag = 1; + } + + str += 7; + if (scene._goToScene != OVERHEAD_MAP) + _scriptSaveIndex = str - _scriptStart; + + _endStr = true; + _wait = 0; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdNextSong(const byte *&str) { + Music &music = *_vm->_music; + + // Get the name of the next song to play + ++str; + music._nextSongName = ""; + for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx) + music._nextSongName += str[idx]; + str += 7; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdNPCLabelGoto(const byte *&str) { + int npcNum = *++str; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + + if (person._resetNPCPath) { + person._npcIndex = person._npcPause = 0; + person._resetNPCPath = false; + memset(person._npcPath, 0, 100); + } + + person._npcPath[person._npcIndex] = 8; + person._npcPath[person._npcIndex + 1] = str[1]; + person._npcIndex += 2; + str++; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdNPCLabelIfFlagGoto(const byte *&str) { + int npcNum = *++str; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + + if (person._resetNPCPath) { + person._npcIndex = person._npcPause = 0; + person._resetNPCPath = false; + memset(person._npcPath, 0, 100); + } + + person._npcPath[person._npcIndex] = 9; + for (int i = 1; i <= 3; i++) + person._npcPath[person._npcIndex + i] = str[i]; + + person._npcIndex += 4; + str += 3; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdNPCLabelSet(const byte *&str) { + int npcNum = *++str; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + + if (person._resetNPCPath) { + person._npcIndex = person._npcPause = 0; + person._resetNPCPath = false; + memset(person._npcPath, 0, 100); + } + + person._npcPath[person._npcIndex] = 7; + person._npcPath[person._npcIndex + 1] = str[1]; + person._npcIndex += 2; + str++; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdPassword(const byte *&str) { + _vm->_ui->clearWindow(); + _passwordWidget.show(); + return RET_EXIT; +} + +OpcodeReturn TattooTalk::cmdPlaySong(const byte *&str) { + Music &music = *_vm->_music; + Common::String currentSong = music._currentSongName; + + // Get the name of the song to play + music._currentSongName = ""; + str++; + for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx) + music._currentSongName += str[idx]; + str += 7; + + // Play the song + music.loadSong(music._currentSongName); + + // Copy the old song name to _nextSongName so that when the new song is finished, the old song will restart + music._nextSongName = currentSong; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdRestorePeopleSequence(const byte *&str) { + int npcNum = *++str - 1; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + person._misc = 0; + + if (person._seqTo) { + person._walkSequences[person._sequenceNumber]._sequences[person._frameNumber] = person._seqTo; + person._seqTo = 0; + } + person._sequenceNumber = person._savedNpcSequence; + person._frameNumber = person._savedNpcFrame; + person.checkWalkGraphics(); + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCDescOnOff(const byte *&str) { + int npcNum = *++str; + ++str; + TattooPeople &people = *(TattooPeople *)_vm->_people; + Person &person = people[npcNum]; + + // Copy over the NPC examine text until we reach a stop marker, which is + // the same as a start marker, or we reach the end of the file + person._examine = ""; + while (*str && *str != _opcodes[OP_NPC_DESC_ON_OFF]) + person._examine += *str++; + + // Move past any leftover text till we reach a stop marker + while (*str && *str != _opcodes[OP_NPC_DESC_ON_OFF]) + str++; + + if (!*str) + // Reached end of file, so decrement pointer so outer loop will terminate on NULL + --str; + else + // Move past the ending OP_NPC_DEST_ON_OFF opcode + ++str; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCInfoLine(const byte *&str) { + int npcNum = *++str; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + + person._description = ""; + int len = *++str; + for (int idx = 0; idx < len; ++idx) + person._description += str[idx + 1]; + + str += len; + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCOff(const byte *&str) { + TattooPeople &people = *(TattooPeople *)_vm->_people; + int npcNum = *++str; + people[npcNum]._type = REMOVE; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCOn(const byte *&str) { + TattooPeople &people = *(TattooPeople *)_vm->_people; + int npcNum = *++str; + people[npcNum]._type = CHARACTER; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCPathDest(const byte *&str) { + int npcNum = *++str; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + + if (person._resetNPCPath) { + person._npcIndex = person._npcPause = 0; + person._resetNPCPath = false; + memset(person._npcPath, 0, 100); + } + + person._npcPath[person._npcIndex] = 1; + for (int i = 1; i <= 4; i++) + person._npcPath[person._npcIndex + i] = str[i]; + person._npcPath[person._npcIndex + 5] = DIRECTION_CONVERSION[str[5] - 1] + 1; + + person._npcIndex += 6; + str += 5; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCPathPause(const byte *&str) { + int npcNum = *++str; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + + if (person._resetNPCPath) { + person._npcIndex = person._npcPause = 0; + person._resetNPCPath = false; + memset(person._npcPath, 0, 100); + } + + person._npcPath[person._npcIndex] = 2; + for (int i = 1; i <= 2; i++) + person._npcPath[person._npcIndex + i] = str[i]; + + person._npcIndex += 3; + str += 2; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCPathPauseTakingNotes(const byte *&str) { + int npcNum = *++str; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + + if (person._resetNPCPath) { + person._npcIndex = person._npcPause = 0; + person._resetNPCPath = false; + memset(person._npcPath, 0, 100); + } + + person._npcPath[person._npcIndex] = 5; + for (int i = 1; i <= 2; i++) + person._npcPath[person._npcIndex + i] = str[i]; + + person._npcIndex += 3; + str += 2; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCPathPauseLookingHolmes(const byte *&str) { + int npcNum = *++str; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + + if (person._resetNPCPath) { + person._npcIndex = person._npcPause = 0; + person._resetNPCPath = false; + memset(person._npcPath, 0, 100); + } + + person._npcPath[person._npcIndex] = 6; + for (int i = 1; i <= 2; i++) + person._npcPath[person._npcIndex + i] = str[i]; + + person._npcIndex += 3; + str += 2; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCPosition(const byte *&str) { + int npcNum = *++str - 1; + ++str; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + int posX = (str[0] - 1) * 256 + str[1] - 1; + if (posX > 16384) + posX = -1 * (posX - 16384); + int posY = (str[2] - 1) * 256 + str[3] - 1; + + person._position = Point32(posX * FIXED_INT_MULTIPLIER, posY * FIXED_INT_MULTIPLIER); + if (person._seqTo && person._walkLoaded) { + person._walkSequences[person._sequenceNumber]._sequences[person._frameNumber] = person._seqTo; + person._seqTo = 0; + } + + assert(str[4] - 1 < 16); + person._sequenceNumber = DIRECTION_CONVERSION[str[4] - 1]; + person._frameNumber = 0; + + if (person._walkLoaded) + person.checkWalkGraphics(); + + if (person._walkLoaded && person._type == CHARACTER && + person._sequenceNumber >= STOP_UP && person._sequenceNumber <= STOP_UPLEFT) { + bool done = false; + do { + person.checkSprite(); + for (int x = 0; x < person._frameNumber; x++) { + if (person._walkSequences[person._sequenceNumber]._sequences[x] == 0) { + done = true; + break; + } + } + } while (!done); + } + + str += 4; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCTalkFile(const byte *&str) { + int npcNum = *++str; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + + if (person._resetNPCPath) { + person._npcIndex = person._npcPause = 0; + person._resetNPCPath = false; + memset(person._npcPath, 0, 100); + } + + person._npcPath[person._npcIndex] = NPCPATH_SET_TALK_FILE; + for (int i = 1; i <= 8; i++) + person._npcPath[person._npcIndex + i] = str[i]; + + person._npcIndex += 9; + str += 8; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCVerb(const byte *&str) { + int npcNum = *++str; + int verbNum = *++str - 1; + TattooPeople &people = *(TattooPeople *)_vm->_people; + Common::String &verb = people[npcNum]._use[verbNum]._verb; + + // Get the verb name + verb = ""; + for (int idx = 0; idx < 12 && str[idx + 1] != '~'; ++idx) + verb += str[idx + 1]; + + // Strip off any trailing whitespace + while (verb.hasSuffix(" ")) + verb.deleteLastChar(); + + str += 12; + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCVerbCAnimation(const byte *&str) { + int npcNum = *++str; + int verbNum = *++str - 1; + TattooPeople &people = *(TattooPeople *)_vm->_people; + UseType &useType = people[npcNum]._use[verbNum]; + + useType._cAnimNum = (str[1] - 1) & 127; + useType._cAnimSpeed = 1 + 128 * (str[1] >= 128); + str++; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCVerbScript(const byte *&str) { + int npcNum = *++str; + int verbNum = *++str - 1; + TattooPeople &people = *(TattooPeople *)_vm->_people; + UseType &useType = people[npcNum]._use[verbNum]; + + Common::String &name = useType._names[0]; + name = "*C"; + + for (int idx = 0; idx < 8 && str[idx + 1] != '~'; ++idx) + name += str[idx + 1]; + + useType._cAnimNum = 99; + useType._cAnimSpeed = 1; + str += 8; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCVerbTarget(const byte *&str) { + int npcNum = *++str; + int verbNum = *++str - 1; + TattooPeople &people = *(TattooPeople *)_vm->_people; + Common::String &target = people[npcNum]._use[verbNum]._target; + + target = ""; + for (int idx = 0; idx < 12 && str[idx + 1] != '~'; ++idx) + target += str[idx + 1]; + + while (target.hasSuffix(" ")) + target.deleteLastChar(); + + str += 12; + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetNPCWalkGraphics(const byte *&str) { + int npcNum = *++str - 1; + TattooPeople &people = *(TattooPeople *)_vm->_people; + Person &person = people[npcNum]; + + // Build up walk library name for the given NPC + person._walkVGSName = ""; + for (int idx = 0; idx < 8 && str[idx + 1] != '~'; ++idx) + person._walkVGSName += str[idx + 1]; + + person._walkVGSName += ".VGS"; + people._forceWalkReload = true; + str += 8; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetSceneEntryFlag(const byte *&str) { + TattooScene &scene = *(TattooScene *)_vm->_scene; + ++str; + int flag = (str[0] - 1) * 256 + str[1] - 1 - (str[1] == 1); + + int flag1 = flag & 16383; + if (flag > 16383) + flag1 *= -1; + + str += 2; + + // Make sure that this instance is not already being tracked + bool found = false; + for (uint idx = 0; idx < scene._sceneTripCounters.size() && !found; ++idx) { + SceneTripEntry &entry = scene._sceneTripCounters[idx]; + if (entry._flag == flag1 && entry._sceneNumber == str[0] - 1) + found = true; + } + + // Only add it if it's not being tracked already + if (!found) + scene._sceneTripCounters.push_back(SceneTripEntry(flag1, str[0] - 1, str[1] - 1)); + + ++str; + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetTalkSequence(const byte *&str) { + TattooPeople &people = *(TattooPeople *)_vm->_people; + int speaker = str[1] - 1; + int sequenceNumber = str[2]; + + if (sequenceNumber < 128) + people.setTalkSequence(speaker, sequenceNumber); + else + people.setListenSequence(speaker, sequenceNumber); + + str += 2; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdSetWalkControl(const byte *&str) { + TattooPeople &people = *(TattooPeople *)_vm->_people; + ++str; + people._walkControl = str[0] - 1; + + return RET_SUCCESS; +} + +// Dummy opcode +OpcodeReturn TattooTalk::cmdTalkInterruptsDisable(const byte *&str) { error("Dummy opcode cmdTalkInterruptsDisable called"); } + +// Dummy opcode +OpcodeReturn TattooTalk::cmdTalkInterruptsEnable(const byte *&str) { error("Dummy opcode cmdTalkInterruptsEnable called"); } + +OpcodeReturn TattooTalk::cmdTurnSoundsOff(const byte *&str) { error("TODO: script opcode (cmdTurnSoundsOff)"); } + +OpcodeReturn TattooTalk::cmdWalkHolmesAndNPCToCAnimation(const byte *&str) { + int npcNum = *++str; + int cAnimNum = *++str; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + Scene &scene = *_vm->_scene; + CAnim &anim = scene._cAnim[cAnimNum]; + + if (person._pathStack.empty()) + person.pushNPCPath(); + person._npcMoved = true; + + person.walkToCoords(anim._goto[1], anim._goto[1]._facing); + + if (_talkToAbort) + return RET_EXIT; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdWalkNPCToCAnimation(const byte *&str) { + int npcNum = *++str; + int cAnimNum = *++str; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + Scene &scene = *_vm->_scene; + CAnim &anim = scene._cAnim[cAnimNum]; + + if (person._pathStack.empty()) + person.pushNPCPath(); + person._npcMoved = true; + + person.walkToCoords(anim._goto[1], anim._goto[1]._facing); + + if (_talkToAbort) + return RET_EXIT; + + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdWalkNPCToCoords(const byte *&str) { + int npcNum = *++str; + str++; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + + if (person._pathStack.empty()) + person.pushNPCPath(); + person._npcMoved = true; + + int xp = (str[0] - 1) * 256 + str[1] - 1; + if (xp > 16384) + xp = -1 * (xp - 16384); + int yp = (str[2] - 1) * 256 + str[3] - 1; + + person.walkToCoords(Point32(xp * FIXED_INT_MULTIPLIER, yp * FIXED_INT_MULTIPLIER), + DIRECTION_CONVERSION[str[4] - 1]); + if (_talkToAbort) + return RET_EXIT; + + str += 4; + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdWalkHomesAndNPCToCoords(const byte *&str) { + int npcNum = *++str; + str++; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooPerson &person = people[npcNum]; + + if (person._pathStack.empty()) + person.pushNPCPath(); + person._npcMoved = true; + + // Get destination position and facing for Holmes + int xp = (str[0] - 1) * 256 + str[1] - 1; + if (xp > 16384) + xp = -1 * (xp - 16384); + int yp = (str[2] - 1) * 256 + str[3] - 1; + PositionFacing holmesDest(xp * FIXED_INT_MULTIPLIER, yp * FIXED_INT_MULTIPLIER, DIRECTION_CONVERSION[str[4] - 1]); + + // Get destination position and facing for specified NPC + xp = (str[5] - 1) * 256 + str[6] - 1; + if (xp > 16384) + xp = -1 * (xp - 16384); + yp = (str[7] - 1) * 256 + str[8] - 1; + PositionFacing npcDest(xp * FIXED_INT_MULTIPLIER, yp * FIXED_INT_MULTIPLIER, DIRECTION_CONVERSION[str[9] - 1]); + + person.walkBothToCoords(holmesDest, npcDest); + + if (_talkToAbort) + return RET_EXIT; + + str += 9; + return RET_SUCCESS; +} + +OpcodeReturn TattooTalk::cmdCallTalkFile(const byte *&str) { + TattooPeople &people = *(TattooPeople *)_vm->_people; + Common::String tempString; + + int npc = *++str; + assert(npc >= 1 && npc < MAX_CHARACTERS); + TattooPerson &person = people[npc]; + + if (person._resetNPCPath) { + person._npcIndex = person._npcPause = 0; + person._resetNPCPath = false; + Common::fill(&person._npcPath[0], &person._npcPath[100], 0); + } + + // Set the path control code and copy the filename + person._npcPath[person._npcIndex] = 4; + for (int idx = 1; idx <= 8; ++idx) + person._npcPath[person._npcIndex + idx] = str[idx]; + + person._npcIndex += 9; + str += 8; + + return RET_SUCCESS; +} + +void TattooTalk::pushSequenceEntry(Object *obj) { + // Check if the shape is already on the stack + for (uint idx = 0; idx < TALK_SEQUENCE_STACK_SIZE; ++idx) { + if (_sequenceStack[idx]._obj == obj) + return; + } + + // Find a free slot and save the details in it + for (uint idx = 0; idx < TALK_SEQUENCE_STACK_SIZE; ++idx) { + SequenceEntry &seq = _sequenceStack[idx]; + if (seq._obj == nullptr) { + seq._obj = obj; + seq._frameNumber = obj->_frameNumber; + seq._sequenceNumber = obj->_sequenceNumber; + seq._seqStack = obj->_seqStack; + seq._seqTo = obj->_seqTo; + seq._seqCounter = obj->_seqCounter; + seq._seqCounter2 = obj->_seqCounter2; + return; + } + } + + error("Ran out of talk sequence stack space"); +} + +void TattooTalk::pullSequence(int slot) { + People &people = *_vm->_people; + + for (int idx = 0; idx < TALK_SEQUENCE_STACK_SIZE; ++idx) { + SequenceEntry &seq = _sequenceStack[idx]; + if (slot != -1 && idx != slot) + continue; + + // Check for an entry in this slot + if (seq._obj) { + Object &o = *seq._obj; + + // See if we're not supposed to restore it until an Allow Talk Interrupt + if (slot == -1 && seq._obj->hasAborts()) { + seq._obj->_gotoSeq = -1; + seq._obj->_restoreSlot = idx; + } else { + // Restore the object's sequence information immediately + o._frameNumber = seq._frameNumber; + o._sequenceNumber = seq._sequenceNumber; + o._seqStack = seq._seqStack; + o._seqTo = seq._seqTo; + o._seqCounter = seq._seqCounter; + o._seqCounter2 = seq._seqCounter2; + o._gotoSeq = 0; + o._talkSeq = 0; + + // Flag the slot as free again + seq._obj = nullptr; + } + } + } + + // Handle restoring any character positioning + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { + Person &person = people[idx]; + + if (person._type == CHARACTER && !person._walkSequences.empty() && person._sequenceNumber >= TALK_UPRIGHT + && person._sequenceNumber <= LISTEN_UPLEFT) { + person.gotoStand(); + + bool done = false; + do { + person.checkSprite(); + for (int frameNum = 0; frameNum < person._frameNumber; ++frameNum) { + if (person._walkSequences[person._sequenceNumber]._sequences[frameNum] == 0) + done = true; + } + } while (!done); + } + } +} + +bool TattooTalk::isSequencesEmpty() const { + for (int idx = 0; idx < TALK_SEQUENCE_STACK_SIZE; ++idx) { + if (_sequenceStack[idx]._obj) + return false; + } + + return true; +} + +void TattooTalk::clearSequences() { + for (int idx = 0; idx < TALK_SEQUENCE_STACK_SIZE; ++idx) { + _sequenceStack[idx]._obj = nullptr; + } +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/tattoo_talk.h b/engines/sherlock/tattoo/tattoo_talk.h new file mode 100644 index 0000000000..9b010513dc --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_talk.h @@ -0,0 +1,143 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_TATTOO_TALK_H +#define SHERLOCK_TATTOO_TALK_H + +#include "common/scummsys.h" +#include "common/array.h" +#include "common/rect.h" +#include "common/serializer.h" +#include "common/stream.h" +#include "common/stack.h" +#include "sherlock/talk.h" +#include "sherlock/tattoo/widget_password.h" +#include "sherlock/tattoo/widget_talk.h" + +namespace Sherlock { + +namespace Tattoo { + +#define TALK_SEQUENCE_STACK_SIZE 20 + +class WidgetTalk; + +class TattooTalk : public Talk { + friend class WidgetTalk; +private: + WidgetTalk _talkWidget; + WidgetPassword _passwordWidget; + SequenceEntry _sequenceStack[TALK_SEQUENCE_STACK_SIZE]; + + OpcodeReturn cmdCallTalkFile(const byte *&str); + OpcodeReturn cmdSwitchSpeaker(const byte *&str); + OpcodeReturn cmdMouseOnOff(const byte *&str); + OpcodeReturn cmdGotoScene(const byte *&str); + OpcodeReturn cmdWalkHolmesToCoords(const byte *&str); + OpcodeReturn cmdNextSong(const byte *&str); + OpcodeReturn cmdPassword(const byte *&str); + OpcodeReturn cmdPlaySong(const byte *&str); + OpcodeReturn cmdRestorePeopleSequence(const byte *&str); + OpcodeReturn cmdSetNPCDescOnOff(const byte *&str); + OpcodeReturn cmdSetNPCInfoLine(const byte *&str); + OpcodeReturn cmdNPCLabelGoto(const byte *&str); + OpcodeReturn cmdNPCLabelIfFlagGoto(const byte *&str); + OpcodeReturn cmdNPCLabelSet(const byte *&str); + OpcodeReturn cmdSetNPCOff(const byte *&str); + OpcodeReturn cmdSetNPCOn(const byte *&str); + OpcodeReturn cmdSetNPCPathDest(const byte *&str); + OpcodeReturn cmdSetNPCPathPause(const byte *&str); + OpcodeReturn cmdSetNPCPathPauseTakingNotes(const byte *&str); + OpcodeReturn cmdSetNPCPathPauseLookingHolmes(const byte *&str); + OpcodeReturn cmdSetNPCPosition(const byte *&str); + OpcodeReturn cmdSetNPCTalkFile(const byte *&str); + OpcodeReturn cmdSetNPCVerb(const byte *&str); + OpcodeReturn cmdSetNPCVerbCAnimation(const byte *&str); + OpcodeReturn cmdSetNPCVerbScript(const byte *&str); + OpcodeReturn cmdSetNPCVerbTarget(const byte *&str); + OpcodeReturn cmdSetNPCWalkGraphics(const byte *&str); + OpcodeReturn cmdSetSceneEntryFlag(const byte *&str); + OpcodeReturn cmdSetTalkSequence(const byte *&str); + OpcodeReturn cmdSetWalkControl(const byte *&str); + OpcodeReturn cmdTalkInterruptsDisable(const byte *&str); + OpcodeReturn cmdTalkInterruptsEnable(const byte *&str); + OpcodeReturn cmdTurnSoundsOff(const byte *&str); + OpcodeReturn cmdWalkHolmesAndNPCToCAnimation(const byte *&str); + OpcodeReturn cmdWalkNPCToCAnimation(const byte *&str); + OpcodeReturn cmdWalkNPCToCoords(const byte *&str); + OpcodeReturn cmdWalkHomesAndNPCToCoords(const byte *&str); +protected: + /** + * Display the talk interface window + */ + virtual void talkInterface(const byte *&str); + + /** + * Called when a character being spoken to has no talk options to display + */ + virtual void nothingToSay(); + + /** + * Show the talk display + */ + virtual void showTalk(); +public: + TattooTalk(SherlockEngine *vm); + virtual ~TattooTalk() {} + + /** + * Called whenever a conversation or item script needs to be run. For standard conversations, + * it opens up a description window similar to how 'talk' does, but shows a 'reply' directly + * instead of waiting for a statement option. + * @remarks It seems that at some point, all item scripts were set up to use this as well. + * In their case, the conversation display is simply suppressed, and control is passed on to + * doScript to implement whatever action is required. + */ + virtual void talkTo(const Common::String filename); + + /** + * Push the details of a passed object onto the saved sequences stack + */ + virtual void pushSequenceEntry(Object *obj); + + /** + * Pulls a background object sequence from the sequence stack and restore's the + * object's sequence + */ + virtual void pullSequence(int slot = -1); + + /** + * Returns true if the script stack is empty + */ + virtual bool isSequencesEmpty() const; + + /** + * Clears the stack of pending object sequences associated with speakers in the scene + */ + virtual void clearSequences(); +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/tattoo_user_interface.cpp b/engines/sherlock/tattoo/tattoo_user_interface.cpp index e76322833f..160b1ca5a0 100644 --- a/engines/sherlock/tattoo/tattoo_user_interface.cpp +++ b/engines/sherlock/tattoo/tattoo_user_interface.cpp @@ -21,6 +21,8 @@ */ #include "sherlock/tattoo/tattoo_user_interface.h" +#include "sherlock/tattoo/tattoo_fixed_text.h" +#include "sherlock/tattoo/tattoo_journal.h" #include "sherlock/tattoo/tattoo_scene.h" #include "sherlock/tattoo/tattoo.h" @@ -28,121 +30,936 @@ namespace Sherlock { namespace Tattoo { -TattooUserInterface::TattooUserInterface(SherlockEngine *vm): UserInterface(vm) { - _menuBuffer = nullptr; - _invMenuBuffer = nullptr; - _tagBuffer = nullptr; - _invGraphic = nullptr; +bool WidgetList::contains(const WidgetBase *item) const { + for (const_iterator i = begin(); i != end(); ++i) { + if ((*i) == item) + return true; + } + + return false; } -void TattooUserInterface::handleInput() { - // TODO - _vm->_events->pollEventsAndWait(); +/*-------------------------------------------------------------------------*/ + +TattooUserInterface::TattooUserInterface(SherlockEngine *vm): UserInterface(vm), + _inventoryWidget(vm), _messageWidget(vm), _textWidget(vm), _tooltipWidget(vm), + _verbsWidget(vm), _creditsWidget(vm), _optionsWidget(vm), _quitWidget(vm) { + Common::fill(&_lookupTable[0], &_lookupTable[PALETTE_COUNT], 0); + Common::fill(&_lookupTable1[0], &_lookupTable1[PALETTE_COUNT], 0); + _scrollSize = 0; + _scrollSpeed = 16; + _drawMenu = false; + _bgShape = nullptr; + _personFound = false; + _lockoutTimer = 0; + _exitZone = -1; + _scriptZone = -1; + _arrowZone = _oldArrowZone = -1; + _activeObj = -1; + _cAnimFramePause = 0; + _scrollHighlight = SH_NONE; + _mask = _mask1 = nullptr; + _maskCounter = 0; + + _interfaceImages = new ImageFile("intrface.vgs"); } -void TattooUserInterface::drawInterface(int bufferNum) { +TattooUserInterface::~TattooUserInterface() { + delete _interfaceImages; + delete _mask; + delete _mask1; +} + +void TattooUserInterface::initScrollVars() { Screen &screen = *_vm->_screen; - TattooEngine &vm = *((TattooEngine *)_vm); + _scrollSize = screen._backBuffer1.w() - SHERLOCK_SCREEN_WIDTH; + _targetScroll = Common::Point(0, 0); + screen._currentScroll = Common::Point(0, 0); +} + +void TattooUserInterface::lookAtObject() { + Events &events = *_vm->_events; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + Sound &sound = *_vm->_sound; + Talk &talk = *_vm->_talk; + Common::Point mousePos = events.mousePos(); + Common::String desc; + int cAnimSpeed = 0; + + _lookPos = mousePos; + _menuMode = LOOK_MODE; + + if (_personFound) { + desc = people[_bgFound - 1000]._examine; + } else { + // Check if there is a Look animation + if (_bgShape->_lookcAnim != 0) { + cAnimSpeed = _bgShape->_lookcAnim & 0xe0; + cAnimSpeed >>= 5; + ++cAnimSpeed; + + _cAnimFramePause = _bgShape->_lookFrames; + desc = _bgShape->_examine; + + int cNum = (_bgShape->_lookcAnim & 0x1f) - 1; + scene.startCAnim(cNum); + } else if (_bgShape->_lookPosition.y != 0) { + // Need to walk to object before looking at it + people[HOLMES].walkToCoords(_bgShape->_lookPosition, _bgShape->_lookPosition._facing); + } + + if (!talk._talkToAbort) { + desc = _bgShape->_examine; + + if (_bgShape->_lookFlag) + _vm->setFlags(_bgShape->_lookFlag); + + // Find the Sound File to Play if there is one + if (!desc.hasPrefix("_")) { + for (uint idx = 0; idx < scene._objSoundList.size(); ++idx) { + // Get the object name up to the equals + const char *p = strchr(scene._objSoundList[idx].c_str(), '='); + + // Form the name and remove any trailing spaces + Common::String name(scene._objSoundList[idx].c_str(), p); + while (name.hasSuffix(" ")) + name.deleteLastChar(); + + // See if this Object Sound List entry matches the object's name + if (!_bgShape->_name.compareToIgnoreCase(name)) { + // Move forward to get the sound filename + while ((*p == ' ') || (*p == '=')) + ++p; + + // If it's not "NONE", play the speech File + Common::String soundName(p); + if (soundName.compareToIgnoreCase("NONE")) { + soundName.toLowercase(); + if (!soundName.contains('.')) + soundName += ".wav"; + + sound.playSound(soundName, WAIT_RETURN_IMMEDIATELY); + } + + break; + } + } + } + } + } + + // Only show the desciption if the object has one, and if no talk file interrupted while walking to it + if (!talk._talkToAbort && !desc.empty()) { + if (_cAnimFramePause == 0) + printObjectDesc(desc, true); + else + // The description was already printed by an animation + _cAnimFramePause = 0; + } else if (desc.empty()) { + // There was no description to display, so reset back to STD_MODE + _menuMode = STD_MODE; + } +} + +void TattooUserInterface::printObjectDesc(const Common::String &str, bool firstTime) { + Events &events = *_vm->_events; + TattooScene &scene = *(TattooScene *)_vm->_scene; + Talk &talk = *_vm->_talk; + + if (str.hasPrefix("_")) { + // The passed string specifies a talk file + _lookScriptFlag = true; + events.setCursor(MAGNIFY); + int savedSelector = _selector; + + if (!_invLookFlag) + _windowOpen = false; + + talk.talkTo(str.c_str() + 1); + _lookScriptFlag = false; + + if (talk._talkToAbort) { + events.setCursor(ARROW); + return; + } + + // See if we're looking at an inventory item + if (_invLookFlag) { + _selector = _oldSelector = savedSelector; + doInventory(0); + _invLookFlag = false; + + } else { + // Nope + events.setCursor(ARROW); + _key = -1; + _menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; + events._pressed = events._released = events._rightReleased = false; + events._oldButtons = 0; + } + } else { + events._pressed = events._released = events._rightReleased = false; + + // Show text dialog + _textWidget.load(str); + _textWidget.summonWindow(); + + if (firstTime) + _selector = _oldSelector = -1; + + _drawMenu = _windowOpen = true; + } +} + +void TattooUserInterface::doJournal() { + TattooJournal &journal = *(TattooJournal *)_vm->_journal; + TattooScene &scene = *(TattooScene *)_vm->_scene; + Screen &screen = *_vm->_screen; + byte lookupTable[PALETTE_SIZE]; + + Common::copy(&_lookupTable[0], &_lookupTable[PALETTE_SIZE], &lookupTable[0]); + _menuMode = JOURNAL_MODE; + journal.show(); + + _menuMode = STD_MODE; + _windowOpen = false; + _key = -1; + + // Restore the the old screen palette and greyscale lookup table + screen.clear(); + screen.setPalette(screen._cMap); + Common::copy(&lookupTable[0], &lookupTable[PALETTE_SIZE], &_lookupTable[0]); + + // Restore the scene + screen._backBuffer1.blitFrom(screen._backBuffer2); + scene.updateBackground(); + screen.slamArea(screen._currentScroll.x, screen._currentScroll.y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); +} + +void TattooUserInterface::reset() { + UserInterface::reset(); + _lookPos = Common::Point(SHERLOCK_SCREEN_WIDTH / 2, SHERLOCK_SCREEN_HEIGHT / 2); + _tooltipWidget.setText(""); + _widgets.clear(); + _fixedWidgets.clear(); +} + +void TattooUserInterface::handleInput() { + TattooEngine &vm = *(TattooEngine *)_vm; + Events &events = *_vm->_events; + TattooScene &scene = *(TattooScene *)_vm->_scene; + Common::Point mousePos = events.mousePos(); + + _keyState.keycode = Common::KEYCODE_INVALID; + + // Check for credits starting + if (_vm->readFlags(3000) && !_creditsWidget.active()) + _creditsWidget.initCredits(); + + // Check the mouse positioning + if (events.isCursorVisible()) + _bgFound = scene.findBgShape(mousePos); + _personFound = _bgFound >= 1000; + _bgShape = (_bgFound != -1 && _bgFound < 1000) ? &scene._bgShapes[_bgFound] : nullptr; + + if (_lockoutTimer) + --_lockoutTimer; - if (_invMenuBuffer != nullptr) { - Common::Rect r = _invMenuBounds; - r.grow(-3); - r.translate(-screen._currentScroll, 0); - _grayAreas.clear(); - _grayAreas.push_back(r); + // Key handling + if (events.kbHit()) { + _keyState = events.getKey(); - drawGrayAreas(); - screen._backBuffer1.transBlitFrom(*_invMenuBuffer, Common::Point(_invMenuBounds.left, _invMenuBounds.top)); + if (_keyState.keycode == Common::KEYCODE_ESCAPE && vm._runningProlog && !_lockoutTimer) { + vm.setFlags(-76); + vm.setFlags(396); + scene._goToScene = STARTING_GAME_SCENE; + } else if (_menuMode == STD_MODE) { + if (_keyState.keycode == Common::KEYCODE_s && vm._allowFastMode) { + vm._fastMode = !vm._fastMode; + + } else if (_keyState.keycode == Common::KEYCODE_l && _bgFound != -1) { + // Beging used for testing that Look dialogs work + lookAtObject(); + } + } } - if (_menuBuffer != nullptr) { - Common::Rect r = _menuBounds; - r.grow(-3); - r.translate(-screen._currentScroll, 0); - _grayAreas.clear(); - _grayAreas.push_back(r); + if (!events.isCursorVisible()) + _keyState.keycode = Common::KEYCODE_INVALID; + + // If there's any active widgets/windows, let the most recently open one do event processing + if (!_widgets.empty()) + _widgets.back()->handleEvents(); + else if (!_fixedWidgets.empty()) + _fixedWidgets.back()->handleEvents(); - drawGrayAreas(); - screen._backBuffer1.transBlitFrom(*_menuBuffer, Common::Point(_invMenuBounds.left, _invMenuBounds.top)); + // Handle input depending on what mode we're in + switch (_menuMode) { + case STD_MODE: + doStandardControl(); + break; + case LOOK_MODE: + doLookControl(); + break; + default: + break; } +} - // See if we need to draw a Text Tag floating with the cursor - if (_tagBuffer != nullptr) - screen._backBuffer1.transBlitFrom(*_tagBuffer, Common::Point(_tagBounds.left, _tagBounds.top)); +void TattooUserInterface::drawInterface(int bufferNum) { + Screen &screen = *_vm->_screen; - // See if we need to draw an Inventory Item Graphic floating with the cursor - if (_invGraphic != nullptr) - screen._backBuffer1.transBlitFrom(*_invGraphic, Common::Point(_invGraphicBounds.left, _invGraphicBounds.top)); + // Draw any active on-screen widgets + for (Common::List<WidgetBase *>::iterator i = _fixedWidgets.begin(); i != _fixedWidgets.end(); ++i) + (*i)->draw(); + for (Common::List<WidgetBase *>::iterator i = _widgets.begin(); i != _widgets.end(); ++i) + (*i)->draw(); - if (vm._creditsActive) - vm.drawCredits(); + // Handle drawing credits + // TODO: See if credits are only shown on a single screen. If so, _fixedWidgets could be used + if (_creditsWidget.active()) + _creditsWidget.drawCredits(); + + // Bring the widgets to the screen + if (_mask != nullptr) + screen._flushScreen = true; } void TattooUserInterface::doBgAnimRestoreUI() { TattooScene &scene = *((TattooScene *)_vm->_scene); Screen &screen = *_vm->_screen; - // If _oldMenuBounds was set, then either a new menu has been opened or the current menu has been closed. - // Either way, we need to restore the area where the menu was displayed - if (_oldMenuBounds.width() > 0) - screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_oldMenuBounds.left, _oldMenuBounds.top), - _oldMenuBounds); - - if (_oldInvMenuBounds.width() > 0) - screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_oldInvMenuBounds.left, _oldInvMenuBounds.top), - _oldInvMenuBounds); - - if (_menuBuffer != nullptr) - screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_menuBounds.left, _menuBounds.top), _menuBounds); - if (_invMenuBuffer != nullptr) - screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_invMenuBounds.left, _invMenuBounds.top), _invMenuBounds); + // If there are any on-screen widgets, then erase them + for (Common::List<WidgetBase *>::iterator i = _widgets.begin(); i != _widgets.end(); ++i) + (*i)->erase(); + for (Common::List<WidgetBase *>::iterator i = _fixedWidgets.begin(); i != _fixedWidgets.end(); ++i) + (*i)->erase(); // If there is a Text Tag being display, restore the area underneath it - if (_oldTagBounds.width() > 0) - screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_oldTagBounds.left, _oldTagBounds.top), - _oldTagBounds); - - // If there is an Inventory being shown, restore the graphics underneath it - if (_oldInvGraphicBounds.width() > 0) - screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_oldInvGraphicBounds.left, _oldInvGraphicBounds.top), - _oldInvGraphicBounds); + _tooltipWidget.erase(); // If a canimation is active, restore the graphics underneath it - if (scene._activeCAnim._images != nullptr) + if (scene._activeCAnim.active()) screen.restoreBackground(scene._activeCAnim._oldBounds); - // If a canimation just ended, remove it's graphics from the backbuffer + // If a canimation just ended, remove its graphics from the backbuffer if (scene._activeCAnim._removeBounds.width() > 0) screen.restoreBackground(scene._activeCAnim._removeBounds); } void TattooUserInterface::doScroll() { Screen &screen = *_vm->_screen; - int oldScroll = screen._currentScroll; // If we're already at the target scroll position, nothing needs to be done - if (screen._targetScroll == screen._currentScroll) + if (_targetScroll.x == screen._currentScroll.x) return; screen._flushScreen = true; - if (screen._targetScroll > screen._currentScroll) { - screen._currentScroll += screen._scrollSpeed; - if (screen._currentScroll > screen._targetScroll) - screen._currentScroll = screen._targetScroll; - } else if (screen._targetScroll < screen._currentScroll) { - screen._currentScroll -= screen._scrollSpeed; - if (screen._currentScroll < screen._targetScroll) - screen._currentScroll = screen._targetScroll; + if (_targetScroll.x > screen._currentScroll.x) { + screen._currentScroll.x += _scrollSpeed; + if (screen._currentScroll.x > _targetScroll.x) + screen._currentScroll.x = _targetScroll.x; + } else if (_targetScroll.x < screen._currentScroll.x) { + screen._currentScroll.x -= _scrollSpeed; + if (screen._currentScroll.x < _targetScroll.x) + screen._currentScroll.x = _targetScroll.x; + } + + // Reset the default look position to the center of the new screen area + _lookPos = screen._currentScroll + Common::Point(SHERLOCK_SCREEN_WIDTH / 2, SHERLOCK_SCREEN_HEIGHT / 2); +} + +void TattooUserInterface::doStandardControl() { + TattooEngine &vm = *(TattooEngine *)_vm; + Events &events = *_vm->_events; + People &people = *_vm->_people; + TattooScene &scene = *(TattooScene *)_vm->_scene; + Talk &talk = *_vm->_talk; + Common::Point mousePos = events.mousePos(); + bool noDesc = false; + + // Don't do any input processing whilst the prolog is running + if (vm._runningProlog) + return; + + // When the end credits are active, any press will open the ScummVM global main menu + if (_creditsWidget.active()) { + if (_keyState.keycode || events._released || events._rightReleased) { + vm._canLoadSave = true; + vm.openMainMenuDialog(); + vm._canLoadSave = false; + } + + return; + } + + // Display the names of any Objects the cursor is pointing at + displayObjectNames(); + + switch (_keyState.keycode) { + case Common::KEYCODE_F5: + // Save game + events.warpMouse(); + saveGame(); + return; + + case Common::KEYCODE_F7: + // Load game + events.warpMouse(); + loadGame(); + return; + + case Common::KEYCODE_F1: + // Display journal + if (vm.readFlags(FLAG_PLAYER_IS_HOLMES)) { + freeMenu(); + doJournal(); + + // See if we're in a Lab Table Room + _menuMode = (scene._labTableScene) ? LAB_MODE : STD_MODE; + return; + } + break; + + case Common::KEYCODE_TAB: + case Common::KEYCODE_F3: + // Display inventory + freeMenu(); + doInventory(3); + return; + + case Common::KEYCODE_F4: + // Display options + events.warpMouse(); + _optionsWidget.load(); + return; + + case Common::KEYCODE_F10: + // Quit menu + freeMenu(); + events.warpMouse(); + doQuitMenu(); + return; + + default: + break; + } + + // See if a mouse button was released + if (events._released || events._rightReleased) { + // See if the mouse was released in an exit (Arrow) zone. Unless it's also pointing at an object + // within the zone, in which case the object gets precedence + _exitZone = -1; + if (_arrowZone != -1 && events._released) + _exitZone = _arrowZone; + + // Turn any Text display off + if (_arrowZone == -1 || events._rightReleased) + freeMenu(); + + if (_personFound) { + if (people[_bgFound - 1000]._description.empty() || people[_bgFound - 1000]._description.hasPrefix(" ")) + noDesc = true; + } else if (_bgFound != -1) { + if (_bgShape->_description.empty() || _bgShape->_description.hasPrefix(" ")) + noDesc = true; + } else { + noDesc = true; + } + + if (events._rightReleased) { + // Show the verbs menu for the highlighted object + _tooltipWidget.banishWindow(); + _verbsWidget.load(!noDesc); + _verbsWidget.summonWindow(); + + _selector = _oldSelector = -1; + _activeObj = _bgFound; + _menuMode = VERB_MODE; + } else if (_personFound || (_bgFound != -1 && _bgFound < 1000 && _bgShape->_aType == PERSON)) { + // The object found is a person (the default for people is TALK) + talk.initTalk(_bgFound); + _activeObj = -1; + } else if (!noDesc) { + // Either call the code to Look at its Examine Field or call the Exit animation + // if the object is an exit, specified by the first four characters of the name being "EXIT" + Common::String name = _personFound ? people[_bgFound - 1000]._name : _bgShape->_name; + if (!name.hasPrefix("EXIT")) { + lookAtObject(); + } else { + // Run the Exit animation and set which scene to go to next + for (int idx = 0; idx < 6; ++idx) { + if (!_bgShape->_use[idx]._verb.compareToIgnoreCase("Open")) { + checkAction(_bgShape->_use[idx], _bgFound); + _activeObj = -1; + } + } + } + } else { + // See if there are any Script Zones where they clicked + if (scene.checkForZones(mousePos, _scriptZone) != 0) { + // Mouse click in a script zone + events._pressed = events._released = false; + } else if (scene.checkForZones(mousePos, NOWALK_ZONE) != 0) { + events._pressed = events._released = false; + } else { + // Walk to where the mouse was clicked + people[HOLMES]._walkDest = mousePos; + people[HOLMES].goAllTheWay(); + } + } + } +} + +void TattooUserInterface::doLookControl() { + Events &events = *_vm->_events; + TattooScene &scene = *(TattooScene *)_vm->_scene; + Sound &sound = *_vm->_sound; + + // See if a mouse button was released or a key pressed, and we want to initiate an action + // TODO: Not sure about _soundOn.. should be check for speaking voice for text being complete + if (events._released || events._rightReleased || _keyState.keycode || (sound._voices && !sound._soundOn)) { + // See if we were looking at an inventory object + if (!_invLookFlag) { + // See if there is any more text to display + if (!_textWidget._remainingText.empty()) { + printObjectDesc(_textWidget._remainingText, false); + } else { + // Otherwise restore the background and go back into STD_MODE + freeMenu(); + _key = -1; + _menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; + + events.setCursor(ARROW); + events._pressed = events._released = events._rightReleased = false; + events._oldButtons = 0; + } + } else { + // We were looking at a Inventory object + // Erase the text window, and then redraw the inventory window + _textWidget.banishWindow(); + doInventory(0); + + _invLookFlag = false; + _key = -1; + + events.setCursor(ARROW); + events._pressed = events._released = events._rightReleased = false; + events._oldButtons = 0; + } + } +} + +void TattooUserInterface::displayObjectNames() { + Events &events = *_vm->_events; + Scene &scene = *_vm->_scene; + Common::Point mousePos = events.mousePos(); + _arrowZone = -1; + + if (_bgFound == -1 || scene._currentScene == OVERHEAD_MAP2) { + for (uint idx = 0; idx < scene._exits.size() && _arrowZone == -1; ++idx) { + Exit &exit = scene._exits[idx]; + if (exit.contains(mousePos)) + _arrowZone = idx; + } } - if (_menuBuffer != nullptr) - _menuBounds.translate(screen._currentScroll - oldScroll, 0); - if (_invMenuBuffer != nullptr) - _invMenuBounds.translate(screen._currentScroll - oldScroll, 0); + _tooltipWidget.handleEvents(); + _oldArrowZone = _arrowZone; +} + +void TattooUserInterface::doInventory(int mode) { + People &people = *_vm->_people; + people[HOLMES].gotoStand(); + + _inventoryWidget.load(mode); + _inventoryWidget.summonWindow(); + + _menuMode = INV_MODE; +} + +void TattooUserInterface::doControls() { + _optionsWidget.load(); +} + +void TattooUserInterface::pickUpObject(int objNum) { + Inventory &inv = *_vm->_inventory; + Scene &scene = *_vm->_scene; + Talk &talk = *_vm->_talk; + Object &obj = scene._bgShapes[objNum]; + bool printed = false; + int verbField = -1; + + // Find which Verb field to use for pick up data + for (int idx = 0; idx < 6; ++idx) { + if (!scumm_stricmp(obj._use[idx]._target.c_str(), "*PICKUP")) + verbField = idx; + } + + if (verbField != -1) { + if (obj._use[verbField]._cAnimNum) + scene.startCAnim(obj._use[verbField]._cAnimNum - 1); + } + + if (!talk._talkToAbort) { + if (obj._type == NO_SHAPE) + obj._type = INVALID; + else + // Erase shape + obj._type = REMOVE; + } else { + return; + } + + if (verbField != -1) { + for (int idx = 0; idx < 4 && !talk._talkToAbort; ++idx) { + if (obj.checkNameForCodes(obj._use[verbField]._names[idx])) { + if (!talk._talkToAbort) + printed = true; + } + } + } + + if (talk._talkToAbort) + return; + + // Add the item to the player's inventory + inv.putItemInInventory(obj); + + if (!printed) { + Common::String desc = obj._description; + desc.setChar(tolower(desc[0]), 0); + + putMessage("%s %s", FIXED(PickedUp), desc.c_str()); + } + + if (_menuMode != TALK_MODE && _menuMode != MESSAGE_MODE) { + _menuMode = STD_MODE; + _keyState.keycode = Common::KEYCODE_INVALID; + } +} + +void TattooUserInterface::doQuitMenu() { + _quitWidget.show(); +} + +void TattooUserInterface::putMessage(const char *formatStr, ...) { + // Create the string to display + va_list args; + va_start(args, formatStr); + Common::String str = Common::String::vformat(formatStr, args); + va_end(args); + + // Open the message widget + _menuMode = MESSAGE_MODE; + _messageWidget.load(str, 25); + _messageWidget.summonWindow(); +} + +void TattooUserInterface::setupBGArea(const byte cMap[PALETTE_SIZE]) { + Scene &scene = *_vm->_scene; + + // This requires that there is a 16 grayscale palette sequence in the palette that goes from lighter + // to darker as the palette numbers go up. The last palette entry in that run is specified by _bgColor + byte *p = &_lookupTable[0]; + for (int idx = 0; idx < PALETTE_COUNT; ++idx) + *p++ = BG_GREYSCALE_RANGE_END - (cMap[idx * 3] * 30 + cMap[idx * 3 + 1] * 59 + cMap[idx * 3 + 2] * 11) / 480; + + // If we're going to a scene with a haze special effect, initialize the translate table to lighten the colors + if (_mask != nullptr) { + p = &_lookupTable1[0]; + + for (int idx = 0; idx < PALETTE_COUNT; ++idx) { + int r, g, b; + switch (scene._currentScene) { + case 8: + r = cMap[idx * 3] * 4 / 5; + g = cMap[idx * 3 + 1] * 3 / 4; + b = cMap[idx * 3 + 2] * 3 / 4; + break; + + case 18: + case 68: + r = cMap[idx * 3] * 4 / 3; + g = cMap[idx * 3 + 1] * 4 / 3; + b = cMap[idx * 3 + 2] * 4 / 3; + break; + + case 7: + case 53: + r = cMap[idx * 3] * 4 / 3; + g = cMap[idx * 3 + 1] * 4 / 3; + b = cMap[idx * 3 + 2] * 4 / 3; + break; + + default: + r = g = b = 0; + break; + } + + byte c = 0xff; + int cd = 99999; + + for (int pal = 0; pal < PALETTE_COUNT; ++pal) { + int d = (r - cMap[pal * 3]) * (r - cMap[pal * 3]) + (g - cMap[pal * 3 + 1]) * (g - cMap[pal * 3 + 1]) + + (b - cMap[pal * 3 + 2]) * (b - cMap[pal * 3 + 2]); + + if (d < cd) { + c = pal; + cd = d; + if (!d) + break; + } + } + *p++ = c; + } + } +} + +void TattooUserInterface::doBgAnimEraseBackground() { + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + + static const int16 OFFSETS[16] = { -1, -2, -3, -3, -2, -1, -1, 0, 1, 2, 3, 3, 2, 1, 0, 0 }; + + if (_mask != nullptr) { + // Since a mask is active, restore the screen from the secondary back buffer prior to applying the mask + screen._backBuffer1.blitFrom(screen._backBuffer2, screen._currentScroll, Common::Rect(screen._currentScroll.x, 0, + screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + + switch (scene._currentScene) { + case 7: + if (++_maskCounter == 2) { + _maskCounter = 0; + if (--_maskOffset.x < 0) + _maskOffset.x = SHERLOCK_SCREEN_WIDTH - 1; + } + break; + + case 8: + _maskOffset.x += 2; + if (_maskOffset.x >= SHERLOCK_SCREEN_WIDTH) + _maskOffset.x = 0; + break; + + case 18: + case 68: + ++_maskCounter; + if (_maskCounter / 4 >= 16) + _maskCounter = 0; + + _maskOffset.x = OFFSETS[_maskCounter / 4]; + break; + + case 53: + if (++_maskCounter == 2) { + _maskCounter = 0; + if (++_maskOffset.x == screen._backBuffer1.w()) + _maskOffset.x = 0; + } + break; + + default: + break; + } + } else { + // Standard scene without mask, so call user interface to erase any UI elements as necessary + doBgAnimRestoreUI(); + + // Restore background for any areas covered by characters and shapes + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) + screen.restoreBackground(Common::Rect(people[idx]._oldPosition.x, people[idx]._oldPosition.y, + people[idx]._oldPosition.x + people[idx]._oldSize.x, people[idx]._oldPosition.y + people[idx]._oldSize.y)); + + for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) { + Object &obj = scene._bgShapes[idx]; + + if ((obj._type == ACTIVE_BG_SHAPE && (obj._maxFrames > 1 || obj._delta.x != 0 || obj._delta.y != 0)) || + obj._type == HIDE_SHAPE || obj._type == REMOVE) + screen._backBuffer1.blitFrom(screen._backBuffer2, obj._oldPosition, + Common::Rect(obj._oldPosition.x, obj._oldPosition.y, obj._oldPosition.x + obj._oldSize.x, + obj._oldPosition.y + obj._oldSize.y)); + } + + // If credits are active, erase the area they cover + if (_creditsWidget.active()) + _creditsWidget.eraseCredits(); + } + + for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) { + Object &obj = scene._bgShapes[idx]; + + if (obj._type == NO_SHAPE && (obj._flags & 1) == 0) { + screen._backBuffer1.blitFrom(screen._backBuffer2, obj._position, obj.getNoShapeBounds()); + + obj._oldPosition = obj._position; + obj._oldSize = obj._noShapeSize; + } + } + + // Adjust the Target Scroll if needed + if ((people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER - screen._currentScroll.x) < + (SHERLOCK_SCREEN_WIDTH / 8) && people[people._walkControl]._delta.x < 0) { + + _targetScroll.x = (short)(people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER - + SHERLOCK_SCREEN_WIDTH / 8 - 250); + if (_targetScroll.x < 0) + _targetScroll.x = 0; + } + + if ((people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER - screen._currentScroll.x) > + (SHERLOCK_SCREEN_WIDTH / 4 * 3) && people[people._walkControl]._delta.x > 0) + _targetScroll.x = (short)(people[people._walkControl]._position.x / FIXED_INT_MULTIPLIER - + SHERLOCK_SCREEN_WIDTH / 4 * 3 + 250); + + if (_targetScroll.x > _scrollSize) + _targetScroll.x = _scrollSize; + + doScroll(); +} + +void TattooUserInterface::drawMaskArea(bool mode) { + Scene &scene = *_vm->_scene; + int xp = mode ? _maskOffset.x : 0; + + if (_mask != nullptr) { + switch (scene._currentScene) { + case 7: + maskArea(*_mask, Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 110)); + maskArea(*_mask, Common::Point(_maskOffset.x, 110)); + maskArea(*_mask, Common::Point(_maskOffset.x + SHERLOCK_SCREEN_WIDTH, 110)); + break; + + case 8: + maskArea(*_mask, Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 180)); + maskArea(*_mask, Common::Point(_maskOffset.x, 180)); + maskArea(*_mask, Common::Point(_maskOffset.x + SHERLOCK_SCREEN_WIDTH, 180)); + if (!_vm->readFlags(880)) + maskArea(*_mask1, Common::Point(940, 300)); + break; + + case 18: + maskArea(*_mask, Common::Point(xp, 203)); + if (!_vm->readFlags(189)) + maskArea(*_mask1, Common::Point(124 + xp, 239)); + break; + + case 53: + maskArea(*_mask, Common::Point(_maskOffset.x, 110)); + if (mode) + maskArea(*_mask, Common::Point(_maskOffset.x - SHERLOCK_SCREEN_WIDTH, 110)); + break; + + case 68: + maskArea(*_mask, Common::Point(xp, 203)); + maskArea(*_mask1, Common::Point(124 + xp, 239)); + break; + } + } +} + +void TattooUserInterface::maskArea(Common::SeekableReadStream &mask, const Common::Point &pt) { + Screen &screen = *_vm->_screen; + Surface &bb1 = screen._backBuffer1; + mask.seek(0); + int xSize = mask.readUint16LE(); + int ySize = mask.readUint16LE(); + int pixel, len, xp, yp; + + for (yp = 0; yp < ySize; ++yp) { + byte *ptr = bb1.getBasePtr(pt.x, pt.y + yp); + + for (xp = 0; xp < xSize;) { + // The mask data consists of pairs of pixel/lengths, where all non-zero pixels means that the + // given pixel on the back buffer is darkened (the mask pixel value isn't otherwise used) + pixel = mask.readByte(); + len = mask.readByte(); + + for (; len > 0; --len, ++xp, ++ptr) { + if (pixel && (pt.x + xp) >= screen._currentScroll.x && (pt.x + xp) < (screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH)) { + *ptr = _lookupTable1[*ptr]; + } + } + } + + assert(xp == xSize); + } +} + +void TattooUserInterface::makeBGArea(const Common::Rect &r) { + Screen &screen = *_vm->_screen; + + for (int yp = r.top; yp < r.bottom; ++yp) { + byte *ptr = screen._backBuffer1.getBasePtr(r.left, yp); + + for (int xp = r.left; xp < r.right; ++xp, ++ptr) + *ptr = _lookupTable[*ptr]; + } + + screen.slamRect(r); +} + +void TattooUserInterface::drawDialogRect(Surface &s, const Common::Rect &r, bool raised) { + if (raised) { + // Draw Left + s.vLine(r.left, r.top, r.bottom - 1, INFO_TOP); + s.vLine(r.left + 1, r.top, r.bottom - 2, INFO_TOP); + // Draw Top + s.hLine(r.left + 2, r.top, r.right - 1, INFO_TOP); + s.hLine(r.left + 2, r.top + 1, r.right - 2, INFO_TOP); + // Draw Right + s.vLine(r.right - 1, r.top + 1, r.bottom - 1, INFO_BOTTOM); + s.vLine(r.right - 2, r.top + 2, r.bottom - 1, INFO_BOTTOM); + // Draw Bottom + s.hLine(r.left + 1, r.bottom - 1, r.right - 3, INFO_BOTTOM); + s.hLine(r.left + 2, r.bottom - 2, r.right - 3, INFO_BOTTOM); + + } else { + // Draw Left + s.vLine(r.left, r.top, r.bottom - 1, INFO_BOTTOM); + s.vLine(r.left + 1, r.top, r.bottom - 2, INFO_BOTTOM); + // Draw Top + s.hLine(r.left + 2, r.top, r.right - 1, INFO_BOTTOM); + s.hLine(r.left + 2, r.top + 1, r.right - 2, INFO_BOTTOM); + // Draw Right + s.vLine(r.right - 1, r.top + 1, r.bottom - 1, INFO_TOP); + s.vLine(r.right - 2, r.top + 2, r.bottom - 1, INFO_TOP); + // Draw Bottom + s.hLine(r.left + 1, r.bottom - 1, r.right - 3, INFO_TOP); + s.hLine(r.left + 2, r.bottom - 2, r.right - 3, INFO_TOP); + } +} + +void TattooUserInterface::banishWindow(bool slideUp) { + if (!_widgets.empty()) + _widgets.back()->banishWindow(); +} + +void TattooUserInterface::freeMenu() { + for (Common::List<WidgetBase *>::iterator i = _widgets.begin(); i != _widgets.end(); ++i) + (*i)->erase(); + _widgets.clear(); +} + +void TattooUserInterface::clearWindow() { + banishWindow(); +} + +void TattooUserInterface::loadGame() { + WidgetFiles &files = *(WidgetFiles *)_vm->_saves; + files.show(SAVEMODE_LOAD); +} + +void TattooUserInterface::saveGame() { + WidgetFiles &files = *(WidgetFiles *)_vm->_saves; + files.show(SAVEMODE_SAVE); } -void TattooUserInterface::drawGrayAreas() { - // TODO +void TattooUserInterface::addFixedWidget(WidgetBase *widget) { + _fixedWidgets.push_back(widget); + widget->summonWindow(); } } // End of namespace Tattoo diff --git a/engines/sherlock/tattoo/tattoo_user_interface.h b/engines/sherlock/tattoo/tattoo_user_interface.h index 2125f1ba07..c92ff21dd1 100644 --- a/engines/sherlock/tattoo/tattoo_user_interface.h +++ b/engines/sherlock/tattoo/tattoo_user_interface.h @@ -24,34 +24,99 @@ #define SHERLOCK_TATTOO_UI_H #include "common/scummsys.h" +#include "common/list.h" +#include "sherlock/saveload.h" +#include "sherlock/screen.h" #include "sherlock/user_interface.h" +#include "sherlock/tattoo/widget_credits.h" +#include "sherlock/tattoo/widget_files.h" +#include "sherlock/tattoo/widget_inventory.h" +#include "sherlock/tattoo/widget_options.h" +#include "sherlock/tattoo/widget_quit.h" +#include "sherlock/tattoo/widget_text.h" +#include "sherlock/tattoo/widget_tooltip.h" +#include "sherlock/tattoo/widget_verbs.h" namespace Sherlock { namespace Tattoo { +// Button width/height +#define BUTTON_SIZE 15 +// How long to play the intro before it can be skipped +#define STARTUP_KEYS_DISABLED_DELAY 200 + +class WidgetBase; + +enum ScrollHighlight { SH_NONE = 0, SH_SCROLL_UP = 1, SH_PAGE_UP = 2, SH_THUMBNAIL = 3, SH_PAGE_DOWN = 4, SH_SCROLL_DOWN = 5 }; + +class WidgetList : public Common::List <WidgetBase *> { +public: + bool contains(const WidgetBase *item) const; +}; + class TattooUserInterface : public UserInterface { + friend class WidgetBase; private: - Common::Rect _menuBounds; - Common::Rect _oldMenuBounds; - Common::Rect _invMenuBounds; - Common::Rect _oldInvMenuBounds; - Common::Rect _tagBounds; - Common::Rect _oldTagBounds; - Common::Rect _invGraphicBounds; - Common::Rect _oldInvGraphicBounds; - Surface *_menuBuffer; - Surface *_invMenuBuffer; - Surface *_tagBuffer; - Surface *_invGraphic; - Common::Array<Common::Rect> _grayAreas; + int _scriptZone; + int _cAnimFramePause; + WidgetInventory _inventoryWidget; + WidgetMessage _messageWidget; + WidgetQuit _quitWidget; + WidgetList _fixedWidgets; + WidgetList _widgets; + byte _lookupTable[PALETTE_COUNT]; + byte _lookupTable1[PALETTE_COUNT]; private: /** - * Draws designated areas of the screen that are meant to be grayed out using grayscale colors + * Handle any input when we're in standard mode (with no windows open) */ - void drawGrayAreas(); + void doStandardControl(); + + /** + * Handle input when in look mode + */ + void doLookControl(); + + /** + * Handle input when the File window is open + */ + void doFileControl(); + + /** + * Handle input while the verb menu is open + */ + void doVerbControl(); + + /** + * Free any active menu + */ + void freeMenu(); +public: + Common::Point _targetScroll; + int _scrollSize, _scrollSpeed; + bool _drawMenu; + int _arrowZone, _oldArrowZone; + Object *_bgShape; + bool _personFound; + int _activeObj; + Common::KeyState _keyState; + Common::Point _lookPos; + ScrollHighlight _scrollHighlight; + Common::SeekableReadStream *_mask, *_mask1; + Common::Point _maskOffset; + int _maskCounter; + int _lockoutTimer; + ImageFile *_interfaceImages; + WidgetCredits _creditsWidget; + WidgetOptions _optionsWidget; + WidgetText _textWidget; + WidgetSceneTooltip _tooltipWidget; + WidgetVerbs _verbsWidget; + WidgetList _postRenderWidgets; public: TattooUserInterface(SherlockEngine *vm); + virtual ~TattooUserInterface(); /** * Handles restoring any areas of the back buffer that were/are covered by UI elements @@ -62,8 +127,109 @@ public: * Checks to see if the screen needs to be scrolled. If so, scrolls it towards the target position */ void doScroll(); + + /** + * Initializes scroll variables + */ + void initScrollVars(); + + /** + * Display the long description for an object in a window + */ + void lookAtObject(); + + /** + * Display the passed long description for an object. If the flag firstTime is set, + * the window will be opened to accomodate the text. Otherwise, the remaining text + * will be printed in an already open window + */ + void printObjectDesc(const Common::String &str, bool firstTime); + + /** + * Handles displaying the journal + */ + void doJournal(); + + /** + * Put the game in inventory mode by opening the inventory dialog + */ + void doInventory(int mode); + + /** + * Handle the display of the options/setup menu + */ + void doControls(); + + /** + * Handle the display of the quit menu + */ + void doQuitMenu(); + + /** + * Pick up the selected object + */ + void pickUpObject(int objNum); + + /** + * This will display a text message in a dialog at the bottom of the screen + */ + void putMessage(const char *formatStr, ...) GCC_PRINTF(2, 3); + + /** + * Makes a greyscale translation table for each palette entry in the table + */ + void setupBGArea(const byte cMap[PALETTE_SIZE]); + + /** + * Erase any background as needed before drawing frame + */ + void doBgAnimEraseBackground(); + + void drawMaskArea(bool mode); + + /** + * Takes the data passed in the image and apply it to the surface at the given position. + * The src mask data is encoded with a different color for each item. To highlight one, + the runs that do not match the highlight number will be darkened + */ + void maskArea(Common::SeekableReadStream &mask, const Common::Point &pt); + + /** + * Translate a given area of the back buffer to greyscale shading + */ + void makeBGArea(const Common::Rect &r); + + /** + * Draws all the dialog rectangles for any items that need them + */ + void drawDialogRect(Surface &s, const Common::Rect &r, bool raised); + + /** + * If the mouse cursor is point at the cursor, then display the name of the object on the screen. + * If there is no object being pointed it, clear any previously displayed name + */ + void displayObjectNames(); + + /** + * Show the load game dialog, and allow the user to load a game + */ + void loadGame(); + + /** + * Show the save game dialog, and allow the user to save the game + */ + void saveGame(); + + /** + * Add a widget to the current scene to be executed if there are no active widgets in the + * main _widgets list + */ + void addFixedWidget(WidgetBase *widget); public: - virtual ~TattooUserInterface() {} + /** + * Resets the user interface + */ + virtual void reset(); /** * Main input handler for the user interface @@ -74,6 +240,18 @@ public: * Draw the user interface onto the screen's back buffers */ virtual void drawInterface(int bufferNum = 3); + + /** + * Clear any active text window + */ + virtual void clearWindow(); + + /** + * Banish any active window + * @remarks Tattoo doesn't use sliding windows, but the parameter is in the base + * UserInterface class as a convenience for Scalpel UI code + */ + virtual void banishWindow(bool slideUp = true); }; } // End of namespace Tattoo diff --git a/engines/sherlock/tattoo/widget_base.cpp b/engines/sherlock/tattoo/widget_base.cpp new file mode 100644 index 0000000000..9e10cee0d1 --- /dev/null +++ b/engines/sherlock/tattoo/widget_base.cpp @@ -0,0 +1,375 @@ +/* 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 "sherlock/tattoo/widget_base.h" +#include "sherlock/tattoo/tattoo.h" +#include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo_talk.h" +#include "sherlock/tattoo/tattoo_user_interface.h" + +namespace Sherlock { + +namespace Tattoo { + +WidgetBase::WidgetBase(SherlockEngine *vm) : _vm(vm) { + _scroll = false; + _dialogTimer = 0; +} + +void WidgetBase::summonWindow() { + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + + // Double-check that the same widget isn't added twice + if (ui._widgets.contains(this)) + error("Tried to add a widget multiple times"); + + // Add widget to the screen + if (!ui._fixedWidgets.contains(this)) + ui._widgets.push_back(this); + ui._windowOpen = true; + + _outsideMenu = false; + + draw(); +} + +void WidgetBase::banishWindow() { + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + + erase(); + _surface.free(); + ui._widgets.remove(this); + ui._windowOpen = false; +} + +void WidgetBase::close() { + Events &events = *_vm->_events; + TattooScene &scene = *(TattooScene *)_vm->_scene; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + + banishWindow(); + ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; + events.clearEvents(); +} + +bool WidgetBase::active() const { + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + for (Common::List<WidgetBase *>::iterator i = ui._widgets.begin(); i != ui._widgets.end(); ++i) { + if ((*i) == this) + return true; + } + + return false; +} + + +void WidgetBase::erase() { + Screen &screen = *_vm->_screen; + + if (_oldBounds.width() > 0) { + // Restore the affected area from the secondary back buffer into the first one, and then copy to screen + screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_oldBounds.left, _oldBounds.top), _oldBounds); + screen.slamRect(_oldBounds); + + // Reset the old bounds so it won't be erased again + _oldBounds = Common::Rect(0, 0, 0, 0); + } +} + +void WidgetBase::draw() { + Screen &screen = *_vm->_screen; + + // If there was a previously drawn frame in a different position that hasn't yet been erased, then erase it + if (_oldBounds.width() > 0 && _oldBounds != _bounds) + erase(); + + if (_bounds.width() > 0 && !_surface.empty()) { + // Get the area to draw, adjusted for scroll position + restrictToScreen(); + + // Draw the background for the widget + drawBackground(); + + // Draw the widget onto the back buffer and then slam it to the screen + screen._backBuffer1.transBlitFrom(_surface, Common::Point(_bounds.left, _bounds.top)); + screen.slamRect(_bounds); + + // Store a copy of the drawn area for later erasing + _oldBounds = _bounds; + } +} + +void WidgetBase::drawBackground() { + TattooEngine &vm = *(TattooEngine *)_vm; + Screen &screen = *_vm->_screen; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + + Common::Rect bounds = _bounds; + + if (vm._transparentMenus) { + ui.makeBGArea(bounds); + } else { + bounds.grow(-3); + screen._backBuffer1.fillRect(bounds, MENU_BACKGROUND); + } +} + +Common::String WidgetBase::splitLines(const Common::String &str, Common::StringArray &lines, int maxWidth, uint maxLines) { + Talk &talk = *_vm->_talk; + const char *strP = str.c_str(); + + // Loop counting up lines + lines.clear(); + do { + int width = 0; + const char *spaceP = nullptr; + const char *lineStartP = strP; + + // Find how many characters will fit on the next line + while (width < maxWidth && *strP && ((byte)*strP < talk._opcodes[OP_SWITCH_SPEAKER] || + (byte)*strP == talk._opcodes[OP_NULL])) { + width += _surface.charWidth(*strP); + + // Keep track of the last space + if (*strP == ' ') + spaceP = strP; + ++strP; + } + + // If the line was too wide to fit on a single line, go back to the last space + // if there was one, or otherwise simply break the line at this point + if (width >= maxWidth && spaceP != nullptr) + strP = spaceP; + + // Add the line to the output array + lines.push_back(Common::String(lineStartP, strP)); + + // Move the string ahead to the next line + if (*strP == ' ' || *strP == 13) + ++strP; + } while (*strP && (lines.size() < maxLines) && ((byte)*strP < talk._opcodes[OP_SWITCH_SPEAKER] + || (byte)*strP == talk._opcodes[OP_NULL])); + + // Return any remaining text left over + return *strP ? Common::String(strP) : Common::String(); +} + +void WidgetBase::restrictToScreen() { + Screen &screen = *_vm->_screen; + + if (_bounds.left < screen._currentScroll.x) + _bounds.moveTo(screen._currentScroll.x, _bounds.top); + if (_bounds.top < 0) + _bounds.moveTo(_bounds.left, 0); + if (_bounds.right > (screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH)) + _bounds.moveTo(screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH - _bounds.width(), _bounds.top); + if (_bounds.bottom > screen._backBuffer1.h()) + _bounds.moveTo(_bounds.left, screen._backBuffer1.h() - _bounds.height()); +} + +void WidgetBase::makeInfoArea(Surface &s) { + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + ImageFile &images = *ui._interfaceImages; + + // Draw the four corners of the Info Box + s.transBlitFrom(images[0], Common::Point(0, 0)); + s.transBlitFrom(images[1], Common::Point(s.w() - images[1]._width, 0)); + s.transBlitFrom(images[2], Common::Point(0, s.h() - images[2]._height)); + s.transBlitFrom(images[3], Common::Point(s.w() - images[3]._width, s.h())); + + // Draw the top of the Info Box + s.hLine(images[0]._width, 0, s.w() - images[1]._width, INFO_TOP); + s.hLine(images[0]._width, 1, s.w() - images[1]._width, INFO_MIDDLE); + s.hLine(images[0]._width, 2, s.w() - images[1]._width, INFO_BOTTOM); + + // Draw the bottom of the Info Box + s.hLine(images[0]._width, s.h()- 3, s.w() - images[1]._width, INFO_TOP); + s.hLine(images[0]._width, s.h()- 2, s.w() - images[1]._width, INFO_MIDDLE); + s.hLine(images[0]._width, s.h()- 1, s.w() - images[1]._width, INFO_BOTTOM); + + // Draw the left Side of the Info Box + s.vLine(0, images[0]._height, s.h()- images[2]._height, INFO_TOP); + s.vLine(1, images[0]._height, s.h()- images[2]._height, INFO_MIDDLE); + s.vLine(2, images[0]._height, s.h()- images[2]._height, INFO_BOTTOM); + + // Draw the right Side of the Info Box + s.vLine(s.w() - 3, images[0]._height, s.h()- images[2]._height, INFO_TOP); + s.vLine(s.w() - 2, images[0]._height, s.h()- images[2]._height, INFO_MIDDLE); + s.vLine(s.w() - 1, images[0]._height, s.h()- images[2]._height, INFO_BOTTOM); +} + +void WidgetBase::makeInfoArea() { + makeInfoArea(_surface); +} + +void WidgetBase::drawDialogRect(const Common::Rect &r, bool raised) { + static_cast<TattooUserInterface *>(_vm->_ui)->drawDialogRect(_surface, r, raised); +} + +void WidgetBase::checkTabbingKeys(int numOptions) { +} + +Common::Rect WidgetBase::getScrollBarBounds() const { + Common::Rect r(BUTTON_SIZE, _bounds.height() - 6); + r.moveTo(_bounds.width() - BUTTON_SIZE - 3, 3); + return r; +} + +void WidgetBase::drawScrollBar(int index, int pageSize, int count) { + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + + // Fill the area with transparency + Common::Rect r = getScrollBarBounds(); + _surface.fillRect(r, TRANSPARENCY); + + bool raised = ui._scrollHighlight != 1; + _surface.fillRect(Common::Rect(r.left + 2, r.top + 2, r.right - 2, r.top + BUTTON_SIZE - 2), INFO_MIDDLE); + ui.drawDialogRect(_surface, Common::Rect(r.left, r.top, r.left + BUTTON_SIZE, r.top + BUTTON_SIZE), raised); + + raised = ui._scrollHighlight != 5; + _surface.fillRect(Common::Rect(r.left + 2, r.bottom - BUTTON_SIZE + 2, r.right - 2, r.bottom - 2), INFO_MIDDLE); + ui.drawDialogRect(_surface, Common::Rect(r.left, r.bottom - BUTTON_SIZE, r.right, r.bottom), raised); + + // Draw the arrows on the scroll buttons + byte color = index ? INFO_BOTTOM + 2 : INFO_BOTTOM; + _surface.hLine(r.left + r.width() / 2, r.top - 2 + BUTTON_SIZE / 2, r.left + r.width() / 2, color); + _surface.hLine(r.left + r.width() / 2 - 1, r.top - 1 + BUTTON_SIZE / 2, r.left + r.width() / 2 + 1, color); + _surface.hLine(r.left + r.width() / 2 - 2, r.top + BUTTON_SIZE / 2, r.left + r.width() / 2 + 2, color); + _surface.hLine(r.left + r.width() / 2 - 3, r.top + 1 + BUTTON_SIZE / 2, r.left + r.width() / 2 + 3, color); + + color = (index + pageSize) < count ? INFO_BOTTOM + 2 : INFO_BOTTOM; + _surface.hLine(r.left + r.width() / 2 - 3, r.bottom - 1 - BUTTON_SIZE + BUTTON_SIZE / 2, r.left + r.width() / 2 + 3, color); + _surface.hLine(r.left + r.width() / 2 - 2, r.bottom - 1 - BUTTON_SIZE + 1 + BUTTON_SIZE / 2, r.left + r.width() / 2 + 2, color); + _surface.hLine(r.left + r.width() / 2 - 1, r.bottom - 1 - BUTTON_SIZE + 2 + BUTTON_SIZE / 2, r.left + r.width() / 2 + 1, color); + _surface.hLine(r.left + r.width() / 2, r.bottom - 1 - BUTTON_SIZE + 3 + BUTTON_SIZE / 2, r.left + r.width() / 2, color); + + // Draw the scroll position bar + int barHeight = (r.height() - BUTTON_SIZE * 2) * pageSize / count; + barHeight = CLIP(barHeight, BUTTON_SIZE, r.height() - BUTTON_SIZE * 2); + int barY = (count <= pageSize) ? r.top + BUTTON_SIZE : r.top + BUTTON_SIZE + + (r.height() - BUTTON_SIZE * 2 - barHeight) * index / (count - pageSize); + + _surface.fillRect(Common::Rect(r.left + 2, barY + 2, r.right - 2, barY + barHeight - 3), INFO_MIDDLE); + ui.drawDialogRect(_surface, Common::Rect(r.left, barY, r.right, barY + barHeight), true); +} + +void WidgetBase::handleScrollbarEvents(int index, int pageSize, int count) { + Events &events = *_vm->_events; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + Common::Point mousePos = events.mousePos(); + + // If they're dragging the scrollbar thumb, keep it selected whilst the button is being held + if ((events._pressed || events._released) && ui._scrollHighlight == SH_THUMBNAIL) + return; + + ui._scrollHighlight = SH_NONE; + + if ((!events._pressed && !events._rightReleased) || !_scroll) + return; + + Common::Rect r = getScrollBarBounds(); + r.translate(_bounds.left, _bounds.top); + + // Calculate the Scroll Position bar + int barHeight = (r.height() - BUTTON_SIZE * 2) * pageSize / count; + barHeight = CLIP(barHeight, BUTTON_SIZE, r.height() - BUTTON_SIZE * 2); + int barY = (count <= pageSize) ? r.top + BUTTON_SIZE : r.top + BUTTON_SIZE + + (r.height() - BUTTON_SIZE * 2 - barHeight) * index / (count - pageSize); + + if (Common::Rect(r.left, r.top, r.right, r.top + BUTTON_SIZE).contains(mousePos)) + // Mouse on scroll up button + ui._scrollHighlight = SH_SCROLL_UP; + else if (Common::Rect(r.left, r.top + BUTTON_SIZE, r.right, barY).contains(mousePos)) + // Mouse on paging up area (the area of the vertical bar above the thumbnail) + ui._scrollHighlight = SH_PAGE_UP; + else if (Common::Rect(r.left, barY, r.right, barY + barHeight).contains(mousePos)) + // Mouse on scrollbar thumb + ui._scrollHighlight = SH_THUMBNAIL; + else if (Common::Rect(r.left, barY + barHeight, r.right, r.bottom - BUTTON_SIZE).contains(mousePos)) + // Mouse on paging down area (the area of the vertical bar below the thumbnail) + ui._scrollHighlight = SH_PAGE_DOWN; + else if (Common::Rect(r.left, r.bottom - BUTTON_SIZE, r.right, r.bottom).contains(mousePos)) + // Mouse on scroll down button + ui._scrollHighlight = SH_SCROLL_DOWN; +} + +void WidgetBase::handleScrolling(int &scrollIndex, int pageSize, int max) { + Events &events = *_vm->_events; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + Common::KeyCode keycode = ui._keyState.keycode; + Common::Point mousePos = events.mousePos(); + + Common::Rect r = getScrollBarBounds(); + r.translate(_bounds.left, _bounds.top); + + if (ui._scrollHighlight != SH_NONE || keycode == Common::KEYCODE_HOME || keycode == Common::KEYCODE_END + || keycode == Common::KEYCODE_PAGEUP || keycode == Common::KEYCODE_PAGEDOWN + || keycode == Common::KEYCODE_UP || keycode == Common::KEYCODE_DOWN) { + // Check for the scrollbar + if (ui._scrollHighlight == SH_THUMBNAIL) { + int yp = mousePos.y; + yp = CLIP(yp, r.top + BUTTON_SIZE + 3, r.bottom - BUTTON_SIZE - 3); + + // Calculate the line number that corresponds to the position that the mouse is on the scrollbar + int lineNum = (yp - r.top - BUTTON_SIZE - 3) * (max - pageSize) / (r.height() - BUTTON_SIZE * 2 - 6); + scrollIndex = CLIP(lineNum, 0, max - pageSize); + } + + // Get the current frame so we can check the scroll timer against it + uint32 frameNum = events.getFrameCounter(); + + if (frameNum > _dialogTimer) { + // Set the timeout for the next scroll if the mouse button remains held down + _dialogTimer = (_dialogTimer == 0) ? frameNum + pageSize : frameNum + 1; + + // Check for Scroll Up + if ((ui._scrollHighlight == SH_SCROLL_UP || keycode == Common::KEYCODE_UP) && scrollIndex) + --scrollIndex; + + // Check for Page Up + else if ((ui._scrollHighlight == SH_PAGE_UP || keycode == Common::KEYCODE_PAGEUP) && scrollIndex) + scrollIndex -= pageSize; + + // Check for Page Down + else if ((ui._scrollHighlight == SH_PAGE_DOWN || keycode == Common::KEYCODE_PAGEDOWN) + && (scrollIndex + pageSize < max)) { + scrollIndex += pageSize; + if (scrollIndex + pageSize >max) + scrollIndex = max - pageSize; + } + + // Check for Scroll Down + else if ((ui._scrollHighlight == SH_SCROLL_DOWN || keycode == Common::KEYCODE_DOWN) && (scrollIndex + pageSize < max)) + ++scrollIndex; + } + + if (keycode == Common::KEYCODE_END) + scrollIndex = max - pageSize; + + if (scrollIndex < 0 || keycode == Common::KEYCODE_HOME) + scrollIndex = 0; + } +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/widget_base.h b/engines/sherlock/tattoo/widget_base.h new file mode 100644 index 0000000000..dcafc8fb21 --- /dev/null +++ b/engines/sherlock/tattoo/widget_base.h @@ -0,0 +1,147 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_TATTOO_WIDGET_BASE_H +#define SHERLOCK_TATTOO_WIDGET_BASE_H + +#include "common/scummsys.h" +#include "common/rect.h" +#include "common/str-array.h" +#include "sherlock/surface.h" + +namespace Sherlock { + +class SherlockEngine; +class ImageFile; + +namespace Tattoo { + +class WidgetBase { +private: + uint32 _dialogTimer; +protected: + SherlockEngine *_vm; + Common::Rect _bounds; + Common::Rect _oldBounds; + Surface _surface; + bool _outsideMenu; + bool _scroll; + + /** + * Used by descendent classes to split up long text for display across multiple lines + */ + Common::String splitLines(const Common::String &str, Common::StringArray &lines, int maxWidth, uint maxLines); + + /** + * Ensure that menu is drawn entirely on-screen + */ + void restrictToScreen(); + + /** + * Draw a window frame around the dges of the passed surface + */ + void makeInfoArea(Surface &s); + + /** + * Draw a window frame around the widget's surface + */ + void makeInfoArea(); + + /** + * Draw a dialog rectangle + */ + void drawDialogRect(const Common::Rect &r, bool raised = true); + + /** + * Return the area of a widget that the scrollbar will be drawn in + */ + virtual Common::Rect getScrollBarBounds() const; + + /** + * Draw the scrollbar for the dialog + */ + void drawScrollBar(int index, int pageSize, int count); + + /** + * Handles any events when the mouse is on the scrollbar + */ + void handleScrollbarEvents(int index, int pageSize, int count); + + /** + * Handle adjusting a passed scrolling index as necessary + */ + void handleScrolling(int &scrollIndex, int pageSize, int max); + + /** + * Close the dialog + */ + void close(); + + /** + * Handle drawing the background on the area the widget is going to cover + */ + virtual void drawBackground(); +public: + WidgetBase(SherlockEngine *vm); + virtual ~WidgetBase() {} + + /** + * Returns true if the given widget is active in the user interface's widget list + */ + bool active() const; + + /** + * Erase any previous display of the widget on the screen + */ + virtual void erase(); + + /** + * Update the display of the widget on the screen + */ + virtual void draw(); + + /** + * Used by some descendents to check for keys to mouse the mouse within the dialog + */ + void checkTabbingKeys(int numOptions); + + /** + * Summon the window + */ + virtual void summonWindow(); + + /** + * Close a currently active menu + */ + virtual void banishWindow(); + + /** + * Handle event processing + */ + virtual void handleEvents() {} +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/widget_credits.cpp b/engines/sherlock/tattoo/widget_credits.cpp new file mode 100644 index 0000000000..b8e297709f --- /dev/null +++ b/engines/sherlock/tattoo/widget_credits.cpp @@ -0,0 +1,215 @@ +/* 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 "sherlock/tattoo/widget_credits.h" +#include "sherlock/tattoo/tattoo.h" + +namespace Sherlock { + +namespace Tattoo { + +WidgetCredits::WidgetCredits(SherlockEngine *vm) : _vm(vm) { + _creditSpeed = 4; + _creditsActive = false; +} + +void WidgetCredits::initCredits() { + Resources &res = *_vm->_res; + Screen &screen = *_vm->_screen; + Common::SeekableReadStream *stream = res.load("credits.txt"); + int spacing = screen.fontHeight() * 2; + int yp = screen.h(); + + _creditsActive = true; + _creditLines.clear(); + + while (stream->pos() < stream->size()) { + Common::String line = stream->readLine(); + + if (line.hasPrefix("Scroll Speed")) { + const char *p = line.c_str() + 12; + while ((*p < '0') || (*p > '9')) + p++; + + _creditSpeed = atoi(p); + } else if (line.hasPrefix("Y Spacing")) { + const char *p = line.c_str() + 12; + while ((*p < '0') || (*p > '9')) + p++; + + spacing = atoi(p) + screen.fontHeight() + 1; + } else { + int width = screen.stringWidth(line) + 2; + + _creditLines.push_back(CreditLine(line, Common::Point((screen.w() - width) / 2 + 1, yp), width)); + yp += spacing; + } + } + + // Post-processing for finding split lines + for (int l = 0; l < (int)_creditLines.size(); ++l) { + CreditLine &cl = _creditLines[l]; + const char *p = strchr(cl._line.c_str(), '-'); + + if (p != nullptr && p[1] == '>') { + cl._line2 = Common::String(p + 3); + cl._line = Common::String(cl._line.c_str(), p); + + int width = cl._width; + int width1 = screen.stringWidth(cl._line); + int width2 = screen.stringWidth(cl._line2); + + int c = 1; + for (int l1 = l + 1; l1 < (int)_creditLines.size(); ++l1) { + if ((p = strchr(_creditLines[l1]._line.c_str(), '-')) != nullptr) { + if (p[1] == '>') { + Common::String line1 = Common::String(_creditLines[l1]._line.c_str(), p); + Common::String line2 = Common::String(p + 3); + + width1 = MAX(width1, screen.stringWidth(line1)); + + if (screen.stringWidth(line2) > width2) + width2 = screen.stringWidth(line2); + ++c; + } else { + break; + } + } else { + break; + } + } + + width = width1 + width2 + screen.widestChar(); + width1 += screen.widestChar(); + + for (int l1 = l; l1 < l + c; ++l1) { + _creditLines[l1]._width = width; + _creditLines[l1]._xOffset = width1; + } + + l += c - 1; + } + } + + delete stream; +} + +void WidgetCredits::close() { + _creditsActive = false; + _creditLines.clear(); +} + +void WidgetCredits::drawCredits() { + Screen &screen = *_vm->_screen; + Common::Rect screenRect(0, 0, screen.w(), screen.h()); + Surface &bb1 = screen._backBuffer1; + + for (uint idx = 0; idx < _creditLines.size() && _creditLines[idx]._position.y < screen.h(); ++idx) { + if (screenRect.contains(_creditLines[idx]._position)) { + if (!_creditLines[idx]._line2.empty()) { + int x1 = _creditLines[idx]._position.x; + int x2 = x1 + _creditLines[idx]._xOffset; + const Common::String &line1 = _creditLines[idx]._line; + const Common::String &line2 = _creditLines[idx]._line2; + + bb1.writeString(line1, Common::Point(x1 - 1, _creditLines[idx]._position.y - 1), 0); + bb1.writeString(line1, Common::Point(x1, _creditLines[idx]._position.y - 1), 0); + bb1.writeString(line1, Common::Point(x1 + 1, _creditLines[idx]._position.y - 1), 0); + + bb1.writeString(line1, Common::Point(x1 - 1, _creditLines[idx]._position.y), 0); + bb1.writeString(line1, Common::Point(x1 + 1, _creditLines[idx]._position.y), 0); + + bb1.writeString(line1, Common::Point(x1 - 1, _creditLines[idx]._position.y + 1), 0); + bb1.writeString(line1, Common::Point(x1, _creditLines[idx]._position.y + 1), 0); + bb1.writeString(line1, Common::Point(x1 + 1, _creditLines[idx]._position.y + 1), 0); + + bb1.writeString(line1, Common::Point(x1, _creditLines[idx]._position.y), INFO_TOP); + + bb1.writeString(line2, Common::Point(x2 - 1, _creditLines[idx]._position.y - 1), 0); + bb1.writeString(line2, Common::Point(x2, _creditLines[idx]._position.y - 1), 0); + bb1.writeString(line2, Common::Point(x2 + 1, _creditLines[idx]._position.y - 1), 0); + + bb1.writeString(line2, Common::Point(x2 - 1, _creditLines[idx]._position.y), 0); + bb1.writeString(line2, Common::Point(x2 + 1, _creditLines[idx]._position.y), 0); + + bb1.writeString(line2, Common::Point(x2 - 1, _creditLines[idx]._position.y + 1), 0); + bb1.writeString(line2, Common::Point(x2, _creditLines[idx]._position.y + 1), 0); + bb1.writeString(line2, Common::Point(x2 + 1, _creditLines[idx]._position.y + 1), 0); + + bb1.writeString(line2, Common::Point(x2, _creditLines[idx]._position.y), INFO_TOP); + } else { + bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x - 1, _creditLines[idx]._position.y - 1), 0); + bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x, _creditLines[idx]._position.y - 1), 0); + bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x + 1, _creditLines[idx]._position.y - 1), 0); + + bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x - 1, _creditLines[idx]._position.y), 0); + bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x + 1, _creditLines[idx]._position.y), 0); + + bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x - 1, _creditLines[idx]._position.y + 1), 0); + bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x, _creditLines[idx]._position.y + 1), 0); + bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x + 1, _creditLines[idx]._position.y + 1), 0); + + bb1.writeString(_creditLines[idx]._line, Common::Point(_creditLines[idx]._position.x, _creditLines[idx]._position.y), INFO_TOP); + } + } + } +} + +void WidgetCredits::blitCredits() { + Screen &screen = *_vm->_screen; + Common::Rect screenRect(0, -_creditSpeed, screen.w(), screen.h() + _creditSpeed); + + for (uint idx = 0; idx < _creditLines.size(); ++idx) { + if (screenRect.contains(_creditLines[idx]._position)) { + Common::Rect r(_creditLines[idx]._width, screen.fontHeight() + 2); + r.moveTo(_creditLines[idx]._position.x, _creditLines[idx]._position.y - 1); + + screen.slamRect(r); + } + + _creditLines[idx]._position.y -= _creditSpeed; + } +} + +void WidgetCredits::eraseCredits() { + Screen &screen = *_vm->_screen; + Common::Rect screenRect(0, -_creditSpeed, screen.w(), screen.h() + _creditSpeed); + + for (uint idx = 0; idx < _creditLines.size(); ++idx) { + if (screenRect.contains(_creditLines[idx]._position)) { + Common::Rect r(_creditLines[idx]._width, screen.fontHeight() + 3); + r.moveTo(_creditLines[idx]._position.x, _creditLines[idx]._position.y - 1 + _creditSpeed); + + screen.restoreBackground(r); + } + } + + if (_creditLines[_creditLines.size() - 1]._position.y < -_creditSpeed) { + // Completely finished credits display. Note that the credits will still remain flagged as active, + // so that the user interface knows not to allow and standard scene interaction + _creditLines.clear(); + } +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/widget_credits.h b/engines/sherlock/tattoo/widget_credits.h new file mode 100644 index 0000000000..d31c6ac216 --- /dev/null +++ b/engines/sherlock/tattoo/widget_credits.h @@ -0,0 +1,89 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_TATTOO_CREDITS_H +#define SHERLOCK_TATTOO_CREDITS_H + +#include "common/array.h" +#include "common/rect.h" + +namespace Sherlock { + + class SherlockEngine; + +namespace Tattoo { + +struct CreditLine { + Common::Point _position; + int _xOffset; + int _width; + Common::String _line, _line2; + + CreditLine(const Common::String &line, const Common::Point &pt, int width) : + _line(line), _position(pt), _width(width), _xOffset(0) {} +}; + +class WidgetCredits { +private: + SherlockEngine *_vm; + Common::Array<CreditLine> _creditLines; + int _creditSpeed; + bool _creditsActive; +public: + WidgetCredits(SherlockEngine *vm); + + /** + * Returns true if the credits are active + */ + bool active() const { return _creditsActive; } + + /** + * Initialize and load credit data for display + */ + void initCredits(); + + /** + * Closes down credits display + */ + void close(); + + /** + * Draw credits on the screen + */ + void drawCredits(); + + /** + * Blit the drawn credits to the screen + */ + void blitCredits(); + + /** + * Erase any area of the screen covered by credits + */ + void eraseCredits(); +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/widget_files.cpp b/engines/sherlock/tattoo/widget_files.cpp new file mode 100644 index 0000000000..c9a20b0f8a --- /dev/null +++ b/engines/sherlock/tattoo/widget_files.cpp @@ -0,0 +1,428 @@ +/* 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 "common/translation.h" +#include "gui/saveload.h" +#include "sherlock/tattoo/widget_files.h" +#include "sherlock/tattoo/tattoo.h" +#include "sherlock/tattoo/tattoo_fixed_text.h" +#include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo_user_interface.h" + +namespace Sherlock { + +namespace Tattoo { + +#define FILES_LINES_COUNT 5 + +WidgetFiles::WidgetFiles(SherlockEngine *vm, const Common::String &target) : + SaveManager(vm, target), WidgetBase(vm), _vm(vm) { + _fileMode = SAVEMODE_NONE; + _selector = _oldSelector = -1; +} + +void WidgetFiles::show(SaveMode mode) { + Events &events = *_vm->_events; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + Common::Point mousePos = events.mousePos(); + + if (_vm->_showOriginalSavesDialog) { + // Render and display the file dialog + _fileMode = mode; + ui._menuMode = FILES_MODE; + _selector = _oldSelector = -1; + _scroll = true; + createSavegameList(); + + // Set up the display area + _bounds = Common::Rect(SHERLOCK_SCREEN_WIDTH * 2 / 3, (_surface.fontHeight() + 1) * + (FILES_LINES_COUNT + 1) + 17); + _bounds.moveTo(mousePos.x - _bounds.width() / 2, mousePos.y - _bounds.height() / 2); + + // Create the surface and render its contents + _surface.create(_bounds.width(), _bounds.height()); + render(RENDER_ALL); + + summonWindow(); + ui._menuMode = FILES_MODE; + } else if (mode == SAVEMODE_LOAD) { + showScummVMRestoreDialog(); + } else { + showScummVMSaveDialog(); + } +} + +void WidgetFiles::showScummVMSaveDialog() { + GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true); + + int slot = dialog->runModalWithCurrentTarget(); + if (slot >= 0) { + Common::String desc = dialog->getResultString(); + + if (desc.empty()) { + // create our own description for the saved game, the user didn't enter it + desc = dialog->createDefaultSaveDescription(slot); + } + + _vm->saveGameState(slot, desc); + } + + close(); + delete dialog; +} + +void WidgetFiles::showScummVMRestoreDialog() { + GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false); + int slot = dialog->runModalWithCurrentTarget(); + close(); + delete dialog; + + if (slot >= 0) { + _vm->loadGameState(slot); + } +} + +void WidgetFiles::render(FilesRenderMode mode) { + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + ImageFile &images = *ui._interfaceImages; + byte color; + + if (mode == RENDER_ALL) { + _surface.fill(TRANSPARENCY); + makeInfoArea(); + + switch (_fileMode) { + case SAVEMODE_LOAD: + _surface.writeString(FIXED(LoadGame), + Common::Point((_surface.w() - _surface.stringWidth(FIXED(LoadGame))) / 2, 5), INFO_TOP); + break; + + case SAVEMODE_SAVE: + _surface.writeString(FIXED(SaveGame), + Common::Point((_surface.w() - _surface.stringWidth(FIXED(SaveGame))) / 2, 5), INFO_TOP); + break; + + default: + break; + } + + _surface.hLine(3, _surface.fontHeight() + 7, _surface.w() - 4, INFO_TOP); + _surface.hLine(3, _surface.fontHeight() + 8, _surface.w() - 4, INFO_MIDDLE); + _surface.hLine(3, _surface.fontHeight() + 9, _surface.w() - 4, INFO_BOTTOM); + _surface.transBlitFrom(images[4], Common::Point(0, _surface.fontHeight() + 6)); + _surface.transBlitFrom(images[5], Common::Point(_surface.w() - images[5]._width, _surface.fontHeight() + 6)); + + int xp = _surface.w() - BUTTON_SIZE - 6; + _surface.vLine(xp, _surface.fontHeight() + 10, _bounds.height() - 4, INFO_TOP); + _surface.vLine(xp + 1, _surface.fontHeight() + 10, _bounds.height() - 4, INFO_MIDDLE); + _surface.vLine(xp + 2, _surface.fontHeight() + 10, _bounds.height() - 4, INFO_BOTTOM); + _surface.transBlitFrom(images[6], Common::Point(xp - 1, _surface.fontHeight() + 8)); + _surface.transBlitFrom(images[7], Common::Point(xp - 1, _bounds.height() - 4)); + } + + int xp = _surface.stringWidth("00.") + _surface.widestChar() + 5; + int yp = _surface.fontHeight() + 14; + + for (int idx = _savegameIndex; idx < (_savegameIndex + FILES_LINES_COUNT); ++idx) { + if (OP_NAMES || idx == _selector || idx == _oldSelector) { + if (idx == _selector && mode != RENDER_ALL) + color = COMMAND_HIGHLIGHTED; + else + color = INFO_TOP; + + if (mode == RENDER_NAMES_AND_SCROLLBAR) + _surface.fillRect(Common::Rect(4, yp, _surface.w() - BUTTON_SIZE - 9, yp + _surface.fontHeight()), TRANSPARENCY); + + Common::String numStr = Common::String::format("%d.", idx + 1); + _surface.writeString(numStr, Common::Point(_surface.widestChar(), yp), color); + _surface.writeString(_savegames[idx], Common::Point(xp, yp), color); + } + + yp += _surface.fontHeight() + 1; + } + + // Draw the Scrollbar if neccessary + if (mode != RENDER_NAMES) + drawScrollBar(_savegameIndex, FILES_LINES_COUNT, _savegames.size()); +} + +void WidgetFiles::handleEvents() { + Events &events = *_vm->_events; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + Common::Point mousePos = events.mousePos(); + Common::KeyState keyState = ui._keyState; + + // Handle scrollbar events + ScrollHighlight oldHighlight = ui._scrollHighlight; + handleScrollbarEvents(_savegameIndex, FILES_LINES_COUNT, _savegames.size()); + + int oldScrollIndex = _savegameIndex; + handleScrolling(_savegameIndex, FILES_LINES_COUNT, _savegames.size()); + + // See if the mouse is pointing at any filenames in the window + if (Common::Rect(_bounds.left, _bounds.top + _surface.fontHeight() + 14, + _bounds.right - BUTTON_SIZE - 5, _bounds.bottom - 5).contains(mousePos)) { + _selector = (mousePos.y - _bounds.top - _surface.fontHeight() - 14) / (_surface.fontHeight() + 1) + + _savegameIndex; + } else { + _selector = -1; + } + + // Check for the Tab key + if (keyState.keycode == Common::KEYCODE_TAB) { + // If the mouse is not over any of the filenames, move the mouse so that it points to the first one + if (_selector == -1) { + events.warpMouse(Common::Point(_bounds.right - BUTTON_SIZE - 20, + _bounds.top + _surface.fontHeight() * 2 + 8)); + } else { + // See if we're doing Tab or Shift Tab + if (keyState.flags & Common::KBD_SHIFT) { + // We're doing Shift Tab + if (_selector == _savegameIndex) + _selector = _savegameIndex + 4; + else + --_selector; + } else { + // We're doing Tab + ++_selector; + if (_selector >= _savegameIndex + 5) + _selector = _savegameIndex; + } + + events.warpMouse(Common::Point(mousePos.x, _bounds.top + _surface.fontHeight() * 2 + + 8 + (_selector - _savegameIndex) * (_surface.fontHeight() + 1))); + } + } + + // Only redraw the window if the the scrollbar position has changed + if (ui._scrollHighlight != oldHighlight || oldScrollIndex != _savegameIndex || _selector != _oldSelector) + render(RENDER_NAMES_AND_SCROLLBAR); + _oldSelector = _selector; + + if (events._firstPress && !_bounds.contains(mousePos)) + _outsideMenu = true; + + if (events._released || events._rightReleased || keyState.keycode == Common::KEYCODE_ESCAPE) { + ui._scrollHighlight = SH_NONE; + + if (_outsideMenu && !_bounds.contains(mousePos)) { + close(); + } else { + _outsideMenu = false; + + if (_selector != -1) { + if (_fileMode == SAVEMODE_LOAD) { + // We're in Load Mode + _vm->loadGameState(_selector); + } else if (_fileMode == SAVEMODE_SAVE) { + // We're in Save Mode + if (getFilename()) + _vm->saveGameState(_selector, _savegames[_selector]); + close(); + } + } + } + } +} + +bool WidgetFiles::getFilename() { + Events &events = *_vm->_events; + TattooScene &scene = *(TattooScene *)_vm->_scene; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + int index = 0; + int done = 0; + bool blinkFlag = false; + int blinkCountdown = 0; + int width; + int cursorColor = 192; + byte color, textColor; + bool insert = true; + + assert(_selector != -1); + Common::Point pt(_surface.stringWidth("00.") + _surface.widestChar() + 5, + _surface.fontHeight() + 14 + (_selector - _savegameIndex) * (_surface.fontHeight() + 1)); + + Common::String numStr = Common::String::format("%d.", _selector + 1); + _surface.writeString(numStr, Common::Point(_surface.widestChar(), pt.y), COMMAND_HIGHLIGHTED); + + Common::String filename = _savegames[_selector]; + + if (isSlotEmpty(_selector)) { + index = 0; + _surface.fillRect(Common::Rect(pt.x, pt.y, _bounds.right - BUTTON_SIZE - 9, pt.y + _surface.fontHeight() - 1), TRANSPARENCY); + filename = ""; + } else { + index = filename.size(); + _surface.writeString(filename, pt, COMMAND_HIGHLIGHTED); + pt.x = _surface.stringWidth("00.") + _surface.stringWidth(filename) + _surface.widestChar() + 5; + } + + do { + scene.doBgAnim(); + + if (talk._talkToAbort) + return false; + + char currentChar = (index == (int)filename.size()) ? ' ' : filename[index]; + Common::String charString = Common::String::format("%c", currentChar); + width = screen.charWidth(currentChar); + + // Wait for keypress + while (!events.kbHit()) { + events.pollEventsAndWait(); + events.setButtonState(); + + scene.doBgAnim(); + + if (talk._talkToAbort) + return false; + + if (--blinkCountdown <= 0) { + blinkCountdown = 3; + blinkFlag = !blinkFlag; + if (blinkFlag) { + textColor = 236; + color = cursorColor; + } else { + textColor = COMMAND_HIGHLIGHTED; + color = TRANSPARENCY; + } + + _surface.fillRect(Common::Rect(pt.x, pt.y, pt.x + width, pt.y + _surface.fontHeight()), color); + if (currentChar != ' ') + _surface.writeString(charString, pt, textColor); + } + if (_vm->shouldQuit()) + return false; + } + + Common::KeyState keyState = events.getKey(); + if (keyState.keycode == Common::KEYCODE_BACKSPACE && index > 0) { + pt.x -= _surface.charWidth(filename[index - 1]); + --index; + + if (insert) { + filename.deleteChar(index); + } else { + filename.setChar(' ', index); + } + + _surface.fillRect(Common::Rect(pt.x, pt.y, _surface.w() - BUTTON_SIZE - 9, pt.y + _surface.fontHeight() - 1), TRANSPARENCY); + _surface.writeString(filename.c_str() + index, pt, COMMAND_HIGHLIGHTED); + + } else if ((keyState.keycode == Common::KEYCODE_LEFT && index > 0) + || (keyState.keycode == Common::KEYCODE_RIGHT && index < 49 && pt.x < (_bounds.right - BUTTON_SIZE - 20)) + || (keyState.keycode == Common::KEYCODE_HOME && index > 0) + || (keyState.keycode == Common::KEYCODE_END)) { + _surface.fillRect(Common::Rect(pt.x, pt.y, pt.x + width, pt.y + _surface.fontHeight()), TRANSPARENCY); + if (currentChar) + _surface.writeString(charString, pt, COMMAND_HIGHLIGHTED); + + switch (keyState.keycode) { + case Common::KEYCODE_LEFT: + pt.x -= _surface.charWidth(filename[index - 1]); + --index; + break; + + case Common::KEYCODE_RIGHT: + pt.x += _surface.charWidth(filename[index]); + ++index; + break; + + case Common::KEYCODE_HOME: + pt.x = _surface.stringWidth("00.") + _surface.widestChar() + 5; + index = 0; + break; + + case Common::KEYCODE_END: + pt.x = _surface.stringWidth("00.") + _surface.stringWidth(filename) + _surface.widestChar() + 5; + index = filename.size(); + + while (filename[index - 1] == ' ' && index > 0) { + pt.x -= _surface.charWidth(filename[index - 1]); + --index; + } + break; + + default: + break; + } + } else if (keyState.keycode == Common::KEYCODE_INSERT) { + insert = !insert; + if (insert) + cursorColor = 192; + else + cursorColor = 200; + + } else if (keyState.keycode == Common::KEYCODE_DELETE) { + filename.deleteChar(index); + + _surface.fillRect(Common::Rect(pt.x, pt.y, _bounds.right - BUTTON_SIZE - 9, pt.y + _surface.fontHeight() - 1), TRANSPARENCY); + _surface.writeString(filename + index, pt, COMMAND_HIGHLIGHTED); + + } else if (keyState.keycode == Common::KEYCODE_RETURN) { + done = 1; + + } else if (keyState.keycode == Common::KEYCODE_ESCAPE) { + _selector = -1; + render(RENDER_NAMES_AND_SCROLLBAR); + done = -1; + } + + if ((keyState.ascii >= ' ') && ((keyState.ascii <= 168) || (keyState.ascii == 225)) && (index < 50)) { + if (pt.x + _surface.charWidth(keyState.ascii) < _surface.w() - BUTTON_SIZE - 20) { + if (insert) + filename.insertChar(keyState.ascii, index); + else + filename.setChar(keyState.ascii, index); + + _surface.fillRect(Common::Rect(pt.x, pt.y, _bounds.width() - BUTTON_SIZE - 9, + pt.y + _surface.fontHeight() - 1), TRANSPARENCY); + _surface.writeString(filename.c_str() + index, pt, COMMAND_HIGHLIGHTED); + pt.x += _surface.charWidth(keyState.ascii); + ++index; + } + } + } while (!done && !_vm->shouldQuit()); + + scene.doBgAnim(); + + if (talk._talkToAbort) + return false; + + if (done == 1) + _savegames[_selector] = filename; + + return done == 1; +} + +Common::Rect WidgetFiles::getScrollBarBounds() const { + Common::Rect scrollRect(BUTTON_SIZE, _bounds.height() - _surface.fontHeight() - 16); + scrollRect.moveTo(_bounds.width() - BUTTON_SIZE - 3, _surface.fontHeight() + 13); + + return scrollRect; +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/widget_files.h b/engines/sherlock/tattoo/widget_files.h new file mode 100644 index 0000000000..94a029d18d --- /dev/null +++ b/engines/sherlock/tattoo/widget_files.h @@ -0,0 +1,87 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_TATTOO_WIDGET_FILES_H +#define SHERLOCK_TATTOO_WIDGET_FILES_H + +#include "common/scummsys.h" +#include "sherlock/tattoo/widget_base.h" +#include "sherlock/saveload.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Tattoo { + +enum FilesRenderMode { RENDER_ALL, RENDER_NAMES, RENDER_NAMES_AND_SCROLLBAR }; + +class WidgetFiles: public WidgetBase, public SaveManager { +private: + SherlockEngine *_vm; + SaveMode _fileMode; + int _selector, _oldSelector; + + /** + * Render the dialog + */ + void render(FilesRenderMode mode); + + /** + * Show the ScummVM Save Game dialog + */ + void showScummVMSaveDialog(); + + /** + * Show the ScummVM Load Game dialog + */ + void showScummVMRestoreDialog(); + + /** + * Prompt the user for a savegame name in the currently selected slot + */ + bool getFilename(); + + /** + * Return the area of a widget that the scrollbar will be drawn in + */ + virtual Common::Rect getScrollBarBounds() const; +public: + WidgetFiles(SherlockEngine *vm, const Common::String &target); + virtual ~WidgetFiles() {} + + /** + * Prompt the user whether to quit + */ + void show(SaveMode mode); + + /** + * Handle event processing + */ + virtual void handleEvents(); +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/widget_foolscap.cpp b/engines/sherlock/tattoo/widget_foolscap.cpp new file mode 100644 index 0000000000..8246e9a371 --- /dev/null +++ b/engines/sherlock/tattoo/widget_foolscap.cpp @@ -0,0 +1,299 @@ +/* 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 "sherlock/tattoo/widget_foolscap.h" +#include "sherlock/tattoo/tattoo_fixed_text.h" +#include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo_user_interface.h" +#include "sherlock/tattoo/tattoo.h" + +namespace Sherlock { + +namespace Tattoo { + +WidgetFoolscap::WidgetFoolscap(TattooEngine *vm) : WidgetBase(vm) { + for (int idx = 0; idx < 3; ++idx) { + Common::fill(&_answers[idx][0], &_answers[idx][10], 0); + _solutions[idx] = nullptr; + } + _images = nullptr; + _numWide = 0; + _spacing = 0; + _blinkFlag = false; + _blinkCounter = 0; + _lineNum = _charNum = 0; + _solved = false; +} + +WidgetFoolscap::~WidgetFoolscap() { + delete _images; +} + +void WidgetFoolscap::show() { + Screen &screen = *_vm->_screen; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + + switch (_vm->getLanguage()) { + case Common::FR_FRA: + _lines[0] = Common::Point(34, 210); + _lines[1] = Common::Point(72, 242); + _lines[2] = Common::Point(34, 276); + _numWide = 8; + _spacing = 19; + _images = new ImageFile("paperf.vgs"); + break; + + case Common::DE_DEU: + _lines[0] = Common::Point(44, 73); + _lines[1] = Common::Point(56, 169); + _lines[2] = Common::Point(47, 256); + _numWide = 7; + _spacing = 19; + _images = new ImageFile("paperg.vgs"); + break; + + default: + // English + _lines[0] = Common::Point(65, 84); + _lines[1] = Common::Point(65, 159); + _lines[2] = Common::Point(75, 234); + _numWide = 5; + _spacing = 20; + _images = new ImageFile("paper.vgs"); + break; + } + + _solved = false; + _blinkFlag = false; + _blinkCounter = 0; + _lineNum = _charNum = 0; + _cursorPos = Common::Point(_lines[0].x + 8 - screen.widestChar() / 2, _lines[0].y - screen.fontHeight() - 2); + + // Set up window bounds + ImageFrame &paperFrame = (*_images)[0]; + _bounds = Common::Rect(paperFrame._width, paperFrame._height); + _bounds.moveTo(screen._currentScroll.x + (SHERLOCK_SCREEN_WIDTH - paperFrame._width) / 2, + (SHERLOCK_SCREEN_HEIGHT - paperFrame._height) / 2); + + // Clear answer data and set correct solution strings + for (int idx = 0; idx < 3; ++idx) + Common::fill(&_answers[idx][0], &_answers[idx][10], 0); + _solutions[0] = FIXED(Apply); + _solutions[1] = FIXED(Water); + _solutions[2] = FIXED(Heat); + + // Set up the window background + _surface.create(_bounds.width(), _bounds.height()); + _surface.blitFrom(paperFrame, Common::Point(0, 0)); + + // If they have already solved the puzzle, put the answer on the graphic + if (_vm->readFlags(299)) { + Common::Point cursorPos; + for (int line = 0; line < 3; ++line) { + cursorPos.y = _lines[line].y - screen.fontHeight() - 2; + + for (uint idx = 0; idx < strlen(_solutions[line]); ++idx) { + cursorPos.x = _lines[line].x + 8 - screen.widestChar() / 2 + idx * _spacing; + char c = _solutions[line][idx]; + + Common::String str = Common::String::format("%c", c); + _surface.writeString(str, Common::Point(cursorPos.x + screen.widestChar() / 2 + - screen.charWidth(c) / 2, cursorPos.y), 0); + } + } + } + + // Show the window + summonWindow(); + ui._menuMode = FOOLSCAP_MODE; +} + +void WidgetFoolscap::handleEvents() { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + Common::Point mousePos = events.mousePos(); + byte cursorColor = 254; + + if (events._firstPress && !_bounds.contains(mousePos)) + _outsideMenu = true; + + // If they have not solved the puzzle, let them solve it here + if (!_vm->readFlags(299)) { + if (!ui._keyState.keycode) { + if (--_blinkCounter < 0) { + _blinkCounter = 3; + _blinkFlag = !_blinkFlag; + + if (_blinkFlag) { + // Draw the caret + _surface.fillRect(Common::Rect(_cursorPos.x, _cursorPos.y, _cursorPos.x + screen.widestChar() - 1, + _cursorPos.y + screen.fontHeight() - 1), cursorColor); + + if (_answers[_lineNum][_charNum]) { + Common::String str = Common::String::format("%c", _answers[_lineNum][_charNum]); + _surface.writeString(str, Common::Point(_cursorPos.x + screen.widestChar() / 2 + - screen.charWidth(_answers[_lineNum][_charNum]) / 2, _cursorPos.y), 0); + } + } else { + // Restore background + restoreChar(); + + // Draw the character at that position if there is one + if (_answers[_lineNum][_charNum]) { + Common::String str = Common::String::format("%c", _answers[_lineNum][_charNum]); + _surface.writeString(str, Common::Point(_cursorPos.x + screen.widestChar() / 2 + - screen.charWidth(_answers[_lineNum][_charNum]) / 2, _cursorPos.y), 0); + } + } + } + } else { + // Handle keyboard events + handleKeyboardEvents(); + } + } + + if ((events._released || events._rightReleased) && _outsideMenu && !_bounds.contains(mousePos)) { + // Clicked outside window to close it + events.clearEvents(); + close(); + } +} + +void WidgetFoolscap::handleKeyboardEvents() { + Screen &screen = *_vm->_screen; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + Common::KeyState keyState = ui._keyState; + + if (((toupper(keyState.ascii) >= 'A') && (toupper(keyState.ascii) <= 'Z')) || + ((keyState.ascii >= 128) && ((keyState.ascii <= 168) || (keyState.ascii == 225)))) { + // Visible key pressed, set it and set the keycode to move the caret to the right + _answers[_lineNum][_charNum] = keyState.ascii; + keyState.keycode = Common::KEYCODE_RIGHT; + } + + // Restore background + restoreChar(); + + if (_answers[_lineNum][_charNum]) { + Common::String str = Common::String::format("%c", _answers[_lineNum][_charNum]); + _surface.writeString(str, Common::Point(_cursorPos.x + screen.widestChar() / 2 + - screen.charWidth(_answers[_lineNum][_charNum]) / 2, _cursorPos.y), 0); + } + + switch (keyState.keycode) { + case Common::KEYCODE_ESCAPE: + close(); + break; + + case Common::KEYCODE_UP: + if (_lineNum) { + --_lineNum; + if (_charNum >= (int)strlen(_solutions[_lineNum])) + _charNum = (int)strlen(_solutions[_lineNum]) - 1; + } + break; + + case Common::KEYCODE_DOWN: + if (_lineNum < 2) { + ++_lineNum; + if (_charNum >= (int)strlen(_solutions[_lineNum])) + _charNum = (int)strlen(_solutions[_lineNum]) - 1; + } + break; + + case Common::KEYCODE_BACKSPACE: + case Common::KEYCODE_LEFT: + if (_charNum) + --_charNum; + else if (_lineNum) { + --_lineNum; + + _charNum = strlen(_solutions[_lineNum]) - 1; + } + + if (keyState.keycode == Common::KEYCODE_BACKSPACE) + _answers[_lineNum][_charNum] = ' '; + break; + + case Common::KEYCODE_RIGHT: + if (_charNum < (int)strlen(_solutions[_lineNum]) - 1) + ++_charNum; + else if (_lineNum < 2) { + ++_lineNum; + _charNum = 0; + } + break; + + case Common::KEYCODE_DELETE: + _answers[_lineNum][_charNum] = ' '; + break; + + default: + break; + } + + _cursorPos.x = _lines[_lineNum].x + 8 - screen.widestChar() / 2 + _charNum * _spacing; + _cursorPos.y = _lines[_lineNum].y - screen.fontHeight() - 2; + + // See if all of their anwers are correct + if (!scumm_stricmp(_answers[0], _solutions[0]) && !scumm_stricmp(_answers[1], _solutions[1]) + && !scumm_stricmp(_answers[2], _solutions[2])) { + _solved = true; + close(); + } +} + +void WidgetFoolscap::restoreChar() { + Screen &screen = *_vm->_screen; + ImageFrame &bgFrame = (*_images)[0]; + _surface.blitFrom(bgFrame, _cursorPos, Common::Rect(_cursorPos.x, _cursorPos.y, + _cursorPos.x + screen.widestChar(), _cursorPos.y + screen.fontHeight())); +} + +void WidgetFoolscap::close() { + TattooScene &scene = *(TattooScene *)_vm->_scene; + Talk &talk = *_vm->_talk; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + delete _images; + _images = nullptr; + + // Close the window + banishWindow(); + ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; + + // Don't call the talk files if the puzzle has already been solved + if (!_vm->readFlags(299)) { + // Run the appropriate script depending on whether or not they solved the puzzle correctly + if (_solved) { + talk.talkTo("SLVE12S.TLK"); + talk.talkTo("WATS12X.TLK"); + _vm->setFlags(299); + } else { + talk.talkTo("HOLM12X.TLK"); + } + } +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/widget_foolscap.h b/engines/sherlock/tattoo/widget_foolscap.h new file mode 100644 index 0000000000..3c85998cee --- /dev/null +++ b/engines/sherlock/tattoo/widget_foolscap.h @@ -0,0 +1,82 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_TATTOO_FOOLSCAP_H +#define SHERLOCK_TATTOO_FOOLSCAP_H + +#include "sherlock/tattoo/widget_base.h" +#include "sherlock/image_file.h" + +namespace Sherlock { + +namespace Tattoo { + +class TattooEngine; + +class WidgetFoolscap: public WidgetBase { +private: + ImageFile *_images; + Common::Point _lines[3]; + char _answers[3][10]; + const char *_solutions[3]; + int _numWide; + int _spacing; + Common::Point _cursorPos; + int _blinkCounter; + bool _blinkFlag; + int _lineNum, _charNum; + bool _solved; + + /** + * Handle keyboard events + */ + void handleKeyboardEvents(); + + /** + * Restore the background for the current line/horiz position + */ + void restoreChar(); +public: + WidgetFoolscap(TattooEngine *vm); + virtual ~WidgetFoolscap(); + + /** + * Show the foolscap puzzle + */ + void show(); + + /** + * Close the window + */ + void close(); + + /** + * Handle events whilst the widget is on-screen + */ + virtual void handleEvents(); +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/widget_inventory.cpp b/engines/sherlock/tattoo/widget_inventory.cpp new file mode 100644 index 0000000000..3555ecdffd --- /dev/null +++ b/engines/sherlock/tattoo/widget_inventory.cpp @@ -0,0 +1,789 @@ +/* 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 "sherlock/tattoo/widget_inventory.h" +#include "sherlock/tattoo/tattoo_fixed_text.h" +#include "sherlock/tattoo/tattoo_people.h" +#include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo_user_interface.h" +#include "sherlock/tattoo/tattoo.h" + +namespace Sherlock { + +namespace Tattoo { + +#define INVENTORY_XSIZE 70 // Width of the box that surrounds inventory items +#define INVENTORY_YSIZE 70 // Height of the box that surrounds inventory items +#define MAX_INV_COMMANDS 10 // Maximum elements in dialog +#define NUM_INV_PER_LINE 4 // Number of inentory items per line in the dialog + +WidgetInventoryTooltip::WidgetInventoryTooltip(SherlockEngine *vm, WidgetInventory *owner) : + WidgetTooltipBase(vm), _owner(owner) { +} + +void WidgetInventoryTooltip::setText(const Common::String &str) { + // If no text specified, erase any previously displayed tooltip and free its surface + if (str.empty()) { + erase(); + _surface.free(); + return; + } + + int width = _surface.stringWidth(str) + 2; + int height = 0; + Common::String line1 = str, line2; + + // See if we need to split it into two lines + if (width > 150) { + // Yes, we do + const char *s = str.c_str(); + const char *space = nullptr; + int dif = 10000; + + while (*s) { + s = strchr(s, ' '); + + if (!s) { + if (!space) { + height = _surface.stringHeight(str) + 2; + } else { + line1 = Common::String(str.c_str(), space); + line2 = Common::String(space + 1); + height = _surface.stringHeight(line1) + _surface.stringHeight(line2) + 4; + } + break; + } else { + line1 = Common::String(str.c_str(), s); + line2 = Common::String(s + 1); + int width1 = _surface.stringWidth(line1); + int width2 = _surface.stringWidth(line2); + + if (ABS(width1 - width2) < dif) { + // Found a split point that results in less overall width + space = s; + dif = ABS(width1 - width2); + width = MAX(width1, width2); + } + + s++; + } + } + } else { + height = _surface.stringHeight(str) + 2; + } + + // Allocate a fresh surface for the new string + _bounds = Common::Rect(width, height); + _surface.create(width, height); + _surface.fill(TRANSPARENCY); + + if (line2.empty()) { + _surface.writeFancyString(str, Common::Point(0, 0), BLACK, INFO_TOP); + } else { + int xp, yp; + + xp = (_bounds.width() - _surface.stringWidth(line1) - 2) / 2; + _surface.writeFancyString(line1, Common::Point(xp, 0), BLACK, INFO_TOP); + + xp = (_bounds.width() - _surface.stringWidth(line2) - 2) / 2; + yp = _surface.stringHeight(line2) + 2; + _surface.writeFancyString(line2, Common::Point(xp, yp), BLACK, INFO_TOP); + } +} + +void WidgetInventoryTooltip::handleEvents() { + Events &events = *_vm->_events; + FixedText &fixedText = *_vm->_fixedText; + Inventory &inv = *_vm->_inventory; + TattooPeople &people = *(TattooPeople *)_vm->_people; + Scene &scene = *_vm->_scene; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + Common::Point mousePos = events.mousePos(); + Common::String str; + int select = -1, oldSelect = 999; + Common::String strWith = fixedText.getText(kFixedText_With); + Common::String strUse = fixedText.getText(kFixedText_Use); + + // Register the tooltip for requiring post-rendering drawing, since we draw directly to the screen if a scene + // mask is active, since the initial draw to the screen will be covered by the mask rendering + if (ui._mask) { + ui._postRenderWidgets.push_back(this); + } + + // If we are using an inventory item on an object in the room, display the appropriate text above the mouse cursor + if (_owner->_invVerbMode == 3) { + select = ui._bgFound; + oldSelect = ui._oldBgFound; + + if (select != -1 && (select != oldSelect || (select != -1 && _surface.empty()))) { + // See if we're pointing at a shape or a sprite + if (select < 1000) { + Object &obj = scene._bgShapes[select]; + + if (!obj._description.empty() && !obj._description.hasPrefix(" ")) { + if (_vm->getLanguage() == Common::GR_GRE) { + + if (!_owner->_swapItems) + str = Common::String::format("%s %s %s %s", _owner->_action.c_str(), obj._description.c_str(), + inv[_owner->_invSelect]._name.c_str(), _owner->_verb.c_str()); + else + str = Common::String::format("%s %s %s %s", _owner->_action.c_str(), inv[_owner->_invSelect]._name.c_str(), + obj._description.c_str(), _owner->_verb.c_str()); + } else { + if (_owner->_swapItems) + str = Common::String::format("%s %s %s %s", _owner->_verb.c_str(), obj._description.c_str(), _owner->_action.c_str(), + inv[_owner->_invSelect]._name.c_str()); + else + str = Common::String::format("%s %s %s %s", _owner->_verb.c_str(), inv[_owner->_invSelect]._name.c_str(), + _owner->_action.c_str(), obj._description.c_str()); + } + } + } else { + Person &person = people[ui._bgFound - 1000]; + + if (!person._description.empty() && !person._description.hasPrefix(" ")) { + if (_vm->getLanguage() == Common::GR_GRE) { + if (!_owner->_swapItems) + str = Common::String::format("%s %s %s %s", _owner->_action.c_str(), person._description.c_str(), + inv[_owner->_invSelect]._name.c_str(), _owner->_verb.c_str()); + else + str = Common::String::format("%s %s %s %s", _owner->_action.c_str(), inv[_owner->_invSelect]._name.c_str(), + person._description.c_str(), _owner->_verb.c_str()); + } else { + + if (_owner->_swapItems) + str = Common::String::format("%s %s %s %s", _owner->_verb.c_str(), person._description.c_str(), + _owner->_action.c_str(), inv[_owner->_invSelect]._name.c_str()); + else + str = Common::String::format("%s %s %s %s", _owner->_verb.c_str(), + inv[_owner->_invSelect]._name.c_str(), _owner->_action.c_str(), person._description.c_str()); + } + } + } + } + } else { + const Common::Rect &b = _owner->_bounds; + Common::Rect r(b.left + 3, b.top + 3, b.right - 3 - BUTTON_SIZE, b.bottom - 3); + + if (r.contains(mousePos)) { + select = (mousePos.x - r.left) / (INVENTORY_XSIZE + 3) + NUM_INVENTORY_SHOWN / 2 * + ((mousePos.y - r.top) / (INVENTORY_YSIZE + 3)) + inv._invIndex; + + if (select >= inv._holdings) { + select = -1; + } else { + oldSelect = _owner->_invSelect; + + if (select != _owner->_invSelect || _surface.empty()) { + + if (_owner->_invMode == 1) { + // See if we were pointing at a shapre or sprite + if (ui._activeObj < 1000) { + Object &obj = scene._bgShapes[ui._activeObj]; + + if (!obj._description.empty() && !obj._description.hasPrefix(" ")) + str = Common::String::format("%s %s %s %s", strUse.c_str(), inv[select]._name.c_str(), + strWith.c_str(), obj._description.c_str()); + } else { + Person &person = people[ui._activeObj - 1000]; + + if (!person._description.empty() && !person._description.hasPrefix(" ")) + str = Common::String::format("%s %s %s %s", strUse.c_str(), inv[select]._name.c_str(), + strWith.c_str(), person._description.c_str()); + } + } else { + if (_owner->_invVerbMode == 2) + str = Common::String::format("%s %s %s %s", strUse.c_str(), inv[_owner->_invSelect]._name.c_str(), + strWith.c_str(), inv[select]._name.c_str()); + else + str = inv[select]._description.c_str(); + } + } + } + } + } + + // See if they are pointing at a different inventory object and we need to + // change the graphics of the Text Tag + if (select != oldSelect || (select != -1 && _surface.empty())) { + // Set the text + setText(str); + + if (_owner->_invVerbMode != 3) + _owner->_invSelect = select; + else + ui._oldBgFound = select; + } else if (select == -1 && oldSelect != -1) { + setText(Common::String()); + return; + } + + if (_owner->_invVerbMode == 3) + // Adjust tooltip to be above the inventory item being shown above the standard cursor + mousePos.y -= events._hotspotPos.y; + + // Update the position of the tooltip + int xs = CLIP(mousePos.x - _bounds.width() / 2, 0, SHERLOCK_SCENE_WIDTH - _bounds.width()); + int ys = CLIP(mousePos.y - _bounds.height(), 0, SHERLOCK_SCREEN_HEIGHT - _bounds.height()); + _bounds.moveTo(xs, ys); +} + +/*----------------------------------------------------------------*/ + +WidgetInventoryVerbs::WidgetInventoryVerbs(SherlockEngine *vm, WidgetInventory *owner) : + WidgetBase(vm), _owner(owner) { + _invVerbSelect = _oldInvVerbSelect = -1; +} + +void WidgetInventoryVerbs::load() { + Events &events = *_vm->_events; + Inventory &inv = *_vm->_inventory; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + Common::Point mousePos = events.mousePos(); + + // Make the Verb List for this Inventory Item + _inventCommands.clear(); + _inventCommands.push_back(FIXED(Look)); + + // Default the Action word to "with" + _owner->_action = _vm->getLanguage() == Common::GR_GRE ? "" : FIXED(With); + + // Search all the bgshapes for any matching Target Fields + for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) { + Object &obj = scene._bgShapes[idx]; + + if (obj._type != INVALID && obj._type != HIDDEN) { + for (int useNum = 0; useNum < 6; ++useNum) { + if (!obj._use[useNum]._verb.hasPrefix("*") && + !obj._use[useNum]._target.compareToIgnoreCase(inv[_owner->_invSelect]._name)) { + // Make sure the Verb is not already in the list + bool found1 = false; + for (uint cmdNum = 0; cmdNum < _inventCommands.size() && !found1; ++cmdNum) { + if (!_inventCommands[cmdNum].compareToIgnoreCase(obj._use[useNum]._verb)) + found1 = true; + } + + if (!found1) { + _inventCommands.push_back(obj._use[useNum]._verb); + + // Check for any Special Action commands + for (int nameNum = 0; nameNum < 4; ++nameNum) { + if (!scumm_strnicmp(obj._use[useNum]._names[nameNum].c_str(), "*V", 2)) { + if (!scumm_strnicmp(obj._use[useNum]._names[nameNum].c_str(), "*VSWAP", 6)) + _owner->_swapItems = true; + else + _owner->_action = Common::String(obj._use[useNum]._names[nameNum].c_str() + 2); + } + } + } + } + } + } + } + + // Search the NPCs for matches as well + for (int idx = 1; idx < MAX_CHARACTERS; ++idx) { + for (int useNum = 0; useNum < 2; ++useNum) { + if (!people[idx]._use[useNum]._target.compareToIgnoreCase(inv[_owner->_invSelect]._name) && + !people[idx]._use[useNum]._verb.empty() && !people[idx]._use[useNum]._verb.hasPrefix(" ")) { + bool found1 = false; + for (uint cmdNum = 0; cmdNum < _inventCommands.size() && !found1; ++cmdNum) { + if (!_inventCommands[cmdNum].compareToIgnoreCase(people[idx]._use[cmdNum]._verb)) + found1 = true; + } + + if (!found1) + _inventCommands.push_back(people[idx]._use[useNum]._verb); + } + } + } + + // Finally see if the item itself has a verb + if (!inv[_owner->_invSelect]._verb._verb.empty()) { + // Don't add "Solve" to the Foolscap if it's already been "Solved" + if (inv[_owner->_invSelect]._verb._verb.compareToIgnoreCase(FIXED(Solve)) || !_vm->readFlags(299)) + _inventCommands.push_back(inv[_owner->_invSelect]._verb._verb); + } + + // Now find the widest command in the _inventCommands array + int width = 0; + for (uint idx = 0; idx < _inventCommands.size(); ++idx) + width = MAX(width, _surface.stringWidth(_inventCommands[idx])); + + // Set up bounds for the menu + _bounds = Common::Rect(width + _surface.widestChar() * 2 + 6, + (_surface.fontHeight() + 7) * _inventCommands.size() + 3); + _bounds.moveTo(mousePos.x - _bounds.width() / 2, mousePos.y - _bounds.height() / 2); + + // Create the surface + _surface.create(_bounds.width(), _bounds.height()); + _surface.fill(TRANSPARENCY); + makeInfoArea(); + + // Draw the Verb commands and the lines separating them + ImageFile &images = *ui._interfaceImages; + for (int idx = 0; idx < (int)_inventCommands.size(); ++idx) { + _surface.writeString(_inventCommands[idx], Common::Point((_bounds.width() - + _surface.stringWidth(_inventCommands[idx])) / 2, (_surface.fontHeight() + 7) * idx + 5), INFO_TOP); + + if (idx < (int)_inventCommands.size() - 1) { + _surface.vLine(3, (_surface.fontHeight() + 7) * (idx + 1), _bounds.right - 4, INFO_TOP); + _surface.vLine(3, (_surface.fontHeight() + 7) * (idx + 1) + 1, _bounds.right - 4, INFO_MIDDLE); + _surface.vLine(3, (_surface.fontHeight() + 7) * (idx + 1) + 2, _bounds.right - 4, INFO_BOTTOM); + + _surface.transBlitFrom(images[4], Common::Point(0, (_surface.fontHeight() + 7) * (idx + 1))); + _surface.transBlitFrom(images[5], Common::Point(_bounds.width() - images[5]._width, + (_surface.fontHeight() + 7) * (idx + 1) - 1)); + } + } + + summonWindow(); +} + +void WidgetInventoryVerbs::handleEvents() { + Events &events = *_vm->_events; + Inventory &inv = *_vm->_inventory; + TattooScene &scene = *(TattooScene *)_vm->_scene; + Common::Point mousePos = events.mousePos(); + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + TattooEngine &vm = *(TattooEngine *)_vm; + + // Handle changing highlighted verb entry + highlightControls(); + + // See if they want to close the menu (by clicking outside the menu) + Common::Rect innerBounds = _bounds; + innerBounds.grow(-3); + + // Flag is they started pressing outside of the menu + if (events._firstPress && !_bounds.contains(mousePos)) + _outsideMenu = true; + + if (events._released || events._rightReleased || ui._keyState.keycode == Common::KEYCODE_ESCAPE) { + ui._scrollHighlight = SH_NONE; + banishWindow(); + + if ((_outsideMenu && !innerBounds.contains(mousePos)) || ui._keyState.keycode == Common::KEYCODE_ESCAPE) { + _owner->_invVerbMode = 0; + } else if (innerBounds.contains(mousePos)) { + _outsideMenu = false; + + // Check if they are trying to solve the Foolscap puzzle, or looking at the completed puzzle + bool doFoolscap = !inv[_owner->_invSelect]._name.compareToIgnoreCase(FIXED(Inv6)) && + !_inventCommands[_invVerbSelect].compareToIgnoreCase(FIXED(Solve)); + doFoolscap |= (!inv[_owner->_invSelect]._name.compareToIgnoreCase(FIXED(Inv6)) || !inv[_owner->_invSelect]._name.compareToIgnoreCase(FIXED(Inv7))) + && !_inventCommands[_invVerbSelect].compareToIgnoreCase(FIXED(Look)) && vm.readFlags(299); + + if (doFoolscap) { + // Close the entire Inventory and return to Standard Mode + _owner->_invVerbMode = 0; + + _owner->_tooltipWidget.banishWindow(); + _owner->banishWindow(); + inv.freeInv(); + + events.clearEvents(); + vm.doFoolscapPuzzle(); + } else if (_invVerbSelect == 0) { + // They have released the mouse on the Look Verb command, so Look at the inventory item + ui._invLookFlag = true; + inv.freeInv(); + ui._windowOpen = false; + ui._lookPos = mousePos; + ui.printObjectDesc(inv[_owner->_invSelect]._examine, true); + } else { + _owner->_invVerbMode = 3; + ui._oldBgFound = -1; + + // See if the selected Verb with the selected Iventory Item, is to be used by itself + if (!_inventCommands[_invVerbSelect].compareToIgnoreCase(inv[_owner->_invSelect]._verb._verb) || + !inv[_owner->_invSelect]._verb._target.compareToIgnoreCase("*SELF")) { + inv.freeInv(); + + ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; + events.clearEvents(); + ui.checkAction(inv[_owner->_invSelect]._verb, 2000); + } else { + _owner->_verb = _inventCommands[_invVerbSelect]; + } + + // If we are still in Inventory Mode, setup the graphic to float in front of the mouse cursor + if (ui._menuMode == INV_MODE) { + // Add the inventory item to the cursor + ImageFrame &imgFrame = (*inv._invShapes[_owner->_invSelect - inv._invIndex])[0]; + events.setCursor(ARROW, Common::Point(-100, imgFrame._height), imgFrame._frame); + + // Close the inventory dialog without banishing it, so it can keep getting events + // to handle tooltips and actually making the selection of what object to use them item on + inv.freeInv(); + _owner->_surface.free(); + } + } + } + } +} + +void WidgetInventoryVerbs::highlightControls() { + Events &events = *_vm->_events; + Common::Point mousePos = events.mousePos(); + + Common::Rect innerBounds = _bounds; + innerBounds.grow(-3); + + // Set the highlighted verb + _invVerbSelect = -1; + if (innerBounds.contains(mousePos)) + _invVerbSelect = (mousePos.y - _bounds.top - 3) / (_surface.fontHeight() + 7); + + // See if the highlighted verb has changed + if (_invVerbSelect != _oldInvVerbSelect) { + // Draw the list again, with the new highlighting + for (int idx = 0; idx < (int)_inventCommands.size(); ++idx) { + byte color = (idx == _invVerbSelect) ? COMMAND_HIGHLIGHTED : INFO_TOP; + _surface.writeString(_inventCommands[idx], Common::Point( + (_bounds.width() - _surface.stringWidth(_inventCommands[idx])) / 2, + (_surface.fontHeight() + 7) * idx + 5), color); + } + + _oldInvVerbSelect = _invVerbSelect; + } +} + +/*----------------------------------------------------------------*/ + +WidgetInventory::WidgetInventory(SherlockEngine *vm) : WidgetBase(vm), + _tooltipWidget(vm, this), _verbList(vm, this) { + _invMode = 0; + _invVerbMode = 0; + _invSelect = _oldInvSelect = -1; + _selector = _oldSelector = -1; + _swapItems = false; +} + +void WidgetInventory::load(int mode) { + Events &events = *_vm->_events; + Inventory &inv = *_vm->_inventory; + Screen &screen = *_vm->_screen; + Common::Point mousePos = events.mousePos(); + + if (mode == 3) { + mode = 2; + mousePos = Common::Point(screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH / 2, SHERLOCK_SCREEN_HEIGHT / 2); + } + + if (mode != 0) + _invMode = mode; + _invVerbMode = 0; + _invSelect = _oldInvSelect = -1; + _selector = _oldSelector = -1; + _scroll = true; + + if (mode == 0) { + banishWindow(); + } else { + _bounds = Common::Rect((INVENTORY_XSIZE + 3) * NUM_INVENTORY_SHOWN / 2 + BUTTON_SIZE + 6, + (INVENTORY_YSIZE + 3) * 2 + 3); + _bounds.moveTo(mousePos.x - _bounds.width() / 2, mousePos.y - _bounds.height() / 2); + } + + // Ensure menu will be on-screen + restrictToScreen(); + + // Load the inventory data + inv.loadInv(); + + // Redraw the inventory menu on the widget surface + _surface.create(_bounds.width(), _bounds.height()); + _surface.fill(TRANSPARENCY); + + // Draw the window background and then the inventory on top of it + makeInfoArea(_surface); + drawBars(); + drawInventory(); +} + +void WidgetInventory::drawBars() { + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + ImageFile &images = *ui._interfaceImages; + int x; + + _surface.hLine(3, INVENTORY_YSIZE + 3, _bounds.width() - 4, INFO_TOP); + _surface.hLine(3, INVENTORY_YSIZE + 4, _bounds.width() - 4, INFO_MIDDLE); + _surface.hLine(3, INVENTORY_YSIZE + 5, _bounds.width() - 4, INFO_BOTTOM); + _surface.transBlitFrom(images[4], Common::Point(0, INVENTORY_YSIZE + 2)); + + for (int idx = 1; idx <= NUM_INVENTORY_SHOWN / 2; ++idx) { + x = idx * (INVENTORY_XSIZE + 3); + + _surface.vLine(x, 3, _bounds.height() - 4, INFO_TOP); + _surface.vLine(x + 1, 3, _bounds.height() - 4, INFO_MIDDLE); + _surface.vLine(x + 2, 3, _bounds.height() - 4, INFO_BOTTOM); + + _surface.transBlitFrom(images[6], Common::Point(x - 1, 1)); + _surface.transBlitFrom(images[7], Common::Point(x - 1, _bounds.height() - 4)); + _surface.transBlitFrom(images[6], Common::Point(x - 1, INVENTORY_YSIZE + 5)); + _surface.transBlitFrom(images[7], Common::Point(x - 1, INVENTORY_YSIZE + 2)); + } + + _surface.hLine(x + 2, INVENTORY_YSIZE + 2, INVENTORY_YSIZE + 8, INFO_BOTTOM); +} + +void WidgetInventory::drawInventory() { + Inventory &inv = *_vm->_inventory; + + // TODO: Refactor _invIndex into this widget class + for (int idx = 0, itemId = inv._invIndex; idx < NUM_INVENTORY_SHOWN; ++idx, ++itemId) { + // Figure out the drawing position + Common::Point pt(3 + (INVENTORY_XSIZE + 3) * (idx % (NUM_INVENTORY_SHOWN / 2)), + 3 + (INVENTORY_YSIZE + 3) * (idx / (NUM_INVENTORY_SHOWN / 2))); + + // Draw the box to serve as the background for the item + _surface.hLine(pt.x + 1, pt.y, pt.x + INVENTORY_XSIZE - 2, TRANSPARENCY); + _surface.fillRect(Common::Rect(pt.x, pt.y + 1, pt.x + INVENTORY_XSIZE, pt.y + INVENTORY_YSIZE - 1), TRANSPARENCY); + _surface.hLine(pt.x + 1, pt.y + INVENTORY_YSIZE - 1, pt.x + INVENTORY_XSIZE - 2, TRANSPARENCY); + + // Draw the item + if (itemId < inv._holdings) { + ImageFrame &img = (*inv._invShapes[idx])[0]; + _surface.transBlitFrom(img, Common::Point(pt.x + (INVENTORY_XSIZE - img._width) / 2, + pt.y + (INVENTORY_YSIZE - img._height) / 2)); + } + } + + drawScrollBar(inv._invIndex / NUM_INV_PER_LINE, NUM_INVENTORY_SHOWN / NUM_INV_PER_LINE, + (inv._holdings + NUM_INV_PER_LINE - 1) / NUM_INV_PER_LINE); +} + +void WidgetInventory::handleEvents() { + TattooEngine &vm = *(TattooEngine *)_vm; + Events &events = *_vm->_events; + Inventory &inv = *_vm->_inventory; + People &people = *_vm->_people; + TattooScene &scene = *(TattooScene *)_vm->_scene; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + Common::Point mousePos = events.mousePos(); + + if (_invVerbMode == 1) { + checkTabbingKeys(MAX_INV_COMMANDS); + } else if (_invVerbMode == 0) { + checkInvTabbingKeys(); + + // Handle scrollbar events + int oldScrollIndex = inv._invIndex / NUM_INV_PER_LINE; + int invIndex = inv._invIndex / NUM_INV_PER_LINE; + + ScrollHighlight oldHighlight = ui._scrollHighlight; + handleScrollbarEvents(invIndex, NUM_INVENTORY_SHOWN / NUM_INV_PER_LINE, + (inv._holdings + NUM_INV_PER_LINE - 1) / NUM_INV_PER_LINE); + + handleScrolling(invIndex, NUM_INVENTORY_SHOWN / NUM_INV_PER_LINE, + (inv._holdings + NUM_INV_PER_LINE - 1) / NUM_INV_PER_LINE); + + if (oldScrollIndex != invIndex) { + // Starting visible item index has changed, so set the index and reload inventory graphics + inv._invIndex = invIndex * NUM_INV_PER_LINE; + inv.freeGraphics(); + inv.loadGraphics(); + } + + if (ui._scrollHighlight != oldHighlight || oldScrollIndex != invIndex) { + drawInventory(); + return; + } + } + + if (_invVerbMode != 1) + _tooltipWidget.handleEvents(); + + // Flag is they started pressing outside of the menu + if (events._firstPress && !_bounds.contains(mousePos)) + _outsideMenu = true; + + if (_invVerbMode != 3) + highlightControls(); + + // See if they released a mouse button button + if (events._released || events._rightReleased || ui._keyState.keycode == Common::KEYCODE_ESCAPE) { + ui._scrollHighlight = SH_NONE; + + // See if they have a Verb List open for an Inventry Item + if (_invVerbMode == 1) + return; + + if (_invVerbMode == 3) { + // Selecting object after inventory verb has been selected + _tooltipWidget.banishWindow(); + close(); + + if (ui._keyState.keycode != Common::KEYCODE_ESCAPE) { + // If user pointed at an item, use the selected inventory item with this item + bool found = false; + if (ui._bgFound != -1) { + if (ui._personFound) { + for (int idx = 0; idx < 2; ++idx) { + if (!people[ui._bgFound - 1000]._use[idx]._verb.compareToIgnoreCase(_verb) && + !people[ui._bgFound - 1000]._use[idx]._target.compareToIgnoreCase(_invTarget)) { + ui.checkAction(people[ui._bgFound - 1000]._use[idx], ui._bgFound); + found = true; + } + } + } else { + for (int idx = 0; idx < 6; ++idx) { + if (!ui._bgShape->_use[idx]._verb.compareToIgnoreCase(_verb) && + !ui._bgShape->_use[idx]._target.compareToIgnoreCase(_invTarget)) { + ui.checkAction(ui._bgShape->_use[idx], ui._bgFound); + found = true; + } + } + } + } + + if (!found) + ui.putMessage("%s", FIXED(NoEffect)); + } + } else if ((_outsideMenu && !_bounds.contains(mousePos)) || ui._keyState.keycode == Common::KEYCODE_ESCAPE) { + // Want to close the window (clicked outside of it). So close the window and return to Standard + close(); + + } else if (_bounds.contains(mousePos)) { + // Mouse button was released inside the inventory window + _outsideMenu = false; + + // See if they are pointing at one of the inventory items + if (_invSelect != -1) { + // See if they are in Use Obj with Inv. Mode (they right clicked on an item + // in the room and selected "Use with Inv.") + if (_invMode == 1) { + _tooltipWidget.banishWindow(); + banishWindow(); + + // See if the item in the room that they started with was a person + bool found = false; + if (ui._activeObj >= 1000) { + // Object was a person, activate anything in his two verb fields + for (int idx = 0; idx < 2; ++idx) { + if (!people[ui._activeObj - 1000]._use[idx]._target.compareToIgnoreCase(inv[_invSelect]._name)) { + ui.checkAction(people[ui._activeObj - 1000]._use[idx], ui._activeObj); + found = true; + } + } + } else { + // Object was a regular object, activate anything in its verb fields + for (int idx = 0; idx < 6; ++idx) { + if (!scene._bgShapes[ui._activeObj]._use[idx]._target.compareToIgnoreCase(inv[_invSelect]._name)) { + ui.checkAction(scene._bgShapes[ui._activeObj]._use[idx], ui._activeObj); + found = true; + } + } + } + if (!found) + ui.putMessage("%s", FIXED(NoEffect)); + + } else { + // See if they right clicked on an item + if (events._rightReleased) { + _invVerbMode = 1; + _verbList._oldInvVerbSelect = -1; + _tooltipWidget.banishWindow(); + + // Keep track of the name of the inventory object so we can check it against the target fields + // of verbs when we activate it + _invTarget = inv[_invSelect]._name; + _swapItems = false; + + _verbList.load(); + } else { + // They left clicked on an inventory item, so Look at it + + // Check if they are looking at the solved Foolscap + if ((!inv[_invSelect]._name.compareToIgnoreCase(FIXED(Inv6)) || !inv[_invSelect]._name.compareToIgnoreCase(FIXED(Inv7))) + && vm.readFlags(299)) { + banishWindow(); + _tooltipWidget.erase(); + + _invVerbMode = 0; + inv.freeInv(); + + events.clearEvents(); + events.setCursor(ARROW); + ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; + + scene.doBgAnim(); + vm.doFoolscapPuzzle(); + } else { + ui._invLookFlag = true; + inv.freeInv(); + + _tooltipWidget.banishWindow(); + ui._windowOpen = false; + ui._lookPos = mousePos; + ui.printObjectDesc(inv[_invSelect]._examine, true); + } + } + } + } + } + } +} + +void WidgetInventory::checkInvTabbingKeys() { +} + +void WidgetInventory::highlightControls() { + // TODO +} + +void WidgetInventory::banishWindow() { + WidgetBase::banishWindow(); + + _verbList.banishWindow(); +} + +void WidgetInventory::draw() { + WidgetBase::draw(); + _tooltipWidget.draw(); +} + +void WidgetInventory::erase() { + WidgetBase::erase(); + _tooltipWidget.erase(); +} + +void WidgetInventory::close() { + Events &events = *_vm->_events; + Inventory &inv = *_vm->_inventory; + TattooScene &scene = *(TattooScene *)_vm->_scene; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + + banishWindow(); + inv.freeInv(); + events.clearEvents(); + + events.setCursor(ARROW); + ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/widget_inventory.h b/engines/sherlock/tattoo/widget_inventory.h new file mode 100644 index 0000000000..a051c328e9 --- /dev/null +++ b/engines/sherlock/tattoo/widget_inventory.h @@ -0,0 +1,158 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_TATTOO_WIDGET_INVENTORY_H +#define SHERLOCK_TATTOO_WIDGET_INVENTORY_H + +#include "common/scummsys.h" +#include "sherlock/tattoo/widget_base.h" +#include "sherlock/tattoo/widget_tooltip.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Tattoo { + +#define NUM_INVENTORY_SHOWN 8 // Number of Inventory Items Shown + +class WidgetInventory; + +class WidgetInventoryTooltip: public WidgetTooltipBase { +private: + WidgetInventory *_owner; +protected: + /** + * Overriden from base class, since tooltips have a completely transparent background + */ + virtual void drawBackground() {} +public: + WidgetInventoryTooltip(SherlockEngine *vm, WidgetInventory *owner); + virtual ~WidgetInventoryTooltip() {} + + /** + * Set the text for the tooltip + */ + void setText(const Common::String &str); + + /** + * Handle updating the tooltip state + */ + virtual void handleEvents(); +}; + +class WidgetInventoryVerbs : public WidgetBase { +private: + WidgetInventory *_owner; + Common::StringArray _inventCommands; + + void highlightControls(); +public: + int _invVerbSelect, _oldInvVerbSelect; +public: + WidgetInventoryVerbs(SherlockEngine *vm, WidgetInventory *owner); + virtual ~WidgetInventoryVerbs() {} + + void load(); + + /** + * Handle updating the tooltip state + */ + virtual void handleEvents(); +}; + +class WidgetInventory: public WidgetBase { + friend class WidgetInventoryTooltip; + friend class WidgetInventoryVerbs; +private: + int _invVerbMode; + int _selector, _oldSelector; + int _invSelect, _oldInvSelect; + WidgetInventoryTooltip _tooltipWidget; + WidgetInventoryVerbs _verbList; + bool _swapItems; + Surface _menuSurface; + Common::String _invTarget; + + /** + * Draw the bars within the dialog + */ + void drawBars(); + + /** + * Check for keys to mouse the mouse within the inventory dialog + */ + void checkInvTabbingKeys(); + + /** + * Highlights the controls + */ + void highlightControls(); +public: + int _invMode; + Common::String _action; + Common::String _verb; +public: + WidgetInventory(SherlockEngine *vm); + virtual ~WidgetInventory() {} + + /** + * Load the inventory window + */ + void load(int mode); + + /** + * Draw the inventory on the surface + */ + void drawInventory(); + + /** + * Close the window + */ + void close(); + + /** + * Handle events whilst the widget is on-screen + */ + virtual void handleEvents(); + + /** + * Close a currently active menu + */ + virtual void banishWindow(); + + /** + * Erase any previous display of the widget on the screen + */ + virtual void erase(); + + /** + * Update the display of the widget on the screen + */ + virtual void draw(); +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/widget_lab.cpp b/engines/sherlock/tattoo/widget_lab.cpp new file mode 100644 index 0000000000..2873b12f22 --- /dev/null +++ b/engines/sherlock/tattoo/widget_lab.cpp @@ -0,0 +1,198 @@ +/* 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 "sherlock/tattoo/widget_lab.h" +#include "sherlock/tattoo/tattoo_fixed_text.h" +#include "sherlock/tattoo/tattoo_user_interface.h" +#include "sherlock/tattoo/tattoo.h" + +namespace Sherlock { + +namespace Tattoo { + +WidgetLab::WidgetLab(SherlockEngine *vm) : WidgetBase(vm) { + _labObject = nullptr; +} + +void WidgetLab::summonWindow() { + WidgetBase::summonWindow(); + _labObject = nullptr; +} + +void WidgetLab::handleEvents() { + Events &events = *_vm->_events; + Scene &scene = *_vm->_scene; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + Common::Point mousePos = events.mousePos(); + + WidgetBase::handleEvents(); + bool noDesc = false; + + // Handle drawing tooltips. If the user is dragging a lab item, display a tooltip for using the item + // on another. Otherwise, fall back on showing standard tooltips + if (events.getCursor() == INVALID_CURSOR) + displayLabNames(); + else + ui.displayObjectNames(); + + // See if they've released a mouse button to do an action + if (events._released || events._rightReleased) { + // See if the mouse was released in an exit/arrow zone (ie. the "Exit" button) + ui._exitZone = -1; + if (ui._arrowZone != -1 && events._released) + ui._exitZone = ui._arrowZone; + + // Turn any current tooltip off + if (ui._arrowZone == -1 || events._rightReleased) + ui._tooltipWidget.setText(""); + + if (ui._bgFound != -1) { + if (ui._bgShape->_description.hasPrefix(" ") || ui._bgShape->_description.empty()) + noDesc = true; + } else { + noDesc = true; + } + + events.setCursor(ARROW); + + if (events._rightReleased) { + // If the player is dragging an object around, restore it to its previous location and reset the cursor + if (_labObject) { + _labObject->toggleHidden(); + + // Toggle any other objects (like shadows) tied to this object + for (int idx = 0; idx < 6; ++idx) { + if (!_labObject->_use[idx]._target.compareToIgnoreCase("Toggle")) { + for (int nameNum = 0; nameNum < 4; ++nameNum) + scene.toggleObject(_labObject->_use[idx]._names[nameNum]); + } + } + + events.setCursor(ARROW); + } + + // Show the command list for this object + ui._verbsWidget.load(!noDesc); + } else if (!noDesc) { + // The player has released on an object, see if they had an object selected + // that will be used with this new object + if (_labObject) { + // See if the dragged object can be used with the new object + for (int idx = 0; idx < 6; ++idx) { + // See if the name of the dragged object is in any of the Target + // fields of the verbs for the new object + if (!_labObject->_name.compareToIgnoreCase(ui._bgShape->_use[idx]._target.c_str())) { + // This object can be used, so use it + ui.checkAction(ui._bgShape->_use[idx], ui._bgFound); + ui._activeObj = -1; + } + } + + // Restore the dragged object to its previous location + _labObject->toggleHidden(); + + // Toggle any other objects (like shadows) tied to this object + for (int idx = 0; idx < 6; ++idx) { + if (!_labObject->_use[idx]._target.compareToIgnoreCase("Toggle")) { + for (int nameNum = 0; nameNum < 4; ++nameNum) + scene.toggleObject(_labObject->_use[idx]._names[nameNum]); + } + } + } else if (!ui._bgShape->_name.compareToIgnoreCase("Exit")) { + // Execute the Exit button's script, which will leave the scene + ui.lookAtObject(); + } + } else { + // The player has released the mouse while NOT over an object. If theu were dragging an object + // around with the mouse, restore it to its previous location and reset the cursor + if (_labObject) { + _labObject->toggleHidden(); + + // Toggle any other objects (like shadows) tied to this object + for (int idx = 0; idx < 6; ++idx) { + if (!_labObject->_use[idx]._target.compareToIgnoreCase("Toggle")) { + for (int nameNum = 0; nameNum < 4; ++nameNum) + scene.toggleObject(_labObject->_use[idx]._names[nameNum]); + } + } + } + } + + _labObject = nullptr; + ui._tooltipWidget._offsetY = 0; + } else if (events._pressed && !_labObject) { + // If the mouse is over an object and the object is not SOLID, then we need to pick this object + // up so the player can move it around + if (ui._bgFound != -1) { + // Check if the object is set as SOLID, you can't pick up Solid items + if (ui._bgShape->_aType != SOLID && ui._bgShape->_type != NO_SHAPE) { + // Save a reference to the object about to be dragged + _labObject = ui._bgShape; + + // Set the mouse cursor to the object + Graphics::Surface &img = _labObject->_imageFrame->_frame; + Common::Point cursorOffset = mousePos - _labObject->_position; + events.setCursor(ARROW, cursorOffset, img); + ui._tooltipWidget._offsetY = cursorOffset.y; + + // Hide this object until they are done with it (releasing it) + _labObject->toggleHidden(); + + // Toggle any other objects (like shadows) tied to this object + for (int idx = 0; idx < 6; ++idx) { + if (!_labObject->_use[idx]._target.compareToIgnoreCase("Toggle")) { + for (int nameNum = 0; nameNum < 4; ++nameNum) + scene.toggleObject(_labObject->_use[idx]._names[nameNum]); + } + } + } + } + } +} + +void WidgetLab::displayLabNames() { + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + + // See if thay are pointing at a different object and we need to change the tooltip + if (ui._bgFound != ui._oldBgFound) { + // See if there is a new object to be displayed + if (ui._bgFound == -1) { + ui._tooltipWidget.setText(""); + } else { + Common::String str = Common::String::format("%s %s %s %s", FIXED(Use), _labObject->_description.c_str(), + FIXED(With), ui._bgShape->_description.c_str()); + + // Make sure that the Object has a name + if (!ui._bgShape->_description.empty() && !ui._bgShape->_description.hasPrefix(" ")) { + ui._tooltipWidget.setText(str); + } else { + ui._tooltipWidget.setText(""); + } + } + } + + ui._oldArrowZone = ui._arrowZone; +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/widget_lab.h b/engines/sherlock/tattoo/widget_lab.h new file mode 100644 index 0000000000..2f19200b81 --- /dev/null +++ b/engines/sherlock/tattoo/widget_lab.h @@ -0,0 +1,66 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_TATTOO_WIDGET_LAB_H +#define SHERLOCK_TATTOO_WIDGET_LAB_H + +#include "common/scummsys.h" +#include "sherlock/tattoo/widget_base.h" +#include "sherlock/objects.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Tattoo { + +class WidgetLab: public WidgetBase { +private: + Object *_labObject; + + /** + * Display tooltips of an object being dragged along with any object the dragged + * object is currently over + */ + void displayLabNames(); +public: + Common::String _remainingText; +public: + WidgetLab(SherlockEngine *vm); + virtual ~WidgetLab() {} + + /** + * Summon the window + */ + virtual void summonWindow(); + + /** + * Handle event processing + */ + virtual void handleEvents(); +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/widget_options.cpp b/engines/sherlock/tattoo/widget_options.cpp new file mode 100644 index 0000000000..5dc6fc55b9 --- /dev/null +++ b/engines/sherlock/tattoo/widget_options.cpp @@ -0,0 +1,388 @@ +/* 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 "sherlock/tattoo/widget_options.h" +#include "sherlock/tattoo/tattoo.h" +#include "sherlock/tattoo/tattoo_fixed_text.h" +#include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo_user_interface.h" + +namespace Sherlock { + +namespace Tattoo { + +WidgetOptions::WidgetOptions(SherlockEngine *vm) : WidgetBase(vm) { + _midiSliderX = _digiSliderX = 0; + _selector = _oldSelector = -1; +} + +void WidgetOptions::load() { + Events &events = *_vm->_events; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + _centerPos = events.mousePos(); + + render(); + + summonWindow(); + ui._menuMode = OPTION_MODE; +} + +void WidgetOptions::handleEvents() { + TattooEngine &vm = *(TattooEngine *)_vm; + Events &events = *_vm->_events; + Music &music = *_vm->_music; + Screen &screen = *_vm->_screen; + Sound &sound = *_vm->_sound; + Talk &talk = *_vm->_talk; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + Common::Point mousePos = events.mousePos(); + + if (talk._talkToAbort) { + sound.stopSound(); + return; + } + + // Flag if they started pressing outside the window + if (events._firstPress && !_bounds.contains(mousePos)) + _outsideMenu = true; + + if (events.kbHit()) { + ui._keyState = events.getKey(); + + // Emulate a mouse release if Enter or Space Bar is pressed + if (ui._keyState.keycode == Common::KEYCODE_RETURN || ui._keyState.keycode == Common::KEYCODE_SPACE) { + events._pressed = events._oldButtons = false; + events._released = true; + } else if (ui._keyState.keycode == Common::KEYCODE_ESCAPE) { + close(); + return; + } else { + checkTabbingKeys(11); + } + } + + // Check highlighting the various controls + if (_bounds.contains(mousePos)) { + _selector = (mousePos.y - _bounds.top) / (_surface.fontHeight() + 7); + + // If one of the sliders has been selected, & the mouse is not pressed, reset the selector to -1 + if ((_selector == 3 || _selector == 6) && !events._pressed) + _selector = -1; + } else { + _selector = -1; + if (_outsideMenu && (events._released || events._rightReleased)) { + events.clearEvents(); + close(); + return; + } + } + + // If the selected control has changed, redraw the dialog contents + if (_selector != _oldSelector) + render(OP_CONTENTS); + _oldSelector = _selector; + + // Adjust the Volume Sliders (if neccessary) here + switch (_selector) { + case 3: { + // Set Music Volume + _midiSliderX = mousePos.x - _bounds.left; + if (_midiSliderX < _surface.widestChar()) + _midiSliderX = _surface.widestChar(); + else + if (_midiSliderX > _bounds.width() - _surface.widestChar()) + _midiSliderX = _bounds.width() - _surface.widestChar(); + + int newVolume = (_midiSliderX - _surface.widestChar()) * 255 / (_bounds.width() - _surface.widestChar() * 2); + if (newVolume != music._musicVolume) { + music.setMusicVolume(newVolume); + vm.saveConfig(); + } + + render(OP_NAMES); + break; + } + + case 6: { + // Set Digitized Volume + _digiSliderX = mousePos.x - _bounds.left; + if (_digiSliderX < _surface.widestChar()) + _digiSliderX = _surface.widestChar(); + else if (_digiSliderX > _bounds.width() - _surface.widestChar()) + _digiSliderX = _bounds.width() - _surface.widestChar(); + + int temp = sound._soundVolume; + sound._soundVolume = (_digiSliderX - _surface.widestChar()) * 255 / (_bounds.width() - _surface.widestChar() * 2); + if (sound._soundVolume != temp) { + sound.setVolume(sound._soundVolume); + vm.saveConfig(); + } + + render(OP_NAMES); + break; + } + + default: + break; + } + + // Option selected + if (events._released || events._rightReleased) { + events.clearEvents(); + _outsideMenu = false; + int temp = _selector; + _selector = -1; + + switch (temp) { + case 0: + // Load Game + close(); + ui.loadGame(); + break; + + case 1: + // Save Game + close(); + ui.saveGame(); + break; + + case 2: + // Toggle Music + music._musicOn = !music._musicOn; + if (!music._musicOn) + music.stopMusic(); + else + music.startSong(); + + render(OP_NAMES); + vm.saveConfig(); + break; + + case 4: + // Toggle Sound Effects + sound.stopSound(); + sound._digitized = !sound._digitized; + + render(OP_NAMES); + vm.saveConfig(); + break; + + case 5: + // Toggle Voices + sound._voices = !sound._voices; + + render(OP_NAMES); + vm.saveConfig(); + break; + + case 7: + // Toggle Text Windows + vm._textWindowsOn = !vm._textWindowsOn; + + render(OP_NAMES); + vm.saveConfig(); + break; + + case 8: { + // New Font Style + int fontNumber = screen.fontNumber() + 1; + if (fontNumber == 7) + fontNumber = 0; + screen.setFont(fontNumber); + + render(OP_ALL); + vm.saveConfig(); + break; + } + + case 9: + // Toggle Transparent Menus + vm._transparentMenus = !vm._transparentMenus; + + render(OP_NAMES); + vm.saveConfig(); + break; + + case 10: + // Quit + banishWindow(); + ui.doQuitMenu(); + break; + + default: + break; + } + + _oldSelector = -1; + } +} + +void WidgetOptions::render(OptionRenderMode mode) { + TattooEngine &vm = *(TattooEngine *)_vm; + Music &music = *_vm->_music; + Sound &sound = *_vm->_sound; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + ImageFile &images = *ui._interfaceImages; + const char *const OFF_ON[2] = { FIXED(Off), FIXED(On) }; + + // Draw the border if necessary + if (mode == OP_ALL) { + // Set bounds for the dialog + Common::String widestString = Common::String::format("%s %s", FIXED(TransparentMenus), FIXED(Off)); + _bounds = Common::Rect(_surface.stringWidth(widestString) + _surface.widestChar() * 2 + 6, + (_surface.fontHeight() + 7) * 11 + 3); + _bounds.moveTo(_centerPos.x - _bounds.width() / 2, _centerPos.y - _bounds.height() / 2); + + // Get slider positions + _midiSliderX = music._musicVolume * (_bounds.width() - _surface.widestChar() * 2) / 255 + _surface.widestChar(); + _digiSliderX = sound._soundVolume * (_bounds.width() - _surface.widestChar() * 2) / 255 + _surface.widestChar(); + + // Setup the dialog + _surface.create(_bounds.width(), _bounds.height()); + _surface.fill(TRANSPARENCY); + makeInfoArea(); + + // Draw the lines separating options in the dialog + int yp = _surface.fontHeight() + 7; + for (int idx = 0; idx < 7; ++idx) { + _surface.transBlitFrom(images[4], Common::Point(0, yp - 1)); + _surface.transBlitFrom(images[5], Common::Point(_surface.w() - images[5]._width, yp - 1)); + _surface.hLine(3, yp, _surface.w() - 4, INFO_TOP); + _surface.hLine(3, yp + 1, _surface.w() - 4, INFO_MIDDLE); + _surface.hLine(3, yp + 2, _surface.w() - 4, INFO_BOTTOM); + + yp += _surface.fontHeight() + 7; + if (idx == 1) + yp += _surface.fontHeight() + 7; + else if (idx == 2) + yp += (_surface.fontHeight() + 7) * 2; + } + } + + // Now go through and display all the items that can be highlighted + for (int idx = 0, yp = 5; idx < 11; ++idx, yp += _surface.fontHeight() + 7) { + if (mode == OP_ALL || idx == _selector || idx == _oldSelector) { + if (mode == OP_NAMES) + _surface.fillRect(Common::Rect(4, yp, _surface.w() - 5, yp + _surface.fontHeight() - 1), TRANSPARENCY); + byte color = (idx == _selector) ? COMMAND_HIGHLIGHTED : INFO_TOP; + Common::String str; + + switch (idx) { + case 0: + str = FIXED(LoadGame); + break; + + case 1: + str = FIXED(SaveGame); + break; + + case 2: + str = Common::String::format("%s %s", FIXED(Music), OFF_ON[music._musicOn]); + break; + + case 3: { + int num = (_surface.fontHeight() + 4) & 0xfe; + int sliderY = yp + num / 2 - 8; + + _surface.fillRect(Common::Rect(4, sliderY - (num - 6) / 2, _surface.w() - 5, + sliderY - (num - 6) / 2 + num - 1), TRANSPARENCY); + _surface.fillRect(Common::Rect(_surface.widestChar(), sliderY + 2, + _surface.w() - _surface.widestChar() - 1, sliderY + 3), INFO_MIDDLE); + drawDialogRect(Common::Rect(_surface.widestChar(), sliderY, _surface.w() - _surface.widestChar(), sliderY + 6)); + + _surface.fillRect(Common::Rect(_midiSliderX - 1, sliderY - (num - 6) / 2 + 2, + _midiSliderX + 1, sliderY - (num - 6) / 2 + num - 3), INFO_MIDDLE); + drawDialogRect(Common::Rect(_midiSliderX - 3, sliderY - (num - 6) / 2, + _midiSliderX + 4, sliderY - (num - 6) / 2 + num)); + + if (_midiSliderX - 4 > _surface.widestChar()) + _surface.fillRect(Common::Rect(_midiSliderX - 4, sliderY, _midiSliderX - 4, sliderY + 4), INFO_BOTTOM); + if (_midiSliderX + 4 < _surface.w() - _surface.widestChar()) + _surface.fillRect(Common::Rect(_midiSliderX + 4, sliderY, _midiSliderX + 4, sliderY + 4), INFO_BOTTOM); + break; + } + + case 4: + str = Common::String::format("%s %s", FIXED(SoundEffects), OFF_ON[sound._digitized]); + break; + + case 5: + str = Common::String::format("%s %s", FIXED(Voices), OFF_ON[sound._voices]); + break; + + case 6: { + int num = (_surface.fontHeight() + 4) & 0xfe; + int sliderY = yp + num / 2 - 8; + + _surface.fillRect(Common::Rect(4, sliderY - (num - 6) / 2, _surface.w() - 5, + sliderY - (num - 6) / 2 + num - 1), TRANSPARENCY); + _surface.fillRect(Common::Rect(_surface.widestChar(), sliderY + 2, _surface.w() - _surface.widestChar() - 1, + sliderY + 3), INFO_MIDDLE); + drawDialogRect(Common::Rect(_surface.widestChar(), sliderY, _surface.w() - _surface.widestChar(), sliderY + 6)); + _surface.fillRect(Common::Rect(_digiSliderX - 1, sliderY - (num - 6) / 2 + 2, _digiSliderX + 1, + sliderY - (num - 6) / 2 + num - 3), INFO_MIDDLE); + drawDialogRect(Common::Rect(_digiSliderX - 3, sliderY - (num - 6) / 2, _digiSliderX + 4, + sliderY - (num - 6) / 2 + num)); + if (_digiSliderX - 4 > _surface.widestChar()) + _surface.fillRect(Common::Rect(_digiSliderX - 4, sliderY, _digiSliderX - 4, sliderY + 4), INFO_BOTTOM); + if (_digiSliderX + 4 < _surface.w() - _surface.widestChar()) + _surface.fillRect(Common::Rect(_digiSliderX + 4, sliderY, _digiSliderX + 4, sliderY + 4), INFO_BOTTOM); + break; + } + + case 7: + if (!sound._voices) { + color = INFO_BOTTOM; + str = Common::String::format("%s %s", FIXED(TextWindows), FIXED(On)); + } else { + str = Common::String::format("%s %s", FIXED(TextWindows), OFF_ON[vm._textWindowsOn]); + } + break; + + case 8: + str = FIXED(ChangeFont); + break; + + case 9: + str = Common::String::format("%s %s", FIXED(TransparentMenus), OFF_ON[vm._transparentMenus]); + break; + + case 10: + str = FIXED(Quit); + break; + + default: + break; + } + + // Unless we're doing one of the Slider Controls, print the text for the line + if (idx != 3 && idx != 6) { + int xp = (_surface.w() - _surface.stringWidth(str)) / 2; + _surface.writeString(str, Common::Point(xp, yp), color); + } + } + } +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/widget_options.h b/engines/sherlock/tattoo/widget_options.h new file mode 100644 index 0000000000..9be0105ab4 --- /dev/null +++ b/engines/sherlock/tattoo/widget_options.h @@ -0,0 +1,69 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_TATTOO_WIDGET_OPTIONS_H +#define SHERLOCK_TATTOO_WIDGET_OPTIONS_H + +#include "common/scummsys.h" +#include "sherlock/tattoo/widget_base.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Tattoo { + +enum OptionRenderMode { OP_ALL = 0, OP_CONTENTS = 1, OP_NAMES = 2}; + +/** + * Handles displaying the options dialog + */ +class WidgetOptions : public WidgetBase { +private: + int _midiSliderX, _digiSliderX; + int _selector, _oldSelector; + Common::Point _centerPos; + + /** + * Render the contents of the dialog onto the widget's surface + */ + void render(OptionRenderMode mode = OP_ALL); +public: + WidgetOptions(SherlockEngine *vm); + virtual ~WidgetOptions() {} + + /** + * Load and then display the options dialog + */ + void load(); + + /** + * Handle event processing + */ + virtual void handleEvents(); +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/widget_password.cpp b/engines/sherlock/tattoo/widget_password.cpp new file mode 100644 index 0000000000..3dd0e308ff --- /dev/null +++ b/engines/sherlock/tattoo/widget_password.cpp @@ -0,0 +1,210 @@ +/* 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 "sherlock/tattoo/widget_password.h" +#include "sherlock/tattoo/tattoo.h" +#include "sherlock/tattoo/tattoo_fixed_text.h" +#include "sherlock/tattoo/tattoo_user_interface.h" + +namespace Sherlock { + +namespace Tattoo { + +WidgetPassword::WidgetPassword(SherlockEngine *vm) : WidgetBase(vm) { + _blinkFlag = false; + _blinkCounter = 0; + _index = 0; + _cursorColor = 192; + _insert = true; +} + +void WidgetPassword::show() { + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + ImageFile &images = *ui._interfaceImages; + + // Set the up window to be centered on the screen + _bounds = Common::Rect(_surface.widestChar() * 20 + 6, (_surface.fontHeight() + 7) * 2 + 3); + _bounds.moveTo(SHERLOCK_SCREEN_WIDTH / 2 - _bounds.width() / 2, SHERLOCK_SCREEN_HEIGHT / 2 - _bounds.height() / 2); + + // Create the surface + _surface.create(_bounds.width(), _bounds.height()); + _surface.fill(TRANSPARENCY); + makeInfoArea(); + + // Draw the header area + _surface.writeString(FIXED(EnterPassword), Common::Point((_bounds.width() - _surface.stringWidth(FIXED(EnterPassword))) / 2, 5), INFO_TOP); + _surface.hLine(3, _surface.fontHeight() + 7, _bounds.width() - 4, INFO_TOP); + _surface.hLine(3, _surface.fontHeight() + 8, _bounds.width() - 4, INFO_MIDDLE); + _surface.hLine(3, _surface.fontHeight() + 9, _bounds.width() - 4, INFO_BOTTOM); + _surface.transBlitFrom(images[4], Common::Point(0, _surface.fontHeight() + 7 - 1)); + _surface.transBlitFrom(images[5], Common::Point(_bounds.width() - images[5]._width, _surface.fontHeight() + 7 - 1)); + + // Set the password entry data + _cursorPos = Common::Point(_surface.widestChar(), _surface.fontHeight() + 12); + _password = ""; + _index = 0; + _cursorColor = 192; + _insert = true; + + // Show the dialog + ui._menuMode = PASSWORD_MODE; + summonWindow(); +} + +void WidgetPassword::handleEvents() { + Events &events = *_vm->_events; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + Common::Point mousePos = events.mousePos(); + const Common::KeyCode &keycode = ui._keyState.keycode; + char currentChar = (_index == (int)_password.size()) ? ' ' : _password[_index]; + int width = _surface.charWidth(currentChar); + + if (!keycode) { + // Nothing entered, so keep blinking the cursor + if (--_blinkCounter < 0) { + _blinkCounter = 3; + _blinkFlag = !_blinkFlag; + + byte color, textColor; + if (_blinkFlag) { + textColor = 236; + color = _cursorColor; + } else { + textColor = COMMAND_HIGHLIGHTED; + color = TRANSPARENCY; + } + + // Draw the cursor and the character it's over + _surface.fillRect(Common::Rect(_cursorPos.x, _cursorPos.y, _cursorPos.x + width, _cursorPos.y + _surface.fontHeight()), color); + if (currentChar != ' ') + _surface.writeString(Common::String::format("%c", _password[_index]), _cursorPos, textColor); + } + } else if (keycode == Common::KEYCODE_BACKSPACE && _index) { + _cursorPos.x -= _surface.charWidth(_password[_index - 1]); + + if (_insert) + _password.deleteChar(_index - 1); + else + _password.setChar(' ', _index - 1); + + // Redraw the text + --_index; + _surface.fillRect(Common::Rect(_cursorPos.x, _cursorPos.y, _bounds.width() - 9, _cursorPos.y + + _surface.fontHeight() - 1), TRANSPARENCY); + _surface.writeString(_password.c_str() + _index, _cursorPos, COMMAND_HIGHLIGHTED); + } else if ((keycode == Common::KEYCODE_LEFT && _index > 0) + || (keycode == Common::KEYCODE_RIGHT && _index < (int)_password.size() && _cursorPos.x < (_bounds.width() - _surface.widestChar() - 3)) + || (keycode == Common::KEYCODE_HOME && _index > 0) + || (keycode == Common::KEYCODE_END)) { + // Restore character the cursor was previously over + _surface.fillRect(Common::Rect(_cursorPos.x, _cursorPos.y, _cursorPos.x + width, _cursorPos.y + _surface.fontHeight()), TRANSPARENCY); + if (currentChar != ' ') + _surface.writeString(Common::String::format("%c", _password[_index]), _cursorPos, COMMAND_HIGHLIGHTED); + + switch (keycode) { + case Common::KEYCODE_LEFT: + _cursorPos.x -= _surface.charWidth(_password[_index - 1]); + --_index; + break; + case Common::KEYCODE_RIGHT: + _cursorPos.x += _surface.charWidth(_password[_index]); + ++_index; + break; + case Common::KEYCODE_HOME: + _cursorPos.x = _surface.widestChar(); + _index = 0; + break; + case Common::KEYCODE_END: + _cursorPos.x = _surface.stringWidth(_password) + _surface.widestChar(); + _index = _password.size(); + + while (_index > 0 && _password[_index - 1] == ' ') { + _cursorPos.x -= _surface.charWidth(_password[_index - 1]); + --_index; + } + break; + default: + break; + } + } else if (keycode == Common::KEYCODE_INSERT) { + _insert = !_insert; + _cursorColor = _insert ? 192 : 200; + } else if (keycode == Common::KEYCODE_DELETE) { + if (_index < (int)_password.size()) + _password.deleteChar(_index); + + // Redraw the text + _surface.fillRect(Common::Rect(_cursorPos.x, _cursorPos.y, _bounds.width() - 9, _cursorPos.y + + _surface.fontHeight() - 1), TRANSPARENCY); + _surface.writeString(_password.c_str() + _index, _cursorPos, COMMAND_HIGHLIGHTED); + } else if (keycode == Common::KEYCODE_RETURN || keycode == Common::KEYCODE_ESCAPE) { + close(); + return; + } else if (((ui._keyState.ascii >= ' ' && ui._keyState.ascii < 169) || ui._keyState.ascii == 225)) { + if (_cursorPos.x + _surface.charWidth(ui._keyState.ascii) < _bounds.width() - _surface.widestChar() - 3) { + if (_insert) + _password.insertChar(ui._keyState.ascii, _index); + else + _password.setChar(ui._keyState.ascii, _index); + + // Redraw the text + _surface.fillRect(Common::Rect(_cursorPos.x, _cursorPos.y, _bounds.width() - 9, _cursorPos.y + + _surface.fontHeight() - 1), TRANSPARENCY); + _surface.writeString(_password.c_str() + _index, _cursorPos, COMMAND_HIGHLIGHTED); + + _cursorPos.x += _surface.charWidth(ui._keyState.ascii); + ++_index; + } + } + + // Also handle clicking outside the window to abort + if (events._firstPress && !_bounds.contains(mousePos)) + _outsideMenu = true; + + if ((events._released || events._rightReleased) && _outsideMenu && !_bounds.contains(mousePos)) { + close(); + } +} + +void WidgetPassword::close() { + Talk &talk = *_vm->_talk; + + banishWindow(); + if (talk._talkToAbort) + return; + + // See if they entered the correct password + Common::String correct1 = FIXED(CorrectPassword); + Common::String correct2 = Common::String::format("%s?", FIXED(CorrectPassword)); + Common::String correct3 = Common::String::format("%s ?", FIXED(CorrectPassword)); + + if (!_password.compareToIgnoreCase(correct1) || !_password.compareToIgnoreCase(correct2) + || !_password.compareToIgnoreCase(correct3)) + // They got it correct + _vm->setFlags(149); + + talk.talkTo("LASC52P"); +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/widget_password.h b/engines/sherlock/tattoo/widget_password.h new file mode 100644 index 0000000000..f7e82c798d --- /dev/null +++ b/engines/sherlock/tattoo/widget_password.h @@ -0,0 +1,68 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_TATTOO_WIDGET_PASSWORD_H +#define SHERLOCK_TATTOO_WIDGET_PASSWORD_H + +#include "common/scummsys.h" +#include "sherlock/tattoo/widget_base.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Tattoo { + +class WidgetPassword: public WidgetBase { +private: + Common::Point _cursorPos; + Common::String _password; + int _index; + bool _blinkFlag; + int _blinkCounter; + byte _cursorColor; + bool _insert; + + /** + * Close the window and check if the entered password is correct + */ + void close(); +public: + WidgetPassword(SherlockEngine *vm); + virtual ~WidgetPassword() {} + + /** + * Show the password entry window + */ + void show(); + + /** + * Handle event processing + */ + virtual void handleEvents(); +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/widget_quit.cpp b/engines/sherlock/tattoo/widget_quit.cpp new file mode 100644 index 0000000000..f853e7f47f --- /dev/null +++ b/engines/sherlock/tattoo/widget_quit.cpp @@ -0,0 +1,155 @@ +/* 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 "sherlock/tattoo/widget_quit.h" +#include "sherlock/tattoo/tattoo.h" +#include "sherlock/tattoo/tattoo_fixed_text.h" +#include "sherlock/tattoo/tattoo_user_interface.h" + +namespace Sherlock { + +namespace Tattoo { + +WidgetQuit::WidgetQuit(SherlockEngine *vm) : WidgetBase(vm) { + _select = _oldSelect = -1; +} + +void WidgetQuit::show() { + Events &events = *_vm->_events; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + ImageFile &images = *ui._interfaceImages; + Common::Point mousePos = events.mousePos(); + const char *YES = FIXED(Yes); + const char *NO = FIXED(No); + + // Set up the display area + _bounds = Common::Rect(_surface.stringWidth(FIXED(AreYouSureYou)) + _surface.widestChar() * 2, + (_surface.fontHeight() + 7) * 4); + _bounds.moveTo(mousePos.x - _bounds.width() / 2, mousePos.y - _bounds.height() / 2); + + // Create the surface + _surface.create(_bounds.width(), _bounds.height()); + _surface.fill(TRANSPARENCY); + makeInfoArea(); + + // Draw the message text + _surface.writeString(FIXED(AreYouSureYou), Common::Point((_surface.w() - _surface.stringWidth(FIXED(AreYouSureYou))) / 2, 5), INFO_TOP); + _surface.writeString(FIXED(WishToQuit), Common::Point((_surface.w() - _surface.stringWidth(FIXED(WishToQuit))) / 2, + _surface.fontHeight() + 9), INFO_TOP); + + // Draw the horizontal bars seperating the commands and the message + int yp = (_surface.fontHeight() + 4) * 2 + 3; + for (int idx = 0; idx < 2; ++idx) { + _surface.transBlitFrom(images[4], Common::Point(0, yp - 1)); + _surface.transBlitFrom(images[5], Common::Point(_surface.w() - images[5]._width, yp - 1)); + _surface.hLine(3, yp, _surface.w() - 4, INFO_TOP); + _surface.hLine(3, yp + 1, _surface.w() - 4, INFO_MIDDLE); + _surface.hLine(3, yp + 2, _surface.w() - 4, INFO_BOTTOM); + + const char *btn = (idx == 0) ? YES : NO; + _surface.writeString(btn, Common::Point((_bounds.width() - _surface.stringWidth(btn)) / 2, yp + 5), INFO_TOP); + yp += _surface.fontHeight() + 7; + } + + ui._menuMode = QUIT_MODE; + summonWindow(); +} + +void WidgetQuit::handleEvents() { + Events &events = *_vm->_events; + Talk &talk = *_vm->_talk; + Common::Point mousePos = events.mousePos(); + Common::Rect yesRect(_bounds.left, _bounds.top + (_surface.fontHeight() + 4) * 2 + 3, _bounds.right, + _bounds.top + (_surface.fontHeight() + 4) * 2 + 3 + _surface.fontHeight() + 7); + Common::Rect noRect(_bounds.left, _bounds.top + (_surface.fontHeight() + 4) * 2 + _surface.fontHeight() + 10, + _bounds.right, _bounds.top + (_surface.fontHeight() + 4) * 2 + 10 + _surface.fontHeight() * 2 + 7); + + if (talk._talkToAbort) + return; + + // Determine the highlighted item + _select = -1; + if (yesRect.contains(mousePos)) + _select = 1; + else if (noRect.contains(mousePos)) + _select = 0; + + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + + switch (keyState.keycode) { + case Common::KEYCODE_TAB: + // If the mouse is not over any of the options, move the mouse so that it points to the first option + if (_select == -1) + events.warpMouse(Common::Point(_bounds.right - 10, _bounds.top + (_surface.fontHeight() + 4) * 2 + + 3 + _surface.fontHeight() + 1)); + else if (_select == 1) + events.warpMouse(Common::Point(mousePos.x, _bounds.top + (_surface.fontHeight() + 4) * 2 + + 3 + _surface.fontHeight() * 2 + 11)); + else + events.warpMouse(Common::Point(mousePos.x, _bounds.top + (_surface.fontHeight() + 4) * 2 + + 3 + _surface.fontHeight() + 1)); + break; + + case Common::KEYCODE_ESCAPE: + case Common::KEYCODE_n: + close(); + return; + + case Common::KEYCODE_y: + close(); + _vm->quitGame(); + break; + + default: + break; + } + } + + // Check for change of the highlighted item + if (_select != _oldSelect) { + byte color = (_select == 1) ? COMMAND_HIGHLIGHTED : INFO_TOP; + int yp = (_surface.fontHeight() + 4) * 2 + 8; + _surface.writeString(FIXED(Yes), Common::Point((_surface.w() - _surface.stringWidth(FIXED(Yes))) / 2, yp), color); + + color = (_select == 0) ? COMMAND_HIGHLIGHTED : INFO_TOP; + yp += (_surface.fontHeight() + 7); + _surface.writeString(FIXED(No), Common::Point((_surface.w() - _surface.stringWidth(FIXED(No))) / 2, yp), color); + } + _oldSelect = _select; + + // Flag is they started pressing outside of the menu + if (events._firstPress && !_bounds.contains(mousePos)) + _outsideMenu = true; + + if (events._released || events._rightReleased) { + events.clearEvents(); + close(); + if (_select == 1) + // Yes selected + _vm->quitGame(); + } +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/widget_quit.h b/engines/sherlock/tattoo/widget_quit.h new file mode 100644 index 0000000000..b8b640f2d2 --- /dev/null +++ b/engines/sherlock/tattoo/widget_quit.h @@ -0,0 +1,57 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_TATTOO_WIDGET_QUIT_H +#define SHERLOCK_TATTOO_WIDGET_QUIT_H + +#include "common/scummsys.h" +#include "sherlock/tattoo/widget_base.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Tattoo { + +class WidgetQuit: public WidgetBase { +private: + int _select, _oldSelect; +public: + WidgetQuit(SherlockEngine *vm); + virtual ~WidgetQuit() {} + + /** + * Prompt the user whether to quit + */ + void show(); + + /** + * Handle event processing + */ + virtual void handleEvents(); +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/widget_talk.cpp b/engines/sherlock/tattoo/widget_talk.cpp new file mode 100644 index 0000000000..00e8233a95 --- /dev/null +++ b/engines/sherlock/tattoo/widget_talk.cpp @@ -0,0 +1,470 @@ +/* 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 "sherlock/tattoo/widget_talk.h" +#include "sherlock/tattoo/tattoo_fixed_text.h" +#include "sherlock/tattoo/tattoo_journal.h" +#include "sherlock/tattoo/tattoo_people.h" +#include "sherlock/tattoo/tattoo_talk.h" +#include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo_user_interface.h" +#include "sherlock/tattoo/tattoo.h" + +namespace Sherlock { + +namespace Tattoo { + +#define STATEMENT_NUM_X 6 +#define NUM_VISIBLE_TALK_LINES 6 + +WidgetTalk::WidgetTalk(SherlockEngine *vm) : WidgetBase(vm) { + _talkScrollIndex = 0; + _selector = _oldSelector = -1; + _talkTextX = 0; + _dialogTimer = 0; +} + +void WidgetTalk::getTalkWindowSize() { + TattooTalk &talk = *(TattooTalk *)_vm->_talk; + int width, height; + + // See how many statements are going to be available + int numStatements = 0; + for (uint idx = 0; idx < talk._statements.size(); ++idx) { + if (talk._statements[idx]._talkMap != -1) + ++numStatements; + } + + width = SHERLOCK_SCREEN_WIDTH * 2 / 3; + + // Split up the questions into separate strings for each line + _bounds = Common::Rect(width, 1); + setStatementLines(); + + // Make sure that the window does not get too big + if (_statementLines.size() < 7) { + height = (_surface.fontHeight() + 1) * _statementLines.size() + 9; + _scroll = false; + } else { + // Set up the height to a constrained amount, and add extra width for the scrollbar + width += BUTTON_SIZE + 3; + height = (_surface.fontHeight() + 1) * 6 + 9; + _scroll = true; + } + + _bounds = Common::Rect(width, height); +} + +void WidgetTalk::load() { + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooScene &scene = *(TattooScene *)_vm->_scene; + + // Figure out the window size + getTalkWindowSize(); + + // Place the window centered above the player + Common::Point pt; + int scaleVal = scene.getScaleVal(people[HOLMES]._position); + pt.x = people[HOLMES]._position.x / FIXED_INT_MULTIPLIER - _bounds.width() / 2; + + if (scaleVal == SCALE_THRESHOLD) { + pt.x += people[0].frameWidth() / 2; + pt.y = people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES].frameHeight() + - _bounds.height() - _surface.fontHeight(); + } else { + pt.x += people[HOLMES]._imageFrame->sDrawXSize(scaleVal) / 2; + pt.y = people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES]._imageFrame->sDrawYSize(scaleVal) + - _bounds.height() - _surface.fontHeight(); + } + + _bounds.moveTo(pt); + + // Set up the surface + _surface.create(_bounds.width(), _bounds.height()); + _surface.fill(TRANSPARENCY); + + // Form the background for the new window + makeInfoArea(); +} + +void WidgetTalk::handleEvents() { + Events &events = *_vm->_events; + TattooJournal &journal = *(TattooJournal *)_vm->_journal; + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooScene &scene = *(TattooScene *)_vm->_scene; + Sound &sound = *_vm->_sound; + TattooTalk &talk = *(TattooTalk *)_vm->_talk; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + Common::Point mousePos = events.mousePos(); + Common::KeyCode keycode = ui._keyState.keycode; + bool hotkey = false; + bool callParrotFile = false; + + // Handle scrollbar events + ScrollHighlight oldHighlight = ui._scrollHighlight; + handleScrollbarEvents(_talkScrollIndex, NUM_VISIBLE_TALK_LINES, _statementLines.size()); + + int oldScrollIndex = _talkScrollIndex; + handleScrolling(_talkScrollIndex, NUM_VISIBLE_TALK_LINES, _statementLines.size()); + + // Only redraw the window if the the scrollbar position has changed + if (ui._scrollHighlight != oldHighlight || oldScrollIndex != _talkScrollIndex) + render(HL_NO_HIGHLIGHTING); + + // Flag if they started pressing outside of the window + if (events._firstPress && !_bounds.contains(mousePos)) + _outsideMenu = true; + + // Check for which statement they are pointing at + _selector = -1; + if (ui._scrollHighlight == SH_NONE) { + if (Common::Rect(_bounds.left, _bounds.top + 5, _bounds.right - 3, _bounds.bottom - 5).contains(mousePos)) { + if (_scroll) { + // Disregard the scrollbar when setting the statement number + if (!Common::Rect(_bounds.right - BUTTON_SIZE, _bounds.top, _bounds.right, _bounds.bottom).contains(mousePos)) + _selector = (mousePos.y - _bounds.top - 5) / (_surface.fontHeight() + 1) + _talkScrollIndex; + } else { + _selector = (mousePos.y - _bounds.top - 5) / (_surface.fontHeight() + 1); + } + + // Now translate the line number of the displayed line into the appropriate + // Statement number or set it to 255 to indicate no Statement selected + if (_selector >= 0 && _selector < (int)_statementLines.size()) + _selector = _statementLines[_selector]._num; + else + _selector = -1; + } + } + + // Check for the tab keys + if (keycode == Common::KEYCODE_TAB && ui._scrollHighlight == SH_NONE) { + if (_selector == -1) { + _selector = _statementLines[_scroll ? _talkScrollIndex : 0]._num; + + events.warpMouse(Common::Point(_bounds.right - BUTTON_SIZE - 10, _bounds.top + _surface.fontHeight() + 2)); + } else { + if (ui._keyState.flags & Common::KBD_SHIFT) { + _selector = (mousePos.y - _bounds.top - 5) / (_surface.fontHeight() + 1) + _talkScrollIndex; + if (_statementLines[_selector]._num == _statementLines[_talkScrollIndex]._num) { + _selector = (_bounds.height() - 10) / (_surface.fontHeight() + 1) + _talkScrollIndex; + } else { + int idx = _selector; + do { + --_selector; + } while (_selector > 0 && _statementLines[idx]._num == _statementLines[_selector]._num); + } + + int idx = _selector; + while ((_statementLines[idx]._num == _statementLines[_selector - 1]._num) && (_selector > _talkScrollIndex)) + --_selector; + } else { + _selector = (mousePos.y - _bounds.top - 5) / (_surface.fontHeight() + 1) + _talkScrollIndex; + if (_statementLines[_selector]._num == _statementLines[(_bounds.height() - 10) / (_surface.fontHeight() + 1) + _talkScrollIndex]._num) { + _selector = _talkScrollIndex; + } else { + int idx = _selector; + do { + ++_selector; + } while (_selector < (int)_statementLines.size() && _statementLines[idx]._num == _statementLines[_selector]._num); + } + } + + events.warpMouse(Common::Point(mousePos.x, _bounds.top + _surface.fontHeight() + 2 + (_surface.fontHeight() + 1) + * (_selector - _talkScrollIndex))); + _selector = _statementLines[_selector]._num; + } + } + + // Handle selecting a talk entry if a numeric key has been pressed + if (keycode >= Common::KEYCODE_1 && keycode <= Common::KEYCODE_9) { + int x = 0, t = 0, y = 0; + + do { + if (y == (keycode - Common::KEYCODE_1)) { + _selector = _statementLines[t]._num; + _outsideMenu = false; + hotkey = true; + break; + } + + ++t; + if (_statementLines[x]._num != _statementLines[t]._num) { + x = t; + ++y; + } + } while (t < (int)_statementLines.size()); + } + + // Display the selected statement highlighted and reset the last statement. + if (_selector != _oldSelector) { + render(HL_CHANGED_HIGHLIGHTS); + _oldSelector = _selector; + } + + if (events._released || events._rightReleased || keycode == Common::KEYCODE_ESCAPE || hotkey) { + events.clearEvents(); + _dialogTimer = 0; + ui._scrollHighlight = SH_NONE; + + // See if they want to close the menu (click outside the window or Escape pressed) + if ((_outsideMenu && !_bounds.contains(mousePos)) || keycode == Common::KEYCODE_ESCAPE) { + if (keycode == Common::KEYCODE_ESCAPE) + _selector = -1; + + talk.freeTalkVars(); + talk.pullSequence(); + + for (int idx = 1; idx < MAX_CHARACTERS; ++idx) { + if (people[idx]._type == CHARACTER) { + while (!people[idx]._pathStack.empty()) + people[idx].pullNPCPath(); + } + } + + banishWindow(); + ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; + + if (scene._currentScene == WEARY_PUNT) + callParrotFile = true; + } + + _outsideMenu = false; + + // See if they have selected a statement to say + if (_selector != -1) { + if (!talk._talkHistory[talk._converseNum][_selector] && talk._statements[_selector]._journal) + journal.record(talk._converseNum, _selector); + talk._talkHistory[talk._converseNum][_selector] = true; + + banishWindow(); + talk._speaker = _vm->readFlags(FLAG_PLAYER_IS_HOLMES) ? HOLMES : WATSON; + _scroll = false; + const byte *msg = (const byte *)talk._statements[_selector]._statement.c_str(); + talk.talkInterface(msg); + + if (sound._speechOn) + sound._talkSoundFile += Common::String::format("%02dA", _selector + 1); + + int msgLen = MAX((int)talk._statements[_selector]._statement.size(), 160); + people.setTalkSequence(talk._speaker); + + talk.waitForMore(msgLen); + if (talk._talkToAbort) + return; + + people.setListenSequence(talk._speaker); + + do { + talk._scriptSelect = _selector; + talk._speaker = talk._talkTo; + + // Make a copy of the reply (since talkTo can reload the statements list), and call talkTo + Common::String reply = talk._statements[_selector]._reply; + talk.doScript(reply); + + // Reset the misc field in case any people changed their sequences + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) + people[idx]._misc = 0; + + if (!talk._talkToAbort) { + if (!talk._statements[_selector]._modified.empty()) { + for (uint idx = 0; idx < talk._statements[_selector]._modified.size(); ++idx) + _vm->setFlags(talk._statements[_selector]._modified[idx]); + + talk.setTalkMap(); + } + + // See if there is another talk file linked to this. + if (!talk._statements[_selector]._linkFile.empty() && !talk._scriptMoreFlag) { + Common::String linkFile = talk._statements[_selector]._linkFile; + talk.freeTalkVars(); + talk.loadTalkFile(linkFile); + + _talkScrollIndex = 0; + int select = -1; + _selector = _oldSelector = -1; + + // Find the first statement that has all its flags set correctly + for (uint idx = 0; idx < talk._statements.size() && select == -1; ++select) { + if (!talk._statements[idx]._talkMap) + select = idx; + } + + if (select == -1) { + talk.freeTalkVars(); + talk.nothingToSay(); + return; + } + + // See is the new statement is in stealth mode + talk._talkStealth = (talk._statements[select]._statement.hasPrefix("^")) ? 2 : 0; + + // See if the new file is a standard file, a reply first file, or a Stealth Mode file + if (!talk._statements[select]._statement.hasPrefix("*") && !talk._statements[select]._statement.hasPrefix("^")) { + load(); + summonWindow(); + + setStatementLines(); + render(HL_NO_HIGHLIGHTING); + break; + } else { + _selector = select; + + if (!talk._talkHistory[talk._converseNum][_selector] && talk._statements[_selector]._journal) + journal.record(talk._converseNum, _selector); + + talk._talkHistory[talk._converseNum][_selector] = true; + } + } else { + talk.freeTalkVars(); + talk.pullSequence(); + + for (int idx = 1; idx < MAX_CHARACTERS; ++idx) { + if (people[idx]._type == CHARACTER) + while (!people[idx]._pathStack.empty()) + people[idx].pullNPCPath(); + } + + if (ui._menuMode != PASSWORD_MODE) { + ui.banishWindow(); + ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; + events.setCursor(ARROW); + } + break; + } + } else { + break; + } + } while (!_vm->shouldQuit()); + + events.clearEvents(); + + // Now, if a script was pushed onto the script stack, restore them to allow the previous script to continue. + talk.popStack(); + } + } + + if (callParrotFile) + talk.talkTo("POUT52A"); +} + +void WidgetTalk::render(Highlight highlightMode) { + TattooTalk &talk = *(TattooTalk *)_vm->_talk; + int yp = 5; + int statementNum = 1; + byte color; + + if (highlightMode != HL_SCROLLBAR_ONLY) { + // Draw all the statements + // Check whether scrolling has occurred, and if so, figure out what the starting + // number for the first visible statement will be + if (_talkScrollIndex) { + for (int idx = 1; idx <= _talkScrollIndex; ++idx) { + if (_statementLines[idx - 1]._num != _statementLines[idx]._num) + ++statementNum; + } + } + + // Main drawing loop + for (uint idx = _talkScrollIndex; idx < _statementLines.size() && yp < (_bounds.height() - _surface.fontHeight()); ++idx) { + if (highlightMode == HL_NO_HIGHLIGHTING || _statementLines[idx]._num == _selector || + _statementLines[idx]._num == _oldSelector) { + // Erase the line contents + _surface.fillRect(Common::Rect(3, yp, _surface.w() - BUTTON_SIZE - 3, yp + _surface.fontHeight()), TRANSPARENCY); + + // Different coloring based on whether the option has been previously chosen or not + color = (!talk._talkHistory[talk._converseNum][_statementLines[idx]._num]) ? + INFO_TOP : INFO_BOTTOM; + + if (_statementLines[idx]._num == _selector && highlightMode == HL_CHANGED_HIGHLIGHTS) + color = COMMAND_HIGHLIGHTED; + + // See if it's the start of a new statement, so needs the statement number to be displayed + if (!idx || _statementLines[idx]._num != _statementLines[idx - 1]._num) { + Common::String numStr = Common::String::format("%d.", statementNum); + _surface.writeString(numStr, Common::Point(STATEMENT_NUM_X, yp), color); + } + + // Display the statement line + _surface.writeString(_statementLines[idx]._line, Common::Point(_talkTextX, yp), color); + } + yp += _surface.fontHeight() + 1; + + // If the next line starts a new statement, then increment the statement number + if (idx == (_statementLines.size() - 1) || _statementLines[idx]._num != _statementLines[idx + 1]._num) + ++statementNum; + } + } + + // See if the scroll bar needs to be drawn + if (_scroll && highlightMode != HL_CHANGED_HIGHLIGHTS) + drawScrollBar(_talkScrollIndex, NUM_VISIBLE_TALK_LINES, _statementLines.size()); +} + +void WidgetTalk::setStatementLines() { + TattooTalk &talk = *(TattooTalk *)_vm->_talk; + const char *numStr = "19."; + + // See how many statements are going to be available + int numStatements = 0; + for (uint idx = 0; idx < talk._statements.size(); ++idx) { + if (talk._statements[idx]._talkMap != -1) + ++numStatements; + } + + // If there are more lines than can be displayed in the interface window at one time, adjust the allowed + // width to take into account needing a scrollbar + int xSize = _scroll ? _bounds.width() - BUTTON_SIZE - 3 : _bounds.width(); + + // Also adjust the width to allow room for the statement numbers at the left edge of the display + int n = (numStatements < 10) ? 1 : 0; + xSize -= _surface.stringWidth(numStr + n) + _surface.widestChar() / 2 + 9; + _talkTextX = _surface.stringWidth(numStr + n) + _surface.widestChar() / 4 + 6; + _statementLines.clear(); + + for (uint statementNum = 0; statementNum < talk._statements.size(); ++statementNum) { + // See if this statment meets all of its flag requirements + if (talk._statements[statementNum]._talkMap != -1) { + // Get the next statement text to process + Common::String str = talk._statements[statementNum]._statement; + + Common::StringArray statementLines; + splitLines(str, statementLines, xSize, 999); + + // Add the lines in + for (uint idx = 0; idx < statementLines.size(); ++idx) + _statementLines.push_back(StatementLine(statementLines[idx], statementNum)); + } + } +} + +void WidgetTalk::refresh() { + _talkScrollIndex = 0; + _selector = _oldSelector = -1; + + setStatementLines(); + render(HL_NO_HIGHLIGHTING); +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/widget_talk.h b/engines/sherlock/tattoo/widget_talk.h new file mode 100644 index 0000000000..f5e939b393 --- /dev/null +++ b/engines/sherlock/tattoo/widget_talk.h @@ -0,0 +1,95 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_TATTOO_WIDGET_TALK_H +#define SHERLOCK_TATTOO_WIDGET_TALK_H + +#include "common/scummsys.h" +#include "sherlock/tattoo/widget_base.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Tattoo { + +enum Highlight { HL_NO_HIGHLIGHTING, HL_CHANGED_HIGHLIGHTS, HL_SCROLLBAR_ONLY }; + +/** + * Handles displaying a dialog with conversation options the player can select from + */ +class WidgetTalk: public WidgetBase { + struct StatementLine { + Common::String _line; + int _num; + + StatementLine() : _num(0) {} + StatementLine(const Common::String &line, int num) : _line(line), _num(num) {} + }; +private: + int _talkScrollIndex; + Common::Array<StatementLine> _statementLines; + int _selector, _oldSelector; + int _talkTextX; + uint32 _dialogTimer; + + /** + * Get the needed size for a talk window + */ + void getTalkWindowSize(); + + /** + * Re-renders the contenst of the window to the widget's surface + */ + void render(Highlight highlightMode); + + /** + * This initializes the _statementLines array, which contains the talk options split up line + * by line, as well as which statement a particular line is part of. + */ + void setStatementLines(); +public: + WidgetTalk(SherlockEngine *vm); + virtual ~WidgetTalk() {} + + /** + * Figures out how many lines the available talk lines will take up, and opens a text window + * of appropriate size + */ + void load(); + + /** + * Refresh the talk display + */ + void refresh(); + + /** + * Handle event processing + */ + virtual void handleEvents(); +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/widget_text.cpp b/engines/sherlock/tattoo/widget_text.cpp new file mode 100644 index 0000000000..86aa067301 --- /dev/null +++ b/engines/sherlock/tattoo/widget_text.cpp @@ -0,0 +1,228 @@ +/* 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 "sherlock/tattoo/widget_text.h" +#include "sherlock/tattoo/tattoo_people.h" +#include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo_user_interface.h" +#include "sherlock/tattoo/tattoo.h" + +namespace Sherlock { + +namespace Tattoo { + +WidgetText::WidgetText(SherlockEngine *vm) : WidgetBase(vm) { +} + +void WidgetText::load(const Common::String &str, int speaker) { + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + Common::StringArray lines; + + int width = SHERLOCK_SCREEN_WIDTH / 3; + int height; + + for (;;) { + splitLines(str, lines, width - _surface.widestChar() * 2, 100); + height = (screen.fontHeight() + 1) * lines.size() + 9; + + if ((width - _surface.widestChar() * 2 > height * 3 / 2) || (width - _surface.widestChar() * 2 + > SHERLOCK_SCREEN_WIDTH * 3 / 4)) + break; + + width += (width / 4); + } + + // See if it's only a single line long + if (height == _surface.fontHeight() + 10) { + width = _surface.widestChar() * 2 + 6; + + const char *strP = str.c_str(); + while (*strP && (*strP < talk._opcodes[OP_SWITCH_SPEAKER] || *strP == talk._opcodes[OP_NULL])) + width += _surface.charWidth(*strP++); + } + + _bounds = Common::Rect(width, height); + + if (speaker == -1) { + // No speaker specified, so center window on look position + _bounds.translate(ui._lookPos.x - width / 2, ui._lookPos.y - height / 2); + } else { + // Speaker specified, so center the window above them + centerWindowOnSpeaker(speaker); + } + + render(str); +} + +void WidgetText::centerWindowOnSpeaker(int speaker) { + TattooPeople &people = *(TattooPeople *)_vm->_people; + TattooScene &scene = *(TattooScene *)_vm->_scene; + Common::Point pt; + + bool flag = _vm->readFlags(FLAG_PLAYER_IS_HOLMES); + if (people[HOLMES]._type == CHARACTER && ((speaker == HOLMES && flag) || (speaker == WATSON && !flag))) { + // Place the window centered above the player + pt.x = people[HOLMES]._position.x / FIXED_INT_MULTIPLIER - _bounds.width() / 2; + + int scaleVal = scene.getScaleVal(people[HOLMES]._position); + if (scaleVal == SCALE_THRESHOLD) { + pt.x += people[HOLMES].frameWidth() / 2; + pt.y = people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES].frameHeight() + - _bounds.height() - _surface.fontHeight(); + } else { + pt.x += people[HOLMES]._imageFrame->sDrawXSize(scaleVal) / 2; + pt.y = people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES]._imageFrame->sDrawYSize(scaleVal) + - _bounds.height() - _surface.fontHeight(); + } + } else { + pt.y = -1; + + // Check each NPC to see if they are the one that is talking + for (int idx = 1; idx < MAX_CHARACTERS; ++idx) { + // WORKAROUND: Fixes an original game bug where the positioning for Watson's dialogs + // during conversations at the Park Lake lake scene is in the incorrect position + if (speaker == 1 && scene._currentScene == 30) + continue; + + if (people[idx]._type == CHARACTER) { + if (!scumm_strnicmp(people[idx]._npcName.c_str(), people._characters[speaker]._portrait, 4)) { + // Place the window above the player + pt.x = people[idx]._position.x / FIXED_INT_MULTIPLIER - _bounds.width() / 2; + + int scaleVal = scene.getScaleVal(people[idx]._position); + if (scaleVal == SCALE_THRESHOLD) { + pt.x += people[idx].frameWidth() / 2; + pt.y = people[idx]._position.y / FIXED_INT_MULTIPLIER - people[idx].frameHeight() + - _bounds.height() - _surface.fontHeight(); + } + else { + pt.x += people[idx]._imageFrame->sDrawXSize(scaleVal) / 2; + pt.y = people[idx]._position.y / FIXED_INT_MULTIPLIER - people[idx]._imageFrame->sDrawYSize(scaleVal) + - _bounds.height() - _surface.fontHeight(); + } + + if (pt.y < 0) + pt.y = 0; + break; + } + } + } + + if (pt.y == -1) { + for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) { + Object &obj = scene._bgShapes[idx]; + + if (obj._type == ACTIVE_BG_SHAPE && !scumm_strnicmp(obj._name.c_str(), people._characters[speaker]._portrait, 4)) { + // Place the window centered above the character + pt.x = obj._position.x - _bounds.width() / 2; + pt.y = obj._position.y - _bounds.height() - _surface.fontHeight(); + if (pt.y < 0) + pt.y = 0; + if (obj._scaleVal == SCALE_THRESHOLD) + pt.x += obj.frameWidth() / 2; + else + pt.x += obj._imageFrame->sDrawXSize(obj._scaleVal) / 2; + + break; + } + } + } + + if (pt.y == -1) { + pt.x = SHERLOCK_SCREEN_WIDTH / 2 - _bounds.width() / 2; + pt.y = SHERLOCK_SCREEN_HEIGHT / 2 - _bounds.height() / 2; + } + } + + _bounds.moveTo(pt); +} + +void WidgetText::render(const Common::String &str) { + Common::StringArray lines; + _remainingText = splitLines(str, lines, _bounds.width() - _surface.widestChar() * 2, + _bounds.height() / (_surface.fontHeight() + 1)); + + // Allocate a surface for the window + _surface.create(_bounds.width(), _bounds.height()); + _surface.fill(TRANSPARENCY); + + // Form the background for the new window + makeInfoArea(); + + int yp = 5; + for (int lineNum = 0; yp < (_bounds.height() - _surface.fontHeight() / 2); ++lineNum) { + _surface.writeString(lines[lineNum], Common::Point(_surface.widestChar(), yp), INFO_TOP); + yp += _surface.fontHeight() + 1; + } +} + +/*----------------------------------------------------------------*/ + +WidgetMessage::WidgetMessage(SherlockEngine *vm) : WidgetBase(vm) { + _menuCounter = 0; +} + +void WidgetMessage::load(const Common::String &str, int time) { + Events &events = *_vm->_events; + Common::Point mousePos = events.mousePos(); + _menuCounter = time; + + // Set up the bounds for the dialog to be a single line + _bounds = Common::Rect(_surface.stringWidth(str) + _surface.widestChar() * 2 + 6, _surface.fontHeight() + 10); + _bounds.moveTo(mousePos.x - _bounds.width() / 2, mousePos.y - _bounds.height() / 2); + + // Allocate a surface for the window + _surface.create(_bounds.width(), _bounds.height()); + _surface.fill(TRANSPARENCY); + + // Form the background for the new window and write the line of text + makeInfoArea(); + _surface.writeString(str, Common::Point(_surface.widestChar() + 3, 5), INFO_TOP); +} + +void WidgetMessage::handleEvents() { + Events &events = *_vm->_events; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + WidgetBase::handleEvents(); + + --_menuCounter; + + // Check if a mouse or keypress has occurred, or the display counter has expired + if (events._pressed || events._released || events._rightPressed || events._rightReleased || + ui._keyState.keycode || !_menuCounter) { + // Close the window + banishWindow(); + + // Reset cursor and switch back to standard mode + events.setCursor(ARROW); + events.clearEvents(); + ui._key = -1; + ui._oldBgFound = -1; + ui._menuMode = STD_MODE; + } +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/widget_text.h b/engines/sherlock/tattoo/widget_text.h new file mode 100644 index 0000000000..f027bdae9f --- /dev/null +++ b/engines/sherlock/tattoo/widget_text.h @@ -0,0 +1,80 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_TATTOO_WIDGET_TEXT_H +#define SHERLOCK_TATTOO_WIDGET_TEXT_H + +#include "common/scummsys.h" +#include "sherlock/tattoo/widget_base.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Tattoo { + +class WidgetText: public WidgetBase { +private: + /** + * Center the area the dialog will be drawn on above a given speaker + */ + void centerWindowOnSpeaker(int speaker); + + /** + * Build up the text dialog based on the previously set bounds + */ + void render(const Common::String &str); +public: + Common::String _remainingText; +public: + WidgetText(SherlockEngine *vm); + virtual ~WidgetText() {} + + /** + * Load the data for the text window + */ + void load(const Common::String &str, int speaker = -1); +}; + +class WidgetMessage : public WidgetBase { +private: + int _menuCounter; +public: + WidgetMessage(SherlockEngine *vm); + virtual ~WidgetMessage() {} + + /** + * Load the data for the text window + */ + void load(const Common::String &str, int time); + + /** + * Handle event processing + */ + virtual void handleEvents(); +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/widget_tooltip.cpp b/engines/sherlock/tattoo/widget_tooltip.cpp new file mode 100644 index 0000000000..b29f45f531 --- /dev/null +++ b/engines/sherlock/tattoo/widget_tooltip.cpp @@ -0,0 +1,223 @@ +/* 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 "sherlock/tattoo/widget_tooltip.h" +#include "sherlock/tattoo/tattoo_map.h" +#include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo_user_interface.h" +#include "sherlock/tattoo/tattoo.h" + +namespace Sherlock { + +namespace Tattoo { + +#define MAX_TOOLTIP_WIDTH 150 + +void WidgetTooltipBase::draw() { + Screen &screen = *_vm->_screen; + + // If there was a previously drawn frame in a different position that hasn't yet been erased, then erase it + if (_oldBounds.width() > 0 && _oldBounds != _bounds) + erase(); + + if (_bounds.width() > 0 && !_surface.empty()) { + restrictToScreen(); + + // Blit the affected area to the screen + screen.slamRect(_bounds); + + // Draw the widget directly onto the screen. Unlike other widgets, we don't draw to the back buffer, + // since nothing should be drawing on top of tooltips, so there's no need to store in the back buffer + screen.transBlitFrom(_surface, Common::Point(_bounds.left - screen._currentScroll.x, + _bounds.top - screen._currentScroll.y)); + + // Store a copy of the drawn area for later erasing + _oldBounds = _bounds; + } +} + +void WidgetTooltipBase::erase() { + Screen &screen = *_vm->_screen; + + if (_oldBounds.width() > 0) { + // Restore the affected area from the back buffer to the screen + screen.slamRect(_oldBounds); + + // Reset the old bounds so it won't be erased again + _oldBounds = Common::Rect(0, 0, 0, 0); + } +} + +/*----------------------------------------------------------------*/ + +WidgetTooltip::WidgetTooltip(SherlockEngine *vm) : WidgetTooltipBase (vm), _offsetY(0) { +} + +void WidgetTooltip::setText(const Common::String &str) { + Events &events = *_vm->_events; + Common::Point mousePos = events.mousePos(); + bool reset = false; + + // Make sure that the description is present + if (!str.empty()) { + int width = _surface.stringWidth(str) + 2; + int height = _surface.stringHeight(str) + 2; + Common::String line1 = str, line2 = ""; + + // See if we need to split it into two lines + if (width > MAX_TOOLTIP_WIDTH) { + // Go forward word by word to find out where to split the line + const char *s = str.c_str(); + const char *space = nullptr; + int dif = 10000; + + for (;;) { + // Find end of next word + s = strchr(s + 1, ' '); + + if (s == nullptr) { + // Reached end of string + if (space != nullptr) { + line1 = Common::String(str.c_str(), space); + line2 = Common::String(space + 1); + height = _surface.stringHeight(line1) + _surface.stringHeight(line2) + 4; + } + break; + } + + // Found space separating words, so see what width the string up to now is + Common::String tempLine1 = Common::String(str.c_str(), s); + Common::String tempLine2 = Common::String(s + 1); + int width1 = _surface.stringWidth(tempLine1); + int width2 = _surface.stringWidth(tempLine2); + + // See if we've found a split point that results in a less overall width + if (ABS(width1 - width2) < dif) { + // Found a better split point + dif = ABS(width1 - width2); + space = s; + line1 = tempLine1; + line2 = tempLine2; + } + } + } else { + // No line split needed + height = _surface.stringHeight(str) + 2; + } + + // Reallocate the text surface with the new size + _surface.create(width, height); + _surface.fill(TRANSPARENCY); + + if (line2.empty()) { + // Only a single line + _surface.writeFancyString(str, Common::Point(0, 0), BLACK, INFO_TOP); + } else { + // Two lines to display + int xp, yp; + xp = (width - _surface.stringWidth(line1) - 2) / 2; + _surface.writeFancyString(line1, Common::Point(xp, 0), BLACK, INFO_TOP); + + xp = (width - _surface.stringWidth(line2) - 2) / 2; + yp = _surface.stringHeight(line1) + 2; + _surface.writeFancyString(line2, Common::Point(xp, yp), BLACK, INFO_TOP); + } + + // Set the initial display position for the tooltip text + int tagX = mousePos.x - width / 2; + int tagY = mousePos.y - height - _offsetY; + + _bounds = Common::Rect(tagX, tagY, tagX + width, tagY + height); + } else { + reset = true; + } + + if (reset && !_surface.empty()) { + _surface.free(); + } +} + +void WidgetTooltip::handleEvents() { + Events &events = *_vm->_events; + Common::Point mousePos = events.mousePos(); + + // Set the new position for the tooltip + int xp = mousePos.x - _bounds.width() / 2; + int yp = mousePos.y - _bounds.height() - _offsetY; + + _bounds.moveTo(xp, yp); +} + +/*----------------------------------------------------------------*/ + +void WidgetSceneTooltip::handleEvents() { + Events &events = *_vm->_events; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + Common::Point mousePos = events.mousePos(); + + // See if thay are pointing at a different object and we need to regenerate the tooltip text + if (ui._bgFound != ui._oldBgFound || (ui._bgFound != -1 && _surface.empty()) || + ui._arrowZone != ui._oldArrowZone || (ui._arrowZone != -1 && _surface.empty())) { + // See if there is a new object to display text for + if ((ui._bgFound != -1 && (ui._bgFound != ui._oldBgFound || (ui._bgFound != -1 && _surface.empty()))) || + (ui._arrowZone != -1 && (ui._arrowZone != ui._oldArrowZone || (ui._arrowZone != -1 && _surface.empty())))) { + Common::String str; + if (ui._bgFound != -1) { + // Clear the Arrow Zone fields so it won't think we're displaying an Arrow Zone cursor + if (scene._currentScene != OVERHEAD_MAP2) + ui._arrowZone = ui._oldArrowZone = -1; + + // Get the description string + str = (ui._bgFound < 1000) ? scene._bgShapes[ui._bgFound]._description : + people[ui._bgFound - 1000]._description; + + // WORKAORUND: On the train ride to Cambridge, don't show any tooltips + if (scene._currentScene == TRAIN_RIDE) + str = ""; + } else { + // Get the exit zone description + str = scene._exits[ui._arrowZone]._dest; + } + + setText(str.hasPrefix(" ") ? Common::String() : str); + } else if ((ui._bgFound == -1 && ui._oldBgFound != -1) || (ui._arrowZone == -1 && ui._oldArrowZone != -1)) { + setText(""); + } + + ui._oldBgFound = ui._bgFound; + } else { + // Set the new position for the tooltip + int tagX = CLIP(mousePos.x - _bounds.width() / 2, 0, SHERLOCK_SCREEN_WIDTH - _bounds.width()); + int tagY = MAX(mousePos.y - _bounds.height() - _offsetY, 0); + _bounds.moveTo(tagX, tagY); + } + + ui._oldArrowZone = ui._arrowZone; + + WidgetTooltip::handleEvents(); +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/widget_tooltip.h b/engines/sherlock/tattoo/widget_tooltip.h new file mode 100644 index 0000000000..87f5d54f0a --- /dev/null +++ b/engines/sherlock/tattoo/widget_tooltip.h @@ -0,0 +1,89 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_TATTOO_WIDGET_TOOLTIP_H +#define SHERLOCK_TATTOO_WIDGET_TOOLTIP_H + +#include "common/scummsys.h" +#include "common/rect.h" +#include "sherlock/tattoo/widget_base.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Tattoo { + +class WidgetTooltipBase : public WidgetBase { +public: + WidgetTooltipBase(SherlockEngine *vm) : WidgetBase(vm) {} + virtual ~WidgetTooltipBase() {} + + /** + * Erase any previous display of the widget on the screen + */ + virtual void erase(); + + /** + * Update the display of the widget on the screen + */ + virtual void draw(); +}; + +class WidgetTooltip: public WidgetTooltipBase { +public: + int _offsetY; +public: + WidgetTooltip(SherlockEngine *vm); + virtual ~WidgetTooltip() {} + + /** + * Set the text for the tooltip + */ + void setText(const Common::String &str); + + /** + * Handle updating the tooltip state + */ + virtual void handleEvents(); +}; + +class WidgetSceneTooltip : public WidgetTooltip { +public: + WidgetSceneTooltip(SherlockEngine *vm) : WidgetTooltip(vm) {} + + /** + * Handle updating the tooltip state + */ + virtual void handleEvents(); +}; + +class WidgetMapTooltip : public WidgetTooltip { +public: + WidgetMapTooltip(SherlockEngine *vm) : WidgetTooltip(vm) {} +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/widget_verbs.cpp b/engines/sherlock/tattoo/widget_verbs.cpp new file mode 100644 index 0000000000..0b523a93e9 --- /dev/null +++ b/engines/sherlock/tattoo/widget_verbs.cpp @@ -0,0 +1,314 @@ +/* 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 "sherlock/tattoo/widget_verbs.h" +#include "sherlock/tattoo/tattoo_fixed_text.h" +#include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo_user_interface.h" +#include "sherlock/tattoo/tattoo_people.h" +#include "sherlock/tattoo/tattoo.h" + +namespace Sherlock { + +namespace Tattoo { + +WidgetVerbs::WidgetVerbs(SherlockEngine *vm) : WidgetBase(vm) { + _selector = _oldSelector = -1; + _outsideMenu = false; +} + +void WidgetVerbs::load(bool objectsOn) { + Events &events = *_vm->_events; + TattooPeople &people = *(TattooPeople *)_vm->_people; + Talk &talk = *_vm->_talk; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + Common::Point mousePos = events.mousePos(); + bool isWatson = false; + + if (talk._talkToAbort) + return; + + ui._activeObj = ui._bgFound; + _outsideMenu = false; + _verbCommands.clear(); + _selector = _oldSelector = -1; + + // Check if we need to show options for the highlighted object + if (objectsOn) { + // Set the verb list accordingly, depending on the target being a + // person or an object + if (ui._personFound) { + TattooPerson &person = people[ui._activeObj - 1000]; + TattooPerson &npc = people[ui._activeObj - 1001]; + + if (!scumm_strnicmp(npc._npcName.c_str(), "WATS", 4)) + isWatson = true; + + + if (scumm_strnicmp(person._examine.c_str(), "_EXIT", 5)) + _verbCommands.push_back(FIXED(Look)); + + _verbCommands.push_back(FIXED(Talk)); + + // Add any extra active verbs from the NPC's verb list + for (int idx = 0; idx < 2; ++idx) { + if (!person._use[idx]._verb.empty() && !person._use[idx]._verb.hasPrefix(" ") && + (person._use[idx]._target.empty() || person._use[idx]._target.hasPrefix(" "))) { + _verbCommands.push_back(person._use[idx]._verb); + } + } + } else { + if (!scumm_strnicmp(ui._bgShape->_name.c_str(), "WATS", 4)) + // Looking at Watson + isWatson = true; + + if (scumm_strnicmp(ui._bgShape->_examine.c_str(), "_EXIT", 5)) + // It's not an exit, so include Look as an option + _verbCommands.push_back(FIXED(Look)); + + if (ui._bgShape->_aType == PERSON) + _verbCommands.push_back(FIXED(Talk)); + + // Add any extra active verbs from the object's verb list + for (int idx = 0; idx < 6; ++idx) { + UseType &use = ui._bgShape->_use[idx]; + if (!use._verb.empty() && !use._verb.hasPrefix(" ") && !use._verb.hasPrefix("*") && + (use._target.empty() || use._target.hasPrefix("*") || use._target.hasPrefix(" "))) { + _verbCommands.push_back(use._verb); + } + } + } + } + + // If clicked on Watson, have Journal as an option + if (isWatson) + _verbCommands.push_back(FIXED(Journal)); + + // Add the system commands + _verbCommands.push_back(FIXED(Inventory)); + _verbCommands.push_back(FIXED(Options)); + + // Figure out the needed width to show the commands + int width = 0; + for (uint idx = 0; idx < _verbCommands.size(); ++idx) + width = MAX(width, _surface.stringWidth(_verbCommands[idx])); + width += _surface.widestChar() * 2 + 6; + int height = (_surface.fontHeight() + 7) * _verbCommands.size() + 3; + + // Set the bounds + _bounds = Common::Rect(width, height); + _bounds.moveTo(mousePos.x - _bounds.width() / 2, mousePos.y - _bounds.height() / 2); + + // Render the window on the internal surface + render(); +} + +void WidgetVerbs::render() { + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + ImageFile &images = *ui._interfaceImages; + + // Create the drawing surface + _surface.create(_bounds.width(), _bounds.height()); + _surface.fill(TRANSPARENCY); + + // Draw basic background + makeInfoArea(); + + // Draw the verb commands and the lines separating them + for (uint idx = 0; idx < _verbCommands.size(); ++idx) { + _surface.writeString(_verbCommands[idx], Common::Point((_bounds.width() - _surface.stringWidth(_verbCommands[idx])) / 2, + (_surface.fontHeight() + 7) * idx + 5), INFO_TOP); + + if (idx < (_verbCommands.size() - 1)) { + _surface.hLine(3, (_surface.fontHeight() + 7) * (idx + 1), _bounds.width() - 4, INFO_TOP); + _surface.hLine(3, (_surface.fontHeight() + 7) * (idx + 1) + 1, _bounds.width() - 4, INFO_MIDDLE); + _surface.hLine(3, (_surface.fontHeight() + 7) * (idx + 1) + 2, _bounds.width() - 4, INFO_BOTTOM); + + _surface.transBlitFrom(images[4], Common::Point(0, (_surface.fontHeight() + 7) * (idx + 1) - 1)); + _surface.transBlitFrom(images[5], Common::Point(_bounds.width() - images[5]._width, + (_surface.fontHeight() + 7) * (idx + 1) - 1)); + } + } +} + +void WidgetVerbs::handleEvents() { + Events &events = *_vm->_events; + FixedText &fixedText = *_vm->_fixedText; + People &people = *_vm->_people; + TattooScene &scene = *(TattooScene *)_vm->_scene; + Talk &talk = *_vm->_talk; + TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; + Common::Point mousePos = events.mousePos(); + bool noDesc = false; + + Common::String strLook = fixedText.getText(kFixedText_Look); + Common::String strTalk = fixedText.getText(kFixedText_Talk); + Common::String strJournal = fixedText.getText(kFixedText_Journal); + + checkTabbingKeys(_verbCommands.size()); + + // Highlight verb display as necessary + highlightVerbControls(); + + // Flag if the user has started pressing the button with the cursor outsie the menu + if (events._firstPress && !_bounds.contains(mousePos)) + _outsideMenu = true; + + // See if they released the mouse button + if (events._released || events._rightReleased) { + // See if they want to close the menu (they clicked outside of the menu) + if (!_bounds.contains(mousePos)) { + if (_outsideMenu) { + if (events._rightReleased) { + // Change to the item (if any) that was right-clicked on, and re-draw the verb menu + ui._bgFound = scene.findBgShape(mousePos); + ui._personFound = ui._bgFound >= 1000; + ui._bgShape = ui._personFound || ui._bgFound == -1 ? nullptr : &scene._bgShapes[ui._bgFound]; + + if (ui._personFound) { + if (people[ui._bgFound - 1000]._description.empty() || people[ui._bgFound - 1000]._description.hasPrefix(" ")) + noDesc = true; + } else if (ui._bgFound != -1) { + if (ui._bgShape->_description.empty() || ui._bgShape->_description.hasPrefix(" ")) + noDesc = true; + } else { + noDesc = true; + } + + // Call the Routine to turn on the Commands for this Object + load(!noDesc); + } else { + // Close the window and clear the events + banishWindow(); + events.clearEvents(); + + // Reset the active UI mode + ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; + } + } + } else if (_bounds.contains(mousePos) && _selector != -1) { + // Mouse is within the menu + // Erase the menu + banishWindow(); + events.clearEvents(); + + // See if they are activating the Look Command + if (!_verbCommands[_selector].compareToIgnoreCase(strLook)) { + ui._bgFound = ui._activeObj; + if (ui._activeObj >= 1000) { + ui._personFound = true; + } else { + ui._personFound = false; + ui._bgShape = &scene._bgShapes[ui._activeObj]; + } + + ui.lookAtObject(); + + } else if (!_verbCommands[_selector].compareToIgnoreCase(strTalk)) { + // Talk command is being activated + talk.initTalk(ui._activeObj); + ui._activeObj = -1; + + } else if (!_verbCommands[_selector].compareToIgnoreCase(strJournal)) { + ui.doJournal(); + + // See if we're in a Lab Table scene + ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; + } else if (_selector >= ((int)_verbCommands.size() - 2)) { + switch (_selector - (int)_verbCommands.size() + 2) { + case 0: + // Inventory + ui.doInventory(2); + break; + + case 1: + // Options + ui.doControls(); + break; + + default: + ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; + break; + } + } else { + // If they have selected anything else, process it + people[HOLMES].gotoStand(); + + if (ui._activeObj < 1000) { + for (int idx = 0; idx < 6; ++idx) { + if (!_verbCommands[_selector].compareToIgnoreCase(scene._bgShapes[ui._activeObj]._use[idx]._verb)) { + // See if they are Picking this object up + if (!scene._bgShapes[ui._activeObj]._use[idx]._target.compareToIgnoreCase("*PICKUP")) + ui.pickUpObject(ui._activeObj); + else + ui.checkAction(scene._bgShapes[ui._activeObj]._use[idx], ui._activeObj); + } + } + } else { + for (int idx = 0; idx < 2; ++idx) { + if (!_verbCommands[_selector].compareToIgnoreCase(people[ui._activeObj - 1000]._use[idx]._verb)) + ui.checkAction(people[ui._activeObj - 1000]._use[idx], ui._activeObj); + } + } + + ui._activeObj = -1; + if (ui._menuMode != MESSAGE_MODE) { + // See if we're in a Lab Table Room + ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; + } + } + } + } else if (ui._keyState.keycode == Common::KEYCODE_ESCAPE) { + // User closing the menu with the ESC key + banishWindow(); + ui._menuMode = scene._labTableScene ? LAB_MODE : STD_MODE; + } +} + +void WidgetVerbs::highlightVerbControls() { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + Common::Point mousePos = events.mousePos(); + + // Get highlighted verb + _selector = -1; + Common::Rect bounds = _bounds; + bounds.grow(-3); + if (bounds.contains(mousePos)) + _selector = (mousePos.y - bounds.top) / (screen.fontHeight() + 7); + + // See if a new verb is being pointed at + if (_selector != _oldSelector) { + // Redraw the verb list + for (int idx = 0; idx < (int)_verbCommands.size(); ++idx) { + byte color = (idx == _selector) ? (byte)COMMAND_HIGHLIGHTED : (byte)INFO_TOP; + _surface.writeString(_verbCommands[idx], Common::Point((_bounds.width() - screen.stringWidth(_verbCommands[idx])) / 2, + (screen.fontHeight() + 7) * idx + 5), color); + } + + _oldSelector = _selector; + } +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/widget_verbs.h b/engines/sherlock/tattoo/widget_verbs.h new file mode 100644 index 0000000000..ce67842409 --- /dev/null +++ b/engines/sherlock/tattoo/widget_verbs.h @@ -0,0 +1,72 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_TATTOO_WIDGET_VERBS_H +#define SHERLOCK_TATTOO_WIDGET_VERBS_H + +#include "common/scummsys.h" +#include "common/rect.h" +#include "common/str-array.h" +#include "sherlock/tattoo/widget_base.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Tattoo { + +class WidgetVerbs: public WidgetBase { +private: + int _selector, _oldSelector; + bool _outsideMenu; + + /** + * Highlights the controls for the verb list + */ + void highlightVerbControls(); + + /** + * Renders the window on an internal surface for later drawing on-screen + */ + void render(); +public: + Common::StringArray _verbCommands; +public: + WidgetVerbs(SherlockEngine *vm); + virtual ~WidgetVerbs() {} + + /** + * Turns on the menu with all the verbs that are available for the given object + */ + void load(bool objectsOn); + + /** + * Process input for the dialog + */ + virtual void handleEvents(); +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/user_interface.cpp b/engines/sherlock/user_interface.cpp index 9fff7cc999..bb0667d66a 100644 --- a/engines/sherlock/user_interface.cpp +++ b/engines/sherlock/user_interface.cpp @@ -21,8 +21,9 @@ */ #include "sherlock/user_interface.h" -#include "sherlock/sherlock.h" +#include "sherlock/scalpel/scalpel.h" #include "sherlock/scalpel/scalpel_user_interface.h" +#include "sherlock/tattoo/tattoo.h" #include "sherlock/tattoo/tattoo_user_interface.h" namespace Sherlock { @@ -45,7 +46,9 @@ UserInterface::UserInterface(SherlockEngine *vm) : _vm(vm) { _helpStyle = false; _windowBounds = Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH - 1, SHERLOCK_SCREEN_HEIGHT - 1); _lookScriptFlag = false; + _exitZone = -1; + _bgFound = _oldBgFound = -1; _key = _oldKey = '\0'; _selector = _oldSelector = -1; _temp = _oldTemp = 0; @@ -53,4 +56,154 @@ UserInterface::UserInterface(SherlockEngine *vm) : _vm(vm) { _lookHelp = 0; } +void UserInterface::checkAction(ActionType &action, int objNum, FixedTextActionId fixedTextActionId) { + Events &events = *_vm->_events; + FixedText &fixedText = *_vm->_fixedText; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + Point32 pt(-1, -1); + + if (action._useFlag) + // Automatically set the given flag + _vm->setFlags(action._useFlag); + + if (IS_SERRATED_SCALPEL && objNum >= 1000) + // Ignore actions done on characters + return; + + if (IS_SERRATED_SCALPEL && !action._cAnimSpeed) { + // Invalid action, to print error message + _infoFlag = true; + clearInfo(); + Common::String errorMessage = fixedText.getActionMessage(fixedTextActionId, action._cAnimNum); + screen.print(Common::Point(0, INFO_LINE + 1), COL_INFO_FOREGROUND, "%s", errorMessage.c_str()); + _infoFlag = true; + + // Set how long to show the message + _menuCounter = 30; + } else { + BaseObject *obj; + if (objNum >= 1000) + obj = &people[objNum - 1000]; + else + obj = &scene._bgShapes[objNum]; + + int cAnimNum; + if (action._cAnimNum == 0) + // Really a 10 + cAnimNum = 9; + else + cAnimNum = action._cAnimNum - 1; + + int dir = -1; + if (action._cAnimNum != 99) { + CAnim &anim = scene._cAnim[cAnimNum]; + + if (action._cAnimNum != 99) { + if (action._cAnimSpeed & REVERSE_DIRECTION) { + pt = anim._teleport[0]; + dir = anim._teleport[0]._facing; + } else { + pt = anim._goto[0]; + dir = anim._goto[0]._facing; + } + } + } else { + pt = Point32(-1, -1); + dir = -1; + } + + // Has a value, so do action + // Show wait cursor whilst walking to object and doing action + events.setCursor(WAIT); + bool printed = false; + + for (int nameIdx = 0; nameIdx < NAMES_COUNT; ++nameIdx) { + if (action._names[nameIdx].hasPrefix("*") && action._names[nameIdx].size() >= 2 + && toupper(action._names[nameIdx][1]) == 'W') { + if (obj->checkNameForCodes(Common::String(action._names[nameIdx].c_str() + 2), fixedTextActionId)) { + if (!talk._talkToAbort) + printed = true; + } + } + } + + bool doCAnim = true; + for (int nameIdx = 0; nameIdx < NAMES_COUNT; ++nameIdx) { + if (action._names[nameIdx].hasPrefix("*") && action._names[nameIdx].size() >= 2) { + char ch = toupper(action._names[nameIdx][1]); + + if (ch == 'T' || ch == 'B') { + printed = true; + if (pt.x != -1) + // Holmes needs to walk to object before the action is done + people[HOLMES].walkToCoords(pt, dir); + + if (!talk._talkToAbort) { + // Ensure Holmes is on the exact intended location + people[HOLMES]._position = pt; + people[HOLMES]._sequenceNumber = dir; + people[HOLMES].gotoStand(); + + talk.talkTo(action._names[nameIdx].c_str() + 2); + if (ch == 'T') + doCAnim = false; + } + } + } + } + + if (doCAnim && !talk._talkToAbort) { + if (pt.x != -1) + // Holmes needs to walk to object before the action is done + people[HOLMES].walkToCoords(pt, dir); + } + + for (int nameIdx = 0; nameIdx < NAMES_COUNT; ++nameIdx) { + if (action._names[nameIdx].hasPrefix("*") && action._names[nameIdx].size() >= 2 + && toupper(action._names[nameIdx][1]) == 'F') { + if (obj->checkNameForCodes(action._names[nameIdx].c_str() + 2, fixedTextActionId)) { + if (!talk._talkToAbort) + printed = true; + } + } + } + + if (doCAnim && !talk._talkToAbort && action._cAnimNum != 99) + scene.startCAnim(cAnimNum, action._cAnimSpeed); + + if (!talk._talkToAbort) { + for (int nameIdx = 0; nameIdx < NAMES_COUNT && !talk._talkToAbort; ++nameIdx) { + if (obj->checkNameForCodes(action._names[nameIdx], fixedTextActionId)) { + if (!talk._talkToAbort) + printed = true; + } + } + + // Unless we're leaving the scene, print a "Done" message unless the printed flag has been set + if (IS_SERRATED_SCALPEL && scene._goToScene != 1 && !printed && !talk._talkToAbort) { + _infoFlag = true; + clearInfo(); + screen.print(Common::Point(0, INFO_LINE + 1), COL_INFO_FOREGROUND, "Done..."); + + // Set how long to show the message + _menuCounter = 30; + } + } + } + + // Reset cursor back to arrow + events.setCursor(ARROW); +} + +void UserInterface::reset() { + _bgFound = _oldBgFound = -1; + _oldKey = -1; + _oldTemp = _temp = -1; + _exitZone = -1; +} + + } // End of namespace Sherlock diff --git a/engines/sherlock/user_interface.h b/engines/sherlock/user_interface.h index 042997a3e2..c16c9f5d11 100644 --- a/engines/sherlock/user_interface.h +++ b/engines/sherlock/user_interface.h @@ -28,6 +28,7 @@ #include "sherlock/surface.h" #include "sherlock/objects.h" #include "sherlock/resources.h" +#include "sherlock/fixed_text.h" namespace Sherlock { @@ -47,7 +48,16 @@ enum MenuMode { GIVE_MODE = 9, JOURNAL_MODE = 10, FILES_MODE = 11, - SETUP_MODE = 12 + SETUP_MODE = 12, + + // Rose Tattoo specific + LAB_MODE = 20, + MESSAGE_MODE = 21, + VERB_MODE = 22, + OPTION_MODE = 23, + QUIT_MODE = 24, + FOOLSCAP_MODE = 25, + PASSWORD_MODE = 26 }; class UserInterface { @@ -66,10 +76,12 @@ public: bool _helpStyle; Common::Rect _windowBounds; bool _lookScriptFlag; + int _bgFound, _oldBgFound; + int _exitZone; // TODO: Not so sure these should be in the base class. May want to refactor them to SherlockEngine, or refactor // various Scalpel dialogs to keep their own private state of key/selections - char _key, _oldKey; + signed char _key, _oldKey; int _selector, _oldSelector; int _temp, _oldTemp; int _temp1; @@ -79,9 +91,14 @@ public: virtual ~UserInterface() {} /** + * Called for OPEN, CLOSE, and MOVE actions are being done + */ + void checkAction(ActionType &action, int objNum, FixedTextActionId fixedTextActionId = kFixedTextAction_Invalid); +public: + /** * Resets the user interface */ - virtual void reset() {} + virtual void reset(); /** * Draw the user interface onto the screen's back buffers @@ -118,11 +135,6 @@ public: * Clear any active text window */ virtual void clearWindow() {} - - /** - * Print the previously selected object's decription - */ - virtual void printObjectDesc() {} }; } // End of namespace Sherlock diff --git a/engines/sky/music/adlibchannel.cpp b/engines/sky/music/adlibchannel.cpp index 8400fef6eb..c7acb9b6c1 100644 --- a/engines/sky/music/adlibchannel.cpp +++ b/engines/sky/music/adlibchannel.cpp @@ -29,7 +29,7 @@ namespace Sky { -AdLibChannel::AdLibChannel(FM_OPL *opl, uint8 *pMusicData, uint16 startOfData) { +AdLibChannel::AdLibChannel(OPL::OPL *opl, uint8 *pMusicData, uint16 startOfData) { _opl = opl; _musicData = pMusicData; _channelData.loopPoint = startOfData; @@ -45,6 +45,8 @@ AdLibChannel::AdLibChannel(FM_OPL *opl, uint8 *pMusicData, uint16 startOfData) { _channelData.frequency = 0; _channelData.instrumentData = NULL; + _musicVolume = 128; + uint16 instrumentDataLoc; if (SkyEngine::_systemVars.gameVersion == 109) { @@ -86,7 +88,7 @@ bool AdLibChannel::isActive() { } void AdLibChannel::updateVolume(uint16 pVolume) { - // Do nothing. The mixer handles the music volume for us. + _musicVolume = pVolume; } /* This class uses the same area for the register mirror as the original @@ -95,7 +97,7 @@ void AdLibChannel::updateVolume(uint16 pVolume) { */ void AdLibChannel::setRegister(uint8 regNum, uint8 value) { if (_adlibRegMirror[regNum] != value) { - OPLWriteReg (_opl, regNum, value); + _opl->writeReg(regNum, value); _adlibRegMirror[regNum] = value; } } @@ -208,6 +210,8 @@ void AdLibChannel::setupChannelVolume(uint8 volume) { uint32 resVol = ((volume + 1) * (_channelData.instrumentData->totOutLev_Op2 + 1)) << 1; resVol &= 0xFFFF; resVol *= (_channelData.channelVolume + 1) << 1; + resVol >>= 8; + resVol *= _musicVolume << 1; resVol >>= 16; assert(resVol < 0x81); resultOp = ((_channelData.instrumentData->scalingLevel << 6) & 0xC0) | _opOutputTable[resVol]; @@ -216,6 +220,8 @@ void AdLibChannel::setupChannelVolume(uint8 volume) { resVol = ((volume + 1) * (_channelData.instrumentData->totOutLev_Op1 + 1)) << 1; resVol &= 0xFFFF; resVol *= (_channelData.channelVolume + 1) << 1; + resVol >>= 8; + resVol *= _musicVolume << 1; resVol >>= 16; } else resVol = _channelData.instrumentData->totOutLev_Op1; diff --git a/engines/sky/music/adlibchannel.h b/engines/sky/music/adlibchannel.h index 80dae93b2c..4504e3b570 100644 --- a/engines/sky/music/adlibchannel.h +++ b/engines/sky/music/adlibchannel.h @@ -60,14 +60,15 @@ typedef struct { class AdLibChannel : public ChannelBase { public: - AdLibChannel (FM_OPL *opl, uint8 *pMusicData, uint16 startOfData); + AdLibChannel (OPL::OPL *opl, uint8 *pMusicData, uint16 startOfData); virtual ~AdLibChannel(); virtual uint8 process(uint16 aktTime); virtual void updateVolume(uint16 pVolume); virtual bool isActive(); private: - FM_OPL *_opl; + OPL::OPL *_opl; uint8 *_musicData; + uint16 _musicVolume; AdLibChannelType _channelData; InstrumentStruct *_instruments; diff --git a/engines/sky/music/adlibmusic.cpp b/engines/sky/music/adlibmusic.cpp index dd64c5bc81..be5e7b2353 100644 --- a/engines/sky/music/adlibmusic.cpp +++ b/engines/sky/music/adlibmusic.cpp @@ -22,6 +22,7 @@ #include "common/endian.h" +#include "common/textconsole.h" #include "sky/music/adlibmusic.h" #include "sky/music/adlibchannel.h" @@ -32,44 +33,21 @@ namespace Sky { AdLibMusic::AdLibMusic(Audio::Mixer *pMixer, Disk *pDisk) : MusicBase(pMixer, pDisk) { _driverFileBase = 60202; - _sampleRate = pMixer->getOutputRate(); - _opl = makeAdLibOPL(_sampleRate); + _opl = OPL::Config::create(); + if (!_opl || !_opl->init()) + error("Failed to create OPL"); - _mixer->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); + _opl->start(new Common::Functor0Mem<void, AdLibMusic>(this, &AdLibMusic::onTimer), 50); } AdLibMusic::~AdLibMusic() { - OPLDestroy(_opl); - _mixer->stopHandle(_soundHandle); + delete _opl; } -int AdLibMusic::readBuffer(int16 *data, const int numSamples) { - if (_musicData == NULL) { - // no music loaded - memset(data, 0, numSamples * sizeof(int16)); - } else if ((_currentMusic == 0) || (_numberOfChannels == 0)) { - // music loaded but not played as of yet - memset(data, 0, numSamples * sizeof(int16)); - // poll anyways as pollMusic() can activate the music +void AdLibMusic::onTimer() { + if (_musicData != NULL) pollMusic(); - _nextMusicPoll = _sampleRate / 50; - } else { - uint32 render; - uint remaining = numSamples; - while (remaining) { - render = (remaining > _nextMusicPoll) ? _nextMusicPoll : remaining; - remaining -= render; - _nextMusicPoll -= render; - YM3812UpdateOne(_opl, data, render); - data += render; - if (_nextMusicPoll == 0) { - pollMusic(); - _nextMusicPoll = _sampleRate / 50; - } - } - } - return numSamples; } void AdLibMusic::setupPointers() { @@ -87,7 +65,6 @@ void AdLibMusic::setupPointers() { _musicDataLoc = READ_LE_UINT16(_musicData + 0x1201); _initSequence = _musicData + 0xE91; } - _nextMusicPoll = 0; } void AdLibMusic::setupChannels(uint8 *channelData) { @@ -102,26 +79,15 @@ void AdLibMusic::setupChannels(uint8 *channelData) { void AdLibMusic::startDriver() { uint16 cnt = 0; while (_initSequence[cnt] || _initSequence[cnt + 1]) { - OPLWriteReg (_opl, _initSequence[cnt], _initSequence[cnt + 1]); + _opl->writeReg(_initSequence[cnt], _initSequence[cnt + 1]); cnt += 2; } } void AdLibMusic::setVolume(uint16 param) { _musicVolume = param; - _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, 2 * param); -} - -bool AdLibMusic::isStereo() const { - return false; -} - -bool AdLibMusic::endOfData() const { - return false; -} - -int AdLibMusic::getRate() const { - return _sampleRate; + for (uint8 cnt = 0; cnt < _numberOfChannels; cnt++) + _channels[cnt]->updateVolume(_musicVolume); } } // End of namespace Sky diff --git a/engines/sky/music/adlibmusic.h b/engines/sky/music/adlibmusic.h index 886eef026e..7b51f2d3a0 100644 --- a/engines/sky/music/adlibmusic.h +++ b/engines/sky/music/adlibmusic.h @@ -25,32 +25,32 @@ #include "sky/music/musicbase.h" #include "audio/audiostream.h" -#include "audio/fmopl.h" + +namespace OPL { +class OPL; +} namespace Sky { -class AdLibMusic : public Audio::AudioStream, public MusicBase { +class AdLibMusic : public MusicBase { public: AdLibMusic(Audio::Mixer *pMixer, Disk *pDisk); ~AdLibMusic(); // AudioStream API - int readBuffer(int16 *buffer, const int numSamples); - bool isStereo() const; - bool endOfData() const; - int getRate() const; virtual void setVolume(uint16 param); private: - FM_OPL *_opl; - Audio::SoundHandle _soundHandle; + OPL::OPL *_opl; uint8 *_initSequence; - uint32 _sampleRate, _nextMusicPoll; + uint32 _sampleRate; virtual void setupPointers(); virtual void setupChannels(uint8 *channelData); virtual void startDriver(); void premixerCall(int16 *buf, uint len); + + void onTimer(); }; } // End of namespace Sky diff --git a/engines/sword25/fmv/movieplayer.cpp b/engines/sword25/fmv/movieplayer.cpp index 5d7dcf2506..eb0f0390dc 100644 --- a/engines/sword25/fmv/movieplayer.cpp +++ b/engines/sword25/fmv/movieplayer.cpp @@ -123,7 +123,7 @@ void MoviePlayer::update() { if (_decoder.endOfVideo()) { // Movie complete, so unload the movie unloadMovie(); - } else { + } else if (_decoder.needsUpdate()) { const Graphics::Surface *s = _decoder.decodeNextFrame(); if (s) { // Transfer the next frame diff --git a/engines/tinsel/music.cpp b/engines/tinsel/music.cpp index 8a7305f63b..9b4e2494e0 100644 --- a/engines/tinsel/music.cpp +++ b/engines/tinsel/music.cpp @@ -27,6 +27,8 @@ #include "audio/audiostream.h" #include "audio/mididrv.h" #include "audio/midiparser.h" +// Miles Audio for Discworld 1 +#include "audio/miles.h" #include "audio/decoders/adpcm.h" #include "backends/audiocd/audiocd.h" @@ -373,8 +375,78 @@ void DeleteMidiBuffer() { g_midiBuffer.pDat = NULL; } -MidiMusicPlayer::MidiMusicPlayer() { - MidiPlayer::createDriver(); +MidiMusicPlayer::MidiMusicPlayer(TinselEngine *vm) { + _driver = NULL; + _milesAudioMode = false; + bool milesAudioEnabled = false; + + if (vm->getPlatform() == Common::kPlatformDOS) { + // Enable Miles Audio for DOS only + milesAudioEnabled = true; + } + + if ((vm->getGameId() == GID_DW1) && (milesAudioEnabled)) { + // Discworld 1 (DOS) uses Miles Audio 3 + // use our own Miles Audio drivers + // + // It seems that there are multiple versions of Discworld 1 + // + // Version 1: + // Has SAMPLE.AD for AdLib and SAMPLE.OPL for OPL-3 + // Timbre files are inside a subdirectory of the CD called "/drivers". Main game files are in + // another subdirectory, which means the user has to copy those files over. + // Installer script copies all drivers directly to harddrive without name changes + // + // Version 2: + // Has FAT.OPL only (gets copied by the installer into MIDPAK.AD or MIDPAK.OPL) + // Timbre file is inside subdirectory "drivers" right in the main game directory. + // Installer copies FAT.OPL to MIDPAK.AD all the time, even when user selected AWE32 + // + // Neither have timbre data for MT32 + + ::MidiDriver::DeviceHandle dev = ::MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM); + ::MusicType musicType = ::MidiDriver::getMusicType(dev); + Common::File fileClass; + + switch (musicType) { + case MT_ADLIB: + if (fileClass.exists("FAT.OPL")) { + // Version 2: fat.opl, may be in drivers-subdirectory + _driver = Audio::MidiDriver_Miles_AdLib_create("", "FAT.OPL"); + } else { + if (fileClass.exists("MIDPAK.AD")) { + // Version 2: drivers got installed and fat.opl got copied over by the user + _driver = Audio::MidiDriver_Miles_AdLib_create("MIDPAK.AD", ""); + } else { + // Version 1: sample.ad / sample.opl, have to be copied over by the user for this version + // That's why we check those last, because then the user gets a proper error message for them + _driver = Audio::MidiDriver_Miles_AdLib_create("SAMPLE.AD", "SAMPLE.OPL"); + } + } + break; + case MT_MT32: + // Discworld 1 doesn't have a MT32 timbre file + _driver = Audio::MidiDriver_Miles_MT32_create(""); + break; + case MT_GM: + if (ConfMan.getBool("native_mt32")) { + _driver = Audio::MidiDriver_Miles_MT32_create(""); + musicType = MT_MT32; + } + break; + default: + break; + } + if (!_driver) { + // nothing got created yet? -> create default driver + MidiPlayer::createDriver(); + } else { + _milesAudioMode = true; + } + + } else { + MidiPlayer::createDriver(); + } int ret = _driver->open(); if (ret == 0) { @@ -394,6 +466,11 @@ void MidiMusicPlayer::setVolume(int volume) { } void MidiMusicPlayer::send(uint32 b) { + if (_milesAudioMode) { + _driver->send(b); + return; + } + Audio::MidiPlayer::send(b); byte channel = (byte)(b & 0x0F); diff --git a/engines/tinsel/music.h b/engines/tinsel/music.h index 0a78c39a76..422d80ae30 100644 --- a/engines/tinsel/music.h +++ b/engines/tinsel/music.h @@ -60,7 +60,7 @@ void dumpMusic(); class MidiMusicPlayer : public Audio::MidiPlayer { public: - MidiMusicPlayer(); + MidiMusicPlayer(TinselEngine *vm); virtual void setVolume(int volume); @@ -77,6 +77,8 @@ public: // means. The default is 120. uint32 getBaseTempo() { return _driver ? (109 * _driver->getBaseTempo()) / 120 : 0; } + bool _milesAudioMode; + private: void playXMIDI(uint32 size, bool loop); void playSEQ(uint32 size, bool loop); diff --git a/engines/tinsel/tinsel.cpp b/engines/tinsel/tinsel.cpp index 57d8432f0e..6dc8e3bb35 100644 --- a/engines/tinsel/tinsel.cpp +++ b/engines/tinsel/tinsel.cpp @@ -885,12 +885,15 @@ void TinselEngine::initializePath(const Common::FSNode &gamePath) { } else { // Add DW2 subfolder to search path in case user is running directly from the CDs SearchMan.addSubDirectoryMatching(gamePath, "dw2"); + + // Location of Miles audio files (sample.ad and sample.opl) in Discworld 1 + SearchMan.addSubDirectoryMatching(gamePath, "drivers"); Engine::initializePath(gamePath); } } Common::Error TinselEngine::run() { - _midiMusic = new MidiMusicPlayer(); + _midiMusic = new MidiMusicPlayer(this); _pcmMusic = new PCMMusicPlayer(); _sound = new SoundManager(this); _bmv = new BMVPlayer(); diff --git a/engines/toltecs/music.cpp b/engines/toltecs/music.cpp index e4e067de39..97d8b1aea2 100644 --- a/engines/toltecs/music.cpp +++ b/engines/toltecs/music.cpp @@ -21,6 +21,7 @@ */ #include "audio/midiparser.h" +#include "audio/miles.h" #include "common/textconsole.h" #include "toltecs/toltecs.h" @@ -30,20 +31,47 @@ namespace Toltecs { MusicPlayer::MusicPlayer(bool isGM) : _isGM(isGM), _buffer(NULL) { - MidiPlayer::createDriver(); + MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB | MDT_PREFER_GM); + MusicType musicType = MidiDriver::getMusicType(dev); + + switch (musicType) { + case MT_ADLIB: + _milesAudioMode = true; + _driver = Audio::MidiDriver_Miles_AdLib_create("SAMPLE.AD", "SAMPLE.OPL"); + break; + case MT_MT32: + // Not recommended since it sounds awful, but apparently the + // original sounded just as bad. I guess MT-32 support was + // added by default, not because anyone actually put any work + // into it. + _milesAudioMode = true; + _driver = Audio::MidiDriver_Miles_MT32_create(""); + break; + default: + _milesAudioMode = false; + MidiPlayer::createDriver(); + break; + } int ret = _driver->open(); if (ret == 0) { - if (_nativeMT32) - _driver->sendMT32Reset(); - else - _driver->sendGMReset(); + if (musicType != MT_ADLIB) { + if (musicType == MT_MT32 || _nativeMT32) + _driver->sendMT32Reset(); + else + _driver->sendGMReset(); + } _driver->setTimerCallback(this, &timerCallback); } } void MusicPlayer::send(uint32 b) { + if (_milesAudioMode) { + _driver->send(b); + return; + } + if ((b & 0xF0) == 0xC0 && !_isGM && !_nativeMT32) { b = (b & 0xFFFF00FF) | MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8; } diff --git a/engines/toltecs/music.h b/engines/toltecs/music.h index e6dc3dd146..25d67e9334 100644 --- a/engines/toltecs/music.h +++ b/engines/toltecs/music.h @@ -47,6 +47,7 @@ protected: private: byte *_buffer; + bool _milesAudioMode; }; class Music : public MusicPlayer { diff --git a/engines/tony/window.cpp b/engines/tony/window.cpp index 3b3687419b..d312d58091 100644 --- a/engines/tony/window.cpp +++ b/engines/tony/window.cpp @@ -28,7 +28,7 @@ #include "common/scummsys.h" #include "graphics/surface.h" -#include "util.h" +#include "engines/util.h" #include "tony/window.h" #include "tony/game.h" #include "tony/tony.h" diff --git a/engines/tsage/detection_tables.h b/engines/tsage/detection_tables.h index 1dfc3e6fd2..109ac353e6 100644 --- a/engines/tsage/detection_tables.h +++ b/engines/tsage/detection_tables.h @@ -185,7 +185,7 @@ static const tSageGameDescription gameDescriptions[] = { GType_Ringworld2, GF_CD | GF_ALT_REGIONS | GF_DEMO }, - +#ifdef TSAGE_SHERLOCK_ENABLED // The Lost Files of Sherlock Holmes - The Case of the Serrated Scalpel (Logo) { { @@ -200,6 +200,7 @@ static const tSageGameDescription gameDescriptions[] = { GType_Sherlock1, GF_FLOPPY }, +#endif { AD_TABLE_END_MARKER, 0, 0 } }; diff --git a/engines/tsage/globals.cpp b/engines/tsage/globals.cpp index 1be3e2b6da..b880f35007 100644 --- a/engines/tsage/globals.cpp +++ b/engines/tsage/globals.cpp @@ -157,12 +157,15 @@ Globals::Globals() : _dialogCenter(160, 140), _gfxManagerInstance(_screenSurface _game = new Ringworld2::Ringworld2Game(); _sceneHandler = new Ringworld2::SceneHandlerExt(); break; - +#ifdef TSAGE_SHERLOCK_ENABLED case GType_Sherlock1: _inventory = nullptr; _sceneHandler = new Sherlock::SherlockSceneHandler(); _game = new Sherlock::SherlockLogo(); break; +#endif + default: + break; } } diff --git a/engines/tsage/sherlock/sherlock_logo.cpp b/engines/tsage/sherlock/sherlock_logo.cpp index 437fdc6d94..e27ce76576 100644 --- a/engines/tsage/sherlock/sherlock_logo.cpp +++ b/engines/tsage/sherlock/sherlock_logo.cpp @@ -20,6 +20,7 @@ * */ +#ifdef TSAGE_SHERLOCK_ENABLED #include "tsage/sherlock/sherlock_logo.h" #include "tsage/scenes.h" #include "tsage/tsage.h" @@ -148,7 +149,7 @@ void SherlockLogoScene::Action1::signal() { scene._object1.changeZoom(100); scene._object1.setPosition(Common::Point(170, 142)); scene._object1._numFrames = 7; - scene._object1.animate(ANIM_MODE_5, nullptr); + scene._object1.animate(ANIM_MODE_5, (const void *)nullptr); ADD_MOVER(scene._object1, 158, 71); break; @@ -164,7 +165,7 @@ void SherlockLogoScene::Action1::signal() { scene._object2._frame = 1; scene._object2.setPosition(Common::Point(152, 98)); scene._object2.changeZoom(100); - scene._object2.animate(ANIM_MODE_NONE, nullptr); + scene._object2.animate(ANIM_MODE_NONE, (const void *)nullptr); setDelay(120); break; @@ -176,7 +177,7 @@ void SherlockLogoScene::Action1::signal() { scene._object3._frame = 1; scene._object3.setPosition(Common::Point(33, 91)); scene._object3.changeZoom(100); - scene._object3.animate(ANIM_MODE_NONE, nullptr); + scene._object3.animate(ANIM_MODE_NONE, (const void *)nullptr); setDelay(5); break; @@ -341,7 +342,7 @@ void SherlockLogoScene::postInit(SceneObjectList *OwnerList) { _object4._frame = 1; _object4.setPosition(Common::Point(155, 94)); _object4.changeZoom(100); - _object4.animate(ANIM_MODE_NONE, nullptr); + _object4.animate(ANIM_MODE_NONE, (const void *)nullptr); _object4.hide(); setAction(&_action1); @@ -354,3 +355,5 @@ void SherlockLogoScene::finish() { } // End of namespace Sherlock } // End of namespace TsAGE + +#endif diff --git a/engines/tsage/sherlock/sherlock_logo.h b/engines/tsage/sherlock/sherlock_logo.h index 95fc0e272f..01b5b7f75f 100644 --- a/engines/tsage/sherlock/sherlock_logo.h +++ b/engines/tsage/sherlock/sherlock_logo.h @@ -20,6 +20,7 @@ * */ +#ifdef TSAGE_SHERLOCK_ENABLED #ifndef TSAGE_SHERLOCK_LOGO_H #define TSAGE_SHERLOCK_LOGO_H @@ -76,3 +77,4 @@ public: } // End of namespace TsAGE #endif +#endif diff --git a/engines/tsage/sound.cpp b/engines/tsage/sound.cpp index b95b614f09..0d3fb55dd3 100644 --- a/engines/tsage/sound.cpp +++ b/engines/tsage/sound.cpp @@ -20,9 +20,9 @@ * */ +#include "audio/fmopl.h" #include "audio/decoders/raw.h" #include "common/config-manager.h" -#include "audio/decoders/raw.h" #include "audio/audiostream.h" #include "tsage/core.h" #include "tsage/globals.h" @@ -2743,17 +2743,9 @@ AdlibSoundDriver::AdlibSoundDriver(): SoundDriver() { _groupData._pData = &adlib_group_data[0]; _mixer = g_vm->_mixer; - _sampleRate = _mixer->getOutputRate(); _opl = OPL::Config::create(); assert(_opl); - _opl->init(_sampleRate); - - _samplesTillCallback = 0; - _samplesTillCallbackRemainder = 0; - _samplesPerCallback = getRate() / CALLBACKS_PER_SECOND; - _samplesPerCallbackRemainder = getRate() % CALLBACKS_PER_SECOND; - - _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); + _opl->init(); Common::fill(_channelVoiced, _channelVoiced + ADLIB_CHANNEL_COUNT, false); memset(_channelVolume, 0, ADLIB_CHANNEL_COUNT * sizeof(int)); @@ -2772,11 +2764,12 @@ AdlibSoundDriver::AdlibSoundDriver(): SoundDriver() { _channelVoiced[i] = false; _pitchBlend[i] = 0; } + + _opl->start(new Common::Functor0Mem<void, AdlibSoundDriver>(this, &AdlibSoundDriver::onTimer), CALLBACKS_PER_SECOND); } AdlibSoundDriver::~AdlibSoundDriver() { DEALLOCATE(_patchData); - _mixer->stopHandle(_soundHandle); delete _opl; } @@ -3019,33 +3012,12 @@ void AdlibSoundDriver::setFrequency(int channel) { ((dataWord >> 8) & 3) | (var2 << 2)); } -int AdlibSoundDriver::readBuffer(int16 *buffer, const int numSamples) { +void AdlibSoundDriver::onTimer() { Common::StackLock slock1(SoundManager::sfManager()._serverDisabledMutex); Common::StackLock slock2(SoundManager::sfManager()._serverSuspendedMutex); - int32 samplesLeft = numSamples; - memset(buffer, 0, sizeof(int16) * numSamples); - while (samplesLeft) { - if (!_samplesTillCallback) { - SoundManager::sfUpdateCallback(NULL); - flush(); - - _samplesTillCallback = _samplesPerCallback; - _samplesTillCallbackRemainder += _samplesPerCallbackRemainder; - if (_samplesTillCallbackRemainder >= CALLBACKS_PER_SECOND) { - _samplesTillCallback++; - _samplesTillCallbackRemainder -= CALLBACKS_PER_SECOND; - } - } - - int32 render = MIN<int>(samplesLeft, _samplesTillCallback); - samplesLeft -= render; - _samplesTillCallback -= render; - - _opl->readBuffer(buffer, render); - buffer += render; - } - return numSamples; + SoundManager::sfUpdateCallback(NULL); + flush(); } /*--------------------------------------------------------------------------*/ diff --git a/engines/tsage/sound.h b/engines/tsage/sound.h index 49558b4bca..68755a48c8 100644 --- a/engines/tsage/sound.h +++ b/engines/tsage/sound.h @@ -27,12 +27,15 @@ #include "common/mutex.h" #include "common/queue.h" #include "audio/audiostream.h" -#include "audio/fmopl.h" #include "audio/mixer.h" #include "common/list.h" #include "tsage/saveload.h" #include "tsage/core.h" +namespace OPL { +class OPL; +} + namespace TsAGE { class Sound; @@ -446,21 +449,15 @@ public: #define ADLIB_CHANNEL_COUNT 9 -class AdlibSoundDriver: public SoundDriver, Audio::AudioStream { +class AdlibSoundDriver: public SoundDriver { private: GroupData _groupData; Audio::Mixer *_mixer; - FM_OPL *_opl; - Audio::SoundHandle _soundHandle; - int _sampleRate; + OPL::OPL *_opl; byte _portContents[256]; const byte *_patchData; int _masterVolume; Common::Queue<RegisterValue> _queue; - int _samplesTillCallback; - int _samplesTillCallbackRemainder; - int _samplesPerCallback; - int _samplesPerCallbackRemainder; bool _channelVoiced[ADLIB_CHANNEL_COUNT]; int _channelVolume[ADLIB_CHANNEL_COUNT]; @@ -495,13 +492,8 @@ public: virtual void proc38(int channel, int cmd, int value); virtual void setPitch(int channel, int pitchBlend); - // AudioStream interface - virtual int readBuffer(int16 *buffer, const int numSamples); - virtual bool isStereo() const { return false; } - virtual bool endOfData() const { return false; } - virtual int getRate() const { return _sampleRate; } - - void update(int16 *buf, int len); +private: + void onTimer(); }; class SoundBlasterDriver: public SoundDriver { diff --git a/engines/tsage/tsage.cpp b/engines/tsage/tsage.cpp index 4412d0670f..b94b82f423 100644 --- a/engines/tsage/tsage.cpp +++ b/engines/tsage/tsage.cpp @@ -112,10 +112,12 @@ void TSageEngine::initialize() { // Reset all global variables R2_GLOBALS.reset(); } else if (g_vm->getGameID() == GType_Sherlock1) { +#ifdef TSAGE_SHERLOCK_ENABLED g_resourceManager->addLib("SF3.RLB"); g_globals = new Globals(); return; +#endif } g_globals->gfxManager().setDefaults(); diff --git a/engines/wintermute/base/base_script_holder.cpp b/engines/wintermute/base/base_script_holder.cpp index 8383657239..5b1c961479 100644 --- a/engines/wintermute/base/base_script_holder.cpp +++ b/engines/wintermute/base/base_script_holder.cpp @@ -302,7 +302,7 @@ bool BaseScriptHolder::addScript(const char *filename) { for (uint32 i = 0; i < _scripts.size(); i++) { if (scumm_stricmp(_scripts[i]->_filename, filename) == 0) { if (_scripts[i]->_state != SCRIPT_FINISHED) { - BaseEngine::LOG(0, "BaseScriptHolder::AddScript - trying to add script '%s' mutiple times (obj: '%s')", filename, getName()); + BaseEngine::LOG(0, "BaseScriptHolder::AddScript - trying to add script '%s' multiple times (obj: '%s')", filename, getName()); return STATUS_OK; } } diff --git a/engines/zvision/configure.engine b/engines/zvision/configure.engine index 38a5959995..226870c3fd 100644 --- a/engines/zvision/configure.engine +++ b/engines/zvision/configure.engine @@ -1,3 +1,3 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] -add_engine zvision "ZVision" yes "" "" "freetype2 16bit" +add_engine zvision "Z-Vision" yes "" "" "freetype2 16bit" diff --git a/engines/zvision/core/clock.h b/engines/zvision/core/clock.h index cbf52be560..ae8c968111 100644 --- a/engines/zvision/core/clock.h +++ b/engines/zvision/core/clock.h @@ -67,14 +67,14 @@ public: } /** - * Pause the clock. Any future delta times will take this pause into account. - * Has no effect if the clock is already paused. - */ + * Un-pause the clock. + * Has no effect if the clock is already un-paused. + */ void start(); /** - * Un-pause the clock. - * Has no effect if the clock is already un-paused. + * Pause the clock. Any future delta times will take this pause into account. + * Has no effect if the clock is already paused. */ void stop(); }; diff --git a/engines/zvision/core/console.cpp b/engines/zvision/core/console.cpp index f5cacb582c..336541d82a 100644 --- a/engines/zvision/core/console.cpp +++ b/engines/zvision/core/console.cpp @@ -275,7 +275,7 @@ bool Console::cmdDumpFiles(int argc, const char **argv) { bool Console::cmdDumpImage(int argc, const char **argv) { if (argc != 2) { - debugPrintf("Use %s <TGA/TGZ name> to dump a ZVision TGA/TGZ image into a regular BMP image\n", argv[0]); + debugPrintf("Use %s <TGA/TGZ name> to dump a Z-Vision TGA/TGZ image into a regular BMP image\n", argv[0]); return true; } diff --git a/engines/zvision/detection.cpp b/engines/zvision/detection.cpp index c817cbf3e9..f44e653c2a 100644 --- a/engines/zvision/detection.cpp +++ b/engines/zvision/detection.cpp @@ -24,8 +24,9 @@ #include "base/plugins.h" +#include "engines/advancedDetector.h" + #include "zvision/zvision.h" -#include "zvision/detection.h" #include "zvision/file/save_manager.h" #include "zvision/scripting/script_manager.h" @@ -36,277 +37,39 @@ namespace ZVision { -uint32 ZVision::getFeatures() const { - return _gameDescription->desc.flags; -} +struct ZVisionGameDescription { + ADGameDescription desc; + ZVisionGameId gameId; +}; +ZVisionGameId ZVision::getGameId() const { + return _gameDescription->gameId; +} Common::Language ZVision::getLanguage() const { return _gameDescription->desc.language; } +uint32 ZVision::getFeatures() const { + return _gameDescription->desc.flags; +} } // End of namespace ZVision -static const PlainGameDescriptor zVisionGames[] = { - {"zvision", "ZVision Game"}, - {"znemesis", "Zork Nemesis: The Forbidden Lands"}, - {"zgi", "Zork: Grand Inquisitor"}, - {0, 0} -}; - -namespace ZVision { - -#define GAMEOPTION_ORIGINAL_SAVELOAD GUIO_GAMEOPTIONS1 -#define GAMEOPTION_DOUBLE_FPS GUIO_GAMEOPTIONS2 -#define GAMEOPTION_ENABLE_VENUS GUIO_GAMEOPTIONS3 -#define GAMEOPTION_DISABLE_ANIM_WHILE_TURNING GUIO_GAMEOPTIONS4 -#define GAMEOPTION_USE_HIRES_MPEG_MOVIES GUIO_GAMEOPTIONS5 - -static const ZVisionGameDescription gameDescriptions[] = { - - { - // Zork Nemesis English version - { - "znemesis", - 0, - AD_ENTRY1s("CSCR.ZFS", "88226e51a205d2e50c67a5237f3bd5f2", 2397741), - Common::EN_ANY, - Common::kPlatformDOS, - ADGF_NO_FLAGS, - GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) - }, - GID_NEMESIS - }, - - { - // Zork Nemesis French version - { - "znemesis", - 0, - {{"CSCR.ZFS", 0, "f04113357b4748c13efcb58b4629887c", 2577873}, - {"NEMESIS.STR", 0, "333bcb17bbb7f57cae742fbbe44f56f3", 9219}, - AD_LISTEND - }, - Common::FR_FRA, - Common::kPlatformDOS, - ADGF_NO_FLAGS, - GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) - }, - GID_NEMESIS - }, - - { - // Zork Nemesis German version - { - "znemesis", - 0, - {{"CSCR.ZFS", 0, "f04113357b4748c13efcb58b4629887c", 2577873}, - {"NEMESIS.STR", 0, "3d1a12b907751653866cffc6d4dfb331", 9505}, - AD_LISTEND - }, - Common::DE_DEU, - Common::kPlatformDOS, - ADGF_NO_FLAGS, - GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) - }, - GID_NEMESIS - }, - - { - // Zork Nemesis Italian version - { - "znemesis", - 0, - {{"CSCR.ZFS", 0, "f04113357b4748c13efcb58b4629887c", 2577873}, - {"NEMESIS.STR", 0, "7c568feca8d9f9ae855c47183612c305", 9061}, - AD_LISTEND - }, - Common::IT_ITA, - Common::kPlatformDOS, - ADGF_NO_FLAGS, - GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) - }, - GID_NEMESIS - }, - - { - // Zork Nemesis English demo version - { - "znemesis", - "Demo", - AD_ENTRY1s("SCRIPTS.ZFS", "64f1e881394e9462305104f99513c833", 380539), - Common::EN_ANY, - Common::kPlatformWindows, - ADGF_DEMO, - GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) - }, - GID_NEMESIS - }, - - { - // Zork Grand Inquisitor English CD version - { - "zgi", - "CD", - AD_ENTRY1s("SCRIPTS.ZFS", "81efd40ecc3d22531e211368b779f17f", 8336944), - Common::EN_ANY, - Common::kPlatformWindows, - ADGF_NO_FLAGS, - GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) - }, - GID_GRANDINQUISITOR - }, - - { - // Zork Grand Inquisitor French CD version, reported by ulrichh on IRC - { - "zgi", - "CD", - AD_ENTRY1s("SCRIPTS.ZFS", "4d1ec4ade7ecc9ee9ec591d43ca3d213", 8338133), - Common::FR_FRA, - Common::kPlatformWindows, - ADGF_NO_FLAGS, - GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) - }, - GID_GRANDINQUISITOR - }, - - { - // Zork Grand Inquisitor German CD version, reported by breit in bug #6760 - { - "zgi", - "CD", - AD_ENTRY1s("SCRIPTS.ZFS", "b7ac7e331b9b7f884590b0b325b560c8", 8338133), - Common::DE_DEU, - Common::kPlatformWindows, - ADGF_NO_FLAGS, - GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) - }, - GID_GRANDINQUISITOR - }, - - { - // Zork Grand Inquisitor Spanish CD version, reported by dianiu in bug #6764 - { - "zgi", - "CD", - AD_ENTRY1s("SCRIPTS.ZFS", "5cdc4b99c1134053af135aae71326fd1", 8338141), - Common::ES_ESP, - Common::kPlatformWindows, - ADGF_NO_FLAGS, - GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) - }, - GID_GRANDINQUISITOR - }, - - { - // Zork Grand Inquisitor English DVD version - { - "zgi", - "DVD", - AD_ENTRY1s("SCRIPTS.ZFS", "03157a3399513bfaaf8dc6d5ab798b36", 8433326), - Common::EN_ANY, - Common::kPlatformWindows, - ADGF_NO_FLAGS, - GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING, GAMEOPTION_USE_HIRES_MPEG_MOVIES) - }, - GID_GRANDINQUISITOR - }, - - { - // Zork Grand Inquisitor English demo version - { - "zgi", - "Demo", - AD_ENTRY1s("SCRIPTS.ZFS", "71a2494fd2fb999347deb13401e9b998", 304239), - Common::EN_ANY, - Common::kPlatformWindows, - ADGF_DEMO, - GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) - }, - GID_GRANDINQUISITOR - }, - - { - AD_TABLE_END_MARKER, - GID_NONE - } -}; - -} // End of namespace ZVision - -static const char *directoryGlobs[] = { - "znemscr", - 0 -}; - -static const ADExtraGuiOptionsMap optionsList[] = { - { - GAMEOPTION_ORIGINAL_SAVELOAD, - { - _s("Use original save/load screens"), - _s("Use the original save/load screens, instead of the ScummVM ones"), - "originalsaveload", - false - } - }, - - { - GAMEOPTION_DOUBLE_FPS, - { - _s("Double FPS"), - _s("Increase game FPS from 30 to 60"), - "doublefps", - false - } - }, - - { - GAMEOPTION_ENABLE_VENUS, - { - _s("Enable Venus"), - _s("Enable the Venus help system"), - "venusenabled", - true - } - }, - - { - GAMEOPTION_DISABLE_ANIM_WHILE_TURNING, - { - _s("Disable animation while turning"), - _s("Disable animation while turning in panoramic mode"), - "noanimwhileturning", - false - } - }, - - { - GAMEOPTION_USE_HIRES_MPEG_MOVIES, - { - _s("Use the hires MPEG movies"), - _s("Use the hires MPEG movies of the DVD version, instead of the lowres AVI ones"), - "mpegmovies", - true - } - }, - - AD_EXTRA_GUI_OPTIONS_TERMINATOR -}; +#include "zvision/detection_tables.h" class ZVisionMetaEngine : public AdvancedMetaEngine { public: - ZVisionMetaEngine() : AdvancedMetaEngine(ZVision::gameDescriptions, sizeof(ZVision::ZVisionGameDescription), zVisionGames, optionsList) { + ZVisionMetaEngine() : AdvancedMetaEngine(ZVision::gameDescriptions, sizeof(ZVision::ZVisionGameDescription), ZVision::zVisionGames, ZVision::optionsList) { _maxScanDepth = 2; - _directoryGlobs = directoryGlobs; + _directoryGlobs = ZVision::directoryGlobs; _singleid = "zvision"; } virtual const char *getName() const { - return "ZVision"; + return "Z-Vision"; } virtual const char *getOriginalCopyright() const { - return "ZVision Activision (C) 1996"; + return "Z-Vision (C) 1996 Activision"; } virtual bool hasFeature(MetaEngineFeature f) const; diff --git a/engines/zvision/detection_tables.h b/engines/zvision/detection_tables.h new file mode 100644 index 0000000000..06bc58ee7f --- /dev/null +++ b/engines/zvision/detection_tables.h @@ -0,0 +1,277 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef ZVISION_DETECTION_TABLES_H +#define ZVISION_DETECTION_TABLES_H + +namespace ZVision { + +static const PlainGameDescriptor zVisionGames[] = { + { "zvision", "Z-Vision Game" }, + { "znemesis", "Zork Nemesis: The Forbidden Lands" }, + { "zgi", "Zork: Grand Inquisitor" }, + { 0, 0 } +}; + +static const char *directoryGlobs[] = { + "znemscr", + 0 +}; + +#define GAMEOPTION_ORIGINAL_SAVELOAD GUIO_GAMEOPTIONS1 +#define GAMEOPTION_DOUBLE_FPS GUIO_GAMEOPTIONS2 +#define GAMEOPTION_ENABLE_VENUS GUIO_GAMEOPTIONS3 +#define GAMEOPTION_DISABLE_ANIM_WHILE_TURNING GUIO_GAMEOPTIONS4 +#define GAMEOPTION_USE_HIRES_MPEG_MOVIES GUIO_GAMEOPTIONS5 + +static const ADExtraGuiOptionsMap optionsList[] = { + + { + GAMEOPTION_ORIGINAL_SAVELOAD, + { + _s("Use original save/load screens"), + _s("Use the original save/load screens instead of the ScummVM interface"), + "originalsaveload", + false + } + }, + + { + GAMEOPTION_DOUBLE_FPS, + { + _s("Double FPS"), + _s("Increase framerate from 30 to 60 FPS"), + "doublefps", + false + } + }, + + { + GAMEOPTION_ENABLE_VENUS, + { + _s("Enable Venus"), + _s("Enable the Venus help system"), + "venusenabled", + true + } + }, + + { + GAMEOPTION_DISABLE_ANIM_WHILE_TURNING, + { + _s("Disable animation while turning"), + _s("Disable animation while turning in panorama mode"), + "noanimwhileturning", + false + } + }, + + { + GAMEOPTION_USE_HIRES_MPEG_MOVIES, + { + _s("Use high resolution MPEG video"), + _s("Use MPEG video from the DVD version, instead of lower resolution AVI"), + "mpegmovies", + true + } + }, + + AD_EXTRA_GUI_OPTIONS_TERMINATOR +}; + +static const ZVisionGameDescription gameDescriptions[] = { + + { + // Zork Nemesis English version + { + "znemesis", + 0, + AD_ENTRY1s("CSCR.ZFS", "88226e51a205d2e50c67a5237f3bd5f2", 2397741), + Common::EN_ANY, + Common::kPlatformDOS, + ADGF_NO_FLAGS, + GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) + }, + GID_NEMESIS + }, + + { + // Zork Nemesis French version + { + "znemesis", + 0, + { + { "CSCR.ZFS", 0, "f04113357b4748c13efcb58b4629887c", 2577873 }, + { "NEMESIS.STR", 0, "333bcb17bbb7f57cae742fbbe44f56f3", 9219 }, + AD_LISTEND + }, + Common::FR_FRA, + Common::kPlatformDOS, + ADGF_NO_FLAGS, + GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) + }, + GID_NEMESIS + }, + + { + // Zork Nemesis German version + { + "znemesis", + 0, + { + { "CSCR.ZFS", 0, "f04113357b4748c13efcb58b4629887c", 2577873 }, + { "NEMESIS.STR", 0, "3d1a12b907751653866cffc6d4dfb331", 9505 }, + AD_LISTEND + }, + Common::DE_DEU, + Common::kPlatformDOS, + ADGF_NO_FLAGS, + GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) + }, + GID_NEMESIS + }, + + { + // Zork Nemesis Italian version + { + "znemesis", + 0, + { + { "CSCR.ZFS", 0, "f04113357b4748c13efcb58b4629887c", 2577873 }, + { "NEMESIS.STR", 0, "7c568feca8d9f9ae855c47183612c305", 9061 }, + AD_LISTEND + }, + Common::IT_ITA, + Common::kPlatformDOS, + ADGF_NO_FLAGS, + GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) + }, + GID_NEMESIS + }, + + { + // Zork Nemesis English demo version + { + "znemesis", + "Demo", + AD_ENTRY1s("SCRIPTS.ZFS", "64f1e881394e9462305104f99513c833", 380539), + Common::EN_ANY, + Common::kPlatformWindows, + ADGF_DEMO, + GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_ENABLE_VENUS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) + }, + GID_NEMESIS + }, + + { + // Zork Grand Inquisitor English CD version + { + "zgi", + "CD", + AD_ENTRY1s("SCRIPTS.ZFS", "81efd40ecc3d22531e211368b779f17f", 8336944), + Common::EN_ANY, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) + }, + GID_GRANDINQUISITOR + }, + + { + // Zork Grand Inquisitor French CD version, reported by ulrichh on IRC + { + "zgi", + "CD", + AD_ENTRY1s("SCRIPTS.ZFS", "4d1ec4ade7ecc9ee9ec591d43ca3d213", 8338133), + Common::FR_FRA, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) + }, + GID_GRANDINQUISITOR + }, + + { + // Zork Grand Inquisitor German CD version, reported by breit in bug #6760 + { + "zgi", + "CD", + AD_ENTRY1s("SCRIPTS.ZFS", "b7ac7e331b9b7f884590b0b325b560c8", 8338133), + Common::DE_DEU, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) + }, + GID_GRANDINQUISITOR + }, + + { + // Zork Grand Inquisitor Spanish CD version, reported by dianiu in bug #6764 + { + "zgi", + "CD", + AD_ENTRY1s("SCRIPTS.ZFS", "5cdc4b99c1134053af135aae71326fd1", 8338141), + Common::ES_ESP, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) + }, + GID_GRANDINQUISITOR + }, + + { + // Zork Grand Inquisitor English DVD version + { + "zgi", + "DVD", + AD_ENTRY1s("SCRIPTS.ZFS", "03157a3399513bfaaf8dc6d5ab798b36", 8433326), + Common::EN_ANY, + Common::kPlatformWindows, + ADGF_NO_FLAGS, + GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING, GAMEOPTION_USE_HIRES_MPEG_MOVIES) + }, + GID_GRANDINQUISITOR + }, + + { + // Zork Grand Inquisitor English demo version + { + "zgi", + "Demo", + AD_ENTRY1s("SCRIPTS.ZFS", "71a2494fd2fb999347deb13401e9b998", 304239), + Common::EN_ANY, + Common::kPlatformWindows, + ADGF_DEMO, + GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_DOUBLE_FPS, GAMEOPTION_DISABLE_ANIM_WHILE_TURNING) + }, + GID_GRANDINQUISITOR + }, + + { + AD_TABLE_END_MARKER, + GID_NONE + } +}; + +} // End of namespace ZVision + +#endif diff --git a/engines/zvision/file/save_manager.cpp b/engines/zvision/file/save_manager.cpp index 63b54269de..d169679e28 100644 --- a/engines/zvision/file/save_manager.cpp +++ b/engines/zvision/file/save_manager.cpp @@ -205,7 +205,7 @@ bool SaveManager::readSaveGameHeader(Common::InSaveFile *in, SaveGameHeader &hea return true; } if (tag != SAVEGAME_ID) { - warning("File is not a ZVision save file. Aborting load"); + warning("File is not a Z-Vision save file. Aborting load"); return false; } diff --git a/engines/zvision/graphics/render_manager.cpp b/engines/zvision/graphics/render_manager.cpp index ce0a02a1ad..f978ef7844 100644 --- a/engines/zvision/graphics/render_manager.cpp +++ b/engines/zvision/graphics/render_manager.cpp @@ -196,7 +196,7 @@ void RenderManager::readImageToSurface(const Common::String &fileName, Graphics: uint32 imageHeight; Image::TGADecoder tga; uint16 *buffer; - // All ZVision images are in RGB 555 + // All Z-Vision images are in RGB 555 destination.format = _engine->_resourcePixelFormat; bool isTGZ; diff --git a/engines/zvision/scripting/actions.cpp b/engines/zvision/scripting/actions.cpp index 9a8b734e0c..e1380b0eb2 100644 --- a/engines/zvision/scripting/actions.cpp +++ b/engines/zvision/scripting/actions.cpp @@ -47,15 +47,18 @@ namespace ZVision { -ResultAction::ResultAction(ZVision *engine, int32 slotkey) : _engine(engine), _slotKey(slotkey), _scriptManager(engine->getScriptManager()) { +ResultAction::ResultAction(ZVision *engine, int32 slotKey) : + _engine(engine), + _slotKey(slotKey), + _scriptManager(engine->getScriptManager()) { } ////////////////////////////////////////////////////////////////////////////// // ActionAdd ////////////////////////////////////////////////////////////////////////////// -ActionAdd::ActionAdd(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionAdd::ActionAdd(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { _key = 0; _value = 0; @@ -71,8 +74,8 @@ bool ActionAdd::execute() { // ActionAssign ////////////////////////////////////////////////////////////////////////////// -ActionAssign::ActionAssign(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionAssign::ActionAssign(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { _key = 0; char buf[64]; @@ -94,8 +97,8 @@ bool ActionAssign::execute() { // ActionAttenuate ////////////////////////////////////////////////////////////////////////////// -ActionAttenuate::ActionAttenuate(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionAttenuate::ActionAttenuate(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { _key = 0; _attenuation = 0; @@ -115,8 +118,8 @@ bool ActionAttenuate::execute() { // ActionChangeLocation ////////////////////////////////////////////////////////////////////////////// -ActionChangeLocation::ActionChangeLocation(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionChangeLocation::ActionChangeLocation(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { _world = 'g'; _room = 'a'; _node = 'r'; @@ -137,8 +140,8 @@ bool ActionChangeLocation::execute() { // ActionCrossfade ////////////////////////////////////////////////////////////////////////////// -ActionCrossfade::ActionCrossfade(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionCrossfade::ActionCrossfade(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { _keyOne = 0; _keyTwo = 0; _oneStartVolume = 0; @@ -181,8 +184,8 @@ bool ActionCrossfade::execute() { // ActionCursor ////////////////////////////////////////////////////////////////////////////// -ActionCursor::ActionCursor(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionCursor::ActionCursor(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { Common::String up = line; up.toUppercase(); _action = 0; @@ -213,8 +216,8 @@ bool ActionCursor::execute() { // ActionDelayRender ////////////////////////////////////////////////////////////////////////////// -ActionDelayRender::ActionDelayRender(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionDelayRender::ActionDelayRender(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { _framesToDelay = 0; sscanf(line.c_str(), "%u", &_framesToDelay); // Limit to 10 frames maximum. This fixes the script bug in ZGI scene px10 @@ -231,8 +234,8 @@ bool ActionDelayRender::execute() { // ActionDisableControl ////////////////////////////////////////////////////////////////////////////// -ActionDisableControl::ActionDisableControl(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionDisableControl::ActionDisableControl(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { _key = 0; sscanf(line.c_str(), "%u", &_key); @@ -247,8 +250,8 @@ bool ActionDisableControl::execute() { // ActionDisplayMessage ////////////////////////////////////////////////////////////////////////////// -ActionDisplayMessage::ActionDisplayMessage(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionDisplayMessage::ActionDisplayMessage(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { _control = 0; _msgid = 0; @@ -282,8 +285,8 @@ bool ActionDissolve::execute() { // ActionDistort - only used by Zork: Nemesis for the "treatment" puzzle in the Sanitarium (aj30) ////////////////////////////////////////////////////////////////////////////// -ActionDistort::ActionDistort(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionDistort::ActionDistort(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { _distSlot = 0; _speed = 0; _startAngle = 60.0; @@ -311,8 +314,8 @@ bool ActionDistort::execute() { // ActionEnableControl ////////////////////////////////////////////////////////////////////////////// -ActionEnableControl::ActionEnableControl(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionEnableControl::ActionEnableControl(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { _key = 0; sscanf(line.c_str(), "%u", &_key); @@ -327,8 +330,8 @@ bool ActionEnableControl::execute() { // ActionFlushMouseEvents ////////////////////////////////////////////////////////////////////////////// -ActionFlushMouseEvents::ActionFlushMouseEvents(ZVision *engine, int32 slotkey) : - ResultAction(engine, slotkey) { +ActionFlushMouseEvents::ActionFlushMouseEvents(ZVision *engine, int32 slotKey) : + ResultAction(engine, slotKey) { } bool ActionFlushMouseEvents::execute() { @@ -341,8 +344,8 @@ bool ActionFlushMouseEvents::execute() { // ActionInventory ////////////////////////////////////////////////////////////////////////////// -ActionInventory::ActionInventory(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionInventory::ActionInventory(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { _type = -1; _key = 0; @@ -393,8 +396,8 @@ bool ActionInventory::execute() { // ActionKill - only used by ZGI ////////////////////////////////////////////////////////////////////////////// -ActionKill::ActionKill(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionKill::ActionKill(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { _key = 0; _type = 0; char keytype[25]; @@ -432,8 +435,8 @@ bool ActionKill::execute() { // ActionMenuBarEnable ////////////////////////////////////////////////////////////////////////////// -ActionMenuBarEnable::ActionMenuBarEnable(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionMenuBarEnable::ActionMenuBarEnable(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { _menus = 0xFFFF; sscanf(line.c_str(), "%hu", &_menus); @@ -448,8 +451,8 @@ bool ActionMenuBarEnable::execute() { // ActionMusic ////////////////////////////////////////////////////////////////////////////// -ActionMusic::ActionMusic(ZVision *engine, int32 slotkey, const Common::String &line, bool global) : - ResultAction(engine, slotkey), +ActionMusic::ActionMusic(ZVision *engine, int32 slotKey, const Common::String &line, bool global) : + ResultAction(engine, slotKey), _note(0), _prog(0), _universe(global) { @@ -527,8 +530,8 @@ bool ActionMusic::execute() { // ActionPanTrack ////////////////////////////////////////////////////////////////////////////// -ActionPanTrack::ActionPanTrack(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey), +ActionPanTrack::ActionPanTrack(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey), _pos(0), _musicSlot(0) { @@ -552,8 +555,8 @@ bool ActionPanTrack::execute() { // ActionPreferences ////////////////////////////////////////////////////////////////////////////// -ActionPreferences::ActionPreferences(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionPreferences::ActionPreferences(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { if (line.compareToIgnoreCase("save") == 0) _save = true; else @@ -573,8 +576,8 @@ bool ActionPreferences::execute() { // ActionPreloadAnimation ////////////////////////////////////////////////////////////////////////////// -ActionPreloadAnimation::ActionPreloadAnimation(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionPreloadAnimation::ActionPreloadAnimation(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { _mask = 0; _framerate = 0; @@ -612,8 +615,8 @@ bool ActionPreloadAnimation::execute() { // ActionUnloadAnimation ////////////////////////////////////////////////////////////////////////////// -ActionUnloadAnimation::ActionUnloadAnimation(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionUnloadAnimation::ActionUnloadAnimation(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { _key = 0; sscanf(line.c_str(), "%u", &_key); @@ -632,8 +635,8 @@ bool ActionUnloadAnimation::execute() { // ActionPlayAnimation ////////////////////////////////////////////////////////////////////////////// -ActionPlayAnimation::ActionPlayAnimation(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionPlayAnimation::ActionPlayAnimation(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { _x = 0; _y = 0; _x2 = 0; @@ -690,8 +693,8 @@ bool ActionPlayAnimation::execute() { // ActionPlayPreloadAnimation ////////////////////////////////////////////////////////////////////////////// -ActionPlayPreloadAnimation::ActionPlayPreloadAnimation(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionPlayPreloadAnimation::ActionPlayPreloadAnimation(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { _controlKey = 0; _x1 = 0; _y1 = 0; @@ -729,8 +732,8 @@ bool ActionQuit::execute() { // ActionRegion - only used by Zork: Nemesis ////////////////////////////////////////////////////////////////////////////// -ActionRegion::ActionRegion(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionRegion::ActionRegion(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { _delay = 0; _type = 0; _unk1 = 0; @@ -808,8 +811,8 @@ bool ActionRegion::execute() { // ActionRandom ////////////////////////////////////////////////////////////////////////////// -ActionRandom::ActionRandom(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionRandom::ActionRandom(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { char maxBuffer[64]; memset(maxBuffer, 0, 64); sscanf(line.c_str(), "%s", maxBuffer); @@ -830,8 +833,8 @@ bool ActionRandom::execute() { // ActionRestoreGame ////////////////////////////////////////////////////////////////////////////// -ActionRestoreGame::ActionRestoreGame(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionRestoreGame::ActionRestoreGame(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { char buf[128]; sscanf(line.c_str(), "%s", buf); _fileName = Common::String(buf); @@ -846,8 +849,8 @@ bool ActionRestoreGame::execute() { // ActionRotateTo ////////////////////////////////////////////////////////////////////////////// -ActionRotateTo::ActionRotateTo(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionRotateTo::ActionRotateTo(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { _time = 0; _toPos = 0; @@ -864,8 +867,8 @@ bool ActionRotateTo::execute() { // ActionSetPartialScreen ////////////////////////////////////////////////////////////////////////////// -ActionSetPartialScreen::ActionSetPartialScreen(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionSetPartialScreen::ActionSetPartialScreen(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { _x = 0; _y = 0; @@ -904,8 +907,8 @@ bool ActionSetPartialScreen::execute() { // ActionSetScreen ////////////////////////////////////////////////////////////////////////////// -ActionSetScreen::ActionSetScreen(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionSetScreen::ActionSetScreen(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { char fileName[25]; sscanf(line.c_str(), "%24s", fileName); @@ -922,8 +925,8 @@ bool ActionSetScreen::execute() { // ActionStop ////////////////////////////////////////////////////////////////////////////// -ActionStop::ActionStop(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionStop::ActionStop(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { _key = 0; sscanf(line.c_str(), "%u", &_key); } @@ -937,8 +940,8 @@ bool ActionStop::execute() { // ActionStreamVideo ////////////////////////////////////////////////////////////////////////////// -ActionStreamVideo::ActionStreamVideo(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionStreamVideo::ActionStreamVideo(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { _x1 = 0; _x2 = 0; _y1 = 0; @@ -1019,8 +1022,8 @@ bool ActionStreamVideo::execute() { // ActionSyncSound ////////////////////////////////////////////////////////////////////////////// -ActionSyncSound::ActionSyncSound(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionSyncSound::ActionSyncSound(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { _syncto = 0; char fileName[25]; @@ -1047,8 +1050,8 @@ bool ActionSyncSound::execute() { // ActionTimer ////////////////////////////////////////////////////////////////////////////// -ActionTimer::ActionTimer(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionTimer::ActionTimer(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { char timeBuffer[64]; memset(timeBuffer, 0, 64); sscanf(line.c_str(), "%s", timeBuffer); @@ -1071,8 +1074,8 @@ bool ActionTimer::execute() { // ActionTtyText ////////////////////////////////////////////////////////////////////////////// -ActionTtyText::ActionTtyText(ZVision *engine, int32 slotkey, const Common::String &line) : - ResultAction(engine, slotkey) { +ActionTtyText::ActionTtyText(ZVision *engine, int32 slotKey, const Common::String &line) : + ResultAction(engine, slotKey) { _delay = 0; char filename[64]; diff --git a/engines/zvision/scripting/actions.h b/engines/zvision/scripting/actions.h index ff19fc54fc..bde1baa291 100644 --- a/engines/zvision/scripting/actions.h +++ b/engines/zvision/scripting/actions.h @@ -269,7 +269,6 @@ public: bool execute(); private: - uint32 _animationKey; uint32 _controlKey; uint32 _x1; uint32 _y1; diff --git a/engines/zvision/scripting/controls/input_control.cpp b/engines/zvision/scripting/controls/input_control.cpp index df0c77ba96..9525333ef0 100644 --- a/engines/zvision/scripting/controls/input_control.cpp +++ b/engines/zvision/scripting/controls/input_control.cpp @@ -43,7 +43,6 @@ InputControl::InputControl(ZVision *engine, uint32 key, Common::SeekableReadStre _nextTabstop(0), _focused(false), _textChanged(false), - _cursorOffset(0), _enterPressed(false), _readOnly(false), _txtWidth(0), diff --git a/engines/zvision/scripting/controls/input_control.h b/engines/zvision/scripting/controls/input_control.h index 9b48514e16..6abdb3c692 100644 --- a/engines/zvision/scripting/controls/input_control.h +++ b/engines/zvision/scripting/controls/input_control.h @@ -51,15 +51,12 @@ private: Common::String _currentInputText; bool _textChanged; - uint _cursorOffset; bool _enterPressed; bool _readOnly; int16 _txtWidth; int16 _maxTxtWidth; Video::VideoDecoder *_animation; - int32 _frameDelay; - int16 _frame; public: void focus() { diff --git a/engines/zvision/scripting/menu.cpp b/engines/zvision/scripting/menu.cpp index e7775cbe3f..064bd1b57d 100644 --- a/engines/zvision/scripting/menu.cpp +++ b/engines/zvision/scripting/menu.cpp @@ -46,7 +46,7 @@ MenuHandler::MenuHandler(ZVision *engine) { MenuZGI::MenuZGI(ZVision *engine) : MenuHandler(engine) { menuMouseFocus = -1; - inmenu = false; + inMenu = false; scrolled[0] = false; scrolled[1] = false; scrolled[2] = false; @@ -60,15 +60,15 @@ MenuZGI::MenuZGI(ZVision *engine) : char buf[24]; for (int i = 1; i < 4; i++) { sprintf(buf, "gmzau%2.2x1.tga", i); - _engine->getRenderManager()->readImageToSurface(buf, menuback[i - 1][0], false); + _engine->getRenderManager()->readImageToSurface(buf, menuBack[i - 1][0], false); sprintf(buf, "gmzau%2.2x1.tga", i + 0x10); - _engine->getRenderManager()->readImageToSurface(buf, menuback[i - 1][1], false); + _engine->getRenderManager()->readImageToSurface(buf, menuBack[i - 1][1], false); } for (int i = 0; i < 4; i++) { sprintf(buf, "gmzmu%2.2x1.tga", i); - _engine->getRenderManager()->readImageToSurface(buf, menubar[i][0], false); + _engine->getRenderManager()->readImageToSurface(buf, menuBar[i][0], false); sprintf(buf, "gmznu%2.2x1.tga", i); - _engine->getRenderManager()->readImageToSurface(buf, menubar[i][1], false); + _engine->getRenderManager()->readImageToSurface(buf, menuBar[i][1], false); } for (int i = 0; i < 50; i++) { @@ -86,12 +86,12 @@ MenuZGI::MenuZGI(ZVision *engine) : MenuZGI::~MenuZGI() { for (int i = 0; i < 3; i++) { - menuback[i][0].free(); - menuback[i][1].free(); + menuBack[i][0].free(); + menuBack[i][1].free(); } for (int i = 0; i < 4; i++) { - menubar[i][0].free(); - menubar[i][1].free(); + menuBar[i][0].free(); + menuBar[i][1].free(); } for (int i = 0; i < 50; i++) { if (items[i][0]) { @@ -208,9 +208,9 @@ void MenuZGI::onMouseUp(const Common::Point &Pos) { void MenuZGI::onMouseMove(const Common::Point &Pos) { if (Pos.y < 40) { - if (!inmenu) + if (!inMenu) redraw = true; - inmenu = true; + inMenu = true; switch (menuMouseFocus) { case kMenuItem: if (menuBarFlag & kMenubarItems) { @@ -311,7 +311,7 @@ void MenuZGI::onMouseMove(const Common::Point &Pos) { if (Common::Rect(64, 0, 64 + 512, 8).contains(Pos)) { // Main menuMouseFocus = kMenuMain; scrolled[kMenuMain] = false; - scrollPos[kMenuMain] = menuback[kMenuMain][1].h - menuback[kMenuMain][0].h; + scrollPos[kMenuMain] = menuBack[kMenuMain][1].h - menuBack[kMenuMain][0].h; _engine->getScriptManager()->setStateValue(StateKey_MenuState, 2); } @@ -337,9 +337,9 @@ void MenuZGI::onMouseMove(const Common::Point &Pos) { break; } } else { - if (inmenu) + if (inMenu) clean = true; - inmenu = false; + inMenu = false; if (_engine->getScriptManager()->getStateValue(StateKey_MenuState) != 0) _engine->getScriptManager()->setStateValue(StateKey_MenuState, 0); menuMouseFocus = -1; @@ -369,7 +369,7 @@ void MenuZGI::process(uint32 deltatime) { } } if (redraw) { - _engine->getRenderManager()->blitSurfaceToMenu(menuback[kMenuItem][0], scrollPos[kMenuItem], 0); + _engine->getRenderManager()->blitSurfaceToMenu(menuBack[kMenuItem][0], scrollPos[kMenuItem], 0); int itemCount = _engine->getScriptManager()->getStateValue(StateKey_Inv_TotalSlots); if (itemCount == 0) @@ -438,7 +438,7 @@ void MenuZGI::process(uint32 deltatime) { } } if (redraw) { - _engine->getRenderManager()->blitSurfaceToMenu(menuback[kMenuMagic][0], 640 - scrollPos[kMenuMagic], 0); + _engine->getRenderManager()->blitSurfaceToMenu(menuBack[kMenuMagic][0], 640 - scrollPos[kMenuMagic], 0); for (int i = 0; i < 12; i++) { bool inrect = false; @@ -503,45 +503,45 @@ void MenuZGI::process(uint32 deltatime) { } } if (redraw) { - _engine->getRenderManager()->blitSurfaceToMenu(menuback[kMenuMain][0], 30, scrollPos[kMenuMain]); + _engine->getRenderManager()->blitSurfaceToMenu(menuBack[kMenuMain][0], 30, scrollPos[kMenuMain]); if (menuBarFlag & kMenubarExit) { if (mouseOnItem == kMainMenuExit) - _engine->getRenderManager()->blitSurfaceToMenu(menubar[kMainMenuExit][1], 320 + 135, scrollPos[kMenuMain]); + _engine->getRenderManager()->blitSurfaceToMenu(menuBar[kMainMenuExit][1], 320 + 135, scrollPos[kMenuMain]); else - _engine->getRenderManager()->blitSurfaceToMenu(menubar[kMainMenuExit][0], 320 + 135, scrollPos[kMenuMain]); + _engine->getRenderManager()->blitSurfaceToMenu(menuBar[kMainMenuExit][0], 320 + 135, scrollPos[kMenuMain]); } if (menuBarFlag & kMenubarSettings) { if (mouseOnItem == kMainMenuPrefs) - _engine->getRenderManager()->blitSurfaceToMenu(menubar[kMainMenuPrefs][1], 320, scrollPos[kMenuMain]); + _engine->getRenderManager()->blitSurfaceToMenu(menuBar[kMainMenuPrefs][1], 320, scrollPos[kMenuMain]); else - _engine->getRenderManager()->blitSurfaceToMenu(menubar[kMainMenuPrefs][0], 320, scrollPos[kMenuMain]); + _engine->getRenderManager()->blitSurfaceToMenu(menuBar[kMainMenuPrefs][0], 320, scrollPos[kMenuMain]); } if (menuBarFlag & kMenubarRestore) { if (mouseOnItem == kMainMenuLoad) - _engine->getRenderManager()->blitSurfaceToMenu(menubar[kMainMenuLoad][1], 320 - 135, scrollPos[kMenuMain]); + _engine->getRenderManager()->blitSurfaceToMenu(menuBar[kMainMenuLoad][1], 320 - 135, scrollPos[kMenuMain]); else - _engine->getRenderManager()->blitSurfaceToMenu(menubar[kMainMenuLoad][0], 320 - 135, scrollPos[kMenuMain]); + _engine->getRenderManager()->blitSurfaceToMenu(menuBar[kMainMenuLoad][0], 320 - 135, scrollPos[kMenuMain]); } if (menuBarFlag & kMenubarSave) { if (mouseOnItem == kMainMenuSave) - _engine->getRenderManager()->blitSurfaceToMenu(menubar[kMainMenuSave][1], 320 - 135 * 2, scrollPos[kMenuMain]); + _engine->getRenderManager()->blitSurfaceToMenu(menuBar[kMainMenuSave][1], 320 - 135 * 2, scrollPos[kMenuMain]); else - _engine->getRenderManager()->blitSurfaceToMenu(menubar[kMainMenuSave][0], 320 - 135 * 2, scrollPos[kMenuMain]); + _engine->getRenderManager()->blitSurfaceToMenu(menuBar[kMainMenuSave][0], 320 - 135 * 2, scrollPos[kMenuMain]); } redraw = false; } break; default: if (redraw) { - if (inmenu) { - _engine->getRenderManager()->blitSurfaceToMenu(menuback[kMenuMain][1], 30, 0); + if (inMenu) { + _engine->getRenderManager()->blitSurfaceToMenu(menuBack[kMenuMain][1], 30, 0); if (menuBarFlag & kMenubarItems) - _engine->getRenderManager()->blitSurfaceToMenu(menuback[kMenuItem][1], 0, 0); + _engine->getRenderManager()->blitSurfaceToMenu(menuBack[kMenuItem][1], 0, 0); if (menuBarFlag & kMenubarMagic) - _engine->getRenderManager()->blitSurfaceToMenu(menuback[kMenuMagic][1], 640 - 28, 0); + _engine->getRenderManager()->blitSurfaceToMenu(menuBack[kMenuMagic][1], 640 - 28, 0); } redraw = false; } @@ -551,7 +551,7 @@ void MenuZGI::process(uint32 deltatime) { MenuNemesis::MenuNemesis(ZVision *engine) : MenuHandler(engine) { - inmenu = false; + inMenu = false; scrolled = false; scrollPos = 0; mouseOnItem = -1; @@ -565,7 +565,7 @@ MenuNemesis::MenuNemesis(ZVision *engine) : _engine->getRenderManager()->readImageToSurface(buf, but[i][j], false); } - _engine->getRenderManager()->readImageToSurface("bar.tga", menubar, false); + _engine->getRenderManager()->readImageToSurface("bar.tga", menuBar, false); frm = 0; } @@ -575,7 +575,7 @@ MenuNemesis::~MenuNemesis() { for (int j = 0; j < 6; j++) but[i][j].free(); - menubar.free(); + menuBar.free(); } static const int16 buts[4][2] = { {120 , 64}, {144, 184}, {128, 328}, {120, 456} }; @@ -631,7 +631,7 @@ void MenuNemesis::onMouseUp(const Common::Point &Pos) { void MenuNemesis::onMouseMove(const Common::Point &Pos) { if (Pos.y < 40) { - inmenu = true; + inMenu = true; if (_engine->getScriptManager()->getStateValue(StateKey_MenuState) != 2) _engine->getScriptManager()->setStateValue(StateKey_MenuState, 2); @@ -681,7 +681,7 @@ void MenuNemesis::onMouseMove(const Common::Point &Pos) { delay = 200; } } else { - inmenu = false; + inMenu = false; if (_engine->getScriptManager()->getStateValue(StateKey_MenuState) != 0) _engine->getScriptManager()->setStateValue(StateKey_MenuState, 0); mouseOnItem = -1; @@ -689,7 +689,7 @@ void MenuNemesis::onMouseMove(const Common::Point &Pos) { } void MenuNemesis::process(uint32 deltatime) { - if (inmenu) { + if (inMenu) { if (!scrolled) { float scrl = 32.0 * 2.0 * (deltatime / 1000.0); @@ -715,7 +715,7 @@ void MenuNemesis::process(uint32 deltatime) { } if (redraw) { - _engine->getRenderManager()->blitSurfaceToMenu(menubar, 64, scrollPos); + _engine->getRenderManager()->blitSurfaceToMenu(menuBar, 64, scrollPos); if (menuBarFlag & kMenubarExit) if (mouseOnItem == kMainMenuExit) @@ -752,7 +752,7 @@ void MenuNemesis::process(uint32 deltatime) { scrollPos = -32; if (redraw) { - _engine->getRenderManager()->blitSurfaceToMenu(menubar, 64, scrollPos); + _engine->getRenderManager()->blitSurfaceToMenu(menuBar, 64, scrollPos); redraw = false; } } diff --git a/engines/zvision/scripting/menu.h b/engines/zvision/scripting/menu.h index a88587966f..f6b21b9c97 100644 --- a/engines/zvision/scripting/menu.h +++ b/engines/zvision/scripting/menu.h @@ -68,8 +68,8 @@ public: void onMouseUp(const Common::Point &Pos); void process(uint32 deltaTimeInMillis); private: - Graphics::Surface menuback[3][2]; - Graphics::Surface menubar[4][2]; + Graphics::Surface menuBack[3][2]; + Graphics::Surface menuBar[4][2]; Graphics::Surface *items[50][2]; uint itemId[50]; @@ -77,11 +77,11 @@ private: uint magicId[12]; int menuMouseFocus; - bool inmenu; + bool inMenu; int mouseOnItem; - bool scrolled[3]; + bool scrolled[3]; int16 scrollPos[3]; bool clean; @@ -98,13 +98,13 @@ public: void process(uint32 deltaTimeInMillis); private: Graphics::Surface but[4][6]; - Graphics::Surface menubar; + Graphics::Surface menuBar; - bool inmenu; + bool inMenu; int mouseOnItem; - bool scrolled; + bool scrolled; int16 scrollPos; bool redraw; @@ -114,6 +114,6 @@ private: }; -} +} // End of namespace ZVision #endif diff --git a/engines/zvision/sound/zork_raw.cpp b/engines/zvision/sound/zork_raw.cpp index 7bdd4875fc..124235e0e0 100644 --- a/engines/zvision/sound/zork_raw.cpp +++ b/engines/zvision/sound/zork_raw.cpp @@ -33,7 +33,6 @@ #include "zvision/sound/zork_raw.h" #include "zvision/zvision.h" -#include "zvision/detection.h" namespace ZVision { @@ -136,7 +135,8 @@ int RawChunkStream::readBuffer(int16 *buffer, Common::SeekableReadStream *stream return bytesRead; } -const SoundParams RawZorkStream::_zNemSoundParamLookupTable[32] = {{'0', 0x1F40, false, false, false}, +const SoundParams RawZorkStream::_zNemSoundParamLookupTable[32] = { + {'0', 0x1F40, false, false, false}, {'1', 0x1F40, true, false, false}, {'2', 0x1F40, false, false, true}, {'3', 0x1F40, true, false, true}, @@ -170,7 +170,8 @@ const SoundParams RawZorkStream::_zNemSoundParamLookupTable[32] = {{'0', 0x1F40, {'x', 0xAC44, true, true, true} }; -const SoundParams RawZorkStream::_zgiSoundParamLookupTable[24] = {{'4', 0x2B11, false, false, false}, +const SoundParams RawZorkStream::_zgiSoundParamLookupTable[24] = { + {'4', 0x2B11, false, false, false}, {'5', 0x2B11, true, false, false}, {'6', 0x2B11, false, false, true}, {'7', 0x2B11, true, false, true}, diff --git a/engines/zvision/text/string_manager.h b/engines/zvision/text/string_manager.h index f4564ee1ec..2c31cf7afe 100644 --- a/engines/zvision/text/string_manager.h +++ b/engines/zvision/text/string_manager.h @@ -23,7 +23,6 @@ #ifndef ZVISION_STRING_MANAGER_H #define ZVISION_STRING_MANAGER_H -#include "zvision/detection.h" #include "zvision/text/truetype_font.h" namespace Graphics { diff --git a/engines/zvision/text/text.h b/engines/zvision/text/text.h index d35b90499d..5dd872a440 100644 --- a/engines/zvision/text/text.h +++ b/engines/zvision/text/text.h @@ -24,7 +24,6 @@ #ifndef ZVISION_TEXT_H #define ZVISION_TEXT_H -#include "zvision/detection.h" #include "zvision/text/truetype_font.h" #include "zvision/zvision.h" @@ -59,7 +58,7 @@ public: public: Common::String _fontname; - TextJustification _justification; // 0 - center, 1-left, 2-right + TextJustification _justification; int16 _size; uint8 _red; // 0-255 uint8 _green; // 0-255 diff --git a/engines/zvision/zvision.cpp b/engines/zvision/zvision.cpp index da80ff9d02..779fdc4464 100644 --- a/engines/zvision/zvision.cpp +++ b/engines/zvision/zvision.cpp @@ -29,7 +29,6 @@ #include "zvision/graphics/cursors/cursor_manager.h" #include "zvision/file/save_manager.h" #include "zvision/text/string_manager.h" -#include "zvision/detection.h" #include "zvision/scripting/menu.h" #include "zvision/file/search_manager.h" #include "zvision/text/text.h" @@ -184,10 +183,10 @@ void ZVision::initialize() { _searchManager->addDir("FONTS"); _searchManager->addDir("addon"); - if (_gameDescription->gameId == GID_GRANDINQUISITOR) { + if (getGameId() == GID_GRANDINQUISITOR) { if (!_searchManager->loadZix("INQUIS.ZIX")) error("Unable to load file INQUIS.ZIX"); - } else if (_gameDescription->gameId == GID_NEMESIS) { + } else if (getGameId() == GID_NEMESIS) { if (!_searchManager->loadZix("NEMESIS.ZIX")) { // The game might not be installed, try MEDIUM.ZIX instead if (!_searchManager->loadZix("ZNEMSCR/MEDIUM.ZIX")) @@ -209,7 +208,7 @@ void ZVision::initialize() { _textRenderer = new TextRenderer(this); _midiManager = new MidiManager(); - if (_gameDescription->gameId == GID_GRANDINQUISITOR) + if (getGameId() == GID_GRANDINQUISITOR) _menu = new MenuZGI(this); else _menu = new MenuNemesis(this); @@ -217,7 +216,7 @@ void ZVision::initialize() { // Initialize the managers _cursorManager->initialize(); _scriptManager->initialize(); - _stringManager->initialize(_gameDescription->gameId); + _stringManager->initialize(getGameId()); registerDefaultSettings(); @@ -396,8 +395,8 @@ void ZVision::fpsTimer() { } void ZVision::initScreen() { - uint16 workingWindowWidth = (_gameDescription->gameId == GID_NEMESIS) ? ZNM_WORKING_WINDOW_WIDTH : ZGI_WORKING_WINDOW_WIDTH; - uint16 workingWindowHeight = (_gameDescription->gameId == GID_NEMESIS) ? ZNM_WORKING_WINDOW_HEIGHT : ZGI_WORKING_WINDOW_HEIGHT; + uint16 workingWindowWidth = (getGameId() == GID_NEMESIS) ? ZNM_WORKING_WINDOW_WIDTH : ZGI_WORKING_WINDOW_WIDTH; + uint16 workingWindowHeight = (getGameId() == GID_NEMESIS) ? ZNM_WORKING_WINDOW_HEIGHT : ZGI_WORKING_WINDOW_HEIGHT; _workingWindow = Common::Rect( (WINDOW_WIDTH - workingWindowWidth) / 2, (WINDOW_HEIGHT - workingWindowHeight) / 2, diff --git a/engines/zvision/zvision.h b/engines/zvision/zvision.h index 854cd77bb8..4c948faaa4 100644 --- a/engines/zvision/zvision.h +++ b/engines/zvision/zvision.h @@ -24,7 +24,6 @@ #ifndef ZVISION_ZVISION_H #define ZVISION_ZVISION_H -#include "zvision/detection.h" #include "zvision/core/clock.h" #include "zvision/file/search_manager.h" @@ -51,7 +50,6 @@ class VideoDecoder; * - Zork: Grand Inquisitor * */ - namespace ZVision { struct ZVisionGameDescription; @@ -74,12 +72,12 @@ enum { HIRES_WINDOW_WIDTH = 800, HIRES_WINDOW_HEIGHT = 600, - // Zork nemesis working window sizes - ZNM_WORKING_WINDOW_WIDTH = 512, + // Zork Nemesis working window sizes + ZNM_WORKING_WINDOW_WIDTH = 512, ZNM_WORKING_WINDOW_HEIGHT = 320, // ZGI working window sizes - ZGI_WORKING_WINDOW_WIDTH = 640, + ZGI_WORKING_WINDOW_WIDTH = 640, ZGI_WORKING_WINDOW_HEIGHT = 344, ROTATION_SCREEN_EDGE_OFFSET = 60, @@ -88,6 +86,12 @@ enum { KEYBUF_SIZE = 20 }; +enum ZVisionGameId { + GID_NONE = 0, + GID_NEMESIS = 1, + GID_GRANDINQUISITOR = 2 +}; + class ZVision : public Engine { public: ZVision(OSystem *syst, const ZVisionGameDescription *gameDesc); @@ -116,12 +120,12 @@ private: ScriptManager *_scriptManager; RenderManager *_renderManager; CursorManager *_cursorManager; - SaveManager *_saveManager; StringManager *_stringManager; - MenuHandler *_menu; SearchManager *_searchManager; TextRenderer *_textRenderer; MidiManager *_midiManager; + SaveManager *_saveManager; + MenuHandler *_menu; // Clock Clock _clock; @@ -141,12 +145,15 @@ private: bool _videoIsPlaying; uint8 _cheatBuffer[KEYBUF_SIZE]; + public: - uint32 getFeatures() const; - Common::Language getLanguage() const; Common::Error run(); void pauseEngineIntern(bool pause); + ZVisionGameId getGameId() const; + Common::Language getLanguage() const; + uint32 getFeatures() const; + ScriptManager *getScriptManager() const { return _scriptManager; } @@ -174,12 +181,10 @@ public: MenuHandler *getMenuHandler() const { return _menu; } + Common::RandomSource *getRandomSource() const { return _rnd; } - ZVisionGameId getGameId() const { - return _gameDescription->gameId; - } int16 getKeyboardVelocity() const { return _keyboardVelocity; } @@ -236,6 +241,7 @@ public: bool canSaveGameStateCurrently(); Common::Error loadGameState(int slot); Common::Error saveGameState(int slot, const Common::String &desc); + private: void initialize(); void initFonts(); |