diff options
Diffstat (limited to 'engines/sherlock/objects.cpp')
-rw-r--r-- | engines/sherlock/objects.cpp | 1533 |
1 files changed, 1533 insertions, 0 deletions
diff --git a/engines/sherlock/objects.cpp b/engines/sherlock/objects.cpp new file mode 100644 index 0000000000..6ef08c28cc --- /dev/null +++ b/engines/sherlock/objects.cpp @@ -0,0 +1,1533 @@ +/* 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; + _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 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 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); + } 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 (_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 BaseObject::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(); + } +} + +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() + 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; + } + + 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; + _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; + _altImages = nullptr; + _altSeq = 0; + Common::fill(&_stopFrames[0], &_stopFrames[8], (ImageFrame *)nullptr); +} + +void Sprite::setImageFrame() { + int frameNum = MAX(_frameNumber, 0); + int imageNumber = _walkSequences[_sequenceNumber][frameNum]; + + if (IS_SERRATED_SCALPEL) + imageNumber = imageNumber + _walkSequences[_sequenceNumber][0] - 2; + else if (imageNumber > _maxFrames) + imageNumber = 1; + + // Get the images to use + ImageFile *images = _altSeq ? _altImages : _images; + assert(images); + + if (IS_3DO) { + // only do this to the image-array with 110 entries + // map uses another image-array and this code + if (images->size() == 110) { + // 3DO has 110 animation frames inside walk.anim + // PC has 55 + // this adjusts the framenumber accordingly + // sort of HACK + imageNumber *= 2; + } + } else if (IS_ROSE_TATTOO) { + --imageNumber; + } + + // Set the frame pointer + _imageFrame = &(*images)[imageNumber]; +} + +void Sprite::checkSprite() { + Events &events = *_vm->_events; + People &people = *_vm->_people; + Scene &scene = *_vm->_scene; + Screen &screen = *_vm->_screen; + Talk &talk = *_vm->_talk; + Point32 pt; + Common::Rect objBounds; + Common::Point spritePt(_position.x / FIXED_INT_MULTIPLIER, _position.y / FIXED_INT_MULTIPLIER); + + if (_type != CHARACTER || (IS_SERRATED_SCALPEL && talk._talkCounter)) + return; + + pt = _walkCount ? _position + _delta : _position; + pt.x /= FIXED_INT_MULTIPLIER; + pt.y /= FIXED_INT_MULTIPLIER; + + if (IS_ROSE_TATTOO) { + checkObject(); + + // For Rose Tattoo, we only do the further processing for Sherlock + if (this != &people[HOLMES]) + return; + } + + for (uint idx = 0; idx < scene._bgShapes.size() && !talk._talkToAbort; ++idx) { + Object &obj = scene._bgShapes[idx]; + if (obj._aType <= PERSON || obj._type == INVALID || obj._type == HIDDEN) + continue; + + if (obj._type == NO_SHAPE) { + objBounds = Common::Rect(obj._position.x, obj._position.y, + obj._position.x + obj._noShapeSize.x + 1, obj._position.y + obj._noShapeSize.y + 1); + } else { + int xp = obj._position.x + obj._imageFrame->_offset.x; + int yp = obj._position.y + obj._imageFrame->_offset.y; + objBounds = Common::Rect(xp, yp, + xp + obj._imageFrame->_frame.w + 1, yp + obj._imageFrame->_frame.h + 1); + } + + if (objBounds.contains(pt)) { + if (objBounds.contains(spritePt)) { + // Current point is already inside the the bounds, so impact occurred + // on a previous call. So simply do nothing until we're clear of the box + switch (obj._aType) { + case TALK_MOVE: + if (_walkCount) { + // Holmes is moving + obj._type = HIDDEN; + obj.setFlagsAndToggles(); + talk.talkTo(obj._use[0]._target); + } + break; + + case PAL_CHANGE: + case PAL_CHANGE2: + if (_walkCount) { + int palStart = atoi(obj._use[0]._names[0].c_str()) * 3; + int palLength = atoi(obj._use[0]._names[1].c_str()) * 3; + int templ = atoi(obj._use[0]._names[2].c_str()) * 3; + if (templ == 0) + templ = 100; + + // Ensure only valid palette change data found + if (palLength > 0) { + // Figure out how far into the shape Holmes is so that we + // can figure out what percentage of the original palette + // to set the current palette to + int palPercent = (pt.x - objBounds.left) * 100 / objBounds.width(); + palPercent = palPercent * templ / 100; + if (obj._aType == PAL_CHANGE) + // Invert percentage + palPercent = 100 - palPercent; + + for (int i = palStart; i < (palStart + palLength); ++i) + screen._sMap[i] = screen._cMap[i] * palPercent / 100; + + events.pollEvents(); + screen.setPalette(screen._sMap); + } + } + break; + + case TALK: + case TALK_EVERY: + obj._type = HIDDEN; + obj.setFlagsAndToggles(); + talk.talkTo(obj._use[0]._target); + break; + + default: + break; + } + } else { + // New impact just occurred + switch (obj._aType) { + case BLANK_ZONE: + // A blank zone masks out all other remaining zones underneath it. + // If this zone is hit, exit the outer loop so we do not check anymore + return; + + case SOLID: + case TALK: + // Stop walking + if (obj._aType == TALK) { + obj.setFlagsAndToggles(); + talk.talkTo(obj._use[0]._target); + } else { + gotoStand(); + } + break; + + case TALK_EVERY: + if (obj._aType == TALK_EVERY) { + obj._type = HIDDEN; + obj.setFlagsAndToggles(); + talk.talkTo(obj._use[0]._target); + } else { + gotoStand(); + } + break; + + case FLAG_SET: + obj.setFlagsAndToggles(); + obj._type = HIDDEN; + break; + + case WALK_AROUND: + if (objBounds.contains(people[HOLMES]._walkTo.front())) { + // Reached zone + gotoStand(); + } else { + // Destination not within box, walk to best corner + Common::Point walkPos; + + if (spritePt.x >= objBounds.left && spritePt.x < objBounds.right) { + // Impact occurred due to vertical movement. Determine whether to + // travel to the left or right side + if (_delta.x > 0) + // Go to right side + walkPos.x = objBounds.right + CLEAR_DIST_X; + else if (_delta.x < 0) { + // Go to left side + walkPos.x = objBounds.left - CLEAR_DIST_X; + } else { + // Going straight up or down. So choose best side + if (spritePt.x >= (objBounds.left + objBounds.width() / 2)) + walkPos.x = objBounds.right + CLEAR_DIST_X; + else + walkPos.x = objBounds.left - CLEAR_DIST_X; + } + + walkPos.y = (_delta.y >= 0) ? objBounds.top - CLEAR_DIST_Y : + objBounds.bottom + CLEAR_DIST_Y; + } else { + // Impact occurred due to horizontal movement + if (_delta.y > 0) + // Go to bottom of box + walkPos.y = objBounds.bottom + CLEAR_DIST_Y; + else if (_delta.y < 0) + // Go to top of box + walkPos.y = objBounds.top - CLEAR_DIST_Y; + else { + // Going straight horizontal, so choose best side + if (spritePt.y >= (objBounds.top + objBounds.height() / 2)) + walkPos.y = objBounds.bottom + CLEAR_DIST_Y; + else + walkPos.y = objBounds.top - CLEAR_DIST_Y; + } + + walkPos.x = (_delta.x >= 0) ? objBounds.left - CLEAR_DIST_X : + objBounds.right + CLEAR_DIST_X; + } + + walkPos.x += people[HOLMES]._imageFrame->_frame.w / 2; + people[HOLMES]._walkDest = walkPos; + people[HOLMES]._walkTo.push(walkPos); + people[HOLMES].setWalking(); + } + break; + + case DELTA: + _position.x += 200; + break; + + default: + break; + } + } + } + } +} + +const Common::Rect Sprite::getOldBounds() const { + return Common::Rect(_oldPosition.x, _oldPosition.y, _oldPosition.x + _oldSize.x, _oldPosition.y + _oldSize.y); +} + +/*----------------------------------------------------------------*/ + +void WalkSequence::load(Common::SeekableReadStream &s) { + char buffer[9]; + s.read(buffer, 9); + _vgsName = Common::String(buffer); + _horizFlip = s.readByte() != 0; + + _sequences.resize(s.readUint16LE()); + s.skip(4); // Skip over pointer field of structure + + s.read(&_sequences[0], _sequences.size()); +} + +/*----------------------------------------------------------------*/ + +WalkSequences &WalkSequences::operator=(const WalkSequences &src) { + resize(src.size()); + for (uint idx = 0; idx < size(); ++idx) { + const WalkSequence &wSrc = src[idx]; + WalkSequence &wDest = (*this)[idx]; + wDest._horizFlip = wSrc._horizFlip; + + wDest._sequences.resize(wSrc._sequences.size()); + Common::copy(&wSrc._sequences[0], &wSrc._sequences[0] + wSrc._sequences.size(), &wDest._sequences[0]); + } + + return *this; +} + +/*----------------------------------------------------------------*/ + +ActionType::ActionType() { + _cAnimNum = _cAnimSpeed = 0; + _useFlag = 0; +} + +void ActionType::load(Common::SeekableReadStream &s) { + char buffer[12]; + + _cAnimNum = s.readByte(); + _cAnimSpeed = s.readByte(); + if (_cAnimSpeed & 0x80) + _cAnimSpeed = -(_cAnimSpeed & 0x7f); + + for (int idx = 0; idx < NAMES_COUNT; ++idx) { + s.read(buffer, 12); + _names[idx] = Common::String(buffer); + } +} + +/*----------------------------------------------------------------*/ + +UseType::UseType(): ActionType() { +} + +void UseType::load(Common::SeekableReadStream &s, bool isRoseTattoo) { + char buffer[12]; + + if (isRoseTattoo) { + s.read(buffer, 12); + _verb = Common::String(buffer); + } + + ActionType::load(s); + + _useFlag = s.readSint16LE(); + + if (!isRoseTattoo) + s.skip(6); + + s.read(buffer, 12); + _target = Common::String(buffer); +} + +void UseType::load3DO(Common::SeekableReadStream &s) { + char buffer[12]; + + _cAnimNum = s.readByte(); + _cAnimSpeed = s.readByte(); + if (_cAnimSpeed & 0x80) + _cAnimSpeed = -(_cAnimSpeed & 0x7f); + + for (int idx = 0; idx < NAMES_COUNT; ++idx) { + s.read(buffer, 12); + _names[idx] = Common::String(buffer); + } + + _useFlag = s.readSint16BE(); + + s.skip(6); + + s.read(buffer, 12); + _target = Common::String(buffer); +} + +void UseType::synchronize(Serializer &s) { + s.syncString(_verb); + s.syncAsSint16LE(_cAnimNum); + s.syncAsSint16LE(_cAnimSpeed); + s.syncAsSint16LE(_useFlag); + + for (int idx = 0; idx < 4; ++idx) + s.syncString(_names[idx]); + s.syncString(_target); +} + +/*----------------------------------------------------------------*/ + +Object::Object(): BaseObject() { + _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[0] = s.readSint16LE(); + _noShapeSize.x = s.readUint16LE(); + _noShapeSize.y = s.readUint16LE(); + _status = s.readUint16LE(); + _misc = s.readByte(); + _maxFrames = s.readUint16LE(); + _flags = s.readByte(); + + if (!isRoseTattoo) + _aOpen.load(s); + + _aType = (AType)s.readByte(); + _lookFrames = s.readByte(); + _seqCounter = s.readByte(); + if (isRoseTattoo) { + _lookPosition.x = s.readUint16LE() * FIXED_INT_MULTIPLIER; + _lookPosition.y = s.readSint16LE() * FIXED_INT_MULTIPLIER; + } else { + _lookPosition.x = s.readUint16LE() * FIXED_INT_MULTIPLIER / 100; + _lookPosition.y = s.readByte() * FIXED_INT_MULTIPLIER; + } + _lookPosition._facing = s.readByte(); + _lookcAnim = s.readByte(); + + if (!isRoseTattoo) + _aClose.load(s); + + _seqStack = s.readByte(); + _seqTo = s.readByte(); + _descOffset = s.readUint16LE(); + _seqCounter2 = s.readByte(); + _seqSize = s.readUint16LE(); + + if (isRoseTattoo) { + for (int idx = 0; idx < 6; ++idx) + _use[idx].load(s, true); + + _quickDraw = s.readByte(); + _scaleVal = s.readUint16LE(); + _requiredFlag[1] = s.readSint16LE(); + _gotoSeq = s.readByte(); + _talkSeq = s.readByte(); + _restoreSlot = s.readByte(); + } else { + s.skip(1); + _aMove.load(s); + s.skip(8); + + for (int idx = 0; idx < 4; ++idx) + _use[idx].load(s, false); + } + //warning("object %s, useAnim %d", _name.c_str(), _use[0]._cAnimNum); +} + +void Object::load3DO(Common::SeekableReadStream &s) { + int32 streamStartPos = s.pos(); + char buffer[41]; + + _examine.clear(); + _sequences = nullptr; + _images = nullptr; + _imageFrame = nullptr; + + // on 3DO all of this data is reordered!!! + // it seems that possibly the 3DO compiler reordered the global struct + // 3DO size for 1 object is 588 bytes + s.skip(4); + _sequenceOffset = s.readUint16LE(); // weird that this seems to be LE + s.seek(10, SEEK_CUR); + + // Offset 16 + _frameNumber = s.readSint16BE(); + _sequenceNumber = s.readSint16BE(); + _position.x = s.readSint16BE(); + _position.y = s.readSint16BE(); + _delta.x = s.readSint16BE(); + _delta.y = s.readSint16BE(); + _type = (SpriteType)s.readUint16BE(); + _oldPosition.x = s.readSint16BE(); + _oldPosition.y = s.readSint16BE(); + _oldSize.x = s.readUint16BE(); + _oldSize.y = s.readUint16BE(); + + _goto.x = s.readSint16BE(); + _goto.y = s.readSint16BE(); + _goto.x = _goto.x * FIXED_INT_MULTIPLIER / 100; + _goto.y = _goto.y * FIXED_INT_MULTIPLIER / 100; + + // Offset 42 + warning("pos %d", s.pos()); + + // Unverified + _lookFlag = s.readSint16BE(); + _pickupFlag = s.readSint16BE(); + _requiredFlag[0] = s.readSint16BE(); + _noShapeSize.x = s.readUint16BE(); + _noShapeSize.y = s.readUint16BE(); + _status = s.readUint16BE(); + // Unverified END + + _maxFrames = s.readUint16BE(); + // offset 56 + _lookPosition.x = s.readUint16BE() * FIXED_INT_MULTIPLIER / 100; + // offset 58 + _descOffset = s.readUint16BE(); + _seqSize = s.readUint16BE(); + + s.skip(2); // boundary filler + + // 288 bytes + for (int idx = 0; idx < 4; ++idx) { + _use[idx].load3DO(s); + s.skip(2); // Filler + } + + // 158 bytes + _aOpen.load(s); // 2 + 12*4 bytes = 50 bytes + s.skip(2); // Boundary filler + _aClose.load(s); + s.skip(2); // Filler + _aMove.load(s); + s.skip(2); // Filler + + // offset 508 + // 3DO: name is at the end + s.read(buffer, 12); + _name = Common::String(buffer); + s.read(buffer, 41); + _description = Common::String(buffer); + + // Unverified + _walkCount = s.readByte(); + _allow = s.readByte(); + _pickup = s.readByte(); + _defaultCommand = s.readByte(); + // Unverified END + + // Probably those here?!?! + _misc = s.readByte(); + _flags = s.readByte(); + + // Unverified + _aType = (AType)s.readByte(); + _lookFrames = s.readByte(); + _seqCounter = s.readByte(); + // Unverified END + + _lookPosition.y = s.readByte() * FIXED_INT_MULTIPLIER; + _lookPosition._facing = s.readByte(); + + // Unverified + _lookcAnim = s.readByte(); + _seqStack = s.readByte(); + _seqTo = s.readByte(); + _seqCounter2 = s.readByte(); + // Unverified END + + s.skip(12); // Unknown + + //warning("object %s, offset %d", _name.c_str(), streamPos); + //warning("object %s, lookPosX %d, lookPosY %d", _name.c_str(), _lookPosition.x, _lookPosition.y); + //warning("object %s, defCmd %d", _name.c_str(), _defaultCommand); + int32 dataSize = s.pos() - streamStartPos; + assert(dataSize == 588); +} + +void Object::toggleHidden() { + if (_type != HIDDEN && _type != HIDE_SHAPE && _type != INVALID) { + if (_seqTo != 0) + _sequences[_frameNumber] = _seqTo + SEQ_TO_CODE + 128; + _seqTo = 0; + + if (_images == nullptr || _images->size() == 0) + // No shape to erase, so flag as hidden + _type = HIDDEN; + else + // Otherwise, flag it to be hidden after it gets erased + _type = HIDE_SHAPE; + } else if (_type != INVALID) { + if (_seqTo != 0) + _sequences[_frameNumber] = _seqTo + SEQ_TO_CODE + 128; + _seqTo = 0; + + _seqCounter = _seqCounter2 = 0; + _seqStack = 0; + _frameNumber = -1; + + if (_images == nullptr || _images->size() == 0) { + _type = NO_SHAPE; + } else { + _type = ACTIVE_BG_SHAPE; + int idx = _sequences[0]; + if (idx >= _maxFrames) + // Turn on: set up first frame + idx = 0; + + _imageFrame = &(*_images)[idx]; + } + } +} + +void Object::setObjTalkSequence(int seq) { + Talk &talk = *_vm->_talk; + + // See if we're supposed to restore the object's sequence from the talk sequence stack + if (seq == -1) { + 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); + + 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; + } +} + +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(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; + itemName.setChar(tolower(itemName[0]), 0); + screen.print(Common::Point(0, INFO_LINE + 1), COL_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, 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 |