diff options
-rw-r--r-- | engines/sherlock/debugger.cpp | 2 | ||||
-rw-r--r-- | engines/sherlock/graphics.cpp | 7 | ||||
-rw-r--r-- | engines/sherlock/graphics.h | 1 | ||||
-rw-r--r-- | engines/sherlock/inventory.cpp | 92 | ||||
-rw-r--r-- | engines/sherlock/inventory.h | 24 | ||||
-rw-r--r-- | engines/sherlock/objects.cpp | 12 | ||||
-rw-r--r-- | engines/sherlock/people.cpp | 15 | ||||
-rw-r--r-- | engines/sherlock/people.h | 2 | ||||
-rw-r--r-- | engines/sherlock/scalpel/scalpel.cpp | 31 | ||||
-rw-r--r-- | engines/sherlock/scalpel/scalpel.h | 1 | ||||
-rw-r--r-- | engines/sherlock/scene.cpp | 72 | ||||
-rw-r--r-- | engines/sherlock/scene.h | 6 | ||||
-rw-r--r-- | engines/sherlock/sherlock.cpp | 34 | ||||
-rw-r--r-- | engines/sherlock/sherlock.h | 3 | ||||
-rw-r--r-- | engines/sherlock/talk.cpp | 8 | ||||
-rw-r--r-- | engines/sherlock/talk.h | 4 |
16 files changed, 267 insertions, 47 deletions
diff --git a/engines/sherlock/debugger.cpp b/engines/sherlock/debugger.cpp index 1e0716c3d3..b8ac8bef6a 100644 --- a/engines/sherlock/debugger.cpp +++ b/engines/sherlock/debugger.cpp @@ -51,7 +51,7 @@ bool Debugger::cmd_scene(int argc, const char **argv) { debugPrintf("Format: scene <room>\n"); return true; } else { - _vm->_scene->_goToRoom = strToInt(argv[1]); + _vm->_scene->_goToScene = strToInt(argv[1]); return false; } } diff --git a/engines/sherlock/graphics.cpp b/engines/sherlock/graphics.cpp index 8c096e3c72..63988a2f30 100644 --- a/engines/sherlock/graphics.cpp +++ b/engines/sherlock/graphics.cpp @@ -145,7 +145,12 @@ void Surface::transBlitFrom(const Graphics::Surface &src, const Common::Point &p * Fill a given area of the surface with a given color */ void Surface::fillRect(int x1, int y1, int x2, int y2, byte color) { - Graphics::Surface::fillRect(Common::Rect(x1, y1, x2, y2), color); + fillRect(Common::Rect(x1, y1, x2, y2), color); +} + +void Surface::fillRect(const Common::Rect &r, byte color) { + Graphics::Surface::fillRect(r, color); + addDirtyRect(r); } /** diff --git a/engines/sherlock/graphics.h b/engines/sherlock/graphics.h index 0536c016bf..b33495a2b9 100644 --- a/engines/sherlock/graphics.h +++ b/engines/sherlock/graphics.h @@ -47,6 +47,7 @@ public: bool flipped = false, int overrideColor = 0); void fillRect(int x1, int y1, int x2, int y2, byte color); + void fillRect(const Common::Rect &r, byte color); Surface getSubArea(const Common::Rect &r); }; diff --git a/engines/sherlock/inventory.cpp b/engines/sherlock/inventory.cpp index 634e664f5a..cbb69f1041 100644 --- a/engines/sherlock/inventory.cpp +++ b/engines/sherlock/inventory.cpp @@ -21,7 +21,99 @@ */ #include "sherlock/inventory.h" +#include "sherlock/sherlock.h" namespace Sherlock { +Inventory::Inventory(SherlockEngine *vm) : Common::Array<InventoryItem>(), _vm(vm) { + Common::fill(&_invShapes[0], &_invShapes[MAX_VISIBLE_INVENTORY], (ImageFile *)nullptr); + _invGraphicsLoaded = false; + _invIndex = 0; + _holdings = 0; +} + +Inventory::~Inventory() { + freeGraphics(); +} + +void Inventory::freeInventory() { + freeGraphics(); + + _names.clear(); + _invGraphicsLoaded = false; +} + +/** + * Free any loaded inventory graphics + */ +void Inventory::freeGraphics() { + for (uint idx = 0; idx < MAX_VISIBLE_INVENTORY; ++idx) + delete _invShapes[idx]; + + Common::fill(&_invShapes[0], &_invShapes[MAX_VISIBLE_INVENTORY], (ImageFile *)nullptr); + _invGraphicsLoaded = false; +} + +/** Load the list of names the inventory items correspond to. + */ +void Inventory::loadInv() { + // Exit if the inventory names are already loaded + if (_names.size() > 0) + return; + + // Load the inventory names + Common::SeekableReadStream *stream = _vm->_res->load("invent.txt"); + _names.clear(); + + while (stream->pos() < stream->size()) { + Common::String name; + char c; + while ((c = stream->readByte()) != 0) + name += c; + + _names.push_back(name); + } + + delete stream; +} + +/** + * Load the list of names of graphics for the inventory + */ +void Inventory::loadGraphics() { + if (_invGraphicsLoaded) + return; + + // Default all inventory slots to empty + Common::fill(&_invShapes[0], &_invShapes[MAX_VISIBLE_INVENTORY], (ImageFile *)nullptr); + + for (int idx = _invIndex; (idx < _holdings) && (idx - _invIndex) < 6; ++idx) { + // Get the name of the item to be dispalyed, figure out it's accompanying + // .VGS file with it's picture, and then load it + int invNum = findInv((*this)[idx]._name); + Common::String fName = Common::String::format("item%02d.vgs", invNum); + + _invShapes[idx] = new ImageFile(fName); + } + + _invGraphicsLoaded = true; +} + +/** + * Searches through the list of names that correspond to the inventory items + * and returns the numer that matches the passed name + */ +int Inventory::findInv(const Common::String &name) { + int result = -1; + + for (uint idx = 0; (idx < _holdings) && result == -1; ++idx) { + if (scumm_stricmp(name.c_str(), _names[idx].c_str()) == 0) + result = idx; + } + + if (result == -1) + result = 1; + return result; +} + } // End of namespace Sherlock diff --git a/engines/sherlock/inventory.h b/engines/sherlock/inventory.h index dc56e93841..e561f0318a 100644 --- a/engines/sherlock/inventory.h +++ b/engines/sherlock/inventory.h @@ -25,9 +25,13 @@ #include "common/scummsys.h" #include "common/array.h" +#include "common/str-array.h" +#include "sherlock/resources.h" namespace Sherlock { +#define MAX_VISIBLE_INVENTORY 6 + struct InventoryItem { int _requiredFlag; Common::String _name; @@ -37,10 +41,26 @@ struct InventoryItem { }; class Inventory : public Common::Array<InventoryItem> { +private: + SherlockEngine *_vm; +public: + ImageFile *_invShapes[MAX_VISIBLE_INVENTORY]; + Common::StringArray _names; + bool _invGraphicsLoaded; + int _invIndex; + int _holdings; + void freeGraphics(); public: - uint _holdings; + Inventory(SherlockEngine *vm); + ~Inventory(); + + void freeInventory(); + + void loadInv(); + + void loadGraphics(); - Inventory() : Common::Array<InventoryItem>(), _holdings(0) {} + int findInv(const Common::String &name); }; } // End of namespace Sherlock diff --git a/engines/sherlock/objects.cpp b/engines/sherlock/objects.cpp index 6b681b42e9..1643bef7b0 100644 --- a/engines/sherlock/objects.cpp +++ b/engines/sherlock/objects.cpp @@ -159,7 +159,7 @@ void Sprite::adjustSprite() { setImageFrame(); // Check to see if character has entered an exit zone - if (!_walkCount && scene._walkedInScene && scene._goToRoom == -1) { + if (!_walkCount && scene._walkedInScene && scene._goToScene == -1) { Common::Rect charRect(_position.x / 100 - 5, _position.y / 100 - 2, _position.x / 100 + 5, _position.y / 100 + 2); Exit *exit = scene.checkForExit(charRect); @@ -819,11 +819,11 @@ int Object::checkNameForCodes(const Common::String &name, Common::StringArray *m default: if (ch >= '0' && ch <= '9') { - scene._goToRoom = atoi(name.c_str() + 1); + scene._goToScene = atoi(name.c_str() + 1); - if (scene._goToRoom < 97 && _vm->_map[scene._goToRoom].x) { - _vm->_over.x = _vm->_map[scene._goToRoom].x * 100 - 600; - _vm->_over.y = _vm->_map[scene._goToRoom].y * 100 + 900; + if (scene._goToScene < 97 && _vm->_map[scene._goToScene].x) { + _vm->_over.x = _vm->_map[scene._goToScene].x * 100 - 600; + _vm->_over.y = _vm->_map[scene._goToScene].y * 100 + 900; } if ((p = strchr(name.c_str(), ',')) != nullptr) { @@ -844,7 +844,7 @@ int Object::checkNameForCodes(const Common::String &name, Common::StringArray *m scene._hsavedFs = 100 + atoi(p + 1); } } else { - scene._goToRoom = 100; + scene._goToScene = 100; } people[AL]._position = Common::Point(0, 0); diff --git a/engines/sherlock/people.cpp b/engines/sherlock/people.cpp index d2fbb3d8c5..91689e296c 100644 --- a/engines/sherlock/people.cpp +++ b/engines/sherlock/people.cpp @@ -100,6 +100,21 @@ bool People::loadWalk() { } /** + * If the walk data has been loaded, then it will be freed + */ +bool People::freeWalk() { + if (_walkLoaded) { + delete _player._images; + _player._images = nullptr; + + _walkLoaded = false; + return true; + } else { + return false; + } +} + +/** * Set the variables for moving a character from one poisition to another * in a straight line - goAllTheWay must have been previously called to * check for any obstacles in the path. diff --git a/engines/sherlock/people.h b/engines/sherlock/people.h index 7c218671c9..de84675e66 100644 --- a/engines/sherlock/people.h +++ b/engines/sherlock/people.h @@ -79,6 +79,8 @@ public: bool loadWalk(); + bool freeWalk(); + void setWalking(); void gotoStand(Sprite &sprite); diff --git a/engines/sherlock/scalpel/scalpel.cpp b/engines/sherlock/scalpel/scalpel.cpp index 1fe1901212..3ec971895a 100644 --- a/engines/sherlock/scalpel/scalpel.cpp +++ b/engines/sherlock/scalpel/scalpel.cpp @@ -43,7 +43,6 @@ ScalpelEngine::ScalpelEngine(OSystem *syst, const SherlockGameDescription *gameD SherlockEngine(syst, gameDesc) { _chess = nullptr; _darts = nullptr; - _tempFadeStyle = 0; _chessResult = 0; } @@ -70,7 +69,7 @@ void ScalpelEngine::initialize() { _map.push_back(Common::Point(MAP_X[idx], MAP_Y[idx])); // Starting scene - _scene->_goToRoom = 4; + _scene->_goToScene = 4; } /** @@ -181,7 +180,7 @@ bool ScalpelEngine::showOfficeCutscene() { * Starting a scene within the game */ void ScalpelEngine::startScene() { - if (_scene->_goToRoom == 100 || _scene->_goToRoom == 98) { + if (_scene->_goToScene == 100 || _scene->_goToScene == 98) { // Chessboard selection if (_sound->_musicOn) { if (_sound->loadSong(100)) { @@ -190,7 +189,7 @@ void ScalpelEngine::startScene() { } } - _scene->_goToRoom = _chess->doChessBoard(); + _scene->_goToScene = _chess->doChessBoard(); _sound->freeSong(); _scene->_hsavedPos = Common::Point(-1, -1); @@ -203,17 +202,17 @@ void ScalpelEngine::startScene() { // 53: Moorehead's death / subway train // 55: Fade out and exit // 70: Brumwell suicide - switch (_scene->_goToRoom) { + switch (_scene->_goToScene) { case 2: case 52: case 53: case 70: - if (_sound->_musicOn && _sound->loadSong(_scene->_goToRoom)) { + if (_sound->_musicOn && _sound->loadSong(_scene->_goToScene)) { if (_sound->_music) _sound->startSong(); } - switch (_scene->_goToRoom) { + switch (_scene->_goToScene) { case 2: // Blackwood's capture _res->addToCache("final2.vda", "epilogue.lib"); @@ -269,7 +268,7 @@ void ScalpelEngine::startScene() { _animation->playPrologue("SUBWAY3", 1, 0, false, 4); // Set fading to direct fade temporary so the transition goes quickly. - _tempFadeStyle = _screen->_fadeStyle ? 257 : 256; + _scene->_tempFadeStyle = _screen->_fadeStyle ? 257 : 256; _screen->_fadeStyle = false; break; @@ -282,28 +281,28 @@ void ScalpelEngine::startScene() { } // Except for the Moorehead Murder scene, fade to black first - if (_scene->_goToRoom != 53) { + if (_scene->_goToScene != 53) { _events->wait(40); _screen->fadeToBlack(3); } - switch (_scene->_goToRoom) { + switch (_scene->_goToScene) { case 52: - _scene->_goToRoom = 27; // Go to the Lawyer's Office + _scene->_goToScene = 27; // Go to the Lawyer's Office _scene->_bigPos = Common::Point(0, 0); // Overland scroll position _scene->_overPos = Common::Point(22900 - 600, 9400 + 900); // Overland position _scene->_oldCharPoint = 27; break; case 53: - _scene->_goToRoom = 17; // Go to St. Pancras Station + _scene->_goToScene = 17; // Go to St. Pancras Station _scene->_bigPos = Common::Point(0, 0); // Overland scroll position _scene->_overPos = Common::Point(32500 - 600, 3000 + 900); // Overland position _scene->_oldCharPoint = 17; break; default: - _scene->_goToRoom = 4; // Back to Baker st. + _scene->_goToScene = 4; // Back to Baker st. _scene->_bigPos = Common::Point(0, 0); // Overland scroll position _scene->_overPos = Common::Point(14500 - 600, 8400 + 900); // Overland position _scene->_oldCharPoint = 4; @@ -327,13 +326,13 @@ void ScalpelEngine::startScene() { _events->loadCursors("rmouse.vgs"); _events->setCursor(ARROW); - if (_scene->_goToRoom == 99) { + if (_scene->_goToScene == 99) { // Chess Board _darts->playDarts(); - _chessResult = _scene->_goToRoom = 19; // Go back to the bar + _chessResult = _scene->_goToScene = 19; // Go back to the bar } - _chessResult = _scene->_goToRoom; + _chessResult = _scene->_goToScene; } /** diff --git a/engines/sherlock/scalpel/scalpel.h b/engines/sherlock/scalpel/scalpel.h index 2d5deeb882..caf33052aa 100644 --- a/engines/sherlock/scalpel/scalpel.h +++ b/engines/sherlock/scalpel/scalpel.h @@ -35,7 +35,6 @@ class ScalpelEngine : public SherlockEngine { private: Chess *_chess; Darts *_darts; - int _tempFadeStyle; int _chessResult; bool showCityCutscene(); diff --git a/engines/sherlock/scene.cpp b/engines/sherlock/scene.cpp index 92a8757fd8..e0b5dc1711 100644 --- a/engines/sherlock/scene.cpp +++ b/engines/sherlock/scene.cpp @@ -118,7 +118,7 @@ Scene::Scene(SherlockEngine *vm): _vm(vm) { for (int idx = 0; idx < SCENES_COUNT; ++idx) Common::fill(&_stats[idx][0], &_stats[idx][9], false); _currentScene = -1; - _goToRoom = -1; + _goToScene = -1; _changes = false; _charPoint = _oldCharPoint = 0; _windowOpen = false; @@ -139,6 +139,8 @@ Scene::Scene(SherlockEngine *vm): _vm(vm) { _lookHelp = false; _animating = 0; _doBgAnimDone = true; + _oldLook = 0; + _tempFadeStyle = 0; _controlPanel = new ImageFile("controls.vgs"); _controls = nullptr; // new ImageFile("menu.all"); @@ -156,7 +158,14 @@ Scene::~Scene() { void Scene::clear() { } +/** + * Handles loading the scene specified by _goToScene + */ void Scene::selectScene() { + Events &events = *_vm->_events; + People &people = *_vm->_people; + Screen &screen = *_vm->_screen; + // Reset fields _windowOpen = _infoFlag = false; _menuMode = STD_MODE; @@ -165,12 +174,39 @@ void Scene::selectScene() { _oldTemp = _temp = 0; // Load the scene - Common::String sceneFile = Common::String::format("res%02d", _goToRoom); - _rrmName = Common::String::format("res%02d.rrm", _goToRoom); - _currentScene = _goToRoom; - _goToRoom = -1; + Common::String sceneFile = Common::String::format("res%02d", _goToScene); + _rrmName = Common::String::format("res%02d.rrm", _goToScene); + _currentScene = _goToScene; + _goToScene = -1; loadScene(sceneFile); + + // If the fade style was changed from running amovie, then reset it + if (_tempFadeStyle) { + screen._fadeStyle = _tempFadeStyle; + _tempFadeStyle = 0; + } + + people._walkDest = Common::Point(people[AL]._position.x / 100, + people[AL]._position.y / 100); + + _restoreFlag = true; + events.clearEvents(); + + // If there were any scripst waiting to be run, but were interrupt by a running + // canimation (probably the last scene's exit canim), clear the _scriptMoreFlag + if (_vm->_scriptMoreFlag == 3) + _vm->_scriptMoreFlag = 0; +} + +/** + * Fres all the graphics and other dynamically allocated data for the scene + */ +void Scene::freeScene() { + _vm->_inventory->freeInventory(); + + _roomBounds.clear(); + _canimShapes.clear(); } /** @@ -506,7 +542,7 @@ void Scene::checkSceneFlags(bool flag) { } // Check inventory - for (uint idx = 0; idx < _vm->_inventory->_holdings; ++idx) { + for (int idx = 0; idx < _vm->_inventory->_holdings; ++idx) { InventoryItem &ii = (*_vm->_inventory)[idx]; if (ii._requiredFlag && !_vm->readFlags(ii._requiredFlag)) { // Kill object: move it after the active holdings @@ -1013,10 +1049,10 @@ int Scene::startCAnim(int cAnimNum, int playRate) { cObj.checkObject(_bgShapes[0]); if (gotoCode > 0 && !talk._talkToAbort) { - _goToRoom = gotoCode; + _goToScene = gotoCode; - if (_goToRoom < 97 && _vm->_map[_goToRoom].x) { - _overPos = _vm->_map[_goToRoom]; + if (_goToScene < 97 && _vm->_map[_goToScene].x) { + _overPos = _vm->_map[_goToScene]; } } @@ -1305,7 +1341,7 @@ void Scene::doBgAnim() { _animating = 0; screen.slamRect(Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCENE_HEIGHT)); } else { - if (people[AL]._type != INVALID && ((_goToRoom == -1 || _ongoingCans == 0))) { + if (people[AL]._type != INVALID && ((_goToScene == -1 || _ongoingCans == 0))) { if (people[AL]._type == REMOVE) { screen.slamRect(Common::Rect( people[AL]._oldPosition.x, people[AL]._oldPosition.y, @@ -1325,7 +1361,7 @@ void Scene::doBgAnim() { for (uint idx = 0; idx < _bgShapes.size(); ++idx) { Object &o = _bgShapes[idx]; - if ((o._type == ACTIVE_BG_SHAPE || o._type == REMOVE) && _goToRoom == -1) { + 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); } @@ -1347,7 +1383,7 @@ void Scene::doBgAnim() { people._portrait._type = INVALID; } - if (_goToRoom == -1) { + if (_goToScene == -1) { for (uint idx = 0; idx < _bgShapes.size(); ++idx) { Object &o = _bgShapes[idx]; if (o._type == NO_SHAPE && (o._flags & 1) == 0) { @@ -1363,7 +1399,7 @@ void Scene::doBgAnim() { for (int idx = _canimShapes.size() - 1; idx >= 0; --idx) { Object &o = _canimShapes[idx]; if (o._type == REMOVE) { - if (_goToRoom == -1) + if (_goToScene == -1) screen.slamArea(o._position.x, o._position.y, o._delta.x, o._delta.y); _canimShapes.remove_at(idx); @@ -1389,8 +1425,16 @@ void Scene::doBgAnim() { } } +/** + * Clears the info line of the screen + */ void Scene::clearInfo() { - // TODO + if (_infoFlag) { + _vm->_screen->fillRect(16, INFO_LINE, SHERLOCK_SCREEN_WIDTH - 200, INFO_LINE + 9, + INFO_BLACK); + _infoFlag = false; + _oldLook = -1; + } } diff --git a/engines/sherlock/scene.h b/engines/sherlock/scene.h index 4b76739402..25c97c097a 100644 --- a/engines/sherlock/scene.h +++ b/engines/sherlock/scene.h @@ -120,6 +120,7 @@ private: int _selector; bool _invLookFlag; bool _lookHelp; + int _oldLook; bool loadScene(const Common::String &filename); @@ -134,7 +135,7 @@ private: void checkBgShapes(ImageFrame *frame, const Common::Point &pt); public: int _currentScene; - int _goToRoom; + int _goToScene; bool _changes; bool _stats[SCENES_COUNT][9]; bool _savedStats[SCENES_COUNT][9]; @@ -170,6 +171,7 @@ public: bool _restoreFlag; int _animating; bool _doBgAnimDone; + int _tempFadeStyle; public: Scene(SherlockEngine *vm); ~Scene(); @@ -178,6 +180,8 @@ public: void selectScene(); + void freeScene(); + void checkSceneFlags(bool mode); Exit *checkForExit(const Common::Rect &r); diff --git a/engines/sherlock/sherlock.cpp b/engines/sherlock/sherlock.cpp index 330da6075b..e023bf9b16 100644 --- a/engines/sherlock/sherlock.cpp +++ b/engines/sherlock/sherlock.cpp @@ -75,7 +75,7 @@ void SherlockEngine::initialize() { _animation = new Animation(this); _debugger = new Debugger(this); _events = new Events(this); - _inventory = new Inventory(); + _inventory = new Inventory(this); _journal = new Journal(); _people = new People(this); _scene = new Scene(this); @@ -102,14 +102,40 @@ Common::Error SherlockEngine::run() { // Initialize and load the scene. _scene->selectScene(); - // TODO: Implement game and remove this dummy loop - while (!shouldQuit()) - _events->pollEventsAndWait(); + // Scene handling loop + sceneLoop(); } return Common::kNoError; } +void SherlockEngine::sceneLoop() { + while (!shouldQuit() && _scene->_goToScene != -1) { + // See if a script needs to be completed from either a goto room code, + // or a script that was interrupted by another script + if (_scriptMoreFlag == 1 || _scriptMoreFlag == 3) + _talk->talkTo(_scriptName); + else + _scriptMoreFlag = 0; + + // Handle any input from the keyboard or mouse + handleInput(); + + if (_scene->_hsavedPos.x == -1) + _scene->doBgAnim(); + } + + _scene->freeScene(); + _talk->freeTalkVars(); + _people->freeWalk(); + +} + +void SherlockEngine::handleInput() { + // TODO +} + + /** * Read the state of a global flag */ diff --git a/engines/sherlock/sherlock.h b/engines/sherlock/sherlock.h index 92aee9cdc3..11bea9bdcd 100644 --- a/engines/sherlock/sherlock.h +++ b/engines/sherlock/sherlock.h @@ -68,6 +68,9 @@ class Resource; class SherlockEngine : public Engine { private: + void sceneLoop(); + + void handleInput(); protected: virtual void initialize(); diff --git a/engines/sherlock/talk.cpp b/engines/sherlock/talk.cpp index 414f3b90a6..4c10b7b7ef 100644 --- a/engines/sherlock/talk.cpp +++ b/engines/sherlock/talk.cpp @@ -34,4 +34,12 @@ void Talk::talkTo(const Common::String &name) { // TODO } +/** + * Clear loaded talk data + */ +void Talk::freeTalkVars() { + _statements.clear(); +} + + } // End of namespace Sherlock diff --git a/engines/sherlock/talk.h b/engines/sherlock/talk.h index 55a4c0fd69..fc73994cc6 100644 --- a/engines/sherlock/talk.h +++ b/engines/sherlock/talk.h @@ -43,13 +43,15 @@ class Talk { private: SherlockEngine *_vm; public: - Common::Array<TalkHistoryEntry> _history; + Common::Array<TalkHistoryEntry> _statements; bool _talkToAbort; int _talkCounter; public: Talk(SherlockEngine *vm); void talkTo(const Common::String &name); + + void freeTalkVars(); }; } // End of namespace Sherlock |