aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--base/plugins.cpp3
-rwxr-xr-xconfigure1
-rw-r--r--engines/draci/animation.cpp647
-rw-r--r--engines/draci/animation.h193
-rw-r--r--engines/draci/barchive.cpp427
-rw-r--r--engines/draci/barchive.h99
-rw-r--r--engines/draci/detection.cpp133
-rw-r--r--engines/draci/draci.cpp284
-rw-r--r--engines/draci/draci.h94
-rw-r--r--engines/draci/font.cpp344
-rw-r--r--engines/draci/font.h105
-rw-r--r--engines/draci/game.cpp1695
-rw-r--r--engines/draci/game.h397
-rw-r--r--engines/draci/module.mk25
-rw-r--r--engines/draci/mouse.cpp114
-rw-r--r--engines/draci/mouse.h73
-rw-r--r--engines/draci/screen.cpp184
-rw-r--r--engines/draci/screen.h66
-rw-r--r--engines/draci/script.cpp1087
-rw-r--r--engines/draci/script.h176
-rw-r--r--engines/draci/sprite.cpp377
-rw-r--r--engines/draci/sprite.h155
-rw-r--r--engines/draci/surface.cpp179
-rw-r--r--engines/draci/surface.h69
-rw-r--r--engines/engines.mk5
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
diff --git a/configure b/configure
index c375c82895..854596e18a 100755
--- a/configure
+++ b/configure
@@ -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> &params) {
+ 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> &params) {
+ 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> &params) {
+ 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> &params) {
+ 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> &params) {
+ int expression = params.pop();
+ int jump = params.pop();
+
+ if (expression)
+ _jump = jump;
+}
+
+void Script::c_Goto(Common::Queue<int> &params) {
+ int jump = params.pop();
+
+ _jump = jump;
+}
+
+void Script::c_Let(Common::Queue<int> &params) {
+ int var = params.pop() - 1;
+ int value = params.pop();
+
+ _vm->_game->setVariable(var, value);
+}
+
+void Script::mark(Common::Queue<int> &params) {
+ _vm->_game->setMarkedAnimationIndex(_vm->_anims->getLastIndex());
+}
+
+void Script::release(Common::Queue<int> &params) {
+ 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> &params) {
+ 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> &params) {
+ 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> &params) {
+ 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> &params) {
+ 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> &params) {
+ 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> &params) {
+ 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> &params) {
+ 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> &params) {
+ 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> &params) {
+
+ 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> &params) {
+
+ 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> &params) {
+ int dialogueID = params.pop() - 1;
+
+ _vm->_game->dialogueMenu(dialogueID);
+}
+
+void Script::loadMap(Common::Queue<int> &params) {
+ int mapID = params.pop() - 1;
+
+ _vm->_game->loadWalkingMap(mapID);
+}
+
+void Script::resetDialogue(Common::Queue<int> &params) {
+
+ 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> &params) {
+
+ 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> &params) {
+ int blockID = params.pop() - 1;
+
+ const int currentOffset = _vm->_game->getCurrentDialogueOffset();
+
+ _vm->_game->setDialogueVar(currentOffset + blockID, 0);
+}
+
+void Script::exitDialogue(Common::Queue<int> &params) {
+ _vm->_game->setDialogueExit(true);
+}
+
+void Script::roomMap(Common::Queue<int> &params) {
+
+ // Load the default walking map for the room
+ _vm->_game->loadWalkingMap();
+}
+
+void Script::loadPalette(Common::Queue<int> &params) {
+ int palette = params.pop() - 1;
+
+ _vm->_game->schedulePalette(palette);
+}
+
+void Script::blackPalette(Common::Queue<int> &params) {
+
+ _vm->_game->schedulePalette(kBlackPalette);
+}
+
+void Script::setPalette(Common::Queue<int> &params) {
+
+ 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> &params);
+ void c_Goto(Common::Queue<int> &params);
+ void c_Let(Common::Queue<int> &params);
+ void load(Common::Queue<int> &params);
+ void start(Common::Queue<int> &params);
+ void mark(Common::Queue<int> &params);
+ void release(Common::Queue<int> &params);
+ void icoStat(Common::Queue<int> &params);
+ void objStat(Common::Queue<int> &params);
+ void objStatOn(Common::Queue<int> &params);
+ void execInit(Common::Queue<int> &params);
+ void execLook(Common::Queue<int> &params);
+ void execUse(Common::Queue<int> &params);
+ void walkOn(Common::Queue<int> &params);
+ void walkOnPlay(Common::Queue<int> &params);
+ void play(Common::Queue<int> &params);
+ void startPlay(Common::Queue<int> &params);
+ void newRoom(Common::Queue<int> &params);
+ void talk(Common::Queue<int> &params);
+ void loadMap(Common::Queue<int> &params);
+ void roomMap(Common::Queue<int> &params);
+ void dialogue(Common::Queue<int> &params);
+ void exitDialogue(Common::Queue<int> &params);
+ void resetDialogue(Common::Queue<int> &params);
+ void resetDialogueFrom(Common::Queue<int> &params);
+ void resetBlock(Common::Queue<int> &params);
+ void setPalette(Common::Queue<int> &params);
+ void blackPalette(Common::Queue<int> &params);
+ void loadPalette(Common::Queue<int> &params);
+
+ 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