/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "common/util.h" #include "sherlock/sherlock.h" #include "sherlock/objects.h" #include "sherlock/people.h" #include "sherlock/scene.h" #include "sherlock/scalpel/scalpel_people.h" namespace Sherlock { #define START_FRAME 0 #define UPPER_LIMIT 0 #define LOWER_LIMIT (IS_SERRATED_SCALPEL ? CONTROLS_Y : SHERLOCK_SCREEN_HEIGHT) #define LEFT_LIMIT 0 #define RIGHT_LIMIT SHERLOCK_SCREEN_WIDTH #define NUM_ADJUSTED_WALKS 21 // Distance to walk around WALK_AROUND boxes #define CLEAR_DIST_X 5 #define CLEAR_DIST_Y 0 struct AdjustWalk { char _vgsName[9]; int _xAdjust; int _flipXAdjust; int _yAdjust; } ; #define NUM_ADJUSTED_WALKS 21 static const AdjustWalk ADJUST_WALKS[NUM_ADJUSTED_WALKS] = { { "TUPRIGHT", -7, -19, 6 }, { "TRIGHT", 8, -14, 0 }, { "TDOWNRG", 14, -12, 0 }, { "TWUPRIGH", 12, 4, 2 }, { "TWRIGHT", 31, -14, 0 }, { "TWDOWNRG", 6, -24, 0 }, { "HTUPRIGH", 2, -20, 0 }, { "HTRIGHT", 28, -20, 0 }, { "HTDOWNRG", 8, -2, 0 }, { "GTUPRIGH", 4, -12, 0 }, { "GTRIGHT", 12, -16, 0 }, { "GTDOWNRG", 10, -18, 0 }, { "JTUPRIGH", 8, -10, 0 }, { "JTRIGHT", 22, -6, 0 }, { "JTDOWNRG", 4, -20, 0 }, { "CTUPRIGH", 10, 0, 0 }, { "CTRIGHT", 26, -22, 0 }, { "CTDOWNRI", 16, 4, 0 }, { "ITUPRIGH", 0, 0, 0 }, { "ITRIGHT", 20, 0, 0 }, { "ITDOWNRG", 8, 0, 0 } }; SherlockEngine *Sprite::_vm; /*----------------------------------------------------------------*/ BaseObject::BaseObject() { _type = INVALID; _sequences = nullptr; _images = nullptr; _imageFrame = nullptr; _walkCount = 0; _allow = 0; _frameNumber = 0; _lookFlag = 0; _requiredFlag = 0; _status = 0; _misc = 0; _maxFrames = 0; _flags = 0; _aType = OBJECT; _lookFrames = 0; _seqCounter = 0; _lookFacing = 0; _lookcAnim = 0; _seqStack = 0; _seqTo = 0; _descOffset = 0; _seqCounter2 = 0; _seqSize = 0; _quickDraw = 0; _scaleVal = 0; _requiredFlags1 = 0; _gotoSeq = 0; _talkSeq = 0; _restoreSlot = 0; } bool BaseObject::hasAborts() const { int seqNum = _talkSeq; // See if the object is in it's regular sequence bool startChecking = !seqNum || _type == CHARACTER; uint idx = 0; do { // Get the Frame value int v = _sequences[idx++]; // See if we found an Allow Talk Interrupt Code if (startChecking && v == ALLOW_TALK_CODE) return true; // If we've started checking and we've encountered another Talk or Listen Sequence Code, // then we're done checking this sequence because this is where it would repeat if (startChecking && (v == TALK_SEQ_CODE || v == TALK_LISTEN_CODE)) return false; // See if we've found the beginning of a Talk Sequence if ((v == TALK_SEQ_CODE && seqNum < 128) || (v == TALK_LISTEN_CODE && seqNum >= 128)) { // If checking was already on and we came across one of these codes, then there couldn't // have been an Allow Talk Interrupt code in the sequence we were checking, so we're done. if (startChecking) return false; seqNum--; // See if we're at the correct Talk Sequence Number if (!(seqNum & 127)) { // Correct Sequence, Start Checking Now startChecking = true; } } else { // Move ahead any extra because of special control codes switch (v) { case 0: idx++; break; case MOVE_CODE: case TELEPORT_CODE: idx += 4; break; case CALL_TALK_CODE:idx += 8; break; case HIDE_CODE: idx += 2; break; } } } while (idx < _seqSize); return true; } /*----------------------------------------------------------------*/ void Sprite::clear() { _name = ""; _description = ""; _examine.clear(); _pickUp = ""; _walkSequences.clear(); _sequences = nullptr; _images = nullptr; _imageFrame = nullptr; _walkCount = 0; _allow = 0; _frameNumber = _sequenceNumber = 0; _position.x = _position.y = 0; _delta.x = _delta.y = 0; _oldPosition.x = _oldPosition.y = 0; _oldSize.x = _oldSize.y = 0; _goto.x = _goto.y = 0; _type = INVALID; _pickUp.clear(); _noShapeSize.x = _noShapeSize.y = 0; _status = 0; _misc = 0; _numFrames = 0; _altImages = nullptr; _altSeq = 0; Common::fill(&_stopFrames[0], &_stopFrames[8], (ImageFrame *)nullptr); } void Sprite::setImageFrame() { int frameNum = MAX(_frameNumber, 0); int imageNumber = _walkSequences[_sequenceNumber][frameNum]; if (IS_SERRATED_SCALPEL) imageNumber = imageNumber + _walkSequences[_sequenceNumber][0] - 2; else if (imageNumber > _numFrames) imageNumber = 1; // Get the images to use ImageFile *images = _altSeq ? _altImages : _images; assert(images); // Set the frame pointer _imageFrame = &(*images)[imageNumber]; } void Sprite::adjustSprite() { Map &map = *_vm->_map; People &people = *_vm->_people; Scene &scene = *_vm->_scene; Talk &talk = *_vm->_talk; if (_type == INVALID || (_type == CHARACTER && scene._animating)) return; if (!talk._talkCounter && _type == CHARACTER && _walkCount) { // Handle active movement for the sprite _position += _delta; --_walkCount; if (!_walkCount) { // If there any points left for the character to walk to along the // route to a destination, then move to the next point if (!people._walkTo.empty()) { people._walkDest = people._walkTo.pop(); people.setWalking(); } else { people.gotoStand(*this); } } } if (_type == CHARACTER && !map._active) { if ((_position.y / FIXED_INT_MULTIPLIER) > LOWER_LIMIT) { _position.y = LOWER_LIMIT * FIXED_INT_MULTIPLIER; people.gotoStand(*this); } if ((_position.y / FIXED_INT_MULTIPLIER) < UPPER_LIMIT) { _position.y = UPPER_LIMIT * FIXED_INT_MULTIPLIER; people.gotoStand(*this); } if ((_position.x / FIXED_INT_MULTIPLIER) < LEFT_LIMIT) { _position.x = LEFT_LIMIT * FIXED_INT_MULTIPLIER; people.gotoStand(*this); } } else if (!map._active) { _position.y = CLIP((int)_position.y, (int)UPPER_LIMIT, (int)LOWER_LIMIT); _position.x = CLIP((int)_position.x, (int)LEFT_LIMIT, (int)RIGHT_LIMIT); } if (!map._active || (map._frameChangeFlag = !map._frameChangeFlag)) ++_frameNumber; if (_walkSequences[_sequenceNumber][_frameNumber] == 0) { switch (_sequenceNumber) { case Scalpel::STOP_UP: case Scalpel::STOP_DOWN: case Scalpel::STOP_LEFT: case Scalpel::STOP_RIGHT: case Scalpel::STOP_UPRIGHT: case Scalpel::STOP_UPLEFT: case Scalpel::STOP_DOWNRIGHT: case Scalpel::STOP_DOWNLEFT: // We're in a stop sequence, so reset back to the last frame, so // the character is shown as standing still --_frameNumber; break; default: // Move 1 past the first frame - we need to compensate, since we // already passed the frame increment _frameNumber = 1; break; } } // Update the _imageFrame to point to the new frame's image setImageFrame(); // Check to see if character has entered an exit zone if (!_walkCount && scene._walkedInScene && scene._goToScene == -1) { Common::Rect charRect(_position.x / FIXED_INT_MULTIPLIER - 5, _position.y / FIXED_INT_MULTIPLIER - 2, _position.x / FIXED_INT_MULTIPLIER + 5, _position.y / FIXED_INT_MULTIPLIER + 2); Exit *exit = scene.checkForExit(charRect); if (exit) { scene._goToScene = exit->_scene; if (exit->_people.x != 0) { people._hSavedPos = exit->_people; people._hSavedFacing = exit->_peopleDir; if (people._hSavedFacing > 100 && people._hSavedPos.x < 1) people._hSavedPos.x = 100; } } } } void Sprite::checkSprite() { Events &events = *_vm->_events; People &people = *_vm->_people; Scene &scene = *_vm->_scene; Screen &screen = *_vm->_screen; Talk &talk = *_vm->_talk; Point32 pt; Common::Rect objBounds; Common::Point spritePt(_position.x / FIXED_INT_MULTIPLIER, _position.y / FIXED_INT_MULTIPLIER); if (!talk._talkCounter && _type == CHARACTER) { pt = _walkCount ? _position + _delta : _position; pt.x /= 100; pt.y /= 100; for (uint idx = 0; idx < scene._bgShapes.size() && !talk._talkToAbort; ++idx) { Object &obj = scene._bgShapes[idx]; if (obj._aType <= PERSON || obj._type == INVALID || obj._type == HIDDEN) continue; if (obj._type == NO_SHAPE) { objBounds = Common::Rect(obj._position.x, obj._position.y, obj._position.x + obj._noShapeSize.x + 1, obj._position.y + obj._noShapeSize.y + 1); } else { int xp = obj._position.x + obj._imageFrame->_offset.x; int yp = obj._position.y + obj._imageFrame->_offset.y; objBounds = Common::Rect(xp, yp, xp + obj._imageFrame->_frame.w + 1, yp + obj._imageFrame->_frame.h + 1); } if (objBounds.contains(pt)) { if (objBounds.contains(spritePt)) { // Current point is already inside the the bounds, so impact occurred // on a previous call. So simply do nothing until we're clear of the box switch (obj._aType) { case TALK_MOVE: if (_walkCount) { // Holmes is moving obj._type = HIDDEN; obj.setFlagsAndToggles(); talk.talkTo(obj._use[0]._target); } break; case PAL_CHANGE: case PAL_CHANGE2: if (_walkCount) { int palStart = atoi(obj._use[0]._names[0].c_str()) * 3; int palLength = atoi(obj._use[0]._names[1].c_str()) * 3; int templ = atoi(obj._use[0]._names[2].c_str()) * 3; if (templ == 0) templ = 100; // Ensure only valid palette change data found if (palLength > 0) { // Figure out how far into the shape Holmes is so that we // can figure out what percentage of the original palette // to set the current palette to int palPercent = (pt.x - objBounds.left) * 100 / objBounds.width(); palPercent = palPercent * templ / 100; if (obj._aType == PAL_CHANGE) // Invert percentage palPercent = 100 - palPercent; for (int i = palStart; i < (palStart + palLength); ++i) screen._sMap[i] = screen._cMap[i] * palPercent / 100; events.pollEvents(); screen.setPalette(screen._sMap); } } break; case TALK: case TALK_EVERY: obj._type = HIDDEN; obj.setFlagsAndToggles(); talk.talkTo(obj._use[0]._target); break; default: break; } } else { // New impact just occurred switch (obj._aType) { case BLANK_ZONE: // A blank zone masks out all other remaining zones underneath it. // If this zone is hit, exit the outer loop so we do not check anymore return; case SOLID: case TALK: // Stop walking if (obj._aType == TALK) { obj.setFlagsAndToggles(); talk.talkTo(obj._use[0]._target); } else { people.gotoStand(*this); } break; case TALK_EVERY: if (obj._aType == TALK_EVERY) { obj._type = HIDDEN; obj.setFlagsAndToggles(); talk.talkTo(obj._use[0]._target); } else { people.gotoStand(*this); } break; case FLAG_SET: obj.setFlagsAndToggles(); obj._type = HIDDEN; break; case WALK_AROUND: if (objBounds.contains(people._walkTo.front())) { // Reached zone people.gotoStand(*this); } else { // Destination not within box, walk to best corner Common::Point walkPos; if (spritePt.x >= objBounds.left && spritePt.x < objBounds.right) { // Impact occurred due to vertical movement. Determine whether to // travel to the left or right side if (_delta.x > 0) // Go to right side walkPos.x = objBounds.right + CLEAR_DIST_X; else if (_delta.x < 0) { // Go to left side walkPos.x = objBounds.left - CLEAR_DIST_X; } else { // Going straight up or down. So choose best side if (spritePt.x >= (objBounds.left + objBounds.width() / 2)) walkPos.x = objBounds.right + CLEAR_DIST_X; else walkPos.x = objBounds.left - CLEAR_DIST_X; } walkPos.y = (_delta.y >= 0) ? objBounds.top - CLEAR_DIST_Y : objBounds.bottom + CLEAR_DIST_Y; } else { // Impact occurred due to horizontal movement if (_delta.y > 0) // Go to bottom of box walkPos.y = objBounds.bottom + CLEAR_DIST_Y; else if (_delta.y < 0) // Go to top of box walkPos.y = objBounds.top - CLEAR_DIST_Y; else { // Going straight horizontal, so choose best side if (spritePt.y >= (objBounds.top + objBounds.height() / 2)) walkPos.y = objBounds.bottom + CLEAR_DIST_Y; else walkPos.y = objBounds.top - CLEAR_DIST_Y; } walkPos.x = (_delta.x >= 0) ? objBounds.left - CLEAR_DIST_X : objBounds.right + CLEAR_DIST_X; } walkPos.x += people[AL]._imageFrame->_frame.w / 2; people._walkDest = walkPos; people._walkTo.push(walkPos); people.setWalking(); } break; case DELTA: _position.x += 200; break; default: break; } } } } } } const Common::Rect Sprite::getOldBounds() const { return Common::Rect(_oldPosition.x, _oldPosition.y, _oldPosition.x + _oldSize.x, _oldPosition.y + _oldSize.y); } void Sprite::setObjTalkSequence(int seq) { assert(seq != -1 && _type == CHARACTER); if (_seqTo) { // reset to previous value _walkSequences[_sequenceNumber]._sequences[_frameNumber] = _seqTo; _seqTo = 0; } _sequenceNumber = _gotoSeq; _frameNumber = 0; checkWalkGraphics(); } void Sprite::checkWalkGraphics() { People &people = *_vm->_people; int npcNum = -1; if (_images == nullptr) { freeAltGraphics(); return; } Common::String filename = Common::String::format("%s.vgs", _walkSequences[_sequenceNumber]._vgsName.c_str()); // Set the adjust depending on if we have to fine tune the x position of this particular graphic _adjust.x = _adjust.y = 0; for (int idx = 0; idx < NUM_ADJUSTED_WALKS; ++idx) { if (!scumm_strnicmp(_walkSequences[_sequenceNumber]._vgsName.c_str(), ADJUST_WALKS[idx]._vgsName, strlen(ADJUST_WALKS[idx]._vgsName))) { if (_walkSequences[_sequenceNumber]._horizFlip) _adjust.x = ADJUST_WALKS[idx]._flipXAdjust; else _adjust.x = ADJUST_WALKS[idx]._xAdjust; _adjust.y = ADJUST_WALKS[idx]._yAdjust; break; } } // See if we're already using Alternate Graphics if (_altSeq) { // See if the VGS file called for is different than the alternate graphics already loaded if (!_walkSequences[_sequenceNumber]._vgsName.compareToIgnoreCase(_walkSequences[_altSeq - 1]._vgsName)) { // Different AltGraphics, Free the old ones freeAltGraphics(); } } // If there is no Alternate Sequence set, see if we need to load a new one if (!_altSeq) { // Find which NPC this is so we can check the name of the graphics loaded for (int idx = 0; idx < MAX_CHARACTERS; ++idx) { if (this == &people[idx]) { npcNum = idx; break; } } if (npcNum != -1) { // See if the VGS file called for is different than the main graphics which are already loaded if (!filename.compareToIgnoreCase(people[npcNum]._walkVGSName)) { // See if this is one of the more used Walk Graphics stored in WALK.LIB for (int idx = 0; idx < NUM_IN_WALK_LIB; ++idx) { if (!scumm_stricmp(filename.c_str(), WALK_LIB_NAMES[idx])) { people._useWalkLib = true; break; } } _altImages = new ImageFile(filename); people._useWalkLib = false; _altSeq = _sequenceNumber + 1; } } } // If this is a different seqeunce from the current sequence, reset the appropriate variables if (_sequences != &_walkSequences[_sequenceNumber]._sequences[0]) { _seqTo = _seqCounter = _seqCounter2 = _seqStack = _startSeq = 0; _sequences = &_walkSequences[_sequenceNumber]._sequences[0]; _seqSize = _walkSequences[_sequenceNumber]._sequences.size(); } setImageFrame(); } void Sprite::freeAltGraphics() { if (_altImages != nullptr) { delete _altImages; _altImages = nullptr; } _altSeq = 0; } /*----------------------------------------------------------------*/ void WalkSequence::load(Common::SeekableReadStream &s) { char buffer[9]; s.read(buffer, 9); _vgsName = Common::String(buffer); _horizFlip = s.readByte() != 0; _sequences.resize(s.readUint16LE()); s.read(&_sequences[0], _sequences.size()); } /*----------------------------------------------------------------*/ WalkSequences &WalkSequences::operator=(const WalkSequences &src) { resize(src.size()); for (uint idx = 0; idx < size(); ++idx) { const WalkSequence &wSrc = src[idx]; WalkSequence &wDest = (*this)[idx]; wDest._horizFlip = wSrc._horizFlip; wDest._sequences.resize(wSrc._sequences.size()); Common::copy(&wSrc._sequences[0], &wSrc._sequences[0] + wSrc._sequences.size(), &wDest._sequences[0]); } return *this; } /*----------------------------------------------------------------*/ void ActionType::load(Common::SeekableReadStream &s) { char buffer[12]; _cAnimNum = s.readByte(); _cAnimSpeed = s.readByte(); if (_cAnimSpeed & 0x80) _cAnimSpeed = -(_cAnimSpeed & 0x7f); for (int idx = 0; idx < NAMES_COUNT; ++idx) { s.read(buffer, 12); _names[idx] = Common::String(buffer); } } /*----------------------------------------------------------------*/ UseType::UseType() { _cAnimNum = _cAnimSpeed = 0; _useFlag = 0; } void UseType::load(Common::SeekableReadStream &s, bool isRoseTattoo) { char buffer[12]; if (isRoseTattoo) { s.read(buffer, 12); _verb = Common::String(buffer); } _cAnimNum = s.readByte(); _cAnimSpeed = s.readByte(); if (_cAnimSpeed & 0x80) _cAnimSpeed = -(_cAnimSpeed & 0x7f); for (int idx = 0; idx < NAMES_COUNT; ++idx) { s.read(buffer, 12); _names[idx] = Common::String(buffer); } _useFlag = s.readSint16LE(); if (!isRoseTattoo) s.skip(6); s.read(buffer, 12); _target = Common::String(buffer); } /*----------------------------------------------------------------*/ SherlockEngine *Object::_vm; bool Object::_countCAnimFrames; void Object::setVm(SherlockEngine *vm) { _vm = vm; _countCAnimFrames = false; } Object::Object(): BaseObject() { _sequenceNumber = 0; _sequenceOffset = 0; _pickup = 0; _defaultCommand = 0; _pickupFlag = 0; } void Object::load(Common::SeekableReadStream &s, bool isRoseTattoo) { char buffer[41]; s.read(buffer, 12); _name = Common::String(buffer); s.read(buffer, 41); _description = Common::String(buffer); _examine.clear(); _sequences = nullptr; _images = nullptr; _imageFrame = nullptr; s.skip(4); _sequenceOffset = s.readUint16LE(); s.seek(10, SEEK_CUR); _walkCount = s.readByte(); _allow = s.readByte(); _frameNumber = s.readSint16LE(); _sequenceNumber = s.readSint16LE(); _position.x = s.readSint16LE(); _position.y = s.readSint16LE(); _delta.x = s.readSint16LE(); _delta.y = s.readSint16LE(); _type = (SpriteType)s.readUint16LE(); _oldPosition.x = s.readSint16LE(); _oldPosition.y = s.readSint16LE(); _oldSize.x = s.readUint16LE(); _oldSize.y = s.readUint16LE(); _goto.x = s.readSint16LE(); _goto.y = s.readSint16LE(); if (!isRoseTattoo) { _goto.x = _goto.x * FIXED_INT_MULTIPLIER / 100; _goto.y = _goto.y * FIXED_INT_MULTIPLIER / 100; } _pickup = isRoseTattoo ? 0 : s.readByte(); _defaultCommand = isRoseTattoo ? 0 : s.readByte(); _lookFlag = s.readSint16LE(); _pickupFlag = isRoseTattoo ? 0 : s.readSint16LE(); _requiredFlag = s.readSint16LE(); _noShapeSize.x = s.readUint16LE(); _noShapeSize.y = s.readUint16LE(); _status = s.readUint16LE(); _misc = s.readByte(); _maxFrames = s.readUint16LE(); _flags = s.readByte(); if (!isRoseTattoo) _aOpen.load(s); _aType = (AType)s.readByte(); _lookFrames = s.readByte(); _seqCounter = s.readByte(); _lookPosition.x = s.readUint16LE() * FIXED_INT_MULTIPLIER / 100; _lookPosition.y = (isRoseTattoo ? s.readSint16LE() : s.readByte()) * FIXED_INT_MULTIPLIER; _lookFacing = s.readByte(); _lookcAnim = s.readByte(); if (!isRoseTattoo) _aClose.load(s); _seqStack = s.readByte(); _seqTo = s.readByte(); _descOffset = s.readUint16LE(); _seqCounter2 = s.readByte(); _seqSize = s.readUint16LE(); if (isRoseTattoo) { for (int idx = 0; idx < 6; ++idx) _use[idx].load(s, true); _quickDraw = s.readByte(); _scaleVal = s.readUint16LE(); _requiredFlags1 = s.readSint16LE(); _gotoSeq = s.readByte(); _talkSeq = s.readByte(); _restoreSlot = s.readByte(); } else { s.skip(1); _aMove.load(s); s.skip(8); for (int idx = 0; idx < 4; ++idx) _use[idx].load(s, false); } } void Object::toggleHidden() { if (_type != HIDDEN && _type != HIDE_SHAPE && _type != INVALID) { if (_seqTo != 0) _sequences[_frameNumber] = _seqTo + SEQ_TO_CODE + 128; _seqTo = 0; if (_images == nullptr || _images->size() == 0) // No shape to erase, so flag as hidden _type = HIDDEN; else // Otherwise, flag it to be hidden after it gets erased _type = HIDE_SHAPE; } else if (_type != INVALID) { if (_seqTo != 0) _sequences[_frameNumber] = _seqTo + SEQ_TO_CODE + 128; _seqTo = 0; _seqCounter = _seqCounter2 = 0; _seqStack = 0; _frameNumber = -1; if (_images == nullptr || _images->size() == 0) { _type = NO_SHAPE; } else { _type = ACTIVE_BG_SHAPE; int idx = _sequences[0]; if (idx >= _maxFrames) // Turn on: set up first frame idx = 0; _imageFrame = &(*_images)[idx]; } } } void Object::checkObject() { Scene &scene = *_vm->_scene; Sound &sound = *_vm->_sound; Talk &talk = *_vm->_talk; int checkFrame = _allow ? MAX_FRAME : FRAMES_END; bool codeFound; if (_seqTo) { byte *ptr = &_sequences[_frameNumber]; if (*ptr == _seqTo) { // The sequence is completed *ptr = _seqTo + SEQ_TO_CODE + 128; // Reset to normal _seqTo = 0; } else { // Continue doing sequence if (*ptr > _seqTo) *ptr -= 1; else *ptr += 1; return; } } ++_frameNumber; do { // Check for end of sequence codeFound = checkEndOfSequence(); if (_sequences[_frameNumber] >= 128 && _frameNumber < checkFrame) { codeFound = true; int v = _sequences[_frameNumber]; // Check for a Talk or Listen Sequence if (IS_ROSE_TATTOO && v == ALLOW_TALK_CODE) { if (_gotoSeq) { setObjTalkSequence(_gotoSeq); } else { ++_frameNumber; } } else if (IS_ROSE_TATTOO && (v == TALK_SEQ_CODE || v == TALK_LISTEN_CODE)) { if (_talkSeq) setObjTalkSequence(_talkSeq); else setObjSequence(0, false); } else if (v >= GOTO_CODE) { // Goto code found v -= GOTO_CODE; _seqCounter2 = _seqCounter; _seqStack = _frameNumber + 1; setObjSequence(v, false); } else if (v >= SOUND_CODE && (v < (SOUND_CODE + 30))) { codeFound = true; ++_frameNumber; v -= SOUND_CODE + (IS_SERRATED_SCALPEL ? 1 : 0); if (sound._soundOn && !_countCAnimFrames) { if (!scene._sounds[v]._name.empty() && sound._digitized) sound.playLoadedSound(v, WAIT_RETURN_IMMEDIATELY); } } else if (v >= FLIP_CODE && v < (FLIP_CODE + 3)) { // Flip code codeFound = true; ++_frameNumber; v -= FLIP_CODE; // Alter the flipped status switch (v) { case 0: // Clear the flag _flags &= ~OBJ_FLIPPED; break; case 1: // Set the flag _flags |= OBJ_FLIPPED; break; case 2: // Toggle the flag _flags ^= OBJ_FLIPPED; break; default: break; } } else if (IS_ROSE_TATTOO && v == TELEPORT_CODE) { _position.x = READ_LE_UINT16(&_sequences[_frameNumber + 1]); _position.y = READ_LE_UINT16(&_sequences[_frameNumber + 3]); _frameNumber += 5; } else if (IS_ROSE_TATTOO && v == CALL_TALK_CODE) { Common::String filename; for (int idx = 0; idx < 8; ++idx) { if (_sequences[_frameNumber + 1 + idx] != 1) filename += (char)_sequences[_frameNumber + 1 + idx]; else break; } _frameNumber += 8; talk.talkTo(filename); } else if (IS_ROSE_TATTOO && v == HIDE_CODE) { switch (_sequences[_frameNumber + 2]) { case 1: // Hide Object if (scene._bgShapes[_sequences[_frameNumber + 1] - 1]._type != HIDDEN) scene._bgShapes[_sequences[_frameNumber + 1] - 1].toggleHidden(); break; case 2: // Activate Object if (scene._bgShapes[_sequences[_frameNumber + 1] - 1]._type == HIDDEN) scene._bgShapes[_sequences[_frameNumber + 1] - 1].toggleHidden(); break; case 3: // Toggle Object scene._bgShapes[_sequences[_frameNumber + 1] - 1].toggleHidden(); break; default: break; } _frameNumber += 3; } else { v -= 128; // 68-99 is a squence code if (v > SEQ_TO_CODE) { byte *p = &_sequences[_frameNumber]; v -= SEQ_TO_CODE; // # from 1-32 _seqTo = v; *p = *(p - 1); if (*p > 128) // If the high bit is set, convert to a real frame *p -= (byte)(SEQ_TO_CODE - 128); if (*p > _seqTo) *p -= 1; else *p += 1; // Will be incremented below to return back to original value --_frameNumber; v = 0; } else if (IS_ROSE_TATTOO && v == 10) { // Set delta for objects _delta = Common::Point(READ_LE_UINT16(&_sequences[_frameNumber + 1]), READ_LE_UINT16(&_sequences[_frameNumber + 3])); _noShapeSize = Common::Point(0, 0); _frameNumber += 4; } else if (v == 10) { // Set delta for objects Common::Point pt(_sequences[_frameNumber + 1], _sequences[_frameNumber + 2]); if (pt.x > 128) pt.x = (pt.x - 128) * -1; else pt.x--; if (pt.y > 128) pt.y = (pt.y - 128) * -1; else pt.y--; _delta = pt; _frameNumber += 2; } else if (v < USE_COUNT) { for (int idx = 0; idx < NAMES_COUNT; ++idx) { checkNameForCodes(_use[v]._names[idx], nullptr); } if (_use[v]._useFlag) _vm->setFlags(_use[v]._useFlag); } ++_frameNumber; } } } while (codeFound); } bool Object::checkEndOfSequence() { Screen &screen = *_vm->_screen; int checkFrame = _allow ? MAX_FRAME : FRAMES_END; bool result = false; if (_type == REMOVE || _type == INVALID) return false; if (_sequences[_frameNumber] == 0 || _frameNumber >= checkFrame) { result = true; if (_frameNumber >= (checkFrame - 1)) { _frameNumber = START_FRAME; } else { // Determine next sequence to use int seq = _sequences[_frameNumber + 1]; if (seq == 99) { --_frameNumber; screen._backBuffer1.transBlitFrom(*_imageFrame, _position); screen._backBuffer2.transBlitFrom(*_imageFrame, _position); _type = INVALID; } else if (IS_ROSE_TATTOO && _talkSeq && seq == 0) { setObjTalkSequence(_talkSeq); } else { setObjSequence(seq, false); } } if (_allow && _frameNumber == 0) { // canimation just ended if (_type != NO_SHAPE && _type != REMOVE) { _type = REMOVE; if (!_countCAnimFrames) { // Save details before shape is removed _delta.x = _imageFrame->_frame.w; _delta.y = _imageFrame->_frame.h; _position += _imageFrame->_offset; // Free the images delete _images; _images = nullptr; _imageFrame = nullptr; } } else { _type = INVALID; } } } return result; } void Object::setObjSequence(int seq, bool wait) { Scene &scene = *_vm->_scene; int checkFrame = _allow ? MAX_FRAME : FRAMES_END; if (seq >= 128) { // Loop the sequence until the count exceeded seq -= 128; ++_seqCounter; if (_seqCounter >= seq) { // Go to next sequence if (_seqStack) { _frameNumber = _seqStack; _seqStack = 0; _seqCounter = _seqCounter2; _seqCounter2 = 0; if (_frameNumber >= checkFrame) _frameNumber = START_FRAME; return; } _frameNumber += 2; if (_frameNumber >= checkFrame) _frameNumber = 0; _seqCounter = 0; if (_sequences[_frameNumber] == 0) seq = _sequences[_frameNumber + 1]; else return; } else { // Find beginning of sequence do { --_frameNumber; } while (_frameNumber > 0 && _sequences[_frameNumber] != 0); if (_frameNumber != 0) _frameNumber += 2; return; } } else { // Reset sequence counter _seqCounter = 0; } int idx = 0; int seqCc = 0; while (seqCc < seq && idx < checkFrame) { ++idx; if (_sequences[idx] == 0) { ++seqCc; idx += 2; } } if (idx >= checkFrame) idx = 0; _frameNumber = idx; if (wait) { seqCc = idx; while (_sequences[idx] != 0) ++idx; idx = idx - seqCc + 2; for (; idx > 0; --idx) scene.doBgAnim(); } } void Object::setObjTalkSequence(int seq) { Talk &talk = *_vm->_talk; // See if we're supposed to restore the object's sequence from the talk sequence stack if (seq == -1) { TalkSequence &ts = talk._talkSequenceStack[_restoreSlot]; if (_seqTo != 0) _sequences[_frameNumber] = _seqTo; _frameNumber = ts._frameNumber; _sequenceNumber = ts._sequenceNumber; _seqStack = ts._seqStack; _seqTo = ts._seqTo; _seqCounter = ts._seqCounter; _seqCounter2 = ts._seqCounter2; _talkSeq = 0; // Flag this slot as free again ts._obj = nullptr; return; } assert(_type != CHARACTER); // If the object passed in is an NPC, set it's sequence through the sequence number rather // than adjusting the frame number to a specific sub-sequence /* s = (SpriteType *)bg; if (s->seqto) { // reset to previous value s->WalkSeqs[s->fs]->Seq[s->fn] = s->seqto; s->seqto = 0; } s->fs = s->GotoSeq; s->fn = 0; CheckWalkGraphics(s); */ talk.pushTalkSequence(this); int talkSeqNum = seq; // Find where the talk sequence data begins in the object int idx = 0; for (;;) { // Get the Frame value byte f = _sequences[idx++]; // See if we've found the beginning of a Talk Sequence if ((f == TALK_SEQ_CODE && seq < 128) || (f == TALK_LISTEN_CODE && seq > 128)) { --seq; // See if we're at the correct Talk Sequence Number if (!(seq & 127)) { // Correct Sequence, Start Talking Here if (_seqTo != 0) _sequences[_frameNumber] = _seqTo; _frameNumber = idx; _seqTo = 0; _seqStack = 0; _seqCounter = 0; _seqCounter2 = 0; _talkSeq = talkSeqNum; break; } } else { // Move ahead any extra because of special control codes switch (f) { case 0: idx++; break; case MOVE_CODE: case TELEPORT_CODE: idx += 4; break; case CALL_TALK_CODE: idx += 8; break; case HIDE_CODE: idx += 2; break; } } // See if we're out of sequence data if (idx >= (int)_seqSize) break; } } int Object::checkNameForCodes(const Common::String &name, const char *const messages[]) { Map &map = *_vm->_map; People &people = *_vm->_people; Scene &scene = *_vm->_scene; Screen &screen = *_vm->_screen; Talk &talk = *_vm->_talk; UserInterface &ui = *_vm->_ui; bool printed = false; scene.toggleObject(name); if (name.hasPrefix("*")) { // A code was found printed = true; char ch = (name == "*") ? 0 : toupper(name[1]); switch (ch) { case 'C': talk.talkTo(name.c_str() + 2); break; case 'T': case 'B': case 'F': case 'W': // Nothing: action was already done before canimation break; case 'G': case 'A': { // G: Have object go somewhere // A: Add onto existing co-ordinates Common::String sx(name.c_str() + 2, name.c_str() + 5); Common::String sy(name.c_str() + 6, name.c_str() + 9); if (ch == 'G') _position = Common::Point(atoi(sx.c_str()), atoi(sy.c_str())); else _position += Common::Point(atoi(sx.c_str()), atoi(sy.c_str())); break; } default: if (ch >= '0' && ch <= '9') { scene._goToScene = atoi(name.c_str() + 1); if (IS_SERRATED_SCALPEL && scene._goToScene < 97 && map[scene._goToScene].x) { map._overPos.x = (map[scene._goToScene].x - 6) * FIXED_INT_MULTIPLIER; map._overPos.y = (map[scene._goToScene].y + 9) * FIXED_INT_MULTIPLIER; } const char *p; if ((p = strchr(name.c_str(), ',')) != nullptr) { ++p; Common::String s(p, p + 3); people._hSavedPos.x = atoi(s.c_str()); s = Common::String(p + 3, p + 6); people._hSavedPos.y = atoi(s.c_str()); s = Common::String(p + 6, p + 9); people._hSavedFacing = atoi(s.c_str()); if (people._hSavedFacing == 0) people._hSavedFacing = 10; } else if ((p = strchr(name.c_str(), '/')) != nullptr) { people._hSavedPos = Common::Point(1, 0); people._hSavedFacing = 100 + atoi(p + 1); } } else { scene._goToScene = 100; } people[AL]._position = Point32(0, 0); break; } } else if (name.hasPrefix("!")) { // Message attached to canimation int messageNum = atoi(name.c_str() + 1); ui._infoFlag = true; ui.clearInfo(); screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", messages[messageNum]); ui._menuCounter = 25; } else if (name.hasPrefix("@")) { // Message attached to canimation ui._infoFlag = true; ui.clearInfo(); screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", name.c_str() + 1); printed = true; ui._menuCounter = 25; } return printed; } void Object::setFlagsAndToggles() { Scene &scene = *_vm->_scene; Talk &talk = *_vm->_talk; for (int useIdx = 0; useIdx < USE_COUNT; ++useIdx) { if (_use[useIdx]._useFlag) { if (!_vm->readFlags(_use[useIdx]._useFlag)) _vm->setFlags(_use[useIdx]._useFlag); } if (_use[useIdx]._cAnimSpeed) { if (_use[useIdx]._cAnimNum == 0) // 0 is really a 10 scene.startCAnim(9, _use[useIdx]._cAnimSpeed); else scene.startCAnim(_use[useIdx]._cAnimNum - 1, _use[useIdx]._cAnimSpeed); } if (!talk._talkToAbort) { for (int idx = 0; idx < NAMES_COUNT; ++idx) scene.toggleObject(_use[useIdx]._names[idx]); } } } void Object::adjustObject() { if (_type == REMOVE) return; if (IS_ROSE_TATTOO && (_delta.x || _delta.y)) { // The shape position is in pixels, and the delta is in fixed integer amounts int t; _noShapeSize.x += _delta.x; t = _noShapeSize.x / (FIXED_INT_MULTIPLIER / 10); _noShapeSize.x -= t * (FIXED_INT_MULTIPLIER / 10); _position.x += t; _noShapeSize.y += _delta.y; t = _noShapeSize.y / (FIXED_INT_MULTIPLIER / 10); _noShapeSize.y -= t * (FIXED_INT_MULTIPLIER / 10); _position.y += t; } else if (IS_SERRATED_SCALPEL) { // The delta is in whole pixels, so simply adjust the position with it _position += _delta; } if (_position.y > LOWER_LIMIT) _position.y = LOWER_LIMIT; if (_type != NO_SHAPE) { int frame = _frameNumber; if (frame == -1) frame = 0; int imgNum = _sequences[frame]; if (imgNum > _maxFrames) imgNum = 1; _imageFrame = &(*_images)[imgNum - 1]; } } int Object::pickUpObject(const char *const messages[]) { Inventory &inv = *_vm->_inventory; People &people = *_vm->_people; Scene &scene = *_vm->_scene; Screen &screen = *_vm->_screen; Talk &talk = *_vm->_talk; UserInterface &ui = *_vm->_ui; int pickup = _pickup & 0x7f; bool printed = false; int numObjects = 0; if (pickup == 99) { for (int idx = 0; idx < NAMES_COUNT && !talk._talkToAbort; ++idx) { if (checkNameForCodes(_use[0]._names[idx], nullptr)) { if (!talk._talkToAbort) printed = true; } } return 0; } if (!pickup || (pickup > 50 && pickup <= 80)) { int message = _pickup; if (message > 50) message -= 50; ui._infoFlag = true; ui.clearInfo(); screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", messages[message]); ui._menuCounter = 30; } else { // Pick it up bool takeFlag = true; if ((_pickup & 0x80) == 0) { // Play an animation if (pickup > 80) { takeFlag = false; // Don't pick it up scene.startCAnim(pickup - 81, 1); if (_pickupFlag) _vm->setFlags(_pickupFlag); } else { scene.startCAnim(pickup - 1, 1); if (!talk._talkToAbort) { // Erase the shape _type = _type == NO_SHAPE ? INVALID : REMOVE; } } if (talk._talkToAbort) return 0; } else { // Play generic pickup sequence // Original moved cursor position here people.goAllTheWay(); ui._menuCounter = 25; ui._temp1 = 1; } for (int idx = 0; idx < NAMES_COUNT && !talk._talkToAbort; ++idx) { if (checkNameForCodes(_use[0]._names[idx], nullptr)) { if (!talk._talkToAbort) printed = true; } } if (talk._talkToAbort) return 0; // Add the item to the player's inventory if (takeFlag) numObjects = inv.putItemInInventory(*this); if (!printed) { ui._infoFlag = true; ui.clearInfo(); Common::String itemName = _description; itemName.setChar(tolower(itemName[0]), 0); screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "Picked up %s", itemName.c_str()); ui._menuCounter = 25; } } return numObjects; } const Common::Rect Object::getNewBounds() const { Point32 pt = _position; if (_imageFrame) pt += _imageFrame->_offset; return Common::Rect(pt.x, pt.y, pt.x + frameWidth(), pt.y + frameHeight()); } const Common::Rect Object::getNoShapeBounds() const { return Common::Rect(_position.x, _position.y, _position.x + _noShapeSize.x, _position.y + _noShapeSize.y); } const Common::Rect Object::getOldBounds() const { return Common::Rect(_oldPosition.x, _oldPosition.y, _oldPosition.x + _oldSize.x, _oldPosition.y + _oldSize.y); } /*----------------------------------------------------------------*/ void CAnim::load(Common::SeekableReadStream &s, bool isRoseTattoo) { char buffer[12]; s.read(buffer, 12); _name = Common::String(buffer); 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(); 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(); _teleportPos.x = s.readSint16LE(); _teleportPos.y = s.readSint16LE(); if (!isRoseTattoo) { _goto.x = _goto.x * FIXED_INT_MULTIPLIER / 100; _goto.y = _goto.y * FIXED_INT_MULTIPLIER / 100; _teleportPos.x = _teleportPos.x * FIXED_INT_MULTIPLIER / 100; _teleportPos.y = _teleportPos.y * FIXED_INT_MULTIPLIER / 100; } _teleportDir = s.readSint16LE(); } /*----------------------------------------------------------------*/ 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; _filesize = 0; } } // End of namespace Sherlock