scummvm/engines/sherlock/objects.cpp

1585 lines
40 KiB
C++

/* 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;
default: 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", (int)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;
default: 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