diff options
author | Filippos Karapetis | 2008-04-20 14:47:37 +0000 |
---|---|---|
committer | Filippos Karapetis | 2008-04-20 14:47:37 +0000 |
commit | 7ca439f410ac1c46a387567b30271ae4e4a2ed30 (patch) | |
tree | 4d4154169b074293581ad6a11ee821290418f4fb | |
parent | d0590a09eac68d5cde64d37fb2e5bbd1471a676a (diff) | |
download | scummvm-rg350-7ca439f410ac1c46a387567b30271ae4e4a2ed30.tar.gz scummvm-rg350-7ca439f410ac1c46a387567b30271ae4e4a2ed30.tar.bz2 scummvm-rg350-7ca439f410ac1c46a387567b30271ae4e4a2ed30.zip |
Initial import of the work in progress M4 engine
svn-id: r31600
60 files changed, 20007 insertions, 0 deletions
diff --git a/engines/m4/actor.cpp b/engines/m4/actor.cpp new file mode 100644 index 0000000000..c7c90ac3ae --- /dev/null +++ b/engines/m4/actor.cpp @@ -0,0 +1,202 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "common/system.h" +#include "common/array.h" +#include "m4/actor.h" +#include "m4/m4_views.h" +#include "m4/assets.h" + +namespace M4 { + +#define WALKER_BURGER "Wilbur0%i" // wilbur, with a number denoting his current direction + +Actor::Actor(M4Engine *vm) : _vm(vm) { + _scaling = 100; + _direction = 5; + _walkerSprites.resize(10); + loadWalkers(); +} + +Actor::~Actor() { + unloadWalkers(); +} + +int Actor::getWalkerWidth() { return _walkerSprites[kFacingSouth]->getFrame(0)->w; } +int Actor::getWalkerHeight() { return _walkerSprites[kFacingSouth]->getFrame(0)->h; } + +void Actor::placeWalkerSpriteAt(int spriteNum, int x, int y) { + if (_direction < 1 || _direction > 9) { + warning("Direction is %i, fixing", _direction); + _direction = 1; // TODO: this is a temporary fix + } + SpriteInfo info; + info.sprite = _walkerSprites[_direction]->getFrame(spriteNum); + info.hotX = info.hotY = 0; + info.width = info.sprite->w; + info.height = info.sprite->h; + info.scaleX = info.scaleY = _scaling; + info.palette = _walkerSprites[_direction]->getPalette(); + info.inverseColorTable = _vm->_scene->getInverseColorTable(); + + _vm->_scene->drawSprite(x, y, info, Common::Rect(640, 400)); +} + +void Actor::loadWalkers() { + for (uint8 i = 1; i < 10; i++) { + if (i == 6) + continue; // walker sprite 6 is unused + loadWalkerDirection(i); + } +} + +void Actor::loadWalkerDirection(uint8 direction) { + char name[20]; + Common::SeekableReadStream *walkerS; + + if (_vm->getGameType() == GType_Burger) { + sprintf(name, WALKER_BURGER, direction); + } else { + //warning("Actor::loadWalkerDirection: unspecified walker type, not loading walker"); + // TODO: Master Lu walkers + return; + } + + walkerS = _vm->res()->get(name); + _walkerSprites.insert_at(direction, new SpriteAsset(_vm, walkerS, walkerS->size(), name)); + _vm->res()->toss(name); +} + +void Actor::unloadWalkers() { + for (uint8 i = 9; i > 0; i--) { + if (i == 6) + continue; // walker sprite 6 is unused + SpriteAsset *tempSprite = _walkerSprites[i]; + _walkerSprites.remove_at(i); + if (tempSprite) + delete tempSprite; + } +} + +void Actor::setWalkerPalette() { + _vm->_palette->setPalette(_walkerSprites[kFacingSouthEast]->getPalette(), 0, + _walkerSprites[kFacingSouthEast]->getColorCount()); +} + +Inventory::Inventory(M4Engine *vm) : _vm(vm) { +} + +Inventory::~Inventory() { + _inventory.clear(); +} + +void Inventory::registerObject(char* name, int32 scene, int32 icon) { + InventoryObject *newObject = new InventoryObject(); + int newObjectIndex = 0; + + // Capitalize registered inventory object names + str_upper(name); + + newObject->name = strdup(name); + newObject->scene = scene; + newObject->icon = icon; + + newObjectIndex = _inventory.size(); + + _inventory.push_back(newObject); + + if (scene == BACKPACK) + addToBackpack(newObjectIndex); +} + +void Inventory::moveObject(char* name, int32 scene) { + uint i = 0; + + for (i = 0; i < _inventory.size(); i++) { + if (!scumm_stricmp(_inventory[i]->name, name)) { + if (_inventory[i]->scene == BACKPACK && scene != BACKPACK) + removeFromBackpack(i); + + _inventory[i]->scene = scene; + + if (scene == BACKPACK) + addToBackpack(i); + + return; + } + } +} + +void Inventory::addToBackpack(uint32 objectIndex) { + _vm->_interfaceView->inventoryAdd(_inventory[objectIndex]->name, "", _inventory[objectIndex]->icon); +} + +void Inventory::removeFromBackpack(uint32 objectIndex) { + _vm->_interfaceView->inventoryRemove(_inventory[objectIndex]->name); +} + +bool Inventory::isInCurrentScene(char* name) { + return (getScene(name) == _vm->_scene->getCurrentScene()); +} + +int Inventory::getScene(char* name) { + uint i = 0; + + for (i = 0; i < _inventory.size(); i++) { + if (!scumm_stricmp(_inventory[i]->name, name)) + return _inventory[i]->scene; + } + return UNKNOWN_OBJECT; +} + +int Inventory::getIcon(char* name) { + uint i = 0; + + for (i = 0; i < _inventory.size(); i++) { + if (!scumm_stricmp(_inventory[i]->name, name)) + return _inventory[i]->icon; + } + return UNKNOWN_OBJECT; +} + +int Inventory::getIndex(char* name) { + uint i = 0; + + for (i = 0; i < _inventory.size(); i++) { + if (!scumm_stricmp(_inventory[i]->name, name)) + return i; + } + return UNKNOWN_OBJECT; +} + +void Inventory::clear() { + for (uint i = 0; i < _inventory.size(); i++) { + delete _inventory[i]->name; + delete _inventory[i]; + _inventory.remove_at(i); + } +} + +} // End of namespace M4 diff --git a/engines/m4/actor.h b/engines/m4/actor.h new file mode 100644 index 0000000000..a39398db55 --- /dev/null +++ b/engines/m4/actor.h @@ -0,0 +1,117 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef M4_ACTOR_H +#define M4_ACTOR_H + +#include "common/array.h" + +#include "m4/m4.h" +#include "m4/scene.h" +#include "m4/graphics.h" +#include "m4/assets.h" + +namespace M4 { + +struct InventoryObject { + const char* name; + int32 scene; + int32 icon; +}; + +enum inventoryObjectFlags { + UNKNOWN_OBJECT = 997, + BACKPACK = 998, + NOWHERE = 999 +}; + +enum WalkerDirection { + kFacingNorth = 1, // has shadow + kFacingNorthEast = 2, // has shadow + kFacingEast = 3, // has shadow + kFacingSouthEast = 4, // has shadow + kFacingSouth = 5, // has shadow + // 6 is unused + kFacingSouthAlt = 7, // no shadow + kFacingSouthWest = 8, // no shadow + kFacingWest = 9 // no shadow +}; + +class Actor { +public: + Actor(M4Engine *vm); + ~Actor(); + void placeWalkerSpriteAt(int spriteNum, int x, int y); + void setWalkerScaling(int scaling) { _scaling = scaling; } + int getWalkerScaling() { return _scaling; } + void setWalkerDirection(uint8 direction) { _direction = direction; } + uint8 getWalkerDirection() { return _direction; } + void setWalkerPalette(); + int getWalkerWidth(); + int getWalkerHeight(); +private: + M4Engine *_vm; + int _scaling; + uint8 _direction; + Common::Array<SpriteAsset*> _walkerSprites; + + void loadWalkers(); + void loadWalkerDirection(uint8 direction); + void unloadWalkers(); +}; + +// TODO: perhaps the inventory and its view could be merged? +// TODO: the original game capitalizes all inventory object names +// internally, which we do as well, but perhaps we could make sure +// that all object names are parsed with the same case and avoid +// case-insensitive string comparing through scumm_stricmp, using +// the normal strcmp method instead +class Inventory { +public: + Inventory(M4Engine *vm); + ~Inventory(); + void clear(); + void registerObject(char* name, int32 scene, int32 icon); + void moveObject(char* name, int32 scene); + void giveToPlayer(char* name) { moveObject(name, BACKPACK); } + void addToBackpack(uint32 objectIndex); + void removeFromBackpack(uint32 objectIndex); + bool isInBackpack(char* name) { return (getScene(name) == BACKPACK); } + bool isInScene(char* name, int32 scene) { return (getScene(name) == scene); } + bool isInCurrentScene(char* name); + int getScene(char* name); + int getIcon(char* name); + int getIndex(char* name); + int getTotalItems() { return _inventory.size(); } + +private: + M4Engine *_vm; + Common::Array<InventoryObject *> _inventory; +}; + +} // End of namespace M4 + + +#endif diff --git a/engines/m4/animation.cpp b/engines/m4/animation.cpp new file mode 100644 index 0000000000..6c9d90a3d2 --- /dev/null +++ b/engines/m4/animation.cpp @@ -0,0 +1,210 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "m4/assets.h" +#include "m4/animation.h" +#include "m4/compression.h" + +namespace M4 { + +// TODO: this code needs cleanup + +Animation::Animation(M4Engine *vm) { + _vm = vm; + _playing = false; +} + +void Animation::loadFullScreen(const char *filename) { + _vm->_palette->deleteAllRanges(); + load(filename); +} + +void Animation::load(const char *filename) { + MadsPack anim(filename, _vm); + char buffer[20]; + + // Chunk 1: header + // header + // TODO: there are some unknown fields here, plus we don't read + // the entire chunk + Common::SeekableReadStream *animStream = anim.getItemStream(0); + Common::SeekableReadStream *spriteSeriesStream; + //printf("Chunk 0, size %i\n", animStream->size()); + _seriesCount = animStream->readUint16LE(); + _frameCount = animStream->readUint16LE(); + _frameEntryCount = animStream->readUint16LE(); + + // Unknown + for (int i = 0; i < 43; i++) + animStream->readByte(); + + _spriteSeriesNames = new Common::String[_seriesCount]; + printf("%i sprite series\n", _seriesCount); + + // TODO: for now, we only load the first sprite series + if (_seriesCount > 1) + printf("TODO: Anim has %i sprite series, for now, we only load the first one\n", _seriesCount); + _seriesCount = 1; // TODO + + for (int i = 0; i < _seriesCount; i++) { + animStream->read(buffer, 13); + _spriteSeriesNames[i] = Common::String(buffer); + //printf("%03d: %s\n", i, _spriteSeriesNames[i].c_str()); + + spriteSeriesStream = _vm->res()->get(_spriteSeriesNames[i].c_str()); + _spriteSeries = new SpriteAsset(_vm, spriteSeriesStream, + spriteSeriesStream->size(), _spriteSeriesNames[i].c_str()); + _vm->res()->toss(_spriteSeriesNames[i].c_str()); + + // Adjust the palette of the sprites in the sprite series + // so that they can be displayed on screen correctly + RGBList *palData = new RGBList(_spriteSeries->getColorCount(), _spriteSeries->getPalette(), true); + _vm->_palette->addRange(palData); + + for (int k = 0; k < _spriteSeries->getCount(); k++) { + M4Sprite *spr = _spriteSeries->getFrame(k); + spr->translate(palData); // sprite pixel translation + } + } + + //printf("End pos: %i\n", animStream->pos()); + + delete animStream; + + // ------------------ + + // Chunk 2: anim info + AnimationFrame frame; + animStream = anim.getItemStream(1); + //printf("Chunk 1, size %i\n", animStream->size()); + + _frameEntries = new AnimationFrame[_frameEntryCount]; + + for (int i = 0; i < _frameEntryCount; i++) { + + frame.animFrameIndex = animStream->readUint16LE(); + frame.u = animStream->readByte(); + frame.seriesIndex = animStream->readByte(); + frame.seriesFrameIndex = animStream->readUint16LE(); + frame.x = animStream->readUint16LE(); + frame.y = animStream->readUint16LE(); + frame.v = animStream->readByte(); + frame.w = animStream->readByte(); + + _frameEntries[i] = frame; + + /* + printf( + "animFrameIndex = %4d, " + "u = %3d, " + "seriesIndex = %3d, " + "seriesFrameIndex = %6d, " + "x = %3d, " + "y = %3d, " + "v = %3d, " + "w = %3d\n", + + frame.animFrameIndex, + frame.u, + frame.seriesIndex, + frame.seriesFrameIndex, + frame.x, + frame.y, + frame.v, + frame.w + ); + */ + } + //printf("End pos: %i\n", animStream->pos()); + + delete animStream; + + // Chunk 3: unknown (seems to be sound data?) + // TODO +} + +Animation::~Animation() { + //delete[] _spriteSeriesNames; + //delete[] _spriteSeries; + //delete[] _frameEntries; +} + +void Animation::start() { + _curFrame = 0; + _curFrameEntry = 0; + //for (int i = 0; i < _seriesCount; i++) { + //_spriteSeries[i] = new SpriteSeries((char*)_spriteSeriesNames[i].c_str()); + //} + _playing = true; + updateAnim(); +} + +bool Animation::updateAnim() { + if (!_playing) + return true; + + // Get the scene background surface + M4Surface *bg = _vm->_scene->getBackgroundSurface(); + + while (_frameEntries[_curFrameEntry].animFrameIndex == _curFrame) { + AnimationFrame *frame = &_frameEntries[_curFrameEntry]; + int seriesFrameIndex = (frame->seriesFrameIndex & 0x7FFF) - 1; + + // Write the sprite onto the screen + M4Sprite *spr = _spriteSeries->getFrame(seriesFrameIndex); + + // FIXME: We assume that the transparent color is the color of the top left pixel + byte *transparentColor = (byte *)spr->pixels; + + // FIXME: correct x, y + spr->copyTo(bg, frame->x, frame->y, (int)*transparentColor); + + // HACK: wait a bit + g_system->delayMillis(100); + + //printf("_curFrameEntry = %d\n", _curFrameEntry); + _curFrameEntry++; + } + + //printf("_curFrame = %d\n", _curFrame); + + _curFrame++; + if (_curFrame >= _frameCount) // anim done + stop(); + + return _curFrame >= _frameCount; +} + +void Animation::stop() { + _playing = false; + + for (int i = 0; i < _seriesCount; i++) { + // TODO: cleanup + //delete _spriteSeries[i]; + //_spriteSeries[i] = NULL; + } +} + +} // End of namespace M4 diff --git a/engines/m4/animation.h b/engines/m4/animation.h new file mode 100644 index 0000000000..6aafc53310 --- /dev/null +++ b/engines/m4/animation.h @@ -0,0 +1,69 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef M4_ANIMATION_H +#define M4_ANIMATION_H + +#include "m4/m4.h" +#include "m4/graphics.h" +#include "m4/assets.h" + +namespace M4 { + +struct AnimationFrame { + uint16 animFrameIndex; + byte u; + byte seriesIndex; + uint16 seriesFrameIndex; + uint16 x, y; + byte v, w; +}; + +class Animation { + public: + Animation(M4Engine *vm); + ~Animation(); + + void load(const char *filename); + void loadFullScreen(const char *filename); + void start(); + bool updateAnim(); + void stop(); + + private: + bool _playing; + M4Engine *_vm; + int _seriesCount; + int _frameCount; + int _frameEntryCount; + AnimationFrame *_frameEntries; + Common::String *_spriteSeriesNames; + SpriteAsset *_spriteSeries; + int _curFrame, _curFrameEntry; +}; + +} // End of namespace M4 + +#endif diff --git a/engines/m4/assets.cpp b/engines/m4/assets.cpp new file mode 100644 index 0000000000..55decc2f00 --- /dev/null +++ b/engines/m4/assets.cpp @@ -0,0 +1,544 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "m4/assets.h" +#include "m4/globals.h" +#include "m4/compression.h" + +namespace M4 { + +BaseAsset::BaseAsset(M4Engine *vm, Common::SeekableReadStream* stream, int size, const char *name) : _vm(vm) { +} + +BaseAsset::~BaseAsset() { +} + +MachineAsset::MachineAsset(M4Engine *vm, Common::SeekableReadStream* stream, int size, const char *name) : BaseAsset(vm, stream, size, name) { + uint32 stateCount = stream->readUint32LE(); + for (uint32 curState = 0; curState < stateCount; curState++) { + uint32 stateOffset = stream->readUint32LE(); + _stateTable.push_back(stateOffset); + } + _codeSize = size - 4 - 4 * stateCount; + _code = new byte[_codeSize]; + stream->read(_code, _codeSize); +} + +MachineAsset::~MachineAsset() { + delete[] _code; +} + +void MachineAsset::getCode(byte *&code, uint32 &codeSize) { + code = _code; + codeSize = _codeSize; +} + +uint32 MachineAsset::getStateOffset(uint32 state) { + assert(state < _stateTable.size()); + return _stateTable[state]; +} + +SequenceAsset::SequenceAsset(M4Engine *vm, Common::SeekableReadStream* stream, int size, const char *name) : BaseAsset(vm, stream, size, name) { + _localVarCount = stream->readUint32LE(); + _codeSize = size - 4; + _code = new byte[_codeSize]; + stream->read(_code, _codeSize); +} + +SequenceAsset::~SequenceAsset() { + delete[] _code; +} + +void SequenceAsset::getCode(byte *&code, uint32 &codeSize) { + code = _code; + codeSize = _codeSize; +} + + +DataAsset::DataAsset(M4Engine *vm, Common::SeekableReadStream* stream, int size, const char *name) : BaseAsset(vm, stream, size, name) { + + _recCount = stream->readUint32LE(); + _recSize = stream->readUint32LE(); + _dataSize = _recCount * _recSize; + _data = new long[_dataSize]; + for (uint32 i = 0; i < _dataSize; i++) + _data[i] = (long)stream->readUint32LE(); + +} + +DataAsset::~DataAsset() { + delete _data; +} + +long *DataAsset::getRow(int index) { + assert(index < _recCount); + return &_data[_recSize * index]; +} + +SpriteAsset::SpriteAsset(M4Engine *vm, Common::SeekableReadStream* stream, int size, const char *name, bool asStream) : BaseAsset(vm, stream, size, name) { + _stream = stream; + + if (_vm->isM4()) { + loadM4SpriteAsset(vm, stream, asStream); + } else { + loadMadsSpriteAsset(vm, stream); + } +} + +void SpriteAsset::loadM4SpriteAsset(M4Engine *vm, Common::SeekableReadStream* stream, bool asStream) { + bool isBigEndian = false; + uint32 frameOffset; + + uint32 header = _stream->readUint32LE(); + if (header == HEAD_M4SS) { + printf("LE-encoded sprite\n"); + } else { + printf("BE-encoded sprite\n"); + isBigEndian = true; + } + + _srcSize = parseSprite(isBigEndian); + + _stream->readUint32LE(); + _frameRate = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE(); + _pixelSpeed = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE(); + _maxWidth = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE(); + _maxHeight = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE(); + _stream->skip(6 * 4); + _frameCount = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE(); + + printf("SpriteAsset::SpriteAsset() srcSize = %d; frameRate = %04X; pixelSpeed = %04X; maxWidth = %d; maxHeight = %d; frameCount = %d\n", _srcSize, _frameRate, _pixelSpeed, _maxWidth, _maxHeight, _frameCount); + + for (int curFrame = 0; curFrame < _frameCount; curFrame++) { + frameOffset = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE(); + _frameOffsets.push_back(frameOffset); + } + _frameOffsets.push_back(_srcSize - 48 - _frameCount * 4); + + _frameStartOffset = _stream->pos(); + + // We don't need to load frames when streaming + if (asStream) + return; + + for (int curFrame = 0; curFrame < _frameCount; curFrame++) { + frameOffset = _frameStartOffset + _frameOffsets[curFrame]; + _stream->seek(frameOffset); + + SpriteAssetFrame frame; + loadFrameHeader(frame, isBigEndian); + + // Load & unpack RLE data if it's not a streaming animation + if (frame.stream != 1) { + + frame.frame = new M4Sprite(stream, frame.x, frame.y, frame.w, frame.h, true, frame.comp); +#if 0 + char fn[512]; + sprintf(fn, "%04d.raw", curFrame); + FILE *h = fopen(fn, "wb"); + fwrite((byte*)frame.frame->getData(), frame.w * frame.h, 1, h); + fclose(h); +#endif + } + + _frames.push_back(frame); + + } + +} + +void SpriteAsset::loadMadsSpriteAsset(M4Engine *vm, Common::SeekableReadStream* stream) { + int curFrame = 0; + uint32 frameOffset = 0; + MadsPack sprite(stream); + _frameRate = 0; + _pixelSpeed = 0; + _maxWidth = 0; + _maxHeight = 0; + + Common::SeekableReadStream *spriteStream = sprite.getItemStream(0); + for (int i = 0; i < 19; i++) { + spriteStream->readUint16LE(); + } + _frameCount = spriteStream->readUint16LE(); + // we skip the rest of the data + delete spriteStream; + + // Get the palette data + spriteStream = sprite.getItemStream(2); + int numColors = 0; + RGB8 *palData = Palette::decodeMadsPalette(spriteStream, &numColors); + Common::copy(palData, &palData[numColors], &_palette[0]); + if (numColors < 256) + Common::set_to((byte *)&_palette[numColors], (byte *)&_palette[256], 0); + _colorCount = numColors; + delete[] palData; + delete spriteStream; + + spriteStream = sprite.getItemStream(1); + Common::SeekableReadStream *spriteDataStream = sprite.getItemStream(3); + SpriteAssetFrame frame; + for (curFrame = 0; curFrame < _frameCount; curFrame++) { + frame.comp = 0; + frameOffset = spriteStream->readUint32LE(); + _frameOffsets.push_back(frameOffset); + spriteStream->readUint32LE(); // frame size + frame.x = spriteStream->readUint16LE(); + frame.y = spriteStream->readUint16LE(); + frame.w = spriteStream->readUint16LE(); + frame.h = spriteStream->readUint16LE(); + if (curFrame == 0) + printf("%i frames, x = %i, y = %i, w = %i, h = %i\n", _frameCount, frame.x, frame.y, frame.w, frame.h); + + frame.frame = new M4Sprite(spriteDataStream, frame.x, frame.y, frame.w, frame.h, false); + _frames.push_back(frame); + } + delete spriteStream; + delete spriteDataStream; +} + +SpriteAsset::~SpriteAsset() { + for (Common::Array<SpriteAssetFrame>::iterator it = _frames.begin(); it != _frames.end(); it++) { + delete (*it).frame; + } +} + +int32 SpriteAsset::parseSprite(bool isBigEndian) { + + uint32 format, chunkType, chunkSize = 0; + + _colorCount = 0; + + format = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE(); + + chunkType = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE(); + + if (chunkType == CELS__PAL) { + chunkSize = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE(); + uint32 numColors = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE(); + // TODO + //if (palette) { + // TODO: A sprite set palette specifies the indexes, which need not start at + // index 0. For now, I'm simply preloading the currently active palette + // before starting to replace existing entries + + _vm->_palette->grabPalette((byte *) _palette, 0, 256); + _colorCount = 0; + + for (uint32 i = 0; i < numColors; i++) { + uint32 paletteEntry = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE(); + uint index = (paletteEntry >> 24) & 0xFF; + + _palette[index].r = ((paletteEntry >> 16) & 0xFF) << 2; + _palette[index].g = ((paletteEntry >> 8) & 0xFF) << 2; + _palette[index].b = (paletteEntry & 0xFF) << 2; + + _colorCount = MAX(_colorCount, index); + } + + /* + } else { + stream.seek(colorCount, ) + data += colorCount * 4; + } + */ + chunkType = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE(); + } + + if (chunkType == CELS___SS) { + chunkSize = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE(); + } else { + warning("SpriteAsset::parseSprite() Expected chunk type %08X, got %08X", CELS___SS, chunkType); + } + + return chunkSize; + +} + +void SpriteAsset::loadFrameHeader(SpriteAssetFrame &frameHeader, bool isBigEndian) { + _stream->readUint32LE(); + frameHeader.stream = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE(); + frameHeader.x = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE(); + frameHeader.y = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE(); + frameHeader.w = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE(); + frameHeader.h = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE(); + frameHeader.comp = (!isBigEndian) ? _stream->readUint32LE() : _stream->readUint32BE(); + frameHeader.frame = NULL; + _stream->seek(8 * 4, SEEK_CUR); + //printf("SpriteAsset::loadFrameHeader() stream = %d; x = %d; y = %d; w = %d; h = %d; comp = %d\n", frameHeader.stream, frameHeader.x, frameHeader.y, frameHeader.w, frameHeader.h, frameHeader.comp); +} + +M4Sprite *SpriteAsset::getFrame(int frameIndex) { + return _frames[frameIndex].frame; +} + +void SpriteAsset::loadStreamingFrame(M4Sprite *frame, int frameIndex, int destX, int destY) { + uint32 frameOffset = _frameStartOffset + _frameOffsets[frameIndex]; + _stream->seek(frameOffset); + + SpriteAssetFrame frameHeader; + loadFrameHeader(frameHeader); + + if (frameHeader.w > 0 && frameHeader.h > 0) { + Common::MemoryReadStream *frameData = _stream->readStream(getFrameSize(frameIndex)); + if (frameHeader.stream) { + frame->loadDeltaRle(frameData, destX - frameHeader.x, destY - frameHeader.y); + } else { + frame->loadRle(frameData); + } + delete frameData; + } + +} + +RGBList *SpriteAsset::getRgbList() { + RGBList *result = new RGBList(_colorCount); + Common::copy((byte *)&_palette[0], (byte *)&_palette[_colorCount], (byte *)result->data()); + return result; +} + +void SpriteAsset::translate(RGBList *list, bool isTransparent) { + for (int frameIndex = 0; frameIndex < _frameCount; ++frameIndex) + _frames[frameIndex].frame->translate(list, isTransparent); +} + +int32 SpriteAsset::getFrameSize(int index) { + /* + if (index + 1 == _frameCount) { + } else { + + } + */ + return _frameOffsets[index + 1] - _frameOffsets[index]; +} + +AssetManager::AssetManager(M4Engine *vm) { + + _vm = vm; + + /* Initialize asset arrays */ + for (int i = 0; i < 256; i++) { + _MACH[i] = NULL; + _SEQU[i] = NULL; + _DATA[i] = NULL; + _CELS[i] = NULL; + } + +} + +AssetManager::~AssetManager() { + // unload all remaining assets + clearAssets(kAssetTypeMACH, 0, 255); + clearAssets(kAssetTypeSEQU, 0, 255); + clearAssets(kAssetTypeCELS, 0, 255); + clearAssets(kAssetTypeDATA, 0, 255); +} + +bool AssetManager::clearAssets(AssetType assetType, int32 minHash, int32 maxHash) { + + minHash = MAX(0, minHash); + maxHash = MIN(maxHash, 255); + + switch (assetType) { + case kAssetTypeMACH: + for (int i = minHash; i <= maxHash; i++) + if (_MACH[i]) { + delete _MACH[i]; + _MACH[i] = NULL; + } + break; + case kAssetTypeSEQU: + for (int i = minHash; i <= maxHash; i++) + if (_SEQU[i]) { + delete _SEQU[i]; + _SEQU[i] = NULL; + } + break; + case kAssetTypeDATA: + for (int i = minHash; i <= maxHash; i++) + if (_DATA[i]) { + delete _DATA[i]; + _DATA[i] = NULL; + } + break; + case kAssetTypeCELS: + for (int i = minHash; i <= maxHash; i++) + if (_CELS[i]) { + delete _CELS[i]; + _CELS[i] = NULL; + } + break; + } + + // FIXME: no value is returned, returning true for now + return true; +} + +bool AssetManager::loadAsset(const char *assetName, RGB8 *palette) { + + printf("AssetManager::loadAsset() %s\n", assetName); + + // TODO, better use MemoryReadStreamEndian? + //convertAssetToLE(assetData, assetSize); + + Common::SeekableReadStream *assetS = _vm->res()->get(assetName); + + while (assetS->pos() + 12 < assetS->size()) { + uint32 chunkType, chunkSize, chunkHash; + + chunkType = assetS->readUint32LE(); + chunkSize = assetS->readUint32LE() - 12; // sub 12 for the chunk header + chunkHash = assetS->readUint32LE(); + + printf("hash = %d\n", chunkHash); + + // Until loading code is complete, so that chunks not fully read are skipped correctly + uint32 nextOfs = assetS->pos() + chunkSize; + + switch (chunkType) { + case CHUNK_MACH: + printf("MACH\n"); + clearAssets(kAssetTypeMACH, chunkHash, chunkHash); + _MACH[chunkHash] = new MachineAsset(_vm, assetS, chunkSize, assetName); + break; + case CHUNK_SEQU: + printf("SEQU\n"); + clearAssets(kAssetTypeSEQU, chunkHash, chunkHash); + _SEQU[chunkHash] = new SequenceAsset(_vm, assetS, chunkSize, assetName); + break; + case CHUNK_DATA: + printf("DATA\n"); + clearAssets(kAssetTypeDATA, chunkHash, chunkHash); + _DATA[chunkHash] = new DataAsset(_vm, assetS, chunkSize, assetName); + break; + case CHUNK_CELS: + printf("CELS\n"); + clearAssets(kAssetTypeCELS, chunkHash, chunkHash); + _CELS[chunkHash] = new SpriteAsset(_vm, assetS, chunkSize, assetName); + break; + default: + printf("AssetManager::loadAsset() Unknown chunk type %08X\n", chunkType); + } + + // Until loading code is complete (see above) + assetS->seek(nextOfs); + + } + + _vm->res()->toss(assetName); + + // FIXME: no value is returned, returning true for now + return true; +} + +int32 AssetManager::addSpriteAsset(const char *assetName, int32 hash, RGB8 *palette) { + + bool alreadyLoaded = false; + + if (hash < 0) { + for (int i = 0; i <= 255; i++) { + if (_CELS[i] != NULL) { + if (_CELS[i]->getName() == assetName) { + alreadyLoaded = true; + hash = i; + break; + } + } else { + hash = i; + break; + } + } + } else { + alreadyLoaded = _CELS[hash] != NULL && _CELS[hash]->getName() == assetName; + } + + /* Not loaded and no empty slots */ + if (hash < 0) + return -1; + + if (!alreadyLoaded) { + + printf("AssetManager::addSpriteAsset() asset %s not loaded, loading into %d\n", assetName, hash); + + clearAssets(kAssetTypeCELS, hash, hash); + + Common::SeekableReadStream *assetS = _vm->res()->get(assetName); + _CELS[hash] = new SpriteAsset(_vm, assetS, assetS->size(), assetName); + _vm->res()->toss(assetName); + + } else { + + printf("AssetManager::addSpriteAsset() asset %s already loaded in %d\n", assetName, hash); + + /* TODO/FIXME + if (_CELS[hash].palOffset >= 0 && palette) + restorePalette(palette, _CELS[hash].data + _CELS[hash].palOffset); + */ + + } + + return hash; + +} + +void AssetManager::restorePalette(RGB8 *palette, byte *data) { + // TODO +} + +void AssetManager::convertAssetToLE(byte *assetData, uint32 assetSize) { + +} + +MachineAsset *AssetManager::getMachine(int32 hash) { + assert(_MACH[hash] != NULL); + return _MACH[hash]; +} + +SequenceAsset *AssetManager::getSequence(int32 hash) { + assert(_SEQU[hash] != NULL); + return _SEQU[hash]; +} + +DataAsset *AssetManager::getData(int32 hash) { + assert(_DATA[hash] != NULL); + return _DATA[hash]; +} + +SpriteAsset *AssetManager::getSprite(int32 hash) { + assert(_CELS[hash] != NULL); + return _CELS[hash]; +} + +M4Sprite *AssetManager::getSpriteFrame(int32 hash, int frameIndex) { + assert(_CELS[hash] != NULL); + return _CELS[hash]->getFrame(frameIndex); +} + +int32 AssetManager::getSpriteFrameCount(int32 hash) { + assert(_CELS[hash] != NULL); + return _CELS[hash]->getCount(); +} + +} // End of namespace M4 diff --git a/engines/m4/assets.h b/engines/m4/assets.h new file mode 100644 index 0000000000..efc9afed83 --- /dev/null +++ b/engines/m4/assets.h @@ -0,0 +1,184 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ +#ifndef M4_ASSETS_H +#define M4_ASSETS_H + +#include "common/scummsys.h" +#include "common/stream.h" + +#include "m4/sprite.h" + +namespace M4 { + +// Sequence chunks +#define CHUNK_SCEN MKID_BE('SCEN') +#define CHUNK_MACH MKID_BE('MACH') +#define CHUNK_SEQU MKID_BE('SEQU') +#define CHUNK_DATA MKID_BE('DATA') +#define CHUNK_CELS MKID_BE('CELS') + +// Sprite chunks +#define HEAD_M4SS MKID_BE('M4SS') //'M4SS' +#define CELS__PAL MKID_BE(' PAL') //' PAL' +#define CELS___SS MKID_BE(' SS') //' SS' + +class M4Engine; + +class BaseAsset { +public: + BaseAsset(M4Engine *vm, Common::SeekableReadStream* stream, int size, const char *name); + ~BaseAsset(); + const Common::String getName() const { return _name; } +protected: + M4Engine *_vm; + Common::String _name; +}; + +class MachineAsset : public BaseAsset { +public: + MachineAsset(M4Engine *vm, Common::SeekableReadStream* stream, int size, const char *name); + ~MachineAsset(); + void getCode(byte *&code, uint32 &codeSize); + uint32 getStateOffset(uint32 state); +protected: + Common::Array<uint32> _stateTable; + byte *_code; + uint32 _codeSize; +}; + +class SequenceAsset : public BaseAsset { +public: + SequenceAsset(M4Engine *vm, Common::SeekableReadStream* stream, int size, const char *name); + ~SequenceAsset(); + void getCode(byte *&code, uint32 &codeSize); + int localVarCount() const { return _localVarCount; } +protected: + int _localVarCount; + byte *_code; + uint32 _codeSize; +}; + +class DataAsset : public BaseAsset { +public: + DataAsset(M4Engine *vm, Common::SeekableReadStream* stream, int size, const char *name); + ~DataAsset(); + int getCount() const { return _recCount; } + long *getRow(int index); +protected: + long *_data; + uint32 _recSize, _dataSize; + int _recCount; +}; + +struct SpriteAssetFrame { + uint32 stream; + int x, y, w, h; + uint32 comp; + M4Sprite *frame; +}; + +class SpriteAsset : public BaseAsset { +public: + SpriteAsset(M4Engine *vm, Common::SeekableReadStream* stream, int size, const char *name, bool asStream = false); + ~SpriteAsset(); + void loadM4SpriteAsset(M4Engine *vm, Common::SeekableReadStream* stream, bool asStream); + void loadMadsSpriteAsset(M4Engine *vm, Common::SeekableReadStream* stream); + int32 getCount() { return _frameCount; } + int32 getFrameRate() const { return _frameRate; } + int32 getPixelSpeed() const { return _pixelSpeed; } + int32 getFrameWidth(int index); + int32 getFrameHeight(int index); + int32 getMaxFrameWidth() const { return _maxWidth; } + int32 getMaxFrameHeight() const { return _maxHeight; } + M4Sprite *getFrame(int frameIndex); + void loadStreamingFrame(M4Sprite *frame, int frameIndex, int destX, int destY); + RGB8* getPalette() { return _palette; } + int getColorCount() { return _colorCount; } + RGBList *getRgbList(); + void translate(RGBList *list, bool isTransparent = false); + int32 getFrameSize(int index); + M4Sprite *operator[](int index) { return getFrame(index); } +protected: + RGB8 _palette[256]; + uint32 _colorCount; + uint32 _srcSize; + int32 _frameRate, _pixelSpeed; + int _maxWidth, _maxHeight; + int _frameCount; + Common::Array<uint32> _frameOffsets; + Common::Array<SpriteAssetFrame> _frames; + uint32 _frameStartOffset; + Common::SeekableReadStream *_stream; + int32 parseSprite(bool isBigEndian = false); + void loadFrameHeader(SpriteAssetFrame &frameHeader, bool isBigEndian = false); +}; + +enum AssetType { + kAssetTypeMACH, + kAssetTypeSEQU, + kAssetTypeDATA, + kAssetTypeCELS +}; + +enum CallbackHandlers { + kCallbackTriggerDispatch +}; + +class AssetManager { +public: + + AssetManager(M4Engine *vm); + ~AssetManager(); + + bool clearAssets(AssetType assetType, int32 minHash, int32 maxHash); + bool loadAsset(const char *assetName, RGB8 *palette); + int32 addSpriteAsset(const char *assetName, int32 hash, RGB8 *palette); + + // TODO: Move to Palette class + void restorePalette(RGB8 *palette, byte *data); + + MachineAsset *getMachine(int32 hash); + SequenceAsset *getSequence(int32 hash); + DataAsset *getData(int32 hash); + SpriteAsset *getSprite(int32 hash); + M4Sprite *getSpriteFrame(int32 hash, int frameIndex); + int32 getSpriteFrameCount(int32 hash); + +protected: + // TODO: Check if we need _vm + M4Engine *_vm; + + MachineAsset *_MACH[256]; + SequenceAsset *_SEQU[256]; + DataAsset *_DATA[256]; + SpriteAsset *_CELS[256]; + + void convertAssetToLE(byte *assetData, uint32 assetSize); + +}; + +} // End of namespace M4 + +#endif diff --git a/engines/m4/burger_data.h b/engines/m4/burger_data.h new file mode 100644 index 0000000000..000d0a9654 --- /dev/null +++ b/engines/m4/burger_data.h @@ -0,0 +1,85 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef M4_BURGER_DATA_H +#define M4_BURGER_DATA_H + +#include "m4/graphics.h" +#include "m4/actor.h" + +namespace M4 { + +InventoryObject burger_inventory [] = { + // name scene icon + //-------------------- ----- ----- + { "empty jug", 303, 14 }, + { "distilled juice", 999, 15 }, + { "broken puz dispenser", 999, 16 }, + { "puz dispenser", 999, 17 }, + { "broken mouse trap", 999, 18 }, + { "mouse trap", 999, 19 }, + { "kindling", 999, 20 }, + { "burning kindling", 999, 21 }, + { "lights", 508, 22 }, + { "lights on", 508, 23 }, + { "bottle", 999, 24 }, + { "carrot juice", 999, 25 }, + { "soapy water", 999, 26 }, + { "iron filings", 999, 27 }, + { "waxed hair", 999, 28 }, + { "fish", 999, 29 }, + { "hook", 999, 30 }, + { "keys", 999, 31 }, + { "records", 999, 32 }, + { "collar", 999, 33 }, + { "amp", 999, 34 }, + { "rubber gloves", 999, 35 }, + { "sock", 504, 36 }, + { "jaws of life", 999, 37 }, + { "deed", 999, 38 }, + { "burger morsel", 999, 39 }, + { "whistle", 999, 40 }, + { "coin", 999, 41 }, + { "matches", 999, 42 }, + { "phone cord", 999, 43 }, + { "kibble", 602, 44 }, // picked up from tray + { "pantyhose", 999, 45 }, + { "fan belt", 999, 46 }, + { "spring", 999, 47 }, + { "mirror", 999, 48 }, + { "grate", 999, 49 }, + { "ray gun", 604, 50 }, // given to Wilbur when he enters 604 + { "grasshoppers", 999, 51 }, + { "rolling pin", 999, 52 }, + { "rubber duck", 999, 53 }, + { "ladder", 999, 54 }, + { "money", 999, 55 }, + { "crow bar", 999, 56 }, + { "Wilbur", 999, 57 } +}; + +} // End of namespace M4 + +#endif diff --git a/engines/m4/compression.cpp b/engines/m4/compression.cpp new file mode 100644 index 0000000000..53c801e0b7 --- /dev/null +++ b/engines/m4/compression.cpp @@ -0,0 +1,189 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "m4/compression.h" +#include "m4/m4.h" + +namespace M4 { + +const char *madsPackString = "MADSPACK"; + +bool MadsPack::isCompressed(Common::SeekableReadStream *stream) { + // Check whether the passed stream is packed + + char tempBuffer[8]; + stream->seek(0); + if (stream->read(tempBuffer, 8) == 8) { + if (!strncmp(tempBuffer, madsPackString, 8)) + return true; + } + + return false; +} + +MadsPack::MadsPack(Common::SeekableReadStream *stream) { + initialise(stream); +} + +MadsPack::MadsPack(const char *resourceName, M4Engine* vm) { + Common::SeekableReadStream *stream = vm->_resourceManager->get(resourceName); + initialise(stream); + vm->_resourceManager->toss(resourceName); +} + +void MadsPack::initialise(Common::SeekableReadStream *stream) { + if (!MadsPack::isCompressed(stream)) + error("Attempted to decompress a resource that was not MadsPacked"); + + stream->seek(14); + _count = stream->readUint16LE(); + _items = new MadsPackEntry[_count]; + + byte *headerData = new byte[0xA0]; + byte *header = headerData; + stream->read(headerData, 0xA0); + + for (int i = 0; i < _count; ++i, header += 10) { + // Get header data + _items[i].hash = READ_LE_UINT16(header); + _items[i].size = READ_LE_UINT32(header + 2); + _items[i].compressedSize = READ_LE_UINT32(header + 6); + + _items[i].data = new byte[_items[i].size]; + if (_items[i].size == _items[i].compressedSize) { + // Entry isn't compressed + stream->read(_items[i].data, _items[i].size); + } else { + // Decompress the entry + byte *compressedData = new byte[_items[i].compressedSize]; + stream->read(compressedData, _items[i].compressedSize); + + FabDecompressor fab; + fab.decompress(compressedData, _items[i].compressedSize, _items[i].data, _items[i].size); + delete[] compressedData; + } + } + + delete[] headerData; + _dataOffset = stream->pos(); +} + +MadsPack::~MadsPack() { + for (int i = 0; i < _count; ++i) + delete[] _items[i].data; + delete[] _items; +} + +//-------------------------------------------------------------------------- + +const char *FabInputExceededError = "FabDecompressor - Passed end of input buffer during decompression"; +const char *FabOutputExceededError = "FabDecompressor - Decompressed data exceeded specified size"; + +void FabDecompressor::decompress(const byte *srcData, int srcSize, byte *destData, int destSize) { + byte copyLen, copyOfsShift, copyOfsMask, copyLenMask; + unsigned long copyOfs; + byte *destP; + + // Validate that the data starts with the FAB header + if (strncmp((const char *)srcData, "FAB", 3) != 0) + error("FabDecompressor - Invalid compressed data"); + + int shiftVal = srcData[3]; + if ((shiftVal < 10) || (shiftVal > 13)) + error("FabDecompressor - Invalid shift start"); + + copyOfsShift = 16 - shiftVal; + copyOfsMask = 0xFF << (shiftVal - 8); + copyLenMask = (1 << copyOfsShift) - 1; + copyOfs = 0xFFFF0000; + destP = destData; + + // Initialise data fields + _srcData = srcData; + _srcP = _srcData + 6; + _srcSize = srcSize; + _bitsLeft = 16; + _bitBuffer = READ_LE_UINT16(srcData + 4); + + for (;;) { + if (getBit() == 0) { + if (getBit() == 0) { + copyLen = ((getBit() << 1) | getBit()) + 2; + copyOfs = *_srcP++ | 0xFFFFFF00; + } else { + copyOfs = (((_srcP[1] >> copyOfsShift) | copyOfsMask) << 8) | _srcP[0]; + copyLen = _srcP[1] & copyLenMask; + _srcP += 2; + if (copyLen == 0) { + copyLen = *_srcP++; + if (copyLen == 0) + break; + else if (copyLen == 1) + continue; + else + copyLen++; + } else { + copyLen += 2; + } + copyOfs |= 0xFFFF0000; + } + while (copyLen-- > 0) { + if (destP - destData == destSize) + error(FabOutputExceededError); + + *destP = destP[(signed int)copyOfs]; + destP++; + } + } else { + if (_srcP - srcData == srcSize) + error(FabInputExceededError); + if (destP - destData == destSize) + error(FabOutputExceededError); + + *destP++ = *_srcP++; + } + } + + if (destP - destData != destSize) + error("FabDecompressor - Decompressed data does not match header decompressed size"); +} + +int FabDecompressor::getBit() { + _bitsLeft--; + if (_bitsLeft == 0) { + if (_srcP - _srcData == _srcSize) + error(FabInputExceededError); + + _bitBuffer = (READ_LE_UINT16(_srcP) << 1) | (_bitBuffer & 1); + _srcP += 2; + _bitsLeft = 16; + } + + int bit = _bitBuffer & 1; + _bitBuffer >>= 1; + return bit; +} + +} // End of namespace M4 diff --git a/engines/m4/compression.h b/engines/m4/compression.h new file mode 100644 index 0000000000..11e419951e --- /dev/null +++ b/engines/m4/compression.h @@ -0,0 +1,82 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef M4_COMPRESSION_H +#define M4_COMPRESSION_H + +#include "common/scummsys.h" +#include "common/stream.h" +#include "common/endian.h" + +#include "m4/m4.h" + +namespace M4 { + +struct MadsPackEntry { +public: + uint16 hash; + uint32 size; + uint32 compressedSize; + byte *data; +}; + +class MadsPack { +private: + MadsPackEntry *_items; + int _count; + int _dataOffset; + + void initialise(Common::SeekableReadStream *stream); +public: + static bool isCompressed(Common::SeekableReadStream *stream); + MadsPack(Common::SeekableReadStream *stream); + MadsPack(const char *resourceName, M4Engine* _vm); + ~MadsPack(); + + int getCount() const { return _count; } + MadsPackEntry &getItem(int index) const { return _items[index]; } + MadsPackEntry &operator[](int index) const { return _items[index]; } + Common::MemoryReadStream *getItemStream(int index) { + return new Common::MemoryReadStream(_items[index].data, _items[index].size, false); + } + int getDataOffset() const { return _dataOffset; } +}; + +class FabDecompressor { +private: + int _bitsLeft; + uint32 _bitBuffer; + const byte *_srcData, *_srcP; + int _srcSize; + + int getBit(); +public: + void decompress(const byte *srcData, int srcSize, byte *destData, int destSize); +}; + +} // End of namespace M4 + + +#endif diff --git a/engines/m4/console.cpp b/engines/m4/console.cpp new file mode 100644 index 0000000000..ccf6d98f33 --- /dev/null +++ b/engines/m4/console.cpp @@ -0,0 +1,287 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "m4/m4.h" +#include "m4/console.h" +#include "m4/scene.h" + +namespace M4 { + +Console::Console(M4Engine *vm) : GUI::Debugger() { + _vm = vm; + + DCmd_Register("scene", WRAP_METHOD(Console, cmdLoadScene)); + DCmd_Register("start", WRAP_METHOD(Console, cmdStartingScene)); + DCmd_Register("scene_info", WRAP_METHOD(Console, cmdSceneInfo)); + DCmd_Register("show_hotspots", WRAP_METHOD(Console, cmdShowHotSpots)); + DCmd_Register("list_hotspots", WRAP_METHOD(Console, cmdListHotSpots)); + DCmd_Register("play_sound", WRAP_METHOD(Console, cmdPlaySound)); + DCmd_Register("play_dsr_sound", WRAP_METHOD(Console, cmdPlayDSRSound)); + DCmd_Register("show_resources", WRAP_METHOD(Console, cmdShowResources)); + DCmd_Register("show_codes", WRAP_METHOD(Console, cmdShowCodes)); + DCmd_Register("dump_file", WRAP_METHOD(Console, cmdDumpFile)); + DCmd_Register("sprite", WRAP_METHOD(Console, cmdShowSprite)); + DCmd_Register("start_conv", WRAP_METHOD(Console, cmdStartConversation)); + DCmd_Register("textview", WRAP_METHOD(Console, cmdShowTextview)); + DCmd_Register("animview", WRAP_METHOD(Console, cmdShowAnimview)); + DCmd_Register("anim", WRAP_METHOD(Console, cmdPlayAnimation)); +} + +Console::~Console() { +} + +bool Console::cmdLoadScene(int argc, const char **argv) { + if (argc != 2) { + DebugPrintf("Usage: %s <scene number>\n", argv[0]); + return true; + } else { + if (_vm->isM4()) + _vm->_kernel->newRoom = atoi(argv[1]); + else + _vm->_scene->loadScene(atoi(argv[1])); + return false; + } +} + +bool Console::cmdStartingScene(int argc, const char **argv) { + if (_vm->getGameType() != GType_Riddle) { + if (_vm->isM4()) + _vm->_kernel->newRoom = FIRST_SCENE; + else + _vm->_scene->loadScene(FIRST_SCENE); + return false; + } else { + DebugPrintf("%s: Riddle of Master Lu is not supported", argv[0]); + return true; + } +} + +bool Console::cmdShowHotSpots(int argc, const char **argv) { + _vm->_scene->showHotSpots(); + return false; +} + +bool Console::cmdSceneInfo(int argc, const char **argv) { + DebugPrintf("Current scene is: %i\n", _vm->_scene->getCurrentScene()); + if (_vm->isM4()) { + DebugPrintf("Scene resources:\n"); + DebugPrintf("artBase: %s\n", _vm->_scene->getSceneResources().artBase); + DebugPrintf("pictureBase: %s\n", _vm->_scene->getSceneResources().pictureBase); + DebugPrintf("hotspotCount: %i\n", _vm->_scene->getSceneResources().hotspotCount); + DebugPrintf("parallaxCount: %i\n", _vm->_scene->getSceneResources().parallaxCount); + DebugPrintf("propsCount: %i\n", _vm->_scene->getSceneResources().propsCount); + DebugPrintf("frontY: %i\n", _vm->_scene->getSceneResources().frontY); + DebugPrintf("backY: %i\n", _vm->_scene->getSceneResources().backY); + DebugPrintf("frontScale: %i\n", _vm->_scene->getSceneResources().frontScale); + DebugPrintf("backScale: %i\n", _vm->_scene->getSceneResources().backScale); + DebugPrintf("depthTable: "); + for (uint i = 0; i < 16; i++) + DebugPrintf("%i ", _vm->_scene->getSceneResources().depthTable[i]); + DebugPrintf("\n"); + DebugPrintf("railNodeCount: %i\n", _vm->_scene->getSceneResources().railNodeCount); + } + return true; +} + +bool Console::cmdListHotSpots(int argc, const char **argv) { + DebugPrintf("Scene hotspots\n"); + _vm->_scene->getSceneResources().hotspots->dump(); + if (_vm->isM4()) { + DebugPrintf("Scene parallax\n"); + _vm->_scene->getSceneResources().parallax->dump(); + DebugPrintf("Scene props\n"); + _vm->_scene->getSceneResources().props->dump(); + } + return true; +} + +bool Console::cmdPlaySound(int argc, const char **argv) { + if (argc != 2) { + DebugPrintf("Usage: %s <sound file>\n", argv[0]); + } else { + _vm->_sound->playSound(argv[1], 255, false); + } + return true; +} + +bool Console::cmdPlayDSRSound(int argc, const char **argv) { + if (argc != 2 && argc != 3) { + DebugPrintf("Usage: %s <sound index> <DSR file>\n", argv[0]); + DebugPrintf("The DSR file parameter is optional, and specifies which DSR to load\n", argv[0]); + } else { + if (argc == 3) + _vm->_sound->loadDSRFile(argv[2]); + _vm->_sound->playDSRSound(atoi(argv[1]), 255, false); + } + return true; +} + +bool Console::cmdShowResources(int argc, const char **argv) { + _vm->res()->dump(); + return true; +} + +bool Console::cmdShowCodes(int argc, const char **argv) { + if (_vm->getGameType() != GType_RexNebular) + _vm->_scene->showCodes(); + else + DebugPrintf("Pathfinding codes not done yet for Rex Nebular"); + return false; +} + +bool Console::cmdDumpFile(int argc, const char **argv) { + if (argc != 2 && argc != 3) { + DebugPrintf("Usage: %s <file> <uncompress>\n", argv[0]); + DebugPrintf("If uncompress is 1, the file is uncompressed (for MADS games)\n"); + } else { + if (argc == 2) { + _vm->dumpFile(strdup(argv[1])); + } else { + if (argc == 3 && atoi(argv[2]) == 1) + _vm->dumpFile(strdup(argv[1]), true); + else + _vm->dumpFile(strdup(argv[1])); + } + } + return true; +} + +bool Console::cmdShowSprite(int argc, const char **argv) { + View *view = _vm->_viewManager->getView(VIEWID_SCENE); + if (view == NULL) + DebugPrintf("The scene view isn't currently active\n"); + else if (argc < 2) + DebugPrintf("Usage: %s resource_name\n", argv[0]); + else { + char resourceName[20]; + strncpy(resourceName, argv[1], 15); + resourceName[15] = '\0'; + if (!strchr(resourceName, '.')) + strcat(resourceName, ".SS"); + + _vm->_viewManager->moveToFront(view); + Common::SeekableReadStream *data = _vm->res()->get(resourceName); + SpriteAsset *asset = new SpriteAsset(_vm, data, data->size(), resourceName); + _vm->res()->toss(resourceName); + + RGBList *palData = new RGBList(asset->getColorCount(), asset->getPalette(), true); + _vm->_palette->addRange(palData); + + // Get the scene background surface + M4Surface *bg = _vm->_scene->getBackgroundSurface(); + + // Write the sprite onto the screen + int x = 0, y = 0, yMax = 0; + for (int index = 0; index < asset->getCount(); index++) { + M4Sprite *spr = asset->getFrame(index); + spr->translate(palData); // sprite pixel translation + + if ((x + spr->width() >= bg->width()) && (yMax != 0)) { + x = 0; + y += yMax; + yMax = 0; + } + + if (y >= bg->height()) + break; + + // FIXME: We assume that the transparent color is the color of the top left pixel + byte *transparentColor = (byte *)spr->pixels; + + spr->copyTo(bg, x, y, (int)*transparentColor); + + x += spr->width(); + yMax = MAX(yMax, spr->height()); + } + + view->restore(0, 0, view->width(), view->height()); + return false; + } + + return true; +} + +bool Console::cmdStartConversation(int argc, const char **argv) { + if (argc != 2) { + DebugPrintf("Usage: %s <conversation file name>\n", argv[0]); + return true; + } else { + _vm->_converse->startConversation(argv[1]); + return false; + } +} + +bool Console::cmdShowTextview(int argc, const char **argv) { + if (argc != 2) { + DebugPrintf("Usage: %s <txr resource>\n", argv[0]); + return true; + } + + _vm->_viewManager->showTextView(argv[1], false); + return false; +} + +bool Console::cmdShowAnimview(int argc, const char **argv) { + if (argc != 2) { + DebugPrintf("Usage: %s <res resource>\n", argv[0]); + return true; + } + + char resName[80]; + strcpy(resName, "@"); + strcat(resName, *argv[1] == '@' ? argv[1] + 1 : argv[1]); + + _vm->_viewManager->showAnimView(resName, false); + return false; +} + +bool Console::cmdPlayAnimation(int argc, const char **argv) { + View *view = _vm->_viewManager->getView(VIEWID_SCENE); + if (view == NULL) { + DebugPrintf("The scene view isn't currently active\n"); + } else if (argc != 2 && argc != 3) { + DebugPrintf("Usage: %s <anim resource (*.aa)> <fullscreen>\n", argv[0]); + DebugPrintf("If fullscreen is 1, the screen palette is replaced with the palette of the animation\n"); + } else { + char resourceName[20]; + strncpy(resourceName, argv[1], 15); + resourceName[15] = '\0'; + if (!strchr(resourceName, '.')) + strcat(resourceName, ".AA"); + + _vm->_viewManager->moveToFront(view); + if (argc == 3 && atoi(argv[2]) == 1) + _vm->_animation->loadFullScreen(resourceName); + else + _vm->_animation->load(resourceName); + _vm->_animation->start(); + view->restore(0, 0, view->width(), view->height()); + return false; + } + + return true; +} + +} // End of namespace M4 diff --git a/engines/m4/console.h b/engines/m4/console.h new file mode 100644 index 0000000000..45c9d1abec --- /dev/null +++ b/engines/m4/console.h @@ -0,0 +1,64 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef M4_CONSOLE_H +#define M4_CONSOLE_H + +#include "gui/debugger.h" + +namespace M4 { + +class M4Engine; + +class Console : public GUI::Debugger { +public: + Console(M4Engine *vm); + virtual ~Console(void); + +private: + bool cmdLoadScene(int argc, const char **argv); + bool cmdStartingScene(int argc, const char **argv); + bool cmdSceneInfo(int argc, const char **argv); + bool cmdShowHotSpots(int argc, const char **argv); + bool cmdListHotSpots(int argc, const char **argv); + bool cmdPlaySound(int argc, const char **argv); + bool cmdPlayDSRSound(int argc, const char **argv); + bool cmdShowResources(int argc, const char **argv); + bool cmdShowCodes(int argc, const char **argv); + bool cmdDumpFile(int argc, const char **argv); + bool cmdShowSprite(int argc, const char **argv); + bool cmdStartConversation(int argc, const char **argv); + bool cmdShowTextview(int argc, const char **argv); + bool cmdShowAnimview(int argc, const char **argv); + bool cmdPlayAnimation(int argc, const char **argv); + +private: + M4Engine *_vm; +}; + +} // End of namespace M4 + + +#endif diff --git a/engines/m4/converse.cpp b/engines/m4/converse.cpp new file mode 100644 index 0000000000..68daa08942 --- /dev/null +++ b/engines/m4/converse.cpp @@ -0,0 +1,1211 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "common/array.h" +#include "common/hashmap.h" + +#include "m4/converse.h" +#include "m4/resource.h" +#include "m4/globals.h" +#include "m4/m4_views.h" +#include "m4/compression.h" + +namespace M4 { + +#define CONV_ENTRIES_X_OFFSET 20 +#define CONV_ENTRIES_Y_OFFSET 4 +#define CONV_ENTRIES_HEIGHT 20 +#define CONV_MAX_SHOWN_ENTRIES 5 + +#define CONVERSATION_ENTRY_HIGHLIGHTED 22 +#define CONVERSATION_ENTRY_NORMAL 3 + +// Conversation chunks +// Header +#define HEAD_CONV MKID_BE('CONV') // conversation +#define CHUNK_DECL MKID_BE('DECL') // declaration +#define CHUNK_NODE MKID_BE('NODE') // node +#define CHUNK_LNOD MKID_BE('LNOD') // linear node +#define CHUNK_ETRY MKID_BE('ETRY') // entry +#define CHUNK_TEXT MKID_BE('TEXT') // text +#define CHUNK_MESG MKID_BE('MESG') // message +// Conversation chunks - entry related (unconditional) +#define CHUNK_RPLY MKID_BE('RPLY') // reply +#define CHUNK_HIDE MKID_BE('HIDE') // hide entry +#define CHUNK_UHID MKID_BE('UHID') // unhide entry +#define CHUNK_DSTR MKID_BE('DSTR') // destroy entry +// Conversation chunks - entry related (conditional) +#define CHUNK_CRPL MKID_BE('CRPL') // reply +#define CHUNK_CHDE MKID_BE('CHDE') // hide entry +#define CHUNK_CUHD MKID_BE('CUHD') // unhide entry +#define CHUNK_CDST MKID_BE('DDTS') // destroy entry +// Conversation chunks - branching and logic (unconditional) +#define CHUNK_ASGN MKID_BE('ASGN') // assign +#define CHUNK_GOTO MKID_BE('GOTO') // goto chunk +#define CHUNK_EXIT MKID_BE('EXIT') // exit/return from goto +// Conversation chunks - branching and logic (conditional) +#define CHUNK_CASN MKID_BE('CASN') // assign +#define CHUNK_CCGO MKID_BE('CCGO') // goto chunk +#define CHUNK_CEGO MKID_BE('CEGO') // exit/return from goto +// Others +#define CHUNK_FALL MKID_BE('FALL') // fallthrough +#define CHUNK_WRPL MKID_BE('WRPL') // weighted reply chunk +#define CHUNK_WPRL MKID_BE('WPRL') // weighted preply chunk + + +ConversationView::ConversationView(M4Engine *vm): View(vm, Common::Rect(0, + vm->_screen->height() - INTERFACE_HEIGHT, vm->_screen->width(), vm->_screen->height())) { + + _screenType = VIEWID_CONVERSATION; + _screenFlags.layer = LAYER_INTERFACE; + _screenFlags.visible = false; + _screenFlags.get = SCREVENT_MOUSE; + _conversationState = kNoConversation; + _currentHandle = NULL; +} + +ConversationView::~ConversationView() { + _activeItems.clear(); +} + +void ConversationView::setNode(int32 nodeIndex) { + _highlightedIndex = -1; + _xEnd = CONV_ENTRIES_X_OFFSET; + _vm->_font->setFont(FONT_CONVERSATION); + + // TODO: Conversation styles and colors + _vm->_font->setColors(2, 1, 3); + + _currentNodeIndex = nodeIndex; + + _activeItems.clear(); + + if (nodeIndex != -1) { + ConvEntry *node = _vm->_converse->getNode(nodeIndex); + + for (uint i = 0; i < node->entries.size(); ++i) { + if (!node->entries[i]->visible) + continue; + + if ((int)_activeItems.size() > CONV_MAX_SHOWN_ENTRIES) { + warning("TODO: scrolling. Max shown entries are %i, skipping entry %i", + CONV_MAX_SHOWN_ENTRIES, i); + } + + // Add node to active items list + _activeItems.push_back(node->entries[i]); + + if (node->entries[i]->autoSelect || strlen(node->entries[i]->text) == 0) { + //printf("Auto selecting entry %i of node %i\n", i, nodeIndex); + selectEntry(i); + return; + } + + // Figure out the longest string to determine where option highlighting ends + int tempX = _vm->_font->getWidth(node->entries[i]->text, 0) + + CONV_ENTRIES_X_OFFSET + 10; + _xEnd = MAX(_xEnd, tempX); + } + + // Make sure that there aren't too many entries + //assert((int)_activeItems.size() < (height() - CONV_ENTRIES_Y_OFFSET) / CONV_ENTRIES_HEIGHT); + + // Fallthrough + if (node->fallthroughMinEntries >= 0 && node->fallthroughOffset >= 0) { + //printf("Current node falls through node at offset %i when entries are less or equal than %i\n", + // node->fallthroughOffset, node->fallthroughMinEntries); + if (_activeItems.size() <= (uint32)node->fallthroughMinEntries) { + const EntryInfo *entryInfo = _vm->_converse->getEntryInfo(node->fallthroughOffset); + //printf("Entries are less than or equal to %i, falling through to node at offset %i, index %i\n", + // node->fallthroughMinEntries, node->fallthroughOffset, entryInfo->nodeIndex); + setNode(entryInfo->nodeIndex); + return; + } + } + + _entriesShown = true; + _conversationState = kConversationOptionsShown; + } +} + +void ConversationView::onRefresh(RectList *rects, M4Surface *destSurface) { + //if (!this->isVisible()) + // return; + empty(); + + if (_entriesShown) { + // Write out the conversation options + _vm->_font->setFont(FONT_CONVERSATION); + for (int i = 0; i < (int)_activeItems.size(); ++i) { + // TODO: scrolling + if (i > CONV_MAX_SHOWN_ENTRIES - 1) + break; + + _vm->_font->setColor((_highlightedIndex == i) ? CONVERSATION_ENTRY_HIGHLIGHTED : + CONVERSATION_ENTRY_NORMAL); + + _vm->_font->writeString(this, _activeItems[i]->text, CONV_ENTRIES_X_OFFSET, + CONV_ENTRIES_Y_OFFSET + CONV_ENTRIES_HEIGHT * i, 0, 0); + } + } + View::onRefresh(rects, destSurface); +} + +bool ConversationView::onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents) { + //if (!this->isVisible()) + // return false; + if (!_entriesShown) + return false; + if (eventType == KEVENT_KEY) + return false; + + int localY = y - _coords.top; + int selectedIndex = _highlightedIndex; + + switch (eventType) { + case MEVENT_MOVE: + if ((x < CONV_ENTRIES_X_OFFSET) || (x >= _xEnd) || (localY < CONV_ENTRIES_Y_OFFSET)) + _highlightedIndex = -1; + else { + int index = (localY - CONV_ENTRIES_Y_OFFSET) / CONV_ENTRIES_HEIGHT; + _highlightedIndex = (index >= (int)_activeItems.size()) ? -1 : index; + } + break; + + case MEVENT_LEFT_RELEASE: + if (_highlightedIndex != -1) { + selectEntry(selectedIndex); + } + break; + + default: + break; + } + + return true; +} + +void ConversationView::selectEntry(int entryIndex) { + char buffer[20]; + sprintf(buffer, "%s.raw", _activeItems[entryIndex]->voiceFile); + + _entriesShown = false; + _conversationState = kEntryIsActive; + _vm->_player->setCommandsAllowed(false); + // Necessary, as entries can be selected programmatically + _highlightedIndex = entryIndex; + + // Play the selected entry's voice + if (strlen(_activeItems[entryIndex]->voiceFile) > 0) { + _currentHandle = _vm->_sound->getHandle(); + _vm->_sound->playVoice(buffer, 255); + } else { + _currentHandle = NULL; + } + + // Hide selected entry, unless it has a persistent flag set + if (!(_activeItems[entryIndex]->flags & kEntryPersists)) { + //printf("Hiding selected entry\n"); + _vm->_converse->getNode(_currentNodeIndex)->entries[entryIndex]->visible = false; + } else { + //printf("Selected entry is persistent, not hiding it\n"); + } +} + +void ConversationView::updateState() { + switch(_conversationState) { + case kConversationOptionsShown: + return; + case kEntryIsActive: + case kReplyIsActive: + // FIXME: for now, we determine whether a conversation entry is + // finished by waiting for its associated speech file to finish playing + if (_currentHandle != NULL && _vm->_sound->isHandleActive(_currentHandle)) { + return; + } else { + playNextReply(); + } // end else + break; + case kNoConversation: + return; + default: + error("Unknown converstation state"); + break; + } +} + +void ConversationView::playNextReply() { + char buffer[20]; + + assert(_highlightedIndex >= 0); + + // Start playing the first reply + for (uint32 i = 0; i < _activeItems[_highlightedIndex]->entries.size(); i++) { + ConvEntry *currentEntry = _activeItems[_highlightedIndex]->entries[i]; + + if (currentEntry->isConditional) { + if (!_vm->_converse->evaluateCondition( + _vm->_converse->getValue(currentEntry->condition.offset), + currentEntry->condition.op, currentEntry->condition.val)) + continue; // don't play this reply + } + + if (currentEntry->entryType != kWeightedReply) { + sprintf(buffer, "%s.raw", currentEntry->voiceFile); + if (strlen(currentEntry->voiceFile) > 0) { + _currentHandle = _vm->_sound->getHandle(); + _vm->_sound->playVoice(buffer, 255); + // Remove reply from the list of replies + _activeItems[_highlightedIndex]->entries.remove_at(i); + _conversationState = kReplyIsActive; + return; + } else { + _currentHandle = NULL; + } + } else { + int selectedWeight = _vm->_random->getRandomNumber(currentEntry->totalWeight - 1) + 1; + //printf("Selected weight: %i\n", selectedWeight); + int previousWeight = 1; + int currentWeight = 0; + + for(uint32 j = 0; j < currentEntry->entries.size(); j++) { + currentWeight += currentEntry->entries[j]->weight; + if (selectedWeight >= previousWeight && selectedWeight <= currentWeight) { + sprintf(buffer, "%s.raw", currentEntry->entries[j]->voiceFile); + if (strlen(currentEntry->entries[j]->voiceFile) > 0) { + _currentHandle = _vm->_sound->getHandle(); + _vm->_sound->playVoice(buffer, 255); + // Remove reply from the list of replies + _activeItems[_highlightedIndex]->entries.remove_at(i); + _conversationState = kReplyIsActive; + return; + } else { + _currentHandle = NULL; + } + break; + } + previousWeight += currentWeight; + } // end for j + } // end if + } // end for i + + // If we reached here, there are no more replies, so perform the active entry's actions + + //printf("Current selection does %i actions\n", _activeItems[entryIndex]->actions.size()); + for (uint32 i = 0; i < _activeItems[_highlightedIndex]->actions.size(); i++) { + if (!_vm->_converse->performAction(_activeItems[_highlightedIndex]->actions[i])) + break; + } // end for + + // Refresh the conversation node, in case it hasn't changed + setNode(_currentNodeIndex); + + _entriesShown = true; + _vm->_player->setCommandsAllowed(true); + + // Check if the conversation has been ended + if (_currentNodeIndex == -1) { + _conversationState = kNoConversation; + } +} + +//-------------------------------------------------------------------------- + +void Converse::startConversation(const char *convName, bool showConverseBox, ConverseStyle style) { + if (_vm->isM4()) + loadConversation(convName); + else + loadConversationMads(convName); + + if (!_vm->isM4()) showConverseBox = false; // TODO: remove + + _playerCommandsAllowed = _vm->_player->commandsAllowed; + if (_vm->isM4()) // TODO: remove (interface not implemented yet in MADS games) + _interfaceWasVisible = _vm->_interfaceView->isVisible(); + _vm->_player->setCommandsAllowed(false); + _style = style; + + if (showConverseBox) { + _vm->_conversationView->show(); + _vm->_mouse->lockCursor(CURSOR_ARROW); + + if (_interfaceWasVisible) + _vm->_interfaceView->hide(); + + _vm->_conversationView->setNode(0); + _vm->_conversationView->show(); + } +} + +void Converse::endConversation() { + _vm->_conversationView->setNode(-1); + _vm->_conversationView->hide(); + // TODO: do a more proper cleanup here + _convNodes.clear(); + _variables.clear(); + _offsetMap.clear(); + _vm->_player->setCommandsAllowed(_playerCommandsAllowed); + if (_interfaceWasVisible) + _vm->_interfaceView->show(); +} + +void Converse::loadConversation(const char *convName) { + char name[40]; + char buffer[256]; + sprintf(name, "%s.chk", convName); + Common::SeekableReadStream *convS = _vm->res()->get(name); + uint32 header = convS->readUint32LE(); + uint32 size; + uint32 chunk; + uint32 data; + uint32 i; + ConvEntry* curEntry = NULL; + ConvEntry* replyEntry = NULL; + int32 currentWeightedEntry = -1; + EntryAction* curAction = NULL; + uint32 curNode = 0; + uint32 chunkPos = 0; + uint32 val; + int32 autoSelectIndex = -1; + int returnAddress = -1; + + bool debugFlag = false; // set to true for debug messages + + // Conversation *.chk files contain a 'CONV' header in LE format + if (header != HEAD_CONV) { + warning("Unexpected conversation file external header"); + return; + } + size = convS->readUint32LE(); // is this used at all? + if (debugFlag) printf("Conv chunk size (external header): %i\n", size); + + // Conversation name, which is the conversation file's name + // without the extension + convS->read(buffer, 8); + if (debugFlag) printf("Conversation name: %s\n", buffer); + + while(!convS->eos()) { + chunkPos = convS->pos(); + if (debugFlag) printf("***** Pos: %i -> ", chunkPos); + chunk = convS->readUint32LE(); // read chunk + switch(chunk) { + case CHUNK_DECL: // Declare + if (debugFlag) printf("DECL chunk\n"); + data = convS->readUint32LE(); + if (debugFlag) printf("Tag: %i\n", data); + if (data > 0) + warning("Tag > 0 in DECL chunk, value is: %i", data); // TODO + val = convS->readUint32LE(); + if (debugFlag) printf("Value: %i\n", val); + data = convS->readUint32LE(); + if (debugFlag) printf("Flags: %i\n", data); + if (data > 0) + warning("Flags != 0 in DECL chunk, value is %i", data); // TODO + setValue(chunkPos, val); + break; + // ---------------------------------------------------------------------------- + case CHUNK_NODE: // Node + case CHUNK_LNOD: // Linear node + // Create new node + curEntry = new ConvEntry(); + curEntry->offset = chunkPos; + curEntry->entryType = (chunk == CHUNK_NODE) ? kNode : kLinearNode; + curEntry->fallthroughMinEntries = -1; + curEntry->fallthroughOffset = -1; + if (chunk == CHUNK_NODE) { + if (debugFlag) printf("NODE chunk\n"); + } else { + if (debugFlag) printf("LNOD chunk\n"); + } + curNode = convS->readUint32LE(); + if (debugFlag) printf("Node number: %i\n", curNode); + data = convS->readUint32LE(); + if (debugFlag) printf("Tag: %i\n", data); + if (chunk == CHUNK_LNOD) { + autoSelectIndex = convS->readUint32LE(); + if (debugFlag) printf("Autoselect entry number: %i\n", autoSelectIndex); + } + size = convS->readUint32LE(); + if (debugFlag) printf("Number of entries: %i\n", size); + for (i = 0; i < size; i++) { + data = convS->readUint32LE(); + if (debugFlag) printf("Entry %i: %i\n", i + 1, data); + } + _convNodes.push_back(curEntry); + setEntryInfo(curEntry->offset, curEntry->entryType, curNode, -1); + break; + case CHUNK_ETRY: // Entry + // Create new entry + curEntry = new ConvEntry(); + curEntry->offset = chunkPos; + curEntry->entryType = kEntry; + strcpy(curEntry->voiceFile, ""); + strcpy(curEntry->text, ""); + if (debugFlag) printf("ETRY chunk\n"); + data = convS->readUint32LE(); + if (debugFlag) printf("Unknown (attributes perhaps?): %i\n", data); + data = convS->readUint32LE(); + if (debugFlag) printf("Entry flags: "); + if (debugFlag) if (data & kEntryInitial) printf ("Initial "); + if (debugFlag) if (data & kEntryPersists) printf ("Persists "); + if (debugFlag) printf("\n"); + curEntry->flags = data; + curEntry->visible = (curEntry->flags & kEntryInitial) ? true : false; + if (autoSelectIndex >= 0) { + if (_convNodes[curNode]->entries.size() == (uint32)autoSelectIndex) { + curEntry->autoSelect = true; + autoSelectIndex = -1; + } else { + curEntry->autoSelect = false; + } + } else { + curEntry->autoSelect = false; + } + _convNodes[curNode]->entries.push_back(curEntry); + setEntryInfo(curEntry->offset, curEntry->entryType, + curNode, _convNodes[curNode]->entries.size() - 1); + replyEntry = NULL; + break; + case CHUNK_WPRL: // Weighted preply + // WPRL chunks are random entries that the character would say, not an NPC + // They don't seem to be used in Orion Burger + warning("WPRL chunk - treating as WRPL chunk"); + case CHUNK_WRPL: // Weighted reply + case CHUNK_CRPL: // Conditional reply + case CHUNK_RPLY: // Reply + { + ConvEntry* weightedEntry = NULL; + // Create new reply + replyEntry = new ConvEntry(); + replyEntry->offset = chunkPos; + strcpy(replyEntry->voiceFile, ""); + + // Conditional part + if (chunk == CHUNK_CRPL) { + replyEntry->isConditional = true; + replyEntry->condition.offset = convS->readUint32LE(); + replyEntry->condition.op = convS->readUint32LE(); + replyEntry->condition.val = convS->readUint32LE(); + } else { + replyEntry->isConditional = false; + } + + if (chunk == CHUNK_WPRL || chunk == CHUNK_WRPL) { + replyEntry->entryType = kWeightedReply; + replyEntry->totalWeight = 0; + if (debugFlag) printf("WRPL chunk\n"); + size = convS->readUint32LE(); + if (debugFlag) printf("Weighted reply %i - %i entries:\n", i, size); + for (i = 0; i < size; i++) { + weightedEntry = new ConvEntry(); + weightedEntry->offset = chunkPos; + strcpy(weightedEntry->voiceFile, ""); + weightedEntry->entryType = kWeightedReply; + data = convS->readUint32LE(); + if (debugFlag) printf("- Weight: %i ", data); + weightedEntry->weight = data; + replyEntry->totalWeight += weightedEntry->weight; + data = convS->readUint32LE(); + if (debugFlag) printf("offset: %i\n", data); + replyEntry->entries.push_back(weightedEntry); + } + currentWeightedEntry = 0; + } else { + replyEntry->entryType = kReply; + if (debugFlag) printf("RPLY chunk\n"); + data = convS->readUint32LE(); + if (debugFlag) printf("Reply data offset: %i\n", data); + } + + curEntry->entries.push_back(replyEntry); + setEntryInfo(replyEntry->offset, replyEntry->entryType, + curNode, _convNodes[curNode]->entries.size() - 1); + // Seek to chunk data (i.e. TEXT/MESG tag, which is usually right + // after this chunk but it can be further on in conditional reply chunks + assert(data >= convS->pos()); + // If the entry's data is not right after the entry, remember the position + // to return to after the data is read + if (chunk == CHUNK_CRPL && data != convS->pos()) + returnAddress = convS->pos(); + convS->seek(data, SEEK_SET); + } + break; + // ---------------------------------------------------------------------------- + case CHUNK_TEXT: // Text (contains text and voice) + case CHUNK_MESG: // Message (contains voice only) + { + ConvEntry* parentEntry = NULL; + if (chunk == CHUNK_TEXT) { + if (debugFlag) printf("TEXT chunk\n"); + } else { + if (debugFlag) printf("MESG chunk\n"); + } + + if (replyEntry == NULL) { + parentEntry = curEntry; + } else if (replyEntry != NULL && replyEntry->entryType != kWeightedReply) { + parentEntry = replyEntry; + } else if (replyEntry != NULL && replyEntry->entryType == kWeightedReply) { + parentEntry = replyEntry->entries[currentWeightedEntry]; + currentWeightedEntry++; + } + + size = convS->readUint32LE(); + if (debugFlag) printf("Entry data size: %i\n", size); + convS->read(buffer, 8); + size -= 8; // subtract maximum length of voice file name + // If the file name is 8 characters, it will not be 0-terminated, so use strncpy + strncpy(parentEntry->voiceFile, buffer, 8); + parentEntry->voiceFile[8] = '\0'; + if (debugFlag) printf("Voice file: %s\n", parentEntry->voiceFile); + + if (chunk == CHUNK_TEXT) { + convS->read(buffer, size); + if (debugFlag) printf("Text: %s\n", buffer); + sprintf(parentEntry->text, "%s", buffer); + } else { + while (size > 0) { + data = convS->readUint32LE(); + if (debugFlag) printf("Unknown: %i\n", data); // TODO + size -= 4; + } + } + // Now that the data chunk has been read, if we skipped a reply node, + // jump back to it + if (returnAddress != -1) { + convS->seek(returnAddress, SEEK_SET); + returnAddress = -1; + } + } + break; + // ---------------------------------------------------------------------------- + // Entry action chunks + case CHUNK_CASN: // Conditional assign + case CHUNK_ASGN: // Assign + curAction = new EntryAction(); + if (debugFlag) printf("ASGN chunk\n"); + curAction->actionType = kAssignValue; + + // Conditional part + if (chunk == CHUNK_CASN) { + curAction->isConditional = true; + curAction->condition.offset = convS->readUint32LE(); + curAction->condition.op = convS->readUint32LE(); + curAction->condition.val = convS->readUint32LE(); + } else { + curAction->isConditional = false; + } + + curAction->targetOffset = convS->readUint32LE(); + assert(convS->readUint32LE() == kOpAssign); + curAction->value = convS->readUint32LE(); + break; + case CHUNK_CCGO: // Conditional go to entry + case CHUNK_CHDE: // Conditional hide entry + case CHUNK_CUHD: // Conditional unhide entry + case CHUNK_CDST: // Conditional destroy entry + case CHUNK_CEGO: // Conditional exit conversation / go to + + case CHUNK_GOTO: // Go to entry + case CHUNK_HIDE: // Hide entry + case CHUNK_UHID: // Unhide entry + case CHUNK_DSTR: // Destroy entry + case CHUNK_EXIT: // Exit conversation + curAction = new EntryAction(); + + // Conditional part + if (chunk == CHUNK_CCGO || chunk == CHUNK_CHDE || chunk == CHUNK_CUHD || + chunk == CHUNK_CDST || chunk == CHUNK_CEGO) { + curAction->isConditional = true; + curAction->condition.offset = convS->readUint32LE(); + curAction->condition.op = convS->readUint32LE(); + curAction->condition.val = convS->readUint32LE(); + } else { + curAction->isConditional = false; + } + + if (chunk == CHUNK_GOTO || chunk == CHUNK_CCGO) { + curAction->actionType = kGotoEntry; + if (debugFlag) printf("GOTO chunk\n"); + } else if (chunk == CHUNK_HIDE || chunk == CHUNK_CHDE) { + curAction->actionType = kHideEntry; + if (debugFlag) printf("HIDE chunk\n"); + } else if (chunk == CHUNK_UHID || chunk == CHUNK_CUHD) { + curAction->actionType = kUnhideEntry; + if (debugFlag) printf("UHID chunk\n"); + } else if (chunk == CHUNK_DSTR || chunk == CHUNK_CDST) { + curAction->actionType = kDestroyEntry; + if (debugFlag) printf("DSTR chunk\n"); + } else if (chunk == CHUNK_EXIT || chunk == CHUNK_CEGO) { + curAction->actionType = kExitConv; + if (debugFlag) printf("EXIT chunk\n"); + } + data = convS->readUint32LE(); + if (debugFlag) printf("Offset: %i\n", data); + curAction->targetOffset = data; + curEntry->actions.push_back(curAction); + break; + case CHUNK_FALL: // Fallthrough + if (debugFlag) printf("FALL chunk\n"); + size = convS->readUint32LE(); + if (debugFlag) printf("Minimum nodes: %i\n", size); + _convNodes[curNode]->fallthroughMinEntries = size; + data = convS->readUint32LE(); + if (debugFlag) printf("Offset: %i\n", data); + _convNodes[curNode]->fallthroughOffset = data; + break; + // ---------------------------------------------------------------------------- + default: + // Should never happen + error("Unknown conversation chunk"); + break; + } + } + + _vm->res()->toss(name); +} + +void Converse::loadConversationMads(const char *convName) { + char name[40]; + char buffer[256]; + char *buf; + Common::SeekableReadStream *convS; + int curPos = 0; + int unk = 0; + uint32 stringIndex = 0; + uint32 stringCount = 0; + int offset = 0; + int flags = 0; + int count = 0; + uint32 i, j; + ConvEntry* curEntry = NULL; + MessageEntry *curMessage; + Common::Array<char *> messageList; + // TODO + + // CND file + sprintf(name, "%s.cnd", convName); + MadsPack convDataD(name, _vm); + + // ------------------------------------------------------------ + // Chunk 0 + convS = convDataD.getItemStream(0); + printf("Chunk 0\n"); + printf("Conv stream size: %i\n", convS->size()); + + while(!convS->eos()) { + printf("%i ", convS->readByte()); + } + printf("\n"); + + // ------------------------------------------------------------ + // Chunk 1 + convS = convDataD.getItemStream(1); + printf("Chunk 1\n"); + printf("Conv stream size: %i\n", convS->size()); + + while(!convS->eos()) { + printf("%i ", convS->readByte()); + } + printf("\n"); + + // ------------------------------------------------------------ + // Chunk 2 + convS = convDataD.getItemStream(2); + printf("Chunk 2\n"); + printf("Conv stream size: %i\n", convS->size()); + + while(!convS->eos()) { + printf("%i ", convS->readByte()); + } + printf("\n"); + + // ------------------------------------------------------------ + // CNV file + sprintf(name, "%s.cnv", convName); + MadsPack convData(name, _vm); + // *.cnv files have 7 chunks + // Here is the chunk output of conv001.cnv (from the dump_file command) + /* + Dumping conv001.cnv, size: 3431 + Dumping compressed chunk 1 of 7, size is 150 + Dumping compressed chunk 2 of 7, size is 130 + Dumping compressed chunk 3 of 7, size is 224 + Dumping compressed chunk 4 of 7, size is 92 + Dumping compressed chunk 5 of 7, size is 168 + Dumping compressed chunk 6 of 7, size is 4064 + Dumping compressed chunk 7 of 7, size is 2334 + */ + + // ------------------------------------------------------------ + // TODO: finish this + // Chunk 0 + convS = convData.getItemStream(0); + printf("Chunk 0\n"); + printf("Conv stream size: %i\n", convS->size()); + printf("%i ", convS->readUint16LE()); + printf("%i ", convS->readUint16LE()); + printf("%i ", convS->readUint16LE()); + printf("%i ", convS->readUint16LE()); + printf("%i ", convS->readUint16LE()); + printf("%i ", convS->readUint16LE()); + printf("\n"); + count = convS->readUint16LE(); // conversation face count (usually 2) + printf("Conversation faces: %i\n", count); + for (i = 0; i < 5; i++) { + convS->read(buffer, 16); + printf("Face %i: %s ", i + 1, buffer); + } + printf("\n"); + + // 5 face slots + // 1 = face slot has a face (with the filename specified above) + // 0 = face slot doesn't contain face data + for (i = 0; i < 5; i++) { + printf("%i ", convS->readUint16LE()); + } + printf("\n"); + + convS->read(buffer, 14); // speech file + printf("Speech file: %s\n", buffer); + + while(!convS->eos()) { + printf("%i ", convS->readByte()); + } + printf("\n"); + + delete convS; + + // ------------------------------------------------------------ + // Chunk 1: Conversation nodes + convS = convData.getItemStream(1); + printf("Chunk 1: conversation nodes\n"); + printf("Conv stream size: %i\n", convS->size()); + + while(!convS->eos()) { + curEntry = new ConvEntry(); + curEntry->id = convS->readUint16LE(); + curEntry->entryCount = convS->readUint16LE(); + curEntry->flags = convS->readUint16LE(); + if (curEntry->entryCount == 1 && curEntry->flags != 65535) { + warning("Entry count is 1 and flags is not 65535 (it's %i)", flags); + } else if (curEntry->entryCount != 1 && flags != 0) { + warning("Entry count > 1 and flags is not 0 (it's %i)", flags); + } + unk = convS->readUint16LE(); + assert (unk == 65535); + unk = convS->readUint16LE(); + assert (unk == 65535); + _convNodes.push_back(curEntry); + printf("Node %i, ID %i, entries %i\n", _convNodes.size(), curEntry->id, curEntry->entryCount); + // flags = 0: node has more than 1 entry + // flags = 65535: node has 1 entry + } + printf("Conversation has %i nodes\n", _convNodes.size()); + printf("\n"); + + delete convS; + + // ------------------------------------------------------------ + // Chunk 4 contains the conversation string offsets of chunk 5 + // (unused, as it's unneeded - we find the offsets ourselves) + + // ------------------------------------------------------------ + // Chunk 5 contains the conversation strings + convS = convData.getItemStream(5); + //printf("Chunk 5: conversation strings\n"); + //printf("Conv stream size: %i\n", convS->size()); + + *buffer = 0; + + while(!convS->eos()) { + //if (curPos == 0) + // printf("%i: Offset %i: ", _convStrings.size(), convS->pos()); + buffer[curPos++] = convS->readByte(); + if (buffer[curPos - 1] == '~') { // filter out special characters + curPos--; + continue; + } + if (buffer[curPos - 1] == '\0') { + // end of string + //printf("%s\n", buffer); + buf = new char[strlen(buffer)]; + sprintf(buf, "%s", buffer); + _convStrings.push_back(buf); + curPos = 0; + *buffer = 0; + } + } + + delete convS; + + // ------------------------------------------------------------ + // Chunk 2: entry data + convS = convData.getItemStream(2); + //printf("Chunk 2 - entry data\n"); + //printf("Conv stream size: %i\n", convS->size()); + + for (i = 0; i < _convNodes.size(); i++) { + for (j = 0; j < _convNodes[i]->entryCount; j++) { + curEntry = new ConvEntry(); + stringIndex = convS->readUint16LE(); + if (stringIndex != 65535) + sprintf(curEntry->text, "%s", _convStrings[stringIndex]); + else + *curEntry->text = 0; + curEntry->id = convS->readUint16LE(); + curEntry->offset = convS->readUint16LE(); + curEntry->size = convS->readUint16LE(); + + _convNodes[i]->entries.push_back(curEntry); + //printf("Node %i, entry %i, id %i, offset %i, size %i, text: %s\n", + // i, j, curEntry->id, curEntry->offset, curEntry->size, curEntry->text); + } + } + + delete convS; + + // ------------------------------------------------------------ + // Chunk 3: message (MESG) chunks, created from the strings of chunk 5 + convS = convData.getItemStream(3); + //printf("Chunk 3 - MESG chunk data\n"); + //printf("Conv stream size: %i\n", convS->size()); + + while(!convS->eos()) { + curMessage = new MessageEntry(); + stringIndex = convS->readUint16LE(); + stringCount = convS->readUint16LE(); + *buffer = 0; + //printf("Message: %i\n", _madsMessageList.size()); + for (i = stringIndex; i < stringIndex + stringCount; i++) { + //printf("%i: %s\n", i, _convStrings[i]); + curMessage->messageStrings.push_back(_convStrings[i]); + } + _madsMessageList.push_back(curMessage); + //printf("----------\n"); + } + //printf("\n"); + + delete convS; + + // ------------------------------------------------------------ + // TODO: finish this + // Chunk 6: conversation script + convS = convData.getItemStream(6); + printf("Chunk 6\n"); + printf("Conv stream size: %i\n", convS->size()); + /*while(!convS->eos()) { + printf("%i ", convS->readByte()); + printf("%i ", convS->readByte()); + printf("%i ", convS->readByte()); + printf("%i ", convS->readByte()); + printf("%i ", convS->readByte()); + printf("%i ", convS->readByte()); + printf("%i ", convS->readByte()); + printf("%i ", convS->readByte()); + printf("%i ", convS->readByte()); + printf("%i ", convS->readByte()); + printf("\n"); + } + return;*/ + + for (i = 0; i < _convNodes.size(); i++) { + for (j = 0; j < _convNodes[i]->entryCount; j++) { + printf("*** Node %i entry %i data size %i\n", i, j, _convNodes[i]->entries[j]->size); + printf("Entry ID %i, text %s\n", _convNodes[i]->entries[j]->id, _convNodes[i]->entries[j]->text); + Common::SubReadStream *entryStream = new Common::SubReadStream(convS, _convNodes[i]->entries[j]->size); + readConvEntryActions(entryStream, _convNodes[i]->entries[j]); + delete entryStream; + printf("--------------------\n"); + } + } + + delete convS; +} + +void Converse::readConvEntryActions(Common::SubReadStream *convS, ConvEntry *curEntry) { + uint8 chunk; + uint8 type; // 255: normal, 11: conditional + uint8 hasText1, hasText2; + int target; + int count = 0; + int var, val; + int messageIndex = 0; + int unk = 0; + + while(!convS->eos()) { + chunk = convS->readByte(); + type = convS->readByte(); + + switch (chunk) { + case 1: + printf("TODO: chunk type %i\n", chunk); + break; + case 2: + printf("HIDE\n"); + convS->readByte(); + count = convS->readByte(); + printf("%i entries: ", count); + for (int i = 0; i < count; i++) + printf("%i %d", i, convS->readUint16LE()); + printf("\n"); + break; + case 3: + printf("UNHIDE\n"); + convS->readByte(); + count = convS->readByte(); + printf("%i entries: ", count); + for (int i = 0; i < count; i++) + printf("%i %d", i, convS->readUint16LE()); + printf("\n"); + break; + case 4: // MESSAGE + printf("MESSAGE\n"); + + if (type == 255) { + //printf("unconditional\n"); + } else if (type == 11) { + //printf("conditional\n"); + } else { + printf("unknown type: %i\n", type); + } + + // Conditional part + if (type == 11) { + unk = convS->readUint16LE(); // 1 + if (unk != 1) + printf("Message: unk != 1 (it's %i)\n", unk); + + var = convS->readUint16LE(); + val = convS->readUint16LE(); + printf("Var %i == %i\n", var, val); + } + unk = convS->readUint16LE(); // 256 + if (unk != 256) + printf("Message: unk != 256 (it's %i)\n", unk); + + // it seems that the first text entry is set when the message + // chunk is supposed to be shown unconditionally, whereas the second text + // entry is set when the message is supposed to be shown conditionally + hasText1 = convS->readByte(); + hasText2 = convS->readByte(); + + if (hasText1 == 1) { + messageIndex = convS->readUint16LE(); + printf("Message 1 index: %i, text:\n", messageIndex); + for (uint32 i = 0; i < _madsMessageList[messageIndex]->messageStrings.size(); i++) { + printf("%s\n", _madsMessageList[messageIndex]->messageStrings[i]); + } + } + + if (hasText2 == 1) { + messageIndex = convS->readUint16LE(); + if (hasText1 == 0) { + printf("Message 2 index: %i, text:\n", messageIndex); + for (uint32 i = 0; i < _madsMessageList[messageIndex]->messageStrings.size(); i++) { + printf("%s\n", _madsMessageList[messageIndex]->messageStrings[i]); + } + } + } + + break; + case 5: // AUTO + printf("AUTO\n"); + for (int k = 0; k < 4; k++) + convS->readByte(); + messageIndex = convS->readUint16LE(); + printf("Message index: %i, text:\n", messageIndex); + for (uint32 i = 0; i < _madsMessageList[messageIndex]->messageStrings.size(); i++) { + printf("%s\n", _madsMessageList[messageIndex]->messageStrings[i]); + } + + convS->readUint16LE(); + break; + case 6: + printf("TODO: chunk type %i\n", chunk); + break; + case 7: // GOTO + unk = convS->readUint32LE(); // 0 + if (unk != 0 && unk != 1) + printf("Goto: unk != 0 or 1 (it's %i)\n", unk); + + target = convS->readUint16LE(); + convS->readUint16LE(); // 255 + + if (unk != 0) + printf("Goto: unk != 0 (it's %i)\n", unk); + + if (target != 65535) + printf("GOTO node %i\n", target); + else + printf("GOTO exit\n"); + break; + case 8: + printf("TODO: chunk type %i\n", chunk); + break; + case 9: // ASSIGN + //printf("ASSIGN\n"); + unk = convS->readUint32LE(); // 0 + + if (unk != 0) + printf("Assign: unk != 0 (it's %i)\n", unk); + + val = convS->readUint16LE(); + var = convS->readUint16LE(); + //printf("Var %i = %i\n", var, val); + break; + default: + printf ("Unknown chunk type! (%i)\n", chunk); + break; + } + } + printf("\n"); +} + +void Converse::setEntryInfo(int32 offset, EntryType type, int32 nodeIndex, int32 entryIndex) { + char hashOffset[10]; + sprintf(hashOffset, "%i", offset); + EntryInfo info; + info.targetType = type; + info.nodeIndex = nodeIndex; + info.entryIndex = entryIndex; + _offsetMap[hashOffset] = info; + //printf("Set entry info: offset %i, type %i, node %i, entry %i\n", offset, type, nodeIndex, entryIndex); +} + +const EntryInfo* Converse::getEntryInfo(int32 offset) { + char hashOffset[10]; + sprintf(hashOffset, "%i", offset); + OffsetHashMap::const_iterator entry = _offsetMap.find(hashOffset); + if (entry != _offsetMap.end()) + return &(entry->_value); + else + error("Undeclared entry offset: %i", offset); +} + +void Converse::setValue(int32 offset, int32 value) { + char hashOffset[10]; + sprintf(hashOffset, "%i", offset); + _variables[hashOffset] = value; +} + +const int32 Converse::getValue(int32 offset) { + char hashOffset[10]; + sprintf(hashOffset, "%i", offset); + ConvVarHashMap::const_iterator entry = _variables.find(hashOffset); + if (entry != _variables.end()) + return entry->_value; + else + error("Undeclared variable offset: %i", offset); +} + +bool Converse::evaluateCondition(int32 leftVal, int32 op, int32 rightVal) { + switch (op) { + case kOpPercent: + return (leftVal % rightVal == 0); + case kOpGreaterOrEqual: + return leftVal >= rightVal; + case kOpLessOrEqual: + return leftVal <= rightVal; + case kOpGreaterThan: + return leftVal > rightVal; + case kOpLessThan: + return leftVal < rightVal; + case kOpNotEqual: + case kOpCondNotEqual: + return leftVal != rightVal; + case kOpAssign: + return leftVal == rightVal; + case kOpAnd: + return leftVal && rightVal; + case kOpOr: + return leftVal || rightVal; + default: + error("Unknown conditional operator: %i", op); + } +} + +bool Converse::performAction(EntryAction *action) { + if (action->isConditional) { + if (!evaluateCondition(getValue(action->condition.offset), + action->condition.op, action->condition.val)) + return true; // don't perform this action + } + + if (action->actionType == kAssignValue) { + //printf("Assigning variable at offset %i to value %i\n", + // action->targetOffset, action->value); + setValue(action->targetOffset, action->value); + return true; // nothing else to do in an assignment action + } + + const EntryInfo *entryInfo = getEntryInfo(action->targetOffset); + ConvEntry *targetEntry; + + if (entryInfo->nodeIndex >= 0 && entryInfo->entryIndex >= 0) + targetEntry = getNode(entryInfo->nodeIndex)->entries[entryInfo->entryIndex]; + else if (entryInfo->nodeIndex >= 0) + targetEntry = getNode(entryInfo->nodeIndex); + else + error("Target node id is negative"); + + switch (action->actionType) { + case kGotoEntry: + //printf("Goto entry at offset %i. Associated node is %i, entry %i\n", + // action->targetOffset, entryInfo->nodeIndex, entryInfo->entryIndex); + _vm->_conversationView->setNode(entryInfo->nodeIndex); + if (entryInfo->entryIndex >= 0) + _vm->_conversationView->selectEntry(entryInfo->entryIndex); + return false; + case kHideEntry: + //printf("Hide entry at offset %i. Associated node is %i, entry %i\n", + // targetEntry->offset, entryInfo->nodeIndex, entryInfo->entryIndex); + targetEntry->visible = false; + return true; + case kUnhideEntry: + //printf("Show entry at offset %i. Associated node is %i, entry %i\n", + // targetEntry->offset, entryInfo->nodeIndex, entryInfo->entryIndex); + targetEntry->visible = true; + return true; + case kDestroyEntry: + //printf("Destroy entry at offset %i. Associated node is %i, entry %i\n", + // targetEntry->offset, entryInfo->nodeIndex, entryInfo->entryIndex); + if (entryInfo->entryIndex >= 0) + getNode(entryInfo->nodeIndex)->entries.remove_at(entryInfo->entryIndex); + else + warning("Target entry is a node, not destroying it"); + targetEntry->visible = true; + return true; + case kExitConv: + //printf("Exit conversation\n"); + endConversation(); + return false; + default: + warning("Unknown entry action"); + return false; + } // end switch +} + +} // End of namespace M4 diff --git a/engines/m4/converse.h b/engines/m4/converse.h new file mode 100644 index 0000000000..bea90d9ddf --- /dev/null +++ b/engines/m4/converse.h @@ -0,0 +1,200 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef M4_CONVERSE_H +#define M4_CONVERSE_H + +#include "common/array.h" +#include "common/hashmap.h" + +#include "m4/globals.h" +#include "m4/m4.h" +#include "m4/viewmgr.h" + +namespace M4 { + +enum ConversationState { + kConversationOptionsShown = 0, + kEntryIsActive = 1, + kReplyIsActive = 2, + kNoConversation = 3 +}; + +enum EntryType { + kVariable = 0, + kNode = 1, + kLinearNode = 2, + kEntry = 3, + kReply = 4, + kWeightedReply = 5 +}; + +// Flags are: +// bit 0: if it's 1, the entry is "initial", i.e. not hidden when the dialog starts +// bit 1: if it's 1, the entry persists if selected +enum EntryFlags { + kEntryInitial = 1, // byte 0 + kEntryPersists = 2 // byte 1 +}; + +enum EntryActionType { + kUnknownAction = 0, + kGotoEntry = 1, + kHideEntry = 2, + kUnhideEntry = 3, + kDestroyEntry = 4, + kAssignValue = 5, + kExitConv = 6 +}; + +enum LogicOp { + kOpPercent = 405, + kOpGreaterOrEqual = 421, + kOpLessOrEqual = 420, + kOpGreaterThan = 413, + kOpLessThan = 412, + kOpNotEqual = 422, + kOpCondNotEqual = 448, + kOpAssign = 407, + kOpAnd = 444, + kOpOr = 445 +}; + +struct Condition { + int32 offset; + int32 op; + int32 val; +}; + +struct EntryAction { + int32 targetOffset; // absolute offset (inside the *.chk file) of the action's target + int32 value; // the value set by assignment chunks + EntryActionType actionType; + EntryType targetType; + int32 nodeId; + int32 entryId; + bool isConditional; + Condition condition; +}; + +struct ConvEntry { + EntryType entryType; + int32 id; + int32 offset; // absolute offset inside the *.chk file, referenced by other chunks + int32 size; // entry action data size (for MADS games) + int32 flags; + int32 fallthroughMinEntries; + int32 fallthroughOffset; + int32 weight; // weight for weighted replies + int32 totalWeight; + uint16 entryCount; // entries inside this node (for MADS games) + char voiceFile[10]; + char text[512]; + bool autoSelect; + bool visible; + bool isConditional; + Condition condition; + Common::Array<EntryAction*>actions; + Common::Array<ConvEntry*>entries; +}; + +struct EntryInfo { + EntryType targetType; + int32 nodeIndex; + int32 entryIndex; +}; + +struct MessageEntry { + Common::Array<char*>messageStrings; +}; + +enum ConverseStyle {CONVSTYLE_EARTH, CONVSTYLE_SPACE}; + +typedef Common::HashMap<Common::String,EntryInfo,Common::IgnoreCase_Hash,Common::IgnoreCase_EqualTo> OffsetHashMap; +typedef Common::HashMap<Common::String,int32,Common::IgnoreCase_Hash,Common::IgnoreCase_EqualTo> ConvVarHashMap; + +class ConversationView: public View { +public: + ConversationView(M4Engine *vm); + ~ConversationView(); + void setNode(int32 nodeIndex); + + void onRefresh(RectList *rects, M4Surface *destSurface); + bool onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents); + int32 getCurrentNodeIndex() { return _currentNodeIndex; } + void selectEntry(int entryIndex); + +private: + void updateState(); + void playNextReply(); + + int32 _currentNodeIndex; + Common::Array<ConvEntry *> _activeItems; + int _highlightedIndex; + int _xEnd; + bool _entriesShown; + ConversationState _conversationState; + SndHandle *_currentHandle; +}; + +class Converse { + +public: + Converse(M4Engine *vm) : _vm(vm) {} + ~Converse() {} + + void startConversation(const char *convName, bool showConversebox = true, ConverseStyle style = CONVSTYLE_EARTH ); + void endConversation(); + const EntryInfo* getEntryInfo(int32 offset); + ConvEntry *getNode(int32 index) { return _convNodes[index]; } + + void setValue(int32 offset, int32 value); + const int32 getValue(int32 offset); + bool evaluateCondition(int32 leftVal, int32 op, int32 rightVal); + bool performAction(EntryAction *action); + /* + void resume() { play(); } + void play(); + */ +private: + M4Engine *_vm; + Common::Array<ConvEntry*>_convNodes; + Common::Array<MessageEntry*>_madsMessageList; + Common::Array<char *>_convStrings; + bool _playerCommandsAllowed; + bool _interfaceWasVisible; + ConverseStyle _style; + OffsetHashMap _offsetMap; + ConvVarHashMap _variables; + + void loadConversation(const char *convName); + void loadConversationMads(const char *convName); + void readConvEntryActions(Common::SubReadStream *convS, ConvEntry *curEntry); + void setEntryInfo(int32 offset, EntryType type, int32 nodeIndex, int32 entryIndex); +}; + +} // End of namespace M4 + +#endif diff --git a/engines/m4/detection.cpp b/engines/m4/detection.cpp new file mode 100644 index 0000000000..7aee6167b3 --- /dev/null +++ b/engines/m4/detection.cpp @@ -0,0 +1,345 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "base/plugins.h" + +#include "common/advancedDetector.h" + +#include "m4/m4.h" +#include "m4/resource.h" + +namespace M4 { + +struct M4GameDescription { + Common::ADGameDescription desc; + + int gameType; + uint32 features; +}; + +int M4Engine::getGameType() const { return _gameDescription->gameType; } +uint32 M4Engine::getFeatures() const { return _gameDescription->features; } +Common::Language M4Engine::getLanguage() const { return _gameDescription->desc.language; } +Common::Platform M4Engine::getPlatform() const { return _gameDescription->desc.platform; } + +} + +static const PlainGameDescriptor m4Games[] = { + {"m4", "MADS/M4 engine game"}, + {"riddle", "Riddle of Master Lu: Believe it or Not!"}, + {"burger", "Orion Burger"}, + {"rex", "Rex Nebular and the Cosmic Gender Bender"}, + {"dragon", "DragonSphere"}, + {"dragoncd", "DragonSphere CD"}, + {"phantom", "Return of the Phantom"}, + {"phantomcd", "Return of the Phantom CD"}, + {0, 0} +}; + +namespace M4 { + +const char *M4Engine::getGameFile(int fileType) { + for (int i = 0; _gameDescription->desc.filesDescriptions[i].fileName; i++) { + if (_gameDescription->desc.filesDescriptions[i].fileType == fileType) + return _gameDescription->desc.filesDescriptions[i].fileName; + } + return NULL; +} + +static const M4GameDescription gameDescriptions[] = { + { + { + "burger", + "", + { + { "burger.has", kFileTypeHash, "10c8064e9c771072122f50737ac97245", 730771}, + { NULL, 0, NULL, 0} + }, + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + GType_Burger, + kFeaturesCD + }, + { + { + "burger", + "", + { + { "burger.has", kFileTypeHash, "55be8693a4c36e7efcdca0f0c77758ae", 724191}, + { NULL, 0, NULL, 0} + }, + Common::DE_DEU, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + GType_Burger, + kFeaturesCD + }, + { + { + "riddle", + "", + { + { "ripley.has", kFileTypeHash, "056d517360c89eb4c297a319f21a7071", 700469}, + { NULL, 0, NULL, 0} + }, + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + GType_Riddle, + kFeaturesCD + }, + { + { + "riddle", + "", + { + { "ripley.has", kFileTypeHash, "d073582c9011d44dd0d7e2ede317a86d", 700469}, + { NULL, 0, NULL, 0} + }, + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + GType_Riddle, + kFeaturesCD + }, + { + { + "riddle", + "", + { + { "ripley.has", kFileTypeHash, "d9e9f8befec432a047b1047fb2bc7aea", 710997}, + { NULL, 0, NULL, 0} + }, + Common::DE_DEU, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + GType_Riddle, + kFeaturesCD + }, + { + { + "riddle", + "", + { + { "ripley.has", kFileTypeHash, "3d48c5700785d11e6a5bc832b95be918", 701973}, + { NULL, 0, NULL, 0} + }, + Common::FR_FRA, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + GType_Riddle, + kFeaturesCD + }, + { // Demo + { + "riddle", + "Demo", + { + { "ripley.has", kFileTypeHash, "3a90dd7052860b6e246ec7e0aaf202f6", 104527}, + { NULL, 0, NULL, 0} + }, + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_DEMO + }, + GType_Riddle, + kFeaturesDemo + }, + { + { + "rex", + "", + { + { "global.hag", kFileTypeHAG, "0530cbeee109fc79cc24421128dea1ce", 2083078}, + { NULL, 0, NULL, 0} + }, + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + GType_RexNebular, + kFeaturesNone + }, + { // Demo + { + "rex", + "Demo", + { + { "global.hag", kFileTypeHAG, "d5a481d14bc1bda66e46965a39badcc7", 220429}, + { NULL, 0, NULL, 0} + }, + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_DEMO + }, + GType_RexNebular, + kFeaturesDemo + }, + { + { + "dragon", + "", + { + { "global.hag", kFileTypeHAG, "c3a6877665e7f21bf3d2b1e667155562", 2320567}, + { NULL, 0, NULL, 0} + }, + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + GType_DragonSphere, + kFeaturesNone + }, + { // CD version + { + "dragoncd", + "", + { + { "global.hag", kFileTypeHAG, "c3a6877665e7f21bf3d2b1e667155562", 2320567}, + { "speech.hag", kFileTypeHAG, NULL, -1}, + { NULL, 0, NULL, 0} + }, + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + GType_DragonSphere, + kFeaturesCD + }, + { // Demo + { + "dragon", + "Demo", + { + { "global.hag", kFileTypeHAG, "541e12d8e9aad0c65d65f150de47582e", 212718}, + { NULL, 0, NULL, 0} + }, + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_DEMO + }, + GType_DragonSphere, + kFeaturesDemo + }, + { + { + "phantom", + "", + { + { "global.hag", kFileTypeHAG, "bdce9ca93a015f0883d1bc0fabd0cdfa", 812150}, + { NULL, 0, NULL, 0} + }, + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + GType_Phantom, + kFeaturesNone + }, + { // CD version + { + "phantomcd", + "", + { + { "global.hag", kFileTypeHAG, "8a51c984eb4c64e8b30a7e6670f6bddb", 101154000}, + { NULL, 0, NULL, 0} + }, + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_NO_FLAGS + }, + GType_Phantom, + kFeaturesCD + }, + { // CD version + { + "phantom", + "Demo", + { + { "global.hag", kFileTypeHAG, "e810adbc6fac77ac2fec841a9ec5e20e", 115266}, + { NULL, 0, NULL, 0} + }, + Common::EN_ANY, + Common::kPlatformPC, + Common::ADGF_DEMO + }, + GType_Phantom, + kFeaturesDemo + }, + { { NULL, NULL, { { NULL, 0, NULL, 0 } }, Common::UNK_LANG, Common::kPlatformUnknown, Common::ADGF_NO_FLAGS }, 0, 0 } +}; + +} + +static const Common::ADParams detectionParams = { + // Pointer to ADGameDescription or its superset structure + (const byte *)M4::gameDescriptions, + // Size of that superset structure + sizeof(M4::M4GameDescription), + // Number of bytes to compute MD5 sum for + 5000, + // List of all engine targets + m4Games, + // Structure for autoupgrading obsolete targets + 0, + // Name of single gameid (optional) + "m4", + // List of files for file-based fallback detection (optional) + 0, + // Flags + 0 +}; + +class M4MetaEngine : public Common::AdvancedMetaEngine { +public: + M4MetaEngine() : Common::AdvancedMetaEngine(detectionParams) {} + + virtual const char *getName() const { + return "MADS/M4 engine"; + } + + virtual const char *getCopyright() const { + return "Riddle of Master Lu & Orion Burger (C) Sanctuary Woods"; + } + + virtual bool createInstance(OSystem *syst, Engine **engine, const Common::ADGameDescription *desc) const; +}; + +bool M4MetaEngine::createInstance(OSystem *syst, Engine **engine, const Common::ADGameDescription *desc) const { + const M4::M4GameDescription *gd = (const M4::M4GameDescription *)desc; + if (gd) { + *engine = new M4::M4Engine(syst, gd); + } + return gd != 0; +} + +REGISTER_PLUGIN(M4, PLUGIN_TYPE_ENGINE, M4MetaEngine); diff --git a/engines/m4/events.cpp b/engines/m4/events.cpp new file mode 100644 index 0000000000..101a41015f --- /dev/null +++ b/engines/m4/events.cpp @@ -0,0 +1,351 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +// TODO: There is a 'please_hyperwalk' variable that gets accessed that is meant to be global, but +// at the moment it's implemented as a local variable + +#include "graphics/cursorman.h" + +#include "m4/events.h" +#include "m4/graphics.h" +#include "m4/scene.h" +#include "m4/viewmgr.h" +#include "m4/m4.h" + +namespace M4 { + +bool please_hyperwalk = false; + +/*--------------------------------------------------------------------------* + * Events * + * * + * Implements an interface to the event system * + *--------------------------------------------------------------------------*/ + +Events::Events(M4Engine *vm) : _vm(vm) { + _mouseState = MSTATE_NO_EVENT; + quitFlag = false; + _keyCode = 0; + _console = new Console(_vm); + _mouseButtons = 0; +} + +M4EventType Events::handleEvents() { + static int oldX = -1, oldY = -1; + static uint32 dclickTime = 0; + + // Handle event types + while (g_system->getEventManager()->pollEvent(_event)) { + switch (_event.type) { + case Common::EVENT_QUIT: + quitFlag = true; + break; + case Common::EVENT_KEYDOWN: + if (_event.kbd.flags == Common::KBD_CTRL) { + if (_event.kbd.keycode == Common::KEYCODE_d) + _console->attach(); + _console->onFrame(); + } + _keyCode = (int)_event.kbd.keycode; + + break; + case Common::EVENT_LBUTTONDOWN: + case Common::EVENT_LBUTTONUP: + case Common::EVENT_RBUTTONDOWN: + case Common::EVENT_RBUTTONUP: + case Common::EVENT_MBUTTONDOWN: + case Common::EVENT_MBUTTONUP: + case Common::EVENT_MOUSEMOVE: + case Common::EVENT_WHEELUP: + case Common::EVENT_WHEELDOWN: + _vm->_mouse->handleEvent(_event); + break; + default: + break; + } + } + + _mouseButtons = g_system->getEventManager()->getButtonState(); + + // State machine for moving between states + switch (_mouseState) { + case MSTATE_NO_EVENT: + if (_mouseButtons & LEFT_BUTTON_DOWN) { + if ((dclickTime != 0) && (g_system->getMillis() < dclickTime)) { + _mouseState = MSTATE_DOUBLECLICK_DOWN; + dclickTime = 0; + return MEVENT_DOUBLECLICK; + } + dclickTime = 0; + _mouseState = MSTATE_LEFT_CLICK_DOWN; + return MEVENT_LEFT_CLICK; + } + if (_mouseButtons & RIGHT_BUTTON_DOWN) { + _mouseState = MSTATE_RIGHT_CLICK_DOWN; + return MEVENT_RIGHT_CLICK; + } + if ((_event.mouse.x != oldX) || (_event.mouse.y != oldY)) { + oldX = _event.mouse.x; oldY = _event.mouse.y; + return MEVENT_MOVE; + } + return MEVENT_NO_EVENT; + + case MSTATE_LEFT_CLICK_DOWN: + if (!(_mouseButtons & LEFT_BUTTON_DOWN)) { + dclickTime = g_system->getMillis() + 1000 * 15 / 60; + _mouseState = MSTATE_NO_EVENT; + return MEVENT_LEFT_RELEASE; + } + if ((_event.mouse.x != oldX) || (_event.mouse.y != oldY)) { + oldX = _event.mouse.x; oldY = _event.mouse.y; + return MEVENT_LEFT_DRAG; + } + return MEVENT_LEFT_HOLD; + + case MSTATE_RIGHT_CLICK_DOWN: + if (!(_mouseButtons & RIGHT_BUTTON_DOWN)) { + _mouseState = MSTATE_NO_EVENT; + please_hyperwalk = true; + return MEVENT_RIGHT_RELEASE; + } + if ((_event.mouse.x != oldX) || (_event.mouse.y != oldY)) { + oldX = _event.mouse.x; oldY = _event.mouse.y; + return MEVENT_RIGHT_DRAG; + } + return MEVENT_RIGHT_HOLD; + + case MSTATE_DOUBLECLICK_DOWN: + if (!(_mouseButtons & LEFT_BUTTON_DOWN)) { + _mouseState = MSTATE_NO_EVENT; + return MEVENT_DOUBLECLICK_RELEASE; + } + if ((_event.mouse.x != oldX) || (_event.mouse.y != oldY)) { + oldX = _event.mouse.x; oldY = _event.mouse.y; + return MEVENT_DOUBLECLICK_DRAG; + } + return MEVENT_DOUBLECLICK_HOLD; + + default: + return MEVENT_NO_EVENT; + } +} + +bool Events::kbdCheck(uint32 &keyCode) { + if (_keyCode == 0) + return false; + + keyCode = _keyCode; + _keyCode = 0; + return true; +} + + +/*--------------------------------------------------------------------------* + * Mouse * + * * + * Implements an interface to the mouse * + *--------------------------------------------------------------------------*/ + +Mouse::Mouse(M4Engine *vm) : _vm(vm) { + _locked = false; + _cursorOn = false; + _cursor = NULL; + _cursorSprites = NULL; + resetMouse(); +} + +Mouse::~Mouse() { + if (_cursorSprites) + delete _cursorSprites; +} + +bool Mouse::init(const char *seriesName, RGB8 *palette) { + Common::SeekableReadStream *stream = _vm->res()->get(seriesName); + int colorCount = 0; + RGB8* cursorPalette; + + _cursorSprites = new SpriteAsset(_vm, stream, stream->size(), seriesName); + + // Remove cursor special pixels and set the mouse cursor hotspot in MADS games + if (!_vm->isM4()) { + byte *data = NULL; + for (int i = 0; i < _cursorSprites->getCount(); i++) { + bool hotSpotSet = false; + + for (int x = 0; x < _cursorSprites->getFrame(i)->width(); x++) { + for (int y = 0; y < _cursorSprites->getFrame(i)->height(); y++) { + data = _cursorSprites->getFrame(i)->getBasePtr(x, y); + if (*data == 1) { + // It seems that some cursors have more than one hotspot + // In such a case, the first hotspot seems to set the x and + // the second one the y hotspot offset + if (!hotSpotSet) { + _cursorSprites->getFrame(i)->xOffset = x; + _cursorSprites->getFrame(i)->yOffset = y; + hotSpotSet = true; + } else { + _cursorSprites->getFrame(i)->yOffset = y; + } + *data = 0; + } + } // for y + } // for x + } // for i + } + + colorCount = _cursorSprites->getColorCount(); + cursorPalette = _cursorSprites->getPalette(); + _vm->_palette->setPalette(cursorPalette, 0, colorCount); + + //printf("Cursor count: %d\n", _cursorSprites->getCount()); + + _vm->res()->toss(seriesName); + + _currentCursor = -1; + return true; +} + +bool Mouse::setCursorNum(int cursorIndex) { + if ((cursorIndex < 0) || (cursorIndex >= (int)_cursorSprites->getCount())) + return false; + + _lockedCursor = cursorIndex; + if (_locked) + // Cursor is locked, so don't go ahead with changing cursor + return true; + + _currentCursor = _lockedCursor; + _cursor = _cursorSprites->getFrame(cursorIndex); + + // Set the cursor to the sprite + CursorMan.replaceCursor((const byte *)_cursor->getData(), _cursor->w, _cursor->h, _cursor->xOffset, _cursor->yOffset, 0); + + return true; +} + +int Mouse::cursorCount() { + return _cursorSprites->getCount(); +} + +void Mouse::cursorOn() { + _cursorOn = true; + CursorMan.showMouse(!inHideArea()); +} + +void Mouse::cursorOff() { + _cursorOn = false; + CursorMan.showMouse(false); +} + +void Mouse::lockCursor(int cursorIndex) { + _locked = false; + setCursorNum(cursorIndex); + _locked = true; +} + +void Mouse::unlockCursor() { + _locked = false; + setCursorNum(_lockedCursor); +} + +const char *Mouse::getVerb() { + switch (_vm->_mouse->getCursorNum()) { + case CURSOR_LOOK: + return "LOOK AT"; + case CURSOR_TAKE: + return "TAKE"; + case CURSOR_USE: + return "GEAR"; + default: + return NULL; + } +} + +void Mouse::resetMouse() { + _hideRect.left = -1; + _hideRect.top = -1; + _hideRect.right = -1; + _hideRect.bottom = -1; + _showRect.left = -1; + _showRect.top = -1; + _showRect.right = -1; + _showRect.bottom = -1; +} + +void Mouse::setHideRect(Common::Rect &r) { + _hideRect = r; +} + +void Mouse::setShowRect(Common::Rect &r) { + _showRect = r; +} + +const Common::Rect *Mouse::getHideRect() { + return &_hideRect; +} + +const Common::Rect *Mouse::getShowRect() { + if ((_showRect.top == -1) || (_showRect.left == -1)) { + // Show rectangle uninitialised - set it to current screen dimensions + _showRect.top = 0; + _showRect.left = 0; + _showRect.right = _vm->_screen->width() - 1; + _showRect.bottom = _vm->_screen->height() -1; + } + + return &_showRect; +} + +void Mouse::handleEvent(Common::Event &event) { + _currentPos.x = event.mouse.x; + _currentPos.y = event.mouse.y; + + // If mouse is turned on, check to see if the position is in the hide rect, or outside the show rect. + // If so, handle toggling the visibility of the mouse + bool showFlag = !inHideArea(); + if (_cursorOn && (CursorMan.isVisible() != showFlag)) { + CursorMan.showMouse(showFlag); + } +} + +bool Mouse::inHideArea() { + // Returns true if the mouse is inside a specified hide rect, or if a show rect is specified and + // the mouse is currently outside it + if ((_currentPos.x >= _hideRect.left) && (_currentPos.x <= _hideRect.right) && + (_currentPos.y >= _hideRect.top) && (_currentPos.y <= _hideRect.bottom)) + // Inside a hide area + return true; + + + if ((_showRect.top == -1) && (_showRect.left == -1)) + // No show rect defined + return false; + + // Return true if the mouse is outside the show area + return (_currentPos.x < _showRect.left) || (_currentPos.x > _showRect.right) || + (_currentPos.y < _showRect.top) || (_currentPos.y > _showRect.bottom); +} + +} // End of namespace M4 diff --git a/engines/m4/events.h b/engines/m4/events.h new file mode 100644 index 0000000000..d261f29013 --- /dev/null +++ b/engines/m4/events.h @@ -0,0 +1,132 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef M4_EVENTS_H +#define M4_EVENTS_H + +#include "common/events.h" +#include "common/rect.h" + +#include "m4/globals.h" +#include "m4/assets.h" +#include "m4/sprite.h" +#include "m4/graphics.h" +#include "m4/console.h" + +namespace M4 { + +#define LEFT_BUTTON_DOWN 1 << 0 +#define RIGHT_BUTTON_DOWN 1 << 1 + +enum M4EventType { + MEVENT_NO_EVENT, MEVENT_MOVE, + MEVENT_LEFT_CLICK, MEVENT_LEFT_HOLD, MEVENT_LEFT_DRAG, MEVENT_LEFT_RELEASE, + MEVENT_RIGHT_CLICK, MEVENT_RIGHT_HOLD, MEVENT_RIGHT_DRAG, MEVENT_RIGHT_RELEASE, + MEVENT_BOTH_CLICK, MEVENT_BOTH_HOLD, MEVENT_BOTH_DRAG, MEVENT_BOTH_RELEASE, + MEVENT_DOUBLECLICK, MEVENT_DOUBLECLICK_HOLD, MEVENT_DOUBLECLICK_DRAG, MEVENT_DOUBLECLICK_RELEASE, + KEVENT_KEY +}; + +enum M4MouseState { + MSTATE_NO_EVENT, MSTATE_LEFT_CLICK_DOWN, MSTATE_RIGHT_CLICK_DOWN, MSTATE_BOTH_CLICK_DOWN, + MSTATE_DOUBLECLICK_DOWN +}; + +enum M4CommonCursors { + CURSOR_ARROW = 0, + CURSOR_HOURGLASS = 5, + CURSOR_LOOK = 6, + CURSOR_TAKE = 8, + CURSOR_USE = 9 +}; + +class M4Sprite; +class SpriteAsset; + +class Events { +private: + M4Engine *_vm; + Common::Event _event; + M4MouseState _mouseState; + int _keyCode; + int _mouseButtons; + Console *_console; +public: + bool quitFlag; + Events(M4Engine *vm); + + Common::Event &event() { return _event; } + Common::EventType type() { return _event.type; } + + // M4-centric methods + M4EventType handleEvents(); + bool kbdCheck(uint32 &keyCode); + int getMouseButtonsState() { return _mouseButtons; } + Console* getConsole() { return _console; } +}; + + +class Mouse { +private: + M4Engine *_vm; + int _currentCursor, _lockedCursor; + bool _locked; + bool _cursorOn; + M4Sprite *_cursor; + SpriteAsset *_cursorSprites; + Common::Rect _hideRect, _showRect; + Common::Point _currentPos; + + void handleEvent(Common::Event &event); + bool inHideArea(); + friend class Events; +public: + Mouse(M4Engine *vm); + ~Mouse(); + + bool init(const char *seriesName, RGB8 *palette); + bool setCursorNum(int cursorIndex); + int getCursorNum() { return _currentCursor; } + int cursorCount(); + Common::Point currentPos() const { return _currentPos; } + M4Sprite *cursor() { return _cursor; } + void cursorOn(); + void cursorOff(); + bool getCursorOn() { return _cursorOn; } + void lockCursor(int cursorIndex); + void unlockCursor(); + + const char *getVerb(); + + void resetMouse(); + void setHideRect(Common::Rect &r); + void setShowRect(Common::Rect &r); + const Common::Rect *getHideRect(); + const Common::Rect *getShowRect(); +}; + +} + +#endif diff --git a/engines/m4/font.cpp b/engines/m4/font.cpp new file mode 100644 index 0000000000..c9b9a44f23 --- /dev/null +++ b/engines/m4/font.cpp @@ -0,0 +1,267 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "m4/font.h" +#include "m4/m4.h" +#include "m4/compression.h" + +namespace M4 { + +Font::Font(M4Engine *vm) : _vm(vm) { + _sysFont = true; + _filename = NULL; + //TODO: System font + _fontColors[0] = _vm->_palette->BLACK; + _fontColors[1] = _vm->_palette->WHITE; + _fontColors[2] = _vm->_palette->BLACK; + _fontColors[3] = _vm->_palette->DARK_GRAY; +} + +void Font::setFont(const char *filename) { + if ((_filename != NULL) && (strcmp(filename, _filename) == 0)) + // Already using specified font, so don't bother reloading + return; + + _sysFont = false; + _filename = filename; + + if (_vm->isM4()) + setFontM4(filename); + else + setFontMads(filename); +} + +void Font::setFontM4(const char *filename) { + Common::SeekableReadStream *fontFile = _vm->res()->openFile(filename); + + if (fontFile->readUint32LE() != MKID_BE('FONT')) { + printf("Font::Font: FONT tag expected\n"); + return; + } + + _maxHeight = fontFile->readByte(); + _maxWidth = fontFile->readByte(); + uint32 fontSize = fontFile->readUint32LE(); + + //printf("Font::Font: _maxWidth = %d, _maxHeight = %d, fontSize = %d\n", _maxWidth, _maxHeight, fontSize); + + if (fontFile->readUint32LE() != MKID_BE('WIDT')) { + printf("Font::Font: WIDT tag expected\n"); + return; + } + + _charWidths = new uint8[256]; + fontFile->read(_charWidths, 256); + + if (fontFile->readUint32LE() != MKID_BE('OFFS')) { + printf("Font::Font: OFFS tag expected\n"); + return; + } + + _charOffs = new uint16[256]; + + for (int i = 0; i < 256; i++) + _charOffs[i] = fontFile->readUint16LE(); + + if (fontFile->readUint32LE() != MKID_BE('PIXS')) { + printf("Font::Font: PIXS tag expected\n"); + return; + } + + _charData = new uint8[fontSize]; + fontFile->read(_charData, fontSize); + + _vm->res()->toss(filename); +} + +void Font::setFontMads(const char *filename) { + MadsPack fontData(filename, _vm); + Common::SeekableReadStream *fontFile = fontData.getItemStream(0); + + _maxHeight = fontFile->readByte(); + _maxWidth = fontFile->readByte(); + + _charWidths = new uint8[128]; + // Char data is shifted by 1 + _charWidths[0] = 0; + fontFile->read(_charWidths + 1, 127); + fontFile->readByte(); // remainder + + _charOffs = new uint16[128]; + + uint32 startOffs = 2 + 128 + 256; + uint32 fontSize = fontFile->size() - startOffs; + + // Char data is shifted by 1 + _charOffs[0] = 0; + for (int i = 1; i < 128; i++) + _charOffs[i] = fontFile->readUint16LE() - startOffs; + fontFile->readUint16LE(); // remainder + + _charData = new uint8[fontSize]; + fontFile->read(_charData, fontSize); + + delete fontFile; +} + +Font::~Font() { + if (!_sysFont) { + delete[] _charWidths; + delete[] _charOffs; + delete[] _charData; + } +} + +void Font::setColor(uint8 color) { + if (_sysFont) + _fontColors[1] = color; + else + _fontColors[3] = color; +} + +void Font::setColors(uint8 alt1, uint8 alt2, uint8 foreground) { + if (_sysFont) + _fontColors[1] = foreground; + else { + _fontColors[1] = alt1; + _fontColors[2] = alt2; + _fontColors[3] = foreground; + } +} + +int32 Font::write(M4Surface *surface, const char *text, int x, int y, int width, int spaceWidth, uint8 colors[]) { + + /*TODO + if (custom_ascii_converter) { // if there is a function to convert the extended ASCII characters + custom_ascii_converter(out_string); // call it with the string + } + */ + + if (width > 0) + width = MIN(surface->width(), x + width); + else + width = surface->width(); + + x++; + y++; + + int skipY = 0; + if (y < 0) { + skipY = -y; + y = 0; + } + + int height = MAX(0, _maxHeight - skipY); + if (height == 0) + return x; + + int bottom = y + height - 1; + if (bottom > surface->height() - 1) { + height -= MIN(height, bottom - (surface->height() - 1)); + } + + if (height <= 0) + return x; + + uint8 *destPtr = (uint8*)surface->getBasePtr(x, y); + uint8 *oldDestPtr = destPtr; + + int xPos = x; + + while (*text) { + + char theChar = (*text++) & 0x7F; + int charWidth = _charWidths[theChar]; + + if (charWidth > 0) { + + if (xPos + charWidth >= width) + return xPos; + + uint8 *charData = &_charData[_charOffs[theChar]]; + int bpp = charWidth / 4 + 1; + + if (!_vm->isM4()) { + if (charWidth > 12) + bpp = 4; + else if (charWidth > 8) + bpp = 3; + else if (charWidth > 4) + bpp = 2; + else + bpp = 1; + } + + if (skipY != 0) + charData += bpp * skipY; + + for (int i = 0; i < height; i++) { + for (int j = 0; j < bpp; j++) { + if (*charData & 0xc0) + *destPtr = colors[(*charData & 0xc0) >> 6]; + destPtr++; + if (*charData & 0x30) + *destPtr = colors[(*charData & 0x30) >> 4]; + destPtr++; + if (*charData & 0x0C) + *destPtr = colors[(*charData & 0x0C) >> 2]; + destPtr++; + if (*charData & 0x03) + *destPtr = colors[*charData & 0x03]; + destPtr++; + charData++; + } + + destPtr += surface->width() - bpp * 4; + + } + + destPtr = oldDestPtr + charWidth + spaceWidth; + oldDestPtr = destPtr; + + } + + xPos += charWidth + spaceWidth; + + } + + surface->freeData(); + return xPos; + +} + +int32 Font::getWidth(char *text, int spaceWidth) { + /* + if (custom_ascii_converter) { // if there is a function to convert the extended ASCII characters + custom_ascii_converter(out_string); // call it with the string + } + */ + int width = 0; + while (*text) + width += _charWidths[*text++ & 0x7F] + spaceWidth; + return width; +} + +} // End of namespace M4 diff --git a/engines/m4/font.h b/engines/m4/font.h new file mode 100644 index 0000000000..75321d820c --- /dev/null +++ b/engines/m4/font.h @@ -0,0 +1,91 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef M4_FONT_H +#define M4_FONT_H + +#include "common/util.h" +#include "common/endian.h" + +#include "m4/graphics.h" + +/* + TODO: + - make a FontSystem class that creates/manages the fonts + (similar to FileSystem) +*/ + +namespace M4 { + +#define FONT_MENU "fontmenu.fnt" +#define FONT_INTERFACE "fontintr.fnt" +#define FONT_TINY "small.fnt" +#define FONT_SMALL "small.fnt" +#define FONT_MEDIUM "medium.fnt" +#define FONT_LINE "fontline.fnt" +#define FONT_CONVERSATION "fontconv.fnt" +#define FONT_4X6 "4x6pp.fnt" +#define FONT_5X6 "5x6pp.fnt" + +#define FONT_CONVERSATION_MADS "fontconv.ff" +#define FONT_INTERFACE_MADS "fontintr.ff" +#define FONT_MAIN_MADS "fontmain.ff" +#define FONT_MENU_MADS "fontmenu.ff" // Not in Rex (uses bitmap files for menu strings) +#define FONT_MISC_MADS "fontmisc.ff" +#define FONT_TELE_MADS "fonttele.ff" // Not in Phantom +#define FONT_PHAN_MADS "fontphan.ff" // Phantom only + +class Font { +public: + Font(M4Engine *vm); + ~Font(); + void setFont(const char *filename); + void setColor(uint8 color); + void setColors(uint8 alt1, uint8 alt2, uint8 foreground); + + int32 getWidth(char *text, int spaceWidth = -1); + int32 getHeight() const { return _maxHeight; } + int32 write(M4Surface *surface, const char *text, int x, int y, int width, int spaceWidth, uint8 colors[]); + int32 writeString(M4Surface *surface, const char *text, int x, int y, int width = 0, int spaceWidth = -1) { + return write(surface, text, x, y, width, spaceWidth, _fontColors); + } + +private: + void setFontM4(const char *filename); + void setFontMads(const char *filename); + + M4Engine *_vm; + uint8 _maxWidth, _maxHeight; + uint8 *_charWidths; + uint16 *_charOffs; + uint8 *_charData; + bool _sysFont; + const char *_filename; + uint8 _fontColors[4]; +}; + +} // End of namespace M4 + +#endif diff --git a/engines/m4/globals.cpp b/engines/m4/globals.cpp new file mode 100644 index 0000000000..12d9a24d37 --- /dev/null +++ b/engines/m4/globals.cpp @@ -0,0 +1,447 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "m4/m4.h" +#include "m4/globals.h" +#include "m4/graphics.h" +#include "m4/gui.h" +#include "m4/viewmgr.h" +#include "m4/script.h" +#include "m4/m4_views.h" +#include "m4/compression.h" + +namespace M4 { + +Kernel::Kernel(M4Engine *vm) : _vm(vm) { + daemonTriggerAvailable = true; + firstFadeColorIndex = 0; + paused = false; + betweenRooms = false; + currentSection = 0; + newSection = 0; + previousSection = 0; + currentRoom = 0; + newRoom = 0; + previousRoom = 0; + trigger = 0; + triggerMode = KT_DAEMON; + + _globalDaemonFn = NULL; + _globalParserFn = NULL; + + _sectionInitFn = NULL; + _sectionDaemonFn = NULL; + _sectionParserFn = NULL; + + _roomInitFn = NULL; + _roomDaemonFn = NULL; + _roomPreParserFn = NULL; + _roomParserFn = NULL; + +} + +int32 Kernel::createTrigger(int32 triggerNum) { + if (triggerNum < 0) + return triggerNum; + else + return triggerNum | (currentRoom << 16) | (triggerMode << 28); +} + +bool Kernel::sendTrigger(int32 triggerNum) { + return handleTrigger(createTrigger(triggerNum)); +} + +bool Kernel::handleTrigger(int32 triggerNum) { + + printf("betweenRooms = %d; triggerNum = %08X\n", betweenRooms, triggerNum); + + if (betweenRooms) + return true; + + if (triggerNum < 0) + return false; + + KernelTriggerType saveTriggerMode = triggerMode; + int32 saveTrigger = trigger; + bool result = false; + + int room = (triggerNum >> 16) & 0xFFF; + + printf("room = %d; currentRoom = %d\n", room, currentRoom); fflush(stdout); + + if (room != currentRoom) { + printf("Kernel::handleTrigger() Trigger from another room\n"); + return false; + } + + trigger = triggerNum & 0xFFFF; + KernelTriggerType mode = (KernelTriggerType)(triggerNum >> 28); + + switch (mode) { + + case KT_PREPARSE: + if (trigger < 32000) { + triggerMode = KT_PREPARSE; + roomPreParser(); + result = true; + } + break; + + case KT_PARSE: + if (trigger < 32000) { + triggerMode = KT_PARSE; + // TODO player.commandReady = TRUE; + roomParser(); + /* TODO + if (player.commandReady) + globalParser(); + */ + result = true; + } + break; + + case KT_DAEMON: + printf("KT_DAEMON\n"); + fflush(stdout); + triggerMode = KT_DAEMON; + daemonTriggerAvailable = false; + roomDaemon(); + if (daemonTriggerAvailable) { + daemonTriggerAvailable = false; + sectionDaemon(); + } + if (daemonTriggerAvailable) { + daemonTriggerAvailable = false; + globalDaemon(); + } + + break; + + default: + printf("Kernel::handleTrigger() Unknown trigger mode %d\n", mode); + + } + + triggerMode = saveTriggerMode; + trigger = saveTrigger; + + return result; +} + +void Kernel::loadGlobalScriptFunctions() { + _globalDaemonFn = _vm->_script->loadFunction("global_daemon"); + _globalParserFn = _vm->_script->loadFunction("global_parser"); +} + +void Kernel::loadSectionScriptFunctions() { + char tempFnName[128]; + snprintf(tempFnName, 128, "section_init_%d", currentSection); + _sectionInitFn = _vm->_script->loadFunction(tempFnName); + snprintf(tempFnName, 128, "section_daemon_%d", currentSection); + _sectionDaemonFn = _vm->_script->loadFunction(tempFnName); + snprintf(tempFnName, 128, "section_parser_%d", currentSection); + _sectionParserFn = _vm->_script->loadFunction(tempFnName); +} + +void Kernel::loadRoomScriptFunctions() { + char tempFnName[128]; + snprintf(tempFnName, 128, "room_init_%d", currentRoom); + _roomInitFn = _vm->_script->loadFunction(tempFnName); + snprintf(tempFnName, 128, "room_daemon_%d", currentRoom); + _roomDaemonFn = _vm->_script->loadFunction(tempFnName); + snprintf(tempFnName, 128, "room_pre_parser_%d", currentRoom); + _roomPreParserFn = _vm->_script->loadFunction(tempFnName); + snprintf(tempFnName, 128, "room_parser_%d", currentRoom); + _roomParserFn = _vm->_script->loadFunction(tempFnName); +} + +void Kernel::globalDaemon() { + if (_globalDaemonFn) + _vm->_script->runFunction(_globalDaemonFn); + else { + printf("Kernel::globalDaemon() _globalDaemonFn is NULL\n"); + } +} + +void Kernel::globalParser() { + if (_globalParserFn) + _vm->_script->runFunction(_globalParserFn); + else { + printf("Kernel::globalParser() _globalParserFn is NULL\n"); + } +} + +void Kernel::sectionInit() { + if (_sectionInitFn) + _vm->_script->runFunction(_sectionInitFn); + else { + printf("Kernel::sectionInit() _sectionInitFn is NULL\n"); + } +} + +void Kernel::sectionDaemon() { + if (_sectionDaemonFn) + _vm->_script->runFunction(_sectionDaemonFn); + else { + printf("Kernel::sectionDaemon() _sectionDaemonFn is NULL\n"); + } +} + +void Kernel::sectionParser() { + if (_sectionParserFn) + _vm->_script->runFunction(_sectionParserFn); + else { + printf("Kernel::sectionParser() _sectionParserFn is NULL\n"); + } +} + +void Kernel::roomInit() { + if (_roomInitFn) + _vm->_script->runFunction(_roomInitFn); + else { + printf("Kernel::roomInit() _roomInitFn is NULL\n"); + } +} + +void Kernel::roomDaemon() { + if (_roomDaemonFn) + _vm->_script->runFunction(_roomDaemonFn); + else { + printf("Kernel::roomDaemon() _roomDaemonFn is NULL\n"); + } +} + +void Kernel::roomPreParser() { + if (_roomPreParserFn) + _vm->_script->runFunction(_roomPreParserFn); + else { + printf("Kernel::roomPreParser() _roomPreParserFn is NULL\n"); + } +} + +void Kernel::roomParser() { + if (_roomParserFn) + _vm->_script->runFunction(_roomParserFn); + else { + printf("Kernel::roomParser() _roomParserFn is NULL\n"); + } +} + +void Kernel::pauseGame(bool value) { + paused = value; + + if (paused) pauseEngines(); + else unpauseEngines(); +} + +void Kernel::pauseEngines() { + // TODO: A proper implementation of game pausing. At the moment I'm using a hard-coded + // check in events.cpp on Kernel::paused to prevent any events going to the scene +} + +void Kernel::unpauseEngines() { + // TODO: A proper implementation of game unpausing +} + +//-------------------------------------------------------------------------- + +Globals::Globals(M4Engine *vm): _vm(vm) { +} + +Globals::~Globals() { + for(uint32 i = 0; i < _madsVocab.size(); i++) + free(_madsVocab[i]); + _madsVocab.clear(); + + for(uint32 i = 0; i < _madsQuotes.size(); i++) + free(_madsQuotes[i]); + _madsQuotes.clear(); + + _madsMessages.clear(); +} + +bool Globals::isInterfaceVisible() { + return _vm->_interfaceView->isVisible(); +} + +void Globals::loadMadsVocab() { + Common::SeekableReadStream *vocabS = _vm->res()->get("vocab.dat"); + int curPos = 0; + + char buffer[30]; + strcpy(buffer, ""); + + while(!vocabS->eos()) { + buffer[curPos++] = vocabS->readByte(); + if (buffer[curPos - 1] == '\0') { + // end of string, add it to the strings list + _madsVocab.push_back(strdup(buffer)); + curPos = 0; + strcpy(buffer, ""); + } + } + + _vm->res()->toss("vocab.dat"); +} + +void Globals::loadMadsQuotes() { + Common::SeekableReadStream *quoteS = _vm->res()->get("quotes.dat"); + int curPos = 0; + + char buffer[128]; + strcpy(buffer, ""); + + while(!quoteS->eos()) { + buffer[curPos++] = quoteS->readByte(); + if (buffer[curPos - 1] == '\0') { + // end of string, add it to the strings list + _madsQuotes.push_back(strdup(buffer)); + curPos = 0; + strcpy(buffer, ""); + } + } + + _vm->res()->toss("quotes.dat"); +} + +void Globals::loadMadsMessagesInfo() { + Common::SeekableReadStream *messageS = _vm->res()->get("messages.dat"); + + int16 count = messageS->readUint16LE(); + //printf("%i messages\n", count); + + for (int i = 0; i < count; i++) { + MessageItem *curMessage = new MessageItem(); + curMessage->id = messageS->readUint32LE(); + curMessage->offset = messageS->readUint32LE(); + curMessage->uncompSize = messageS->readUint16LE(); + + if (i > 0) + _madsMessages[i - 1]->compSize = curMessage->offset - _madsMessages[i - 1]->offset; + + if (i == count - 1) + curMessage->compSize = messageS->size() - curMessage->offset; + + //printf("id: %i, offset: %i, uncomp size: %i\n", curMessage->id, curMessage->offset, curMessage->uncompSize); + _madsMessages.push_back(curMessage); + } + + _vm->res()->toss("messages.dat"); +} + +char* Globals::loadMessage(uint32 index) { + if (index > _madsMessages.size() - 1) { + warning("Invalid message index: %i", index); + return NULL; + } + + FabDecompressor fab; + byte *compData = new byte[_madsMessages[index]->compSize]; + byte *buffer = new byte[_madsMessages[index]->uncompSize]; + + Common::SeekableReadStream *messageS = _vm->res()->get("messages.dat"); + messageS->seek(_madsMessages[index]->offset, SEEK_SET); + messageS->read(compData, _madsMessages[index]->compSize); + fab.decompress(compData, _madsMessages[index]->compSize, buffer, _madsMessages[index]->uncompSize); + + for (int i = 0; i < _madsMessages[index]->uncompSize - 1; i++) + if (buffer[i] == '\0') buffer[i] = '\n'; + + _vm->res()->toss("messages.dat"); + + return (char*)buffer; +} + +//-------------------------------------------------------------------------- + +Player::Player(M4Engine *vm) : _vm(vm) { + commandsAllowed = true; + needToWalk = false; + readyToWalk = false; + waitingForWalk = false; + commandReady = false; + strcpy(verb, ""); + strcpy(noun, ""); + strcpy(prep, ""); + strcpy(object, ""); +} + +void Player::setCommandsAllowed(bool value) { + setCommandsAllowedFlag = true; + commandsAllowed = value; + if (value) { + // Player commands are enabled again + _vm->_mouse->lockCursor(CURSOR_ARROW); + //_vm->_interfaceView->cancelSentence(); + } else { + // Player commands are disabled, so show hourglass cursor + _vm->_mouse->lockCursor(CURSOR_HOURGLASS); + } +} + +bool Player::said(const char *word1, const char *word2, const char *word3) { + const char *words[3]; + words[0] = word1; + words[1] = word2; + words[2] = word2; + for (int i = 0; i < 3; i++) { + if (words[i]) + if ((scumm_stricmp(noun, words[i])) && + (scumm_stricmp(object, words[i])) && + (scumm_stricmp(verb, words[i]))) + return false; + } + return true; +} + +bool Player::saidAny(const char *word1, const char *word2, const char *word3, + const char *word4, const char *word5, const char *word6, const char *word7, + const char *word8, const char *word9, const char *word10) { + const char *words[10]; + words[0] = word1; + words[1] = word2; + words[2] = word3; + words[3] = word4; + words[4] = word5; + words[5] = word6; + words[6] = word7; + words[7] = word8; + words[8] = word9; + words[9] = word10; + for (int i = 0; i < 10; i++) { + if (words[i]) { + if (!scumm_stricmp(noun, words[i])) + return true; + if (!scumm_stricmp(object, words[i])) + return true; + if (!scumm_stricmp(verb, words[i])) + return true; + } + } + return false; +} + + +} // End of namespace M4 diff --git a/engines/m4/globals.h b/engines/m4/globals.h new file mode 100644 index 0000000000..a0133db2d6 --- /dev/null +++ b/engines/m4/globals.h @@ -0,0 +1,221 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef M4_GLOBALS_H +#define M4_GLOBALS_H + +#include "common/array.h" +#include "common/rect.h" +#include "common/scummsys.h" + +namespace M4 { + +class M4Engine; +class ScriptInterpreter; +class ScriptFunction; + +// Globals +enum WoodScriptGlobals { + kGlobTime = 0, + kGlobTimeDelta = 1, + kGlobMinY = 2, + kGlobMaxY = 3, + kGlobMinScale = 4, + kGlobMaxScale = 5, + kGlobScaler = 6, + kGlobTemp1 = 7, + kGlobTemp2 = 8, + kGlobTemp3 = 9, + kGlobTemp4 = 10, + kGlobTemp5 = 11, + kGlobTemp6 = 12, + kGlobTemp7 = 13, + kGlobTemp8 = 14, + kGlobTemp9 = 15, + kGlobTemp10 = 16, + kGlobTemp11 = 17, + kGlobTemp12 = 18, + kGlobTemp13 = 19, + kGlobTemp14 = 20, + kGlobTemp15 = 21, + kGlobTemp16 = 22, + kGlobTemp17 = 23, + kGlobTemp18 = 24, + kGlobTemp19 = 25, + kGlobTemp20 = 26, + kGlobTemp21 = 27, + kGlobTemp22 = 28, + kGlobTemp23 = 29, + kGlobTemp24 = 30, + kGlobTemp25 = 31, + kGlobTemp26 = 32, + kGlobTemp27 = 33, + kGlobTemp28 = 34, + kGlobTemp29 = 35, + kGlobTemp30 = 36, + kGlobTemp31 = 37, + kGlobTemp32 = 38 +}; + +const uint32 SERIES_FORWARD = 0; +const uint32 SERIES_PINGPONG = 1; +const uint32 SERIES_BACKWARD = 2; +const uint32 SERIES_RANDOM = 4; +const uint32 SERIES_NO_TOSS = 8; +const uint32 SERIES_STICK = 16; +const uint32 SERIES_LOOP_TRIGGER = 32; +const uint32 SERIES_LOAD_PALETTE = 64; +const uint32 SERIES_HORZ_FLIP =128; + +enum KernelTriggerType { + KT_PARSE = 1, + KT_DAEMON, + KT_PREPARSE +}; + +class Kernel { +private: + M4Engine *_vm; + ScriptFunction *_globalDaemonFn, *_globalParserFn; + ScriptFunction *_sectionInitFn, *_sectionDaemonFn, *_sectionParserFn; + ScriptFunction *_roomInitFn, *_roomDaemonFn, *_roomPreParserFn, *_roomParserFn; + void pauseEngines(); + void unpauseEngines(); +public: + Kernel(M4Engine *vm); + + // TODO: Move to some palette/fading class + int fadeUpDuration, firstFadeColorIndex; + int minPalEntry, maxPalEntry; + + bool paused; + //machine* myWalker; + bool repeatedlyCallDeamon; + bool daemonTriggerAvailable; + bool betweenRooms; + int currentSection, newSection, previousSection; + int currentRoom, newRoom, previousRoom; + + int32 trigger; + KernelTriggerType triggerMode; + + int32 createTrigger(int32 triggerNum); + bool sendTrigger(int32 triggerNum); + bool handleTrigger(int32 triggerNum); + + void loadGlobalScriptFunctions(); + void loadSectionScriptFunctions(); + void loadRoomScriptFunctions(); + + void globalDaemon(); + void globalParser(); + + void sectionInit(); + void sectionDaemon(); + void sectionParser(); + + void roomInit(); + void roomDaemon(); + void roomPreParser(); + void roomParser(); + + void pauseGame(bool value); +}; + +#define TOTAL_NUM_VARIABLES 256 + +class Globals { +private: + struct MessageItem { + uint32 id; + uint32 offset; + uint16 uncompSize; + uint16 compSize; + }; + + M4Engine *_vm; + Common::Array<char* > _madsVocab; + Common::Array<char* > _madsQuotes; + Common::Array<MessageItem* > _madsMessages; +public: + Globals(M4Engine *vm); + ~Globals(); + bool isInterfaceVisible(); + + // M4 variables + bool invSuppressClickSound; + + void loadMadsVocab(); + uint32 getVocabSize() { return _madsVocab.size(); } + char* getVocab(uint32 index) { return _madsVocab[index]; } + + void loadMadsQuotes(); + uint32 getQuotesSize() { return _madsQuotes.size(); } + char* getQuote(uint32 index) { return _madsQuotes[index]; } + + void loadMadsMessagesInfo(); + uint32 getMessagesSize() { return _madsMessages.size(); } + char* loadMessage(uint32 index); +}; + +#define PLAYER_FIELD_LENGTH 40 + +class Player { +public: + Player(M4Engine *vm); + void setCommandsAllowed(bool value); + + // Variables + Common::Point position; // Player's current position + int facing; // Facing direction + + char verb[PLAYER_FIELD_LENGTH]; // Action strings + char noun[PLAYER_FIELD_LENGTH]; + char prep[PLAYER_FIELD_LENGTH]; + char object[PLAYER_FIELD_LENGTH]; + Common::String assetName, shadowName; + int walkerType, shadowType; + bool needToWalk, readyToWalk, waitingForWalk; + bool commandsAllowed; + bool commandReady; + bool visible; + bool beenInRoomBefore; + bool walkerInCurrentRoom; + int32 walkerTriggerNum; + int walkFacing; + bool setCommandsAllowedFlag; + + bool said(const char *word1, const char *word2 = NULL, const char *word3 = NULL); + bool saidAny(const char *word1, const char *word2, const char *word3, + const char *word4, const char *word5, const char *word6, const char *word7, + const char *word8, const char *word9, const char *word10); + +private: + M4Engine *_vm; +}; + +} // End of namespace M4 + +#endif diff --git a/engines/m4/graphics.cpp b/engines/m4/graphics.cpp new file mode 100644 index 0000000000..beda178344 --- /dev/null +++ b/engines/m4/graphics.cpp @@ -0,0 +1,1074 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "common/file.h" +#include "common/endian.h" +#include "common/system.h" +#include "common/util.h" +#include "common/ptr.h" + +#include "m4/globals.h" +#include "m4/graphics.h" +#include "m4/sprite.h" +#include "m4/m4.h" +#include "m4/compression.h" + +namespace M4 { + +RGBList::RGBList(int numEntries, RGB8 *srcData, bool freeData) { + _size = numEntries; + assert(numEntries <= 256); + + if (srcData == NULL) { + _data = new RGB8[numEntries]; + _freeData = true; + } else { + _data = srcData; + _freeData = freeData; + } + + _palIndexes = new byte[numEntries]; + Common::set_to(&_palIndexes[0], &_palIndexes[numEntries], 0); +} + +RGBList::~RGBList() { + if (_freeData) + delete[] _data; + delete[] _palIndexes; +} + +//-------------------------------------------------------------------------- + +#define VGA_COLOR_TRANS(x) (x == 0x3f ? 255 : x << 2) + +void M4Surface::loadCodesM4(Common::SeekableReadStream *source) { + if (!source) { + free(); + return; + } + + uint16 widthVal = source->readUint16LE(); + uint16 heightVal = source->readUint16LE(); + + create(widthVal, heightVal, 1); + source->read(pixels, widthVal * heightVal); +} + +void M4Surface::loadCodesMads(Common::SeekableReadStream *source) { + if (!source) { + free(); + return; + } + + uint16 widthVal = 320; + uint16 heightVal = 156; + byte *walkMap = new byte[source->size()]; + + create(widthVal, heightVal, 1); + source->read(walkMap, source->size()); + + byte *ptr = (byte *)getBasePtr(0, 0); + + for (int y = 0; y < heightVal; y++) { + for (int x = 0; x < widthVal; x++) { + int ofs = x + (y * widthVal); + if ((walkMap[ofs / 8] << (ofs % 8)) & 0x80) + *ptr++ = 1; // walkable + else + *ptr++ = 0; + } + } +} + +// Sprite related methods + +void M4Surface::vLine(int x, int y1, int y2) { + Graphics::Surface::vLine(x, y1, y2, _color); +} + +void M4Surface::hLine(int x1, int x2, int y) { + Graphics::Surface::hLine(x1, y, x2, _color); +} + +void M4Surface::vLineXor(int x, int y1, int y2) { + // Clipping + if (x < 0 || x >= w) + return; + + if (y2 < y1) + SWAP(y2, y1); + + if (y1 < 0) + y1 = 0; + if (y2 >= h) + y2 = h - 1; + + byte *ptr = (byte *)getBasePtr(x, y1); + while (y1++ <= y2) { + *ptr ^= 0xFF; + ptr += pitch; + } + +} + +void M4Surface::hLineXor(int x1, int x2, int y) { + // Clipping + if (y < 0 || y >= h) + return; + + if (x2 < x1) + SWAP(x2, x1); + + if (x1 < 0) + x1 = 0; + if (x2 >= w) + x2 = w - 1; + + if (x2 < x1) + return; + + byte *ptr = (byte *)getBasePtr(x1, y); + while (x1++ <= x2) + *ptr++ ^= 0xFF; + +} + +void M4Surface::line(int x1, int y1, int x2, int y2, byte color) { + Graphics::Surface::drawLine(x1, y1, x2, y2, color); +} + + +void M4Surface::frameRect(int x1, int y1, int x2, int y2) { + Graphics::Surface::frameRect(Common::Rect(x1, y1, x2, y2), _color); +} + +void M4Surface::fillRect(int x1, int y1, int x2, int y2) { + Graphics::Surface::fillRect(Common::Rect(x1, y1, x2, y2), _color); +} + +void M4Surface::drawSprite(int x, int y, SpriteInfo &info, const Common::Rect &clipRect) { + + enum { + kStatusSkip, + kStatusScale, + kStatusDraw + }; + + // NOTE: The current clipping code assumes that the top left corner of the clip + // rectangle is always 0, 0 + assert(clipRect.top == 0 && clipRect.left == 0); + + // TODO: Put err* and scaled* into SpriteInfo + int errX = info.hotX * info.scaleX % 100; + int errY = info.hotY * info.scaleY % 100; + int scaledWidth = scaleValue(info.width, info.scaleX, errX); + int scaledHeight = scaleValue(info.height, info.scaleY, errY); + + /* + printf("M4Surface::drawSprite() info.width = %d; info.scaleX = %d; info.height = %d; info.scaleY = %d; scaledWidth = %d; scaledHeight = %d\n", + info.width, info.scaleX, info.height, info.scaleY, scaledWidth, scaledHeight); fflush(stdout); + */ + + int clipX = 0, clipY = 0; + // Clip the sprite's width and height according to the clip rectangle's dimensions + // This clips the sprite to the bottom and right + if (x >= 0) { + scaledWidth = MIN<int>(x + scaledWidth, clipRect.right) - x; + } else { + clipX = x; + scaledWidth = x + scaledWidth; + } + if (y >= 0) { + scaledHeight = MIN<int>(y + scaledHeight, clipRect.bottom) - y; + } else { + clipY = y; + scaledHeight = y + scaledHeight; + } + + //printf("M4Surface::drawSprite() width = %d; height = %d; scaledWidth = %d; scaledHeight = %d\n", info.width, info.height, scaledWidth, scaledHeight); fflush(stdout); + + // Check if sprite is inside the screen. If it's not, there's no need to draw it + if (scaledWidth + x <= 0 || scaledHeight + y <= 0) // check left and top (in case x,y are negative) + return; + if (scaledWidth <= 0 || scaledHeight <= 0) // check right and bottom + return; + int heightAmt = scaledHeight; + + byte *src = info.sprite->getData(); + byte *dst = getBasePtr(x - info.hotX - clipX, y - info.hotY - clipY); + + int status = kStatusSkip; + byte *scaledLineBuf = new byte[scaledWidth]; + + while (heightAmt > 0) { + + if (status == kStatusSkip) { + // Skip line + errY -= info.scaleY; + if (errY < 0) + status = kStatusScale; + else + src += info.width; + } else { + + if (status == kStatusScale) { + // Scale current line + byte *lineDst = scaledLineBuf; + int curErrX = errX; + int widthVal = scaledWidth; + byte *tempSrc = src; + int startX = clipX; + while (widthVal > 0) { + byte pixel = *tempSrc++; + curErrX -= info.scaleX; + while (curErrX < 0) { + if (startX == 0) { + *lineDst++ = pixel; + widthVal--; + } else { + startX++; + } + curErrX += 100; + } + } + src += info.width; + status = kStatusDraw; + } + + if (status == kStatusDraw && clipY == 0) { + // Draw previously scaled line + // TODO Implement different drawing types (depth, shadow etc.) + byte *tempDst = dst; + for (int lineX = 0; lineX < scaledWidth; lineX++) { + byte pixel = scaledLineBuf[lineX]; + + if (info.encoding & 0x80) { + + if (pixel == 0x80) { + pixel = 0; + } else { + byte destPixel = *tempDst; + byte r, g, b; + r = CLIP((info.palette[destPixel].r * pixel) >> 10, 0, 31); + g = CLIP((info.palette[destPixel].g * pixel) >> 10, 0, 31); + b = CLIP((info.palette[destPixel].b * pixel) >> 10, 0, 31); + pixel = info.inverseColorTable[(b << 10) | (g << 5) | r]; + } + } + + if (pixel) + *tempDst = pixel; + + tempDst++; + } + dst += pitch; + heightAmt--; + // TODO depth etc. + //depthAddress += Destination -> Width; + + errY += 100; + if (errY >= 0) + status = kStatusSkip; + } else if (status == kStatusDraw && clipY < 0) { + clipY++; + + errY += 100; + if (errY >= 0) + status = kStatusSkip; + } + + } + + } + + delete[] scaledLineBuf; + +} + +// Surface methods + +byte *M4Surface::getData() { + return (byte *)pixels; +} + +byte *M4Surface::getBasePtr(int x, int y) { + return (byte *)Graphics::Surface::getBasePtr(x, y); +} + +void M4Surface::freeData() { +} + +void M4Surface::empty() { + Common::set_to((byte *) pixels, (byte *) pixels + w * h, _vm->_palette->BLACK); +} + +void M4Surface::frameRect(const Common::Rect &r, uint8 color) { + Graphics::Surface::frameRect(r, color); +} + +void M4Surface::fillRect(const Common::Rect &r, uint8 color) { + Graphics::Surface::fillRect(r, color); +} + +void M4Surface::copyFrom(M4Surface *src, const Common::Rect &srcBounds, int destX, int destY, + int transparentColor) { + // Validation of the rectangle and position + if ((destX >= w) || (destY >= h)) + return; + + Common::Rect copyRect = srcBounds; + if (destX < 0) { + copyRect.left += -destX; + destX = 0; + } else if (destX + copyRect.width() > w) { + copyRect.right -= destX + copyRect.width() - w; + } + if (destY < 0) { + copyRect.top += -destY; + destY = 0; + } else if (destY + copyRect.height() > h) { + copyRect.bottom -= destY + copyRect.height() - h; + } + + if (!copyRect.isValidRect()) + return; + + // Copy the specified area + + byte *data = src->getData(); + byte *srcPtr = data + (src->width() * copyRect.top + copyRect.left); + byte *destPtr = (byte *)pixels + (destY * width()) + destX; + + for (int rowCtr = 0; rowCtr < copyRect.height(); ++rowCtr) { + if (transparentColor == -1) + // No transparency, so copy line over + Common::copy(srcPtr, srcPtr + copyRect.width(), destPtr); + else { + // Copy each byte one at a time checking for the transparency color + for (int xCtr = 0; xCtr < copyRect.width(); ++xCtr) + if (srcPtr[xCtr] != transparentColor) destPtr[xCtr] = srcPtr[xCtr]; + } + + srcPtr += src->width(); + destPtr += width(); + } + + src->freeData(); +} + +void M4Surface::loadBackgroundRiddle(const char *sceneName) { + char resourceName[20]; + Common::SeekableReadStream *stream; + // Loads a Riddle scene + sprintf(resourceName, "%s.tt", sceneName); + stream = _vm->_resourceManager->get(resourceName); + m4LoadBackground(stream); + _vm->_resourceManager->toss(resourceName); +} + +void M4Surface::loadBackground(int sceneNumber, RGBList **palData) { + this->empty(); // clear previous scene + + if (_vm->isM4() || (_vm->getGameType() == GType_RexNebular)) { + char resourceName[20]; + Common::SeekableReadStream *stream; + + if (_vm->getGameType() == GType_RexNebular) { + // Load Rex Nebular screen + sprintf(resourceName, "rm%d.art", sceneNumber); + stream = _vm->_resourceManager->get(resourceName); + rexLoadBackground(stream, palData); + } else { + // Loads M4 game scene + if (palData) + *palData = NULL; + sprintf(resourceName, "%i.tt", sceneNumber); + stream = _vm->_resourceManager->get(resourceName); + m4LoadBackground(stream); + } + + _vm->_resourceManager->toss(resourceName); + + } else { + madsLoadBackground(sceneNumber, palData); + } +} + +void M4Surface::madsLoadBackground(int roomNumber, RGBList **palData) { + // Get a MadsPack reference to the tile set and mapping + char resourceName[20]; + int i; + + // Uncompressed tile map resource + sprintf(resourceName, "rm%d.mm", roomNumber); + MadsPack tileMapFile(resourceName, _vm); + Common::SeekableReadStream *mapStream = tileMapFile.getItemStream(0); + + // Get the details of the tiles and map + mapStream->readUint32LE(); + int tileCountX = mapStream->readUint16LE(); + int tileCountY = mapStream->readUint16LE(); + int tileWidthMap = mapStream->readUint16LE(); + int tileHeightMap = mapStream->readUint16LE(); + int screenWidth = mapStream->readUint16LE(); + int screenHeight = mapStream->readUint16LE(); + int tileCountMap = tileCountX * tileCountY; + delete mapStream; + + // Obtain tile map information + typedef Common::List<Common::SharedPtr<M4Surface> > TileSetList; + typedef TileSetList::iterator TileSetIterator; + TileSetList tileSet; + uint16 *tileMap = new uint16[tileCountMap]; + mapStream = tileMapFile.getItemStream(1); + for (i = 0; i < tileCountMap; ++i) + tileMap[i] = mapStream->readUint16LE(); + delete mapStream; + _vm->res()->toss(resourceName); + + // -------------------------------------------------------------------------------- + + // Tile map data, which needs to be kept compressed, as the tile offsets refer to + // the compressed data. Each tile is then uncompressed separately + sprintf(resourceName, "rm%d.tt", roomNumber); + Common::SeekableReadStream *tileDataComp = _vm->_resourceManager->get(resourceName); + MadsPack tileData(tileDataComp); + Common::SeekableReadStream *tileDataUncomp = tileData.getItemStream(0); + + // Validate that the data matches between the tiles and tile map file and is valid + int tileCount = tileDataUncomp->readUint16LE(); + int tileWidth = tileDataUncomp->readUint16LE(); + int tileHeight = tileDataUncomp->readUint16LE(); + delete tileDataUncomp; + assert(tileCountMap == tileCount); + assert(tileWidth == tileWidthMap); + assert(tileHeight == tileHeightMap); + assert(screenWidth == _vm->_screen->width()); + assert(screenHeight <= _vm->_screen->height()); + + // -------------------------------------------------------------------------------- + + // Get the palette to use + tileDataUncomp = tileData.getItemStream(2); + // Set palette + if (!palData) { + _vm->_palette->setMadsPalette(tileDataUncomp, 4); + } else { + int numColors; + RGB8 *rgbList = _vm->_palette->decodeMadsPalette(tileDataUncomp, &numColors); + *palData = new RGBList(numColors, rgbList, true); + } + delete tileDataUncomp; + + // -------------------------------------------------------------------------------- + + // Get tile data + + tileDataUncomp = tileData.getItemStream(1); + FabDecompressor fab; + uint32 compressedTileDataSize = 0; + + for (i = 0; i < tileCount; i++) { + tileDataUncomp->seek(i * 4, SEEK_SET); + uint32 tileOfs = tileDataUncomp->readUint32LE(); + M4Surface* newTile = new M4Surface(tileWidth, tileHeight); + + if (i == tileCount - 1) + compressedTileDataSize = tileDataComp->size() - tileOfs; + else + compressedTileDataSize = tileDataUncomp->readUint32LE() - tileOfs; + + //printf("Tile: %i, compressed size: %i\n", i, compressedTileDataSize); + + newTile->empty(); + + byte *compressedTileData = new byte[compressedTileDataSize]; + + tileDataComp->seek(tileData.getDataOffset() + tileOfs, SEEK_SET); + tileDataComp->read(compressedTileData, compressedTileDataSize); + + fab.decompress(compressedTileData, compressedTileDataSize, (byte*)newTile->pixels, tileWidth * tileHeight); + tileSet.push_back(TileSetList::value_type(newTile)); + delete[] compressedTileData; + } + + delete tileDataUncomp; + + // -------------------------------------------------------------------------------- + + // Loop through the mapping data to place the tiles on the screen + + uint16 *tIndex = &tileMap[0]; + for (int y = 0; y < tileCountY; y++) { + for (int x = 0; x < tileCountX; x++) { + int tileIndex = *tIndex++; + assert(tileIndex < tileCount); + TileSetIterator tile = tileSet.begin(); + for (i = 0; i < tileIndex; i++) + ++tile; + ((*tile).get())->copyTo(this, x * tileWidth, y * tileHeight); + } + } + tileSet.clear(); + _vm->res()->toss(resourceName); +} + +void M4Surface::rexLoadBackground(Common::SeekableReadStream *source, RGBList **palData) { + MadsPack packData(source); + Common::MemoryReadStream *sourceUnc = packData.getItemStream(0); + + int sceneWidth = sourceUnc->readUint16LE(); + int sceneHeight = sourceUnc->readUint16LE(); + int sceneSize = sceneWidth * sceneHeight; + if (sceneWidth > this->width()) { + warning("Background width is %i, too large to fit in screen. Setting it to %i", sceneWidth, this->width()); + sceneWidth = this->width(); + sceneSize = sceneWidth * sceneHeight; + } + if (sceneHeight > this->height()) { + warning("Background height is %i, too large to fit in screen.Setting it to %i", sceneHeight, this->height()); + sceneHeight = this->height(); + sceneSize = sceneWidth * sceneHeight; + } + + // Set palette + if (!palData) { + _vm->_palette->setMadsPalette(sourceUnc, 4); + } else { + int numColors; + RGB8 *rgbList = _vm->_palette->decodeMadsPalette(sourceUnc, &numColors); + *palData = new RGBList(numColors, rgbList, true); + } + delete sourceUnc; + + // Get the raw data for the background + sourceUnc = packData.getItemStream(1); + assert((int)sourceUnc->size() >= sceneSize); + + byte *pData = (byte *)pixels; + sourceUnc->read(pData, sceneSize); + + freeData(); + delete sourceUnc; +} + +#undef COL_TRANS + +void M4Surface::m4LoadBackground(Common::SeekableReadStream *source) { + M4Surface *tileBuffer = new M4Surface(); + uint curTileX = 0, curTileY = 0; + int clipX = 0, clipY = 0; + RGB8 palette[256]; + + source->readUint32LE(); // magic, unused + /*uint32 size =*/ source->readUint32LE(); + uint32 widthVal = source->readUint32LE(); + uint32 heightVal = source->readUint32LE(); + uint32 tilesX = source->readUint32LE(); + uint32 tilesY = source->readUint32LE(); + uint32 tileWidth = source->readUint32LE(); + uint32 tileHeight = source->readUint32LE(); + uint8 blackIndex = 0; + + // Debug + //printf("loadBackground(): %dx%d picture (%d bytes) - %dx%d tiles of size %dx%d\n", + // widthVal, heightVal, size, tilesX, tilesY, tileWidth, tileHeight); + + // BGR data, which is converted to RGB8 + for (uint i = 0; i < 256; i++) { + palette[i].b = source->readByte() << 2; + palette[i].g = source->readByte() << 2; + palette[i].r = source->readByte() << 2; + palette[i].u = source->readByte() << 2; + + if ((blackIndex == 0) && !palette[i].r && !palette[i].g && !palette[i].b) + blackIndex = i; + } + + _vm->_palette->setPalette(palette, 0, 256); + + // resize or create the surface + // note that the height of the scene in game scenes is smaller than 480, as the bottom part of the + // screen is the inventory + assert(width() == (int)widthVal); + //printf("width(): %d, widthVal: %d, height(): %d, heightVal: %d\n", width(), widthVal, height(), heightVal); + + tileBuffer->create(tileWidth, tileHeight, 1); + + for (curTileY = 0; curTileY < tilesY; curTileY++) { + clipY = MIN(heightVal, (1 + curTileY) * tileHeight) - (curTileY * tileHeight); + + for (curTileX = 0; curTileX < tilesX; curTileX++) { + clipX = MIN(widthVal, (1 + curTileX) * tileWidth) - (curTileX * tileWidth); + + // Read a tile and copy it to the destination surface + source->read(tileBuffer->pixels, tileWidth * tileHeight); + Common::Rect srcBounds(0, 0, clipX, clipY); + copyFrom(tileBuffer, srcBounds, curTileX * tileWidth, curTileY * tileHeight); + } + } + + if (heightVal < (uint)height()) + fillRect(Common::Rect(0, heightVal, width(), height()), blackIndex); + + delete tileBuffer; +} + +void M4Surface::madsloadInterface(int index, RGBList **palData) { + char resourceName[20]; + sprintf(resourceName, "i%d.int", index); + MadsPack intFile(resourceName, _vm); + RGB8 *palette = new RGB8[16]; + + // Chunk 0, palette + Common::SeekableReadStream *intStream = intFile.getItemStream(0); + + for (int i = 0; i < 16; i++) { + palette[i].r = intStream->readByte() << 2; + palette[i].g = intStream->readByte() << 2; + palette[i].b = intStream->readByte() << 2; + intStream->readByte(); + intStream->readByte(); + intStream->readByte(); + } + *palData = new RGBList(16, palette, true); + delete intStream; + + // Chunk 1, data + intStream = intFile.getItemStream(1); + create(320, 44, 1); + intStream->read(pixels, 320 * 44); + delete intStream; +} + +void M4Surface::translate(RGBList *list, bool isTransparent) { + byte *p = getBasePtr(0, 0); + byte *palIndexes = list->palIndexes(); + + for (int i = 0; i < width() * height(); ++i, ++p) { + if (!isTransparent || (*p != 0)) { + assert(*p < list->size()); + *p = palIndexes[*p]; + } + } + + freeData(); +} + +//-------------------------------------------------------------------------- +// Palette class +// + +#define GREEN_START 32 +#define NUM_GREENS 32 +#define GREEN_END (GREEN_START + NUM_GREENS - 1) +#define NORMAL_START 64 +#define NORMAL_END 255 +#define NUM_NORMAL (NORMAL_END - NORMAL_START + 1) + +// Support function for creating a list of palette indexes to change entries in the shaded range to + +static void makeTranslationList(RGB8 *palData, byte transList[NUM_GREENS]) { + int i, j, minDistance; + byte bestIndex; + + for (i = 0; i < NUM_GREENS; ++i) { + bestIndex = NORMAL_START; + minDistance = 255; + + uint8 findCol = palData[GREEN_START + i].g; + + // Find the closest matching palette color + for (j = NORMAL_START; j <= NORMAL_END; ++j) { + int greenVal = palData[j].g; + if (ABS(findCol - greenVal) < minDistance) { + minDistance = ABS(findCol - greenVal); + bestIndex = j; + } + + if (minDistance == 0) + break; + } + + transList[i] = bestIndex; + } +} + +// Support function for fading in or out + +static void fadeRange(M4Engine *vm, RGB8 *srcPal, RGB8 *destPal, int startIndex, int endIndex, + int numSteps, uint delayAmount) { + RGB8 tempPal[256]; + + // perform the fade + for(int stepCtr = 1; stepCtr <= numSteps; ++stepCtr) { + // Delay the specified amount + uint32 startTime = g_system->getMillis(); + while ((g_system->getMillis() - startTime) < delayAmount) { + vm->_events->handleEvents(); + g_system->delayMillis(10); + } + + for (int i = startIndex; i <= endIndex; ++i) { + // Handle the intermediate rgb values for fading + tempPal[i].r = (byte) (srcPal[i].r + (destPal[i].r - srcPal[i].r) * stepCtr / numSteps); + tempPal[i].g = (byte) (srcPal[i].g + (destPal[i].g - srcPal[i].g) * stepCtr / numSteps); + tempPal[i].b = (byte) (srcPal[i].b + (destPal[i].b - srcPal[i].b) * stepCtr / numSteps); + } + + vm->_palette->setPalette(&tempPal[startIndex], startIndex, endIndex - startIndex + 1); + vm->_viewManager->refreshAll(); + } + + // Make sure the end palette exactly matches what is wanted + vm->_palette->setPalette(&destPal[startIndex], startIndex, endIndex - startIndex + 1); +} + +Palette::Palette(M4Engine *vm) : _vm(vm) { + reset(); + _fading_in_progress = false; + Common::set_to(&_usageCount[0], &_usageCount[256], 0); +} + +void Palette::setPalette(const byte *colors, uint start, uint num) { + g_system->setPalette(colors, start, num); + reset(); +} + +void Palette::setPalette(const RGB8 *colors, uint start, uint num) { + g_system->setPalette((const byte *)colors, start, num); + reset(); +} + +void Palette::grabPalette(byte *colors, uint start, uint num) { + g_system->grabPalette(colors, start, num); + reset(); +} + +uint8 Palette::palIndexFromRgb(byte r, byte g, byte b, RGB8 *paletteData) { + byte index = 0; + int32 minDist = 0x7fffffff; + RGB8 palData[256]; + int Rdiff, Gdiff, Bdiff; + + if (paletteData == NULL) { + g_system->grabPalette((byte *)palData, 0, 256); + paletteData = &palData[0]; + } + + for (int palIndex = 0; palIndex < 256; ++palIndex) { + Rdiff = r - paletteData[palIndex].r; + Gdiff = g - paletteData[palIndex].g; + Bdiff = b - paletteData[palIndex].b; + + if (Rdiff * Rdiff + Gdiff * Gdiff + Bdiff * Bdiff < minDist) { + minDist = Rdiff * Rdiff + Gdiff * Gdiff + Bdiff * Bdiff; + index = (uint8)palIndex; + } + } + + return (uint8)index; +} + +void Palette::reset() { + RGB8 palData[256]; + g_system->grabPalette((byte *)palData, 0, 256); + + BLACK = palIndexFromRgb(0, 0, 0, palData); + BLUE = palIndexFromRgb(0, 0, 255, palData); + GREEN = palIndexFromRgb(0, 255, 0, palData); + CYAN = palIndexFromRgb(0, 255, 255, palData); + RED = palIndexFromRgb(255, 0, 0, palData); + VIOLET = palIndexFromRgb(255, 0, 255, palData); + BROWN = palIndexFromRgb(168, 84, 84, palData); + LIGHT_GRAY = palIndexFromRgb(168, 168, 168, palData); + DARK_GRAY = palIndexFromRgb(84, 84, 84, palData); + LIGHT_BLUE = palIndexFromRgb(0, 0, 127, palData); + LIGHT_GREEN = palIndexFromRgb(0, 127, 0, palData); + LIGHT_CYAN = palIndexFromRgb(0, 127, 127, palData); + LIGHT_RED = palIndexFromRgb(84, 0, 0, palData); + PINK = palIndexFromRgb(84, 0, 0, palData); + YELLOW = palIndexFromRgb(0, 84, 84, palData); + WHITE = palIndexFromRgb(255, 255, 255, palData); +} + +void Palette::fadeToGreen(int numSteps, uint delayAmount) { + if (_fading_in_progress) + return; + _fading_in_progress = true; + byte translationList[NUM_GREENS]; + + int i; + byte *tempP; + uint8 greenAmount = 0; + RGB8 *srcPalette = (RGB8 *) &_originalPalette[0]; + RGB8 *destPalette = (RGB8 *) &_fadedPalette[0]; + + _vm->_palette->grabPalette(srcPalette, 0, 256); + + // Create the destination 'greenish' palette to fade to by setting the green component + // to the average of the RGB bytes, and leaving the Red and Blue parts as 0 + + Common::copy(&srcPalette[0], &srcPalette[256], &destPalette[0]); + for (i = 32; i < 256; ++i) { + byte luminance = (byte)((destPalette[i].r + destPalette[i].g + destPalette[i].b) / 3); + destPalette[i].g = MIN((byte)255, luminance); + destPalette[i].r = destPalette[i].b = 0; + } + + // Handle the actual fading + fadeRange(_vm, srcPalette, destPalette, 21, 255, numSteps, delayAmount); + + // Create a translation table to be used in translating pixels in the game surface + // using palette indexes in the range the range #32-63 into values from #64-255 + + makeTranslationList(destPalette, translationList); + + // Use palette indexes from #32-63 for the range of possible shades + + for (i = GREEN_START; i <= GREEN_END; ++i, greenAmount += 8) { + destPalette[i].g = greenAmount; + destPalette[i].r = destPalette[i].b = 0; + } + + // Remap all pixels into the #32-63 range + + tempP = _vm->_scene->getData(); + for (int pixelCtr = 0; pixelCtr < _vm->_scene->width() * _vm->_scene->height(); + ++pixelCtr, ++tempP) { + // If pixel is in #32-63 range already, remap to higher palette entries + if ((*tempP >= GREEN_START) && (*tempP <= GREEN_END)) + *tempP = translationList[*tempP - GREEN_START]; + + *tempP = (uint8) (GREEN_START + (destPalette[*tempP].g >> 3)); + } + + _vm->_palette->setPalette(&destPalette[GREEN_START], GREEN_START, NUM_GREENS); + _vm->_viewManager->refreshAll(); + _fading_in_progress = false; +} + +void Palette::fadeFromGreen(int numSteps, uint delayAmount, bool fadeToBlack) { + if (_fading_in_progress) + return; + _fading_in_progress = true; + RGB8 blackPalette[256]; + RGB8 *fadedPalette = (RGB8 *) &_fadedPalette[0]; + RGB8 *destPalette = (RGB8 *) &_originalPalette[0]; + + if (fadeToBlack) { + Common::set_to((byte *)&blackPalette[0], (byte *)&blackPalette[256], 0); + destPalette = &blackPalette[0]; + } + + // Initially restore the faded palette + _vm->_palette->setPalette(fadedPalette, 0, 256); + _vm->_viewManager->refreshAll(); + + // Restore the pixel data from the original screen + _vm->_scene->update(); + + // Handle the actual fading + fadeRange(_vm, fadedPalette, destPalette, GREEN_START, NORMAL_END, numSteps, delayAmount); + + _fading_in_progress = false; +} + +void Palette::fadeIn(int numSteps, uint delayAmount, RGBList *destPalette) { + fadeIn(numSteps, delayAmount, destPalette->data(), destPalette->size()); +} + +void Palette::fadeIn(int numSteps, uint delayAmount, RGB8 *destPalette, int numColors) { + if (_fading_in_progress) + return; + + _fading_in_progress = true; + RGB8 blackPalette[256]; + Common::set_to((byte *)&blackPalette[0], (byte *)&blackPalette[256], 0); + + // Initially set the black palette + _vm->_palette->setPalette(blackPalette, 0, numColors); + + // Handle the actual fading + fadeRange(_vm, blackPalette, destPalette, 0, numColors - 1, numSteps, delayAmount); + + _fading_in_progress = false; +} + +RGB8 *Palette::decodeMadsPalette(Common::SeekableReadStream *palStream, int *numColors) { + *numColors = palStream->readUint16LE(); + assert(*numColors <= 252); + + RGB8 *palData = new RGB8[*numColors]; + Common::set_to((byte *)&palData[0], (byte *)&palData[*numColors], 0); + + for (int i = 0; i < *numColors; ++i) { + byte r = palStream->readByte(); + byte g = palStream->readByte(); + byte b = palStream->readByte(); + palData[i].r = VGA_COLOR_TRANS(r); + palData[i].g = VGA_COLOR_TRANS(g); + palData[i].b = VGA_COLOR_TRANS(b); + + // The next 3 bytes are unused + palStream->skip(3); + } + + return palData; +} + +int Palette::setMadsPalette(Common::SeekableReadStream *palStream, int indexStart) { + int colorCount; + RGB8 *palData = Palette::decodeMadsPalette(palStream, &colorCount); + _vm->_palette->setPalette(palData, indexStart, colorCount); + delete palData; + return colorCount; +} + +void Palette::setMadsSystemPalette() { + // Rex Nebular default system palette + resetColorCounts(); + + RGB8 palData[4]; + palData[0].r = palData[0].g = palData[0].b = 0; + palData[1].r = palData[1].g = palData[1].b = 0x54; + palData[2].r = palData[2].g = palData[2].b = 0xb4; + palData[3].r = palData[3].g = palData[3].b = 0xff; + + setPalette(palData, 0, 4); + blockRange(0, 4); +} + +void Palette::resetColorCounts() { + Common::set_to(&_usageCount[0], &_usageCount[256], 0); +} + +void Palette::blockRange(int startIndex, int size) { + // Use a reference count of -1 to signal a palette index shouldn't be used + Common::set_to(&_usageCount[startIndex], &_usageCount[startIndex + size], -1); +} + +void Palette::addRange(RGBList *list) { + RGB8 *data = list->data(); + byte *palIndexes = list->palIndexes(); + RGB8 palData[256]; + g_system->grabPalette((byte *)&palData[0], 0, 256); + bool paletteChanged = false; + + for (int colIndex = 0; colIndex < list->size(); ++colIndex) { + // Scan through for an existing copy of the RGB value + int palIndex = -1; + while (++palIndex < 256) { + if (_usageCount[palIndex] <= 0) + // Palette index is to be skipped + continue; + + if ((palData[palIndex].r == data[colIndex].r) && + (palData[palIndex].g == data[colIndex].g) && + (palData[palIndex].b == data[colIndex].b)) + // Match found + break; + } + + if (palIndex == 256) { + // No match found, so find a free slot to use + palIndex = -1; + while (++palIndex < 256) { + if (_usageCount[palIndex] == 0) + break; + } + + if (palIndex == 256) + error("addRange - Ran out of palette space to allocate"); + + palData[palIndex].r = data[colIndex].r; + palData[palIndex].g = data[colIndex].g; + palData[palIndex].b = data[colIndex].b; + paletteChanged = true; + } + + palIndexes[colIndex] = palIndex; + ++_usageCount[palIndex]; + } + + if (paletteChanged) { + g_system->setPalette((byte *)&palData[0], 0, 256); + reset(); + } +} + +void Palette::deleteRange(RGBList *list) { + // Release the reference count on each of the palette entries + for (int colIndex = 0; colIndex < list->size(); ++colIndex) { + int palIndex = list->palIndexes()[colIndex]; + assert(_usageCount[palIndex] > 0); + --_usageCount[palIndex]; + } +} + +void Palette::deleteAllRanges() { + for (int colIndex = 0; colIndex < 255; ++colIndex) + _usageCount[colIndex] = 0; +} + +//-------------------------------------------------------------------------- +// Support methods + +void decompressRle(byte *rleData, int rleSize, byte *celData, int w, int h) { + byte *src = rleData; + byte *dst = celData; + byte len; + while (1) { + len = *src++; + if (len == 0) { + len = *src++; + if (len <= 2) { + if (len == 1) // end of sprite marker + break; + } else { + while (len--) + *dst++ = *src++; + } + } else { + while (len--) + *dst++ = *src; + *src++; + } + } +} + +int scaleValue(int value, int scale, int err) { + int scaled = 0; + while (value--) { + err -= scale; + while (err < 0) { + scaled++; + err += 100; + } + } + return scaled; +} + +} // End of namespace M4 diff --git a/engines/m4/graphics.h b/engines/m4/graphics.h new file mode 100644 index 0000000000..60e608c148 --- /dev/null +++ b/engines/m4/graphics.h @@ -0,0 +1,222 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef M4_GRAPHICS_H +#define M4_GRAPHICS_H + +#include "common/rect.h" +#include "common/system.h" +#include "common/stream.h" +#include "graphics/surface.h" + +#include "m4/globals.h" + +namespace M4 { + +struct BGR8 { + uint8 b, g, r; +}; + +struct RGB8 { + uint8 r, g, b, u; +}; + +//later use ScummVM's Rect? +struct M4Rect { + int32 x1, y1, x2, y2; +}; + +class M4Surface; + +// RGBList +// Implements a list of RGB entries + +class RGBList { +private: + int _size; + RGB8 *_data; + byte *_palIndexes; + bool _freeData; +public: + RGBList(int numEntries = 256, RGB8 *srcData = NULL, bool freeData = true); + ~RGBList(); + + RGB8 *data() { return _data; } + byte *palIndexes() { return _palIndexes; } + int size() { return _size; } +}; + +// M4Surface +// Class representing either a buffered surface or the physical screen. + +class M4Sprite; + +struct SpriteInfo { + M4Sprite *sprite; + int hotX, hotY; + int width, height; + int scaleX, scaleY; + uint8 encoding; + byte *inverseColorTable; + RGB8 *palette; +}; + +class M4Surface : public Graphics::Surface { +private: + byte _color; + bool _isScreen; + + void rexLoadBackground(Common::SeekableReadStream *source, RGBList **palData = NULL); + void madsLoadBackground(int roomNumber, RGBList **palData = NULL); + void m4LoadBackground(Common::SeekableReadStream *source); +public: + M4Surface(bool isScreen = false) { + create(g_system->getWidth(), g_system->getHeight(), 1); + _isScreen = isScreen; + } + M4Surface(int Width, int Height) { create(Width, Height, 1); _isScreen = false; } + + // loads a .COD file into the M4Surface + // TODO: maybe move this to the rail system? check where it makes sense + // The sprite drawing needs this, too, so should be more global. + void loadCodesM4(Common::SeekableReadStream *source); + void loadCodesMads(Common::SeekableReadStream *source); + + // loads the specified background + void loadBackground(int sceneNumber, RGBList **palData = NULL); + void loadBackgroundRiddle(const char *sceneName); + void madsloadInterface(int index, RGBList **palData); + + void setColor(byte value) { _color = value; } + byte getColor() { return _color; } + void vLine(int x, int y1, int y2); + void hLine(int x1, int x2, int y); + void vLineXor(int x, int y1, int y2); + void hLineXor(int x1, int x2, int y); + void line(int x1, int y1, int x2, int y2, byte color); + void frameRect(int x1, int y1, int x2, int y2); + void fillRect(int x1, int y1, int x2, int y2); + + void drawSprite(int x, int y, SpriteInfo &info, const Common::Rect &clipRect); + + // Surface methods + int width() { return w; } + int height() { return h; } + void setSize(int sizeX, int sizeY) { create(sizeX, sizeY, 1); } + byte *getData(); + byte *getBasePtr(int x, int y); + void freeData(); + void empty(); + void frameRect(const Common::Rect &r, uint8 color); + void fillRect(const Common::Rect &r, uint8 color); + void copyFrom(M4Surface *src, const Common::Rect &srcBounds, int destX, int destY, + int transparentColor = -1); + + void update() { + if (_isScreen) { + g_system->copyRectToScreen((const byte *)pixels, pitch, 0, 0, w, h); + g_system->updateScreen(); + } + } + + // copyTo methods + void copyTo(M4Surface *dest, int transparentColor = -1) { + dest->copyFrom(this, Common::Rect(width(), height()), 0, 0, transparentColor); + } + void copyTo(M4Surface *dest, int x, int y, int transparentColor = -1) { + dest->copyFrom(this, Common::Rect(width(), height()), x, y, transparentColor); + } + void copyTo(M4Surface *dest, const Common::Rect &srcBounds, int destX, int destY, + int transparentColor = -1) { + dest->copyFrom(this, srcBounds, destX, destY, transparentColor); + } + + void translate(RGBList *list, bool isTransparent = false); +}; + +enum FadeType {FT_TO_GREY, FT_TO_COLOR, FT_TO_BLOCK}; + +class Palette { +private: + M4Engine *_vm; + bool _colorsChanged; + bool _fading_in_progress; + byte _originalPalette[256 * 4]; + byte _fadedPalette[256 * 4]; + int _usageCount[256]; + + void reset(); +public: + Palette(M4Engine *vm); + + void setPalette(const byte *colors, uint start, uint num); + void setPalette(const RGB8 *colors, uint start, uint num); + void grabPalette(byte *colors, uint start, uint num); + void grabPalette(RGB8 *colors, uint start, uint num) { + grabPalette((byte *)colors, start, num); + } + uint8 palIndexFromRgb(byte r, byte g, byte b, RGB8 *paletteData = NULL); + + void fadeToGreen(int numSteps, uint delayAmount); + void fadeFromGreen(int numSteps, uint delayAmount, bool fadeToBlack); + void fadeIn(int numSteps, uint delayAmount, RGB8 *destPalette, int numColors); + void fadeIn(int numSteps, uint delayAmount, RGBList *destPalette); + static RGB8 *decodeMadsPalette(Common::SeekableReadStream *palStream, int *numColors); + int setMadsPalette(Common::SeekableReadStream *palStream, int indexStart = 0); + void setMadsSystemPalette(); + + // Methods used for reference counting color usage + void resetColorCounts(); + void blockRange(int startIndex, int size); + void addRange(RGBList *list); + void deleteRange(RGBList *list); + void deleteAllRanges(); + + // Color indexes + uint8 BLACK; + uint8 BLUE; + uint8 GREEN; + uint8 CYAN; + uint8 RED; + uint8 VIOLET; + uint8 BROWN; + uint8 LIGHT_GRAY; + uint8 DARK_GRAY; + uint8 LIGHT_BLUE; + uint8 LIGHT_GREEN; + uint8 LIGHT_CYAN; + uint8 LIGHT_RED; + uint8 PINK; + uint8 YELLOW; + uint8 WHITE; +}; + +void decompressRle(byte *rleData, int rleSize, byte *celData, int w, int h); +void decompressRle(Common::SeekableReadStream &rleData, byte *celData, int w, int h); +int scaleValue(int value, int scale, int err); + +} // End of namespace M4 + +#endif diff --git a/engines/m4/gui.cpp b/engines/m4/gui.cpp new file mode 100644 index 0000000000..67c1c7a1a7 --- /dev/null +++ b/engines/m4/gui.cpp @@ -0,0 +1,1217 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "common/events.h" +#include "common/keyboard.h" + +#include "m4/globals.h" +#include "m4/events.h" +#include "m4/font.h" +#include "m4/graphics.h" +#include "m4/viewmgr.h" +#include "m4/gui.h" +#include "m4/midi.h" +#include "m4/scene.h" +#include "m4/m4.h" + +namespace M4 { + +//-------------------------------------------------------------------------- +// DialogView class +// +// Defines a generic base class for dialogs, that some of the classes +// in the object hierharchy require as a parent +//-------------------------------------------------------------------------- + +void DialogView::close() { + // Default to simply destroying the given dialog + _vm->_viewManager->deleteView(this); +} + +//-------------------------------------------------------------------------- +// GUIObject class +// +// Defines a generic object that appears in a view +//-------------------------------------------------------------------------- + +GUIObject::GUIObject(View *owner, const Common::Rect &bounds) { + _parent = owner; + _bounds = bounds; +} + +//-------------------------------------------------------------------------- +// MenuObject class +// +// Defines a specialised GUI object that appears in a dialog +//-------------------------------------------------------------------------- + +MenuObject::MenuObject(DialogView *owner, int objectId, int xs, int ys, int width, int height, + bool greyed, bool transparent): + GUIObject(owner, Common::Rect(xs, ys, xs + width, ys + height)) { + + _objectId = objectId; + _bounds.top = ys; + _bounds.bottom = ys + height - 1; + _bounds.left = xs; + _bounds.right = xs + width - 1; + _transparent = transparent; + _objectState = greyed ? OS_GREYED : OS_NORMAL; + _callback = NULL; + + if (transparent) { + _background = new M4Surface(width, height); + Common::Rect srcBounds(xs, ys, xs + width - 1, ys + height - 1); + _background->copyFrom(owner, srcBounds, 0, 0); + } else { + _background = NULL; + } +} + +MenuObject::~MenuObject() { + if (_background) + delete _background; +} + +void MenuObject::onExecute() { + // If a callback function has been specified, then execute it + if (_callback) + _callback(parent(), this); +} + +//-------------------------------------------------------------------------- +// MenuButton class +// +// Defines a button object +//-------------------------------------------------------------------------- + +MenuButton::MenuButton(DialogView *owner, int buttonId, int xs, int ys, int width, int height, + MenuButton::Callback callbackFn, bool greyed, bool transparent, + ObjectType buttonType): + MenuObject(owner, buttonId, xs, ys, width, height, greyed, transparent) { + + _objectType = buttonType; + _callback = callbackFn; +} + +bool MenuButton::onEvent(M4EventType event, int param, int x, int y, MenuObject *¤tItem) { + bool redrawFlag = false; + bool callbackFlag = false; + bool handledFlag = true; + + if (_objectState == OS_GREYED) + return false; + + switch (event) { + case MEVENT_LEFT_CLICK: + case MEVENT_DOUBLECLICK: + if (isInside(x, y)) { + _objectState = OS_PRESSED; + if (currentItem != NULL) + currentItem = this; + redrawFlag = true; + } else { + currentItem = NULL; + if (_objectState != OS_NORMAL) { + _objectState = OS_PRESSED; + redrawFlag = true; + } + } + break; + + case MEVENT_LEFT_DRAG: + case MEVENT_DOUBLECLICK_DRAG: + if (!currentItem) { + return true; + } + if (isInside(x, y)) { + if (_objectState != OS_PRESSED) { + _objectState = OS_PRESSED; + redrawFlag = true; + } + } + else { + if (_objectState != OS_MOUSEOVER) { + _objectState = OS_MOUSEOVER; + redrawFlag = true; + } + } + break; + + case MEVENT_LEFT_RELEASE: + case MEVENT_DOUBLECLICK_RELEASE: + if (isInside(x, y)) { + if (currentItem) { + callbackFlag = true; + if (_objectType == OBJTYPE_OM_SWITCH_ON) + _objectType = OBJTYPE_OM_SWITCH_OFF; + else if (_objectType == OBJTYPE_OM_SWITCH_OFF) + _objectType = OBJTYPE_OM_SWITCH_ON; + } + else { + currentItem = this; + } + + _objectState = OS_MOUSEOVER; + redrawFlag = true; + + } else { + currentItem = NULL; + _objectState = OS_MOUSEOVER; + redrawFlag = true; + handledFlag = false; + } + break; + + case MEVENT_MOVE: + if (isInside(x, y)) { + currentItem = this; + if (_objectState != OS_MOUSEOVER) { + _objectState = OS_MOUSEOVER; + redrawFlag = true; + } + } + else { + currentItem = NULL; + if (_objectState != OS_NORMAL) { + _objectState = OS_NORMAL; + redrawFlag = true; + handledFlag = false; + } + } + break; + + case MEVENT_LEFT_HOLD: + case MEVENT_DOUBLECLICK_HOLD: + break; + + default: + break; + } + + //see if we need to redraw the button + if (redrawFlag) { + onRefresh(); + + // TODO: There may be a more efficient mechanism than refreshing the entire screen + // when a menu object refreshes itself + if (parent()->screenFlags().visible) + _vm->_viewManager->refreshAll(); + } + + // If a callback is flagged, then handle it + + if (callbackFlag) + onExecute(); + + return handledFlag; +} + +void MenuButton::onRefresh() { + M4Sprite *sprite = NULL; + SpriteAsset &sprites = *parent()->sprites(); + + // Switch handling for the various button types + switch (_objectType) { + case OBJTYPE_BUTTON: + sprite = sprites[GM_BUTTON_GREYED + _objectState]; + break; + + case OBJTYPE_OM_SWITCH_ON: + switch (_objectState) { + case OS_MOUSEOVER: + sprite = sprites[MENU_SS_SWITCH_ON_MOUSEOVER]; + break; + case OS_PRESSED: + sprite = sprites[MENU_SS_SWITCH_ON_PRESSED]; + break; + default: + sprite = sprites[MENU_SS_SWITCH_ON_NORMAL]; + break; + } + break; + + case OBJTYPE_OM_SWITCH_OFF: + switch (_objectState) { + case OS_MOUSEOVER: + sprite = sprites[MENU_SS_SWITCH_OFF_MOUSEOVER]; + break; + case OS_PRESSED: + sprite = sprites[MENU_SS_SWITCH_OFF_PRESSED]; + break; + default: + sprite = sprites[MENU_SS_SWITCH_OFF_NORMAL]; + break; + } + break; + + case OBJTYPE_OM_DONE: + sprite = sprites[OM_DONE_BTN_GREYED + _objectState]; + break; + + case OBJTYPE_OM_CANCEL: + sprite = (_objectState == OS_GREYED) ? sprites[OM_CANCEL_BTN_NORMAL] : + sprites[OM_CANCEL_BTN_NORMAL + _objectState - 1]; + break; + + case OBJTYPE_SL_SAVE: + sprite = sprites[SL_SAVE_BTN_GREYED + _objectState]; + break; + + case OBJTYPE_SL_LOAD: + sprite = sprites[SL_LOAD_BTN_GREYED + _objectState]; + break; + + case OBJTYPE_SL_CANCEL: + sprite = (_objectState == OS_GREYED) ? sprites[SL_CANCEL_BTN_NORMAL] : + sprites[SL_CANCEL_BTN_NORMAL + _objectState - 1]; + break; + + case OBJTYPE_SL_TEXT: + switch (_objectState) { + case OS_MOUSEOVER: + _vm->_font->setColors(TEXT_COLOR_MOUSEOVER_SHADOW, TEXT_COLOR_MOUSEOVER_FOREGROUND, + TEXT_COLOR_MOUSEOVER_HILIGHT); + sprite = sprites[SL_LINE_MOUSEOVER]; + break; + + case OS_PRESSED: + _vm->_font->setColors(TEXT_COLOR_PRESSED_SHADOW, TEXT_COLOR_PRESSED_FOREGROUND, + TEXT_COLOR_PRESSED_HILIGHT); + sprite = sprites[SL_LINE_PRESSED]; + break; + + case OS_GREYED: + _vm->_font->setColors(TEXT_COLOR_GREYED_SHADOW, TEXT_COLOR_GREYED_FOREGROUND, + TEXT_COLOR_GREYED_HILIGHT); + sprite = sprites[SL_LINE_NORMAL]; + break; + + default: + case OS_NORMAL: + _vm->_font->setColors(TEXT_COLOR_NORMAL_SHADOW, TEXT_COLOR_NORMAL_FOREGROUND, + TEXT_COLOR_NORMAL_HILIGHT); + sprite = sprites[SL_LINE_NORMAL]; + break; + } + break; + + default: + error("Unknown object type"); + break; + } + + // If no sprite object was set, then exit without doing anything + if (!sprite) + return; + + // Draw the button + if (_transparent) { + // Transparent button, so restore original background + if (!_background) + return; + else + _background->copyTo(parent(), _bounds.left, _bounds.top); + } + + sprite->copyTo(parent(), _bounds.left, _bounds.top, 0); +} + +//-------------------------------------------------------------------------- +// MenuHorizSlider class +// +// Defines a horizontal slider that allows selection of a percentage +//-------------------------------------------------------------------------- + +MenuHorizSlider::MenuHorizSlider(DialogView *owner, int objectId, int xs, int ys, + int width, int height, int initialPercentage, Callback callbackFn, bool transparent): + MenuObject(owner, objectId, xs, ys, width, height, false, transparent) { + + _objectType = OBJTYPE_SLIDER; + _callback = callbackFn; + + SpriteAsset &sprites = *owner->sprites(); + _sliderState = HSLIDER_THUMB_NORMAL; + _thumbSize.x = sprites[OM_SLIDER_BTN_NORMAL]->width(); + _thumbSize.y = sprites[OM_SLIDER_BTN_NORMAL]->height(); + _maxThumbX = width - _thumbSize.x; + _percent = MAX(MIN(initialPercentage, 100), 0); + _thumbX = initialPercentage * _maxThumbX / 100; +} + +void MenuHorizSlider::onRefresh() { + // If the slider is transparent, first copy in the background + if (_transparent) { + // Transparent button + if (!_background) + return; + + _background->copyTo(parent(), _bounds.left, _bounds.top, 0); + } + + // Get the thumb sprite for the slider + SpriteAsset &sprites = *parent()->sprites(); + M4Sprite *sprite = sprites[OM_SLIDER_BTN_NORMAL + _sliderState]; + assert(sprite); + + // Fill in the area to the left of the thumbnail + if (_thumbX > 2) { + Common::Rect leftBounds(_bounds.left + 3, _bounds.top + 9, _bounds.left + _thumbX, + _bounds.top + _thumbSize.y - 9); + parent()->fillRect(leftBounds, SLIDER_BAR_COLOR); + } + + // Draw the thumbnail + sprite->copyTo(parent(), _bounds.left + _thumbX, _bounds.top, 0); +} + +bool MenuHorizSlider::onEvent(M4EventType event, int param, int x, int y, MenuObject *¤tItem) { + static bool movingFlag = false; + static int movingX = 0; + bool redrawFlag = false, handledFlag = false, callbackFlag = false; + + if (event == KEVENT_KEY) + return false; + + switch (event) { + case MEVENT_LEFT_CLICK: + case MEVENT_DOUBLECLICK: + if (isInside(x, y) && (x - _bounds.left >= _thumbX) && + (x - _bounds.left <= _thumbX + _thumbSize.x - 1)) { + // The thumbnail has been clicked + _sliderState = HSLIDER_THUMB_PRESSED; + movingFlag = true; + movingX = x; + currentItem = this; + redrawFlag = true; + } else { + currentItem = NULL; + _sliderState = HSLIDER_THUMB_NORMAL; + redrawFlag = true; + } + redrawFlag = true; + break; + + case MEVENT_LEFT_DRAG: + case MEVENT_DOUBLECLICK_DRAG: + if (!currentItem) + return true; + + if (movingFlag) { + if (x != movingX) { + if (x < movingX) + _thumbX -= MIN(_thumbX, movingX - x); + else + _thumbX += MIN(_maxThumbX - _thumbX, x - movingX); + _percent = _thumbX * 100 / _maxThumbX; + redrawFlag = callbackFlag = true; + } + movingX = CLIP(x, _bounds.left + _thumbX, + _bounds.left + _thumbX + _thumbSize.x - 1); + } else { + currentItem = NULL; + } + break; + + case MEVENT_LEFT_RELEASE: + case MEVENT_DOUBLECLICK_RELEASE: + if (!currentItem) + return true; + + movingFlag = false; + if (isInside(x, y) && (x - _bounds.left >= _thumbX) && + (x - _bounds.left <= _thumbX + _thumbSize.x - 1)) { + _sliderState = HSLIDER_THUMB_MOUSEOVER; + currentItem = this; + } else { + _sliderState = HSLIDER_THUMB_NORMAL; + currentItem = NULL; + } + + redrawFlag = true; + callbackFlag = true; + break; + + case MEVENT_MOVE: + if (isInside(x, y) && (x - _bounds.left >= _thumbX) && + (x - _bounds.left <= _thumbX + _thumbSize.x - 1)) { + if (_sliderState != HSLIDER_THUMB_MOUSEOVER) { + _sliderState = HSLIDER_THUMB_MOUSEOVER; + currentItem = this; + } + } else { + if (_sliderState != HSLIDER_THUMB_NORMAL) { + _sliderState = HSLIDER_THUMB_NORMAL; + currentItem = NULL; + handledFlag = false; + } + } + redrawFlag = true; + break; + + default: + break; + } + + if (redrawFlag) + onRefresh(); + + if (callbackFlag) + onExecute(); + + return handledFlag; +} + +//-------------------------------------------------------------------------- +// MenuVertSlider class +// +// Defines a vertical slider that's used in the save/load dialog +//-------------------------------------------------------------------------- + +MenuVertSlider::MenuVertSlider(DialogView *owner, int objectId, int xs, int ys, + int width, int height, int initialPercentage, Callback callbackFn, bool transparent): + MenuObject(owner, objectId, xs, ys, width, height, false, transparent) { + + _objectType = OBJTYPE_SLIDER; + _callback = callbackFn; + + SpriteAsset &sprites = *owner->sprites(); + _sliderState = VSLIDER_NONE; + _thumbSize.x = sprites[SL_SLIDER_BTN_NORMAL]->width(); + _thumbSize.y = sprites[SL_SLIDER_BTN_NORMAL]->height(); + _minThumbY = sprites[SL_UP_BTN_NORMAL]->height() + 1; + _maxThumbY = sprites[SL_UP_BTN_NORMAL]->height() + sprites[SL_SCROLLBAR]->height() - + sprites[SL_SLIDER_BTN_NORMAL]->height() - 1; + + _percent = MAX(MIN(initialPercentage, 100), 0); + _thumbY = _minThumbY + ((_percent * (_maxThumbY - _minThumbY)) / 100); +} + +MenuVertSliderState MenuVertSlider::getSliderArea(int y) { + if (y < _minThumbY) + return VSLIDER_UP; + else if (y < _thumbY) + return VSLIDER_PAGE_UP; + else if (y < _thumbY + _thumbSize.y) + return VSLIDER_THUMBNAIL; + else if (y < _maxThumbY + _thumbSize.y) + return VSLIDER_PAGE_DOWN; + else + return VSLIDER_DOWN; +} + +void MenuVertSlider::onRefresh() { + // If the slider is transparent, first copy in the background + if (_transparent) { + // Transparent button + if (!_background) + return; + + _background->copyTo(parent(), _bounds.left, _bounds.top, 0); + } + + // Get the various needed sprites + SpriteAsset &sprites = *parent()->sprites(); + M4Sprite *barSprite = sprites[SL_SCROLLBAR]; + M4Sprite *thumbSprite = sprites[SL_SLIDER_BTN_NORMAL]; + M4Sprite *upSprite = sprites[SL_UP_BTN_NORMAL]; + M4Sprite *downSprite = sprites[SL_DOWN_BTN_NORMAL]; + + if (_objectState == OS_GREYED) { + upSprite = sprites[SL_UP_BTN_GREYED]; + downSprite = sprites[SL_DOWN_BTN_GREYED]; + thumbSprite = NULL; + + } else if (_objectState == OS_MOUSEOVER) { + if (_sliderState == VSLIDER_UP) + upSprite = sprites[SL_UP_BTN_MOUSEOVER]; + else if (_sliderState == VSLIDER_THUMBNAIL) + thumbSprite = sprites[SL_SLIDER_BTN_MOUSEOVER]; + else if (_sliderState == VSLIDER_DOWN) + downSprite = sprites[SL_DOWN_BTN_MOUSEOVER]; + } + else if (_objectState == OS_PRESSED) { + if (_sliderState == VSLIDER_UP) + upSprite = sprites[SL_UP_BTN_PRESSED]; + else if (_sliderState == VSLIDER_THUMBNAIL) + thumbSprite = sprites[SL_SLIDER_BTN_PRESSED]; + else if (_sliderState == VSLIDER_DOWN) + downSprite = sprites[SL_DOWN_BTN_PRESSED]; + } + + // Draw the sprites + upSprite->copyTo(parent(), _bounds.left, _bounds.top, 0); + barSprite->copyTo(parent(), _bounds.left, _bounds.top + upSprite->height(), 0); + downSprite->copyTo(parent(), _bounds.left, _bounds.top + upSprite->height() + barSprite->height(), 0); + if (thumbSprite) + thumbSprite->copyTo(parent(), _bounds.left, _bounds.top + _thumbY, 0); +} + +bool MenuVertSlider::onEvent(M4EventType event, int param, int x, int y, MenuObject *¤tItem) { + static bool movingFlag = false; + static int movingY = 0; + static uint32 callbackTime; + MenuVertSliderState tempState; + int delta; + uint32 currentTime = g_system->getMillis(); + bool redrawFlag = false; + bool handledFlag = true; + bool callbackFlag = false; + + if (event == KEVENT_KEY) + return false; + + if (_objectState == OS_GREYED) { + currentItem = NULL; + return false; + } + + switch (event) { + case MEVENT_LEFT_CLICK: + case MEVENT_DOUBLECLICK: + if (isInside(x, y)) { + currentItem = this; + tempState = getSliderArea(y - _bounds.top); + if (tempState == VSLIDER_THUMBNAIL) { + movingFlag = true; + movingY = y; + } + if ((tempState == VSLIDER_PAGE_UP) || (tempState == VSLIDER_PAGE_DOWN)) { + _sliderState = tempState; + setState(OS_NORMAL); + } else { + _sliderState = tempState; + setState(OS_PRESSED); + redrawFlag = true; + } + callbackFlag = true; + } else { + currentItem = NULL; + setState(OS_NORMAL); + redrawFlag = true; + } + break; + + case MEVENT_LEFT_DRAG: + case MEVENT_DOUBLECLICK_DRAG: + if (!currentItem) + return true; + + if (movingFlag) { + if (y < movingY) { + delta = MIN(_thumbY - _minThumbY, movingY - y); + if (delta > 0) { + _thumbY -= delta; + _percent = ((_thumbY - _minThumbY) * 100) / (_maxThumbY - _minThumbY); + redrawFlag = true; + callbackFlag = true; + } + } + else if (y > movingY) { + delta = MIN(_maxThumbY - _thumbY, y - movingY); + if (delta > 0) { + _thumbY += delta; + _percent = ((_thumbY - _minThumbY) * 100) / (_maxThumbY - _minThumbY); + redrawFlag = true; + callbackFlag = true; + } + } + movingY = y; + + if (movingY < (_thumbY + _bounds.top)) + movingY = _thumbY + _bounds.top; + else if (movingY > (_bounds.top + _thumbY + _thumbSize.y - 1)) + movingY = _bounds.top + _thumbY + _thumbSize.y - 1; + + } else { + if (isInside(x, y)) { + tempState = getSliderArea(y - _bounds.top); + if (_sliderState == tempState) { + if ((tempState != VSLIDER_PAGE_UP) && (tempState != VSLIDER_PAGE_DOWN) && + (_objectState != OS_PRESSED)) { + _sliderState = tempState; + setState(OS_PRESSED); + redrawFlag = true; + } + if (currentTime - callbackTime > 100) + callbackFlag = true; + + } else { + if (_objectState != OS_MOUSEOVER) + setState(OS_MOUSEOVER); + redrawFlag = true; + } + callbackFlag = true; + + } else { + if (_objectState != OS_MOUSEOVER) { + setState(OS_MOUSEOVER); + redrawFlag = true; + } + } + } + break; + + case MEVENT_LEFT_RELEASE: + case MEVENT_DOUBLECLICK_RELEASE: + movingFlag = false; + if (isInside(x, y)) { + tempState = getSliderArea(y - _bounds.top); + if ((tempState == VSLIDER_PAGE_UP) || (tempState == VSLIDER_PAGE_DOWN)) + setState(OS_NORMAL); + else { + setState(OS_MOUSEOVER); + currentItem = this; + } + } else { + setState(OS_NORMAL); + currentItem = NULL; + } + + redrawFlag = true; + if (parent()->getMenuType() == LOAD_MENU) + updateThumbnails(); + break; + + case MEVENT_MOVE: + if (isInside(x, y)) { + currentItem = this; + tempState = getSliderArea(y - _bounds.top); + if (_sliderState != tempState) { + if ((tempState == VSLIDER_PAGE_UP) || (tempState == VSLIDER_PAGE_DOWN)) + _objectState = OS_NORMAL; + else { + _sliderState = tempState; + _objectState = OS_MOUSEOVER; + } + redrawFlag = true; + } + } else { + currentItem = NULL; + + if (_objectState != OS_NORMAL) { + _objectState = OS_NORMAL; + redrawFlag = true; + handledFlag = false; + } + } + break; + + case MEVENT_LEFT_HOLD: + case MEVENT_DOUBLECLICK_HOLD: + if (!currentItem) + return true; + + if (isInside(x, y)) { + tempState = getSliderArea(y - _bounds.top); + + if (_sliderState == tempState) { + if (currentTime - callbackTime > 100) + callbackFlag = true; + } + } + break; + + default: + break; + } + + if (redrawFlag) + onRefresh(); + + if (callbackFlag) { + callbackTime = currentTime; + onExecute(); + } + + return handledFlag; +} + +void MenuVertSlider::setPercentage(int value) { + _percent = value; + _thumbY = _minThumbY + ((_percent * (_maxThumbY - _minThumbY)) / 100); + onRefresh(); +} + +//-------------------------------------------------------------------------- +// MenuMessage class +// +// Defines a message menu object +//-------------------------------------------------------------------------- + +MenuMessage::MenuMessage(DialogView *owner, int objectId, int xs, int ys, int width, int height, + bool transparent): + MenuObject(owner, objectId, xs, ys, width, height, false, transparent) { +} + +void MenuMessage::onRefresh() { + SpriteAsset &sprites = *parent()->sprites(); + M4Surface *sprite = NULL; + + // Get the correct sprite to use + switch (_objectId) { + case SLTAG_SAVELOAD_LABEL: + sprite = (parent()->getMenuType() == SAVE_MENU) ? sprites[SL_SAVE_LABEL] : + sprites[SL_LOAD_LABEL]; + break; + } + assert(sprite); + + // Draw the sprite + if (_transparent) { + // Transparent button + if (!_background) + return; + + // Restore original background and then do a transparent copy of the sprite + _background->copyTo(parent(), _bounds.left, _bounds.top); + } + + sprite->copyTo(parent(), _bounds.left, _bounds.top, 0); +} + +//-------------------------------------------------------------------------- +// MenuImage class +// +// Defines a menu item that displays a given surface +//-------------------------------------------------------------------------- + +MenuImage::MenuImage(DialogView *owner, int objectId, int xs, int ys, int width, int height, + M4Surface *image, bool transparent): + MenuObject(owner, objectId, xs, ys, width, height, false, transparent) { + + _sprite = image; +} + +void MenuImage::onRefresh() { + if (!_sprite) + return; + + // Draw the sprite + if (_transparent) { + // Transparent button + if (!_background) + return; + + // Restore original background and then do a transparent copy of the sprite + _background->copyTo(parent(), _bounds.left, _bounds.top); + } + + _sprite->copyTo(parent(), _bounds.left + (_bounds.width() - _sprite->width()) / 2, + _bounds.top + (_bounds.height() - _sprite->height()) / 2, 0); +} + +//-------------------------------------------------------------------------- +// MenuSaveLoadText class +// +// Defines a save/load dialog text entry +//-------------------------------------------------------------------------- + +MenuSaveLoadText::MenuSaveLoadText(DialogView *owner, int textId, int xs, int ys, + int width, int height, Callback callbackFn, bool greyed, bool transparent, + bool loadFlag, const char *displayText, int displayValue): + MenuButton(owner, textId, xs, ys, width, height, callbackFn, greyed, transparent, OBJTYPE_SL_TEXT) { + + _loadFlag = loadFlag; + _displayText = displayText; + _displayValue = displayValue; + _index = textId - SLTAG_SLOTS_START; + _visible = true; +} + +void MenuSaveLoadText::onRefresh() { + if (!_visible) return; + _vm->_font->setFont(FONT_MENU); + MenuButton::onRefresh(); + + if ((_objectType == OBJTYPE_SL_TEXT) && (_displayText != NULL)) { + int xp = _bounds.left + 4; + if (_displayValue != 0) { + char tempBuffer[5]; + sprintf(tempBuffer, "%02d", _displayValue); + _vm->_font->writeString(_parent, tempBuffer, xp, _bounds.top + 1, 0, -1); + xp = _bounds.left + 26; + } + + _vm->_font->writeString(_parent, _displayText, xp, _bounds.top + 1, 0, -1); + } +} + +bool MenuSaveLoadText::onEvent(M4::M4EventType event, int param, int x, int y, M4::MenuObject *¤tItem) { + if (!_visible) return false; + bool handledFlag = MenuButton::onEvent(event, param, x, y, currentItem); + + // Further handling will only be done when in load mode and a slot hasn't been selected + if (!_loadFlag || (parent()->_selectedSlot != -1)) + return handledFlag; + + // Only handling for certain event types + if ((event != MEVENT_MOVE) && (event != MEVENT_LEFT_DRAG) && + (event != MEVENT_LEFT_RELEASE) && (event != MEVENT_DOUBLECLICK_DRAG) && + (event != MEVENT_DOUBLECLICK_RELEASE)) + return handledFlag; + + MenuImage *thumbnail = (MenuImage *) parent()->getItem(SLTAG_THUMBNAIL); + + // Check whether the cursor is over the button + if ((_objectState == OS_MOUSEOVER) || (_objectState == OS_PRESSED)) { + if (_index != parent()->_highlightedSlot) { + // Selected slot has changed + if (parent()->_savegameThumbnail != NULL) + delete parent()->_savegameThumbnail; + + parent()->_highlightedSlot = _index; + parent()->_savegameThumbnail = _vm->_saveLoad->getThumbnail(_index + 1); + thumbnail->setSprite(parent()->_savegameThumbnail); + } + + } else { + // If the mouse has moved outside the area of all the save slots, then the + // thumbnail needs to be removed + + Common::Rect slotArea(50, 256, 288, 377); + if (isInside(x, y) || !slotArea.contains(x, y)) { + if (parent()->_savegameThumbnail) { + delete parent()->_savegameThumbnail; + parent()->_savegameThumbnail = NULL; + } + + thumbnail->setSprite(parent()->sprites()->getFrame(SL_EMPTY_THUMBNAIL)); + parent()->_highlightedSlot = -1; + } + } + + return handledFlag; +} + +void MenuSaveLoadText::setVisible(bool value) { + _visible = value; + parent()->refresh(_bounds); +} + +//-------------------------------------------------------------------------- +// MenuTextField class +// +// Defines a text entry field +//-------------------------------------------------------------------------- + +MenuTextField::MenuTextField(DialogView *owner, int fieldId, int xs, int ys, int width, + int height, bool greyed, Callback callbackFn, + const char *displayText, int displayValue, bool transparent): + MenuObject(owner, fieldId, xs, ys, width, height, greyed, transparent) { + + _displayValue = displayValue; + _callback = callbackFn; + _pixelWidth = width - 27; + if (displayText) { + strcpy(_displayText, displayText); + _promptEnd = &_displayText[strlen(_displayText)]; + } else { + _displayText[0] = '\0'; + _promptEnd = &_displayText[0]; + } + _cursor = _promptEnd; +} + +void MenuTextField::onRefresh() { + bool focused = _objectState != OS_GREYED; + + // Get the thumb sprite for the slider + SpriteAsset &sprites = *parent()->sprites(); + M4Sprite *sprite = sprites[SL_LINE_NORMAL + OS_MOUSEOVER - 1]; + + // If the slider is transparent, first copy in the background + if (_transparent) { + // Transparent button + if (!_background) + return; + + _background->copyTo(parent(), _bounds.left, _bounds.top, 0); + } + + // Draw the sprite + sprite->copyTo(parent(), _bounds.left, _bounds.top, 0); + + // Draw the text + + _vm->_font->setFont(FONT_MENU); + _vm->_font->setColors(TEXT_COLOR_NORMAL_SHADOW, TEXT_COLOR_NORMAL_FOREGROUND, + TEXT_COLOR_NORMAL_HILIGHT); + int xp = _bounds.left + 4; + + if (_displayValue != 0) { + char tempBuffer[5]; + sprintf(tempBuffer, "%02d", _displayValue); + _vm->_font->writeString(_parent, tempBuffer, xp, _bounds.top + 1, 0, -1); + xp = _bounds.left + 26; + } + + _vm->_font->writeString(_parent, _displayText, xp, _bounds.top + 1, 0, -1); + + if (focused) { + // Draw in the cursor + + if (_cursor) { + // Get the width of the string up to the cursor position + char tempCh = *_cursor; + *_cursor = '\0'; + int stringWidth = _vm->_font->getWidth(_displayText); + *_cursor = tempCh; + + parent()->setColor(TEXT_COLOR_MOUSEOVER_FOREGROUND); + parent()->vLine(xp + stringWidth + 1, _bounds.top + 1, _bounds.top + 12); + } + } +} + +bool MenuTextField::onEvent(M4EventType event, int param, int x, int y, MenuObject *¤tItem) { + char tempStr[MAX_SAVEGAME_NAME]; + int tempLen; + char *tempP; + bool handledFlag = false, redrawFlag = false, callbackFlag = false; + + if (_objectState == OS_GREYED) + return false; + + switch (event) { + case MEVENT_LEFT_CLICK: + case MEVENT_DOUBLECLICK: + parent()->_deleteSaveDesc = false; + if (isInside(x, y)) + currentItem = this; + break; + + case MEVENT_LEFT_RELEASE: + case MEVENT_DOUBLECLICK_RELEASE: + if (!currentItem) + return true; + currentItem = NULL; + + if (isInside(x, y)) { + if (_objectState == OS_MOUSEOVER) { + tempLen = strlen(_displayText); + if (tempLen > 0) { + strcpy(tempStr, _displayText); + tempP = &tempStr[tempLen]; + _vm->_font->setFont(FONT_MENU); + + tempLen = _vm->_font->getWidth(tempStr); + while ((tempP != &tempStr[0]) && (tempLen > x - _bounds.left - 26)) { + *--tempP = '\0'; + tempLen = _vm->_font->getWidth(tempStr); + } + + _cursor = &_displayText[tempP - &tempStr[0]]; + redrawFlag = true; + } + } else if (event == MEVENT_DOUBLECLICK_RELEASE) { + callbackFlag = true; + } + } + break; + + case KEVENT_KEY: + switch (param) { + case Common::KEYCODE_RETURN: + case Common::KEYCODE_KP_ENTER: + parent()->_deleteSaveDesc = false; + callbackFlag = true; + break; + break; + + case Common::KEYCODE_HOME: + parent()->_deleteSaveDesc = false; + _cursor = &_displayText[0]; + redrawFlag = true; + break; + + case Common::KEYCODE_END: + parent()->_deleteSaveDesc = false; + _cursor = _promptEnd; + redrawFlag = true; + break; + + case Common::KEYCODE_LEFT: + case Common::KEYCODE_KP4: + parent()->_deleteSaveDesc = false; + if (_cursor > &_displayText[0]) { + --_cursor; + redrawFlag = true; + } + break; + + case Common::KEYCODE_RIGHT: + case Common::KEYCODE_KP6: + parent()->_deleteSaveDesc = false; + if (_cursor < _promptEnd) { + ++_cursor; + redrawFlag = true; + } + break; + + case Common::KEYCODE_DELETE: + if (parent()->_deleteSaveDesc) { + _displayText[0] = '\0'; + _promptEnd = &_displayText[0]; + _cursor = _promptEnd; + redrawFlag = true; + } else if (_cursor < _promptEnd) { + strcpy(tempStr, _cursor + 1); + strcpy(_cursor, tempStr); + --_promptEnd; + redrawFlag = true; + } + break; + + case Common::KEYCODE_BACKSPACE: + parent()->_deleteSaveDesc = false; + if (_cursor > &_displayText[0]) { + strcpy(tempStr, _cursor); + --_promptEnd; + --_cursor; + strcpy(_cursor, tempStr); + redrawFlag = true; + } + break; + + default: + parent()->_deleteSaveDesc = false; + _vm->_font->setFont(FONT_MENU); + + tempLen = _vm->_font->getWidth(_displayText); + if ((strlen(_displayText) < MAX_SAVEGAME_NAME - 1) && + (tempLen < _pixelWidth - 12) && (param >= 32) && (param <= 127)) { + + // Valid displayable character + if (_cursor < _promptEnd) { + strcpy(tempStr, _cursor); + sprintf(_cursor, "%c%s", (char)param, tempStr); + } else { + *_cursor = (char)param; + *(_cursor + 1) = '\0'; + } + ++_cursor; + ++_promptEnd; + + redrawFlag = true; + } + break; + } + break; + + default: + break; + } + + if (redrawFlag) + onRefresh(); + + if (callbackFlag) + onExecute(); + + return handledFlag; +} + +//-------------------------------------------------------------------------- + + +GUITextField::GUITextField(View *owner, const Common::Rect &bounds): GUIRect(owner, bounds, 0) { +} + +void GUITextField::onRefresh() { + _parent->fillRect(_bounds, _vm->_palette->BLACK); + _vm->_font->setColors(3, 3, 3); + _vm->_font->setFont(FONT_INTERFACE); + _vm->_font->writeString(_parent, _text.c_str(), _bounds.left, _bounds.top, 0, 1); +} + +//-------------------------------------------------------------------------- + +GUIButton::GUIButton(View *owner, const Common::Rect &bounds, int tag, + M4Surface *normalSprite, M4Surface *mouseOverSprite, M4Surface *pressedSprite): + GUIRect(owner, bounds, tag) { + + _normalSprite = normalSprite; + _mouseOverSprite = mouseOverSprite; + _pressedSprite = pressedSprite; + _visible = true; + _tracking = false; +} + +void GUIButton::onRefresh() { + _parent->fillRect(_bounds, _vm->_palette->BLACK); + + if (_visible) { + switch (_buttonState) { + case BUTTON_MOUSEOVER: + _mouseOverSprite->copyTo(_parent, _bounds.left, _bounds.top, 0); + break; + case BUTTON_PRESSED: + _pressedSprite->copyTo(_parent, _bounds.left, _bounds.top, 0); + break; + default: + _normalSprite->copyTo(_parent, _bounds.left, _bounds.top, 0); + break; + } + } +} + +bool GUIButton::onEvent(M4EventType eventType, int param, int x, int y, GUIObject *¤tItem) { + bool result = false; + bool isPressed = (eventType == MEVENT_LEFT_CLICK) || (eventType == MEVENT_LEFT_HOLD) || + (eventType == MEVENT_LEFT_DRAG); + bool isOver = isInside(x, y); + GUIButtonState oldState = _buttonState; + + if (isOver) { + if (isPressed) { + // Button is pressed + if (!_tracking) { + _tracking = true; + _buttonState = BUTTON_PRESSED; + } + + _vm->_globals->invSuppressClickSound = false; + } else { + // Button isn't pressed + if (_tracking) + result = true; + else { + result = false; + _buttonState = BUTTON_MOUSEOVER; + _tracking = false; + } + } + } else { + _tracking = false; + _buttonState = BUTTON_NORMAL; + } + + if (oldState != _buttonState) + onRefresh(); + + return result; +} + +} // End of namespace M4 diff --git a/engines/m4/gui.h b/engines/m4/gui.h new file mode 100644 index 0000000000..3f7e6f20c3 --- /dev/null +++ b/engines/m4/gui.h @@ -0,0 +1,446 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef M4_GUI_H +#define M4_GUI_H + +#include "common/list.h" +#include "common/rect.h" + +#include "m4/viewmgr.h" +#include "m4/events.h" +#include "m4/globals.h" +#include "m4/graphics.h" +#include "m4/saveload.h" +#include "m4/sprite.h" +#include "m4/assets.h" + +namespace M4 { + +class GUIObject; +class MenuObject; +class GUIObjectButton; +class SpriteAsset; +class View; + +enum MenuType {GAME_MENU, OPTIONS_MENU, SAVE_MENU, LOAD_MENU, MAIN_MENU}; + +enum { + BTNID_QUIT = 1, + BTNID_OPTIONS = 2, + BTNID_RESUME = 3, + BTNID_SAVE = 4, + BTNID_LOAD = 5, + BTNID_MAIN = 6, + + OPTIONID_DONE = 1, + OPTIONID_CANCEL = 2, + OPTIONID_HSLIDER_DIGI = 3, + OPTIONID_HSLIDER_MIDI = 4, + + SAVELOADID_SAVE = 1, + SAVELOADID_CANCELSAVE = 2, + SAVELOADID_LOAD = 3, + SAVELOADID_CANCELLOAD = 4, + + SLTAG_SAVELOAD = 100, + SLTAG_SAVELOAD_LABEL = 101, + SLTAG_CANCEL = 102, + SLTAG_VSLIDER = 103, + SLTAG_THUMBNAIL = 104, + + SLTAG_SLOTS_START = 1001, + SLTAG_TEXTFIELD = 2000 +}; + +enum ObjectType { + OBJTYPE_BUTTON, + + OBJTYPE_SLIDER, + OBJTYPE_OM_SWITCH_ON, + OBJTYPE_OM_SWITCH_OFF, + OBJTYPE_OM_DONE, + OBJTYPE_OM_CANCEL, + + OBJTYPE_SL_SAVE, + OBJTYPE_SL_LOAD, + OBJTYPE_SL_CANCEL, + OBJTYPE_SL_TEXT +}; + +enum GameMenuSpriteType { + GM_DIALOG_BOX, + + GM_BUTTON_GREYED, + GM_BUTTON_NORMAL, + GM_BUTTON_MOUSEOVER, + GM_BUTTON_PRESSED +}; + +enum OptionMenuSpriteType { + OM_DIALOG_BOX, + + OM_SLIDER_BTN_NORMAL, + OM_SLIDER_BTN_MOUSEOVER, + OM_SLIDER_BTN_PRESSED, + OM_SLIDER_BAR, + OM_DONE_BTN_GREYED, + OM_DONE_BTN_NORMAL, + OM_DONE_BTN_MOUSEOVER, + OM_DONE_BTN_PRESSED, + OM_CANCEL_BTN_NORMAL, + OM_CANCEL_BTN_MOUSEOVER, + OM_CANCEL_BTN_PRESSED +}; + + +enum SaveLoadSpriteType { + SL_DIALOG_BOX, + SL_EMPTY_THUMBNAIL, + + SL_SAVE_BTN_GREYED, + SL_SAVE_BTN_NORMAL, + SL_SAVE_BTN_MOUSEOVER, + SL_SAVE_BTN_PRESSED, + + SL_LOAD_BTN_GREYED, + SL_LOAD_BTN_NORMAL, + SL_LOAD_BTN_MOUSEOVER, + SL_LOAD_BTN_PRESSED, + + SL_CANCEL_BTN_NORMAL, + SL_CANCEL_BTN_MOUSEOVER, + SL_CANCEL_BTN_PRESSED, + + SL_UP_BTN_GREYED, + SL_UP_BTN_NORMAL, + SL_UP_BTN_MOUSEOVER, + SL_UP_BTN_PRESSED, + + SL_DOWN_BTN_GREYED, + SL_DOWN_BTN_NORMAL, + SL_DOWN_BTN_MOUSEOVER, + SL_DOWN_BTN_PRESSED, + + SL_SAVE_LABEL, + SL_LOAD_LABEL, + + SL_SLIDER_BTN_NORMAL, + SL_SLIDER_BTN_MOUSEOVER, + SL_SLIDER_BTN_PRESSED, + + SL_LINE_NORMAL, + SL_LINE_MOUSEOVER, + SL_LINE_PRESSED, + + SL_SCROLLBAR +}; + +enum TextColors { + TEXT_COLOR_NORMAL = 1, + TEXT_COLOR_GREYED = 1, + TEXT_COLOR_MOUSEOVER = 2, + TEXT_COLOR_PRESSED = 2, + + TEXT_COLOR_GREYED_HILIGHT = 236, + TEXT_COLOR_GREYED_FOREGROUND = 131, + TEXT_COLOR_GREYED_SHADOW = 186, + + TEXT_COLOR_NORMAL_HILIGHT = 129, + TEXT_COLOR_NORMAL_FOREGROUND = 130, + TEXT_COLOR_NORMAL_SHADOW = 236, + + TEXT_COLOR_MOUSEOVER_HILIGHT = 129, + TEXT_COLOR_MOUSEOVER_FOREGROUND = 130, + TEXT_COLOR_MOUSEOVER_SHADOW = 236, + + TEXT_COLOR_PRESSED_HILIGHT = 236, + TEXT_COLOR_PRESSED_FOREGROUND = 130, + TEXT_COLOR_PRESSED_SHADOW = 129 +}; + +#define MENU_SS_FIELD_NORMAL 5 +#define MENU_SS_FIELD_MOUSEOVER 6 + +#define MENU_SS_H_SLIDER_NORMAL 5 +#define MENU_SS_H_SLIDER_MOUSE_OVER 6 +#define MENU_SS_H_SLIDER_PRESSED 7 + +#define MENU_SS_SWITCH_ON_NORMAL 8 +#define MENU_SS_SWITCH_ON_MOUSEOVER 9 +#define MENU_SS_SWITCH_ON_PRESSED 13 + +#define MENU_SS_SWITCH_OFF_PRESSED 10 +#define MENU_SS_SWITCH_OFF_NORMAL 11 +#define MENU_SS_SWITCH_OFF_MOUSEOVER 12 + +#define MENU_GUI "gui menu" +#define MENU_GAME "gamemenu" +#define MENU_OPTIONS "opmenu" +#define MENU_ERROR "errmenu" +#define MENU_SAVELOAD "slmenu" +#define MENU_BURGMAIN "903menu" +#define MENU_BURGDEMOMAIN "901menu" + +#define SL_NUM_VISIBLE_SLOTS 8 +#define SL_THUMBNAIL_WIDTH 215 +#define SL_THUMBNAIL_HEIGHT 162 + +enum MenuObjectState {OS_GREYED = 0, OS_NORMAL = 1, OS_MOUSEOVER = 2, OS_PRESSED = 3}; + +class DialogView: public View { +public: + DialogView(M4Engine *Vm, const Common::Rect &viewBounds, bool transparent = false): + View(Vm, viewBounds, transparent) {}; + DialogView(M4Engine *Vm, int x = 0, int y = 0, bool transparent = false): + View(Vm, x, y, transparent) {}; + + M4Engine *vm() { return _vm; } + virtual SpriteAsset *sprites() = 0; + virtual MenuType getMenuType() = 0; + virtual MenuObject *getItem(int objectId) { return NULL; } + virtual void refresh(const Common::Rect &areaRect) {} + virtual void close(); + + int _topSaveSlotIndex, _selectedSlot; + int _highlightedSlot; + bool _deleteSaveDesc; + M4Surface *_savegameThumbnail; +}; + +class GUIObject { +protected: + View *_parent; + Common::Rect _bounds; + M4Surface *_background; +public: + GUIObject(View *owner, const Common::Rect &bounds); + virtual ~GUIObject() {} + + bool isInside(int x, int y) { return _bounds.contains(x, y); } + Common::Rect getBounds() const { return _bounds; } + + virtual void onRefresh() {} +}; + +class MenuObject: public GUIObject { +public: + typedef void (*Callback)(DialogView *view, MenuObject *item); +protected: + MenuObject::Callback _callback; + ObjectType _objectType; + MenuObjectState _objectState; + bool _transparent; + int _objectId; +public: + MenuObject(DialogView *owner, int objectId, int xs, int ys, int width, int height, + bool greyed = false, bool transparent = false); + ~MenuObject(); + + DialogView *parent() { return (DialogView *)_parent; } + MenuObjectState getState() { return _objectState; } + virtual void setState(MenuObjectState state) { + _objectState = state; + onRefresh(); + } + int getObjectId() { return _objectId; } + + void onExecute(); + virtual bool onEvent(M4EventType event, int param, int x, int y, MenuObject *¤tItem) { return false; } +}; + +class MenuButton: public MenuObject { +public: + MenuButton(DialogView *owner, int buttonId, int xs, int ys, int width, int height, + Callback callbackFn = NULL, bool greyed = false, bool transparent = false, + ObjectType buttonType = OBJTYPE_BUTTON); + + void onRefresh(); + bool onEvent(M4EventType event, int param, int x, int y, MenuObject *¤tItem); + bool getToggled() { return _objectType == OBJTYPE_OM_SWITCH_ON; } +}; + +enum MenuHorizSliderState {HSLIDER_THUMB_NORMAL = 0, HSLIDER_THUMB_MOUSEOVER = 1, HSLIDER_THUMB_PRESSED = 2}; +#define SLIDER_BAR_COLOR 129 + +class MenuHorizSlider: public MenuObject { +protected: + MenuHorizSliderState _sliderState; + Common::Point _thumbSize; + int _maxThumbX; + int _percent; + int _thumbX; +public: + MenuHorizSlider(DialogView *owner, int sliderId, int xs, int ys, int width, int height, + int initialPercentage, Callback callbackFn = NULL, bool transparent = false); + + void onRefresh(); + bool onEvent(M4EventType event, int param, int x, int y, MenuObject *¤tItem); + int percent() { return _percent; } +}; + +enum MenuVertSliderState { + VSLIDER_NONE = 0x0000, + VSLIDER_UP = 0x0010, + VSLIDER_PAGE_UP = 0x0020, + VSLIDER_THUMBNAIL = 0x0030, + VSLIDER_PAGE_DOWN = 0x0040, + VSLIDER_DOWN = 0x0050 +}; + +class MenuVertSlider: public MenuObject { +protected: + MenuVertSliderState _sliderState; + Common::Point _thumbSize; + int _percent; + int _thumbY; + int _minThumbY; + int _maxThumbY; + + MenuVertSliderState getSliderArea(int y); + void updateThumbnails() {} +public: + MenuVertSlider(DialogView *owner, int sliderId, int xs, int ys, int width, int height, + int initialPercentage, Callback callbackFn = NULL, bool transparent = false); + + void onRefresh(); + bool onEvent(M4EventType event, int param, int x, int y, MenuObject *¤tItem); + MenuVertSliderState sliderState() { return _sliderState; } + int percent() { return _percent; } + void setPercentage(int value); +}; + +class MenuMessage: public MenuObject { +public: + MenuMessage(DialogView *owner, int objectId, int x, int y, int w, int h, bool transparent = false); + + void onRefresh(); +}; + +class MenuImage: public MenuObject { +private: + M4Surface *_sprite; +public: + MenuImage(DialogView *owner, int objectId, int xs, int ys, int width, int height, + M4Surface *image, bool transparent = false); + + void onRefresh(); + const M4Surface *sprite() { return _sprite; } + void setSprite(M4Surface *v) { + _sprite = v; + onRefresh(); + } +}; + +class MenuSaveLoadText: public MenuButton { +private: + bool _loadFlag; + const char *_displayText; + int _displayValue; + int _index; + bool _visible; +public: + MenuSaveLoadText(DialogView *owner, int textId, int xs, int ys, int width, int height, + Callback callbackFn = NULL, bool greyed = false, bool transparent = false, + bool loadFlag = false, const char *displayText = NULL, int displayValue = 0); + + void onRefresh(); + bool onEvent(M4EventType event, int param, int x, int y, MenuObject *¤tItem); + void setDisplay(int value, const char *text) { _displayValue = value; _displayText = text; } + int getIndex() { return _index; } + const char *getText() { return _displayText; } + bool getVisible() const { return _visible; } + void setVisible(bool value); +}; + +class MenuTextField: public MenuObject { +private: + int _displayValue; + char _displayText[MAX_SAVEGAME_NAME]; + int _pixelWidth; + char *_promptEnd; + char *_cursor; +public: + MenuTextField(DialogView *owner, int fieldId, int xs, int ys, int width, int height, + bool greyed, Callback callbackFn = NULL, const char *displayText = NULL, + int displayValue = 0, bool transparent = true); + + void onRefresh(); + bool onEvent(M4EventType event, int param, int x, int y, MenuObject *¤tItem); + + const char *getText() { return _displayText; } + int getDisplayValue() { return _displayValue; } + +}; + +class GUIRect: public GUIObject { +private: + int _tag; +public: + GUIRect(View *owner, const Common::Rect &bounds, int tag): GUIObject(owner, bounds) { _tag = tag; }; + + virtual bool onEvent(M4EventType eventType, int param, int x, int y, GUIObject *¤tItem) { return false; } + int getTag() const { return _tag; } +}; + +enum GUIButtonState {BUTTON_NORMAL, BUTTON_MOUSEOVER, BUTTON_PRESSED}; + +class GUIButton: public GUIRect { +protected: + M4Surface *_normalSprite, *_mouseOverSprite, *_pressedSprite; + GUIButtonState _buttonState; + bool _visible; + bool _tracking; +public: + GUIButton(View *owner, const Common::Rect &bounds, int tag, + M4Surface *normalSprite, M4Surface *mouseOverSprite, M4Surface *pressedSprite); + + void onRefresh(); + bool onEvent(M4EventType eventType, int param, int x, int y, GUIObject *¤tItem); + GUIButtonState getState() const { return _buttonState; } +}; + +class GUITextField: public GUIRect { +private: + Common::String _text; +public: + GUITextField(View *owner, const Common::Rect &bounds); + void setText(const char *value) { + _text = value; + onRefresh(); + } + + void onRefresh(); +}; + + +class Dialogs { +public: + void keyMouseCollision() {} +}; + +} + +#endif diff --git a/engines/m4/hotspot.cpp b/engines/m4/hotspot.cpp new file mode 100644 index 0000000000..bf791e09fc --- /dev/null +++ b/engines/m4/hotspot.cpp @@ -0,0 +1,293 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "m4/m4.h" +#include "m4/events.h" +#include "m4/hotspot.h" +#include "gui/debugger.h" + +namespace M4 { + +/* + HotSpot +*/ + +HotSpot::HotSpot(int x1, int y1, int x2, int y2) : + _vocab(NULL), _verb(NULL), _prep(NULL), _sprite(NULL) { + + _rect.left = x1; + _rect.top = y1; + _rect.right = x2 + 1; + _rect.bottom = y2 + 1; + _active = true; + + _syntax = 0; + _cursor = 0; + _facing = 5; + _feetX = 32767; + _feetY = 32767; +} + +HotSpot::~HotSpot() { +} + +void HotSpot::setRect(int x1, int y1, int x2, int y2) { + _rect.left = x1; + _rect.top = y1; + _rect.right = x2 + 1; + _rect.bottom = y2 + 1; +} + +void HotSpot::setFeet(int x, int y) { + _feetX = x; + _feetY = y; +} + +void HotSpot::setVocab(const char *value) { + free(_vocab); + _vocab = strdup(value); +} + +void HotSpot::setVerb(const char *value) { + free(_verb); + _verb = strdup(value); +} + +void HotSpot::setPrep(const char *value) { + free(_prep); + _prep = strdup(value); +} + +void HotSpot::setSprite(const char *value) { + free(_sprite); + _sprite = strdup(value); +} + +Common::Rect HotSpot::getRect() const { + Common::Rect tempRect; + tempRect.left = _rect.left; + tempRect.top = _rect.top; + tempRect.right = _rect.right - 1; + tempRect.bottom = _rect.bottom - 1; + + return tempRect; +} + +/* + HotSpotList +*/ + +HotSpotList::HotSpotList() { +} + +HotSpotList::~HotSpotList() { + clear(); +} + +int HotSpotList::add(HotSpot *hotspot, bool head) { + if (head || _hotspots.size() == 0) { + _hotspots.insert_at(0, hotspot); + return 0; + } else { + int32 area = hotspot->area(); + int index = _hotspots.size(); + for (uint i = 0; i < _hotspots.size(); i++) { + if (area < _hotspots[i]->area()) { + index = i; + break; + } + } + _hotspots.insert_at(index, hotspot); + return index; + } +} + +void HotSpotList::remove(HotSpot *hotspot) { + unlink(hotspot); + delete hotspot; //TODO: check this? +} + +void HotSpotList::unlink(HotSpot *hotspot) { + uint index = 0; + while (index < _hotspots.size()) { + if (_hotspots[index] == hotspot) { + _hotspots.remove_at(index); + } else { + index++; + } + } +} + +void HotSpotList::clear() { + for (uint i = 0; i < _hotspots.size(); i++) + delete _hotspots[i]; + _hotspots.clear(); +} + +HotSpot *HotSpotList::findByXY(int x, int y) { + for (uint i = 0; i < _hotspots.size(); i++) { + if (_hotspots[i]->getActive() && _hotspots[i]->pointInside(x, y)) { + return _hotspots[i]; + } + } + return NULL; +} + +void HotSpotList::setActive(const char *name, bool active) { + for (uint i = 0; i < _hotspots.size(); i++) { + if (!scumm_stricmp(_hotspots[i]->_vocab, name)) { + _hotspots[i]->setActive(active); + } + } +} + +void HotSpotList::setActiveXY(const char *name, int x, int y, bool active) { + for (uint i = 0; i < _hotspots.size(); i++) { + if (_hotspots[i]->pointInside(x, y) && !scumm_stricmp(_hotspots[i]->_vocab, name)) { + _hotspots[i]->setActive(active); + } + } +} + +void HotSpotList::dump() { + _vm->_events->getConsole()->DebugPrintf("%d hotspots in the list\n", _hotspots.size()); + + for (uint index = 0; index < _hotspots.size(); index++) { + _vm->_events->getConsole()->DebugPrintf("(%d): %p x1 = %d; y1 = %d; x2 = %d; y2 = %d\n", + index, (void *)_hotspots[index], + _hotspots[index]->_rect.left, _hotspots[index]->_rect.top, + _hotspots[index]->_rect.right - 1, _hotspots[index]->_rect.bottom - 1); + } +} + +void HotSpotList::loadHotSpotsM4(Common::SeekableReadStream* hotspotStream, int hotspotCount) { + uint32 x1, y1, x2, y2; + char buffer[256]; + uint32 strLength = 0; + uint32 index = 0; + HotSpot *currentHotSpot; + uint32 feetX, feetY; + + for (int i = 0; i < hotspotCount; i++) { + x1 = hotspotStream->readUint32LE(); + y1 = hotspotStream->readUint32LE(); + x2 = hotspotStream->readUint32LE(); + y2 = hotspotStream->readUint32LE(); + index = add(new HotSpot(x1, y1, x2, y2), i == 0); + currentHotSpot = get(index); + feetX = hotspotStream->readUint32LE(); + feetY = hotspotStream->readUint32LE(); + currentHotSpot->setFeet(feetX, feetY); + currentHotSpot->setFacing((uint8)hotspotStream->readByte()); + currentHotSpot->setActive(hotspotStream->readByte() != 0); + currentHotSpot->setCursor((uint8)hotspotStream->readByte()); + hotspotStream->readByte(); // syntax (unused) + hotspotStream->readUint32LE(); // vocabID + hotspotStream->readUint32LE(); // verbID + strLength = hotspotStream->readUint32LE(); // vocabLength + hotspotStream->read(buffer, strLength); // vocab (the hotspot's name) + // Capitalize the hotspot's name here + str_upper(buffer); + currentHotSpot->setVocab(buffer); + // Verbs are used internally by the game scripts in Orion Burger + strLength = hotspotStream->readUint32LE(); // verbLength + hotspotStream->read(buffer, strLength); // verb + // Capitalize the hotspot's verb here + str_upper(buffer); + currentHotSpot->setVerb(buffer); + strLength = hotspotStream->readUint32LE(); // prepLength + hotspotStream->read(buffer, strLength); // prep + str_upper(buffer); + + /* Hotspot names for non-English versions are stored in prep. + Prep can be set two ways: For English versions, copy the + text from vocab. For non-English versions, use the prep text + from the room file. + */ + if (strlen(buffer) > 0 && strcmp(buffer, "--") != 0 && strcmp(buffer, "ON") != 0) + currentHotSpot->setPrep(buffer); + else + currentHotSpot->setPrep(currentHotSpot->getVocab()); + + // The following values are not used at all by Orion Burger + strLength = hotspotStream->readUint32LE(); // spriteLength + hotspotStream->read(buffer, strLength); // sprite + hotspotStream->readUint16LE(); // sprite hash + } +} + +void HotSpotList::loadHotSpotsMads(Common::SeekableReadStream* hotspotStream, int hotspotCount) { + uint16 x1, y1, x2, y2; + HotSpot *currentHotSpot; + uint16 feetX, feetY; + uint16 index = 0; + + for (int i = 0; i < hotspotCount; i++) { + x1 = hotspotStream->readUint16LE(); + y1 = hotspotStream->readUint16LE(); + x2 = hotspotStream->readUint16LE(); + y2 = hotspotStream->readUint16LE(); + index = add(new HotSpot(x1, y1, x2, y2), i == 0); + currentHotSpot = get(index); + //printf("x1, y1, x2, y2: %i %i %i %i\n", x1, y1, x2, y2); + feetX = hotspotStream->readUint16LE(); + feetY = hotspotStream->readUint16LE(); + currentHotSpot->setFeet(feetX, feetY); + currentHotSpot->setFacing((uint8)hotspotStream->readByte()); + index = hotspotStream->readByte(); // unknown (initial facing?) + + hotspotStream->readByte(); // unused (always 255) + + index = hotspotStream->readByte(); // cursor + if (index == 0) + currentHotSpot->setCursor(0); + else + currentHotSpot->setCursor(index - 1); + + // Rex Nebular doesn't have this field + if (_vm->getGameType() != GType_RexNebular) { + // This looks to be some sort of bitmask. Perhaps it signifies + // the valid verbs for this hotspot + index = hotspotStream->readUint16LE(); // unknown + //printf("%i ", index); + } + + index = hotspotStream->readUint16LE(); // noun index + currentHotSpot->setVocabID(index); + currentHotSpot->setVocab(_vm->_globals->getVocab(index - 1)); + index = hotspotStream->readUint16LE(); // verb index (default left click verb) + currentHotSpot->setVerbID(index); + if (index != 0) { + currentHotSpot->setVerb(_vm->_globals->getVocab(index - 1)); + } else { + currentHotSpot->setVerb(""); + } + //printf("%s ", currentHotSpot->getVerb()); + //printf("%s ", currentHotSpot->getVocab()); + //printf("\n"); + } +} + +} // End of namespace M4 diff --git a/engines/m4/hotspot.h b/engines/m4/hotspot.h new file mode 100644 index 0000000000..bcd2aba919 --- /dev/null +++ b/engines/m4/hotspot.h @@ -0,0 +1,120 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef M4_HOTSPOT_H +#define M4_HOTSPOT_H + +#include "common/array.h" +#include "common/rect.h" +#include "common/util.h" + +/* + TODO: + - check if hotspot_duplicate is needed + NOTES: + - hotspot_add_dynamic unused in Orion Burger +*/ + +//???: should String be used instead of char* here? + +namespace M4 { + +class HotSpot { + friend class HotSpotList;//just for debugging, to be removed later +public: + + HotSpot(int x1 = 0, int y1 = 0, int x2 = 0, int y2 = 0); + ~HotSpot(); + + void setVocab(const char *value); + void setVocabID(int32 value) { _vocabID = value; } + void setVerb(const char *value); + void setVerbID(int32 value) { _verbID = value; } + void setPrep(const char *value); + void setSprite(const char *value); + void setActive(bool value) { _active = value; } + void setCursor(uint8 value) { _cursor = value; } + void setRect(int x1, int y1, int x2, int y2); + void setFeet(int x, int y); + void setFacing(uint8 facing) { _facing = facing; } + char *getVocab() const { return _vocab; } + int32 getVocabID() { return _vocabID; } + char *getVerb() const { return _verb; } + int32 getVerbID() { return _verbID; } + char *getPrep() const { return _prep; } + char *getSprite() const { return _sprite; } + uint8 getCursor() const { return _cursor; } + bool getActive() const { return _active; } + uint8 getFacing() const { return _facing; } + int getFeetX() { return _feetX; } + int getFeetY() { return _feetY; } + Common::Rect getRect() const; + + int32 area() const { return (_rect.width() - 1) * (_rect.height() - 1); } + bool pointInside(int x, int y) { return _rect.contains(x, y); } + +private: + char *_vocab, *_verb, *_prep, *_sprite; + Common::Rect _rect; + bool _active; + int _feetX, _feetY; + uint8 _facing, _cursor; + + // Unused in Orion Burger, used in MADS games + uint8 _syntax; + int32 _vocabID, _verbID; + //TODO: check if this is actually needed by the game + int16 _hash; +}; + +class HotSpotList { +public: + HotSpotList(); + ~HotSpotList(); + + int add(HotSpot *hotspot, bool head = false); + HotSpot *get(int index) { return _hotspots[index]; } + void remove(HotSpot *hotspot); + void unlink(HotSpot *hotspot); + void clear(); + HotSpot *findByXY(int x, int y); + void setActive(const char *name, bool active); + void setActiveXY(const char *name, int x, int y, bool active); + + void dump(); + + void loadHotSpotsM4(Common::SeekableReadStream* hotspotStream, int hotspotCount); + void loadHotSpotsMads(Common::SeekableReadStream* hotspotStream, int hotspotCount); + +private: + typedef Common::Array<HotSpot*> HotSpotArray; + HotSpotArray _hotspots; +}; + + +} // End of namespace M4 + + +#endif diff --git a/engines/m4/m4.cpp b/engines/m4/m4.cpp new file mode 100644 index 0000000000..6707639d1c --- /dev/null +++ b/engines/m4/m4.cpp @@ -0,0 +1,555 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +//#define SCRIPT_TEST +//#define INTRO_TEST + +#include "m4/globals.h" +#include "m4/burger_data.h" +#include "m4/m4.h" +#include "m4/resource.h" +#include "m4/sprite.h" +#include "m4/hotspot.h" +#include "m4/font.h" +#include "m4/rails.h" +#include "m4/midi.h" +#include "m4/events.h" +#include "m4/graphics.h" +#include "m4/viewmgr.h" +#include "m4/gui.h" +#include "m4/woodscript.h" +#include "m4/actor.h" +#include "m4/sound.h" +#include "m4/rails.h" +#include "m4/script.h" +#include "m4/compression.h" +#include "m4/animation.h" +#include "m4/m4_menus.h" +#include "m4/m4_views.h" +#include "m4/mads_anim.h" +#include "m4/mads_menus.h" + +#include "common/file.h" +#include "common/events.h" +#include "common/endian.h" +#include "common/system.h" +#include "common/config-manager.h" +#include "engines/engine.h" +#include "graphics/surface.h" +#include "sound/mididrv.h" + +namespace M4 { + +// FIXME: remove global +M4Engine *_vm; + +void escapeHotkeyHandler(M4Engine *vm, View *view, uint32 key) { + // For now, simply exit the game + vm->_events->quitFlag = true; +} + +// Temporary hotkey handler for use in testing the TextviewView class + +void textviewHotkeyHandler(M4Engine *vm, View *view, uint32 key) { + // Deactivate the scene if it's currently active + View *sceneView = vm->_viewManager->getView(VIEWID_SCENE); + if (sceneView != NULL) + vm->_viewManager->deleteView(sceneView); + + // Activate the textview view + vm->_font->setFont(FONT_CONVERSATION_MADS); + TextviewView *textView = new TextviewView(vm); + vm->_viewManager->addView(textView); + textView->setScript("quotes", NULL); +} + +void saveGameHotkeyHandler(M4Engine *vm, View *view, uint32 key) { + // TODO: See CreateF2SaveMenu - save menu should only be activated when + // certain conditions are met, such as player_commands_allowed, and isInterfaceVisible + vm->loadMenu(SAVE_MENU, true); +} + +void loadGameHotkeyHandler(M4Engine *vm, View *view, uint32 key) { + // TODO: See CreateF3LoadMenu - save menu should only be activated when + // certain conditions are met, such as player_commands_allowed, and isInterfaceVisible + vm->loadMenu(LOAD_MENU, true); +} + +void gameMenuHotkeyHandler(M4Engine *vm, View *view, uint32 key) { + vm->loadMenu(GAME_MENU); +} + +M4Engine::M4Engine(OSystem *syst, const M4GameDescription *gameDesc) : + Engine(syst), _gameDescription(gameDesc) { + + // FIXME + _vm = this; + + Common::File::addDefaultDirectory(_gameDataPath); + Common::File::addDefaultDirectory("goodstuf"); + Common::File::addDefaultDirectory("resource"); + + Common::addSpecialDebugLevel(kDebugScript, "script", "Script debug level"); + Common::addSpecialDebugLevel(kDebugConversations, "conversations", "Conversations debugging"); +} + + +M4Engine::~M4Engine() { + delete _globals; + delete _midi; + delete _saveLoad; + delete _kernel; + delete _player; + delete _mouse; + delete _events; + delete _font; + delete _actor; +// delete _scene; // deleted by the viewmanager + delete _dialogs; + delete _screen; + delete _inventory; + delete _viewManager; + delete _rails; + delete _converse; + delete _script; + delete _ws; + delete _random; + delete _animation; + delete _palette; + delete _resourceManager; +} + +int M4Engine::init() { + // Initialize backend + _system->beginGFXTransaction(); + initCommonGFX(isM4()); + if (isM4()) + _system->initSize(640, 480); + else + _system->initSize(320, 200); + _system->endGFXTransaction(); + + _screen = new M4Surface(true); // Special form for creating screen reference + + int midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MIDI); + bool native_mt32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32")); + + MidiDriver *driver = MidiDriver::createMidi(midiDriver); + if (native_mt32) + driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); + + _midi = new MidiPlayer(this, driver); + _midi->setGM(true); + _midi->setNativeMT32(native_mt32); + + _globals = new Globals(this); + if (isM4()) + _resourceManager = new M4ResourceManager(this); + else + _resourceManager = new MADSResourceManager(this); + _saveLoad = new SaveLoad(this); + _palette = new Palette(this); + _mouse = new Mouse(this); + _events = new Events(this); + _kernel = new Kernel(this); + _player = new Player(this); + _font = new Font(this); + if (getGameType() == GType_Burger) { + _actor = new Actor(this); + _interfaceView = new GameInterfaceView(this); + _conversationView = new ConversationView(this); + } else { + _actor = NULL; + } + _rails = new Rails(); // needs to be initialized before _scene + _scene = new Scene(this); + _dialogs = new Dialogs(); + _viewManager = new ViewManager(this); + _inventory = new Inventory(this); + _sound = new Sound(this, _mixer, 255); + _converse = new Converse(this); + _script = new ScriptInterpreter(this); + _ws = new WoodScript(this); + _animation = new Animation(this); + //_callbacks = new Callbacks(this); + _random = new Common::RandomSource(); + g_system->getEventManager()->registerRandomSource(*_random, "m4"); + + return 0; +} + +void M4Engine::eventHandler() { + M4EventType event; + uint32 keycode = 0; + + if ((event = _events->handleEvents()) != MEVENT_NO_EVENT) { + if (_viewManager->viewCount() > 0) + _viewManager->handleMouseEvents(event); + } + + if (_events->kbdCheck(keycode)) + _viewManager->handleKeyboardEvents(keycode); +} + +bool M4Engine::delay(int duration, bool keyAborts, bool clickAborts) { + uint32 endTime = g_system->getMillis() + duration; + M4EventType event; + uint32 keycode = 0; + + while (!_events->quitFlag && (g_system->getMillis() < endTime)) { + event = _events->handleEvents(); + if (clickAborts && (event == MEVENT_LEFT_RELEASE) || (event == MEVENT_RIGHT_RELEASE)) + return true; + + if (_events->kbdCheck(keycode)) { + if (keyAborts) + return true; + } + + g_system->delayMillis(10); + } + + return false; +} + +void M4Engine::loadMenu(MenuType menuType, bool loadSaveFromHotkey, bool calledFromMainMenu) { + if (isM4() && (menuType != MAIN_MENU)) { + bool menuActive = _viewManager->getView(VIEWID_MENU) != NULL; + + if (!menuActive) + _palette->fadeToGreen(M4_DIALOG_FADE_STEPS, M4_DIALOG_FADE_DELAY); + } + + View *view; + + switch (menuType) { + case MAIN_MENU: + if (getGameType() == GType_RexNebular) + view = new RexMainMenuView(this); + else if (getGameType() == GType_DragonSphere) + view = new DragonMainMenuView(this); + else + view = new MadsMainMenuView(this); + break; + case GAME_MENU: + view = new OrionMenuView(this, 200, 100, GAME_MENU, calledFromMainMenu, loadSaveFromHotkey); + break; + case OPTIONS_MENU: + view = new OrionMenuView(this, 172, 160, OPTIONS_MENU, calledFromMainMenu, loadSaveFromHotkey); + break; + case LOAD_MENU: + case SAVE_MENU: + view = new OrionMenuView(this, 145, 10, menuType, calledFromMainMenu, loadSaveFromHotkey); + break; + default: + error("Unknown menu type"); + break; + } + + _viewManager->addView(view); + _viewManager->moveToFront(view); +} + +int M4Engine::go() { + if (isM4()) + return goM4(); + else + return goMADS(); +} + +int M4Engine::goMADS() { + _palette->setMadsSystemPalette(); + + _mouse->init("cursor.ss", NULL); + _mouse->setCursorNum(0); + + // Load MADS data files + _globals->loadMadsVocab(); // vocab.dat + _globals->loadMadsQuotes(); // quotes.dat + _globals->loadMadsMessagesInfo(); // messages.dat + // TODO: objects.dat + // TODO: hoganus.dat (what is it used for?) + + // Test code to dump all messages to the console + //for (int i = 0; i < _globals->getMessagesSize(); i++) + //printf("%s\n----------\n", _globals->loadMessage(i)); + + if ((getGameType() == GType_RexNebular) || (getGameType() == GType_DragonSphere)) { + loadMenu(MAIN_MENU); + + } else { + if (getGameType() == GType_DragonSphere) { + _scene->loadScene(FIRST_SCENE); + } else if (getGameType() == GType_Phantom) { + //_scene->loadScene(FIRST_SCENE); + _scene->loadScene(106); // a more interesting scene + } + + _viewManager->addView(_scene); + + _font->setFont(FONT_MAIN_MADS); + _font->setColors(2, 1, 3); + _font->writeString(_scene->getBackgroundSurface(), "Testing the M4/MADS ScummVM engine", 5, 160, 310, 2); + _font->writeString(_scene->getBackgroundSurface(), "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 5, 180, 310, 2); + + if (getGameType() == GType_DragonSphere) { + //_scene->showMADSV2TextBox("Test", 10, 10, NULL); + } + + _mouse->cursorOn(); + } + + _viewManager->systemHotkeys().add(Common::KEYCODE_ESCAPE, &escapeHotkeyHandler); + _viewManager->systemHotkeys().add(Common::KEYCODE_KP_MULTIPLY, &textviewHotkeyHandler); + + // Load the general game SFX/voices + if (getGameType() == GType_RexNebular) { + _sound->loadDSRFile("rex009.dsr"); + } else if (getGameType() == GType_Phantom) { + _sound->loadDSRFile("phan009.dsr"); + } else if (getGameType() == GType_DragonSphere) { + _sound->loadDSRFile("drag009.dsr"); + } + + uint32 nextFrame = g_system->getMillis(); + while (!_events->quitFlag) { + eventHandler(); + + _animation->updateAnim(); + + // Call the updateState method of all views + _viewManager->updateState(); + + if (g_system->getMillis() >= nextFrame) { + + _viewManager->refreshAll(); + nextFrame = g_system->getMillis();// + GAME_FRAME_DELAY; + } + + g_system->delayMillis(10); + } + + return 0; +} + +int M4Engine::goM4() { + + _script->open("m4.dat"); + +#ifdef SCRIPT_TEST + +#if 0 + ScriptFunction *func = _script->loadFunction("room_parser_142"); + _script->runFunction(func); +#endif + +#if 1 + ScriptFunction *func = _script->loadFunction("room_daemon_951"); + for (int i = 1; i < 58; i++) { + _vm->_kernel->trigger = i; + _script->runFunction(func); + printf("=================================\n"); + fflush(stdout); + } +#endif + + return 0; +#endif + + // Set up the inventory + + // Set up the game interface view + //_interfaceView->inventoryAdd("Money", "", 55); // Sample item + + if (getGameType() == GType_Burger) { + for (int i = 0; i < ARRAYSIZE(burger_inventory); i++) { + char* itemName = strdup(burger_inventory[i].name); + _inventory->registerObject(itemName, burger_inventory[i].scene, + burger_inventory[i].icon); + _inventory->addToBackpack(i); // debug: this adds ALL objects to the player's backpack + } + + _viewManager->addView(_interfaceView); + } + + // Show intro + + if (getGameType() == GType_Burger) + _kernel->newRoom = TITLE_SCENE_BURGER; + else + _scene->getBackgroundSurface()->loadBackgroundRiddle("main menu"); + + _viewManager->addView(_scene); + + // Setup game wide hotkeys. Note that Orion Burger used F2/F3 for Save/Restore, + // but for standardisation with most other games, F5/F7 are also mapped + + _viewManager->systemHotkeys().add(Common::KEYCODE_ESCAPE, &escapeHotkeyHandler); + _viewManager->systemHotkeys().add(Common::KEYCODE_F2, &saveGameHotkeyHandler); + _viewManager->systemHotkeys().add(Common::KEYCODE_F3, &loadGameHotkeyHandler); + _viewManager->systemHotkeys().add(Common::KEYCODE_F5, &saveGameHotkeyHandler); + _viewManager->systemHotkeys().add(Common::KEYCODE_F7, &loadGameHotkeyHandler); + _viewManager->systemHotkeys().add(Common::KEYCODE_F9, &gameMenuHotkeyHandler); + + // Start playing Orion Burger intro music + //_midi->playMusic("999intro", 255, false, -1, -1); + + // TODO: start playing intro animations + + // TODO: Master Lu + + // Test for mouse + _mouse->init("cursor", NULL); + _mouse->setCursorNum(0); + _mouse->cursorOn(); + + _ws->assets()->loadAsset("SHOW SCRIPT", NULL); + _ws->assets()->loadAsset("STREAM SCRIPT", NULL); + +#ifdef INTRO_TEST + int curPart = 0; + Machine *mach = NULL; +#endif + + _ws->setSurfaceView(_scene); + + uint32 nextFrame = g_system->getMillis(); + while (!_events->quitFlag) { + + // This should probably be moved to either Scene or Kernel + if (_kernel->currentRoom != _kernel->newRoom) { + + _ws->clear(); + + _kernel->currentSection = _kernel->newRoom / 100; + _kernel->currentRoom = _kernel->newRoom; + + _scene->loadScene(_kernel->currentRoom); + + _ws->setBackgroundSurface(_scene->getBackgroundSurface()); + _ws->setInverseColorTable(_scene->getInverseColorTable()); + + _kernel->loadSectionScriptFunctions(); + _kernel->loadRoomScriptFunctions(); + + _kernel->roomInit(); + +#ifdef INTRO_TEST + if (_kernel->currentRoom == 951) { + curPart = 0; + mach = _ws->streamSeries("PLANET X HILLTOP A", 1, 0x1000, 0); + } +#endif + + } + + eventHandler(); + + // Call the updateState method of all views + _viewManager->updateState(); + + // Handle frame updates + if (g_system->getMillis() >= nextFrame) { +#ifdef INTRO_TEST + // Orion Burger intro test (scene 951) + // This is ugly and bad, machine is not deleted so there's a huge memory + // leak too. But hey, we can see some of the intro! + if (mach && mach->getState() == -1) { + if (curPart == 0) + mach = _ws->streamSeries("Planet X Low Ground Shot", 1, 0x1000, 0); + else if (curPart == 1) + mach = _ws->streamSeries("Planet X Hilltop B", 1, 0x1000, 0); + else if (curPart == 2) + mach = _ws->streamSeries("Space Station Panorama A", 1, 0x1000, 0); + else if (curPart == 3) + mach = _ws->streamSeries("Cargo Transfer Area A", 1, 0x1000, 0); + else if (curPart == 4) + mach = _ws->streamSeries("VP's Office A", 1, 0x1000, 0); + else if (curPart == 5) + mach = _ws->streamSeries("Hologram", 1, 0x1000, 0); + else if (curPart == 6) + mach = _ws->streamSeries("VP's Office B", 1, 0x1000, 0); + else if (curPart == 7) + mach = _ws->streamSeries("Cargo Transfer Area B", 1, 0x1000, 0); + else if (curPart == 8) + mach = _ws->streamSeries("Cargo Transfer Controls", 1, 0x1000, 0); + else if (curPart == 9) + mach = _ws->streamSeries("Space Station Panorama B", 1, 0x1000, 0); + // This last scene is from the rolling demo + //else if (curPart == 10) + // mach = _ws->streamSeries("Call To Action", 1, 0x1000, 0); + curPart++; + } +#endif + _ws->update(); + _viewManager->refreshAll(); + nextFrame = g_system->getMillis();// + GAME_FRAME_DELAY; + + // TEST STUFF ONLY + if (_player->commandReady) { + _kernel->roomParser(); + _player->commandReady = false; + } + + } + + g_system->delayMillis(10); + } + + return 0; +} + +void M4Engine::dumpFile(const char* filename, bool uncompress) { + Common::SeekableReadStream *fileS = res()->get(filename); + byte buffer[256]; + FILE *destFile = fopen(filename, "wb"); + int bytesRead = 0; + printf("Dumping %s, size: %i\n", filename, fileS->size()); + + if (!uncompress) { + while(!fileS->eos()) { + bytesRead = fileS->read(buffer, 256); + fwrite(buffer, bytesRead, 1, destFile); + } + } else { + MadsPack packData(fileS); + Common::MemoryReadStream *sourceUnc; + for (int i = 0; i < packData.getCount(); i++) { + sourceUnc = packData.getItemStream(i); + printf("Dumping compressed chunk %i of %i, size is %i\n", i + 1, packData.getCount(), sourceUnc->size()); + while(!sourceUnc->eos()) { + bytesRead = sourceUnc->read(buffer, 256); + fwrite(buffer, bytesRead, 1, destFile); + } + delete sourceUnc; + } + } + + fclose(destFile); + res()->toss(filename); + res()->purge(); +} + +} // End of namespace M4 diff --git a/engines/m4/m4.h b/engines/m4/m4.h new file mode 100644 index 0000000000..e989bcc4e6 --- /dev/null +++ b/engines/m4/m4.h @@ -0,0 +1,184 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef M4_H +#define M4_H + +#include "common/scummsys.h" +#include "common/util.h" + +#include "engines/engine.h" + +#include "m4/globals.h" +#include "m4/graphics.h" +#include "m4/resource.h" +#include "m4/saveload.h" +#include "m4/viewmgr.h" +#include "m4/gui.h" +#include "m4/events.h" +#include "m4/font.h" +#include "m4/scene.h" +#include "m4/actor.h" +#include "m4/sound.h" +#include "m4/rails.h" +#include "m4/converse.h" +#include "m4/animation.h" + +//#define DUMP_SCRIPTS + +namespace M4 { + +class MidiPlayer; +class FileSystem; +class ResourceManager; +class Mouse; +class Events; +class Scene; +class ViewManager; +class View; +class Inventory; +class GameInterfaceView; +class ConversationView; +class Actor; +class Converse; +class ScriptInterpreter; +class WoodScript; +class Animation; + +enum M4GameType { + GType_Riddle = 1, + GType_Burger, + GType_RexNebular, + GType_DragonSphere, + GType_Phantom +}; + +enum Features { + kFeaturesNone = 0, + kFeaturesCD = 1 << 0, + kFeaturesDemo = 1 << 1 +}; + +enum { + kFileTypeHash, + kFileTypeHAG +}; + +enum { + kDebugScript = 1 << 0, + kDebugConversations = 2 << 0 +}; + +#define MESSAGE_BASIC 1 +#define MESSAGE_INTERMEDIATE 2 +#define MESSAGE_DETAILED 3 + +struct M4GameDescription; + +#define GAME_FRAME_DELAY 50 + +FORCEINLINE void str_lower(char *s) { while (*s) { *s = tolower(*s); s++; } } +FORCEINLINE void str_upper(char *s) { while (*s) { *s = toupper(*s); s++; } } + +FORCEINLINE long FixedMul(long a, long b) { return (long)(((float)a * (float)b) / 65536.0); } +FORCEINLINE long FixedDiv(long a, long b) { return (long)(((float)a / (float)b) * 65536.0); } + +class M4Engine : public Engine { +private: + int goMADS(); + int goM4(); + +protected: + int init(); + int go(); + void shutdown(); + + MidiPlayer *_midi; + +public: + M4Engine(OSystem *syst, const M4GameDescription *gameDesc); + virtual ~M4Engine(); + + int getGameType() const; + uint32 getFeatures() const; + Common::Language getLanguage() const; + Common::Platform getPlatform() const; + bool isM4() const { return (getGameType() == GType_Riddle) || (getGameType() == GType_Burger); } + + const char *getGameFile(int fileType); + Common::EventManager *eventMan() { return _eventMan; } + OSystem *system() { return _system; } + + const M4GameDescription *_gameDescription; + + ResourceManager *res() const { return _resourceManager; } + MidiPlayer *midi() { return _midi; } + Common::SaveFileManager *saveManager() { return _saveFileMan; } + void dumpFile(const char* filename, bool uncompress = false); + void eventHandler(); + bool delay(int duration, bool keyAborts = true, bool clickAborts = true); + void loadMenu(MenuType menuType, bool loadSaveFromHotkey = false, + bool calledFromMainMenu = false); + + // TODO: eventually these have to be removed + int32 seed; + void imath_seed(int32 seednum) { seed = seednum; } + uint32 imath_random() { return(seed = (25173*seed + 13849) & 0xffff); } + int32 imath_ranged_rand(int32 a, int32 b) { return (a + (((1 + ABS<int32>(b-a))*imath_random())>>16)); } + long imath_ranged_rand16(long a, long b) { return ((a + FixedMul(1+ABS<int32>(b-a),imath_random()))); } + // + + ResourceManager *_resourceManager; + SaveLoad *_saveLoad; + ViewManager *_viewManager; + Palette *_palette; + Kernel *_kernel; + Globals *_globals; + Player *_player; + Mouse *_mouse; + Events *_events; + Font *_font; + Actor *_actor; + Scene *_scene; + Dialogs *_dialogs; + M4Surface *_screen; + Inventory *_inventory; + GameInterfaceView *_interfaceView; + ConversationView *_conversationView; + Sound *_sound; + Rails *_rails; + Converse *_converse; + ScriptInterpreter *_script; + WoodScript *_ws; + Animation *_animation; + Common::RandomSource *_random; +}; + +// FIXME: remove global +extern M4Engine *_vm; + +} // End of namespace M4 + +#endif diff --git a/engines/m4/m4_menus.cpp b/engines/m4/m4_menus.cpp new file mode 100644 index 0000000000..09c0afe313 --- /dev/null +++ b/engines/m4/m4_menus.cpp @@ -0,0 +1,727 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "common/algorithm.h" // for find() +#include "gui/dialog.h" +#include "gui/message.h" + +#include "m4/m4_menus.h" +#include "m4/m4_views.h" +#include "m4/woodscript.h" +#include "m4/midi.h" + +namespace M4 { + +const char *EmptySaveString = "<empty>"; + +//-------------------------------------------------------------------------- +// Callback methods +// +// Following is a set of callback methods used to handle the execution +// of buttons in the various dialogs +//-------------------------------------------------------------------------- + +// General function which simply closes the active menu + +void OrionCallbacks::closeMenuFn(DialogView *view, MenuObject *item) { + view->close(); +} + +void OrionCallbacks::closeMenuFn(OrionMenuView *view) { + closeMenuFn(view, NULL); +} + +/* Game menu functions */ + +void OrionCallbacks::gameOptionsMenuFn(DialogView *view, MenuObject *item) { + view->vm()->loadMenu(OPTIONS_MENU); + view->close(); +} + +void OrionCallbacks::gameSaveGameFn(DialogView *view, MenuObject *item) { + view->vm()->loadMenu(SAVE_MENU); + view->close(); +} + +void OrionCallbacks::gameLoadGameFn(DialogView *view, MenuObject *item) { + view->vm()->loadMenu(LOAD_MENU); + view->close(); +} + +void OrionCallbacks::gameExitFn(DialogView *view, MenuObject *item) { + view->vm()->_events->quitFlag = true; + view->close(); +} + +/* Options menu */ + +void OrionCallbacks::optionsDigiSliderFn(DialogView *view, MenuObject *item) { + // Digi volume slider changed + int percent = ((MenuHorizSlider *) item)->percent(); + + view->vm()->_sound->setVolume(percent * 255 / 100); +} + +void OrionCallbacks::optionsMidiSliderFn(DialogView *view, MenuObject *item) { + // Midi volume slider changed + int percent = ((MenuHorizSlider *) item)->percent(); + + view->vm()->midi()->setVolume(percent * 255 / 100); +} + +void OrionCallbacks::optionsScrollingFn(DialogView *view, MenuObject *item) { + // TODO: Change current Digi volume settings here +} + +void OrionCallbacks::optionsCancelFn(DialogView *view, MenuObject *item) { + // TODO: Reset original option settings here + OrionMenuView *vw = (OrionMenuView *) view; + + vw->vm()->midi()->setVolume(vw->_originalMidiVolume); + + vw->vm()->loadMenu(GAME_MENU); + vw->close(); +} + +void OrionCallbacks::optionsDoneFn(DialogView *view, MenuObject *item) { + view->vm()->loadMenu(GAME_MENU); + view->close(); +} + +void OrionCallbacks::optionsReturnFn(OrionMenuView *view) { + optionsDoneFn(view, NULL); +} + +void OrionCallbacks::optionsEscapeFn(OrionMenuView *view) { + optionsCancelFn(view, NULL); +} + +/* Save/Load dialog functions */ + +// Save the current game + +void OrionCallbacks::saveLoadSaveFn(DialogView *view, MenuObject *item) { + if (view->_selectedSlot == -1) + return; + + MenuTextField *textItem = (MenuTextField *) view->getItem(SLTAG_TEXTFIELD); + if (!textItem) + return; + + textItem->setState(OS_NORMAL); + + // Save the game + bool succeeded = view->vm()->_saveLoad->save(view->_selectedSlot + 1, textItem->getText()); + + if (!succeeded) { + GUI::MessageDialog dialog("Save game failed!"); + dialog.runModal(); + } + + // Close the menu + closeMenuFn(view, item); +} + +void OrionCallbacks::saveLoadLoadFn(DialogView *view, MenuObject *item) { + // TODO: load the selected save game + closeMenuFn(view, item); +} + +void OrionCallbacks::saveLoadSlotFn(DialogView *view, MenuObject *item) { + OrionMenuView *vw = (OrionMenuView *) view; + MenuSaveLoadText *button = (MenuSaveLoadText *) item; + + view->_selectedSlot = button->getIndex(); + view->_deleteSaveDesc = true; + + // Disable all the slots except the selected one + for (int index = 0; index < SL_NUM_VISIBLE_SLOTS; ++index) { + MenuSaveLoadText *currentItem = (MenuSaveLoadText *) view->getItem(SLTAG_SLOTS_START + index); + if (currentItem->getIndex() != button->getIndex()) { + currentItem->setState(OS_GREYED); + } + } + + // Get a copy of the slot bounds + Common::Rect slotBounds = button->getBounds(); + + if (view->getMenuType() == SAVE_MENU) { + // Add in a text field for entry of the savegame name + vw->items().push_back(new MenuTextField(view, SLTAG_TEXTFIELD, + slotBounds.left, slotBounds.top, slotBounds.width(), slotBounds.height(), false, + saveLoadSaveFn, (button->getText() == EmptySaveString) ? NULL : button->getText(), + button->getIndex() + 1)); + + } else { + vw->items().push_back(new MenuTextField(view, SLTAG_TEXTFIELD, + slotBounds.left, slotBounds.top, slotBounds.width(), slotBounds.height(), true, + saveLoadLoadFn, button->getText(), button->getIndex() + 1)); + } + + // Hide the existing slot + button->setVisible(false); + + // Disable the slider + + MenuVertSlider *slider = (MenuVertSlider *) view->getItem(SLTAG_VSLIDER); + slider->setState(OS_GREYED); + + // Enable the save/load button + MenuButton *btn = (MenuButton *) view->getItem(SLTAG_SAVELOAD); + btn->setState(OS_NORMAL); +} + +void OrionCallbacks::saveLoadCancelFn(DialogView *view, MenuObject *item) { + OrionMenuView *vw = (OrionMenuView *) view; + + if (view->_selectedSlot != -1) { + // Pressed cancel with a save selected, so revert back to no selection + + // Re-enable all the other slots + + for (int index = 0; index < SL_NUM_VISIBLE_SLOTS; ++index) { + if (index != view->_selectedSlot) { + MenuSaveLoadText *currentItem = (MenuSaveLoadText *) view->getItem(SLTAG_SLOTS_START + index); + currentItem->setState(OS_NORMAL); + } + } + + // Show the previously hidden slot again + MenuSaveLoadText *slot = (MenuSaveLoadText *) view->getItem(SLTAG_SLOTS_START + view->_selectedSlot); + slot->setVisible(true); + slot->setState(OS_NORMAL); + + // Remove the text selection + MenuTextField *textField = (MenuTextField *) view->getItem(SLTAG_TEXTFIELD); + delete textField; + vw->items().remove(textField); + + // Set button enablement + MenuButton *btn = (MenuButton *) view->getItem(SLTAG_SAVELOAD); + btn->setState(OS_GREYED); + btn = (MenuButton *) view->getItem(SLTAG_CANCEL); + btn->setState(OS_NORMAL); + + // Re-enable the slider + + MenuVertSlider *slider = (MenuVertSlider *) view->getItem(SLTAG_VSLIDER); + slider->setState(OS_NORMAL); + + view->_selectedSlot = -1; + + } else { + // Close the dialog + if (vw->_loadSaveFromHotkey) + // Since dialog was called from hotkey, return directly to the game + closeMenuFn(view, item); + else { + // Return to the game menu + view->vm()->loadMenu(GAME_MENU); + view->close(); + } + } +} + +void OrionCallbacks::saveLoadSliderFn(DialogView *view, MenuObject *item) { + OrionMenuView *vw = (OrionMenuView *) view; + MenuVertSlider *slider = (MenuVertSlider *) item; + + if (slider->sliderState() == VSLIDER_THUMBNAIL) { + // Callback generated by slider thumb, so set top slot using slider percentage + vw->setTopSaveSlot(slider->percent() * 89 / 100); + + } else { + int newIndex = view->_topSaveSlotIndex; + + switch (slider->sliderState()) { + case VSLIDER_UP: + if (newIndex > 0) + --newIndex; + break; + + case VSLIDER_PAGE_UP: + if (newIndex > 0) + newIndex = MAX(newIndex - 10, 0); + break; + + case VSLIDER_PAGE_DOWN: + if (newIndex < 89) + newIndex = MIN(newIndex + 10, 89); + break; + + case VSLIDER_DOWN: + if (newIndex < 89) + ++newIndex; + break; + + default: + break; + } + + if (newIndex != view->_topSaveSlotIndex) { + // Set the new top slot + vw->setTopSaveSlot(newIndex); + + // Set the new slider position + slider->setPercentage(newIndex * 100 / 89); + } + } +} + +void OrionCallbacks::saveLoadEscapeFn(OrionMenuView *view) { + saveLoadCancelFn(view, NULL); +} + +void OrionCallbacks::saveLoadReturnFn(OrionMenuView *view) { + MenuTextField *textItem = (MenuTextField *) view->getItem(SLTAG_TEXTFIELD); + if (textItem) { + if (view->getMenuType() == SAVE_MENU) + saveLoadSaveFn(view, NULL); + else + saveLoadLoadFn(view, NULL); + } +} + +//-------------------------------------------------------------------------- + +OrionMenuView::OrionMenuView(M4Engine *Vm, int x, int y, MenuType menuType, bool calledFromMainMenu, + bool loadSaveFromHotkey): DialogView(Vm, x, y, true) { + _menuType = menuType; + _screenType = VIEWID_MENU; + _screenFlags.layer = LAYER_MENU; + _screenFlags.get = SCREVENT_ALL; + _screenFlags.blocks = SCREVENT_ALL; + _screenFlags.immovable = true; + //_screenFlags.immovable = false; // uncomment to make menu movable + _coords.left = x; + _coords.top = y; + _currentItem = NULL; + _escapeHandler = &OrionCallbacks::closeMenuFn; + _returnHandler = NULL; + _saveNames = NULL; + _savegameThumbnail = NULL; + _deleteSaveDesc = false; + _closeFlag = false; + + _calledFromMainMenu = calledFromMainMenu; + _loadSaveFromHotkey = loadSaveFromHotkey; + + _interfaceWasVisible = _vm->_interfaceView->isVisible(); + if (_interfaceWasVisible) + _vm->_interfaceView->hide(); + + _vm->_mouse->setCursorNum(CURSOR_ARROW); + + switch (menuType) { + case GAME_MENU: + loadSprites(MENU_GAME); + + // Add menu contents + _menuObjects.push_back(new MenuButton(this, BTNID_MAIN, 45, 53, 24, 24, &OrionCallbacks::closeMenuFn)); + _menuObjects.push_back(new MenuButton(this, BTNID_OPTIONS, 45, 94, 24, 24, &OrionCallbacks::gameOptionsMenuFn)); + _menuObjects.push_back(new MenuButton(this, BTNID_RESUME, 45, 135, 24, 24, &OrionCallbacks::closeMenuFn)); + _menuObjects.push_back(new MenuButton(this, BTNID_QUIT, 141, 135, 24, 24, &OrionCallbacks::gameExitFn)); + _menuObjects.push_back(new MenuButton(this, BTNID_SAVE, 141, 53, 24, 24, &OrionCallbacks::gameSaveGameFn, _calledFromMainMenu)); + _menuObjects.push_back(new MenuButton(this, BTNID_LOAD, 141, 94, 24, 24, &OrionCallbacks::gameLoadGameFn, + !_vm->_saveLoad->hasSaves())); + + _escapeHandler = &OrionCallbacks::closeMenuFn; + _returnHandler = &OrionCallbacks::closeMenuFn; + break; + + case OPTIONS_MENU: + loadSprites(MENU_OPTIONS); + + // Store the original settings in case user aborts dialog + _originalMidiVolume = _vm->midi()->getVolume(); + + // Add menu contents + // TODO: Currently the Digi slider isn't hooked up to anything + _menuObjects.push_back(new MenuButton(this, OPTIONID_CANCEL, 93, 141, 74, 43, + &OrionCallbacks::optionsCancelFn, false, false, OBJTYPE_OM_CANCEL)); + _menuObjects.push_back(new MenuButton(this, OPTIONID_DONE, 168, 141, 74, 43, + &OrionCallbacks::optionsDoneFn, false, false, OBJTYPE_OM_DONE)); + _menuObjects.push_back(new MenuHorizSlider(this, OPTIONID_HSLIDER_MIDI, 47, 64, 212, 24, + _originalMidiVolume * 100 / 255, &OrionCallbacks::optionsMidiSliderFn, true)); + _menuObjects.push_back(new MenuHorizSlider(this, OPTIONID_HSLIDER_DIGI, 47, 104, 212, 24, + 0, &OrionCallbacks::optionsDigiSliderFn, true)); + + _escapeHandler = &OrionCallbacks::optionsEscapeFn; + _returnHandler = &OrionCallbacks::optionsReturnFn; + break; + + case SAVE_MENU: + case LOAD_MENU: + loadSprites(MENU_SAVELOAD); + + // Set up the defaults for the window + _topSaveSlotIndex = 0; + _selectedSlot = -1; + _highlightedSlot = -1; + _saveNames = _vm->_saveLoad->getSaves(); + + // Set up menu elements + _menuObjects.push_back(new MenuMessage(this, SLTAG_SAVELOAD_LABEL, 50, 241, 70, 16)); + _menuObjects.push_back(new MenuButton(this, SLTAG_SAVELOAD, 214, 384, 72, 41, + (menuType == SAVE_MENU) ? &OrionCallbacks::saveLoadSaveFn : &OrionCallbacks::saveLoadLoadFn, + true, true, (menuType == SAVE_MENU) ? OBJTYPE_SL_SAVE : OBJTYPE_SL_LOAD)); + _menuObjects.push_back(new MenuButton(this, SLTAG_CANCEL, 139, 384, 74, 43, + &OrionCallbacks::saveLoadCancelFn, false, false, OBJTYPE_SL_CANCEL)); + _menuObjects.push_back(new MenuVertSlider(this, SLTAG_VSLIDER, 291, 255, 23, 127, 0, + &OrionCallbacks::saveLoadSliderFn)); + + if (_menuType == SAVE_MENU) + _savegameThumbnail = createThumbnail(); + + _menuObjects.push_back(new MenuImage(this, SLTAG_THUMBNAIL, 66, 28, 215, 162, + (_savegameThumbnail == NULL) ? _sprites->getFrame(SL_EMPTY_THUMBNAIL) : _savegameThumbnail)); + + + { + SaveGameIterator slot = _saveNames->begin(); + for (uint slotIndex = 0; slotIndex < SL_NUM_VISIBLE_SLOTS; ++slotIndex, ++slot) { + // Get save slot + bool isEmpty = (slotIndex >= _saveNames->size()) || (*slot).empty(); + + _menuObjects.push_back(new MenuSaveLoadText(this, SLTAG_SLOTS_START + slotIndex, + 50, 256 + slotIndex * 15, 238, 15, &OrionCallbacks::saveLoadSlotFn, + (menuType == LOAD_MENU) && isEmpty, true, (menuType == LOAD_MENU), + isEmpty ? EmptySaveString : slot->c_str(), slotIndex + 1)); + } + } + + _escapeHandler = &OrionCallbacks::saveLoadEscapeFn; + _returnHandler = &OrionCallbacks::saveLoadReturnFn; + break; + + default: + error("Unknown menu type"); + break; + } + + // Draw all the items onto the background surface + for (MenuObjectsIterator i = _menuObjects.begin(); i != _menuObjects.end(); ++i) + (*i)->onRefresh(); +} + +OrionMenuView::~OrionMenuView() { + delete _sprites; + + for (MenuObjectList::iterator i = _menuObjects.begin(); i != _menuObjects.end(); ++i) + delete *i; + _menuObjects.clear(); + + if (_saveNames) + delete _saveNames; + if (_savegameThumbnail) + delete _savegameThumbnail; +} + +bool OrionMenuView::loadSprites(const char *seriesName) { + Common::SeekableReadStream *data = _vm->res()->get(seriesName); + RGB8 *palette; + + _sprites = new SpriteAsset(_vm, data, data->size(), seriesName); + palette = _sprites->getPalette(); + _vm->_palette->setPalette(palette, 0, _sprites->getColorCount()); + + _vm->res()->toss(seriesName); + + // Update the palette + //_vm->setPalette((byte *) _menuPalette, 59, 197); + + // The first sprite is the menu background + + M4Sprite *bg = _sprites->getFrame(0); + this->setSize(bg->width(), bg->height()); + _coords.setWidth(bg->width()); + _coords.setHeight(bg->height()); + bg->copyTo(this); + + return true; +} + +// Creates a thumbnail based on the current background screen + +M4Surface *OrionMenuView::createThumbnail() { + M4Surface srcSurface(_vm->_screen->width(), _vm->_screen->height()); + M4Surface *result = new M4Surface(_vm->_screen->width() / 3, _vm->_screen->height() / 3); + + // Translate the scene data + + _vm->_scene->onRefresh(NULL, &srcSurface); + byte *srcP = (byte *)srcSurface.pixels; + byte *destP = (byte *)result->pixels; + + for (int yCtr = 0; yCtr < _vm->_scene->height() / 3; ++yCtr, srcP += g_system->getWidth() * 3) { + byte *src0P = srcP; + byte *src1P = srcP + _vm->_screen->width(); + byte *src2P = src1P + _vm->_screen->width(); + + for (int xCtr = 0; xCtr < result->width(); ++xCtr) { + *destP = (byte)((uint32)(( + *src0P + *(src0P + 1) + *(src0P + 2) + + *src1P + *(src1P + 1) + *(src1P + 2) + + *src2P + *(src2P + 1) + *(src2P + 2)) / 9)); + if (*destP == 0) + *destP = 21; + + ++destP; + src0P += 3; + src1P += 3; + src2P += 3; + } + } + + // Translate the game interface view - since it's using standard colors that can't be + // averaged, simply take the top left pixel of every 3x3 pixel block + + _vm->_interfaceView->onRefresh(NULL, &srcSurface); + destP = (byte *)result->pixels + (_vm->_screen->width() / 3) * (_vm->_interfaceView->bounds().top / 3); + + int yStart = _vm->_interfaceView->bounds().top; + int yEnd = MIN(_vm->_screen->height() - 1, (int) _vm->_interfaceView->bounds().bottom - 1); + for (int yCtr = yStart; yCtr <= yEnd; yCtr += 3) { + srcP = (byte *)srcSurface.pixels + (yCtr * _vm->_screen->width()); + + for (int xCtr = 0; xCtr < result->width(); ++xCtr, srcP += 3) + *destP++ = *srcP; + } + + return result; +} + +void OrionMenuView::destroyView() { + M4Engine *engine = _vm; + bool interfaceVisible = _interfaceWasVisible; + engine->_viewManager->deleteView(this); + + // Fade the game back in if no menu views are active (such as if a button was pressed in one menu + // to activate another menu dialog) + bool fadeIn = engine->_viewManager->getView(VIEWID_MENU) == NULL; + + if (fadeIn) { + bool fadeToBlack = engine->_events->quitFlag; + engine->_ws->update(); + engine->_palette->fadeFromGreen(M4_DIALOG_FADE_STEPS, M4_DIALOG_FADE_DELAY, fadeToBlack); + + if (interfaceVisible) + engine->_interfaceView->show(); + } +} + +bool OrionMenuView::onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents) { + static Common::Point movingPos(0, 0); + static bool movingFlag = false; + + bool handledFlag = false; + int localX, localY; + MenuObjectsIterator i; + + if (!_screenFlags.visible) + return false; + + if (!movingFlag) + captureEvents = false; + + // If the escape key is pressed, then pass onto the Escape handler + + if (eventType == KEVENT_KEY) { + if ((param == Common::KEYCODE_ESCAPE) && (_escapeHandler != NULL)) { + // Execute the Escape handler function + _currentItem = NULL; + captureEvents = false; + _escapeHandler(this); + destroyView(); + return true; + } + + if (((param == Common::KEYCODE_RETURN) || (param == Common::KEYCODE_KP_ENTER)) && + (_returnHandler != NULL)) { + // Execute the Return handler function + _currentItem = NULL; + captureEvents = false; + _returnHandler(this); + return true; + } + + MenuTextField *textItem = (MenuTextField *) getItem(SLTAG_TEXTFIELD); + if (textItem && textItem->onEvent(KEVENT_KEY, param, x, y, _currentItem)) + return true; + } + + // Convert the screen position to a relative position within the menu surface + localX = x - _coords.left; + localY = y - _coords.top; + + // If there is an active object handling events, pass it on until it releases control + + if (_currentItem) { + handledFlag = _currentItem->onEvent(eventType, param, localX, localY, _currentItem); + + if (_closeFlag) { + // Dialog has been flagged to be closed + captureEvents = false; + destroyView(); + return true; + } + + if (_currentItem) { + captureEvents = + (Common::find(_menuObjects.begin(), _menuObjects.end(), _currentItem) != _menuObjects.end()); + if (!captureEvents) + // The menu object is no longer active, so reset current item + _currentItem = NULL; + } else { + captureEvents = false; + } + + if (handledFlag) + return true; + } + + if (eventType == KEVENT_KEY) { + // Handle keypresses by looping through the item list to see if any of them want it + + for (i = _menuObjects.begin(); (i != _menuObjects.end()) && !handledFlag; ++i) { + MenuObject *menuObj = *i; + MenuObject *dummyItem; + handledFlag = menuObj->onEvent(eventType, param, localX, localY, dummyItem); + } + + return handledFlag; + + } else { + // Handle mouse events by scanning the item list to see if the cursor is within any + + for (i = _menuObjects.begin(); (i != _menuObjects.end()) && !handledFlag; ++i) { + MenuObject *menuObj = *i; + + if (menuObj->isInside(localX, localY)) { + // Found an item, so pass it the event + menuObj->onEvent(eventType, param, localX, localY, _currentItem); + + if (_closeFlag) { + // Dialog has been flagged to be closed + captureEvents = false; + destroyView(); + return true; + } + + if (_currentItem) { + captureEvents = + (Common::find(_menuObjects.begin(), _menuObjects.end(), _currentItem) != _menuObjects.end()); + if (!captureEvents) + // The menu object is no longer active, so reset current item + _currentItem = NULL; + } else { + captureEvents = false; + } + + return true; + } + } + } + + // None of the items have handled the event, so fall back on menu-wide event handling + + switch (eventType) { + case MEVENT_LEFT_CLICK: + case MEVENT_DOUBLECLICK: + if (!_screenFlags.immovable) { + // Move the entire dialog + captureEvents = true; + movingFlag = true; + movingPos.x = x; + movingPos.y = y; + } + break; + + case MEVENT_LEFT_DRAG: + case MEVENT_DOUBLECLICK_DRAG: + if (movingFlag) { + moveRelative(x - movingPos.x, y - movingPos.y); + movingPos.x = x; + movingPos.y = y; + } + break; + + case MEVENT_LEFT_RELEASE: + case MEVENT_DOUBLECLICK_RELEASE: + captureEvents = false; + movingFlag = false; + break; + + default: + break; + } + + return true; +} + +MenuObject *OrionMenuView::getItem(int objectId) { + MenuObjectsIterator i; + for (i = _menuObjects.begin(); i != _menuObjects.end(); ++i) { + MenuObject *obj = *i; + if (obj->getObjectId() == objectId) + return obj; + } + + return NULL; +} + +void OrionMenuView::setTopSaveSlot(int slotNumber) { + _topSaveSlotIndex = MAX(MIN(slotNumber, 89), 0); + + // Update the details of the load/save slots + + // Get save slot + SaveGameIterator slot = _saveNames->begin(); + for (int i = 0; i < _topSaveSlotIndex; ++i) + ++slot; + + for (uint index = 0; index < SL_NUM_VISIBLE_SLOTS; ++index, ++slot) { + MenuSaveLoadText *item = (MenuSaveLoadText *) getItem(SLTAG_SLOTS_START + index); + uint slotIndex = _topSaveSlotIndex + index; + + bool isEmpty = (slotIndex >= _saveNames->size()) || slot->empty(); + item->setDisplay(slotIndex + 1, isEmpty ? EmptySaveString : slot->c_str()); + + item->setState((_menuType == SAVE_MENU) || !isEmpty ? OS_NORMAL : OS_GREYED); + } +} + +void OrionMenuView::refresh(const Common::Rect &areaRect) { + // Copy the selected portion of the background + _sprites->getFrame(0)->copyTo(this, areaRect, areaRect.left, areaRect.top); + + for (MenuObjectsIterator i = _menuObjects.begin(); i != _menuObjects.end(); ++i) { + MenuObject *obj = *i; + if (obj->getBounds().intersects(areaRect)) + obj->onRefresh(); + } +} + +} diff --git a/engines/m4/m4_menus.h b/engines/m4/m4_menus.h new file mode 100644 index 0000000000..f0a0f07333 --- /dev/null +++ b/engines/m4/m4_menus.h @@ -0,0 +1,106 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef M4_M4_MENUS_H +#define M4_M4_MENUS_H + +#include "common/list.h" +#include "common/ptr.h" + +#include "m4/viewmgr.h" +#include "m4/m4.h" +#include "m4/gui.h" +#include "m4/saveload.h" + +namespace M4 { + +#define M4_DIALOG_FADE_STEPS 5 +#define M4_DIALOG_FADE_DELAY 30 + +typedef Common::List<MenuObject *> MenuObjectList; + +class OrionMenuView: public DialogView { + typedef MenuObjectList::iterator MenuObjectsIterator; +private: + MenuType _menuType; + SpriteAsset *_sprites; + MenuObjectList _menuObjects; + MenuObject *_currentItem; + typedef void (*Callback)(OrionMenuView *view); + OrionMenuView::Callback _escapeHandler, _returnHandler; + bool _closeFlag; + bool _calledFromMainMenu; + bool _interfaceWasVisible; + int _firstSlotIndex; + + bool loadSprites(const char *seriesName); + M4Surface *createThumbnail(); + void destroyView(); +public: + OrionMenuView(M4Engine *vm, int x, int y, MenuType menuType, bool calledFromMainMenu, + bool loadSaveFromHotkey); + ~OrionMenuView(); + MenuType getMenuType() { return _menuType; } + SpriteAsset *sprites() { return _sprites; } + MenuObjectList &items() { return _menuObjects; } + MenuObject *getItem(int objectId); + void setTopSaveSlot(int slotNumber); + void refresh(const Common::Rect &areaRect); + void close() { _closeFlag = true; } + + bool onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents); + + int _originalMidiVolume; + SaveGameList *_saveNames; + bool _loadSaveFromHotkey; +}; + +class OrionCallbacks { +public: + static void closeMenuFn(DialogView *view, MenuObject *item); + static void closeMenuFn(OrionMenuView *view); + static void gameOptionsMenuFn(DialogView *view, MenuObject *item); + static void gameSaveGameFn(DialogView *view, MenuObject *item); + static void gameLoadGameFn(DialogView *view, MenuObject *item); + static void gameExitFn(DialogView *view, MenuObject *item); + static void optionsDigiSliderFn(DialogView *view, MenuObject *item); + static void optionsMidiSliderFn(DialogView *view, MenuObject *item); + static void optionsScrollingFn(DialogView *view, MenuObject *item); + static void optionsCancelFn(DialogView *view, MenuObject *item); + static void optionsDoneFn(DialogView *view, MenuObject *item); + static void optionsReturnFn(OrionMenuView *view); + static void optionsEscapeFn(OrionMenuView *view); + static void saveLoadSaveFn(DialogView *view, MenuObject *item); + static void saveLoadLoadFn(DialogView *view, MenuObject *item); + static void saveLoadSlotFn(DialogView *view, MenuObject *item); + static void saveLoadCancelFn(DialogView *view, MenuObject *item); + static void saveLoadSliderFn(DialogView *view, MenuObject *item); + static void saveLoadEscapeFn(OrionMenuView *view); + static void saveLoadReturnFn(OrionMenuView *view); +}; + +} + +#endif diff --git a/engines/m4/m4_views.cpp b/engines/m4/m4_views.cpp new file mode 100644 index 0000000000..9bf964ee96 --- /dev/null +++ b/engines/m4/m4_views.cpp @@ -0,0 +1,345 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "m4/m4_views.h" +#include "m4/events.h" +#include "m4/font.h" +#include "m4/globals.h" +#include "m4/m4.h" + +namespace M4 { + +GUIInventory::GUIInventory(View *owner, M4Engine *vm, const Common::Rect &bounds, int horizCells, + int vertCells, int cellWidth, int cellHeight, int tag): GUIRect(owner, bounds, tag) { + + _vm = vm; + _cellCount.x = horizCells; + _cellCount.y = vertCells; + _cellSize.x = cellWidth; + _cellSize.y = cellHeight; + + // Validate the cell settings + if ((_cellCount.x * _cellSize.x > _bounds.width()) || + (_cellCount.y * _cellSize.y > _bounds.height())) + error("Cell settings for inventory display exceeded control size"); + + _visible = true; + _scrollPosition = 0; + _scrollable = false; + _highlightedIndex = -1; + _selectedIndex = -1; +} + +void GUIInventory::onRefresh() { + _parent->fillRect(_bounds, _vm->_palette->BLACK); + //_parent->frameRect(_bounds, _vm->_palette->LIGHT_GRAY); + + if (_visible) { + //kernel_trigger_dispatch(kernel_trigger_create(TRIG_INV_CLICK)); + + _scrollable = false; + + // Get to the starting inventory position for display + ItemsIterator i = _inventoryItems.begin(); + int index = _scrollPosition; + while (index-- > 0) ++i; + + // Loop through displaying entries + for (index = 0; (i != _inventoryItems.end()) && (index < _cellCount.x * _cellCount.y); ++index, ++i) { + GUIInventoryItem *item = (*i).get(); + const Common::Point cellPos = getCellPosition(index); +/* Common::Rect cellBounds(_bounds.left + cellPos.x + xOffset, + _bounds.top + cellPos.y + yOffset, + _bounds.left + cellPos.x + xOffset + _cellSize.x, + _bounds.top + cellPos.y + _cellSize.y);*/ + Common::Rect cellBounds(_bounds.left + cellPos.x, _bounds.top + cellPos.y, + _bounds.left + cellPos.x + _cellSize.x, _bounds.top + cellPos.y + _cellSize.y); + + Common::Point iconPt( + cellBounds.left + (cellBounds.width() - item->icon->width()) / 2, + cellBounds.top + (cellBounds.height() - item->icon->height()) / 2); + + item->icon->copyTo(_parent, iconPt.x, iconPt.y, 0); + + if (_highlightedIndex == index) + _parent->frameRect(Common::Rect(iconPt.x - 2, iconPt.y - 2, + iconPt.x + item->icon->width() + 2, iconPt.y + item->icon->height() + 2), + _vm->_palette->LIGHT_GRAY); + } + } +} + +bool GUIInventory::onEvent(M4EventType eventType, int param, int x, int y, GUIObject *¤tItem) { + bool result = false; + int overIndex = getInsideIndex(x, y); + bool isPressed = (eventType == MEVENT_LEFT_CLICK) || (eventType == MEVENT_LEFT_HOLD) || + (eventType == MEVENT_LEFT_DRAG); + ItemsIterator curItem = _inventoryItems.begin(); + + if (isPressed) { + if (_selectedIndex == -1 && overIndex != -1) { + setHighlight(overIndex); + _selectedIndex = overIndex; + for (int i = 0; i < _scrollPosition + _selectedIndex; i++) + ++curItem; + if (_scrollPosition + _selectedIndex < (int)_inventoryItems.size()) + _vm->_mouse->setCursorNum(curItem->get()->iconIndex); + result = true; + } else { + // We are over something being tracked + if (_selectedIndex == overIndex) { + setHighlight(overIndex); + result = true; + } else { + // Otherwise reset highlighting + setHighlight(-1); + result = false; + } + } + } else { + // No button pressed + if (_selectedIndex == overIndex) { + result = (_selectedIndex != -1); + } else { + result = (overIndex + _scrollPosition < (int)_inventoryItems.size()); + if (result) { + for (int i = 0; i < overIndex + _scrollPosition; i++) + ++curItem; + _vm->_interfaceView->setStatusText(curItem->get()->name); + } + } + + // Stop tracking anything + setHighlight(overIndex); + } + + return result; +} + +void GUIInventory::add(const char *name, const char *verb, M4Surface *icon, int iconIndex) { + // First scan through the list to prevent duplicate objects + for (ItemsIterator i = _inventoryItems.begin(); i != _inventoryItems.end(); ++i) { + if (!strcmp(name, ((*i).get())->name)) + return; + } + + _inventoryItems.push_back(InventoryList::value_type(new GUIInventoryItem(name, verb, icon, iconIndex))); +} + +bool GUIInventory::remove(const char *name) { + for (ItemsIterator i = _inventoryItems.begin(); i != _inventoryItems.end(); ++i) { + if (!strcmp(name, ((*i).get())->name)) { + _inventoryItems.erase(i); + _scrollPosition = 0; + return true; + } + } + + return false; +} + +int GUIInventory::getInsideIndex(int x, int y) { + if (!_bounds.contains(x, y)) + return -1; + + int localX = x - _bounds.left; + int localY = y - _bounds.top; + return (localX / _cellSize.x) * _cellCount.y + (localY / _cellSize.y); +} + +const char *GUIInventory::getSelectedObjectName() { + if (_selectedIndex != -1) { + ItemsIterator curItem = _inventoryItems.begin(); + for (int i = 0; i < _selectedIndex; i++) + ++curItem; + return curItem->get()->name; + } else { + return NULL; + } +} + +const Common::Point &GUIInventory::getCellPosition(int index) { + static Common::Point result; + + if (_cellCount.x > _cellCount.y) { + // Horizontal orientation + result.x = (index / _cellCount.y) * _cellSize.x; + result.y = (index % _cellCount.y) * _cellSize.x; + } else { + // Vertical orientation + result.x = (index / _cellCount.x) * _cellSize.y; + result.y = (index % _cellCount.x) * _cellSize.y; + } + + return result; +} + +void GUIInventory::setHighlight(int index) { + if (_highlightedIndex == index) + return; + + _highlightedIndex = index; +} + +void GUIInventory::setScrollPosition(int value) { + if (value < 0) + return; + else if (value >= (int)_inventoryItems.size() - (_cellCount.x * _cellCount.y)) + return; + + _scrollPosition = value; +} + +//-------------------------------------------------------------------------- + +const char *INTERFACE_SERIES = "999intr"; + +#define SPR(x) _sprites->getFrame(x) + +GameInterfaceView::GameInterfaceView(M4Engine *vm): + View(vm, Common::Rect(0, vm->_screen->height() - INTERFACE_HEIGHT, + vm->_screen->width(), vm->_screen->height())), + _statusText(GUITextField(this, Common::Rect(200, 1, 450, 21))), + _inventory(GUIInventory(this, vm, Common::Rect(188, 22, 539, 97), 9, 1, 39, 75, 3)) { + + _screenType = VIEWID_INTERFACE; + _screenFlags.layer = LAYER_INTERFACE; + _screenFlags.visible = false; + _screenFlags.get = SCREVENT_MOUSE; + _highlightedIndex = -1; + _selected = false; + + Common::SeekableReadStream *data = _vm->res()->get(INTERFACE_SERIES); + RGB8 *palette; + + _sprites = new SpriteAsset(_vm, data, data->size(), INTERFACE_SERIES); + palette = _sprites->getPalette(); + + //Palette.setPalette(palette, 0, _sprites->getColorCount()); + + _vm->res()->toss(INTERFACE_SERIES); + + // Setup the interface buttons + + _buttons.push_back(ButtonList::value_type(new GUIButton(this, Common::Rect(15, 35, 47, 66), 0, SPR(0), SPR(1), SPR(2)))); // look + _buttons.push_back(ButtonList::value_type(new GUIButton(this, Common::Rect(60, 35, 92, 66), 1, SPR(3), SPR(4), SPR(5)))); // take + _buttons.push_back(ButtonList::value_type(new GUIButton(this, Common::Rect(105, 35, 137, 66), 2, SPR(6), SPR(7), SPR(8)))); // manipulate + + _buttons.push_back(ButtonList::value_type(new GUIButton(this, Common::Rect(580, 10, 620, 69), 3, SPR(69), SPR(70), SPR(71)))); // abduction + _buttons.push_back(ButtonList::value_type(new GUIButton(this, Common::Rect(582, 70, 619, 105), 4, SPR(76), SPR(77), SPR(78)))); // menu + + _buttons.push_back(ButtonList::value_type(new GUIButton(this, Common::Rect(168, 22, 188, 97), 5, SPR(60), SPR(61), SPR(62)))); // Scroll left + _buttons.push_back(ButtonList::value_type(new GUIButton(this, Common::Rect(539, 22, 559, 97), 6, SPR(64), SPR(65), SPR(66)))); // Scroll right +} + +#undef SPR + +GameInterfaceView::~GameInterfaceView() { + delete _sprites; +} + +void GameInterfaceView::setHighlightedButton(int index) { + if (index == _highlightedIndex) + return; + + _selected = (index == -1); + _highlightedIndex = index; +} + +bool GameInterfaceView::onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents) { + static bool selectionFlag = false; + if (eventType == MEVENT_LEFT_RELEASE) + selectionFlag = false; + + captureEvents = isInside(x, y); + if (!captureEvents) + return false; + + int localX = x - _coords.left; + int localY = y - _coords.top; + GUIObject *currentItem; + + _statusText.onEvent(eventType, param, localX, localY, currentItem); + _inventory.onEvent(eventType, param, localX, localY, currentItem); + + if (_vm->_mouse->getCursorNum() != CURSOR_LOOK && + _vm->_mouse->getCursorNum() != CURSOR_TAKE && + _vm->_mouse->getCursorNum() != CURSOR_USE && + _vm->_interfaceView->_inventory.getSelectedIndex() == -1) { + if (_vm->_mouse->getCursorNum() != 0) + _vm->_mouse->setCursorNum(0); + } + + for (ButtonsIterator i = _buttons.begin(); i != _buttons.end(); ++i) { + GUIButton *btn = (*i).get(); + btn->onEvent(eventType, param, localX, localY, currentItem); + if ((btn->getState() == BUTTON_PRESSED) && !selectionFlag) { + selectionFlag = true; + _highlightedIndex = btn->getTag(); + + switch (_highlightedIndex) { + case 0: + _vm->_mouse->setCursorNum(CURSOR_LOOK); + break; + case 1: + _vm->_mouse->setCursorNum(CURSOR_TAKE); + break; + case 2: + _vm->_mouse->setCursorNum(CURSOR_USE); + break; + case 3: + // TODO: Jump to abduction + break; + case 4: + _vm->loadMenu(GAME_MENU); + break; + case 5: + _inventory.scrollLeft(); + break; + case 6: + _inventory.scrollRight(); + break; + default: + break; + } + } + } + + return true; +} + +void GameInterfaceView::onRefresh(RectList *rects, M4Surface *destSurface) { + empty(); + + _statusText.onRefresh(); + _inventory.onRefresh(); + for (ButtonsIterator i = _buttons.begin(); i != _buttons.end(); ++i) + ((*i).get())->onRefresh(); + + View::onRefresh(rects, destSurface); +} + + +} // End of namespace M4 diff --git a/engines/m4/m4_views.h b/engines/m4/m4_views.h new file mode 100644 index 0000000000..2f2d450332 --- /dev/null +++ b/engines/m4/m4_views.h @@ -0,0 +1,118 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef M4_M4_VIEWS_H +#define M4_M4_VIEWS_H + +#include "m4/gui.h" +#include "m4/viewmgr.h" +#include "common/rect.h" +#include "common/list.h" +#include "common/ptr.h" + +namespace M4 { + +class GUIInventoryItem { +public: + const char *name; + const char *verb; + M4Surface *icon; + int iconIndex; + + GUIInventoryItem(const char *_name, const char *_verb, M4Surface *_icon, int _iconIndex) { + name = _name; _verb = verb; icon = _icon; iconIndex = _iconIndex; + } +}; + +class GUIInventory: public GUIRect { + typedef Common::List<Common::SharedPtr<GUIInventoryItem> > InventoryList; + typedef InventoryList::iterator ItemsIterator; +private: + InventoryList _inventoryItems; + Common::Point _cellSize; + Common::Point _cellCount; + bool _visible; + bool _scrollable; + int _scrollPosition; + int _highlightedIndex; + int _selectedIndex; + M4Engine *_vm; +public: + GUIInventory(View *owner, M4Engine *vm, const Common::Rect &bounds, + int horizCells, int vertCells, int cellWidth, int cellHeight, int tag); + + void onRefresh(); + bool onEvent(M4EventType eventType, int param, int x, int y, GUIObject *¤tItem); + + void add(const char *name, const char *verb, M4Surface *icon, int iconIndex); + bool remove(const char *name); + int getInsideIndex(int x, int y); + int getSelectedIndex() { return _selectedIndex; } + const char *getSelectedObjectName(); + void clearSelected() { + _selectedIndex = -1; + setHighlight(-1); + } + const Common::Point &getCellPosition(int index); + void setHighlight(int index); + bool needLeftButton() { return _scrollPosition != 0; } + bool needRightButton() { + return (uint)(_inventoryItems.size() - _scrollPosition) > (uint)(_cellCount.x * _cellCount.y); + } + void setScrollPosition(int value); + void scrollLeft() { setScrollPosition(_scrollPosition - 1); } + void scrollRight() { setScrollPosition(_scrollPosition + 1); } + void setVisible(bool value) { _visible = value; } +}; + +class GameInterfaceView: public View { + typedef Common::List<Common::SharedPtr<GUIButton> > ButtonList; + typedef ButtonList::iterator ButtonsIterator; +public: + SpriteAsset *_sprites; + ButtonList _buttons; + GUITextField _statusText; + GUIInventory _inventory; + int _highlightedIndex; + bool _selected; +private: + void setHighlightedButton(int index); +public: + GameInterfaceView(M4Engine *vm); + ~GameInterfaceView(); + + void onRefresh(RectList *rects, M4Surface *destSurface); + bool onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents); + void setStatusText(const char *text) { _statusText.setText(text); } + void cancelSentence() { setStatusText(NULL); } + void inventoryAdd(const char *name, const char *verb, int iconIndex) { + _inventory.add(name, verb, _sprites->getFrame(iconIndex), iconIndex); + } + bool inventoryRemove(const char *name) { return _inventory.remove(name); } +}; + +} + +#endif diff --git a/engines/m4/mads_anim.cpp b/engines/m4/mads_anim.cpp new file mode 100644 index 0000000000..4d481f9397 --- /dev/null +++ b/engines/m4/mads_anim.cpp @@ -0,0 +1,705 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "m4/mads_anim.h" +#include "m4/m4.h" + +namespace M4 { + +#define TEXTVIEW_LINE_SPACING 2 +#define TEXT_ANIMATION_DELAY 100 +#define TV_NUM_FADE_STEPS 40 +#define TV_FADE_DELAY_MILLI 50 + +TextviewView::TextviewView(M4Engine *vm): + View(vm, Common::Rect(0, 0, vm->_screen->width(), vm->_screen->height())), + _bgSurface(vm->_screen->width(), MADS_SURFACE_HEIGHT), + _textSurface(vm->_screen->width(), MADS_SURFACE_HEIGHT + vm->_font->getHeight() + + TEXTVIEW_LINE_SPACING) { + + _screenType = VIEWID_TEXTVIEW; + _screenFlags.layer = LAYER_BACKGROUND; + _screenFlags.visible = true; + _screenFlags.get = SCREVENT_ALL; + _callback = NULL; + _script = NULL; + _spareScreen = NULL; + _bgCurrent = NULL; + _bgSpare = NULL; + reset(); + + // Set up system palette colors and the two colors for text display + _vm->_palette->setMadsSystemPalette(); + RGB8 palData[3]; + palData[0].r = palData[0].g = palData[0].b = 0; + palData[1].r = 0; palData[1].g = palData[1].b = 252; + palData[2].r = 0; palData[2].g = palData[2].b = 180; + _vm->_palette->setPalette(&palData[0], 4, 3); + _vm->_palette->blockRange(4, 3); + + _vm->_font->setColors(5, 6, 4); + + empty(); + _bgSurface.empty(); + _textSurface.empty(); + + int y = (height() - MADS_SURFACE_HEIGHT) / 2; + setColor(2); + hLine(0, width() - 1, y - 2); + hLine(0, width() - 1, height() - y + 1); +} + +TextviewView::~TextviewView() { + if (_script) + _vm->res()->toss(_resourceName); + if (_spareScreen) + delete _spareScreen; + if (_bgCurrent) + delete _bgCurrent; + if (_bgSpare) + delete _bgSpare; +} + +void TextviewView::reset() { + _bgSurface.empty(); + _textSurface.empty(); + _animating = false; + _panX = 0; + _panY = 0; + _panSpeed = 0; + _soundDriverLoaded = false; + Common::set_to(&_spareScreens[0], &_spareScreens[10], 0); + _scrollCount = 0; + _lineY = -1; + _scrollTimeout = 0; + _panCountdown = 0; + _processEvents = true; +} + +void TextviewView::setScript(const char *resourceName, TextviewCallback callback) { + _callback = callback; + if (_script) + _vm->res()->toss(_resourceName); + if (_spareScreen) { + delete _spareScreen; + _spareScreen = NULL; + } + + reset(); + + strncpy(_resourceName, resourceName, 15); + _resourceName[15] = '\0'; + if (!strchr(_resourceName, '.')) + strcat(_resourceName, ".txr"); + + _script = _vm->res()->get(_resourceName); + + processLines(); +} + +bool TextviewView::onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents) { + if (!_processEvents) + return false; + + // Wait for the Escape key or a mouse press + if (((eventType == KEVENT_KEY) && (param == Common::KEYCODE_ESCAPE)) || + (eventType == MEVENT_LEFT_RELEASE) || (eventType == MEVENT_RIGHT_RELEASE)) { + scriptDone(); + captureEvents = false; + return true; + } + + return false; +} + +void TextviewView::updateState() { + if (!_animating) + return; + + // Only update state if wait period has expired + uint32 currTime = g_system->getMillis(); + + // If a screen transition is in progress and it's time for another column, handle it + if (_spareScreen) { + byte *srcP = _spareScreen->getBasePtr(_translationX, 0); + byte *destP = _bgSurface.getBasePtr(_translationX, 0); + + for (int y = 0; y < _bgSurface.height(); ++y, srcP += _spareScreen->width(), + destP += _bgSurface.width()) { + *destP = *srcP; + } + + if (++_translationX >= _bgSurface.width()) { + // Surface transition is complete + delete _spareScreen; + _spareScreen = NULL; + + _vm->_palette->deleteRange(_bgCurrent); + delete _bgCurrent; + _bgCurrent = _bgSpare; + _bgSpare = NULL; + } + } + + // Make sure it's time for an update + if (currTime < _scrollTimeout) + return; + _scrollTimeout = g_system->getMillis() + TEXT_ANIMATION_DELAY; + + // If any panning values are set, pan the background surface + if ((_panX != 0) || (_panY != 0)) { + if (_panCountdown > 0) { + --_panCountdown; + return; + } + + // Handle horizontal panning + if (_panX != 0) { + byte *lineTemp = new byte[_panX]; + for (int y = 0; y < _bgSurface.height(); ++y) { + byte *pixelsP = _bgSurface.getBasePtr(0, y); + + // Copy the first X pixels into temp buffer, move the rest of the line + // to the start of the line, and then move temp buffer pixels to end of line + Common::copy(pixelsP, pixelsP + _panX, lineTemp); + Common::copy(pixelsP + _panX, pixelsP + _bgSurface.width(), pixelsP); + Common::copy(lineTemp, lineTemp + _panX, pixelsP + _bgSurface.width() - _panX); + } + + delete[] lineTemp; + } + + // Handle vertical panning + if (_panY != 0) { + // Store the bottom Y lines into a temp buffer, move the rest of the lines down, + // and then copy the stored lines back to the top of the screen + byte *linesTemp = new byte[_panY * _bgSurface.width()]; + byte *pixelsP = _bgSurface.getBasePtr(0, _bgSurface.height() - _panY); + Common::copy(pixelsP, pixelsP + _bgSurface.width() * _panY, linesTemp); + + for (int y = _bgSurface.height() - 1; y >= _panY; --y) { + byte *destP = _bgSurface.getBasePtr(0, y); + byte *srcP = _bgSurface.getBasePtr(0, y - _panY); + Common::copy(srcP, srcP + _bgSurface.width(), destP); + } + + Common::copy(linesTemp, linesTemp + _panY * _bgSurface.width(), (byte *)_bgSurface.pixels); + delete[] linesTemp; + } + } + + // Scroll the text surface up by one row + byte *pixelsP = (byte *)_textSurface.pixels; + Common::copy(pixelsP + width(), pixelsP + _textSurface.width() * _textSurface.height(), pixelsP); + pixelsP = _textSurface.getBasePtr(0, _textSurface.height() - 1); + Common::set_to(pixelsP, pixelsP + _textSurface.width(), _vm->_palette->BLACK); + + if (_scrollCount > 0) { + // Handling final scrolling of text off of screen + if (--_scrollCount == 0) { + scriptDone(); + return; + } + } else { + // Handling a text row + if (++_lineY == (_vm->_font->getHeight() + TEXTVIEW_LINE_SPACING)) + processLines(); + } + + // Refresh the view + int yp = (height() - _bgSurface.height()) / 2; + _bgSurface.copyTo(this, 0, yp); + _textSurface.copyTo(this, Common::Rect(0, 0, _textSurface.width(), _bgSurface.height()), + 0, yp, _vm->_palette->BLACK); +} + +void TextviewView::scriptDone() { + TextviewCallback fn = _callback; + M4Engine *vm = _vm; + + // Remove this view from manager and destroy it + _vm->_viewManager->deleteView(this); + + if (fn) + fn(vm); +} + +void TextviewView::processLines() { + if (_script->eos()) + error("Attempted to read past end of response file"); + + while (!_script->eos()) { + _script->readLine(_currentLine, 79); + + // Commented out line, so go loop for another + if (_currentLine[0] == '#') + continue; + + // Process the line + char *cStart = strchr(_currentLine, '['); + if (cStart) { + while (cStart) { + // Loop for possible multiple commands on one line + char *cEnd = strchr(_currentLine, ']'); + if (!cEnd) + error("Unterminated command '%s' in response file", _currentLine); + + *cEnd = '\0'; + processCommand(); + + // Copy rest of line (if any) to start of buffer + strcpy(_currentLine, cEnd + 1); + + cStart = strchr(_currentLine, '['); + } + + if (_currentLine[0]) { + processText(); + break; + } + + } else { + processText(); + break; + } + } +} + +void TextviewView::processCommand() { + char commandStr[80]; + char *paramP; + strcpy(commandStr, _currentLine + 1); + str_upper(commandStr); + + if (!strncmp(commandStr, "BACKGROUND", 10)) { + // Set the background + paramP = commandStr + 10; + int screenId = getParameter(¶mP); + _bgSurface.loadBackground(screenId, &_bgCurrent); + _vm->_palette->addRange(_bgCurrent); + _bgSurface.translate(_bgCurrent); + + } else if (!strncmp(commandStr, "GO", 2)) { + _animating = true; + + // Grab what the final palete will be + RGB8 destPalette[256]; + _vm->_palette->grabPalette(destPalette, 0, 256); + + // Copy the loaded background, if any, to the view surface + int yp = (height() - _bgSurface.height()) / 2; + _bgSurface.copyTo(this, 0, yp); + + // Handle fade-in + _processEvents = false; // stop processing events during fade-in + _vm->_palette->fadeIn(TV_NUM_FADE_STEPS, TV_FADE_DELAY_MILLI, destPalette, 256); + _processEvents = true; + + } else if (!strncmp(commandStr, "PAN", 3)) { + // Set panning values + paramP = commandStr + 3; + int panX = getParameter(¶mP); + int panY = getParameter(¶mP); + int panSpeed = getParameter(¶mP); + + if ((panX != 0) || (panY != 0)) { + _panX = panX; + _panY = panY; + _panSpeed = panSpeed; + } + + } else if (!strncmp(commandStr, "DRIVER", 6)) { + // Set the driver to use + // TODO: Handling of the sound drivers + + } else if (!strncmp(commandStr, "SOUND", 5)) { + // Set sound number + paramP = commandStr + 5; + //int soundId = getParameter(¶mP); + + //TODO: Proper handling of the sound drivers/sounds + //if (!_soundDriverLoaded) + // error("Attempted to set sound without loading any driver\n"); + + } else if (!strncmp(commandStr, "COLOR", 5) && ((commandStr[5] == '0') || (commandStr[5] == '1'))) { + // Set the text colors + int index = commandStr[5] - '0'; + paramP = commandStr + 6; + + RGB8 palEntry; + palEntry.r = getParameter(¶mP) << 2; + palEntry.g = getParameter(¶mP) << 2; + palEntry.b = getParameter(¶mP) << 2; + _vm->_palette->setPalette(&palEntry, 5 + index, 1); + + } else if (!strncmp(commandStr, "SPARE", 5)) { + // Sets a secondary background number that can be later switched in with a PAGE command + paramP = commandStr + 6; + int spareIndex = commandStr[5] - '0'; + if ((spareIndex >= 0) && (spareIndex <= 9)) { + int screenId = getParameter(¶mP); + + _spareScreens[spareIndex] = screenId; + } + + } else if (!strncmp(commandStr, "PAGE", 4)) { + // Signals to change to a previous specified secondary background + paramP = commandStr + 4; + int spareIndex = getParameter(¶mP); + + // Only allow background switches if one isn't currently in progress + if (!_spareScreen && (_spareScreens[spareIndex] != 0)) { + _spareScreen = new M4Surface(width(), MADS_SURFACE_HEIGHT); + _spareScreen->loadBackground(_spareScreens[spareIndex], &_bgSpare); + _vm->_palette->addRange(_bgSpare); + _spareScreen->translate(_bgSpare); + + _translationX = 0; + } + + } else { + error("Unknown response command: '%s'", commandStr); + } +} + +int TextviewView::getParameter(char **paramP) { + if ((**paramP != '=') && (**paramP != ',')) + return 0; + + int result = 0; + ++*paramP; + while ((**paramP >= '0') && (**paramP <= '9')) { + result = result * 10 + (**paramP - '0'); + ++*paramP; + } + + return result; +} + +void TextviewView::processText() { + int lineWidth, xStart; + + if (!strcmp(_currentLine, "***")) { + // Special signifier for end of script + _scrollCount = _vm->_font->getHeight() * 13; + _lineY = -1; + return; + } + + _lineY = 0; + + // Lines are always centered, except if line contains a '@', in which case the + // '@' marks the position that must be horizontally centered + char *centerP = strchr(_currentLine, '@'); + if (centerP) { + *centerP = '\0'; + xStart = (width() / 2) - _vm->_font->getWidth(_currentLine); + + // Delete the @ character and shift back the remainder of the string + char *p = centerP + 1; + if (*p == ' ') ++p; + strcpy(centerP, p); + + } else { + lineWidth = _vm->_font->getWidth(_currentLine); + xStart = (width() - lineWidth) / 2; + } + + // Copy the text line onto the bottom of the textSurface surface, which will allow it + // to gradually scroll onto the screen + int yp = _textSurface.height() - _vm->_font->getHeight() - TEXTVIEW_LINE_SPACING; + _textSurface.fillRect(Common::Rect(0, yp, _textSurface.width(), _textSurface.height()), + _vm->_palette->BLACK); + _vm->_font->writeString(&_textSurface, _currentLine, xStart, yp); +} + + +//-------------------------------------------------------------------------- + +AnimviewView::AnimviewView(M4Engine *vm): + View(vm, Common::Rect(0, 0, vm->_screen->width(), vm->_screen->height())), + _bgSurface(vm->_screen->width(), MADS_SURFACE_HEIGHT) { + + _screenType = VIEWID_ANIMVIEW; + _screenFlags.layer = LAYER_BACKGROUND; + _screenFlags.visible = true; + _screenFlags.get = SCREVENT_ALL; + _callback = NULL; + _script = NULL; + _palData = NULL; + _previousUpdate = 0; + _transition = kTransitionNone; + reset(); + + // Set up system palette colors + _vm->_palette->setMadsSystemPalette(); + + empty(); + _bgSurface.empty(); + + int y = (height() - MADS_SURFACE_HEIGHT) / 2; + setColor(2); + hLine(0, width() - 1, y - 2); + hLine(0, width() - 1, height() - y + 1); +} + +AnimviewView::~AnimviewView() { + if (_script) + _vm->res()->toss(_resourceName); +} + +void AnimviewView::reset() { + _bgSurface.empty(); + _soundDriverLoaded = false; +} + +void AnimviewView::setScript(const char *resourceName, AnimviewCallback callback) { + _callback = callback; + if (_script) + _vm->res()->toss(_resourceName); + + reset(); + + strncpy(_resourceName, resourceName, 15); + _resourceName[15] = '\0'; + if (!strchr(_resourceName, '.')) + strcat(_resourceName, ".res"); + + _script = _vm->res()->get(_resourceName); + + processLines(); +} + +bool AnimviewView::onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents) { + // Wait for the Escape key or a mouse press + if (((eventType == KEVENT_KEY) && (param == Common::KEYCODE_ESCAPE)) || + (eventType == MEVENT_LEFT_RELEASE) || (eventType == MEVENT_RIGHT_RELEASE)) { + scriptDone(); + captureEvents = false; + return true; + } + + return false; +} + +void AnimviewView::updateState() { + char bgFile[10]; + int bgNumber = 0; + + // Only update state if wait period has expired + if (_previousUpdate > 0) { + if (g_system->getMillis() - _previousUpdate < 3000) { + return; + } else { + // time for an update + _previousUpdate = g_system->getMillis(); + } + } else { + _previousUpdate = g_system->getMillis(); + return; + } + uint32 currTime = g_system->getMillis(); + + strncpy(bgFile, _currentFile, 5); + bgFile[0] = bgFile[2]; + bgFile[1] = bgFile[3]; + bgFile[2] = bgFile[4]; + bgFile[3] = '\0'; + bgNumber = atoi(bgFile); + sprintf(bgFile, "rm%i.art", bgNumber); + + // Not all scenes have a background. If there is one, refresh it + if (_vm->_resourceManager->resourceExists(bgFile)) { + if (_palData) { + _vm->_palette->deleteRange(_palData); + delete _palData; + } + _bgSurface.loadBackground(bgNumber, &_palData); + _vm->_palette->addRange(_palData); + _bgSurface.translate(_palData); + } + + // Grab what the final palete will be + RGB8 destPalette[256]; + _vm->_palette->grabPalette(destPalette, 0, 256); + + // Handle scene transition + switch (_transition) { + case kTransitionNone: + // nothing to do + break; + case kTransitionFadeIn: + case kTransitionFadeIn2: + _vm->_palette->fadeIn(TV_NUM_FADE_STEPS, TV_FADE_DELAY_MILLI, destPalette, 256); + break; + case kTransitionBoxInBottomLeft: + case kTransitionBoxInBottomRight: + case kTransitionBoxInTopLeft: + case kTransitionBoxInTopRight: + // unused + warning("Unsupported box in scene effect"); + break; + case kTransitionPanLeftToRight: + // TODO + break; + case kTransitionPanRightToLeft: + // TODO + break; + case kTransitionCircleIn: + // TODO + break; + default: + // nothing to do + break; + } + + // Refresh the view + int yp = (height() - _bgSurface.height()) / 2; + _bgSurface.copyTo(this, 0, yp); + + // Read next line + processLines(); +} + +void AnimviewView::scriptDone() { + AnimviewCallback fn = _callback; + M4Engine *vm = _vm; + + // Remove this view from manager and destroy it + _vm->_viewManager->deleteView(this); + + if (fn) + fn(vm); +} + +void AnimviewView::processLines() { + if (_script->eos()) { + // end of script, end animation + scriptDone(); + return; + } + + while (!_script->eos()) { + _script->readLine(_currentLine, 79); + + // Process the line + char *cStart = strchr(_currentLine, '-'); + if (cStart) { + while (cStart) { + // Loop for possible multiple commands on one line + char *cEnd = strchr(_currentLine, ' '); + if (!cEnd) + error("Unterminated command '%s' in response file", _currentLine); + + *cEnd = '\0'; + processCommand(); + + // Copy rest of line (if any) to start of buffer + // Don't use strcpy() here, because if the + // rest of the line is the longer of the two + // strings, the memory areas will overlap. + memmove(_currentLine, cEnd + 1, strlen(cEnd + 1) + 1); + + cStart = strchr(_currentLine, '-'); + } + + if (_currentLine[0]) { + sprintf(_currentFile, "%s", _currentLine); + //printf("File: %s\n", _currentLine); + break; + } + + } else { + sprintf(_currentFile, "%s", _currentLine); + //printf("File: %s\n", _currentLine); + break; + } + } +} + +/* +Switches are: (taken from the help of the original executable) + -b Toggle background load status off/on. + -c:char Specify sound card id letter. + -g Stay in graphics mode on exit. + -h[:ex] Disable EMS/XMS high memory support. + -i Switch sound interrupts mode off/on. + -j Wait for music to finish at end. + -k Keystroke jumps to end instead of abort. + -m Operate in non-MADS mode. + -o:xxx Specify opening special effect. + -p Switch MADS path mode to CONCAT. + -r[:abn] Resynch timer (always, beginning, never). + -s:file Specify sound file. + -u[:...] Use DMA speech [optional: addr,type,irq,drq]. + -w Toggle white bars off/on. + -x Exit immediately after last frame. + -y Do not clear screen initially + -z Display statistics after run. + + Opening special effects are: + 0: no effect + 1: fade in + 2: fade in (looks to be the same as 1) + 3: box in from bottom left (unused) + 4: box in from bottom right (unused) + 5: box in from top left (unused) + 6: box in from top right (unused) + 7: pan in from left to right + 8: pan in from right to left + 9: circle in (new scene appears in a circle that expands) + + Animview is ran like this from the original games: + animview.exe @resfilename -c:P,220,20 -u:220,20,07,01 -p -a:mainmenu -p + + Note that the first -p is necessary to watch the animation, otherwise + the program just exits + + To watch an animation within the *.res file, just run animview like this: + animview.exe -x -r:b -o:2 animfilename -p +*/ +void AnimviewView::processCommand() { + char commandStr[80]; + strcpy(commandStr, _currentLine + 1); + str_upper(commandStr); + char *param = commandStr; + + if (!strncmp(commandStr, "X", 1)) { + //printf("X "); + } else if (!strncmp(commandStr, "W", 1)) { + //printf("W "); + } else if (!strncmp(commandStr, "R", 1)) { + param = param + 2; + //printf("R:%s ", param); + } else if (!strncmp(commandStr, "O", 1)) { + param = param + 2; + //printf("O:%i ", atoi(param)); + _transition = atoi(param); + } else { + error("Unknown response command: '%s'", commandStr); + } +} + +} diff --git a/engines/m4/mads_anim.h b/engines/m4/mads_anim.h new file mode 100644 index 0000000000..713e3bbfd5 --- /dev/null +++ b/engines/m4/mads_anim.h @@ -0,0 +1,118 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef M4_MADS_ANIM_H +#define M4_MADS_ANIM_H + +#include "m4/viewmgr.h" + +namespace M4 { + +enum SceneTransition { + kTransitionNone = 0, + kTransitionFadeIn = 1, + kTransitionFadeIn2 = 2, + kTransitionBoxInBottomLeft = 3, + kTransitionBoxInBottomRight = 4, + kTransitionBoxInTopLeft = 5, + kTransitionBoxInTopRight = 6, + kTransitionPanLeftToRight = 7, + kTransitionPanRightToLeft = 8, + kTransitionCircleIn = 9 +}; + +typedef void (*TextviewCallback)(M4Engine *vm); + +class TextviewView: public View { +private: + bool _animating; + + char _resourceName[80]; + Common::SeekableReadStream *_script; + uint16 _spareScreens[10]; + M4Surface *_spareScreen; + RGBList *_bgCurrent, *_bgSpare; + int _translationX; + int _panX, _panY, _panSpeed; + int _panCountdown; + char _currentLine[80]; + uint32 _scrollTimeout; + int _scrollCount; + int _lineY; + M4Surface _bgSurface; + M4Surface _textSurface; + TextviewCallback _callback; + bool _soundDriverLoaded; + bool _processEvents; + + void reset(); + void processLines(); + void processCommand(); + void processText(); + int getParameter(char **paramP); +public: + TextviewView(M4Engine *vm); + ~TextviewView(); + + void setScript(const char *resourceName, TextviewCallback callback); + bool isAnimating() { return _animating; } + void scriptDone(); + + bool onEvent(M4EventType eventType, int param1, int x, int y, bool &captureEvents); + void updateState(); +}; + +typedef void (*AnimviewCallback)(M4Engine *vm); + +class AnimviewView: public View { +private: + char _resourceName[80]; + Common::SeekableReadStream *_script; + uint32 _previousUpdate; + char _currentLine[80]; + char _currentFile[10]; + M4Surface _bgSurface; + AnimviewCallback _callback; + bool _soundDriverLoaded; + RGBList *_palData; + int _transition; + + void reset(); + void processLines(); + void processCommand(); +public: + AnimviewView(M4Engine *vm); + ~AnimviewView(); + + void setScript(const char *resourceName, AnimviewCallback callback); + void scriptDone(); + + bool onEvent(M4EventType eventType, int param1, int x, int y, bool &captureEvents); + void updateState(); +}; + +} + +#endif diff --git a/engines/m4/mads_menus.cpp b/engines/m4/mads_menus.cpp new file mode 100644 index 0000000000..2857fc8080 --- /dev/null +++ b/engines/m4/mads_menus.cpp @@ -0,0 +1,586 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "m4/mads_menus.h" +#include "m4/m4.h" + +namespace M4 { + +#define REX_MENUSCREEN 990 +#define PHANTOM_MENUSCREEN 920 +#define DRAGON_MENUSCREEN 922 + +static Common::Point rexMenuItemPosList[6] = { + Common::Point(12, 68), Common::Point(12, 87), Common::Point(12, 107), + Common::Point(184, 75), Common::Point(245, 75), Common::Point(184, 99) +}; + +static Common::Point dragonMenuItemPosList[6] = { + Common::Point(46, 187), Common::Point(92, 187), Common::Point(138, 187), + Common::Point(184, 187), Common::Point(230, 187), Common::Point(276, 187) +}; + +#define DRAGON_MENU_BUTTON_W = 45 +#define DRAGON_MENU_BUTTON_H = 11 + +RexMainMenuView::RexMainMenuView(M4Engine *vm): + View(vm, Common::Rect(0, 0, vm->_screen->width(), vm->_screen->height())) { + + _screenType = VIEWID_MAINMENU; + _screenFlags.get = SCREVENT_ALL; + + _delayTimeout = 0; + _menuItem = NULL; + _menuItemIndex = 0; + _frameIndex = 0; + _highlightedIndex = -1; + _skipFlag = false; + + // Load the background for the Rex Nebular game + _bgSurface = new M4Surface(width(), MADS_SURFACE_HEIGHT); + _bgSurface->loadBackground(REX_MENUSCREEN, &_bgPalData); + _vm->_palette->addRange(_bgPalData); + _bgSurface->translate(_bgPalData); + + int row = (height() - MADS_SURFACE_HEIGHT) / 2; + _bgSurface->copyTo(this, 0, row); + + // Add in the bounding lines for the background + setColor(2); + hLine(0, width() - 1, row - 1); + hLine(0, width() - 1, height() - row + 1); +} + +RexMainMenuView::~RexMainMenuView() { + if (_menuItem) + delete _menuItem; + + _vm->_palette->deleteRange(_bgPalData); + + delete _bgPalData; + delete _bgSurface; + + for (uint i = 0; i < _itemPalData.size(); ++i) { + _vm->_palette->deleteRange(_itemPalData[i]); + delete _itemPalData[i]; + } +} + +bool RexMainMenuView::onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents) { + // Handle keypresses - these can be done at any time, even when the menu items are being drawn + if (eventType == KEVENT_KEY) { + switch (param) { + case Common::KEYCODE_ESCAPE: + case Common::KEYCODE_F6: + handleAction(EXIT); + break; + + case Common::KEYCODE_F1: + handleAction(START_GAME); + break; + + case Common::KEYCODE_F2: + handleAction(RESUME_GAME); + break; + + case Common::KEYCODE_F3: + handleAction(SHOW_INTRO); + break; + + case Common::KEYCODE_F4: + handleAction(CREDITS); + break; + + case Common::KEYCODE_F5: + handleAction(QUOTES); + break; + + case Common::KEYCODE_s: + // Goodness knows why, but Rex has a key to restart the menuitem animations + + // Delete the current menu items + if (_menuItem) + delete _menuItem; + + _vm->_palette->deleteRange(_bgPalData); + delete _bgPalData; + for (uint i = 0; i < _itemPalData.size(); ++i) { + _vm->_palette->deleteRange(_itemPalData[i]); + delete _itemPalData[i]; + } + _itemPalData.clear(); + + // Reload the background surface, and restart the animation + _bgSurface->loadBackground(REX_MENUSCREEN, &_bgPalData); + _vm->_palette->addRange(_bgPalData); + _bgSurface->translate(_bgPalData); + + _menuItemIndex = 0; + _skipFlag = false; + _menuItem = NULL; + _vm->_mouse->cursorOff(); + break; + + default: + // Any other key skips the menu animation + _skipFlag = true; + return false; + } + + return true; + } + + int row = (height() - MADS_SURFACE_HEIGHT) / 2; + int menuIndex; + + switch (eventType) { + case MEVENT_LEFT_CLICK: + case MEVENT_LEFT_DRAG: + if (_vm->_mouse->getCursorOn()) { + menuIndex = getHighlightedItem(x, y); + if (menuIndex != _highlightedIndex) { + _bgSurface->copyTo(this, 0, row); + + _highlightedIndex = menuIndex; + if (_highlightedIndex != -1) { + M4Sprite *spr = _menuItem->getFrame(_highlightedIndex); + const Common::Point &pt = rexMenuItemPosList[_highlightedIndex]; + spr->copyTo(this, pt.x, row + pt.y, 0); + } + } + } else { + // Skip the menu animation + _skipFlag = true; + } + return true; + + case MEVENT_LEFT_RELEASE: + if (_highlightedIndex != -1) + handleAction((MadsGameAction) _highlightedIndex); + return true; + + default: + break; + } + + return false; +} + +void RexMainMenuView::updateState() { + char resName[20]; + Common::SeekableReadStream *data; + int row = (height() - MADS_SURFACE_HEIGHT) / 2; + int itemSize; + + uint32 currTime = g_system->getMillis(); + if (currTime < _delayTimeout) + return; + _delayTimeout = currTime + MADS_MENU_ANIM_DELAY; + + // Rex Nebular handling to cycle through the animated display of the menu items + if (_menuItemIndex == 7) + return; + + // If the user has chosen to skip the menu animation, show the menu immediately + if (_skipFlag && !_vm->_mouse->getCursorOn()) { + // Clear any pending animation + _bgSurface->copyTo(this, 0, row); + // Quickly loop through all the menuitems to display each's final frame + while (_menuItemIndex < 7) { + + if (_menuItem) { + // Draw the final frame of the menuitem + M4Sprite *spr = _menuItem->getFrame(0); + itemSize = _menuItem->getFrame(0)->height(); + spr->copyTo(this, rexMenuItemPosList[_menuItemIndex - 1].x, + rexMenuItemPosList[_menuItemIndex - 1].y + row + (itemSize / 2) - (spr->height() / 2), 0); + + delete _menuItem; + copyTo(_bgSurface, Common::Rect(0, row, width(), row + MADS_SURFACE_HEIGHT), 0, 0); + } + + // Get the next sprite set + sprintf(resName, "RM%dA%d.SS", REX_MENUSCREEN, ++_menuItemIndex); + data = _vm->res()->get(resName); + _menuItem = new SpriteAsset(_vm, data, data->size(), resName); + _vm->res()->toss(resName); + + // Slot it into available palette space + RGBList *palData = _menuItem->getRgbList(); + _vm->_palette->addRange(palData); + _menuItem->translate(palData, true); + _itemPalData.push_back(palData); + } + + _vm->_mouse->cursorOn(); + return; + } + + if ((_menuItemIndex == 0) || (_frameIndex == 0)) { + // Get the next menu item + if (_menuItem) { + delete _menuItem; + + // Copy over the current display surface area to the background, so the final frame + // of the previous menuitem should be kept on the screen + copyTo(_bgSurface, Common::Rect(0, row, width(), row + MADS_SURFACE_HEIGHT), 0, 0); + } + + // Get the next menuitem resource + sprintf(resName, "RM%dA%d.SS", REX_MENUSCREEN, ++_menuItemIndex); + data = _vm->res()->get(resName); + _menuItem = new SpriteAsset(_vm, data, data->size(), resName); + _vm->res()->toss(resName); + + // Slot it into available palette space + RGBList *palData = _menuItem->getRgbList(); + _vm->_palette->addRange(palData); + _menuItem->translate(palData, true); + _itemPalData.push_back(palData); + + _frameIndex = _menuItem->getCount() - 1; + + // If the final resource is now loaded, which contains the highlighted versions of + // each menuitem, then the startup animation is complete + if (_menuItemIndex == 7) { + _vm->_mouse->cursorOn(); + return; + } + } else { + --_frameIndex; + } + + // Move to the next menuitem frame + + itemSize = _menuItem->getFrame(0)->height(); + + _bgSurface->copyTo(this, 0, row); + M4Sprite *spr = _menuItem->getFrame(_frameIndex); + spr->copyTo(this, rexMenuItemPosList[_menuItemIndex - 1].x, rexMenuItemPosList[_menuItemIndex - 1].y + + row + (itemSize / 2) - (spr->height() / 2), 0); +} + +int RexMainMenuView::getHighlightedItem(int x, int y) { + y -= (height() - MADS_SURFACE_HEIGHT) / 2; + + for (int index = 0; index < 6; ++index) { + const Common::Point &pt = rexMenuItemPosList[index]; + M4Sprite *spr = _menuItem->getFrame(index); + + if ((x >= pt.x) && (y >= pt.y) && (x < (pt.x + spr->width())) && (y < (pt.y + spr->height()))) + return index; + } + + return -1; +} + +void RexMainMenuView::handleAction(MadsGameAction action) { + M4Engine *vm = _vm; + vm->_mouse->cursorOff(); + vm->_viewManager->deleteView(this); + + switch (action) { + case START_GAME: + case RESUME_GAME: + // Load a sample starting scene - note that, currently, calling loadScene automatically + // removes this menu screen from being displayed + vm->_mouse->cursorOn(); + vm->_viewManager->addView(vm->_scene); + vm->_scene->loadScene(101); + return; + + case SHOW_INTRO: + vm->_viewManager->showAnimView("@rexopen"); + break; + + case CREDITS: + vm->_viewManager->showTextView("credits"); + return; + + case QUOTES: + vm->_viewManager->showTextView("quotes"); + return; + + case EXIT: + { + // When the Exit action is done from the menu, show one of two possible advertisements + + // Activate the scene display with the specified scene + bool altAdvert = vm->_random->getRandomNumber(1000) >= 500; + vm->_scene->loadScene(altAdvert ? 995 : 996); + vm->_viewManager->addView(vm->_scene); + + vm->_viewManager->refreshAll(); + vm->delay(10000); + + vm->_events->quitFlag = true; + return; + } + break; + default: + break; + } +} + +//-------------------------------------------------------------------------- + +MadsMainMenuView::MadsMainMenuView(M4Engine *vm): + View(vm, Common::Rect(0, 0, vm->_screen->width(), vm->_screen->height())) { + +} + +bool MadsMainMenuView::onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents) { + return false; +} + +void MadsMainMenuView::updateState() { + // TODO: Implement me +} + +//-------------------------------------------------------------------------- + +DragonMainMenuView::DragonMainMenuView(M4Engine *vm): + View(vm, Common::Rect(0, 0, vm->_screen->width(), vm->_screen->height())) { + + _screenType = VIEWID_MAINMENU; + _screenFlags.get = SCREVENT_ALL; + + _delayTimeout = 0; + _menuItem = NULL; + _menuItemIndex = 0; + _frameIndex = 0; + _highlightedIndex = -1; + _skipFlag = false; + + // Load the background for the Dragonsphere game + this->loadBackground(942, &_bgPalData); + _vm->_palette->addRange(_bgPalData); + this->translate(_bgPalData); +} + +DragonMainMenuView::~DragonMainMenuView() { + //if (_menuItem) + // delete _menuItem; + + _vm->_palette->deleteRange(_bgPalData); + + delete _bgPalData; + + for (uint i = 0; i < _itemPalData.size(); ++i) { + _vm->_palette->deleteRange(_itemPalData[i]); + delete _itemPalData[i]; + } +} + +bool DragonMainMenuView::onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents) { + char resName[20]; + Common::SeekableReadStream *data; + + // Handle keypresses - these can be done at any time, even when the menu items are being drawn + if (eventType == KEVENT_KEY) { + switch (param) { + case Common::KEYCODE_ESCAPE: + case Common::KEYCODE_F6: + handleAction(EXIT); + break; + + case Common::KEYCODE_F1: + handleAction(START_GAME); + break; + + case Common::KEYCODE_F2: + handleAction(RESUME_GAME); + break; + + case Common::KEYCODE_F3: + handleAction(SHOW_INTRO); + break; + + case Common::KEYCODE_F4: + handleAction(CREDITS); + break; + + default: + // Any other key skips the menu animation + _skipFlag = true; + return false; + } + + return true; + } + + int menuIndex; + + switch (eventType) { + case MEVENT_LEFT_CLICK: + case MEVENT_LEFT_DRAG: + if (_vm->_mouse->getCursorOn()) { + menuIndex = getHighlightedItem(x, y); + if (menuIndex != _highlightedIndex) { + + _highlightedIndex = menuIndex; + if (_highlightedIndex != -1) { + sprintf(resName, "MAIN%d.SS", menuIndex); + data = _vm->res()->get(resName); + _menuItem = new SpriteAsset(_vm, data, data->size(), resName); + _vm->res()->toss(resName); + + M4Sprite *spr = _menuItem->getFrame(1); + spr->copyTo(this, spr->xOffset - 25, spr->yOffset - spr->height()); + } + } + } else { + // Skip the menu animation + _skipFlag = true; + } + return true; + + case MEVENT_LEFT_RELEASE: + if (_highlightedIndex != -1) + handleAction((MadsGameAction) _highlightedIndex); + return true; + + default: + break; + } + + return false; +} + +void DragonMainMenuView::updateState() { + char resName[20]; + Common::SeekableReadStream *data; + RGBList *palData; + M4Sprite *spr; + + if (_menuItemIndex == 6) + return; + + while (_menuItemIndex < 6) { + sprintf(resName, "MAIN%d.SS", _menuItemIndex); + data = _vm->res()->get(resName); + _menuItem = new SpriteAsset(_vm, data, data->size(), resName); + _vm->res()->toss(resName); + + // Slot it into available palette space + palData = _menuItem->getRgbList(); + _vm->_palette->addRange(palData); + _menuItem->translate(palData, true); + _itemPalData.push_back(palData); + + spr = _menuItem->getFrame(0); + spr->copyTo(this, spr->xOffset - 25, spr->yOffset - spr->height()); + + if (_menuItemIndex != 5) + delete _menuItem; + _menuItemIndex++; + } + + // Sphere + sprintf(resName, "RM920X0.SS"); + data = _vm->res()->get(resName); + _menuItem = new SpriteAsset(_vm, data, data->size(), resName); + _vm->res()->toss(resName); + + // Slot it into available palette space + palData = _menuItem->getRgbList(); + _vm->_palette->addRange(palData); + _menuItem->translate(palData, true); + _itemPalData.push_back(palData); + + spr = _menuItem->getFrame(0); // empty sphere + spr->copyTo(this, spr->xOffset - 75, spr->yOffset - spr->height()); + spr = _menuItem->getFrame(1); // dragon inside sphere + spr->copyTo(this, spr->xOffset - 75, spr->yOffset - spr->height()); + + // Dragonsphere letters + sprintf(resName, "RM920X3.SS"); + data = _vm->res()->get(resName); + _menuItem = new SpriteAsset(_vm, data, data->size(), resName); + _vm->res()->toss(resName); + + // Slot it into available palette space + palData = _menuItem->getRgbList(); + _vm->_palette->addRange(palData); + _menuItem->translate(palData, true); + _itemPalData.push_back(palData); + + spr = _menuItem->getFrame(1); + // FIXME: We assume that the transparent color is the color of the top left pixel + byte *transparentColor = (byte *)spr->pixels; + spr->copyTo(this, spr->xOffset - 140, spr->yOffset - spr->height(), (int)*transparentColor); + + _vm->_mouse->cursorOn(); +} + +int DragonMainMenuView::getHighlightedItem(int x, int y) { + y -= (height() - MADS_SURFACE_HEIGHT) / 2; + + for (int index = 0; index < 6; ++index) { + const Common::Point &pt = dragonMenuItemPosList[index]; + M4Sprite *spr = _menuItem->getFrame(0); + + if ((x >= pt.x - 25) && (y >= pt.y - spr->height()) && (x < (pt.x - 25 + spr->width())) && (y < (pt.y))) { + printf("x = %d, y = %d, index = %d\n", x, y, index); + return index; + } + } + + return -1; +} + +void DragonMainMenuView::handleAction(MadsGameAction action) { + M4Engine *vm = _vm; + vm->_mouse->cursorOff(); + vm->_viewManager->deleteView(this); + + switch (action) { + case START_GAME: + case RESUME_GAME: + // Load a sample starting scene - note that, currently, calling loadScene automatically + // removes this menu screen from being displayed + vm->_mouse->cursorOn(); + vm->_viewManager->addView(vm->_scene); + vm->_scene->loadScene(101); + return; + + case SHOW_INTRO: + vm->_viewManager->showAnimView("@dragon"); + break; + + case CREDITS: + vm->_viewManager->showTextView("credits"); + return; + + case EXIT: + vm->_events->quitFlag = true; + return; + break; + default: + break; + } +} + +} diff --git a/engines/m4/mads_menus.h b/engines/m4/mads_menus.h new file mode 100644 index 0000000000..36d1e1e640 --- /dev/null +++ b/engines/m4/mads_menus.h @@ -0,0 +1,91 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef M4_MADS_MENUS_H +#define M4_MADS_MENUS_H + +#include "m4/viewmgr.h" + +namespace M4 { + +#define MADS_MENU_ANIM_DELAY 70 + +enum MadsGameAction {START_GAME, RESUME_GAME, SHOW_INTRO, CREDITS, QUOTES, EXIT}; + +class RexMainMenuView: public View { +private: + M4Surface *_bgSurface; + RGBList *_bgPalData; + int _menuItemIndex; + int _frameIndex; + bool _skipFlag; + SpriteAsset *_menuItem; + Common::Array<RGBList *> _itemPalData; + uint32 _delayTimeout; + int _highlightedIndex; + + int getHighlightedItem(int x, int y); + void handleAction(MadsGameAction action); +public: + RexMainMenuView(M4Engine *vm); + ~RexMainMenuView(); + + bool onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents); + void updateState(); +}; + +class DragonMainMenuView: public View { +private: + //M4Surface *_bgSurface; + RGBList *_bgPalData; + int _menuItemIndex; + int _frameIndex; + bool _skipFlag; + SpriteAsset *_menuItem; + Common::Array<RGBList *> _itemPalData; + uint32 _delayTimeout; + int _highlightedIndex; + + int getHighlightedItem(int x, int y); + void handleAction(MadsGameAction action); +public: + DragonMainMenuView(M4Engine *vm); + ~DragonMainMenuView(); + + bool onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents); + void updateState(); +}; + +class MadsMainMenuView: public View { +public: + MadsMainMenuView(M4Engine *vm); + + bool onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents); + void updateState(); +}; + +} + +#endif diff --git a/engines/m4/midi.cpp b/engines/m4/midi.cpp new file mode 100644 index 0000000000..51dd8654ae --- /dev/null +++ b/engines/m4/midi.cpp @@ -0,0 +1,359 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +// FIXME: This is cribbed together from the SAGA music player. It needs cleanup +// and testing. + +#include "m4/m4.h" +#include "m4/midi.h" +#include "common/stream.h" + +namespace M4 { + +MidiPlayer::MidiPlayer(M4Engine *vm, MidiDriver *driver) : _vm(vm), _midiData(NULL), _driver(driver), _isPlaying(false), _passThrough(false), _isGM(false) { + memset(_channel, 0, sizeof(_channel)); + _masterVolume = 0; + _parser = MidiParser::createParser_SMF(); + _parser->setMidiDriver(this); + _parser->setTimerRate(getBaseTempo()); + open(); +} + +MidiPlayer::~MidiPlayer() { + _driver->setTimerCallback(NULL, NULL); + _parser->setMidiDriver(NULL); + stopMusic(); + close(); + delete _parser; + delete _midiData; +} + +void MidiPlayer::setVolume(int volume) { + Common::StackLock lock(_mutex); + + if (volume < 0) + volume = 0; + else if (volume > 255) + volume = 255; + + if (_masterVolume == volume) + return; + + _masterVolume = volume; + + for (int i = 0; i < 16; ++i) { + if (_channel[i]) { + _channel[i]->volume(_channelVolume[i] * _masterVolume / 255); + } + } +} + +int MidiPlayer::open() { + // Don't ever call open without first setting the output driver! + if (!_driver) + return 255; + + int ret = _driver->open(); + if (ret) + return ret; + + _driver->setTimerCallback(this, &onTimer); + return 0; +} + +void MidiPlayer::close() { + stopMusic(); + if (_driver) + _driver->close(); + _driver = 0; +} + +void MidiPlayer::send(uint32 b) { + if (_passThrough) { + _driver->send(b); + return; + } + + byte channel = (byte)(b & 0x0F); + if ((b & 0xFFF0) == 0x07B0) { + // Adjust volume changes by master volume + byte volume = (byte)((b >> 16) & 0x7F); + _channelVolume[channel] = volume; + volume = volume * _masterVolume / 255; + b = (b & 0xFF00FFFF) | (volume << 16); + } else if ((b & 0xF0) == 0xC0 && !_isGM && !_nativeMT32) { + b = (b & 0xFFFF00FF) | MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8; + } + else if ((b & 0xFFF0) == 0x007BB0) { + //Only respond to All Notes Off if this channel + //has currently been allocated + if (_channel[b & 0x0F]) + return; + } + + if (!_channel[channel]) + _channel[channel] = (channel == 9) ? _driver->getPercussionChannel() : _driver->allocateChannel(); + + if (_channel[channel]) + _channel[channel]->send(b); +} + +void MidiPlayer::metaEvent(byte type, byte *data, uint16 length) { + switch (type) { + case 0x2F: + // End of track. (Not called when auto-looping.) + stopMusic(); + break; + case 0x51: + // Set tempo. Handled by the standard MIDI parser already. + break; + default: + warning("Unhandled meta event: %02x", type); + break; + } +} + +void MidiPlayer::onTimer(void *refCon) { + MidiPlayer *midi = (MidiPlayer *)refCon; + Common::StackLock lock(midi->_mutex); + + if (midi->_isPlaying) + midi->_parser->onTimer(); +} + +void MidiPlayer::playMusic(const char *name, int32 vol, bool loop, int32 trigger, int32 scene) { + stopMusic(); + + char fullname[144]; + _vm->res()->changeExtension(fullname, name, "HMP"); + + Common::SeekableReadStream *midiFile = _vm->res()->get(fullname); + byte *hmpData = new byte[midiFile->size()]; + uint32 smfSize; + + midiFile->read(hmpData, midiFile->size()); + _midiData = convertHMPtoSMF(hmpData, midiFile->size(), smfSize); + delete[] hmpData; + _vm->res()->toss(fullname); + _vm->res()->purge(); + + if (_midiData) { + /* + FILE *out = fopen("music.mid", "wb"); + fwrite(_midiData, smfSize, 1, out); + fclose(out); + */ + _parser->loadMusic(_midiData, smfSize); + _parser->property(MidiParser::mpAutoLoop, loop); + } + + setVolume(255); + + _isPlaying = true; +} + +void MidiPlayer::stopMusic() { + Common::StackLock lock(_mutex); + + _isPlaying = false; + if (_parser) { + _parser->unloadMusic(); + } + delete[] _midiData; + _midiData = NULL; +} + +// This function will convert HMP music into type 1 SMF, which our SMF parser +// will be able to handle. It is based on Hans de Goede's HMP 2 MIDI file +// converter, which in turn is "based on the conversion algorithms found in +// d1x, d2x-xl and jjffe". Hans's original code is licensed under the LGPL. +// +// TODO: It would probably be nicer to write a MIDI parser class to deal with +// HMP data directly. Though the multi-track nature of HMP makes that tricky. + +byte *MidiPlayer::convertHMPtoSMF(byte *data, uint32 inSize, uint32 &outSize) { + Common::MemoryReadStream readS(data, inSize); + Common::MemoryWriteStreamDynamic writeS; + + byte buf[8]; + + readS.read(buf, sizeof(buf)); + if (memcmp(buf, "HMIMIDIP", 8) != 0) { + warning("convertHMPtoSMF: Invalid HMP header"); + return NULL; + } + + // Read the number of tracks. Note that all the tracks are still part + // of the same song, just like in type 1 SMF files. + + readS.seek(0x30); + + uint32 numTracks = readS.readUint32LE(); + + // The first track starts on offset 0x300. It's currently unknown what + // the skipped data is for. + + readS.seek(0x300); + + // For some reason, we skip the first track entirely. + + byte a = readS.readByte(); + byte b = readS.readByte(); + byte c = readS.readByte(); + + while (a != 0xFF || b != 0x2F || c != 0x00) { + a = b; + b = c; + c = readS.readByte(); + } + + // The beginning of the MIDI header + static const byte midiHeader1[] = { 'M', 'T', 'h', 'd', 0, 0, 0, 6, 0, 1 }; + // The last 2 bytes of the midi header and track 0 + static const byte midiHeader2[] = { 0, 0xC0, 'M', 'T', 'r', 'k', 0, 0, 0, 0x0B, 0, 0xFF, 0x51, 0x03, 0x18, 0x80, 0, 0, 0xFF, 0x2F, 0 }; + + + // Write the MIDI header + writeS.write(midiHeader1, sizeof(midiHeader1)); + + // Write the number of tracks + writeS.writeUint16BE(numTracks); + + // Write the rest of the MIDI header and track 0. + writeS.write(midiHeader2, sizeof(midiHeader2)); + + // Read and convert all the tracks + for (uint i = 1; i < numTracks; i++) { + if (readS.readUint32LE() != i) { + warning("convertHMPtoSMF: Invalid HMP track number"); + delete[] writeS.getData(); + return NULL; + } + + uint32 trackLength = readS.readUint32LE() - 12; + readS.readUint32LE(); // Unused? + + // Write the track header + writeS.write("MTrk", 4); + + // This is where we will write the length of the track. + uint32 trackLengthPos = writeS.pos(); + writeS.writeUint32LE(0); + + // In the original, this is cleared once at the beginning of + // the function, but surely the last command does not carry + // over to the next track? + + byte lastCmd = 0; + + // Now we can finally convert the track + uint32 endPos = readS.pos() + trackLength; + while (readS.pos() < endPos) { + // Convert the VLQ + byte vlq[4]; + int j = -1; + + do { + j++; + vlq[j] = readS.readByte(); + } while (!(vlq[j] & 0x80)); + + for (int k = 0; k <= j; k++) { + a = vlq[j - k] & 0x7F; + if (k != j) + a |= 0x80; + writeS.writeByte(a); + } + + a = readS.readByte(); + + if (a == 0xFF) { + // META event + b = readS.readByte(); + c = readS.readByte(); + + writeS.writeByte(a); + writeS.writeByte(b); + writeS.writeByte(c); + + if (c > 0) { + byte *metaBuf = new byte[c]; + readS.read(metaBuf, c); + writeS.write(metaBuf, c); + delete[] metaBuf; + } + + if (b == 0x2F) { + if (c != 0x00) { + warning("convertHMPtoSMF: End of track with non-zero size"); + delete[] writeS.getData(); + return NULL; + } + break; + } + } else { + if (a != lastCmd) + writeS.writeByte(a); + + switch (a & 0xF0) { + case 0x80: + case 0x90: + case 0xA0: + case 0xB0: + case 0xE0: + b = readS.readByte(); + c = readS.readByte(); + writeS.writeByte(b); + writeS.writeByte(c); + break; + case 0xC0: + case 0xD0: + b = readS.readByte(); + writeS.writeByte(b); + break; + default: + warning("convertHMPtoSMF: Invalid HMP command %02X", a); + delete[] writeS.getData(); + return NULL; + } + + lastCmd = a; + } + } + + if (readS.pos() != endPos) { + warning("convertHMPtoSMF: Invalid track length"); + delete[] writeS.getData(); + return NULL; + } + + WRITE_BE_UINT32(writeS.getData() + trackLengthPos, writeS.pos() - trackLengthPos - 4); + } + + outSize = writeS.size(); + return writeS.getData(); +} + +} // End of namespace M4 diff --git a/engines/m4/midi.h b/engines/m4/midi.h new file mode 100644 index 0000000000..78dacdf77e --- /dev/null +++ b/engines/m4/midi.h @@ -0,0 +1,99 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +// Music class + +#ifndef M4_MIDI_H +#define M4_MIDI_H + +#include "sound/mididrv.h" +#include "sound/midiparser.h" +#include "common/mutex.h" + +namespace M4 { + +class MidiPlayer : public MidiDriver { +public: + MidiPlayer(M4Engine *vm, MidiDriver *driver); + ~MidiPlayer(); + + bool isPlaying() { return _isPlaying; } + + void setVolume(int volume); + int getVolume() { return _masterVolume; } + + void setNativeMT32(bool b) { _nativeMT32 = b; } + bool hasNativeMT32() { return _nativeMT32; } + void playMusic(const char *name, int32 vol, bool loop, int32 trigger, int32 scene); + void stopMusic(); + void setPassThrough(bool b) { _passThrough = b; } + + void setGM(bool isGM) { _isGM = isGM; } + + //MidiDriver interface implementation + int open(); + void close(); + void send(uint32 b); + + void metaEvent(byte type, byte *data, uint16 length); + + void setTimerCallback(void *timerParam, void (*timerProc)(void *)) { } + uint32 getBaseTempo(void) { return _driver ? _driver->getBaseTempo() : 0; } + + //Channel allocation functions + MidiChannel *allocateChannel() { return 0; } + MidiChannel *getPercussionChannel() { return 0; } + +protected: + static void onTimer(void *data); + + M4Engine *_vm; + byte *_midiData; + + MidiChannel *_channel[16]; + MidiDriver *_driver; + MidiParser *_parser; + byte _channelVolume[16]; + bool _nativeMT32; + bool _isGM; + bool _passThrough; + + bool _isPlaying; + bool _randomLoop; + byte _masterVolume; + + byte *_musicData; + uint16 *_buf; + size_t _musicDataSize; + + Common::Mutex _mutex; + + byte *convertHMPtoSMF(byte *data, uint32 inSize, uint32 &outSize); +}; + +} // End of namespace M4 + +#endif + diff --git a/engines/m4/module.mk b/engines/m4/module.mk new file mode 100644 index 0000000000..9791f66a58 --- /dev/null +++ b/engines/m4/module.mk @@ -0,0 +1,42 @@ +MODULE := engines/m4 + +MODULE_OBJS = \ + actor.o \ + animation.o \ + assets.o \ + compression.o \ + console.o \ + converse.o \ + detection.o \ + events.o \ + font.o \ + globals.o \ + graphics.o \ + gui.o \ + hotspot.o \ + m4.o \ + m4_menus.o \ + m4_views.o \ + mads_anim.o \ + mads_menus.o \ + midi.o \ + rails.o \ + resource.o \ + saveload.o \ + scene.o \ + script.o \ + sound.o \ + sprite.o \ + viewmgr.o \ + woodscript.o \ + ws_machine.o \ + ws_sequence.o + + +# This module can be built as a plugin +ifdef BUILD_PLUGINS +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/m4/rails.cpp b/engines/m4/rails.cpp new file mode 100644 index 0000000000..d5139cc3c4 --- /dev/null +++ b/engines/m4/rails.cpp @@ -0,0 +1,358 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +/* + TODO: + - rewrite functions (GetShortestPath etc.) +*/ + +#include "graphics/primitives.h" +#include "common/list.h" +#include "common/rect.h" +#include "common/util.h" + +#include "m4/rails.h" +#include "m4/m4.h" + +namespace M4 { + +#define TOP_EDGE 1 << 0 +#define LEFT_EDGE 1 << 1 +#define BOTTOM_EDGE 1 << 2 +#define RIGHT_EDGE 1 << 3 + + +Rails::Rails() { +} + + +Rails::~Rails() { + clearRails(); +} + + +void Rails::clearRails() { + uint32 i; + Common::List<NoWalkRect *>::iterator j; + RailNode *tempNode; + + for (i = 0; i < _nodes.size(); i++) { + tempNode = _nodes[i]; + _nodes.remove_at(i); + delete tempNode; + } + + for (i = 0; i < _edges.size(); i++) { + _edges.remove_at(i); + } + + for (j = _noWalkRects.begin(); j != _noWalkRects.end(); ++j) + delete (*j); + _noWalkRects.clear(); +} + +static void checkPoint(int x, int y, int color, void *data) { + IsWalkableData *isWalkableData = (IsWalkableData*)data; + if (!isWalkableData->result) + return; + else { + M4Surface *codes = isWalkableData->codes; + if (x >= 0 && x < codes->w && y >= 0 && y < codes->h) { + isWalkableData->result = !((*((uint8*)codes->getBasePtr(x, y))) & 0x10); + } else { + isWalkableData->result = false; + } + } +} + +bool Rails::isLineWalkable(int x0, int y0, int x1, int y1) { + IsWalkableData isWalkableData; + isWalkableData.codes = _walkCodes; + isWalkableData.result = true; + Graphics::drawLine(x0, y0, x1, y1, 0, &checkPoint, &isWalkableData); + return isWalkableData.result; +} + +// helper function +uint8 getEndCode(int32 x, int32 y, Common::Rect rect) { + uint8 endCode = 0; + endCode = (x < rect.left) ? LEFT_EDGE : endCode; + endCode = (x > rect.right) ? RIGHT_EDGE : endCode; + endCode = (y < rect.top) ? endCode | TOP_EDGE : endCode; + endCode = (y > rect.bottom) ? endCode | BOTTOM_EDGE : endCode; + return endCode; +} + +bool Rails::lineCrossesRect(int32 x1, int32 y1, int32 x2, int32 y2, Common::Rect rect) { + int32 mX, mY; + int32 pX1 = x1, pX2 = x2, pY1 = y1, pY2 = y2; + uint8 endCode1, endCode2, midCode; + + if (rect.left > rect.right || rect.top > rect.bottom) + return false; + + // Cohen-Sutherland line clipping algorithm + + endCode1 = getEndCode(pX1, pY1, rect); + endCode2 = getEndCode(pX2, pY2, rect); + + if (!endCode1 || !endCode2) // if both endcodes are zero + return true; // point is inside the rectangle, therefore the line intersects + + while (true) { + if (endCode1 & endCode2) // if both endcodes have a common bitset + return false; // line is completely off one edge + + // calculate midpoint + mX = (pX1 + pX2)>>1; + mY = (pY1 + pY2)>>1; + + // avoid round-off error: make sure that the midpoint isn't the same as one of the + // two endpoints + if (((mX == pX1) && (mY == pY1)) || ((mX == pX2) && (mY == pY2))) + return false; + + midCode = getEndCode(mX, mY, rect); + + if (!midCode) { + return true; + } else if (midCode & endCode1) { + // the midCode and an end point form a line segment completely off one edge, so + // remove that half of the line segment + pX1 = mX; + pY1 = mY; + endCode1 = midCode; + } else { + pX2 = mX; + pY2 = mY; + endCode2 = midCode; + } + } +} + + +bool Rails::linePassesThroughRect(int32 x1, int32 y1, int32 x2, int32 y2) { + if (_noWalkRects.empty()) + return false; + + bool intersected = false; + Common::List<NoWalkRect *>::iterator i; + + for (i = _noWalkRects.begin(); i != _noWalkRects.end(); ++i) { + intersected = lineCrossesRect(x1, y1, x2, y2, Common::Rect((*i)->x1, (*i)->y1, (*i)->x2, (*i)->y2)); + if (intersected) + break; + } + + return intersected; +} + +long SqrtF16(long n) { + uint32 r = 0, s; + uint32 v = (uint32)n; + + for (int i = 15; i <= 0; i--) { + s = r + (1L << i * 2); + r >>= 1; + if (s <= v) { + v -= s; + r |= (1L << i * 2); + } + } + + return (long)r; +} + +void Rails::createEdge(int32 node1, int32 node2) { + uint32 index; + int32 x1, y1, x2, y2; + bool valid, finished; + long deltaX, deltaY, distance; + uint8 *walkCodePtr; + + if ((node1 < 0) || (node1 >= MAXRAILNODES) || (node2 < 0) || (node2 >= MAXRAILNODES)) + return; + + if (node1 == node2) + return; + + if (node2 < node1) + SWAP(node1, node2); // ensure node1 < node2 + + // Find the table entry i.e. tableWidth * node1 + node2 and then subtract + // n(n+1)/2, since only the upper triangle of the table is stored + index = (MAXRAILNODES-1) * node1 + node2 - 1 - (node1*(node1+1)>>1); + if (index > _edges.size() - 1) + _edges.resize(index + 1); + _edges.insert_at(index, 0); + valid = true; + walkCodePtr = NULL; + finished = false; + + if (_nodes.size() <= (uint32)node1 || _nodes.size() <= (uint32)node2) + return; + + x1 = _nodes[node1]->x; + y1 = _nodes[node1]->y; + x2 = _nodes[node2]->x; + y2 = _nodes[node2]->y; + + // Make sure that the algorithm is symmetric + if (x2 < x1) { + SWAP(x1, x2); + SWAP(y1, y2); + } + + valid = isLineWalkable(_nodes[node1]->x, _nodes[node1]->y, + _nodes[node2]->x, _nodes[node2]->y); + printf("test code says: %d\n", valid); + + // Check if the line passes through a forbidden rectangle + if (valid) { + if (linePassesThroughRect(x1, y1, x2, y2)) { + valid = false; + } + } + + if (valid) { + deltaX = ABS(((long)(x2 - x1)) << 16); + deltaY = ABS(((long)(y2 - y1)) << 16); + if ((deltaX >= 0x800000) || (deltaY >= 0x800000)) { + deltaX >>= 16; + deltaY >>= 16; + distance = (long)(SqrtF16(deltaX * deltaX + deltaY * deltaY) << 16); + } else { + distance = SqrtF16(FixedMul(deltaX, deltaX) + FixedMul(deltaY, deltaY)) << 8; + } + _edges.insert_at(index, (int16*)(distance >> 16)); + } + + printf("node1 = %d, node2 = %d, valid = %d\n", node1, node2, valid); + +} + + +void Rails::restoreNodeEdges(int32 nodeID) { + for (int32 i = 0; i < MAXRAILNODES; i++) { + createEdge(i, nodeID); + } +} + +void Rails::restoreEdgeList() { + int32 j; + for (int32 i = 0; i < MAXRAILNODES; i++) { + for (j = i + 1; j < MAXRAILNODES; j++) { + createEdge(i, j); + } + } +} + +int32 Rails::addRailNode(int32 x, int32 y, bool restoreEdges) { + uint32 i = _nodes.size(); + if (i >= MAXRAILNODES) + return -1; + + RailNode *newNode = new RailNode(); + newNode->nodeID = i; + newNode->x = x; + newNode->y = y; + _nodes.insert_at(i, newNode); + if (restoreEdges) { + for (uint32 j=0; j<_nodes.size(); j++) + createEdge(i, j); + } + return i; +} + +bool Rails::removeRailNode(int32 nodeID, bool restoreEdges) { + if (nodeID < 0 || nodeID >= MAXRAILNODES) + return false; + + if (_nodes.empty() || _edges.empty()) + return false; + + RailNode *tempNode = _nodes[nodeID]; + _nodes.remove_at(nodeID); + delete tempNode; + + if (restoreEdges) { + restoreNodeEdges(nodeID); + } + return true; +} + +int16 Rails::getEdgeLength(int32 node1, int32 node2) { + int32 index; + if (_edges.empty() || node1 == node2) + return 0; + if (node2 < node1) + SWAP(node1, node2); + // Find the table entry i.e. tableWidth * node1 + node2 and then subtract + // n(n+1)/2, since only the upper triangle of the table is stored + index = (MAXRAILNODES-1)*node1 + node2 - 1 - (node1*(node1+1)>>1); + return *_edges[index]; +} + +void Rails::disposePath(RailNode *pathStart) { + RailNode *tempNode = pathStart; + while (tempNode) { + pathStart = pathStart->shortPath; + delete tempNode; + tempNode = pathStart; + } +} + +static RailNode* duplicatePath(RailNode *pathStart) { + RailNode *newNode = NULL; + RailNode *firstNode = NULL; + RailNode *prevNode = NULL; + // A valid path is assumed + RailNode *pathNode = pathStart; + + while (pathNode) { + newNode = new RailNode(); + newNode->x = pathNode->x; + newNode->y = pathNode->y; + newNode->shortPath = NULL; + + if (!firstNode) + firstNode = newNode; + else + prevNode->shortPath = newNode; + + prevNode = newNode; + // Get the next node + pathNode = pathNode->shortPath; + } + + return firstNode; +} + +bool Rails::getShortestPath(int32 origID, int32 destID, RailNode **shortPath) { + // TODO + return true; +} + +} // End of namespace M4 diff --git a/engines/m4/rails.h b/engines/m4/rails.h new file mode 100644 index 0000000000..d7320eef4d --- /dev/null +++ b/engines/m4/rails.h @@ -0,0 +1,97 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef M4_RAILS_H +#define M4_RAILS_H + +#include "m4/graphics.h" +#include "common/list.h" +#include "common/array.h" +#include "common/rect.h" + +// TODO: This needs cleaning up + +namespace M4 { + +#define MAXRAILNODES 32 +#define PATH_END 0xffff + +struct RailNode { + uint8 nodeID; + int32 x, y; + RailNode *shortPath; + int32 pathWeight; +}; + +struct NoWalkRect { + int32 x1, y1, x2, y2; + int32 alternateWalkToNode; + int32 walkAroundNode1; + int32 walkAroundNode2; + int32 walkAroundNode3; + int32 walkAroundNode4; +}; + +struct PathNode { + PathNode *next; + int8 nodeID; +}; + +struct IsWalkableData { + M4Surface *codes; + bool result; +}; + +class Rails { +public: + Rails(); + ~Rails(); + + void setCodeSurface(M4Surface *surface) { _walkCodes = surface; } + void clearRails(); + int32 addRailNode(int32 x, int32 y, bool restoreEdges); + +private: + Common::Array<RailNode *> _nodes; + Common::Array<int16 *> _edges; + Common::List<NoWalkRect *> _noWalkRects; + M4Surface *_walkCodes; + + bool lineCrossesRect(int32 x1, int32 y1, int32 x2, int32 y2, Common::Rect rect); + bool linePassesThroughRect(int32 x1, int32 y1, int32 x2, int32 y2); + void createEdge(int32 node1, int32 node2); + bool removeRailNode(int32 nodeID, bool restoreEdges); + int16 getEdgeLength(int32 node1, int32 node2); + + void restoreNodeEdges(int32 nodeID); + void restoreEdgeList(); + void disposePath(RailNode *pathStart); + bool getShortestPath(int32 origID, int32 destID, RailNode **shortPath); + bool isLineWalkable(int x0, int y0, int x1, int y1); +}; + +} // End of namespace M4 + +#endif diff --git a/engines/m4/resource.cpp b/engines/m4/resource.cpp new file mode 100644 index 0000000000..57816b6600 --- /dev/null +++ b/engines/m4/resource.cpp @@ -0,0 +1,436 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "m4/m4.h" +#include "m4/resource.h" +#include "m4/events.h" + +namespace M4 { + +FileSystem::FileSystem(const char *hashFilename) { + + for (int i = 0; i < 10; i++) { + _hagEntries[i].filename[0] = '\0'; + _hagEntries[i].fileIndex = 0; // Was -1 + _hagEntries[i].hagFile = 0; + } + + Common::File hashFile; + uint32 hashSize; + + hashFile.open(hashFilename); + + if (!hashFile.isOpen()) { + printf("FileSystem::FileSystem: error opening hash %s\n", hashFilename); + } + + hashSize = hashFile.readUint32LE(); + + //printf("FileSystem::FileSystem: hashSize = %d\n", hashSize); + + /* load file records and add them to the hash list */ + for (uint i = 0; i < hashSize; i++) { + HashFileEntry entry; + hashFile.read(entry.filename, kM4MaxFilenameSize); + str_lower(entry.filename); + entry.hagfile = hashFile.readByte(); + hashFile.readByte(); + entry.offset = hashFile.readUint32LE(); + entry.size = hashFile.readUint32LE(); + hashFile.readUint32LE(); + + if (entry.filename[0]) { + /* + printf(" filename: %s\n", entry.filename); + printf(" hagfile: %d\n", entry.hagfile); + printf(" disks: %d\n", entry.disks); + printf(" offset: %08X\n", entry.offset); + printf(" size: %d\n", entry.size); + printf(" next: %08X\n", entry.next); + */ + _fileEntries[entry.filename] = entry; + } + + } + + /* load hagfile records and update the list */ + while (!hashFile.eof()) { + HashHagEntry entry; + hashFile.read(entry.filename, kM4MaxFilenameSize); + entry.fileIndex = hashFile.readByte(); + if (hashFile.eof()) + break; + + changeExtension(_hagEntries[entry.fileIndex].filename, entry.filename, "HAG"); + _hagEntries[entry.fileIndex].fileIndex = entry.fileIndex; + + _hagEntries[entry.fileIndex].hagFile = new Common::File(); + _hagEntries[entry.fileIndex].hagFile->open(_hagEntries[entry.fileIndex].filename); + + if (!_hagEntries[entry.fileIndex].hagFile->isOpen()) { + printf("FileSystem::FileSystem: error opening hag %s\n", _hagEntries[entry.fileIndex].filename); + } + + } + + hashFile.close(); + +} + +FileSystem::~FileSystem() { + + for (int i = 0; i < 10; i++) { + if (_hagEntries[i].hagFile) + delete _hagEntries[i].hagFile; + } + +} + +Common::SeekableReadStream *FileSystem::loadFile(const char *resourceName, bool preloadFlag) { + const HashFileEntry *hfe = getHashFileEntry(resourceName); + Common::SeekableReadStream *result = NULL; + + if (hfe) { + //printf("FileSystem::loadFile() success opening %s\n", filename); + HashHagEntry *hagEntry = &_hagEntries[hfe->hagfile]; + + if (preloadFlag) { + // Creates a MemoryReadStream object that contains all of the resource in memory + hagEntry->hagFile->seek(hfe->offset); + result = _hagEntries[hfe->hagfile].hagFile->readStream(hfe->size); + } + else + // Creates a SeekableSubReadStream, which will read the data in from disk as the + // caller reads in data + result = new Common::SeekableSubReadStream(hagEntry->hagFile, hfe->offset, + hfe->offset + hfe->size); + + } else { + printf("FileSystem::loadFile() error opening %s\n", resourceName); + } + + return result; +} + +const FileSystem::HashFileEntry *FileSystem::getHashFileEntry(const char *filename) { + char resourceName[20]; + strcpy(resourceName, filename); + str_lower(resourceName); + + FileHashMap::const_iterator entry = _fileEntries.find(filename); + if (entry != _fileEntries.end()) + return &(entry->_value); + else + return NULL; +} + +void FileSystem::changeExtension(char *destName, const char *sourceName, const char *extension) { + if (sourceName != destName) + strcpy(destName, sourceName); + char *dot = strrchr(destName, '.'); + if (dot != NULL) + *dot = 0; + sprintf(destName, "%s.%s", destName, extension); + + str_upper(destName); +} + +//-------------------------------------------------------------------------- + +ResourceManager::~ResourceManager() { + ResourceIterator i; + for (i = _resources.begin(); i != _resources.end(); ++i) { + Resource *r = (*i).get(); + delete r->stream; + } +} + +Common::SeekableReadStream *ResourceManager::get(const char *resourceName, bool preloadFlag) { + char lowerName[kM4MaxFilenameSize]; + + strcpy(lowerName, resourceName); + str_lower(lowerName); + + // Check whether the resource is already loaded + ResourceIterator i; + for (i = _resources.begin(); i != _resources.end(); ++i) { + Resource *r = (*i).get(); + if (!strcmp(r->name, resourceName)) { + // Just in case resource was marked to be purged, reactive it again + r->flags &= ~kResourcePurge; + + // Return the existing copy of the resource + r->stream->seek(0, SEEK_SET); + return r->stream; + } + } + + // the resource wasn't found in the list, load it from disk + Resource *newRes = new Resource(); + strncpy(newRes->name, resourceName, 63); + newRes->name[63] = '\0'; + newRes->flags = 0; + newRes->stream = loadResource(resourceName, preloadFlag); + + _resources.push_back(ResourceList::value_type(newRes)); + return newRes->stream; +} + +void ResourceManager::toss(const char *resourceName) { + ResourceIterator i; + for (i = _resources.begin(); i != _resources.end(); ++i) { + Resource *r = (*i).get(); + + if (!strcmp(r->name, resourceName)) { + r->flags |= kResourcePurge; + //printf("M4ResourceManager::toss: mark resource %s to be purged\n", resourceName); + } + } +} + +void ResourceManager::purge() { + ResourceIterator i = _resources.begin(); + while (i != _resources.end()) { + Resource *r = (*i).get(); + + if (r->flags & kResourcePurge) { + delete r->stream; + i = _resources.erase(i); + } else { + ++i; + } + } +} + +void ResourceManager::dump() { + _vm->_events->getConsole()->DebugPrintf("Scene resources:\n"); + + int index = 0; + ResourceIterator i; + for (i = _resources.begin(); i != _resources.end(); ++i) { + Resource *r = (*i).get(); + + if (!(r->flags & kResourcePurge)) { + _vm->_events->getConsole()->DebugPrintf( + "Resource #%i, name: %s, handle pointer: %p, size: %d, flags: %02X\n", + index++, r->name, r->buffer, r->stream->size(), r->flags); + } + } +} + +//-------------------------------------------------------------------------- + +const char *madsConcatString = "MADSCONCAT"; + +ResourceType MADSResourceManager::getResourceType(const char *resourceName) { + if (!strncmp(resourceName, "RM", 2)) { + // Room resource + return RESTYPE_ROOM; + } else if (!strncmp(resourceName, "SC", 2)) { + // SC resource + return RESTYPE_SC; + } else if (strstr(resourceName, ".TXT")) { + // Text resource + return RESTYPE_TEXT; + } else if (strstr(resourceName, ".QUO")) { + // QUO resource + return RESTYPE_QUO; + } else if (*resourceName == 'I') { + // I resource + return RESTYPE_I; + } else if (!strncmp(resourceName, "OB", 2)) { + // OB resource + return RESTYPE_OB; + } else if (!strncmp(resourceName, "FONT", 4)) { + // FONT resource + return RESTYPE_FONT; + } else if (!strncmp(resourceName, "SOUND", 5)) { + // SOUND resource + return RESTYPE_SOUND; + } else if (!strncmp(resourceName, "SPCHC", 5)) { + // SPEECH resource + return RESTYPE_SPEECH; + } + + // Check for a known extension + const char *extPos = strchr(resourceName, '.'); + if (extPos) { + ++extPos; + if (!strcmp(extPos, "FL") || !strcmp(extPos, "LBM") || !strcmp(extPos, "ANM") || + !strcmp(extPos, "AA") || !strcmp(extPos, "SS")) { + return RESTYPE_HAS_EXT; + } + } + + return RESTYPE_NO_EXT; +} + +const char *MADSResourceManager::getResourceFilename(const char *resourceName) { + static char outputFilename[64]; + + ResourceType resType = getResourceType(resourceName); + + strcpy(outputFilename, "GLOBAL.HAG"); + + if ((resType == RESTYPE_ROOM) || (resType == RESTYPE_SC)) { + int value = atoi(resourceName + 2); + int hagFileNum = (resType == RESTYPE_ROOM) ? value / 100 : value; + + if (hagFileNum > 0) + sprintf(outputFilename, "SECTION%d.HAG", hagFileNum); + } + + if (resType == RESTYPE_SPEECH) + strcpy(outputFilename, "SPEECH.HAG"); + + return outputFilename; +} + +Common::SeekableReadStream *MADSResourceManager::loadResource(const char *resourceName, bool loadFlag) { + Common::File hagFile; + uint32 offset, size; + + // If the first character is a '@' then look for an external file + + if (*resourceName == '@') { + ++resourceName; + + hagFile.open(resourceName); + if (loadFlag) + return hagFile.readStream(hagFile.size()); + else + return new Common::SeekableSubReadStream(&hagFile, 0, hagFile.size()); + } + + // If the first character is the wildcard (resource indicator), skip over it + if (*resourceName == '*') + ++resourceName; + + char resName[20]; + strcpy(resName, resourceName); + str_upper(resName); + + hagFile.open(getResourceFilename(resName)); + + // Validate hag file header + char headerBuffer[16]; + if ((hagFile.read(headerBuffer, 16) != 16) || (strncmp(headerBuffer, madsConcatString, 10) != 0)) + error("Invalid HAG file opened"); + + int numEntries = hagFile.readUint16LE(); + + int resIndex = -1; + while (++resIndex < numEntries) { + // Read in the details of the next resource + char resourceBuffer[14]; + offset = hagFile.readUint32LE(); + size = hagFile.readUint32LE(); + hagFile.read(resourceBuffer, 14); + + if (!strcmp(resName, resourceBuffer)) + break; + } + + if (resIndex == numEntries) + error("Invalid resource '%s' specified", resourceName); + + // Get the resource, either loading it in it's entirely or getting a stream reference + + if (loadFlag) { + hagFile.seek(offset); + return hagFile.readStream(size); + } else { + return new Common::SeekableSubReadStream(&hagFile, offset, offset + size); + } +} + +bool MADSResourceManager::resourceExists(const char *resourceName) { + Common::File hagFile; + uint32 offset, size; + + // If the first character is the wildcard (resource indicator), skip over it + if (*resourceName == '*') + ++resourceName; + + char resName[20]; + strcpy(resName, resourceName); + str_upper(resName); + + hagFile.open(getResourceFilename(resName)); + + // Validate hag file header + char headerBuffer[16]; + if ((hagFile.read(headerBuffer, 16) != 16) || (strncmp(headerBuffer, madsConcatString, 10) != 0)) + error("Invalid HAG file opened"); + + int numEntries = hagFile.readUint16LE(); + + int resIndex = -1; + while (++resIndex < numEntries) { + // Read in the details of the next resource + char resourceBuffer[14]; + offset = hagFile.readUint32LE(); + size = hagFile.readUint32LE(); + hagFile.read(resourceBuffer, 14); + + if (!strcmp(resName, resourceBuffer)) + break; + } + + if (resIndex == numEntries) + return false; + else + return true; +} + +//-------------------------------------------------------------------------- + +M4ResourceManager::M4ResourceManager(M4Engine *vm): ResourceManager(vm) { + _hfs = new FileSystem(_vm->getGameFile(kFileTypeHash)); +} + +M4ResourceManager::~M4ResourceManager() { +} + +Common::SeekableReadStream *M4ResourceManager::loadResource(const char *resourceName, bool preloadFlag) { + //printf("M4ResourceManager::loadResource() loading resource %s\n", resourceName); + Common::SeekableReadStream* result = NULL; + if (_hfs) { + // actually load the resource + result = _hfs->loadFile(resourceName, preloadFlag); + if (!result) { + error("M4ResourceManager::loadResource() Resource %s not found", resourceName); + } + } else { + error("M4ResourceManager::loadResource() No FileSystem attached"); + } + return result; +} + +bool M4ResourceManager::resourceExists(const char *resourceName) { + return (_hfs->getHashFileEntry(resourceName) != NULL); +} + +} // End of namespace M4 diff --git a/engines/m4/resource.h b/engines/m4/resource.h new file mode 100644 index 0000000000..dc099f2304 --- /dev/null +++ b/engines/m4/resource.h @@ -0,0 +1,141 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef M4_RESOURCE_H +#define M4_RESOURCE_H + +#include "common/scummsys.h" +#include "common/stream.h" +#include "common/endian.h" +#include "common/file.h" +#include "common/hashmap.h" +#include "common/hash-str.h" +#include "common/list.h" +#include "common/ptr.h" + +/* + TODO: + - change array to HashMap if it turns out to be slow +*/ + +namespace M4 { + +#define MAX_RESOURCES 128 +#define kM4MaxFilenameSize 33 + +enum { + kResourcePurge = 1 << 1 +}; + + +class FileSystem { +public: + struct HashHagEntry { + char filename[kM4MaxFilenameSize]; + byte fileIndex; + Common::File *hagFile; + }; + + struct HashFileEntry { + char filename[kM4MaxFilenameSize]; + byte hagfile; + uint32 offset, size; + }; + + FileSystem(const char *hashFilename); + ~FileSystem(); + + Common::SeekableReadStream *loadFile(const char *resourceName, bool preloadFlag); + static void changeExtension(char *destName, const char *sourceName, const char *extension); + const HashFileEntry *getHashFileEntry(const char *filename); + +private: + typedef Common::HashMap<Common::String,HashFileEntry,Common::IgnoreCase_Hash,Common::IgnoreCase_EqualTo> FileHashMap; + + HashHagEntry _hagEntries[10]; // GLOBAL.HAG and SECTION1.HAG to SECTION9.HAG + FileHashMap _fileEntries; +}; + +struct Resource { + char name[64]; + Common::SeekableReadStream *stream; + uint8 *buffer; + uint8 flags; +}; + +class ResourceManager { +protected: + typedef Common::List<Common::SharedPtr<Resource> > ResourceList; + typedef ResourceList::iterator ResourceIterator; + ResourceList _resources; + M4Engine *_vm; + + virtual Common::SeekableReadStream *loadResource(const char *resourceName, bool loadFlag) = 0; +public: + ResourceManager(M4Engine *vm): _vm(vm) {}; + virtual ~ResourceManager(); + + Common::SeekableReadStream *get(const char *resourceName, bool loadFlag = true); + void toss(const char *resourceName); + void purge(); + void dump(); + virtual bool resourceExists(const char *resourceName) = 0; + + Common::SeekableReadStream *openFile(const char *resourceName) { return get(resourceName, false); } + void changeExtension(char *destName, const char *sourceName, const char *extension) { + FileSystem::changeExtension(destName, sourceName, extension); + } +}; + +enum ResourceType {RESTYPE_ROOM, RESTYPE_SC, RESTYPE_TEXT, RESTYPE_QUO, RESTYPE_I, + RESTYPE_OB, RESTYPE_FONT, RESTYPE_SOUND, RESTYPE_SPEECH, RESTYPE_HAS_EXT, RESTYPE_NO_EXT}; + +class MADSResourceManager: public ResourceManager { +private: + ResourceType getResourceType(const char *resourceName); + const char *getResourceFilename(const char *resourceName); +protected: + Common::SeekableReadStream *loadResource(const char *resourceName, bool loadFlag); +public: + MADSResourceManager(M4Engine *vm): ResourceManager(vm) {}; + bool resourceExists(const char *resourceName); +}; + +class M4ResourceManager: public ResourceManager { +protected: + Common::SeekableReadStream *loadResource(const char *resourceName, bool loadFlag); +public: + M4ResourceManager(M4Engine *vm); + ~M4ResourceManager(); + bool resourceExists(const char *resourceName); + +private: + FileSystem *_hfs; +}; + +} // End of namespace M4 + + +#endif diff --git a/engines/m4/saveload.cpp b/engines/m4/saveload.cpp new file mode 100644 index 0000000000..b46783ef4d --- /dev/null +++ b/engines/m4/saveload.cpp @@ -0,0 +1,168 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "common/file.h" +#include "common/savefile.h" + +#include "m4/m4.h" +#include "m4/saveload.h" +#include "m4/sprite.h" + +namespace M4 { + +const char *orionSavesList = "saves.dir"; + +SaveLoad::SaveLoad(M4Engine *vm) : _vm(vm) { + // For Orion Burger, check the existance of a 'saves.dir' file to determine whether to + // act exactly like the original. Otherwise, we'll use the ScummVM standard, where we'll + // keep all the data for a savegame in a single file + + Common::File file; + _emulateOriginal = file.exists(orionSavesList); +} + +const char *SaveLoad::generateSaveName(int slotNumber) { + static char buffer[15]; + + sprintf(buffer, _emulateOriginal ? "burg%.3d.sav" : "burger.%.3d", slotNumber); + return buffer; +} + +bool SaveLoad::hasSaves() { + // Return true if a savegame file exists for the first slot + + if (_emulateOriginal) { + Common::File f; + return f.exists(generateSaveName(1)); + + } else { + Common::ReadStream *f = _vm->saveManager()->openForLoading(generateSaveName(1)); + if (f == NULL) + return false; + + delete f; + return true; + } +} + +SaveGameList *SaveLoad::getSaves() { + SaveGameList *result = new SaveGameList(); + char saveName[MAX_SAVEGAME_NAME]; + Common::ReadStream *f = NULL; + + if (_emulateOriginal) { + Common::File *saveFile = new Common::File(); + saveFile->open(orionSavesList); + f = saveFile; + } + + for (int slotNumber = 1; slotNumber <= 99; ++slotNumber) { + if (_emulateOriginal) { + // Read in savegame name from save directory + bool isPresent = (f->readByte() != 0); + f->read(&saveName[0], MAX_SAVEGAME_NAME); + + if (isPresent) + result->push_back(Common::String(saveName)); + else { + result->push_back(Common::String()); + } + + } else { + // Read in savegame name from savegame files directly + Common::ReadStream *saveFile = _vm->saveManager()->openForLoading( + generateSaveName(slotNumber)); + if (!saveFile) { + // No savegame prsent at that slot + result->push_back(Common::String()); + } else { + // Skip over byte offset + assert(saveFile->readUint32LE() < 0x100); + + // Read in savegame name + saveFile->read(&saveName[0], MAX_SAVEGAME_NAME); + result->push_back(Common::String(saveName)); + + delete saveFile; + } + } + } + + if (_emulateOriginal) + delete f; + + return result; +} + +M4Surface *SaveLoad::getThumbnail(int slotNumber) { + Common::SeekableReadStream *saveFile; + uint32 dataOffset; + + if (_emulateOriginal) { + // Get savegame file from original game folder + Common::File *f = new Common::File(); + if (!f->open(generateSaveName(slotNumber))) { + delete f; + return NULL; + } + + saveFile = f; + } else { + // Open up savegame for access via savefile manager + saveFile = _vm->saveManager()->openForLoading(generateSaveName(slotNumber)); + } + if (!saveFile) + return NULL; + + dataOffset = saveFile->readUint32LE(); + assert(dataOffset < 0x100); + saveFile->seek(dataOffset, SEEK_CUR); + + // Read in the sprite data + + saveFile->seek(16, SEEK_CUR); + int width = saveFile->readUint32LE(); + int height = saveFile->readUint32LE(); + saveFile->seek(21, SEEK_CUR); + saveFile->readUint32LE(); // sprite data size + + M4Sprite *result = new M4Sprite(saveFile, 0, 0, width, height); + delete saveFile; + + return result; +} + +bool SaveLoad::load(int slotNumber) { + // TODO: Currently it's hardcoded to return a failure + return false; +} + +bool SaveLoad::save(int slotNumber, Common::String saveName) { + // TODO: Currently it's hardcoded to return a failure + return false; +} + + +} // End of namespace M4 diff --git a/engines/m4/saveload.h b/engines/m4/saveload.h new file mode 100644 index 0000000000..5218f7db84 --- /dev/null +++ b/engines/m4/saveload.h @@ -0,0 +1,57 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef M4_SAVELOAD_H +#define M4_SAVELOAD_H + +#include "m4/graphics.h" +#include "common/ptr.h" + +#define MAX_SAVEGAME_NAME 80 + +namespace M4 { + +typedef Common::List<Common::String> SaveGameList; +typedef SaveGameList::iterator SaveGameIterator; + +class SaveLoad { +private: + M4Engine *_vm; + bool _emulateOriginal; + + const char *generateSaveName(int slotNumber); +public: + SaveLoad(M4Engine *vm); + + bool hasSaves(); + SaveGameList *getSaves(); + M4Surface *getThumbnail(int slotNumber); + bool load(int slotNumber); + bool save(int slotNumber, Common::String saveName); +}; + +} + +#endif diff --git a/engines/m4/scene.cpp b/engines/m4/scene.cpp new file mode 100644 index 0000000000..787b8dd8eb --- /dev/null +++ b/engines/m4/scene.cpp @@ -0,0 +1,669 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "common/system.h" + +#include "m4/globals.h" +#include "m4/scene.h" +#include "m4/events.h" +#include "m4/graphics.h" +#include "m4/rails.h" +#include "m4/font.h" +#include "m4/m4_views.h" +#include "m4/compression.h" + +namespace M4 { + +Scene::Scene(M4Engine *vm): View(vm, Common::Rect(0, 0, vm->_screen->width(), vm->_screen->height())) { + _screenType = VIEWID_SCENE; + + _sceneResources.hotspots = new HotSpotList(); + _sceneResources.parallax = new HotSpotList(); + _sceneResources.props = new HotSpotList(); + _backgroundSurface = new M4Surface(); + _codeSurface = new M4Surface(); + _madsInterfaceSurface = new M4Surface(); + _sceneSprites = NULL; + _palData = NULL; + _interfacePal = NULL; + _inverseColorTable = NULL; + strcpy(_statusText, ""); + _vm->_rails->setCodeSurface(_codeSurface); +} + +Scene::~Scene() { + _sceneResources.hotspots->clear(); + _sceneResources.parallax->clear(); + _sceneResources.props->clear(); + + delete _sceneResources.hotspots; + delete _sceneResources.parallax; + delete _sceneResources.props; + + delete _backgroundSurface; + delete _codeSurface; + delete _madsInterfaceSurface; + + if (_sceneSprites) + delete _sceneSprites; + + _vm->_palette->deleteAllRanges(); + + if (_palData) + delete _palData; + + if (_interfacePal) + delete _interfacePal; + + if (_inverseColorTable) + delete[] _inverseColorTable; + +} + +void Scene::loadScene(int sceneNumber) { + _currentScene = sceneNumber; + + // Close the menu if it's active + if (!_vm->isM4()) { + View *mainMenu = _vm->_viewManager->getView(VIEWID_MAINMENU); + if (mainMenu != NULL) { + _vm->_viewManager->deleteView(mainMenu); + } + } + + // TODO: Check if we were loading a game + + + // Load scene background and set palette + if (_palData) { + _vm->_palette->deleteRange(_palData); + delete _palData; + } + + if (_interfacePal) { + _vm->_palette->deleteRange(_interfacePal); + delete _interfacePal; + } + + if (_vm->isM4()) { + _backgroundSurface->loadBackground(sceneNumber); + _palData = NULL; + } else { + _backgroundSurface->loadBackground(sceneNumber, &_palData); + _vm->_palette->addRange(_palData); + _backgroundSurface->translate(_palData); + + if (sceneNumber < 900) { + /*_backgroundSurface->fillRect(Common::Rect(0, MADS_SURFACE_HEIGHT, + _backgroundSurface->width(), _backgroundSurface->height()), + _vm->_palette->BLACK);*/ + // TODO: interface palette + _madsInterfaceSurface->madsloadInterface(0, &_interfacePal); + _vm->_palette->addRange(_interfacePal); + _madsInterfaceSurface->translate(_interfacePal); + _backgroundSurface->copyFrom(_madsInterfaceSurface, Common::Rect(0, 0, 320, 44), 0, 200 - 44); + } + } + + if (_vm->getGameType() == GType_Burger && + sceneNumber != TITLE_SCENE_BURGER && sceneNumber != MAINMENU_SCENE_BURGER) + _vm->_interfaceView->setStatusText(""); + + // Load scene def file (*.CHK) + if (_vm->isM4()) { + loadSceneResources(sceneNumber); + loadSceneInverseColorTable(sceneNumber); + } else { + // Don't load other screen resources for system screens + if (sceneNumber >= 900) + return; + + loadSceneHotSpotsMads(sceneNumber); + } + + // TODO: set walker scaling + // TODO: destroy woodscript buffer + + // Load scene walk path file (*.COD/*.WW?) + loadSceneCodes(sceneNumber); + + // Load inverse color table file (*.IPL) + loadSceneInverseColorTable(sceneNumber); + + if (_vm->isM4()) { + + if (_vm->getGameType() != GType_Burger) { + // Load scene sprites file (*.SSB) + loadSceneSprites(sceneNumber); + + // Load scene sprite codes file (*.SSC) + loadSceneSpriteCodes(sceneNumber); + } + + + if (sceneNumber != TITLE_SCENE_BURGER && sceneNumber != MAINMENU_SCENE_BURGER) { + _vm->_interfaceView->show(); + showSprites(); + } + } + + // Purge resources + _vm->res()->purge(); +} + +void Scene::loadSceneResources(int sceneNumber) { + char filename[kM4MaxFilenameSize]; + int i = 0, x = 0, y = 0; + sprintf(filename, "%i.chk", sceneNumber); + + Common::SeekableReadStream *sceneS = _vm->res()->get(filename); + + if (sceneS != NULL) { + sceneS->read(_sceneResources.artBase, MAX_CHK_FILENAME_SIZE); + sceneS->read(_sceneResources.pictureBase, MAX_CHK_FILENAME_SIZE); + _sceneResources.hotspotCount = sceneS->readUint32LE(); + _sceneResources.parallaxCount = sceneS->readUint32LE(); + _sceneResources.propsCount = sceneS->readUint32LE(); + _sceneResources.frontY = sceneS->readUint32LE(); + _sceneResources.backY = sceneS->readUint32LE(); + _sceneResources.frontScale = sceneS->readUint32LE(); + _sceneResources.backScale = sceneS->readUint32LE(); + for (i = 0; i < 16; i++) + _sceneResources.depthTable[i] = sceneS->readUint16LE(); + _sceneResources.railNodeCount = sceneS->readUint32LE(); + + // Clear rails from previous scene + _vm->_rails->clearRails(); + + for (i = 0; i < _sceneResources.railNodeCount; i++) { + x = sceneS->readUint32LE(); + y = sceneS->readUint32LE(); + if (_vm->_rails->addRailNode(x, y, true) < 0) { + warning("Too many rail nodes defined for scene"); + } + } + + // Clear current hotspot lists + _sceneResources.hotspots->clear(); + _sceneResources.parallax->clear(); + _sceneResources.props->clear(); + + _sceneResources.hotspots->loadHotSpotsM4(sceneS, _sceneResources.hotspotCount); + _sceneResources.parallax->loadHotSpotsM4(sceneS, _sceneResources.parallaxCount); + _sceneResources.props->loadHotSpotsM4(sceneS, _sceneResources.propsCount); + + // Note that toss() deletes the MemoryReadStream + _vm->res()->toss(filename); + } +} + +void Scene::loadSceneHotSpotsMads(int sceneNumber) { + char filename[kM4MaxFilenameSize]; + sprintf(filename, "rm%i.hh", sceneNumber); + MadsPack hotSpotData(filename, _vm); + Common::SeekableReadStream *hotspotStream = hotSpotData.getItemStream(0); + + int hotspotCount = hotspotStream->readUint16LE(); + delete hotspotStream; + + HotSpotList *hotspotList = _sceneResources.hotspots; + _sceneResources.hotspotCount = hotspotCount; + + hotspotStream = hotSpotData.getItemStream(1); + + // Clear current hotspot lists + _sceneResources.hotspots->clear(); + + _sceneResources.hotspots->loadHotSpotsMads(hotspotStream, _sceneResources.hotspotCount); + + delete hotspotStream; +} + +void Scene::loadSceneCodes(int sceneNumber, int index) { + char filename[kM4MaxFilenameSize]; + Common::SeekableReadStream *sceneS; + + if (_vm->isM4()) { + sprintf(filename, "%i.cod", sceneNumber); + sceneS = _vm->res()->openFile(filename); + _codeSurface->loadCodesM4(sceneS); + _vm->res()->toss(filename); + } else if (_vm->getGameType() == GType_Phantom || _vm->getGameType() == GType_DragonSphere) { + sprintf(filename, "rm%i.ww%i", sceneNumber, index); + MadsPack walkData(filename, _vm); + sceneS = walkData.getItemStream(0); + _codeSurface->loadCodesMads(sceneS); + _vm->res()->toss(filename); + } else if (_vm->getGameType() == GType_RexNebular) { + // TODO + return; + } +} + +void Scene::loadSceneInverseColorTable(int sceneNumber) { + char filename[kM4MaxFilenameSize]; + Common::SeekableReadStream *iplS; + + if (_vm->isM4()) { + sprintf(filename, "%i.ipl", sceneNumber); + iplS = _vm->res()->openFile(filename); + if (_inverseColorTable) + delete[] _inverseColorTable; + _inverseColorTable = new byte[iplS->size()]; + iplS->read(_inverseColorTable, iplS->size()); + _vm->res()->toss(filename); + } else { + // TODO? + return; + } + +} + + +void Scene::loadSceneSprites(int sceneNumber) { + char filename[kM4MaxFilenameSize]; + sprintf(filename, "%i.ssb", sceneNumber); + + Common::SeekableReadStream *sceneS = _vm->res()->get(filename); + _sceneSprites = new SpriteAsset(_vm, sceneS, sceneS->size(), filename); + _vm->res()->toss(filename); + + printf("Scene has %d sprites, each one having %d colors\n", _sceneSprites->getCount(), _sceneSprites->getColorCount()); +} + +void Scene::loadSceneSpriteCodes(int sceneNumber) { + char filename[kM4MaxFilenameSize]; + sprintf(filename, "%i.ssc", sceneNumber); + + Common::SeekableReadStream *sceneS = _vm->res()->get(filename); + + // TODO + + if (sceneS != NULL) { + SpriteAsset* _sceneSpriteCodes = new SpriteAsset(_vm, sceneS, sceneS->size(), filename); + int colorCount = _sceneSpriteCodes->getColorCount(); +// RGB8* spritePalette = _sceneSpriteCodes->getPalette(); + //_vm->_palette->setPalette(spritePalette, 0, colorCount); + + printf("Scene has %d sprite codes, each one having %d colors\n", _sceneSpriteCodes->getCount(), colorCount); + + // Note that toss() deletes the MemoryReadStream + _vm->res()->toss(filename); + } +} + +void Scene::showSprites() { + // TODO: This is all experimental code, it needs heavy restructuring + // and cleanup + + // taken from set_walker_scaling() in adv_walk.cpp. A proper implementation will need + // to store these in global variables + int minScaling = FixedDiv(_sceneResources.backScale << 16, 100 << 16); + int maxScaling = FixedDiv(_sceneResources.frontScale << 16, 100 << 16); + int scaler; + + _vm->_actor->setWalkerDirection(kFacingSouthEast); + //_vm->_actor->setWalkerPalette(); + + // taken from set_walker_scaling() in adv_walk.cpp + if (_sceneResources.frontY == _sceneResources.backY) + scaler = 0; + else + scaler = FixedDiv(maxScaling - minScaling, + (_sceneResources.frontY << 16) - (_sceneResources.backY << 16)); + + // FIXME: For now, we (incorrectly) scale the walker to 50% of the scene's max scaling + _vm->_actor->setWalkerScaling(scaler / 2); + // Test code to display the protagonist + _vm->_actor->placeWalkerSpriteAt(0, 320, 200); + + // Test code to display scene sprites + // TODO +} + +void Scene::checkHotspotAtMousePos(int x, int y) { + if (_vm->getGameType() == GType_Riddle) + return; + + // TODO: loads of things to do here, only the mouse cursor and the status + // text is changed for now + + // Only scene hotspots are checked for now, not parallax/props, as the + // latter ones are not used by Orion Burger + HotSpot *currentHotSpot = _sceneResources.hotspots->findByXY(x, y); + if (currentHotSpot != NULL && currentHotSpot->getActive()) { + if (_vm->_mouse->getCursorNum() != CURSOR_LOOK && + _vm->_mouse->getCursorNum() != CURSOR_TAKE && + _vm->_mouse->getCursorNum() != CURSOR_USE && + _vm->_interfaceView->_inventory.getSelectedIndex() == -1) { + _vm->_mouse->setCursorNum(currentHotSpot->getCursor()); + } + _vm->_interfaceView->setStatusText(currentHotSpot->getPrep()); + } else { + if (_vm->_mouse->getCursorNum() != CURSOR_LOOK && + _vm->_mouse->getCursorNum() != CURSOR_TAKE && + _vm->_mouse->getCursorNum() != CURSOR_USE && + _vm->_interfaceView->_inventory.getSelectedIndex() == -1) { + _vm->_mouse->setCursorNum(0); + } else { + + } + } +} + +void Scene::checkHotspotAtMousePosMads(int x, int y) { + HotSpot *currentHotSpot = _sceneResources.hotspots->findByXY(x, y); + if (currentHotSpot != NULL) { + _vm->_mouse->setCursorNum(currentHotSpot->getCursor()); + + // This is the "easy" interface, which updates the status text when the mouse is moved + // TODO: toggle this code for easy/normal interface mode + char statusText[50]; + if (currentHotSpot->getVerbID() != 0) { + sprintf(statusText, "%s %s\n", currentHotSpot->getVerb(), currentHotSpot->getVocab()); + } else { + sprintf(statusText, "%s %s\n", _vm->_globals->getVocab(kVerbWalkTo), currentHotSpot->getVocab()); + } + + statusText[0] = toupper(statusText[0]); // capitalize first letter + setMADSStatusText(statusText); + } else { + _vm->_mouse->setCursorNum(0); + setMADSStatusText(""); + } +} + +// Test function, shows all scene hotspots +void Scene::showHotSpots() { + int i = 0; + HotSpot *currentHotSpot; + // hotspots (green) + for (i = 0; i < _sceneResources.hotspotCount; i++) { + currentHotSpot = _sceneResources.hotspots->get(i); + _backgroundSurface->frameRect(currentHotSpot->getRect(), _vm->_palette->GREEN); + } + if (_vm->isM4()) { + // parallax (yellow) + for (i = 0; i < _sceneResources.parallaxCount; i++) { + currentHotSpot = _sceneResources.parallax->get(i); + _backgroundSurface->frameRect(currentHotSpot->getRect(), _vm->_palette->YELLOW); + } + // props (red) + for (i = 0; i < _sceneResources.propsCount; i++) { + currentHotSpot = _sceneResources.props->get(i); + _backgroundSurface->frameRect(currentHotSpot->getRect(), _vm->_palette->RED); + } + } +} + +// Test function, shows all scene codes +void Scene::showCodes() { + uint8 *pixelData = (uint8*)_codeSurface->pixels; + for (int i = 0; i < _codeSurface->w * _codeSurface->h; i++) + if (pixelData[i] & 0x10) + pixelData[i] = 0xFF; + else + pixelData[i] = 0; + + byte colors[256 * 4]; + memset(colors, 0, sizeof(colors)); + colors[255 * 4 + 0] = 255; + colors[255 * 4 + 1] = 255; + colors[255 * 4 + 2] = 255; + _vm->_palette->setPalette(colors, 0, 256); + + _backgroundSurface->copyFrom(_codeSurface, Common::Rect(0, 0, 640, 480), 0, 0); + //_system->copyRectToScreen((byte *)codes->pixels, codes->w, 0, 0, codes->w, codes->h); +} + +void Scene::playIntro() { + +} + +void Scene::update() { + // TODO: Needs a proper implementation + // NOTE: Don't copy the background when in M4 mode or WoodScript anims won't be shown + if (!_vm->isM4()) { + _backgroundSurface->copyTo(this); + + if (_statusText[0]) { + // Text colors are inverted in Dragonsphere + if (_vm->getGameType() == GType_DragonSphere) + _vm->_font->setColors(_vm->_palette->BLACK, _vm->_palette->WHITE, _vm->_palette->BLACK); + else + _vm->_font->setColors(_vm->_palette->WHITE, _vm->_palette->BLACK, _vm->_palette->BLACK); + + _vm->_font->setFont(FONT_MAIN_MADS); + _vm->_font->writeString(this, _statusText, (width() - _vm->_font->getWidth(_statusText)) / 2, 142, 0); + } + } +} + +void Scene::onRefresh(RectList *rects, M4Surface *destSurface) { + update(); + View::onRefresh(rects, destSurface); +} + +bool Scene::onEvent(M4EventType eventType, int param1, int x, int y, bool &captureEvents) { + //if (_vm->getGameType() != GType_Burger) + // return false; + + // If the game is currently paused, don't do any scene processing + if (_vm->_kernel->paused) + return false; + + switch (eventType) { + case MEVENT_LEFT_CLICK: + { + if (_vm->getGameType() == GType_Burger) { + // Place a Wilbur sprite with the correct facing + HotSpot *currentHotSpot = _sceneResources.hotspots->findByXY(x, y); + if (currentHotSpot != NULL && currentHotSpot->getActive()) { + update(); + _vm->_actor->setWalkerDirection(currentHotSpot->getFacing()); + int posX = currentHotSpot->getFeetX(); + int posY = currentHotSpot->getFeetY() - + scaleValue(_vm->_actor->getWalkerHeight(), _vm->_actor->getWalkerScaling(), 0); + //_vm->_actor->placeWalkerSpriteAt(0, posX, posY); + + // Player said.... (for scene scripts) + printf("Player said: %s %s\n", currentHotSpot->getVerb(), currentHotSpot->getVocab()); + + // FIXME: This should be moved somewhere else, and is incomplete + if (_vm->_interfaceView->_inventory.getSelectedIndex() == -1) { + if (_vm->_mouse->getVerb() == NULL) { + strcpy(_vm->_player->verb, currentHotSpot->getVerb()); + } else { + strcpy(_vm->_player->verb, _vm->_mouse->getVerb()); + } + } else { + strcpy(_vm->_player->verb, _vm->_interfaceView->_inventory.getSelectedObjectName()); + } + strcpy(_vm->_player->noun, currentHotSpot->getVocab()); + strcpy(_vm->_player->object, ""); + _vm->_player->commandReady = true; + + printf("## Player said: %s %s\n", _vm->_player->verb, _vm->_player->noun); + + } + } + + if (!_vm->isM4()) { + HotSpot *currentHotSpot = _sceneResources.hotspots->findByXY(x, y); + if (currentHotSpot != NULL) { + char statusText[50]; + if (currentHotSpot->getVerbID() != 0) { + sprintf(statusText, "%s %s\n", currentHotSpot->getVerb(), currentHotSpot->getVocab()); + } else { + sprintf(statusText, "%s %s\n", _vm->_globals->getVocab(kVerbWalkTo), currentHotSpot->getVocab()); + } + + statusText[0] = toupper(statusText[0]); // capitalize first letter + setMADSStatusText(statusText); + } + } + } + break; + case MEVENT_RIGHT_CLICK: + if (_vm->getGameType() == GType_Burger) { + nextCommonCursor(); + _vm->_interfaceView->_inventory.clearSelected(); + } + break; + case MEVENT_MOVE: + if (_vm->isM4()) + checkHotspotAtMousePos(x, y); + else + checkHotspotAtMousePosMads(x, y); + break; + default: + return false; + } + + return true; +} + +void Scene::nextCommonCursor() { + int cursorIndex = _vm->_mouse->getCursorNum(); + + switch (cursorIndex) { + case CURSOR_ARROW: + cursorIndex = CURSOR_LOOK; + break; + case CURSOR_LOOK: + cursorIndex = CURSOR_TAKE; + break; + case CURSOR_TAKE: + cursorIndex = CURSOR_USE; + break; + case CURSOR_USE: + cursorIndex = CURSOR_ARROW; + break; + default: + cursorIndex = CURSOR_ARROW; + } + + _vm->_mouse->setCursorNum(cursorIndex); +} + +enum boxSprites { + topLeft = 0, + topRight = 1, + bottomLeft = 2, + bottomRight = 3, + left = 4, + right = 5, + top = 6, + bottom = 7, + topMiddle = 8, + filler1 = 9, + filler2 = 10 + // TODO: finish this +}; + +// TODO: calculate width and height, show text, show face if it exists +// TODO: this has been tested with Dragonsphere only, there are some differences +// in the sprites used in Phantom +void Scene::showMADSV2TextBox(char *text, int x, int y, char *faceName) { + int repeatX = 40; // FIXME: this is hardcoded + int repeatY = 30; // FIXME: this is hardcoded + int curX = x, curY = y; + int topRightX = x; // TODO: this is probably not needed + Common::SeekableReadStream *data = _vm->res()->get("box.ss"); + SpriteAsset *boxSprites = new SpriteAsset(_vm, data, data->size(), "box.ss"); + _vm->res()->toss("box.ss"); + + RGBList *palData = new RGBList(boxSprites->getColorCount(), boxSprites->getPalette(), true); + _vm->_palette->addRange(palData); + + for (int i = 0; i < boxSprites->getCount(); i++) + boxSprites->getFrame(i)->translate(palData); // sprite pixel translation + + // Top left corner + boxSprites->getFrame(topLeft)->copyTo(_backgroundSurface, x, curY); + curX += boxSprites->getFrame(topLeft)->width(); + + // Top line + for (int i = 0; i < repeatX; i++) { + boxSprites->getFrame(top)->copyTo(_backgroundSurface, curX, curY + 3); + curX += boxSprites->getFrame(top)->width(); + } + + // Top right corner + boxSprites->getFrame(topRight)->copyTo(_backgroundSurface, curX, curY); + topRightX = curX; + + // Top middle + // FIXME: the transparent color for this is also the black border color + boxSprites->getFrame(topMiddle)->copyTo(_backgroundSurface, + x + (curX - x) / 2 - boxSprites->getFrame(topMiddle)->width() / 2, + curY - 5, 167); + curX = x; + curY += boxSprites->getFrame(topLeft)->height(); + + // ----------------------------------------------------------------------------------------------- + + // Draw contents + for (int i = 0; i < repeatY; i++) { + for (int j = 0; j < repeatX; j++) { + if (j == 0) { + boxSprites->getFrame(left)->copyTo(_backgroundSurface, curX + 3, curY); + curX += boxSprites->getFrame(left)->width(); + } else if (j == repeatX - 1) { + curX = topRightX - 2; + boxSprites->getFrame(right)->copyTo(_backgroundSurface, curX + 3, curY + 1); + } else { + // TODO: the background of the contents follows a pattern which is not understood yet + if (j % 2 == 0) { + boxSprites->getFrame(filler1)->copyTo(_backgroundSurface, curX + 3, curY); + curX += boxSprites->getFrame(filler1)->width(); + } else { + boxSprites->getFrame(filler2)->copyTo(_backgroundSurface, curX + 3, curY); + curX += boxSprites->getFrame(filler2)->width(); + } + } + } // for j + curX = x; + curY += boxSprites->getFrame(left)->height(); + } // for i + + // ----------------------------------------------------------------------------------------------- + curX = x; + + // Bottom left corner + boxSprites->getFrame(bottomLeft)->copyTo(_backgroundSurface, curX, curY); + curX += boxSprites->getFrame(bottomLeft)->width(); + + // Bottom line + for (int i = 0; i < repeatX; i++) { + boxSprites->getFrame(bottom)->copyTo(_backgroundSurface, curX, curY + 1); + curX += boxSprites->getFrame(bottom)->width(); + } + + // Bottom right corner + boxSprites->getFrame(bottomRight)->copyTo(_backgroundSurface, curX, curY + 1); +} + +} // End of namespace M4 diff --git a/engines/m4/scene.h b/engines/m4/scene.h new file mode 100644 index 0000000000..77578f5a02 --- /dev/null +++ b/engines/m4/scene.h @@ -0,0 +1,124 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef M4_SCENE_H +#define M4_SCENE_H + +class View; + +#include "m4/assets.h" +#include "m4/hotspot.h" +#include "m4/graphics.h" +#include "m4/viewmgr.h" + +namespace M4 { + +#define TITLE_SCENE_BURGER 951 // 951 = intro, 901 = demo menu, 971 = first scene +#define MAINMENU_SCENE_BURGER 903 +#define FIRST_SCENE 101 +#define MAX_CHK_FILENAME_SIZE 144 + +#define INTERFACE_HEIGHT 106 +#define MADS_SURFACE_HEIGHT 156 + +enum MADSVerbs { + kVerbLook = 2, + kVerbTake = 3, + kVerbPush = 4, + kVerbOpen = 5, + kVerbPut = 6, + kVerbTalkTo = 7, + kVerbGive = 8, + kVerbPull = 9, + kVerbClose = 10, + kVerbThrow = 11, + kVerbWalkTo = 12 +}; + +struct SceneResources { + char artBase[MAX_CHK_FILENAME_SIZE]; + char pictureBase[MAX_CHK_FILENAME_SIZE]; + int32 hotspotCount; + HotSpotList *hotspots; + int32 parallaxCount; + HotSpotList *parallax; + int32 propsCount; + HotSpotList *props; + int32 frontY, backY; + int32 frontScale, backScale; + int16 depthTable[16]; + int32 railNodeCount; // # of rails +}; + +class Scene: public View { +public: + Scene(M4Engine *vm); + ~Scene(); + + // TODO: perhaps move playIntro() someplace else? + void playIntro(); + void loadScene(int sceneNumber); + void loadSceneResources(int sceneNumber); + void loadSceneHotSpotsMads(int sceneNumber); + void loadSceneCodes(int sceneNumber, int index = 0); + void loadSceneInverseColorTable(int sceneNumber); + void loadSceneSprites(int sceneNumber); + void loadSceneSpriteCodes(int sceneNumber); + void showSprites(); + void checkHotspotAtMousePos(int x, int y); + void checkHotspotAtMousePosMads(int x, int y); + void showHotSpots(); + void showCodes(); + int getCurrentScene() { return _currentScene; } + SceneResources getSceneResources() { return _sceneResources; } + M4Surface *getBackgroundSurface() const { return _backgroundSurface; } + byte *getInverseColorTable() const { return _inverseColorTable; } + void update(); + void setMADSStatusText(const char *text) { strcpy(_statusText, text); } + void showMADSV2TextBox(char *text, int x, int y, char *faceName); + + void onRefresh(RectList *rects, M4Surface *destSurface); + bool onEvent(M4EventType eventType, int param1, int x, int y, bool &captureEvents); + +private: + int _currentScene; + M4Surface *_backgroundSurface; + M4Surface *_codeSurface; + M4Surface *_madsInterfaceSurface; + byte *_inverseColorTable; + RGBList *_palData; + RGBList *_interfacePal; + SceneResources _sceneResources; + HotSpotList _sceneHotspots; + SpriteAsset *_sceneSprites; + SpriteAsset *_walkerSprite; + char _statusText[100]; + + void nextCommonCursor(); +}; + +} // End of namespace M4 + +#endif diff --git a/engines/m4/script.cpp b/engines/m4/script.cpp new file mode 100644 index 0000000000..e7026a381d --- /dev/null +++ b/engines/m4/script.cpp @@ -0,0 +1,1406 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "common/system.h" + +#include "m4/m4.h" +#include "m4/script.h" +#include "m4/resource.h" + +namespace M4 { + +enum OpcodeType { + opRet = 0, + opCall, + opCallKernel, + opPush, + opPush0, + opPush1, + opPushNeg1, + opPop, + opMov, + opAdd, + opSub, + opInc, + opDec, + opCmp, + opJmp, + opJmpByTable, + opJz, + opJnz, + opJe, + opJne, + opJl, + opJle, + opJg, + opJge, + opXor, + opShl, + opShr, + + opDebug, + + opInvalid +}; + +const char *opcodeNames[] = { + "opRet", + "opCall", + "opCallKernel", + "opPush", + "opPush0", + "opPush1", + "opPushNeg1", + "opPop", + "opMov", + "opAdd", + "opSub", + "opInc", + "opDec", + "opCmp", + "opJmp", + "opJmpByTable", + "opJz", + "opJnz", + "opJe", + "opJne", + "opJl", + "opJle", + "opJg", + "opJge", + "opXor", + "opShl", + "opShr", + "opDebug", + "opInvalid" +}; + +StringTable::StringTable() : _stringsData(NULL) { +} + +StringTable::~StringTable() { + if (_stringsData) + delete[] _stringsData; +} + +void StringTable::load(Common::File *fd) { + int stringSize = fd->readUint32LE(); + int stringCount = fd->readUint32LE(); + _stringsData = new char[stringSize]; + fd->read(_stringsData, stringSize); + char *stringPtr = _stringsData; + for (int i = 0; i < stringCount; i++) { + _strings.push_back((const char*)stringPtr); + stringPtr += strlen(stringPtr) + 1; + } +} + +SeriesStreamBreakList::~SeriesStreamBreakList() { +} + +void SeriesStreamBreakList::load(Common::File *fd) { + uint32 count = fd->readUint32LE(); + printf("SeriesStreamBreakList::load() count = %d\n", count); + for (uint32 i = 0; i < count; i++) { + SeriesStreamBreakItem *item = new SeriesStreamBreakItem(); + item->frameNum = fd->readUint32LE(); + item->digiName = _inter->loadGlobalString(fd); + item->digiChannel = fd->readUint32LE(); + item->digiVolume = fd->readUint32LE(); + item->trigger = fd->readUint32LE(); + item->flags = fd->readUint32LE(); + item->variable.type = kGameVar; + item->variable.value = fd->readUint32LE(); + item->value = fd->readUint32LE(); + _items.push_back(item); + + printf("%02d: frameNum = %d; digiName = %s; digiChannel = %d; digiVolume = %d; trigger = %d; flags = %d; variable = %d; value = %d\n", + i, item->frameNum, item->digiName, item->digiChannel, item->digiVolume, item->trigger, item->flags, item->variable.value, item->value); + + } +} + +SaidArray::~SaidArray() { +} + +void SaidArray::load(Common::File *fd) { + uint32 count = fd->readUint32LE(); + printf("SaidArray::load() count = %d\n", count); + for (uint32 i = 0; i < count; i++) { + SaidArrayItem *item = new SaidArrayItem(); + item->itemName = _inter->loadGlobalString(fd); + item->digiNameLook = _inter->loadGlobalString(fd); + item->digiNameTake = _inter->loadGlobalString(fd); + item->digiNameGear = _inter->loadGlobalString(fd); + _items.push_back(item); + + printf("itemName = %s; digiNameLook = %s; digiNameTake = %s; digiNameGear = %s\n", + item->itemName, item->digiNameLook, item->digiNameTake, item->digiNameGear); + + } +} + +ParserArray::~ParserArray() { +} + +void ParserArray::load(Common::File *fd) { + uint32 count = fd->readUint32LE(); + printf("ParserArray::load() count = %d\n", count); + for (uint32 i = 0; i < count; i++) { + ParserArrayItem *item = new ParserArrayItem(); + item->w0 = _inter->loadGlobalString(fd); + item->w1 = _inter->loadGlobalString(fd); + item->trigger = fd->readUint32LE(); + item->testVariable.type = kGameVar; + item->testVariable.value = fd->readUint32LE(); + item->testValue = fd->readUint32LE(); + item->variable.type = kGameVar; + item->variable.value = fd->readUint32LE(); + item->value = fd->readUint32LE(); + _items.push_back(item); + + printf("w0 = %s; w1 = %s; trigger = %d; testVariable = %d; testValue = %d; variable = %d; value = %d\n", + item->w0, item->w1, item->trigger, item->testVariable.value, item->testValue, item->variable.value, item->value); + + } +} + +ScriptFunction::ScriptFunction(ScriptInterpreter *inter) : _inter(inter) { +} + +ScriptFunction::~ScriptFunction() { + if (_code) + delete _code; +} + +void ScriptFunction::load(Common::File *fd) { + printf("ScriptFunction::load()\n"); + uint32 size = fd->readUint32LE(); + printf("ScriptFunction::load() size = %d\n", size); + _code = fd->readStream(size); +} + +void ScriptFunction::jumpAbsolute(uint32 ofs) { + _code->seek(ofs); +} + +void ScriptFunction::jumpRelative(int32 ofs) { + _code->seek(ofs, SEEK_CUR); +} + +byte ScriptFunction::readByte() { + return _code->readByte(); +} + +uint32 ScriptFunction::readUint32() { + return _code->readUint32LE(); +} + + +ScriptInterpreter::ScriptInterpreter(M4Engine *vm) : _scriptFile(NULL), _vm(vm) { + initScriptKernel(); + _dataCache = new ScriptDataCache(this); + _runningFunction = NULL; +} + +ScriptInterpreter::~ScriptInterpreter() { + close(); + delete _dataCache; +} + +void ScriptInterpreter::open(const char *filename) { + if (_scriptFile) + close(); + _scriptFile = new Common::File(); + _scriptFile->open(filename); + if (!_scriptFile->isOpen()) + error("ScriptInterpreter::open() Error opening %s.", filename); + + _scriptFile->readUint32LE(); // skip magic for now + uint32 version = _scriptFile->readUint32LE(); + if (version != kScriptFileVersion) { + error("ScriptInterpreter::open() DAT file version mismatch; requested %li, got %i\n", kScriptFileVersion, version); + } + + int functionCount = _scriptFile->readUint32LE(); + printf("functionCount = %d\n", functionCount); + for (int i = 0; i < functionCount; i++) { + uint32 offset = _scriptFile->readUint32LE(); + printf("func(%d) offset = %08X\n", i, offset); + uint32 len = _scriptFile->readUint32LE(); + if (len > 0) { + char *funcName = new char[len + 1]; + _scriptFile->read(funcName, len); + funcName[len] = '\0'; + printf("func(%d) name = %s\n", i, funcName); + _functionNames[Common::String(funcName)] = _functions.size(); + // DEBUG + _scriptFunctionNames.push_back(Common::String(funcName)); + delete[] funcName; + } + _functions.push_back(new ScriptFunctionEntry(offset)); + } + + int dataCount = _scriptFile->readUint32LE(); + printf("dataCount = %d\n", dataCount); + for (int i = 0; i < dataCount; i++) { + uint32 offset = _scriptFile->readUint32LE(); + ScriptDataType type = (ScriptDataType)_scriptFile->readUint32LE(); + printf("data(%d) offset = %08X; type = %d\n", i, offset, type); + _data.push_back(new ScriptDataEntry(offset, type)); + } + + _globalVarCount = _scriptFile->readUint32LE(); + printf("_globalVarCount = %d\n", _globalVarCount); + + uint32 stringOfs = _scriptFile->readUint32LE(); + _scriptFile->seek(stringOfs); + _constStrings.load(_scriptFile); + + for (int i = 0; i < ARRAYSIZE(_globalVars); i++) { + _globalVars[i].type = kInteger; + _globalVars[i].value = 0; + } + + memset(_logicGlobals, 0, sizeof(_logicGlobals)); + + memset(_registers, 0, sizeof(_registers)); + memset(_stack, 0, sizeof(_stack)); + _stackPtr = 0; + +} + +void ScriptInterpreter::close() { + if (_scriptFile) { + delete _scriptFile; + } +} + +void ScriptInterpreter::initScriptKernel() { + +#include "m4/scripttab.h" + + _kernelFunctions = kernelFunctions; + _kernelFunctionsMax = ARRAYSIZE(kernelFunctions) + 1; + + _kernelVars = kernelVars; + _kernelVarsMax = ARRAYSIZE(kernelVars) + 1; + +} + + +ScriptFunction *ScriptInterpreter::loadFunction(uint32 index) { + //GONE WHILE DEBUGGING assert(index < _functions.size()); + if (index >= _functions.size()) return NULL; + ScriptFunction *scriptFunction; + scriptFunction = _functions[index]->func; + if (!scriptFunction) { + scriptFunction = new ScriptFunction(this); + _scriptFile->seek(_functions[index]->offset); + scriptFunction->load(_scriptFile); + _functions[index]->func = scriptFunction; + } + return scriptFunction; +} + +ScriptFunction *ScriptInterpreter::loadFunction(const Common::String &name) { + FunctionNameMap::iterator iter = _functionNames.find(name); + if (iter == _functionNames.end()) { + printf("ScriptInterpreter::loadFunction() Function '%s' not found!\n", name.c_str()); + return NULL; + } + uint32 funcIndex = (*iter)._value; + printf("ScriptInterpreter::loadFunction() index('%s') = %d\n", name.c_str(), funcIndex); + return loadFunction(funcIndex); +} + +void ScriptInterpreter::unloadFunctions() { + for (uint32 i = 0; i < _functions.size(); i++) { + if (_functions[i]->func) { + delete _functions[i]->func; + _functions[i]->func = NULL; + } + } +} + +int ScriptInterpreter::runFunction(ScriptFunction *scriptFunction) { + bool done = false; + + int oldLocalStackPtr = _localStackPtr; + ScriptFunction *oldRunningFunction = _runningFunction; + + // TODO: Also initialize _localStackPtr + + _runningFunction = scriptFunction; + _runningFunction->jumpAbsolute(0); + while (!done) { + byte opcode = _runningFunction->readByte(); + done = !execOpcode(opcode); + fflush(stdout); + } + + _localStackPtr = oldLocalStackPtr; + _runningFunction = oldRunningFunction; + + return 0; +} + +void ScriptInterpreter::push(const ScriptValue &value) { + if (_stackPtr == ARRAYSIZE(_stack)) + error("ScriptInterpreter::push() Stack overflow!\n"); + _stack[_stackPtr++] = value; +} + +void ScriptInterpreter::pop(ScriptValue &value) { + if (_stackPtr == 0) + error("ScriptInterpreter::pop() Stack underflow!\n"); + value = _stack[_stackPtr--]; +} + +void ScriptInterpreter::dumpStack() { + printf("ScriptInterpreter::dumpStack()\n"); + for (int i = 0; i < _stackPtr; i++) { + printf("%03d. type = %02d; value = %d\n", i, _stack[i].type, _stack[i].value); + } +} + +void ScriptInterpreter::dumpRegisters() { + printf("ScriptInterpreter::dumpRegisters()\n"); + for (int i = 0; i < ARRAYSIZE(_registers); i++) { + printf("%03d. type = %02d; value = %d\n", i, _registers[i].type, _registers[i].value); + } +} + +void ScriptInterpreter::dumpGlobalVars() { + printf("ScriptInterpreter::dumpGlobalVars()\n"); + for (int i = 0; i < ARRAYSIZE(_globalVars); i++) { + if (_globalVars[i].type != -1) + printf("%03d. type = %02d; value = %d\n", i, _globalVars[i].type, _globalVars[i].value); + } +} + +int ScriptInterpreter::toInteger(const ScriptValue &value) { + + switch (value.type) { + + case kInteger: + return value.value; + + default: + printf("ScriptInterpreter::toInteger() Invalid type %d!\n", value.type); + return 0; + + } + +} + +const char *ScriptInterpreter::toString(const ScriptValue &value) { + + switch (value.type) { + + case kInteger: + return NULL; + + case kConstString: + return _constStrings[value.value]; + + default: + printf("ScriptInterpreter::toString() Invalid type %d!\n", value.type); + return NULL; + + } + +} + +const char *ScriptInterpreter::loadGlobalString(Common::File *fd) { + uint32 index = fd->readUint32LE(); + if (index != 0xFFFFFFFF) + return getGlobalString(index); + else + return NULL; +} + +void ScriptInterpreter::test() { +} + +void ScriptInterpreter::loadValue(ScriptValue &value) { + + value.type = (ScriptValueType)_runningFunction->readByte(); + + switch (value.type) { + + case kGameVar: + case kInteger: + case kConstString: + case kDataRef: + case kLogicVar: + case kLogicVarRef: + case kKernelVar: + value.value = _runningFunction->readUint32(); + break; + + case kRegister: + value.value = _runningFunction->readByte(); + break; + + default: + printf("ScriptInterpreter::loadValue() Invalid value type %d!\n", value.type); + + } + +} + +void ScriptInterpreter::copyValue(ScriptValue &destValue, ScriptValue &sourceValue) { + + if (sourceValue.type == -1) { + printf("ScriptInterpreter::copyValue() Trying to read uninitialized value!\n"); + } + + switch (destValue.type) { + + case kGameVar: + _globalVars[destValue.value] = sourceValue; + break; + + case kRegister: + _registers[destValue.value] = sourceValue; + break; + + case kLogicVar: + // TODO: Move to own method + if (sourceValue.type == kInteger) { + _logicGlobals[destValue.value] = sourceValue.value; + } else { + printf("ScriptInterpreter::copyValue() Invalid source value type %d!\n", sourceValue.type); + } + break; + + case kKernelVar: + setKernelVar(destValue.value, sourceValue); + break; + + default: + printf("ScriptInterpreter::copyValue() Invalid dest value type %d!\n", destValue.type); + + } + +} + +void ScriptInterpreter::derefValue(ScriptValue &value) { + + switch (value.type) { + + case kGameVar: + value = _globalVars[value.value]; + break; + + case kInteger: + case kConstString: + case kDataRef: + case kLogicVarRef: + // These need no dereferencing + break; + + case kRegister: + value = _registers[value.value]; + break; + + case kLogicVar: + // TODO: Move to own method + value = _logicGlobals[value.value]; + break; + + case kKernelVar: + getKernelVar(value.value, value); + break; + + default: + printf("ScriptInterpreter::derefValue() Invalid value type %d!\n", value.type); + + } + +} + +void ScriptInterpreter::callKernelFunction(uint32 index) { + + printf("ScriptInterpreter::callKernelFunction() index = %d\n", index); + + if (index > _kernelFunctionsMax) { + printf("ScriptInterpreter::callKernelFunction() Invalid kernel functionindex (%d)\n", index); + return; + } + + printf("ScriptInterpreter::callKernelFunction() name = %s\n", _kernelFunctions[index].desc); + + int args = (this->*(_kernelFunctions[index].proc))(); + // Now remove values from the stack if the function used any + if (args > 4) + _stackPtr -= args - 4; + + printf("-------------\n"); + +} + +ScriptValue ScriptInterpreter::getArg(uint32 index) { + if (index < 4) { + return _registers[index]; + } else { + index -= 4; + return _stack[_stackPtr - index - 1]; + } +} + +void ScriptInterpreter::dumpArgs(uint32 count) { + printf("ScriptInterpreter::dumpArgs() "); + for (uint32 i = 0; i < count; i++) { + ScriptValue argValue = getArg(i); + if (argValue.type == kConstString) { + printf("'%s'", toString(argValue)); + } else { + printf("%d", argValue.value); + } + if (i + 1 < count) + printf(", "); + } + printf("\n"); +} + +void ScriptInterpreter::callFunction(uint32 index) { + // NOTE: This is a temporary hack for script functions not yet in the m4.dat + if (index == 0xFFFFFFFF) + return; + printf("ScriptInterpreter::callFunction() index = %d [%s]\n", index, _scriptFunctionNames[index].c_str()); + fflush(stdout); + ScriptFunction *subFunction = loadFunction(index); + if (!subFunction) { + // This *should* never happen since the linker checks this + printf("ScriptInterpreter::callFunction() Function %d could not be loaded!\n", index); + return; + } + runFunction(subFunction); +} + +bool ScriptInterpreter::execOpcode(byte opcode) { + + printf("opcode = %d (%s)\n", opcode, opcodeNames[opcode]); + + ScriptValue value1, value2, value3; + uint32 temp; + + /* TODO: Put all opcodes into separate functions and into an array + (but only after all needed opcodes are known and frozen) + */ + + switch (opcode) { + + case opRet: + return false; + + case opPush: + loadValue(value1); + derefValue(value1); + push(value1); + return true; + + case opPush0: + push(ScriptValue(0)); + return true; + + case opPush1: + push(ScriptValue(1)); + return true; + + case opPushNeg1: + push(ScriptValue(-1)); + return true; + + case opPop: + loadValue(value1); + pop(value2); + copyValue(value1, value2); + return true; + + case opMov: + loadValue(value1); + loadValue(value2); + derefValue(value2); + copyValue(value1, value2); + return true; + + // Possibly join all jump variants into one opcode + + case opJmp: + temp = _runningFunction->readUint32(); + printf("-> ofs = %08X\n", temp); + _runningFunction->jumpAbsolute(temp); + return true; + + case opJl: + temp = _runningFunction->readUint32(); + if (_cmpFlags < 0) { + printf("-> ofs = %08X\n", temp); + _runningFunction->jumpAbsolute(temp); + } + return true; + + case opJle: + temp = _runningFunction->readUint32(); + if (_cmpFlags <= 0) { + printf("-> ofs = %08X\n", temp); + _runningFunction->jumpAbsolute(temp); + } + return true; + + case opJg: + temp = _runningFunction->readUint32(); + if (_cmpFlags > 0) { + printf("-> ofs = %08X\n", temp); + _runningFunction->jumpAbsolute(temp); + } + return true; + + case opJge: + temp = _runningFunction->readUint32(); + if (_cmpFlags >= 0) { + printf("-> ofs = %08X\n", temp); + _runningFunction->jumpAbsolute(temp); + } + return true; + + case opJz: + temp = _runningFunction->readUint32(); + if (_cmpFlags == 0) { + printf("-> ofs = %08X\n", temp); + _runningFunction->jumpAbsolute(temp); + } + return true; + + case opJnz: + temp = _runningFunction->readUint32(); + if (_cmpFlags != 0) { + printf("-> ofs = %08X\n", temp); + _runningFunction->jumpAbsolute(temp); + } + return true; + + case opJmpByTable: + temp = _runningFunction->readUint32(); + printf("-> index = %d\n", _registers[0].value); + _runningFunction->jumpRelative(_registers[0].value * 4); + temp = _runningFunction->readUint32(); + printf("-> ofs = %08X\n", temp); + _runningFunction->jumpAbsolute(temp); + return true; + + case opCmp: + loadValue(value1); + loadValue(value2); + derefValue(value1); + derefValue(value2); + if (value1.type != kInteger || value2.type != kInteger) + warning("ScriptInterpreter::execOpcode() Trying to compare non-integer values (%d, %d, line %d)!\n", value1.type, value2.type, _lineNum); + _cmpFlags = value1.value - value2.value; + printf("-> cmp %d, %d\n", value1.value, value2.value); + printf("-> _cmpFlags = %d\n", _cmpFlags); + return true; + + case opCall: + temp = _runningFunction->readUint32(); + callFunction(temp); + return true; + + case opCallKernel: + temp = _runningFunction->readUint32(); + callKernelFunction(temp); + return true; + + case opInc: + loadValue(value1); + value2 = value1; + derefValue(value2); + value2.value++; + copyValue(value1, value2); + return true; + + case opDec: + loadValue(value1); + value2 = value1; + derefValue(value2); + value2.value--; + copyValue(value1, value2); + return true; + + case opAdd: + loadValue(value1); + value3 = value1; + loadValue(value2); + derefValue(value3); + derefValue(value2); + value3.value += value2.value; + copyValue(value1, value3); + return true; + + case opSub: + loadValue(value1); + value3 = value1; + loadValue(value2); + derefValue(value3); + derefValue(value2); + value3.value -= value2.value; + copyValue(value1, value3); + return true; + + case opDebug: + _lineNum = (int)_runningFunction->readUint32(); + return true; + + default: + printf("Invalid opcode %d!\n", opcode); + return false; + + } + + return false; + +} + +// Kernel functions + +#define STRING(arg) (toString(getArg(arg))) +#define INTEGER(arg) (toInteger(getArg(arg))) +#define DATA(arg, T) (toData<T>(getArg(arg))) +#define RETURN(value) (_registers[0] = (value)) + +int ScriptInterpreter::o1_handleStreamBreak() { + return 0; +} + +int ScriptInterpreter::o1_handlePlayBreak() { + return 0; +} + +int ScriptInterpreter::o1_dispatchTriggerOnSoundState() { + return 0; +} + +int ScriptInterpreter::o1_getTicks() { + return 0; +} + +int ScriptInterpreter::o1_setSoundVolume() { + return 0; +} + +int ScriptInterpreter::o1_getSoundStatus() { + return 0; +} + +int ScriptInterpreter::o1_getSoundDuration() { + return 0; +} + +int ScriptInterpreter::o1_setSeriesFrameRate() { + return 0; +} + +int ScriptInterpreter::o1_terminateMachine() { + return 0; +} + +int ScriptInterpreter::o1_sendWoodScriptMessage() { + return 0; +} + +int ScriptInterpreter::o1_runConversation() { + return 0; +} + +int ScriptInterpreter::o1_exportConversationValue() { + return 0; +} + +int ScriptInterpreter::o1_exportConversationPointer() { + return 0; +} + +int ScriptInterpreter::o1_playBreakSeries() { + return 0; +} + +int ScriptInterpreter::o1_hideWalker() { + return 0; +} + +int ScriptInterpreter::o1_showWalker() { + return 0; +} + +int ScriptInterpreter::o1_walk() { + return 0; +} + +int ScriptInterpreter::o1_overrideCrunchTime() { + return 0; +} + +int ScriptInterpreter::o1_addBlockingRect() { + return 0; +} + +int ScriptInterpreter::o1_setPlayerCommandsAllowed() { + return 0; +} + +int ScriptInterpreter::o1_getPlayerCommandsAllowed() { + return 0; +} + +int ScriptInterpreter::o1_setPlayerFacingAngle() { + return 0; +} + +int ScriptInterpreter::o1_disablePlayerFadeToBlack() { + return 0; +} + +int ScriptInterpreter::o1_enablePlayer() { + return 0; +} + +int ScriptInterpreter::o1_disablePlayer() { + return 0; +} + +int ScriptInterpreter::o1_freshenSentence() { + return 0; +} + +int ScriptInterpreter::o1_playerGiveItem() { + return 0; +} + +int ScriptInterpreter::o1_moveObject() { + return 0; +} + +int ScriptInterpreter::o1_setStopSoundsBetweenRooms() { + return 0; +} + +int ScriptInterpreter::o1_backupPalette() { + return 0; +} + +int ScriptInterpreter::o1_unloadWilburWalker() { + return 0; +} + +int ScriptInterpreter::o1_wilburTalk() { + return 0; +} + +int ScriptInterpreter::o1_wilburFinishedTalking() { + return 0; +} + +int ScriptInterpreter::o1_preloadSound() { + const char *name = STRING(0); + int room = INTEGER(1); + printf("name = %s; room = %d\n", name, room); + return 2; +} + +int ScriptInterpreter::o1_unloadSound() { + const char *name = STRING(0); + int room = INTEGER(1); + printf("name = %s; room = %d\n", name, room); + return 2; +} + +int ScriptInterpreter::o1_playSound() { + const char *name = STRING(0); + int channel = INTEGER(1); + int volume = INTEGER(2); + int trigger = INTEGER(3); + int room = INTEGER(4); + printf("name = %s; channel = %d; volume = %d; trigger = %d; room = %d\n", + name, channel, volume, trigger, room); + + Common::String soundName = Common::String(name) + ".raw"; + _vm->_sound->playVoice(soundName.c_str(), 100); + + // HACK until fixed + _vm->_kernel->sendTrigger(trigger); + + return 5; +} + +int ScriptInterpreter::o1_playLoopingSound() { + const char *name = STRING(0); + int channel = INTEGER(1); + int volume = INTEGER(2); + int trigger = INTEGER(3); + int room = INTEGER(4); + printf("name = %s; channel = %d; volume = %d; trigger = %d; room = %d\n", + name, channel, volume, trigger, room); + + // HACK until fixed + _vm->_kernel->sendTrigger(trigger); + + return 5; +} + +int ScriptInterpreter::o1_stopSound() { + int channel = INTEGER(0); + printf("channel = %d\n", channel); + return 1; +} + +int ScriptInterpreter::o1_fadeSetStart() { + // skip arg 0: palette ptr + int percent = INTEGER(1); + printf("percent = %d\n", percent); + return 2; +} + +int ScriptInterpreter::o1_fadeInit() { + // skip arg 0: palette ptr + int first = INTEGER(1); + int last = INTEGER(2); + int percent = INTEGER(3); + int ticks = INTEGER(4); + int trigger = INTEGER(5); + printf("first = %d; last = %d; percent = %d; ticks = %d; trigger = %d\n", + first, last, percent, ticks, trigger); + + // HACK until palette fading is implemented + _vm->_kernel->sendTrigger(trigger); + + return 6; +} + +int ScriptInterpreter::o1_fadeToBlack() { + return 0; +} + +int ScriptInterpreter::o1_initPaletteCycle() { + int first = INTEGER(0); + int last = INTEGER(1); + int delay = INTEGER(2); + int ticks = INTEGER(3); + int trigger = INTEGER(4); + printf("first = %d; last = %d; delay = %d; ticks = %d; trigger = %d\n", + first, last, delay, ticks, trigger); + + // HACK until palette cycling is implemented + _vm->_kernel->sendTrigger(trigger); + + return 5; +} + +int ScriptInterpreter::o1_stopPaletteCycle() { + return 0; +} + +int ScriptInterpreter::o1_hasPlayerSaid() { + const char *words[3]; + for (int i = 0; i < 3; i++) + words[i] = STRING(i); + printf("'%s', '%s', '%s'\n", words[0], words[1], words[2]); + + int result = _vm->_player->said(words[0], words[1], words[2]); + + printf(" -> '%d'\n", result); + fflush(stdout); + + RETURN(result); + return 3; +} + +int ScriptInterpreter::o1_hasPlayerSaidAny() { + const char *words[10]; + for (int i = 0; i < 10; i++) + words[i] = STRING(i); + + printf("'%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s'\n", + words[0], words[1], words[2], words[3], words[4], words[5], words[6], words[7], words[8], words[9]); + + int result = _vm->_player->saidAny(words[0], words[1], words[2], words[3], words[4], words[5], words[6], words[7], words[8], words[9]); + printf(" -> '%d'\n", result); + fflush(stdout); + + RETURN(result); + return 10; +} + +int ScriptInterpreter::o1_updatePlayerInfo() { + // skip arg 0: player info struct + return 1; +} + +int ScriptInterpreter::o1_playerHotspotWalkOverride() { + int x1 = INTEGER(0); + int y1 = INTEGER(1); + int x2 = INTEGER(2); + int y2 = INTEGER(3); + printf("(%d, %d); (%d, %d)\n", x1, y1, x2, y2); + return 4; +} + +int ScriptInterpreter::o1_playerHasItem() { + const char *name = STRING(0); + printf("item = '%s'\n", name); + // TODO + RETURN(0); + return 1; +} + +int ScriptInterpreter::o1_setWalkerLocation() { + // skip arg 0: walker + int x = INTEGER(1); + int y = INTEGER(2); + printf("x = %d; y = %d\n", x, y); + return 3; +} + +int ScriptInterpreter::o1_setWalkerFacing() { + // skip arg 0: walker + int facing = INTEGER(1); + printf("facing = %d\n", facing); + return 2; +} + +int ScriptInterpreter::o1_setHotspot() { + // skip arg 0: hotspot list + const char *name = STRING(1); + int value = INTEGER(2); + printf("name = '%s' -> %d\n", name, value); + + _vm->_scene->getSceneResources().hotspots->setActive(name, (value != 0)); + + return 2; +} + +int ScriptInterpreter::o1_loadConversation() { + const char *name = STRING(0); + int trigger = INTEGER(1); + int flag = INTEGER(2); + + // TODO; just to show something + _vm->_converse->startConversation(name); + + return 3; +} + +int ScriptInterpreter::o1_playSeries() { + const char *name = STRING(0); + int layer = INTEGER(1); + int flags = INTEGER(2); + int trigger = INTEGER(3); + int frameRate = INTEGER(4); + int loopCount = INTEGER(5); + int scale = INTEGER(6); + int x = INTEGER(7); + int y = INTEGER(8); + int firstFrame = INTEGER(9); + int lastFrame = INTEGER(10); + + printf("name = %s; layer = %04X; flags = %08X; trigger = %d; frameRate = %d; loopCount = %d; scale = %d; x = %d; y = %d: firstFrame = %d; lastFrame = %d\n", + name, layer, flags, trigger, frameRate, loopCount, scale, x, y, firstFrame, lastFrame); + fflush(stdout); + + // TODO: Return the machine to the script + _vm->_ws->playSeries(name, layer, flags, trigger, frameRate, loopCount, scale, x, y, firstFrame, lastFrame); + + return 11; +} + +int ScriptInterpreter::o1_showSeries() { + const char *name = STRING(0); + int layer = INTEGER(1); + int flags = INTEGER(2); + int trigger = INTEGER(3); + int duration = INTEGER(4); + int frameIndex = INTEGER(5); + int scale = INTEGER(6); + int x = INTEGER(7); + int y = INTEGER(8); + + printf("name = %s; layer = %04X; flags = %08X; trigger = %d; duration = %d; frameIndex = %d; scale = %d; x = %d; y = %d\n", + name, layer, flags, trigger, duration, frameIndex, scale, x, y); + fflush(stdout); + + // TODO: Return the machine to the script + _vm->_ws->showSeries(name, layer, flags, trigger, duration, frameIndex, scale, x, y); + + return 9; +} + +int ScriptInterpreter::o1_loadSeries() { + const char *name = STRING(0); + int hash = INTEGER(1); + // skip arg 3: palette ptr + + printf("name = %s; hash = %d\n", name, hash); + fflush(stdout); + + int result = _vm->_ws->loadSeries(name, hash, NULL); + + RETURN(result); + return 3; +} + +int ScriptInterpreter::o1_unloadSeries() { + return 0; +} + +int ScriptInterpreter::o1_preloadBreakSeries() { + const SeriesStreamBreakList& seriesStreamBreakList = DATA(0, SeriesStreamBreakList); + return 1; +} + +int ScriptInterpreter::o1_unloadBreakSeries() { + const SeriesStreamBreakList& seriesStreamBreakList = DATA(0, SeriesStreamBreakList); + return 1; +} + +int ScriptInterpreter::o1_startBreakSeries() { + const SeriesStreamBreakList& seriesStreamBreakList = DATA(0, SeriesStreamBreakList); + return 1; +} + +int ScriptInterpreter::o1_globalTriggerProc() { + int value1 = INTEGER(0); + int value2 = INTEGER(1); + int value3 = INTEGER(2); + printf("%d; %d; %d\n", value1, value2, value3); + return 3; +} + +int ScriptInterpreter::o1_triggerTimerProc() { + int value1 = INTEGER(0); + int value2 = INTEGER(1); + int value3 = INTEGER(2); + printf("%d; %d; %d\n", value1, value2, value3); + return 3; +} + +int ScriptInterpreter::o1_dispatchTrigger() { + int trigger = INTEGER(0); + printf("trigger = %d\n", trigger); + + _vm->_kernel->sendTrigger(trigger); + //g_system->delayMillis(5000); + + return 1; +} + +int ScriptInterpreter::o1_getRangedRandomValue() { + int minValue = INTEGER(0); + int maxValue = INTEGER(1); + RETURN(_vm->imath_ranged_rand(minValue, maxValue)); + return 2; +} + +int ScriptInterpreter::o1_wilburSaid() { + const SaidArray& saidArray = DATA(0, SaidArray); + + int result = 0; + + // NOTE: The "Common::String soundName" stuff is just temporary until playVoice is fixed. + + for (int i = 0; i < saidArray.size(); i++) { + SaidArrayItem *item = saidArray[i]; + + if (_vm->_player->said("LOOK AT", item->itemName) && item->digiNameLook) { + printf(" -> LOOK AT: '%s'\n", item->digiNameLook); + Common::String soundName = Common::String(item->digiNameLook) + ".raw"; + _vm->_sound->playVoice(soundName.c_str(), 100); + result = 1; + break; + } + + if (_vm->_player->said("TAKE", item->itemName) && item->digiNameTake) { + printf(" -> TAKE: '%s'\n", item->digiNameTake); + Common::String soundName = Common::String(item->digiNameTake) + ".raw"; + _vm->_sound->playVoice(soundName.c_str(), 100); + result = 1; + break; + } + + if (_vm->_player->said("GEAR", item->itemName) && item->digiNameGear) { + printf(" -> GEAR: '%s'\n", item->digiNameGear); + Common::String soundName = Common::String(item->digiNameGear) + ".raw"; + _vm->_sound->playVoice(soundName.c_str(), 100); + result = 1; + break; + } + + /* + printf("##### itemName = '%s'; digiNameLook = %s; digiNameTake = %s; digiNameGear = %s\n", + item->itemName, item->digiNameLook, item->digiNameTake, item->digiNameGear); + */ + } + printf(" -> '%d'\n", result); + fflush(stdout); + + RETURN(result); + return 1; +} + +int ScriptInterpreter::o1_wilburParse() { + const ParserArray& parserArray = DATA(0, ParserArray); + RETURN(0); + return 1; +} + +int ScriptInterpreter::o1_wilburSpeech() { + const char *name = STRING(0); + int trigger = INTEGER(1); + int room = INTEGER(2); + int flag = INTEGER(3); + int volume = INTEGER(4); + int slot = INTEGER(5); + + printf("%s; %d; %d; %d; %d; %d\n", name, trigger, room, flag, volume, slot); + fflush(stdout); + //g_system->delayMillis(5000); + + KernelTriggerType oldTriggerMode = _vm->_kernel->triggerMode; + + // TODO + Common::String soundName = Common::String(name) + ".raw"; + _vm->_sound->playVoice(soundName.c_str(), 100); + + _vm->_kernel->triggerMode = oldTriggerMode; + + return 6; +} + +// Kernel vars + +void ScriptInterpreter::getKernelVar(int index, ScriptValue &value) { + + printf("ScriptInterpreter::getKernelVar() index = %d\n", index); + + if (index > _kernelVarsMax) { + printf("ScriptInterpreter::getKernelVar() Invalid kernel var index %d!\n", index); + return; + } + + printf("ScriptInterpreter::getKernelVar() name = %s\n", _kernelVars[index].desc); + + ScriptKernelVariable var = _kernelVars[index].var; + + switch (var) { + + case kKernelTrigger: + value = _vm->_kernel->trigger; + break; + + case kKernelTriggerMode: + value = (int)_vm->_kernel->triggerMode; + break; + + case kKernelContinueHandlingTrigger: + value = (int)_vm->_kernel->daemonTriggerAvailable; + break; + + case kGameVersion: + // TODO + value = 0; + break; + + case kGameLanguage: + // TODO + value = 0; + break; + + case kGameNewRoom: + // TODO + value = 0; + break; + + case kPlayerCommandReady: + value = (int)_vm->_player->commandReady; + break; + + default: + printf("ScriptInterpreter::getKernelVar() Invalid kernel var %d!\n", var); + //g_system->delayMillis(2000); + + } + +} + +void ScriptInterpreter::setKernelVar(int index, const ScriptValue &value) { + + printf("ScriptInterpreter::setKernelVar() index = %d\n", index); + + if (index > _kernelVarsMax) { + printf("ScriptInterpreter::setKernelVar() Invalid kernel var index %d!\n", index); + return; + } + + printf("ScriptInterpreter::setKernelVar() name = %s\n", _kernelVars[index].desc); + + ScriptKernelVariable var = _kernelVars[index].var; + + switch (var) { + + case kKernelTrigger: + _vm->_kernel->trigger = toInteger(value); + printf("kKernelTrigger -> %d\n", toInteger(value)); + break; + + case kKernelTriggerMode: + _vm->_kernel->triggerMode = (KernelTriggerType)toInteger(value); + printf("kKernelTrigger -> %d\n", toInteger(value)); + break; + + case kKernelContinueHandlingTrigger: + _vm->_kernel->daemonTriggerAvailable = (toInteger(value) != 0); + printf("kKernelContinueHandlingTrigger -> %d\n", toInteger(value)); + break; + + case kGameNewRoom: + _vm->_kernel->newRoom = toInteger(value); + printf("kGameNewRoom -> %d\n", toInteger(value)); + break; + + case kPlayerCommandReady: + // TODO + printf("kPlayerCommandReady -> %d\n", toInteger(value)); + break; + + default: + printf("ScriptInterpreter::setKernelVar() Invalid kernel var %d!\n", var); + //g_system->delayMillis(2000); + + } + +} + +} // End of namespace M4 diff --git a/engines/m4/script.h b/engines/m4/script.h new file mode 100644 index 0000000000..a59bf46e86 --- /dev/null +++ b/engines/m4/script.h @@ -0,0 +1,457 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef M4_SCRIPT_H +#define M4_SCRIPT_H + +#include "common/file.h" +#include "common/stream.h" +#include "common/hashmap.h" +#include "common/str.h" +#include "common/stack.h" + +#include "m4/woodscript.h" + +namespace M4 { + +const unsigned long kScriptFileMagic = 0x5845344D; +const unsigned long kScriptFileVersion = 1; + +enum ScriptValueType { + kInteger, + kConstString, + kLogicVar, + kLogicVarRef, + kGameVar, + kKernelVar, + kDataRef, + kRegister, + kStackVar +}; + +enum ScriptDataType { + kStreamBreakSeries, + kStreamPlaySeries, + kSaidArray, + kParserArray, + kSpeechArray, + kCreditsArray, + kInvObj, + kMineRoom, + kButtonItem +}; + +class ScriptInterpreter; + +class StringTable { +public: + StringTable(); + ~StringTable(); + void load(Common::File *fd); + int size() { return _strings.size(); } + const char *operator[](uint32 index) const { + assert(index < _strings.size() ); + return _strings[index]; + } +protected: + Common::Array<const char*> _strings; + char *_stringsData; +}; + +struct ScriptValue { + + ScriptValueType type; + + union { + int value; + }; + + ScriptValue() : type(kInteger), value(0) {}; + ScriptValue(ScriptValueType itype, int ivalue) : type(itype), value(ivalue) {}; + + ScriptValue(const int intValue) : type(kInteger), value(intValue) {}; + + ScriptValue& operator=(const int intValue) { + type = kInteger; + value = intValue; + return *this; + } + +}; + +class ScriptDataItem { +public: + ScriptDataItem() : _inter(NULL) {} + ScriptDataItem(ScriptInterpreter *inter) : _inter(inter) {} + virtual ~ScriptDataItem() {} + virtual void load(Common::File *fd) = 0; + static int type() { return -1; } +protected: + ScriptInterpreter *_inter; +}; + +class ScriptDataCache { +public: + ScriptDataCache(ScriptInterpreter *inter) : _inter(inter) { + } + ~ScriptDataCache() { + clear(); + } + template<class T> + T *load(Common::File *fd, uint32 ofs) { + T *item; + if (_cache.contains(ofs)) { + item = (T*)(_cache[ofs]); + } else { + item = new T(_inter); + fd->seek(ofs + 4); // "+4" skips the data size + item->load(fd); + _cache[ofs] = item; + } + return item; + } + void clear() { + // TODO: Free all cached items + } +protected: + typedef Common::HashMap<uint32, ScriptDataItem*> CacheMap; + CacheMap _cache; + ScriptInterpreter *_inter; +}; + +struct SeriesStreamBreakItem { + int frameNum; + const char *digiName; + int digiChannel; + int digiVolume; + int trigger; + int flags; + ScriptValue variable; + int value; +}; + +class SeriesStreamBreakList : public ScriptDataItem { +public: + SeriesStreamBreakList(ScriptInterpreter *inter) : ScriptDataItem(inter) {} + ~SeriesStreamBreakList(); + void load(Common::File *fd); + int size() const { return _items.size(); } + SeriesStreamBreakItem *operator[](int index) const { return _items[index]; } + static int type() { return 0; } +protected: + Common::Array<SeriesStreamBreakItem*> _items; +}; + +struct SaidArrayItem { + const char *itemName; + const char *digiNameLook; + const char *digiNameTake; + const char *digiNameGear; +}; + +class SaidArray : public ScriptDataItem { +public: + SaidArray(ScriptInterpreter *inter) : ScriptDataItem(inter) {} + ~SaidArray(); + void load(Common::File *fd); + int size() const { return _items.size(); } + SaidArrayItem *operator[](int index) const { return _items[index]; } + static int type() { return 2; } +protected: + Common::Array<SaidArrayItem*> _items; +}; + +struct ParserArrayItem { + const char *w0; + const char *w1; + int trigger; + ScriptValue testVariable; + int testValue; + ScriptValue variable; + int value; +}; + +class ParserArray : public ScriptDataItem { +public: + ParserArray(ScriptInterpreter *inter) : ScriptDataItem(inter) {} + ~ParserArray(); + void load(Common::File *fd); + int size() const { return _items.size(); } + ParserArrayItem *operator[](int index) const { return _items[index]; } + static int type() { return 3; } +protected: + Common::Array<ParserArrayItem*> _items; +}; + +class ScriptFunction { +public: + ScriptFunction(ScriptInterpreter *inter); + ~ScriptFunction(); + void load(Common::File *fd); + void jumpAbsolute(uint32 ofs); + void jumpRelative(int32 ofs); + byte readByte(); + uint32 readUint32(); +protected: + ScriptInterpreter *_inter; + Common::MemoryReadStream *_code; +}; + +struct ScriptFunctionEntry { + uint32 offset; + ScriptFunction *func; + ScriptFunctionEntry(uint32 funcOffset) : offset(funcOffset), func(NULL) { + } +}; + +struct ScriptDataEntry { + uint32 offset; + ScriptDataType type; + ScriptDataEntry(uint32 dataOffset, ScriptDataType dataType) : offset(dataOffset), type(dataType) { + } +}; + +enum ScriptKernelVariable { + kGameLanguage, + kGameVersion, + kGameCurrentRoom, + kGameNewRoom, + kGamePreviousRoom, + kGameNewSection, + kKernelTrigger, + kKernelTriggerMode, + kKernelFirstFade, + kKernelSuppressFadeUp, + kKernelContinueHandlingTrigger, + kKernelUseDebugMonitor, + kPlayerPosX, + kPlayerPosY, + kPlayerFacing, + kPlayerScale, + kPlayerDepth, + kPlayerWalkX, + kPlayerWalkY, + kPlayerReadyToWalk, + kPlayerNeedToWalk, + kPlayerCommandReady, + kPlayerWalkerInThisScene, + kPlayerVerb, + kWalkerInitialized, + kCallDaemonEveryLoop, + kConvCurrentTalker, + kConvCurrentNode, + kConvCurrentEntry, + kConvSoundToPlay, + kInterfaceVisible +}; + +class ScriptInterpreter { +public: + ScriptInterpreter(M4Engine *vm); + ~ScriptInterpreter(); + /* Opens a M4 program file */ + void open(const char *filename); + void close(); + /* Loads a function via the index. Creates the function object if it's not already loaded. */ + ScriptFunction *loadFunction(uint32 index); + /* Loads a function via the exported name. */ + ScriptFunction *loadFunction(const Common::String &name); + /* Unload all loaded functions. + This should be called before entering a new room to free unused functions. */ + void unloadFunctions(); + //TODO void unloadData(); + /* Executes a function. */ + int runFunction(ScriptFunction *scriptFunction); + + void push(const ScriptValue &value); + void pop(ScriptValue &value); + void dumpStack(); + void dumpRegisters(); + void dumpGlobalVars(); + + int toInteger(const ScriptValue &value); + + const char *toString(const ScriptValue &value); + + // Is this ok? + template<class T> + const T& toData(const ScriptValue &value) { + printf("ScriptInterpreter::toData() index = %d; type = %d; max = %d\n", value.value, _data[value.value]->type, _data.size()); + assert((uint32)value.value < _data.size()); + return *(_dataCache->load<T>(_scriptFile, _data[value.value]->offset)); + } + + const char *getGlobalString(int index) const { + return _constStrings[index]; + } + + const char *loadGlobalString(Common::File *fd); + + void test(); + +protected: + + M4Engine *_vm; + + typedef Common::HashMap<Common::String, uint32, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> FunctionNameMap; + Common::File *_scriptFile; + /* An array of offset/ScriptFunction* pairs for each script function */ + Common::Array<ScriptFunctionEntry*> _functions; + + // DEBUG only + Common::Array<Common::String> _scriptFunctionNames; + + Common::Array<ScriptDataEntry*> _data; + /* Maps function name -> index of function in _functions array */ + FunctionNameMap _functionNames; + StringTable _constStrings; + /* The currently running function */ + ScriptFunction *_runningFunction; + int _localStackPtr; + + ScriptValue _registers[8]; + + ScriptValue _stack[512]; + int _stackPtr; + + int _globalVarCount; + ScriptValue _globalVars[1024]; + + int _logicGlobals[512]; + + int _cmpFlags; + + ScriptDataCache *_dataCache; + + int _lineNum; + + typedef int (ScriptInterpreter::*KernelFunction)(); + struct KernelFunctionEntry { + KernelFunction proc; + const char *desc; + }; + const KernelFunctionEntry *_kernelFunctions; + uint16 _kernelFunctionsMax; + + struct KernelVariableEntry { + ScriptKernelVariable var; + const char *desc; + }; + const KernelVariableEntry *_kernelVars; + int16 _kernelVarsMax; + + void initScriptKernel(); + + void loadValue(ScriptValue &value); + void writeValue(ScriptValue &value); + void copyValue(ScriptValue &destValue, ScriptValue &sourceValue); + void derefValue(ScriptValue &value); + + void callKernelFunction(uint32 index); + ScriptValue getArg(uint32 index); + void dumpArgs(uint32 count); + + void callFunction(uint32 index); + + bool execOpcode(byte opcode); + + // Kernel functions + int o1_handleStreamBreak(); + int o1_handlePlayBreak(); + int o1_dispatchTriggerOnSoundState(); + int o1_getRangedRandomValue(); + int o1_getTicks(); + int o1_preloadSound(); + int o1_unloadSound(); + int o1_stopSound(); + int o1_playSound(); + int o1_playLoopingSound(); + int o1_setSoundVolume(); + int o1_getSoundStatus(); + int o1_getSoundDuration(); + int o1_loadSeries(); + int o1_unloadSeries(); + int o1_showSeries(); + int o1_playSeries(); + int o1_setSeriesFrameRate(); + int o1_playBreakSeries(); + int o1_preloadBreakSeries(); + int o1_unloadBreakSeries(); + int o1_startBreakSeries(); + int o1_dispatchTrigger(); + int o1_terminateMachine(); + int o1_sendWoodScriptMessage(); + int o1_runConversation(); + int o1_loadConversation(); + int o1_exportConversationValue(); + int o1_exportConversationPointer(); + int o1_fadeInit(); + int o1_fadeSetStart(); + int o1_fadeToBlack(); + int o1_initPaletteCycle(); + int o1_stopPaletteCycle(); + int o1_setHotspot(); + int o1_hideWalker(); + int o1_showWalker(); + int o1_setWalkerLocation(); + int o1_setWalkerFacing(); + int o1_walk(); + int o1_overrideCrunchTime(); + int o1_addBlockingRect(); + int o1_triggerTimerProc(); + int o1_setPlayerCommandsAllowed(); + int o1_getPlayerCommandsAllowed(); + int o1_updatePlayerInfo(); + int o1_hasPlayerSaid(); + int o1_hasPlayerSaidAny(); + int o1_playerHotspotWalkOverride(); + int o1_setPlayerFacingAngle(); + int o1_disablePlayerFadeToBlack(); + int o1_enablePlayer(); + int o1_disablePlayer(); + int o1_freshenSentence(); + int o1_playerHasItem(); + int o1_playerGiveItem(); + int o1_moveObject(); + int o1_setStopSoundsBetweenRooms(); + int o1_backupPalette(); + int o1_unloadWilburWalker(); + int o1_globalTriggerProc(); + int o1_wilburSpeech(); + int o1_wilburSaid(); + int o1_wilburParse(); + int o1_wilburTalk(); + int o1_wilburFinishedTalking(); + //int (); + + // Kernel vars + void getKernelVar(int index, ScriptValue &value); + void setKernelVar(int index, const ScriptValue &value); + +}; + +} // End of namespace M4 + +#endif diff --git a/engines/m4/scripttab.h b/engines/m4/scripttab.h new file mode 100644 index 0000000000..b6156f0b82 --- /dev/null +++ b/engines/m4/scripttab.h @@ -0,0 +1,136 @@ +/* This file has been autogenerated by the linker. + Do not edit it or merge it with script.cpp! */ +#define FUNCTION(x) { &ScriptInterpreter::x, #x } + static KernelFunctionEntry kernelFunctions[] = { + /* 000 */ + FUNCTION(o1_handleStreamBreak), + FUNCTION(o1_handlePlayBreak), + FUNCTION(o1_dispatchTriggerOnSoundState), + FUNCTION(o1_getRangedRandomValue), + /* 004 */ + FUNCTION(o1_getTicks), + FUNCTION(o1_preloadSound), + FUNCTION(o1_unloadSound), + FUNCTION(o1_stopSound), + /* 008 */ + FUNCTION(o1_playSound), + FUNCTION(o1_playLoopingSound), + FUNCTION(o1_setSoundVolume), + FUNCTION(o1_getSoundStatus), + /* 012 */ + FUNCTION(o1_getSoundDuration), + FUNCTION(o1_loadSeries), + FUNCTION(o1_unloadSeries), + FUNCTION(o1_showSeries), + /* 016 */ + FUNCTION(o1_playSeries), + FUNCTION(o1_setSeriesFrameRate), + FUNCTION(o1_playBreakSeries), + FUNCTION(o1_preloadBreakSeries), + /* 020 */ + FUNCTION(o1_unloadBreakSeries), + FUNCTION(o1_startBreakSeries), + FUNCTION(o1_dispatchTrigger), + FUNCTION(o1_terminateMachine), + /* 024 */ + FUNCTION(o1_sendWoodScriptMessage), + FUNCTION(o1_runConversation), + FUNCTION(o1_runConversation), + FUNCTION(o1_loadConversation), + /* 028 */ + FUNCTION(o1_exportConversationValue), + FUNCTION(o1_exportConversationPointer), + FUNCTION(o1_runConversation), + FUNCTION(o1_fadeInit), + /* 032 */ + FUNCTION(o1_fadeSetStart), + FUNCTION(o1_fadeToBlack), + FUNCTION(o1_initPaletteCycle), + FUNCTION(o1_stopPaletteCycle), + /* 036 */ + FUNCTION(o1_setHotspot), + FUNCTION(o1_hideWalker), + FUNCTION(o1_showWalker), + FUNCTION(o1_setWalkerLocation), + /* 040 */ + FUNCTION(o1_setWalkerFacing), + FUNCTION(o1_walk), + FUNCTION(o1_overrideCrunchTime), + FUNCTION(o1_addBlockingRect), + /* 044 */ + FUNCTION(o1_triggerTimerProc), + FUNCTION(o1_setPlayerCommandsAllowed), + FUNCTION(o1_getPlayerCommandsAllowed), + FUNCTION(o1_updatePlayerInfo), + /* 048 */ + FUNCTION(o1_hasPlayerSaid), + FUNCTION(o1_hasPlayerSaidAny), + FUNCTION(o1_playerHotspotWalkOverride), + FUNCTION(o1_setPlayerFacingAngle), + /* 052 */ + FUNCTION(o1_disablePlayerFadeToBlack), + FUNCTION(o1_enablePlayer), + FUNCTION(o1_disablePlayer), + FUNCTION(o1_freshenSentence), + /* 056 */ + FUNCTION(o1_playerHasItem), + FUNCTION(o1_playerGiveItem), + FUNCTION(o1_moveObject), + FUNCTION(o1_setStopSoundsBetweenRooms), + /* 060 */ + FUNCTION(o1_backupPalette), + FUNCTION(o1_unloadWilburWalker), + FUNCTION(o1_globalTriggerProc), + FUNCTION(o1_wilburSpeech), + /* 064 */ + FUNCTION(o1_wilburParse), + FUNCTION(o1_wilburSaid), + FUNCTION(o1_wilburTalk), + FUNCTION(o1_wilburFinishedTalking) + }; +#undef FUNCTION + +#define VARIABLE(x) { x, #x } + static KernelVariableEntry kernelVars[] = { + /* 000 */ + VARIABLE(kGameLanguage), + VARIABLE(kGameVersion), + VARIABLE(kGameCurrentRoom), + VARIABLE(kGameNewRoom), + /* 004 */ + VARIABLE(kGamePreviousRoom), + VARIABLE(kGameNewSection), + VARIABLE(kKernelTrigger), + VARIABLE(kKernelTriggerMode), + /* 008 */ + VARIABLE(kKernelFirstFade), + VARIABLE(kKernelSuppressFadeUp), + VARIABLE(kKernelContinueHandlingTrigger), + VARIABLE(kKernelUseDebugMonitor), + /* 012 */ + VARIABLE(kPlayerPosX), + VARIABLE(kPlayerPosY), + VARIABLE(kPlayerFacing), + VARIABLE(kPlayerScale), + /* 016 */ + VARIABLE(kPlayerDepth), + VARIABLE(kPlayerWalkX), + VARIABLE(kPlayerWalkY), + VARIABLE(kPlayerReadyToWalk), + /* 020 */ + VARIABLE(kPlayerNeedToWalk), + VARIABLE(kPlayerCommandReady), + VARIABLE(kPlayerWalkerInThisScene), + VARIABLE(kPlayerVerb), + /* 024 */ + VARIABLE(kWalkerInitialized), + VARIABLE(kCallDaemonEveryLoop), + VARIABLE(kConvCurrentTalker), + VARIABLE(kConvCurrentNode), + /* 028 */ + VARIABLE(kConvCurrentEntry), + VARIABLE(kConvSoundToPlay), + VARIABLE(kInterfaceVisible) + }; +#undef VARIABLE + diff --git a/engines/m4/sound.cpp b/engines/m4/sound.cpp new file mode 100644 index 0000000000..3091592313 --- /dev/null +++ b/engines/m4/sound.cpp @@ -0,0 +1,283 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "m4/m4.h" +#include "m4/sound.h" +#include "m4/compression.h" + +#include "sound/audiostream.h" +#include "sound/mixer.h" +#include "common/stream.h" + +namespace M4 { + +Sound::Sound(M4Engine *vm, Audio::Mixer *mixer, int volume) : + _vm(vm), _mixer(mixer) { + + for (int i = 0; i < SOUND_HANDLES; i++) + _handles[i].type = kFreeHandle; + + _dsrFileLoaded = false; + + setVolume(volume); +} + +Sound::~Sound() { + unloadDSRFile(); +} + +SndHandle *Sound::getHandle() { + for (int i = 0; i < SOUND_HANDLES; i++) { + if (_handles[i].type == kFreeHandle) + return &_handles[i]; + + if (!_mixer->isSoundHandleActive(_handles[i].handle)) { + _handles[i].type = kFreeHandle; + return &_handles[i]; + } + } + + error("Sound::getHandle(): Too many sound handles"); + + return NULL; +} + +bool Sound::isHandleActive(SndHandle *handle) { + return (_mixer->isSoundHandleActive(handle->handle)); +} + +void Sound::playSound(const char *soundName, int volume, bool loop, int channel) { + byte flags; + Common::SeekableReadStream *soundStream = _vm->res()->get(soundName); + SndHandle *handle; + if (channel < 0) { + handle = getHandle(); + } else { + if (_handles[channel].type == kFreeHandle) { + handle = &_handles[channel]; + } else { + warning("Attempted to play a sound on a channel that isn't free"); + return; + } + } + + int bufferSize = soundStream->size(); + byte *buffer = new byte[bufferSize]; + soundStream->read(buffer, bufferSize); + _vm->res()->toss(soundName); + + handle->type = kEffectHandle; + flags = Audio::Mixer::FLAG_AUTOFREE; + flags |= Audio::Mixer::FLAG_UNSIGNED; + + if (loop) + flags |= Audio::Mixer::FLAG_LOOP; + + _vm->res()->toss(soundName); + + // Sound format is 8bit mono, unsigned, 11025kHz + _mixer->playRaw(Audio::Mixer::kSFXSoundType, &handle->handle, buffer, bufferSize, 11025, flags, -1, volume); +} + +void Sound::pauseSound() { + for (int i = 0; i < SOUND_HANDLES; i++) { + if (_handles[i].type == kEffectHandle) + _mixer->pauseHandle(_handles[i].handle, true); + } +} + +void Sound::resumeSound() { + for (int i = 0; i < SOUND_HANDLES; i++) { + if (_handles[i].type == kEffectHandle) + _mixer->pauseHandle(_handles[i].handle, false); + } +} + +void Sound::stopSound(int channel) { + if (channel >= 0) { + if (_handles[channel].type == kEffectHandle) { + _mixer->stopHandle(_handles[channel].handle); + _handles[channel].type = kFreeHandle; + return; + } else { + warning("Attempted to stop a sound on a channel that is already free"); + return; + } + } + + for (int i = 0; i < SOUND_HANDLES; i++) { + if (_handles[i].type == kEffectHandle) { + _mixer->stopHandle(_handles[i].handle); + _handles[i].type = kFreeHandle; + } + } +} + +void Sound::playVoice(const char *soundName, int volume) { + byte flags; + Common::SeekableReadStream *soundStream = _vm->res()->get(soundName); + SndHandle *handle = getHandle(); + byte *buffer; + + buffer = new byte[soundStream->size()]; + soundStream->read(buffer, soundStream->size()); + + handle->type = kEffectHandle; + flags = Audio::Mixer::FLAG_AUTOFREE; + flags |= Audio::Mixer::FLAG_UNSIGNED; + + _vm->res()->toss(soundName); + + // Voice format is 8bit mono, unsigned, 11025kHz + _mixer->playRaw(Audio::Mixer::kSFXSoundType, &handle->handle, buffer, soundStream->size(), 11025, flags, -1, volume); +} + +void Sound::pauseVoice() { + for (int i = 0; i < SOUND_HANDLES; i++) + if (_handles[i].type == kVoiceHandle) + _mixer->pauseHandle(_handles[i].handle, true); +} + +void Sound::resumeVoice() { + for (int i = 0; i < SOUND_HANDLES; i++) + if (_handles[i].type == kVoiceHandle) + _mixer->pauseHandle(_handles[i].handle, false); +} + +void Sound::stopVoice() { + for (int i = 0; i < SOUND_HANDLES; i++) + if (_handles[i].type == kVoiceHandle) { + _mixer->stopHandle(_handles[i].handle); + _handles[i].type = kFreeHandle; + } +} + +void Sound::stopAll() { + stopVoice(); + stopSound(); +} + +void Sound::setVolume(int volume) { + _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, volume); + _mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volume); +} + +void Sound::loadDSRFile(const char *fileName) { + if (_dsrFileLoaded) + unloadDSRFile(); + + Common::SeekableReadStream *fileStream = _vm->res()->get(fileName); + + sprintf(_dsrFile.fileName, "%s", fileName); + + // Read header + _dsrFile.entryCount = fileStream->readUint16LE(); + //printf("DSR has %i entries\n", _dsrFile.entryCount); + + for (int i = 0; i < _dsrFile.entryCount; i++) { + DSREntry* newEntry = new DSREntry(); + newEntry->frequency = fileStream->readUint16LE(); + newEntry->channels = fileStream->readUint32LE(); + newEntry->compSize = fileStream->readUint32LE(); + newEntry->uncompSize = fileStream->readUint32LE(); + newEntry->offset = fileStream->readUint32LE(); + _dsrFile.dsrEntries.push_back(newEntry); + + /* + printf("%i: ", i); + printf("frequency: %i ", newEntry->frequency); + printf("channels: %i ", newEntry->channels); + printf("comp: %i ", newEntry->compSize); + printf("uncomp: %i ", newEntry->uncompSize); + printf("offset: %i ", newEntry->offset); + printf("\n"); + */ + } + + _vm->res()->toss(fileName); + + _dsrFileLoaded = true; +} + +void Sound::unloadDSRFile() { + if (!_dsrFileLoaded) + return; + + for (int i = 0; i < _dsrFile.entryCount; i++) { + _dsrFile.dsrEntries.remove_at(0); + } + + _dsrFile.entryCount = 0; + strcpy(_dsrFile.fileName, ""); + _dsrFileLoaded = false; +} + +void Sound::playDSRSound(int soundIndex, int volume, bool loop) { + if (!_dsrFileLoaded) { + warning("DSR file not loaded, not playing sound"); + return; + } + + if (soundIndex < 0 || soundIndex > _dsrFile.entryCount - 1) { + warning("Invalid sound index: %i, not playing sound", soundIndex); + return; + } + + byte flags; + SndHandle *handle = getHandle(); + + handle->type = kEffectHandle; + flags = Audio::Mixer::FLAG_AUTOFREE; + flags |= Audio::Mixer::FLAG_UNSIGNED; + + if (loop) + flags |= Audio::Mixer::FLAG_LOOP; + + // Get sound data + FabDecompressor fab; + byte *compData = new byte[_dsrFile.dsrEntries[soundIndex]->compSize]; + byte *buffer = new byte[_dsrFile.dsrEntries[soundIndex]->uncompSize]; + Common::SeekableReadStream *fileStream = _vm->res()->get(_dsrFile.fileName); + fileStream->seek(_dsrFile.dsrEntries[soundIndex]->offset, SEEK_SET); + fileStream->read(compData, _dsrFile.dsrEntries[soundIndex]->compSize); + _vm->res()->toss(_dsrFile.fileName); + + fab.decompress(compData, _dsrFile.dsrEntries[soundIndex]->compSize, + buffer, _dsrFile.dsrEntries[soundIndex]->uncompSize); + + // Play sound + _mixer->playRaw(Audio::Mixer::kSFXSoundType, &handle->handle, buffer, + _dsrFile.dsrEntries[soundIndex]->uncompSize, + _dsrFile.dsrEntries[soundIndex]->frequency, flags, -1, volume); + + /* + // Dump the sound file + FILE *destFile = fopen("sound.raw", "wb"); + fwrite(_dsrFile.dsrEntries[soundIndex]->data, _dsrFile.dsrEntries[soundIndex]->uncompSize, 1, destFile); + fclose(destFile); + */ +} + +} // End of namespace M4 diff --git a/engines/m4/sound.h b/engines/m4/sound.h new file mode 100644 index 0000000000..2dae502551 --- /dev/null +++ b/engines/m4/sound.h @@ -0,0 +1,112 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +// Sound class + +#ifndef M4_SOUND_H +#define M4_SOUND_H + +#include "common/file.h" +#include "common/array.h" +#include "sound/mixer.h" +#include "sound/mp3.h" +#include "sound/vorbis.h" +#include "sound/flac.h" + +namespace M4 { + +#define SOUND_HANDLES 10 + +enum SOUND_FLAGS { + SOUND_LOOP = 1 +}; + +enum sndHandleType { + kFreeHandle, + kEffectHandle, + kVoiceHandle +}; + +struct SndHandle { + Audio::SoundHandle handle; + sndHandleType type; +}; + +struct DSREntry { + int16 frequency; + int channels; + int32 compSize; + int32 uncompSize; + int32 offset; +}; + +struct DSRFile { + char fileName[20]; + int entryCount; + Common::Array<DSREntry *> dsrEntries; +}; + +class M4Engine; + +class Sound { +public: + + Sound(M4Engine *vm, Audio::Mixer *mixer, int volume); + ~Sound(); + + void playSound(const char *soundName, int volume, bool loop, int channel = -1); + void pauseSound(); + void resumeSound(); + void stopSound(int channel = -1); + + void playVoice(const char *soundName, int volume); + void pauseVoice(); + void resumeVoice(); + void stopVoice(); + + void stopAll(); + + void setVolume(int volume); + + bool isHandleActive(SndHandle *handle); + SndHandle *getHandle(); + + void loadDSRFile(const char *fileName); + void unloadDSRFile(); + void playDSRSound(int soundIndex, int volume, bool loop); + + private: + + M4Engine *_vm; + Audio::Mixer *_mixer; + SndHandle _handles[SOUND_HANDLES]; + + DSRFile _dsrFile; + bool _dsrFileLoaded; +}; + +} // End of namespace M4 + +#endif diff --git a/engines/m4/sprite.cpp b/engines/m4/sprite.cpp new file mode 100644 index 0000000000..3862435216 --- /dev/null +++ b/engines/m4/sprite.cpp @@ -0,0 +1,174 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "common/rect.h" + +#include "m4/globals.h" +#include "m4/graphics.h" +#include "m4/m4.h" +#include "m4/resource.h" +#include "m4/sprite.h" + +namespace M4 { + +enum { + kEndOfLine = 0, + kEndOfSprite = 1, + kMarker = 2 +}; + +M4Sprite::M4Sprite(Common::SeekableReadStream* source, int xOfs, int yOfs, int widthVal, int heightVal, bool decodeRle, uint8 encodingVal) + : M4Surface(widthVal, heightVal), encoding(encodingVal) { + + if (_vm->isM4()) { + if (decodeRle) { + loadRle(source); + } else { + // Raw sprite data, load directly + byte *dst = getData(); + source->read(dst, widthVal * heightVal); + } + } else { + loadMadsSprite(source); + } + + xOffset = xOfs; + yOffset = yOfs; + +} + +void M4Sprite::loadRle(Common::SeekableReadStream* rleData) { + byte *dst = getData(); + while (1) { + byte len = rleData->readByte(); + if (len == 0) { + len = rleData->readByte(); + if (len <= kMarker) { + if (len == kEndOfSprite) + break; + } else { + while (len--) { + *dst++ = rleData->readByte(); + } + } + } else { + byte value = rleData->readByte(); + while (len--) + *dst++ = value; + } + } +} + +void M4Sprite::loadDeltaRle(Common::SeekableReadStream* rleData, int destX, int destY) { + int lineNum = 0; + byte *dst = getBasePtr(destX, destY); + while (1) { + byte len = rleData->readByte(); + if (len == 0) { + len = rleData->readByte(); + if (len <= kMarker) { + if (len == kEndOfLine) { + dst = getBasePtr(destX, destY + lineNum); + lineNum++; + } else if (len == kEndOfSprite) + break; + } else { + while (len--) { + byte pixel = rleData->readByte(); + if (pixel == 0) + dst++; + else + *dst++ = pixel; + /* NOTE: The change below behaved differently than the old code, + so I put the old code back in again above. + If the pixel value is 0, nothing should be written to the + output buffer, since 0 means transparent. */ + //*dst++ = (pixel == 0xFD) ? 0 : pixel; + } + } + } else { + byte value = rleData->readByte(); + if (value == 0) + dst += len; + else + while (len--) + *dst++ = value; + } + } +} + +// TODO: The sprite outlines (pixel value 0xFD) are not shown +void M4Sprite::loadMadsSprite(Common::SeekableReadStream* source) { + byte *outp, *lineStart; + bool newLine = false; + + outp = getData(); + lineStart = getData(); + + while (1) { + byte cmd1, cmd2, count, pixel; + + if (newLine) { + outp = lineStart + w; + lineStart = outp; + newLine = false; + } + + cmd1 = source->readByte(); + + if (cmd1 == 0xFC) + break; + else if (cmd1 == 0xFF) + newLine = true; + else if (cmd1 == 0xFD) { + while (!newLine) { + count = source->readByte(); + if (count == 0xFF) { + newLine = true; + } else { + pixel = source->readByte(); + while (count--) + *outp++ = (pixel == 0xFD) ? 0 : pixel; + } + } + } else { + while (!newLine) { + cmd2 = source->readByte(); + if (cmd2 == 0xFF) { + newLine = true; + } else if (cmd2 == 0xFE) { + count = source->readByte(); + pixel = source->readByte(); + while (count--) + *outp++ = (pixel == 0xFD) ? 0 : pixel; + } else { + *outp++ = (cmd2 == 0xFD) ? 0 : cmd2; + } + } + } + } +} + +} // End of namespace M4 diff --git a/engines/m4/sprite.h b/engines/m4/sprite.h new file mode 100644 index 0000000000..5caeaa8664 --- /dev/null +++ b/engines/m4/sprite.h @@ -0,0 +1,122 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef M4_SPRITE_H +#define M4_SPRITE_H + +#include "common/util.h" +#include "common/endian.h" +#include "common/stream.h" +#include "m4/graphics.h" + +/* + TODO: + - change DrawRequestX and RendCell +*/ + +namespace M4 { + +typedef struct +{ + int32 x; // x position relative to GrBuff(0, 0) + int32 y; // y position relative to GrBuff(0, 0) + int32 scale_x; // x scale factor (can be negative for reverse draw) + int32 scale_y; // y scale factor (can't be negative) + uint8* depth_map; // depth code array for destination (doesn't care if srcDepth is 0) + BGR8 *Pal; // palette for shadow draw (doesn't care if SHADOW bit is not set in Src.encoding) + uint8* ICT; // Inverse Color Table (doesn't care if SHADOW bit is not set in Src.encoding) + uint8 depth; // depth code for source (0 if no depth processing) +} DrawRequestX; + +typedef struct +{ + uint32 Pack; + uint32 Stream; + long hot_x; + long hot_y; + uint32 Width; + uint32 Height; + uint32 Comp; + uint32 Reserved[8]; + uint8* data; +} RendCell; + +#define SS_HEADER_NUM_FIELDS 14 +struct SpriteSeriesHeader { + uint32 header; + uint32 size; + uint32 packing; + uint32 frameRate; + uint32 pixSpeed; + uint32 maxWidth; + uint32 maxHeight; + uint32 reserved3; + uint32 reserved4; + uint32 reserved5; + uint32 reserved6; + uint32 reserved7; + uint32 reserved8; + uint32 count; +}; + +#define SF_HEADER_NUM_FIELDS 15 +struct SpriteFrameHeader { + uint32 pack; + uint32 stream; + uint32 x; + uint32 y; + uint32 width; + uint32 height; + uint32 comp; + uint32 reserved1; + uint32 reserved2; + uint32 reserved3; + uint32 reserved4; + uint32 reserved5; + uint32 reserved6; + uint32 reserved7; + uint32 reserved8; +}; + +class M4Sprite: public M4Surface { +public: + int x, y; + int xOffset, yOffset; + uint8 encoding; + + M4Sprite(M4Engine *vm): M4Surface() {} + M4Sprite(M4Engine *vm, int widthVal, int heightVal): M4Surface(widthVal, heightVal), xOffset(0), yOffset(0) {} + // Loads a sprite from the given stream, and optionally decompresses the RLE-encoded data + M4Sprite(Common::SeekableReadStream* source, int xOfs, int yOfs, int widthVal, int heightVal, bool decodeRle = true, uint8 encodingVal = 0); + // Loads an RLE compressed sprite; the surface must have been created before + void loadRle(Common::SeekableReadStream* rleData); + void loadDeltaRle(Common::SeekableReadStream* rleData, int destX, int destY); + void loadMadsSprite(Common::SeekableReadStream* source); +protected: +}; + +} // End of namespace M4 + +#endif diff --git a/engines/m4/viewmgr.cpp b/engines/m4/viewmgr.cpp new file mode 100644 index 0000000000..3a8b5d24a8 --- /dev/null +++ b/engines/m4/viewmgr.cpp @@ -0,0 +1,436 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +// TODO: Views have a _coords rect, so I'm not sure if x/y is needed in the onRefresh + +#include "m4/m4.h" +#include "m4/viewmgr.h" +#include "m4/mads_anim.h" + +namespace M4 { + +void returnToMainMenuFn(M4Engine *vm) { + vm->_palette->resetColorCounts(); + vm->_palette->setMadsSystemPalette(); + + vm->loadMenu(MAIN_MENU); +} + +RectList::RectList() { +} + +RectList::~RectList() { +} + +void RectList::addRect(int x1, int y1, int x2, int y2) { + addRect(Common::Rect(x1, y1, x2, y2)); +} + +void RectList::addRect(const Common::Rect &rect) { + /* TODO: + Implement the following: + - Don't add the Rect if it's contained in any Rect in the list + - Split up the Rect if it intersects any Rect in the list + and add the resulting partial Rects instead + */ + push_back(rect); +} + +//-------------------------------------------------------------------------- + +HotkeyList::HotkeyList(View *owner) { + _view = owner; +} + +HotkeyList::~HotkeyList() { + for (uint32 i = 0; i < _hotkeys.size(); i++) + delete _hotkeys[i]; +} + +void HotkeyList::add(uint32 key, Hotkey::Callback callback) { + _hotkeys.push_back(new Hotkey(key, callback)); +} + +void HotkeyList::remove(uint32 key) { + for (uint32 i = 0; i < _hotkeys.size(); i++) { + if (_hotkeys[i]->key == key) { + delete _hotkeys[i]; + _hotkeys.remove_at(i); + break; + } + } +} + +bool HotkeyList::call(uint32 key) { + for (uint32 i = 0; i < _hotkeys.size(); i++) { + if (_hotkeys[i]->key == key) { + if (_hotkeys[i]->callback) + (_hotkeys[i]->callback)(_vm, _view, key); + return true; + } + } + return false; +} + +//-------------------------------------------------------------------------- + +// View constructor + +View::View(M4Engine *vm, const Common::Rect &viewBounds, bool transparent): + _hotkeys(HotkeyList(this)), M4Surface(viewBounds.width(), viewBounds.height()), _vm(vm) { + SCREEN_FLAGS_DEFAULT; + _coords = viewBounds; + _transparent = transparent; +} + +View::View(M4Engine *vm, int x, int y, bool transparent): _hotkeys(HotkeyList(this)), M4Surface(), _vm(vm) { + SCREEN_FLAGS_DEFAULT; + _coords.left = x; + _coords.top = y; + _coords.right = _vm->_screen->width(); + _coords.bottom = _vm->_screen->height(); + _transparent = transparent; +} + +void View::getCoordinates(Common::Rect &rect) { + rect = _coords; +} + +void View::extract(int *status) { +} + +void View::show() { + _screenFlags.visible = true; + _vm->_viewManager->moveToFront(this); + _vm->_viewManager->restore(_coords); +} + +void View::hide() { + _screenFlags.visible = false; + _vm->_viewManager->restore(_coords); +} + +void View::moveToBack() { + _vm->_viewManager->moveToBack(this); +} + +void View::moveAbsolute(int x, int y) { + // TODO: Handle clipping and offscreen + Common::Rect oldCoords = _coords; + _coords.moveTo(x, y); + _vm->_viewManager->restore(oldCoords); + _vm->_viewManager->restore(_coords); +} + +void View::moveRelative(int x, int y) { + // TODO: Handle clipping and offscreen + Common::Rect oldCoords = _coords; + _coords.translate(x, y); + _vm->_viewManager->restore(oldCoords); + _vm->_viewManager->restore(_coords); +} + +void View::resize(int newWidth, int newHeight) { + Common::Rect oldCoords = _coords; + if (newWidth >= 0) + _coords.setWidth(newWidth); + if (newHeight >= 0) + _coords.setHeight(newHeight); + _vm->_viewManager->restore(oldCoords); + _vm->_viewManager->restore(_coords); +} + +void View::restore(int x1, int y1, int x2, int y2) { + _vm->_viewManager->restore(_coords.left + x1, _coords.top + y1, _coords.left + x2, _coords.top + y2); +} + +void View::onRefresh(RectList *rects, M4Surface *destSurface) { + assert(destSurface); + + if (rects == NULL) + // No rect list specified, so copy entire surface + copyTo(destSurface, _coords.left, _coords.top, _transparent ? 0 : -1); + else { + // Loop through the set of specified rectangles + RectList::iterator i; + for (i = rects->begin(); i != rects->end(); ++i) { + Common::Rect &destRect = *i; + Common::Rect srcBounds(destRect.left - _coords.left, destRect.top - _coords.top, + destRect.right - _coords.left, destRect.bottom - _coords.top); + copyTo(destSurface, srcBounds, destRect.left, destRect.top, _transparent ? 0 : -1); + } + } +} + +//-------------------------------------------------------------------------- + +ViewManager::ViewManager(M4Engine *vm): _systemHotkeys(HotkeyList(NULL)), _vm(vm) { + _captureScreen = NULL; + _captureEvents = false; +} + +ViewManager::~ViewManager() { + // Delete any remaining active views + ListIterator i; + for (i = _views.begin(); i != _views.end(); ++i) + delete (*i); +} + +void ViewManager::addView(View *view) { + _views.push_back(view); + moveToFront(view); +} + +// Warning: After calling this method, the passed view object will no longer be valid + +void ViewManager::deleteView(View *view) { + _views.remove(view); + delete view; +} + +void ViewManager::handleEvents(const Common::Event &event) { +} + +void ViewManager::handleKeyboardEvents(uint32 keycode) { + Common::Point mousePos = _vm->_mouse->currentPos(); + View *view; + bool blockedFlag; + bool foundFlag; + bool handledFlag; + + // Scan view list for one which accepts or blocks keyboard events. If one is found, + // then the event is passed to it + + view = NULL; + handledFlag = false; + foundFlag = false; + blockedFlag = false; + + // Loop from the front to back view + ListIterator i; + for (i = _views.reverse_begin(); (i != _views.end()) && !foundFlag; --i) { + view = *i; + if (!view->isVisible()) continue; + + if (view->screenFlags().blocks & SCREVENT_KEY) + blockedFlag = true; + if (view->screenFlags().get & SCREVENT_KEY) { + foundFlag = true; + handledFlag = (view->onEvent)(KEVENT_KEY, keycode, mousePos.x, mousePos.y, _captureEvents); + } + } + + // Scan view list for one with a hotkey list, aborting if a view is found that either + // blocks keyboard events, or has a hotkey list that includes the keycode + + blockedFlag = false; + for (i = _views.reverse_begin(); (i != _views.end()) && !foundFlag && !blockedFlag; --i) { + view = *i; + if (!view->isVisible()) continue; + + if (view->screenFlags().blocks & SCREVENT_KEY) + blockedFlag = true; + if (view->screenFlags().get & SCREVENT_KEY) { + if (view->hotkeys().call(keycode)) { + handledFlag = true; + _captureEvents = false; + //_vm->_dialogs->keyMouseCollision(); // TODO + } + } + } + + // Final check: if no view handled or blocked the key, check against the system hotkey list + + if (!handledFlag && !blockedFlag) { + handledFlag = _systemHotkeys.call(keycode); + if (handledFlag) { + _captureEvents = false; + //_vm->_dialogs->keyMouseCollision(); // TODO + } + } +} + +void ViewManager::handleMouseEvents(M4EventType event) { + Common::Point mousePos = _vm->_mouse->currentPos(); + ListIterator i; + View *view; + bool blockedFlag; + bool foundFlag; + + // If a window sets the _captureEvents flag to true, it will receive all events until + // it sets it to false, even if it's not the top window + if (_captureEvents) { + if (_captureScreen->screenFlags().get & SCREVENT_MOUSE) + (_captureScreen->onEvent)(event, 0, mousePos.x, mousePos.y, _captureEvents); + + } else { + blockedFlag = false; + foundFlag = false; + view = NULL; + + // Loop from the front to back view + for (i = _views.reverse_begin(); (i != _views.end()) && !foundFlag && !blockedFlag; --i) { + view = *i; + if (!view->isVisible()) continue; + + if (view->screenFlags().blocks & SCREVENT_MOUSE) + blockedFlag = true; + if ((view->screenFlags().get & SCREVENT_MOUSE) && view->isInside(mousePos.x, mousePos.y)) + foundFlag = true; + } + + if (foundFlag) + view->onEvent(event, 0, mousePos.x, mousePos.y, _captureEvents); + else + _captureEvents = false; + if (_captureEvents) + _captureScreen = view; + } +} + +void ViewManager::restore(int x1, int y1, int x2, int y2) { + RectList *rl = new RectList(); + Common::Rect redrawBounds(x1, y1, x2, y2); + rl->addRect(x1, y1, x2, y2); + + for (ListIterator i = _views.begin(); i != _views.end(); ++i) { + View *v = *i; + + if (v->isVisible() && v->bounds().intersects(redrawBounds)) + v->onRefresh(rl, _vm->_screen); + } + + _vm->_screen->update(); + +} + +void ViewManager::restore(const Common::Rect &rect) { + restore(rect.left, rect.top, rect.right, rect.bottom); +} + +void ViewManager::moveToFront(View *view) { + if (_views.size() < 2) + return; + + _views.remove(view); + + ListIterator i = _views.begin(); + while ((i != _views.end()) && ((*i)->layer() <= view->layer())) + ++i; + + _views.insert(i, view); +} + +void ViewManager::moveToBack(View *view) { + if (_views.size() < 2) + return; + + _views.remove(view); + + ListIterator i = _views.begin(); + while ((i != _views.end()) && ((*i)->layer() < view->layer())) + ++i; + + _views.insert(i, view); +} + +View *ViewManager::getView(int screenType) { + ListIterator i = _views.begin(); + while (i != _views.end()) { + if ((*i)->screenType() == screenType) + return *i; + ++i; + } + + return NULL; +} + +void ViewManager::updateState() { + Common::List<View *> viewList = _views; + + for (ListIterator i = viewList.begin(); i != viewList.end(); ++i) { + if (_vm->_events->quitFlag) + return; + + View *v = *i; + v->updateState(); + } +} + +void ViewManager::refreshAll() { + _vm->_screen->empty(); + + for (ListIterator i = _views.begin(); i != _views.end(); ++i) { + View *v = *i; + + if (v->isVisible()) + v->onRefresh(NULL, _vm->_screen); + } + + _vm->_screen->update(); +} + +void ViewManager::showTextView(const char *textViewName, bool returnToMainMenu) { + // Deactivate the scene if it's currently active + View *view = _vm->_viewManager->getView(VIEWID_SCENE); + if (view != NULL) + _vm->_viewManager->deleteView(view); + + // Deactivate the main menu if it's currently active + view = _vm->_viewManager->getView(VIEWID_MAINMENU); + if (view != NULL) + _vm->_viewManager->deleteView(view); + + // Activate the textview view + _vm->_font->setFont(FONT_CONVERSATION_MADS); + TextviewView *textView = new TextviewView(_vm); + _vm->_viewManager->addView(textView); + if (returnToMainMenu) + textView->setScript(textViewName, returnToMainMenuFn); + else + textView->setScript(textViewName, NULL); +} + +void ViewManager::showAnimView(const char *animViewName, bool returnToMainMenu) { + // Deactivate the scene if it's currently active + View *view = _vm->_viewManager->getView(VIEWID_SCENE); + if (view != NULL) + _vm->_viewManager->deleteView(view); + + // Deactivate the main menu if it's currently active + view = _vm->_viewManager->getView(VIEWID_MAINMENU); + if (view != NULL) + _vm->_viewManager->deleteView(view); + + // Activate the animview view + AnimviewView *animView = new AnimviewView(_vm); + _vm->_viewManager->addView(animView); + if (returnToMainMenu) + animView->setScript(animViewName, returnToMainMenuFn); + else + animView->setScript(animViewName, NULL); +} + +} // End of namespace M4 diff --git a/engines/m4/viewmgr.h b/engines/m4/viewmgr.h new file mode 100644 index 0000000000..148fb565da --- /dev/null +++ b/engines/m4/viewmgr.h @@ -0,0 +1,191 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef M4_VIEWMGR_H +#define M4_VIEWMGR_H + +#include "common/array.h" +#include "common/list.h" +#include "common/events.h" +#include "common/rect.h" + +#include "m4/globals.h" +#include "m4/events.h" +#include "m4/graphics.h" + +namespace M4 { + +class View; +class ViewManager; + +enum {SCREEN_DIALOG, SCREEN_BUFFER, SCREEN_TEXT, SCREEN_TRANSPARENT}; +enum ScreenEventType {SCREVENT_NONE = 0, SCREVENT_KEY = 1, SCREVENT_MOUSE = 2, SCREVENT_ALL = 3}; +enum ScreenLayers { + LAYER_BACKGROUND = 0, LAYER_DRIFTER = 1, LAYER_INTERFACE = 1, LAYER_FLOATER = 2, + LAYER_SURFACE = 3, LAYER_MENU = 9, LAYER_MOUSE = 15 +}; + +enum ViewIds { + VIEWID_MAINMENU = 1, + VIEWID_SCENE = 2, + VIEWID_TEXTVIEW = 3, + VIEWID_ANIMVIEW = 4, + VIEWID_MENU = 69, + VIEWID_CONVERSATION = 48, + VIEWID_INTERFACE = 49 +}; + +struct ScreenFlags { + bool visible:1; + bool transparent:1; + bool immovable:1; + + enum ScreenEventType blocks:2; + enum ScreenEventType get:2; + + uint layer:4; +}; + +#define SCREEN_FLAGS_DEFAULT _screenFlags.layer = LAYER_DRIFTER; \ + _screenFlags.get = SCREVENT_ALL; _screenFlags.blocks = SCREVENT_NONE; \ + _screenFlags.visible = true; +#define SCREEN_FLAGS_ALERT _screenFlags.layer = LAYER_FLOATER \ + _screenFlags.get = SCREVENT_ALL; _screenFlags.blocks = SCREVENT_ALL; \ + _screenFlags.visible = true; + +class RectList: public Common::Array<Common::Rect> { +public: + RectList(); + ~RectList(); + void addRect(int x1, int y1, int x2, int y2); + void addRect(const Common::Rect &rect); + +// Common::Rect& operator [](int idx) { return _rects[idx]; } +}; + +struct Hotkey { +public: + typedef void (*Callback)(M4Engine *vm, View *view, uint32 key); + Hotkey(uint32 keyVal, Hotkey::Callback callbackFn) : key(keyVal), callback(callbackFn) {}; + uint32 key; + Hotkey::Callback callback; +}; + +class HotkeyList { +public: + HotkeyList(View *owner); + ~HotkeyList(); + void add(uint32 key, Hotkey::Callback callback); + void remove(uint32 key); + bool call(uint32 key); +private: + Common::Array<Hotkey*> _hotkeys; + View *_view; +}; + +class View: public M4Surface { +public: + View(M4Engine *vm, const Common::Rect &viewBounds, bool transparent = false); + View(M4Engine *vm, int x = 0, int y = 0, bool transparent = false); + virtual ~View() {} + + void getCoordinates(Common::Rect &rect); + void extract(int *status); + virtual void show(); + virtual void hide(); + void moveToFront() {} + void moveToBack(); + void moveAbsolute(int x, int y); + void moveRelative(int x, int y); + void resize(int newWidth, int newHeight); + void restore(int x1, int y1, int x2, int y2); + + Common::Rect bounds() const { return _coords; } + bool isInside(int x, int y) const { return _coords.contains(x, y); } + ScreenFlags screenFlags() const { return _screenFlags; } + int screenType() const { return _screenType; } + bool isOffscreen() const { return !_screenFlags.visible; } + bool isTransparent() const { return _screenFlags.transparent; } + bool isVisible() const { return _screenFlags.visible; } + uint layer() const { return _screenFlags.layer; } + HotkeyList &hotkeys() { return _hotkeys; } + + virtual void onRefresh(RectList *rects, M4Surface *destSurface); + virtual bool onEvent(M4EventType eventType, int param, int x, int y, bool &captureEvents) { return false; } + virtual void updateState() {}; + +protected: + M4Engine *_vm; + Common::Rect _coords; + HotkeyList _hotkeys; + int _screenType; + ScreenFlags _screenFlags; + bool _transparent; +}; + +class ViewManager { +private: + M4Engine *_vm; + HotkeyList _systemHotkeys; + Common::List<View *> _views; + View *_captureScreen; + bool _captureEvents; +public: + typedef Common::List<View *>::iterator ListIterator; + + ViewManager(M4Engine *vm); + ~ViewManager(); + + void addView(View *view); + void deleteView(View *view); + + void handleEvents(const Common::Event &event); + void handleKeyboardEvents(uint32 keycode); + void handleMouseEvents(M4EventType event); + void restore(int x1, int y1, int x2, int y2); + void restore(const Common::Rect &rect); + + void moveToFront(View *view); + void moveToBack(View *view); + + Common::List<View *> views() const { return _views; } + bool contains(View *key) const { + return find(_views.begin(), _views.end(), key) != _views.end(); + } + bool contains(int screenType) { return getView(screenType) != NULL; } + View *getView(int screenType); + int viewCount() { return _views.size(); } + + void showTextView(const char *textViewName, bool returnToMainMenu = true); + void showAnimView(const char *animViewName, bool returnToMainMenu = true); + + void updateState(); + void refreshAll(); + HotkeyList &systemHotkeys() { return _systemHotkeys; } +}; + +} + +#endif diff --git a/engines/m4/woodscript.cpp b/engines/m4/woodscript.cpp new file mode 100644 index 0000000000..15d86a407e --- /dev/null +++ b/engines/m4/woodscript.cpp @@ -0,0 +1,398 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "m4/woodscript.h" + +namespace M4 { + +// FIXME: Put in Engine/WoodScript class +RGB8 _mainPalette[256]; + +//Woodscript Assembler/Compiler + +int32 Bytecode::_dataFormats[] = {0, 5, 8, 12, 16}; + +Bytecode::Bytecode(WoodScript *ws, byte *code, int32 codeSize, Sequence *seq) { + _ws = ws; + _code = new Common::MemoryReadStream(code, codeSize); + _sequence = seq; +} + +Bytecode::~Bytecode() { + delete _code; +} + +int Bytecode::loadInstruction(Instruction &instruction) { + + //printf("Bytecode::loadInstruction() ip = %08X\n", _code->pos()); + + int32 format, data; + uint32 code, code2; + + code = _code->readUint32LE(); + + instruction.instr = (code >> 25) & 0xFF; + instruction.argp[0] = NULL; + instruction.argp[1] = NULL; + instruction.argp[2] = NULL; + instruction.argc = 0; + + // Maybe make this a for-loop? + + format = (code >> 22) & 7; + if (format) { + /* Load argument 1 */ + data = code & 0xFFFF; + decodeArgument(format, data, instruction.argp[0], instruction.argv[0]); + instruction.argc++; + /* Load argument 2 */ + format = (code >> 19) & 7; + if (format) { + code2 = _code->readUint32LE(); + data = (code2 >> 16) & 0xFFFF; + decodeArgument(format, data, instruction.argp[1], instruction.argv[1]); + instruction.argc++; + /* Load argument 3 */ + format = (code >> 16) & 7; + if (format) { + data = code2 & 0xFFFF; + decodeArgument(format, data, instruction.argp[2], instruction.argv[2]); + instruction.argc++; + } + } + } + + return 0; //FIXME check if instruction size is needed by caller + +} + +void Bytecode::jumpAbsolute(int32 ofs) { + _code->seek(ofs * 4); + //printf("Bytecode::jumpAbsolute() ofs = %08X\n", _code->pos()); +} + +void Bytecode::jumpRelative(int32 ofs) { + _code->seek(ofs * 4, SEEK_CUR); +} + +void Bytecode::setSequence(Sequence *seq) { + _sequence = seq; +} + +void Bytecode::setCode(byte *code, int32 codeSize) { + delete _code; + _code = new Common::MemoryReadStream(code, codeSize); +} + +Sequence *Bytecode::sequence() const { + assert(_sequence); + return _sequence; +} + +bool Bytecode::decodeArgument(int32 format, int32 data, long *&arg, long &value) { + + int32 index; + + if (format == 1) { + if (data & 0x8000) + index = _sequence->indexReg(); + else + index = data & 0x0FFF; + switch (data & 0x7000) { + case 0x0000: + arg = sequence()->getParentVarPtr(index); + value = *arg; + break; + case 0x1000: + arg = sequence()->getVarPtr(index); + value = *arg; + break; + case 0x2000: + arg = sequence()->getDataPtr(index); + value = *arg; + break; + } + } else if (format == 2) { + if (data & 0x8000) + index = _sequence->indexReg(); + else + index = data & 0x0FFF; + arg = _ws->getGlobalPtr(index); + value = *arg; + } else { + if (data & 0x8000) { + value = -(data & 0x7FFF) << (_dataFormats[format - 3]); + } else { + value = (data & 0x7FFF) << (_dataFormats[format - 3]); + } + arg = &value; + } + + return true; +} + +WoodScript::WoodScript(M4Engine *vm) { + _vm = vm; + _machineId = 0; + _assets = new AssetManager(vm); + _globals = new long[256]; //FIXME Find out how many globals there should be + memset(_globals, 0, sizeof(long)); + + _backgroundSurface = NULL; + + Common::Rect viewBounds = Common::Rect(0, 0, 640, 480); + //_surfaceView = new View(viewBounds); +} + +WoodScript::~WoodScript() { + delete _assets; + delete[] _globals; +} + +Sequence *WoodScript::createSequence(Machine *machine, int32 sequenceHash) { + Sequence *sequence = new Sequence(this, machine, sequenceHash); + _sequences.push_back(sequence); + _layers.push_back(sequence); + return sequence; +} + +void WoodScript::runSequencePrograms() { + // A lot TODO + for (Common::Array<Sequence*>::iterator it = _sequences.begin(); it != _sequences.end(); it++) { + Sequence *sequence = *it; + if (sequence->isActive()) { + sequence->runProgram(); + if (sequence->isTerminated() && sequence->hasEndOfSequenceRequestPending()) { + _endOfSequenceRequestList.push_back(sequence); + } + } + } +} + +void WoodScript::runEndOfSequenceRequests() { +} + +void WoodScript::runTimerSequenceRequests() { +} + +Machine *WoodScript::createMachine(int32 machineHash, Sequence *parentSeq, + int32 dataHash, int32 dataRowIndex, int callbackHandler, const char *machineName) { + + //printf("WoodScript::createMachine(%d)\n", machineHash); fflush(stdout); + + Machine *machine = new Machine(this, machineHash, parentSeq, dataHash, dataRowIndex, callbackHandler, machineName, _machineId); + _machineId++; + + _machines.push_back(machine); + + // goto first state for initialization + machine->enterState(); + + return machine; +} + +int32 WoodScript::loadSeries(const char* seriesName, int32 hash, RGB8* palette) { + return _assets->addSpriteAsset(seriesName, hash, palette); +} + +void WoodScript::unloadSeries(int32 hash) { + _assets->clearAssets(kAssetTypeCELS, hash, hash); +} + +void WoodScript::setSeriesFramerate(Machine *machine, int32 frameRate) { +} + +Machine *WoodScript::playSeries(const char *seriesName, long layer, uint32 flags, int32 triggerNum, + int32 frameRate, int32 loopCount, int32 s, int32 x, int32 y, + int32 firstFrame, int32 lastFrame) { + + //printf("WoodScript::playSeries(%s)\n", seriesName); + + RGB8 *palette = NULL; + if (flags & SERIES_LOAD_PALETTE) + palette = &_mainPalette[0]; + + int32 spriteHash = _assets->addSpriteAsset(seriesName, -1, palette); + + _globals[kGlobTemp1] = (long)spriteHash << 24; + _globals[kGlobTemp2] = layer << 16; + _globals[kGlobTemp3] = _vm->_kernel->createTrigger(triggerNum); + _globals[kGlobTemp4] = frameRate << 16; + _globals[kGlobTemp5] = loopCount << 16; + _globals[kGlobTemp6] = (s << 16) / 100; + _globals[kGlobTemp7] = x << 16; + _globals[kGlobTemp8] = y << 16; + _globals[kGlobTemp9] = firstFrame << 16; + _globals[kGlobTemp10] = lastFrame << 16; + _globals[kGlobTemp11] = (flags & SERIES_PINGPONG) ? 0x10000 : 0; + _globals[kGlobTemp12] = (flags & SERIES_BACKWARD) ? 0x10000 : 0; + _globals[kGlobTemp13] = (flags & SERIES_RANDOM) ? 0x10000 : 0; + _globals[kGlobTemp14] = (flags & SERIES_STICK) ? 0x10000 : 0; + _globals[kGlobTemp15] = (flags & SERIES_LOOP_TRIGGER) ? 0x10000 : 0; + _globals[kGlobTemp16] = (flags & SERIES_HORZ_FLIP) ? 0x10000 : 0; + + return createMachine(0, NULL, -1, -1, kCallbackTriggerDispatch, seriesName); + +} + +Machine *WoodScript::showSeries(const char *seriesName, long layer, uint32 flags, int32 triggerNum, + int32 duration, int32 index, int32 s, int32 x, int32 y) { + + RGB8 *palette = NULL; + if (flags & SERIES_LOAD_PALETTE) + palette = &_mainPalette[0]; + + int32 spriteHash = _assets->addSpriteAsset(seriesName, -1, palette); + + _globals[kGlobTemp1] = spriteHash << 24; + _globals[kGlobTemp2] = layer << 16; + _globals[kGlobTemp3] = _vm->_kernel->createTrigger(triggerNum); + _globals[kGlobTemp4] = duration << 16; + _globals[kGlobTemp5] = index << 16; + _globals[kGlobTemp6] = (s << 16) / 100; + _globals[kGlobTemp7] = x << 16; + _globals[kGlobTemp8] = y << 16; + _globals[kGlobTemp14] = (flags & SERIES_STICK) ? 0x10000 : 0; + _globals[kGlobTemp16] = (flags & SERIES_HORZ_FLIP) ? 0x10000 : 0; + + return createMachine(1, NULL, -1, -1, kCallbackTriggerDispatch, seriesName); + +} + +Machine *WoodScript::streamSeries(const char *seriesName, int32 frameRate, long layer, int32 triggerNum) { + //printf("WoodScript::streamSeries(%s)\n", seriesName); + _globals[kGlobTemp1] = frameRate << 16; + /* FIXME: Single frames from a stream series will be decompressed on-the-fly, contrary to + "normal" sprite series, to save some memory, and since no random access to single + frames is needed, this is ok. + */ + _globals[kGlobTemp4] = 0; // The actual stream is opened in the Sequence + _globals[kGlobTemp5] = 0;//TODO: kernel_trigger_create(triggerNum); // trigger + _globals[kGlobTemp6] = layer << 16; // layer + return createMachine(6, NULL, -1, -1, kCallbackTriggerDispatch, seriesName); +} + +void WoodScript::update() { + // TODO: Don't show hidden sequences etc. + + // TODO: For now, prevent any engine action if a menu is being displayed - eventually this should be + // changed to a proper check of the engine paused variable, which the menus should set while active + if (_vm->_viewManager->getView(VIEWID_MENU) != NULL) + return; + + //TODO: Include _pauseTime + uint32 clockTime = g_system->getMillis() / 60; // FIXME: g_system + _globals[kGlobTimeDelta] = clockTime - _globals[kGlobTime]; + _globals[kGlobTime] += _globals[kGlobTimeDelta]; + + runSequencePrograms(); + + if (_backgroundSurface) { + // FIXME: For now, copy the whole surface. Later, copy only the rectangles that need updating. + _backgroundSurface->copyTo(_surfaceView); + } else { + // "This should never happen." + _surfaceView->fillRect(Common::Rect(0, 0, 640, 480), 0); + } + + { + // FIXME: This should be done when a new palette is set + byte palette[1024]; + g_system->grabPalette(palette, 0, 256); + for (int i = 0; i < 256; i++) { + _mainPalette[i].r = palette[i * 4 + 0]; + _mainPalette[i].g = palette[i * 4 + 1]; + _mainPalette[i].b = palette[i * 4 + 2]; + } + } + + for (Common::Array<Sequence*>::iterator it = _layers.begin(); it != _layers.end(); it++) { + Sequence *sequence = *it; + + // TODO: Use correct clipRect etc. + Common::Rect clipRect = Common::Rect(0, 0, 640, 480); + Common::Rect updateRect; + + sequence->draw(_surfaceView, clipRect, updateRect); + + } + + // Handle end-of-sequence requests + if (_endOfSequenceRequestList.size() > 0) { + for (Common::Array<Sequence*>::iterator it = _endOfSequenceRequestList.begin(); it != _endOfSequenceRequestList.end(); it++) { + Sequence *sequence = *it; + + EndOfSequenceRequestItem endOfSequenceRequestItem = sequence->getEndOfSequenceRequestItem(); + sequence->getMachine()->execBlock(endOfSequenceRequestItem.codeOffset, endOfSequenceRequestItem.count); + } + _endOfSequenceRequestList.clear(); + } + +} + +void WoodScript::clear() { + + for (Common::Array<Sequence*>::iterator it = _sequences.begin(); it != _sequences.end(); it++) + delete *it; + _sequences.clear(); + + for (Common::Array<Machine*>::iterator it = _machines.begin(); it != _machines.end(); it++) + delete *it; + _machines.clear(); + + _layers.clear(); + _endOfSequenceRequestList.clear(); + +} + +void WoodScript::setDepthTable(int16 *depthTable) { + _depthTable = depthTable; +} + +long *WoodScript::getGlobalPtr(int index) { + return &_globals[index]; +} + +long WoodScript::getGlobal(int index) { + return _globals[index]; +} + +void WoodScript::setGlobal(int index, long value) { + _globals[index] = value; +} + +void WoodScript::setBackgroundSurface(M4Surface *backgroundSurface) { + _backgroundSurface = backgroundSurface; +} + +void WoodScript::setSurfaceView(View *view) { + _surfaceView = view; +} + +RGB8 *WoodScript::getMainPalette() const { + return _mainPalette; +} + +} diff --git a/engines/m4/woodscript.h b/engines/m4/woodscript.h new file mode 100644 index 0000000000..7e968742b8 --- /dev/null +++ b/engines/m4/woodscript.h @@ -0,0 +1,351 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef M4_WOODSCRIPT_H +#define M4_WOODSCRIPT_H + +#include "common/scummsys.h" +#include "common/util.h" +#include "common/str.h" +#include "common/array.h" +#include "common/stream.h" +#include "graphics/surface.h" + +#include "m4/globals.h" +#include "m4/assets.h" +#include "m4/resource.h" +#include "m4/sprite.h" +#include "m4/m4.h" +#include "m4/graphics.h" +#include "m4/viewmgr.h" + +namespace M4 { + +class M4Engine; +class WoodScript; +class Machine; +class Sequence; +class AssetManager; +class View; + +struct Instruction { + int32 instr; + long *argp[3]; + long argv[3]; + int argc; + // Helper method; many opcode functions can get either a defined value or a random value + long getValue() { + if (argc == 3) + return _vm->imath_ranged_rand16(argv[1], argv[2]); + else + return argv[1]; + } +}; + +class Bytecode { +public: + Bytecode(WoodScript *ws, byte *code, int32 codeSize, Sequence *seq); + ~Bytecode(); + int loadInstruction(Instruction &instruction); + void jumpAbsolute(int32 ofs); + void jumpRelative(int32 ofs); + void setSequence(Sequence *sequence); + void setCode(byte *code, int32 codeSize); + Sequence *sequence() const; + uint32 pos() const { return _code->pos() / 4; } +protected: + WoodScript *_ws; + Common::MemoryReadStream *_code; + Sequence *_sequence; + static int32 _dataFormats[]; + bool decodeArgument(int32 format, int32 data, long *&arg, long &value); +}; + +struct EndOfSequenceRequestItem { + int32 codeOffset, count; + EndOfSequenceRequestItem() : codeOffset(-1) {} + bool isValid() const { return codeOffset >= 0; } +}; + +typedef Common::Array<EndOfSequenceRequestItem> EndOfSequenceRequestList; + +class Sequence { +public: + Sequence(WoodScript *ws, Machine *machine, int32 sequenceHash); + ~Sequence(); + + void pause(); + void resume(); + void issueEndOfSequenceRequest(int32 codeOffset, int32 count); + void cancelEndOfSequenceRequest(); + + bool runProgram(); + + bool changeProgram(int32 sequenceHash); + + void clearVars(); + + long *getVarPtr(int index); + long *getParentVarPtr(int index); + long *getDataPtr(int index); + + void setActive(bool active) { _active = active; } + bool isActive() const { return _active; } + + bool isTerminated() const { return _terminated; } + + void draw(M4Surface *surface, const Common::Rect &clipRect, Common::Rect &updateRect); + + bool s1_end(Instruction &instruction); + bool s1_clearVars(Instruction &instruction); + bool s1_set(Instruction &instruction); + bool s1_compare(Instruction &instruction); + bool s1_add(Instruction &instruction); + bool s1_sub(Instruction &instruction); + bool s1_mul(Instruction &instruction); + bool s1_div(Instruction &instruction); + bool s1_and(Instruction &instruction); + bool s1_or(Instruction &instruction); + bool s1_not(Instruction &instruction); + bool s1_sin(Instruction &instruction); + bool s1_cos(Instruction &instruction); + bool s1_abs(Instruction &instruction); + bool s1_min(Instruction &instruction); + bool s1_max(Instruction &instruction); + bool s1_mod(Instruction &instruction); + bool s1_floor(Instruction &instruction); + bool s1_round(Instruction &instruction); + bool s1_ceil(Instruction &instruction); + bool s1_point(Instruction &instruction); + bool s1_dist2d(Instruction &instruction); + bool s1_crunch(Instruction &instruction); + bool s1_branch(Instruction &instruction); + bool s1_setFrame(Instruction &instruction); + bool s1_sendMessage(Instruction &instruction); + bool s1_push(Instruction &instruction); + bool s1_pop(Instruction &instruction); + bool s1_jumpSub(Instruction &instruction); + bool s1_return(Instruction &instruction); + bool s1_getFrameCount(Instruction &instruction); + bool s1_getFrameRate(Instruction &instruction); + bool s1_getCelsPixSpeed(Instruction &instruction); + bool s1_setIndex(Instruction &instruction); + bool s1_setLayer(Instruction &instruction); + bool s1_setDepth(Instruction &instruction); + bool s1_setData(Instruction &instruction); + bool s1_openStream(Instruction &instruction); + bool s1_streamNextFrame(Instruction &instruction); + bool s1_closeStream(Instruction &instruction); + + int32 indexReg() const { return _indexReg; } + + EndOfSequenceRequestItem getEndOfSequenceRequestItem() const { return _endOfSequenceRequest; } + bool hasEndOfSequenceRequestPending() const { return _endOfSequenceRequest.isValid(); } + void resetEndOfSequenceRequest() { _endOfSequenceRequest.codeOffset = -1; } + + Machine *getMachine() const { return _machine; } + + +protected: + WoodScript *_ws; + Bytecode *_code; + + long *_vars; + bool _active, _terminated; + Machine *_machine; + Sequence *_parentSequence; + int32 _layer; + int32 _startTime, _switchTime; + long *_dataRow; + int32 _localVarCount; + int32 _cmpFlags; + + EndOfSequenceRequestItem _endOfSequenceRequest; + + int32 _indexReg; + + M4Sprite *_curFrame; + + int32 _sequenceHash; + + int32 _returnHashes[8]; //FIXME: Use constant instead of 8 + uint32 _returnOffsets[8]; + int32 _returnStackIndex; + + Common::SeekableReadStream *_stream; + SpriteAsset *_streamSpriteAsset; + + bool streamOpen(); + bool streamNextFrame(); + void streamClose(); + +}; + +class Machine { +public: + Machine(WoodScript *ws, int32 machineHash, Sequence *parentSeq, int32 dataHash, + int32 dataRowIndex, int callbackHandler, Common::String machineName, int32 id); + ~Machine(); + + void clearMessages(); + void clearPersistentMessages(); + void restorePersistentMessages(); + void sendMessage(uint32 messageHash, long messageValue, Machine *sender); + void resetSwitchTime(); + bool changeSequenceProgram(int32 sequenceHash); + + bool searchMessages(uint32 messageHash, uint32 messageValue, Machine *sender); + bool searchPersistentMessages(uint32 messageHash, uint32 messageValue, Machine *sender); + + void enterState(); + int32 execInstruction(); + void execBlock(int32 offset, int32 count); + int32 getState() { return _currentState; } + + int32 getId() const { return _id; } + + bool m1_gotoState(Instruction &instruction); + bool m1_jump(Instruction &instruction); + bool m1_terminate(Instruction &instruction); + bool m1_startSequence(Instruction &instruction); + bool m1_pauseSequence(Instruction &instruction); + bool m1_resumeSequence(Instruction &instruction); + bool m1_storeValue(Instruction &instruction); + bool m1_sendMessage(Instruction &instruction); + bool m1_broadcastMessage(Instruction &instruction); + bool m1_replyMessage(Instruction &instruction); + bool m1_sendSystemMessage(Instruction &instruction); + bool m1_createMachine(Instruction &instruction); + bool m1_createMachineEx(Instruction &instruction); + bool m1_clearVars(Instruction &instruction); + + void m1_onEndSequence(Instruction &instruction); + void m1_onMessage(Instruction &instruction); + void m1_switchLt(Instruction &instruction); + void m1_switchLe(Instruction &instruction); + void m1_switchEq(Instruction &instruction); + void m1_switchNe(Instruction &instruction); + void m1_switchGe(Instruction &instruction); + void m1_switchGt(Instruction &instruction); + + long *dataRow() const { return _dataRow; } + Sequence *parentSequence() const { return _parentSequence; } + Common::String name() const { return _name; } + +protected: + WoodScript *_ws; + Bytecode *_code; + + Common::String _name; + Sequence *_sequence, *_parentSequence; + byte *_mach; + int32 _machHash, _machineCodeOffset; + int32 _stateCount, _stateTableOffset; + long *_dataRow; + int32 _id, _recursionLevel, _currentState, _targetCount; + /* TODO: + m->msgReplyXM = NULL; + m->CintrMsg = CintrMsg; + _walkPath + _messages + _persistentMessages + _usedPersistentMessages + */ +}; + +class WoodScript { +public: + + WoodScript(M4Engine *vm); + ~WoodScript(); + + Machine *createMachine(int32 machineHash, Sequence *parentSeq, int32 dataHash, int32 dataRowIndex, int callbackHandler, const char *machineName); + Sequence *createSequence(Machine *machine, int32 sequenceHash); + + void runSequencePrograms(); + void runEndOfSequenceRequests(); + void runTimerSequenceRequests(); + + /* Series */ + // Move to own class, e.g. SeriesPlayer + int32 loadSeries(const char* seriesName, int32 hash, RGB8* palette); + void unloadSeries(int32 hash); + void setSeriesFramerate(Machine *machine, int32 frameRate); + Machine *playSeries(const char *seriesName, long layer, uint32 flags, int32 triggerNum, + int32 frameRate, int32 loopCount, int32 s, int32 x, int32 y, + int32 firstFrame, int32 lastFrame); + Machine *showSeries(const char *seriesName, long layer, uint32 flags, int32 triggerNum, + int32 duration, int32 index, int32 s, int32 x, int32 y); + Machine *streamSeries(const char *seriesName, int32 frameRate, long layer, int32 triggerNum); + + void update(); + void clear(); + + /* Misc */ + void setDepthTable(int16 *depthTable); + + long *getGlobalPtr(int index); + long getGlobal(int index); + void setGlobal(int index, long value); + + AssetManager *assets() const { return _assets; } + + // Sets the untouched, clean surface which contains the room background + void setBackgroundSurface(M4Surface *backgroundSurface); + // Sets the view which is used for drawing + void setSurfaceView(View *view); + + RGB8 *getMainPalette() const; + + void setInverseColorTable(byte *inverseColorTable) { _inverseColorTable = inverseColorTable; } + byte *getInverseColorTable() const { return _inverseColorTable; } + +protected: + M4Engine *_vm; + AssetManager *_assets; + + Common::Array<Sequence*> _sequences, _layers; + Common::Array<Machine*> _machines; + int32 _machineId; + + long *_globals; + + Common::Array<Sequence*> _endOfSequenceRequestList; + + int32 _indexReg; + + /* Misc */ + int16 *_depthTable; + byte *_inverseColorTable; + M4Surface *_backgroundSurface; + View *_surfaceView; + +}; + + +} // End of namespace M4 + + +#endif diff --git a/engines/m4/ws_machine.cpp b/engines/m4/ws_machine.cpp new file mode 100644 index 0000000000..ece76fe5ab --- /dev/null +++ b/engines/m4/ws_machine.cpp @@ -0,0 +1,422 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "m4/woodscript.h" + +namespace M4 { + +bool (Machine::*machineCommandsTable[])(Instruction &instruction) = { + NULL, + NULL,//TODO: nop + &Machine::m1_gotoState, + &Machine::m1_jump, + &Machine::m1_terminate, + &Machine::m1_startSequence, + &Machine::m1_pauseSequence, + &Machine::m1_resumeSequence, + &Machine::m1_storeValue, + &Machine::m1_sendMessage, + &Machine::m1_broadcastMessage, + &Machine::m1_replyMessage, + &Machine::m1_sendSystemMessage, + &Machine::m1_createMachine, + &Machine::m1_createMachineEx, + &Machine::m1_clearVars +}; + +void (Machine::*machineConditionalsTable[])(Instruction &instruction) = { + NULL,//TODO: after + &Machine::m1_onEndSequence, + &Machine::m1_onMessage, + NULL,//TODO: on_p_msg + &Machine::m1_switchLt, + &Machine::m1_switchLe, + &Machine::m1_switchEq, + &Machine::m1_switchNe, + &Machine::m1_switchGe, + &Machine::m1_switchGt, +}; + +Machine::Machine(WoodScript *ws, int32 machineHash, Sequence *parentSeq, int32 dataHash, + int32 dataRowIndex, int callbackHandler, Common::String machineName, int32 id) { + + _ws = ws; + + _machHash = machineHash; + _name = machineName; + _id = id; + + // initialize the machine's bytecode + MachineAsset *machineAsset = _ws->assets()->getMachine(_machHash); + byte *code; + uint32 codeSize; + machineAsset->getCode(code, codeSize); + _code = new Bytecode(_ws, code, codeSize, NULL); + + // initialize the machine's data + if (dataHash >= 0) { + DataAsset *dataAsset = _ws->assets()->getData(dataHash); + _dataRow = dataAsset->getRow(dataRowIndex); + } else { + _dataRow = NULL; + } + + _recursionLevel = 0; + _currentState = 0; + _sequence = NULL; + _parentSequence = parentSeq; + _targetCount = 0; + +} + +Machine::~Machine() { + delete _code; +} + +void Machine::clearMessages() { +} + +void Machine::clearPersistentMessages() { +} + +void Machine::restorePersistentMessages() { +} + +void Machine::sendMessage(uint32 messageHash, long messageValue, Machine *sender) { +} + +void Machine::resetSwitchTime() { +} + +bool Machine::changeSequenceProgram(int32 sequenceHash) { + return _sequence->changeProgram(sequenceHash); +} + +bool Machine::searchMessages(uint32 messageHash, uint32 messageValue, Machine *sender) { + return false; +} + +bool Machine::searchPersistentMessages(uint32 messageHash, uint32 messageValue, Machine *sender) { + return false; +} + +void Machine::enterState() { + + MachineAsset *machineAsset = _ws->assets()->getMachine(_machHash); + + _code->jumpAbsolute(machineAsset->getStateOffset(_currentState)); + + int32 instruction = -1; + + _recursionLevel++; + + int32 oldId = _id; + int32 oldRecursionLevel = _recursionLevel; + + while (instruction && instruction != 4 && _id == oldId && _recursionLevel == oldRecursionLevel) { + instruction = execInstruction(); + } + + if (instruction != 4 && _id == oldId && _recursionLevel == oldRecursionLevel) { + _recursionLevel--; + } + +} + +int32 Machine::execInstruction() { + + //printf("Machine::execInstruction()\n"); fflush(stdout); + + bool done = false; + Instruction instruction; + //Sequence *sequence; + int32 machID = _id; + + _code->loadInstruction(instruction); + + if (instruction.instr >= 64) { + if (machineConditionalsTable[instruction.instr - 64] != NULL) + (this->*machineConditionalsTable[instruction.instr - 64])(instruction); + /* The next line is to yield on unimplemented opcodes */ + else { fflush(stdout); g_system->delayMillis(5000); } + } else if (instruction.instr > 0) { + if (machineCommandsTable[instruction.instr] != NULL) + done = !(this->*machineCommandsTable[instruction.instr])(instruction); + /* The next line is to yield on unimplemented opcodes */ + else { fflush(stdout); g_system->delayMillis(5000); } + if (done) { + if (_id == machID) { + //TODO: Cancel all requests + if (_currentState == -1) { + // TODO: Set terminated flag and delete machine in WoodScript update + } + else { + // initialize new state + enterState(); + } + } + } + } + + return instruction.instr; + +} + +void Machine::execBlock(int32 offset, int32 count) { + + // MachineAsset *machineAsset = _ws->assets()->getMachine(_machHash); + + int32 startOffset = offset, endOffset = offset + count; + + _recursionLevel++; + + int32 oldId = _id; + int32 oldRecursionLevel = _recursionLevel; + + _code->jumpAbsolute(offset); + + int32 instruction = -1; + + //printf("---------------------------------------\n"); fflush(stdout); + + while (instruction && instruction != 4 && _id == oldId && _recursionLevel == oldRecursionLevel && + _code->pos() >= (uint32)startOffset && _code->pos() < (uint32)endOffset) { + + instruction = execInstruction(); + //g_system->delayMillis(500); + } + + //printf("---------------------------------------\n"); fflush(stdout); + + if (instruction == 3) { + execInstruction(); + } + + if (instruction != 4 && _id == oldId && _recursionLevel == oldRecursionLevel) { + _recursionLevel--; + } + +} + +bool Machine::m1_gotoState(Instruction &instruction) { + //printf("Machine::m1_gotoState() state = %d\n", (int32)instruction.argv[0] >> 16); + + _currentState = (int32)instruction.argv[0] >> 16; + _recursionLevel = 0; + return false; +} + +bool Machine::m1_jump(Instruction &instruction) { + //printf("Machine::m1_jump() ofs = %08X\n", (int32)instruction.argv[0] >> 16); + + _code->jumpRelative((int32)instruction.argv[0] >> 16); + return true; +} + +bool Machine::m1_terminate(Instruction &instruction) { + //printf("Machine::m1_terminate()\n"); fflush(stdout); + + _currentState = -1; + _recursionLevel = 0; + return false; +} + +bool Machine::m1_startSequence(Instruction &instruction) { + //printf("Machine::m1_startSequence() sequence hash = %d\n", (uint32)instruction.argv[0] >> 16); fflush(stdout); + + int32 sequenceHash = instruction.argv[0] >> 16; + if (_sequence == NULL) { + //printf("Machine::m1_startSequence() creating new sequence\n"); + _sequence = _ws->createSequence(this, sequenceHash); + _code->setSequence(_sequence); + } else { + //printf("Machine::m1_startSequence() using existing sequence\n"); + _sequence->changeProgram(sequenceHash); + //_code->setSequence(_sequence); + } + return true; +} + +bool Machine::m1_pauseSequence(Instruction &instruction) { + //printf("Machine::m1_pauseSequence()\n"); fflush(stdout); + + _sequence->pause(); + return true; +} + +bool Machine::m1_resumeSequence(Instruction &instruction) { + //printf("Machine::m1_resumeSequence()\n"); fflush(stdout); + + _sequence->resume(); + return true; +} + +bool Machine::m1_storeValue(Instruction &instruction) { + //printf("Machine::m1_storeValue() %p = %d (%08X)\n", (void*)instruction.argp[0], (uint32)instruction.argv[1], (uint32)instruction.argv[1]); + + *instruction.argp[0] = instruction.getValue(); + return true; +} + +bool Machine::m1_sendMessage(Instruction &instruction) { + //printf("Machine::m1_sendMessage() %p = %d (%08X)\n", (void*)instruction.argp[0], (uint32)instruction.argv[1], (uint32)instruction.argv[1]); + +#if 0 +//TODO + long messageValue; + + if (instruction.argc == 3) { + messageValue = instruction.argv[2]; + } else { + messageValue = 0; + } + //_ws->sendMessage((uint32)instruction.argv[1], messageValue, (uint32)instruction.argv[0] >> 16); + //void SendWSMessage(uint32 msgHash, long msgValue, machine *recvM, uint32 machHash, machine *sendM, int32 msgCount) { +#endif + return true; + +} + +bool Machine::m1_broadcastMessage(Instruction &instruction) { + //printf("Machine::m1_broadcastMessage() %p = %d (%08X)\n", (void*)instruction.argp[0], (uint32)instruction.argv[1], (uint32)instruction.argv[1]); + +#if 0 +//TODO + long messageValue; + + if (instruction.argc == 3) { + messageValue = instruction.argv[2]; + } else { + messageValue = 0; + } + //_ws->sendMessage((uint32)instruction.argv[1], messageValue, (uint32)instruction.argv[0] >> 16); +#endif + return true; +} + +bool Machine::m1_replyMessage(Instruction &instruction) { + //printf("Machine::m1_replyMessage() messageHash = %d; messageValue = %d\n", (uint32)instruction.argv[0], (uint32)instruction.argv[1]); +#if 0 + if (myArg2) { + msgValue = *myArg2; + } + else { + msgValue = 0; + } + SendWSMessage(*myArg1, msgValue, m->msgReplyXM, 0, m, 1); +#endif + return true; +} + +bool Machine::m1_sendSystemMessage(Instruction &instruction) { + //printf("Machine::m1_sendSystemMessage() messageValue = %d\n", (uint32)instruction.argv[0]); +#if 0 +#endif + return true; +} + +bool Machine::m1_createMachine(Instruction &instruction) { + //printf("Machine::m1_createMachine()\n"); +#if 0 +#endif + return true; +} + +bool Machine::m1_createMachineEx(Instruction &instruction) { + //printf("Machine::m1_createMachineEx()\n"); +#if 0 +#endif + return true; +} + +bool Machine::m1_clearVars(Instruction &instruction) { + //printf("Machine::m1_clearVars()\n"); fflush(stdout); + + _sequence->clearVars(); + return true; +} + + +void Machine::m1_onEndSequence(Instruction &instruction) { + //printf("Machine::m1_onEndSequence() count = %08X\n", (uint32)instruction.argv[0] >> 16); fflush(stdout); + + int32 count = instruction.argv[0] >> 16; + _sequence->issueEndOfSequenceRequest(_code->pos(), count); + _code->jumpRelative(count); +} + +void Machine::m1_onMessage(Instruction &instruction) { + //printf("Machine::m1_onEndSequence() count = %08X\n", (uint32)instruction.argv[0] >> 16); fflush(stdout); + + // TODO: Add message to list + + int32 count = instruction.argv[0] >> 16; + _code->jumpRelative(count); + +} + +void Machine::m1_switchLt(Instruction &instruction) { + //printf("Machine::m1_switchLt() %d < %d -> %08X\n", (uint32)instruction.argv[1], (uint32)instruction.argv[2], (uint32)instruction.argv[0] >> 16); fflush(stdout); + + if (instruction.argv[1] >= instruction.argv[2]) + _code->jumpRelative(instruction.argv[0] >> 16); +} + +void Machine::m1_switchLe(Instruction &instruction) { + //printf("Machine::m1_switchLe() %d <= %d -> %08X\n", (uint32)instruction.argv[1], (uint32)instruction.argv[2], (uint32)instruction.argv[0] >> 16); fflush(stdout); + + if (instruction.argv[1] > instruction.argv[2]) + _code->jumpRelative(instruction.argv[0] >> 16); +} + +void Machine::m1_switchEq(Instruction &instruction) { + //printf("Machine::m1_switchEq() %d == %d -> %08X\n", (uint32)instruction.argv[1], (uint32)instruction.argv[2], (uint32)instruction.argv[0] >> 16); fflush(stdout); + + if (instruction.argv[1] != instruction.argv[2]) + _code->jumpRelative(instruction.argv[0] >> 16); +} + +void Machine::m1_switchNe(Instruction &instruction) { + //printf("Machine::m1_switchNe() %d != %d -> %08X\n", (uint32)instruction.argv[1], (uint32)instruction.argv[2], (uint32)instruction.argv[0] >> 16); fflush(stdout); + + if (instruction.argv[1] == instruction.argv[2]) + _code->jumpRelative(instruction.argv[0] >> 16); +} + +void Machine::m1_switchGe(Instruction &instruction) { + //printf("Machine::m1_switchGe() %d >= %d -> %08X\n", (uint32)instruction.argv[1], (uint32)instruction.argv[2], (uint32)instruction.argv[0] >> 16); fflush(stdout); + + if (instruction.argv[1] < instruction.argv[2]) + _code->jumpRelative(instruction.argv[0] >> 16); +} + +void Machine::m1_switchGt(Instruction &instruction) { + //printf("Machine::m1_switchGt() %d > %d -> %08X\n", (uint32)instruction.argv[1], (uint32)instruction.argv[2], (uint32)instruction.argv[0] >> 16); fflush(stdout); + + if (instruction.argv[1] <= instruction.argv[2]) + _code->jumpRelative(instruction.argv[0] >> 16); +} + +} diff --git a/engines/m4/ws_sequence.cpp b/engines/m4/ws_sequence.cpp new file mode 100644 index 0000000000..918979ad17 --- /dev/null +++ b/engines/m4/ws_sequence.cpp @@ -0,0 +1,764 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "m4/woodscript.h" + +namespace M4 { + +long sinCosTable[320] = { + 0, 1608, 3215, 4821, 6423, 8022, 9616, 11204, + 12785, 14359, 15923, 17479, 19024, 20557, 22078, 23586, + 25079, 26557, 28020, 29465, 30893, 32302, 33692, 35061, + 36409, 37736, 39039, 40319, 41575, 42806, 44011, 45189, + 46340, 47464, 48558, 49624, 50660, 51665, 52639, 53581, + 54491, 55368, 56212, 57022, 57797, 58538, 59243, 59913, + 60547, 61144, 61705, 62228, 62714, 63162, 63571, 63943, + 64276, 64571, 64826, 65043, 65220, 65358, 65457, 65516, + 65536, 65516, 65457, 65358, 65220, 65043, 64826, 64571, + 64276, 63943, 63571, 63162, 62714, 62228, 61705, 61144, + 60547, 59913, 59243, 58538, 57797, 57022, 56212, 55368, + 54491, 53581, 52639, 51665, 50660, 49624, 48558, 47464, + 46340, 45189, 44011, 42806, 41575, 40319, 39039, 37736, + 36409, 35061, 33692, 32302, 30893, 29465, 28020, 26557, + 25079, 23586, 22078, 20557, 19024, 17479, 15923, 14359, + 12785, 11204, 9616, 8022, 6423, 4821, 3215, 1608, + 0, -1608, -3215, -4821, -6423, -8022, -9616, -11204, + -12785, -14359, -15923, -17479, -19024, -20557, -22078, -23586, + -25079, -26557, -28020, -29465, -30893, -32302, -33692, -35061, + -36409, -37736, -39039, -40319, -41575, -42806, -44011, -45189, + -46340, -47464, -48558, -49624, -50660, -51665, -52639, -53581, + -54491, -55368, -56212, -57022, -57797, -58538, -59243, -59913, + -60547, -61144, -61705, -62228, -62714, -63162, -63571, -63943, + -64276, -64571, -64826, -65043, -65220, -65358, -65457, -65516, + -65536, -65516, -65457, -65358, -65220, -65043, -64826, -64571, + -64276, -63943, -63571, -63162, -62714, -62228, -61705, -61144, + -60547, -59913, -59243, -58538, -57797, -57022, -56212, -55368, + -54491, -53581, -52639, -51665, -50660, -49624, -48558, -47464, + -46340, -45189, -44011, -42806, -41575, -40319, -39039, -37736, + -36409, -35061, -33692, -32302, -30893, -29465, -28020, -26557, + -25079, -23586, -22078, -20557, -19024, -17479, -15923, -14359, + -12785, -11204, -9616, -8022, -6423, -4821, -3215, -1608, + 0, 1608, 3215, 4821, 6423, 8022, 9616, 11204, + 12785, 14359, 15923, 17479, 19024, 20557, 22078, 23586, + 25079, 26557, 28020, 29465, 30893, 32302, 33692, 35061, + 36409, 37736, 39039, 40319, 41575, 42806, 44011, 45189, + 46340, 47464, 48558, 49624, 50660, 51665, 52639, 53581, + 54491, 55368, 56212, 57022, 57797, 58538, 59243, 59913, + 60547, 61144, 61705, 62228, 62714, 63162, 63571, 63943, + 64276, 64571, 64826, 65043, 65220, 65358, 65457, 65516 +}; + +long *sinTable = &(sinCosTable[0]); +long *cosTable = &(sinCosTable[64]); + +// FIXME: Tables + +const int sequenceVariableCount = 33; + +enum SequenceVariables { + kSeqVarTimer = 0, + kSeqVarTag = 1, + kSeqVarLayer = 2, + kSeqVarWidth = 3, + kSeqVarHeight = 4, + kSeqVarX = 5, + kSeqVarY = 6, + kSeqVarScale = 7, + kSeqVarR = 8, + kSeqVarSpriteHash = 9, + kSeqVarSpriteFrameNumber = 10, + kSeqVarSpriteFrameCount = 11, + kSeqVarSpriteFrameRate = 12, + kSeqVarSpriteFramePixelSpeed = 13, + kSeqVarTargetS = 14, + kSeqVarTargetR = 15, + kSeqVarTargetX = 16, + kSeqVarTargetY = 17, + kSeqVarDeltaS = 18, + kSeqVarDeltaR = 19, + kSeqVarDeltaX = 20, + kSeqVarDeltaY = 21, + kSeqVarVelocity = 22, + kSeqVarTheta = 23, + kSeqVarTemp1 = 24, + kSeqVarTemp2 = 25, + kSeqVarTemp3 = 26, + kSeqVarTemp4 = 27, + kSeqVarTemp5 = 28, + kSeqVarTemp6 = 29, + kSeqVarTemp7 = 30, + kSeqVarTemp8 = 31, + kSeqVarMachineID = 32 +}; + +bool (Sequence::*sequenceCommandsTable[])(Instruction &instruction) = { + &Sequence::s1_end, + &Sequence::s1_clearVars, + &Sequence::s1_set, + &Sequence::s1_compare, + &Sequence::s1_add, + &Sequence::s1_sub, + &Sequence::s1_mul, + &Sequence::s1_div, + &Sequence::s1_and, + &Sequence::s1_or, + &Sequence::s1_not, + &Sequence::s1_sin, + &Sequence::s1_cos, + &Sequence::s1_abs, + &Sequence::s1_min, + &Sequence::s1_max, + &Sequence::s1_mod, + &Sequence::s1_floor, + &Sequence::s1_round, + &Sequence::s1_ceil, + &Sequence::s1_point, + &Sequence::s1_dist2d, + &Sequence::s1_crunch, + &Sequence::s1_branch, + &Sequence::s1_setFrame, + &Sequence::s1_sendMessage, + &Sequence::s1_push, + &Sequence::s1_pop, + &Sequence::s1_jumpSub, + &Sequence::s1_return, + &Sequence::s1_getFrameCount, + &Sequence::s1_getFrameRate, + &Sequence::s1_getCelsPixSpeed, + &Sequence::s1_setIndex, + &Sequence::s1_setLayer, + &Sequence::s1_setDepth, + &Sequence::s1_setData, + &Sequence::s1_openStream, + &Sequence::s1_streamNextFrame, + &Sequence::s1_closeStream +}; + +Sequence::Sequence(WoodScript *ws, Machine *machine, int32 sequenceHash) { + + _ws = ws; + + SequenceAsset *sequenceAsset = _ws->assets()->getSequence(sequenceHash); + + // initialize the sequence's bytecode + byte *code; + uint32 codeSize; + sequenceAsset->getCode(code, codeSize); + _code = new Bytecode(_ws, code, codeSize, this); + + _active = true; + _sequenceHash = sequenceHash; + _machine = machine; + _parentSequence = _machine->parentSequence(); + _dataRow = _machine->dataRow(); + _startTime = 0; + _switchTime = 0; + //TODO _flags = 0; + _localVarCount = sequenceAsset->localVarCount(); + _vars = new long[sequenceVariableCount + _localVarCount]; + _returnStackIndex = 0; + _layer = 0; + _terminated = false; + + clearVars(); + _vars[kSeqVarMachineID] = _machine->getId(); + +} + +Sequence::~Sequence() { + delete _code; +} + +void Sequence::pause() { + _active = false; +} + +void Sequence::resume() { + _active = true; +} + +void Sequence::issueEndOfSequenceRequest(int32 codeOffset, int32 count) { + + //printf("Sequence::issueEndOfSequenceRequest(%04X, %04X)\n", codeOffset, count); fflush(stdout); + //g_system->delayMillis(5000); + + _endOfSequenceRequest.codeOffset = codeOffset; + _endOfSequenceRequest.count = count; +} + +void Sequence::cancelEndOfSequenceRequest() { + _endOfSequenceRequest.codeOffset = -1; +} + +bool Sequence::runProgram() { + + bool done = true; + + //printf("_ws->getGlobal(kGlobTime) = %ld, _switchTime = %d\n", _ws->getGlobal(kGlobTime), _switchTime); + + if (_switchTime >= 0 && _ws->getGlobal(kGlobTime) >= _switchTime) + done = false; + + _vars[kSeqVarTimer] -= _ws->getGlobal(kGlobTimeDelta) << 16; + + while (!done) { + Instruction instruction; + _code->loadInstruction(instruction); + if (sequenceCommandsTable[instruction.instr] != NULL) + done = !(this->*sequenceCommandsTable[instruction.instr])(instruction); + else { fflush(stdout); /*g_system->delayMillis(1000);*/ } + } + + return _terminated; +} + +void Sequence::clearVars() { + for (int i = 0; i < sequenceVariableCount + _localVarCount; i++) + _vars[i] = 0; + // set default scaling to 100% + _vars[kSeqVarScale] = 0x10000; +} + +bool Sequence::changeProgram(int32 sequenceHash) { + + SequenceAsset *sequenceAsset = _ws->assets()->getSequence(sequenceHash); + + if (sequenceAsset->localVarCount() > _localVarCount) { + //printf("Sequence::changeProgram(%d) sequenceAsset->localVarCount() > _localVarCount\n", sequenceHash); + return false; + } + + // Initialize the sequence's bytecode + byte *code; + uint32 codeSize; + sequenceAsset->getCode(code, codeSize); + _code->setCode(code, codeSize); + + // Reset status variables + _switchTime = 0; + _active = true; + _terminated = false; + _endOfSequenceRequest.codeOffset = -1; + + _sequenceHash = sequenceHash; + _returnStackIndex = 0; + + return true; + +} + +long *Sequence::getVarPtr(int index) { + return &_vars[index]; +} + +long *Sequence::getParentVarPtr(int index) { + return _parentSequence->getVarPtr(index); +} + +long *Sequence::getDataPtr(int index) { + return &_dataRow[index]; +} + +void Sequence::draw(M4Surface *surface, const Common::Rect &clipRect, Common::Rect &updateRect) { + + SpriteInfo info; + + info.sprite = _curFrame; + info.hotX = _curFrame->xOffset; + info.hotY = _curFrame->yOffset; + info.encoding = _curFrame->encoding; + info.inverseColorTable = _vm->_scene->getInverseColorTable(); + info.palette = _ws->getMainPalette(); + info.width = _curFrame->w; + info.height = _curFrame->h; + int32 scaler = FixedMul(_vars[kSeqVarScale], 100 << 16) >> 16; + info.scaleX = _vars[kSeqVarWidth] < 0 ? -scaler : scaler; + info.scaleY = scaler; + surface->drawSprite(_vars[kSeqVarX] >> 16, _vars[kSeqVarY] >> 16, info, clipRect); + +} + +bool Sequence::s1_end(Instruction &instruction) { + //printf("Sequence::s1_end()\n"); + + _terminated = true; + return false; +} + +bool Sequence::s1_clearVars(Instruction &instruction) { + //printf("Sequence::s1_clearVars()\n"); + + clearVars(); + _vars[kSeqVarMachineID] = _machine->getId(); + return true; +} + +bool Sequence::s1_set(Instruction &instruction) { + //printf("Sequence::s1_set()\n"); + + *instruction.argp[0] = instruction.getValue(); + return true; +} + +bool Sequence::s1_compare(Instruction &instruction) { + //printf("Sequence::s1_compare()\n"); + + long value = instruction.getValue(); + if (instruction.argv[0] < value) + _cmpFlags = -1; + else if (instruction.argv[0] > value) + _cmpFlags = 1; + else + _cmpFlags = 0; + return true; +} + +bool Sequence::s1_add(Instruction &instruction) { + //printf("Sequence::s1_add()\n"); + + *instruction.argp[0] += instruction.getValue(); + return true; +} + +bool Sequence::s1_sub(Instruction &instruction) { + //printf("Sequence::s1_sub()\n"); + + *instruction.argp[0] -= instruction.getValue(); + return true; +} + +bool Sequence::s1_mul(Instruction &instruction) { + //printf("Sequence::s1_mul()\n"); + + *instruction.argp[0] = FixedMul(instruction.argv[0], instruction.getValue()); + return true; +} + +bool Sequence::s1_div(Instruction &instruction) { + //printf("Sequence::s1_div()\n"); + + // TODO: Catch divisor = 0 in FixedDiv + *instruction.argp[0] = FixedDiv(instruction.argv[0], instruction.getValue()); + return true; +} + +bool Sequence::s1_and(Instruction &instruction) { + //printf("Sequence::s1_and()\n"); + + *instruction.argp[0] = instruction.argv[0] & instruction.getValue(); + if (*instruction.argp[0]) + _cmpFlags = 0; + else + _cmpFlags = 1; + return true; +} + +bool Sequence::s1_or(Instruction &instruction) { + //printf("Sequence::s1_or()\n"); + + *instruction.argp[0] = instruction.argv[0] | instruction.getValue(); + if (*instruction.argp[0]) + _cmpFlags = 0; + else + _cmpFlags = 1; + return true; +} + +bool Sequence::s1_not(Instruction &instruction) { + //printf("Sequence::s1_not()\n"); + + if (instruction.argv[0] == 0) { + *instruction.argp[0] = 0x10000; + _cmpFlags = 1; + } else { + *instruction.argp[0] = 0; + _cmpFlags = 0; + } + return true; +} + +bool Sequence::s1_sin(Instruction &instruction) { + //printf("Sequence::s1_sin()\n"); + + int32 tempAngle = *instruction.argp[1] >> 16; + if (tempAngle < 0) + tempAngle = 0x0100 - ((-tempAngle) & 0xff); + else + tempAngle &= 0xff; + + *instruction.argp[0] = -cosTable[tempAngle]; + + return true; +} + +bool Sequence::s1_cos(Instruction &instruction) { + //printf("Sequence::s1_cos()\n"); + + int32 tempAngle = *instruction.argp[1] >> 16; + if (tempAngle < 0) + tempAngle = 0x0100 - ((-tempAngle) & 0xff); + else + tempAngle &= 0xff; + + *instruction.argp[0] = sinTable[tempAngle]; + + return true; +} + +bool Sequence::s1_abs(Instruction &instruction) { + //printf("Sequence::s1_abs()\n"); + + *instruction.argp[0] = ABS(instruction.argv[1]); + return true; +} + +bool Sequence::s1_min(Instruction &instruction) { + //printf("Sequence::s1_min()\n"); + + *instruction.argp[0] = MIN(instruction.argv[1], instruction.argv[2]); + return true; +} + +bool Sequence::s1_max(Instruction &instruction) { + //printf("Sequence::s1_max()\n"); + + *instruction.argp[0] = MAX(instruction.argv[1], instruction.argv[2]); + return true; +} + +bool Sequence::s1_mod(Instruction &instruction) { + //printf("Sequence::s1_mod()\n"); + + *instruction.argp[0] = instruction.argv[0] % instruction.getValue(); + return true; +} + +bool Sequence::s1_floor(Instruction &instruction) { + //printf("Sequence::s1_floor()\n"); + + *instruction.argp[0] = instruction.getValue() & 0xffff0000; + return true; +} + +bool Sequence::s1_round(Instruction &instruction) { + //printf("Sequence::s1_round()\n"); + + if ((*instruction.argp[1] & 0xffff) >= 0x8000) + *instruction.argp[0] = (*instruction.argp[1] + 0x10000) & 0xffff0000; + else + *instruction.argp[0] = *instruction.argp[1] & 0xffff0000; + return true; +} + +bool Sequence::s1_ceil(Instruction &instruction) { + //printf("Sequence::s1_ceil()\n"); + + if ((*instruction.argp[1] & 0xffff) >= 0) + *instruction.argp[0] = (*instruction.argp[1] + 0x10000) & 0xffff0000; + else + *instruction.argp[0] = *instruction.argp[1] & 0xffff0000; + return true; +} + +bool Sequence::s1_point(Instruction &instruction) { + printf("Sequence::s1_point()\n"); + // TODO + return true; +} + +bool Sequence::s1_dist2d(Instruction &instruction) { + printf("Sequence::s1_dist2d()\n"); + // TODO + return true; +} + +bool Sequence::s1_crunch(Instruction &instruction) { + //printf("Sequence::s1_crunch()\n"); + + long deltaTime; + + if (instruction.argc == 2) { + deltaTime = _vm->imath_ranged_rand16(instruction.argv[0], instruction.argv[1]); + } else if (instruction.argc == 1) { + deltaTime = instruction.argv[0]; + } else { + deltaTime = 0; + } + + _startTime = _ws->getGlobal(kGlobTime); + + //printf("deltaTime = %ld\n", deltaTime >> 16); fflush(stdout); + //g_system->delayMillis(5000); + + if (deltaTime >= 0) { + _switchTime = _ws->getGlobal(kGlobTime) + (deltaTime >> 16); + //printf("_ws->getGlobal(kGlobTime) = %ld\n", _ws->getGlobal(kGlobTime)); fflush(stdout); + //g_system->delayMillis(5000); + } else { + _switchTime = -1; + } + + // TODO: Update if walking etc. + + return false; +} + +bool Sequence::s1_branch(Instruction &instruction) { + //printf("Sequence::s1_branch()\n"); + + uint32 ofs = instruction.argv[1] >> 16; + switch (instruction.argv[0] >> 16) { + case 0: // jmp + _code->jumpRelative(ofs); + break; + case 1: // < + if (_cmpFlags < 0) + _code->jumpRelative(ofs); + break; + case 2: // <= + if (_cmpFlags <= 0) + _code->jumpRelative(ofs); + break; + case 3: // == + if (_cmpFlags == 0) + _code->jumpRelative(ofs); + break; + case 4: // != + if (_cmpFlags != 0) + _code->jumpRelative(ofs); + break; + case 5: // >= + if (_cmpFlags >= 0) + _code->jumpRelative(ofs); + break; + case 6: // > + if (_cmpFlags > 0) + _code->jumpRelative(ofs); + break; + } + + return true; +} + +bool Sequence::s1_setFrame(Instruction &instruction) { + //printf("Sequence::s1_setFrame()\n"); + + int32 frameIndex; + if (instruction.argc == 3) { + frameIndex = _vm->imath_ranged_rand(instruction.argv[1] >> 16, instruction.argv[2] >> 16); + } else if (instruction.argc == 2) { + frameIndex = instruction.argv[1] >> 16; + } else { + frameIndex = (instruction.argv[0] & 0xFF0000) >> 16; + } + + //printf("Sequence::s1_setFrame() spriteHash = %d\n", (uint32)instruction.argv[0] >> 24); + //printf("Sequence::s1_setFrame() frameIndex = %d\n", frameIndex); + + SpriteAsset *spriteAsset = _ws->assets()->getSprite((uint32)instruction.argv[0] >> 24); + _curFrame = spriteAsset->getFrame(frameIndex); + + return true; +} + +bool Sequence::s1_sendMessage(Instruction &instruction) { + printf("Sequence::s1_sendMessage()\n"); + // TODO + return true; +} + +bool Sequence::s1_push(Instruction &instruction) { + printf("Sequence::s1_push()\n"); + // TODO + return true; +} + +bool Sequence::s1_pop(Instruction &instruction) { + printf("Sequence::s1_pop()\n"); + // TODO + return true; +} + +bool Sequence::s1_jumpSub(Instruction &instruction) { + //printf("Sequence::s1_jumpSub()\n"); + + _returnHashes[_returnStackIndex] = _sequenceHash; + _returnOffsets[_returnStackIndex] = _code->pos(); + _returnStackIndex++; + + _sequenceHash = instruction.argv[0] >> 16; + + SequenceAsset *sequenceAsset = _ws->assets()->getSequence(_sequenceHash); + + // initialize the sequence's bytecode + byte *code; + uint32 codeSize; + sequenceAsset->getCode(code, codeSize); + _code->setCode(code, codeSize); + + return true; +} + +bool Sequence::s1_return(Instruction &instruction) { + //printf("Sequence::s1_return()\n"); + + if (_returnStackIndex <= 0) + return s1_end(instruction); + + _returnStackIndex--; + + _sequenceHash = _returnHashes[_returnStackIndex]; + uint32 ofs = _returnOffsets[_returnStackIndex]; + + SequenceAsset *sequenceAsset = _ws->assets()->getSequence(_sequenceHash); + + // initialize the sequence's bytecode + byte *code; + uint32 codeSize; + sequenceAsset->getCode(code, codeSize); + _code->setCode(code, codeSize); + _code->jumpAbsolute(ofs); + + + return true; +} + +bool Sequence::s1_getFrameCount(Instruction &instruction) { + //printf("Sequence::s1_getFrameCount()\n"); + + SpriteAsset *spriteAsset = _ws->assets()->getSprite(instruction.argv[1] >> 24); + *instruction.argp[0] = spriteAsset->getCount() << 16; + return true; +} + +bool Sequence::s1_getFrameRate(Instruction &instruction) { + //printf("Sequence::s1_getFrameRate()\n"); + + SpriteAsset *spriteAsset = _ws->assets()->getSprite(instruction.argv[1] >> 24); + *instruction.argp[0] = spriteAsset->getFrameRate(); + return true; +} + +bool Sequence::s1_getCelsPixSpeed(Instruction &instruction) { + printf("Sequence::s1_getCelsPixSpeed()\n"); + // TODO + return true; +} + +bool Sequence::s1_setIndex(Instruction &instruction) { + printf("Sequence::s1_setIndex()\n"); + // TODO + return true; +} + +bool Sequence::s1_setLayer(Instruction &instruction) { + printf("Sequence::s1_setLayer()\n"); + //TODO + return true; +} + +bool Sequence::s1_setDepth(Instruction &instruction) { + printf("Sequence::s1_setDepth()\n"); + //TODO + return true; +} + +bool Sequence::s1_setData(Instruction &instruction) { + printf("Sequence::s1_setData()\n"); + //TODO + return true; +} + +bool Sequence::s1_openStream(Instruction &instruction) { + //printf("Sequence::s1_openStream()\n"); + + _stream = _vm->res()->openFile(_machine->name().c_str()); + streamOpen(); + return true; +} + +bool Sequence::s1_streamNextFrame(Instruction &instruction) { + //printf("Sequence::s1_streamNextFrame()\n"); + + streamNextFrame(); + return true; +} + +bool Sequence::s1_closeStream(Instruction &instruction) { + printf("Sequence::s1_closeStream()\n"); + //TODO + return true; +} + +bool Sequence::streamOpen() { + + _streamSpriteAsset = new SpriteAsset(_vm, _stream, _stream->size(), "stream", true); + + _vars[kSeqVarSpriteFrameNumber] = -0x10000; + _vars[kSeqVarSpriteFrameCount] = _streamSpriteAsset->getCount() << 16; + _vars[kSeqVarSpriteFrameRate] = _streamSpriteAsset->getFrameRate() << 16; + + //printf("Sequence::streamOpen() frames = %d; max = %d x %d\n", _streamSpriteAsset->getCount(), _streamSpriteAsset->getMaxFrameWidth(), _streamSpriteAsset->getMaxFrameHeight()); + //fflush(stdout); + + _curFrame = new M4Sprite(_vm, _streamSpriteAsset->getMaxFrameWidth(), _streamSpriteAsset->getMaxFrameHeight()); + streamNextFrame(); + + // TODO: Just a hack to see the series with the correct palette. + _vm->_palette->setPalette(_streamSpriteAsset->getPalette(), 0, 256); + + return true; +} + +bool Sequence::streamNextFrame() { + + _vars[kSeqVarSpriteFrameNumber] += 0x10000; + + int32 frameNum = _vars[kSeqVarSpriteFrameNumber] >> 16; + if (frameNum >= _streamSpriteAsset->getCount()) { + // End reached + return false; + } + + _streamSpriteAsset->loadStreamingFrame(_curFrame, frameNum, _vars[kSeqVarX], _vars[kSeqVarY]); + + _vars[kSeqVarWidth] = _curFrame->w << 16; + _vars[kSeqVarHeight] = _curFrame->h << 16; + + return true; +} + +void Sequence::streamClose() { + _stream = NULL; + _vm->res()->toss(_machine->name().c_str()); + //_vm->res()->purge(); + delete _streamSpriteAsset; + delete _curFrame; + _stream = NULL; + _streamSpriteAsset = NULL; + _curFrame = NULL; +} + +} |