diff options
-rw-r--r-- | base/plugins.cpp | 3 | ||||
-rwxr-xr-x | configure | 1 | ||||
-rw-r--r-- | engines/draci/animation.cpp | 647 | ||||
-rw-r--r-- | engines/draci/animation.h | 193 | ||||
-rw-r--r-- | engines/draci/barchive.cpp | 427 | ||||
-rw-r--r-- | engines/draci/barchive.h | 99 | ||||
-rw-r--r-- | engines/draci/detection.cpp | 133 | ||||
-rw-r--r-- | engines/draci/draci.cpp | 284 | ||||
-rw-r--r-- | engines/draci/draci.h | 94 | ||||
-rw-r--r-- | engines/draci/font.cpp | 344 | ||||
-rw-r--r-- | engines/draci/font.h | 105 | ||||
-rw-r--r-- | engines/draci/game.cpp | 1695 | ||||
-rw-r--r-- | engines/draci/game.h | 397 | ||||
-rw-r--r-- | engines/draci/module.mk | 25 | ||||
-rw-r--r-- | engines/draci/mouse.cpp | 114 | ||||
-rw-r--r-- | engines/draci/mouse.h | 73 | ||||
-rw-r--r-- | engines/draci/screen.cpp | 184 | ||||
-rw-r--r-- | engines/draci/screen.h | 66 | ||||
-rw-r--r-- | engines/draci/script.cpp | 1087 | ||||
-rw-r--r-- | engines/draci/script.h | 176 | ||||
-rw-r--r-- | engines/draci/sprite.cpp | 377 | ||||
-rw-r--r-- | engines/draci/sprite.h | 155 | ||||
-rw-r--r-- | engines/draci/surface.cpp | 179 | ||||
-rw-r--r-- | engines/draci/surface.h | 69 | ||||
-rw-r--r-- | engines/engines.mk | 5 |
25 files changed, 6932 insertions, 0 deletions
diff --git a/base/plugins.cpp b/base/plugins.cpp index 582ea9ebc4..a17e21b92a 100644 --- a/base/plugins.cpp +++ b/base/plugins.cpp @@ -160,6 +160,9 @@ public: #if PLUGIN_ENABLED_STATIC(TUCKER) LINK_PLUGIN(TUCKER) #endif + #if PLUGIN_ENABLED_STATIC(DRACI) + LINK_PLUGIN(DRACI) + #endif // Music plugins // TODO: Use defines to disable or enable each MIDI driver as a @@ -83,6 +83,7 @@ add_engine agos "AGOS" yes "agos2" add_engine agos2 "AGOS 2 games" yes add_engine cine "Cinematique evo 1" yes add_engine cruise "Cinematique evo 2" yes +add_engine draci "Dragon History" no add_engine drascula "Drascula: The Vampire Strikes Back" yes add_engine gob "Gobli*ns" yes add_engine groovie "Groovie" yes "groovie2" diff --git a/engines/draci/animation.cpp b/engines/draci/animation.cpp new file mode 100644 index 0000000000..248cc09197 --- /dev/null +++ b/engines/draci/animation.cpp @@ -0,0 +1,647 @@ +/* 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 "draci/draci.h" +#include "draci/animation.h" + +namespace Draci { + +Animation::Animation(DraciEngine *vm, int index) : _vm(vm) { + _id = kUnused; + _index = index; + _z = 0; + _relX = 0; + _relY = 0; + _scaleX = 1.0; + _scaleY = 1.0; + _playing = false; + _looping = false; + _paused = false; + _tick = _vm->_system->getMillis(); + _currentFrame = 0; + _callback = &Animation::doNothing; +} + +Animation::~Animation() { + deleteFrames(); +} + +bool Animation::isLooping() { + return _looping; +} + +void Animation::setRelative(int relx, int rely) { + + // Delete the previous frame if there is one + if (_frames.size() > 0) + markDirtyRect(_vm->_screen->getSurface()); + + _relX = relx; + _relY = rely; +} + +void Animation::setLooping(bool looping) { + _looping = looping; + debugC(7, kDraciAnimationDebugLevel, "Setting looping to %d on animation %d", + looping, _id); +} + +void Animation::markDirtyRect(Surface *surface) { + // Fetch the current frame's rectangle + Drawable *frame = _frames[_currentFrame]; + Common::Rect frameRect = frame->getRect(); + + // Translate rectangle to compensate for relative coordinates + frameRect.translate(_relX, _relY); + + // Take animation scaling into account + frameRect.setWidth(frameRect.width() * _scaleX); + frameRect.setHeight(frameRect.height() * _scaleY); + + // Mark the rectangle dirty on the surface + surface->markDirtyRect(frameRect); +} + +void Animation::nextFrame(bool force) { + + // If there are no frames or if the animation is not playing, return + if (getFrameCount() == 0 || !_playing) + return; + + Drawable *frame = _frames[_currentFrame]; + Surface *surface = _vm->_screen->getSurface(); + + if (force || (_tick + frame->getDelay() <= _vm->_system->getMillis())) { + // If we are at the last frame and not looping, stop the animation + // The animation is also restarted to frame zero + if ((_currentFrame == getFrameCount() - 1) && !_looping) { + // When the animation reaches its end, call the preset callback + (this->*_callback)(); + } else { + // Mark old frame dirty so it gets deleted + markDirtyRect(surface); + + _currentFrame = nextFrameNum(); + _tick = _vm->_system->getMillis(); + + // Fetch new frame and mark it dirty + markDirtyRect(surface); + } + } + + debugC(6, kDraciAnimationDebugLevel, + "anim=%d tick=%d delay=%d tick+delay=%d currenttime=%d frame=%d framenum=%d x=%d y=%d", + _id, _tick, frame->getDelay(), _tick + frame->getDelay(), _vm->_system->getMillis(), + _currentFrame, _frames.size(), frame->getX(), frame->getY()); +} + +uint Animation::nextFrameNum() { + + if (_paused) + return _currentFrame; + + if ((_currentFrame == getFrameCount() - 1) && _looping) + return 0; + else + return _currentFrame + 1; +} + +void Animation::drawFrame(Surface *surface) { + + // If there are no frames or the animation is not playing, return + if (_frames.size() == 0 || !_playing) + return; + + Drawable *frame = _frames[_currentFrame]; + + if (_id == kOverlayImage) { + frame->draw(surface, false); + } else { + + int x = frame->getX(); + int y = frame->getY(); + + // Take account relative coordinates + int newX = x + _relX; + int newY = y + _relY; + + // Translate the frame to those relative coordinates + frame->setX(newX); + frame->setY(newY); + + // Save scaled width and height + int scaledWidth = frame->getScaledWidth(); + int scaledHeight = frame->getScaledHeight(); + + // Take into account per-animation scaling and adjust the current frames dimensions + if (_scaleX != 1.0 || _scaleY != 1.0) + frame->setScaled(scaledWidth * _scaleX, scaledHeight * _scaleY); + + // Draw frame + frame->drawScaled(surface, false); + + // Revert back to old coordinates + frame->setX(x); + frame->setY(y); + + // Revert back to old dimensions + frame->setScaled(scaledWidth, scaledHeight); + } +} + +void Animation::setID(int id) { + _id = id; +} + +int Animation::getID() { + return _id; +} + +void Animation::setZ(uint z) { + _z = z; +} + +uint Animation::getZ() { + return _z; +} + +int Animation::getRelativeX() { + return _relX; +} + +int Animation::getRelativeY() { + return _relY; +} + +bool Animation::isPlaying() { + return _playing; +} + +void Animation::setPlaying(bool playing) { + _tick = _vm->_system->getMillis(); + _playing = playing; +} + +bool Animation::isPaused() { + return _paused; +} + +void Animation::setPaused(bool paused) { + _paused = paused; +} + +void Animation::setScaleFactors(double scaleX, double scaleY) { + + debugC(5, kDraciAnimationDebugLevel, + "Setting scaling factors on anim %d (scaleX: %.3f scaleY: %.3f)", + _id, scaleX, scaleY); + + markDirtyRect(_vm->_screen->getSurface()); + + _scaleX = scaleX; + _scaleY = scaleY; +} + +double Animation::getScaleX() { + return _scaleX; +} + +double Animation::getScaleY() { + return _scaleY; +} + +void Animation::addFrame(Drawable *frame) { + _frames.push_back(frame); +} + +int Animation::getIndex() { + return _index; +} + +void Animation::setIndex(int index) { + _index = index; +} + +Drawable *Animation::getFrame(int frameNum) { + + // If there are no frames stored, return NULL + if (_frames.size() == 0) { + return NULL; + } + + // If no argument is passed, return the current frame + if (frameNum == kCurrentFrame) { + return _frames[_currentFrame]; + } else { + return _frames[frameNum]; + } +} + +uint Animation::getFrameCount() { + return _frames.size(); +} + +uint Animation::currentFrameNum() { + return _currentFrame; +} + +void Animation::setCurrentFrame(uint frame) { + + // Check whether the value is sane + if (frame >= _frames.size()) { + return; + } + + _currentFrame = frame; +} + +void Animation::deleteFrames() { + + // If there are no frames to delete, return + if (_frames.size() == 0) { + return; + } + + markDirtyRect(_vm->_screen->getSurface()); + + for (int i = getFrameCount() - 1; i >= 0; --i) { + delete _frames[i]; + _frames.pop_back(); + } +} + +void Animation::stopAnimation() { + _vm->_anims->stop(_id); +} + +void Animation::exitGameLoop() { + _vm->_game->setExitLoop(true); +} + +Animation *AnimationManager::addAnimation(int id, uint z, bool playing) { + + // Increment animation index + ++_lastIndex; + + Animation *anim = new Animation(_vm, _lastIndex); + + anim->setID(id); + anim->setZ(z); + anim->setPlaying(playing); + + insertAnimation(anim); + + return anim; +} + +Animation *AnimationManager::addItem(int id, bool playing) { + + Animation *anim = new Animation(_vm, kIgnoreIndex); + + anim->setID(id); + anim->setZ(256); + anim->setPlaying(playing); + + insertAnimation(anim); + + return anim; +} + +Animation *AnimationManager::addText(int id, bool playing) { + + Animation *anim = new Animation(_vm, kIgnoreIndex); + + anim->setID(id); + anim->setZ(257); + anim->setPlaying(playing); + + insertAnimation(anim); + + return anim; +} + +void AnimationManager::play(int id) { + Animation *anim = getAnimation(id); + + if (anim) { + // Mark the first frame dirty so it gets displayed + anim->markDirtyRect(_vm->_screen->getSurface()); + + anim->setPlaying(true); + + debugC(3, kDraciAnimationDebugLevel, "Playing animation %d...", id); + } +} + +void AnimationManager::stop(int id) { + Animation *anim = getAnimation(id); + + if (anim) { + // Clean up the last frame that was drawn before stopping + anim->markDirtyRect(_vm->_screen->getSurface()); + + anim->setPlaying(false); + + // Reset the animation to the beginning + anim->setCurrentFrame(0); + + debugC(3, kDraciAnimationDebugLevel, "Stopping animation %d...", id); + } +} + +void AnimationManager::pauseAnimations() { + + Common::List<Animation *>::iterator it; + + for (it = _animations.begin(); it != _animations.end(); ++it) { + if ((*it)->getID() > 0 || (*it)->getID() == kTitleText) { + // Clean up the last frame that was drawn before stopping + (*it)->markDirtyRect(_vm->_screen->getSurface()); + + (*it)->setPaused(true); + } + } +} + +void AnimationManager::unpauseAnimations() { + + Common::List<Animation *>::iterator it; + + for (it = _animations.begin(); it != _animations.end(); ++it) { + if ((*it)->isPaused()) { + // Clean up the last frame that was drawn before stopping + (*it)->markDirtyRect(_vm->_screen->getSurface()); + + (*it)->setPaused(false); + } + } +} + +Animation *AnimationManager::getAnimation(int id) { + + Common::List<Animation *>::iterator it; + + for (it = _animations.begin(); it != _animations.end(); ++it) { + if ((*it)->getID() == id) { + return *it; + } + } + + return NULL; +} + +void AnimationManager::insertAnimation(Animation *animObj) { + + Common::List<Animation *>::iterator it; + + for (it = _animations.begin(); it != _animations.end(); ++it) { + if (animObj->getZ() < (*it)->getZ()) + break; + } + + _animations.insert(it, animObj); +} + +void AnimationManager::addOverlay(Drawable *overlay, uint z) { + // Since this is an overlay, we don't need it to be deleted + // when the GPL Release command is invoked so we pass the index + // as kIgnoreIndex + Animation *anim = new Animation(_vm, kIgnoreIndex); + + anim->setID(kOverlayImage); + anim->setZ(z); + anim->setPlaying(true); + anim->addFrame(overlay); + + insertAnimation(anim); +} + +void AnimationManager::drawScene(Surface *surf) { + + // Fill the screen with colour zero since some rooms may rely on the screen being black + _vm->_screen->getSurface()->fill(0); + + sortAnimations(); + + Common::List<Animation *>::iterator it; + + for (it = _animations.begin(); it != _animations.end(); ++it) { + if (! ((*it)->isPlaying()) ) { + continue; + } + + (*it)->nextFrame(); + (*it)->drawFrame(surf); + } +} + +void AnimationManager::sortAnimations() { + Common::List<Animation *>::iterator cur; + Common::List<Animation *>::iterator next; + + cur = _animations.begin(); + + // If the list is empty, we're done + if (cur == _animations.end()) + return; + + while(1) { + next = cur; + next++; + + // If we are at the last element, we're done + if (next == _animations.end()) + break; + + // If we find an animation out of order, reinsert it + if ((*next)->getZ() < (*cur)->getZ()) { + + Animation *anim = *next; + _animations.erase(next); + + insertAnimation(anim); + } + + // Advance to next animation + cur = next; + } +} + +void AnimationManager::deleteAnimation(int id) { + + Common::List<Animation *>::iterator it; + + int index = -1; + + // Iterate for the first time to delete the animation + for (it = _animations.begin(); it != _animations.end(); ++it) { + if ((*it)->getID() == id) { + (*it)->deleteFrames(); + _animations.erase(it); + + // Remember index of the deleted animation + index = (*it)->getIndex(); + + debugC(3, kDraciAnimationDebugLevel, "Deleting animation %d...", id); + + break; + } + } + + // Iterate the second time to decrease indexes greater than the deleted animation index + for (it = _animations.begin(); it != _animations.end(); ++it) { + if ((*it)->getIndex() == index && (*it)->getIndex() != kIgnoreIndex) { + (*it)->setIndex(index-1); + } + } + + // Decrement index of last animation + _lastIndex -= 1; +} + +void AnimationManager::deleteOverlays() { + + debugC(3, kDraciAnimationDebugLevel, "Deleting overlays..."); + + Common::List<Animation *>::iterator it; + + for (it = _animations.begin(); it != _animations.end(); ++it) { + if ((*it)->getID() == kOverlayImage) { + (*it)->deleteFrames(); + _animations.erase(it); + } + } +} + +void AnimationManager::deleteAll() { + + debugC(3, kDraciAnimationDebugLevel, "Deleting all animations..."); + + Common::List<Animation *>::iterator it; + + for (it = _animations.begin(); it != _animations.end(); ++it) { + (*it)->deleteFrames(); + } + + _animations.clear(); + + _lastIndex = -1; +} + +int AnimationManager::getLastIndex() { + return _lastIndex; +} + +void AnimationManager::deleteAfterIndex(int index) { + + Common::List<Animation *>::iterator it; + + for (it = _animations.begin(); it != _animations.end(); ++it) { + if ((*it)->getIndex() > index) { + + debugC(3, kDraciAnimationDebugLevel, "Deleting animation %d...", (*it)->getID()); + + (*it)->deleteFrames(); + _animations.erase(it); + } + } + + _lastIndex = index; +} + +int AnimationManager::getTopAnimationID(int x, int y) { + + Common::List<Animation *>::iterator it; + + // The default return value if no animations were found on these coordinates (not even overlays) + // i.e. the black background shows through so treat it as an overlay + int retval = kOverlayImage; + + // Get transparent colour for the current screen + const int transparent = _vm->_screen->getSurface()->getTransparentColour(); + + for (it = _animations.reverse_begin(); it != _animations.end(); --it) { + + Animation *anim = *it; + + // If the animation is not playing, ignore it + if (!anim->isPlaying() || anim->isPaused()) { + continue; + } + + Drawable *frame = anim->getFrame(); + + if (frame == NULL) { + continue; + } + + int oldX = frame->getX(); + int oldY = frame->getY(); + + // Take account relative coordinates + int newX = oldX + anim->getRelativeX(); + int newY = oldY + anim->getRelativeY(); + + // Translate the frame to those relative coordinates + frame->setX(newX); + frame->setY(newY); + + // Save scaled width and height + int scaledWidth = frame->getScaledWidth(); + int scaledHeight = frame->getScaledHeight(); + + // Take into account per-animation scaling and adjust the current frames dimensions + if (anim->getScaleX() != 1.0 || anim->getScaleY() != 1.0) + frame->setScaled(scaledWidth * anim->getScaleX(), scaledHeight * anim->getScaleY()); + + if (frame->getRect().contains(x, y)) { + + if (frame->getType() == kDrawableText) { + + retval = anim->getID(); + + } else if (frame->getType() == kDrawableSprite && + reinterpret_cast<Sprite *>(frame)->getPixel(x, y) != transparent) { + + retval = anim->getID(); + } + } + + // Revert back to old coordinates + frame->setX(oldX); + frame->setY(oldY); + + // Revert back to old dimensions + frame->setScaled(scaledWidth, scaledHeight); + + // Found an animation + if (retval != kOverlayImage) + break; + } + + return retval; +} + +} diff --git a/engines/draci/animation.h b/engines/draci/animation.h new file mode 100644 index 0000000000..5d7c0bf7b6 --- /dev/null +++ b/engines/draci/animation.h @@ -0,0 +1,193 @@ +/* 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 DRACI_ANIMATION_H +#define DRACI_ANIMATION_H + +#include "draci/sprite.h" + +namespace Draci { + +/** + * Animation IDs for those animations that don't have their IDs + * specified in the data files. + */ +enum { kOverlayImage = -1, + kWalkingMapOverlay = -2, + kTitleText = -3, + kSpeechText = -4, + kInventorySprite = -5, + kDialogueLinesID = -6, + kUnused = -10, + kInventoryItemsID = -11}; + +/** + * Default argument to Animation::getFrame() that makes it return + * the current frame instead of the user specifying it. + */ +enum { kCurrentFrame = -1 }; + +/** + * Used by overlays as a neutral index that won't get + * released with the GPL Release command. + */ +enum { kIgnoreIndex = -2 }; + +class DraciEngine; + +class Animation { + +typedef void (Animation::* AnimationCallback)(); + +public: + Animation(DraciEngine *v, int index); + ~Animation(); + + uint getZ(); + void setZ(uint z); + + void setID(int id); + int getID(); + + void nextFrame(bool force = false); + void drawFrame(Surface *surface); + + void addFrame(Drawable *frame); + Drawable *getFrame(int frameNum = kCurrentFrame); + void setCurrentFrame(uint frame); + uint currentFrameNum(); + uint getFrameCount(); + void deleteFrames(); + + bool isPlaying(); + void setPlaying(bool playing); + + bool isPaused(); + void setPaused(bool paused); + + bool isLooping(); + void setLooping(bool looping); + + void setRelative(int relx, int rely); + int getRelativeX(); + int getRelativeY(); + + int getIndex(); + void setIndex(int index); + + void setScaleFactors(double scaleX, double scaleY); + double getScaleX(); + double getScaleY(); + + void markDirtyRect(Surface *surface); + + // Animation callbacks + + void registerCallback(AnimationCallback callback) { _callback = callback; } + + void doNothing() {} + void stopAnimation(); + void exitGameLoop(); + +private: + + uint nextFrameNum(); + + /** Internal animation ID + * (as specified in the data files and the bytecode) + */ + int _id; + + /** The recency index of an animation, i.e. the most recently added animation has + * the highest index. Some script commands need this. + */ + int _index; + + uint _currentFrame; + uint _z; + + int _relX; + int _relY; + + double _scaleX; + double _scaleY; + + uint _tick; + bool _playing; + bool _looping; + bool _paused; + Common::Array<Drawable*> _frames; + + AnimationCallback _callback; + + DraciEngine *_vm; +}; + + +class AnimationManager { + +public: + AnimationManager(DraciEngine *vm) : _vm(vm), _lastIndex(-1) {} + ~AnimationManager() { deleteAll(); } + + Animation *addAnimation(int id, uint z, bool playing = false); + Animation *addText(int id, bool playing = false); + Animation *addItem(int id, bool playing = false); + void addOverlay(Drawable *overlay, uint z); + + void play(int id); + void stop(int id); + void pauseAnimations(); + void unpauseAnimations(); + + void deleteAnimation(int id); + void deleteOverlays(); + void deleteAll(); + + void drawScene(Surface *surf); + + Animation *getAnimation(int id); + + int getLastIndex(); + void deleteAfterIndex(int index); + + int getTopAnimationID(int x, int y); + +private: + void sortAnimations(); + void insertAnimation(Animation *anim); + + DraciEngine *_vm; + Common::List<Animation *> _animations; + + /** The index of the most recently added animation. + * See Animation::_index for details. + */ + int _lastIndex; +}; + +} + +#endif // DRACI_ANIMATION_H diff --git a/engines/draci/barchive.cpp b/engines/draci/barchive.cpp new file mode 100644 index 0000000000..2a27e6a383 --- /dev/null +++ b/engines/draci/barchive.cpp @@ -0,0 +1,427 @@ +/* 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/debug.h" +#include "common/file.h" +#include "common/str.h" +#include "common/stream.h" + +#include "draci/barchive.h" +#include "draci/draci.h" + +namespace Draci { + +const char BArchive::_magicNumber[] = "BAR!"; +const char BArchive::_dfwMagicNumber[] = "BS"; + +/** + * @brief Loads a DFW archive + * @param path Path to input file + * + * Tries to load the file as a DFW archive if opening as BAR fails. Should only be called + * from openArchive(). Only one of the game files appears to use this format (HRA.DFW) + * and this file is compressed using a simple run-length scheme. + * + * archive format: header + * index table + * file0, file1, ... + * + * header format: [uint16LE] file count + * [uint16LE] index table size + * [2 bytes] magic number "BS" + * + * index table format: entry0, entry1, ... + * + * entry<N> format: [uint16LE] compressed size (not including the 2 bytes for the + * "uncompressed size" field) + * [uint32LE] fileN offset from start of file + * + * file<N> format: [uint16LE] uncompressed size + * [uint16LE] compressed size (the same as in the index table entry) + * [byte] stopper mark (for run-length compression) + * [multiple bytes] compressed data + */ + +void BArchive::openDFW(const Common::String &path) { + byte *table; + uint16 tableSize; + byte buf[2]; + + Common::File f; + + f.open(path); + if (!f.isOpen()) { + debugC(2, kDraciArchiverDebugLevel, "Error opening file"); + return; + } + + _fileCount = f.readUint16LE(); + tableSize = f.readUint16LE(); + + f.read(buf, 2); + if (memcmp(buf, _dfwMagicNumber, 2) == 0) { + debugC(2, kDraciArchiverDebugLevel, "Success"); + _isDFW = true; + } else { + debugC(2, kDraciArchiverDebugLevel, "Not a DFW archive"); + f.close(); + return; + } + + debugC(2, kDraciArchiverDebugLevel, "Archive info (DFW): %d files", _fileCount); + + // Read in index table + table = new byte[tableSize]; + f.read(table, tableSize); + + // Read in file headers, but do not read the actual data yet + // The data will be read on demand to save memory + _files = new BAFile[_fileCount]; + Common::MemoryReadStream tableReader(table, tableSize); + for (unsigned int i = 0; i < _fileCount; ++i) { + _files[i]._compLength = tableReader.readUint16LE(); + _files[i]._offset = tableReader.readUint32LE(); + + // Seek to the current file + f.seek(_files[i]._offset); + + _files[i]._length = f.readUint16LE(); // Read in uncompressed length + f.readUint16LE(); // Compressed length again (already read from the index table) + _files[i]._stopper = f.readByte(); + + _files[i]._data = NULL; // File data will be read in on demand + _files[i]._crc = 0; // Dummy value; not used in DFW archives + } + + // Indicate that the archive was successfully opened + _opened = true; + + // Cleanup + delete[] table; + f.close(); +} + +/** + * @brief BArchive open method + * @param path Path to input file + * + * Opens a BAR (Bob's Archiver) archive, which is the game's archiving format. + * BAR archives have a .DFW file extension, due to a historical interface. + * + * archive format: header, + * file0, file1, ... + * footer + * + * header format: [4 bytes] magic number "BAR!" + * [uint16LE] file count (number of archived streams), + * [uint32LE] footer offset from start of file + * + * file<N> format: [2 bytes] compressed length + * [2 bytes] original length + * [1 byte] compression type + * [1 byte] CRC + * [multiple bytes] actual data + * + * footer format: [array of uint32LE] offsets of individual files from start of archive + * (last entry is footer offset again) + */ + +void BArchive::openArchive(const Common::String &path) { + byte buf[4]; + byte *footer; + uint32 footerOffset, footerSize; + Common::File f; + + // Close previously opened archive (if any) + closeArchive(); + + debugCN(2, kDraciArchiverDebugLevel, "Loading archive %s: ", path.c_str()); + + f.open(path); + if (f.isOpen()) { + debugC(2, kDraciArchiverDebugLevel, "Success"); + } else { + debugC(2, kDraciArchiverDebugLevel, "Error"); + return; + } + + // Save path for reading in files later on + _path = path; + + // Read archive header + debugCN(2, kDraciArchiverDebugLevel, "Checking for BAR magic number: "); + + f.read(buf, 4); + if (memcmp(buf, _magicNumber, 4) == 0) { + debugC(2, kDraciArchiverDebugLevel, "Success"); + + // Indicate this archive is a BAR + _isDFW = false; + } else { + debugC(2, kDraciArchiverDebugLevel, "Not a BAR archive"); + debugCN(2, kDraciArchiverDebugLevel, "Retrying as DFW: "); + f.close(); + + // Try to open as DFW + openDFW(_path); + + return; + } + + _fileCount = f.readUint16LE(); + footerOffset = f.readUint32LE(); + footerSize = f.size() - footerOffset; + + debugC(2, kDraciArchiverDebugLevel, "Archive info: %d files, %d data bytes", + _fileCount, footerOffset - _archiveHeaderSize); + + // Read in footer + footer = new byte[footerSize]; + f.seek(footerOffset); + f.read(footer, footerSize); + Common::MemoryReadStream reader(footer, footerSize); + + // Read in file headers, but do not read the actual data yet + // The data will be read on demand to save memory + _files = new BAFile[_fileCount]; + + for (unsigned int i = 0; i < _fileCount; i++) { + uint32 fileOffset; + + fileOffset = reader.readUint32LE(); + f.seek(fileOffset); // Seek to next file in archive + + _files[i]._compLength = f.readUint16LE(); // Compressed size + // should be the same as uncompressed + + _files[i]._length = f.readUint16LE(); // Original size + + _files[i]._offset = fileOffset; // Offset of file from start + + assert(f.readByte() == 0 && + "Compression type flag is non-zero (file is compressed)"); + + _files[i]._crc = f.readByte(); // CRC checksum of the file + _files[i]._data = NULL; // File data will be read in on demand + _files[i]._stopper = 0; // Dummy value; not used in BAR files, needed in DFW + } + + // Last footer item should be equal to footerOffset + assert(reader.readUint32LE() == footerOffset && "Footer offset mismatch"); + + // Indicate that the archive has been successfully opened + _opened = true; + + delete[] footer; + f.close(); +} + +/** + * @brief BArchive close method + * + * Closes the currently opened archive. It can be called explicitly to + * free up memory. + */ +void BArchive::closeArchive(void) { + if (!_opened) { + return; + } + + for (unsigned int i = 0; i < _fileCount; ++i) { + if (_files[i]._data) { + delete[] _files[i]._data; + } + } + + delete[] _files; + + _opened = false; + _files = NULL; + _fileCount = 0; +} + +/** + * @brief On-demand BAR file loader + * @param i Index of file inside an archive + * @return Pointer to a BAFile coresponding to the opened file or NULL (on failure) + * + * Loads individual BAR files from an archive to memory on demand. + * Should not be called directly. Instead, one should access files + * through the operator[] interface. + */ +BAFile *BArchive::loadFileBAR(unsigned int i) const { + Common::File f; + + // Else open archive and read in requested file + f.open(_path); + if (f.isOpen()) { + debugC(2, kDraciArchiverDebugLevel, "Success"); + } else { + debugC(2, kDraciArchiverDebugLevel, "Error"); + return NULL; + } + + // Read in the file (without the file header) + f.seek(_files[i]._offset + _fileHeaderSize); + _files[i]._data = new byte[_files[i]._length]; + f.read(_files[i]._data, _files[i]._length); + + // Calculate CRC + byte tmp = 0; + for (unsigned int j = 0; j < _files[i]._length; j++) { + tmp ^= _files[i]._data[j]; + } + + debugC(3, kDraciArchiverDebugLevel, "Cached file %d from archive %s", + i, _path.c_str()); + assert(tmp == _files[i]._crc && "CRC checksum mismatch"); + + return _files + i; +} + +/** + * @brief On-demand DFW file loader + * @param i Index of file inside an archive + * @return Pointer to a BAFile coresponding to the opened file or NULL (on failure) + * + * Loads individual DFW files from an archive to memory on demand. + * Should not be called directly. Instead, one should access files + * through the operator[] interface. + */ +BAFile *BArchive::loadFileDFW(unsigned int i) const { + Common::File f; + byte *buf; + + // Else open archive and read in requested file + f.open(_path); + if (f.isOpen()) { + debugC(2, kDraciArchiverDebugLevel, "Success"); + } else { + debugC(2, kDraciArchiverDebugLevel, "Error"); + return NULL; + } + + // Seek to raw data of the file + // Five bytes are for the header (uncompressed and compressed length, stopper mark) + f.seek(_files[i]._offset + 5); + + // Since we are seeking directly to raw data, we subtract 3 bytes from the length + // (to take account the compressed length and stopper mark) + uint16 compressedLength = _files[i]._compLength - 3; + uint16 uncompressedLength = _files[i]._length; + + debugC(2, kDraciArchiverDebugLevel, + "File info (DFW): uncompressed %d bytes, compressed %d bytes", + uncompressedLength, compressedLength); + + // Allocate a buffer for the file data + buf = new byte[compressedLength]; + + // Read in file data into the buffer + f.read(buf, compressedLength); + + // Allocate the space for the uncompressed file + byte *dst; + dst = _files[i]._data = new byte[uncompressedLength]; + + Common::MemoryReadStream data(buf, compressedLength); + + // Uncompress file + byte current, what; + byte stopper = _files[i]._stopper; + unsigned int repeat; + unsigned int len = 0; // Sanity check (counts uncompressed bytes) + + current = data.readByte(); // Read initial byte + while (!data.eos()) { + + if (current != stopper) { + *dst++ = current; + ++len; + } else { + // Inflate block + repeat = data.readByte(); + what = data.readByte(); + len += repeat; + for (unsigned int j = 0; j < repeat; ++j) { + *dst++ = what; + } + } + + current = data.readByte(); + } + + assert(len == _files[i]._length && "Uncompressed file not of the expected length"); + + delete[] buf; + + return _files + i; +} + +/** + * Clears the cache of the open files inside the archive without closing it. + * If the files are subsequently accessed, they are read from the disk. + */ +void BArchive::clearCache() { + + // Delete all cached data + for (unsigned int i = 0; i < _fileCount; ++i) { + _files[i].close(); + } +} + + +BAFile *BArchive::getFile(unsigned int i) const { + + // Check whether requested file exists + if (i >= _fileCount) { + return NULL; + } + + debugCN(2, kDraciArchiverDebugLevel, "Accessing file %d from archive %s... ", + i, _path.c_str()); + + // Check if file has already been opened and return that + if (_files[i]._data) { + debugC(2, kDraciArchiverDebugLevel, "Success"); + return _files + i; + } + + BAFile *file; + + // file will be NULL if something goes wrong + if (_isDFW) { + file = loadFileDFW(i); + } else { + file = loadFileBAR(i); + } + + return file; +} + +} // End of namespace Draci + + + diff --git a/engines/draci/barchive.h b/engines/draci/barchive.h new file mode 100644 index 0000000000..6766c3c1fd --- /dev/null +++ b/engines/draci/barchive.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$ + * + */ + +#ifndef DRACI_BARCHIVE_H +#define DRACI_BARCHIVE_H + +#include "common/str.h" + +namespace Draci { + +/** + * Represents individual files inside the archive. + */ + +struct BAFile { + uint _compLength; //!< Compressed length (the same as _length if the file is uncompressed) + uint _length; //!< Uncompressed length + uint32 _offset; //!< Offset of file inside archive + byte *_data; + byte _crc; + byte _stopper; //!< Not used in BAR files, needed for DFW + + /** Releases the file data (for memory considerations) */ + void close(void) { + delete[] _data; + _data = NULL; + } +}; + +class BArchive { +public: + BArchive() : _files(NULL), _fileCount(0), _opened(false) {} + + BArchive(const Common::String &path) : + _files(NULL), _fileCount(0), _opened(false) { + openArchive(path); + } + + ~BArchive() { closeArchive(); } + + void openArchive(const Common::String &path); + void closeArchive(void); + uint size() const { return _fileCount; } + + /** + * Checks whether there is an archive opened. Should be called before reading + * from the archive to check whether openArchive() succeeded. + */ + bool isOpen() const { return _opened; } + + void clearCache(); + + BAFile *getFile(unsigned int i) const; + +private: + // Archive header data + static const char _magicNumber[]; + static const char _dfwMagicNumber[]; + static const unsigned int _archiveHeaderSize = 10; + + // File stream header data + static const unsigned int _fileHeaderSize = 6; + + Common::String _path; //!< Path to file + BAFile *_files; //!< Internal array of files + uint _fileCount; //!< Number of files in archive + bool _isDFW; //!< True if the archive is in DFW format, false otherwise + bool _opened; //!< True if the archive is opened, false otherwise + + void openDFW(const Common::String &path); + BAFile *loadFileDFW(unsigned int i) const; + BAFile *loadFileBAR(unsigned int i) const; +}; + +} // End of namespace Draci + +#endif // DRACI_BARCHIVE_H diff --git a/engines/draci/detection.cpp b/engines/draci/detection.cpp new file mode 100644 index 0000000000..b8aeb11200 --- /dev/null +++ b/engines/draci/detection.cpp @@ -0,0 +1,133 @@ +/* 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 "draci/draci.h" + +#include "base/plugins.h" +#include "engines/metaengine.h" + +static const PlainGameDescriptor draciGames[] = { + { "draci", "Draci Historie" }, + { 0, 0 } +}; + +namespace Draci { + +using Common::GUIO_NONE; + +const ADGameDescription gameDescriptions[] = { + + { + "draci", + 0, + AD_ENTRY1s("INIT.DFW", "b890a5aeebaf16af39219cba2416b0a3", 906), + Common::EN_ANY, + Common::kPlatformPC, + ADGF_NO_FLAGS, + GUIO_NONE + }, + + { + "draci", + 0, + AD_ENTRY1s("INIT.DFW", "9921c8f0045679a8f37eca8d41c5ec02", 906), + Common::CZ_CZE, + Common::kPlatformPC, + ADGF_NO_FLAGS, + GUIO_NONE + }, + + { + "draci", + 0, + AD_ENTRY1s("INIT.DFW", "76b9b78a8a8809a240acc395df4d0715", 906), + Common::PL_POL, + Common::kPlatformPC, + ADGF_NO_FLAGS, + GUIO_NONE + }, + + AD_TABLE_END_MARKER +}; + +} // End of namespace Draci + +const ADParams detectionParams = { + // Pointer to ADGameDescription or its superset structure + (const byte *)Draci::gameDescriptions, + // Size of that superset structure + sizeof(ADGameDescription), + // Number of bytes to compute MD5 sum for + 5000, + // List of all engine targets + draciGames, + // Structure for autoupgrading obsolete targets + 0, + // Name of single gameid (optional) + "draci", + // List of files for file-based fallback detection (optional) + 0, + // Flags + 0, + // Global GUI options + Common::GUIO_NONE +}; + +class DraciMetaEngine : public AdvancedMetaEngine { +public: + DraciMetaEngine() : AdvancedMetaEngine(detectionParams) {} + + virtual const char *getName() const { + return "Draci Historie Engine"; + } + + virtual const char *getOriginalCopyright() const { + return "Copyright (C) 1995 NoSense"; + } + + virtual bool hasFeature(MetaEngineFeature f) const; + virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; +}; + +bool DraciMetaEngine::hasFeature(MetaEngineFeature f) const { + return false; +} + +bool Draci::DraciEngine::hasFeature(EngineFeature f) const { + return false; +} + +bool DraciMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const { + if (desc) { + *engine = new Draci::DraciEngine(syst, desc); + } + return desc != 0; +} + +#if PLUGIN_ENABLED_DYNAMIC(DRACI) + REGISTER_PLUGIN_DYNAMIC(DRACI, PLUGIN_TYPE_ENGINE, DraciMetaEngine); +#else + REGISTER_PLUGIN_STATIC(DRACI, PLUGIN_TYPE_ENGINE, DraciMetaEngine); +#endif diff --git a/engines/draci/draci.cpp b/engines/draci/draci.cpp new file mode 100644 index 0000000000..134b2a3cfa --- /dev/null +++ b/engines/draci/draci.cpp @@ -0,0 +1,284 @@ +/* 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/scummsys.h" + +#include "common/config-manager.h" +#include "common/events.h" +#include "common/file.h" +#include "common/keyboard.h" + +#include "graphics/cursorman.h" +#include "graphics/font.h" + +#include "draci/draci.h" +#include "draci/barchive.h" +#include "draci/script.h" +#include "draci/font.h" +#include "draci/sprite.h" +#include "draci/screen.h" +#include "draci/mouse.h" + +namespace Draci { + +// Data file paths + +const Common::String objectsPath("OBJEKTY.DFW"); +const Common::String palettePath("PALETY.DFW"); +const Common::String spritesPath("OBR_AN.DFW"); +const Common::String overlaysPath("OBR_MAS.DFW"); +const Common::String roomsPath("MIST.DFW"); +const Common::String animationsPath("ANIM.DFW"); +const Common::String iconsPath("HRA.DFW"); +const Common::String walkingMapsPath("MAPY.DFW"); +const Common::String itemsPath("IKONY.DFW"); +const Common::String itemImagesPath("OBR_IK.DFW"); +const Common::String initPath("INIT.DFW"); +const Common::String stringsPath("RETEZCE.DFW"); + +DraciEngine::DraciEngine(OSystem *syst, const ADGameDescription *gameDesc) + : Engine(syst) { + // Put your engine in a sane state, but do nothing big yet; + // in particular, do not load data from files; rather, if you + // need to do such things, do them from init(). + + // Do not initialize graphics here + + // However this is the place to specify all default directories + //Common::File::addDefaultDirectory(_gameDataPath + "sound/"); + + // Here is the right place to set up the engine specific debug levels + Common::addDebugChannel(kDraciGeneralDebugLevel, "general", "Draci general debug info"); + Common::addDebugChannel(kDraciBytecodeDebugLevel, "bytecode", "GPL bytecode instructions"); + Common::addDebugChannel(kDraciArchiverDebugLevel, "archiver", "BAR archiver debug info"); + Common::addDebugChannel(kDraciLogicDebugLevel, "logic", "Game logic debug info"); + Common::addDebugChannel(kDraciAnimationDebugLevel, "animation", "Animation debug info"); + + // Don't forget to register your random source + _eventMan->registerRandomSource(_rnd, "draci"); +} + +int DraciEngine::init() { + // Initialize graphics using following: + initGraphics(kScreenWidth, kScreenHeight, false); + + // Open game's archives + _initArchive = new BArchive(initPath); + _objectsArchive = new BArchive(objectsPath); + _spritesArchive = new BArchive(spritesPath); + _paletteArchive = new BArchive(palettePath); + _roomsArchive = new BArchive(roomsPath); + _overlaysArchive = new BArchive(overlaysPath); + _animationsArchive = new BArchive(animationsPath); + _iconsArchive = new BArchive(iconsPath); + _walkingMapsArchive = new BArchive(walkingMapsPath); + _itemsArchive = new BArchive(itemsPath); + _itemImagesArchive = new BArchive(itemImagesPath); + _stringsArchive = new BArchive(stringsPath); + + // Load the game's fonts + _smallFont = new Font(kFontSmall); + _bigFont = new Font(kFontBig); + + _screen = new Screen(this); + _anims = new AnimationManager(this); + _mouse = new Mouse(this); + _script = new Script(this); + _game = new Game(this); + + if(!_objectsArchive->isOpen()) { + debugC(2, kDraciGeneralDebugLevel, "ERROR - Opening objects archive failed"); + return Common::kUnknownError; + } + + if(!_spritesArchive->isOpen()) { + debugC(2, kDraciGeneralDebugLevel, "ERROR - Opening sprites archive failed"); + return Common::kUnknownError; + } + + if(!_paletteArchive->isOpen()) { + debugC(2, kDraciGeneralDebugLevel, "ERROR - Opening palette archive failed"); + return Common::kUnknownError; + } + + if(!_roomsArchive->isOpen()) { + debugC(2, kDraciGeneralDebugLevel, "ERROR - Opening rooms archive failed"); + return Common::kUnknownError; + } + + if(!_overlaysArchive->isOpen()) { + debugC(2, kDraciGeneralDebugLevel, "ERROR - Opening overlays archive failed"); + return Common::kUnknownError; + } + + if(!_animationsArchive->isOpen()) { + debugC(2, kDraciGeneralDebugLevel, "ERROR - Opening animations archive failed"); + return Common::kUnknownError; + } + + if(!_iconsArchive->isOpen()) { + debugC(2, kDraciGeneralDebugLevel, "ERROR - Opening icons archive failed"); + return Common::kUnknownError; + } + + if(!_walkingMapsArchive->isOpen()) { + debugC(2, kDraciGeneralDebugLevel, "ERROR - Opening walking maps archive failed"); + return Common::kUnknownError; + } + + _showWalkingMap = false; + + // Basic archive test + debugC(2, kDraciGeneralDebugLevel, "Running archive tests..."); + Common::String path("INIT.DFW"); + BArchive ar(path); + BAFile *f; + debugC(3, kDraciGeneralDebugLevel, "Number of file streams in archive: %d", ar.size()); + + if(ar.isOpen()) { + f = ar.getFile(0); + } else { + debugC(2, kDraciGeneralDebugLevel, "ERROR - Archive not opened"); + return Common::kUnknownError; + } + + debugC(3, kDraciGeneralDebugLevel, "First 10 bytes of file %d: ", 0); + for (unsigned int i = 0; i < 10; ++i) { + debugC(3, kDraciGeneralDebugLevel, "0x%02x%c", f->_data[i], (i < 9) ? ' ' : '\n'); + } + + return Common::kNoError; +} + +int DraciEngine::go() { + debugC(1, kDraciGeneralDebugLevel, "DraciEngine::go()"); + + _game->init(); + _game->start(); + + return Common::kNoError; +} + +bool DraciEngine::handleEvents() { + Common::Event event; + bool quit = false; + + while (_eventMan->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_QUIT: + _game->setQuit(true); + break; + case Common::EVENT_KEYDOWN: + if (event.kbd.keycode == Common::KEYCODE_RIGHT) { + _game->setRoomNum(_game->nextRoomNum()); + _game->setGateNum(0); + } + else if (event.kbd.keycode == Common::KEYCODE_LEFT) { + _game->setRoomNum(_game->prevRoomNum()); + _game->setGateNum(0); + } + else if (event.kbd.keycode == Common::KEYCODE_ESCAPE) { + int escRoom = _game->getEscRoom(); + + // Check if there is an escape room defined for the current room + if (escRoom != kNoEscRoom) { + + // Schedule room change + _game->setRoomNum(_game->getEscRoom()); + _game->setGateNum(0); + _game->setExitLoop(true); + + // End any currently running GPL programs + _script->endCurrentProgram(); + } + } + // Show walking map toggle + else if (event.kbd.keycode == Common::KEYCODE_w) { + _showWalkingMap = !_showWalkingMap; + } + else if (event.kbd.keycode == Common::KEYCODE_i) { + if(_game->getLoopStatus() == kStatusInventory && + _game->getLoopSubstatus() == kSubstatusOrdinary) { + _game->inventoryDone(); + } else if (_game->getLoopStatus() == kStatusOrdinary && + _game->getLoopSubstatus() == kSubstatusOrdinary) { + _game->inventoryInit(); + } + } + break; + default: + _mouse->handleEvent(event); + } + } + + // Show walking map overlay + // If the walking map overlay is already in the wanted state don't + // start / stop it constantly + if (_showWalkingMap && !_anims->getAnimation(kWalkingMapOverlay)->isPlaying()) { + _anims->play(kWalkingMapOverlay); + } else if (!_showWalkingMap && _anims->getAnimation(kWalkingMapOverlay)->isPlaying()) { + _anims->stop(kWalkingMapOverlay); + } + + return quit; +} +DraciEngine::~DraciEngine() { + // Dispose your resources here + + // TODO: Investigate possibility of using sharedPtr or similar + + delete _smallFont; + delete _bigFont; + + delete _mouse; + delete _script; + delete _anims; + delete _game; + delete _screen; + + delete _initArchive; + delete _paletteArchive; + delete _objectsArchive; + delete _spritesArchive; + delete _roomsArchive; + delete _overlaysArchive; + delete _animationsArchive; + delete _iconsArchive; + delete _walkingMapsArchive; + delete _itemsArchive; + delete _itemImagesArchive; + delete _stringsArchive; + + // Remove all of our debug levels here + Common::clearAllDebugChannels(); +} + +Common::Error DraciEngine::run() { + init(); + go(); + return Common::kNoError; +} + +} // End of namespace Draci diff --git a/engines/draci/draci.h b/engines/draci/draci.h new file mode 100644 index 0000000000..e2b5390c44 --- /dev/null +++ b/engines/draci/draci.h @@ -0,0 +1,94 @@ +/* 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 DRACI_H +#define DRACI_H + +#include "common/system.h" +#include "engines/engine.h" +#include "engines/advancedDetector.h" + +#include "draci/game.h" +#include "draci/mouse.h" +#include "draci/screen.h" +#include "draci/font.h" +#include "draci/script.h" +#include "draci/barchive.h" +#include "draci/animation.h" + +namespace Draci { + +class DraciEngine : public Engine { +public: + DraciEngine(OSystem *syst, const ADGameDescription *gameDesc); + ~DraciEngine(); + + int init(); + int go(); + Common::Error run(); + + bool hasFeature(Engine::EngineFeature f) const; + + bool handleEvents(); + + Screen *_screen; + Mouse *_mouse; + Game *_game; + Script *_script; + AnimationManager *_anims; + + Font *_smallFont; + Font *_bigFont; + + BArchive *_iconsArchive; + BArchive *_objectsArchive; + BArchive *_spritesArchive; + BArchive *_paletteArchive; + BArchive *_roomsArchive; + BArchive *_overlaysArchive; + BArchive *_animationsArchive; + BArchive *_walkingMapsArchive; + BArchive *_itemsArchive; + BArchive *_itemImagesArchive; + BArchive *_initArchive; + BArchive *_stringsArchive; + + bool _showWalkingMap; + + Common::RandomSource _rnd; +}; + +enum { + kDraciGeneralDebugLevel = 1 << 0, + kDraciBytecodeDebugLevel = 1 << 1, + kDraciArchiverDebugLevel = 1 << 2, + kDraciLogicDebugLevel = 1 << 3, + kDraciAnimationDebugLevel = 1 << 4 +}; + +} // End of namespace Draci + +#endif // DRACI_H + diff --git a/engines/draci/font.cpp b/engines/draci/font.cpp new file mode 100644 index 0000000000..093b8d9d17 --- /dev/null +++ b/engines/draci/font.cpp @@ -0,0 +1,344 @@ +/* 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 "draci/draci.h" +#include "draci/font.h" + +namespace Draci { + +const Common::String kFontSmall("Small.fon"); +const Common::String kFontBig("Big.fon"); + +Font::Font(const Common::String &filename) { + + _fontHeight = 0; + _maxCharWidth = 0; + _charWidths = NULL; + _charData = NULL; + + loadFont(filename); + + _currentFontColour = kFontColour1; +} + +Font::~Font() { + freeFont(); +} + +/** + * @brief Sets the varying font colour + * @param colour The new font colour + */ + +void Font::setColour(uint8 colour) { + _currentFontColour = colour; +} + +/** + * @brief Loads fonts from a file + * @param path Path to font file + * @return true if the font was loaded successfully, false otherwise + * + * Loads fonts from a file into a Font instance. The original game uses two + * fonts (located inside files "Small.fon" and "Big.fon"). The characters in the + * font are indexed from the space character so an appropriate offset must be + * added to convert them to equivalent char values, i.e. kDraciIndexOffset. + * Characters in the higher range are non-ASCII and vary between different + * language versions of the game. + * + * font format: [1 byte] maximum character width + * [1 byte] font height + * [138 bytes] character widths of all 138 characters in the font + * [138 * fontHeight * maxWidth bytes] character data, stored row-wise + */ + +bool Font::loadFont(const Common::String &filename) { + + // Free previously loaded font (if any) + freeFont(); + + Common::File f; + + f.open(filename); + if (f.isOpen()) { + debugC(6, kDraciGeneralDebugLevel, "Opened font file %s", + filename.c_str()); + } else { + debugC(6, kDraciGeneralDebugLevel, "Error opening font file %s", + filename.c_str()); + return false; + } + + _maxCharWidth = f.readByte(); + _fontHeight = f.readByte(); + + // Read in the widths of the glyphs + _charWidths = new uint8[kCharNum]; + for (unsigned int i = 0; i < kCharNum; ++i) { + _charWidths[i] = f.readByte(); + } + + // Calculate size of font data + unsigned int fontDataSize = kCharNum * _maxCharWidth * _fontHeight; + + // Read in all glyphs + _charData = new byte[fontDataSize]; + f.read(_charData, fontDataSize); + + debugC(5, kDraciGeneralDebugLevel, "Font %s loaded", filename.c_str()); + + return true; +} + +void Font::freeFont() { + delete[] _charWidths; + delete[] _charData; +} + +uint8 Font::getCharWidth(uint8 chr) const { + return _charWidths[chr - kCharIndexOffset]; +} + +/** + * @brief Draw a char to a Draci::Surface + * + * @param dst Pointer to the destination surface + * @param chr Character to draw + * @param tx Horizontal offset on the surface + * @param ty Vertical offset on the surface + */ + +void Font::drawChar(Surface *dst, uint8 chr, int tx, int ty) const { + assert(dst != NULL); + assert(tx >= 0); + assert(ty >= 0); + + byte *ptr = (byte *)dst->getBasePtr(tx, ty); + uint8 charIndex = chr - kCharIndexOffset; + int charOffset = charIndex * _fontHeight * _maxCharWidth; + uint8 currentWidth = _charWidths[charIndex]; + + // Determine how many pixels to draw horizontally (to prevent overflow) + int xSpaceLeft = dst->w - tx - 1; + int xPixelsToDraw = (currentWidth < xSpaceLeft) ? currentWidth : xSpaceLeft; + + // Determine how many pixels to draw vertically + int ySpaceLeft = dst->h - ty - 1; + int yPixelsToDraw = (_fontHeight < ySpaceLeft) ? _fontHeight : ySpaceLeft; + + int _transparent = dst->getTransparentColour(); + + for (int y = 0; y < yPixelsToDraw; ++y) { + for (int x = 0; x <= xPixelsToDraw; ++x) { + + int curr = y * _maxCharWidth + x; + int colour = _charData[charOffset + curr]; + + // If pixel is transparent, skip it + if (colour == _transparent) + continue; + + // Replace colour with font colours + switch (colour) { + + case 254: + colour = _currentFontColour; + break; + + case 253: + colour = kFontColour2; + break; + + case 252: + colour = kFontColour3; + break; + + case 251: + colour = kFontColour4; + break; + } + + // Paint the pixel + ptr[x] = colour; + } + + // Advance to next row + ptr += dst->pitch; + } +} + +/** + * @brief Draw a string to a Draci::Surface + * + * @param dst Pointer to the destination surface + * @param str Buffer containing string data + * @param len Length of the data + * @param x Horizontal offset on the surface + * @param y Vertical offset on the surface + * @param spacing Space to leave between individual characters. Defaults to 0. + */ + +void Font::drawString(Surface *dst, const byte *str, uint len, + int x, int y, int spacing, bool markDirty) const { + drawString(dst, Common::String((const char *)str, len), x, y, spacing, markDirty); +} + +/** + * @brief Draw a string to a Draci::Surface + * + * @param dst Pointer to the destination surface + * @param str String to draw + * @param x Horizontal offset on the surface + * @param y Vertical offset on the surface + * @param spacing Space to leave between individual characters. Defaults to 0. + */ + +void Font::drawString(Surface *dst, const Common::String &str, + int x, int y, int spacing, bool markDirty) const { + assert(dst != NULL); + assert(x >= 0); + assert(y >= 0); + + uint widest = getStringWidth(str, spacing); + + int curx = x + (widest - getLineWidth(str, 0, spacing)) / 2; + int cury = y; + + for (uint i = 0; i < str.size(); ++i) { + + // If we encounter the '|' char (newline and end of string marker), + // skip it and go to the start of the next line + if (str[i] == '|') { + cury += getFontHeight(); + curx = x + (widest - getLineWidth(str, i+1, spacing) - 1) / 2; + continue; + } + + // Break early if there's no more space on the screen + if (curx >= dst->w - 1 || cury >= dst->h - 1) { + break; + } + + drawChar(dst, str[i], curx, cury); + curx += getCharWidth(str[i]) + spacing; + } + + if (markDirty) { + Common::Rect r(x, y, x + widest, y + getStringHeight(str)); + dst->markDirtyRect(r); + } +} + +/** + * @brief Calculate the width of a string when drawn in the current font + * + * @param str String to draw + * @param spacing Space to leave between individual characters. Defaults to 0. + * + * @return The calculated width of the string + */ + +uint Font::getStringWidth(const Common::String &str, int spacing) const { + unsigned int width = 0; + + // Real length, including '|' separators + uint len = str.size(); + + for (unsigned int i = 0, tmp = 0; i < len; ++i) { + + if (str[i] != '|') { + uint8 charIndex = str[i] - kCharIndexOffset; + tmp += _charWidths[charIndex]; + tmp += spacing; + } + + // Newline char encountered, skip it and store the new length if it is greater. + // Also, all strings in the data files should end with '|' but not all do. + // This is why we check whether we are at the last char too. + if (str[i] == '|' || i == len - 1) { + if (tmp > width) { + width = tmp; + } + + tmp = 0; + } + } + + return width + 1; +} + +uint Font::getLineWidth(const Common::String &str, uint startIndex, int spacing) const { + + uint width = 0; + + // If the index is greater or equal to the string size, + // the width of the line is 0 + if (startIndex >= str.size()) + return 0; + + for (uint i = startIndex; i < str.size(); ++i) { + + // EOL encountered + if (str[i] == '|') + break; + + // Add width of the current char + uint8 charIndex = str[i] - kCharIndexOffset; + width += _charWidths[charIndex]; + width += spacing; + } + + return width; +} + +/** + * @brief Calculate the height of a string by counting the number of '|' chars (which + * are used as newline characters and end-of-string markers) + * + * @param str String to draw + * @param spacing Space to leave between individual characters. Defaults to 0. + * + * @return The calculated height of the string + */ + + +uint Font::getStringHeight(const Common::String &str) const { + uint len = str.size(); + int separators = 0; + + for (unsigned int i = 0; i < len; ++i) { + // All strings in the data files should end with '|' but not all do. + // This is why we check whether we are at the last char too. + if (str[i] == '|' || i == len - 1) { + ++separators; + } + } + + return separators * getFontHeight(); +} + +} // End of namespace Draci diff --git a/engines/draci/font.h b/engines/draci/font.h new file mode 100644 index 0000000000..5258108add --- /dev/null +++ b/engines/draci/font.h @@ -0,0 +1,105 @@ +/* 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 DRACI_FONT_H +#define DRACI_FONT_H + +#include "graphics/font.h" + +namespace Draci { + +extern const Common::String kFontSmall; +extern const Common::String kFontBig; + +/** + * Default font colours. They all seem to remain constant except for the + * first one which varies depending on the character speaking. + * kOverFontColour is set to transparent. + * TODO: Find out what kFontColour1 should actually be when the game starts + */ +enum { + kFontColour1 = 2, kFontColour2 = 0, + kFontColour3 = 3, kFontColour4 = 4, + kOverFontColour = 255, kTitleColour = 255, + kLineActiveColour = 254, kLineInactiveColour = 255 +}; + +/** + * Represents the game's fonts. See docs for setFont() for font format details. + */ + +class Font { + +public: + + Font(const Common::String &filename); + ~Font(); + + bool loadFont(const Common::String &filename); + uint8 getFontHeight() const { return _fontHeight; }; + uint8 getMaxCharWidth() const { return _maxCharWidth; }; + uint8 getCharWidth(byte chr) const; + void drawChar(Surface *dst, uint8 chr, int tx, int ty) const; + + void drawString(Surface *dst, const byte *str, uint len, int x, int y, + int spacing, bool markDirty = true) const; + void drawString(Surface *dst, const Common::String &str, + int x, int y, int spacing, bool markDirty = true) const; + + uint getStringWidth(const Common::String &str, int spacing = 0) const; + uint getStringHeight(const Common::String &str) const; + uint getLineWidth(const Common::String &str, uint startIndex, int spacing = 0) const; + + void setColour(uint8 colour); + +private: + uint8 _fontHeight; + uint8 _maxCharWidth; + + /** Pointer to an array of individual char widths */ + uint8 *_charWidths; + + /** Pointer to a raw byte array representing font pixels stored row-wise */ + byte *_charData; + + /** Number of glyphs in the font */ + static const unsigned int kCharNum = 138; + + /** + * Chars are indexed from the space character so this should be subtracted + * to get the index of a glyph + */ + static const unsigned int kCharIndexOffset = 32; + + /** The varying font colour; initially set to kFontColour1 */ + uint8 _currentFontColour; + + /** Internal function for freeing fonts when destructing/loading another */ + void freeFont(); +}; + +} // End of namespace Draci + +#endif // DRACI_FONT_H diff --git a/engines/draci/game.cpp b/engines/draci/game.cpp new file mode 100644 index 0000000000..9c68d00907 --- /dev/null +++ b/engines/draci/game.cpp @@ -0,0 +1,1695 @@ +/* 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/stream.h" + +#include "draci/draci.h" +#include "draci/game.h" +#include "draci/barchive.h" +#include "draci/script.h" +#include "draci/animation.h" + +#include <cmath> + +namespace Draci { + +const Common::String dialoguePath("ROZH"); + +static double real_to_double(byte real[6]); + +Game::Game(DraciEngine *vm) : _vm(vm) { + unsigned int i; + + BArchive *initArchive = _vm->_initArchive; + BAFile *file; + + // Read in persons + + file = initArchive->getFile(5); + Common::MemoryReadStream personData(file->_data, file->_length); + + unsigned int numPersons = file->_length / personSize; + _persons = new Person[numPersons]; + + for (i = 0; i < numPersons; ++i) { + _persons[i]._x = personData.readUint16LE(); + _persons[i]._y = personData.readUint16LE(); + _persons[i]._fontColour = personData.readByte(); + } + + // Close persons file + file->close(); + + // Read in dialogue offsets + + file = initArchive->getFile(4); + Common::MemoryReadStream dialogueData(file->_data, file->_length); + + unsigned int numDialogues = file->_length / sizeof(uint16); + _dialogueOffsets = new uint[numDialogues]; + + unsigned int curOffset; + for (i = 0, curOffset = 0; i < numDialogues; ++i) { + _dialogueOffsets[i] = curOffset; + curOffset += dialogueData.readUint16LE(); + } + + _dialogueVars = new int[curOffset]; + memset(_dialogueVars, 0, sizeof (int) * curOffset); + + // Close dialogues file + file->close(); + + // Read in game info + + file = initArchive->getFile(3); + Common::MemoryReadStream gameData(file->_data, file->_length); + + _info._startRoom = gameData.readByte() - 1; + _info._mapRoom = gameData.readByte() - 1; + _info._numObjects = gameData.readUint16LE(); + _info._numItems = gameData.readUint16LE(); + _info._numVariables = gameData.readByte(); + _info._numPersons = gameData.readByte(); + _info._numDialogues = gameData.readByte(); + _info._maxItemWidth = gameData.readUint16LE(); + _info._maxItemHeight = gameData.readUint16LE(); + _info._musicLength = gameData.readUint16LE(); + _info._crc[0] = gameData.readUint16LE(); + _info._crc[1] = gameData.readUint16LE(); + _info._crc[2] = gameData.readUint16LE(); + _info._crc[3] = gameData.readUint16LE(); + + _info._numDialogueBlocks = curOffset; + + // Close game info file + file->close(); + + // Read in variables + + file = initArchive->getFile(2); + unsigned int numVariables = file->_length / sizeof (int16); + + _variables = new int[numVariables]; + Common::MemoryReadStream variableData(file->_data, file->_length); + + for (i = 0; i < numVariables; ++i) { + _variables[i] = variableData.readUint16LE(); + } + + // Close variables file + file->close(); + + // Read in item icon status + + file = initArchive->getFile(1); + _itemStatus = file->_data; + uint numItems = file->_length; + _items = new GameItem[numItems]; + + // Read in object status + + file = initArchive->getFile(0); + unsigned int numObjects = file->_length; + + _objects = new GameObject[numObjects]; + Common::MemoryReadStream objStatus(file->_data, file->_length); + + for (i = 0; i < numObjects; ++i) { + byte tmp = objStatus.readByte(); + + // Set object visibility + _objects[i]._visible = tmp & (1 << 7); + + // Set object location + _objects[i]._location = (~(1 << 7) & tmp) - 1; + } + + // Close object status file + file->close(); + + assert(numDialogues == _info._numDialogues); + assert(numPersons == _info._numPersons); + assert(numVariables == _info._numVariables); + assert(numObjects == _info._numObjects); + assert(numItems == _info._numItems); +} + +void Game::start() { + while (!shouldQuit()) { + + // Whenever the top-level loop is entered, it should not finish unless + // the exit is triggered by a script + _shouldExitLoop = false; + + // If the scheduled room differs from the current one, do a room change + if (_newRoom != _currentRoom._roomNum) { + + // Set the first two variables to the new room / gate + // Before setting these variables we have to convert the values to + // 1-based indexing because this is how everything is stored in the data files + _variables[0] = _newGate + 1; + _variables[1] = _newRoom + 1; + + // If the new room is the map room, set the appropriate coordinates + // for the dragon in the persons array + if (_newRoom == _info._mapRoom) { + _persons[kDragonObject]._x = 160; + _persons[kDragonObject]._y = 0; + } + + setLoopSubstatus(kSubstatusOrdinary); + + // Do the actual change + changeRoom(_newRoom); + + // Set the current room / gate to the new value + _currentRoom._roomNum = _newRoom; + _currentGate = _newGate; + + // Run the program for the gate the dragon came through + runGateProgram(_newGate); + + // Set cursor state + // Need to do this after we set the palette since the cursors use it + if (_currentRoom._mouseOn) { + debugC(6, kDraciLogicDebugLevel, "Mouse: ON"); + _vm->_mouse->cursorOn(); + } else { + debugC(6, kDraciLogicDebugLevel, "Mouse: OFF"); + _vm->_mouse->cursorOff(); + } + } + + // Mimic the original engine by setting the loop status to Ordinary before + // entering the main loop + setLoopStatus(kStatusOrdinary); + + loop(); + } +} + +void Game::init() { + _shouldQuit = false; + _shouldExitLoop = false; + _scheduledPalette = 0; + + _animUnderCursor = kOverlayImage; + + _currentItem = kNoItem; + _itemUnderCursor = kNoItem; + + _vm->_mouse->setCursorType(kNormalCursor); + + _loopStatus = kStatusOrdinary; + _objUnderCursor = kOverlayImage; + + // Set the inventory to empty initially + memset(_inventory, kNoItem, kInventorySlots * sizeof (int)); + + // Initialize animation for object / room titles + Animation *titleAnim = _vm->_anims->addText(kTitleText, true); + Text *title = new Text("", _vm->_smallFont, kTitleColour, 0, 0); + titleAnim->addFrame(title); + + // Initialize animation for speech text + Animation *speechAnim = _vm->_anims->addText(kSpeechText, true); + Text *speech = new Text("", _vm->_bigFont, kFontColour1, 0, 0); + speechAnim->addFrame(speech); + + // Initialize inventory animation + BAFile *f = _vm->_iconsArchive->getFile(13); + Animation *inventoryAnim = _vm->_anims->addAnimation(kInventorySprite, 255, false); + Sprite *inventorySprite = new Sprite(f->_data, f->_length, 0, 0, true); + inventoryAnim->addFrame(inventorySprite); + inventoryAnim->setRelative((kScreenWidth - inventorySprite->getWidth()) / 2, + (kScreenHeight - inventorySprite->getHeight()) / 2); + + for (uint i = 0; i < kDialogueLines; ++i) { + _dialogueAnims[i] = _vm->_anims->addText(kDialogueLinesID - i, true); + Text *dialogueLine = new Text("", _vm->_smallFont, kLineInactiveColour, 0, 0); + _dialogueAnims[i]->addFrame(dialogueLine); + + _dialogueAnims[i]->setZ(254); + _dialogueAnims[i]->setRelative(1, + kScreenHeight - (i + 1) * _vm->_smallFont->getFontHeight()); + + Text *text = reinterpret_cast<Text *>(_dialogueAnims[i]->getFrame()); + text->setText(""); + } + + for (uint i = 0; i < _info._numItems; ++i) { + loadItem(i); + } + + loadObject(kDragonObject); + + GameObject *dragon = getObject(kDragonObject); + debugC(4, kDraciLogicDebugLevel, "Running init program for the dragon object..."); + _vm->_script->run(dragon->_program, dragon->_init); + + _currentRoom._roomNum = _info._startRoom; + _currentGate = 0; + + _newRoom = _currentRoom._roomNum; + _newGate = _currentGate; + + // Before setting these variables we have to convert the values to 1-based indexing + // because this is how everything is stored in the data files + _variables[0] = _currentGate + 1; + _variables[1] = _currentRoom._roomNum + 1; + + changeRoom(_currentRoom._roomNum); + runGateProgram(_currentGate); +} + +void Game::loop() { + + Surface *surface = _vm->_screen->getSurface(); + + do { + + debugC(4, kDraciLogicDebugLevel, "loopstatus: %d, loopsubstatus: %d", + _loopStatus, _loopSubstatus); + + _vm->handleEvents(); + + // Fetch mouse coordinates + int x = _vm->_mouse->getPosX(); + int y = _vm->_mouse->getPosY(); + + if (_loopStatus == kStatusDialogue && _loopSubstatus == kSubstatusOrdinary) { + + Text *text; + for (int i = 0; i < kDialogueLines; ++i) { + text = reinterpret_cast<Text *>(_dialogueAnims[i]->getFrame()); + + if (_animUnderCursor == _dialogueAnims[i]->getID()) { + text->setColour(kLineActiveColour); + } else { + text->setColour(kLineInactiveColour); + } + } + + if (_vm->_mouse->lButtonPressed() || _vm->_mouse->rButtonPressed()) { + _shouldExitLoop = true; + _vm->_mouse->lButtonSet(false); + _vm->_mouse->rButtonSet(false); + } + } + + if(_vm->_mouse->isCursorOn()) { + + // Fetch the dedicated objects' title animation / current frame + Animation *titleAnim = _vm->_anims->getAnimation(kTitleText); + Text *title = reinterpret_cast<Text *>(titleAnim->getFrame()); + + updateCursor(); + updateTitle(); + + if (_loopStatus == kStatusOrdinary && _loopSubstatus == kSubstatusOrdinary) { + + if (_vm->_mouse->lButtonPressed()) { + _vm->_mouse->lButtonSet(false); + + if (_currentItem != kNoItem) { + putItem(_currentItem, 0); + _currentItem = kNoItem; + updateCursor(); + } else { + if (_objUnderCursor != kObjectNotFound) { + GameObject *obj = &_objects[_objUnderCursor]; + + _vm->_mouse->cursorOff(); + titleAnim->markDirtyRect(surface); + title->setText(""); + _objUnderCursor = kObjectNotFound; + + if (!obj->_imLook) { + if (obj->_lookDir == -1) { + walkHero(x, y); + } else { + walkHero(obj->_lookX, obj->_lookY); + } + } + + _vm->_script->run(obj->_program, obj->_look); + _vm->_mouse->cursorOn(); + } else { + walkHero(x, y); + } + } + } + + if (_vm->_mouse->rButtonPressed()) { + _vm->_mouse->rButtonSet(false); + + if (_objUnderCursor != kObjectNotFound) { + GameObject *obj = &_objects[_objUnderCursor]; + + if (_vm->_script->testExpression(obj->_program, obj->_canUse)) { + _vm->_mouse->cursorOff(); + titleAnim->markDirtyRect(surface); + title->setText(""); + _objUnderCursor = kObjectNotFound; + + if (!obj->_imUse) { + if (obj->_useDir == -1) { + walkHero(x, y); + } else { + walkHero(obj->_useX, obj->_useY); + } + } + + _vm->_script->run(obj->_program, obj->_use); + _vm->_mouse->cursorOn(); + } else { + walkHero(x, y); + } + } else { + if (_vm->_script->testExpression(_currentRoom._program, _currentRoom._canUse)) { + _vm->_mouse->cursorOff(); + titleAnim->markDirtyRect(surface); + title->setText(""); + + + _vm->_script->run(_currentRoom._program, _currentRoom._use); + _vm->_mouse->cursorOn(); + } else { + walkHero(x, y); + } + } + } + } + + if (_loopStatus == kStatusInventory && _loopSubstatus == kSubstatusOrdinary) { + if (_inventoryExit) { + inventoryDone(); + } + + // If we are in inventory mode, all the animations except game items' + // images will necessarily be paused so we can safely assume that any + // animation under the cursor (a value returned by + // AnimationManager::getTopAnimationID()) will be an item animation or. + // an overlay, for which we check. Item animations have their IDs + // calculated by offseting their itemID from the ID of the last "special" + // animation ID. In this way, we obtain its itemID. + if (_animUnderCursor != kOverlayImage && _animUnderCursor != kInventorySprite) { + _itemUnderCursor = kInventoryItemsID - _animUnderCursor; + } else { + _itemUnderCursor = kNoItem; + } + + // If the user pressed the left mouse button + if (_vm->_mouse->lButtonPressed()) { + _vm->_mouse->lButtonSet(false); + + // If there is an inventory item under the cursor and we aren't + // holding any item, run its look GPL program + if (_itemUnderCursor != kNoItem && _currentItem == kNoItem) { + const GPL2Program &program = _items[_itemUnderCursor]._program; + const int lookOffset = _items[_itemUnderCursor]._look; + + _vm->_script->run(program, lookOffset); + // Otherwise, if we are holding an item, try to place it inside the + // inventory + } else if (_currentItem != kNoItem) { + // FIXME: This should place the item in the nearest inventory slot, + // not the first one available + putItem(_currentItem, 0); + + // Remove it from our hands + _currentItem = kNoItem; + } + } else if (_vm->_mouse->rButtonPressed()) { + _vm->_mouse->rButtonSet(false); + + Animation *inventoryAnim = _vm->_anims->getAnimation(kInventorySprite); + + // If we right-clicked outside the inventory, close it + if (!inventoryAnim->getFrame()->getRect().contains(x, y)) { + inventoryDone(); + + // If there is an inventory item under our cursor + } else if (_itemUnderCursor != kNoItem) { + + // Again, we have two possibilities: + + // The first is that there is no item in our hands. + // In that case, just take the inventory item from the inventory. + if (_currentItem == kNoItem) { + _currentItem = _itemUnderCursor; + removeItem(_itemUnderCursor); + + // The second is that there *is* an item in our hands. + // In that case, run the canUse script for the inventory item + // which will check if the two items are combinable and, finally, + // run the use script for the item. + } else { + const GPL2Program &program = _items[_itemUnderCursor]._program; + const int canUseOffset = _items[_itemUnderCursor]._canUse; + const int useOffset = _items[_itemUnderCursor]._use; + + if (_vm->_script->testExpression(program, canUseOffset)) { + _vm->_script->run(program, useOffset); + } + } + updateCursor(); + } + } + } + } + + debugC(5, kDraciLogicDebugLevel, "Anim under cursor: %d", _animUnderCursor); + + // Handle character talking (if there is any) + if (_loopSubstatus == kSubstatusTalk) { + Animation *speechAnim = _vm->_anims->getAnimation(kSpeechText); + Text *speechFrame = reinterpret_cast<Text *>(speechAnim->getFrame()); + + uint speechDuration = kBaseSpeechDuration + + speechFrame->getLength() * kSpeechTimeUnit / + (128 / 16 + 1); + + // If the current speech text has expired or the user clicked a mouse button, + // advance to the next line of text + if (_vm->_mouse->lButtonPressed() || + _vm->_mouse->rButtonPressed() || + (_vm->_system->getMillis() - _speechTick) >= speechDuration) { + + _shouldExitLoop = true; + _vm->_mouse->lButtonSet(false); + _vm->_mouse->rButtonSet(false); + } + } + + // This returns true if we got a signal to quit the game + if (shouldQuit()) + return; + + // Advance animations and redraw screen + _vm->_anims->drawScene(surface); + _vm->_screen->copyToScreen(); + _vm->_system->delayMillis(20); + + // HACK: Won't be needed once the game loop is implemented properly + _shouldExitLoop = _shouldExitLoop || (_newRoom != _currentRoom._roomNum && + (_loopStatus == kStatusOrdinary || _loopStatus == kStatusGate)); + + } while (!shouldExitLoop()); +} + +void Game::updateCursor() { + + // Fetch mouse coordinates + int x = _vm->_mouse->getPosX(); + int y = _vm->_mouse->getPosY(); + + // Find animation under cursor + _animUnderCursor = _vm->_anims->getTopAnimationID(x, y); + + // If we are inside a dialogue, all we need is to update the ID of the current + // animation under the cursor. This enables us to update the currently selected + // dialogue line (by recolouring it) but still leave the cursor unupdated when + // over background objects. + if (_loopStatus == kStatusDialogue) + return; + + // If we are in inventory mode, we do a different kind of updating that handles + // inventory items and return early + if (_loopStatus == kStatusInventory && _loopSubstatus == kSubstatusOrdinary) { + + if (_currentItem == kNoItem) { + _vm->_mouse->setCursorType(kNormalCursor); + } else { + _vm->_mouse->loadItemCursor(_currentItem); + } + + if (_itemUnderCursor != kNoItem) { + const GPL2Program &program = _items[_itemUnderCursor]._program; + const int canUseOffset = _items[_itemUnderCursor]._canUse; + + if (_vm->_script->testExpression(program, canUseOffset)) { + if (_currentItem == kNoItem) { + _vm->_mouse->setCursorType(kHighlightedCursor); + } else { + _vm->_mouse->loadItemCursor(_currentItem, true); + } + } + } + + return; + } + + // Find the game object under the cursor + // (to be more precise, one that corresponds to the animation under the cursor) + int curObject = getObjectWithAnimation(_animUnderCursor); + + // Update the game object under the cursor + _objUnderCursor = curObject; + if (_objUnderCursor != _oldObjUnderCursor) { + _oldObjUnderCursor = _objUnderCursor; + } + + // Load the appropriate cursor (item image if an item is held or ordinary cursor + // if not) + if (_currentItem == kNoItem) { + _vm->_mouse->setCursorType(kNormalCursor); + } else { + _vm->_mouse->loadItemCursor(_currentItem); + } + + // TODO: Handle main menu + + // If there is no game object under the cursor, try using the room itself + if (_objUnderCursor == kObjectNotFound) { + + if (_vm->_script->testExpression(_currentRoom._program, _currentRoom._canUse)) { + if (_currentItem == kNoItem) { + _vm->_mouse->setCursorType(kHighlightedCursor); + } else { + _vm->_mouse->loadItemCursor(_currentItem, true); + } + } + // If there *is* a game object under the cursor, update the cursor image + } else { + GameObject *obj = &_objects[_objUnderCursor]; + + // If there is no walking direction set on the object (i.e. the object + // is not a gate / exit), test whether it can be used and, if so, + // update the cursor image (highlight it). + if (obj->_walkDir == 0) { + if (_vm->_script->testExpression(obj->_program, obj->_canUse)) { + if (_currentItem == kNoItem) { + _vm->_mouse->setCursorType(kHighlightedCursor); + } else { + _vm->_mouse->loadItemCursor(_currentItem, true); + } + } + // If the walking direction *is* set, the game object is a gate, so update + // the cursor image to the appropriate arrow. + } else { + _vm->_mouse->setCursorType((CursorType)obj->_walkDir); + } + } +} + +void Game::updateTitle() { + + // If we are inside a dialogue, don't update titles + if (_loopStatus == kStatusDialogue) + return; + + // Fetch current surface and height of the small font (used for titles) + Surface *surface = _vm->_screen->getSurface(); + const int smallFontHeight = _vm->_smallFont->getFontHeight(); + + // Fetch mouse coordinates + int x = _vm->_mouse->getPosX(); + int y = _vm->_mouse->getPosY(); + + // Fetch the dedicated objects' title animation / current frame + Animation *titleAnim = _vm->_anims->getAnimation(kTitleText); + Text *title = reinterpret_cast<Text *>(titleAnim->getFrame()); + + // Mark dirty rectangle to delete the previous text + titleAnim->markDirtyRect(surface); + + // If there is no object under the cursor, delete the title. + // Otherwise, show the object's title. + if (_objUnderCursor == kObjectNotFound) { + title->setText(""); + } else { + GameObject *obj = &_objects[_objUnderCursor]; + title->setText(obj->_title); + } + + // Move the title to the correct place (just above the cursor) + int newX = surface->centerOnX(x, title->getWidth()); + int newY = surface->centerOnY(y - smallFontHeight / 2, title->getHeight() * 2); + titleAnim->setRelative(newX, newY); + + // If we are currently playing the title, mark it dirty so it gets updated. + // Otherwise, start playing the title animation. + if (titleAnim->isPlaying()) { + titleAnim->markDirtyRect(surface); + } else { + _vm->_anims->play(titleAnim->getID()); + } +} + +int Game::getObjectWithAnimation(int animID) { + for (uint i = 0; i < _info._numObjects; ++i) { + GameObject *obj = &_objects[i]; + + for (uint j = 0; j < obj->_anims.size(); ++j) { + if (obj->_anims[j] == animID) { + return i; + } + } + } + + return kObjectNotFound; +} + +void Game::removeItem(int itemID) { + + for (uint i = 0; i < kInventorySlots; ++i) { + if (_inventory[i] == itemID) { + _inventory[i] = kNoItem; + _vm->_anims->stop(kInventoryItemsID - itemID); + break; + } + } +} + +void Game::putItem(int itemID, int position) { + + if (itemID == kNoItem) + return; + + uint i = position; + + if (position >= 0 && + position < kInventoryLines * kInventoryColumns && + _inventory[position] == kNoItem) { + _inventory[position] = itemID; + } else { + for (i = 0; i < kInventorySlots; ++i) { + if (_inventory[i] == kNoItem) { + _inventory[i] = itemID; + break; + } + } + } + + const int line = i / kInventoryColumns + 1; + const int column = i % kInventoryColumns + 1; + + Animation *anim = _vm->_anims->getAnimation(kInventoryItemsID - itemID); + Drawable *frame = anim->getFrame(); + + const int x = kInventoryX + + (column * kInventoryItemWidth) - + (kInventoryItemWidth / 2) - + (frame->getWidth() / 2); + + const int y = kInventoryY + + (line * kInventoryItemHeight) - + (kInventoryItemHeight / 2) - + (frame->getHeight() / 2); + + debug(2, "itemID: %d position: %d line: %d column: %d x: %d y: %d", itemID, position, line, column, x, y); + + anim->setRelative(x, y); + + // If we are in inventory mode, we need to play the item animation, immediately + // upon returning it to its slot but *not* in other modes because it should be + // invisible then (along with the inventory) + if (_loopStatus == kStatusInventory && _loopSubstatus == kSubstatusOrdinary) { + _vm->_anims->play(kInventoryItemsID - itemID); + } +} + +void Game::inventoryInit() { + + // Pause all "background" animations + _vm->_anims->pauseAnimations(); + + // Draw the inventory and the current items + inventoryDraw(); + + // Turn cursor on if it is off + _vm->_mouse->cursorOn(); + + // Set the appropriate loop status + _loopStatus = kStatusInventory; + + // TODO: This will be used for exiting the inventory automatically when the mouse + // is outside it for some time + _inventoryExit = false; +} + +void Game::inventoryDone() { + _vm->_mouse->cursorOn(); + _loopStatus = kStatusOrdinary; + + _vm->_anims->unpauseAnimations(); + + _vm->_anims->stop(kInventorySprite); + + for (uint i = 0; i < kInventorySlots; ++i) { + if (_inventory[i] != kNoItem) { + _vm->_anims->stop(kInventoryItemsID - _inventory[i]); + } + } + + // Reset item under cursor + _itemUnderCursor = kNoItem; + + // TODO: Handle main menu +} + +void Game::inventoryDraw() { + + _vm->_anims->play(kInventorySprite); + + for (uint i = 0; i < kInventorySlots; ++i) { + if (_inventory[i] != kNoItem) { + _vm->_anims->play(kInventoryItemsID - _inventory[i]); + } + } +} + +void Game::dialogueMenu(int dialogueID) { + + int oldLines, hit; + + char tmp[5]; + sprintf(tmp, "%d", dialogueID+1); + Common::String ext(tmp); + _dialogueArchive = new BArchive(dialoguePath + ext + ".dfw"); + + debugC(4, kDraciLogicDebugLevel, "Starting dialogue (ID: %d, Archive: %s)", + dialogueID, (dialoguePath + ext + ".dfw").c_str()); + + _currentDialogue = dialogueID; + oldLines = 255; + dialogueInit(dialogueID); + + do { + _dialogueExit = false; + hit = dialogueDraw(); + + debugC(7, kDraciLogicDebugLevel, + "hit: %d, _lines[hit]: %d, lastblock: %d, dialogueLines: %d, dialogueExit: %d", + hit, _lines[hit], _lastBlock, _dialogueLinesNum, _dialogueExit); + + if ((!_dialogueExit) && (hit != -1) && (_lines[hit] != -1)) { + if ((oldLines == 1) && (_dialogueLinesNum == 1) && (_lines[hit] == _lastBlock)) { + break; + } + _currentBlock = _lines[hit]; + runDialogueProg(_dialogueBlocks[_lines[hit]]._program, 1); + } else { + break; + } + _lastBlock = _lines[hit]; + _dialogueVars[_dialogueOffsets[dialogueID] + _lastBlock] += 1; + _dialogueBegin = false; + oldLines = _dialogueLinesNum; + + } while(!_dialogueExit); + + dialogueDone(); + _currentDialogue = kNoDialogue; +} + +int Game::dialogueDraw() { + _dialogueLinesNum = 0; + int i = 0; + int ret = 0; + + Animation *anim; + Text *dialogueLine; + + while ((_dialogueLinesNum < 4) && (i < _blockNum)) { + + GPL2Program blockTest; + blockTest._bytecode = _dialogueBlocks[i]._canBlock; + blockTest._length = _dialogueBlocks[i]._canLen; + debugC(3, kDraciLogicDebugLevel, "Testing dialogue block %d", i); + if (_vm->_script->testExpression(blockTest, 1)) { + anim = _dialogueAnims[_dialogueLinesNum]; + dialogueLine = reinterpret_cast<Text *>(anim->getFrame()); + dialogueLine->setText(_dialogueBlocks[i]._title); + + dialogueLine->setColour(kLineInactiveColour); + _lines[_dialogueLinesNum] = i; + _dialogueLinesNum++; + } + ++i; + } + + for (i = _dialogueLinesNum; i < kDialogueLines; ++i) { + _lines[i] = -1; + anim = _dialogueAnims[i]; + dialogueLine = reinterpret_cast<Text *>(anim->getFrame()); + dialogueLine->setText(""); + } + + _oldObjUnderCursor = kObjectNotFound; + + if (_dialogueLinesNum > 1) { + _vm->_mouse->cursorOn(); + _shouldExitLoop = false; + loop(); + _vm->_mouse->cursorOff(); + + bool notDialogueAnim = true; + for (uint j = 0; j < kDialogueLines; ++j) { + if (_dialogueAnims[j]->getID() == _animUnderCursor) { + notDialogueAnim = false; + break; + } + } + + if (notDialogueAnim) { + ret = -1; + } else { + ret = _dialogueAnims[0]->getID() - _animUnderCursor; + } + } else { + ret = _dialogueLinesNum - 1; + } + + for (i = 0; i < kDialogueLines; ++i) { + dialogueLine = reinterpret_cast<Text *>(_dialogueAnims[i]->getFrame()); + _dialogueAnims[i]->markDirtyRect(_vm->_screen->getSurface()); + dialogueLine->setText(""); + } + + return ret; +} + +void Game::dialogueInit(int dialogID) { + _vm->_mouse->setCursorType(kDialogueCursor); + + _blockNum = _dialogueArchive->size() / 3; + _dialogueBlocks = new Dialogue[_blockNum]; + + BAFile *f; + + for (uint i = 0; i < kDialogueLines; ++i) { + _lines[i] = 0; + } + + for (int i = 0; i < _blockNum; ++i) { + f = _dialogueArchive->getFile(i * 3); + _dialogueBlocks[i]._canLen = f->_length; + _dialogueBlocks[i]._canBlock = f->_data; + + f = _dialogueArchive->getFile(i * 3 + 1); + + // The first byte of the file is the length of the string (without the length) + assert(f->_length - 1 == f->_data[0]); + + _dialogueBlocks[i]._title = Common::String((char *)(f->_data+1), f->_length-1); + + f = _dialogueArchive->getFile(i * 3 + 2); + _dialogueBlocks[i]._program._bytecode = f->_data; + _dialogueBlocks[i]._program._length = f->_length; + } + + for (uint i = 0; i < kDialogueLines; ++i) { + _vm->_anims->play(_dialogueAnims[i]->getID()); + } + + _loopStatus = kStatusDialogue; + _lastBlock = -1; + _dialogueBegin = true; +} + +void Game::dialogueDone() { + for (uint i = 0; i < kDialogueLines; ++i) { + _vm->_anims->stop(_dialogueAnims[i]->getID()); + } + + _dialogueArchive->closeArchive(); + + delete[] _dialogueBlocks; + + _loopStatus = kStatusOrdinary; + _vm->_mouse->setCursorType(kNormalCursor); +} + +void Game::runDialogueProg(GPL2Program prog, int offset) { + + // Mark last animation + int lastAnimIndex = _vm->_anims->getLastIndex(); + + // Run the dialogue program + _vm->_script->run(prog, offset); + + // Delete all animations loaded after the marked one + // (from objects and from the AnimationManager) + for (uint i = 0; i < getNumObjects(); ++i) { + GameObject *obj = &_objects[i]; + + for (uint j = 0; j < obj->_anims.size(); ++j) { + Animation *anim; + + anim = _vm->_anims->getAnimation(obj->_anims[j]); + if (anim != NULL && anim->getIndex() > lastAnimIndex) + obj->_anims.remove_at(j); + } + } + + _vm->_anims->deleteAfterIndex(lastAnimIndex); +} + +bool Game::isDialogueBegin() { + return _dialogueBegin; +} + +bool Game::shouldExitDialogue() { + return _dialogueExit; +} + +void Game::setDialogueExit(bool exit) { + _dialogueExit = exit; +} + +int Game::getDialogueBlockNum() { + return _blockNum; +} + +int Game::getDialogueVar(int dialogueID) { + return _dialogueVars[dialogueID]; +} + +void Game::setDialogueVar(int dialogueID, int value) { + _dialogueVars[dialogueID] = value; +} + +int Game::getCurrentDialogue() { + return _currentDialogue; +} + +int Game::getDialogueLastBlock() { + return _lastBlock; +} + +int Game::getDialogueLinesNum() { + return _dialogueLinesNum; +} + +int Game::getDialogueCurrentBlock() { + return _currentBlock; +} + +int Game::getCurrentDialogueOffset() { + return _dialogueOffsets[_currentDialogue]; +} + +void Game::walkHero(int x, int y) { + + Surface *surface = _vm->_screen->getSurface(); + + Common::Point p = _currentRoom._walkingMap.findNearestWalkable(x, y, surface->getRect()); + + x = p.x; + y = p.y; + + debugC(4, kDraciLogicDebugLevel, "Walk to x: %d y: %d", x, y); + + // Fetch dragon's animation ID + // FIXME: Need to add proper walking (this only warps the dragon to position) + int animID = getObject(kDragonObject)->_anims[0]; + + Animation *anim = _vm->_anims->getAnimation(animID); + + // Calculate scaling factors + double scaleX = _currentRoom._pers0 + _currentRoom._persStep * y; + double scaleY = scaleX; + + // Set the Z coordinate for the dragon's animation + anim->setZ(y+1); + + // Fetch current frame + Drawable *frame = anim->getFrame(); + + // Fetch base dimensions of the frame + uint height = frame->getHeight(); + uint width = frame->getWidth(); + + _persons[kDragonObject]._x = x + (lround(scaleX) * width) / 2; + _persons[kDragonObject]._y = y - lround(scaleY) * height; + + // Set the per-animation scaling factor + anim->setScaleFactors(scaleX, scaleY); + + // We naturally want the dragon to position its feet to the location of the + // click but sprites are drawn from their top-left corner so we subtract + // the current height of the dragon's sprite + y -= (int)(scaleY * height); + //x -= (int)(scaleX * width) / 2; + anim->setRelative(x, y); + + // Play the animation + _vm->_anims->play(animID); +} + +void Game::loadItem(int itemID) { + + BAFile *f = _vm->_itemsArchive->getFile(itemID * 3); + Common::MemoryReadStream itemReader(f->_data, f->_length); + + GameItem *item = _items + itemID; + + item->_init = itemReader.readSint16LE(); + item->_look = itemReader.readSint16LE(); + item->_use = itemReader.readSint16LE(); + item->_canUse = itemReader.readSint16LE(); + item->_imInit = itemReader.readByte(); + item->_imLook = itemReader.readByte(); + item->_imUse = itemReader.readByte(); + + f = _vm->_itemsArchive->getFile(itemID * 3 + 1); + + // The first byte is the length of the string + item->_title = Common::String((const char *)f->_data + 1, f->_length - 1); + assert(f->_data[0] == item->_title.size()); + + f = _vm->_itemsArchive->getFile(itemID * 3 + 2); + + item->_program._bytecode = f->_data; + item->_program._length = f->_length; +} + +void Game::loadRoom(int roomNum) { + + BAFile *f; + f = _vm->_roomsArchive->getFile(roomNum * 4); + Common::MemoryReadStream roomReader(f->_data, f->_length); + + roomReader.readUint32LE(); // Pointer to room program, not used + roomReader.readUint16LE(); // Program length, not used + roomReader.readUint32LE(); // Pointer to room title, not used + + _currentRoom._music = roomReader.readByte(); + + int mapID = roomReader.readByte() - 1; + loadWalkingMap(mapID); + + _currentRoom._palette = roomReader.readByte() - 1; + _currentRoom._numOverlays = roomReader.readSint16LE(); + _currentRoom._init = roomReader.readSint16LE(); + _currentRoom._look = roomReader.readSint16LE(); + _currentRoom._use = roomReader.readSint16LE(); + _currentRoom._canUse = roomReader.readSint16LE(); + _currentRoom._imInit = roomReader.readByte(); + _currentRoom._imLook = roomReader.readByte(); + _currentRoom._imUse = roomReader.readByte(); + _currentRoom._mouseOn = roomReader.readByte(); + _currentRoom._heroOn = roomReader.readByte(); + + // Read in pers0 and persStep (stored as 6-byte Pascal reals) + byte real[6]; + + for (int i = 5; i >= 0; --i) { + real[i] = roomReader.readByte(); + } + + _currentRoom._pers0 = real_to_double(real); + + for (int i = 5; i >= 0; --i) { + real[i] = roomReader.readByte(); + } + + _currentRoom._persStep = real_to_double(real); + + _currentRoom._escRoom = roomReader.readByte() - 1; + _currentRoom._numGates = roomReader.readByte(); + + debugC(4, kDraciLogicDebugLevel, "Music: %d", _currentRoom._music); + debugC(4, kDraciLogicDebugLevel, "Map: %d", mapID); + debugC(4, kDraciLogicDebugLevel, "Palette: %d", _currentRoom._palette); + debugC(4, kDraciLogicDebugLevel, "Overlays: %d", _currentRoom._numOverlays); + debugC(4, kDraciLogicDebugLevel, "Init: %d", _currentRoom._init); + debugC(4, kDraciLogicDebugLevel, "Look: %d", _currentRoom._look); + debugC(4, kDraciLogicDebugLevel, "Use: %d", _currentRoom._use); + debugC(4, kDraciLogicDebugLevel, "CanUse: %d", _currentRoom._canUse); + debugC(4, kDraciLogicDebugLevel, "ImInit: %d", _currentRoom._imInit); + debugC(4, kDraciLogicDebugLevel, "ImLook: %d", _currentRoom._imLook); + debugC(4, kDraciLogicDebugLevel, "ImUse: %d", _currentRoom._imUse); + debugC(4, kDraciLogicDebugLevel, "MouseOn: %d", _currentRoom._mouseOn); + debugC(4, kDraciLogicDebugLevel, "HeroOn: %d", _currentRoom._heroOn); + debugC(4, kDraciLogicDebugLevel, "Pers0: %f", _currentRoom._pers0); + debugC(4, kDraciLogicDebugLevel, "PersStep: %f", _currentRoom._persStep); + debugC(4, kDraciLogicDebugLevel, "EscRoom: %d", _currentRoom._escRoom); + debugC(4, kDraciLogicDebugLevel, "Gates: %d", _currentRoom._numGates); + + // Read in the gates' numbers + + _currentRoom._gates.clear(); + + for (uint i = 0; i < _currentRoom._numGates; ++i) { + _currentRoom._gates.push_back(roomReader.readSint16LE()); + } + + // Load the room's objects + for (uint i = 0; i < _info._numObjects; ++i) { + debugC(7, kDraciLogicDebugLevel, + "Checking if object %d (%d) is at the current location (%d)", i, + _objects[i]._location, roomNum); + + if (_objects[i]._location == roomNum) { + debugC(6, kDraciLogicDebugLevel, "Loading object %d from room %d", i, roomNum); + loadObject(i); + } + } + + // Run the init scripts for room objects + // We can't do this in the above loop because some objects' scripts reference + // other objects that may not yet be loaded + for (uint i = 0; i < _info._numObjects; ++i) { + if (_objects[i]._location == roomNum) { + debugC(6, kDraciLogicDebugLevel, + "Running init program for object %d (offset %d)", i, getObject(i)->_init); + _vm->_script->run(getObject(i)->_program, getObject(i)->_init); + } + } + + // Load the room's GPL program and run the init part + f = _vm->_roomsArchive->getFile(roomNum * 4 + 3); + _currentRoom._program._bytecode = f->_data; + _currentRoom._program._length = f->_length; + + debugC(4, kDraciLogicDebugLevel, "Running room init program..."); + _vm->_script->run(_currentRoom._program, _currentRoom._init); + + // Set room palette + f = _vm->_paletteArchive->getFile(_currentRoom._palette); + _vm->_screen->setPalette(f->_data, 0, kNumColours); + + // HACK: Create a visible overlay from the walking map so we can test it + byte *wlk = new byte[kScreenWidth * kScreenHeight]; + memset(wlk, 255, kScreenWidth * kScreenHeight); + + for (uint i = 0; i < kScreenWidth; ++i) { + for (uint j = 0; j < kScreenHeight; ++j) { + if (_currentRoom._walkingMap.isWalkable(i, j)) { + wlk[j * kScreenWidth + i] = 2; + } + } + } + + Sprite *ov = new Sprite(wlk, kScreenWidth, kScreenHeight, 0, 0, false); + + Animation *map = _vm->_anims->addAnimation(kWalkingMapOverlay, 255, false); + map->addFrame(ov); +} + +int Game::loadAnimation(uint animNum, uint z) { + + BAFile *animFile = _vm->_animationsArchive->getFile(animNum); + Common::MemoryReadStream animationReader(animFile->_data, animFile->_length); + + uint numFrames = animationReader.readByte(); + + // FIXME: handle these properly + animationReader.readByte(); // Memory logic field, not used + animationReader.readByte(); // Disable erasing field, not used + + bool cyclic = animationReader.readByte(); + + animationReader.readByte(); // Relative field, not used + + Animation *anim = _vm->_anims->addAnimation(animNum, z, false); + + anim->setLooping(cyclic); + + for (uint i = 0; i < numFrames; ++i) { + uint spriteNum = animationReader.readUint16LE() - 1; + int x = animationReader.readSint16LE(); + int y = animationReader.readSint16LE(); + uint scaledWidth = animationReader.readUint16LE(); + uint scaledHeight = animationReader.readUint16LE(); + byte mirror = animationReader.readByte(); + /* uint sample = */ animationReader.readUint16LE(); + /* uint freq = */ animationReader.readUint16LE(); + uint delay = animationReader.readUint16LE(); + + BAFile *spriteFile = _vm->_spritesArchive->getFile(spriteNum); + + Sprite *sp = new Sprite(spriteFile->_data, spriteFile->_length, x, y, true); + + // Some frames set the scaled dimensions to 0 even though other frames + // from the same animations have them set to normal values + // We work around this by assuming it means no scaling is necessary + if (scaledWidth == 0) { + scaledWidth = sp->getWidth(); + } + + if (scaledHeight == 0) { + scaledHeight = sp->getHeight(); + } + + sp->setScaled(scaledWidth, scaledHeight); + + if (mirror) + sp->setMirrorOn(); + + sp->setDelay(delay * 10); + + anim->addFrame(sp); + } + + return animNum; +} + +void Game::loadObject(uint objNum) { + BAFile *file; + + file = _vm->_objectsArchive->getFile(objNum * 3); + Common::MemoryReadStream objReader(file->_data, file->_length); + + GameObject *obj = _objects + objNum; + + obj->_init = objReader.readUint16LE(); + obj->_look = objReader.readUint16LE(); + obj->_use = objReader.readUint16LE(); + obj->_canUse = objReader.readUint16LE(); + obj->_imInit = objReader.readByte(); + obj->_imLook = objReader.readByte(); + obj->_imUse = objReader.readByte(); + obj->_walkDir = objReader.readByte() - 1; + obj->_z = objReader.readByte(); + objReader.readUint16LE(); // idxSeq field, not used + objReader.readUint16LE(); // numSeq field, not used + obj->_lookX = objReader.readUint16LE(); + obj->_lookY = objReader.readUint16LE(); + obj->_useX = objReader.readUint16LE(); + obj->_useY = objReader.readUint16LE(); + obj->_lookDir = objReader.readByte() - 1; + obj->_useDir = objReader.readByte() - 1; + + obj->_absNum = objNum; + + file = _vm->_objectsArchive->getFile(objNum * 3 + 1); + + // The first byte of the file is the length of the string (without the length) + assert(file->_length - 1 == file->_data[0]); + + obj->_title = Common::String((char *)(file->_data+1), file->_length-1); + + file = _vm->_objectsArchive->getFile(objNum * 3 + 2); + obj->_program._bytecode = file->_data; + obj->_program._length = file->_length; +} + +void Game::loadWalkingMap(int mapID) { + + BAFile *f; + f = _vm->_walkingMapsArchive->getFile(mapID); + _currentRoom._walkingMap.load(f->_data, f->_length); +} + +GameObject *Game::getObject(uint objNum) { + return _objects + objNum; +} + +uint Game::getNumObjects() { + return _info._numObjects; +} + +void Game::loadOverlays() { + uint x, y, z, num; + + BAFile *overlayHeader; + + overlayHeader = _vm->_roomsArchive->getFile(_currentRoom._roomNum * 4 + 2); + Common::MemoryReadStream overlayReader(overlayHeader->_data, overlayHeader->_length); + BAFile *overlayFile; + + for (int i = 0; i < _currentRoom._numOverlays; i++) { + + num = overlayReader.readUint16LE() - 1; + x = overlayReader.readUint16LE(); + y = overlayReader.readUint16LE(); + z = overlayReader.readByte(); + + overlayFile = _vm->_overlaysArchive->getFile(num); + Sprite *sp = new Sprite(overlayFile->_data, overlayFile->_length, x, y, true); + + _vm->_anims->addOverlay(sp, z); + } + + _vm->_overlaysArchive->clearCache(); + + _vm->_screen->getSurface()->markDirty(); +} + +void Game::changeRoom(uint roomNum) { + + debugC(1, kDraciLogicDebugLevel, "Changing to room %d", roomNum); + + // Clear archives + _vm->_roomsArchive->clearCache(); + _vm->_spritesArchive->clearCache(); + _vm->_paletteArchive->clearCache(); + _vm->_animationsArchive->clearCache(); + _vm->_walkingMapsArchive->clearCache(); + + _vm->_screen->clearScreen(); + + _vm->_anims->deleteOverlays(); + + // Delete walking map testing overlay + _vm->_anims->deleteAnimation(kWalkingMapOverlay); + + int oldRoomNum = _currentRoom._roomNum; + + // TODO: Make objects capable of stopping their own animations + GameObject *dragon = getObject(kDragonObject); + for (uint i = 0; i < dragon->_anims.size(); ++i) { + _vm->_anims->stop(dragon->_anims[i]); + } + + for (uint i = 0; i < _info._numObjects; ++i) { + GameObject *obj = &_objects[i]; + + if (i != 0 && (obj->_location == oldRoomNum)) { + for (uint j = 0; j < obj->_anims.size(); ++j) { + _vm->_anims->deleteAnimation(obj->_anims[j]); + } + obj->_anims.clear(); + } + } + + _currentRoom._roomNum = roomNum; + loadRoom(roomNum); + loadOverlays(); +} + +void Game::runGateProgram(int gate) { + + debugC(6, kDraciLogicDebugLevel, "Running program for gate %d", gate); + + // Set the appropriate loop statu before executing the gate program + setLoopStatus(kStatusGate); + + // Mark last animation + int lastAnimIndex = _vm->_anims->getLastIndex(); + + // Run gate program + _vm->_script->run(_currentRoom._program, _currentRoom._gates[gate]); + + // Delete all animations loaded after the marked one + // (from objects and from the AnimationManager) + for (uint i = 0; i < getNumObjects(); ++i) { + GameObject *obj = &_objects[i]; + + for (uint j = 0; j < obj->_anims.size(); ++j) { + Animation *anim; + + anim = _vm->_anims->getAnimation(obj->_anims[j]); + if (anim != NULL && anim->getIndex() > lastAnimIndex) + obj->_anims.remove_at(j); + } + } + + _vm->_anims->deleteAfterIndex(lastAnimIndex); + + setExitLoop(false); +} + +int Game::getRoomNum() { + return _currentRoom._roomNum; +} + +void Game::setRoomNum(int room) { + _newRoom = room; +} + +int Game::getGateNum() { + return _currentGate; +} + +void Game::setGateNum(int gate) { + _newGate = gate; +} + +void Game::setLoopStatus(LoopStatus status) { + _loopStatus = status; +} + +void Game::setLoopSubstatus(LoopSubstatus status) { + _loopSubstatus = status; +} + +LoopStatus Game::getLoopStatus() { + return _loopStatus; +} + +LoopSubstatus Game::getLoopSubstatus() { + return _loopSubstatus; +} + +int Game::getVariable(int numVar) { + return _variables[numVar]; +} + +void Game::setVariable(int numVar, int value) { + _variables[numVar] = value; +} + +int Game::getItemStatus(int itemID) { + return _itemStatus[itemID]; +} + +void Game::setItemStatus(int itemID, int status) { + _itemStatus[itemID] = status; +} + +int Game::getCurrentItem() { + return _currentItem; +} + +void Game::setCurrentItem(int itemID) { + _currentItem = itemID; +} + +Person *Game::getPerson(int personID) { + return &_persons[personID]; +} + +void Game::setSpeechTick(uint tick) { + _speechTick = tick; +} + +int Game::getEscRoom() { + return _currentRoom._escRoom; +} + +void Game::schedulePalette(int paletteID) { + _scheduledPalette = paletteID; +} + +int Game::getScheduledPalette() { + return _scheduledPalette; +} + +/** + * The GPL command Mark sets the animation index (which specifies the order in which + * animations were loaded in) which is then used by the Release command to delete + * all animations that have an index greater than the one marked. + */ + +int Game::getMarkedAnimationIndex() { + return _markedAnimationIndex; +} + +/** + * See Game::getMarkedAnimationIndex(). + */ + +void Game::setMarkedAnimationIndex(int index) { + _markedAnimationIndex = index; +} + +Game::~Game() { + delete[] _persons; + delete[] _variables; + delete[] _dialogueOffsets; + delete[] _objects; + delete[] _items; +} + + +bool WalkingMap::isWalkable(int x, int y) { + + // Convert to map pixels + x = x / _deltaX; + y = y / _deltaY; + + int pixelIndex = _mapWidth * y + x; + int byteIndex = pixelIndex / 8; + int mapByte = _data[byteIndex]; + + return mapByte & (1 << pixelIndex % 8); +} + +/** + * @brief For a given point, find a nearest walkable point on the walking map + * + * @param startX x coordinate of the point + * @param startY y coordinate of the point + * + * @return A Common::Point representing the nearest walkable point + * + * The algorithm was copied from the original engine for exactness. + * TODO: Study this algorithm in more detail so it can be documented properly and + * possibly improved / simplified. + */ +Common::Point WalkingMap::findNearestWalkable(int startX, int startY, Common::Rect searchRect) { + + // If the starting point is walkable, just return that + if (searchRect.contains(startX, startY) && isWalkable(startX, startY)) { + return Common::Point(startX, startY); + } + + + int signs[] = { 1, -1 }; + const uint kSignsNum = 2; + + int radius = 0; + int x, y; + int dx, dy; + int prediction; + + // The place where, eventually, the result coordinates will be stored + int finalX, finalY; + + // The algorithm appears to start off with an ellipse with the minor radius equal to + // zero and the major radius equal to the walking map delta (the number of pixels + // one map pixel represents). It then uses a heuristic to gradually reshape it into + // a circle (by shortening the major radius and lengthening the minor one). At each + // such resizing step, it checks some select points on the ellipse for walkability. + // It also does the same check for the ellipse perpendicular to it (rotated by 90 degrees). + + while(1) { + + // The default major radius + radius += _deltaX; + + // The ellipse radii (minor, major) that get resized + x = 0; + y = radius; + + // Heuristic variables + prediction = 1 - radius; + dx = 3; + dy = 2 * radius - 2; + + do { + + // The following two loops serve the purpose of checking the points on the two + // ellipses for walkability. The signs[] array is there to obliterate the need + // of writing out all combinations manually. + + for (uint i = 0; i < kSignsNum; ++i) { + finalY = startY + y * signs[i]; + + for (uint j = 0; j < kSignsNum; ++j) { + finalX = startX + x * signs[j]; + + // If the current point is walkable, return it + if (searchRect.contains(finalX, finalY) && isWalkable(finalX, finalY)) { + return Common::Point(finalX, finalY); + } + } + } + + if (x == y) { + // If the starting point is walkable, just return that + if (searchRect.contains(finalX, finalY) && isWalkable(finalX, finalY)) { + return Common::Point(finalX, finalY); + } + } + + for (uint i = 0; i < kSignsNum; ++i) { + finalY = startY + x * signs[i]; + + for (uint j = 0; j < kSignsNum; ++j) { + finalX = startX + y * signs[j]; + + // If the current point is walkable, return it + if (searchRect.contains(finalX, finalY) && isWalkable(finalX, finalY)) { + return Common::Point(finalX, finalY); + } + } + } + + // If prediction is non-negative, we need to decrease the major radius of the + // ellipse + if (prediction >= 0) { + prediction -= dy; + dy -= 2 * _deltaX; + y -= _deltaX; + } + + // Increase the minor radius of the ellipse and update heuristic variables + prediction += dx; + dx += 2 * _deltaX; + x += _deltaX; + + // If the current ellipse has been reshaped into a circle, + // end this loop and enlarge the radius + } while (x <= y); + } +} + +static double real_to_double(byte real[6]) { + + // Extract sign bit + int sign = real[0] & (1 << 7); + + // Extract exponent and adjust for bias + int exp = real[5] - 129; + + double mantissa; + double tmp = 0.0; + + if (real[5] == 0) { + mantissa = 0.0; + } else { + + // Process the first four least significant bytes + for (int i = 4; i >= 1; --i) { + tmp += real[i]; + tmp /= 1 << 8; + } + + // Process the most significant byte (remove the sign bit) + tmp += real[0] & ((1 << 7) - 1); + tmp /= 1 << 8; + + // Calculate mantissa + mantissa = 1.0; + mantissa += 2.0 * tmp; + } + + // Flip sign if necessary + if (sign) { + mantissa = -mantissa; + } + + // Calculate final value + return ldexp(mantissa, exp); +} + +} diff --git a/engines/draci/game.h b/engines/draci/game.h new file mode 100644 index 0000000000..0197b167b6 --- /dev/null +++ b/engines/draci/game.h @@ -0,0 +1,397 @@ +/* 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 DRACI_GAME_H +#define DRACI_GAME_H + +#include "common/str.h" +#include "draci/barchive.h" +#include "draci/script.h" +#include "draci/animation.h" +#include "draci/sprite.h" + +namespace Draci { + +class DraciEngine; + +enum { + kDragonObject = 0 +}; + +enum StructSizes { + personSize = sizeof(uint16) * 2 + sizeof(byte) +}; + + +// Used as a return value for Game::getObjectWithAnimation() if no object +// owns the animation in question +enum { + kObjectNotFound = -1 +}; + +// Used as the value of the _escRoom field of the current room if there is +// no escape room defined +enum { + kNoEscRoom = -1 +}; + +// Used as a value to Game::_currentIcon and means there is no item selected +// and a "real" cursor image is used +enum { + kNoItem = -1 +}; + +// Used as a default parameter in Game::loadWalkingMap() to specify that the default +// walking map to the room is to be loaded. +enum { + kDefaultRoomMap = -1 +}; + +enum { + kNoDialogue = -1, kDialogueLines = 4 +}; + +enum { + kBlackPalette = -1 +}; + +enum SpeechConstants { + kBaseSpeechDuration = 200, + kSpeechTimeUnit = 400 +}; + +/** Inventory related magical constants */ +enum InventoryConstants { + kInventoryItemWidth = 25, + kInventoryItemHeight = 25, + kInventoryColumns = 7, + kInventoryLines = 5, + kInventoryX = 70, //!< Used for positioning of the inventory sprite on the X axis + kInventoryY = 30, //!< Used for positioning of the inventory sprite on the Y axis + kInventorySlots = kInventoryLines * kInventoryColumns +}; + +class WalkingMap { + +public: + WalkingMap() { + _realWidth = 0; + _realHeight = 0; + _mapWidth = 0; + _mapHeight = 0; + _byteWidth = 0; + _data = NULL; + } + + void load(byte *data, uint length) { + Common::MemoryReadStream mapReader(data, length); + + _realWidth = mapReader.readUint16LE(); + _realHeight = mapReader.readUint16LE(); + _deltaX = mapReader.readUint16LE(); + _deltaY = mapReader.readUint16LE(); + _mapWidth = mapReader.readUint16LE(); + _mapHeight = mapReader.readUint16LE(); + _byteWidth = mapReader.readUint16LE(); + + // Set the data pointer to raw map data + _data = data + mapReader.pos(); + } + + bool isWalkable(int x, int y); + Common::Point findNearestWalkable(int x, int y, Common::Rect searchRect); + +private: + int _realWidth, _realHeight; + int _deltaX, _deltaY; + int _mapWidth, _mapHeight; + int _byteWidth; + byte *_data; +}; + +struct GameObject { + + uint _init, _look, _use, _canUse; + bool _imInit, _imLook, _imUse; + int _walkDir; + byte _z; + uint _lookX, _lookY, _useX, _useY; + int _lookDir, _useDir; + uint _absNum; + Common::Array<int> _anims; + GPL2Program _program; + Common::String _title; + int _location; + bool _visible; +}; + +struct GameInfo { + int _startRoom; + int _mapRoom; + uint _numObjects; + uint _numItems; + byte _numVariables; + byte _numPersons; + byte _numDialogues; + uint _maxItemWidth, _maxItemHeight; + uint _musicLength; + uint _crc[4]; + uint _numDialogueBlocks; +}; + +struct GameItem { + uint _init, _look, _use, _canUse; + bool _imInit, _imLook, _imUse; + GPL2Program _program; + Common::String _title; +}; + +struct Person { + uint _x, _y; + byte _fontColour; +}; + +struct Dialogue { + int _canLen; + byte *_canBlock; + Common::String _title; + GPL2Program _program; +}; + +struct Room { + int _roomNum; + byte _music; + WalkingMap _walkingMap; + byte _palette; + int _numOverlays; + int _init, _look, _use, _canUse; + bool _imInit, _imLook, _imUse; + bool _mouseOn, _heroOn; + double _pers0, _persStep; + int _escRoom; + byte _numGates; + Common::Array<int> _gates; + GPL2Program _program; +}; + +enum LoopStatus { + kStatusOrdinary, + kStatusGate, + kStatusInventory, + kStatusDialogue +}; + +enum LoopSubstatus { + kSubstatusOrdinary, + kSubstatusTalk, + kSubstatusFade, + kSubstatusStrange +}; + +/** + * Enumerates the animations for the dragon's movement. + */ + +enum Movement { + kMoveUndefined, kMoveDown, kMoveUp, kMoveRight, kMoveLeft, + kMoveRightDown, kMoveRightUp, kMoveLeftDown, kMoveLeftUp, + kMoveDownRight, kMoveUpRight, kMoveDownLeft, kMoveUpLeft, + kMoveLeftRight, kMoveRightLeft, kMoveUpStopLeft, kMoveUpStopRight, + kSpeakRight, kSpeakLeft, kStopRight, kStopLeft +}; + +class Game { + +public: + + Game(DraciEngine *vm); + ~Game(); + + void init(); + void start(); + void loop(); + + void changeRoom(uint roomNum); + + // HACK: this is only for testing + int nextRoomNum() { + int n = _currentRoom._roomNum; + n = n < 37 ? n+1 : n; + + // disable former distributor logo + if (n == 30) + ++n; + + return n; + } + + // HACK: same as above + int prevRoomNum() { + int n = _currentRoom._roomNum; + n = n > 0 ? n-1 : n; + + // disable former distributor logo + if (n == 30) + --n; + + return n; + } + + void walkHero(int x, int y); + + void loadRoom(int roomNum); + int loadAnimation(uint animNum, uint z); + void loadOverlays(); + void loadObject(uint numObj); + void loadWalkingMap(int mapID = kDefaultRoomMap); + void loadItem(int itemID); + + uint getNumObjects(); + GameObject *getObject(uint objNum); + int getObjectWithAnimation(int animID); + + int getVariable(int varNum); + void setVariable(int varNum, int value); + + Person *getPerson(int personID); + + int getRoomNum(); + void setRoomNum(int room); + + int getGateNum(); + void setGateNum(int gate); + + int getItemStatus(int itemID); + void setItemStatus(int itemID, int status); + int getCurrentItem(); + void setCurrentItem(int itemID); + void removeItem(int itemID); + void putItem(int itemID, int position); + void addItem(int itemID); + + int getEscRoom(); + + int getMarkedAnimationIndex(); + void setMarkedAnimationIndex(int index); + + void setLoopStatus(LoopStatus status); + void setLoopSubstatus(LoopSubstatus status); + LoopStatus getLoopStatus(); + LoopSubstatus getLoopSubstatus(); + + bool shouldQuit() { return _shouldQuit; } + void setQuit(bool quit) { _shouldQuit = quit; } + + bool shouldExitLoop() { return _shouldExitLoop; } + void setExitLoop(bool exit) { _shouldExitLoop = exit; } + + void runGateProgram(int gate); + + void setSpeechTick(uint tick); + + void updateTitle(); + void updateCursor(); + + void inventoryInit(); + void inventoryDraw(); + void inventoryDone(); + + void dialogueMenu(int dialogueID); + int dialogueDraw(); + void dialogueInit(int dialogID); + void dialogueDone(); + void runDialogueProg(GPL2Program, int offset); + + bool isDialogueBegin(); + bool shouldExitDialogue(); + void setDialogueExit(bool exit); + int getDialogueBlockNum(); + int getDialogueVar(int dialogueID); + void setDialogueVar(int dialogueID, int value); + int getCurrentDialogue(); + int getDialogueCurrentBlock(); + int getDialogueLastBlock(); + int getDialogueLinesNum(); + int getCurrentDialogueOffset(); + + void schedulePalette(int paletteID); + int getScheduledPalette(); + +private: + DraciEngine *_vm; + + GameInfo _info; + + int *_variables; + Person *_persons; + GameObject *_objects; + + byte *_itemStatus; + GameItem *_items; + int _currentItem; + int _itemUnderCursor; + + int _inventory[kInventorySlots]; + bool _inventoryExit; + + Room _currentRoom; + int _currentGate; + int _newRoom; + int _newGate; + + uint *_dialogueOffsets; + int _currentDialogue; + int *_dialogueVars; + BArchive *_dialogueArchive; + Dialogue *_dialogueBlocks; + bool _dialogueBegin; + bool _dialogueExit; + int _currentBlock; + int _lastBlock; + int _dialogueLinesNum; + int _blockNum; + int _lines[kDialogueLines]; + Animation *_dialogueAnims[kDialogueLines]; + + LoopStatus _loopStatus; + LoopSubstatus _loopSubstatus; + + bool _shouldQuit; + bool _shouldExitLoop; + + uint _speechTick; + + int _objUnderCursor; + int _oldObjUnderCursor; + int _animUnderCursor; + + int _markedAnimationIndex; //!< Used by the Mark GPL command + + int _scheduledPalette; +}; + +} // End of namespace Draci + +#endif // DRACI_GAME_H diff --git a/engines/draci/module.mk b/engines/draci/module.mk new file mode 100644 index 0000000000..3eddfd3079 --- /dev/null +++ b/engines/draci/module.mk @@ -0,0 +1,25 @@ +MODULE := engines/draci + +MODULE_OBJS := \ + draci.o \ + detection.o \ + barchive.o \ + script.o \ + font.o \ + sprite.o \ + screen.o \ + surface.o \ + mouse.o \ + game.o \ + animation.o + +MODULE_DIRS += \ + engines/draci + +# This module can be built as a plugin +ifeq ($(ENABLE_DRACI), DYNAMIC_PLUGIN) +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/draci/mouse.cpp b/engines/draci/mouse.cpp new file mode 100644 index 0000000000..53c227d626 --- /dev/null +++ b/engines/draci/mouse.cpp @@ -0,0 +1,114 @@ +/* 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 "draci/draci.h" +#include "draci/mouse.h" +#include "draci/barchive.h" + +namespace Draci { + +Mouse::Mouse(DraciEngine *vm) { + _x = 0; + _y = 0; + _lButton = false; + _rButton = false; + _cursorType = kNormalCursor; + _vm = vm; +} + +void Mouse::handleEvent(Common::Event event) { + + switch (event.type) { + case Common::EVENT_LBUTTONDOWN: + debugC(6, kDraciGeneralDebugLevel, "Left button down (x: %u y: %u)", _x, _y); + _lButton = true; + break; + + case Common::EVENT_LBUTTONUP: + debugC(6, kDraciGeneralDebugLevel, "Left button up (x: %u y: %u)", _x, _y); + _lButton = false; + break; + + case Common::EVENT_RBUTTONDOWN: + debugC(6, kDraciGeneralDebugLevel, "Right button down (x: %u y: %u)", _x, _y); + _rButton = true; + break; + + case Common::EVENT_RBUTTONUP: + debugC(6, kDraciGeneralDebugLevel, "Right button up (x: %u y: %u)", _x, _y); + _rButton = false; + break; + + case Common::EVENT_MOUSEMOVE: + debugC(6, kDraciGeneralDebugLevel, "Mouse move (x: %u y: %u)", _x, _y); + _x = (uint16) event.mouse.x; + _y = (uint16) event.mouse.y; + break; + + default: + break; + } +} + +void Mouse::cursorOn() { + CursorMan.showMouse(true); +} + +void Mouse::cursorOff() { + CursorMan.showMouse(false); +} + +bool Mouse::isCursorOn() { + return CursorMan.isVisible(); +} + +void Mouse::setPosition(uint16 x, uint16 y) { + _vm->_system->warpMouse(x, y); +} + +void Mouse::setCursorType(CursorType cur) { + _cursorType = cur; + + BAFile *f; + f = _vm->_iconsArchive->getFile(_cursorType); + + Sprite sp(f->_data, f->_length, 0, 0, true); + CursorMan.replaceCursorPalette(_vm->_screen->getPalette(), 0, kNumColours); + CursorMan.replaceCursor(sp.getBuffer(), sp.getWidth(), sp.getHeight(), + sp.getWidth() / 2, sp.getHeight() / 2); +} + +void Mouse::loadItemCursor(int itemID, bool highlighted) { + + BAFile *f; + f = _vm->_itemImagesArchive->getFile(2 * itemID + highlighted); + + Sprite sp(f->_data, f->_length, 0, 0, true); + CursorMan.replaceCursorPalette(_vm->_screen->getPalette(), 0, kNumColours); + CursorMan.replaceCursor(sp.getBuffer(), sp.getWidth(), sp.getHeight(), + sp.getWidth() / 2, sp.getHeight() / 2); +} + +} diff --git a/engines/draci/mouse.h b/engines/draci/mouse.h new file mode 100644 index 0000000000..d6df7f312d --- /dev/null +++ b/engines/draci/mouse.h @@ -0,0 +1,73 @@ +/* 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 DRACI_MOUSE_H +#define DRACI_MOUSE_H + +#include "common/events.h" +#include "graphics/cursorman.h" + +namespace Draci { + +enum CursorType { + kNormalCursor, kArrowCursor1, + kArrowCursor2, kArrowCursor3, + kArrowCursor4, kDialogueCursor, + kHighlightedCursor, kMainMenuCursor +}; + +class DraciEngine; + +class Mouse { +public: + Mouse(DraciEngine *vm); + ~Mouse() {}; + + void handleEvent(Common::Event event); + void cursorOn(); + void cursorOff(); + bool isCursorOn(); + void setPosition(uint16 x, uint16 y); + CursorType getCursorType() { return _cursorType; } + void setCursorType(CursorType cur); + void loadItemCursor(int itemID, bool highlighted = false); + bool lButtonPressed() { return _lButton; } + bool rButtonPressed() { return _rButton; } + void lButtonSet(bool state) { _lButton = state; } + void rButtonSet(bool state) { _rButton = state; } + + uint16 getPosX() { return _x; } + uint16 getPosY() { return _y; } + +private: + uint16 _x, _y; + bool _lButton, _rButton; + CursorType _cursorType; + DraciEngine *_vm; +}; + +} + +#endif // DRACI_MOUSE_H diff --git a/engines/draci/screen.cpp b/engines/draci/screen.cpp new file mode 100644 index 0000000000..781147806f --- /dev/null +++ b/engines/draci/screen.cpp @@ -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$ + * + */ + +#include "common/stream.h" + +#include "draci/draci.h" +#include "draci/screen.h" + +namespace Draci { + +Screen::Screen(DraciEngine *vm) : _vm(vm) { + _surface = new Surface(kScreenWidth, kScreenHeight); + _palette = new byte[4 * kNumColours]; + setPaletteEmpty(); + this->clearScreen(); +} + +Screen::~Screen() { + delete _surface; + delete[] _palette; +} + +/** + * @brief Sets the first numEntries of palette to zero + * @param numEntries The number of entries to set to zero (from start) + */ +void Screen::setPaletteEmpty(unsigned int numEntries) { + for (unsigned int i = 0; i < 4 * numEntries; ++i) { + _palette[i] = 0; + } + + _vm->_system->setPalette(_palette, 0, numEntries); +} + +/** + * @brief Sets a part of the palette + * @param data Pointer to a buffer containing new palette data + * start Index of the colour where replacement should start + * num Number of colours to replace + */ +void Screen::setPalette(byte *data, uint16 start, uint16 num) { + + Common::MemoryReadStream pal(data, 3 * kNumColours); + pal.seek(start * 4); + + // Copy the palette + for (unsigned int i = start; i < start + num; ++i) { + _palette[i * 4] = pal.readByte(); + _palette[i * 4 + 1] = pal.readByte(); + _palette[i * 4 + 2] = pal.readByte(); + _palette[i * 4 + 3] = 0; + } + + // TODO: Investigate why this is needed + // Shift the palette two bits to the left to make it brighter + for (unsigned int i = 0; i < 4 * kNumColours; ++i) { + _palette[i] <<= 2; + } + + _vm->_system->setPalette(_palette, start, num); +} + +/** + * @brief Copies the current memory screen buffer to the real screen + */ +void Screen::copyToScreen() const { + Common::List<Common::Rect> *dirtyRects = _surface->getDirtyRects(); + Common::List<Common::Rect>::iterator it; + + // If a full update is needed, update the whole screen + if (_surface->needsFullUpdate()) { + byte *ptr = (byte *)_surface->getBasePtr(0, 0); + + _vm->_system->copyRectToScreen(ptr, kScreenWidth, + 0, 0, kScreenWidth, kScreenHeight); + } else { + + // Otherwise, update only the dirty rectangles + + for (it = dirtyRects->begin(); it != dirtyRects->end(); ++it) { + + // Pointer to the upper left corner of the rectangle + byte *ptr = (byte *)_surface->getBasePtr(it->left, it->top); + + _vm->_system->copyRectToScreen(ptr, kScreenWidth, + it->left, it->top, it->width(), it->height()); + } + } + + // Call the "real" updateScreen and mark the surface clean + _vm->_system->updateScreen(); + _surface->markClean(); +} + +/** + * @brief Clears the screen + * + * Clears the screen and marks the whole screen dirty. + */ +void Screen::clearScreen() const { + byte *ptr = (byte *)_surface->getBasePtr(0, 0); + + _surface->markDirty(); + + memset(ptr, 0, kScreenWidth * kScreenHeight); +} + +/** + * @brief Fills the screen with the specified colour + * @param colour The colour the screen should be filled with + * + * Fills the screen with the specified colour and marks the whole screen dirty. + */ +void Screen::fillScreen(uint8 colour) const { + _surface->fill(colour); + _surface->markDirty(); +} + +/** + * @brief Draws a rectangle on the screen + * @param r Which rectangle to draw + * colour The colour of the rectangle + */ +void Screen::drawRect(Common::Rect &r, uint8 colour) { + + // Clip the rectangle to screen size + r.clip(_surface->w, _surface->h); + + // If the whole rectangle is outside the screen, return + if (r.isEmpty()) + return; + + byte *ptr = (byte *)_surface->getBasePtr(r.left, r.top); + + for (uint16 i = 0; i < r.width(); ++i) { + for (uint16 j = 0; j < r.height(); ++j) { + ptr[j * kScreenWidth + i] = colour; + } + } + + _surface->markDirtyRect(r); +} + +/** + * @brief Fetches the current palette + * @return A byte pointer to the current palette + */ +byte *Screen::getPalette() const { + return _palette; +} + +/** + * @brief Fetches the current surface + * @return A pointer to the current surface + */ +Draci::Surface *Screen::getSurface() { + return _surface; +} + +} // End of namespace Draci + + diff --git a/engines/draci/screen.h b/engines/draci/screen.h new file mode 100644 index 0000000000..42dcf40bae --- /dev/null +++ b/engines/draci/screen.h @@ -0,0 +1,66 @@ +/* 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 DRACI_SCREEN_H +#define DRACI_SCREEN_H + +#include "draci/surface.h" +#include "draci/sprite.h" + +namespace Draci { + +enum ScreenParameters { + kScreenWidth = 320, + kScreenHeight = 200, + kNumColours = 256, + kDefaultTransparent = 255 +}; + +class DraciEngine; + +class Screen { + +public: + Screen(DraciEngine *vm); + ~Screen(); + + void setPaletteEmpty(unsigned int numEntries = kNumColours); + void setPalette(byte *data, uint16 start, uint16 num); + byte *getPalette() const; + void copyToScreen() const; + void clearScreen() const; + void fillScreen(uint8 colour) const; + Surface *getSurface(); + void drawRect(Common::Rect &r, uint8 colour); + +private: + Surface *_surface; + byte *_palette; + DraciEngine *_vm; +}; + +} // End of namespace Draci + +#endif // DRACI_SCREEN_H diff --git a/engines/draci/script.cpp b/engines/draci/script.cpp new file mode 100644 index 0000000000..db5cb80a20 --- /dev/null +++ b/engines/draci/script.cpp @@ -0,0 +1,1087 @@ +/* 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/debug.h" +#include "common/stream.h" +#include "common/stack.h" +#include "common/queue.h" + +#include "draci/draci.h" +#include "draci/script.h" +#include "draci/game.h" + +namespace Draci { + +// FIXME: Change parameter types to names once I figure out what they are exactly + +void Script::setupCommandList() { + /** A table of all the commands the game player uses */ + static const GPL2Command gplCommands[] = { + { 0, 0, "gplend", 0, { 0 }, NULL }, + { 0, 1, "exit", 0, { 0 }, NULL }, + { 1, 1, "goto", 1, { 3 }, &Script::c_Goto }, + { 2, 1, "Let", 2, { 3, 4 }, &Script::c_Let }, + { 3, 1, "if", 2, { 4, 3 }, &Script::c_If }, + { 4, 1, "Start", 2, { 3, 2 }, &Script::start }, + { 5, 1, "Load", 2, { 3, 2 }, &Script::load }, + { 5, 2, "StartPlay", 2, { 3, 2 }, &Script::startPlay }, + { 5, 3, "JustTalk", 0, { 0 }, NULL }, + { 5, 4, "JustStay", 0, { 0 }, NULL }, + { 6, 1, "Talk", 2, { 3, 2 }, &Script::talk }, + { 7, 1, "ObjStat", 2, { 3, 3 }, &Script::objStat }, + { 7, 2, "ObjStat_On", 2, { 3, 3 }, &Script::objStatOn }, + { 8, 1, "IcoStat", 2, { 3, 3 }, &Script::icoStat }, + { 9, 1, "Dialogue", 1, { 2 }, &Script::dialogue }, + { 9, 2, "ExitDialogue", 0, { 0 }, &Script::exitDialogue }, + { 9, 3, "ResetDialogue", 0, { 0 }, &Script::resetDialogue }, + { 9, 4, "ResetDialogueFrom", 0, { 0 }, &Script::resetDialogueFrom }, + { 9, 5, "ResetBlock", 1, { 3 }, &Script::resetBlock }, + { 10, 1, "WalkOn", 3, { 1, 1, 3 }, &Script::walkOn }, + { 10, 2, "StayOn", 3, { 1, 1, 3 }, &Script::walkOn }, // HACK: not a proper implementation + { 10, 3, "WalkOnPlay", 3, { 1, 1, 3 }, &Script::walkOnPlay }, + { 11, 1, "LoadPalette", 1, { 2 }, &Script::loadPalette }, + { 12, 1, "SetPalette", 0, { 0 }, &Script::setPalette }, + { 12, 2, "BlackPalette", 0, { 0 }, &Script::blackPalette }, + { 13, 1, "FadePalette", 3, { 1, 1, 1 }, NULL }, + { 13, 2, "FadePalettePlay", 3, { 1, 1, 1 }, NULL }, + { 14, 1, "NewRoom", 2, { 3, 1 }, &Script::newRoom }, + { 15, 1, "ExecInit", 1, { 3 }, &Script::execInit }, + { 15, 2, "ExecLook", 1, { 3 }, &Script::execLook }, + { 15, 3, "ExecUse", 1, { 3 }, &Script::execUse }, + { 16, 1, "RepaintInventory", 0, { 0 }, NULL }, + { 16, 2, "ExitInventory", 0, { 0 }, NULL }, + { 17, 1, "ExitMap", 0, { 0 }, NULL }, + { 18, 1, "LoadMusic", 1, { 2 }, NULL }, + { 18, 2, "StartMusic", 0, { 0 }, NULL }, + { 18, 3, "StopMusic", 0, { 0 }, NULL }, + { 18, 4, "FadeOutMusic", 1, { 1 }, NULL }, + { 18, 5, "FadeInMusic", 1, { 1 }, NULL }, + { 19, 1, "Mark", 0, { 0 }, &Script::mark }, + { 19, 2, "Release", 0, { 0 }, &Script::release }, + { 20, 1, "Play", 0, { 0 }, &Script::play }, + { 21, 1, "LoadMap", 1, { 2 }, &Script::loadMap }, + { 21, 2, "RoomMap", 0, { 0 }, &Script::roomMap }, + { 22, 1, "DisableQuickHero", 0, { 0 }, NULL }, + { 22, 2, "EnableQuickHero", 0, { 0 }, NULL }, + { 23, 1, "DisableSpeedText", 0, { 0 }, NULL }, + { 23, 2, "EnableSpeedText", 0, { 0 }, NULL }, + { 24, 1, "QuitGame", 0, { 0 }, NULL }, + { 25, 1, "PushNewRoom", 0, { 0 }, NULL }, + { 25, 2, "PopNewRoom", 0, { 0 }, NULL }, + { 26, 1, "ShowCheat", 0, { 0 }, NULL }, + { 26, 2, "HideCheat", 0, { 0 }, NULL }, + { 26, 3, "ClearCheat", 1, { 1 }, NULL }, + { 27, 1, "FeedPassword", 3, { 1, 1, 1 }, NULL } + }; + + /** Operators used by the mathematical evaluator */ + static const GPL2Operator gplOperators[] = { + {"&", &Script::operAnd }, + {"|", &Script::operOr }, + {"^", &Script::operXor }, + {"==", &Script::operEqual }, + {"!=", &Script::operNotEqual }, + {"<", &Script::operLess }, + {">", &Script::operGreater }, + {"<=", &Script::operLessOrEqual }, + {">=", &Script::operGreaterOrEqual }, + {"*", &Script::operMul }, + {"/", &Script::operDiv }, + {"%", &Script::operMod }, + {"+", &Script::operAdd }, + {"-", &Script::operSub } + }; + + /** Functions used by the mathematical evaluator */ + static const GPL2Function gplFunctions[] = { + { "Not", &Script::funcNot }, + { "Random", &Script::funcRandom }, + { "IsIcoOn", &Script::funcIsIcoOn }, + { "IsIcoAct", &Script::funcIsIcoAct }, + { "IcoStat", &Script::funcIcoStat }, + { "ActIco", &Script::funcActIco }, + { "IsObjOn", &Script::funcIsObjOn }, + { "IsObjOff", &Script::funcIsObjOff }, + { "IsObjAway", &Script::funcIsObjAway }, + { "ObjStat", &Script::funcObjStat }, + { "LastBlock", &Script::funcLastBlock }, + { "AtBegin", &Script::funcAtBegin }, + { "BlockVar", &Script::funcBlockVar }, + { "HasBeen", &Script::funcHasBeen }, + { "MaxLine", &Script::funcMaxLine }, + { "ActPhase", &Script::funcActPhase }, + { "Cheat", NULL }, + }; + + _commandList = gplCommands; + _operatorList = gplOperators; + _functionList = gplFunctions; +} + +/** Type of mathematical object */ +enum mathExpressionObject { + kMathEnd, + kMathNumber, + kMathOperator, + kMathFunctionCall, + kMathVariable +}; + +/* GPL operators */ + +int Script::operAnd(int op1, int op2) { + return op1 & op2; +} + +int Script::operOr(int op1, int op2) { + return op1 | op2; +} + +int Script::operXor(int op1, int op2) { + return op1 ^ op2; +} + +int Script::operEqual(int op1, int op2) { + return op1 == op2; +} + +int Script::operNotEqual(int op1, int op2) { + return op1 != op2; +} + +int Script::operLess(int op1, int op2) { + return op1 < op2; +} + +int Script::operGreater(int op1, int op2) { + return op1 > op2; +} + +int Script::operGreaterOrEqual(int op1, int op2) { + return op1 >= op2; +} + +int Script::operLessOrEqual(int op1, int op2) { + return op1 <= op2; +} + +int Script::operMul(int op1, int op2) { + return op1 * op2; +} + +int Script::operAdd(int op1, int op2) { + return op1 + op2; +} + +int Script::operSub(int op1, int op2) { + return op1 - op2; +} + +int Script::operDiv(int op1, int op2) { + return op1 / op2; +} + +int Script::operMod(int op1, int op2) { + return op1 % op2; +} + +/* GPL functions */ + +int Script::funcRandom(int n) { + +// The function needs to return numbers in the [0..n-1] range so we need to deduce 1 +// (RandomSource::getRandomNumber returns a number in the range [0..n]) + + n -= 1; + return _vm->_rnd.getRandomNumber(n); +} + +int Script::funcAtBegin(int yesno) { + return _vm->_game->isDialogueBegin() == yesno; +} + +int Script::funcLastBlock(int blockID) { + blockID -= 1; + + return _vm->_game->getDialogueLastBlock() == blockID; +} + +int Script::funcBlockVar(int blockID) { + blockID -= 1; + + const int currentOffset = _vm->_game->getCurrentDialogueOffset(); + return _vm->_game->getDialogueVar(currentOffset + blockID); +} + +int Script::funcHasBeen(int blockID) { + blockID -= 1; + + const int currentOffset = _vm->_game->getCurrentDialogueOffset(); + return _vm->_game->getDialogueVar(currentOffset + blockID) > 0; +} + +int Script::funcMaxLine(int lines) { + return _vm->_game->getDialogueLinesNum() < lines; +} + +int Script::funcNot(int n) { + return !n; +} + +int Script::funcIsIcoOn(int itemID) { + itemID -= 1; + + return _vm->_game->getItemStatus(itemID) == 1; +} + +int Script::funcIcoStat(int itemID) { + itemID -= 1; + + int status = _vm->_game->getItemStatus(itemID); + return (status == 1) ? 1 : 2; +} + +int Script::funcIsIcoAct(int itemID) { + itemID -= 1; + + return _vm->_game->getCurrentItem() == itemID; +} + +int Script::funcActIco(int itemID) { + + // The parameter seems to be an omission in the original player since it's not + // used in the implementation of the function. It's possible that the functions were + // implemented in such a way that they had to have a single parameter so this is only + // passed as a dummy. + + return _vm->_game->getCurrentItem(); +} + +int Script::funcIsObjOn(int objID) { + objID -= 1; + + GameObject *obj = _vm->_game->getObject(objID); + + return obj->_visible; +} + +int Script::funcIsObjOff(int objID) { + objID -= 1; + + GameObject *obj = _vm->_game->getObject(objID); + + // We index locations from 0 (as opposed to the original player where it was from 1) + // That's why the "away" location 0 from the data files is converted to -1 + return !obj->_visible && obj->_location != -1; +} + +int Script::funcObjStat(int objID) { + objID -= 1; + + GameObject *obj = _vm->_game->getObject(objID); + + if (obj->_location == _vm->_game->getRoomNum()) { + if (obj->_visible) { + return 1; // object is ON (in the room and visible) + } else { + return 2; // object is OFF (in the room, not visible) + } + } else { + return 3; // object is AWAY (not in the room) + } +} + +int Script::funcIsObjAway(int objID) { + objID -= 1; + + GameObject *obj = _vm->_game->getObject(objID); + + // see Script::funcIsObjOff + return !obj->_visible && obj->_location == -1; +} + +int Script::funcActPhase(int objID) { + + objID -= 1; + + // Default return value + int ret = 0; + + if (_vm->_game->getLoopStatus() == kStatusInventory) { + return ret; + } + + GameObject *obj = _vm->_game->getObject(objID); + + bool visible = (obj->_location == _vm->_game->getRoomNum() && obj->_visible); + + if (objID == kDragonObject || visible) { + int animID = obj->_anims[0]; + Animation *anim = _vm->_anims->getAnimation(animID); + ret = anim->currentFrameNum(); + } + + return ret; +} + +/* GPL commands */ + +void Script::play(Common::Queue<int> ¶ms) { + if (_vm->_game->getLoopStatus() == kStatusInventory) { + return; + } + + _vm->_game->setLoopSubstatus(kSubstatusStrange); + _vm->_game->setExitLoop(true); + _vm->_game->loop(); + _vm->_game->setExitLoop(false); + _vm->_game->setLoopSubstatus(kSubstatusOrdinary); +} + +void Script::load(Common::Queue<int> ¶ms) { + if (_vm->_game->getLoopStatus() == kStatusInventory) { + return; + } + + int objID = params.pop() - 1; + int animID = params.pop() - 1; + + uint i; + GameObject *obj = _vm->_game->getObject(objID); + + // If the animation is already loaded, return + for(i = 0; i < obj->_anims.size(); ++i) { + if (obj->_anims[i] == animID) { + return; + } + } + + // Load the animation into memory + + _vm->_game->loadAnimation(animID, obj->_z); + + // We insert the ID of the loaded animation into the object's internal array + // of owned animation IDs. + // Care must be taken to store them sorted (increasing order) as some things + // depend on this. + + for(i = 0; i < obj->_anims.size(); ++i) { + if (obj->_anims[i] > animID) { + break; + } + } + + obj->_anims.insert_at(i, animID); +} + +void Script::start(Common::Queue<int> ¶ms) { + if (_vm->_game->getLoopStatus() == kStatusInventory) { + return; + } + + int objID = params.pop() - 1; + int animID = params.pop() - 1; + + GameObject *obj = _vm->_game->getObject(objID); + + // Stop all animation that the object owns + + for (uint i = 0; i < obj->_anims.size(); ++i) { + _vm->_anims->stop(obj->_anims[i]); + } + + Animation *anim = _vm->_anims->getAnimation(animID); + anim->registerCallback(&Animation::stopAnimation); + + bool visible = (obj->_location == _vm->_game->getRoomNum() && obj->_visible); + + if (objID == kDragonObject || visible) { + _vm->_anims->play(animID); + } +} + +void Script::startPlay(Common::Queue<int> ¶ms) { + if (_vm->_game->getLoopStatus() == kStatusInventory) { + return; + } + + int objID = params.pop() - 1; + int animID = params.pop() - 1; + + GameObject *obj = _vm->_game->getObject(objID); + + // Stop all animation that the object owns + + for (uint i = 0; i < obj->_anims.size(); ++i) { + _vm->_anims->stop(obj->_anims[i]); + } + + Animation *anim = _vm->_anims->getAnimation(animID); + anim->registerCallback(&Animation::exitGameLoop); + + _vm->_game->setLoopSubstatus(kSubstatusStrange); + + bool visible = (obj->_location == _vm->_game->getRoomNum() && obj->_visible); + + if (objID == kDragonObject || visible) { + _vm->_anims->play(animID); + } + + _vm->_game->loop(); + _vm->_game->setExitLoop(false); + _vm->_anims->stop(animID); + _vm->_game->setLoopSubstatus(kSubstatusOrdinary); + + anim->registerCallback(&Animation::doNothing); +} + +void Script::c_If(Common::Queue<int> ¶ms) { + int expression = params.pop(); + int jump = params.pop(); + + if (expression) + _jump = jump; +} + +void Script::c_Goto(Common::Queue<int> ¶ms) { + int jump = params.pop(); + + _jump = jump; +} + +void Script::c_Let(Common::Queue<int> ¶ms) { + int var = params.pop() - 1; + int value = params.pop(); + + _vm->_game->setVariable(var, value); +} + +void Script::mark(Common::Queue<int> ¶ms) { + _vm->_game->setMarkedAnimationIndex(_vm->_anims->getLastIndex()); +} + +void Script::release(Common::Queue<int> ¶ms) { + int markedIndex = _vm->_game->getMarkedAnimationIndex(); + + // Also delete those animations from the game's objects + for (uint i = 0; i < _vm->_game->getNumObjects(); ++i) { + GameObject *obj = _vm->_game->getObject(i); + + for (uint j = 0; j < obj->_anims.size(); ++j) { + Animation *anim; + + anim = _vm->_anims->getAnimation(obj->_anims[j]); + if (anim != NULL && anim->getIndex() > markedIndex) + obj->_anims.remove_at(j); + } + } + + // Delete animations which have an index greater than the marked index + _vm->_anims->deleteAfterIndex(markedIndex); +} + +void Script::icoStat(Common::Queue<int> ¶ms) { + int status = params.pop(); + int itemID = params.pop() - 1; + + _vm->_game->setItemStatus(itemID, status == 1); + + if (_vm->_game->getItemStatus(itemID) == 0) { + + if (itemID != kNoItem) { + _vm->_anims->deleteAnimation(kInventoryItemsID - itemID); + } + + _vm->_game->removeItem(itemID); + + if (_vm->_game->getCurrentItem() == itemID) { + _vm->_game->setCurrentItem(kNoItem); + } + + if (_vm->_mouse->getCursorType() == kNormalCursor) { + if (_vm->_game->getLoopStatus() == kStatusInventory) { + _vm->_mouse->cursorOff(); + } + } + } + + if (_vm->_game->getItemStatus(itemID) == 1) { + + if (itemID != kNoItem) { + Animation *itemAnim = _vm->_anims->addItem(kInventoryItemsID - itemID); + BAFile *f = _vm->_itemImagesArchive->getFile(2 * itemID); + Sprite *sp = new Sprite(f->_data, f->_length, 0, 0, true); + itemAnim->addFrame(sp); + } + + _vm->_game->setCurrentItem(itemID); + + _vm->_mouse->loadItemCursor(itemID); + + // TODO: This is probably not needed but I'm leaving it to be sure for now + // The original engine needed to turn off the mouse temporarily when changing + // the cursor image. I'm just setting it to the final state of that transition. + if (_vm->_game->getLoopStatus() == kStatusInventory) { + _vm->_mouse->cursorOn(); + } + } +} + +void Script::objStatOn(Common::Queue<int> ¶ms) { + int objID = params.pop() - 1; + int roomID = params.pop() - 1; + + GameObject *obj = _vm->_game->getObject(objID); + + obj->_location = roomID; + obj->_visible = true; +} + +void Script::objStat(Common::Queue<int> ¶ms) { + int status = params.pop(); + int objID = params.pop() - 1; + + GameObject *obj = _vm->_game->getObject(objID); + + if (status == 1) { + return; + } else if (status == 2) { + obj->_visible = false; + } else { + obj->_visible = false; + obj->_location = -1; + } + + for (uint i = 0; i < obj->_anims.size(); ++i) { + _vm->_anims->stop(obj->_anims[i]); + } +} + +void Script::execInit(Common::Queue<int> ¶ms) { + if (_vm->_game->getLoopStatus() == kStatusInventory) { + return; + } + + int objID = params.pop() - 1; + + GameObject *obj = _vm->_game->getObject(objID); + run(obj->_program, obj->_init); +} + +void Script::execLook(Common::Queue<int> ¶ms) { + if (_vm->_game->getLoopStatus() == kStatusInventory) { + return; + } + + int objID = params.pop() - 1; + + GameObject *obj = _vm->_game->getObject(objID); + run(obj->_program, obj->_look); +} + +void Script::execUse(Common::Queue<int> ¶ms) { + if (_vm->_game->getLoopStatus() == kStatusInventory) { + return; + } + + int objID = params.pop() - 1; + + GameObject *obj = _vm->_game->getObject(objID); + run(obj->_program, obj->_use); +} + +void Script::walkOn(Common::Queue<int> ¶ms) { + if (_vm->_game->getLoopStatus() == kStatusInventory) { + return; + } + + int x = params.pop(); + int y = params.pop(); + params.pop(); // facing direction, not used yet + + _vm->_game->walkHero(x, y); +} + +void Script::walkOnPlay(Common::Queue<int> ¶ms) { + if (_vm->_game->getLoopStatus() == kStatusInventory) { + return; + } + + int x = params.pop(); + int y = params.pop(); + params.pop(); // facing direction, not used yet + + // HACK: This should be an onDest action when hero walking is properly implemented + _vm->_game->setExitLoop(true); + + _vm->_game->walkHero(x, y); + + _vm->_game->setLoopSubstatus(kSubstatusStrange); + _vm->_game->loop(); + _vm->_game->setLoopSubstatus(kSubstatusOrdinary); + + _vm->_game->setExitLoop(false); +} + +void Script::newRoom(Common::Queue<int> ¶ms) { + + if (_vm->_game->getLoopStatus() == kStatusInventory) { + return; + } + + int room = params.pop() - 1; + int gate = params.pop() - 1; + + _vm->_game->setRoomNum(room); + _vm->_game->setGateNum(gate); +} + +void Script::talk(Common::Queue<int> ¶ms) { + + int personID = params.pop() - 1; + int sentenceID = params.pop() - 1; + + Surface *surface = _vm->_screen->getSurface(); + + // Fetch string + BAFile *f = _vm->_stringsArchive->getFile(sentenceID); + + // Fetch frame for the speech text + Animation *speechAnim = _vm->_anims->getAnimation(kSpeechText); + Text *speechFrame = reinterpret_cast<Text *>(speechAnim->getFrame()); + + // Fetch person info + Person *person = _vm->_game->getPerson(personID); + + // Set the string and text colour + surface->markDirtyRect(speechFrame->getRect(true)); + speechFrame->setText(Common::String((const char *)f->_data+1, f->_length-1)); + speechFrame->setColour(person->_fontColour); + + // HACK: Some strings in the English data files are too long to fit the screen + // This is a temporary resolution. + if (speechFrame->getWidth() >= kScreenWidth) { + speechFrame->setFont(_vm->_smallFont); + } else { + speechFrame->setFont(_vm->_bigFont); + } + + // Set the loop substatus to an appropriate value + _vm->_game->setLoopSubstatus(kSubstatusTalk); + + // Record time + _vm->_game->setSpeechTick(_vm->_system->getMillis()); + + // TODO: Implement inventory part + + // Set speech text coordinates + + int x = surface->centerOnX(person->_x, speechFrame->getWidth()); + int y = surface->centerOnY(person->_y, speechFrame->getHeight() * 2); + + speechFrame->setX(x); + speechFrame->setY(y); + + // Prevent the loop from exiting early if other things left the loop in the + // "exit immediately" state + _vm->_game->setExitLoop(false); + + // Call the game loop to enable interactivity until the text expires + _vm->_game->loop(); + + // Delete the text + _vm->_screen->getSurface()->markDirtyRect(speechFrame->getRect(true)); + speechFrame->setText(""); + + // Revert to "normal" loop status + _vm->_game->setLoopSubstatus(kSubstatusOrdinary); + _vm->_game->setExitLoop(false); +} + +void Script::dialogue(Common::Queue<int> ¶ms) { + int dialogueID = params.pop() - 1; + + _vm->_game->dialogueMenu(dialogueID); +} + +void Script::loadMap(Common::Queue<int> ¶ms) { + int mapID = params.pop() - 1; + + _vm->_game->loadWalkingMap(mapID); +} + +void Script::resetDialogue(Common::Queue<int> ¶ms) { + + const int currentOffset = _vm->_game->getCurrentDialogueOffset(); + + for (int i = 0; i < _vm->_game->getDialogueBlockNum(); ++i) { + _vm->_game->setDialogueVar(currentOffset + i, 0); + } +} + +void Script::resetDialogueFrom(Common::Queue<int> ¶ms) { + + const int currentOffset = _vm->_game->getCurrentDialogueOffset(); + + for (int i = _vm->_game->getDialogueCurrentBlock(); i < _vm->_game->getDialogueBlockNum(); ++i) { + _vm->_game->setDialogueVar(currentOffset + i, 0); + } +} + +void Script::resetBlock(Common::Queue<int> ¶ms) { + int blockID = params.pop() - 1; + + const int currentOffset = _vm->_game->getCurrentDialogueOffset(); + + _vm->_game->setDialogueVar(currentOffset + blockID, 0); +} + +void Script::exitDialogue(Common::Queue<int> ¶ms) { + _vm->_game->setDialogueExit(true); +} + +void Script::roomMap(Common::Queue<int> ¶ms) { + + // Load the default walking map for the room + _vm->_game->loadWalkingMap(); +} + +void Script::loadPalette(Common::Queue<int> ¶ms) { + int palette = params.pop() - 1; + + _vm->_game->schedulePalette(palette); +} + +void Script::blackPalette(Common::Queue<int> ¶ms) { + + _vm->_game->schedulePalette(kBlackPalette); +} + +void Script::setPalette(Common::Queue<int> ¶ms) { + + if (_vm->_game->getScheduledPalette() == -1) { + _vm->_screen->setPaletteEmpty(); + } else { + BAFile *f; + f = _vm->_paletteArchive->getFile(_vm->_game->getScheduledPalette()); + _vm->_screen->setPalette(f->_data, 0, kNumColours); + } +} + +void Script::endCurrentProgram() { + _endProgram = true; +} + +/** + * @brief Evaluates mathematical expressions + * @param reader Stream reader set to the beginning of the expression + */ + +int Script::handleMathExpression(Common::MemoryReadStream &reader) { + Common::Stack<int> stk; + mathExpressionObject obj; + GPL2Operator oper; + GPL2Function func; + + debugC(4, kDraciBytecodeDebugLevel, "\t<MATHEXPR>"); + + // Read in initial math object + obj = (mathExpressionObject)reader.readSint16LE(); + + int value; + int arg1, arg2, res; + + while (1) { + if (obj == kMathEnd) { + // Check whether the expression was evaluated correctly + // The stack should contain only one value after the evaluation + // i.e. the result of the expression + assert(stk.size() == 1 && "Mathematical expression error"); + break; + } + + switch (obj) { + + // If the object type is not known, assume that it's a number + default: + case kMathNumber: + value = reader.readSint16LE(); + stk.push(value); + debugC(4, kDraciBytecodeDebugLevel, "\t\tnumber: %d", value); + break; + + case kMathOperator: + value = reader.readSint16LE(); + arg2 = stk.pop(); + arg1 = stk.pop(); + + // Fetch operator + oper = _operatorList[value-1]; + + // Calculate result + res = (this->*(oper._handler))(arg1, arg2); + + // Push result + stk.push(res); + + debugC(4, kDraciBytecodeDebugLevel, "\t\t%d %s %d (res: %d)", + arg1, oper._name.c_str(), arg2, res); + break; + + case kMathVariable: + value = reader.readSint16LE() - 1; + + stk.push(_vm->_game->getVariable(value)); + + debugC(4, kDraciBytecodeDebugLevel, "\t\tvariable: %d (%d)", value, + _vm->_game->getVariable(value)); + break; + + case kMathFunctionCall: + value = reader.readSint16LE(); + + // Fetch function + func = _functionList[value-1]; + + // If not yet implemented + if (func._handler == NULL) { + stk.pop(); + + // FIXME: Pushing dummy value for now, but should push return value + stk.push(0); + + debugC(4, kDraciBytecodeDebugLevel, "\t\tcall: %s (not implemented)", + func._name.c_str()); + } else { + arg1 = stk.pop(); + + // Calculate result + res = (this->*(func._handler))(arg1); + + // Push the result on the evaluation stack + stk.push(res); + + debugC(4, kDraciBytecodeDebugLevel, "\t\tcall: %s(%d) (res: %d)", + func._name.c_str(), arg1, res); + } + + break; + } + + obj = (mathExpressionObject) reader.readSint16LE(); + } + + return stk.pop(); +} + +/** + * @brief Evaluates a GPL mathematical expression on a given offset and returns + * the result (which is normally a boolean-like value) + * + * @param program A GPL2Program instance of the program containing the expression + * @param offset Offset of the expression inside the program (in multiples of 2 bytes) + * + * @return The result of the expression converted to a bool. + * + * Reference: the function equivalent to this one is called "Can()" in the original engine. + */ +bool Script::testExpression(GPL2Program program, uint16 offset) { + + // Initialize program reader + Common::MemoryReadStream reader(program._bytecode, program._length); + + // Offset is given as number of 16-bit integers so we need to convert + // it to a number of bytes + offset -= 1; + offset *= 2; + + // Seek to the expression + reader.seek(offset); + + debugC(4, kDraciBytecodeDebugLevel, + "Evaluating (standalone) GPL expression at offset %d:", offset); + + return (bool)handleMathExpression(reader); +} + +/** + * @brief Find the current command in the internal table + * + * @param num Command number + * @param subnum Command subnumer + * + * @return NULL if command is not found. Otherwise, a pointer to a GPL2Command + * struct representing the command. + */ +const GPL2Command *Script::findCommand(byte num, byte subnum) { + unsigned int i = 0; + while (1) { + + // Command not found + if (i >= kNumCommands) { + break; + } + + // Return found command + if (_commandList[i]._number == num && + _commandList[i]._subNumber == subnum) { + return &_commandList[i]; + } + + ++i; + } + + return NULL; +} + +/** + * @brief GPL2 bytecode interpreter + * @param program GPL program in the form of a GPL2Program struct + * offset Offset into the program where execution should begin + * + * GPL2 is short for Game Programming Language 2 which is the script language + * used by Draci Historie. This is the interpreter for the language. + * + * A compiled GPL2 program consists of a stream of bytes representing commands + * and their parameters. The syntax is as follows: + * + * Syntax of a command: + * <name of the command> <number> <sub-number> <list of parameters...> + * + * Syntax of a parameter: + * - 1: integer number literally passed to the program + * - 2-1: string stored in the reservouir of game strings (i.e. something to be + * displayed) and stored as an index in this list + * - 2-2: string resolved by the compiler (i.e., a path to another file) and + * replaced by an integer index of this entity in the appropriate namespace + * (e.g., the index of the palette, location, ...) + * - 3-0: relative jump to a label defined in this code. Each label must be + * first declared in the beginning of the program. + * - 3-1 .. 3-9: index of an entity in several namespaces, defined in file ident + * - 4: mathematical expression compiled into a postfix format + * + * In the compiled program, parameters of type 1..3 are represented by a single + * 16-bit integer. The called command knows by its definition what namespace the + * value comes from. + */ + +int Script::run(GPL2Program program, uint16 offset) { + + int oldJump = _jump; + + // Mark the last animation index before we do anything so a Release command + // doesn't unload too many animations if we forget to use a Mark command first + _vm->_game->setMarkedAnimationIndex(_vm->_anims->getLastIndex()); + + // Stream reader for the whole program + Common::MemoryReadStream reader(program._bytecode, program._length); + + // Parameter queue that is passed to each command + Common::Queue<int> params; + + // Offset is given as number of 16-bit integers so we need to convert + // it to a number of bytes + offset -= 1; + offset *= 2; + + // Seek to the requested part of the program + reader.seek(offset); + + debugC(3, kDraciBytecodeDebugLevel, + "Starting GPL program at offset %d (program length: %d)", offset, program._length); + + const GPL2Command *cmd; + do { + + // Account for GPL jump that some commands set + if (_jump != 0) { + debugC(3, kDraciBytecodeDebugLevel, + "Jumping from offset %d to %d (%d bytes)", + reader.pos(), reader.pos() + _jump, _jump); + reader.seek(_jump, SEEK_CUR); + } + + // Reset jump + _jump = 0; + + // Clear any parameters left on the stack from the previous command + // This likely won't be needed once all commands are implemented + params.clear(); + + // read in command pair + uint16 cmdpair = reader.readUint16BE(); + + // extract high byte, i.e. the command number + byte num = (cmdpair >> 8) & 0xFF; + + // extract low byte, i.e. the command subnumber + byte subnum = cmdpair & 0xFF; + + // This might get set by some GPL commands via Script::endCurrentProgram() + // if they need a program to stop midway + _endProgram = false; + + if ((cmd = findCommand(num, subnum))) { + int tmp; + + // Print command name + debugC(1, kDraciBytecodeDebugLevel, "%s", cmd->_name.c_str()); + + for (int i = 0; i < cmd->_numParams; ++i) { + if (cmd->_paramTypes[i] == 4) { + debugC(3, kDraciBytecodeDebugLevel, + "Evaluating (in-script) GPL expression at offset %d: ", offset); + params.push(handleMathExpression(reader)); + } + else { + tmp = reader.readSint16LE(); + params.push(tmp); + debugC(2, kDraciBytecodeDebugLevel, "\t%d", tmp); + } + } + } + else { + debugC(1, kDraciBytecodeDebugLevel, "Unknown opcode %d, %d", + num, subnum); + abort(); + } + + GPLHandler handler = cmd->_handler; + + if (handler != NULL) { + // Call the handler for the current command + (this->*(cmd->_handler))(params); + } + + } while (cmd->_name != "gplend" && cmd->_name != "exit" && !_endProgram); + + _endProgram = false; + _jump = oldJump; + + return 0; +} + +} + diff --git a/engines/draci/script.h b/engines/draci/script.h new file mode 100644 index 0000000000..86abb5ee9b --- /dev/null +++ b/engines/draci/script.h @@ -0,0 +1,176 @@ +/* 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 DRACI_SCRIPT_H +#define DRACI_SCRIPT_H + +#include "common/str.h" +#include "common/stream.h" +#include "common/queue.h" + +namespace Draci { + +/** The maximum number of parameters for a GPL command */ +const int kMaxParams = 3; + +class DraciEngine; +class Script; + +enum { + kNumCommands = 55 +}; + +typedef void (Script::* GPLHandler)(Common::Queue<int> &); +typedef int (Script::* GPLOperatorHandler)(int, int); +typedef int (Script::* GPLFunctionHandler)(int); + +/** + * Represents a single command in the GPL scripting language bytecode. + * Each command is represented in the bytecode by a command number and a + * subnumber. + */ + +struct GPL2Command { + byte _number; + byte _subNumber; + Common::String _name; + uint16 _numParams; + int _paramTypes[kMaxParams]; + GPLHandler _handler; +}; + +struct GPL2Operator { + Common::String _name; + GPLOperatorHandler _handler; +}; + +struct GPL2Function { + Common::String _name; + GPLFunctionHandler _handler; +}; + +/** + * A convenience data type that holds both the actual bytecode and the + * length of the bytecode. Passed to Script::run(). + */ + +struct GPL2Program { + GPL2Program() : _bytecode(NULL), _length(0) {} + + byte *_bytecode; + uint16 _length; +}; + +class Script { + +public: + Script(DraciEngine *vm) : _vm(vm), _jump(0) { setupCommandList(); }; + + int run(GPL2Program program, uint16 offset); + bool testExpression(GPL2Program, uint16 offset); + void endCurrentProgram(); + +private: + + int _jump; + bool _endProgram; + + /** List of all GPL commands. Initialised in the constructor. */ + const GPL2Command *_commandList; + const GPL2Operator *_operatorList; + const GPL2Function *_functionList; + + void c_If(Common::Queue<int> ¶ms); + void c_Goto(Common::Queue<int> ¶ms); + void c_Let(Common::Queue<int> ¶ms); + void load(Common::Queue<int> ¶ms); + void start(Common::Queue<int> ¶ms); + void mark(Common::Queue<int> ¶ms); + void release(Common::Queue<int> ¶ms); + void icoStat(Common::Queue<int> ¶ms); + void objStat(Common::Queue<int> ¶ms); + void objStatOn(Common::Queue<int> ¶ms); + void execInit(Common::Queue<int> ¶ms); + void execLook(Common::Queue<int> ¶ms); + void execUse(Common::Queue<int> ¶ms); + void walkOn(Common::Queue<int> ¶ms); + void walkOnPlay(Common::Queue<int> ¶ms); + void play(Common::Queue<int> ¶ms); + void startPlay(Common::Queue<int> ¶ms); + void newRoom(Common::Queue<int> ¶ms); + void talk(Common::Queue<int> ¶ms); + void loadMap(Common::Queue<int> ¶ms); + void roomMap(Common::Queue<int> ¶ms); + void dialogue(Common::Queue<int> ¶ms); + void exitDialogue(Common::Queue<int> ¶ms); + void resetDialogue(Common::Queue<int> ¶ms); + void resetDialogueFrom(Common::Queue<int> ¶ms); + void resetBlock(Common::Queue<int> ¶ms); + void setPalette(Common::Queue<int> ¶ms); + void blackPalette(Common::Queue<int> ¶ms); + void loadPalette(Common::Queue<int> ¶ms); + + int operAnd(int op1, int op2); + int operOr(int op1, int op2); + int operXor(int op1, int op2); + int operSub(int op1, int op2); + int operAdd(int op1, int op2); + int operDiv(int op1, int op2); + int operMul(int op1, int op2); + int operEqual(int op1, int op2); + int operNotEqual(int op1, int op2); + int operGreater(int op1, int op2); + int operLess(int op1, int op2); + int operGreaterOrEqual(int op1, int op2); + int operLessOrEqual(int op1, int op2); + int operMod(int op1, int op2); + + int funcRandom(int n); + int funcNot(int n); + int funcIsIcoOn(int iconID); + int funcIcoStat(int iconID); + int funcActIco(int iconID); + int funcIsIcoAct(int iconID); + int funcIsObjOn(int objID); + int funcIsObjOff(int objID); + int funcIsObjAway(int objID); + int funcActPhase(int objID); + int funcObjStat(int objID); + int funcLastBlock(int blockID); + int funcAtBegin(int yesno); + int funcBlockVar(int blockID); + int funcHasBeen(int blockID); + int funcMaxLine(int lines); + + void setupCommandList(); + const GPL2Command *findCommand(byte num, byte subnum); + int handleMathExpression(Common::MemoryReadStream &reader); + + DraciEngine *_vm; +}; + +} + +#endif // DRACI_SCRIPT_H diff --git a/engines/draci/sprite.cpp b/engines/draci/sprite.cpp new file mode 100644 index 0000000000..e37d40dd46 --- /dev/null +++ b/engines/draci/sprite.cpp @@ -0,0 +1,377 @@ +/* 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/stream.h" + +#include "draci/draci.h" +#include "draci/sprite.h" +#include "draci/font.h" + +#include <cmath> + +namespace Draci { + +/** + * @brief Transforms an image from column-wise to row-wise + * @param img pointer to the buffer containing the image data + * width width of the image in the buffer + * height height of the image in the buffer + */ +static void transformToRows(byte *img, uint16 width, uint16 height) { + byte *buf = new byte[width * height]; + byte *tmp = buf; + memcpy(buf, img, width * height); + + for (uint16 i = 0; i < width; ++i) { + for (uint16 j = 0; j < height; ++j) { + img[j * width + i] = *tmp++; + } + } + + delete[] buf; +} + +/** + * Constructor for loading sprites from a raw data buffer, one byte per pixel. + */ +Sprite::Sprite(byte *raw_data, uint16 width, uint16 height, int x, int y, + bool columnwise) : _data(NULL) { + + _width = width; + _height = height; + + _scaledWidth = _width; + _scaledHeight = _height; + + _x = x; + _y = y; + + _delay = 0; + + _mirror = false; + + _data = new byte[width * height]; + + memcpy(_data, raw_data, width * height); + + // If the sprite is stored column-wise, transform it to row-wise + if (columnwise) { + transformToRows(_data, width, height); + } +} + +/** + * Constructor for loading sprites from a sprite-formatted buffer, one byte per + * pixel. + */ +Sprite::Sprite(byte *sprite_data, uint16 length, int x, int y, bool columnwise) + : _data(NULL) { + + _x = x; + _y = y; + + _delay = 0; + + _mirror = false; + + Common::MemoryReadStream reader(sprite_data, length); + + _width = reader.readSint16LE(); + _height = reader.readSint16LE(); + + _scaledWidth = _width; + _scaledHeight = _height; + + _data = new byte[_width * _height]; + + reader.read(_data, _width * _height); + + // If the sprite is stored column-wise, transform it to row-wise + if (columnwise) { + transformToRows(_data, _width, _height); + } +} + +Sprite::~Sprite() { + delete[] _data; +} + +void Sprite::setMirrorOn() { + _mirror = true; +} + +void Sprite::setMirrorOff() { + _mirror = false; +} + + +int Sprite::getPixel(int x, int y) const { + + Common::Rect rect = getRect(); + + int dy = y - rect.top; + int dx = x - rect.left; + + // Calculate scaling factors + double scaleX = double(_scaledWidth) / _width; + double scaleY = double(_scaledHeight) / _height; + + int sy = lround(dy * scaleY); + int sx = lround(dx * scaleX); + + if (_mirror) + return _data[sy * _width + (_width - sx)]; + else + return _data[sy * _width + sx]; +} + + +void Sprite::drawScaled(Surface *surface, bool markDirty) const { + + Common::Rect sourceRect(0, 0, _width, _height); + Common::Rect destRect(_x, _y, _x + _scaledWidth, _y + _scaledHeight); + Common::Rect surfaceRect(0, 0, surface->w, surface->h); + Common::Rect clippedDestRect(destRect); + + clippedDestRect.clip(surfaceRect); + + // Calculate by how much we need to adjust the source rectangle to account for cropping + const int adjustLeft = clippedDestRect.left - destRect.left; + const int adjustRight = clippedDestRect.right - destRect.right; + const int adjustTop = clippedDestRect.top - destRect.top; + const int adjustBottom = clippedDestRect.bottom - destRect.bottom; + + // Resize source rectangle + sourceRect.left += adjustLeft; + sourceRect.right += adjustRight; + sourceRect.top += adjustTop; + sourceRect.bottom += adjustBottom; + + // Get pointers to source and destination buffers + byte *dst = (byte *)surface->getBasePtr(clippedDestRect.left, clippedDestRect.top); + byte *src = _data; + + const int transparent = surface->getTransparentColour(); + + // Calculate how many rows and columns we need to draw + const int rows = clippedDestRect.height(); + const int columns = clippedDestRect.width(); + + int *rowIndices = new int[rows]; + int *columnIndices = new int[columns]; + + // Calculate scaling factors + double scaleX = double(_scaledWidth) / _width; + double scaleY = double(_scaledHeight) / _height; + + // Precalculate pixel indexes + for (int i = 0; i < rows; ++i) { + rowIndices[i] = lround(i / scaleY); + } + + for (int j = 0; j < columns; ++j) { + columnIndices[j] = lround(j / scaleX); + } + + // Blit the sprite to the surface + for (int i = 0; i < rows; ++i) { + + // Fetch index of current row to be drawn + int row = rowIndices[i]; + + for (int j = 0; j < columns; ++j) { + + // Fetch index of current column to be drawn + int column = columnIndices[j]; + + // Don't blit if the pixel is transparent on the target surface + if (src[row * _width + column] != transparent) { + + // Draw the sprite mirrored if the _mirror flag is set + if (_mirror) { + dst[sourceRect.left + columns - j - 1] = src[row * _width + column]; + } else { + dst[sourceRect.left + j] = src[row * _width + column]; + } + } + } + + // Advance to next row + dst += surface->pitch; + } + + // Mark the sprite's rectangle dirty + if (markDirty) { + surface->markDirtyRect(destRect); + } + + delete[] rowIndices; + delete[] columnIndices; +} + + +/** + * @brief Draws the sprite to a Draci::Surface + * @param surface Pointer to a Draci::Surface + * + * Draws the sprite to a Draci::Surface and marks its rectangle on the surface as dirty. + * It is safe to call it for sprites that would overflow the surface. + */ +void Sprite::draw(Surface *surface, bool markDirty) const { + + Common::Rect sourceRect(0, 0, _width, _height); + Common::Rect destRect(_x, _y, _x + _width, _y + _height); + Common::Rect surfaceRect(0, 0, surface->w, surface->h); + Common::Rect clippedDestRect(destRect); + + clippedDestRect.clip(surfaceRect); + + // Calculate by how much we need to adjust the source rectangle to account for cropping + const int adjustLeft = clippedDestRect.left - destRect.left; + const int adjustRight = clippedDestRect.right - destRect.right; + const int adjustTop = clippedDestRect.top - destRect.top; + const int adjustBottom = clippedDestRect.bottom - destRect.bottom; + + // Resize source rectangle + sourceRect.left += adjustLeft; + sourceRect.right += adjustRight; + sourceRect.top += adjustTop; + sourceRect.bottom += adjustBottom; + + // Get pointers to source and destination buffers + byte *dst = (byte *)surface->getBasePtr(clippedDestRect.left, clippedDestRect.top); + byte *src = _data; + + const int transparent = surface->getTransparentColour(); + + // Blit the sprite to the surface + for (int i = sourceRect.top; i < sourceRect.bottom; ++i) { + for (int j = sourceRect.left; j < sourceRect.right; ++j) { + + // Don't blit if the pixel is transparent on the target surface + if (src[i * _width + j] != transparent) { + + // Draw the sprite mirrored if the _mirror flag is set + if (_mirror) { + dst[sourceRect.right - j - 1] = src[i * _width + j]; + } else { + dst[j] = src[i * _width + j]; + } + } + } + + // Advance to next row + dst += surface->pitch; + } + + // Mark the sprite's rectangle dirty + if (markDirty) { + surface->markDirtyRect(destRect); + } +} + + +Common::Rect Sprite::getRect(bool scaled) const { + if (scaled) + return Common::Rect(_x, _y, _x + _scaledWidth, _y + _scaledHeight); + else + return Common::Rect(_x, _y, _x + _width, _y + _height); +} + +Text::Text(const Common::String &str, Font *font, byte fontColour, + int x, int y, uint spacing) { + _x = x; + _y = y; + _delay = 0; + + _text = str; + + _length = 0; + for (uint i = 0; i < _text.size(); ++i) { + if (_text[i] != '|') { + ++_length; + } + } + + _spacing = spacing; + _colour = fontColour; + + _font = font; + + _width = _font->getStringWidth(str, _spacing); + _height = _font->getStringHeight(str); + + _scaledWidth = _width; + _scaledHeight = _height; +} + +void Text::setText(const Common::String &str) { + + _width = _font->getStringWidth(str, _spacing); + _height = _font->getStringHeight(str); + + _text = str; + + _length = 0; + for (uint i = 0; i < _text.size(); ++i) { + if (_text[i] != '|') { + ++_length; + } + } +} + +void Text::setColour(byte fontColour) { + _colour = fontColour; +} + +void Text::setSpacing(uint spacing) { + _spacing = spacing; +} + +uint Text::getLength() { + return _length; +} + +void Text::draw(Surface *surface, bool markDirty) const { + _font->setColour(_colour); + + _font->drawString(surface, _text, _x, _y, _spacing); +} + +// TODO: Handle scaled parameter properly by implementing Text scaling +Common::Rect Text::getRect(bool scaled) const { + return Common::Rect(_x, _y, _x + _width, _y + _height); +} + +void Text::setFont(Font *font) { + _font = font; + + _width = _font->getStringWidth(_text, _spacing); + _height = _font->getStringHeight(_text); +} + +} // End of namespace Draci + + diff --git a/engines/draci/sprite.h b/engines/draci/sprite.h new file mode 100644 index 0000000000..2fb668ba65 --- /dev/null +++ b/engines/draci/sprite.h @@ -0,0 +1,155 @@ +/* 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 DRACI_SPRITE_H +#define DRACI_SPRITE_H + +#include "draci/surface.h" +#include "draci/font.h" + +namespace Draci { + +enum DrawableType { kDrawableText, kDrawableSprite }; + +class Drawable { + +public: + virtual void draw(Surface *surface, bool markDirty = true) const = 0; + virtual void drawScaled(Surface *surface, bool markDirty = true) const = 0; + + virtual ~Drawable() {}; + + uint getWidth() const { return _width; } + uint getHeight() const { return _height; } + + uint getScaledWidth() const { return _scaledWidth; } + uint getScaledHeight() const { return _scaledHeight; } + + void setScaled(uint width, uint height) { + _scaledWidth = width; + _scaledHeight = height; + } + + int getX() const { return _x; } + int getY() const { return _y; } + + void setX(int x) { _x = x; } + void setY(int y) { _y = y; } + + void setDelay(int delay) { _delay = delay; } + int getDelay() const { return _delay; } + + virtual Common::Rect getRect(bool scaled = true) const = 0; + + virtual DrawableType getType() const = 0; + +protected: + uint _width; //!< Width of the sprite + uint _height; //!< Height of the sprite + uint _scaledWidth; //!< Scaled width of the sprite + uint _scaledHeight; //!< Scaled height of the sprite + int _x, _y; //!< Sprite coordinates + + /** The time a drawable should stay on the screen + * before being replaced by another or deleted + */ + int _delay; +}; + +/** + * Represents a Draci Historie sprite. Supplies two constructors; one for + * loading a sprite from a raw data buffer and one for loading a sprite in + * the Draci sprite format. Supports loading the sprite from a column-wise + * format (transforming them to row-wise) since that is the way the sprites + * are stored in the original game files. + * + * Sprite format: + * [uint16LE] sprite width + * [uint16LE] sprite height + * [height * width bytes] image pixels stored column-wise, one byte per pixel + */ + +class Sprite : public Drawable { + +public: + Sprite(byte *raw_data, uint16 width, uint16 height, int x, int y, bool columnwise); + + Sprite(byte *sprite_data, uint16 length, int x, int y, bool columnwise); + + ~Sprite(); + + void draw(Surface *surface, bool markDirty = true) const; + void drawScaled(Surface *surface, bool markDirty = true) const; + + void setMirrorOn(); + void setMirrorOff(); + + Common::Rect getRect(bool scaled = true) const; + + const byte *getBuffer() const { return _data; } + int getPixel(int x, int y) const; + + DrawableType getType() const { return kDrawableSprite; } + +private: + byte *_data; //!< Pointer to a buffer containing raw sprite data (row-wise) + bool _mirror; +}; + +class Text : public Drawable { + +public: + Text(const Common::String &str, Font *font, byte fontColour, + int x, int y, uint spacing = 0); + ~Text() {}; + + void setText(const Common::String &str); + void setColour(byte fontColour); + void setSpacing(uint spacing); + void setFont(Font *font); + + uint getLength(); + + void draw(Surface *surface, bool markDirty = true) const; + + // TODO: drawScaled just calls draw so Text can be accessed through a Drawable pointer. + // Handle scaling text sometimes (not essential). + + void drawScaled(Surface *surface, bool markDirty = true) const { draw(surface, markDirty); } + Common::Rect getRect(bool) const; + + DrawableType getType() const { return kDrawableText; } + +private: + Common::String _text; + uint _length; + uint8 _colour; + uint _spacing; + Font *_font; +}; + +} // End of namespace Draci + +#endif // DRACI_SPRITE_H diff --git a/engines/draci/surface.cpp b/engines/draci/surface.cpp new file mode 100644 index 0000000000..a1e9b0f9ab --- /dev/null +++ b/engines/draci/surface.cpp @@ -0,0 +1,179 @@ +/* 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 "draci/screen.h" +#include "draci/surface.h" + +namespace Draci { + +Surface::Surface(int width, int height) { + this->create(width, height, 1); + this->markClean(); + _transparentColour = kDefaultTransparent; +} + +Surface::~Surface() { + this->free(); +} + +/** + * @brief Marks a dirty rectangle on the surface + * @param r The rectangle to be marked dirty + */ +void Surface::markDirtyRect(Common::Rect r) { + Common::List<Common::Rect>::iterator it; + + r.clip(w, h); + + if (r.isEmpty()) + return; + + it = _dirtyRects.begin(); + while (it != _dirtyRects.end()) { + + if (it->contains(r)) + return; + + if (r.contains(*it)) + it = _dirtyRects.erase(it); + else + ++it; + } + + _dirtyRects.push_back(r); +} + +/** + * @brief Clears all dirty rectangles + * + */ +void Surface::clearDirtyRects() { + _dirtyRects.clear(); +} + +/** + * @brief Marks the whole surface dirty + */ +void Surface::markDirty() { + _fullUpdate = true; +} + +/** + * @brief Marks the whole surface clean + */ +void Surface::markClean() { + _fullUpdate = false; + _dirtyRects.clear(); +} + +/** + * @brief Checks whether the surface needs a full update + */ +bool Surface::needsFullUpdate() { + return _fullUpdate; +} + +/** + * @brief Fetches the surface's dirty rectangles + * @return A pointer a list of dirty rectangles + */ +Common::List<Common::Rect> *Surface::getDirtyRects() { + return &_dirtyRects; +} + +/** + * @brief Returns the current transparent colour of the surface + */ +uint Surface::getTransparentColour() { + return _transparentColour; +} + +/** + * @brief Sets the surface's transparent colour + */ +void Surface::setTransparentColour(uint colour) { + _transparentColour = colour; +} + +/** + * @brief Fills the surface with the specified colour + */ +void Surface::fill(uint colour) { + byte *ptr = (byte *)getBasePtr(0, 0); + + memset(ptr, colour, w * h); +} + +/** + * @brief Calculates horizontal center of an object + * + * @param x The x coordinate of the center + * @param width The width of the object to be centered (in pixels) + * + * @return The centered x coordinate + */ +uint Surface::centerOnX(uint x, uint width) { + + int newX = x - width / 2; + + if (newX < 0) + newX = 0; + + if (newX + width >= (uint)w - 1) + newX = (w - 1) - width; + + return newX; +} + +/** + * @brief Calculates vertical center of an object + * + * @param y The y coordinate of the center + * @param height The height of the object to be centered (in pixels) + * + * @return The centered y coordinate + */ +uint Surface::centerOnY(uint y, uint height) { + + int newY = y - height / 2; + + if (newY < 0) + newY = 0; + + if (newY + height >= (uint)h - 1) + newY = (h - 1) - height; + + return newY; +} + +/** + * @brief Returns a Common::Rect corresponding to the surface. + */ + +Common::Rect Surface::getRect() { + return Common::Rect(w, h); +} + +} // End of namespace Draci diff --git a/engines/draci/surface.h b/engines/draci/surface.h new file mode 100644 index 0000000000..ea3830a97f --- /dev/null +++ b/engines/draci/surface.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 DRACI_SURFACE_H +#define DRACI_SURFACE_H + +#include "graphics/surface.h" + +namespace Draci { + +class Surface : public Graphics::Surface { + +public: + Surface(int width, int height); + ~Surface(); + + void markDirtyRect(Common::Rect r); + Common::List<Common::Rect> *getDirtyRects(); + void clearDirtyRects(); + void markDirty(); + void markClean(); + bool needsFullUpdate(); + uint getTransparentColour(); + void setTransparentColour(uint colour); + void fill(uint colour); + uint centerOnY(uint y, uint height); + uint centerOnX(uint x, uint width); + Common::Rect getRect(); + +private: + /** The current transparent colour of the surface. See getTransparentColour() and + * setTransparentColour(). + */ + uint _transparentColour; + + /** Set when the surface is scheduled for a full update. + * See markDirty(), markClean(). Accessed via needsFullUpdate(). + */ + bool _fullUpdate; + + Common::List<Common::Rect> _dirtyRects; //!< List of currently dirty rectangles + +}; + +} // End of namespace Draci + +#endif // DRACI_SURFACE_H diff --git a/engines/engines.mk b/engines/engines.mk index 750291e4d8..04e95dd5c7 100644 --- a/engines/engines.mk +++ b/engines/engines.mk @@ -36,6 +36,11 @@ DEFINES += -DENABLE_CRUISE=$(ENABLE_CRUISE) MODULES += engines/cruise endif +ifdef ENABLE_DRACI +DEFINES += -DENABLE_DRACI=$(ENABLE_DRACI) +MODULES += engines/draci +endif + ifdef ENABLE_DRASCULA DEFINES += -DENABLE_DRASCULA=$(ENABLE_DRASCULA) MODULES += engines/drascula |