diff options
author | Paul Gilbert | 2015-05-30 10:46:19 -0400 |
---|---|---|
committer | Paul Gilbert | 2015-05-30 10:46:19 -0400 |
commit | 6b95fc6b1132cf5ce6edc4216368bd38eddc939b (patch) | |
tree | 45f0d9fa5e65ae9f42fefb301498c2dd704bf772 /engines | |
parent | c3712f9a73eb7511c2a5bea20aa61ea21d3258f4 (diff) | |
parent | 895189e3ffd1931ca01991731f98bbf74796e05d (diff) | |
download | scummvm-rg350-6b95fc6b1132cf5ce6edc4216368bd38eddc939b.tar.gz scummvm-rg350-6b95fc6b1132cf5ce6edc4216368bd38eddc939b.tar.bz2 scummvm-rg350-6b95fc6b1132cf5ce6edc4216368bd38eddc939b.zip |
Merge branch 'sherlock2'
Diffstat (limited to 'engines')
43 files changed, 6372 insertions, 3701 deletions
diff --git a/engines/sherlock/decompress.cpp b/engines/sherlock/decompress.cpp new file mode 100644 index 0000000000..8e02da3212 --- /dev/null +++ b/engines/sherlock/decompress.cpp @@ -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 "sherlock/decompress.h" + +namespace Sherlock { + +/** + * Decompresses an LZW compressed resource. If no outSize is specified, it will + * decompress the entire resource. If, however, an explicit size is specified, + * then it means we're already within a resource, and only want to decompress + * part of it. + */ +Common::SeekableReadStream *decompressLZ(Common::SeekableReadStream &source, int32 outSize) { + if (outSize == -1) { + source.seek(5); + outSize = source.readSint32LE(); + } + + byte lzWindow[4096]; + uint16 lzWindowPos; + uint16 cmd; + + byte *outBuffer = new byte[outSize]; + byte *outBufferEnd = outBuffer + outSize; + Common::MemoryReadStream *outS = new Common::MemoryReadStream(outBuffer, outSize, DisposeAfterUse::YES); + + memset(lzWindow, 0xFF, 0xFEE); + lzWindowPos = 0xFEE; + cmd = 0; + + do { + cmd >>= 1; + if (!(cmd & 0x100)) + cmd = source.readByte() | 0xFF00; + + if (cmd & 1) { + byte literal = source.readByte(); + *outBuffer++ = literal; + lzWindow[lzWindowPos] = literal; + lzWindowPos = (lzWindowPos + 1) & 0x0FFF; + } else { + int copyPos, copyLen; + copyPos = source.readByte(); + copyLen = source.readByte(); + copyPos = copyPos | ((copyLen & 0xF0) << 4); + copyLen = (copyLen & 0x0F) + 3; + while (copyLen--) { + byte literal = lzWindow[copyPos]; + copyPos = (copyPos + 1) & 0x0FFF; + *outBuffer++ = literal; + lzWindow[lzWindowPos] = literal; + lzWindowPos = (lzWindowPos + 1) & 0x0FFF; + } + } + } while (outBuffer < outBufferEnd); + + return outS; +} + + +/** + * Decompresses a Rose Tattoo resource + * +Common::SeekableReadStream *decompress32(Common::SeekableReadStream &source, int32 outSize) { + if (outSize == -1) { + outSize = source.readSint32LE(); + } + + byte lzWindow[8192]; + byte *outBuffer = new byte[outSize]; + byte *outBufferEnd = outBuffer + outSize; + Common::MemoryReadStream *outS = new Common::MemoryReadStream(outBuffer, outSize, DisposeAfterUse::YES); + + memset(lzWindow, 0xFF, 8192); + int lzWindowPos = 0xFEE; + int cmd = 0; + + do { + cmd >>= 1; + if (!(cmd & 0x100)) + cmd = source.readByte() | 0xFF00; + + if (cmd & 1) { + byte literal = source.readByte(); + *outBuffer++ = literal; + lzWindow[lzWindowPos] = literal; + lzWindowPos = (lzWindowPos + 1) & 0x0FFF; + } else { + int copyPos, copyLen; + copyPos = source.readByte(); + copyLen = source.readByte(); + copyPos = copyPos | ((copyLen & 0xF0) << 4); + copyLen = (copyLen & 0x0F) + 3; + while (copyLen--) { + byte literal = lzWindow[copyPos]; + copyPos = (copyPos + 1) & 0x0FFF; + *outBuffer++ = literal; + lzWindow[lzWindowPos] = literal; + lzWindowPos = (lzWindowPos + 1) & 0x0FFF; + } + } + } while (outBuffer < outBufferEnd); + + return outS; +} +*/ + +} // namespace Sherlock diff --git a/engines/sherlock/events.cpp b/engines/sherlock/events.cpp index 94ddc9a792..a02a06cafc 100644 --- a/engines/sherlock/events.cpp +++ b/engines/sherlock/events.cpp @@ -42,6 +42,8 @@ Events::Events(SherlockEngine *vm) { _pressed = _released = false; _rightPressed = _rightReleased = false; _oldButtons = _oldRightButton = false; + + loadCursors("rmouse.vgs"); } Events::~Events() { @@ -73,6 +75,14 @@ void Events::setCursor(const Graphics::Surface &src) { showCursor(); } +void Events::animateCursorIfNeeded() { + if (_cursorId >= WAIT && _cursorId < (WAIT + 3)) { + CursorId newId = (WAIT + 2) ? WAIT : (CursorId)((int)_cursorId + 1); + setCursor(newId); + } +} + + void Events::showCursor() { CursorMan.showMouse(true); } diff --git a/engines/sherlock/events.h b/engines/sherlock/events.h index c19a92de8c..b35109fefe 100644 --- a/engines/sherlock/events.h +++ b/engines/sherlock/events.h @@ -33,7 +33,7 @@ namespace Sherlock { #define GAME_FRAME_RATE 60 #define GAME_FRAME_TIME (1000 / GAME_FRAME_RATE) -enum CursorId { ARROW = 0, MAGNIFY = 1, WAIT = 2, INVALID_CURSOR = -1 }; +enum CursorId { ARROW = 0, MAGNIFY = 1, WAIT = 2, EXIT_ZONES_START = 5, INVALID_CURSOR = -1 }; class SherlockEngine; @@ -78,6 +78,11 @@ public: void setCursor(const Graphics::Surface &src); /** + * Animates the mouse cursor if the Wait cursor is showing + */ + void animateCursorIfNeeded(); + + /** * Show the mouse cursor */ void showCursor(); diff --git a/engines/sherlock/inventory.cpp b/engines/sherlock/inventory.cpp index 7f216a35c3..a8ecb64102 100644 --- a/engines/sherlock/inventory.cpp +++ b/engines/sherlock/inventory.cpp @@ -22,6 +22,7 @@ #include "sherlock/inventory.h" #include "sherlock/sherlock.h" +#include "sherlock/scalpel/scalpel_user_interface.h" namespace Sherlock { @@ -200,29 +201,29 @@ void Inventory::drawInventory(InvNewMode mode) { INV_BACKGROUND); // Draw the buttons - screen.makeButton(Common::Rect(INVENTORY_POINTS[0][0], CONTROLS_Y1, INVENTORY_POINTS[0][1], - CONTROLS_Y1 + 10), INVENTORY_POINTS[0][2] - screen.stringWidth("Exit") / 2, "Exit"); - screen.makeButton(Common::Rect(INVENTORY_POINTS[1][0], CONTROLS_Y1, INVENTORY_POINTS[1][1], - CONTROLS_Y1 + 10), INVENTORY_POINTS[1][2] - screen.stringWidth("Look") / 2, "Look"); - screen.makeButton(Common::Rect(INVENTORY_POINTS[2][0], CONTROLS_Y1, INVENTORY_POINTS[2][1], - CONTROLS_Y1 + 10), INVENTORY_POINTS[2][2] - screen.stringWidth("Use") / 2, "Use"); - screen.makeButton(Common::Rect(INVENTORY_POINTS[3][0], CONTROLS_Y1, INVENTORY_POINTS[3][1], - CONTROLS_Y1 + 10), INVENTORY_POINTS[3][2] - screen.stringWidth("Give") / 2, "Give"); - screen.makeButton(Common::Rect(INVENTORY_POINTS[4][0], CONTROLS_Y1, INVENTORY_POINTS[4][1], - CONTROLS_Y1 + 10), INVENTORY_POINTS[4][2], "^^"); - screen.makeButton(Common::Rect(INVENTORY_POINTS[5][0], CONTROLS_Y1, INVENTORY_POINTS[5][1], - CONTROLS_Y1 + 10), INVENTORY_POINTS[5][2], "^"); - screen.makeButton(Common::Rect(INVENTORY_POINTS[6][0], CONTROLS_Y1, INVENTORY_POINTS[6][1], - CONTROLS_Y1 + 10), INVENTORY_POINTS[6][2], "_"); - screen.makeButton(Common::Rect(INVENTORY_POINTS[7][0], CONTROLS_Y1, INVENTORY_POINTS[7][1], - CONTROLS_Y1 + 10), INVENTORY_POINTS[7][2], "__"); + 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 = INVENTORY_COMMANDS[(int)mode]; + ui._oldKey = Scalpel::INVENTORY_COMMANDS[(int)mode]; } else { ui._oldKey = -1; } @@ -243,7 +244,8 @@ void Inventory::drawInventory(InvNewMode mode) { screen._backBuffer = &screen._backBuffer1; } - ui._oldUse = -1; + assert(IS_SERRATED_SCALPEL); + ((Scalpel::ScalpelUserInterface *)_vm->_ui)->_oldUse = -1; } void Inventory::invCommands(bool slamIt) { @@ -251,55 +253,55 @@ void Inventory::invCommands(bool slamIt) { UserInterface &ui = *_vm->_ui; if (slamIt) { - screen.buttonPrint(Common::Point(INVENTORY_POINTS[0][2], CONTROLS_Y1), + screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[0][2], CONTROLS_Y1), _invMode == INVMODE_EXIT ? COMMAND_HIGHLIGHTED :COMMAND_FOREGROUND, true, "Exit"); - screen.buttonPrint(Common::Point(INVENTORY_POINTS[1][2], CONTROLS_Y1), + screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[1][2], CONTROLS_Y1), _invMode == INVMODE_LOOK ? COMMAND_HIGHLIGHTED :COMMAND_FOREGROUND, true, "Look"); - screen.buttonPrint(Common::Point(INVENTORY_POINTS[2][2], CONTROLS_Y1), + screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[2][2], CONTROLS_Y1), _invMode == INVMODE_USE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND, true, "Use"); - screen.buttonPrint(Common::Point(INVENTORY_POINTS[3][2], CONTROLS_Y1), + screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[3][2], CONTROLS_Y1), _invMode == INVMODE_GIVE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND, true, "Give"); - screen.print(Common::Point(INVENTORY_POINTS[4][2], CONTROLS_Y1 + 1), + screen.print(Common::Point(Scalpel::INVENTORY_POINTS[4][2], CONTROLS_Y1 + 1), _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND, "^^"); - screen.print(Common::Point(INVENTORY_POINTS[5][2], CONTROLS_Y1 + 1), + screen.print(Common::Point(Scalpel::INVENTORY_POINTS[5][2], CONTROLS_Y1 + 1), _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND, "^"); - screen.print(Common::Point(INVENTORY_POINTS[6][2], CONTROLS_Y1 + 1), + screen.print(Common::Point(Scalpel::INVENTORY_POINTS[6][2], CONTROLS_Y1 + 1), (_holdings - _invIndex <= 6) ? COMMAND_NULL : COMMAND_FOREGROUND, "_"); - screen.print(Common::Point(INVENTORY_POINTS[7][2], CONTROLS_Y1 + 1), + 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(INVENTORY_POINTS[0][2], CONTROLS_Y1), + screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[0][2], CONTROLS_Y1), _invMode == INVMODE_EXIT ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND, false, "Exit"); - screen.buttonPrint(Common::Point(INVENTORY_POINTS[1][2], CONTROLS_Y1), + screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[1][2], CONTROLS_Y1), _invMode == INVMODE_LOOK ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND, false, "Look"); - screen.buttonPrint(Common::Point(INVENTORY_POINTS[2][2], CONTROLS_Y1), + screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[2][2], CONTROLS_Y1), _invMode == INVMODE_USE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND, false, "Use"); - screen.buttonPrint(Common::Point(INVENTORY_POINTS[3][2], CONTROLS_Y1), + screen.buttonPrint(Common::Point(Scalpel::INVENTORY_POINTS[3][2], CONTROLS_Y1), _invMode == INVMODE_GIVE ? COMMAND_HIGHLIGHTED : COMMAND_FOREGROUND, false, "Give"); - screen.gPrint(Common::Point(INVENTORY_POINTS[4][2], CONTROLS_Y1), + screen.gPrint(Common::Point(Scalpel::INVENTORY_POINTS[4][2], CONTROLS_Y1), _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND, "^^"); - screen.gPrint(Common::Point(INVENTORY_POINTS[5][2], CONTROLS_Y1), + screen.gPrint(Common::Point(Scalpel::INVENTORY_POINTS[5][2], CONTROLS_Y1), _invIndex == 0 ? COMMAND_NULL : COMMAND_FOREGROUND, "^"); - screen.gPrint(Common::Point(INVENTORY_POINTS[6][2], CONTROLS_Y1), + screen.gPrint(Common::Point(Scalpel::INVENTORY_POINTS[6][2], CONTROLS_Y1), (_holdings - _invIndex < 7) ? COMMAND_NULL : COMMAND_FOREGROUND, "_"); - screen.gPrint(Common::Point(INVENTORY_POINTS[7][2], CONTROLS_Y1), + screen.gPrint(Common::Point(Scalpel::INVENTORY_POINTS[7][2], CONTROLS_Y1), (_holdings - _invIndex < 7) ? COMMAND_NULL : COMMAND_FOREGROUND, "__"); } @@ -318,9 +320,12 @@ void Inventory::highlight(int index, byte color) { } void Inventory::refreshInv() { + if (IS_ROSE_TATTOO) + return; + Screen &screen = *_vm->_screen; Talk &talk = *_vm->_talk; - UserInterface &ui = *_vm->_ui; + Scalpel::ScalpelUserInterface &ui = *(Scalpel::ScalpelUserInterface *)_vm->_ui; ui._invLookFlag = true; freeInv(); diff --git a/engines/sherlock/journal.cpp b/engines/sherlock/journal.cpp index 564db59042..b4b05da991 100644 --- a/engines/sherlock/journal.cpp +++ b/engines/sherlock/journal.cpp @@ -135,6 +135,7 @@ void Journal::loadJournalFile(bool alreadyLoaded) { Screen &screen = *_vm->_screen; Talk &talk = *_vm->_talk; JournalEntry &journalEntry = _journal[_index]; + const byte *opcodes = talk._opcodes; Common::String dirFilename = _directory[journalEntry._converseNum]; bool replyOnly = journalEntry._replyOnly; @@ -243,7 +244,7 @@ void Journal::loadJournalFile(bool alreadyLoaded) { byte c = *replyP++; // Is it a control character? - if (c < SWITCH_SPEAKER) { + if (c < opcodes[0]) { // Nope. Set flag for allowing control codes to insert spaces ctrlSpace = true; assert(c >= ' '); @@ -290,7 +291,7 @@ void Journal::loadJournalFile(bool alreadyLoaded) { byte v; do { v = *strP++; - } while (v && (v < SWITCH_SPEAKER) && (v != '.') && (v != '!') && (v != '?')); + } while (v && (v < opcodes[0]) && (v != '.') && (v != '!') && (v != '?')); if (v == '?') journalString += " asked, \""; @@ -306,11 +307,11 @@ void Journal::loadJournalFile(bool alreadyLoaded) { journalString += c; do { journalString += *replyP++; - } while (*replyP && *replyP < SWITCH_SPEAKER && *replyP != '{' && *replyP != '}'); + } while (*replyP && *replyP < opcodes[0] && *replyP != '{' && *replyP != '}'); commentJustPrinted = false; } - } else if (c == SWITCH_SPEAKER) { + } else if (c == opcodes[OP_SWITCH_SPEAKER]) { if (!startOfReply) { if (!commentFlag && !commentJustPrinted) journalString += "\"\n"; @@ -337,7 +338,7 @@ void Journal::loadJournalFile(bool alreadyLoaded) { byte v; do { v = *strP++; - } while (v && v < SWITCH_SPEAKER && v != '.' && v != '!' && v != '?'); + } while (v && v < opcodes[0] && v != '.' && v != '!' && v != '?'); if (v == '?') journalString += " asked, \""; @@ -345,59 +346,42 @@ void Journal::loadJournalFile(bool alreadyLoaded) { journalString += " said, \""; } else { // Control code, so move past it and any parameters - switch (c) { - case RUN_CANIMATION: - case ASSIGN_PORTRAIT_LOCATION: - case PAUSE: - case PAUSE_WITHOUT_CONTROL: - case WALK_TO_CANIMATION: + 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; - break; - case ADJUST_OBJ_SEQUENCE: + } else if (c == opcodes[OP_ADJUST_OBJ_SEQUENCE]) { replyP += (replyP[0] & 127) + replyP[1] + 2; - break; - case WALK_TO_COORDS: - case MOVE_MOUSE: + } else if (c == opcodes[OP_WALK_TO_COORDS] || c == opcodes[OP_MOVE_MOUSE]) { replyP += 4; - break; - - case SET_FLAG: - case IF_STATEMENT: + + } else if (c == opcodes[OP_SET_FLAG] || c == opcodes[OP_IF_STATEMENT]) { replyP += 2; - break; - case SFX_COMMAND: - case PLAY_PROLOGUE: - case CALL_TALK_FILE: + } else if (c == opcodes[OP_SFX_COMMAND] || c == opcodes[OP_PLAY_PROLOGUE] || + c == opcodes[OP_CALL_TALK_FILE]) { replyP += 8; break; - case TOGGLE_OBJECT: - case ADD_ITEM_TO_INVENTORY: - case SET_OBJECT: - case DISPLAY_INFO_LINE: - case REMOVE_ITEM_FROM_INVENTORY: + } 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; - break; - case GOTO_SCENE: + } else if (c == opcodes[OP_GOTO_SCENE]) { replyP += 5; - break; - case CARRIAGE_RETURN: + } else if (c == opcodes[OP_CARRIAGE_RETURN]) { journalString += "\n"; - break; - - default: - break; } // Put a space in the output for a control character, unless it's // immediately coming after another control character - if (ctrlSpace && c != ASSIGN_PORTRAIT_LOCATION && c != CARRIAGE_RETURN && !commentJustPrinted) { + if (ctrlSpace && c != opcodes[OP_ASSIGN_PORTRAIT_LOCATION] && c != opcodes[OP_CARRIAGE_RETURN] && + !commentJustPrinted) { journalString += " "; ctrlSpace = false; } diff --git a/engines/sherlock/map.cpp b/engines/sherlock/map.cpp index 42775ae4b0..ffbca3fb42 100644 --- a/engines/sherlock/map.cpp +++ b/engines/sherlock/map.cpp @@ -22,6 +22,7 @@ #include "sherlock/map.h" #include "sherlock/sherlock.h" +#include "common/system.h" namespace Sherlock { @@ -50,7 +51,7 @@ const byte *MapPaths::getPath(int srcLocation, int destLocation) { /*----------------------------------------------------------------*/ -Map::Map(SherlockEngine *vm) : _vm(vm), _topLine(SHERLOCK_SCREEN_WIDTH, 12) { +Map::Map(SherlockEngine *vm): _vm(vm), _topLine(g_system->getWidth(), 12) { _active = false; _mapCursors = nullptr; _shapes = nullptr; @@ -64,8 +65,12 @@ Map::Map(SherlockEngine *vm) : _vm(vm), _topLine(SHERLOCK_SCREEN_WIDTH, 12) { _oldCharPoint = 0; _frameChangeFlag = false; - for (int idx = 0; idx < MAX_HOLMES_SEQUENCE; ++idx) - Common::fill(&_sequences[idx][0], &_sequences[idx][MAX_FRAME], 0); + // 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(); @@ -79,12 +84,17 @@ void Map::loadPoints(int count, const int *xList, const int *yList, const int *t void Map::loadSequences(int count, const byte *seq) { for (int idx = 0; idx < count; ++idx, seq += MAX_FRAME) - Common::copy(seq, seq + MAX_FRAME, &_sequences[idx][0]); + 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("chess.txt"); + Common::SeekableReadStream *txtStream = _vm->_res->load( + _vm->getGameID() == GType_SerratedScalpel ? "chess.txt" : "map.txt"); int streamSize = txtStream->size(); while (txtStream->pos() < streamSize) { @@ -283,7 +293,6 @@ void Map::setupSprites() { p._type = CHARACTER; p._position = Common::Point(12400, 5000); p._sequenceNumber = 0; - p._sequences = &_sequences; p._images = _shapes; p._imageFrame = nullptr; p._frameNumber = 0; @@ -296,8 +305,8 @@ void Map::setupSprites() { p._noShapeSize = Common::Point(0, 0); p._goto = Common::Point(28000, 15000); p._status = 0; + p._walkSequences = _walkSequences; p.setImageFrame(); - scene._bgShapes.clear(); } diff --git a/engines/sherlock/map.h b/engines/sherlock/map.h index ab70b0885f..e0c7d038c4 100644 --- a/engines/sherlock/map.h +++ b/engines/sherlock/map.h @@ -76,7 +76,7 @@ private: ImageFile *_mapCursors; ImageFile *_shapes; ImageFile *_iconShapes; - byte _sequences[MAX_HOLMES_SEQUENCE][MAX_FRAME]; + WalkSequences _walkSequences; Point32 _lDrawnPos; int _point; bool _placesShown; diff --git a/engines/sherlock/module.mk b/engines/sherlock/module.mk index d8c1d781a9..9ebe416c4b 100644 --- a/engines/sherlock/module.mk +++ b/engines/sherlock/module.mk @@ -4,7 +4,12 @@ MODULE_OBJS = \ scalpel/darts.o \ scalpel/scalpel.o \ scalpel/drivers/adlib.o \ + scalpel/scalpel_scene.o \ + scalpel/scalpel_user_interface.o \ + scalpel/settings.o \ tattoo/tattoo.o \ + tattoo/tattoo_scene.o \ + tattoo/tattoo_user_interface.o \ animation.o \ debugger.o \ detection.o \ @@ -19,7 +24,6 @@ MODULE_OBJS = \ saveload.o \ scene.o \ screen.o \ - settings.o \ sherlock.o \ sound.o \ surface.o \ diff --git a/engines/sherlock/music.cpp b/engines/sherlock/music.cpp index ed047f61f8..3e31bcc83d 100644 --- a/engines/sherlock/music.cpp +++ b/engines/sherlock/music.cpp @@ -272,6 +272,11 @@ bool Music::loadSong(int songNumber) { return true; } +bool Music::loadSong(const Common::String &songName) { + warning("TODO: Music::loadSong"); + return false; +} + void Music::syncMusicSettings() { _musicOn = !ConfMan.getBool("mute") && !ConfMan.getBool("music_mute"); } diff --git a/engines/sherlock/music.h b/engines/sherlock/music.h index a195b26b68..c3710603cc 100644 --- a/engines/sherlock/music.h +++ b/engines/sherlock/music.h @@ -74,6 +74,11 @@ public: bool loadSong(int songNumber); /** + * Load a specified song + */ + bool loadSong(const Common::String &songName); + + /** * Start playing a song */ void startSong(); diff --git a/engines/sherlock/objects.cpp b/engines/sherlock/objects.cpp index 02f2526ae9..8818f805a5 100644 --- a/engines/sherlock/objects.cpp +++ b/engines/sherlock/objects.cpp @@ -46,7 +46,7 @@ void Sprite::clear() { _description = ""; _examine.clear(); _pickUp = ""; - _sequences = nullptr; + _walkSequences.clear(); _images = nullptr; _imageFrame = nullptr; _walkCount = 0; @@ -63,12 +63,26 @@ void Sprite::clear() { _status = 0; _misc = 0; _numFrames = 0; + _altImages = nullptr; + _altSequences = false; + Common::fill(&_stopFrames[0], &_stopFrames[8], (ImageFrame *)nullptr); } void Sprite::setImageFrame() { - int imageNumber = (*_sequences)[_sequenceNumber][_frameNumber] + - (*_sequences)[_sequenceNumber][0] - 2; - _imageFrame = &(*_images)[imageNumber]; + 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() { @@ -113,14 +127,14 @@ void Sprite::adjustSprite() { people.gotoStand(*this); } } else if (!map._active) { - _position.y = CLIP((int)_position.y, UPPER_LIMIT, LOWER_LIMIT); - _position.x = CLIP((int)_position.x, LEFT_LIMIT, RIGHT_LIMIT); + _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 ((*_sequences)[_sequenceNumber][_frameNumber] == 0) { + if (_walkSequences[_sequenceNumber][_frameNumber] == 0) { switch (_sequenceNumber) { case STOP_UP: case STOP_DOWN: @@ -352,6 +366,34 @@ void Sprite::checkSprite() { /*----------------------------------------------------------------*/ +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]; @@ -373,9 +415,14 @@ UseType::UseType() { _useFlag = 0; } -void UseType::load(Common::SeekableReadStream &s) { +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) @@ -387,7 +434,9 @@ void UseType::load(Common::SeekableReadStream &s) { } _useFlag = s.readSint16LE(); - s.skip(6); + + if (!isRoseTattoo) + s.skip(6); s.read(buffer, 12); _target = Common::String(buffer); @@ -434,9 +483,16 @@ Object::Object() { _descOffset = 0; _seqCounter2 = 0; _seqSize = 0; + + _quickDraw = 0; + _scaleVal = 0; + _requiredFlag1 = 0; + _gotoSeq = 0; + _talkSeq = 0; + _restoreSlot = 0; } -void Object::load(Common::SeekableReadStream &s) { +void Object::load(Common::SeekableReadStream &s, bool isRoseTattoo) { char buffer[41]; s.read(buffer, 12); _name = Common::String(buffer); @@ -468,10 +524,10 @@ void Object::load(Common::SeekableReadStream &s) { _goto.x = s.readSint16LE(); _goto.y = s.readSint16LE(); - _pickup = s.readByte(); - _defaultCommand = s.readByte(); - _lookFlag = s.readUint16LE(); - _pickupFlag = s.readUint16LE(); + _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(); @@ -479,26 +535,45 @@ void Object::load(Common::SeekableReadStream &s) { _misc = s.readByte(); _maxFrames = s.readUint16LE(); _flags = s.readByte(); - _aOpen.load(s); + + if (!isRoseTattoo) + _aOpen.load(s); + _aType = (AType)s.readByte(); _lookFrames = s.readByte(); _seqCounter = s.readByte(); _lookPosition.x = s.readUint16LE(); - _lookPosition.y = s.readByte(); + _lookPosition.y = isRoseTattoo ? s.readSint16LE() : s.readByte(); _lookFacing = s.readByte(); _lookcAnim = s.readByte(); - _aClose.load(s); + + if (!isRoseTattoo) + _aClose.load(s); + _seqStack = s.readByte(); _seqTo = s.readByte(); _descOffset = s.readUint16LE(); _seqCounter2 = s.readByte(); _seqSize = s.readUint16LE(); - s.skip(1); - _aMove.load(s); - s.skip(8); - for (int idx = 0; idx < USE_COUNT; ++idx) - _use[idx].load(s); + 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() { @@ -1032,17 +1107,30 @@ const Common::Rect Object::getOldBounds() const { /*----------------------------------------------------------------*/ -void CAnim::load(Common::SeekableReadStream &s) { +void CAnim::load(Common::SeekableReadStream &s, bool isRoseTattoo) { char buffer[12]; s.read(buffer, 12); _name = Common::String(buffer); - s.read(_sequences, 30); + if (isRoseTattoo) { + Common::fill(&_sequences[0], &_sequences[30], 0); + _size = s.readUint32LE(); + } else { + s.read(_sequences, 30); + } + _position.x = s.readSint16LE(); _position.y = s.readSint16LE(); - _size = s.readUint32LE(); - _type = (SpriteType)s.readUint16LE(); - _flags = s.readByte(); + + if (isRoseTattoo) { + _flags = s.readByte(); + _scaleVal = s.readSint16LE(); + } else { + _size = s.readUint32LE(); + _type = (SpriteType)s.readUint16LE(); + _flags = s.readByte(); + } + _goto.x = s.readSint16LE(); _goto.y = s.readSint16LE(); _gotoDir = s.readSint16LE(); @@ -1053,6 +1141,22 @@ void CAnim::load(Common::SeekableReadStream &s) { /*----------------------------------------------------------------*/ +CAnimStream::CAnimStream() { + _stream = nullptr; + _frameSize = 0; + _images = nullptr; + _imageFrame = nullptr; + _flags = 0; + _scaleVal = 0; + _zPlacement = 0; +} + +void CAnimStream::getNextFrame() { + // TODO +} + +/*----------------------------------------------------------------*/ + SceneImage::SceneImage() { _images = nullptr; _maxFrames = 0; diff --git a/engines/sherlock/objects.h b/engines/sherlock/objects.h index bbd068eef7..d671066a23 100644 --- a/engines/sherlock/objects.h +++ b/engines/sherlock/objects.h @@ -46,7 +46,10 @@ enum SpriteType { REMOVE = 5, // Object should be removed next frame NO_SHAPE = 6, // Background object with no shape HIDDEN = 7, // Hidden backgruond object - HIDE_SHAPE = 8 // Object needs to be hidden + HIDE_SHAPE = 8, // Object needs to be hidden + + // Rose Tattoo + HIDDEN_CHARACTER = 128 }; enum AType { @@ -73,6 +76,7 @@ enum { #define MAX_HOLMES_SEQUENCE 16 #define MAX_FRAME 30 +#define FIXED_INT_MULTIPLIER 100 // code put into sequences to defines 1-10 type seqs #define SEQ_TO_CODE 67 @@ -98,16 +102,67 @@ public: void operator-=(const Point32 &delta) { x -= delta.x; y -= delta.y; } }; + +struct WalkSequence { + Common::String _vgsName; + bool _horizFlip; + Common::Array<byte> _sequences; + + WalkSequence() : _horizFlip(false) {} + const byte &operator[](int idx) { return _sequences[idx]; } + + /** + * Load data for the sequence from a stream + */ + void load(Common::SeekableReadStream &s); +}; + +class WalkSequences : public Common::Array < WalkSequence > { +public: + WalkSequences &operator=(const WalkSequences &src); +}; + +enum { REVERSE_DIRECTION = 0x80 }; +#define NAMES_COUNT 4 + +struct ActionType { + int _cAnimNum; + int _cAnimSpeed; + Common::String _names[NAMES_COUNT]; + + /** + * Load the data for the action + */ + 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) + Common::String _target; + Common::String _verb; + + UseType(); + + /** + * Load the data for the UseType + */ + void load(Common::SeekableReadStream &s, bool isRoseTattoo); +}; + + class Sprite { private: static SherlockEngine *_vm; public: - Common::String _name; - Common::String _description; - Common::StringArray _examine; // Examine in-depth description + 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 - const uint8 (*_sequences)[MAX_HOLMES_SEQUENCE][MAX_FRAME]; // Holds animation sequences + WalkSequences _walkSequences; // Holds animation sequences ImageFile *_images; // Sprite images ImageFrame *_imageFrame; // Pointer to shape in the images int _walkCount; // Character walk counter @@ -124,68 +179,69 @@ public: 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 _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(); } + static void setVm(SherlockEngine *vm) { _vm = vm; } /** - * Reset the data for the sprite - */ + * Reset the data for the sprite + */ void clear(); /** - * Updates the image frame poiner for the sprite - */ + * Updates the image frame poiner for the sprite + */ void setImageFrame(); /** - * This adjusts the sprites position, as well as it's animation sequence: - */ + * 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 - */ + * Checks the sprite's position to see if it's collided with any special objects + */ void checkSprite(); /** - * Return frame width - */ + * Return frame width + */ int frameWidth() const { return _imageFrame ? _imageFrame->_frame.w : 0; } - - /** - * Return frame height - */ - int frameHeight() const { return _imageFrame ? _imageFrame->_frame.h : 0; } -}; - -enum { REVERSE_DIRECTION = 0x80 }; -#define NAMES_COUNT 4 - -struct ActionType { - int _cAnimNum; - int _cAnimSpeed; - Common::String _names[NAMES_COUNT]; - - /** - * Load the data for the action - */ - 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) - Common::String _target; - - UseType(); /** - * Load the data for the UseType - */ - void load(Common::SeekableReadStream &s); + * 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 }; @@ -233,35 +289,45 @@ public: int _pickup; int _defaultCommand; // Default right-click command int _lookFlag; // Which flag LOOK will set (if any) - int _pickupFlag; // Which flag PICKUP 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 - ActionType _aOpen; // Holds data for moving object 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; - ActionType _aClose; 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) + ActionType _aOpen; // Holds data for moving object + ActionType _aClose; ActionType _aMove; - UseType _use[USE_COUNT]; + + // Rose Tattoo fields + int _quickDraw; + int _scaleVal; + int _requiredFlag1; + int _gotoSeq; + int _talkSeq; + int _restoreSlot; Object(); /** * Load the data for the object */ - void load(Common::SeekableReadStream &s); + void load(Common::SeekableReadStream &s, bool isRoseTattoo); /** * Toggle the type of an object between hidden and active @@ -326,20 +392,45 @@ public: struct CAnim { Common::String _name; // Name - byte _sequences[MAX_FRAME]; // Animation sequences Common::Point _position; // Position int _size; // Size of uncompressed animation - SpriteType _type; 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 + // Scalpel specific + byte _sequences[MAX_FRAME]; // Animation sequences + SpriteType _type; + + // Rose Tattoo specific + int _scaleVal; // How much the canim is scaled + /** * Load the data for the animation */ - void load(Common::SeekableReadStream &s); + 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(); }; struct SceneImage { diff --git a/engines/sherlock/people.cpp b/engines/sherlock/people.cpp index 3a630fdd99..0ef49ffefb 100644 --- a/engines/sherlock/people.cpp +++ b/engines/sherlock/people.cpp @@ -50,10 +50,38 @@ static const uint8 CHARACTER_SEQUENCES[MAX_HOLMES_SEQUENCE][MAX_FRAME] = { { 52, 1, 2, 3, 4, 0 } // Goto Stand Down Left }; +// Rose Tattoo walk image libraries +// Walk resources within WALK.LIB +#define NUM_IN_WALK_LIB 10 +const char *const WALK_LIB_NAMES[10] = { + "SVGAWALK.VGS", + "COATWALK.VGS", + "WATSON.VGS", + "NOHAT.VGS", + "TUPRIGHT.VGS", + "TRIGHT.VGS", + "TDOWNRG.VGS", + "TWUPRIGH.VGS", + "TWRIGHT.VGS", + "TWDOWNRG.VGS" +}; + +/*----------------------------------------------------------------*/ + +Person::Person() : Sprite(), _walkLoaded(false), _npcIndex(0), _npcStack(0), _npcPause(false) { + Common::fill(&_npcPath[0], &_npcPath[MAX_NPC_PATH], 0); + _tempX = _tempScaleVal = 0; +} + +void Person::clearNPC() { + Common::fill(&_npcPath[0], &_npcPath[MAX_NPC_PATH], 0); + _npcIndex = _npcStack = 0; + _npcName = ""; +} + /*----------------------------------------------------------------*/ People::People(SherlockEngine *vm) : _vm(vm), _player(_data[0]) { - _walkLoaded = false; _holmesOn = true; _oldWalkSequence = -1; _allowWalkAbort = false; @@ -68,66 +96,168 @@ People::People(SherlockEngine *vm) : _vm(vm), _player(_data[0]) { _holmesQuotient = 0; _hSavedPos = Common::Point(-1, -1); _hSavedFacing = -1; + _forceWalkReload = false; + _useWalkLib = false; + _walkControl = 0; _portrait._sequences = new byte[32]; } People::~People() { - if (_walkLoaded) - delete _data[PLAYER]._images; + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { + if (_data[idx]._walkLoaded) + delete _data[PLAYER]._images; + } + delete _talkPics; delete[] _portrait._sequences; } void People::reset() { - // Note: The engine has theoretical support for two player characters but only the first one is used. - // Watson is, instead, handled by a different sprite in each scene, with a very simple initial movement, if any - Sprite &p = _data[PLAYER]; - - p._description = "Sherlock Holmes!"; - p._type = CHARACTER; - p._position = Common::Point(10000, 11000); - p._sequenceNumber = STOP_DOWNRIGHT; - p._sequences = &CHARACTER_SEQUENCES; - p._imageFrame = nullptr; - p._frameNumber = 1; - p._delta = Common::Point(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); - p._goto = Common::Point(0, 0); - p._status = 0; + _data[0]._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]; + + p._type = (idx == 0) ? CHARACTER : INVALID; + if (IS_SERRATED_SCALPEL) + p._position = Point32(10000, 11000); + else + p._position = Point32(36000, 29000); + + p._sequenceNumber = STOP_DOWNRIGHT; + p._imageFrame = nullptr; + p._frameNumber = 1; + p._delta = Common::Point(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); + p._goto = Common::Point(0, 0); + p._status = 0; + p._seqTo = 0; + p._seqCounter = p._seqCounter2 = 0; + p._seqStack = 0; + p._gotoSeq = p._talkSeq = 0; + p._restoreSlot = 0; + p._startSeq = 0; + p._altImages = nullptr; + p._altSequences = 0; + p._centerWalk = true; + p._adjust = Common::Point(0, 0); + + // Load the default walk sequences + p._oldWalkSequence = -1; + p._walkSequences.clear(); + if (IS_SERRATED_SCALPEL) { + p._walkSequences.resize(MAX_HOLMES_SEQUENCE); + for (int seqIdx = 0; seqIdx < MAX_HOLMES_SEQUENCE; ++seqIdx) { + p._walkSequences[seqIdx]._sequences.clear(); + + const byte *pSrc = &CHARACTER_SEQUENCES[seqIdx][0]; + do { + p._walkSequences[seqIdx]._sequences.push_back(*pSrc); + } while (*pSrc++); + } + } + } // Reset any walk path in progress when Sherlock leaves scenes _walkTo.clear(); } bool People::loadWalk() { - if (_walkLoaded) { - return false; + 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 { - _data[PLAYER]._images = new ImageFile("walk.vgs"); - _data[PLAYER].setImageFrame(); - _walkLoaded = true; + 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; + } + } - return true; + // 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() { - if (_walkLoaded) { - delete _player._images; - _player._images = nullptr; - - _walkLoaded = false; - return true; - } else { - return false; + bool result = false; + + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { + if (_data[idx]._walkLoaded) { + delete _data[idx]._images; + _data[idx]._images = nullptr; + + _data[idx]._walkLoaded = false; + result = true; + } } + + return result; } void People::setWalking() { @@ -553,9 +683,24 @@ void People::setTalking(int speaker) { void People::synchronize(Common::Serializer &s) { s.syncAsByte(_holmesOn); - s.syncAsSint16LE(_player._position.x); - s.syncAsSint16LE(_player._position.y); - s.syncAsSint16LE(_player._sequenceNumber); + + 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()) { diff --git a/engines/sherlock/people.h b/engines/sherlock/people.h index 8244fd9b59..013727d8ba 100644 --- a/engines/sherlock/people.h +++ b/engines/sherlock/people.h @@ -30,13 +30,13 @@ namespace Sherlock { -// Player definitions. The game has theoretical support for two player characters but only the first one is used. -// Watson is, instead, handled by a different sprite in each scene, with a very simple initial movement, if any enum PeopleId { PLAYER = 0, AL = 0, PEG = 1, - MAX_PLAYERS = 2 + MAX_CHARACTERS = 6, + MAX_NPC = 5, + MAX_NPC_PATH = 200 }; // Animation sequence identifiers for characters @@ -63,20 +63,37 @@ struct PersonData { _name(name), _portrait(portrait), _stillSequences(stillSequences), _talkSequences(talkSequences) {} }; -class SherlockEngine; - class Person : public Sprite { public: - Person() : Sprite() {} - + bool _walkLoaded; Common::String _portrait; + + // NPC related fields + int _npcIndex; + int _npcStack; + bool _npcPause; + byte _npcPath[MAX_NPC_PATH]; + Common::String _npcName; + int _tempX; + int _tempScaleVal; + + // Rose Tattoo fields + Common::String _walkVGSName; // Name of walk library person is using +public: + Person(); + + /** + * Clear the NPC related data + */ + void clearNPC(); }; +class SherlockEngine; + class People { private: SherlockEngine *_vm; - Person _data[MAX_PLAYERS]; - bool _walkLoaded; + Person _data[MAX_CHARACTERS]; int _oldWalkSequence; int _srcZone, _destZone; public: @@ -97,25 +114,24 @@ public: bool _speakerFlip; bool _holmesFlip; int _holmesQuotient; + bool _forceWalkReload; + bool _useWalkLib; + + int _walkControl; public: People(SherlockEngine *vm); ~People(); Person &operator[](PeopleId id) { - assert(id < MAX_PLAYERS); + assert(id < MAX_CHARACTERS); return _data[id]; } Person &operator[](int idx) { - assert(idx < MAX_PLAYERS); + assert(idx < MAX_CHARACTERS); return _data[idx]; } /** - * Returns true if Sherlock is visible on the screen and enabled - */ - bool isHolmesActive() const { return _walkLoaded && _holmesOn; } - - /** * Reset the player data */ void reset(); diff --git a/engines/sherlock/resources.cpp b/engines/sherlock/resources.cpp index 091ef3ea12..f1c705b522 100644 --- a/engines/sherlock/resources.cpp +++ b/engines/sherlock/resources.cpp @@ -65,7 +65,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 - Common::SeekableReadStream *decompressed = _vm->_res->decompressLZ(stream); + Common::SeekableReadStream *decompressed = _vm->_res->decompress(stream); cacheEntry.resize(decompressed->size()); decompressed->read(&cacheEntry[0], decompressed->size()); @@ -91,9 +91,12 @@ Resources::Resources(SherlockEngine *vm) : _vm(vm), _cache(vm) { if (_vm->_interactiveFl) { addToCache("vgs.lib"); addToCache("talk.lib"); - addToCache("sequence.txt"); addToCache("journal.txt"); - addToCache("portrait.lib"); + + if (IS_SERRATED_SCALPEL) { + addToCache("sequence.txt"); + addToCache("portrait.lib"); + } } } @@ -103,8 +106,11 @@ void Resources::addToCache(const Common::String &filename) { // Check to see if the file is a library Common::SeekableReadStream *stream = load(filename); uint32 header = stream->readUint32BE(); + if (header == MKTAG('L', 'I', 'B', 26)) - loadLibraryIndex(filename, stream); + loadLibraryIndex(filename, stream, false); + else if (header == MKTAG('L', 'I', 'C', 26)) + loadLibraryIndex(filename, stream, true); delete stream; } @@ -159,12 +165,14 @@ Common::SeekableReadStream *Resources::load(const Common::String &filename) { void Resources::decompressIfNecessary(Common::SeekableReadStream *&stream) { bool isCompressed = stream->readUint32BE() == MKTAG('L', 'Z', 'V', 26); - stream->seek(-4, SEEK_CUR); if (isCompressed) { - Common::SeekableReadStream *newStream = decompressLZ(*stream); + int outSize = stream->readUint32LE(); + Common::SeekableReadStream *newStream = decompressLZ(*stream, outSize); delete stream; stream = newStream; + } else { + stream->seek(-4, SEEK_CUR); } } @@ -174,7 +182,7 @@ Common::SeekableReadStream *Resources::load(const Common::String &filename, cons // Check if the library has already had it's index read, and if not, load it if (!_indexes.contains(libraryFile)) - loadLibraryIndex(libraryFile, libStream); + loadLibraryIndex(libraryFile, libStream, false); // Extract the data for the specified resource and return it LibraryEntry &entry = _indexes[libraryFile][filename]; @@ -192,7 +200,7 @@ bool Resources::exists(const Common::String &filename) const { } void Resources::loadLibraryIndex(const Common::String &libFilename, - Common::SeekableReadStream *stream) { + Common::SeekableReadStream *stream, bool isNewStyle) { uint32 offset, nextOffset; // Create an index entry @@ -203,6 +211,9 @@ void Resources::loadLibraryIndex(const Common::String &libFilename, stream->seek(4); int count = stream->readUint16LE(); + if (isNewStyle) + stream->seek((count + 1) * 8, SEEK_CUR); + // Loop through reading in the entries for (int idx = 0; idx < count; ++idx) { // Read the name of the resource @@ -231,24 +242,48 @@ int Resources::resourceIndex() const { return _resourceIndex; } -Common::SeekableReadStream *Resources::decompressLZ(Common::SeekableReadStream &source) { - if (_vm->getGameID() == GType_SerratedScalpel) { - uint32 id = source.readUint32BE(); - assert(id == MKTAG('L', 'Z', 'V', 0x1A)); - } +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); + + uint32 id = source.readUint32BE(); + assert(id == MKTAG('L', 'Z', 'V', 0x1A)); - uint32 size = source.readUint32LE(); - return decompressLZ(source, size); + uint32 outputSize = source.readUint32LE(); + return decompressLZ(source, outputSize); +} + +Common::SeekableReadStream *Resources::decompress(Common::SeekableReadStream &source, uint32 outSize) { + int inSize = (_vm->getGameID() == GType_RoseTattoo) ? source.readUint32LE() : -1; + byte *outBuffer = (byte *)malloc(outSize); + Common::MemoryReadStream *outStream = new Common::MemoryReadStream(outBuffer, outSize, DisposeAfterUse::YES); + + decompressLZ(source, outBuffer, outSize, inSize); + + return outStream; +} + +void Resources::decompress(Common::SeekableReadStream &source, byte *buffer, uint32 outSize) { + int inputSize = (_vm->getGameID() == GType_RoseTattoo) ? source.readUint32LE() : -1; + + decompressLZ(source, buffer, outSize, inputSize); } Common::SeekableReadStream *Resources::decompressLZ(Common::SeekableReadStream &source, uint32 outSize) { + byte *dataOut = (byte *)malloc(outSize); + decompressLZ(source, dataOut, outSize, -1); + + return new Common::MemoryReadStream(dataOut, outSize, DisposeAfterUse::YES); +} + +void Resources::decompressLZ(Common::SeekableReadStream &source, byte *outBuffer, int32 outSize, int32 inSize) { byte lzWindow[4096]; uint16 lzWindowPos; uint16 cmd; - byte *outBuffer = (byte *)malloc(outSize); byte *outBufferEnd = outBuffer + outSize; - Common::MemoryReadStream *outS = new Common::MemoryReadStream(outBuffer, outSize, DisposeAfterUse::YES); + int endPos = source.pos() + inSize; memset(lzWindow, 0xFF, 0xFEE); lzWindowPos = 0xFEE; @@ -264,8 +299,7 @@ Common::SeekableReadStream *Resources::decompressLZ(Common::SeekableReadStream & *outBuffer++ = literal; lzWindow[lzWindowPos] = literal; lzWindowPos = (lzWindowPos + 1) & 0x0FFF; - } - else { + } else { int copyPos, copyLen; copyPos = source.readByte(); copyLen = source.readByte(); @@ -279,9 +313,7 @@ Common::SeekableReadStream *Resources::decompressLZ(Common::SeekableReadStream & lzWindowPos = (lzWindowPos + 1) & 0x0FFF; } } - } while (outBuffer < outBufferEnd); - - return outS; + } while ((outSize == -1 || outBuffer < outBufferEnd) && (inSize == -1 || source.pos() < endPos)); } /*----------------------------------------------------------------*/ @@ -325,13 +357,14 @@ void ImageFile::load(Common::SeekableReadStream &stream, bool skipPalette, bool // 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._offset.y = stream.readByte(); frame._rleEncoded = !skipPalette && frame._rleEncoded; if (frame._paletteBase) { @@ -385,6 +418,28 @@ void ImageFile::decompressFrame(ImageFrame &frame, const byte *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(); @@ -411,4 +466,34 @@ void ImageFile::decompressFrame(ImageFrame &frame, const byte *src) { } } +/*----------------------------------------------------------------*/ + +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 fb91b30f94..5c071e3922 100644 --- a/engines/sherlock/resources.h +++ b/engines/sherlock/resources.h @@ -90,7 +90,7 @@ private: /** * Reads in the index from a library file, and caches it's index for later use */ - void loadLibraryIndex(const Common::String &libFilename, Common::SeekableReadStream *stream); + void loadLibraryIndex(const Common::String &libFilename, Common::SeekableReadStream *stream, bool isNewStyle); public: Resources(SherlockEngine *vm); @@ -140,14 +140,29 @@ public: int resourceIndex() const; /** - * Decompresses an LZW block of data with a specified output size + * Decompresses LZW compressed data + */ + Common::SeekableReadStream *decompress(Common::SeekableReadStream &source); + + /** + * Decompresses LZW compressed data + */ + Common::SeekableReadStream *decompress(Common::SeekableReadStream &source, uint32 outSize); + + /** + * Decompresses LZW compressed data + */ + void decompress(Common::SeekableReadStream &source, byte *buffer, uint32 outSize); + + /** + * Decompresses LZW compressed data */ static Common::SeekableReadStream *decompressLZ(Common::SeekableReadStream &source, uint32 outSize); - + /** - * Decompress an LZW compressed resource + * Decompresses LZW compressed data */ - Common::SeekableReadStream *decompressLZ(Common::SeekableReadStream &source); + static void decompressLZ(Common::SeekableReadStream &source, byte *outBuffer, int32 outSize, int32 inSize); }; struct ImageFrame { @@ -158,6 +173,16 @@ struct ImageFrame { 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> { diff --git a/engines/sherlock/scalpel/scalpel.cpp b/engines/sherlock/scalpel/scalpel.cpp index bee44bfb7d..304445df76 100644 --- a/engines/sherlock/scalpel/scalpel.cpp +++ b/engines/sherlock/scalpel/scalpel.cpp @@ -24,6 +24,7 @@ #include "sherlock/sherlock.h" #include "sherlock/music.h" #include "sherlock/animation.h" +#include "engines/util.h" namespace Sherlock { @@ -379,6 +380,9 @@ ScalpelEngine::~ScalpelEngine() { } void ScalpelEngine::initialize() { + initGraphics(320, 200, false); + + // Let the base engine intialize SherlockEngine::initialize(); _darts = new Darts(this); @@ -580,9 +584,9 @@ bool ScalpelEngine::scrollCredits() { _screen->transBlitFrom(creditsImages[1], Common::Point(10, 400 - idx), false, 0); // Don't show credit text on the top and bottom ten rows of the screen - _screen->blitFrom(_screen->_backBuffer1, Common::Point(0, 0), Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, 10)); - _screen->blitFrom(_screen->_backBuffer1, Common::Point(0, SHERLOCK_SCREEN_HEIGHT - 10), - Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - 10, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + _screen->blitFrom(_screen->_backBuffer1, Common::Point(0, 0), Common::Rect(0, 0, _screen->w(), 10)); + _screen->blitFrom(_screen->_backBuffer1, Common::Point(0, _screen->h() - 10), + Common::Rect(0, _screen->h() - 10, _screen->w(), _screen->h())); _events->delay(100); } @@ -804,7 +808,6 @@ void ScalpelEngine::startScene() { break; } - _events->loadCursors("rmouse.vgs"); _events->setCursor(ARROW); if (_scene->_goToScene == 99) { @@ -828,9 +831,11 @@ void ScalpelEngine::eraseMirror12() { void ScalpelEngine::doMirror12() { People &people = *_people; + Person &player = people._player; + Common::Point pt((*_people)[AL]._position.x / 100, (*_people)[AL]._position.y / 100); - int frameNum = (*people[AL]._sequences)[people[AL]._sequenceNumber][people[AL]._frameNumber] + - (*people[AL]._sequences)[people[AL]._sequenceNumber][0] - 2; + int frameNum = player._walkSequences[player._sequenceNumber][player._frameNumber] + + player._walkSequences[player._sequenceNumber][0] - 2; switch ((*_people)[AL]._sequenceNumber) { case WALK_DOWN: diff --git a/engines/sherlock/scalpel/scalpel_scene.cpp b/engines/sherlock/scalpel/scalpel_scene.cpp new file mode 100644 index 0000000000..b6a42419d8 --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_scene.cpp @@ -0,0 +1,381 @@ +/* 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_scene.h" +#include "sherlock/scalpel/scalpel.h" +#include "sherlock/events.h" +#include "sherlock/people.h" +#include "sherlock/screen.h" + +namespace Sherlock { + +namespace Scalpel { + +void ScalpelScene::checkBgShapes() { + People &people = *_vm->_people; + Person &holmes = people._player; + Common::Point pt(holmes._position.x / FIXED_INT_MULTIPLIER, holmes._position.y / FIXED_INT_MULTIPLIER); + + // Call the base scene method to handle bg shapes + Scene::checkBgShapes(); + + // Iterate through the canim list + for (uint idx = 0; idx < _canimShapes.size(); ++idx) { + Object &obj = _canimShapes[idx]; + if (obj._type == STATIC_BG_SHAPE || obj._type == ACTIVE_BG_SHAPE) { + if ((obj._flags & 5) == 1) { + obj._misc = (pt.y < (obj._position.y + obj._imageFrame->_frame.h - 1)) ? + NORMAL_FORWARD : NORMAL_BEHIND; + } else if (!(obj._flags & 1)) { + obj._misc = BEHIND; + } else if (obj._flags & 4) { + obj._misc = FORWARD; + } + } + } +} + +void ScalpelScene::doBgAnimCheckCursor() { + Inventory &inv = *_vm->_inventory; + Events &events = *_vm->_events; + Sound &sound = *_vm->_sound; + UserInterface &ui = *_vm->_ui; + Common::Point mousePos = events.mousePos(); + + if (ui._menuMode == LOOK_MODE) { + if (mousePos.y > CONTROLS_Y1) + events.setCursor(ARROW); + else if (mousePos.y < CONTROLS_Y) + events.setCursor(MAGNIFY); + } + + // Check for setting magnifying glass cursor + if (ui._menuMode == INV_MODE || ui._menuMode == USE_MODE || ui._menuMode == GIVE_MODE) { + if (inv._invMode == INVMODE_LOOK) { + // Only show Magnifying glass cursor if it's not on the inventory command line + if (mousePos.y < CONTROLS_Y || mousePos.y >(CONTROLS_Y1 + 13)) + events.setCursor(MAGNIFY); + else + events.setCursor(ARROW); + } else { + events.setCursor(ARROW); + } + } + + if (sound._diskSoundPlaying && !*sound._soundIsOn) { + // Loaded sound just finished playing + sound.freeDigiSound(); + } +} + +void ScalpelScene::doBgAnim() { + ScalpelEngine &vm = *((ScalpelEngine *)_vm); + Events &events = *_vm->_events; + People &people = *_vm->_people; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + + screen.setDisplayBounds(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT)); + talk._talkToAbort = false; + + if (_restoreFlag) { + 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 (people._portraitLoaded && people._portrait._type == ACTIVE_BG_SHAPE) + people._portrait.checkObject(); + + for (uint idx = 0; idx < _canimShapes.size(); ++idx) { + if (_canimShapes[idx]._type != INVALID && _canimShapes[idx]._type != REMOVE) + _canimShapes[idx].checkObject(); + } + + if (_currentScene == 12) + vm.eraseMirror12(); + + // 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::Point pt(bounds.left, bounds.top); + + if (people[AL]._type == CHARACTER) + screen.restoreBackground(bounds); + else if (people[AL]._type == REMOVE) + screen._backBuffer->blitFrom(screen._backBuffer2, pt, bounds); + + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &o = _bgShapes[idx]; + if (o._type == ACTIVE_BG_SHAPE || o._type == HIDE_SHAPE || o._type == REMOVE) + screen.restoreBackground(o.getOldBounds()); + } + + if (people._portraitLoaded) + screen.restoreBackground(Common::Rect( + people._portrait._oldPosition.x, people._portrait._oldPosition.y, + people._portrait._oldPosition.x + people._portrait._oldSize.x, + people._portrait._oldPosition.y + people._portrait._oldSize.y + )); + + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &o = _bgShapes[idx]; + if (o._type == NO_SHAPE && ((o._flags & OBJ_BEHIND) == 0)) { + // Restore screen area + screen._backBuffer->blitFrom(screen._backBuffer2, o._position, + Common::Rect(o._position.x, o._position.y, + o._position.x + o._noShapeSize.x, o._position.y + o._noShapeSize.y)); + + o._oldPosition = o._position; + o._oldSize = o._noShapeSize; + } + } + + for (uint idx = 0; idx < _canimShapes.size(); ++idx) { + Object &o = _canimShapes[idx]; + if (o._type == ACTIVE_BG_SHAPE || o._type == HIDE_SHAPE || o._type == REMOVE) + screen.restoreBackground(Common::Rect(o._oldPosition.x, o._oldPosition.y, + o._oldPosition.x + o._oldSize.x, o._oldPosition.y + o._oldSize.y)); + } + } + + // + // Update the background objects and canimations + // + + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &o = _bgShapes[idx]; + if (o._type == ACTIVE_BG_SHAPE || o._type == NO_SHAPE) + o.adjustObject(); + } + + if (people._portraitLoaded && people._portrait._type == ACTIVE_BG_SHAPE) + people._portrait.adjustObject(); + + for (uint idx = 0; idx < _canimShapes.size(); ++idx) { + if (_canimShapes[idx]._type != INVALID) + _canimShapes[idx].adjustObject(); + } + + if (people[AL]._type == CHARACTER && people._holmesOn) + people[AL].adjustSprite(); + + // Flag the bg shapes which need to be redrawn + checkBgShapes(); + + if (_currentScene == 12) + vm.doMirror12(); + + // Draw all active shapes which are behind the person + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &o = _bgShapes[idx]; + if (o._type == ACTIVE_BG_SHAPE && o._misc == BEHIND) + screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + } + + // Draw all canimations which are behind the person + for (uint idx = 0; idx < _canimShapes.size(); ++idx) { + Object &o = _canimShapes[idx]; + if (o._type == ACTIVE_BG_SHAPE && o._misc == BEHIND) { + screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + } + } + + // Draw all active shapes which are HAPPEN and behind the person + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &o = _bgShapes[idx]; + if (o._type == ACTIVE_BG_SHAPE && o._misc == NORMAL_BEHIND) + screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + } + + // Draw all canimations which are NORMAL and behind the person + for (uint idx = 0; idx < _canimShapes.size(); ++idx) { + Object &o = _canimShapes[idx]; + if (o._type == ACTIVE_BG_SHAPE && o._misc == NORMAL_BEHIND) { + screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + } + } + + // Draw the person if not animating + if (people[AL]._type == CHARACTER && people[AL]._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); + } + + // Draw all static and active shapes are NORMAL and are in front of the person + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &o = _bgShapes[idx]; + if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == NORMAL_FORWARD) + screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + } + + // Draw all static and active canimations that are NORMAL and are in front of the person + for (uint idx = 0; idx < _canimShapes.size(); ++idx) { + Object &o = _canimShapes[idx]; + if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == NORMAL_FORWARD) { + screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + } + } + + // Draw all static and active shapes that are in front of the person + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &o = _bgShapes[idx]; + if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == FORWARD) + screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + } + + // Draw any active portrait + if (people._portraitLoaded && people._portrait._type == ACTIVE_BG_SHAPE) + screen._backBuffer->transBlitFrom(*people._portrait._imageFrame, + people._portrait._position, people._portrait._flags & OBJ_FLIPPED); + + // Draw all static and active canimations that are in front of the person + for (uint idx = 0; idx < _canimShapes.size(); ++idx) { + Object &o = _canimShapes[idx]; + if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == FORWARD) { + screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + } + } + + // Draw all NO_SHAPE shapes which have flag bit 0 clear + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &o = _bgShapes[idx]; + if (o._type == NO_SHAPE && (o._flags & OBJ_BEHIND) == 0) + screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + } + + // Bring the newly built picture to the screen + if (_animating == 2) { + _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) { + 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[AL]._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); + } + } + + if (_currentScene == 12) + vm.flushMirror12(); + + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &o = _bgShapes[idx]; + if ((o._type == ACTIVE_BG_SHAPE || o._type == REMOVE) && _goToScene == -1) { + screen.flushImage(o._imageFrame, o._position, + &o._oldPosition.x, &o._oldPosition.y, &o._oldSize.x, &o._oldSize.y); + } + } + + if (people._portraitLoaded) { + if (people._portrait._type == REMOVE) + screen.slamRect(Common::Rect( + people._portrait._position.x, people._portrait._position.y, + people._portrait._position.x + people._portrait._delta.x, + people._portrait._position.y + people._portrait._delta.y + )); + else + screen.flushImage(people._portrait._imageFrame, people._portrait._position, + &people._portrait._oldPosition.x, &people._portrait._oldPosition.y, + &people._portrait._oldSize.x, &people._portrait._oldSize.y); + + if (people._portrait._type == REMOVE) + people._portrait._type = INVALID; + } + + if (_goToScene == -1) { + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &o = _bgShapes[idx]; + if (o._type == NO_SHAPE && (o._flags & OBJ_BEHIND) == 0) { + screen.slamArea(o._position.x, o._position.y, o._oldSize.x, o._oldSize.y); + screen.slamArea(o._oldPosition.x, o._oldPosition.y, o._oldSize.x, o._oldSize.y); + } else if (o._type == HIDE_SHAPE) { + // Hiding shape, so flush it out and mark it as hidden + screen.flushImage(o._imageFrame, o._position, + &o._oldPosition.x, &o._oldPosition.y, &o._oldSize.x, &o._oldSize.y); + o._type = HIDDEN; + } + } + } + + for (int idx = _canimShapes.size() - 1; idx >= 0; --idx) { + Object &o = _canimShapes[idx]; + + if (o._type == INVALID) { + // Anim shape was invalidated by checkEndOfSequence, so at this point we can remove it + _canimShapes.remove_at(idx); + } else if (o._type == REMOVE) { + if (_goToScene == -1) + screen.slamArea(o._position.x, o._position.y, o._delta.x, o._delta.y); + + // Shape for an animation is no longer needed, so remove it completely + _canimShapes.remove_at(idx); + } else if (o._type == ACTIVE_BG_SHAPE) { + screen.flushImage(o._imageFrame, o._position, + &o._oldPosition.x, &o._oldPosition.y, &o._oldSize.x, &o._oldSize.y); + } + } + } + + _restoreFlag = true; + _doBgAnimDone = true; + + events.wait(3); + screen.resetDisplayBounds(); + + // Check if the method was called for calling a portrait, and a talk was + // interrupting it. This talk file would not have been executed at the time, + // since we needed to finish the 'doBgAnim' to finish clearing the portrait + if (people._clearingThePortrait && talk._scriptMoreFlag == 3) { + // Reset the flags and call to talk + people._clearingThePortrait = false; + talk._scriptMoreFlag = 0; + talk.talkTo(talk._scriptName); + } +} + +} // End of namespace Scalpel + +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/scalpel_scene.h b/engines/sherlock/scalpel/scalpel_scene.h new file mode 100644 index 0000000000..e5a442f44f --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_scene.h @@ -0,0 +1,61 @@ +/* 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_SCENE_H +#define SHERLOCK_SCALPEL_SCENE_H + +#include "common/scummsys.h" +#include "common/array.h" +#include "common/rect.h" +#include "common/serializer.h" +#include "sherlock/objects.h" +#include "sherlock/scene.h" +#include "sherlock/screen.h" + +namespace Sherlock { + +namespace Scalpel { + +class ScalpelScene : public Scene { +private: + void doBgAnimCheckCursor(); +protected: + /** + * 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(); +public: + ScalpelScene(SherlockEngine *vm) : Scene(vm) {} + + /** + * Draw all objects and characters. + */ + virtual void doBgAnim(); +}; + +} // 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 new file mode 100644 index 0000000000..f9ec8bb1bb --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_user_interface.cpp @@ -0,0 +1,2276 @@ +/* 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_user_interface.h" +#include "sherlock/sherlock.h" +#include "sherlock/scalpel/settings.h" + +namespace Sherlock { + +namespace Scalpel { + +// Main user interface menu control locations +const int MENU_POINTS[12][4] = { + { 13, 153, 72, 165 }, + { 13, 169, 72, 181 }, + { 13, 185, 72, 197 }, + { 88, 153, 152, 165 }, + { 88, 169, 152, 181 }, + { 88, 185, 152, 197 }, + { 165, 153, 232, 165 }, + { 165, 169, 232, 181 }, + { 165, 185, 233, 197 }, + { 249, 153, 305, 165 }, + { 249, 169, 305, 181 }, + { 249, 185, 305, 197 } +}; + +// Inventory control locations */ +const int INVENTORY_POINTS[8][3] = { + { 4, 50, 29 }, + { 52, 99, 77 }, + { 101, 140, 123 }, + { 142, 187, 166 }, + { 189, 219, 198 }, + { 221, 251, 234 }, + { 253, 283, 266 }, + { 285, 315, 294 } +}; + +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" +}; + +/*----------------------------------------------------------------*/ + + +ScalpelUserInterface::ScalpelUserInterface(SherlockEngine *vm): UserInterface(vm) { + _controls = new ImageFile("menu.all"); + _controlPanel = new ImageFile("controls.vgs"); + _keyPress = '\0'; + _lookHelp = 0; + _bgFound = 0; + _oldBgFound = -1; + _help = _oldHelp = 0; + _key = _oldKey = '\0'; + _temp = _oldTemp = 0; + _oldLook = 0; + _keyboardInput = false; + _pause = false; + _cNum = 0; + _find = 0; + _oldUse = 0; +} + +ScalpelUserInterface::~ScalpelUserInterface() { + delete _controls; + delete _controlPanel; +} + +void ScalpelUserInterface::reset() { + _oldKey = -1; + _help = _oldHelp = -1; + _oldTemp = _temp = -1; +} + +void ScalpelUserInterface::drawInterface(int bufferNum) { + Screen &screen = *_vm->_screen; + + if (bufferNum & 1) + screen._backBuffer1.transBlitFrom((*_controlPanel)[0], Common::Point(0, CONTROLS_Y)); + if (bufferNum & 2) + screen._backBuffer2.transBlitFrom((*_controlPanel)[0], Common::Point(0, CONTROLS_Y)); + if (bufferNum == 3) + screen._backBuffer2.fillRect(0, INFO_LINE, SHERLOCK_SCREEN_WIDTH, INFO_LINE + 10, INFO_BLACK); +} + +void ScalpelUserInterface::handleInput() { + Events &events = *_vm->_events; + Inventory &inv = *_vm->_inventory; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + + if (_menuCounter) + whileMenuCounter(); + + Common::Point pt = events.mousePos(); + _bgFound = scene.findBgShape(Common::Rect(pt.x, pt.y, pt.x + 1, pt.y + 1)); + _keyPress = '\0'; + + // Check kbd and set the mouse released flag if Enter or space is pressed. + // Otherwise, the pressed _key is stored for later use + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + _keyPress = keyState.ascii; + + if (keyState.keycode == Common::KEYCODE_x && keyState.flags & Common::KBD_ALT) { + _vm->quitGame(); + events.pollEvents(); + return; + } + } + + // Do button highlighting check + if (!talk._scriptMoreFlag) { // Don't if scripts are running + if (((events._rightPressed || events._rightReleased) && _helpStyle) || + (!_helpStyle && !_menuCounter)) { + // Handle any default commands if we're in STD_MODE + if (_menuMode == STD_MODE) { + if (pt.y < CONTROLS_Y && + (events._rightPressed || (!_helpStyle && !events._released)) && + (_bgFound != -1) && (_bgFound < 1000) && + (scene._bgShapes[_bgFound]._defaultCommand || + !scene._bgShapes[_bgFound]._description.empty())) { + // If there is no default command, so set it to Look + if (scene._bgShapes[_bgFound]._defaultCommand) + _help = scene._bgShapes[_bgFound]._defaultCommand - 1; + else + _help = 0; + + // Reset 'help' if it is an invalid command + if (_help > 5) + _help = -1; + } else if (pt.y < CONTROLS_Y && + ((events._rightReleased && _helpStyle) || (events._released && !_helpStyle)) && + (_bgFound != -1 && _bgFound < 1000) && + (scene._bgShapes[_bgFound]._defaultCommand || + !scene._bgShapes[_bgFound]._description.empty())) { + // If there is no default command, set it to Look + if (scene._bgShapes[_bgFound]._defaultCommand) + _menuMode = (MenuMode)scene._bgShapes[_bgFound]._defaultCommand; + else + _menuMode = LOOK_MODE; + events._released = true; + events._pressed = events._oldButtons = false; + _help = _oldHelp = -1; + + if (_menuMode == LOOK_MODE) { + // Set the flag to tell the game that this was a right-click + // call to look and should exit without the look button being pressed + _lookHelp = true; + } + } else { + _help = -1; + } + + // Check if highlighting a different button than last time + if (_help != _oldHelp) { + // If another button was highlighted previously, restore it + if (_oldHelp != -1) + restoreButton(_oldHelp); + + // If we're highlighting a new button, then draw it pressed + if (_help != -1) + depressButton(_help); + + _oldHelp = _help; + } + + if (_bgFound != _oldBgFound || _oldBgFound == -1) { + _infoFlag = true; + clearInfo(); + + if (_help != -1 && !scene._bgShapes[_bgFound]._description.empty() + && scene._bgShapes[_bgFound]._description[0] != ' ') + screen.print(Common::Point(0, INFO_LINE + 1), + INFO_FOREGROUND, "%s", scene._bgShapes[_bgFound]._description.c_str()); + + _oldBgFound = _bgFound; + } + } else { + // We're not in STD_MODE + // If there isn't a window open, then revert back to STD_MODE + if (!_windowOpen && events._rightReleased) { + // Restore all buttons + for (int idx = 0; idx < 12; ++idx) + restoreButton(idx); + + _menuMode = STD_MODE; + _key = _oldKey = -1; + _temp = _oldTemp = _lookHelp = _invLookFlag = 0; + events.clearEvents(); + } + } + } + } + + // Reset the old bgshape number if the mouse button is released, so that + // it can e re-highlighted when we come back here + if ((events._rightReleased && _helpStyle) || (events._released && !_helpStyle)) + _oldBgFound = -1; + + // Do routines that should be done before input processing + switch (_menuMode) { + case LOOK_MODE: + if (!_windowOpen) { + if (events._released && _bgFound >= 0 && _bgFound < 1000) { + if (!scene._bgShapes[_bgFound]._examine.empty()) + examine(); + } else { + lookScreen(pt); + } + } + break; + + case MOVE_MODE: + case OPEN_MODE: + case CLOSE_MODE: + case PICKUP_MODE: + lookScreen(pt); + break; + + case TALK_MODE: + if (!_windowOpen) { + bool personFound; + + if (_bgFound >= 1000) { + personFound = false; + if (!events._released) + lookScreen(pt); + } else { + personFound = _bgFound != -1 && scene._bgShapes[_bgFound]._aType == PERSON; + } + + if (events._released && personFound) + talk.talk(_bgFound); + else if (personFound) + lookScreen(pt); + else if (_bgFound < 1000) + clearInfo(); + } + break; + + case USE_MODE: + case GIVE_MODE: + case INV_MODE: + if (inv._invMode == INVMODE_LOOK || inv._invMode == INVMODE_USE || inv._invMode == INVMODE_GIVE) { + if (pt.y > CONTROLS_Y) + lookInv(); + else + lookScreen(pt); + } + break; + + default: + break; + } + + // + // Do input processing + // + if (events._pressed || events._released || events._rightPressed || _keyPress || _pause) { + if (((events._released && (_helpStyle || _help == -1)) || (events._rightReleased && !_helpStyle)) && + (pt.y <= CONTROLS_Y) && (_menuMode == STD_MODE)) { + // The mouse was clicked in the playing area with no action buttons down. + // Check if the mouse was clicked in a script zone. If it was, + // then execute the script. Otherwise, walk to the given position + if (scene.checkForZones(pt, SCRIPT_ZONE) != 0 || + scene.checkForZones(pt, NOWALK_ZONE) != 0) { + // Mouse clicked in script zone + events._pressed = events._released = false; + } else { + people._walkDest = pt; + people._allowWalkAbort = false; + people.goAllTheWay(); + } + + if (_oldKey != -1) { + restoreButton(_oldTemp); + _oldKey = -1; + } + } + + // Handle action depending on selected mode + switch (_menuMode) { + case LOOK_MODE: + if (_windowOpen) + doLookControl(); + break; + + case MOVE_MODE: + doMiscControl(ALLOW_MOVE); + break; + + case TALK_MODE: + if (_windowOpen) + doTalkControl(); + break; + + case OPEN_MODE: + doMiscControl(ALLOW_OPEN); + break; + + case CLOSE_MODE: + doMiscControl(ALLOW_CLOSE); + break; + + case PICKUP_MODE: + doPickControl(); + break; + + case USE_MODE: + case GIVE_MODE: + case INV_MODE: + doInvControl(); + break; + + case FILES_MODE: + doEnvControl(); + break; + + default: + break; + } + + // As long as there isn't an open window, do main input processing. + // Windows are opened when in TALK, USE, INV, and GIVE modes + if ((!_windowOpen && !_menuCounter && pt.y > CONTROLS_Y) || + _keyPress) { + if (events._pressed || events._released || _pause || _keyPress) + doMainControl(); + } + + if (pt.y < CONTROLS_Y && events._pressed && _oldTemp != (int)(_menuMode - 1) && _oldKey != -1) + restoreButton(_oldTemp); + } +} + +void ScalpelUserInterface::depressButton(int num) { + Screen &screen = *_vm->_screen; + Common::Point pt(MENU_POINTS[num][0], MENU_POINTS[num][1]); + + ImageFrame &frame = (*_controls)[num]; + screen._backBuffer1.transBlitFrom(frame, pt); + screen.slamArea(pt.x, pt.y, pt.x + frame._width, pt.y + frame._height); +} + +void ScalpelUserInterface::restoreButton(int num) { + Screen &screen = *_vm->_screen; + Common::Point pt(MENU_POINTS[num][0], MENU_POINTS[num][1]); + Graphics::Surface &frame = (*_controls)[num]._frame; + + 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); + + if (!_menuCounter) { + _infoFlag = true; + clearInfo(); + } +} + +void ScalpelUserInterface::pushButton(int num) { + Events &events = *_vm->_events; + _oldKey = -1; + + if (!events._released) { + if (_oldHelp != -1) + restoreButton(_oldHelp); + if (_help != -1) + restoreButton(_help); + + depressButton(num); + events.wait(6); + } + + restoreButton(num); +} + +void ScalpelUserInterface::toggleButton(int num) { + Screen &screen = *_vm->_screen; + + if (_menuMode != (MenuMode)(num + 1)) { + _menuMode = (MenuMode)(num + 1); + _oldKey = COMMANDS[num]; + _oldTemp = num; + + if (_keyboardInput) { + if (_oldHelp != -1 && _oldHelp != num) + restoreButton(_oldHelp); + if (_help != -1 && _help != num) + restoreButton(_help); + + _keyboardInput = false; + + ImageFrame &frame = (*_controls)[num]; + Common::Point pt(MENU_POINTS[num][0], MENU_POINTS[num][1]); + screen._backBuffer1.transBlitFrom(frame, pt); + screen.slamArea(pt.x, pt.y, pt.x + frame._width, pt.y + frame._height); + } + } else { + _menuMode = STD_MODE; + _oldKey = -1; + restoreButton(num); + } +} + +void ScalpelUserInterface::clearInfo() { + if (_infoFlag) { + _vm->_screen->vgaBar(Common::Rect(16, INFO_LINE, SHERLOCK_SCREEN_WIDTH - 19, + INFO_LINE + 10), INFO_BLACK); + _infoFlag = false; + _oldLook = -1; + } +} + +void ScalpelUserInterface::clearWindow() { + if (_windowOpen) { + _vm->_screen->vgaBar(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2, + SHERLOCK_SCREEN_HEIGHT - 2), INV_BACKGROUND); + } +} + +void ScalpelUserInterface::whileMenuCounter() { + if (!(--_menuCounter) || _vm->_events->checkInput()) { + _menuCounter = 0; + _infoFlag = true; + clearInfo(); + } +} + +void ScalpelUserInterface::examine() { + Events &events = *_vm->_events; + Inventory &inv = *_vm->_inventory; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + Talk &talk = *_vm->_talk; + Common::Point pt = events.mousePos(); + + if (pt.y < (CONTROLS_Y + 9)) { + Object &obj = scene._bgShapes[_bgFound]; + + if (obj._lookcAnim != 0) { + int canimSpeed = ((obj._lookcAnim & 0xe0) >> 5) + 1; + scene._cAnimFramePause = obj._lookFrames; + _cAnimStr = obj._examine; + _cNum = (obj._lookcAnim & 0x1f) - 1; + + 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); + } + + if (!talk._talkToAbort) { + _cAnimStr = obj._examine; + if (obj._lookFlag) + _vm->setFlags(obj._lookFlag); + } + } else { + // Looking at an inventory item + _cAnimStr = inv[_selector]._examine; + if (inv[_selector]._lookFlag) + _vm->setFlags(inv[_selector]._lookFlag); + } + + if (_invLookFlag) { + // Don't close the inventory window when starting an examine display, since its + // window will slide up to replace the inventory display + _windowOpen = false; + _menuMode = LOOK_MODE; + } + + if (!talk._talkToAbort) { + if (!scene._cAnimFramePause) + printObjectDesc(_cAnimStr, true); + else + // description was already printed in startCAnimation + scene._cAnimFramePause = 0; + } +} + +void ScalpelUserInterface::lookScreen(const Common::Point &pt) { + Events &events = *_vm->_events; + Inventory &inv = *_vm->_inventory; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + Common::Point mousePos = events.mousePos(); + int temp; + Common::String tempStr; + + // Don't display anything for right button command + if ((events._rightPressed || events._rightPressed) && !events._pressed) + return; + + if (mousePos.y < CONTROLS_Y && (temp = _bgFound) != -1) { + if (temp != _oldLook) { + _infoFlag = true; + clearInfo(); + + if (temp < 1000) + tempStr = scene._bgShapes[temp]._description; + else + tempStr = scene._bgShapes[temp - 1000]._description; + + _infoFlag = true; + clearInfo(); + + // Only print description if there is one + if (!tempStr.empty() && tempStr[0] != ' ') { + // If inventory is active and an item is selected for a Use or Give action + if ((_menuMode == INV_MODE || _menuMode == USE_MODE || _menuMode == GIVE_MODE) && + (inv._invMode == INVMODE_USE || inv._invMode == INVMODE_GIVE)) { + int width1 = 0, width2 = 0; + int x, width; + if (inv._invMode == INVMODE_USE) { + // Using an object + x = width = screen.stringWidth("Use "); + + if (temp < 1000 && scene._bgShapes[temp]._aType != PERSON) + // It's not a person, so make it lowercase + tempStr.setChar(tolower(tempStr[0]), 0); + + x += screen.stringWidth(tempStr); + + // If we're using an inventory object, add in the width + // of the object name and the " on " + if (_selector != -1) { + width1 = screen.stringWidth(inv[_selector]._name); + x += width1; + width2 = screen.stringWidth(" on "); + x += width2; + } + + // If the line will be too long, keep cutting off characters + // until the string will fit + while (x > 280) { + x -= screen.charWidth(tempStr.lastChar()); + tempStr.deleteLastChar(); + } + + int xStart = (SHERLOCK_SCREEN_WIDTH - x) / 2; + screen.print(Common::Point(xStart, INFO_LINE + 1), + INFO_FOREGROUND, "Use "); + + if (_selector != -1) { + screen.print(Common::Point(xStart + width, INFO_LINE + 1), + TALK_FOREGROUND, "%s", inv[_selector]._name.c_str()); + screen.print(Common::Point(xStart + width + width1, INFO_LINE + 1), + INFO_FOREGROUND, " on "); + screen.print(Common::Point(xStart + width + width1 + width2, INFO_LINE + 1), + INFO_FOREGROUND, "%s", tempStr.c_str()); + } else { + screen.print(Common::Point(xStart + width, INFO_LINE + 1), + INFO_FOREGROUND, "%s", tempStr.c_str()); + } + } else if (temp >= 0 && temp < 1000 && _selector != -1 && + scene._bgShapes[temp]._aType == PERSON) { + // Giving an object to a person + width1 = screen.stringWidth(inv[_selector]._name); + x = width = screen.stringWidth("Give "); + x += width1; + width2 = screen.stringWidth(" to "); + x += width2; + x += screen.stringWidth(tempStr); + + // Ensure string will fit on-screen + while (x > 280) { + x -= screen.charWidth(tempStr.lastChar()); + tempStr.deleteLastChar(); + } + + int xStart = (SHERLOCK_SCREEN_WIDTH - x) / 2; + screen.print(Common::Point(xStart, INFO_LINE + 1), + INFO_FOREGROUND, "Give "); + screen.print(Common::Point(xStart + width, INFO_LINE + 1), + TALK_FOREGROUND, "%s", inv[_selector]._name.c_str()); + screen.print(Common::Point(xStart + width + width1, INFO_LINE + 1), + INFO_FOREGROUND, " to "); + screen.print(Common::Point(xStart + width + width1 + width2, INFO_LINE + 1), + INFO_FOREGROUND, "%s", tempStr.c_str()); + } + } else { + screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", tempStr.c_str()); + } + + _infoFlag = true; + _oldLook = temp; + } + } + } else { + clearInfo(); + } +} + +void ScalpelUserInterface::lookInv() { + Events &events = *_vm->_events; + Inventory &inv = *_vm->_inventory; + Screen &screen = *_vm->_screen; + Common::Point mousePos = events.mousePos(); + + if (mousePos.x > 15 && mousePos.x < 314 && mousePos.y > (CONTROLS_Y1 + 11) + && mousePos.y < (SHERLOCK_SCREEN_HEIGHT - 2)) { + int temp = (mousePos.x - 6) / 52 + inv._invIndex; + if (temp < inv._holdings) { + if (temp < inv._holdings) { + clearInfo(); + screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, + "%s", inv[temp]._description.c_str()); + _infoFlag = true; + _oldLook = temp; + } + } else { + clearInfo(); + } + } else { + clearInfo(); + } +} + +void ScalpelUserInterface::doEnvControl() { + Events &events = *_vm->_events; + SaveManager &saves = *_vm->_saves; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + Common::Point mousePos = events.mousePos(); + static const char ENV_COMMANDS[7] = "ELSUDQ"; + + byte color; + + _key = _oldKey = -1; + _keyboardInput = false; + int found = saves.getHighlightedButton(); + + if (events._pressed || events._released) { + events.clearKeyboard(); + + // Check for a filename entry being highlighted + if ((events._pressed || events._released) && mousePos.y > (CONTROLS_Y + 10)) { + int found1 = 0; + for (_selector = 0; (_selector < ONSCREEN_FILES_COUNT) && !found1; ++_selector) + if (mousePos.y > (CONTROLS_Y + 11 + _selector * 10) && mousePos.y < (CONTROLS_Y + 21 + _selector * 10)) + found1 = 1; + + if (_selector + saves._savegameIndex - 1 < MAX_SAVEGAME_SLOTS + (saves._envMode != SAVEMODE_LOAD)) + _selector = _selector + saves._savegameIndex - 1; + else + _selector = -1; + + if (!found1) + _selector = -1; + } + + // Handle selecting buttons, if any + saves.highlightButtons(found); + + if (found == 0 || found == 5) + saves._envMode = SAVEMODE_NONE; + } + + if (_keyPress) { + _key = toupper(_keyPress); + + // Escape _key will close the dialog + if (_key == Common::KEYCODE_ESCAPE) + _key = 'E'; + + if (_key == 'E' || _key == 'L' || _key == 'S' || _key == 'U' || _key == 'D' || _key == 'Q') { + const char *chP = strchr(ENV_COMMANDS, _key); + int btnIndex = !chP ? -1 : chP - ENV_COMMANDS; + saves.highlightButtons(btnIndex); + _keyboardInput = true; + + if (_key == 'E' || _key == 'Q') { + saves._envMode = SAVEMODE_NONE; + } else if (_key >= '1' && _key <= '9') { + _keyboardInput = true; + _selector = _key - '1'; + if (_selector >= MAX_SAVEGAME_SLOTS + (saves._envMode == SAVEMODE_LOAD ? 0 : 1)) + _selector = -1; + + if (saves.checkGameOnScreen(_selector)) + _oldSelector = _selector; + } else { + _selector = -1; + } + } + } + + if (_selector != _oldSelector) { + if (_oldSelector != -1 && _oldSelector >= saves._savegameIndex && _oldSelector < (saves._savegameIndex + ONSCREEN_FILES_COUNT)) { + screen.print(Common::Point(6, CONTROLS_Y + 12 + (_oldSelector - saves._savegameIndex) * 10), + INV_FOREGROUND, "%d.", _oldSelector + 1); + screen.print(Common::Point(24, CONTROLS_Y + 12 + (_oldSelector - saves._savegameIndex) * 10), + INV_FOREGROUND, "%s", saves._savegames[_oldSelector].c_str()); + } + + if (_selector != -1) { + screen.print(Common::Point(6, CONTROLS_Y + 12 + (_selector - saves._savegameIndex) * 10), + TALK_FOREGROUND, "%d.", _selector + 1); + screen.print(Common::Point(24, CONTROLS_Y + 12 + (_selector - saves._savegameIndex) * 10), + TALK_FOREGROUND, "%s", saves._savegames[_selector].c_str()); + } + + _oldSelector = _selector; + } + + if (events._released || _keyboardInput) { + if ((found == 0 && events._released) || _key == 'E') { + banishWindow(); + _windowBounds.top = CONTROLS_Y1; + + events._pressed = events._released = _keyboardInput = false; + _keyPress = '\0'; + } else if ((found == 1 && events._released) || _key == 'L') { + saves._envMode = SAVEMODE_LOAD; + if (_selector != -1) { + saves.loadGame(_selector + 1); + } + } else if ((found == 2 && events._released) || _key == 'S') { + saves._envMode = SAVEMODE_SAVE; + if (_selector != -1) { + if (saves.checkGameOnScreen(_selector)) + _oldSelector = _selector; + + if (saves.promptForDescription(_selector)) { + saves.saveGame(_selector + 1, saves._savegames[_selector]); + + banishWindow(1); + _windowBounds.top = CONTROLS_Y1; + _key = _oldKey = -1; + _keyPress = '\0'; + _keyboardInput = false; + } else { + if (!talk._talkToAbort) { + screen._backBuffer1.fillRect(Common::Rect(6, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10, + SHERLOCK_SCREEN_WIDTH - 2, CONTROLS_Y + 20 + (_selector - saves._savegameIndex) * 10), INV_BACKGROUND); + screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10), INV_FOREGROUND, + "%d.", _selector + 1); + screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10), INV_FOREGROUND, + "%s", saves._savegames[_selector].c_str()); + + screen.slamArea(6, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10, 311, 10); + _selector = _oldSelector = -1; + } + } + } + } else if (((found == 3 && events._released) || _key == 'U') && saves._savegameIndex) { + bool moreKeys; + do { + saves._savegameIndex--; + screen._backBuffer1.fillRect(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2, + SHERLOCK_SCREEN_HEIGHT - 1), INV_BACKGROUND); + + for (int idx = saves._savegameIndex; idx < (saves._savegameIndex + ONSCREEN_FILES_COUNT); ++idx) { + color = INV_FOREGROUND; + if (idx == _selector && idx >= saves._savegameIndex && idx < (saves._savegameIndex + ONSCREEN_FILES_COUNT)) + color = TALK_FOREGROUND; + + screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (idx - saves._savegameIndex) * 10), color, "%d.", idx + 1); + screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (idx - saves._savegameIndex) * 10), color, "%s", saves._savegames[idx].c_str()); + } + + screen.slamRect(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2, SHERLOCK_SCREEN_HEIGHT)); + + color = !saves._savegameIndex ? COMMAND_NULL : COMMAND_FOREGROUND; + screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), color, true, "Up"); + color = (saves._savegameIndex == MAX_SAVEGAME_SLOTS - ONSCREEN_FILES_COUNT) ? COMMAND_NULL : COMMAND_FOREGROUND; + screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), color, true, "Down"); + + // Check whether there are more pending U keys pressed + moreKeys = false; + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + + _key = toupper(keyState.keycode); + moreKeys = _key == 'U'; + } + } while ((saves._savegameIndex) && moreKeys); + } else if (((found == 4 && events._released) || _key == 'D') && saves._savegameIndex < (MAX_SAVEGAME_SLOTS - ONSCREEN_FILES_COUNT)) { + bool moreKeys; + do { + saves._savegameIndex++; + screen._backBuffer1.fillRect(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2, + SHERLOCK_SCREEN_HEIGHT - 1), INV_BACKGROUND); + + for (int idx = saves._savegameIndex; idx < (saves._savegameIndex + ONSCREEN_FILES_COUNT); ++idx) { + if (idx == _selector && idx >= saves._savegameIndex && idx < (saves._savegameIndex + ONSCREEN_FILES_COUNT)) + color = TALK_FOREGROUND; + else + color = INV_FOREGROUND; + + screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (idx - saves._savegameIndex) * 10), color, + "%d.", idx + 1); + screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (idx - saves._savegameIndex) * 10), color, + "%s", saves._savegames[idx].c_str()); + } + + screen.slamRect(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2, SHERLOCK_SCREEN_HEIGHT)); + + color = (!saves._savegameIndex) ? COMMAND_NULL : COMMAND_FOREGROUND; + screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), color, true, "Up"); + + color = (saves._savegameIndex == MAX_SAVEGAME_SLOTS - ONSCREEN_FILES_COUNT) ? COMMAND_NULL : COMMAND_FOREGROUND; + screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), color, true, "Down"); + + // Check whether there are more pending D keys pressed + moreKeys = false; + if (events.kbHit()) { + Common::KeyState keyState; + _key = toupper(keyState.keycode); + + moreKeys = _key == 'D'; + } + } while (saves._savegameIndex < (MAX_SAVEGAME_SLOTS - ONSCREEN_FILES_COUNT) && moreKeys); + } else if ((found == 5 && events._released) || _key == 'Q') { + clearWindow(); + screen.print(Common::Point(0, CONTROLS_Y + 20), INV_FOREGROUND, "Are you sure you wish to Quit ?"); + screen.vgaBar(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y + 10), BORDER_COLOR); + + screen.makeButton(Common::Rect(112, CONTROLS_Y, 160, CONTROLS_Y + 10), 136 - screen.stringWidth("Yes") / 2, "Yes"); + screen.makeButton(Common::Rect(161, CONTROLS_Y, 209, CONTROLS_Y + 10), 184 - screen.stringWidth("No") / 2, "No"); + screen.slamArea(112, CONTROLS_Y, 97, 10); + + do { + scene.doBgAnim(); + + if (talk._talkToAbort) + return; + + events.pollEventsAndWait(); + events.setButtonState(); + mousePos = events.mousePos(); + + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + _key = toupper(keyState.keycode); + + if (_key == 'X' && (keyState.flags & Common::KBD_ALT) != 0) { + _vm->quitGame(); + events.pollEvents(); + return; + } + + if (_key == Common::KEYCODE_ESCAPE) + _key = 'N'; + + if (_key == Common::KEYCODE_RETURN || _key == ' ') { + events._pressed = false; + events._released = true; + events._oldButtons = 0; + _keyPress = '\0'; + } + } + + if (events._pressed || events._released) { + if (mousePos.x > 112 && mousePos.x < 159 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 9)) + color = COMMAND_HIGHLIGHTED; + else + color = COMMAND_FOREGROUND; + screen.buttonPrint(Common::Point(136, CONTROLS_Y), color, true, "Yes"); + + if (mousePos.x > 161 && mousePos.x < 208 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 9)) + color = COMMAND_HIGHLIGHTED; + else + color = COMMAND_FOREGROUND; + screen.buttonPrint(Common::Point(184, CONTROLS_Y), color, true, "No"); + } + + if (mousePos.x > 112 && mousePos.x < 159 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 9) && events._released) + _key = 'Y'; + + if (mousePos.x > 161 && mousePos.x < 208 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 9) && events._released) + _key = 'N'; + } while (!_vm->shouldQuit() && _key != 'Y' && _key != 'N'); + + if (_key == 'Y') { + _vm->quitGame(); + events.pollEvents(); + return; + } else { + screen.buttonPrint(Common::Point(184, CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "No"); + banishWindow(1); + _windowBounds.top = CONTROLS_Y1; + _key = -1; + } + } else { + if (_selector != -1) { + // Are we already in Load mode? + if (saves._envMode == SAVEMODE_LOAD) { + saves.loadGame(_selector + 1); + } else if (saves._envMode == SAVEMODE_SAVE || saves.isSlotEmpty(_selector)) { + // We're already in save mode, or pointing to an empty save slot + if (saves.checkGameOnScreen(_selector)) + _oldSelector = _selector; + + if (saves.promptForDescription(_selector)) { + saves.saveGame(_selector + 1, saves._savegames[_selector]); + banishWindow(); + _windowBounds.top = CONTROLS_Y1; + _key = _oldKey = -1; + _keyPress = '\0'; + _keyboardInput = false; + } else { + if (!talk._talkToAbort) { + screen._backBuffer1.fillRect(Common::Rect(6, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10, + 317, CONTROLS_Y + 20 + (_selector - saves._savegameIndex) * 10), INV_BACKGROUND); + screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10), + INV_FOREGROUND, "%d.", _selector + 1); + screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10), + INV_FOREGROUND, "%s", saves._savegames[_selector].c_str()); + screen.slamArea(6, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10, 311, 10); + _selector = _oldSelector = -1; + } + } + } + } + } + } +} + +void ScalpelUserInterface::doInvControl() { + Events &events = *_vm->_events; + Inventory &inv = *_vm->_inventory; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + int colors[8]; + Common::Point mousePos = events.mousePos(); + + _key = _oldKey = -1; + _keyboardInput = false; + + // Check whether any inventory slot is highlighted + int found = -1; + Common::fill(&colors[0], &colors[8], (int)COMMAND_FOREGROUND); + for (int idx = 0; idx < 8; ++idx) { + Common::Rect r(INVENTORY_POINTS[idx][0], CONTROLS_Y1, + INVENTORY_POINTS[idx][1], CONTROLS_Y1 + 10); + if (r.contains(mousePos)) { + found = idx; + break; + } + } + + if (events._pressed || events._released) { + events.clearKeyboard(); + + 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"); + + 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"); + inv._invMode = (InvMode)found; + _selector = -1; + } + + if (inv._invIndex) { + screen.print(Common::Point(INVENTORY_POINTS[4][2], CONTROLS_Y1 + 1), colors[4], "^^"); + screen.print(Common::Point(INVENTORY_POINTS[5][2], CONTROLS_Y1 + 1), colors[5], "^"); + } + + if ((inv._holdings - inv._invIndex) > 6) { + screen.print(Common::Point(INVENTORY_POINTS[6][2], CONTROLS_Y1 + 1), colors[6], "_"); + screen.print(Common::Point(INVENTORY_POINTS[7][2], CONTROLS_Y1 + 1), colors[7], "__"); + } + + bool flag = false; + if (inv._invMode == INVMODE_LOOK || inv._invMode == INVMODE_USE || inv._invMode == INVMODE_GIVE) { + Common::Rect r(15, CONTROLS_Y1 + 11, 314, SHERLOCK_SCREEN_HEIGHT - 2); + if (r.contains(mousePos)) { + _selector = (mousePos.x - 6) / 52 + inv._invIndex; + if (_selector < inv._holdings) + flag = true; + } + } + + if (!flag && mousePos.y >(CONTROLS_Y1 + 11)) + _selector = -1; + } + + if (_keyPress) { + _key = toupper(_keyPress); + + if (_key == Common::KEYCODE_ESCAPE) + // Escape will also 'E'xit out of inventory display + _key = 'E'; + + if (_key == 'E' || _key == 'L' || _key == 'U' || _key == 'G' + || _key == '-' || _key == '+') { + InvMode temp = inv._invMode; + + const char *chP = strchr(INVENTORY_COMMANDS, _key); + inv._invMode = !chP ? INVMODE_INVALID : (InvMode)(chP - INVENTORY_COMMANDS); + inv.invCommands(true); + + inv._invMode = temp; + _keyboardInput = true; + if (_key == 'E') + inv._invMode = INVMODE_EXIT; + _selector = -1; + } else { + _selector = -1; + } + } + + if (_selector != _oldSelector) { + if (_oldSelector != -1) { + // Un-highlight + if (_oldSelector >= inv._invIndex && _oldSelector < (inv._invIndex + 6)) + inv.highlight(_oldSelector, BUTTON_MIDDLE); + } + + if (_selector != -1) + inv.highlight(_selector, 235); + + _oldSelector = _selector; + } + + if (events._released || _keyboardInput) { + if ((found == 0 && events._released) || _key == 'E') { + inv.freeInv(); + _infoFlag = true; + clearInfo(); + banishWindow(false); + _key = -1; + events.clearEvents(); + events.setCursor(ARROW); + } else if ((found == 1 && events._released) || (_key == 'L')) { + inv._invMode = INVMODE_LOOK; + } else if ((found == 2 && events._released) || (_key == 'U')) { + inv._invMode = INVMODE_USE; + } else if ((found == 3 && events._released) || (_key == 'G')) { + inv._invMode = INVMODE_GIVE; + } else if (((found == 4 && events._released) || _key == ',') && inv._invIndex) { + if (inv._invIndex >= 6) + inv._invIndex -= 6; + else + inv._invIndex = 0; + + screen.print(Common::Point(INVENTORY_POINTS[4][2], CONTROLS_Y1 + 1), + COMMAND_HIGHLIGHTED, "^^"); + inv.freeGraphics(); + inv.loadGraphics(); + inv.putInv(SLAM_DISPLAY); + inv.invCommands(true); + } else if (((found == 5 && events._released) || _key == '-') && inv._invIndex > 0) { + --inv._invIndex; + screen.print(Common::Point(INVENTORY_POINTS[4][2], CONTROLS_Y1 + 1), COMMAND_HIGHLIGHTED, "^"); + inv.freeGraphics(); + inv.loadGraphics(); + inv.putInv(SLAM_DISPLAY); + inv.invCommands(true); + } else if (((found == 6 && events._released) || _key == '+') && (inv._holdings - inv._invIndex) > 6) { + ++inv._invIndex; + screen.print(Common::Point(INVENTORY_POINTS[6][2], CONTROLS_Y1 + 1), COMMAND_HIGHLIGHTED, "_"); + inv.freeGraphics(); + inv.loadGraphics(); + inv.putInv(SLAM_DISPLAY); + inv.invCommands(true); + } else if (((found == 7 && events._released) || _key == '.') && (inv._holdings - inv._invIndex) > 6) { + inv._invIndex += 6; + if ((inv._holdings - 6) < inv._invIndex) + inv._invIndex = inv._holdings - 6; + + screen.print(Common::Point(INVENTORY_POINTS[7][2], CONTROLS_Y1 + 1), COMMAND_HIGHLIGHTED, "_"); + inv.freeGraphics(); + inv.loadGraphics(); + inv.putInv(SLAM_DISPLAY); + inv.invCommands(true); + } else { + // If something is being given, make sure it's being given to a person + if (inv._invMode == INVMODE_GIVE) { + if (_bgFound != -1 && scene._bgShapes[_bgFound]._aType == PERSON) + _find = _bgFound; + else + _find = -1; + } else { + _find = _bgFound; + } + + if ((mousePos.y < CONTROLS_Y1) && (inv._invMode == INVMODE_LOOK) && (_find >= 0) && (_find < 1000)) { + if (!scene._bgShapes[_find]._examine.empty() && + scene._bgShapes[_find]._examine[0] >= ' ') + inv.refreshInv(); + } else if (_selector != -1 || _find >= 0) { + // Selector is the inventory object that was clicked on, or selected. + // If it's -1, then no inventory item is highlighted yet. Otherwise, + // an object in the scene has been clicked. + + if (_selector != -1 && inv._invMode == INVMODE_LOOK + && mousePos.y >(CONTROLS_Y1 + 11)) + inv.refreshInv(); + + if (talk._talkToAbort) + return; + + // Now check for the Use and Give actions. If inv_mode is INVMODE_GIVE, + // that means GIVE is in effect, _selector is the object being + // given, and _find is the target. + // The same applies to USE, except if _selector is -1, then USE + // is being tried on an object in the scene without an inventory + // object being highlighted first. + + if ((inv._invMode == INVMODE_USE || (_selector != -1 && inv._invMode == INVMODE_GIVE)) && _find >= 0) { + events._pressed = events._released = false; + _infoFlag = true; + clearInfo(); + + int tempSel = _selector; // Save the selector + _selector = -1; + + inv.putInv(SLAM_DISPLAY); + _selector = tempSel; // Restore it + InvMode tempMode = inv._invMode; + inv._invMode = INVMODE_USE55; + inv.invCommands(true); + + _infoFlag = true; + clearInfo(); + banishWindow(false); + _key = -1; + + inv.freeInv(); + + 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); + else + // Now inv object has been highlighted + checkUseAction(&scene._bgShapes[_find]._use[0], "*SELF*", MUSE, _find, giveFl); + + _selector = _oldSelector = -1; + } + } + } + } +} + +void ScalpelUserInterface::doLookControl() { + Events &events = *_vm->_events; + Inventory &inv = *_vm->_inventory; + Screen &screen = *_vm->_screen; + + _key = _oldKey = -1; + _keyboardInput = (_keyPress != '\0'); + + if (events._released || events._rightReleased || _keyboardInput) { + // Is an inventory object being looked at? + if (!_invLookFlag) { + // Is there any remaining text to display? + if (!_descStr.empty()) { + printObjectDesc(_descStr, false); + } else if (!_lookHelp) { + // Need to close the window and depress the Look button + Common::Point pt(MENU_POINTS[0][0], MENU_POINTS[0][1]); + screen._backBuffer2.blitFrom((*_controls)[0], pt); + banishWindow(true); + + _windowBounds.top = CONTROLS_Y1; + _key = _oldKey = COMMANDS[LOOK_MODE - 1]; + _temp = _oldTemp = 0; + _menuMode = LOOK_MODE; + events.clearEvents(); + + // Restore UI + drawInterface(); + } else { + events.setCursor(ARROW); + banishWindow(true); + _windowBounds.top = CONTROLS_Y1; + _key = _oldKey = -1; + _temp = _oldTemp = 0; + _menuMode = STD_MODE; + events.clearEvents(); + } + } else { + // Looking at an inventory object + // Backup the user interface + Surface tempSurface(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT - CONTROLS_Y1); + tempSurface.blitFrom(screen._backBuffer2, Common::Point(0, 0), + Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + + inv.drawInventory(INVENTORY_DONT_DISPLAY); + banishWindow(true); + + // Restore the ui + screen._backBuffer2.blitFrom(tempSurface, Common::Point(0, CONTROLS_Y1)); + + _windowBounds.top = CONTROLS_Y1; + _key = _oldKey = COMMANDS[LOOK_MODE - 1]; + _temp = _oldTemp = 0; + events.clearEvents(); + _invLookFlag = false; + _menuMode = INV_MODE; + _windowOpen = true; + } + } +} + +void ScalpelUserInterface::doMainControl() { + Events &events = *_vm->_events; + Inventory &inv = *_vm->_inventory; + SaveManager &saves = *_vm->_saves; + Common::Point pt = events.mousePos(); + + if ((events._pressed || events._released) && pt.y > CONTROLS_Y) { + events.clearKeyboard(); + _key = -1; + + // Check whether the mouse is in any of the command areas + 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 (r.contains(pt)) + _key = COMMANDS[_temp]; + } + --_temp; + } else if (_keyPress) { + // Keyboard control + _keyboardInput = true; + + if (_keyPress >= 'A' && _keyPress <= 'Z') { + const char *c = strchr(COMMANDS, _keyPress); + _temp = !c ? 12 : c - COMMANDS; + } else { + _temp = 12; + } + + if (_temp == 12) + _key = -1; + + if (events._rightPressed) { + _temp = 12; + _key = -1; + } + } else if (!events._released) { + _key = -1; + } + + // Check if the button being pointed to has changed + if (_oldKey != _key && !_windowOpen) { + // Clear the info line + _infoFlag = true; + clearInfo(); + + // If there was an old button selected, restore it + if (_oldKey != -1) { + _menuMode = STD_MODE; + restoreButton(_oldTemp); + } + + // If a new button is being pointed to, highlight it + if (_key != -1 && _temp < 12 && !_keyboardInput) + depressButton(_temp); + + // Save the new button selection + _oldKey = _key; + _oldTemp = _temp; + } + + if (!events._pressed && !_windowOpen) { + switch (_key) { + case 'L': + toggleButton(0); + break; + case 'M': + toggleButton(1); + break; + case 'T': + toggleButton(2); + break; + case 'P': + toggleButton(3); + break; + case 'O': + toggleButton(4); + break; + case 'C': + toggleButton(5); + break; + case 'I': + pushButton(6); + _selector = _oldSelector = -1; + _menuMode = INV_MODE; + inv.drawInventory(PLAIN_INVENTORY); + break; + case 'U': + pushButton(7); + _selector = _oldSelector = -1; + _menuMode = USE_MODE; + inv.drawInventory(USE_INVENTORY_MODE); + break; + case 'G': + pushButton(8); + _selector = _oldSelector = -1; + _menuMode = GIVE_MODE; + inv.drawInventory(GIVE_INVENTORY_MODE); + break; + case 'J': + pushButton(9); + _menuMode = JOURNAL_MODE; + journalControl(); + break; + case 'F': + pushButton(10); + + // Create a thumbnail of the current screen before the files dialog is shown, in case + // the user saves the game + saves.createThumbnail(); + + _selector = _oldSelector = -1; + + if (_vm->_showOriginalSavesDialog) { + // Show the original dialog + _menuMode = FILES_MODE; + saves.drawInterface(); + _windowOpen = true; + } else { + // Show the ScummVM GMM instead + _vm->_canLoadSave = true; + _vm->openMainMenuDialog(); + _vm->_canLoadSave = false; + } + break; + case 'S': + pushButton(11); + _menuMode = SETUP_MODE; + Settings::show(_vm); + break; + default: + break; + } + + _help = _oldHelp = _oldBgFound = -1; + } +} + +void ScalpelUserInterface::doMiscControl(int allowed) { + Events &events = *_vm->_events; + Scene &scene = *_vm->_scene; + Talk &talk = *_vm->_talk; + + if (events._released) { + _temp = _bgFound; + if (_bgFound != -1) { + // Only allow pointing to objects, not people + if (_bgFound < 1000) { + events.clearEvents(); + Object &obj = scene._bgShapes[_bgFound]; + + switch (allowed) { + case ALLOW_OPEN: + checkAction(obj._aOpen, MOPEN, _temp); + if (_menuMode != TALK_MODE && !talk._talkToAbort) { + _menuMode = STD_MODE; + restoreButton(OPEN_MODE - 1); + _key = _oldKey = -1; + } + break; + + case ALLOW_CLOSE: + checkAction(obj._aClose, MCLOSE, _temp); + if (_menuMode != TALK_MODE && !talk._talkToAbort) { + _menuMode = STD_MODE; + restoreButton(CLOSE_MODE - 1); + _key = _oldKey = -1; + } + break; + + case ALLOW_MOVE: + checkAction(obj._aMove, MMOVE, _temp); + if (_menuMode != TALK_MODE && !talk._talkToAbort) { + _menuMode = STD_MODE; + restoreButton(MOVE_MODE - 1); + _key = _oldKey = -1; + } + break; + + default: + break; + } + } + } + } +} + +void ScalpelUserInterface::doPickControl() { + Events &events = *_vm->_events; + Scene &scene = *_vm->_scene; + Talk &talk = *_vm->_talk; + + if (events._released) { + if ((_temp = _bgFound) != -1) { + events.clearEvents(); + + // Don't allow characters to be picked up + if (_bgFound < 1000) { + scene._bgShapes[_bgFound].pickUpObject(MPICK); + + if (!talk._talkToAbort && _menuMode != TALK_MODE) { + _key = _oldKey = -1; + _menuMode = STD_MODE; + restoreButton(PICKUP_MODE - 1); + } + } + } + } +} + +void ScalpelUserInterface::doTalkControl() { + Events &events = *_vm->_events; + Journal &journal = *_vm->_journal; + People &people = *_vm->_people; + Screen &screen = *_vm->_screen; + Sound &sound = *_vm->_sound; + Talk &talk = *_vm->_talk; + Common::Point mousePos = events.mousePos(); + + _key = _oldKey = -1; + _keyboardInput = false; + + 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"); + else if (_endKeyActive) + screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_FOREGROUND, true, "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"); + else if (talk._moreTalkUp) + screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_FOREGROUND, true, "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"); + else if (talk._moreTalkDown) + screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_FOREGROUND, true, "Down"); + + bool found = false; + for (_selector = talk._talkIndex; _selector < (int)talk._statements.size() && !found; ++_selector) { + if (mousePos.y > talk._statements[_selector]._talkPos.top && + mousePos.y < talk._statements[_selector]._talkPos.bottom) + found = true; + } + --_selector; + if (!found) + _selector = -1; + } + + if (_keyPress) { + _key = toupper(_keyPress); + if (_key == Common::KEYCODE_ESCAPE) + _key = 'E'; + + // Check for number press indicating reply line + if (_key >= '1' && _key <= ('1' + (int)talk._statements.size() - 1)) { + for (uint idx = 0; idx < talk._statements.size(); ++idx) { + if (talk._statements[idx]._talkMap == (_key - '1')) { + // Found the given statement + _selector = idx; + _key = -1; + _keyboardInput = true; + break; + } + } + } else if (_key == 'E' || _key == 'U' || _key == 'D') { + _keyboardInput = true; + } else { + _selector = -1; + } + } + + if (_selector != _oldSelector) { + // Remove highlighting from previous line, if any + if (_oldSelector != -1) { + if (!((talk._talkHistory[talk._converseNum][_oldSelector] >> (_oldSelector & 7)) & 1)) + talk.talkLine(_oldSelector, talk._statements[_oldSelector]._talkMap, INV_FOREGROUND, + talk._statements[_oldSelector]._talkPos.top, true); + else + talk.talkLine(_oldSelector, talk._statements[_oldSelector]._talkMap, TALK_NULL, + talk._statements[_oldSelector]._talkPos.top, true); + } + + // Add highlighting to new line, if any + if (_selector != -1) + talk.talkLine(_selector, talk._statements[_selector]._talkMap, TALK_FOREGROUND, + talk._statements[_selector]._talkPos.top, true); + + _oldSelector = _selector; + } + + if (events._released || _keyboardInput) { + if (((Common::Rect(99, CONTROLS_Y, 138, CONTROLS_Y + 10).contains(mousePos) && events._released) + || _key == 'E') && _endKeyActive) { + talk.freeTalkVars(); + talk.pullSequence(); + banishWindow(); + _windowBounds.top = CONTROLS_Y1; + } else if (((Common::Rect(140, CONTROLS_Y, 179, CONTROLS_Y + 10).contains(mousePos) && events._released) + || _key == 'U') && talk._moreTalkUp) { + while (talk._statements[--talk._talkIndex]._talkMap == -1) + ; + screen._backBuffer1.fillRect(Common::Rect(5, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2, + SHERLOCK_SCREEN_HEIGHT - 1), INV_BACKGROUND); + talk.displayTalk(false); + + screen.slamRect(Common::Rect(5, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH - 5, SHERLOCK_SCREEN_HEIGHT - 2)); + } else if (((Common::Rect(181, CONTROLS_Y, 220, CONTROLS_Y + 10).contains(mousePos) && events._released) + || _key == 'D') && talk._moreTalkDown) { + do { + ++talk._talkIndex; + } while (talk._talkIndex < (int)talk._statements.size() && talk._statements[talk._talkIndex]._talkMap == -1); + + screen._backBuffer1.fillRect(Common::Rect(5, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2, + SHERLOCK_SCREEN_HEIGHT - 1), INV_BACKGROUND); + talk.displayTalk(false); + + 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"); + + // If the reply is new, add it to the journal + if (!talk._talkHistory[talk._converseNum][_selector]) { + journal.record(talk._converseNum, _selector); + + // Add any Holmes point to Holmes' total, if any + if (talk._statements[_selector]._quotient) + people._holmesQuotient += talk._statements[_selector]._quotient; + } + + // Flag the response as having been used + talk._talkHistory[talk._converseNum][_selector] = true; + + clearWindow(); + screen.print(Common::Point(16, CONTROLS_Y + 12), TALK_FOREGROUND, "Sherlock Holmes"); + talk.talkLine(_selector + 128, talk._statements[_selector]._talkMap, COMMAND_FOREGROUND, CONTROLS_Y + 21, true); + + switch (talk._statements[_selector]._portraitSide & 3) { + case 0: + case 1: + people._portraitSide = 20; + break; + case 2: + people._portraitSide = 220; + break; + case 3: + people._portraitSide = 120; + break; + } + + // Check for flipping Holmes + if (talk._statements[_selector]._portraitSide & REVERSE_DIRECTION) + people._holmesFlip = true; + + talk._speaker = 0; + people.setTalking(0); + + if (!talk._statements[_selector]._voiceFile.empty() && sound._voices) { + sound.playSound(talk._statements[_selector]._voiceFile, WAIT_RETURN_IMMEDIATELY); + + // Set voices as an indicator for waiting + sound._voices = 2; + sound._speechOn = *sound._soundIsOn; + } else { + sound._speechOn = false; + } + + talk.waitForMore(talk._statements[_selector]._statement.size()); + if (talk._talkToAbort) + return; + + people.clearTalking(); + if (talk._talkToAbort) + return; + + while (!_vm->shouldQuit()) { + talk._scriptSelect = _selector; + talk._speaker = talk._talkTo; + talk.doScript(talk._statements[_selector]._reply); + + if (!talk._talkToAbort) { + if (!talk._talkStealth) + clearWindow(); + + 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(); + } + + // Check for another linked talk file + Common::String linkFilename = talk._statements[_selector]._linkFile; + if (!linkFilename.empty() && !talk._scriptMoreFlag) { + talk.freeTalkVars(); + talk.loadTalkFile(linkFilename); + + // Find the first new statement + int select = _selector = _oldSelector = -1; + for (uint idx = 0; idx < talk._statements.size() && select == -1; ++idx) { + if (!talk._statements[idx]._talkMap) + select = talk._talkIndex = idx; + } + + // See if the new statement is a stealth reply + talk._talkStealth = talk._statements[select]._statement.hasPrefix("^") ? 2 : 0; + + // Is the new talk file a standard file, reply first file, or a stealth file + if (!talk._statements[select]._statement.hasPrefix("*") && + !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"); + else + screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, true, "Exit"); + + talk.displayTalk(true); + events.setCursor(ARROW); + break; + } else { + _selector = select; + + if (!talk._talkHistory[talk._converseNum][_selector]) + journal.record(talk._converseNum, _selector); + + talk._talkHistory[talk._converseNum][_selector] = true; + } + } else { + talk.freeTalkVars(); + talk.pullSequence(); + banishWindow(); + _windowBounds.top = CONTROLS_Y1; + break; + } + } else { + break; + } + } + + events._pressed = events._released = false; + events._oldButtons = 0; + talk._talkStealth = 0; + + // If a script was pushed onto the script stack, restore it + if (!talk._scriptStack.empty()) { + ScriptStackEntry stackEntry = talk._scriptStack.pop(); + talk._scriptName = stackEntry._name; + talk._scriptSaveIndex = stackEntry._currentIndex; + talk._scriptSelect = stackEntry._select; + } + } + } +} + +void ScalpelUserInterface::journalControl() { + Events &events = *_vm->_events; + Journal &journal = *_vm->_journal; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + bool doneFlag = false; + + // Draw the journal screen + journal.drawInterface(); + + // Handle journal events + do { + _key = -1; + events.setButtonState(); + + // Handle keypresses + if (events.kbHit()) { + Common::KeyState keyState = events.getKey(); + if (keyState.keycode == Common::KEYCODE_x && (keyState.flags & Common::KBD_ALT)) { + _vm->quitGame(); + return; + } else if (keyState.keycode == Common::KEYCODE_e || keyState.keycode == Common::KEYCODE_ESCAPE) { + doneFlag = true; + } else { + _key = toupper(keyState.keycode); + } + } + + if (!doneFlag) + doneFlag = journal.handleEvents(_key); + } while (!_vm->shouldQuit() && !doneFlag); + + // Finish up + _infoFlag = _keyboardInput = false; + _keyPress = '\0'; + _windowOpen = false; + _windowBounds.top = CONTROLS_Y1; + _key = -1; + _menuMode = STD_MODE; + + // Reset the palette + screen.setPalette(screen._cMap); + + screen._backBuffer1.blitFrom(screen._backBuffer2); + scene.updateBackground(); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); +} + +void ScalpelUserInterface::printObjectDesc(const Common::String &str, bool firstTime) { + Events &events = *_vm->_events; + Inventory &inv = *_vm->_inventory; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + + if (str.hasPrefix("_")) { + _lookScriptFlag = true; + events.setCursor(MAGNIFY); + int savedSelector = _selector; + talk.talkTo(str.c_str() + 1); + _lookScriptFlag = false; + + if (talk._talkToAbort) { + events.setCursor(ARROW); + return; + } + + // Check if looking at an inventory object + if (!_invLookFlag) { + // See if this look was called by a right button click or not + if (!_lookHelp) { + // If it wasn't a right button click, then we need depress + // the look button before we close the window. So save a copy of the + // 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]); + + tempSurface.blitFrom(screen._backBuffer2, Common::Point(0, 0), + Common::Rect(pt.x, pt.y, pt.x + tempSurface.w(), pt.y + tempSurface.h())); + screen._backBuffer2.transBlitFrom((*_controls)[0], pt); + + banishWindow(1); + events.setCursor(MAGNIFY); + _windowBounds.top = CONTROLS_Y1; + _key = _oldKey = COMMANDS[LOOK_MODE - 1]; + _temp = _oldTemp = 0; + _menuMode = LOOK_MODE; + events.clearEvents(); + + screen._backBuffer2.blitFrom(tempSurface, pt); + } else { + events.setCursor(ARROW); + banishWindow(true); + _windowBounds.top = CONTROLS_Y1; + _key = _oldKey = -1; + _temp = _oldTemp = 0; + _menuMode = STD_MODE; + _lookHelp = 0; + events.clearEvents(); + } + } else { + // Looking at an inventory object + _selector = _oldSelector = savedSelector; + + // Reload the inventory graphics and draw the inventory + inv.loadInv(); + inv.putInv(SLAM_SECONDARY_BUFFER); + inv.freeInv(); + banishWindow(1); + + _windowBounds.top = CONTROLS_Y1; + _key = _oldKey = COMMANDS[INV_MODE - 1]; + _temp = _oldTemp = 0; + events.clearEvents(); + + _invLookFlag = 0; + _menuMode = INV_MODE; + _windowOpen = true; + } + + return; + } + + Surface &bb = *screen._backBuffer; + if (firstTime) { + // Only draw the border on the first call + _infoFlag = true; + clearInfo(); + + bb.fillRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, + CONTROLS_Y1 + 10), BORDER_COLOR); + bb.fillRect(Common::Rect(0, CONTROLS_Y + 10, 1, SHERLOCK_SCREEN_HEIGHT - 1), + 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, + SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); + } + + // Clear background + bb.fillRect(Common::Rect(2, CONTROLS_Y + 10, SHERLOCK_SCREEN_WIDTH - 2, + SHERLOCK_SCREEN_HEIGHT - 2), INV_BACKGROUND); + + _windowBounds.top = CONTROLS_Y; + events.clearEvents(); + + // Loop through displaying up to five lines + bool endOfStr = false; + const char *msgP = str.c_str(); + for (int lineNum = 0; lineNum < ONSCREEN_FILES_COUNT && !endOfStr; ++lineNum) { + int width = 0; + const char *lineStartP = msgP; + + // Determine how much can be displayed on the line + do { + width += screen.charWidth(*msgP++); + } while (width < 300 && *msgP); + + if (*msgP) + --msgP; + else + endOfStr = true; + + // If the line needs to be wrapped, scan backwards to find + // the end of the previous word as a splitting point + if (width >= 300) { + while (*msgP != ' ') + --msgP; + endOfStr = false; + } + + // Print out the line + Common::String line(lineStartP, msgP); + screen.gPrint(Common::Point(16, CONTROLS_Y + 12 + lineNum * 9), + INV_FOREGROUND, "%s", line.c_str()); + + if (!endOfStr) + // Start next line at start of the nxet word after space + ++msgP; + } + + // Handle display depending on whether all the message was shown + if (!endOfStr) { + screen.makeButton(Common::Rect(46, CONTROLS_Y, 272, CONTROLS_Y + 10), + (SHERLOCK_SCREEN_WIDTH - screen.stringWidth(PRESS_KEY_FOR_MORE)) / 2, + PRESS_KEY_FOR_MORE); + screen.gPrint(Common::Point((SHERLOCK_SCREEN_WIDTH - + screen.stringWidth(PRESS_KEY_FOR_MORE)) / 2, CONTROLS_Y), + COMMAND_FOREGROUND, "P"); + _descStr = msgP; + } else { + screen.makeButton(Common::Rect(46, CONTROLS_Y, 272, CONTROLS_Y + 10), + (SHERLOCK_SCREEN_WIDTH - screen.stringWidth(PRESS_KEY_TO_CONTINUE)) / 2, + PRESS_KEY_TO_CONTINUE); + screen.gPrint(Common::Point((SHERLOCK_SCREEN_WIDTH - + screen.stringWidth(PRESS_KEY_TO_CONTINUE)) / 2, CONTROLS_Y), + COMMAND_FOREGROUND, "P"); + _descStr = ""; + } + + if (firstTime) { + if (!_slideWindows) { + screen.slamRect(Common::Rect(0, CONTROLS_Y, + SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + } else { + // Display the window + summonWindow(); + } + + _selector = _oldSelector = -1; + _windowOpen = true; + } else { + screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, + SHERLOCK_SCREEN_HEIGHT)); + } +} + +void ScalpelUserInterface::printObjectDesc() { + printObjectDesc(_cAnimStr, true); +} + +void ScalpelUserInterface::summonWindow(const Surface &bgSurface, bool slideUp) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + + if (_windowOpen) + // A window is already open, so can't open another one + return; + + if (slideUp) { + // Gradually slide up the display of the window + for (int idx = 1; idx <= bgSurface.h(); idx += 2) { + screen._backBuffer->blitFrom(bgSurface, Common::Point(0, SHERLOCK_SCREEN_HEIGHT - idx), + Common::Rect(0, 0, bgSurface.w(), idx)); + screen.slamRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - idx, + SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + + events.delay(10); + } + } else { + // Gradually slide down the display of the window + for (int idx = 1; idx <= bgSurface.h(); idx += 2) { + screen._backBuffer->blitFrom(bgSurface, + Common::Point(0, SHERLOCK_SCREEN_HEIGHT - bgSurface.h()), + Common::Rect(0, bgSurface.h() - idx, bgSurface.w(), bgSurface.h())); + screen.slamRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - bgSurface.h(), + SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT - bgSurface.h() + idx)); + + events.delay(10); + } + } + + // Final display of the entire window + screen._backBuffer->blitFrom(bgSurface, Common::Point(0, SHERLOCK_SCREEN_HEIGHT - bgSurface.h()), + Common::Rect(0, 0, bgSurface.w(), bgSurface.h())); + screen.slamArea(0, SHERLOCK_SCREEN_HEIGHT - bgSurface.h(), bgSurface.w(), bgSurface.h()); + + _windowOpen = true; +} + +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)); + Common::Rect r(0, height, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + tempSurface.blitFrom(screen._backBuffer1, Common::Point(0, 0), r); + + // Remove drawn window with original user interface + screen._backBuffer1.blitFrom(screen._backBuffer2, + Common::Point(0, height), r); + + // Display the window gradually on-screen + summonWindow(tempSurface, slideUp); +} + +void ScalpelUserInterface::banishWindow(bool slideUp) { + Events &events = *_vm->_events; + Screen &screen = *_vm->_screen; + + if (_windowOpen) { + if (slideUp || !_slideWindows) { + // Slide window down + // Only slide the window if the window style allows it + if (_slideWindows) { + for (int idx = 2; idx < (SHERLOCK_SCREEN_HEIGHT - CONTROLS_Y); idx += 2) { + // Shift the window down by 2 lines + byte *pSrc = (byte *)screen._backBuffer1.getBasePtr(0, CONTROLS_Y + idx - 2); + byte *pSrcEnd = (byte *)screen._backBuffer1.getBasePtr(0, SHERLOCK_SCREEN_HEIGHT - 2); + byte *pDest = (byte *)screen._backBuffer1.getBasePtr(0, SHERLOCK_SCREEN_HEIGHT); + Common::copy_backward(pSrc, pSrcEnd, pDest); + + // Restore lines from the ui in the secondary back buffer + screen._backBuffer1.blitFrom(screen._backBuffer2, + Common::Point(0, CONTROLS_Y), + Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y + idx)); + + screen.slamArea(0, CONTROLS_Y + idx - 2, SHERLOCK_SCREEN_WIDTH, + SHERLOCK_SCREEN_HEIGHT - CONTROLS_Y - idx + 2); + events.delay(10); + } + + // Restore final two old lines + screen._backBuffer1.blitFrom(screen._backBuffer2, + Common::Point(0, SHERLOCK_SCREEN_HEIGHT - 2), + Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - 2, + SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + screen.slamArea(0, SHERLOCK_SCREEN_HEIGHT - 2, SHERLOCK_SCREEN_WIDTH, 2); + } else { + // Restore old area to completely erase window + screen._backBuffer1.blitFrom(screen._backBuffer2, + Common::Point(0, CONTROLS_Y), + Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, + SHERLOCK_SCREEN_HEIGHT)); + } + } else { + // Slide the original user interface up to cover the dialog + for (int idx = 1; idx < (SHERLOCK_SCREEN_HEIGHT - CONTROLS_Y1); idx += 2) { + byte *pSrc = (byte *)screen._backBuffer2.getBasePtr(0, CONTROLS_Y1); + byte *pSrcEnd = (byte *)screen._backBuffer2.getBasePtr(0, CONTROLS_Y1 + idx); + byte *pDest = (byte *)screen._backBuffer1.getBasePtr(0, SHERLOCK_SCREEN_HEIGHT - idx); + Common::copy(pSrc, pSrcEnd, pDest); + + screen.slamArea(0, SHERLOCK_SCREEN_HEIGHT - idx, SHERLOCK_SCREEN_WIDTH, + SHERLOCK_SCREEN_HEIGHT); + events.delay(10); + } + + // Show entire final area + screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(0, CONTROLS_Y1), + Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + screen.slamRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + } + + _infoFlag = false; + _windowOpen = false; + } + + _menuMode = STD_MODE; +} + +void ScalpelUserInterface::checkUseAction(const UseType *use, const Common::String &invName, + const char *const messages[], int objNum, bool giveMode) { + Events &events = *_vm->_events; + Inventory &inv = *_vm->_inventory; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + bool printed = messages == nullptr; + + if (objNum >= 1000) { + // Holmes was specified, so do nothing + _infoFlag = true; + clearInfo(); + _infoFlag = true; + + // Display error message + _menuCounter = 30; + screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "You can't do that to yourself."); + return; + } + + // Scan for target item + int targetNum = -1; + if (giveMode) { + for (int idx = 0; idx < USE_COUNT && targetNum == -1; ++idx) { + if ((use[idx]._target.equalsIgnoreCase("*GIVE*") || use[idx]._target.equalsIgnoreCase("*GIVEP*")) + && use[idx]._names[0].equalsIgnoreCase(invName)) { + // Found a match + targetNum = idx; + if (use[idx]._target.equalsIgnoreCase("*GIVE*")) + inv.deleteItemFromInventory(invName); + } + } + } else { + for (int idx = 0; idx < USE_COUNT && targetNum == -1; ++idx) { + if (use[idx]._target.equalsIgnoreCase(invName)) + targetNum = idx; + } + } + + if (targetNum != -1) { + // Found a target, so do the action + const UseType &action = use[targetNum]; + + events.setCursor(WAIT); + + if (action._useFlag) + _vm->setFlags(action._useFlag); + + if (action._cAnimNum != 99) { + if (action._cAnimNum == 0) + scene.startCAnim(9, action._cAnimSpeed); + else + scene.startCAnim(action._cAnimNum - 1, action._cAnimSpeed); + } + + 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 (!talk._talkToAbort) + printed = true; + } + } + + // Print "Done..." as an ending, unless flagged for leaving scene or otherwise flagged + if (scene._goToScene != 1 && !printed && !talk._talkToAbort) { + _infoFlag = true; + clearInfo(); + screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "Done..."); + _menuCounter = 25; + } + } + } else { + // Couldn't find target, so print error + _infoFlag = true; + clearInfo(); + + if (giveMode) { + screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "No, thank you."); + } else if (messages == nullptr) { + 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]); + } + + _infoFlag = true; + _menuCounter = 30; + } + + 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; + } + } + } + + // Reset cursor back to arrow + events.setCursor(ARROW); +} + +} // End of namespace Scalpel + +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/scalpel_user_interface.h b/engines/sherlock/scalpel/scalpel_user_interface.h new file mode 100644 index 0000000000..8ed69ac8b5 --- /dev/null +++ b/engines/sherlock/scalpel/scalpel_user_interface.h @@ -0,0 +1,227 @@ +/* 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_UI_H +#define SHERLOCK_SCALPEL_UI_H + +#include "common/scummsys.h" +#include "sherlock/user_interface.h" + +namespace Sherlock { + +class Inventory; +class Talk; + +namespace Scalpel { + +extern const char COMMANDS[13]; +extern const int MENU_POINTS[12][4]; + +extern const int INVENTORY_POINTS[8][3]; +extern const char INVENTORY_COMMANDS[9]; +extern const char *const PRESS_KEY_FOR_MORE; +extern const char *const PRESS_KEY_TO_CONTINUE; + +class Settings; + +class ScalpelUserInterface: public UserInterface { + friend class Inventory; + friend class Settings; + friend class Talk; +private: + ImageFile *_controlPanel; + ImageFile *_controls; + char _keyPress; + int _lookHelp; + int _bgFound, _oldBgFound; + int _help, _oldHelp; + char _key, _oldKey; + int _temp, _oldTemp; + int _oldLook; + bool _keyboardInput; + bool _pause; + int _cNum; + Common::String _cAnimStr; + Common::String _descStr; + int _find; + int _oldUse; +private: + /** + * Draws the image for a user interface button in the down/pressed state. + */ + void depressButton(int num); + + /** + * If he mouse button is pressed, then calls depressButton to draw the button + * as pressed; if not, it will show it as released with a call to "restoreButton". + */ + void pushButton(int num); + + /** + * By the time this method has been called, the graphics for the button change + * have already been drawn. This simply takes care of switching the mode around + * accordingly + */ + void toggleButton(int num); + + /** + * Creates a text window and uses it to display the in-depth description + * of the highlighted object + */ + void examine(); + + /** + * Print the name of an object in the scene + */ + void lookScreen(const Common::Point &pt); + + /** + * Gets the item in the inventory the mouse is on and display's it's description + */ + void lookInv(); + + /** + * Handles input when the file list window is being displayed + */ + void doEnvControl(); + + /** + * Handle input whilst the inventory is active + */ + void doInvControl(); + + /** + * Handles waiting whilst an object's description window is open. + */ + void doLookControl(); + + /** + * Handles input until one of the user interface buttons/commands is selected + */ + void doMainControl(); + + /** + * Handles the input for the MOVE, OPEN, and CLOSE commands + */ + void doMiscControl(int allowed); + + /** + * Handles input for picking up items + */ + void doPickControl(); + + /** + * Handles input when in talk mode. It highlights the buttons and available statements, + * and handles allowing the user to click on them + */ + void doTalkControl(); + + /** + * Handles events when the Journal is active. + * @remarks Whilst this would in theory be better in the Journal class, since it displays in + * the user interface, it uses so many internal UI fields, that it sort of made some sense + * to put it in the UserInterface class. + */ + void journalControl(); + + /** + * 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[], + 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); +public: + ScalpelUserInterface(SherlockEngine *vm); + virtual ~ScalpelUserInterface(); + + /** + * Handles counting down whilst checking for input, then clears the info line. + */ + void whileMenuCounter(); + + /** + * Draws the image for the given user interface button in the up + * (not selected) position + */ + void restoreButton(int num); +public: + /** + * Resets the user interface + */ + virtual void reset(); + + /** + * Main input handler for the user interface + */ + virtual void handleInput(); + + /** + * Draw the user interface onto the screen's back buffers + */ + virtual void drawInterface(int bufferNum = 3); + + /** + * Displays a passed window by gradually scrolling it vertically on-screen + */ + virtual void summonWindow(const Surface &bgSurface, bool slideUp = true); + + /** + * Slide the window stored in the back buffer onto the screen + */ + virtual void summonWindow(bool slideUp = true, int height = CONTROLS_Y); + + /** + * Close a currently open window + * @param flag 0 = slide old window down, 1 = slide prior UI back up + */ + virtual void banishWindow(bool slideUp = true); + + /** + * Clears the info line of the screen + */ + virtual void clearInfo(); + + /** + * Clear any active text window + */ + virtual void clearWindow(); + + /** + * Print the previously selected object's decription + */ + virtual void printObjectDesc(); +}; + +} // End of namespace Scalpel + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/settings.cpp b/engines/sherlock/scalpel/settings.cpp index 397d882e0f..aa8033d25e 100644 --- a/engines/sherlock/settings.cpp +++ b/engines/sherlock/scalpel/settings.cpp @@ -21,10 +21,13 @@ */ #include "sherlock/sherlock.h" -#include "sherlock/settings.h" +#include "sherlock/scalpel/settings.h" +#include "sherlock/scalpel/scalpel_user_interface.h" namespace Sherlock { +namespace Scalpel { + static const int SETUP_POINTS[12][4] = { { 4, 154, 101, 53 }, // Exit { 4, 165, 101, 53 }, // Music Toggle @@ -212,9 +215,10 @@ void Settings::show(SherlockEngine *vm) { Sound &sound = *vm->_sound; Music &music = *vm->_music; Talk &talk = *vm->_talk; - UserInterface &ui = *vm->_ui; + ScalpelUserInterface &ui = *(ScalpelUserInterface *)vm->_ui; bool updateConfig = false; + assert(vm->getGameID() == GType_SerratedScalpel); Settings settings(vm); settings.drawInteface(false); @@ -332,4 +336,6 @@ void Settings::show(SherlockEngine *vm) { ui._key = -1; } +} // End of namespace Scalpel + } // End of namespace Sherlock diff --git a/engines/sherlock/settings.h b/engines/sherlock/scalpel/settings.h index fc5fb2959f..ff2e647a62 100644 --- a/engines/sherlock/settings.h +++ b/engines/sherlock/scalpel/settings.h @@ -28,7 +28,8 @@ namespace Sherlock { class SherlockEngine; -class UserInterface; + +namespace Scalpel { class Settings { private: @@ -55,6 +56,8 @@ public: static void show(SherlockEngine *vm); }; +} // End of namespace Scalpel + } // End of namespace Sherlock #endif diff --git a/engines/sherlock/scene.cpp b/engines/sherlock/scene.cpp index 781e596f40..76fb7ae3a7 100644 --- a/engines/sherlock/scene.cpp +++ b/engines/sherlock/scene.cpp @@ -23,6 +23,10 @@ #include "sherlock/scene.h" #include "sherlock/sherlock.h" #include "sherlock/scalpel/scalpel.h" +#include "sherlock/scalpel/scalpel_scene.h" +#include "sherlock/screen.h" +#include "sherlock/tattoo/tattoo.h" +#include "sherlock/tattoo/tattoo_scene.h" namespace Sherlock { @@ -33,13 +37,38 @@ static const int FS_TRANS[8] = { /*----------------------------------------------------------------*/ -void BgFileHeader::load(Common::SeekableReadStream &s) { +BgFileHeader::BgFileHeader() { + _numStructs = -1; + _numImages = -1; + _numcAnimations = -1; + _descSize = -1; + _seqSize = -1; + + // Serrated Scalpel + _fill = -1; + + // Rose Tattoo + _scrollSize = -1; + _bytesWritten = -1; + _fadeStyle = -1; + Common::fill(&_palette[0], &_palette[PALETTE_SIZE], 0); +} + +void BgFileHeader::load(Common::SeekableReadStream &s, bool isRoseTattoo) { _numStructs = s.readUint16LE(); _numImages = s.readUint16LE(); _numcAnimations = s.readUint16LE(); _descSize = s.readUint16LE(); _seqSize = s.readUint16LE(); - _fill = s.readUint16LE(); + + if (isRoseTattoo) { + _scrollSize = s.readUint16LE(); + _bytesWritten = s.readUint32LE(); + _fadeStyle = s.readByte(); + } else { + _fill = s.readUint16LE(); + + } } /*----------------------------------------------------------------*/ @@ -55,18 +84,30 @@ void BgFileHeaderInfo::load(Common::SeekableReadStream &s) { /*----------------------------------------------------------------*/ -void Exit::load(Common::SeekableReadStream &s) { - int xp = s.readSint16LE(); - int yp = s.readSint16LE(); - int xSize = s.readSint16LE(); - int ySize = s.readSint16LE(); - _bounds = Common::Rect(xp, yp, xp + xSize, yp + ySize); +void Exit::load(Common::SeekableReadStream &s, bool isRoseTattoo) { + if (isRoseTattoo) { + char buffer[41]; + s.read(buffer, 41); + _dest = Common::String(buffer); + } + + left = s.readSint16LE(); + top = s.readSint16LE(); + setWidth(s.readUint16LE()); + setHeight(s.readUint16LE()); + _image = isRoseTattoo ? s.readByte() : 0; _scene = s.readSint16LE(); - _allow = s.readSint16LE(); + + if (!isRoseTattoo) + _allow = s.readSint16LE(); + _people.x = s.readSint16LE(); _people.y = s.readSint16LE(); _peopleDir = s.readUint16LE(); + + if (isRoseTattoo) + _allow = s.readSint16LE(); } /*----------------------------------------------------------------*/ @@ -100,7 +141,26 @@ int ObjectArray::indexOf(const Object &obj) const { /*----------------------------------------------------------------*/ -Scene::Scene(SherlockEngine *vm) : _vm(vm) { +void ScaleZone::load(Common::SeekableReadStream &s) { + left = s.readSint16LE(); + top = s.readSint16LE(); + setWidth(s.readUint16LE()); + setHeight(s.readUint16LE()); + + _topNumber = s.readByte(); + _bottomNumber = s.readByte(); +} + +/*----------------------------------------------------------------*/ + +Scene *Scene::init(SherlockEngine *vm) { + if (vm->getGameID() == GType_SerratedScalpel) + return new Scalpel::ScalpelScene(vm); + else + return new Tattoo::TattooScene(vm); +} + +Scene::Scene(SherlockEngine *vm): _vm(vm) { for (int idx = 0; idx < SCENES_COUNT; ++idx) Common::fill(&_sceneStats[idx][0], &_sceneStats[idx][65], false); _currentScene = -1; @@ -115,6 +175,7 @@ Scene::Scene(SherlockEngine *vm) : _vm(vm) { _animating = 0; _doBgAnimDone = true; _tempFadeStyle = 0; + _exitZone = -1; } Scene::~Scene() { @@ -149,8 +210,8 @@ void Scene::selectScene() { _tempFadeStyle = 0; } - people._walkDest = Common::Point(people[AL]._position.x / 100, - people[AL]._position.y / 100); + people._walkDest = Common::Point(people[AL]._position.x / FIXED_INT_MULTIPLIER, + people[AL]._position.y / FIXED_INT_MULTIPLIER); _restoreFlag = true; events.clearEvents(); @@ -193,11 +254,13 @@ 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; - Music &music = *_vm->_music; + Talk &talk = *_vm->_talk; UserInterface &ui = *_vm->_ui; bool flag; @@ -213,8 +276,38 @@ 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); + } + } + } + + 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(); + } + } + } + } + // - // Load background shapes from <filename>.rrm + // Load the room resource file for the scene // Common::String rrmFile = filename + ".rrm"; @@ -223,15 +316,36 @@ bool Scene::loadScene(const Common::String &filename) { Common::SeekableReadStream *rrmStream = _vm->_res->load(rrmFile); rrmStream->seek(39); - _version = rrmStream->readByte(); - _lzwMode = _version == 10; + if (IS_SERRATED_SCALPEL) { + _version = rrmStream->readByte(); + _lzwMode = _version == 10; + } else { + _lzwMode = rrmStream->readByte() > 0; + } // Go to header and read it in rrmStream->seek(rrmStream->readUint32LE()); + BgFileHeader bgHeader; - bgHeader.load(*rrmStream); + bgHeader.load(*rrmStream, IS_ROSE_TATTOO); _invGraphicItems = bgHeader._numImages + 1; + if (IS_ROSE_TATTOO) { + screen.initPaletteFade(bgHeader._bytesWritten); + rrmStream->read(screen._cMap, PALETTE_SIZE); + screen.translatePalette(screen._cMap); + screen.setupBGArea(screen._cMap); + + screen.initScrollVars(); + + // Read in background + if (_lzwMode) { + res.decompress(*rrmStream, (byte *)screen._backBuffer1.getPixels(), SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCREEN_HEIGHT); + } else { + rrmStream->read(screen._backBuffer1.getPixels(), SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCREEN_HEIGHT); + } + } + // Read in the shapes header info Common::Array<BgFileHeaderInfo> bgInfo; bgInfo.resize(bgHeader._numStructs); @@ -240,10 +354,35 @@ bool Scene::loadScene(const Common::String &filename) { bgInfo[idx].load(*rrmStream); // Read information - if (!_lzwMode) { + if (IS_ROSE_TATTOO) { + // Load shapes + Common::SeekableReadStream *infoStream = !_lzwMode ? rrmStream : res.decompress(*rrmStream, bgHeader._numStructs * 625); + _bgShapes.resize(bgHeader._numStructs); for (int idx = 0; idx < bgHeader._numStructs; ++idx) - _bgShapes[idx].load(*rrmStream); + _bgShapes[idx].load(*infoStream, _vm->getGameID() == GType_RoseTattoo); + + if (_lzwMode) + delete infoStream; + + // 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); @@ -255,6 +394,7 @@ bool Scene::loadScene(const Common::String &filename) { rrmStream->read(&_sequenceBuffer[0], bgHeader._seqSize); } } else { + // Serrated Scalpel compressed info Common::SeekableReadStream *infoStream; // Read shapes @@ -262,7 +402,7 @@ bool Scene::loadScene(const Common::String &filename) { _bgShapes.resize(bgHeader._numStructs); for (int idx = 0; idx < bgHeader._numStructs; ++idx) - _bgShapes[idx].load(*infoStream); + _bgShapes[idx].load(*infoStream, false); delete infoStream; @@ -295,7 +435,7 @@ bool Scene::loadScene(const Common::String &filename) { // Read in the image data Common::SeekableReadStream *imageStream = _lzwMode ? - Resources::decompressLZ(*rrmStream, bgInfo[idx]._filesize) : + res.decompress(*rrmStream, bgInfo[idx]._filesize) : rrmStream->readStream(bgInfo[idx]._filesize); _images[idx + 1]._images = new ImageFile(*imageStream); @@ -323,13 +463,14 @@ bool Scene::loadScene(const Common::String &filename) { // Load in cAnim list _cAnim.clear(); if (bgHeader._numcAnimations) { + int animSize = IS_SERRATED_SCALPEL ? 65 : 47; Common::SeekableReadStream *canimStream = _lzwMode ? - Resources::decompressLZ(*rrmStream, 65 * bgHeader._numcAnimations) : - rrmStream->readStream(65 * bgHeader._numcAnimations); + res.decompress(*rrmStream, animSize * bgHeader._numcAnimations) : + rrmStream->readStream(animSize * bgHeader._numcAnimations); _cAnim.resize(bgHeader._numcAnimations); for (uint idx = 0; idx < _cAnim.size(); ++idx) - _cAnim[idx].load(*canimStream); + _cAnim[idx].load(*canimStream, IS_ROSE_TATTOO); delete canimStream; } @@ -337,7 +478,7 @@ bool Scene::loadScene(const Common::String &filename) { // Read in the room bounding areas int size = rrmStream->readUint16LE(); Common::SeekableReadStream *boundsStream = !_lzwMode ? rrmStream : - Resources::decompressLZ(*rrmStream, size); + res.decompress(*rrmStream, size); _zones.resize(size / 10); for (uint idx = 0; idx < _zones.size(); ++idx) { @@ -352,10 +493,11 @@ bool Scene::loadScene(const Common::String &filename) { delete boundsStream; // Ensure we've reached the path version byte - if (rrmStream->readByte() != 254) + if (rrmStream->readByte() != (IS_SERRATED_SCALPEL ? 254 : 251)) error("Invalid scene path data"); // Load the walk directory + 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(); @@ -364,7 +506,7 @@ bool Scene::loadScene(const Common::String &filename) { // Read in the walk data size = rrmStream->readUint16LE(); Common::SeekableReadStream *walkStream = !_lzwMode ? rrmStream : - Resources::decompressLZ(*rrmStream, size); + res.decompress(*rrmStream, size); _walkData.resize(size); walkStream->read(&_walkData[0], size); @@ -372,15 +514,27 @@ bool Scene::loadScene(const Common::String &filename) { if (_lzwMode) delete walkStream; + 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); + } + // Read in the exits + _exitZone = -1; int numExits = rrmStream->readByte(); _exits.resize(numExits); for (int idx = 0; idx < numExits; ++idx) - _exits[idx].load(*rrmStream); + _exits[idx].load(*rrmStream, IS_ROSE_TATTOO); - // Read in the entrance - _entrance.load(*rrmStream); + if (IS_SERRATED_SCALPEL) + // Read in the entrance + _entrance.load(*rrmStream); // Initialize sound list int numSounds = rrmStream->readByte(); @@ -389,24 +543,32 @@ bool Scene::loadScene(const Common::String &filename) { for (int idx = 0; idx < numSounds; ++idx) _sounds[idx].load(*rrmStream); - for (int idx = 0; idx < numSounds; ++idx) - sound.loadSound(_sounds[idx]._name, _sounds[idx]._priority); - - // Read in palette - rrmStream->read(screen._cMap, PALETTE_SIZE); - for (int idx = 0; idx < PALETTE_SIZE; ++idx) - screen._cMap[idx] = VGA_COLOR_TRANS(screen._cMap[idx]); + loadSceneSounds(); - Common::copy(screen._cMap, screen._cMap + PALETTE_SIZE, screen._sMap); + 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 = !_lzwMode ? rrmStream : - Resources::decompressLZ(*rrmStream, SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCENE_HEIGHT); + // Read in the background + Common::SeekableReadStream *bgStream = !_lzwMode ? rrmStream : + res.decompress(*rrmStream, SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCENE_HEIGHT); - bgStream->read(screen._backBuffer1.getPixels(), SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCENE_HEIGHT); + bgStream->read(screen._backBuffer1.getPixels(), SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCENE_HEIGHT); - if (_lzwMode) - delete bgStream; + if (_lzwMode) + delete bgStream; + } // Backup the image and set the palette screen._backBuffer2.blitFrom(screen._backBuffer1); @@ -415,7 +577,7 @@ bool Scene::loadScene(const Common::String &filename) { delete rrmStream; } - // Clear user interface area and draw controls + // Handle drawing any on-screen interface ui.drawInterface(); checkSceneStatus(); @@ -447,7 +609,7 @@ bool Scene::loadScene(const Common::String &filename) { checkInventory(); // Handle starting any music for the scene - if (music._musicOn && music.loadSong(_currentScene)) + if (IS_SERRATED_SCALPEL && music._musicOn && music.loadSong(_currentScene)) music.startSong(); // Load walking images if not already loaded @@ -463,14 +625,25 @@ bool Scene::loadScene(const Common::String &filename) { if (!_vm->isDemo()) { // Reset the previous map location and position on overhead map map._oldCharPoint = _currentScene; - map._overPos.x = map[_currentScene].x * 100 - 600; - map._overPos.y = map[_currentScene].y * 100 + 900; + + 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; } +void Scene::loadSceneSounds() { + Sound &sound = *_vm->_sound; + + for (uint idx = 0; idx < _sounds.size(); ++idx) + sound.loadSound(_sounds[idx]._name, _sounds[idx]._priority); +} + void Scene::checkSceneStatus() { if (_sceneStats[_currentScene][64]) { for (uint idx = 0; idx < 64; ++idx) { @@ -597,8 +770,8 @@ void Scene::transitionToScene() { // Otherwise, this is a linked scene or entrance info, and must be translated if (hSavedFacing < 8 && !saves._justLoaded) { hSavedFacing = FS_TRANS[hSavedFacing]; - hSavedPos.x *= 100; - hSavedPos.y *= 100; + hSavedPos.x *= FIXED_INT_MULTIPLIER; + hSavedPos.y *= FIXED_INT_MULTIPLIER; } } @@ -638,7 +811,8 @@ void Scene::transitionToScene() { } if (Common::Rect(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y).contains( - Common::Point(people[PLAYER]._position.x / 100, people[PLAYER]._position.y / 100))) { + Common::Point(people[PLAYER]._position.x / FIXED_INT_MULTIPLIER, + people[PLAYER]._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 @@ -669,11 +843,14 @@ void Scene::transitionToScene() { updateBackground(); + // Actually do the transition if (screen._fadeStyle) screen.randomTransition(); else screen.blitFrom(screen._backBuffer1); + screen.update(); + // Start any initial animation for the scene if (cAnimNum != -1) { CAnim &c = _cAnim[cAnimNum]; Common::Point pt = c._goto; @@ -701,19 +878,26 @@ int Scene::toggleObject(const Common::String &name) { void Scene::updateBackground() { People &people = *_vm->_people; - Screen &screen = *_vm->_screen; - Sprite &player = people[AL]; - - // Restrict drawing window - screen.setDisplayBounds(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT)); // Update Holmes if he's turned on - if (people._holmesOn) - player.adjustSprite(); + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { + if (people[idx]._type == CHARACTER) + people[idx].adjustSprite(); + } // Flag the bg shapes which need to be redrawn - checkBgShapes(player._imageFrame, Common::Point(player._position.x / 100, - player._position.y / 100)); + checkBgShapes(); + + // Draw the shapes for the scene + 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) { @@ -725,7 +909,7 @@ void Scene::updateBackground() { 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); + _canimShapes[idx]._position, _canimShapes[idx]._flags & OBJ_FLIPPED); } // Draw all active shapes which are normal and behind the person @@ -738,33 +922,37 @@ void Scene::updateBackground() { 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); + _canimShapes[idx]._flags & OBJ_FLIPPED); } - // Draw the player if he's active - if (player._type == CHARACTER && people.isHolmesActive()) { - bool flipped = player._sequenceNumber == WALK_LEFT || player._sequenceNumber == STOP_LEFT || - player._sequenceNumber == WALK_UPLEFT || player._sequenceNumber == STOP_UPLEFT || - player._sequenceNumber == WALK_DOWNRIGHT || player._sequenceNumber == STOP_DOWNRIGHT; - - screen._backBuffer->transBlitFrom(*player._imageFrame, Common::Point(player._position.x / 100, - player._position.y / 100 - player.frameHeight()), 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) + _bgShapes[idx]._misc == NORMAL_FORWARD) screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, - _bgShapes[idx]._flags & OBJ_FLIPPED); + _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) + _canimShapes[idx]._misc == NORMAL_FORWARD) screen._backBuffer->transBlitFrom(*_canimShapes[idx]._imageFrame, _canimShapes[idx]._position, - _canimShapes[idx]._flags & OBJ_FLIPPED); + _canimShapes[idx]._flags & OBJ_FLIPPED); } // Draw all static and active shapes that are FORWARD @@ -774,9 +962,9 @@ void Scene::updateBackground() { _bgShapes[idx].frameHeight()); if ((_bgShapes[idx]._type == ACTIVE_BG_SHAPE || _bgShapes[idx]._type == STATIC_BG_SHAPE) && - _bgShapes[idx]._misc == FORWARD) + _bgShapes[idx]._misc == FORWARD) screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, - _bgShapes[idx]._flags & OBJ_FLIPPED); + _bgShapes[idx]._flags & OBJ_FLIPPED); } // Draw all static and active canimations that are forward @@ -784,7 +972,7 @@ void Scene::updateBackground() { 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); + _canimShapes[idx]._flags & OBJ_FLIPPED); } screen.resetDisplayBounds(); @@ -792,47 +980,13 @@ void Scene::updateBackground() { Exit *Scene::checkForExit(const Common::Rect &r) { for (uint idx = 0; idx < _exits.size(); ++idx) { - if (_exits[idx]._bounds.intersects(r)) + if (_exits[idx].intersects(r)) return &_exits[idx]; } return nullptr; } -void Scene::checkBgShapes(ImageFrame *frame, const Common::Point &pt) { - // Iterate through the shapes - for (uint idx = 0; idx < _bgShapes.size(); ++idx) { - Object &obj = _bgShapes[idx]; - if (obj._type == STATIC_BG_SHAPE || obj._type == ACTIVE_BG_SHAPE) { - if ((obj._flags & 5) == 1) { - obj._misc = (pt.y < (obj._position.y + obj.frameHeight() - 1)) ? - NORMAL_FORWARD : NORMAL_BEHIND; - } else if (!(obj._flags & OBJ_BEHIND)) { - obj._misc = BEHIND; - } else if (obj._flags & OBJ_FORWARD) { - obj._misc = FORWARD; - } - } - } - - // Iterate through the canimshapes - for (uint idx = 0; idx < _canimShapes.size(); ++idx) { - Object &obj = _canimShapes[idx]; - if (obj._type == STATIC_BG_SHAPE || obj._type == ACTIVE_BG_SHAPE) { - if ((obj._flags & 5) == 1) { - obj._misc = (pt.y < (obj._position.y + obj._imageFrame->_frame.h - 1)) ? - NORMAL_FORWARD : NORMAL_BEHIND; - } - else if (!(obj._flags & 1)) { - obj._misc = BEHIND; - } - else if (obj._flags & 4) { - obj._misc = FORWARD; - } - } - } -} - int Scene::startCAnim(int cAnimNum, int playRate) { Events &events = *_vm->_events; Map &map = *_vm->_map; @@ -1045,333 +1199,6 @@ int Scene::startCAnim(int cAnimNum, int playRate) { return 1; } -void Scene::doBgAnim() { - Events &events = *_vm->_events; - Inventory &inv = *_vm->_inventory; - People &people = *_vm->_people; - Screen &screen = *_vm->_screen; - Sound &sound = *_vm->_sound; - Talk &talk = *_vm->_talk; - UserInterface &ui = *_vm->_ui; - - screen.setDisplayBounds(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT)); - - int cursorId = events.getCursor(); - Common::Point mousePos = events.mousePos(); - - talk._talkToAbort = false; - - // Animate the mouse cursor - if (cursorId >= WAIT) { - if (++cursorId > (WAIT + 2)) - cursorId = WAIT; - - events.setCursor((CursorId)cursorId); - } - - if (ui._menuMode == LOOK_MODE) { - if (mousePos.y > CONTROLS_Y1) - events.setCursor(ARROW); - else if (mousePos.y < CONTROLS_Y) - events.setCursor(MAGNIFY); - } - - // Check for setting magnifying glass cursor - if (ui._menuMode == INV_MODE || ui._menuMode == USE_MODE || ui._menuMode == GIVE_MODE) { - if (inv._invMode == INVMODE_LOOK) { - // Only show Magnifying glass cursor if it's not on the inventory command line - if (mousePos.y < CONTROLS_Y || mousePos.y >(CONTROLS_Y1 + 13)) - events.setCursor(MAGNIFY); - else - events.setCursor(ARROW); - } else { - events.setCursor(ARROW); - } - } - - if (sound._diskSoundPlaying && !*sound._soundIsOn) { - // Loaded sound just finished playing - sound.freeDigiSound(); - } - - if (_restoreFlag) { - if (people[AL]._type == CHARACTER) - people[AL].checkSprite(); - - for (uint idx = 0; idx < _bgShapes.size(); ++idx) { - if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE) - _bgShapes[idx].checkObject(); - } - - if (people._portraitLoaded && people._portrait._type == ACTIVE_BG_SHAPE) - people._portrait.checkObject(); - - for (uint idx = 0; idx < _canimShapes.size(); ++idx) { - if (_canimShapes[idx]._type != INVALID && _canimShapes[idx]._type != REMOVE) - _canimShapes[idx].checkObject(); - } - - if (_currentScene == 12 && _vm->getGameID() == GType_SerratedScalpel) - ((Scalpel::ScalpelEngine *)_vm)->eraseMirror12(); - - // 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::Point pt(bounds.left, bounds.top); - - if (people[AL]._type == CHARACTER) - screen.restoreBackground(bounds); - else if (people[AL]._type == REMOVE) - screen._backBuffer->blitFrom(screen._backBuffer2, pt, bounds); - - for (uint idx = 0; idx < _bgShapes.size(); ++idx) { - Object &o = _bgShapes[idx]; - if (o._type == ACTIVE_BG_SHAPE || o._type == HIDE_SHAPE || o._type == REMOVE) - screen.restoreBackground(o.getOldBounds()); - } - - if (people._portraitLoaded) - screen.restoreBackground(Common::Rect( - people._portrait._oldPosition.x, people._portrait._oldPosition.y, - people._portrait._oldPosition.x + people._portrait._oldSize.x, - people._portrait._oldPosition.y + people._portrait._oldSize.y - )); - - for (uint idx = 0; idx < _bgShapes.size(); ++idx) { - Object &o = _bgShapes[idx]; - if (o._type == NO_SHAPE && ((o._flags & OBJ_BEHIND) == 0)) { - // Restore screen area - screen._backBuffer->blitFrom(screen._backBuffer2, o._position, - Common::Rect(o._position.x, o._position.y, - o._position.x + o._noShapeSize.x, o._position.y + o._noShapeSize.y)); - - o._oldPosition = o._position; - o._oldSize = o._noShapeSize; - } - } - - for (uint idx = 0; idx < _canimShapes.size(); ++idx) { - Object &o = _canimShapes[idx]; - if (o._type == ACTIVE_BG_SHAPE || o._type == HIDE_SHAPE || o._type == REMOVE) - screen.restoreBackground(Common::Rect(o._oldPosition.x, o._oldPosition.y, - o._oldPosition.x + o._oldSize.x, o._oldPosition.y + o._oldSize.y)); - } - } - - // - // Update the background objects and canimations - // - - for (uint idx = 0; idx < _bgShapes.size(); ++idx) { - Object &o = _bgShapes[idx]; - if (o._type == ACTIVE_BG_SHAPE || o._type == NO_SHAPE) - o.adjustObject(); - } - - if (people._portraitLoaded && people._portrait._type == ACTIVE_BG_SHAPE) - people._portrait.adjustObject(); - - for (uint idx = 0; idx < _canimShapes.size(); ++idx) { - if (_canimShapes[idx]._type != INVALID) - _canimShapes[idx].adjustObject(); - } - - if (people[AL]._type == CHARACTER && people._holmesOn) - people[AL].adjustSprite(); - - // Flag the bg shapes which need to be redrawn - checkBgShapes(people[AL]._imageFrame, - Common::Point(people[AL]._position.x / 100, people[AL]._position.y / 100)); - - if (_currentScene == 12 && _vm->getGameID() == GType_SerratedScalpel) - ((Scalpel::ScalpelEngine *)_vm)->doMirror12(); - - // Draw all active shapes which are behind the person - for (uint idx = 0; idx < _bgShapes.size(); ++idx) { - Object &o = _bgShapes[idx]; - if (o._type == ACTIVE_BG_SHAPE && o._misc == BEHIND) - screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); - } - - // Draw all canimations which are behind the person - for (uint idx = 0; idx < _canimShapes.size(); ++idx) { - Object &o = _canimShapes[idx]; - if (o._type == ACTIVE_BG_SHAPE && o._misc == BEHIND) { - screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); - } - } - - // Draw all active shapes which are HAPPEN and behind the person - for (uint idx = 0; idx < _bgShapes.size(); ++idx) { - Object &o = _bgShapes[idx]; - if (o._type == ACTIVE_BG_SHAPE && o._misc == NORMAL_BEHIND) - screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); - } - - // Draw all canimations which are NORMAL and behind the person - for (uint idx = 0; idx < _canimShapes.size(); ++idx) { - Object &o = _canimShapes[idx]; - if (o._type == ACTIVE_BG_SHAPE && o._misc == NORMAL_BEHIND) { - screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); - } - } - - // Draw the person if not animating - if (people[AL]._type == CHARACTER && people.isHolmesActive()) { - // 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 / 100, 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 / 100 - people[AL]._imageFrame->_frame.h), flipped); - } - - // Draw all static and active shapes are NORMAL and are in front of the person - for (uint idx = 0; idx < _bgShapes.size(); ++idx) { - Object &o = _bgShapes[idx]; - if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == NORMAL_FORWARD) - screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); - } - - // Draw all static and active canimations that are NORMAL and are in front of the person - for (uint idx = 0; idx < _canimShapes.size(); ++idx) { - Object &o = _canimShapes[idx]; - if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == NORMAL_FORWARD) { - screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); - } - } - - // Draw all static and active shapes that are in front of the person - for (uint idx = 0; idx < _bgShapes.size(); ++idx) { - Object &o = _bgShapes[idx]; - if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == FORWARD) - screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); - } - - // Draw any active portrait - if (people._portraitLoaded && people._portrait._type == ACTIVE_BG_SHAPE) - screen._backBuffer->transBlitFrom(*people._portrait._imageFrame, - people._portrait._position, people._portrait._flags & OBJ_FLIPPED); - - // Draw all static and active canimations that are in front of the person - for (uint idx = 0; idx < _canimShapes.size(); ++idx) { - Object &o = _canimShapes[idx]; - if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == FORWARD) { - screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); - } - } - - // Draw all NO_SHAPE shapes which have flag bit 0 clear - for (uint idx = 0; idx < _bgShapes.size(); ++idx) { - Object &o = _bgShapes[idx]; - if (o._type == NO_SHAPE && (o._flags & OBJ_BEHIND) == 0) - screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); - } - - // Bring the newly built picture to the screen - if (_animating == 2) { - _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) { - 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[AL]._type = INVALID; - } else { - screen.flushImage(people[AL]._imageFrame, - Common::Point(people[AL]._position.x / 100, - people[AL]._position.y / 100 - people[AL].frameHeight()), - &people[AL]._oldPosition.x, &people[AL]._oldPosition.y, - &people[AL]._oldSize.x, &people[AL]._oldSize.y); - } - } - - if (_currentScene == 12 && _vm->getGameID() == GType_SerratedScalpel) - ((Scalpel::ScalpelEngine *)_vm)->flushMirror12(); - - for (uint idx = 0; idx < _bgShapes.size(); ++idx) { - Object &o = _bgShapes[idx]; - if ((o._type == ACTIVE_BG_SHAPE || o._type == REMOVE) && _goToScene == -1) { - screen.flushImage(o._imageFrame, o._position, - &o._oldPosition.x, &o._oldPosition.y, &o._oldSize.x, &o._oldSize.y); - } - } - - if (people._portraitLoaded) { - if (people._portrait._type == REMOVE) - screen.slamRect(Common::Rect( - people._portrait._position.x, people._portrait._position.y, - people._portrait._position.x + people._portrait._delta.x, - people._portrait._position.y + people._portrait._delta.y - )); - else - screen.flushImage(people._portrait._imageFrame, people._portrait._position, - &people._portrait._oldPosition.x, &people._portrait._oldPosition.y, - &people._portrait._oldSize.x, &people._portrait._oldSize.y); - - if (people._portrait._type == REMOVE) - people._portrait._type = INVALID; - } - - if (_goToScene == -1) { - for (uint idx = 0; idx < _bgShapes.size(); ++idx) { - Object &o = _bgShapes[idx]; - if (o._type == NO_SHAPE && (o._flags & OBJ_BEHIND) == 0) { - screen.slamArea(o._position.x, o._position.y, o._oldSize.x, o._oldSize.y); - screen.slamArea(o._oldPosition.x, o._oldPosition.y, o._oldSize.x, o._oldSize.y); - } else if (o._type == HIDE_SHAPE) { - // Hiding shape, so flush it out and mark it as hidden - screen.flushImage(o._imageFrame, o._position, - &o._oldPosition.x, &o._oldPosition.y, &o._oldSize.x, &o._oldSize.y); - o._type = HIDDEN; - } - } - } - - for (int idx = _canimShapes.size() - 1; idx >= 0; --idx) { - Object &o = _canimShapes[idx]; - - if (o._type == INVALID) { - // Anim shape was invalidated by checkEndOfSequence, so at this point we can remove it - _canimShapes.remove_at(idx); - } else if (o._type == REMOVE) { - if (_goToScene == -1) - screen.slamArea(o._position.x, o._position.y, o._delta.x, o._delta.y); - - // Shape for an animation is no longer needed, so remove it completely - _canimShapes.remove_at(idx); - } else if (o._type == ACTIVE_BG_SHAPE) { - screen.flushImage(o._imageFrame, o._position, - &o._oldPosition.x, &o._oldPosition.y, &o._oldSize.x, &o._oldSize.y); - } - } - } - - _restoreFlag = true; - _doBgAnimDone = true; - - events.wait(3); - screen.resetDisplayBounds(); - - // Check if the method was called for calling a portrait, and a talk was - // interrupting it. This talk file would not have been executed at the time, - // since we needed to finish the 'doBgAnim' to finish clearing the portrait - if (people._clearingThePortrait && talk._scriptMoreFlag == 3) { - // Reset the flags and call to talk - people._clearingThePortrait = false; - talk._scriptMoreFlag = 0; - talk.talkTo(talk._scriptName); - } -} - int Scene::findBgShape(const Common::Rect &r) { if (!_doBgAnimDone) // New frame hasn't been drawn yet @@ -1457,4 +1284,46 @@ void Scene::synchronize(Common::Serializer &s) { } } +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; + Common::Point pt(holmes._position.x / FIXED_INT_MULTIPLIER, holmes._position.y / FIXED_INT_MULTIPLIER); + + // Iterate through the shapes + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &obj = _bgShapes[idx]; + if (obj._type == ACTIVE_BG_SHAPE || (IS_SERRATED_SCALPEL && obj._type == STATIC_BG_SHAPE)) { + if ((obj._flags & 5) == 1) { + obj._misc = (pt.y < (obj._position.y + obj.frameHeight() - 1)) ? + NORMAL_FORWARD : NORMAL_BEHIND; + } else if (!(obj._flags & OBJ_BEHIND)) { + obj._misc = BEHIND; + } else if (obj._flags & OBJ_FORWARD) { + obj._misc = FORWARD; + } + } + } +} + } // End of namespace Sherlock diff --git a/engines/sherlock/scene.h b/engines/sherlock/scene.h index 88d12a3fb0..37a1b32740 100644 --- a/engines/sherlock/scene.h +++ b/engines/sherlock/scene.h @@ -29,6 +29,7 @@ #include "common/serializer.h" #include "sherlock/objects.h" #include "sherlock/resources.h" +#include "sherlock/screen.h" namespace Sherlock { @@ -44,12 +45,23 @@ struct BgFileHeader { int _numcAnimations; int _descSize; int _seqSize; + + // Serrated Scalpel int _fill; + // Rose Tattoo + int _scrollSize; + int _bytesWritten; // Size of the main body of the RRM + int _fadeStyle; // Fade style + byte _palette[PALETTE_SIZE]; // Palette + + + BgFileHeader(); + /** * Load the data for the object */ - void load(Common::SeekableReadStream &s); + void load(Common::SeekableReadStream &s, bool isRoseTattoo); }; struct BgFileHeaderInfo { @@ -63,18 +75,20 @@ struct BgFileHeaderInfo { void load(Common::SeekableReadStream &s); }; -struct Exit { - Common::Rect _bounds; - +class Exit: public Common::Rect { +public: int _scene; int _allow; Common::Point _people; int _peopleDir; + Common::String _dest; + int _image; // Arrow image to use + /** * Load the data for the object */ - void load(Common::SeekableReadStream &s); + void load(Common::SeekableReadStream &s, bool isRoseTattoo); }; struct SceneEntry { @@ -106,9 +120,26 @@ public: int indexOf(const Object &obj) const; }; +class ScaleZone: public Common::Rect { +public: + int _topNumber; // Numerator of scale size at the top of the zone + int _bottomNumber; // Numerator of scale size at the bottom of the zone + + void load(Common::SeekableReadStream &s); +}; + +struct SceneTripEntry { + bool _flag; + int _sceneNumber; + int _numTimes; + + SceneTripEntry() : _flag(false), _sceneNumber(0), _numTimes(0) {} + SceneTripEntry(bool flag, int sceneNumber, int numTimes) : _flag(flag), + _sceneNumber(sceneNumber), _numTimes(numTimes) {} +}; + class Scene { private: - SherlockEngine *_vm; Common::String _rrmName; bool _loadingSavedGame; @@ -124,6 +155,11 @@ private: bool loadScene(const Common::String &filename); /** + * Loads sounds for the scene + */ + void loadSceneSounds(); + + /** * Set objects to their current persistent state. This includes things such as * opening or moving them */ @@ -143,17 +179,26 @@ private: void transitionToScene(); /** + * Restores objects to the correct status. This ensures that things like being opened or moved + * will remain the same on future visits to the scene + */ + void saveSceneStatus(); +protected: + SherlockEngine *_vm; + + /** * 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 */ - void checkBgShapes(ImageFrame *frame, const Common::Point &pt); + virtual void checkBgShapes(); /** - * Restores objects to the correct status. This ensures that things like being opened or moved - * will remain the same on future visits to the scene + * Draw all the shapes, people and NPCs in the correct order */ - void saveSceneStatus(); + void drawAllShapes(); + + Scene(SherlockEngine *vm); public: int _currentScene; int _goToScene; @@ -173,17 +218,21 @@ public: int _walkDirectory[MAX_ZONES][MAX_ZONES]; Common::Array<byte> _walkData; Common::Array<Exit> _exits; + int _exitZone; SceneEntry _entrance; Common::Array<SceneSound> _sounds; ObjectArray _canimShapes; + Common::Array<ScaleZone> _scaleZones; + Common::StringArray _objSoundList; bool _restoreFlag; int _animating; bool _doBgAnimDone; int _tempFadeStyle; int _cAnimFramePause; + Common::Array<SceneTripEntry> _sceneTripCounters; public: - Scene(SherlockEngine *vm); - ~Scene(); + static Scene *init(SherlockEngine *vm); + virtual ~Scene(); /** * Handles loading the scene specified by _goToScene @@ -224,11 +273,6 @@ public: int toggleObject(const Common::String &name); /** - * Animate all objects and people. - */ - void doBgAnim(); - - /** * Attempts to find a background shape within the passed bounds. If found, * it will return the shape number, or -1 on failure. */ @@ -251,15 +295,27 @@ public: int closestZone(const Common::Point &pt); /** - * Update the screen back buffer with all of the scene objects which need - * to be drawn + * Synchronize the data for a savegame */ - void updateBackground(); + void synchronize(Common::Serializer &s); /** - * Synchronize the data for a savegame + * 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 synchronize(Common::Serializer &s); + void setNPCPath(int npc); +public: + /** + * Draw all objects and characters. + */ + virtual void doBgAnim() = 0; + + /** + * Update the screen back buffer with all of the scene objects which need + * to be drawn + */ + virtual void updateBackground(); }; } // End of namespace Sherlock diff --git a/engines/sherlock/screen.cpp b/engines/sherlock/screen.cpp index e70d0614d1..a3af5559c7 100644 --- a/engines/sherlock/screen.cpp +++ b/engines/sherlock/screen.cpp @@ -28,9 +28,9 @@ namespace Sherlock { -Screen::Screen(SherlockEngine *vm) : Surface(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), _vm(vm), - _backBuffer1(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), - _backBuffer2(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), +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; @@ -38,7 +38,17 @@ Screen::Screen(SherlockEngine *vm) : Surface(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCR _fontHeight = 0; Common::fill(&_cMap[0], &_cMap[PALETTE_SIZE], 0); Common::fill(&_sMap[0], &_sMap[PALETTE_SIZE], 0); - setFont(1); + Common::fill(&_tMap[0], &_tMap[PALETTE_SIZE], 0); + 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() { @@ -174,12 +184,13 @@ void Screen::randomTransition() { Events &events = *_vm->_events; const int TRANSITION_MULTIPLIER = 0x15a4e35; _dirtyRects.clear(); + assert(IS_SERRATED_SCALPEL); for (int idx = 0; idx <= 65535 && !_vm->shouldQuit(); ++idx) { _transitionSeed = _transitionSeed * TRANSITION_MULTIPLIER + 1; int offset = _transitionSeed & 0xFFFF; - if (offset < (SHERLOCK_SCREEN_WIDTH * SHERLOCK_SCREEN_HEIGHT)) + if (offset < (this->w() * this->h())) *((byte *)getPixels() + offset) = *((const byte *)_backBuffer->getPixels() + offset); if (idx != 0 && (idx % 300) == 0) { @@ -199,12 +210,12 @@ void Screen::randomTransition() { void Screen::verticalTransition() { Events &events = *_vm->_events; - byte table[SHERLOCK_SCREEN_WIDTH]; - Common::fill(&table[0], &table[SHERLOCK_SCREEN_WIDTH], 0); + byte table[640]; + Common::fill(&table[0], &table[640], 0); - for (int yp = 0; yp < SHERLOCK_SCREEN_HEIGHT; ++yp) { - for (int xp = 0; xp < SHERLOCK_SCREEN_WIDTH; ++xp) { - int temp = (table[xp] >= 197) ? SHERLOCK_SCREEN_HEIGHT - table[xp] : + for (int yp = 0; yp < this->h(); ++yp) { + for (int xp = 0; xp < this->w(); ++xp) { + int temp = (table[xp] >= (this->h() - 3)) ? this->h() - table[xp] : _vm->getRandomNumber(3) + 1; if (temp) { @@ -221,7 +232,7 @@ 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, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT)); + tempRect.clip(Common::Rect(0, 0, this->w(), SHERLOCK_SCENE_HEIGHT)); if (tempRect.isValidRect()) _backBuffer1.blitFrom(_backBuffer2, Common::Point(tempRect.left, tempRect.top), tempRect); @@ -235,31 +246,62 @@ 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, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); + tempRect.clip(Common::Rect(0, 0, this->w(), this->h())); if (tempRect.isValidRect()) blitFrom(*_backBuffer, Common::Point(tempRect.left, tempRect.top), tempRect); } } -void Screen::flushImage(ImageFrame *frame, const Common::Point &pt, - int16 *xp, int16 *yp, int16 *width, int16 *height) { +void Screen::flushImage(ImageFrame *frame, const Common::Point &pt, int16 *xp, int16 *yp, + int16 *width, int16 *height) { Common::Point imgPos = pt + frame->_offset; Common::Rect newBounds(imgPos.x, imgPos.y, imgPos.x + frame->_frame.w, imgPos.y + frame->_frame.h); Common::Rect oldBounds(*xp, *yp, *xp + *width, *yp + *height); - // See if the areas of the old and new overlap, and if so combine the areas - if (newBounds.intersects(oldBounds)) { - Common::Rect mergedBounds = newBounds; - mergedBounds.extend(oldBounds); - mergedBounds.right += 1; - mergedBounds.bottom += 1; + if (!_flushScreen) { + // See if the areas of the old and new overlap, and if so combine the areas + if (newBounds.intersects(oldBounds)) { + Common::Rect mergedBounds = newBounds; + mergedBounds.extend(oldBounds); + mergedBounds.right += 1; + mergedBounds.bottom += 1; - slamRect(mergedBounds); - } else { - // The two areas are independent, so copy them both - slamRect(newBounds); - slamRect(oldBounds); + slamRect(mergedBounds); + } else { + // The two areas are independent, so copy them both + slamRect(newBounds); + slamRect(oldBounds); + } + } + + *xp = newBounds.left; + *yp = newBounds.top; + *width = newBounds.width(); + *height = newBounds.height(); +} + +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::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); + + if (!_flushScreen) { + // See if the areas of the old and new overlap, and if so combine the areas + if (newBounds.intersects(oldBounds)) { + Common::Rect mergedBounds = newBounds; + mergedBounds.extend(oldBounds); + mergedBounds.right += 1; + mergedBounds.bottom += 1; + + slamRect(mergedBounds); + } else { + // The two areas are independent, so copy them both + slamRect(newBounds); + slamRect(oldBounds); + } } *xp = newBounds.left; @@ -281,13 +323,13 @@ void Screen::print(const Common::Point &pt, byte color, const char *formatStr, . pos.y--; // Font is always drawing one line higher if (!pos.x) // Center text horizontally - pos.x = (SHERLOCK_SCREEN_WIDTH - width) / 2; + pos.x = (this->w() - width) / 2; Common::Rect textBounds(pos.x, pos.y, pos.x + width, pos.y + _fontHeight); - if (textBounds.right > SHERLOCK_SCREEN_WIDTH) - textBounds.moveTo(SHERLOCK_SCREEN_WIDTH - width, textBounds.top); - if (textBounds.bottom > SHERLOCK_SCREEN_HEIGHT) - textBounds.moveTo(textBounds.left, SHERLOCK_SCREEN_HEIGHT - _fontHeight); + if (textBounds.right > this->w()) + textBounds.moveTo(this->w() - width, textBounds.top); + if (textBounds.bottom > this->h()) + textBounds.moveTo(textBounds.left, this->h() - _fontHeight); // Write out the string at the given position writeString(str, Common::Point(textBounds.left, textBounds.top), color); @@ -416,7 +458,7 @@ void Screen::resetDisplayBounds() { Common::Rect Screen::getDisplayBounds() { return (_backBuffer == &_sceneSurface) ? Common::Rect(0, 0, _sceneSurface.w(), _sceneSurface.h()) : - Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); + Common::Rect(0, 0, this->w(), this->h()); } void Screen::synchronize(Common::Serializer &s) { @@ -426,4 +468,41 @@ void Screen::synchronize(Common::Serializer &s) { setFont(fontNumb); } +void Screen::initPaletteFade(int bytesToRead) { + Common::copy(&_cMap[0], &_cMap[PALETTE_SIZE], &_sMap[0]); + Common::copy(&_cMap[0], &_cMap[PALETTE_SIZE], &_tMap[0]); + + // Set how many bytes need to be read / have been read + _fadeBytesRead = 0; + _fadeBytesToRead = bytesToRead; + _oldFadePercent = 0; +} + +int Screen::fadeRead(Common::SeekableReadStream &stream, byte *buf, int totalSize) { + warning("TODO: fadeRead"); + stream.read(buf, totalSize); + 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]); +} + } // End of namespace Sherlock diff --git a/engines/sherlock/screen.h b/engines/sherlock/screen.h index a2c0aa3c84..8fda9cbb9c 100644 --- a/engines/sherlock/screen.h +++ b/engines/sherlock/screen.h @@ -66,6 +66,12 @@ private: 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 */ @@ -92,6 +98,10 @@ public: bool _fadeStyle; byte _cMap[PALETTE_SIZE]; byte _sMap[PALETTE_SIZE]; + byte _tMap[PALETTE_SIZE]; + int _currentScroll, _targetScroll; + int _scrollSize, _scrollSpeed; + bool _flushScreen; public: Screen(SherlockEngine *vm); virtual ~Screen(); @@ -171,8 +181,15 @@ public: * Copy an image from the back buffer to the screen, taking care of both the * new area covered by the shape as well as the old area, which must be restored */ - void flushImage(ImageFrame *frame, const Common::Point &pt, - int16 *xp, int16 *yp, int16 *width, int16 *height); + void flushImage(ImageFrame *frame, const Common::Point &pt, int16 *xp, int16 *yp, + int16 *width, int16 *height); + + /** + * Similar to flushImage, this method takes in an extra parameter for the scale proporation, + * which affects the calculated bounds accordingly + */ + void flushScaleImage(ImageFrame *frame, const Common::Point &pt, int16 *xp, int16 *yp, + int16 *width, int16 *height, int scaleVal); /** * Returns the width of a string in pixels @@ -232,6 +249,21 @@ public: * Synchronize the data for a savegame */ void synchronize(Common::Serializer &s); + + // 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 + */ + static void translatePalette(byte palette[PALETTE_SIZE]); }; } // End of namespace Sherlock diff --git a/engines/sherlock/sherlock.cpp b/engines/sherlock/sherlock.cpp index 9807c23fee..d3a409a67b 100644 --- a/engines/sherlock/sherlock.cpp +++ b/engines/sherlock/sherlock.cpp @@ -25,7 +25,6 @@ #include "common/scummsys.h" #include "common/config-manager.h" #include "common/debug-channels.h" -#include "engines/util.h" namespace Sherlock { @@ -72,8 +71,6 @@ SherlockEngine::~SherlockEngine() { } void SherlockEngine::initialize() { - initGraphics(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT, false); - DebugMan.addDebugChannel(kDebugScript, "scripts", "Script debug level"); ImageFile::setVm(this); @@ -98,11 +95,11 @@ void SherlockEngine::initialize() { _journal = new Journal(this); _people = new People(this); _saves = new SaveManager(this, _targetName); - _scene = new Scene(this); + _scene = Scene::init(this); _screen = new Screen(this); _sound = new Sound(this, _mixer); - _talk = new Talk(this); - _ui = new UserInterface(this); + _talk = Talk::init(this); + _ui = UserInterface::init(this); // Load game settings loadConfig(); @@ -213,7 +210,9 @@ void SherlockEngine::loadConfig() { ConfMan.registerDefault("font", 1); _screen->setFont(ConfMan.getInt("font")); - _screen->_fadeStyle = ConfMan.getBool("fade_style"); + if (getGameID() == GType_SerratedScalpel) + _screen->_fadeStyle = ConfMan.getBool("fade_style"); + _ui->_helpStyle = ConfMan.getBool("help_style"); _ui->_slideWindows = ConfMan.getBool("window_style"); _people->_portraitsOn = ConfMan.getBool("portraits_on"); diff --git a/engines/sherlock/sherlock.h b/engines/sherlock/sherlock.h index 2e85651700..e71c729893 100644 --- a/engines/sherlock/sherlock.h +++ b/engines/sherlock/sherlock.h @@ -59,9 +59,9 @@ enum GameType { GType_RoseTattoo = 1 }; -#define SHERLOCK_SCREEN_WIDTH 320 -#define SHERLOCK_SCREEN_HEIGHT 200 -#define SHERLOCK_SCENE_HEIGHT 138 +#define SHERLOCK_SCREEN_WIDTH _vm->_screen->w() +#define SHERLOCK_SCREEN_HEIGHT _vm->_screen->h() +#define SHERLOCK_SCENE_HEIGHT (IS_SERRATED_SCALPEL ? 138 : 480) struct SherlockGameDescription; @@ -199,6 +199,9 @@ public: void synchronize(Common::Serializer &s); }; +#define IS_ROSE_TATTOO (_vm->getGameID() == GType_RoseTattoo) +#define IS_SERRATED_SCALPEL (_vm->getGameID() == GType_SerratedScalpel) + } // End of namespace Sherlock #endif diff --git a/engines/sherlock/sound.cpp b/engines/sherlock/sound.cpp index a923cf9e57..e7f4fe8a9a 100644 --- a/engines/sherlock/sound.cpp +++ b/engines/sherlock/sound.cpp @@ -58,18 +58,27 @@ Sound::Sound(SherlockEngine *vm, Audio::Mixer *mixer) : _vm(vm), _mixer(mixer) { _soundIsOn = &_soundPlaying; _curPriority = 0; _digiBuf = nullptr; + _midiDrvLoaded = false; + _musicVolume = 0; _soundOn = true; _speechOn = true; + _vm->_res->addToCache("MUSIC.LIB"); if (!_vm->_interactiveFl) _vm->_res->addToCache("TITLE.SND"); else { - _vm->_res->addToCache("SND.SND"); + _vm->_res->addToCache("MUSIC.LIB"); + + if (IS_ROSE_TATTOO) { + _vm->_res->addToCache("SOUND.LIB"); + } else { + _vm->_res->addToCache("SND.SND"); - if (!_vm->isDemo()) { - _vm->_res->addToCache("TITLE.SND"); - _vm->_res->addToCache("EPILOGUE.SND"); + if (!_vm->isDemo()) { + _vm->_res->addToCache("TITLE.SND"); + _vm->_res->addToCache("EPILOGUE.SND"); + } } } } @@ -210,5 +219,9 @@ void Sound::freeDigiSound() { _soundPlaying = false; } +void Sound::setMIDIVolume(int volume) { + // TODO +} + } // End of namespace Sherlock diff --git a/engines/sherlock/sound.h b/engines/sherlock/sound.h index 2351bebd6e..e1c0777763 100644 --- a/engines/sherlock/sound.h +++ b/engines/sherlock/sound.h @@ -56,6 +56,9 @@ public: bool _soundPlaying; bool *_soundIsOn; byte *_digiBuf; + bool _midiDrvLoaded; + Common::String _currentSongName, _nextSongName; + int _musicVolume; public: Sound(SherlockEngine *vm, Audio::Mixer *mixer); @@ -91,6 +94,7 @@ public: void stopSndFuncPtr(int v1, int v2); void freeDigiSound(); + void setMIDIVolume(int volume); }; } // End of namespace Sherlock diff --git a/engines/sherlock/surface.cpp b/engines/sherlock/surface.cpp index 80495a398c..5a9e59e01b 100644 --- a/engines/sherlock/surface.cpp +++ b/engines/sherlock/surface.cpp @@ -96,6 +96,12 @@ void Surface::transBlitFrom(const ImageFrame &src, const Common::Point &pt, transBlitFrom(src._frame, pt + src._offset, flipped, overrideColor); } +void Surface::transBlitFrom(const Surface &src, const Common::Point &pt, + bool flipped, int overrideColor) { + const Graphics::Surface &s = src._surface; + transBlitFrom(s, pt, flipped, overrideColor); +} + void Surface::transBlitFrom(const Graphics::Surface &src, const Common::Point &pt, bool flipped, int overrideColor) { Common::Rect drawRect(0, 0, src.w, src.h); @@ -186,4 +192,10 @@ void Surface::setPixels(byte *pixels, int width, int height) { _surface.setPixels(pixels); } +void Surface::maskArea(const ImageFrame &src, const Common::Point &pt, int scrollX) { + // TODO + error("TODO: maskArea"); +} + + } // End of namespace Sherlock diff --git a/engines/sherlock/surface.h b/engines/sherlock/surface.h index ccabf02a23..663f87f37f 100644 --- a/engines/sherlock/surface.h +++ b/engines/sherlock/surface.h @@ -126,6 +126,8 @@ public: */ void fillRect(const Common::Rect &r, byte color); + void maskArea(const ImageFrame &src, const Common::Point &pt, int scrollX); + /** * Clear the screen */ diff --git a/engines/sherlock/talk.cpp b/engines/sherlock/talk.cpp index 319d1a383f..dd6e776082 100644 --- a/engines/sherlock/talk.cpp +++ b/engines/sherlock/talk.cpp @@ -23,11 +23,153 @@ #include "sherlock/talk.h" #include "sherlock/sherlock.h" #include "sherlock/screen.h" +#include "sherlock/scalpel/scalpel_user_interface.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; @@ -90,6 +232,13 @@ void TalkSequences::clear() { /*----------------------------------------------------------------*/ +Talk *Talk::init(SherlockEngine *vm) { + if (vm->getGameID() == GType_SerratedScalpel) + return new ScalpelTalk(vm); + else + return new TattooTalk(vm); +} + Talk::Talk(SherlockEngine *vm) : _vm(vm) { _talkCounter = 0; _talkToAbort = false; @@ -103,6 +252,15 @@ Talk::Talk(SherlockEngine *vm) : _vm(vm) { _moreTalkDown = _moreTalkUp = false; _scriptMoreFlag = 0; _scriptSaveIndex = -1; + _opcodes = IS_SERRATED_SCALPEL ? SCALPEL_OPCODES : TATTOO_OPCODES; + + _charCount = 0; + _line = 0; + _yp = 0; + _wait = 0; + _pauseFlag = false; + _seqCount = 0; + _scriptStart = _scriptEnd = nullptr; } void Talk::talkTo(const Common::String &filename) { @@ -172,9 +330,11 @@ void Talk::talkTo(const Common::String &filename) { while (!_sequenceStack.empty()) pullSequence(); - // Restore any pressed button - if (!ui._windowOpen && savedMode != STD_MODE) - ui.restoreButton((int)(savedMode - 1)); + if (IS_SERRATED_SCALPEL) { + // Restore any pressed button + if (!ui._windowOpen && savedMode != STD_MODE) + ((Scalpel::ScalpelUserInterface *)_vm->_ui)->restoreButton((int)(savedMode - 1)); + } // Clear the ui counter so that anything displayed on the info line // before the window was opened isn't cleared @@ -388,7 +548,7 @@ void Talk::talkTo(const Common::String &filename) { } - ui._key = ui._oldKey = COMMANDS[TALK_MODE - 1]; + ui._key = ui._oldKey = Scalpel::COMMANDS[TALK_MODE - 1]; ui._temp = ui._oldTemp = 0; ui._menuMode = TALK_MODE; _talkToFlag = 2; @@ -570,7 +730,7 @@ void Talk::stripVoiceCommands() { // Scan for an sound effect byte, which indicates to play a sound for (uint idx = 0; idx < statement._reply.size(); ++idx) { - if (statement._reply[idx] == (char)SFX_COMMAND) { + if (statement._reply[idx] == (char)_opcodes[OP_SFX_COMMAND]) { // Replace instruction character with a space, and delete the // rest of the name following it statement._reply = Common::String(statement._reply.c_str(), @@ -625,9 +785,9 @@ void Talk::drawInterface() { screen.makeButton(Common::Rect(181, CONTROLS_Y, 221, CONTROLS_Y + 10), 200 - screen.stringWidth("Down") / 2, "Down"); } else { - int strWidth = screen.stringWidth(PRESS_KEY_TO_CONTINUE); + int strWidth = screen.stringWidth(Scalpel::PRESS_KEY_TO_CONTINUE); screen.makeButton(Common::Rect(46, CONTROLS_Y, 273, CONTROLS_Y + 10), - 160 - strWidth / 2, PRESS_KEY_TO_CONTINUE); + 160 - strWidth / 2, Scalpel::PRESS_KEY_TO_CONTINUE); screen.gPrint(Common::Point(160 - strWidth / 2, CONTROLS_Y), COMMAND_FOREGROUND, "P"); } } @@ -919,38 +1079,33 @@ void Talk::setStillSeq(int speaker) { } void Talk::doScript(const Common::String &script) { - Animation &anim = *_vm->_animation; - Events &events = *_vm->_events; - Inventory &inv = *_vm->_inventory; - Map &map = *_vm->_map; People &people = *_vm->_people; Scene &scene = *_vm->_scene; Screen &screen = *_vm->_screen; - Sound &sound = *_vm->_sound; UserInterface &ui = *_vm->_ui; - int wait = 0; - bool pauseFlag = false; - bool endStr = false; - int yp = CONTROLS_Y + 12; - int charCount = 0; - int line = 0; - bool noTextYet = true; bool openTalkWindow = false; - int seqCount; _savedSequences.clear(); - const byte *scriptStart = (const byte *)script.c_str(); - const byte *scriptEnd = scriptStart + script.size(); - const byte *str = scriptStart; + _scriptStart = (const byte *)script.c_str(); + _scriptEnd = _scriptStart + script.size(); + const byte *str = _scriptStart; + _yp = CONTROLS_Y + 12; + _charCount = 0; + _line = 0; + _wait = 0; + _pauseFlag = false; + _seqCount = 0; + _noTextYet = true; + _endStr = false; if (_scriptMoreFlag) { _scriptMoreFlag = 0; - str = scriptStart + _scriptSaveIndex; + str = _scriptStart + _scriptSaveIndex; } // Check if the script begins with a Stealh Mode Active command - if (str[0] == STEALTH_MODE_ACTIVE || _talkStealth) { + if (str[0] == _opcodes[OP_STEALTH_MODE_ACTIVE] || _talkStealth) { _talkStealth = 2; _speaker |= SPEAKER_REMOVE; } else { @@ -958,7 +1113,7 @@ void Talk::doScript(const Common::String &script) { ui.clearWindow(); // Need to switch speakers? - if (str[0] == SWITCH_SPEAKER) { + if (str[0] == _opcodes[OP_SWITCH_SPEAKER]) { _speaker = str[1] - 1; str += 2; pullSequence(); @@ -969,7 +1124,7 @@ void Talk::doScript(const Common::String &script) { } // Assign portrait location? - if (str[0] == ASSIGN_PORTRAIT_LOCATION) { + if (str[0] == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION]) { switch (str[1] & 15) { case 1: people._portraitSide = 20; @@ -991,7 +1146,7 @@ void Talk::doScript(const Common::String &script) { } // Remove portrait? - if (str[0] == REMOVE_PORTRAIT) { + if (str[0] == _opcodes[OP_REMOVE_PORTRAIT]) { _speaker = 255; } else { // Nope, so set the first speaker @@ -1001,401 +1156,22 @@ void Talk::doScript(const Common::String &script) { do { Common::String tempString; - wait = 0; + _wait = 0; byte c = str[0]; if (!c) { - endStr = true; + _endStr = true; } else if (c == '{') { // Start of comment, so skip over it while (*str++ != '}') ; - } else if (c >= SWITCH_SPEAKER) { + } else if (c >= 128 && c <= 227 && _opcodeTable[c - 128]) { // Handle control code - switch (c) { - case SWITCH_SPEAKER: - if (!(_speaker & SPEAKER_REMOVE)) - people.clearTalking(); - if (_talkToAbort) - return; - - ui.clearWindow(); - yp = CONTROLS_Y + 12; - charCount = line = 0; - - _speaker = *++str - 1; - people.setTalking(_speaker); - pullSequence(); - pushSequence(_speaker); - setSequence(_speaker); - break; - - case RUN_CANIMATION: - ++str; - scene.startCAnim((str[0] - 1) & 127, (str[0] & 0x80) ? -1 : 1); - if (_talkToAbort) - return; - - // Check if next character is changing side or changing portrait - if (charCount && (str[1] == SWITCH_SPEAKER || str[1] == ASSIGN_PORTRAIT_LOCATION)) - wait = 1; - break; - - case ASSIGN_PORTRAIT_LOCATION: - ++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; - break; - - case PAUSE: - // Pause - charCount = *++str; - wait = pauseFlag = true; - break; - - case REMOVE_PORTRAIT: - if (_speaker >= 0 && _speaker < SPEAKER_REMOVE) - people.clearTalking(); - pullSequence(); - if (_talkToAbort) - return; - - _speaker |= SPEAKER_REMOVE; - break; - - case CLEAR_WINDOW: - ui.clearWindow(); - yp = CONTROLS_Y + 12; - charCount = line = 0; - break; - - case ADJUST_OBJ_SEQUENCE: - { - // Get the name of the object to adjust - ++str; - for (int idx = 0; idx < (str[0] & 127); ++idx) - tempString += str[idx + 2]; - - // Scan for object - int objId = -1; - for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) { - if (tempString.equalsIgnoreCase(scene._bgShapes[idx]._name)) - objId = idx; - } - if (objId == -1) - error("Could not find object %s to change", tempString.c_str()); - - // Should the script be overwritten? - if (str[0] > 0x80) { - // Save the current sequence - _savedSequences.push(SequenceEntry()); - SequenceEntry &seqEntry = _savedSequences.top(); - seqEntry._objNum = objId; - seqEntry._seqTo = scene._bgShapes[objId]._seqTo; - for (uint idx = 0; idx < scene._bgShapes[objId]._seqSize; ++idx) - seqEntry._sequences.push_back(scene._bgShapes[objId]._sequences[idx]); - } - - // Get number of bytes to change - seqCount = str[1]; - str += (str[0] & 127) + 2; - - // Copy in the new sequence - for (int idx = 0; idx < seqCount; ++idx, ++str) - scene._bgShapes[objId]._sequences[idx] = str[0] - 1; - - // Reset object back to beginning of new sequence - scene._bgShapes[objId]._frameNumber = 0; + switch ((this->*_opcodeTable[c - 128])(str)) { + case RET_EXIT: + return; + case RET_CONTINUE: continue; - } - - case WALK_TO_COORDS: - ++str; - - people.walkToCoords(Common::Point(((str[0] - 1) * 256 + str[1] - 1) * 100, - str[2] * 100), str[3] - 1); - if (_talkToAbort) - return; - - str += 3; - break; - - case PAUSE_WITHOUT_CONTROL: - ++str; - - for (int idx = 0; idx < (str[0] - 1); ++idx) { - scene.doBgAnim(); - if (_talkToAbort) - return; - - // Check for button press - events.pollEvents(); - events.setButtonState(); - } - break; - - case BANISH_WINDOW: - if (!(_speaker & SPEAKER_REMOVE)) - people.clearTalking(); - pullSequence(); - - if (_talkToAbort) - return; - - _speaker |= SPEAKER_REMOVE; - ui.banishWindow(); - ui._menuMode = TALK_MODE; - noTextYet = true; - break; - - case SUMMON_WINDOW: - 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"); - } - break; - - case SET_FLAG: { - ++str; - int flag1 = (str[0] - 1) * 256 + str[1] - 1 - (str[1] == 1 ? 1 : 0); - int flag = (flag1 & 0x3fff) * (flag1 >= 0x4000 ? -1 : 1); - _vm->setFlags(flag); - ++str; - break; - } - - case SFX_COMMAND: - ++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; - break; - - case TOGGLE_OBJECT: - ++str; - for (int idx = 0; idx < str[0]; ++idx) - tempString += str[idx + 1]; - - scene.toggleObject(tempString); - str += str[0]; - break; - - case STEALTH_MODE_ACTIVE: - _talkStealth = 2; - break; - - case IF_STATEMENT: { - ++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] != ELSE_STATEMENT && str[0] != END_IF_STATEMENT); - - if (!str[0]) - endStr = true; - } - break; - } - - case ELSE_STATEMENT: - // 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] != END_IF_STATEMENT); - break; - - case STEALTH_MODE_DEACTIVATE: - _talkStealth = 0; - events.clearKeyboard(); - break; - - case TURN_HOLMES_OFF: - people._holmesOn = false; - break; - - case TURN_HOLMES_ON: - people._holmesOn = true; - break; - - case GOTO_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; - break; - - case PLAY_PROLOGUE: - ++str; - for (int idx = 0; idx < 8 && str[idx] != '~'; ++idx) - tempString += str[idx]; - - anim.play(tempString, 1, 3, true, 4); - break; - - case ADD_ITEM_TO_INVENTORY: - ++str; - for (int idx = 0; idx < str[0]; ++idx) - tempString += str[idx + 1]; - str += str[0]; - - inv.putNameInInventory(tempString); - break; - - case SET_OBJECT: { - ++str; - for (int idx = 0; idx < (str[0] & 127); ++idx) - tempString += str[idx + 1]; - - // Set comparison state according to if we want to hide or unhide - bool state = (str[0] >= SPEAKER_REMOVE); - str += str[0] & 127; - - for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) { - Object &object = scene._bgShapes[idx]; - if (tempString.equalsIgnoreCase(object._name)) { - // Only toggle the object if it's not in the desired state already - if ((object._type == HIDDEN && state) || (object._type != HIDDEN && !state)) - object.toggleHidden(); - } - } - break; - } - - case CALL_TALK_FILE: { - ++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; - break; - } - - case MOVE_MOUSE: - ++str; - events.moveMouse(Common::Point((str[0] - 1) * 256 + str[1] - 1, str[2])); - if (_talkToAbort) - return; - str += 3; - break; - - case DISPLAY_INFO_LINE: - ++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; - break; - - case CLEAR_INFO_LINE: - ui._infoFlag = true; - ui.clearInfo(); - break; - - case WALK_TO_CANIMATION: { - ++str; - CAnim &animation = scene._cAnim[str[0] - 1]; - - people.walkToCoords(animation._goto, animation._gotoDir); - if (_talkToAbort) - return; - break; - } - - case REMOVE_ITEM_FROM_INVENTORY: - ++str; - for (int idx = 0; idx < str[0]; ++idx) - tempString += str[idx + 1]; - str += str[0]; - - inv.deleteItemFromInventory(tempString); - break; - - case ENABLE_END_KEY: - ui._endKeyActive = true; - break; - - case DISABLE_END_KEY: - ui._endKeyActive = false; - break; - default: break; } @@ -1403,8 +1179,8 @@ 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; + if (!ui._windowOpen && _noTextYet) { + _noTextYet = false; drawInterface(); if (_talkTo != -1) { @@ -1415,19 +1191,19 @@ void Talk::doScript(const Common::String &script) { } // If it's the first line, display the speaker - if (!line && _speaker >= 0 && _speaker < (int)people._characters.size()) { + 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", + 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", + screen.gPrint(Common::Point(16, _yp - 1), TALK_FOREGROUND, "%s", people._characters[_speaker & 127]._name); openTalkWindow = true; } - yp += 9; + _yp += 9; } // Find amount of text that will fit on the line @@ -1435,23 +1211,23 @@ void Talk::doScript(const Common::String &script) { do { width += screen.charWidth(str[idx]); ++idx; - ++charCount; - } while (width < 298 && str[idx] && str[idx] != '{' && str[idx] < SWITCH_SPEAKER); + ++_charCount; + } while (width < 298 && str[idx] && str[idx] != '{' && str[idx] < _opcodes[0]); if (str[idx] || width >= 298) { - if (str[idx] < SWITCH_SPEAKER && str[idx] != '{') { + if (str[idx] < _opcodes[0] && str[idx] != '{') { --idx; - --charCount; + --_charCount; } } else { - endStr = true; + _endStr = true; } // If word wrap is needed, find the start of the current word if (width >= 298) { while (str[idx] != ' ') { --idx; - --charCount; + --_charCount; } } @@ -1461,16 +1237,16 @@ void Talk::doScript(const Common::String &script) { // 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()); + 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()); + 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()); + 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()); + screen.gPrint(Common::Point(16, _yp - 1), COMMAND_FOREGROUND, "%s", lineStr.c_str()); openTalkWindow = true; } } @@ -1479,37 +1255,27 @@ void Talk::doScript(const Common::String &script) { str += idx; // If line wrap occurred, then move to after the separating space between the words - if (str[0] < SWITCH_SPEAKER && str[0] != '{') + if (str[0] < _opcodes[0] && str[0] != '{') ++str; - yp += 9; - ++line; + _yp += 9; + ++_line; // Certain different conditions require a wait - if ((line == 4 && str < scriptEnd && str[0] != SFX_COMMAND && str[0] != PAUSE && _speaker != -1) || - (line == 5 && str < scriptEnd && str[0] != PAUSE && _speaker == -1) || - endStr) { - wait = 1; + 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; } - switch (str >= scriptEnd ? 0 : str[0]) { - case SWITCH_SPEAKER: - case ASSIGN_PORTRAIT_LOCATION: - case BANISH_WINDOW: - case IF_STATEMENT: - case ELSE_STATEMENT: - case END_IF_STATEMENT: - case GOTO_SCENE: - case CALL_TALK_FILE: - wait = 1; - break; - default: - break; - } + byte v = (str >= _scriptEnd ? 0 : str[0]); + _wait = v == _opcodes[OP_SWITCH_SPEAKER] || v == _opcodes[OP_ASSIGN_PORTRAIT_LOCATION] || + v == _opcodes[OP_BANISH_WINDOW] || _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]; } // Open window if it wasn't already open, and text has already been printed - if ((openTalkWindow && wait) || (openTalkWindow && str[0] >= SWITCH_SPEAKER && str[0] != CARRIAGE_RETURN)) { + if ((openTalkWindow && _wait) || (openTalkWindow && str[0] >= _opcodes[0] && str[0] != _opcodes[OP_CARRIAGE_RETURN])) { if (!ui._slideWindows) { screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); } else { @@ -1520,34 +1286,34 @@ void Talk::doScript(const Common::String &script) { openTalkWindow = false; } - if (wait) { + if (_wait) { // Handling pausing - if (!pauseFlag && charCount < 160) - charCount = 160; + if (!_pauseFlag && _charCount < 160) + _charCount = 160; - wait = waitForMore(charCount); - if (wait == -1) - endStr = true; + _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] == SFX_COMMAND) + 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] != SFX_COMMAND) { + if (!_pauseFlag && _wait != -1 && str < _scriptEnd && str[0] != _opcodes[OP_SFX_COMMAND]) { if (!_talkStealth) ui.clearWindow(); - yp = CONTROLS_Y + 12; - charCount = line = 0; + _yp = CONTROLS_Y + 12; + _charCount = _line = 0; } - pauseFlag = false; + _pauseFlag = false; } - } while (!_vm->shouldQuit() && !endStr); + } while (!_vm->shouldQuit() && !_endStr); - if (wait != -1) { + if (_wait != -1) { for (int ssIndex = 0; ssIndex < (int)_savedSequences.size(); ++ssIndex) { SequenceEntry &seq = _savedSequences[ssIndex]; Object &object = scene._bgShapes[seq._objNum]; @@ -1662,4 +1428,704 @@ void Talk::synchronize(Common::Serializer &s) { } } +OpcodeReturn Talk::cmdAddItemToInventory(const byte *&str) { + Inventory &inv = *_vm->_inventory; + Common::String tempString; + + ++str; + for (int idx = 0; idx < str[0]; ++idx) + tempString += str[idx + 1]; + str += str[0]; + + inv.putNameInInventory(tempString); + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdAdjustObjectSequence(const byte *&str) { + Scene &scene = *_vm->_scene; + Common::String tempString; + + // Get the name of the object to adjust + ++str; + for (int idx = 0; idx < (str[0] & 127); ++idx) + tempString += str[idx + 2]; + + // Scan for object + int objId = -1; + for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) { + if (tempString.equalsIgnoreCase(scene._bgShapes[idx]._name)) + objId = idx; + } + if (objId == -1) + error("Could not find object %s to change", tempString.c_str()); + + // Should the script be overwritten? + if (str[0] > 0x80) { + // Save the current sequence + _savedSequences.push(SequenceEntry()); + SequenceEntry &seqEntry = _savedSequences.top(); + seqEntry._objNum = objId; + seqEntry._seqTo = scene._bgShapes[objId]._seqTo; + for (uint idx = 0; idx < scene._bgShapes[objId]._seqSize; ++idx) + seqEntry._sequences.push_back(scene._bgShapes[objId]._sequences[idx]); + } + + // Get number of bytes to change + _seqCount = str[1]; + str += (str[0] & 127) + 2; + + // Copy in the new sequence + for (int idx = 0; idx < _seqCount; ++idx, ++str) + scene._bgShapes[objId]._sequences[idx] = str[0] - 1; + + // Reset object back to beginning of new sequence + scene._bgShapes[objId]._frameNumber = 0; + + return RET_CONTINUE; +} + +OpcodeReturn Talk::cmdBanishWindow(const byte *&str) { + People &people = *_vm->_people; + UserInterface &ui = *_vm->_ui; + + if (!(_speaker & SPEAKER_REMOVE)) + people.clearTalking(); + pullSequence(); + + if (_talkToAbort) + return RET_EXIT; + + _speaker |= SPEAKER_REMOVE; + ui.banishWindow(); + ui._menuMode = TALK_MODE; + _noTextYet = true; + + 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; +} + +OpcodeReturn Talk::cmdEnableEndKey(const byte *&str) { + _vm->_ui->_endKeyActive = true; + 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; + + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdHolmesOff(const byte *&str) { + People &people = *_vm->_people; + people[PLAYER]._type = REMOVE; + + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdHolmesOn(const byte *&str) { + People &people = *_vm->_people; + people[PLAYER]._type = CHARACTER; + + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdPause(const byte *&str) { + _charCount = *++str; + _wait = _pauseFlag = true; + + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdPauseWithoutControl(const byte *&str) { + Events &events = *_vm->_events; + Scene &scene = *_vm->_scene; + ++str; + + for (int idx = 0; idx < (str[0] - 1); ++idx) { + scene.doBgAnim(); + if (_talkToAbort) + return RET_EXIT; + + // Check for button press + events.pollEvents(); + events.setButtonState(); + } + + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdRemoveItemFromInventory(const byte *&str) { + Inventory &inv = *_vm->_inventory; + Common::String tempString; + + ++str; + for (int idx = 0; idx < str[0]; ++idx) + tempString += str[idx + 1]; + str += str[0]; + + inv.deleteItemFromInventory(tempString); + + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdRunCAnimation(const byte *&str) { + Scene &scene = *_vm->_scene; + + ++str; + scene.startCAnim((str[0] - 1) & 127, (str[0] & 0x80) ? -1 : 1); + if (_talkToAbort) + 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 = 1; + + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdSetFlag(const byte *&str) { + ++str; + int flag1 = (str[0] - 1) * 256 + str[1] - 1 - (str[1] == 1 ? 1 : 0); + int flag = (flag1 & 0x3fff) * (flag1 >= 0x4000 ? -1 : 1); + _vm->setFlags(flag); + ++str; + + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdSetObject(const byte *&str) { + Scene &scene = *_vm->_scene; + Common::String tempString; + + ++str; + for (int idx = 0; idx < (str[0] & 127); ++idx) + tempString += str[idx + 1]; + + // Set comparison state according to if we want to hide or unhide + bool state = (str[0] >= SPEAKER_REMOVE); + str += str[0] & 127; + + for (uint idx = 0; idx < scene._bgShapes.size(); ++idx) { + Object &object = scene._bgShapes[idx]; + if (tempString.equalsIgnoreCase(object._name)) { + // Only toggle the object if it's not in the desired state already + if ((object._type == HIDDEN && state) || (object._type != HIDDEN && !state)) + object.toggleHidden(); + } + } + + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdStealthModeActivate(const byte *&str) { + _talkStealth = 2; + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdStealthModeDeactivate(const byte *&str) { + Events &events = *_vm->_events; + + _talkStealth = 0; + events.clearKeyboard(); + + 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; + + ++str; + for (int idx = 0; idx < str[0]; ++idx) + tempString += str[idx + 1]; + + scene.toggleObject(tempString); + str += str[0]; + + return RET_SUCCESS; +} + +OpcodeReturn Talk::cmdWalkToCAnimation(const byte *&str) { + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + + ++str; + CAnim &animation = scene._cAnim[str[0] - 1]; + people.walkToCoords(animation._goto, animation._gotoDir); + + 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; +} + +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.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; +} + +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 d26259dbfd..a5f81f38ba 100644 --- a/engines/sherlock/talk.h +++ b/engines/sherlock/talk.h @@ -36,42 +36,83 @@ namespace Sherlock { #define MAX_TALK_FILES 500 enum { - SWITCH_SPEAKER = 128, - RUN_CANIMATION = 129, - ASSIGN_PORTRAIT_LOCATION = 130, - PAUSE = 131, - REMOVE_PORTRAIT = 132, - CLEAR_WINDOW = 133, - ADJUST_OBJ_SEQUENCE = 134, - WALK_TO_COORDS = 135, - PAUSE_WITHOUT_CONTROL = 136, - BANISH_WINDOW = 137, - SUMMON_WINDOW = 138, - SET_FLAG = 139, - SFX_COMMAND = 140, - TOGGLE_OBJECT = 141, - STEALTH_MODE_ACTIVE = 142, - IF_STATEMENT = 143, - ELSE_STATEMENT = 144, - END_IF_STATEMENT = 145, - STEALTH_MODE_DEACTIVATE = 146, - TURN_HOLMES_OFF = 147, - TURN_HOLMES_ON = 148, - GOTO_SCENE = 149, - PLAY_PROLOGUE = 150, - ADD_ITEM_TO_INVENTORY = 151, - SET_OBJECT = 152, - CALL_TALK_FILE = 153, - MOVE_MOUSE = 154, - DISPLAY_INFO_LINE = 155, - CLEAR_INFO_LINE = 156, - WALK_TO_CANIMATION = 157, - REMOVE_ITEM_FROM_INVENTORY = 158, - ENABLE_END_KEY = 159, - DISABLE_END_KEY = 160, - CARRIAGE_RETURN = 161 + OP_SWITCH_SPEAKER = 0, + OP_RUN_CANIMATION = 1, + OP_ASSIGN_PORTRAIT_LOCATION = 2, + OP_PAUSE = 3, + OP_REMOVE_PORTRAIT = 4, + OP_CLEAR_WINDOW = 5, + OP_ADJUST_OBJ_SEQUENCE = 6, + OP_WALK_TO_COORDS = 7, + OP_PAUSE_WITHOUT_CONTROL = 8, + OP_BANISH_WINDOW = 9, + OP_SUMMON_WINDOW = 10, + OP_SET_FLAG = 11, + OP_SFX_COMMAND = 12, + OP_TOGGLE_OBJECT = 13, + OP_STEALTH_MODE_ACTIVE = 14, + OP_IF_STATEMENT = 15, + OP_ELSE_STATEMENT = 16, + OP_END_IF_STATEMENT = 17, + OP_STEALTH_MODE_DEACTIVATE = 18, + OP_TURN_HOLMES_OFF = 19, + OP_TURN_HOLMES_ON = 20, + OP_GOTO_SCENE = 21, + OP_PLAY_PROLOGUE = 22, + OP_ADD_ITEM_TO_INVENTORY = 23, + OP_SET_OBJECT = 24, + OP_CALL_TALK_FILE = 25, + OP_MOVE_MOUSE = 26, + OP_DISPLAY_INFO_LINE = 27, + OP_CLEAR_INFO_LINE = 28, + OP_WALK_TO_CANIMATION = 29, + OP_REMOVE_ITEM_FROM_INVENTORY = 30, + OP_ENABLE_END_KEY = 31, + OP_DISABLE_END_KEY = 32, + OP_CARRIAGE_RETURN = 33, + + OP_MOUSE_OFF_ON = 34, + OP_SET_WALK_CONTROL = 35, + OP_SET_TALK_SEQUENCE = 36, + OP_PLAY_SONG = 37, + OP_WALK_HOLMES_AND_NPC_TO_CANIM = 38, + OP_SET_NPC_PATH_DEST = 39, + OP_NEXT_SONG = 40, + OP_SET_NPC_PATH_PAUSE = 41, + 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 }; +enum OpcodeReturn { RET_EXIT = -1, RET_SUCCESS = 0, RET_CONTINUE = 1 }; + +class SherlockEngine; +class Talk; +namespace Scalpel { class ScalpelUserInterface; }; + +typedef OpcodeReturn(Talk::*OpcodeMethod)(const byte *&str); + struct SequenceEntry { int _objNum; Common::Array<byte> _sequences; @@ -122,24 +163,8 @@ struct TalkSequences { void clear(); }; -class SherlockEngine; -class UserInterface; - class Talk { - friend class UserInterface; -private: - SherlockEngine *_vm; - Common::Stack<SequenceEntry> _savedSequences; - Common::Stack<SequenceEntry> _sequenceStack; - Common::Stack<ScriptStackEntry> _scriptStack; - Common::Array<Statement> _statements; - TalkHistoryEntry _talkHistory[MAX_TALK_FILES]; - int _speaker; - int _talkIndex; - int _scriptSelect; - int _talkStealth; - int _talkToFlag; - int _scriptSaveIndex; + friend class Scalpel::ScalpelUserInterface; private: /** * Remove any voice commands from a loaded statement list @@ -173,6 +198,55 @@ private: * 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; + int _talkIndex; + int _scriptSelect; + int _talkStealth; + int _talkToFlag; + int _scriptSaveIndex; + + // 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 + int _yp; + int _charCount; + int _line; + int _wait; + bool _pauseFlag; + bool _endStr, _noTextYet; + int _seqCount; + const byte *_scriptStart, *_scriptEnd; +protected: + Talk(SherlockEngine *vm); + + 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 cmdHolmesOff(const byte *&str); + OpcodeReturn cmdHolmesOn(const byte *&str); + OpcodeReturn cmdPause(const byte *&str); + OpcodeReturn cmdPauseWithoutControl(const byte *&str); + OpcodeReturn cmdRemoveItemFromInventory(const byte *&str); + OpcodeReturn cmdRunCAnimation(const byte *&str); + OpcodeReturn cmdSetFlag(const byte *&str); + 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); public: bool _talkToAbort; int _talkCounter; @@ -181,8 +255,11 @@ public: Common::String _scriptName; bool _moreTalkUp, _moreTalkDown; int _converseNum; + const byte *_opcodes; + public: - Talk(SherlockEngine *vm); + static Talk *init(SherlockEngine *vm); + virtual ~Talk() {} /** * Return a given talk statement @@ -267,6 +344,65 @@ public: void synchronize(Common::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() {} +}; + +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() {} +}; + } // End of namespace Sherlock #endif diff --git a/engines/sherlock/tattoo/tattoo.cpp b/engines/sherlock/tattoo/tattoo.cpp index d4059ac413..368b24bfcd 100644 --- a/engines/sherlock/tattoo/tattoo.cpp +++ b/engines/sherlock/tattoo/tattoo.cpp @@ -21,15 +21,61 @@ */ #include "sherlock/tattoo/tattoo.h" +#include "engines/util.h" namespace Sherlock { namespace Tattoo { +TattooEngine::TattooEngine(OSystem *syst, const SherlockGameDescription *gameDesc) : + SherlockEngine(syst, gameDesc) { + _creditsActive = false; +} + void TattooEngine::showOpening() { // TODO } +void TattooEngine::initialize() { + initGraphics(640, 480, true); + + // Initialize the base engine + SherlockEngine::initialize(); + + _flags.resize(100 * 8); + + // Add some more files to the cache + _res->addToCache("walk.lib"); + + // Starting scene + _scene->_goToScene = 91; + + // Load an initial palette + loadInitialPalette(); +} + +void TattooEngine::startScene() { + // TODO +} + +void TattooEngine::loadInitialPalette() { + byte palette[768]; + Common::SeekableReadStream *stream = _res->load("room.pal"); + stream->read(palette, PALETTE_SIZE); + _screen->translatePalette(palette); + _screen->setPalette(palette); + + delete stream; +} + +void TattooEngine::drawCredits() { + // TODO +} + +void TattooEngine::eraseCredits() { + // TODO +} + } // End of namespace Tattoo } // End of namespace Scalpel diff --git a/engines/sherlock/tattoo/tattoo.h b/engines/sherlock/tattoo/tattoo.h index b98395597c..bb6310dbe3 100644 --- a/engines/sherlock/tattoo/tattoo.h +++ b/engines/sherlock/tattoo/tattoo.h @@ -30,12 +30,38 @@ namespace Sherlock { namespace Tattoo { class TattooEngine : public SherlockEngine { +private: + /** + * Loads the initial palette for the game + */ + void loadInitialPalette(); protected: + /** + * Initialize the engine + */ + virtual void initialize(); + virtual void showOpening(); + + /** + * Starting a scene within the game + */ + virtual void startScene(); +public: + bool _creditsActive; public: - TattooEngine(OSystem *syst, const SherlockGameDescription *gameDesc) : - SherlockEngine(syst, gameDesc) {} + TattooEngine(OSystem *syst, const SherlockGameDescription *gameDesc); virtual ~TattooEngine() {} + + /** + * Draw credits on the screen + */ + void drawCredits(); + + /** + * Erase any area of the screen covered by credits + */ + void eraseCredits(); }; } // End of namespace Tattoo diff --git a/engines/sherlock/tattoo/tattoo_scene.cpp b/engines/sherlock/tattoo/tattoo_scene.cpp new file mode 100644 index 0000000000..dd96f3358e --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_scene.cpp @@ -0,0 +1,402 @@ +/* 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_scene.h" +#include "sherlock/tattoo/tattoo.h" +#include "sherlock/tattoo/tattoo_user_interface.h" +#include "sherlock/events.h" +#include "sherlock/people.h" + +namespace Sherlock { + +namespace Tattoo { + +TattooScene::TattooScene(SherlockEngine *vm) : Scene(vm) { + _arrowZone = -1; + _mask = _mask1 = nullptr; + _maskCounter = 0; +} + +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); + + // Call the base scene method to handle bg shapes + Scene::checkBgShapes(); + + // Check for any active playing animation + if (_activeCAnim._images && _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)) ? + NORMAL_FORWARD : NORMAL_BEHIND; + break; + case 2: + _activeCAnim._zPlacement = FORWARD; + break; + default: + break; + } + } +} + +void TattooScene::doBgAnimCheckCursor() { + Events &events = *_vm->_events; + UserInterface &ui = *_vm->_ui; + Common::Point mousePos = events.mousePos(); + + // If we're in Look Mode, make sure the cursor is the magnifying glass + if (ui._menuMode == LOOK_MODE && events.getCursor() != MAGNIFY) + events.setCursor(MAGNIFY); + + // See if the mouse is over any of the arrow zones, and if so, change the cursor to the correct + // arrow cursor indicating the direcetion of the exit + if (events.getCursor() == ARROW || events.getCursor() >= EXIT_ZONES_START) { + CursorId cursorId = ARROW; + + if (ui._menuMode == STD_MODE && _arrowZone != -1 && _currentScene != 90) { + for (uint idx = 0; idx < _exits.size(); ++idx) { + Exit &exit = _exits[idx]; + if (exit.contains(mousePos)) + cursorId = (CursorId)(exit._image + EXIT_ZONES_START); + } + } + + 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(); + } + + 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() { + 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) { + 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(); + } + + // Erase any affected background areas + doBgAnimEraseBackground(); + + doBgAnimUpdateBgObjectsAndAnim(); + + ui.drawInterface(); +} + +void TattooScene::doBgAnimUpdateBgObjectsAndAnim() { + TattooEngine &vm = *((TattooEngine *)_vm); + People &people = *_vm->_people; + Screen &screen = *_vm->_screen; + + for (uint idx = 0; idx < _bgShapes.size(); ++idx) { + Object &obj = _bgShapes[idx]; + if (obj._type == ACTIVE_BG_SHAPE || obj._type == NO_SHAPE) + obj.adjustObject(); + } + + for (uint 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; + } + } +} + + +void TattooScene::updateBackground() { + People &people = *_vm->_people; + Screen &screen = *_vm->_screen; + + 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; + } + } + + screen._flushScreen = true; + + for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { + Person &p = people[idx]; + + if (p._type != INVALID) { + if (_goToScene == -1 || _cAnim.size() == 0) { + if (p._type == REMOVE) { + screen.slamArea(p._oldPosition.x, p._oldPosition.y, p._oldSize.x, p._oldSize.y); + p._type = INVALID; + } else { + if (p._tempScaleVal == 256) { + 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 { + int ts = p._imageFrame->sDrawYSize(p._tempScaleVal); + int ty = p._position.y / FIXED_INT_MULTIPLIER - ts; + screen.flushScaleImage(p._imageFrame, Common::Point(p._tempX, ty), + &p._oldPosition.x, &p._oldPosition.y, &p._oldSize.x, &p._oldSize.y, p._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 == 256) + 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 == 256) + 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; + } + } + } + + screen._flushScreen = false; +} + + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/tattoo_scene.h b/engines/sherlock/tattoo/tattoo_scene.h new file mode 100644 index 0000000000..de28306c1b --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_scene.h @@ -0,0 +1,77 @@ +/* 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_SCENE_H +#define SHERLOCK_TATTOO_SCENE_H + +#include "common/scummsys.h" +#include "sherlock/scene.h" + +namespace Sherlock { + +namespace Tattoo { + +class TattooScene : public Scene { +private: + int _arrowZone; + int _maskCounter; + Common::Point _maskOffset; +private: + void doBgAnimCheckCursor(); + + void doBgAnimEraseBackground(); + + /** + * Update the background objects and canimations as part of doBgAnim + */ + void doBgAnimUpdateBgObjectsAndAnim(); +protected: + /** + * 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(); +public: + ImageFile *_mask, *_mask1; + CAnimStream _activeCAnim; +public: + TattooScene(SherlockEngine *vm); + + /** + * Draw all objects and characters. + */ + virtual void doBgAnim(); + + /** + * Update the screen back buffer with all of the scene objects which need + * to be drawn + */ + virtual void updateBackground(); + +}; + +} // 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 new file mode 100644 index 0000000000..e76322833f --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_user_interface.cpp @@ -0,0 +1,150 @@ +/* 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_user_interface.h" +#include "sherlock/tattoo/tattoo_scene.h" +#include "sherlock/tattoo/tattoo.h" + +namespace Sherlock { + +namespace Tattoo { + +TattooUserInterface::TattooUserInterface(SherlockEngine *vm): UserInterface(vm) { + _menuBuffer = nullptr; + _invMenuBuffer = nullptr; + _tagBuffer = nullptr; + _invGraphic = nullptr; +} + +void TattooUserInterface::handleInput() { + // TODO + _vm->_events->pollEventsAndWait(); +} + +void TattooUserInterface::drawInterface(int bufferNum) { + Screen &screen = *_vm->_screen; + TattooEngine &vm = *((TattooEngine *)_vm); + + if (_invMenuBuffer != nullptr) { + Common::Rect r = _invMenuBounds; + r.grow(-3); + r.translate(-screen._currentScroll, 0); + _grayAreas.clear(); + _grayAreas.push_back(r); + + drawGrayAreas(); + screen._backBuffer1.transBlitFrom(*_invMenuBuffer, Common::Point(_invMenuBounds.left, _invMenuBounds.top)); + } + + if (_menuBuffer != nullptr) { + Common::Rect r = _menuBounds; + r.grow(-3); + r.translate(-screen._currentScroll, 0); + _grayAreas.clear(); + _grayAreas.push_back(r); + + drawGrayAreas(); + screen._backBuffer1.transBlitFrom(*_menuBuffer, Common::Point(_invMenuBounds.left, _invMenuBounds.top)); + } + + // 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)); + + // 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)); + + if (vm._creditsActive) + vm.drawCredits(); +} + +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 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); + + // If a canimation is active, restore the graphics underneath it + if (scene._activeCAnim._images != nullptr) + screen.restoreBackground(scene._activeCAnim._oldBounds); + + // If a canimation just ended, remove it's 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) + 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 (_menuBuffer != nullptr) + _menuBounds.translate(screen._currentScroll - oldScroll, 0); + if (_invMenuBuffer != nullptr) + _invMenuBounds.translate(screen._currentScroll - oldScroll, 0); +} + +void TattooUserInterface::drawGrayAreas() { + // TODO +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/tattoo_user_interface.h b/engines/sherlock/tattoo/tattoo_user_interface.h new file mode 100644 index 0000000000..2125f1ba07 --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_user_interface.h @@ -0,0 +1,83 @@ +/* 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_UI_H +#define SHERLOCK_TATTOO_UI_H + +#include "common/scummsys.h" +#include "sherlock/user_interface.h" + +namespace Sherlock { + +namespace Tattoo { + +class TattooUserInterface : public UserInterface { +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; +private: + /** + * Draws designated areas of the screen that are meant to be grayed out using grayscale colors + */ + void drawGrayAreas(); +public: + TattooUserInterface(SherlockEngine *vm); + + /** + * Handles restoring any areas of the back buffer that were/are covered by UI elements + */ + void doBgAnimRestoreUI(); + + /** + * Checks to see if the screen needs to be scrolled. If so, scrolls it towards the target position + */ + void doScroll(); +public: + virtual ~TattooUserInterface() {} + + /** + * Main input handler for the user interface + */ + virtual void handleInput(); + + /** + * Draw the user interface onto the screen's back buffers + */ + virtual void drawInterface(int bufferNum = 3); +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/user_interface.cpp b/engines/sherlock/user_interface.cpp index 524ecf3d2f..9fff7cc999 100644 --- a/engines/sherlock/user_interface.cpp +++ b/engines/sherlock/user_interface.cpp @@ -22,2269 +22,35 @@ #include "sherlock/user_interface.h" #include "sherlock/sherlock.h" -#include "sherlock/settings.h" +#include "sherlock/scalpel/scalpel_user_interface.h" +#include "sherlock/tattoo/tattoo_user_interface.h" namespace Sherlock { -// Main user interface menu control locations -const int MENU_POINTS[12][4] = { - { 13, 153, 72, 165 }, - { 13, 169, 72, 181 }, - { 13, 185, 72, 197 }, - { 88, 153, 152, 165 }, - { 88, 169, 152, 181 }, - { 88, 185, 152, 197 }, - { 165, 153, 232, 165 }, - { 165, 169, 232, 181 }, - { 165, 185, 233, 197 }, - { 249, 153, 305, 165 }, - { 249, 169, 305, 181 }, - { 249, 185, 305, 197 } -}; - -// Inventory control locations */ -const int INVENTORY_POINTS[8][3] = { - { 4, 50, 29 }, - { 52, 99, 77 }, - { 101, 140, 123 }, - { 142, 187, 166 }, - { 189, 219, 198 }, - { 221, 251, 234 }, - { 253, 283, 266 }, - { 285, 315, 294 } -}; - -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" -}; - -/*----------------------------------------------------------------*/ +UserInterface *UserInterface::init(SherlockEngine *vm) { + if (vm->getGameID() == GType_SerratedScalpel) + return new Scalpel::ScalpelUserInterface(vm); + else + return new Tattoo::TattooUserInterface(vm); +} UserInterface::UserInterface(SherlockEngine *vm) : _vm(vm) { - if (_vm->_interactiveFl) { - _controls = new ImageFile("menu.all"); - _controlPanel = new ImageFile("controls.vgs"); - } else { - _controls = nullptr; - _controlPanel = nullptr; - } - - _bgFound = 0; - _oldBgFound = -1; - _keyPress = '\0'; - _helpStyle = false; - _menuCounter = 0; _menuMode = STD_MODE; - _help = _oldHelp = 0; - _lookHelp = 0; - _key = _oldKey = '\0'; - _temp = _oldTemp = 0; - _temp1 = 0; - _invLookFlag = 0; + _menuCounter = 0; + _infoFlag = false; _windowOpen = false; - _oldLook = false; - _keyboardInput = false; - _pause = false; - _cNum = 0; - _selector = _oldSelector = -1; - _windowBounds = Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH - 1, - SHERLOCK_SCREEN_HEIGHT - 1); - _slideWindows = true; - _find = 0; - _oldUse = 0; _endKeyActive = true; + _invLookFlag = 0; + _slideWindows = true; + _helpStyle = false; + _windowBounds = Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH - 1, SHERLOCK_SCREEN_HEIGHT - 1); _lookScriptFlag = false; - _infoFlag = false; -} - -UserInterface::~UserInterface() { - delete _controls; - delete _controlPanel; -} - -void UserInterface::reset() { - _oldKey = -1; - _help = _oldHelp = -1; - _oldTemp = _temp = -1; -} - -void UserInterface::drawInterface(int bufferNum) { - Screen &screen = *_vm->_screen; - - if (bufferNum & 1) - screen._backBuffer1.transBlitFrom((*_controlPanel)[0], Common::Point(0, CONTROLS_Y)); - if (bufferNum & 2) - screen._backBuffer2.transBlitFrom((*_controlPanel)[0], Common::Point(0, CONTROLS_Y)); - if (bufferNum == 3) - screen._backBuffer2.fillRect(0, INFO_LINE, SHERLOCK_SCREEN_WIDTH, INFO_LINE + 10, INFO_BLACK); -} - -void UserInterface::handleInput() { - Events &events = *_vm->_events; - Inventory &inv = *_vm->_inventory; - People &people = *_vm->_people; - Scene &scene = *_vm->_scene; - Screen &screen = *_vm->_screen; - Talk &talk = *_vm->_talk; - - if (_menuCounter) - whileMenuCounter(); - - Common::Point pt = events.mousePos(); - _bgFound = scene.findBgShape(Common::Rect(pt.x, pt.y, pt.x + 1, pt.y + 1)); - _keyPress = '\0'; - - // Check kbd and set the mouse released flag if Enter or space is pressed. - // Otherwise, the pressed _key is stored for later use - if (events.kbHit()) { - Common::KeyState keyState = events.getKey(); - _keyPress = keyState.ascii; - - if (keyState.keycode == Common::KEYCODE_x && keyState.flags & Common::KBD_ALT) { - _vm->quitGame(); - events.pollEvents(); - return; - } - } - - // Do button highlighting check - if (!talk._scriptMoreFlag) { // Don't if scripts are running - if (((events._rightPressed || events._rightReleased) && _helpStyle) || - (!_helpStyle && !_menuCounter)) { - // Handle any default commands if we're in STD_MODE - if (_menuMode == STD_MODE) { - if (pt.y < CONTROLS_Y && - (events._rightPressed || (!_helpStyle && !events._released)) && - (_bgFound != -1) && (_bgFound < 1000) && - (scene._bgShapes[_bgFound]._defaultCommand || - !scene._bgShapes[_bgFound]._description.empty())) { - // If there is no default command, so set it to Look - if (scene._bgShapes[_bgFound]._defaultCommand) - _help = scene._bgShapes[_bgFound]._defaultCommand - 1; - else - _help = 0; - - // Reset 'help' if it is an invalid command - if (_help > 5) - _help = -1; - } else if (pt.y < CONTROLS_Y && - ((events._rightReleased && _helpStyle) || (events._released && !_helpStyle)) && - (_bgFound != -1 && _bgFound < 1000) && - (scene._bgShapes[_bgFound]._defaultCommand || - !scene._bgShapes[_bgFound]._description.empty())) { - // If there is no default command, set it to Look - if (scene._bgShapes[_bgFound]._defaultCommand) - _menuMode = (MenuMode)scene._bgShapes[_bgFound]._defaultCommand; - else - _menuMode = LOOK_MODE; - events._released = true; - events._pressed = events._oldButtons = false; - _help = _oldHelp = -1; - - if (_menuMode == LOOK_MODE) { - // Set the flag to tell the game that this was a right-click - // call to look and should exit without the look button being pressed - _lookHelp = true; - } - } else { - _help = -1; - } - - // Check if highlighting a different button than last time - if (_help != _oldHelp) { - // If another button was highlighted previously, restore it - if (_oldHelp != -1) - restoreButton(_oldHelp); - - // If we're highlighting a new button, then draw it pressed - if (_help != -1) - depressButton(_help); - - _oldHelp = _help; - } - - if (_bgFound != _oldBgFound || _oldBgFound == -1) { - _infoFlag = true; - clearInfo(); - - if (_help != -1 && !scene._bgShapes[_bgFound]._description.empty() - && scene._bgShapes[_bgFound]._description[0] != ' ') - screen.print(Common::Point(0, INFO_LINE + 1), - INFO_FOREGROUND, "%s", scene._bgShapes[_bgFound]._description.c_str()); - - _oldBgFound = _bgFound; - } - } else { - // We're not in STD_MODE - // If there isn't a window open, then revert back to STD_MODE - if (!_windowOpen && events._rightReleased) { - // Restore all buttons - for (int idx = 0; idx < 12; ++idx) - restoreButton(idx); - - _menuMode = STD_MODE; - _key = _oldKey = -1; - _temp = _oldTemp = _lookHelp = _invLookFlag = 0; - events.clearEvents(); - } - } - } - } - - // Reset the old bgshape number if the mouse button is released, so that - // it can e re-highlighted when we come back here - if ((events._rightReleased && _helpStyle) || (events._released && !_helpStyle)) - _oldBgFound = -1; - - // Do routines that should be done before input processing - switch (_menuMode) { - case LOOK_MODE: - if (!_windowOpen) { - if (events._released && _bgFound >= 0 && _bgFound < 1000) { - if (!scene._bgShapes[_bgFound]._examine.empty()) - examine(); - } else { - lookScreen(pt); - } - } - break; - - case MOVE_MODE: - case OPEN_MODE: - case CLOSE_MODE: - case PICKUP_MODE: - lookScreen(pt); - break; - - case TALK_MODE: - if (!_windowOpen) { - bool personFound; - - if (_bgFound >= 1000) { - personFound = false; - if (!events._released) - lookScreen(pt); - } else { - personFound = _bgFound != -1 && scene._bgShapes[_bgFound]._aType == PERSON; - } - - if (events._released && personFound) - talk.talk(_bgFound); - else if (personFound) - lookScreen(pt); - else if (_bgFound < 1000) - clearInfo(); - } - break; - - case USE_MODE: - case GIVE_MODE: - case INV_MODE: - if (inv._invMode == INVMODE_LOOK || inv._invMode == INVMODE_USE || inv._invMode == INVMODE_GIVE) { - if (pt.y > CONTROLS_Y) - lookInv(); - else - lookScreen(pt); - } - break; - - default: - break; - } - - // - // Do input processing - // - if (events._pressed || events._released || events._rightPressed || _keyPress || _pause) { - if (((events._released && (_helpStyle || _help == -1)) || (events._rightReleased && !_helpStyle)) && - (pt.y <= CONTROLS_Y) && (_menuMode == STD_MODE)) { - // The mouse was clicked in the playing area with no action buttons down. - // Check if the mouse was clicked in a script zone. If it was, - // then execute the script. Otherwise, walk to the given position - if (scene.checkForZones(pt, SCRIPT_ZONE) != 0 || - scene.checkForZones(pt, NOWALK_ZONE) != 0) { - // Mouse clicked in script zone - events._pressed = events._released = false; - } else { - people._walkDest = pt; - people._allowWalkAbort = false; - people.goAllTheWay(); - } - - if (_oldKey != -1) { - restoreButton(_oldTemp); - _oldKey = -1; - } - } - - // Handle action depending on selected mode - switch (_menuMode) { - case LOOK_MODE: - if (_windowOpen) - doLookControl(); - break; - - case MOVE_MODE: - doMiscControl(ALLOW_MOVE); - break; - - case TALK_MODE: - if (_windowOpen) - doTalkControl(); - break; - - case OPEN_MODE: - doMiscControl(ALLOW_OPEN); - break; - - case CLOSE_MODE: - doMiscControl(ALLOW_CLOSE); - break; - - case PICKUP_MODE: - doPickControl(); - break; - - case USE_MODE: - case GIVE_MODE: - case INV_MODE: - doInvControl(); - break; - - case FILES_MODE: - doEnvControl(); - break; - - default: - break; - } - - // As long as there isn't an open window, do main input processing. - // Windows are opened when in TALK, USE, INV, and GIVE modes - if ((!_windowOpen && !_menuCounter && pt.y > CONTROLS_Y) || - _keyPress) { - if (events._pressed || events._released || _pause || _keyPress) - doMainControl(); - } - - if (pt.y < CONTROLS_Y && events._pressed && _oldTemp != (int)(_menuMode - 1) && _oldKey != -1) - restoreButton(_oldTemp); - } -} - -void UserInterface::depressButton(int num) { - Screen &screen = *_vm->_screen; - Common::Point pt(MENU_POINTS[num][0], MENU_POINTS[num][1]); - - ImageFrame &frame = (*_controls)[num]; - screen._backBuffer1.transBlitFrom(frame, pt); - screen.slamArea(pt.x, pt.y, pt.x + frame._width, pt.y + frame._height); -} - -void UserInterface::restoreButton(int num) { - Screen &screen = *_vm->_screen; - Common::Point pt(MENU_POINTS[num][0], MENU_POINTS[num][1]); - Graphics::Surface &frame = (*_controls)[num]._frame; - - 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); - - if (!_menuCounter) { - _infoFlag = true; - clearInfo(); - } -} - -void UserInterface::pushButton(int num) { - Events &events = *_vm->_events; - _oldKey = -1; - - if (!events._released) { - if (_oldHelp != -1) - restoreButton(_oldHelp); - if (_help != -1) - restoreButton(_help); - - depressButton(num); - events.wait(6); - } - - restoreButton(num); -} - -void UserInterface::toggleButton(int num) { - Screen &screen = *_vm->_screen; - - if (_menuMode != (MenuMode)(num + 1)) { - _menuMode = (MenuMode)(num + 1); - _oldKey = COMMANDS[num]; - _oldTemp = num; - - if (_keyboardInput) { - if (_oldHelp != -1 && _oldHelp != num) - restoreButton(_oldHelp); - if (_help != -1 && _help != num) - restoreButton(_help); - - _keyboardInput = false; - - ImageFrame &frame = (*_controls)[num]; - Common::Point pt(MENU_POINTS[num][0], MENU_POINTS[num][1]); - screen._backBuffer1.transBlitFrom(frame, pt); - screen.slamArea(pt.x, pt.y, pt.x + frame._width, pt.y + frame._height); - } - } else { - _menuMode = STD_MODE; - _oldKey = -1; - restoreButton(num); - } -} - -void UserInterface::clearInfo() { - if (_infoFlag) { - _vm->_screen->vgaBar(Common::Rect(16, INFO_LINE, SHERLOCK_SCREEN_WIDTH - 19, - INFO_LINE + 10), INFO_BLACK); - _infoFlag = false; - _oldLook = -1; - } -} - -void UserInterface::clearWindow() { - if (_windowOpen) { - _vm->_screen->vgaBar(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2, - SHERLOCK_SCREEN_HEIGHT - 2), INV_BACKGROUND); - } -} - -void UserInterface::whileMenuCounter() { - if (!(--_menuCounter) || _vm->_events->checkInput()) { - _menuCounter = 0; - _infoFlag = true; - clearInfo(); - } -} - -void UserInterface::examine() { - Events &events = *_vm->_events; - Inventory &inv = *_vm->_inventory; - People &people = *_vm->_people; - Scene &scene = *_vm->_scene; - Talk &talk = *_vm->_talk; - Common::Point pt = events.mousePos(); - - if (pt.y < (CONTROLS_Y + 9)) { - Object &obj = scene._bgShapes[_bgFound]; - - if (obj._lookcAnim != 0) { - int canimSpeed = ((obj._lookcAnim & 0xe0) >> 5) + 1; - scene._cAnimFramePause = obj._lookFrames; - _cAnimStr = obj._examine; - _cNum = (obj._lookcAnim & 0x1f) - 1; - - 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); - } - - if (!talk._talkToAbort) { - _cAnimStr = obj._examine; - if (obj._lookFlag) - _vm->setFlags(obj._lookFlag); - } - } else { - // Looking at an inventory item - _cAnimStr = inv[_selector]._examine; - if (inv[_selector]._lookFlag) - _vm->setFlags(inv[_selector]._lookFlag); - } - - if (_invLookFlag) { - // Don't close the inventory window when starting an examine display, since its - // window will slide up to replace the inventory display - _windowOpen = false; - _menuMode = LOOK_MODE; - } - - if (!talk._talkToAbort) { - if (!scene._cAnimFramePause) - printObjectDesc(_cAnimStr, true); - else - // description was already printed in startCAnimation - scene._cAnimFramePause = 0; - } -} - -void UserInterface::lookScreen(const Common::Point &pt) { - Events &events = *_vm->_events; - Inventory &inv = *_vm->_inventory; - Scene &scene = *_vm->_scene; - Screen &screen = *_vm->_screen; - Common::Point mousePos = events.mousePos(); - int temp; - Common::String tempStr; - - // Don't display anything for right button command - if ((events._rightPressed || events._rightPressed) && !events._pressed) - return; - - if (mousePos.y < CONTROLS_Y && (temp = _bgFound) != -1) { - if (temp != _oldLook) { - _infoFlag = true; - clearInfo(); - - if (temp < 1000) - tempStr = scene._bgShapes[temp]._description; - else - tempStr = scene._bgShapes[temp - 1000]._description; - - _infoFlag = true; - clearInfo(); - - // Only print description if there is one - if (!tempStr.empty() && tempStr[0] != ' ') { - // If inventory is active and an item is selected for a Use or Give action - if ((_menuMode == INV_MODE || _menuMode == USE_MODE || _menuMode == GIVE_MODE) && - (inv._invMode == INVMODE_USE || inv._invMode == INVMODE_GIVE)) { - int width1 = 0, width2 = 0; - int x, width; - if (inv._invMode == INVMODE_USE) { - // Using an object - x = width = screen.stringWidth("Use "); - - if (temp < 1000 && scene._bgShapes[temp]._aType != PERSON) - // It's not a person, so make it lowercase - tempStr.setChar(tolower(tempStr[0]), 0); - - x += screen.stringWidth(tempStr); - - // If we're using an inventory object, add in the width - // of the object name and the " on " - if (_selector != -1) { - width1 = screen.stringWidth(inv[_selector]._name); - x += width1; - width2 = screen.stringWidth(" on "); - x += width2; - } - - // If the line will be too long, keep cutting off characters - // until the string will fit - while (x > 280) { - x -= screen.charWidth(tempStr.lastChar()); - tempStr.deleteLastChar(); - } - - int xStart = (SHERLOCK_SCREEN_WIDTH - x) / 2; - screen.print(Common::Point(xStart, INFO_LINE + 1), - INFO_FOREGROUND, "Use "); - - if (_selector != -1) { - screen.print(Common::Point(xStart + width, INFO_LINE + 1), - TALK_FOREGROUND, "%s", inv[_selector]._name.c_str()); - screen.print(Common::Point(xStart + width + width1, INFO_LINE + 1), - INFO_FOREGROUND, " on "); - screen.print(Common::Point(xStart + width + width1 + width2, INFO_LINE + 1), - INFO_FOREGROUND, "%s", tempStr.c_str()); - } else { - screen.print(Common::Point(xStart + width, INFO_LINE + 1), - INFO_FOREGROUND, "%s", tempStr.c_str()); - } - } else if (temp >= 0 && temp < 1000 && _selector != -1 && - scene._bgShapes[temp]._aType == PERSON) { - // Giving an object to a person - width1 = screen.stringWidth(inv[_selector]._name); - x = width = screen.stringWidth("Give "); - x += width1; - width2 = screen.stringWidth(" to "); - x += width2; - x += screen.stringWidth(tempStr); - - // Ensure string will fit on-screen - while (x > 280) { - x -= screen.charWidth(tempStr.lastChar()); - tempStr.deleteLastChar(); - } - - int xStart = (SHERLOCK_SCREEN_WIDTH - x) / 2; - screen.print(Common::Point(xStart, INFO_LINE + 1), - INFO_FOREGROUND, "Give "); - screen.print(Common::Point(xStart + width, INFO_LINE + 1), - TALK_FOREGROUND, "%s", inv[_selector]._name.c_str()); - screen.print(Common::Point(xStart + width + width1, INFO_LINE + 1), - INFO_FOREGROUND, " to "); - screen.print(Common::Point(xStart + width + width1 + width2, INFO_LINE + 1), - INFO_FOREGROUND, "%s", tempStr.c_str()); - } - } else { - screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", tempStr.c_str()); - } - - _infoFlag = true; - _oldLook = temp; - } - } - } else { - clearInfo(); - } -} - -void UserInterface::lookInv() { - Events &events = *_vm->_events; - Inventory &inv = *_vm->_inventory; - Screen &screen = *_vm->_screen; - Common::Point mousePos = events.mousePos(); - - if (mousePos.x > 15 && mousePos.x < 314 && mousePos.y > (CONTROLS_Y1 + 11) - && mousePos.y < (SHERLOCK_SCREEN_HEIGHT - 2)) { - int temp = (mousePos.x - 6) / 52 + inv._invIndex; - if (temp < inv._holdings) { - if (temp < inv._holdings) { - clearInfo(); - screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, - "%s", inv[temp]._description.c_str()); - _infoFlag = true; - _oldLook = temp; - } - } else { - clearInfo(); - } - } else { - clearInfo(); - } -} - -void UserInterface::doEnvControl() { - Events &events = *_vm->_events; - SaveManager &saves = *_vm->_saves; - Scene &scene = *_vm->_scene; - Screen &screen = *_vm->_screen; - Talk &talk = *_vm->_talk; - Common::Point mousePos = events.mousePos(); - static const char ENV_COMMANDS[7] = "ELSUDQ"; - - byte color; - - _key = _oldKey = -1; - _keyboardInput = false; - int found = saves.getHighlightedButton(); - - if (events._pressed || events._released) { - events.clearKeyboard(); - - // Check for a filename entry being highlighted - if ((events._pressed || events._released) && mousePos.y > (CONTROLS_Y + 10)) { - int found1 = 0; - for (_selector = 0; (_selector < ONSCREEN_FILES_COUNT) && !found1; ++_selector) - if (mousePos.y > (CONTROLS_Y + 11 + _selector * 10) && mousePos.y < (CONTROLS_Y + 21 + _selector * 10)) - found1 = 1; - - if (_selector + saves._savegameIndex - 1 < MAX_SAVEGAME_SLOTS + (saves._envMode != SAVEMODE_LOAD)) - _selector = _selector + saves._savegameIndex - 1; - else - _selector = -1; - - if (!found1) - _selector = -1; - } - - // Handle selecting buttons, if any - saves.highlightButtons(found); - - if (found == 0 || found == 5) - saves._envMode = SAVEMODE_NONE; - } - - if (_keyPress) { - _key = toupper(_keyPress); - - // Escape _key will close the dialog - if (_key == Common::KEYCODE_ESCAPE) - _key = 'E'; - - if (_key == 'E' || _key == 'L' || _key == 'S' || _key == 'U' || _key == 'D' || _key == 'Q') { - const char *chP = strchr(ENV_COMMANDS, _key); - int btnIndex = !chP ? -1 : chP - ENV_COMMANDS; - saves.highlightButtons(btnIndex); - _keyboardInput = true; - - if (_key == 'E' || _key == 'Q') { - saves._envMode = SAVEMODE_NONE; - } else if (_key >= '1' && _key <= '9') { - _keyboardInput = true; - _selector = _key - '1'; - if (_selector >= MAX_SAVEGAME_SLOTS + (saves._envMode == SAVEMODE_LOAD ? 0 : 1)) - _selector = -1; - - if (saves.checkGameOnScreen(_selector)) - _oldSelector = _selector; - } else { - _selector = -1; - } - } - } - - if (_selector != _oldSelector) { - if (_oldSelector != -1 && _oldSelector >= saves._savegameIndex && _oldSelector < (saves._savegameIndex + ONSCREEN_FILES_COUNT)) { - screen.print(Common::Point(6, CONTROLS_Y + 12 + (_oldSelector - saves._savegameIndex) * 10), - INV_FOREGROUND, "%d.", _oldSelector + 1); - screen.print(Common::Point(24, CONTROLS_Y + 12 + (_oldSelector - saves._savegameIndex) * 10), - INV_FOREGROUND, "%s", saves._savegames[_oldSelector].c_str()); - } - - if (_selector != -1) { - screen.print(Common::Point(6, CONTROLS_Y + 12 + (_selector - saves._savegameIndex) * 10), - TALK_FOREGROUND, "%d.", _selector + 1); - screen.print(Common::Point(24, CONTROLS_Y + 12 + (_selector - saves._savegameIndex) * 10), - TALK_FOREGROUND, "%s", saves._savegames[_selector].c_str()); - } - - _oldSelector = _selector; - } - - if (events._released || _keyboardInput) { - if ((found == 0 && events._released) || _key == 'E') { - banishWindow(); - _windowBounds.top = CONTROLS_Y1; - - events._pressed = events._released = _keyboardInput = false; - _keyPress = '\0'; - } else if ((found == 1 && events._released) || _key == 'L') { - saves._envMode = SAVEMODE_LOAD; - if (_selector != -1) { - saves.loadGame(_selector + 1); - } - } else if ((found == 2 && events._released) || _key == 'S') { - saves._envMode = SAVEMODE_SAVE; - if (_selector != -1) { - if (saves.checkGameOnScreen(_selector)) - _oldSelector = _selector; - - if (saves.promptForDescription(_selector)) { - saves.saveGame(_selector + 1, saves._savegames[_selector]); - - banishWindow(1); - _windowBounds.top = CONTROLS_Y1; - _key = _oldKey = -1; - _keyPress = '\0'; - _keyboardInput = false; - } else { - if (!talk._talkToAbort) { - screen._backBuffer1.fillRect(Common::Rect(6, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10, - SHERLOCK_SCREEN_WIDTH - 2, CONTROLS_Y + 20 + (_selector - saves._savegameIndex) * 10), INV_BACKGROUND); - screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10), INV_FOREGROUND, - "%d.", _selector + 1); - screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10), INV_FOREGROUND, - "%s", saves._savegames[_selector].c_str()); - - screen.slamArea(6, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10, 311, 10); - _selector = _oldSelector = -1; - } - } - } - } else if (((found == 3 && events._released) || _key == 'U') && saves._savegameIndex) { - bool moreKeys; - do { - saves._savegameIndex--; - screen._backBuffer1.fillRect(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2, - SHERLOCK_SCREEN_HEIGHT - 1), INV_BACKGROUND); - - for (int idx = saves._savegameIndex; idx < (saves._savegameIndex + ONSCREEN_FILES_COUNT); ++idx) { - color = INV_FOREGROUND; - if (idx == _selector && idx >= saves._savegameIndex && idx < (saves._savegameIndex + ONSCREEN_FILES_COUNT)) - color = TALK_FOREGROUND; - - screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (idx - saves._savegameIndex) * 10), color, "%d.", idx + 1); - screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (idx - saves._savegameIndex) * 10), color, "%s", saves._savegames[idx].c_str()); - } - - screen.slamRect(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2, SHERLOCK_SCREEN_HEIGHT)); - - color = !saves._savegameIndex ? COMMAND_NULL : COMMAND_FOREGROUND; - screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), color, true, "Up"); - color = (saves._savegameIndex == MAX_SAVEGAME_SLOTS - ONSCREEN_FILES_COUNT) ? COMMAND_NULL : COMMAND_FOREGROUND; - screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), color, true, "Down"); - - // Check whether there are more pending U keys pressed - moreKeys = false; - if (events.kbHit()) { - Common::KeyState keyState = events.getKey(); - - _key = toupper(keyState.keycode); - moreKeys = _key == 'U'; - } - } while ((saves._savegameIndex) && moreKeys); - } else if (((found == 4 && events._released) || _key == 'D') && saves._savegameIndex < (MAX_SAVEGAME_SLOTS - ONSCREEN_FILES_COUNT)) { - bool moreKeys; - do { - saves._savegameIndex++; - screen._backBuffer1.fillRect(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2, - SHERLOCK_SCREEN_HEIGHT - 1), INV_BACKGROUND); - - for (int idx = saves._savegameIndex; idx < (saves._savegameIndex + ONSCREEN_FILES_COUNT); ++idx) { - if (idx == _selector && idx >= saves._savegameIndex && idx < (saves._savegameIndex + ONSCREEN_FILES_COUNT)) - color = TALK_FOREGROUND; - else - color = INV_FOREGROUND; - - screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (idx - saves._savegameIndex) * 10), color, - "%d.", idx + 1); - screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (idx - saves._savegameIndex) * 10), color, - "%s", saves._savegames[idx].c_str()); - } - - screen.slamRect(Common::Rect(3, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2, SHERLOCK_SCREEN_HEIGHT)); - - color = (!saves._savegameIndex) ? COMMAND_NULL : COMMAND_FOREGROUND; - screen.buttonPrint(Common::Point(ENV_POINTS[3][2], CONTROLS_Y), color, true, "Up"); - - color = (saves._savegameIndex == MAX_SAVEGAME_SLOTS - ONSCREEN_FILES_COUNT) ? COMMAND_NULL : COMMAND_FOREGROUND; - screen.buttonPrint(Common::Point(ENV_POINTS[4][2], CONTROLS_Y), color, true, "Down"); - - // Check whether there are more pending D keys pressed - moreKeys = false; - if (events.kbHit()) { - Common::KeyState keyState; - _key = toupper(keyState.keycode); - - moreKeys = _key == 'D'; - } - } while (saves._savegameIndex < (MAX_SAVEGAME_SLOTS - ONSCREEN_FILES_COUNT) && moreKeys); - } else if ((found == 5 && events._released) || _key == 'Q') { - clearWindow(); - screen.print(Common::Point(0, CONTROLS_Y + 20), INV_FOREGROUND, "Are you sure you wish to Quit ?"); - screen.vgaBar(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y + 10), BORDER_COLOR); - - screen.makeButton(Common::Rect(112, CONTROLS_Y, 160, CONTROLS_Y + 10), 136 - screen.stringWidth("Yes") / 2, "Yes"); - screen.makeButton(Common::Rect(161, CONTROLS_Y, 209, CONTROLS_Y + 10), 184 - screen.stringWidth("No") / 2, "No"); - screen.slamArea(112, CONTROLS_Y, 97, 10); - - do { - scene.doBgAnim(); - - if (talk._talkToAbort) - return; - - events.pollEventsAndWait(); - events.setButtonState(); - mousePos = events.mousePos(); - - if (events.kbHit()) { - Common::KeyState keyState = events.getKey(); - _key = toupper(keyState.keycode); - - if (_key == 'X' && (keyState.flags & Common::KBD_ALT) != 0) { - _vm->quitGame(); - events.pollEvents(); - return; - } - - if (_key == Common::KEYCODE_ESCAPE) - _key = 'N'; - - if (_key == Common::KEYCODE_RETURN || _key == ' ') { - events._pressed = false; - events._released = true; - events._oldButtons = 0; - _keyPress = '\0'; - } - } - - if (events._pressed || events._released) { - if (mousePos.x > 112 && mousePos.x < 159 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 9)) - color = COMMAND_HIGHLIGHTED; - else - color = COMMAND_FOREGROUND; - screen.buttonPrint(Common::Point(136, CONTROLS_Y), color, true, "Yes"); - - if (mousePos.x > 161 && mousePos.x < 208 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 9)) - color = COMMAND_HIGHLIGHTED; - else - color = COMMAND_FOREGROUND; - screen.buttonPrint(Common::Point(184, CONTROLS_Y), color, true, "No"); - } - - if (mousePos.x > 112 && mousePos.x < 159 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 9) && events._released) - _key = 'Y'; - - if (mousePos.x > 161 && mousePos.x < 208 && mousePos.y > CONTROLS_Y && mousePos.y < (CONTROLS_Y + 9) && events._released) - _key = 'N'; - } while (!_vm->shouldQuit() && _key != 'Y' && _key != 'N'); - - if (_key == 'Y') { - _vm->quitGame(); - events.pollEvents(); - return; - } else { - screen.buttonPrint(Common::Point(184, CONTROLS_Y), COMMAND_HIGHLIGHTED, true, "No"); - banishWindow(1); - _windowBounds.top = CONTROLS_Y1; - _key = -1; - } - } else { - if (_selector != -1) { - // Are we already in Load mode? - if (saves._envMode == SAVEMODE_LOAD) { - saves.loadGame(_selector + 1); - } else if (saves._envMode == SAVEMODE_SAVE || saves.isSlotEmpty(_selector)) { - // We're already in save mode, or pointing to an empty save slot - if (saves.checkGameOnScreen(_selector)) - _oldSelector = _selector; - - if (saves.promptForDescription(_selector)) { - saves.saveGame(_selector + 1, saves._savegames[_selector]); - banishWindow(); - _windowBounds.top = CONTROLS_Y1; - _key = _oldKey = -1; - _keyPress = '\0'; - _keyboardInput = false; - } else { - if (!talk._talkToAbort) { - screen._backBuffer1.fillRect(Common::Rect(6, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10, - 317, CONTROLS_Y + 20 + (_selector - saves._savegameIndex) * 10), INV_BACKGROUND); - screen.gPrint(Common::Point(6, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10), - INV_FOREGROUND, "%d.", _selector + 1); - screen.gPrint(Common::Point(24, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10), - INV_FOREGROUND, "%s", saves._savegames[_selector].c_str()); - screen.slamArea(6, CONTROLS_Y + 11 + (_selector - saves._savegameIndex) * 10, 311, 10); - _selector = _oldSelector = -1; - } - } - } - } - } - } -} - -void UserInterface::doInvControl() { - Events &events = *_vm->_events; - Inventory &inv = *_vm->_inventory; - Scene &scene = *_vm->_scene; - Screen &screen = *_vm->_screen; - Talk &talk = *_vm->_talk; - int colors[8]; - Common::Point mousePos = events.mousePos(); - - _key = _oldKey = -1; - _keyboardInput = false; - - // Check whether any inventory slot is highlighted - int found = -1; - Common::fill(&colors[0], &colors[8], (int)COMMAND_FOREGROUND); - for (int idx = 0; idx < 8; ++idx) { - Common::Rect r(INVENTORY_POINTS[idx][0], CONTROLS_Y1, - INVENTORY_POINTS[idx][1], CONTROLS_Y1 + 10); - if (r.contains(mousePos)) { - found = idx; - break; - } - } - - if (events._pressed || events._released) { - events.clearKeyboard(); - - 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"); - - 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"); - inv._invMode = (InvMode)found; - _selector = -1; - } - - if (inv._invIndex) { - screen.print(Common::Point(INVENTORY_POINTS[4][2], CONTROLS_Y1 + 1), colors[4], "^^"); - screen.print(Common::Point(INVENTORY_POINTS[5][2], CONTROLS_Y1 + 1), colors[5], "^"); - } - - if ((inv._holdings - inv._invIndex) > 6) { - screen.print(Common::Point(INVENTORY_POINTS[6][2], CONTROLS_Y1 + 1), colors[6], "_"); - screen.print(Common::Point(INVENTORY_POINTS[7][2], CONTROLS_Y1 + 1), colors[7], "__"); - } - - bool flag = false; - if (inv._invMode == INVMODE_LOOK || inv._invMode == INVMODE_USE || inv._invMode == INVMODE_GIVE) { - Common::Rect r(15, CONTROLS_Y1 + 11, 314, SHERLOCK_SCREEN_HEIGHT - 2); - if (r.contains(mousePos)) { - _selector = (mousePos.x - 6) / 52 + inv._invIndex; - if (_selector < inv._holdings) - flag = true; - } - } - - if (!flag && mousePos.y >(CONTROLS_Y1 + 11)) - _selector = -1; - } - - if (_keyPress) { - _key = toupper(_keyPress); - - if (_key == Common::KEYCODE_ESCAPE) - // Escape will also 'E'xit out of inventory display - _key = 'E'; - - if (_key == 'E' || _key == 'L' || _key == 'U' || _key == 'G' - || _key == '-' || _key == '+') { - InvMode temp = inv._invMode; - - const char *chP = strchr(INVENTORY_COMMANDS, _key); - inv._invMode = !chP ? INVMODE_INVALID : (InvMode)(chP - INVENTORY_COMMANDS); - inv.invCommands(true); - - inv._invMode = temp; - _keyboardInput = true; - if (_key == 'E') - inv._invMode = INVMODE_EXIT; - _selector = -1; - } else { - _selector = -1; - } - } - - if (_selector != _oldSelector) { - if (_oldSelector != -1) { - // Un-highlight - if (_oldSelector >= inv._invIndex && _oldSelector < (inv._invIndex + 6)) - inv.highlight(_oldSelector, BUTTON_MIDDLE); - } - - if (_selector != -1) - inv.highlight(_selector, 235); - - _oldSelector = _selector; - } - - if (events._released || _keyboardInput) { - if ((found == 0 && events._released) || _key == 'E') { - inv.freeInv(); - _infoFlag = true; - clearInfo(); - banishWindow(false); - _key = -1; - events.clearEvents(); - events.setCursor(ARROW); - } else if ((found == 1 && events._released) || (_key == 'L')) { - inv._invMode = INVMODE_LOOK; - } else if ((found == 2 && events._released) || (_key == 'U')) { - inv._invMode = INVMODE_USE; - } else if ((found == 3 && events._released) || (_key == 'G')) { - inv._invMode = INVMODE_GIVE; - } else if (((found == 4 && events._released) || _key == ',') && inv._invIndex) { - if (inv._invIndex >= 6) - inv._invIndex -= 6; - else - inv._invIndex = 0; - - screen.print(Common::Point(INVENTORY_POINTS[4][2], CONTROLS_Y1 + 1), - COMMAND_HIGHLIGHTED, "^^"); - inv.freeGraphics(); - inv.loadGraphics(); - inv.putInv(SLAM_DISPLAY); - inv.invCommands(true); - } else if (((found == 5 && events._released) || _key == '-') && inv._invIndex > 0) { - --inv._invIndex; - screen.print(Common::Point(INVENTORY_POINTS[4][2], CONTROLS_Y1 + 1), COMMAND_HIGHLIGHTED, "^"); - inv.freeGraphics(); - inv.loadGraphics(); - inv.putInv(SLAM_DISPLAY); - inv.invCommands(true); - } else if (((found == 6 && events._released) || _key == '+') && (inv._holdings - inv._invIndex) > 6) { - ++inv._invIndex; - screen.print(Common::Point(INVENTORY_POINTS[6][2], CONTROLS_Y1 + 1), COMMAND_HIGHLIGHTED, "_"); - inv.freeGraphics(); - inv.loadGraphics(); - inv.putInv(SLAM_DISPLAY); - inv.invCommands(true); - } else if (((found == 7 && events._released) || _key == '.') && (inv._holdings - inv._invIndex) > 6) { - inv._invIndex += 6; - if ((inv._holdings - 6) < inv._invIndex) - inv._invIndex = inv._holdings - 6; - - screen.print(Common::Point(INVENTORY_POINTS[7][2], CONTROLS_Y1 + 1), COMMAND_HIGHLIGHTED, "_"); - inv.freeGraphics(); - inv.loadGraphics(); - inv.putInv(SLAM_DISPLAY); - inv.invCommands(true); - } else { - // If something is being given, make sure it's being given to a person - if (inv._invMode == INVMODE_GIVE) { - if (_bgFound != -1 && scene._bgShapes[_bgFound]._aType == PERSON) - _find = _bgFound; - else - _find = -1; - } else { - _find = _bgFound; - } - - if ((mousePos.y < CONTROLS_Y1) && (inv._invMode == INVMODE_LOOK) && (_find >= 0) && (_find < 1000)) { - if (!scene._bgShapes[_find]._examine.empty() && - scene._bgShapes[_find]._examine[0] >= ' ') - inv.refreshInv(); - } else if (_selector != -1 || _find >= 0) { - // Selector is the inventory object that was clicked on, or selected. - // If it's -1, then no inventory item is highlighted yet. Otherwise, - // an object in the scene has been clicked. - if (_selector != -1 && inv._invMode == INVMODE_LOOK - && mousePos.y >(CONTROLS_Y1 + 11)) - inv.refreshInv(); - - if (talk._talkToAbort) - return; - - // Now check for the Use and Give actions. If inv_mode is INVMODE_GIVE, - // that means GIVE is in effect, _selector is the object being - // given, and _find is the target. - // The same applies to USE, except if _selector is -1, then USE - // is being tried on an object in the scene without an inventory - // object being highlighted first. - - if ((inv._invMode == INVMODE_USE || (_selector != -1 && inv._invMode == INVMODE_GIVE)) && _find >= 0) { - events._pressed = events._released = false; - _infoFlag = true; - clearInfo(); - - int tempSel = _selector; // Save the selector - _selector = -1; - - inv.putInv(SLAM_DISPLAY); - _selector = tempSel; // Restore it - InvMode tempMode = inv._invMode; - inv._invMode = INVMODE_USE55; - inv.invCommands(true); - - _infoFlag = true; - clearInfo(); - banishWindow(false); - _key = -1; - - inv.freeInv(); - - 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); - else - // Now inv object has been highlighted - checkUseAction(&scene._bgShapes[_find]._use[0], "*SELF*", MUSE, _find, giveFl); - - _selector = _oldSelector = -1; - } - } - } - } -} - -void UserInterface::doLookControl() { - Events &events = *_vm->_events; - Inventory &inv = *_vm->_inventory; - Screen &screen = *_vm->_screen; - - _key = _oldKey = -1; - _keyboardInput = (_keyPress != '\0'); - - if (events._released || events._rightReleased || _keyboardInput) { - // Is an inventory object being looked at? - if (!_invLookFlag) { - // Is there any remaining text to display? - if (!_descStr.empty()) { - printObjectDesc(_descStr, false); - } else if (!_lookHelp) { - // Need to close the window and depress the Look button - Common::Point pt(MENU_POINTS[0][0], MENU_POINTS[0][1]); - screen._backBuffer2.blitFrom((*_controls)[0], pt); - banishWindow(true); - - _windowBounds.top = CONTROLS_Y1; - _key = _oldKey = COMMANDS[LOOK_MODE - 1]; - _temp = _oldTemp = 0; - _menuMode = LOOK_MODE; - events.clearEvents(); - - // Restore UI - drawInterface(); - } else { - events.setCursor(ARROW); - banishWindow(true); - _windowBounds.top = CONTROLS_Y1; - _key = _oldKey = -1; - _temp = _oldTemp = 0; - _menuMode = STD_MODE; - events.clearEvents(); - } - } else { - // Looking at an inventory object - // Backup the user interface - Surface tempSurface(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT - CONTROLS_Y1); - tempSurface.blitFrom(screen._backBuffer2, Common::Point(0, 0), - Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); - - inv.drawInventory(INVENTORY_DONT_DISPLAY); - banishWindow(true); - - // Restore the ui - screen._backBuffer2.blitFrom(tempSurface, Common::Point(0, CONTROLS_Y1)); - - _windowBounds.top = CONTROLS_Y1; - _key = _oldKey = COMMANDS[LOOK_MODE - 1]; - _temp = _oldTemp = 0; - events.clearEvents(); - _invLookFlag = false; - _menuMode = INV_MODE; - _windowOpen = true; - } - } -} - -void UserInterface::doMainControl() { - Events &events = *_vm->_events; - Inventory &inv = *_vm->_inventory; - SaveManager &saves = *_vm->_saves; - Common::Point pt = events.mousePos(); - - if ((events._pressed || events._released) && pt.y > CONTROLS_Y) { - events.clearKeyboard(); - _key = -1; - - // Check whether the mouse is in any of the command areas - 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 (r.contains(pt)) - _key = COMMANDS[_temp]; - } - --_temp; - } else if (_keyPress) { - // Keyboard control - _keyboardInput = true; - - if (_keyPress >= 'A' && _keyPress <= 'Z') { - const char *c = strchr(COMMANDS, _keyPress); - _temp = !c ? 12 : c - COMMANDS; - } else { - _temp = 12; - } - - if (_temp == 12) - _key = -1; - - if (events._rightPressed) { - _temp = 12; - _key = -1; - } - } else if (!events._released) { - _key = -1; - } - - // Check if the button being pointed to has changed - if (_oldKey != _key && !_windowOpen) { - // Clear the info line - _infoFlag = true; - clearInfo(); - - // If there was an old button selected, restore it - if (_oldKey != -1) { - _menuMode = STD_MODE; - restoreButton(_oldTemp); - } - - // If a new button is being pointed to, highlight it - if (_key != -1 && _temp < 12 && !_keyboardInput) - depressButton(_temp); - - // Save the new button selection - _oldKey = _key; - _oldTemp = _temp; - } - - if (!events._pressed && !_windowOpen) { - switch (_key) { - case 'L': - toggleButton(0); - break; - case 'M': - toggleButton(1); - break; - case 'T': - toggleButton(2); - break; - case 'P': - toggleButton(3); - break; - case 'O': - toggleButton(4); - break; - case 'C': - toggleButton(5); - break; - case 'I': - pushButton(6); - _selector = _oldSelector = -1; - _menuMode = INV_MODE; - inv.drawInventory(PLAIN_INVENTORY); - break; - case 'U': - pushButton(7); - _selector = _oldSelector = -1; - _menuMode = USE_MODE; - inv.drawInventory(USE_INVENTORY_MODE); - break; - case 'G': - pushButton(8); - _selector = _oldSelector = -1; - _menuMode = GIVE_MODE; - inv.drawInventory(GIVE_INVENTORY_MODE); - break; - case 'J': - pushButton(9); - _menuMode = JOURNAL_MODE; - journalControl(); - break; - case 'F': - pushButton(10); - - // Create a thumbnail of the current screen before the files dialog is shown, in case - // the user saves the game - saves.createThumbnail(); - - _selector = _oldSelector = -1; - - if (_vm->_showOriginalSavesDialog) { - // Show the original dialog - _menuMode = FILES_MODE; - saves.drawInterface(); - _windowOpen = true; - } else { - // Show the ScummVM GMM instead - _vm->_canLoadSave = true; - _vm->openMainMenuDialog(); - _vm->_canLoadSave = false; - } - break; - case 'S': - pushButton(11); - _menuMode = SETUP_MODE; - Settings::show(_vm); - break; - default: - break; - } - - _help = _oldHelp = _oldBgFound = -1; - } -} - -void UserInterface::doMiscControl(int allowed) { - Events &events = *_vm->_events; - Scene &scene = *_vm->_scene; - Talk &talk = *_vm->_talk; - - if (events._released) { - _temp = _bgFound; - if (_bgFound != -1) { - // Only allow pointing to objects, not people - if (_bgFound < 1000) { - events.clearEvents(); - Object &obj = scene._bgShapes[_bgFound]; - - switch (allowed) { - case ALLOW_OPEN: - checkAction(obj._aOpen, MOPEN, _temp); - if (_menuMode != TALK_MODE && !talk._talkToAbort) { - _menuMode = STD_MODE; - restoreButton(OPEN_MODE - 1); - _key = _oldKey = -1; - } - break; - - case ALLOW_CLOSE: - checkAction(obj._aClose, MCLOSE, _temp); - if (_menuMode != TALK_MODE && !talk._talkToAbort) { - _menuMode = STD_MODE; - restoreButton(CLOSE_MODE - 1); - _key = _oldKey = -1; - } - break; - - case ALLOW_MOVE: - checkAction(obj._aMove, MMOVE, _temp); - if (_menuMode != TALK_MODE && !talk._talkToAbort) { - _menuMode = STD_MODE; - restoreButton(MOVE_MODE - 1); - _key = _oldKey = -1; - } - break; - - default: - break; - } - } - } - } -} - -void UserInterface::doPickControl() { - Events &events = *_vm->_events; - Scene &scene = *_vm->_scene; - Talk &talk = *_vm->_talk; - - if (events._released) { - if ((_temp = _bgFound) != -1) { - events.clearEvents(); - - // Don't allow characters to be picked up - if (_bgFound < 1000) { - scene._bgShapes[_bgFound].pickUpObject(MPICK); - - if (!talk._talkToAbort && _menuMode != TALK_MODE) { - _key = _oldKey = -1; - _menuMode = STD_MODE; - restoreButton(PICKUP_MODE - 1); - } - } - } - } -} - -void UserInterface::doTalkControl() { - Events &events = *_vm->_events; - Journal &journal = *_vm->_journal; - People &people = *_vm->_people; - Screen &screen = *_vm->_screen; - Sound &sound = *_vm->_sound; - Talk &talk = *_vm->_talk; - Common::Point mousePos = events.mousePos(); - - _key = _oldKey = -1; - _keyboardInput = false; - - 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"); - else if (_endKeyActive) - screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_FOREGROUND, true, "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"); - else if (talk._moreTalkUp) - screen.buttonPrint(Common::Point(159, CONTROLS_Y), COMMAND_FOREGROUND, true, "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"); - else if (talk._moreTalkDown) - screen.buttonPrint(Common::Point(200, CONTROLS_Y), COMMAND_FOREGROUND, true, "Down"); - - bool found = false; - for (_selector = talk._talkIndex; _selector < (int)talk._statements.size() && !found; ++_selector) { - if (mousePos.y > talk._statements[_selector]._talkPos.top && - mousePos.y < talk._statements[_selector]._talkPos.bottom) - found = true; - } - --_selector; - if (!found) - _selector = -1; - } - - if (_keyPress) { - _key = toupper(_keyPress); - if (_key == Common::KEYCODE_ESCAPE) - _key = 'E'; - - // Check for number press indicating reply line - if (_key >= '1' && _key <= ('1' + (int)talk._statements.size() - 1)) { - for (uint idx = 0; idx < talk._statements.size(); ++idx) { - if (talk._statements[idx]._talkMap == (_key - '1')) { - // Found the given statement - _selector = idx; - _key = -1; - _keyboardInput = true; - break; - } - } - } else if (_key == 'E' || _key == 'U' || _key == 'D') { - _keyboardInput = true; - } else { - _selector = -1; - } - } - - if (_selector != _oldSelector) { - // Remove highlighting from previous line, if any - if (_oldSelector != -1) { - if (!((talk._talkHistory[talk._converseNum][_oldSelector] >> (_oldSelector & 7)) & 1)) - talk.talkLine(_oldSelector, talk._statements[_oldSelector]._talkMap, INV_FOREGROUND, - talk._statements[_oldSelector]._talkPos.top, true); - else - talk.talkLine(_oldSelector, talk._statements[_oldSelector]._talkMap, TALK_NULL, - talk._statements[_oldSelector]._talkPos.top, true); - } - - // Add highlighting to new line, if any - if (_selector != -1) - talk.talkLine(_selector, talk._statements[_selector]._talkMap, TALK_FOREGROUND, - talk._statements[_selector]._talkPos.top, true); - - _oldSelector = _selector; - } - - if (events._released || _keyboardInput) { - if (((Common::Rect(99, CONTROLS_Y, 138, CONTROLS_Y + 10).contains(mousePos) && events._released) - || _key == 'E') && _endKeyActive) { - talk.freeTalkVars(); - talk.pullSequence(); - banishWindow(); - _windowBounds.top = CONTROLS_Y1; - } else if (((Common::Rect(140, CONTROLS_Y, 179, CONTROLS_Y + 10).contains(mousePos) && events._released) - || _key == 'U') && talk._moreTalkUp) { - while (talk._statements[--talk._talkIndex]._talkMap == -1) - ; - screen._backBuffer1.fillRect(Common::Rect(5, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2, - SHERLOCK_SCREEN_HEIGHT - 1), INV_BACKGROUND); - talk.displayTalk(false); - - screen.slamRect(Common::Rect(5, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH - 5, SHERLOCK_SCREEN_HEIGHT - 2)); - } else if (((Common::Rect(181, CONTROLS_Y, 220, CONTROLS_Y + 10).contains(mousePos) && events._released) - || _key == 'D') && talk._moreTalkDown) { - do { - ++talk._talkIndex; - } while (talk._talkIndex < (int)talk._statements.size() && talk._statements[talk._talkIndex]._talkMap == -1); - - screen._backBuffer1.fillRect(Common::Rect(5, CONTROLS_Y + 11, SHERLOCK_SCREEN_WIDTH - 2, - SHERLOCK_SCREEN_HEIGHT - 1), INV_BACKGROUND); - talk.displayTalk(false); - - 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"); - - // If the reply is new, add it to the journal - if (!talk._talkHistory[talk._converseNum][_selector]) { - journal.record(talk._converseNum, _selector); - - // Add any Holmes point to Holmes' total, if any - if (talk._statements[_selector]._quotient) - people._holmesQuotient += talk._statements[_selector]._quotient; - } - - // Flag the response as having been used - talk._talkHistory[talk._converseNum][_selector] = true; - - clearWindow(); - screen.print(Common::Point(16, CONTROLS_Y + 12), TALK_FOREGROUND, "Sherlock Holmes"); - talk.talkLine(_selector + 128, talk._statements[_selector]._talkMap, COMMAND_FOREGROUND, CONTROLS_Y + 21, true); - - switch (talk._statements[_selector]._portraitSide & 3) { - case 0: - case 1: - people._portraitSide = 20; - break; - case 2: - people._portraitSide = 220; - break; - case 3: - people._portraitSide = 120; - break; - } - - // Check for flipping Holmes - if (talk._statements[_selector]._portraitSide & REVERSE_DIRECTION) - people._holmesFlip = true; - - talk._speaker = 0; - people.setTalking(0); - - if (!talk._statements[_selector]._voiceFile.empty() && sound._voices) { - sound.playSound(talk._statements[_selector]._voiceFile, WAIT_RETURN_IMMEDIATELY); - - // Set voices as an indicator for waiting - sound._voices = 2; - sound._speechOn = *sound._soundIsOn; - } else { - sound._speechOn = false; - } - - talk.waitForMore(talk._statements[_selector]._statement.size()); - if (talk._talkToAbort) - return; - - people.clearTalking(); - if (talk._talkToAbort) - return; - - while (!_vm->shouldQuit()) { - talk._scriptSelect = _selector; - talk._speaker = talk._talkTo; - talk.doScript(talk._statements[_selector]._reply); - - if (!talk._talkToAbort) { - if (!talk._talkStealth) - clearWindow(); - - 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(); - } - - // Check for another linked talk file - Common::String linkFilename = talk._statements[_selector]._linkFile; - if (!linkFilename.empty() && !talk._scriptMoreFlag) { - talk.freeTalkVars(); - talk.loadTalkFile(linkFilename); - - // Find the first new statement - int select = _selector = _oldSelector = -1; - for (uint idx = 0; idx < talk._statements.size() && select == -1; ++idx) { - if (!talk._statements[idx]._talkMap) - select = talk._talkIndex = idx; - } - - // See if the new statement is a stealth reply - talk._talkStealth = talk._statements[select]._statement.hasPrefix("^") ? 2 : 0; - - // Is the new talk file a standard file, reply first file, or a stealth file - if (!talk._statements[select]._statement.hasPrefix("*") && - !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"); - else - screen.buttonPrint(Common::Point(119, CONTROLS_Y), COMMAND_NULL, true, "Exit"); - - talk.displayTalk(true); - events.setCursor(ARROW); - break; - } else { - _selector = select; - - if (!talk._talkHistory[talk._converseNum][_selector]) - journal.record(talk._converseNum, _selector); - - talk._talkHistory[talk._converseNum][_selector] = true; - } - } else { - talk.freeTalkVars(); - talk.pullSequence(); - banishWindow(); - _windowBounds.top = CONTROLS_Y1; - break; - } - } else { - break; - } - } - - events._pressed = events._released = false; - events._oldButtons = 0; - talk._talkStealth = 0; - - // If a script was pushed onto the script stack, restore it - if (!talk._scriptStack.empty()) { - ScriptStackEntry stackEntry = talk._scriptStack.pop(); - talk._scriptName = stackEntry._name; - talk._scriptSaveIndex = stackEntry._currentIndex; - talk._scriptSelect = stackEntry._select; - } - } - } -} - -void UserInterface::journalControl() { - Events &events = *_vm->_events; - Journal &journal = *_vm->_journal; - Scene &scene = *_vm->_scene; - Screen &screen = *_vm->_screen; - bool doneFlag = false; - - // Draw the journal screen - journal.drawInterface(); - - // Handle journal events - do { - _key = -1; - events.setButtonState(); - - // Handle keypresses - if (events.kbHit()) { - Common::KeyState keyState = events.getKey(); - if (keyState.keycode == Common::KEYCODE_x && (keyState.flags & Common::KBD_ALT)) { - _vm->quitGame(); - return; - } else if (keyState.keycode == Common::KEYCODE_e || keyState.keycode == Common::KEYCODE_ESCAPE) { - doneFlag = true; - } else { - _key = toupper(keyState.keycode); - } - } - - if (!doneFlag) - doneFlag = journal.handleEvents(_key); - } while (!_vm->shouldQuit() && !doneFlag); - - // Finish up - _infoFlag = _keyboardInput = false; - _keyPress = '\0'; - _windowOpen = false; - _windowBounds.top = CONTROLS_Y1; - _key = -1; - _menuMode = STD_MODE; - - // Reset the palette - screen.setPalette(screen._cMap); - - screen._backBuffer1.blitFrom(screen._backBuffer2); - scene.updateBackground(); - screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); -} - -void UserInterface::printObjectDesc(const Common::String &str, bool firstTime) { - Events &events = *_vm->_events; - Inventory &inv = *_vm->_inventory; - Screen &screen = *_vm->_screen; - Talk &talk = *_vm->_talk; - - if (str.hasPrefix("_")) { - _lookScriptFlag = true; - events.setCursor(MAGNIFY); - int savedSelector = _selector; - talk.talkTo(str.c_str() + 1); - _lookScriptFlag = false; - - if (talk._talkToAbort) { - events.setCursor(ARROW); - return; - } - - // Check if looking at an inventory object - if (!_invLookFlag) { - // See if this look was called by a right button click or not - if (!_lookHelp) { - // If it wasn't a right button click, then we need depress - // the look button before we close the window. So save a copy of the - // 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]); - - tempSurface.blitFrom(screen._backBuffer2, Common::Point(0, 0), - Common::Rect(pt.x, pt.y, pt.x + tempSurface.w(), pt.y + tempSurface.h())); - screen._backBuffer2.transBlitFrom((*_controls)[0], pt); - - banishWindow(1); - events.setCursor(MAGNIFY); - _windowBounds.top = CONTROLS_Y1; - _key = _oldKey = COMMANDS[LOOK_MODE - 1]; - _temp = _oldTemp = 0; - _menuMode = LOOK_MODE; - events.clearEvents(); - - screen._backBuffer2.blitFrom(tempSurface, pt); - } else { - events.setCursor(ARROW); - banishWindow(true); - _windowBounds.top = CONTROLS_Y1; - _key = _oldKey = -1; - _temp = _oldTemp = 0; - _menuMode = STD_MODE; - _lookHelp = 0; - events.clearEvents(); - } - } else { - // Looking at an inventory object - _selector = _oldSelector = savedSelector; - - // Reload the inventory graphics and draw the inventory - inv.loadInv(); - inv.putInv(SLAM_SECONDARY_BUFFER); - inv.freeInv(); - banishWindow(1); - - _windowBounds.top = CONTROLS_Y1; - _key = _oldKey = COMMANDS[INV_MODE - 1]; - _temp = _oldTemp = 0; - events.clearEvents(); - - _invLookFlag = 0; - _menuMode = INV_MODE; - _windowOpen = true; - } - - return; - } - - Surface &bb = *screen._backBuffer; - if (firstTime) { - // Only draw the border on the first call - _infoFlag = true; - clearInfo(); - - bb.fillRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, - CONTROLS_Y1 + 10), BORDER_COLOR); - bb.fillRect(Common::Rect(0, CONTROLS_Y + 10, 1, SHERLOCK_SCREEN_HEIGHT - 1), - 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, - SHERLOCK_SCREEN_HEIGHT), BORDER_COLOR); - } - - // Clear background - bb.fillRect(Common::Rect(2, CONTROLS_Y + 10, SHERLOCK_SCREEN_WIDTH - 2, - SHERLOCK_SCREEN_HEIGHT - 2), INV_BACKGROUND); - - _windowBounds.top = CONTROLS_Y; - events.clearEvents(); - - // Loop through displaying up to five lines - bool endOfStr = false; - const char *msgP = str.c_str(); - for (int lineNum = 0; lineNum < ONSCREEN_FILES_COUNT && !endOfStr; ++lineNum) { - int width = 0; - const char *lineStartP = msgP; - - // Determine how much can be displayed on the line - do { - width += screen.charWidth(*msgP++); - } while (width < 300 && *msgP); - - if (*msgP) - --msgP; - else - endOfStr = true; - - // If the line needs to be wrapped, scan backwards to find - // the end of the previous word as a splitting point - if (width >= 300) { - while (*msgP != ' ') - --msgP; - endOfStr = false; - } - - // Print out the line - Common::String line(lineStartP, msgP); - screen.gPrint(Common::Point(16, CONTROLS_Y + 12 + lineNum * 9), - INV_FOREGROUND, "%s", line.c_str()); - - if (!endOfStr) - // Start next line at start of the nxet word after space - ++msgP; - } - - // Handle display depending on whether all the message was shown - if (!endOfStr) { - screen.makeButton(Common::Rect(46, CONTROLS_Y, 272, CONTROLS_Y + 10), - (SHERLOCK_SCREEN_WIDTH - screen.stringWidth(PRESS_KEY_FOR_MORE)) / 2, - PRESS_KEY_FOR_MORE); - screen.gPrint(Common::Point((SHERLOCK_SCREEN_WIDTH - - screen.stringWidth(PRESS_KEY_FOR_MORE)) / 2, CONTROLS_Y), - COMMAND_FOREGROUND, "P"); - _descStr = msgP; - } else { - screen.makeButton(Common::Rect(46, CONTROLS_Y, 272, CONTROLS_Y + 10), - (SHERLOCK_SCREEN_WIDTH - screen.stringWidth(PRESS_KEY_TO_CONTINUE)) / 2, - PRESS_KEY_TO_CONTINUE); - screen.gPrint(Common::Point((SHERLOCK_SCREEN_WIDTH - - screen.stringWidth(PRESS_KEY_TO_CONTINUE)) / 2, CONTROLS_Y), - COMMAND_FOREGROUND, "P"); - _descStr = ""; - } - - if (firstTime) { - if (!_slideWindows) { - screen.slamRect(Common::Rect(0, CONTROLS_Y, - SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); - } else { - // Display the window - summonWindow(); - } - - _selector = _oldSelector = -1; - _windowOpen = true; - } else { - screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, - SHERLOCK_SCREEN_HEIGHT)); - } -} - -void UserInterface::printObjectDesc() { - printObjectDesc(_cAnimStr, true); -} - -void UserInterface::summonWindow(const Surface &bgSurface, bool slideUp) { - Events &events = *_vm->_events; - Screen &screen = *_vm->_screen; - - if (_windowOpen) - // A window is already open, so can't open another one - return; - - if (slideUp) { - // Gradually slide up the display of the window - for (int idx = 1; idx <= bgSurface.h(); idx += 2) { - screen._backBuffer->blitFrom(bgSurface, Common::Point(0, SHERLOCK_SCREEN_HEIGHT - idx), - Common::Rect(0, 0, bgSurface.w(), idx)); - screen.slamRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - idx, - SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); - - events.delay(10); - } - } else { - // Gradually slide down the display of the window - for (int idx = 1; idx <= bgSurface.h(); idx += 2) { - screen._backBuffer->blitFrom(bgSurface, - Common::Point(0, SHERLOCK_SCREEN_HEIGHT - bgSurface.h()), - Common::Rect(0, bgSurface.h() - idx, bgSurface.w(), bgSurface.h())); - screen.slamRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - bgSurface.h(), - SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT - bgSurface.h() + idx)); - - events.delay(10); - } - } - - // Final display of the entire window - screen._backBuffer->blitFrom(bgSurface, Common::Point(0, SHERLOCK_SCREEN_HEIGHT - bgSurface.h()), - Common::Rect(0, 0, bgSurface.w(), bgSurface.h())); - screen.slamArea(0, SHERLOCK_SCREEN_HEIGHT - bgSurface.h(), bgSurface.w(), bgSurface.h()); - - _windowOpen = true; -} - -void UserInterface::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)); - Common::Rect r(0, height, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); - tempSurface.blitFrom(screen._backBuffer1, Common::Point(0, 0), r); - - // Remove drawn window with original user interface - screen._backBuffer1.blitFrom(screen._backBuffer2, - Common::Point(0, height), r); - - // Display the window gradually on-screen - summonWindow(tempSurface, slideUp); -} - -void UserInterface::banishWindow(bool slideUp) { - Events &events = *_vm->_events; - Screen &screen = *_vm->_screen; - - if (_windowOpen) { - if (slideUp || !_slideWindows) { - // Slide window down - // Only slide the window if the window style allows it - if (_slideWindows) { - for (int idx = 2; idx < (SHERLOCK_SCREEN_HEIGHT - CONTROLS_Y); idx += 2) { - // Shift the window down by 2 lines - byte *pSrc = (byte *)screen._backBuffer1.getBasePtr(0, CONTROLS_Y + idx - 2); - byte *pSrcEnd = (byte *)screen._backBuffer1.getBasePtr(0, SHERLOCK_SCREEN_HEIGHT - 2); - byte *pDest = (byte *)screen._backBuffer1.getBasePtr(0, SHERLOCK_SCREEN_HEIGHT); - Common::copy_backward(pSrc, pSrcEnd, pDest); - - // Restore lines from the ui in the secondary back buffer - screen._backBuffer1.blitFrom(screen._backBuffer2, - Common::Point(0, CONTROLS_Y), - Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y + idx)); - - screen.slamArea(0, CONTROLS_Y + idx - 2, SHERLOCK_SCREEN_WIDTH, - SHERLOCK_SCREEN_HEIGHT - CONTROLS_Y - idx + 2); - events.delay(10); - } - - // Restore final two old lines - screen._backBuffer1.blitFrom(screen._backBuffer2, - Common::Point(0, SHERLOCK_SCREEN_HEIGHT - 2), - Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - 2, - SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); - screen.slamArea(0, SHERLOCK_SCREEN_HEIGHT - 2, SHERLOCK_SCREEN_WIDTH, 2); - } else { - // Restore old area to completely erase window - screen._backBuffer1.blitFrom(screen._backBuffer2, - Common::Point(0, CONTROLS_Y), - Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); - screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, - SHERLOCK_SCREEN_HEIGHT)); - } - } else { - // Slide the original user interface up to cover the dialog - for (int idx = 1; idx < (SHERLOCK_SCREEN_HEIGHT - CONTROLS_Y1); idx += 2) { - byte *pSrc = (byte *)screen._backBuffer2.getBasePtr(0, CONTROLS_Y1); - byte *pSrcEnd = (byte *)screen._backBuffer2.getBasePtr(0, CONTROLS_Y1 + idx); - byte *pDest = (byte *)screen._backBuffer1.getBasePtr(0, SHERLOCK_SCREEN_HEIGHT - idx); - Common::copy(pSrc, pSrcEnd, pDest); - - screen.slamArea(0, SHERLOCK_SCREEN_HEIGHT - idx, SHERLOCK_SCREEN_WIDTH, - SHERLOCK_SCREEN_HEIGHT); - events.delay(10); - } - - // Show entire final area - screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(0, CONTROLS_Y1), - Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); - screen.slamRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); - } - - _infoFlag = false; - _windowOpen = false; - } - - _menuMode = STD_MODE; -} - -void UserInterface::checkUseAction(const UseType *use, const Common::String &invName, - const char *const messages[], int objNum, bool giveMode) { - Events &events = *_vm->_events; - Inventory &inv = *_vm->_inventory; - Scene &scene = *_vm->_scene; - Screen &screen = *_vm->_screen; - Talk &talk = *_vm->_talk; - bool printed = messages == nullptr; - - if (objNum >= 1000) { - // Holmes was specified, so do nothing - _infoFlag = true; - clearInfo(); - _infoFlag = true; - - // Display error message - _menuCounter = 30; - screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "You can't do that to yourself."); - return; - } - - // Scan for target item - int targetNum = -1; - if (giveMode) { - for (int idx = 0; idx < USE_COUNT && targetNum == -1; ++idx) { - if ((use[idx]._target.equalsIgnoreCase("*GIVE*") || use[idx]._target.equalsIgnoreCase("*GIVEP*")) - && use[idx]._names[0].equalsIgnoreCase(invName)) { - // Found a match - targetNum = idx; - if (use[idx]._target.equalsIgnoreCase("*GIVE*")) - inv.deleteItemFromInventory(invName); - } - } - } else { - for (int idx = 0; idx < USE_COUNT && targetNum == -1; ++idx) { - if (use[idx]._target.equalsIgnoreCase(invName)) - targetNum = idx; - } - } - - if (targetNum != -1) { - // Found a target, so do the action - const UseType &action = use[targetNum]; - - events.setCursor(WAIT); - - if (action._useFlag) - _vm->setFlags(action._useFlag); - - if (action._cAnimNum != 99) { - if (action._cAnimNum == 0) - scene.startCAnim(9, action._cAnimSpeed); - else - scene.startCAnim(action._cAnimNum - 1, action._cAnimSpeed); - } - - 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 (!talk._talkToAbort) - printed = true; - } - } - - // Print "Done..." as an ending, unless flagged for leaving scene or otherwise flagged - if (scene._goToScene != 1 && !printed && !talk._talkToAbort) { - _infoFlag = true; - clearInfo(); - screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "Done..."); - _menuCounter = 25; - } - } - } else { - // Couldn't find target, so print error - _infoFlag = true; - clearInfo(); - - if (giveMode) { - screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "No, thank you."); - } else if (messages == nullptr) { - 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]); - } - - _infoFlag = true; - _menuCounter = 30; - } - - events.setCursor(ARROW); -} - -void UserInterface::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; - } - } - } - - // Reset cursor back to arrow - events.setCursor(ARROW); + _key = _oldKey = '\0'; + _selector = _oldSelector = -1; + _temp = _oldTemp = 0; + _temp1 = 0; + _lookHelp = 0; } } // End of namespace Sherlock diff --git a/engines/sherlock/user_interface.h b/engines/sherlock/user_interface.h index 1f7b5feaab..042997a3e2 100644 --- a/engines/sherlock/user_interface.h +++ b/engines/sherlock/user_interface.h @@ -50,134 +50,11 @@ enum MenuMode { SETUP_MODE = 12 }; -extern const char COMMANDS[13]; -extern const int MENU_POINTS[12][4]; - -extern const int INVENTORY_POINTS[8][3]; -extern const char INVENTORY_COMMANDS[9]; -extern const char *const PRESS_KEY_FOR_MORE; -extern const char *const PRESS_KEY_TO_CONTINUE; - -class SherlockEngine; -class Inventory; -class Talk; -class UserInterface; - class UserInterface { - friend class Inventory; - friend class Settings; - friend class Talk; -private: +protected: SherlockEngine *_vm; - ImageFile *_controlPanel; - ImageFile *_controls; - int _bgFound; - int _oldBgFound; - char _keyPress; - int _lookHelp; - int _help, _oldHelp; - char _key, _oldKey; - int _temp, _oldTemp; - int _oldLook; - bool _keyboardInput; - bool _pause; - int _cNum; - int _selector, _oldSelector; - Common::String _cAnimStr; - bool _lookScriptFlag; - Common::Rect _windowBounds; - Common::String _descStr; - int _find; - int _oldUse; -private: - /** - * Draws the image for a user interface button in the down/pressed state. - */ - void depressButton(int num); - - /** - * If he mouse button is pressed, then calls depressButton to draw the button - * as pressed; if not, it will show it as released with a call to "restoreButton". - */ - void pushButton(int num); - - /** - * By the time this method has been called, the graphics for the button change - * have already been drawn. This simply takes care of switching the mode around - * accordingly - */ - void toggleButton(int num); - - /** - * Creates a text window and uses it to display the in-depth description - * of the highlighted object - */ - void examine(); - /** - * Print the name of an object in the scene - */ - void lookScreen(const Common::Point &pt); - - /** - * Gets the item in the inventory the mouse is on and display's it's description - */ - void lookInv(); - - /** - * Handles input when the file list window is being displayed - */ - void doEnvControl(); - - /** - * Handle input whilst the inventory is active - */ - void doInvControl(); - - /** - * Handles waiting whilst an object's description window is open. - */ - void doLookControl(); - - /** - * Handles input until one of the user interface buttons/commands is selected - */ - void doMainControl(); - - /** - * Handles the input for the MOVE, OPEN, and CLOSE commands - */ - void doMiscControl(int allowed); - - /** - * Handles input for picking up items - */ - void doPickControl(); - - /** - * Handles input when in talk mode. It highlights the buttons and available statements, - * and handles allowing the user to click on them - */ - void doTalkControl(); - - /** - * Handles events when the Journal is active. - * @remarks Whilst this would in theory be better in the Journal class, since it displays in - * the user interface, it uses so many internal UI fields, that it sort of made some sense - * to put it in the UserInterface class. - */ - void journalControl(); - - /** - * 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[], - int objNum, bool giveMode); - - /** - * Called for OPEN, CLOSE, and MOVE actions are being done - */ - void checkAction(ActionType &action, const char *const messages[], int objNum); + UserInterface(SherlockEngine *vm); public: MenuMode _menuMode; int _menuCounter; @@ -185,74 +62,67 @@ public: bool _windowOpen; bool _endKeyActive; int _invLookFlag; - int _temp1; bool _slideWindows; bool _helpStyle; + Common::Rect _windowBounds; + bool _lookScriptFlag; + + // 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; + int _selector, _oldSelector; + int _temp, _oldTemp; + int _temp1; + int _lookHelp; public: - UserInterface(SherlockEngine *vm); - ~UserInterface(); + static UserInterface *init(SherlockEngine *vm); + virtual ~UserInterface() {} /** * Resets the user interface */ - void reset(); + virtual void reset() {} /** * Draw the user interface onto the screen's back buffers - */ - void drawInterface(int bufferNum = 3); + */ + virtual void drawInterface(int bufferNum = 3) {} /** * Main input handler for the user interface */ - void handleInput(); + virtual void handleInput() {} /** - * Clears the info line of the screen + * Displays a passed window by gradually scrolling it vertically on-screen */ - void clearInfo(); + virtual void summonWindow(const Surface &bgSurface, bool slideUp = true) {} /** - * Clear any active text window + * Slide the window stored in the back buffer onto the screen */ - void clearWindow(); + virtual void summonWindow(bool slideUp = true, int height = CONTROLS_Y) {} /** - * Handles counting down whilst checking for input, then clears the info line. + * Close a currently open window + * @param flag 0 = slide old window down, 1 = slide prior UI back up */ - void whileMenuCounter(); + virtual void banishWindow(bool slideUp = true) {} /** - * Print the description of an object - */ - void printObjectDesc(const Common::String &str, bool firstTime); - - /** - * Print the previously selected object's decription + * Clears the info line of the screen */ - void printObjectDesc(); + virtual void clearInfo() {} /** - * Displays a passed window by gradually scrolling it vertically on-screen - */ - void summonWindow(const Surface &bgSurface, bool slideUp = true); - - /** - * Slide the window stored in the back buffer onto the screen - */ - void summonWindow(bool slideUp = true, int height = CONTROLS_Y); - - /** - * Close a currently open window - * @param flag 0 = slide old window down, 1 = slide prior UI back up + * Clear any active text window */ - void banishWindow(bool slideUp = true); + virtual void clearWindow() {} /** - * Draws the image for the given user interface button in the up - * (not selected) position + * Print the previously selected object's decription */ - void restoreButton(int num); + virtual void printObjectDesc() {} }; } // End of namespace Sherlock |