/* 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/objects.h" #include "sherlock/people.h" #include "sherlock/scene.h" #include "sherlock/scalpel/scalpel.h" #include "sherlock/scalpel/scalpel_map.h" #include "sherlock/scalpel/scalpel_people.h" #include "sherlock/tattoo/tattoo.h" namespace Sherlock { #define START_FRAME 0 #define NUM_ADJUSTED_WALKS 21 // Distance to walk around WALK_AROUND boxes #define CLEAR_DIST_X 5 #define CLEAR_DIST_Y 0 #define ADJUST_COORD(COORD) \ if (COORD.x != -1) \ COORD.x *= FIXED_INT_MULTIPLIER; \ if (COORD.y != -1) \ COORD.y *= FIXED_INT_MULTIPLIER SherlockEngine *BaseObject::_vm; bool BaseObject::_countCAnimFrames; /*----------------------------------------------------------------*/ void BaseObject::setVm(SherlockEngine *vm) { _vm = vm; _countCAnimFrames = false; } BaseObject::BaseObject() { _type = INVALID; _sequences = nullptr; _images = nullptr; _imageFrame = nullptr; _sequenceNumber = 0; _startSeq = 0; _walkCount = 0; _allow = 0; _frameNumber = 0; _lookFlag = 0; _requiredFlag[0] = _requiredFlag[1] = 0; _status = 0; _misc = 0; _maxFrames = 0; _flags = 0; _aType = OBJECT; _lookFrames = 0; _seqCounter = 0; _lookcAnim = 0; _seqStack = 0; _seqTo = 0; _descOffset = 0; _seqCounter2 = 0; _seqSize = 0; _quickDraw = 0; _scaleVal = 0; _gotoSeq = 0; _talkSeq = 0; _restoreSlot = 0; } bool BaseObject::hasAborts() const { int seqNum = _talkSeq; // See if the object is in its 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)) break; // See if we've found the beginning of a Talk Sequence if ((v == TALK_SEQ_CODE && seqNum < 128) || (v == TALK_LISTEN_CODE && seqNum >= 128)) { // If checking was already on and we came across one of these codes, then there couldn't // have been an Allow Talk Interrupt code in the sequence we were checking, so we're done. if (startChecking) break; seqNum--; // See if we're at the correct Talk Sequence Number if (!(seqNum & 127)) { // Correct Sequence, Start Checking Now startChecking = true; } } else { // Move ahead any extra because of special control codes switch (v) { case 0: idx++; break; case MOVE_CODE: case TELEPORT_CODE: idx += 4; break; case CALL_TALK_CODE:idx += 8; break; case HIDE_CODE: idx += 2; break; } } } while (idx < _seqSize); return false; } void BaseObject::checkObject() { Scene &scene = *_vm->_scene; Sound &sound = *_vm->_sound; Talk &talk = *_vm->_talk; int checkFrame = _allow ? MAX_FRAME : FRAMES_END; bool codeFound; if (_seqTo) { byte *ptr = &_sequences[_frameNumber]; if (*ptr == _seqTo) { // The sequence is completed. Reset to normal *ptr = _seqTo + (IS_ROSE_TATTOO ? 0 : SEQ_TO_CODE + 128); _seqTo = 0; } else { // Continue doing sequence if (*ptr > _seqTo) *ptr -= 1; else *ptr += 1; return; } } ++_frameNumber; do { if (!_sequences) { warning("checkObject: _sequences is not set"); break; } // 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); _gotoSeq = 0; } else { ++_frameNumber; } } else if (IS_ROSE_TATTOO && (v == TALK_SEQ_CODE || v == TALK_LISTEN_CODE)) { if (_talkSeq) setObjTalkSequence(_talkSeq); else setObjSequence(0, false); } else if (v >= GOTO_CODE) { // Goto code found v -= 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 sequence code if (v > SEQ_TO_CODE) { if (IS_ROSE_TATTOO) { ++_frameNumber; byte *p = &_sequences[_frameNumber]; _seqTo = *p; *p = *(p - 2); if (*p > _seqTo) *p -= 1; else *p += 1; --_frameNumber; } else { byte *p = &_sequences[_frameNumber]; v -= SEQ_TO_CODE; // # from 1-32 _seqTo = v; *p = *(p - 1); if (*p > 128) // If the high bit is set, convert to a real frame *p -= (byte)(SEQ_TO_CODE - 128); if (*p > _seqTo) *p -= 1; else *p += 1; // Will be incremented below to return back to original value --_frameNumber; v = 0; } } else if (IS_ROSE_TATTOO && v == 10) { // Set delta for objects _delta = Common::Point(READ_LE_UINT16(&_sequences[_frameNumber + 1]), READ_LE_UINT16(&_sequences[_frameNumber + 3])); _noShapeSize = Common::Point(0, 0); _frameNumber += 4; } 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]); } if (_use[v]._useFlag) _vm->setFlags(_use[v]._useFlag); } ++_frameNumber; } } } while (codeFound); } bool BaseObject::checkEndOfSequence() { Screen &screen = *_vm->_screen; int checkFrame = _allow ? MAX_FRAME : FRAMES_END; bool result = false; if (_type == REMOVE || _type == INVALID) return false; if (_frameNumber < 0 || _frameNumber >= checkFrame || _sequences[_frameNumber] == 0) { result = true; if (_frameNumber < 0 || _frameNumber >= (checkFrame - 1)) { _frameNumber = START_FRAME; } else { // Determine next sequence to use int seq = _sequences[_frameNumber + 1]; // If the object has been turned off, we're going nowhere if (IS_ROSE_TATTOO && (_type == HIDE_SHAPE || _type == HIDDEN || _type == REMOVE)) return false; if (seq == 99) { --_frameNumber; screen._backBuffer1.SHtransBlitFrom(*_imageFrame, _position); screen._backBuffer2.SHtransBlitFrom(*_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 BaseObject::setObjSequence(int seq, bool wait) { Scene &scene = *_vm->_scene; int checkFrame = _allow ? MAX_FRAME : FRAMES_END; if (IS_ROSE_TATTOO && (seq == -1 || seq == 255)) // This means goto beginning seq = 0; if (seq >= 128) { // Loop the sequence until the count exceeded seq -= 128; ++_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; // For Rose Tattoo, save the starting frame for new sequences if (IS_ROSE_TATTOO) _startSeq = _frameNumber; _seqCounter = 0; if (_sequences[_frameNumber] == 0) seq = _sequences[_frameNumber + 1]; else return; } else { // Find beginning of sequence if (IS_ROSE_TATTOO) { // Use the saved start of the sequence to reset the frame _frameNumber = _startSeq; } else { // For Scalpel, scan backwards from the end of the sequence to find its start do { --_frameNumber; } while (_frameNumber > 0 && _sequences[_frameNumber] != 0); if (_frameNumber != 0) _frameNumber += 2; } return; } } else { // Reset sequence counter _seqCounter = 0; } int idx = 0; int seqCc = 0; while (seqCc < seq && idx < checkFrame) { if (IS_SERRATED_SCALPEL) { ++idx; if (_sequences[idx] == 0) { ++seqCc; idx += 2; } } else { byte s = _sequences[idx]; if (s == 0) { ++seqCc; ++idx; } else if (s == MOVE_CODE || s == TELEPORT_CODE) { idx += 4; } else if (s == CALL_TALK_CODE) { idx += 8; } else if (s == HIDE_CODE) { idx += 2; } ++idx; } } if (idx >= checkFrame) idx = 0; _frameNumber = idx; _startSeq = idx; if (wait) { seqCc = idx; while (_sequences[idx] != 0) ++idx; idx = idx - seqCc + 2; for (; idx > 0; --idx) scene.doBgAnim(); } } int BaseObject::checkNameForCodes(const Common::String &name, FixedTextActionId fixedTextActionId) { FixedText &fixedText = *_vm->_fixedText; People &people = *_vm->_people; Scene &scene = *_vm->_scene; Screen &screen = *_vm->_screen; 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() + 5, name.c_str() + 8); 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; } case 'V': // Do nothing for Verb codes. This is only a flag for Inventory syntax break; default: if (ch >= '0' && ch <= '9') { scene._goToScene = atoi(name.c_str() + 1); if (IS_SERRATED_SCALPEL && scene._goToScene < 97) { Scalpel::ScalpelMap &map = *(Scalpel::ScalpelMap *)_vm->_map; if (map[scene._goToScene].x) { map._overPos.x = (map[scene._goToScene].x - 6) * FIXED_INT_MULTIPLIER; map._overPos.y = (map[scene._goToScene].y + 9) * FIXED_INT_MULTIPLIER; } } const char *p; if ((p = strchr(name.c_str(), ',')) != nullptr) { ++p; Common::String s(p, p + 3); people._savedPos.x = atoi(s.c_str()); s = Common::String(p + 3, p + 6); people._savedPos.y = atoi(s.c_str()); s = Common::String(p + 6, p + 9); people._savedPos._facing = atoi(s.c_str()); if (people._savedPos._facing == 0) people._savedPos._facing = 10; } else if ((p = strchr(name.c_str(), '/')) != nullptr) { people._savedPos = PositionFacing(1, 0, 100 + atoi(p + 1)); } } else { scene._goToScene = 100; } people[HOLMES]._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(); Common::String errorMessage = fixedText.getActionMessage(fixedTextActionId, messageNum); screen.print(Common::Point(0, INFO_LINE + 1), COL_INFO_FOREGROUND, "%s", errorMessage.c_str()); ui._menuCounter = 25; } else if (name.hasPrefix("@")) { // Message attached to canimation ui._infoFlag = true; ui.clearInfo(); screen.print(Common::Point(0, INFO_LINE + 1), COL_INFO_FOREGROUND, "%s", name.c_str() + 1); printed = true; ui._menuCounter = 25; } return printed; } /*----------------------------------------------------------------*/ void Sprite::clear() { _name = ""; _description = ""; _examine.clear(); _pickUp = ""; _walkSequences.clear(); _sequences = nullptr; _images = nullptr; _imageFrame = nullptr; _walkCount = 0; _oldWalkSequence = 0; _allow = 0; _frameNumber = 0; _position.x = _position.y = 0; _delta.x = _delta.y = 0; _oldPosition.x = _oldPosition.y = 0; _oldSize.x = _oldSize.y = 0; _goto.x = _goto.y = 0; _type = INVALID; _pickUp.clear(); _noShapeSize.x = _noShapeSize.y = 0; _status = 0; _misc = 0; _altImages = nullptr; _altSeq = 0; _centerWalk = 0; for (int i = 0; i < 8; i++) _stopFrames[i] = nullptr; } void Sprite::setImageFrame() { int frameNum = MAX(_frameNumber, 0); int imageNumber = _walkSequences[_sequenceNumber][frameNum]; if (IS_SERRATED_SCALPEL) imageNumber = imageNumber + _walkSequences[_sequenceNumber][0] - 2; else if (imageNumber > _maxFrames) imageNumber = 1; // Get the images to use ImageFile *images = _altSeq ? _altImages : _images; assert(images); if (IS_3DO) { // only do this to the image-array with 110 entries // map uses another image-array and this code if (images->size() == 110) { // 3DO has 110 animation frames inside walk.anim // PC has 55 // this adjusts the framenumber accordingly // sort of HACK imageNumber *= 2; } } else if (IS_ROSE_TATTOO) { --imageNumber; } // Set the frame pointer _imageFrame = &(*images)[imageNumber]; } void Sprite::checkSprite() { Events &events = *_vm->_events; People &people = *_vm->_people; Scene &scene = *_vm->_scene; Screen &screen = *_vm->_screen; Talk &talk = *_vm->_talk; Point32 pt; Common::Rect objBounds; Common::Point spritePt(_position.x / FIXED_INT_MULTIPLIER, _position.y / FIXED_INT_MULTIPLIER); if (_type != CHARACTER || (IS_SERRATED_SCALPEL && talk._talkCounter)) return; pt = _walkCount ? _position + _delta : _position; pt.x /= FIXED_INT_MULTIPLIER; pt.y /= FIXED_INT_MULTIPLIER; if (IS_ROSE_TATTOO) { checkObject(); // For Rose Tattoo, we only do the further processing for Sherlock if (this != &people[HOLMES]) return; } for (uint idx = 0; idx < scene._bgShapes.size() && !talk._talkToAbort; ++idx) { Object &obj = scene._bgShapes[idx]; if (obj._aType <= PERSON || obj._type == INVALID || obj._type == HIDDEN) continue; if (obj._type == NO_SHAPE) { objBounds = Common::Rect(obj._position.x, obj._position.y, obj._position.x + obj._noShapeSize.x + 1, obj._position.y + obj._noShapeSize.y + 1); } else { int xp = obj._position.x + obj._imageFrame->_offset.x; int yp = obj._position.y + obj._imageFrame->_offset.y; objBounds = Common::Rect(xp, yp, xp + obj._imageFrame->_frame.w + 1, yp + obj._imageFrame->_frame.h + 1); } if (objBounds.contains(pt)) { if (objBounds.contains(spritePt)) { // Current point is already inside the the bounds, so impact occurred // on a previous call. So simply do nothing until we're clear of the box switch (obj._aType) { case TALK_MOVE: if (_walkCount) { // Holmes is moving obj._type = HIDDEN; obj.setFlagsAndToggles(); talk.talkTo(obj._use[0]._target); } break; case PAL_CHANGE: case PAL_CHANGE2: if (_walkCount) { int palStart = atoi(obj._use[0]._names[0].c_str()) * 3; int palLength = atoi(obj._use[0]._names[1].c_str()) * 3; int templ = atoi(obj._use[0]._names[2].c_str()) * 3; if (templ == 0) templ = 100; // Ensure only valid palette change data found if (palLength > 0) { // Figure out how far into the shape Holmes is so that we // can figure out what percentage of the original palette // to set the current palette to int palPercent = (pt.x - objBounds.left) * 100 / objBounds.width(); palPercent = palPercent * templ / 100; if (obj._aType == PAL_CHANGE) // Invert percentage palPercent = 100 - palPercent; for (int i = palStart; i < (palStart + palLength); ++i) screen._sMap[i] = screen._cMap[i] * palPercent / 100; events.pollEvents(); screen.setPalette(screen._sMap); } } break; case TALK: case TALK_EVERY: obj._type = HIDDEN; obj.setFlagsAndToggles(); talk.talkTo(obj._use[0]._target); break; default: break; } } else { // New impact just occurred switch (obj._aType) { case BLANK_ZONE: // A blank zone masks out all other remaining zones underneath it. // If this zone is hit, exit the outer loop so we do not check anymore return; case SOLID: case TALK: // Stop walking if (obj._aType == TALK) { obj.setFlagsAndToggles(); talk.talkTo(obj._use[0]._target); } else { gotoStand(); } break; case TALK_EVERY: if (obj._aType == TALK_EVERY) { obj._type = HIDDEN; obj.setFlagsAndToggles(); talk.talkTo(obj._use[0]._target); } else { gotoStand(); } break; case FLAG_SET: obj.setFlagsAndToggles(); obj._type = HIDDEN; break; case WALK_AROUND: if (objBounds.contains(people[HOLMES]._walkTo.front())) { // Reached zone gotoStand(); } else { // Destination not within box, walk to best corner Common::Point walkPos; if (spritePt.x >= objBounds.left && spritePt.x < objBounds.right) { // Impact occurred due to vertical movement. Determine whether to // travel to the left or right side if (_delta.x > 0) // Go to right side walkPos.x = objBounds.right + CLEAR_DIST_X; else if (_delta.x < 0) { // Go to left side walkPos.x = objBounds.left - CLEAR_DIST_X; } else { // Going straight up or down. So choose best side if (spritePt.x >= (objBounds.left + objBounds.width() / 2)) walkPos.x = objBounds.right + CLEAR_DIST_X; else walkPos.x = objBounds.left - CLEAR_DIST_X; } walkPos.y = (_delta.y >= 0) ? objBounds.top - CLEAR_DIST_Y : objBounds.bottom + CLEAR_DIST_Y; } else { // Impact occurred due to horizontal movement if (_delta.y > 0) // Go to bottom of box walkPos.y = objBounds.bottom + CLEAR_DIST_Y; else if (_delta.y < 0) // Go to top of box walkPos.y = objBounds.top - CLEAR_DIST_Y; else { // Going straight horizontal, so choose best side if (spritePt.y >= (objBounds.top + objBounds.height() / 2)) walkPos.y = objBounds.bottom + CLEAR_DIST_Y; else walkPos.y = objBounds.top - CLEAR_DIST_Y; } walkPos.x = (_delta.x >= 0) ? objBounds.left - CLEAR_DIST_X : objBounds.right + CLEAR_DIST_X; } walkPos.x += people[HOLMES]._imageFrame->_frame.w / 2; people[HOLMES]._walkDest = walkPos; people[HOLMES]._walkTo.push(walkPos); people[HOLMES].setWalking(); } break; case DELTA: _position.x += 200; break; default: break; } } } } } const Common::Rect Sprite::getOldBounds() const { return Common::Rect(_oldPosition.x, _oldPosition.y, _oldPosition.x + _oldSize.x, _oldPosition.y + _oldSize.y); } /*----------------------------------------------------------------*/ void WalkSequence::load(Common::SeekableReadStream &s) { char buffer[9]; s.read(buffer, 9); _vgsName = Common::String(buffer); _horizFlip = s.readByte() != 0; _sequences.resize(s.readUint16LE()); s.skip(4); // Skip over pointer field of structure s.read(&_sequences[0], _sequences.size()); } /*----------------------------------------------------------------*/ WalkSequences &WalkSequences::operator=(const WalkSequences &src) { resize(src.size()); for (uint idx = 0; idx < size(); ++idx) { const WalkSequence &wSrc = src[idx]; WalkSequence &wDest = (*this)[idx]; wDest._horizFlip = wSrc._horizFlip; wDest._sequences.resize(wSrc._sequences.size()); Common::copy(&wSrc._sequences[0], &wSrc._sequences[0] + wSrc._sequences.size(), &wDest._sequences[0]); } return *this; } /*----------------------------------------------------------------*/ ActionType::ActionType() { _cAnimNum = _cAnimSpeed = 0; _useFlag = 0; } void ActionType::load(Common::SeekableReadStream &s) { char buffer[12]; _cAnimNum = s.readByte(); _cAnimSpeed = s.readByte(); if (_cAnimSpeed & 0x80) _cAnimSpeed = -(_cAnimSpeed & 0x7f); for (int idx = 0; idx < NAMES_COUNT; ++idx) { s.read(buffer, 12); _names[idx] = Common::String(buffer); } } /*----------------------------------------------------------------*/ UseType::UseType(): ActionType() { } void UseType::load(Common::SeekableReadStream &s, bool isRoseTattoo) { char buffer[12]; if (isRoseTattoo) { s.read(buffer, 12); _verb = Common::String(buffer); } ActionType::load(s); _useFlag = s.readSint16LE(); if (!isRoseTattoo) s.skip(6); s.read(buffer, 12); _target = Common::String(buffer); } void UseType::load3DO(Common::SeekableReadStream &s) { char buffer[12]; _cAnimNum = s.readByte(); _cAnimSpeed = s.readByte(); if (_cAnimSpeed & 0x80) _cAnimSpeed = -(_cAnimSpeed & 0x7f); for (int idx = 0; idx < NAMES_COUNT; ++idx) { s.read(buffer, 12); _names[idx] = Common::String(buffer); } _useFlag = s.readSint16BE(); s.skip(6); s.read(buffer, 12); _target = Common::String(buffer); } void UseType::synchronize(Serializer &s) { s.syncString(_verb); s.syncAsSint16LE(_cAnimNum); s.syncAsSint16LE(_cAnimSpeed); s.syncAsSint16LE(_useFlag); for (int idx = 0; idx < 4; ++idx) s.syncString(_names[idx]); s.syncString(_target); } /*----------------------------------------------------------------*/ Object::Object(): BaseObject() { _sequenceOffset = 0; _pickup = 0; _defaultCommand = 0; _pickupFlag = 0; } void Object::load(Common::SeekableReadStream &s, bool isRoseTattoo) { char buffer[41]; s.read(buffer, 12); _name = Common::String(buffer); s.read(buffer, 41); _description = Common::String(buffer); _examine.clear(); _sequences = nullptr; _images = nullptr; _imageFrame = nullptr; s.skip(4); _sequenceOffset = s.readUint16LE(); s.seek(10, SEEK_CUR); _walkCount = s.readByte(); _allow = s.readByte(); _frameNumber = s.readSint16LE(); _sequenceNumber = s.readSint16LE(); _position.x = s.readSint16LE(); _position.y = s.readSint16LE(); _delta.x = s.readSint16LE(); _delta.y = s.readSint16LE(); _type = (SpriteType)s.readUint16LE(); _oldPosition.x = s.readSint16LE(); _oldPosition.y = s.readSint16LE(); _oldSize.x = s.readUint16LE(); _oldSize.y = s.readUint16LE(); _goto.x = s.readSint16LE(); _goto.y = s.readSint16LE(); if (!isRoseTattoo) { _goto.x = _goto.x * FIXED_INT_MULTIPLIER / 100; _goto.y = _goto.y * FIXED_INT_MULTIPLIER / 100; } _pickup = isRoseTattoo ? 0 : s.readByte(); _defaultCommand = isRoseTattoo ? 0 : s.readByte(); _lookFlag = s.readSint16LE(); _pickupFlag = isRoseTattoo ? 0 : s.readSint16LE(); _requiredFlag[0] = s.readSint16LE(); _noShapeSize.x = s.readUint16LE(); _noShapeSize.y = s.readUint16LE(); _status = s.readUint16LE(); _misc = s.readByte(); _maxFrames = s.readUint16LE(); _flags = s.readByte(); if (!isRoseTattoo) _aOpen.load(s); _aType = (AType)s.readByte(); _lookFrames = s.readByte(); _seqCounter = s.readByte(); if (isRoseTattoo) { _lookPosition.x = s.readUint16LE() * FIXED_INT_MULTIPLIER; _lookPosition.y = s.readSint16LE() * FIXED_INT_MULTIPLIER; } else { _lookPosition.x = s.readUint16LE() * FIXED_INT_MULTIPLIER / 100; _lookPosition.y = s.readByte() * FIXED_INT_MULTIPLIER; } _lookPosition._facing = s.readByte(); _lookcAnim = s.readByte(); if (!isRoseTattoo) _aClose.load(s); _seqStack = s.readByte(); _seqTo = s.readByte(); _descOffset = s.readUint16LE(); _seqCounter2 = s.readByte(); _seqSize = s.readUint16LE(); if (isRoseTattoo) { for (int idx = 0; idx < 6; ++idx) _use[idx].load(s, true); // WORKAROUND: Fix German version using hatpin/pin in pillow in Pratt's loft if (_use[1]._target == "Nadel" && _use[1]._verb == "Untersuche" && _use[2]._target == "Nadel" && _use[2]._verb == "Untersuche") _use[1]._target = "Alte Nadel"; _quickDraw = s.readByte(); _scaleVal = s.readUint16LE(); _requiredFlag[1] = s.readSint16LE(); _gotoSeq = s.readByte(); _talkSeq = s.readByte(); _restoreSlot = s.readByte(); } else { s.skip(1); _aMove.load(s); s.skip(8); for (int idx = 0; idx < 4; ++idx) _use[idx].load(s, false); } //warning("object %s, useAnim %d", _name.c_str(), _use[0]._cAnimNum); } void Object::load3DO(Common::SeekableReadStream &s) { int32 streamStartPos = s.pos(); char buffer[41]; _examine.clear(); _sequences = nullptr; _images = nullptr; _imageFrame = nullptr; // on 3DO all of this data is reordered!!! // it seems that possibly the 3DO compiler reordered the global struct // 3DO size for 1 object is 588 bytes s.skip(4); _sequenceOffset = s.readUint16LE(); // weird that this seems to be LE s.seek(10, SEEK_CUR); // Offset 16 _frameNumber = s.readSint16BE(); _sequenceNumber = s.readSint16BE(); _position.x = s.readSint16BE(); _position.y = s.readSint16BE(); _delta.x = s.readSint16BE(); _delta.y = s.readSint16BE(); _type = (SpriteType)s.readUint16BE(); _oldPosition.x = s.readSint16BE(); _oldPosition.y = s.readSint16BE(); _oldSize.x = s.readUint16BE(); _oldSize.y = s.readUint16BE(); _goto.x = s.readSint16BE(); _goto.y = s.readSint16BE(); _goto.x = _goto.x * FIXED_INT_MULTIPLIER / 100; _goto.y = _goto.y * FIXED_INT_MULTIPLIER / 100; // Offset 42 warning("pos %d", s.pos()); // Unverified _lookFlag = s.readSint16BE(); _pickupFlag = s.readSint16BE(); _requiredFlag[0] = s.readSint16BE(); _noShapeSize.x = s.readUint16BE(); _noShapeSize.y = s.readUint16BE(); _status = s.readUint16BE(); // Unverified END _maxFrames = s.readUint16BE(); // offset 56 _lookPosition.x = s.readUint16BE() * FIXED_INT_MULTIPLIER / 100; // offset 58 _descOffset = s.readUint16BE(); _seqSize = s.readUint16BE(); s.skip(2); // boundary filler // 288 bytes for (int idx = 0; idx < 4; ++idx) { _use[idx].load3DO(s); s.skip(2); // Filler } // 158 bytes _aOpen.load(s); // 2 + 12*4 bytes = 50 bytes s.skip(2); // Boundary filler _aClose.load(s); s.skip(2); // Filler _aMove.load(s); s.skip(2); // Filler // offset 508 // 3DO: name is at the end s.read(buffer, 12); _name = Common::String(buffer); s.read(buffer, 41); _description = Common::String(buffer); // Unverified _walkCount = s.readByte(); _allow = s.readByte(); _pickup = s.readByte(); _defaultCommand = s.readByte(); // Unverified END // Probably those here?!?! _misc = s.readByte(); _flags = s.readByte(); // Unverified _aType = (AType)s.readByte(); _lookFrames = s.readByte(); _seqCounter = s.readByte(); // Unverified END _lookPosition.y = s.readByte() * FIXED_INT_MULTIPLIER; _lookPosition._facing = s.readByte(); // Unverified _lookcAnim = s.readByte(); _seqStack = s.readByte(); _seqTo = s.readByte(); _seqCounter2 = s.readByte(); // Unverified END s.skip(12); // Unknown //warning("object %s, offset %d", _name.c_str(), streamPos); //warning("object %s, lookPosX %d, lookPosY %d", _name.c_str(), _lookPosition.x, _lookPosition.y); //warning("object %s, defCmd %d", _name.c_str(), _defaultCommand); int32 dataSize = s.pos() - streamStartPos; assert(dataSize == 588); } void Object::toggleHidden() { if (_type != HIDDEN && _type != HIDE_SHAPE && _type != INVALID) { if (_seqTo != 0) _sequences[_frameNumber] = _seqTo + SEQ_TO_CODE + 128; _seqTo = 0; if (_images == nullptr || _images->size() == 0) // No shape to erase, so flag as hidden _type = HIDDEN; else // Otherwise, flag it to be hidden after it gets erased _type = HIDE_SHAPE; } else if (_type != INVALID) { if (_seqTo != 0) _sequences[_frameNumber] = _seqTo + SEQ_TO_CODE + 128; _seqTo = 0; _seqCounter = _seqCounter2 = 0; _seqStack = 0; _frameNumber = -1; if (_images == nullptr || _images->size() == 0) { _type = NO_SHAPE; } else { _type = ACTIVE_BG_SHAPE; int idx = _sequences[0]; if (idx >= _maxFrames) // Turn on: set up first frame idx = 0; _imageFrame = &(*_images)[idx]; } } } void Object::setObjTalkSequence(int seq) { Talk &talk = *_vm->_talk; // See if we're supposed to restore the object's sequence from the talk sequence stack if (seq == -1) { if (_seqTo != 0) _sequences[_frameNumber] = _seqTo; talk.pullSequence(_restoreSlot); return; } assert(_type != CHARACTER); talk.pushSequenceEntry(this); int talkSeqNum = seq; // Find where the talk sequence data begins in the object int idx = 0; for (;;) { // Get the Frame value byte f = _sequences[idx++]; // See if we've found the beginning of a Talk Sequence if ((f == TALK_SEQ_CODE && seq < 128) || (f == TALK_LISTEN_CODE && seq > 128)) { --seq; // See if we're at the correct Talk Sequence Number if (!(seq & 127)) { // Correct Sequence, Start Talking Here if (_seqTo != 0) _sequences[_frameNumber] = _seqTo; _frameNumber = idx; _seqTo = 0; _seqStack = 0; _seqCounter = 0; _seqCounter2 = 0; _talkSeq = talkSeqNum; break; } } else { // Move ahead any extra because of special control codes switch (f) { case 0: idx++; break; case MOVE_CODE: case TELEPORT_CODE: idx += 4; break; case CALL_TALK_CODE: idx += 8; break; case HIDE_CODE: idx += 2; break; } } // See if we're out of sequence data if (idx >= (int)_seqSize) break; } } void Object::setFlagsAndToggles() { Scene &scene = *_vm->_scene; Talk &talk = *_vm->_talk; 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 == 0) imgNum = 1; _imageFrame = &(*_images)[imgNum - 1]; } } int Object::pickUpObject(FixedTextActionId fixedTextActionId) { FixedText &fixedText = *_vm->_fixedText; 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], kFixedTextAction_Invalid)) { 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(); Common::String errorMessage = fixedText.getActionMessage(fixedTextActionId, message); screen.print(Common::Point(0, INFO_LINE + 1), COL_INFO_FOREGROUND, "%s", errorMessage.c_str()); ui._menuCounter = 30; } else { // Pick it up 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[HOLMES].goAllTheWay(); ui._menuCounter = 25; ui._temp1 = 1; } for (int idx = 0; idx < NAMES_COUNT && !talk._talkToAbort; ++idx) { if (checkNameForCodes(_use[0]._names[idx], kFixedTextAction_Invalid)) { 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; // It's an item, make it lowercase switch (_vm->getLanguage()) { case Common::DE_DEU: // don't do this for German version break; default: // do it for English + Spanish version itemName.setChar(tolower(itemName[0]), 0); break; } screen.print(Common::Point(0, INFO_LINE + 1), COL_INFO_FOREGROUND, fixedText.getObjectPickedUpText(), 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, uint32 dataOffset) { char buffer[12]; s.read(buffer, 12); _name = Common::String(buffer); if (isRoseTattoo) { Common::fill(&_sequences[0], &_sequences[30], 0); _dataSize = s.readUint32LE(); } else { s.read(_sequences, 30); } _position.x = s.readSint16LE(); _position.y = s.readSint16LE(); if (isRoseTattoo) { _flags = s.readByte(); _scaleVal = s.readSint16LE(); } else { _dataSize = s.readUint32LE(); _type = (SpriteType)s.readUint16LE(); _flags = s.readByte(); } _goto[0].x = s.readSint16LE(); _goto[0].y = s.readSint16LE(); _goto[0]._facing = s.readSint16LE(); ADJUST_COORD(_goto[0]); if (isRoseTattoo) { // Get Goto position and facing for second NPC _goto[1].x = s.readSint16LE(); _goto[1].y = s.readSint16LE(); _goto[1]._facing = s.readSint16LE(); ADJUST_COORD(_goto[1]); } else if (_goto[0].x != -1) { // For Serrated Scalpel, adjust the loaded co-ordinates _goto[0].x = _goto[0].x / 100; _goto[0].y = _goto[0].y / 100; } _teleport[0].x = s.readSint16LE(); _teleport[0].y = s.readSint16LE(); _teleport[0]._facing = s.readSint16LE(); ADJUST_COORD(_teleport[0]); if (isRoseTattoo) { // Get Teleport position and facing for second NPC _teleport[1].x = s.readSint16LE(); _teleport[1].y = s.readSint16LE(); _teleport[1]._facing = s.readSint16LE(); ADJUST_COORD(_teleport[1]); } else if (_teleport[0].x != -1) { // For Serrated Scalpel, adjust the loaded co-ordinates _teleport[0].x = _teleport[0].x / 100; _teleport[0].y = _teleport[0].y / 100; } // Save offset of data, which is actually inside another table inside the room data file // This table is at offset 44 for Serrated Scalpel // TODO: find it for the other game _dataOffset = dataOffset; } void CAnim::load3DO(Common::SeekableReadStream &s, uint32 dataOffset) { // this got reordered on 3DO // maybe it was the 3DO compiler _dataSize = s.readUint32BE(); // Save offset of data, which is inside another table inside the room data file _dataOffset = dataOffset; _position.x = s.readSint16BE(); _position.y = s.readSint16BE(); _type = (SpriteType)s.readUint16BE(); _goto[0].x = s.readSint16BE(); _goto[0].y = s.readSint16BE(); _goto[0]._facing = s.readSint16BE(); _teleport[0].x = s.readSint16BE(); _teleport[0].y = s.readSint16BE(); _teleport[0]._facing = s.readSint16BE(); char buffer[12]; s.read(buffer, 12); _name = Common::String(buffer); s.read(_sequences, 30); _flags = s.readByte(); s.skip(3); // Filler _goto[0].x = _goto[0].x * FIXED_INT_MULTIPLIER / 100; _goto[0].y = _goto[0].y * FIXED_INT_MULTIPLIER / 100; _teleport[0].x = _teleport[0].x * FIXED_INT_MULTIPLIER / 100; _teleport[0].y = _teleport[0].y * FIXED_INT_MULTIPLIER / 100; } /*----------------------------------------------------------------*/ SceneImage::SceneImage() { _images = nullptr; _maxFrames = 0; _filesize = 0; } } // End of namespace Sherlock