Initial commit
This commit is contained in:
1
engines/draci/POTFILES
Normal file
1
engines/draci/POTFILES
Normal file
@@ -0,0 +1 @@
|
||||
engines/draci/metaengine.cpp
|
||||
623
engines/draci/animation.cpp
Normal file
623
engines/draci/animation.cpp
Normal file
@@ -0,0 +1,623 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "draci/draci.h"
|
||||
#include "draci/animation.h"
|
||||
#include "draci/barchive.h"
|
||||
#include "draci/game.h"
|
||||
#include "draci/screen.h"
|
||||
#include "draci/sound.h"
|
||||
#include "draci/surface.h"
|
||||
|
||||
#include "common/memstream.h"
|
||||
#include "common/system.h"
|
||||
|
||||
namespace Draci {
|
||||
|
||||
Animation::Animation(DraciEngine *vm, int id, uint z, bool playing) : _vm(vm) {
|
||||
_id = id;
|
||||
_index = kIgnoreIndex;
|
||||
_z = z;
|
||||
clearShift();
|
||||
_displacement = kNoDisplacement;
|
||||
_playing = playing;
|
||||
_looping = false;
|
||||
_paused = false;
|
||||
_canBeQuick = false;
|
||||
_tick = _vm->_system->getMillis();
|
||||
_currentFrame = 0;
|
||||
_hasChangedFrame = true;
|
||||
_callback = &Animation::doNothing;
|
||||
_isRelative = false;
|
||||
}
|
||||
|
||||
Animation::~Animation() {
|
||||
deleteFrames();
|
||||
}
|
||||
|
||||
void Animation::setRelative(int relx, int rely) {
|
||||
// Delete the previous frame if there is one
|
||||
if (_frames.size() > 0)
|
||||
markDirtyRect(_vm->_screen->getSurface());
|
||||
|
||||
_displacement.relX = relx;
|
||||
_displacement.relY = rely;
|
||||
}
|
||||
|
||||
Displacement Animation::getCurrentFrameDisplacement() const {
|
||||
Displacement dis = _displacement;
|
||||
dis.relX += lround(dis.extraScaleX * _shift.x);
|
||||
dis.relY += lround(dis.extraScaleY * _shift.y);
|
||||
return dis;
|
||||
}
|
||||
|
||||
Common::Point Animation::getCurrentFramePosition() const {
|
||||
Displacement dis = getCurrentFrameDisplacement();
|
||||
return Common::Point(dis.relX, dis.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) const {
|
||||
if (getFrameCount() == 0)
|
||||
return;
|
||||
|
||||
// Fetch the current frame's rectangle
|
||||
const Drawable *frame = getConstCurrentFrame();
|
||||
Common::Rect frameRect = frame->getRect(getCurrentFrameDisplacement());
|
||||
|
||||
// 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;
|
||||
|
||||
const Drawable *frame = getConstCurrentFrame();
|
||||
Surface *surface = _vm->_screen->getSurface();
|
||||
|
||||
if (force || (_tick + frame->getDelay() <= _vm->_system->getMillis()) ||
|
||||
(_canBeQuick && _vm->_game->getEnableQuickHero() && _vm->_game->getWantQuickHero())) {
|
||||
// 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);
|
||||
|
||||
_shift.x += _relativeShifts[_currentFrame].x;
|
||||
_shift.y += _relativeShifts[_currentFrame].y;
|
||||
_currentFrame = nextFrameNum();
|
||||
_tick = _vm->_system->getMillis();
|
||||
|
||||
// Fetch new frame and mark it dirty
|
||||
markDirtyRect(surface);
|
||||
|
||||
// If the animation is paused, then nextFrameNum()
|
||||
// returns the same frame number even though the time
|
||||
// has elapsed to switch to another frame. We must not
|
||||
// flip _hasChangedFrame to true, otherwise the sample
|
||||
// assigned to this frame will be re-started over and
|
||||
// over until all sound handles are exhausted (happens,
|
||||
// e.g., when switching to the inventory which pauses
|
||||
// all animations).
|
||||
_hasChangedFrame = !_paused;
|
||||
}
|
||||
}
|
||||
|
||||
debugC(6, kDraciAnimationDebugLevel,
|
||||
"anim=%d tick=%d delay=%d tick+delay=%d currenttime=%d frame=%d framenum=%d x=%d y=%d z=%d",
|
||||
_id, _tick, frame->getDelay(), _tick + frame->getDelay(), _vm->_system->getMillis(),
|
||||
_currentFrame, _frames.size(), frame->getX() + getRelativeX(), frame->getY() + getRelativeY(), _z);
|
||||
}
|
||||
|
||||
uint Animation::nextFrameNum() const {
|
||||
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;
|
||||
|
||||
const Drawable *frame = getConstCurrentFrame();
|
||||
|
||||
if (_id == kOverlayImage) {
|
||||
// No displacement or relative animations is supported.
|
||||
frame->draw(surface, false, 0, 0);
|
||||
} else {
|
||||
// Draw frame: first shifted by the relative shift and then
|
||||
// scaled/shifted by the given displacement.
|
||||
frame->drawReScaled(surface, false, getCurrentFrameDisplacement());
|
||||
}
|
||||
|
||||
const SoundSample *sample = _samples[_currentFrame];
|
||||
if (_hasChangedFrame && sample) {
|
||||
uint duration = _vm->_sound->playSound(sample, Audio::Mixer::kMaxChannelVolume, false);
|
||||
debugC(3, kDraciSoundDebugLevel,
|
||||
"Playing sample on animation %d, frame %d: %d+%d at %dHz: %dms",
|
||||
_id, _currentFrame, sample->_offset, sample->_length, sample->_frequency, duration);
|
||||
}
|
||||
_hasChangedFrame = false;
|
||||
}
|
||||
|
||||
void Animation::setPlaying(bool playing) {
|
||||
_tick = _vm->_system->getMillis();
|
||||
_playing = playing;
|
||||
|
||||
// When restarting an animation, allow playing sounds.
|
||||
_hasChangedFrame |= playing;
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
_displacement.extraScaleX = scaleX;
|
||||
_displacement.extraScaleY = scaleY;
|
||||
}
|
||||
|
||||
void Animation::addFrame(Drawable *frame, const SoundSample *sample) {
|
||||
_frames.push_back(frame);
|
||||
_samples.push_back(sample);
|
||||
_relativeShifts.push_back(Common::Point(0, 0));
|
||||
}
|
||||
|
||||
void Animation::makeLastFrameRelative(int x, int y) {
|
||||
_relativeShifts.back() = Common::Point(x, y);
|
||||
}
|
||||
|
||||
void Animation::clearShift() {
|
||||
_shift = Common::Point(0, 0);
|
||||
}
|
||||
|
||||
void Animation::replaceFrame(int i, Drawable *frame, const SoundSample *sample) {
|
||||
_frames[i] = frame;
|
||||
_samples[i] = sample;
|
||||
}
|
||||
|
||||
const Drawable *Animation::getConstCurrentFrame() const {
|
||||
// If there are no frames stored, return NULL
|
||||
return _frames.size() > 0 ? _frames[_currentFrame] : NULL;
|
||||
}
|
||||
|
||||
Drawable *Animation::getCurrentFrame() {
|
||||
// If there are no frames stored, return NULL
|
||||
return _frames.size() > 0 ? _frames[_currentFrame] : NULL;
|
||||
}
|
||||
|
||||
Drawable *Animation::getFrame(int frameNum) {
|
||||
// If there are no frames stored, return NULL
|
||||
return _frames.size() > 0 ? _frames[frameNum] : NULL;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
_relativeShifts.clear();
|
||||
_samples.clear();
|
||||
}
|
||||
|
||||
void Animation::exitGameLoop() {
|
||||
_vm->_game->setExitLoop(true);
|
||||
}
|
||||
|
||||
void Animation::tellWalkingState() {
|
||||
_vm->_game->heroAnimationFinished();
|
||||
}
|
||||
|
||||
void Animation::play() {
|
||||
if (isPlaying()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Title screen in the intro cutscene
|
||||
if (getID() == 671 && _vm->_game->getMapID() == 42) {
|
||||
_vm->setTTSVoice(kNarratorID);
|
||||
_vm->sayText("Dra\x87\xa1 Historie");
|
||||
}
|
||||
|
||||
// Mark the first frame dirty so it gets displayed
|
||||
markDirtyRect(_vm->_screen->getSurface());
|
||||
|
||||
setPlaying(true);
|
||||
|
||||
debugC(3, kDraciAnimationDebugLevel, "Playing animation %d...", getID());
|
||||
}
|
||||
|
||||
void Animation::stop() {
|
||||
if (!isPlaying()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up the last frame that was drawn before stopping
|
||||
markDirtyRect(_vm->_screen->getSurface());
|
||||
|
||||
setPlaying(false);
|
||||
|
||||
// Reset the animation to the beginning
|
||||
setCurrentFrame(0);
|
||||
clearShift();
|
||||
|
||||
debugC(3, kDraciAnimationDebugLevel, "Stopping animation %d...", getID());
|
||||
}
|
||||
|
||||
void Animation::del() {
|
||||
_vm->_anims->deleteAnimation(this);
|
||||
}
|
||||
|
||||
void AnimationManager::pauseAnimations() {
|
||||
if (_animationPauseCounter++) {
|
||||
// Already paused
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &anim : _animations) {
|
||||
if (anim->getID() > 0 || anim->getID() == kTitleText) {
|
||||
// Clean up the last frame that was drawn before stopping
|
||||
anim->markDirtyRect(_vm->_screen->getSurface());
|
||||
|
||||
anim->setPaused(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationManager::unpauseAnimations() {
|
||||
if (--_animationPauseCounter) {
|
||||
// Still paused
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &anim : _animations) {
|
||||
if (anim->isPaused()) {
|
||||
// Clean up the last frame that was drawn before stopping
|
||||
anim->markDirtyRect(_vm->_screen->getSurface());
|
||||
|
||||
anim->setPaused(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Animation *AnimationManager::getAnimation(int id) {
|
||||
for (auto &anim : _animations) {
|
||||
if (anim->getID() == id) {
|
||||
return anim;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void AnimationManager::insert(Animation *anim, bool allocateIndex) {
|
||||
if (allocateIndex)
|
||||
anim->setIndex(++_lastIndex);
|
||||
|
||||
Common::List<Animation *>::iterator it;
|
||||
|
||||
for (it = _animations.begin(); it != _animations.end(); ++it) {
|
||||
if (anim->getZ() < (*it)->getZ())
|
||||
break;
|
||||
}
|
||||
|
||||
_animations.insert(it, anim);
|
||||
}
|
||||
|
||||
void AnimationManager::drawScene(Surface *surf) {
|
||||
// Fill the screen with color zero since some rooms may rely on the screen being black
|
||||
_vm->_screen->getSurface()->fill(0);
|
||||
|
||||
sortAnimations();
|
||||
|
||||
for (auto &anim : _animations) {
|
||||
if (!(anim->isPlaying())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
anim->nextFrame(false);
|
||||
anim->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;
|
||||
|
||||
bool hasChanged;
|
||||
|
||||
do {
|
||||
hasChanged = false;
|
||||
cur = _animations.begin();
|
||||
next = cur;
|
||||
|
||||
while (true) {
|
||||
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;
|
||||
next = _animations.reverse_erase(next);
|
||||
|
||||
insert(anim, false);
|
||||
hasChanged = true;
|
||||
}
|
||||
|
||||
// Advance to next animation
|
||||
cur = next;
|
||||
}
|
||||
} while (hasChanged);
|
||||
}
|
||||
|
||||
void AnimationManager::deleteAnimation(Animation *anim) {
|
||||
if (!anim) {
|
||||
return;
|
||||
}
|
||||
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 == anim) {
|
||||
// Remember index of the deleted animation
|
||||
index = (*it)->getIndex();
|
||||
|
||||
debugC(3, kDraciAnimationDebugLevel, "Deleting animation %d...", anim->getID());
|
||||
|
||||
delete *it;
|
||||
_animations.erase(it);
|
||||
|
||||
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((*it)->getIndex() - 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) {
|
||||
delete *it;
|
||||
it = _animations.reverse_erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationManager::deleteAll() {
|
||||
debugC(3, kDraciAnimationDebugLevel, "Deleting all animations...");
|
||||
|
||||
for (auto *anim : _animations) {
|
||||
delete anim;
|
||||
}
|
||||
|
||||
_animations.clear();
|
||||
|
||||
_lastIndex = -1;
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
delete *it;
|
||||
it = _animations.reverse_erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
_lastIndex = index;
|
||||
}
|
||||
|
||||
const Animation *AnimationManager::getTopAnimation(int x, int y) const {
|
||||
Common::List<Animation *>::const_iterator it;
|
||||
|
||||
Animation *retval = nullptr;
|
||||
|
||||
// Get transparent color for the current screen
|
||||
const int transparent = _vm->_screen->getSurface()->getTransparentColor();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const Drawable *frame = anim->getConstCurrentFrame();
|
||||
|
||||
if (frame == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool matches = false;
|
||||
if (frame->getRect(anim->getCurrentFrameDisplacement()).contains(x, y)) {
|
||||
if (frame->getType() == kDrawableText) {
|
||||
|
||||
matches = true;
|
||||
|
||||
} else if (frame->getType() == kDrawableSprite &&
|
||||
reinterpret_cast<const Sprite *>(frame)->getPixel(x, y, anim->getCurrentFrameDisplacement()) != transparent) {
|
||||
|
||||
matches = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the top-most animation object, unless it is a
|
||||
// non-clickable sprite (overlay, debugging sprites for
|
||||
// walking, or title/speech text) and there is an actual object
|
||||
// underneath it.
|
||||
if (matches) {
|
||||
if (anim->getID() > kOverlayImage || anim->getID() < kSpeechText) {
|
||||
return anim;
|
||||
} else if (retval == nullptr) {
|
||||
retval = anim;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The default return value if no animations were found on these coordinates (not even overlays)
|
||||
return retval;
|
||||
}
|
||||
|
||||
Animation *AnimationManager::load(uint animNum) {
|
||||
// Make double-sure that an animation isn't loaded more than twice,
|
||||
// otherwise horrible things happen in the AnimationManager, because
|
||||
// they use a simple link-list without duplicate checking. This should
|
||||
// never happen unless there is a bug in the game, because all GPL2
|
||||
// commands are guarded.
|
||||
assert(!getAnimation(animNum));
|
||||
|
||||
const BAFile *animFile = _vm->_animationsArchive->getFile(animNum);
|
||||
Common::MemoryReadStream animationReader(animFile->_data, animFile->_length);
|
||||
|
||||
uint numFrames = animationReader.readByte();
|
||||
|
||||
// The following two flags are ignored by the played. Memory logic was
|
||||
// a hint to the old player whether it should cache the sprites or load
|
||||
// them on demand. We have 1 memory manager and ignore these hints.
|
||||
animationReader.readByte();
|
||||
// The disable erasing field is just a (poor) optimization flag that
|
||||
// turns of drawing the background underneath the sprite. By reading
|
||||
// the source code of the old player, I'm not sure if that would ever
|
||||
// have worked. There are only 6 animations in the game with this flag
|
||||
// true. All of them have just 1 animation phase and they are used to
|
||||
// patch a part of the original background by a new sprite. This
|
||||
// should work with the default logic as well---just play this
|
||||
// animation on top of the background. Since the only meaning of the
|
||||
// flag was optimization, ignoring should be OK even without dipping
|
||||
// into details.
|
||||
animationReader.readByte();
|
||||
const bool cyclic = animationReader.readByte();
|
||||
const bool relative = animationReader.readByte();
|
||||
|
||||
Animation *anim = new Animation(_vm, animNum, 0, false);
|
||||
insert(anim, true);
|
||||
|
||||
anim->setLooping(cyclic);
|
||||
anim->setIsRelative(relative);
|
||||
|
||||
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();
|
||||
int sample = animationReader.readUint16LE() - 1;
|
||||
uint freq = animationReader.readUint16LE();
|
||||
uint delay = animationReader.readUint16LE();
|
||||
|
||||
// _spritesArchive is flushed when entering a room. All
|
||||
// scripts in a room are responsible for loading their animations.
|
||||
const BAFile *spriteFile = _vm->_spritesArchive->getFile(spriteNum);
|
||||
Sprite *sp = new Sprite(spriteFile->_data, spriteFile->_length,
|
||||
relative ? 0 : x, relative ? 0 : 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, _vm->_soundsArchive->getSample(sample, freq));
|
||||
if (relative) {
|
||||
anim->makeLastFrameRelative(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
return anim;
|
||||
}
|
||||
|
||||
} // End of namespace Draci
|
||||
225
engines/draci/animation.h
Normal file
225
engines/draci/animation.h
Normal file
@@ -0,0 +1,225 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DRACI_ANIMATION_H
|
||||
#define DRACI_ANIMATION_H
|
||||
|
||||
#include "common/array.h"
|
||||
#include "common/list.h"
|
||||
#include "common/rect.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,
|
||||
kWalkingShortestPathOverlay = -3,
|
||||
kWalkingObliquePathOverlay = -4,
|
||||
kTitleText = -5,
|
||||
kSpeechText = -6,
|
||||
kInventorySprite = -7,
|
||||
kDialogueLinesID = -8,
|
||||
kUnused = -12,
|
||||
kInventoryItemsID = -13
|
||||
};
|
||||
|
||||
/**
|
||||
* Used by overlays as a neutral index that won't get
|
||||
* released with the GPL Release command.
|
||||
*/
|
||||
enum { kIgnoreIndex = -2 };
|
||||
|
||||
class DraciEngine;
|
||||
class Surface;
|
||||
struct SoundSample;
|
||||
|
||||
class Animation {
|
||||
|
||||
typedef void (Animation::* AnimationCallback)();
|
||||
|
||||
public:
|
||||
Animation(DraciEngine *v, int id, uint z, bool playing);
|
||||
~Animation();
|
||||
|
||||
uint getZ() const { return _z; }
|
||||
void setZ(uint z) { _z = z; }
|
||||
|
||||
void setID(int id) { _id = id; }
|
||||
int getID() const { return _id; }
|
||||
|
||||
void nextFrame(bool force);
|
||||
void drawFrame(Surface *surface);
|
||||
|
||||
void addFrame(Drawable *frame, const SoundSample *sample);
|
||||
void replaceFrame(int i, Drawable *frame, const SoundSample *sample);
|
||||
const Drawable *getConstCurrentFrame() const;
|
||||
Drawable *getCurrentFrame();
|
||||
Drawable *getFrame(int frameNum);
|
||||
void setCurrentFrame(uint frame);
|
||||
uint currentFrameNum() const { return _currentFrame; }
|
||||
uint getFrameCount() const { return _frames.size(); }
|
||||
void makeLastFrameRelative(int x, int y);
|
||||
void clearShift();
|
||||
|
||||
bool isPlaying() const { return _playing; }
|
||||
void setPlaying(bool playing);
|
||||
|
||||
bool isPaused() const { return _paused; }
|
||||
void setPaused(bool paused) { _paused = paused; }
|
||||
|
||||
bool isLooping() const { return _looping; }
|
||||
void setLooping(bool looping);
|
||||
|
||||
void setIsRelative(bool value) { _isRelative = value; }
|
||||
bool isRelative() const { return _isRelative; }
|
||||
void setRelative(int relx, int rely);
|
||||
int getRelativeX() const { return _displacement.relX; }
|
||||
int getRelativeY() const { return _displacement.relY; }
|
||||
const Displacement &getDisplacement() const { return _displacement; } // displacement of the whole animation
|
||||
Displacement getCurrentFrameDisplacement() const; // displacement of the current frame (includes _shift)
|
||||
Common::Point getCurrentFramePosition() const; // with displacement and shift applied
|
||||
|
||||
void supportsQuickAnimation(bool val) { _canBeQuick = val; }
|
||||
|
||||
int getIndex() const { return _index; }
|
||||
void setIndex(int index) { _index = index; }
|
||||
|
||||
void setScaleFactors(double scaleX, double scaleY);
|
||||
double getScaleX() const { return _displacement.extraScaleX; }
|
||||
double getScaleY() const { return _displacement.extraScaleY; }
|
||||
|
||||
void markDirtyRect(Surface *surface) const;
|
||||
|
||||
// Animation callbacks. They can only do simple things, such as
|
||||
// setting the value of some variable or stopping an animation. In
|
||||
// particular, they cannot run sub-loops or anything like that, because
|
||||
// the callback is called at an arbitrary time without much control
|
||||
// over what the state of the rest of the program is.
|
||||
void registerCallback(AnimationCallback callback) { _callback = callback; }
|
||||
|
||||
void doNothing() {}
|
||||
void exitGameLoop();
|
||||
void tellWalkingState();
|
||||
|
||||
void play();
|
||||
void stop();
|
||||
void del();
|
||||
|
||||
private:
|
||||
uint nextFrameNum() const;
|
||||
void deleteFrames();
|
||||
|
||||
/** 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;
|
||||
Common::Point _shift; // partial sum of _relativeShifts from the beginning of the animation until the current frame
|
||||
bool _hasChangedFrame;
|
||||
|
||||
Displacement _displacement;
|
||||
bool _isRelative;
|
||||
|
||||
uint _tick;
|
||||
bool _playing;
|
||||
bool _looping;
|
||||
bool _paused;
|
||||
|
||||
bool _canBeQuick;
|
||||
|
||||
/** Array of frames of the animation. The animation object owns these pointers.
|
||||
*/
|
||||
Common::Array<Drawable *> _frames;
|
||||
Common::Array<Common::Point> _relativeShifts;
|
||||
/** Array of samples played during the animation. The animation
|
||||
* object doesn't own these pointers, but they are stored in the
|
||||
* cache.
|
||||
*/
|
||||
Common::Array<const SoundSample *> _samples;
|
||||
|
||||
AnimationCallback _callback;
|
||||
|
||||
DraciEngine *_vm;
|
||||
};
|
||||
|
||||
|
||||
class AnimationManager {
|
||||
|
||||
public:
|
||||
AnimationManager(DraciEngine *vm) : _vm(vm), _lastIndex(-1), _animationPauseCounter(0) {}
|
||||
~AnimationManager() { deleteAll(); }
|
||||
|
||||
void insert(Animation *anim, bool allocateIndex);
|
||||
Animation *load(uint animNum);
|
||||
|
||||
void pauseAnimations();
|
||||
void unpauseAnimations();
|
||||
|
||||
void deleteAnimation(Animation *anim);
|
||||
void deleteOverlays();
|
||||
void deleteAll();
|
||||
|
||||
void drawScene(Surface *surf);
|
||||
|
||||
Animation *getAnimation(int id);
|
||||
|
||||
int getLastIndex() const { return _lastIndex; }
|
||||
void deleteAfterIndex(int index);
|
||||
|
||||
const Animation *getTopAnimation(int x, int y) const;
|
||||
|
||||
private:
|
||||
void sortAnimations();
|
||||
|
||||
DraciEngine *_vm;
|
||||
|
||||
/** List of animation objects, maintained sorted by decreasing Z-coordinates.
|
||||
* The animation manager owns the pointers.
|
||||
*/
|
||||
Common::List<Animation *> _animations;
|
||||
|
||||
/** The index of the most recently added animation.
|
||||
* See Animation::_index for details.
|
||||
*/
|
||||
int _lastIndex;
|
||||
|
||||
/** How many times the animations are paused.
|
||||
* Needed because the animations can be paused once by entering the
|
||||
* inventory and then again by entering the game menu. When they are
|
||||
* unpaused the first time, they should be kept paused. */
|
||||
int _animationPauseCounter;
|
||||
};
|
||||
|
||||
} // End of namespace Draci
|
||||
|
||||
#endif // DRACI_ANIMATION_H
|
||||
401
engines/draci/barchive.cpp
Normal file
401
engines/draci/barchive.cpp
Normal file
@@ -0,0 +1,401 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/str.h"
|
||||
#include "common/memstream.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::Path &path) {
|
||||
byte *table;
|
||||
uint16 tableSize;
|
||||
byte buf[2];
|
||||
|
||||
_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 (uint 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 = nullptr; // 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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::Path &path) {
|
||||
byte buf[4];
|
||||
byte *footer;
|
||||
uint32 footerOffset, footerSize;
|
||||
|
||||
// Close previously opened archive (if any)
|
||||
closeArchive();
|
||||
|
||||
debugCN(2, kDraciArchiverDebugLevel, "Loading archive %s: ", path.toString(Common::Path::kNativeSeparator).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 (uint 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
|
||||
|
||||
byte compressionType = _f.readByte();
|
||||
assert(compressionType == 0 &&
|
||||
"Compression type flag is non-zero (file is compressed)");
|
||||
|
||||
_files[i]._crc = _f.readByte(); // CRC checksum of the file
|
||||
_files[i]._data = nullptr; // 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
|
||||
uint32 footerOffset2 = reader.readUint32LE();
|
||||
assert(footerOffset2 == footerOffset && "Footer offset mismatch");
|
||||
|
||||
// Indicate that the archive has been successfully opened
|
||||
_opened = true;
|
||||
|
||||
delete[] footer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief BArchive close method
|
||||
*
|
||||
* Closes the currently opened archive. It can be called explicitly to
|
||||
* free up memory.
|
||||
*/
|
||||
void BArchive::closeArchive() {
|
||||
if (!_opened) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _fileCount; ++i) {
|
||||
if (_files[i]._data) {
|
||||
delete[] _files[i]._data;
|
||||
}
|
||||
}
|
||||
|
||||
delete[] _files;
|
||||
_f.close();
|
||||
|
||||
_opened = false;
|
||||
_files = nullptr;
|
||||
_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.
|
||||
*/
|
||||
BAFile *BArchive::loadFileBAR(uint i) {
|
||||
// Else open archive and read in requested file
|
||||
if (!_f.isOpen()) {
|
||||
debugC(2, kDraciArchiverDebugLevel, "Error");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 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 (uint j = 0; j < _files[i]._length; j++) {
|
||||
tmp ^= _files[i]._data[j];
|
||||
}
|
||||
|
||||
debugC(2, kDraciArchiverDebugLevel, "Read %d bytes", _files[i]._length);
|
||||
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.
|
||||
*/
|
||||
BAFile *BArchive::loadFileDFW(uint i) {
|
||||
byte *buf;
|
||||
|
||||
// Else open archive and read in requested file
|
||||
if (!_f.isOpen()) {
|
||||
debugC(2, kDraciArchiverDebugLevel, "Error");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 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;
|
||||
uint repeat;
|
||||
uint 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 (uint 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 (uint i = 0; i < _fileCount; ++i) {
|
||||
_files[i].close();
|
||||
}
|
||||
}
|
||||
|
||||
const BAFile *BArchive::getFile(uint i) {
|
||||
// Check whether requested file exists
|
||||
if (i >= _fileCount) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
debugCN(2, kDraciArchiverDebugLevel, "Accessing file %d from archive %s... ",
|
||||
i, _path.toString(Common::Path::kNativeSeparator).c_str());
|
||||
|
||||
// Check if file has already been opened and return that
|
||||
if (_files[i]._data) {
|
||||
debugC(2, kDraciArchiverDebugLevel, "Cached");
|
||||
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
|
||||
96
engines/draci/barchive.h
Normal file
96
engines/draci/barchive.h
Normal file
@@ -0,0 +1,96 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DRACI_BARCHIVE_H
|
||||
#define DRACI_BARCHIVE_H
|
||||
|
||||
#include "common/str.h"
|
||||
#include "common/file.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() {
|
||||
delete[] _data;
|
||||
_data = NULL;
|
||||
}
|
||||
};
|
||||
|
||||
class BArchive {
|
||||
public:
|
||||
BArchive() : _files(NULL), _fileCount(0), _opened(false) {}
|
||||
|
||||
BArchive(const Common::Path &path) :
|
||||
_files(NULL), _fileCount(0), _opened(false) {
|
||||
openArchive(path);
|
||||
}
|
||||
|
||||
~BArchive() { closeArchive(); }
|
||||
|
||||
void openArchive(const Common::Path &path);
|
||||
void closeArchive();
|
||||
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();
|
||||
|
||||
const BAFile *getFile(uint i);
|
||||
|
||||
private:
|
||||
// Archive header data
|
||||
static const char _magicNumber[];
|
||||
static const char _dfwMagicNumber[];
|
||||
static const uint _archiveHeaderSize = 10;
|
||||
|
||||
// File stream header data
|
||||
static const uint _fileHeaderSize = 6;
|
||||
|
||||
Common::Path _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
|
||||
Common::File _f; ///< Opened file handle
|
||||
|
||||
void openDFW(const Common::Path &path);
|
||||
BAFile *loadFileDFW(uint i);
|
||||
BAFile *loadFileBAR(uint i);
|
||||
};
|
||||
|
||||
} // End of namespace Draci
|
||||
|
||||
#endif // DRACI_BARCHIVE_H
|
||||
3
engines/draci/configure.engine
Normal file
3
engines/draci/configure.engine
Normal file
@@ -0,0 +1,3 @@
|
||||
# This file is included from the main "configure" script
|
||||
# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] [components]
|
||||
add_engine draci "Dragon History" yes "" "" "" "midi"
|
||||
34
engines/draci/console.cpp
Normal file
34
engines/draci/console.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "draci/console.h"
|
||||
#include "draci/draci.h"
|
||||
|
||||
namespace Draci {
|
||||
|
||||
DraciConsole::DraciConsole(DraciEngine *vm) : GUI::Debugger(), _vm(vm) {
|
||||
assert(_vm);
|
||||
}
|
||||
|
||||
DraciConsole::~DraciConsole() {
|
||||
}
|
||||
|
||||
} // End of namespace Draci
|
||||
42
engines/draci/console.h
Normal file
42
engines/draci/console.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DRACI_CONSOLE_H
|
||||
#define DRACI_CONSOLE_H
|
||||
|
||||
#include "gui/debugger.h"
|
||||
|
||||
namespace Draci {
|
||||
|
||||
class DraciEngine;
|
||||
|
||||
class DraciConsole : public GUI::Debugger {
|
||||
public:
|
||||
DraciConsole(DraciEngine *vm);
|
||||
~DraciConsole(void) override;
|
||||
|
||||
private:
|
||||
DraciEngine *_vm;
|
||||
};
|
||||
|
||||
} // End of namespace Draci
|
||||
|
||||
#endif
|
||||
4
engines/draci/credits.pl
Normal file
4
engines/draci/credits.pl
Normal file
@@ -0,0 +1,4 @@
|
||||
begin_section("Draci");
|
||||
add_person("Denis Kasak", "dkasak13", "");
|
||||
add_person("Robert Špalek", "spalek", "");
|
||||
end_section();
|
||||
116
engines/draci/detection.cpp
Normal file
116
engines/draci/detection.cpp
Normal file
@@ -0,0 +1,116 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "base/plugins.h"
|
||||
|
||||
#include "engines/advancedDetector.h"
|
||||
#include "engines/metaengine.h"
|
||||
|
||||
#include "draci/draci.h"
|
||||
#include "draci/detection.h"
|
||||
|
||||
static const DebugChannelDef debugFlagList[] = {
|
||||
{Draci::kDraciGeneralDebugLevel, "general", "Draci general debug info"},
|
||||
{Draci::kDraciBytecodeDebugLevel, "bytecode", "GPL bytecode instructions"},
|
||||
{Draci::kDraciArchiverDebugLevel, "archiver", "BAR archiver debug info"},
|
||||
{Draci::kDraciLogicDebugLevel, "logic", "Game logic debug info"},
|
||||
{Draci::kDraciAnimationDebugLevel, "animation", "Animation debug info"},
|
||||
{Draci::kDraciSoundDebugLevel, "sound", "Sound debug info"},
|
||||
{Draci::kDraciWalkingDebugLevel, "walking", "Walking debug info"},
|
||||
DEBUG_CHANNEL_END
|
||||
};
|
||||
|
||||
static const PlainGameDescriptor draciGames[] = {
|
||||
{ "draci", "Dra\304\215\303\255 Historie" },
|
||||
{ nullptr, nullptr }
|
||||
};
|
||||
|
||||
namespace Draci {
|
||||
|
||||
const ADGameDescription gameDescriptions[] = {
|
||||
{
|
||||
"draci",
|
||||
nullptr,
|
||||
AD_ENTRY1s("INIT.DFW", "b890a5aeebaf16af39219cba2416b0a3", 906),
|
||||
Common::EN_ANY,
|
||||
Common::kPlatformDOS,
|
||||
ADGF_NO_FLAGS,
|
||||
GUIO2(GAMEOPTION_TTS_OBJECTS, GAMEOPTION_TTS_SPEECH)
|
||||
},
|
||||
|
||||
{
|
||||
"draci",
|
||||
nullptr,
|
||||
AD_ENTRY1s("INIT.DFW", "9921c8f0045679a8f37eca8d41c5ec02", 906),
|
||||
Common::CS_CZE,
|
||||
Common::kPlatformDOS,
|
||||
ADGF_NO_FLAGS,
|
||||
GUIO2(GAMEOPTION_TTS_OBJECTS, GAMEOPTION_TTS_MISSING_VOICE)
|
||||
},
|
||||
|
||||
{
|
||||
"draci",
|
||||
nullptr,
|
||||
AD_ENTRY1s("INIT.DFW", "76b9b78a8a8809a240acc395df4d0715", 906),
|
||||
Common::PL_POL,
|
||||
Common::kPlatformDOS,
|
||||
ADGF_NO_FLAGS,
|
||||
GUIO2(GAMEOPTION_TTS_OBJECTS, GAMEOPTION_TTS_MISSING_VOICE)
|
||||
},
|
||||
|
||||
{
|
||||
"draci",
|
||||
nullptr,
|
||||
AD_ENTRY1s("INIT.DFW", "9a7115b91cdea361bcaff3e046ac7ded", 906),
|
||||
Common::DE_DEU,
|
||||
Common::kPlatformDOS,
|
||||
ADGF_NO_FLAGS,
|
||||
GUIO2(GAMEOPTION_TTS_OBJECTS, GAMEOPTION_TTS_SPEECH)
|
||||
},
|
||||
|
||||
AD_TABLE_END_MARKER
|
||||
};
|
||||
|
||||
} // End of namespace Draci
|
||||
|
||||
class DraciMetaEngineDetection : public AdvancedMetaEngineDetection<ADGameDescription> {
|
||||
public:
|
||||
DraciMetaEngineDetection() : AdvancedMetaEngineDetection(Draci::gameDescriptions, draciGames) {
|
||||
}
|
||||
|
||||
const char *getName() const override {
|
||||
return "draci";
|
||||
}
|
||||
|
||||
const char *getEngineName() const override {
|
||||
return "Dra\304\215\303\255 Historie";
|
||||
}
|
||||
|
||||
const char *getOriginalCopyright() const override {
|
||||
return "Dra\304\215\303\255 Historie (C) 1995 NoSense";
|
||||
}
|
||||
|
||||
const DebugChannelDef *getDebugChannels() const override {
|
||||
return debugFlagList;
|
||||
}
|
||||
};
|
||||
|
||||
REGISTER_PLUGIN_STATIC(DRACI_DETECTION, PLUGIN_TYPE_ENGINE_DETECTION, DraciMetaEngineDetection);
|
||||
29
engines/draci/detection.h
Normal file
29
engines/draci/detection.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DRACI_DETECTION_H
|
||||
#define DRACI_DETECTION_H
|
||||
|
||||
#define GAMEOPTION_TTS_OBJECTS GUIO_GAMEOPTIONS1
|
||||
#define GAMEOPTION_TTS_SPEECH GUIO_GAMEOPTIONS2
|
||||
#define GAMEOPTION_TTS_MISSING_VOICE GUIO_GAMEOPTIONS3
|
||||
|
||||
#endif // DRACI_DETECTION_H
|
||||
678
engines/draci/draci.cpp
Normal file
678
engines/draci/draci.cpp
Normal file
@@ -0,0 +1,678 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/scummsys.h"
|
||||
|
||||
#include "common/config-manager.h"
|
||||
#include "common/debug-channels.h"
|
||||
#include "common/events.h"
|
||||
#include "common/file.h"
|
||||
#include "common/keyboard.h"
|
||||
#include "common/text-to-speech.h"
|
||||
|
||||
#include "engines/util.h"
|
||||
#include "engines/advancedDetector.h"
|
||||
|
||||
#include "graphics/cursorman.h"
|
||||
#include "graphics/font.h"
|
||||
|
||||
#include "draci/draci.h"
|
||||
#include "draci/animation.h"
|
||||
#include "draci/barchive.h"
|
||||
#include "draci/font.h"
|
||||
#include "draci/game.h"
|
||||
#include "draci/mouse.h"
|
||||
#include "draci/music.h"
|
||||
#include "draci/saveload.h"
|
||||
#include "draci/screen.h"
|
||||
#include "draci/script.h"
|
||||
#include "draci/sound.h"
|
||||
#include "draci/sprite.h"
|
||||
|
||||
namespace Draci {
|
||||
|
||||
#ifdef USE_TTS
|
||||
|
||||
// Used by all languages but Polish
|
||||
static const uint16 kamenickyEncodingTable[] = {
|
||||
0xc48c, 0xc3bc, 0xc3a9, 0xc48f, // Č, ü, é, ď
|
||||
0xc3a4, 0xc48e, 0xc5a4, 0xc48d, // ä, Ď, Ť, č
|
||||
0xc49b, 0xc494, 0xc4b9, 0xc38d, // ě, Ě, Ĺ, Í
|
||||
0xc4be, 0xc4ba, 0xc384, 0xc381, // ľ, ĺ, Ä, Á
|
||||
0xc389, 0xc5be, 0xc5bd, 0xc3b4, // É, ž, Ž, ô
|
||||
0xc3b6, 0xc393, 0xc5af, 0xc39a, // ö, Ó, ů, Ú
|
||||
0xc3bd, 0xc396, 0xc39c, 0xc5a0, // ý, Ö, Ü, Š
|
||||
0xc4bd, 0xc39d, 0xc598, 0xc5a5, // Ľ, Ý, Ř, ť
|
||||
0xc3a1, 0xc3ad, 0xc3b3, 0xc3ba, // á, í, ó, ú
|
||||
0xc588, 0xc587, 0xc5ae, 0xc394, // ň, Ň, Ů, Ô
|
||||
0xc5a1, 0xc599, 0xc595, 0xc594 // š, ř, ŕ, Ŕ
|
||||
};
|
||||
|
||||
// Name of encoding unknown (described by the Draci website as "some ridiculous proprietary encoding")
|
||||
// After 0x9b (0xc5bb/Ż), it matches Kamenický encoding (though some of these Czech characters are replaced
|
||||
// for TTS because it struggles to pronounce them)
|
||||
static const uint16 polishEncodingTable[] = {
|
||||
0xc485, 0xc487, 0xc499, 0xc582, // ą, ć, ę, ł
|
||||
0xc584, 0xc3b3, 0xc59b, 0xc5ba, // ń, ó, ś, ź
|
||||
0xc5bc, 0xc484, 0xc486, 0xc498, // ż, Ą, Ć, Ę
|
||||
0xc581, 0xc583, 0xc393, 0xc59a, // Ł, Ń, Ó, Ś
|
||||
0xc5b9, 0xc5bb, 0x5a, 0x6f, // Ź, Ż, Z, o
|
||||
0xc3b6, 0xc393, 0xc5af, 0xc39a, // ö, Ó, ů, Ú
|
||||
0xc3bd, 0xc396, 0xc39c, 0x53, // ý, Ö, Ü, S
|
||||
0xc4bd, 0xc39d, 0x52, 0x74, // Ľ, Ý, R, t
|
||||
0xc3a1, 0xc3ad, 0xc3b3, 0xc3ba, // á, í, ó, ú
|
||||
0x6e, 0x4e, 0xc5ae, 0xc394, // n, N, Ů, Ô
|
||||
0x73, 0x72, 0x72, 0x52 // s, r, r, R
|
||||
};
|
||||
|
||||
// TTS for all languages but Czech struggles to voice a lot of Czech characters in the credits,
|
||||
// and oftentimes skips them entirely (i.e. "Špalek" is pronounced as "Palek")
|
||||
// To more closely resemble how the names are supposed to be pronounced,
|
||||
// this table replaces certain Czech characters with an alternative
|
||||
static const uint16 czechCharacterConversionTable[] = {
|
||||
0x43, 0xc3bc, 0xc3a9, 0x64, // C, ü, é, d
|
||||
0xc3a4, 0x44, 0x54, 0x63, // ä, D, T, c
|
||||
0x65, 0x45, 0x4c, 0xc38d, // e, E, L, Í
|
||||
0x6c, 0xc4ba, 0xc384, 0xc381, // l, ĺ, Ä, Á
|
||||
0xc389, 0x7a, 0x5a, 0x6f, // É, z, Z, o
|
||||
0xc3b6, 0xc393, 0x75, 0xc39a, // ö, Ó, u, Ú
|
||||
0x0079, 0xc396, 0xc39c, 0x53, // y, Ö, Ü, S
|
||||
0x4c, 0x59, 0x52, 0x74, // L, Y, R, t
|
||||
0xc3a1, 0xc3ad, 0xc3b3, 0xc3ba, // á, í, ó, ú
|
||||
0x6e, 0x4e, 0x55, 0x4f, // n, N, U, O
|
||||
0x73, 0x72, 0x72, 0x52 // s, r, r, R
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
// Data file paths
|
||||
|
||||
const char *objectsPath = "OBJEKTY.DFW";
|
||||
const char *palettePath = "PALETY.DFW";
|
||||
const char *spritesPath = "OBR_AN.DFW";
|
||||
const char *overlaysPath = "OBR_MAS.DFW";
|
||||
const char *roomsPath = "MIST.DFW";
|
||||
const char *animationsPath = "ANIM.DFW";
|
||||
const char *iconsPath = "HRA.DFW";
|
||||
const char *walkingMapsPath = "MAPY.DFW";
|
||||
const char *itemsPath = "IKONY.DFW";
|
||||
const char *itemImagesPath = "OBR_IK.DFW";
|
||||
const char *initPath = "INIT.DFW";
|
||||
const char *stringsPath = "RETEZCE.DFW";
|
||||
const char *soundsPath = "CD2.SAM";
|
||||
const char *dubbingPath = "CD.SAM";
|
||||
const char *musicPathMask = "HUDBA%d.MID";
|
||||
|
||||
const uint kSoundsFrequency = 13000;
|
||||
const uint kDubbingFrequency = 22050;
|
||||
|
||||
DraciEngine::DraciEngine(OSystem *syst, const ADGameDescription *gameDesc)
|
||||
: Engine(syst), _gameDescription(gameDesc), _rnd("draci") {
|
||||
|
||||
setDebugger(new DraciConsole(this));
|
||||
|
||||
_screen = nullptr;
|
||||
_mouse = nullptr;
|
||||
_game = nullptr;
|
||||
_script = nullptr;
|
||||
_anims = nullptr;
|
||||
_sound = nullptr;
|
||||
_music = nullptr;
|
||||
_smallFont = nullptr;
|
||||
_bigFont = nullptr;
|
||||
_iconsArchive = nullptr;
|
||||
_objectsArchive = nullptr;
|
||||
_spritesArchive = nullptr;
|
||||
_paletteArchive = nullptr;
|
||||
_roomsArchive = nullptr;
|
||||
_overlaysArchive = nullptr;
|
||||
_animationsArchive = nullptr;
|
||||
_walkingMapsArchive = nullptr;
|
||||
_itemsArchive = nullptr;
|
||||
_itemImagesArchive = nullptr;
|
||||
_initArchive = nullptr;
|
||||
_stringsArchive = nullptr;
|
||||
_soundsArchive = nullptr;
|
||||
_dubbingArchive = nullptr;
|
||||
_showWalkingMap = 0;
|
||||
_pauseStartTime = 0;
|
||||
}
|
||||
|
||||
bool DraciEngine::hasFeature(EngineFeature f) const {
|
||||
return (f == kSupportsSubtitleOptions) ||
|
||||
(f == kSupportsReturnToLauncher) ||
|
||||
(f == kSupportsLoadingDuringRuntime) ||
|
||||
(f == kSupportsSavingDuringRuntime);
|
||||
}
|
||||
|
||||
static SoundArchive* openAnyPossibleDubbing() {
|
||||
debugC(1, kDraciGeneralDebugLevel, "Trying to find original dubbing");
|
||||
LegacySoundArchive *legacy = new LegacySoundArchive(dubbingPath, kDubbingFrequency);
|
||||
if (legacy->isOpen() && legacy->size()) {
|
||||
debugC(1, kDraciGeneralDebugLevel, "Found original dubbing");
|
||||
return legacy;
|
||||
}
|
||||
delete legacy;
|
||||
|
||||
// The original uncompressed dubbing cannot be found. Try to open the
|
||||
// newer compressed version.
|
||||
debugC(1, kDraciGeneralDebugLevel, "Trying to find compressed dubbing");
|
||||
ZipSoundArchive *zip = new ZipSoundArchive();
|
||||
|
||||
zip->openArchive("dub-raw.zzz", "buf", RAW80, kDubbingFrequency);
|
||||
if (zip->isOpen() && zip->size()) return zip;
|
||||
#ifdef USE_FLAC
|
||||
zip->openArchive("dub-flac.zzz", "flac", FLAC);
|
||||
if (zip->isOpen() && zip->size()) return zip;
|
||||
#endif
|
||||
#ifdef USE_VORBIS
|
||||
zip->openArchive("dub-ogg.zzz", "ogg", OGG);
|
||||
if (zip->isOpen() && zip->size()) return zip;
|
||||
#endif
|
||||
#ifdef USE_MAD
|
||||
zip->openArchive("dub-mp3.zzz", "mp3", MP3);
|
||||
if (zip->isOpen() && zip->size()) return zip;
|
||||
#endif
|
||||
|
||||
// Return an empty (but initialized) archive anyway.
|
||||
return zip;
|
||||
}
|
||||
|
||||
int DraciEngine::init() {
|
||||
// Initialize graphics using following:
|
||||
initGraphics(kScreenWidth, kScreenHeight);
|
||||
|
||||
// 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);
|
||||
|
||||
_soundsArchive = new LegacySoundArchive(soundsPath, kSoundsFrequency);
|
||||
_dubbingArchive = openAnyPossibleDubbing();
|
||||
_sound = new Sound(_mixer);
|
||||
|
||||
_music = new MusicPlayer(musicPathMask);
|
||||
|
||||
// Setup mixer
|
||||
syncSoundSettings();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (!_soundsArchive->isOpen()) {
|
||||
debugC(2, kDraciGeneralDebugLevel, "ERROR - Opening sounds archive failed");
|
||||
return Common::kUnknownError;
|
||||
}
|
||||
|
||||
if (!_dubbingArchive->isOpen()) {
|
||||
debugC(2, kDraciGeneralDebugLevel, "WARNING - Opening dubbing archive failed");
|
||||
}
|
||||
|
||||
_showWalkingMap = false;
|
||||
|
||||
// Basic archive test
|
||||
debugC(2, kDraciGeneralDebugLevel, "Running archive tests...");
|
||||
Common::Path path("INIT.DFW");
|
||||
BArchive ar(path);
|
||||
const 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 (uint i = 0; i < 10; ++i) {
|
||||
debugC(3, kDraciGeneralDebugLevel, "0x%02x%c", f->_data[i], (i < 9) ? ' ' : '\n');
|
||||
}
|
||||
|
||||
return Common::kNoError;
|
||||
}
|
||||
|
||||
void DraciEngine::handleEvents() {
|
||||
Common::Event event;
|
||||
|
||||
while (_eventMan->pollEvent(event)) {
|
||||
switch (event.type) {
|
||||
case Common::EVENT_KEYDOWN:
|
||||
switch (event.kbd.keycode) {
|
||||
case Common::KEYCODE_RIGHT:
|
||||
if (gDebugLevel >= 0) {
|
||||
_game->scheduleEnteringRoomUsingGate(_game->nextRoomNum(), 0);
|
||||
}
|
||||
break;
|
||||
case Common::KEYCODE_LEFT:
|
||||
if (gDebugLevel >= 0) {
|
||||
_game->scheduleEnteringRoomUsingGate(_game->prevRoomNum(), 0);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
|
||||
switch (event.customType) {
|
||||
case kActionEscape: {
|
||||
if (_game->getLoopStatus() == kStatusInventory &&
|
||||
_game->getLoopSubstatus() == kOuterLoop) {
|
||||
_game->inventoryDone();
|
||||
break;
|
||||
}
|
||||
|
||||
const int escRoom = _game->getRoomNum() != _game->getMapRoom()
|
||||
? _game->getEscRoom() : _game->getPreviousRoomNum();
|
||||
|
||||
// Check if there is an escape room defined for the current room
|
||||
if (escRoom >= 0) {
|
||||
|
||||
// Schedule room change
|
||||
// TODO: gate 0 (always present) is not always best for
|
||||
// returning from the map, e.g. in the starting location.
|
||||
// also, after loading the game, we shouldn't run any gate
|
||||
// program, but rather restore the state of all objects.
|
||||
_game->scheduleEnteringRoomUsingGate(escRoom, 0);
|
||||
|
||||
// Immediately cancel any running animation or dubbing and
|
||||
// end any currently running GPL programs. In the intro it
|
||||
// works as intended---skipping the rest of it.
|
||||
//
|
||||
// In the map, this causes that animation on newly
|
||||
// discovered locations will be re-run next time and
|
||||
// cut-scenes won't be played.
|
||||
_game->setExitLoop(true);
|
||||
_script->endCurrentProgram(true);
|
||||
|
||||
stopTextToSpeech();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case kActionMap:
|
||||
if (_game->getLoopStatus() == kStatusOrdinary) {
|
||||
const int new_room = _game->getRoomNum() != _game->getMapRoom()
|
||||
? _game->getMapRoom() : _game->getPreviousRoomNum();
|
||||
_game->scheduleEnteringRoomUsingGate(new_room, 0);
|
||||
}
|
||||
break;
|
||||
case kActionShowWalkMap:
|
||||
// Show walking map toggle
|
||||
_showWalkingMap = !_showWalkingMap;
|
||||
_game->switchWalkingAnimations(_showWalkingMap);
|
||||
break;
|
||||
case kActionToggleWalkSpeed:
|
||||
_game->setWantQuickHero(!_game->getWantQuickHero());
|
||||
break;
|
||||
case kActionInventory:
|
||||
if (_game->getRoomNum() == _game->getMapRoom() ||
|
||||
_game->getLoopSubstatus() != kOuterLoop) {
|
||||
break;
|
||||
}
|
||||
if (_game->getLoopStatus() == kStatusInventory) {
|
||||
_game->inventoryDone();
|
||||
} else if (_game->getLoopStatus() == kStatusOrdinary) {
|
||||
_game->inventoryInit();
|
||||
}
|
||||
break;
|
||||
case kActionOpenMainMenu:
|
||||
if (event.kbd.hasFlags(0)) {
|
||||
openMainMenuDialog();
|
||||
}
|
||||
break;
|
||||
case kActionTogglePointerItem:
|
||||
case kActionInvRotatePrevious:
|
||||
case kActionInvRotateNext:
|
||||
if ((_game->getLoopStatus() == kStatusOrdinary ||
|
||||
_game->getLoopStatus() == kStatusInventory) &&
|
||||
_game->getLoopSubstatus() == kOuterLoop &&
|
||||
_game->getRoomNum() != _game->getMapRoom()) {
|
||||
_game->inventorySwitch(event.customType);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
_mouse->handleEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle EVENT_QUIT and EVENT_RETURN_TO_LAUNCHER.
|
||||
if (shouldQuit()) {
|
||||
_game->setQuit(true);
|
||||
_script->endCurrentProgram(true);
|
||||
}
|
||||
}
|
||||
|
||||
DraciEngine::~DraciEngine() {
|
||||
// Dispose your resources here
|
||||
|
||||
// If the common library supported Boost's scoped_ptr<>, then wrapping
|
||||
// all the following pointers and many more would be appropriate. So
|
||||
// far, there is only SharedPtr, which I feel being an overkill for
|
||||
// easy deallocation.
|
||||
// TODO: We have ScopedPtr nowadays. Maybe should adapt this code then?
|
||||
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;
|
||||
|
||||
delete _sound;
|
||||
delete _music;
|
||||
delete _soundsArchive;
|
||||
delete _dubbingArchive;
|
||||
}
|
||||
|
||||
Common::Error DraciEngine::run() {
|
||||
init();
|
||||
setTotalPlayTime(0);
|
||||
_game->init();
|
||||
|
||||
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
|
||||
if (ttsMan != nullptr) {
|
||||
ttsMan->enable(ConfMan.getBool("tts_enabled_objects") || ConfMan.getBool("tts_enabled_speech") || ConfMan.getBool("tts_enabled_missing_voice"));
|
||||
ttsMan->setLanguage(ConfMan.get("language"));
|
||||
}
|
||||
|
||||
// Load game from specified slot, if any
|
||||
if (ConfMan.hasKey("save_slot")) {
|
||||
loadGameState(ConfMan.getInt("save_slot"));
|
||||
}
|
||||
|
||||
_game->start();
|
||||
return Common::kNoError;
|
||||
}
|
||||
|
||||
void DraciEngine::pauseEngineIntern(bool pause) {
|
||||
Engine::pauseEngineIntern(pause);
|
||||
if (pause) {
|
||||
_pauseStartTime = _system->getMillis();
|
||||
|
||||
_anims->pauseAnimations();
|
||||
_sound->pauseSound();
|
||||
_sound->pauseVoice();
|
||||
_music->pause();
|
||||
} else {
|
||||
_anims->unpauseAnimations();
|
||||
_sound->resumeSound();
|
||||
_sound->resumeVoice();
|
||||
_music->resume();
|
||||
|
||||
// Adjust engine start time
|
||||
const int delta = _system->getMillis() - _pauseStartTime;
|
||||
_game->shiftSpeechAndFadeTick(delta);
|
||||
_pauseStartTime = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void DraciEngine::syncSoundSettings() {
|
||||
Engine::syncSoundSettings();
|
||||
|
||||
_sound->setVolume();
|
||||
_music->syncVolume();
|
||||
}
|
||||
|
||||
Common::String DraciEngine::getSavegameFile(int saveGameIdx) {
|
||||
return Common::String::format("draci.s%02d", saveGameIdx);
|
||||
}
|
||||
|
||||
Common::Error DraciEngine::loadGameState(int slot) {
|
||||
// When called from run() using save_slot, the next operation is the
|
||||
// call to start() calling enterNewRoom().
|
||||
// When called from handleEvents() in the middle of the game, the next
|
||||
// operation after handleEvents() exits from loop(), and returns to
|
||||
// start() to the same place as above.
|
||||
// In both cases, we are safe to override the data structures right
|
||||
// here are now, without waiting for any other code to finish, thanks
|
||||
// to our constraint in canLoadGameStateCurrently() and to having
|
||||
// enterNewRoom() called right after we exit from here.
|
||||
return loadSavegameData(slot, this);
|
||||
}
|
||||
|
||||
bool DraciEngine::canLoadGameStateCurrently(Common::U32String *msg) {
|
||||
return (_game->getLoopStatus() == kStatusOrdinary) &&
|
||||
(_game->getLoopSubstatus() == kOuterLoop);
|
||||
}
|
||||
|
||||
Common::Error DraciEngine::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
|
||||
return saveSavegameData(slot, desc, *this);
|
||||
}
|
||||
|
||||
bool DraciEngine::canSaveGameStateCurrently(Common::U32String *msg) {
|
||||
return (_game->getLoopStatus() == kStatusOrdinary) &&
|
||||
(_game->getLoopSubstatus() == kOuterLoop);
|
||||
}
|
||||
|
||||
void DraciEngine::sayText(const Common::String &text, bool isSubtitle) {
|
||||
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
|
||||
// _previousSaid is used to prevent the TTS from looping when sayText is called inside a loop,
|
||||
// for example when the cursor stays on a dialog option. Without it when the text ends it would speak
|
||||
// the same text again.
|
||||
// _previousSaid is cleared when appropriate to allow for repeat requests
|
||||
bool speak = (!isSubtitle && ConfMan.getBool("tts_enabled_objects") && _previousSaid != text) ||
|
||||
(isSubtitle && (ConfMan.getBool("tts_enabled_speech") || ConfMan.getBool("tts_enabled_missing_voice")));
|
||||
if (ttsMan != nullptr && speak) {
|
||||
#ifdef USE_TTS
|
||||
ttsMan->say(convertText(text), Common::TextToSpeechManager::INTERRUPT);
|
||||
#endif
|
||||
_previousSaid = text;
|
||||
}
|
||||
}
|
||||
|
||||
void DraciEngine::stopTextToSpeech() {
|
||||
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
|
||||
if (ttsMan != nullptr && (ConfMan.getBool("tts_enabled_objects") || ConfMan.getBool("tts_enabled_speech") || ConfMan.getBool("tts_enabled_missing_voice")) &&
|
||||
ttsMan->isSpeaking()) {
|
||||
ttsMan->stop();
|
||||
_previousSaid.clear();
|
||||
setTTSVoice(kBertID);
|
||||
}
|
||||
}
|
||||
|
||||
void DraciEngine::setTTSVoice(int characterID) const {
|
||||
#ifdef USE_TTS
|
||||
Common::TextToSpeechManager *ttsMan = g_system->getTextToSpeechManager();
|
||||
if (ttsMan != nullptr && (ConfMan.getBool("tts_enabled_objects") || ConfMan.getBool("tts_enabled_speech") || ConfMan.getBool("tts_enabled_missing_voice"))) {
|
||||
Common::Array<int> voices;
|
||||
int pitch = 0;
|
||||
Common::TTSVoice::Gender gender;
|
||||
|
||||
if (characterDialogData[characterID].male) {
|
||||
voices = ttsMan->getVoiceIndicesByGender(Common::TTSVoice::MALE);
|
||||
gender = Common::TTSVoice::MALE;
|
||||
} else {
|
||||
voices = ttsMan->getVoiceIndicesByGender(Common::TTSVoice::FEMALE);
|
||||
gender = Common::TTSVoice::FEMALE;
|
||||
}
|
||||
|
||||
// If no voice is available for the necessary gender, set the voice to default
|
||||
if (voices.empty()) {
|
||||
ttsMan->setVoice(0);
|
||||
} else {
|
||||
int voiceIndex = characterDialogData[characterID].voiceID % voices.size();
|
||||
ttsMan->setVoice(voices[voiceIndex]);
|
||||
}
|
||||
|
||||
// If no voices are available for this gender, alter the pitch to mimic a voice
|
||||
// of the other gender
|
||||
if (ttsMan->getVoice().getGender() != gender) {
|
||||
if (gender == Common::TTSVoice::MALE) {
|
||||
pitch -= 50;
|
||||
} else {
|
||||
pitch += 50;
|
||||
}
|
||||
}
|
||||
|
||||
ttsMan->setPitch(pitch);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_TTS
|
||||
|
||||
Common::U32String DraciEngine::convertText(const Common::String &text) const {
|
||||
const uint16 *translationTable;
|
||||
|
||||
if (getLanguage() == Common::PL_POL) {
|
||||
translationTable = polishEncodingTable;
|
||||
} else {
|
||||
translationTable = kamenickyEncodingTable;
|
||||
}
|
||||
|
||||
const byte *bytes = (const byte *)text.c_str();
|
||||
byte *convertedBytes = new byte[text.size() * 2 + 1];
|
||||
|
||||
int i = 0;
|
||||
for (const byte *b = bytes; *b; ++b) {
|
||||
if (*b == 0x7c) { // Convert | to a space
|
||||
convertedBytes[i] = 0x20;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (*b < 0x80 || *b > 0xab) {
|
||||
convertedBytes[i] = *b;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool inTable = false;
|
||||
for (int j = 0; translationTable[j]; ++j) {
|
||||
if (*b - 0x80 == j) {
|
||||
int convertedValue = translationTable[j];
|
||||
|
||||
if (translationTable[j] == 0xc3b4 && getLanguage() == Common::DE_DEU) {
|
||||
// German encoding replaces ô with ß
|
||||
convertedValue = 0xc39f;
|
||||
} else if ((getLanguage() == Common::EN_ANY || getLanguage() == Common::DE_DEU) &&
|
||||
translationTable[j] != czechCharacterConversionTable[j]) {
|
||||
// Replace certain Czech characters for English and German TTS with close alternatives
|
||||
// in those languages, for better TTS
|
||||
convertedValue = czechCharacterConversionTable[j];
|
||||
}
|
||||
|
||||
if (convertedValue <= 0xff) {
|
||||
convertedBytes[i] = convertedValue;
|
||||
i++;
|
||||
} else {
|
||||
convertedBytes[i] = (convertedValue >> 8) & 0xff;
|
||||
convertedBytes[i + 1] = convertedValue & 0xff;
|
||||
i += 2;
|
||||
}
|
||||
|
||||
inTable = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!inTable) {
|
||||
convertedBytes[i] = *b;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
convertedBytes[i] = 0;
|
||||
|
||||
Common::U32String result((char *)convertedBytes);
|
||||
delete[] convertedBytes;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
} // End of namespace Draci
|
||||
202
engines/draci/draci.h
Normal file
202
engines/draci/draci.h
Normal file
@@ -0,0 +1,202 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DRACI_DRACI_H
|
||||
#define DRACI_DRACI_H
|
||||
|
||||
#include "engines/engine.h"
|
||||
#include "common/random.h"
|
||||
#include "draci/console.h"
|
||||
|
||||
struct ADGameDescription;
|
||||
|
||||
class MidiDriver;
|
||||
class OSystem;
|
||||
|
||||
/**
|
||||
* This is the namespace of the Draci engine.
|
||||
*
|
||||
* Status of this engine: Complete
|
||||
*
|
||||
* Games using this engine:
|
||||
* - Dragon History
|
||||
*/
|
||||
namespace Draci {
|
||||
|
||||
enum DRACIAction {
|
||||
kActionNone,
|
||||
kActionEscape,
|
||||
kActionMap,
|
||||
kActionShowWalkMap,
|
||||
kActionToggleWalkSpeed,
|
||||
kActionInventory,
|
||||
kActionOpenMainMenu,
|
||||
kActionTogglePointerItem,
|
||||
kActionInvRotatePrevious,
|
||||
kActionInvRotateNext
|
||||
};
|
||||
|
||||
#ifdef USE_TTS
|
||||
|
||||
struct CharacterDialogData {
|
||||
uint8 voiceID;
|
||||
bool male;
|
||||
};
|
||||
|
||||
static const CharacterDialogData characterDialogData[] = {
|
||||
{ 0, true }, // Bert
|
||||
{ 1, true }, // Narrator
|
||||
{ 2, true }, // Giant
|
||||
{ 3, true }, // Captured dwarf
|
||||
{ 4, true }, // Troll
|
||||
{ 0, false }, // Berta
|
||||
{ 5, true }, // Herbert
|
||||
{ 1, false }, // Evelyn
|
||||
{ 1, false }, // Evelyn
|
||||
{ 2, false }, // Karmela
|
||||
{ 6, true }, // King
|
||||
{ 7, true }, // Wind
|
||||
{ 8, true }, // Worm
|
||||
{ 9, true }, // Pub dwarf
|
||||
{ 10, true }, // Card player 2
|
||||
{ 11, true }, // Card player 1
|
||||
{ 12, true }, // Barkeeper
|
||||
{ 13, true }, // Lazy man
|
||||
{ 14, true }, // Goblin 1
|
||||
{ 15, true }, // Goblin 2
|
||||
{ 3, false }, // Chronicle
|
||||
{ 16, true }, // Beggar
|
||||
{ 17, true }, // Horn-blower
|
||||
{ 18, true }, // Herbert
|
||||
{ 19, true }, // Wizard
|
||||
{ 20, true }, // Comedian
|
||||
{ 21, true }, // Darter
|
||||
{ 4, true }, // Troll
|
||||
{ 21, true }, // Darter
|
||||
{ 0, true }, // Skull
|
||||
{ 22, true }, // Canary
|
||||
{ 23, true }, // Oak-tree
|
||||
{ 24, true }, // Beech-tree
|
||||
{ 4, false }, // Agatha
|
||||
{ 5, false }, // Eulanie
|
||||
{ 25, true }, // Knight
|
||||
{ 1, false }, // Evelyn
|
||||
{ 20, true }, // Comedian
|
||||
{ 1, true }, // Narrator
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
static const int kBertID = 0;
|
||||
static const int kNarratorID = 1;
|
||||
|
||||
class Screen;
|
||||
class Mouse;
|
||||
class Game;
|
||||
class Script;
|
||||
class AnimationManager;
|
||||
class Sound;
|
||||
class MusicPlayer;
|
||||
class Font;
|
||||
class BArchive;
|
||||
class SoundArchive;
|
||||
|
||||
class DraciEngine : public Engine {
|
||||
public:
|
||||
DraciEngine(OSystem *syst, const ADGameDescription *gameDesc);
|
||||
~DraciEngine() override;
|
||||
|
||||
int init();
|
||||
Common::Error run() override;
|
||||
|
||||
Common::Language getLanguage() const;
|
||||
|
||||
bool hasFeature(Engine::EngineFeature f) const override;
|
||||
void pauseEngineIntern(bool pause) override;
|
||||
void syncSoundSettings() override;
|
||||
|
||||
void handleEvents();
|
||||
|
||||
static Common::String getSavegameFile(int saveGameIdx);
|
||||
Common::String getSaveStateName(int slot) const override { return getSavegameFile(slot); }
|
||||
Common::Error loadGameState(int slot) override;
|
||||
bool canLoadGameStateCurrently(Common::U32String *msg = nullptr) override;
|
||||
Common::Error saveGameState(int slot, const Common::String &desc, bool isAutosave = false) override;
|
||||
bool canSaveGameStateCurrently(Common::U32String *msg = nullptr) override;
|
||||
|
||||
void sayText(const Common::String &text, bool isSubtitle = false);
|
||||
void stopTextToSpeech();
|
||||
void setTTSVoice(int characterID) const;
|
||||
#ifdef USE_TTS
|
||||
Common::U32String convertText(const Common::String &text) const;
|
||||
#endif
|
||||
|
||||
const ADGameDescription *_gameDescription;
|
||||
|
||||
Screen *_screen;
|
||||
Mouse *_mouse;
|
||||
Game *_game;
|
||||
Script *_script;
|
||||
AnimationManager *_anims;
|
||||
Sound *_sound;
|
||||
MusicPlayer *_music;
|
||||
|
||||
Font *_smallFont;
|
||||
Font *_bigFont;
|
||||
|
||||
Common::String _previousSaid;
|
||||
|
||||
BArchive *_iconsArchive;
|
||||
BArchive *_objectsArchive;
|
||||
BArchive *_spritesArchive;
|
||||
BArchive *_paletteArchive;
|
||||
BArchive *_roomsArchive;
|
||||
BArchive *_overlaysArchive;
|
||||
BArchive *_animationsArchive;
|
||||
BArchive *_walkingMapsArchive;
|
||||
BArchive *_itemsArchive;
|
||||
BArchive *_itemImagesArchive;
|
||||
BArchive *_initArchive;
|
||||
BArchive *_stringsArchive;
|
||||
|
||||
SoundArchive *_soundsArchive;
|
||||
SoundArchive *_dubbingArchive;
|
||||
|
||||
bool _showWalkingMap;
|
||||
|
||||
Common::RandomSource _rnd;
|
||||
|
||||
int32 _pauseStartTime;
|
||||
};
|
||||
|
||||
enum {
|
||||
kDraciGeneralDebugLevel = 1,
|
||||
kDraciBytecodeDebugLevel,
|
||||
kDraciArchiverDebugLevel,
|
||||
kDraciLogicDebugLevel,
|
||||
kDraciAnimationDebugLevel,
|
||||
kDraciSoundDebugLevel,
|
||||
kDraciWalkingDebugLevel,
|
||||
};
|
||||
|
||||
} // End of namespace Draci
|
||||
|
||||
#endif // DRACI_DRACI_H
|
||||
330
engines/draci/font.cpp
Normal file
330
engines/draci/font.cpp
Normal file
@@ -0,0 +1,330 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/file.h"
|
||||
|
||||
#include "draci/draci.h"
|
||||
#include "draci/font.h"
|
||||
#include "draci/surface.h"
|
||||
|
||||
namespace Draci {
|
||||
|
||||
const char * const kFontSmall = "Small.fon";
|
||||
const char * const kFontBig = "Big.fon";
|
||||
|
||||
Font::Font(const Common::Path &filename) {
|
||||
_fontHeight = 0;
|
||||
_maxCharWidth = 0;
|
||||
_charWidths = nullptr;
|
||||
_charData = nullptr;
|
||||
|
||||
loadFont(filename);
|
||||
}
|
||||
|
||||
Font::~Font() {
|
||||
freeFont();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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::Path &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.toString(Common::Path::kNativeSeparator).c_str());
|
||||
} else {
|
||||
debugC(6, kDraciGeneralDebugLevel, "Error opening font file %s",
|
||||
filename.toString(Common::Path::kNativeSeparator).c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
_maxCharWidth = f.readByte();
|
||||
_fontHeight = f.readByte();
|
||||
|
||||
// Read in the widths of the glyphs
|
||||
_charWidths = new uint8[kCharNum];
|
||||
for (uint i = 0; i < kCharNum; ++i) {
|
||||
_charWidths[i] = f.readByte();
|
||||
}
|
||||
|
||||
// Calculate size of font data
|
||||
uint fontDataSize = kCharNum * _maxCharWidth * _fontHeight;
|
||||
|
||||
// Read in all glyphs
|
||||
_charData = new byte[fontDataSize];
|
||||
f.read(_charData, fontDataSize);
|
||||
|
||||
debugC(5, kDraciGeneralDebugLevel, "Font %s loaded", filename.toString(Common::Path::kNativeSeparator).c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Font::freeFont() {
|
||||
delete[] _charWidths;
|
||||
delete[] _charData;
|
||||
}
|
||||
|
||||
uint8 Font::getCharWidth(uint8 chr) const {
|
||||
// Safe-guard against incorrect strings containing localized characters
|
||||
// with inaccessible codes. These strings do not exist in the original
|
||||
// Czech version, but they do in the (never properly reviewed) English
|
||||
// version.
|
||||
return chr >= kCharIndexOffset && chr < kCharIndexOffset + kCharNum
|
||||
? _charWidths[chr - kCharIndexOffset] : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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, int with_color) const {
|
||||
assert(dst != nullptr);
|
||||
assert(tx >= 0);
|
||||
assert(ty >= 0);
|
||||
|
||||
byte *ptr = (byte *)dst->getBasePtr(tx, ty);
|
||||
const uint8 currentWidth = getCharWidth(chr);
|
||||
if (currentWidth == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8 charIndex = chr - kCharIndexOffset;
|
||||
const int charOffset = charIndex * _fontHeight * _maxCharWidth;
|
||||
|
||||
// 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->getTransparentColor();
|
||||
|
||||
for (int y = 0; y < yPixelsToDraw; ++y) {
|
||||
for (int x = 0; x <= xPixelsToDraw; ++x) {
|
||||
|
||||
int curr = y * _maxCharWidth + x;
|
||||
int color = _charData[charOffset + curr];
|
||||
|
||||
// If pixel is transparent, skip it
|
||||
if (color == _transparent)
|
||||
continue;
|
||||
|
||||
// Replace color with font colors
|
||||
switch (color) {
|
||||
case 254:
|
||||
color = with_color;
|
||||
break;
|
||||
|
||||
case 253:
|
||||
color = kFontColor2;
|
||||
break;
|
||||
|
||||
case 252:
|
||||
color = kFontColor3;
|
||||
break;
|
||||
|
||||
case 251:
|
||||
color = kFontColor4;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Paint the pixel
|
||||
ptr[x] = color;
|
||||
}
|
||||
|
||||
// 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 with_color, int spacing, bool markDirty) const {
|
||||
drawString(dst, Common::String((const char *)str, len), x, y, with_color, 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 with_color, int spacing, bool markDirty) const {
|
||||
assert(dst != nullptr);
|
||||
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, with_color);
|
||||
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 {
|
||||
uint width = 0;
|
||||
|
||||
// Real length, including '|' separators
|
||||
uint len = str.size();
|
||||
|
||||
for (uint i = 0, tmp = 0; i < len; ++i) {
|
||||
|
||||
if (str[i] != '|') {
|
||||
tmp += getCharWidth(str[i]) + 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
|
||||
width += getCharWidth(str[i]) + 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 (uint 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
|
||||
100
engines/draci/font.h
Normal file
100
engines/draci/font.h
Normal file
@@ -0,0 +1,100 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DRACI_FONT_H
|
||||
#define DRACI_FONT_H
|
||||
|
||||
#include "graphics/font.h"
|
||||
#include "common/path.h"
|
||||
|
||||
namespace Draci {
|
||||
|
||||
extern const char * const kFontSmall;
|
||||
extern const char * const kFontBig;
|
||||
|
||||
class Surface;
|
||||
|
||||
/**
|
||||
* Default font colors. They all seem to remain constant except for the
|
||||
* first one which varies depending on the character speaking.
|
||||
* kOverFontColor is set to transparent.
|
||||
*/
|
||||
enum {
|
||||
kFontColor1 = 2,
|
||||
kFontColor2 = 0,
|
||||
kFontColor3 = 3,
|
||||
kFontColor4 = 4,
|
||||
kOverFontColor = 255,
|
||||
kTitleColor = 255,
|
||||
kLineActiveColor = 254,
|
||||
kLineInactiveColor = 255
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents the game's fonts. See docs for setFont() for font format details.
|
||||
*/
|
||||
class Font {
|
||||
|
||||
public:
|
||||
Font(const Common::Path &filename);
|
||||
~Font();
|
||||
|
||||
bool loadFont(const Common::Path &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, int with_color) const;
|
||||
|
||||
void drawString(Surface *dst, const byte *str, uint len, int x, int y, int with_color,
|
||||
int spacing, bool markDirty) const;
|
||||
void drawString(Surface *dst, const Common::String &str,
|
||||
int x, int y, int with_color, int spacing, bool markDirty) const;
|
||||
|
||||
uint getStringWidth(const Common::String &str, int spacing) const;
|
||||
uint getStringHeight(const Common::String &str) const;
|
||||
uint getLineWidth(const Common::String &str, uint startIndex, int spacing) const;
|
||||
|
||||
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 uint kCharNum = 138;
|
||||
|
||||
/**
|
||||
* Chars are indexed from the space character so this should be subtracted
|
||||
* to get the index of a glyph
|
||||
*/
|
||||
static const uint kCharIndexOffset = 32;
|
||||
|
||||
/** Internal function for freeing fonts when destructing/loading another */
|
||||
void freeFont();
|
||||
};
|
||||
|
||||
} // End of namespace Draci
|
||||
|
||||
#endif // DRACI_FONT_H
|
||||
1906
engines/draci/game.cpp
Normal file
1906
engines/draci/game.cpp
Normal file
File diff suppressed because it is too large
Load Diff
437
engines/draci/game.h
Normal file
437
engines/draci/game.h
Normal file
@@ -0,0 +1,437 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DRACI_GAME_H
|
||||
#define DRACI_GAME_H
|
||||
|
||||
#include "common/str.h"
|
||||
#include "draci/script.h"
|
||||
#include "draci/walking.h"
|
||||
|
||||
namespace Common {
|
||||
class Serializer;
|
||||
}
|
||||
|
||||
namespace Draci {
|
||||
|
||||
class BArchive;
|
||||
class DraciEngine;
|
||||
|
||||
enum {
|
||||
kDragonObject = 0
|
||||
};
|
||||
|
||||
enum {
|
||||
kDialogueLines = 4
|
||||
};
|
||||
|
||||
enum {
|
||||
kBlackPalette = -1
|
||||
};
|
||||
|
||||
enum {
|
||||
kMouseEnableSwitching = -1,
|
||||
kMouseDoNotSwitch = -2
|
||||
};
|
||||
|
||||
// Constants tuned such that with ScummVM's default talkspeed kStandardSpeed, the speed
|
||||
// computed by equation (kBaseSpeechDuration + kSpeechTimeUnit * #characters) /
|
||||
// talkspeed is equal to the original game.
|
||||
enum SpeechConstants {
|
||||
kBaseSpeechDuration = 12000,
|
||||
kSpeechTimeUnit = 2640,
|
||||
kStandardSpeed = 60
|
||||
};
|
||||
|
||||
enum FadeConstants {
|
||||
// One fading phase called from the game scripts is 50ms.
|
||||
kFadingTimeUnit = 50,
|
||||
// Fading in/out when entering/leaving a location takes 15 iterations of (at least) 7ms each.
|
||||
kBlackFadingIterations = 15,
|
||||
kBlackFadingTimeUnit = 7
|
||||
};
|
||||
|
||||
enum AnimationConstants {
|
||||
kTimeUnit = 20
|
||||
};
|
||||
|
||||
/** 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,
|
||||
kStatusChangeTimeout = 500
|
||||
};
|
||||
|
||||
static const int kCreditsMapID = 46;
|
||||
|
||||
class GameObject {
|
||||
public:
|
||||
int _absNum;
|
||||
uint _init, _look, _use, _canUse;
|
||||
bool _imInit, _imLook, _imUse;
|
||||
int _walkDir;
|
||||
byte _z;
|
||||
uint _lookX, _lookY, _useX, _useY;
|
||||
SightDirection _lookDir, _useDir;
|
||||
GPL2Program _program;
|
||||
Common::String _title;
|
||||
int _location;
|
||||
bool _visible;
|
||||
|
||||
Common::Array<Animation *> _anim;
|
||||
int _playingAnim;
|
||||
|
||||
int getAnim(int animID) const;
|
||||
int addAnim(Animation *anim);
|
||||
int playingAnim() const { return _playingAnim; }
|
||||
void playAnim(int i);
|
||||
void stopAnim();
|
||||
void deleteAnims();
|
||||
void deleteAnimsFrom(int index);
|
||||
void load(uint objNum, BArchive *archive);
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
class GameItem {
|
||||
public:
|
||||
int _absNum;
|
||||
uint _init, _look, _use, _canUse;
|
||||
bool _imInit, _imLook, _imUse;
|
||||
GPL2Program _program;
|
||||
Common::String _title;
|
||||
|
||||
Animation *_anim;
|
||||
|
||||
void load(int itemID, BArchive *archive);
|
||||
};
|
||||
|
||||
struct Person {
|
||||
uint _x, _y;
|
||||
byte _fontColor;
|
||||
};
|
||||
|
||||
struct Dialogue {
|
||||
int _canLen;
|
||||
byte *_canBlock;
|
||||
Common::String _title;
|
||||
GPL2Program _program;
|
||||
};
|
||||
|
||||
class Room {
|
||||
public:
|
||||
int _roomNum;
|
||||
byte _music;
|
||||
int _mapID;
|
||||
int _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;
|
||||
|
||||
void load(int roomNum, BArchive *archive);
|
||||
};
|
||||
|
||||
enum LoopStatus {
|
||||
kStatusOrdinary, // normal game-play: everything allowed
|
||||
kStatusGate, // during running init-scripts when entering a room: disable interactivity
|
||||
kStatusInventory, // inventory is open: cannot change the room or go to map
|
||||
kStatusDialogue // during a dialogue: cannot change the room, go to inventory
|
||||
};
|
||||
|
||||
enum LoopSubstatus {
|
||||
kOuterLoop, // outer loop: everything is allowed
|
||||
kInnerWhileTalk, // playing a voice: inner loop will exit afterwards
|
||||
kInnerWhileFade, // fading a palette: inner loop will exit when done
|
||||
kInnerDuringDialogue, // selecting continuation block: inner block will exit afterwards
|
||||
kInnerUntilExit // other inner loop: either immediately exiting or waiting for an animation to end (whose callback ends the loop)
|
||||
};
|
||||
|
||||
class Game {
|
||||
public:
|
||||
Game(DraciEngine *vm);
|
||||
~Game();
|
||||
|
||||
void init();
|
||||
void start();
|
||||
void loop(LoopSubstatus substatus, bool shouldExit);
|
||||
|
||||
// HACK: this is only for testing
|
||||
int nextRoomNum() const {
|
||||
int n = _currentRoom._roomNum;
|
||||
n = n < 37 ? n+1 : n;
|
||||
return n;
|
||||
}
|
||||
|
||||
// HACK: same as above
|
||||
int prevRoomNum() const {
|
||||
int n = _currentRoom._roomNum;
|
||||
n = n > 0 ? n-1 : n;
|
||||
return n;
|
||||
}
|
||||
|
||||
Common::Point findNearestWalkable(int x, int y) const { return _walkingMap.findNearestWalkable(x, y); }
|
||||
void heroAnimationFinished() { _walkingState.heroAnimationFinished(); }
|
||||
void stopWalking() { _walkingState.stopWalking(); } // and clear callback
|
||||
void walkHero(int x, int y, SightDirection dir); // start walking and leave callback as is
|
||||
void setHeroPosition(const Common::Point &p);
|
||||
const Common::Point &getHeroPosition() const { return _hero; }
|
||||
const Common::Point &getHeroLoadingPosition() const { return _heroLoading; }
|
||||
void positionAnimAsHero(Animation *anim);
|
||||
void positionHeroAsAnim(Animation *anim);
|
||||
|
||||
// Makes sure animation anim_index plays on the hero. If the hero's
|
||||
// position has changed, it updates the animation position. If the new
|
||||
// animation is different, it stops the old one and starts the new one,
|
||||
// otherwise it just marks dirty rectangles for moving the position.
|
||||
// Returns the current animation phase of the new animation (usually 0
|
||||
// unless the animation hasn't changed).
|
||||
int playHeroAnimation(int anim_index);
|
||||
|
||||
void loadOverlays();
|
||||
void loadWalkingMap(int mapID); // but leaves _currentRoom._mapID untouched
|
||||
void switchWalkingAnimations(bool enabled);
|
||||
|
||||
uint getNumObjects() const { return _info._numObjects; }
|
||||
GameObject *getObject(uint objNum) { return _objects + objNum; }
|
||||
const GameObject *getObjectWithAnimation(const Animation *anim) const;
|
||||
void deleteObjectAnimations();
|
||||
void deleteAnimationsAfterIndex(int lastAnimIndex);
|
||||
|
||||
int getVariable(int varNum) const { return _variables[varNum]; }
|
||||
void setVariable(int varNum, int value) { _variables[varNum] = value; }
|
||||
|
||||
const Person *getPerson(int personID) const { return &_persons[personID]; }
|
||||
|
||||
int getRoomNum() const { return _currentRoom._roomNum; }
|
||||
void setRoomNum(int num) { _currentRoom._roomNum = num; }
|
||||
int getPreviousRoomNum() const { return _previousRoom; }
|
||||
void rememberRoomNumAsPrevious() { _previousRoom = getRoomNum(); }
|
||||
void scheduleEnteringRoomUsingGate(int room, int gate) { _newRoom = room; _newGate = gate; }
|
||||
void pushNewRoom();
|
||||
void popNewRoom();
|
||||
|
||||
double getPers0() const { return _currentRoom._pers0; }
|
||||
double getPersStep() const { return _currentRoom._persStep; }
|
||||
int getMusicTrack() const { return _currentRoom._music; }
|
||||
void setMusicTrack(int num) { _currentRoom._music = num; }
|
||||
|
||||
int getItemStatus(int itemID) const { return _itemStatus[itemID]; }
|
||||
void setItemStatus(int itemID, int status) { _itemStatus[itemID] = status; }
|
||||
GameItem *getItem(int id) { return id >= 0 && id < (int)_info._numItems ? &_items[id] : NULL; }
|
||||
GameItem *getCurrentItem() const { return _currentItem; }
|
||||
void setCurrentItem(GameItem *item) { _currentItem = item; }
|
||||
int getPreviousItemPosition() const { return _previousItemPosition; }
|
||||
void setPreviousItemPosition(int pos) { _previousItemPosition = pos; }
|
||||
void removeItem(GameItem *item);
|
||||
void loadItemAnimation(GameItem *item);
|
||||
void putItem(GameItem *item, int position);
|
||||
void addItem(int itemID);
|
||||
|
||||
int getEscRoom() const { return _currentRoom._escRoom; }
|
||||
int getMapRoom() const { return _info._mapRoom; }
|
||||
int getMapID() const { return _currentRoom._mapID; }
|
||||
|
||||
/**
|
||||
* 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 getMarkedAnimationIndex() const { return _markedAnimationIndex; }
|
||||
void setMarkedAnimationIndex(int index) { _markedAnimationIndex = index; }
|
||||
|
||||
void setLoopStatus(LoopStatus status) { _loopStatus = status; }
|
||||
void setLoopSubstatus(LoopSubstatus status) { _loopSubstatus = status; }
|
||||
LoopStatus getLoopStatus() const { return _loopStatus; }
|
||||
LoopSubstatus getLoopSubstatus() const { return _loopSubstatus; }
|
||||
|
||||
bool gameShouldQuit() const { return _shouldQuit; }
|
||||
void setQuit(bool quit) { _shouldQuit = quit; }
|
||||
bool shouldExitLoop() const { return _shouldExitLoop; }
|
||||
void setExitLoop(bool exit) { _shouldExitLoop = exit; }
|
||||
bool isReloaded() const { return _isReloaded; }
|
||||
void setIsReloaded(bool value) { _isReloaded = value; }
|
||||
bool isPositionLoaded() { return _isPositionLoaded; }
|
||||
void setPositionLoaded(bool value) { _isPositionLoaded = value; }
|
||||
|
||||
void setSpeechTiming(uint tick, uint duration);
|
||||
void shiftSpeechAndFadeTick(int delta);
|
||||
|
||||
void inventoryInit();
|
||||
void inventoryDraw();
|
||||
void inventoryDone();
|
||||
void inventoryReload();
|
||||
void inventorySwitch(int action);
|
||||
|
||||
void dialogueMenu(int dialogueID);
|
||||
int dialogueDraw();
|
||||
void dialogueInit(int dialogID);
|
||||
void dialogueDone();
|
||||
|
||||
bool isDialogueBegin() const { return _dialogueBegin; }
|
||||
bool shouldExitDialogue() const { return _dialogueExit; }
|
||||
void setDialogueExit(bool exit) { _dialogueExit = exit; }
|
||||
int getDialogueBlockNum() const { return _blockNum; }
|
||||
int getDialogueVar(int dialogueID) const { return _dialogueVars[dialogueID]; }
|
||||
void setDialogueVar(int dialogueID, int value) { _dialogueVars[dialogueID] = value; }
|
||||
int getCurrentDialogue() const { return _currentDialogue; }
|
||||
int getDialogueCurrentBlock() const { return _currentBlock; }
|
||||
int getDialogueLastBlock() const { return _lastBlock; }
|
||||
int getDialogueLinesNum() const { return _dialogueLinesNum; }
|
||||
int getCurrentDialogueOffset() const { return _dialogueOffsets[_currentDialogue]; }
|
||||
|
||||
void schedulePalette(int paletteID) { _scheduledPalette = paletteID; }
|
||||
int getScheduledPalette() const { return _scheduledPalette; }
|
||||
void initializeFading(int phases);
|
||||
void setEnableQuickHero(bool value) { _enableQuickHero = value; }
|
||||
bool getEnableQuickHero() const { return _enableQuickHero; }
|
||||
void setWantQuickHero(bool value) { _wantQuickHero = value; }
|
||||
bool getWantQuickHero() const { return _wantQuickHero; }
|
||||
void setEnableSpeedText(bool value) { _enableSpeedText = value; }
|
||||
bool getEnableSpeedText() const { return _enableSpeedText; }
|
||||
|
||||
void synchronize(Common::Serializer &s, uint8 saveVersion);
|
||||
|
||||
private:
|
||||
void updateOrdinaryCursor();
|
||||
void updateInventoryCursor();
|
||||
int inventoryPositionFromMouse() const;
|
||||
void handleOrdinaryLoop(int x, int y);
|
||||
void handleInventoryLoop();
|
||||
void handleDialogueLoop();
|
||||
void updateTitle(int x, int y);
|
||||
void updateCursor();
|
||||
void fadePalette(bool fading_out);
|
||||
void advanceAnimationsAndTestLoopExit();
|
||||
void handleStatusChangeByMouse();
|
||||
|
||||
void enterNewRoom();
|
||||
void initWalkingOverlays();
|
||||
void loadRoomObjects();
|
||||
void redrawWalkingPath(Animation *anim, byte color, const WalkingPath &path);
|
||||
|
||||
DraciEngine *_vm;
|
||||
|
||||
GameInfo _info;
|
||||
|
||||
Common::Point _hero;
|
||||
Common::Point _heroLoading;
|
||||
Common::Point _lastTarget;
|
||||
|
||||
int *_variables;
|
||||
Person *_persons;
|
||||
GameObject *_objects;
|
||||
|
||||
byte *_itemStatus;
|
||||
GameItem *_items;
|
||||
GameItem *_currentItem;
|
||||
GameItem *_itemUnderCursor;
|
||||
|
||||
// Last position in the inventory of the item currently in the hands, resp. of the item that
|
||||
// was last in our hands.
|
||||
int _previousItemPosition;
|
||||
|
||||
GameItem *_inventory[kInventorySlots];
|
||||
|
||||
Room _currentRoom;
|
||||
int _newRoom;
|
||||
int _newGate;
|
||||
int _previousRoom;
|
||||
int _pushedNewRoom; // used in GPL programs
|
||||
int _pushedNewGate;
|
||||
|
||||
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;
|
||||
bool _isReloaded;
|
||||
bool _isPositionLoaded;
|
||||
|
||||
uint _speechTick;
|
||||
uint _speechDuration;
|
||||
|
||||
const GameObject *_objUnderCursor;
|
||||
const Animation *_animUnderCursor;
|
||||
|
||||
int _markedAnimationIndex; ///< Used by the Mark GPL command
|
||||
|
||||
int _scheduledPalette;
|
||||
int _fadePhases;
|
||||
int _fadePhase;
|
||||
uint _fadeTick;
|
||||
bool _isFadeOut;
|
||||
int _mouseChangeTick;
|
||||
|
||||
bool _enableQuickHero;
|
||||
bool _wantQuickHero;
|
||||
bool _enableSpeedText;
|
||||
|
||||
WalkingMap _walkingMap;
|
||||
WalkingState _walkingState;
|
||||
|
||||
Animation *_titleAnim;
|
||||
Animation *_inventoryAnim;
|
||||
Animation *_walkingMapOverlay;
|
||||
Animation *_walkingShortestPathOverlay;
|
||||
Animation *_walkingObliquePathOverlay;
|
||||
};
|
||||
|
||||
} // End of namespace Draci
|
||||
|
||||
#endif // DRACI_GAME_H
|
||||
279
engines/draci/metaengine.cpp
Normal file
279
engines/draci/metaengine.cpp
Normal file
@@ -0,0 +1,279 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "draci/draci.h"
|
||||
#include "draci/saveload.h"
|
||||
#include "draci/detection.h"
|
||||
|
||||
#include "backends/keymapper/action.h"
|
||||
#include "backends/keymapper/keymapper.h"
|
||||
#include "backends/keymapper/standard-actions.h"
|
||||
|
||||
#include "base/plugins.h"
|
||||
#include "common/system.h"
|
||||
#include "common/translation.h"
|
||||
#include "engines/advancedDetector.h"
|
||||
#include "engines/metaengine.h"
|
||||
|
||||
namespace Draci {
|
||||
|
||||
#ifdef USE_TTS
|
||||
|
||||
static const ADExtraGuiOptionsMap optionsList[] = {
|
||||
{
|
||||
GAMEOPTION_TTS_OBJECTS,
|
||||
{
|
||||
_s("Enable Text to Speech for Objects and Options"),
|
||||
_s("Use TTS to read the descriptions (if TTS is available)"),
|
||||
"tts_enabled_objects",
|
||||
false,
|
||||
0,
|
||||
0
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
GAMEOPTION_TTS_SPEECH,
|
||||
{
|
||||
_s("Enable Text to Speech for Subtitles"),
|
||||
_s("Use TTS to read the subtitles (if TTS is available)"),
|
||||
"tts_enabled_speech",
|
||||
false,
|
||||
0,
|
||||
0
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
GAMEOPTION_TTS_MISSING_VOICE,
|
||||
{
|
||||
_s("Enable Text to Speech for Missing Voiceovers"),
|
||||
_s("Use TTS to read the subtitles of missing voiceovers (if TTS is available)"),
|
||||
"tts_enabled_missing_voice",
|
||||
false,
|
||||
0,
|
||||
0
|
||||
}
|
||||
},
|
||||
|
||||
AD_EXTRA_GUI_OPTIONS_TERMINATOR
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
Common::Language DraciEngine::getLanguage() const {
|
||||
return _gameDescription->language;
|
||||
}
|
||||
|
||||
} // End of namespace Draci
|
||||
|
||||
class DraciMetaEngine : public AdvancedMetaEngine<ADGameDescription> {
|
||||
public:
|
||||
const char *getName() const override {
|
||||
return "draci";
|
||||
}
|
||||
|
||||
#ifdef USE_TTS
|
||||
const ADExtraGuiOptionsMap *getAdvancedExtraGuiOptions() const override {
|
||||
return Draci::optionsList;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool hasFeature(MetaEngineFeature f) const override;
|
||||
int getMaximumSaveSlot() const override { return 99; }
|
||||
SaveStateList listSaves(const char *target) const override;
|
||||
bool removeSaveState(const char *target, int slot) const override;
|
||||
SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const override;
|
||||
Common::Error createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const override;
|
||||
|
||||
Common::KeymapArray initKeymaps(const char *target) const override;
|
||||
};
|
||||
|
||||
bool DraciMetaEngine::hasFeature(MetaEngineFeature f) const {
|
||||
return
|
||||
(f == kSupportsListSaves) ||
|
||||
(f == kSupportsDeleteSave) ||
|
||||
(f == kSavesSupportMetaInfo) ||
|
||||
(f == kSavesSupportThumbnail) ||
|
||||
(f == kSavesSupportCreationDate) ||
|
||||
(f == kSavesSupportPlayTime) ||
|
||||
(f == kSupportsLoadingDuringStartup);
|
||||
}
|
||||
|
||||
SaveStateList DraciMetaEngine::listSaves(const char *target) const {
|
||||
Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
|
||||
Common::String pattern("draci.s##");
|
||||
Common::StringArray filenames = saveFileMan->listSavefiles(pattern);
|
||||
|
||||
SaveStateList saveList;
|
||||
for (const auto &filename : filenames) {
|
||||
// Obtain the last 2 digits of the filename, since they correspond to the save slot
|
||||
int slotNum = atoi(filename.c_str() + filename.size() - 2);
|
||||
|
||||
if (slotNum >= 0 && slotNum <= 99) {
|
||||
Common::InSaveFile *in = saveFileMan->openForLoading(filename);
|
||||
if (in) {
|
||||
Draci::DraciSavegameHeader header;
|
||||
if (Draci::readSavegameHeader(in, header)) {
|
||||
saveList.push_back(SaveStateDescriptor(this, slotNum, header.saveName));
|
||||
}
|
||||
delete in;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort saves based on slot number.
|
||||
Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
|
||||
return saveList;
|
||||
}
|
||||
|
||||
bool DraciMetaEngine::removeSaveState(const char *target, int slot) const {
|
||||
return g_system->getSavefileManager()->removeSavefile(Draci::DraciEngine::getSavegameFile(slot));
|
||||
}
|
||||
|
||||
SaveStateDescriptor DraciMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
|
||||
Common::InSaveFile *f = g_system->getSavefileManager()->openForLoading(
|
||||
Draci::DraciEngine::getSavegameFile(slot));
|
||||
|
||||
if (f) {
|
||||
Draci::DraciSavegameHeader header;
|
||||
if (!Draci::readSavegameHeader(f, header, false)) {
|
||||
delete f;
|
||||
return SaveStateDescriptor();
|
||||
}
|
||||
|
||||
delete f;
|
||||
|
||||
// Create the return descriptor
|
||||
SaveStateDescriptor desc(this, slot, header.saveName);
|
||||
desc.setThumbnail(header.thumbnail);
|
||||
|
||||
int day = (header.date >> 24) & 0xFF;
|
||||
int month = (header.date >> 16) & 0xFF;
|
||||
int year = header.date & 0xFFFF;
|
||||
desc.setSaveDate(year, month, day);
|
||||
|
||||
int hour = (header.time >> 8) & 0xFF;
|
||||
int minutes = header.time & 0xFF;
|
||||
desc.setSaveTime(hour, minutes);
|
||||
|
||||
desc.setPlayTime(header.playtime * 1000);
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
return SaveStateDescriptor();
|
||||
}
|
||||
|
||||
Common::Error DraciMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
|
||||
*engine = new Draci::DraciEngine(syst, desc);
|
||||
return Common::kNoError;
|
||||
}
|
||||
|
||||
Common::KeymapArray DraciMetaEngine::initKeymaps(const char *target) const {
|
||||
using namespace Common;
|
||||
using namespace Draci;
|
||||
|
||||
Keymap *engineKeyMap = new Keymap(Keymap::kKeymapTypeGame, "draci-default", _("Default keymappings"));
|
||||
Keymap *gameKeyMap = new Keymap(Keymap::kKeymapTypeGame, "game-shortcuts", _("Game keymappings"));
|
||||
|
||||
Action *act;
|
||||
|
||||
act = new Action(kStandardActionLeftClick, _("Left click"));
|
||||
act->setLeftClickEvent();
|
||||
act->addDefaultInputMapping("MOUSE_LEFT");
|
||||
act->addDefaultInputMapping("JOY_A");
|
||||
engineKeyMap->addAction(act);
|
||||
|
||||
act = new Action(kStandardActionRightClick, _("Right click"));
|
||||
act->setRightClickEvent();
|
||||
act->addDefaultInputMapping("MOUSE_RIGHT");
|
||||
act->addDefaultInputMapping("JOY_B");
|
||||
engineKeyMap->addAction(act);
|
||||
|
||||
act = new Action("ESCAPE", _("Skip intro / Exit map or inventory"));
|
||||
act->setCustomEngineActionEvent(kActionEscape);
|
||||
act->addDefaultInputMapping("ESCAPE");
|
||||
act->addDefaultInputMapping("JOY_LEFT_SHOULDER");
|
||||
gameKeyMap->addAction(act);
|
||||
|
||||
act = new Action("MAP", _("Open map"));
|
||||
act->setCustomEngineActionEvent(kActionMap);
|
||||
act->addDefaultInputMapping("m");
|
||||
act->addDefaultInputMapping("JOY_A");
|
||||
gameKeyMap->addAction(act);
|
||||
|
||||
// I18N: shows where the game actor is able to move
|
||||
act = new Action("WALKMAP", _("Show walking map"));
|
||||
act->setCustomEngineActionEvent(kActionShowWalkMap);
|
||||
act->addDefaultInputMapping("w");
|
||||
act->addDefaultInputMapping("JOY_LEFT_STICK");
|
||||
gameKeyMap->addAction(act);
|
||||
|
||||
act = new Action("TOGGLEWALKSPEED", _("Toggle walk speed"));
|
||||
act->setCustomEngineActionEvent(kActionToggleWalkSpeed);
|
||||
act->addDefaultInputMapping("q");
|
||||
act->addDefaultInputMapping("JOY_RIGHT_STICK");
|
||||
gameKeyMap->addAction(act);
|
||||
|
||||
act = new Action("INV", _("Inventory"));
|
||||
act->setCustomEngineActionEvent(kActionInventory);
|
||||
act->addDefaultInputMapping("i");
|
||||
act->addDefaultInputMapping("JOY_B");
|
||||
gameKeyMap->addAction(act);
|
||||
|
||||
act = new Action("MAINMENU", _("Open main menu"));
|
||||
act->setCustomEngineActionEvent(kActionOpenMainMenu);
|
||||
act->addDefaultInputMapping("F5");
|
||||
act->addDefaultInputMapping("JOY_RIGHT_SHOULDER");
|
||||
gameKeyMap->addAction(act);
|
||||
|
||||
act = new Action("POINTERORITEM", _("Toggle between mouse pointer and the last game item"));
|
||||
act->setCustomEngineActionEvent(kActionTogglePointerItem);
|
||||
act->addDefaultInputMapping("SLASH");
|
||||
act->addDefaultInputMapping("JOY_LEFT");
|
||||
gameKeyMap->addAction(act);
|
||||
|
||||
act = new Action("PREVITEM", _("Previous item in inventory"));
|
||||
act->setCustomEngineActionEvent(kActionInvRotatePrevious);
|
||||
act->addDefaultInputMapping("COMMA");
|
||||
act->addDefaultInputMapping("JOY_RIGHT");
|
||||
gameKeyMap->addAction(act);
|
||||
|
||||
act = new Action("NEXTITEM", _("Next item in inventory"));
|
||||
act->setCustomEngineActionEvent(kActionInvRotateNext);
|
||||
act->addDefaultInputMapping("PERIOD");
|
||||
act->addDefaultInputMapping("JOY_UP");
|
||||
gameKeyMap->addAction(act);
|
||||
|
||||
|
||||
KeymapArray keymaps(2);
|
||||
keymaps[0] = engineKeyMap;
|
||||
keymaps[1] = gameKeyMap;
|
||||
|
||||
return keymaps;
|
||||
}
|
||||
|
||||
#if PLUGIN_ENABLED_DYNAMIC(DRACI)
|
||||
REGISTER_PLUGIN_DYNAMIC(DRACI, PLUGIN_TYPE_ENGINE, DraciMetaEngine);
|
||||
#else
|
||||
REGISTER_PLUGIN_STATIC(DRACI, PLUGIN_TYPE_ENGINE, DraciMetaEngine);
|
||||
#endif
|
||||
30
engines/draci/module.mk
Normal file
30
engines/draci/module.mk
Normal file
@@ -0,0 +1,30 @@
|
||||
MODULE := engines/draci
|
||||
|
||||
MODULE_OBJS := \
|
||||
animation.o \
|
||||
barchive.o \
|
||||
console.o \
|
||||
draci.o \
|
||||
font.o \
|
||||
game.o \
|
||||
metaengine.o \
|
||||
mouse.o \
|
||||
music.o \
|
||||
saveload.o \
|
||||
screen.o \
|
||||
script.o \
|
||||
sound.o \
|
||||
sprite.o \
|
||||
surface.o \
|
||||
walking.o
|
||||
|
||||
# This module can be built as a plugin
|
||||
ifeq ($(ENABLE_DRACI), DYNAMIC_PLUGIN)
|
||||
PLUGIN := 1
|
||||
endif
|
||||
|
||||
# Include common rules
|
||||
include $(srcdir)/rules.mk
|
||||
|
||||
# Detection objects
|
||||
DETECT_OBJS += $(MODULE)/detection.o
|
||||
128
engines/draci/mouse.cpp
Normal file
128
engines/draci/mouse.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "draci/draci.h"
|
||||
#include "draci/game.h"
|
||||
#include "draci/mouse.h"
|
||||
#include "draci/barchive.h"
|
||||
#include "draci/screen.h"
|
||||
#include "draci/sprite.h"
|
||||
|
||||
#include "graphics/cursorman.h"
|
||||
|
||||
#include "common/system.h"
|
||||
|
||||
namespace Draci {
|
||||
|
||||
Mouse::Mouse(DraciEngine *vm) {
|
||||
_x = 0;
|
||||
_y = 0;
|
||||
_lButton = false;
|
||||
_rButton = false;
|
||||
_cursorType = kUninitializedCursor;
|
||||
_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);
|
||||
// Don't set _lButton to false, because some touchpads generate
|
||||
// down and up at such a quick succession, that they will
|
||||
// cancel each other in the same call of handleEvents(). Let
|
||||
// the game clear this flag by calling lButtonSet() instead.
|
||||
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);
|
||||
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() const {
|
||||
return CursorMan.isVisible();
|
||||
}
|
||||
|
||||
void Mouse::setPosition(uint16 x, uint16 y) {
|
||||
_vm->_system->warpMouse(x, y);
|
||||
}
|
||||
|
||||
void Mouse::setCursorType(CursorType cur) {
|
||||
if (cur == getCursorType()) {
|
||||
return;
|
||||
}
|
||||
_cursorType = cur;
|
||||
|
||||
const BAFile *f;
|
||||
f = _vm->_iconsArchive->getFile(cur);
|
||||
|
||||
Sprite sp(f->_data, f->_length, 0, 0, true);
|
||||
CursorMan.replaceCursorPalette(_vm->_screen->getPalette(), 0, kNumColors);
|
||||
CursorMan.replaceCursor(sp.getBuffer(), sp.getWidth(), sp.getHeight(),
|
||||
sp.getWidth() / 2, sp.getHeight() / 2, 255);
|
||||
}
|
||||
|
||||
void Mouse::loadItemCursor(const GameItem *item, bool highlighted) {
|
||||
const int itemID = item->_absNum;
|
||||
const int archiveIndex = 2 * itemID + (highlighted ? 1 : 0);
|
||||
CursorType newCursor = static_cast<CursorType> (kItemCursor + archiveIndex);
|
||||
if (newCursor == getCursorType()) {
|
||||
return;
|
||||
}
|
||||
_cursorType = newCursor;
|
||||
|
||||
const BAFile *f;
|
||||
f = _vm->_itemImagesArchive->getFile(archiveIndex);
|
||||
|
||||
Sprite sp(f->_data, f->_length, 0, 0, true);
|
||||
CursorMan.replaceCursorPalette(_vm->_screen->getPalette(), 0, kNumColors);
|
||||
CursorMan.replaceCursor(sp.getBuffer(), sp.getWidth(), sp.getHeight(),
|
||||
sp.getWidth() / 2, sp.getHeight() / 2, 255);
|
||||
}
|
||||
|
||||
} // End of namespace Draci
|
||||
75
engines/draci/mouse.h
Normal file
75
engines/draci/mouse.h
Normal file
@@ -0,0 +1,75 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DRACI_MOUSE_H
|
||||
#define DRACI_MOUSE_H
|
||||
|
||||
#include "common/events.h"
|
||||
|
||||
namespace Draci {
|
||||
|
||||
enum CursorType {
|
||||
kNormalCursor,
|
||||
kArrowCursor1,
|
||||
kArrowCursor2,
|
||||
kArrowCursor3,
|
||||
kArrowCursor4,
|
||||
kDialogueCursor,
|
||||
kHighlightedCursor,
|
||||
kMainMenuCursor,
|
||||
kUninitializedCursor = 100,
|
||||
kItemCursor // + the index in the BArchive
|
||||
};
|
||||
|
||||
class DraciEngine;
|
||||
class GameItem;
|
||||
|
||||
class Mouse {
|
||||
public:
|
||||
Mouse(DraciEngine *vm);
|
||||
~Mouse() {}
|
||||
|
||||
void handleEvent(Common::Event event);
|
||||
void cursorOn();
|
||||
void cursorOff();
|
||||
bool isCursorOn() const;
|
||||
void setPosition(uint16 x, uint16 y);
|
||||
CursorType getCursorType() const { return _cursorType; }
|
||||
void setCursorType(CursorType cur);
|
||||
void loadItemCursor(const GameItem *item, bool highlighted);
|
||||
bool lButtonPressed() const { return _lButton; }
|
||||
bool rButtonPressed() const { return _rButton; }
|
||||
void lButtonSet(bool state) { _lButton = state; }
|
||||
void rButtonSet(bool state) { _rButton = state; }
|
||||
|
||||
uint16 getPosX() const { return _x; }
|
||||
uint16 getPosY() const { return _y; }
|
||||
|
||||
private:
|
||||
uint16 _x, _y;
|
||||
bool _lButton, _rButton;
|
||||
CursorType _cursorType;
|
||||
DraciEngine *_vm;
|
||||
};
|
||||
|
||||
} // End of namespace Draci
|
||||
|
||||
#endif // DRACI_MOUSE_H
|
||||
119
engines/draci/music.cpp
Normal file
119
engines/draci/music.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// MIDI and digital music class
|
||||
|
||||
#include "audio/mididrv.h"
|
||||
#include "audio/midiparser.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/file.h"
|
||||
|
||||
#include "draci/draci.h"
|
||||
#include "draci/music.h"
|
||||
|
||||
namespace Draci {
|
||||
|
||||
MusicPlayer::MusicPlayer(const char *pathMask) : _pathMask(pathMask), _isGM(false), _track(-1) {
|
||||
|
||||
MidiPlayer::createDriver();
|
||||
|
||||
int ret = _driver->open();
|
||||
if (ret == 0) {
|
||||
if (_nativeMT32)
|
||||
_driver->sendMT32Reset();
|
||||
else
|
||||
_driver->sendGMReset();
|
||||
|
||||
// TODO: Load cmf.ins with the instrument table. It seems that an
|
||||
// interface for such an operation is supported for AdLib. Maybe for
|
||||
// this card, setting instruments is necessary.
|
||||
|
||||
_driver->setTimerCallback(this, &timerCallback);
|
||||
}
|
||||
}
|
||||
|
||||
void MusicPlayer::sendToChannel(byte channel, uint32 b) {
|
||||
if (!_channelsTable[channel]) {
|
||||
_channelsTable[channel] = (channel == 15) ? _driver->getPercussionChannel() : _driver->allocateChannel();
|
||||
// If a new channel is allocated during the playback, make sure
|
||||
// its volume is correctly initialized.
|
||||
if (_channelsTable[channel])
|
||||
_channelsTable[channel]->volume(_channelsVolume[channel] * _masterVolume / 255);
|
||||
}
|
||||
|
||||
if (_channelsTable[channel])
|
||||
_channelsTable[channel]->send(b);
|
||||
}
|
||||
|
||||
void MusicPlayer::playSMF(int track, bool loop) {
|
||||
Common::StackLock lock(_mutex);
|
||||
|
||||
if (_isPlaying && track == _track) {
|
||||
debugC(2, kDraciSoundDebugLevel, "Already plaing track %d", track);
|
||||
return;
|
||||
}
|
||||
|
||||
stop();
|
||||
|
||||
_isGM = true;
|
||||
|
||||
// Load MIDI resource data
|
||||
Common::File musicFile;
|
||||
Common::String musicFileName = Common::String::format(_pathMask.c_str(), track);
|
||||
musicFile.open(musicFileName.c_str());
|
||||
if (!musicFile.isOpen()) {
|
||||
debugC(2, kDraciSoundDebugLevel, "Cannot open track %d", track);
|
||||
return;
|
||||
}
|
||||
int midiMusicSize = musicFile.size();
|
||||
free(_midiData);
|
||||
_midiData = (byte *)malloc(midiMusicSize);
|
||||
musicFile.read(_midiData, midiMusicSize);
|
||||
musicFile.close();
|
||||
|
||||
MidiParser *parser = MidiParser::createParser_SMF();
|
||||
if (parser->loadMusic(_midiData, midiMusicSize)) {
|
||||
parser->setTrack(0);
|
||||
parser->setMidiDriver(this);
|
||||
parser->setTimerRate(_driver->getBaseTempo());
|
||||
parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1);
|
||||
|
||||
_parser = parser;
|
||||
|
||||
syncVolume();
|
||||
|
||||
_isLooping = loop;
|
||||
_isPlaying = true;
|
||||
_track = track;
|
||||
debugC(2, kDraciSoundDebugLevel, "Playing track %d", track);
|
||||
} else {
|
||||
debugC(2, kDraciSoundDebugLevel, "Cannot play track %d", track);
|
||||
delete parser;
|
||||
}
|
||||
}
|
||||
|
||||
void MusicPlayer::stop() {
|
||||
Audio::MidiPlayer::stop();
|
||||
debugC(2, kDraciSoundDebugLevel, "Stopping track %d", _track);
|
||||
_track = -1;
|
||||
}
|
||||
|
||||
} // End of namespace Draci
|
||||
52
engines/draci/music.h
Normal file
52
engines/draci/music.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Music class
|
||||
|
||||
#ifndef DRACI_MUSIC_H
|
||||
#define DRACI_MUSIC_H
|
||||
|
||||
#include "audio/midiplayer.h"
|
||||
|
||||
namespace Draci {
|
||||
|
||||
// Taken from MADE, which took it from SAGA.
|
||||
|
||||
class MusicPlayer : public Audio::MidiPlayer {
|
||||
public:
|
||||
MusicPlayer(const char *pathMask);
|
||||
|
||||
void playSMF(int track, bool loop);
|
||||
void stop() override;
|
||||
|
||||
// Overload Audio::MidiPlayer method
|
||||
void sendToChannel(byte channel, uint32 b) override;
|
||||
|
||||
protected:
|
||||
Common::String _pathMask;
|
||||
bool _isGM;
|
||||
|
||||
int _track;
|
||||
};
|
||||
|
||||
} // End of namespace Draci
|
||||
|
||||
#endif
|
||||
153
engines/draci/saveload.cpp
Normal file
153
engines/draci/saveload.cpp
Normal file
@@ -0,0 +1,153 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "draci/draci.h"
|
||||
#include "draci/game.h"
|
||||
#include "draci/saveload.h"
|
||||
|
||||
#include "common/serializer.h"
|
||||
#include "common/savefile.h"
|
||||
#include "common/system.h"
|
||||
|
||||
#include "graphics/scaler.h"
|
||||
#include "graphics/thumbnail.h"
|
||||
|
||||
namespace Draci {
|
||||
|
||||
static const char *const draciIdentString = "DRACI";
|
||||
|
||||
WARN_UNUSED_RESULT bool readSavegameHeader(Common::InSaveFile *in, DraciSavegameHeader &header, bool skipThumbnail) {
|
||||
char saveIdentBuffer[6];
|
||||
|
||||
// Validate the header Id
|
||||
in->read(saveIdentBuffer, 6);
|
||||
if (strcmp(saveIdentBuffer, draciIdentString) != 0)
|
||||
return false;
|
||||
|
||||
header.version = in->readByte();
|
||||
// Version 1 is compatible with Version 2
|
||||
if (header.version > DRACI_SAVEGAME_VERSION)
|
||||
return false;
|
||||
|
||||
// Read in the string
|
||||
header.saveName.clear();
|
||||
char ch;
|
||||
while ((ch = (char)in->readByte()) != '\0') header.saveName += ch;
|
||||
|
||||
header.date = in->readUint32LE();
|
||||
header.time = in->readUint16LE();
|
||||
header.playtime = in->readUint32LE();
|
||||
|
||||
// Get the thumbnail
|
||||
if (!Graphics::loadThumbnail(*in, header.thumbnail, skipThumbnail)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void writeSavegameHeader(Common::OutSaveFile *out, const DraciSavegameHeader &header) {
|
||||
// Write out a savegame header
|
||||
out->write(draciIdentString, 6);
|
||||
out->writeByte(DRACI_SAVEGAME_VERSION);
|
||||
|
||||
// Write savegame name
|
||||
out->write(header.saveName.c_str(), header.saveName.size() + 1);
|
||||
|
||||
out->writeUint32LE(header.date);
|
||||
out->writeUint16LE(header.time);
|
||||
out->writeUint32LE(header.playtime);
|
||||
|
||||
// Create a thumbnail and save it
|
||||
Graphics::saveThumbnail(*out);
|
||||
}
|
||||
|
||||
Common::Error saveSavegameData(int saveGameIdx, const Common::String &saveName, DraciEngine &vm) {
|
||||
Common::String filename = vm.getSavegameFile(saveGameIdx);
|
||||
Common::SaveFileManager *saveMan = g_system->getSavefileManager();
|
||||
Common::OutSaveFile *f = saveMan->openForSaving(filename);
|
||||
if (f == nullptr)
|
||||
return Common::kNoGameDataFoundError;
|
||||
|
||||
TimeDate curTime;
|
||||
vm._system->getTimeAndDate(curTime);
|
||||
|
||||
// Save the savegame header
|
||||
DraciSavegameHeader header;
|
||||
header.saveName = saveName;
|
||||
header.date = ((curTime.tm_mday & 0xFF) << 24) | (((curTime.tm_mon + 1) & 0xFF) << 16) | ((curTime.tm_year + 1900) & 0xFFFF);
|
||||
header.time = ((curTime.tm_hour & 0xFF) << 8) | ((curTime.tm_min) & 0xFF);
|
||||
header.playtime = vm.getTotalPlayTime() / 1000;
|
||||
writeSavegameHeader(f, header);
|
||||
|
||||
if (f->err()) {
|
||||
delete f;
|
||||
saveMan->removeSavefile(filename);
|
||||
return Common::kWritingFailed;
|
||||
} else {
|
||||
// Create the remainder of the savegame
|
||||
Common::Serializer s(nullptr, f);
|
||||
vm._game->synchronize(s, header.version);
|
||||
|
||||
f->finalize();
|
||||
delete f;
|
||||
return Common::kNoError;
|
||||
}
|
||||
}
|
||||
|
||||
Common::Error loadSavegameData(int saveGameIdx, DraciEngine *vm) {
|
||||
Common::String saveName;
|
||||
|
||||
Common::SaveFileManager *saveMan = g_system->getSavefileManager();
|
||||
Common::InSaveFile *f = saveMan->openForLoading(vm->getSavegameFile(saveGameIdx));
|
||||
|
||||
if (f == nullptr) {
|
||||
return Common::kNoGameDataFoundError;
|
||||
}
|
||||
|
||||
// Skip over the savegame header
|
||||
DraciSavegameHeader header;
|
||||
if (!readSavegameHeader(f, header)) {
|
||||
return Common::kNoGameDataFoundError;
|
||||
}
|
||||
|
||||
// Pre-processing
|
||||
vm->_game->rememberRoomNumAsPrevious();
|
||||
vm->_game->deleteObjectAnimations();
|
||||
|
||||
// Synchronise the remaining data of the savegame
|
||||
Common::Serializer s(f, nullptr);
|
||||
vm->_game->synchronize(s, header.version);
|
||||
delete f;
|
||||
|
||||
// Post-processing
|
||||
vm->_game->scheduleEnteringRoomUsingGate(vm->_game->getRoomNum(), 0);
|
||||
vm->_game->setExitLoop(true);
|
||||
vm->_game->setIsReloaded(true);
|
||||
|
||||
vm->_game->inventoryReload();
|
||||
|
||||
vm->setTotalPlayTime(header.playtime * 1000);
|
||||
|
||||
return Common::kNoError;
|
||||
}
|
||||
|
||||
} // End of namespace Draci
|
||||
51
engines/draci/saveload.h
Normal file
51
engines/draci/saveload.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DRACI_SAVELOAD_H
|
||||
#define DRACI_SAVELOAD_H
|
||||
|
||||
#include "common/savefile.h"
|
||||
#include "common/scummsys.h"
|
||||
#include "graphics/surface.h"
|
||||
|
||||
namespace Draci {
|
||||
|
||||
#define DRACI_SAVEGAME_VERSION 2
|
||||
|
||||
struct DraciSavegameHeader {
|
||||
uint8 version;
|
||||
Common::String saveName;
|
||||
uint32 date;
|
||||
uint16 time;
|
||||
uint32 playtime;
|
||||
Graphics::Surface *thumbnail;
|
||||
};
|
||||
|
||||
class DraciEngine;
|
||||
|
||||
WARN_UNUSED_RESULT bool readSavegameHeader(Common::InSaveFile *in, DraciSavegameHeader &header, bool skipThumbnail = true);
|
||||
void writeSavegameHeader(Common::OutSaveFile *out, const DraciSavegameHeader &header);
|
||||
Common::Error saveSavegameData(int saveGameIdx, const Common::String &saveName, DraciEngine &vm);
|
||||
Common::Error loadSavegameData(int saveGameIdx, DraciEngine *vm);
|
||||
|
||||
} // End of namespace Draci
|
||||
|
||||
#endif
|
||||
145
engines/draci/screen.cpp
Normal file
145
engines/draci/screen.cpp
Normal file
@@ -0,0 +1,145 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/memstream.h"
|
||||
#include "common/system.h"
|
||||
|
||||
#include "graphics/paletteman.h"
|
||||
|
||||
#include "draci/draci.h"
|
||||
#include "draci/screen.h"
|
||||
#include "draci/surface.h"
|
||||
#include "draci/sprite.h"
|
||||
|
||||
|
||||
namespace Draci {
|
||||
|
||||
Screen::Screen(DraciEngine *vm) : _vm(vm) {
|
||||
_surface = new Surface(kScreenWidth, kScreenHeight);
|
||||
_palette = new byte[3 * kNumColors];
|
||||
_blackPalette = new byte[3 * kNumColors];
|
||||
for (int i = 0; i < 3 * kNumColors; ++i) {
|
||||
_blackPalette[i] = 0;
|
||||
}
|
||||
setPalette(nullptr, 0, kNumColors);
|
||||
this->clearScreen();
|
||||
}
|
||||
|
||||
Screen::~Screen() {
|
||||
delete _surface;
|
||||
delete[] _palette;
|
||||
delete[] _blackPalette;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets a part of the palette
|
||||
* @param data Pointer to a buffer containing new palette data
|
||||
* start Index of the color where replacement should start
|
||||
* num Number of colors to replace
|
||||
*/
|
||||
void Screen::setPalette(const byte *data, uint16 start, uint16 num) {
|
||||
Common::MemoryReadStream pal(data ? data : _blackPalette, 3 * kNumColors);
|
||||
pal.seek(start * 3);
|
||||
|
||||
// Copy the palette
|
||||
for (uint16 i = start; i < start + num; ++i) {
|
||||
_palette[i * 3] = pal.readByte();
|
||||
_palette[i * 3 + 1] = pal.readByte();
|
||||
_palette[i * 3 + 2] = pal.readByte();
|
||||
}
|
||||
|
||||
// Shift the palette two bits to the left to make it brighter. The
|
||||
// original game only uses 6-bit colors 0..63.
|
||||
for (int i = start * 3; i < (start + num) * 3; ++i) {
|
||||
_palette[i] <<= 2;
|
||||
}
|
||||
|
||||
_vm->_system->getPaletteManager()->setPalette(_palette, start, num);
|
||||
}
|
||||
|
||||
void Screen::interpolatePalettes(const byte *first, const byte *second, uint16 start, uint16 num, int index, int number) {
|
||||
Common::MemoryReadStream firstPal(first ? first : _blackPalette, 3 * kNumColors);
|
||||
Common::MemoryReadStream secondPal(second ? second : _blackPalette, 3 * kNumColors);
|
||||
firstPal.seek(start * 3);
|
||||
secondPal.seek(start * 3);
|
||||
|
||||
// Interpolate the palettes
|
||||
for (uint16 i = start; i < start + num; ++i) {
|
||||
_palette[i * 3] = interpolate(firstPal.readByte(), secondPal.readByte(), index, number);
|
||||
_palette[i * 3 + 1] = interpolate(firstPal.readByte(), secondPal.readByte(), index, number);
|
||||
_palette[i * 3 + 2] = interpolate(firstPal.readByte(), secondPal.readByte(), index, number);
|
||||
}
|
||||
|
||||
// Shift the palette two bits to the left to make it brighter
|
||||
for (int i = start * 3; i < (start + num) * 3; ++i) {
|
||||
_palette[i] <<= 2;
|
||||
}
|
||||
|
||||
_vm->_system->getPaletteManager()->setPalette(_palette, start, num);
|
||||
}
|
||||
|
||||
int Screen::interpolate(int first, int second, int index, int number) {
|
||||
return (second * index + first * (number - index)) / number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copies the current memory screen buffer to the real screen
|
||||
*/
|
||||
void Screen::copyToScreen() {
|
||||
const Common::List<Common::Rect> *dirtyRects = _surface->getDirtyRects();
|
||||
|
||||
// If a full update is needed, update the whole screen
|
||||
if (_surface->needsFullUpdate()) {
|
||||
byte *ptr = (byte *)_surface->getPixels();
|
||||
|
||||
_vm->_system->copyRectToScreen(ptr, kScreenWidth,
|
||||
0, 0, kScreenWidth, kScreenHeight);
|
||||
} else {
|
||||
// Otherwise, update only the dirty rectangles
|
||||
|
||||
for (const auto &r : *dirtyRects) {
|
||||
// Pointer to the upper left corner of the rectangle
|
||||
byte *ptr = (byte *)_surface->getBasePtr(r.left, r.top);
|
||||
|
||||
_vm->_system->copyRectToScreen(ptr, kScreenWidth,
|
||||
r.left, r.top, r.width(), r.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() {
|
||||
byte *ptr = (byte *)_surface->getPixels();
|
||||
|
||||
_surface->markDirty();
|
||||
|
||||
memset(ptr, 0, kScreenWidth * kScreenHeight);
|
||||
}
|
||||
|
||||
} // End of namespace Draci
|
||||
63
engines/draci/screen.h
Normal file
63
engines/draci/screen.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DRACI_SCREEN_H
|
||||
#define DRACI_SCREEN_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
|
||||
namespace Draci {
|
||||
|
||||
enum ScreenParameters {
|
||||
kScreenWidth = 320,
|
||||
kScreenHeight = 200,
|
||||
kNumColors = 256,
|
||||
kDefaultTransparent = 255
|
||||
};
|
||||
|
||||
class DraciEngine;
|
||||
class Surface;
|
||||
|
||||
class Screen {
|
||||
|
||||
public:
|
||||
Screen(DraciEngine *vm);
|
||||
~Screen();
|
||||
|
||||
void setPalette(const byte *data, uint16 start, uint16 num);
|
||||
void interpolatePalettes(const byte *first, const byte *second, uint16 start, uint16 num, int index, int number);
|
||||
const byte *getPalette() const { return _palette; }
|
||||
void copyToScreen();
|
||||
void clearScreen();
|
||||
Surface *getSurface() { return _surface; }
|
||||
|
||||
private:
|
||||
int interpolate(int first, int second, int index, int number);
|
||||
|
||||
Surface *_surface;
|
||||
byte *_palette;
|
||||
byte *_blackPalette;
|
||||
DraciEngine *_vm;
|
||||
};
|
||||
|
||||
} // End of namespace Draci
|
||||
|
||||
#endif // DRACI_SCREEN_H
|
||||
1233
engines/draci/script.cpp
Normal file
1233
engines/draci/script.cpp
Normal file
File diff suppressed because it is too large
Load Diff
199
engines/draci/script.h
Normal file
199
engines/draci/script.h
Normal file
@@ -0,0 +1,199 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DRACI_SCRIPT_H
|
||||
#define DRACI_SCRIPT_H
|
||||
|
||||
#include "common/array.h"
|
||||
#include "common/stream.h"
|
||||
|
||||
namespace Common {
|
||||
class ReadStream;
|
||||
}
|
||||
|
||||
namespace Draci {
|
||||
|
||||
enum {
|
||||
/** The maximum number of parameters for a GPL command */
|
||||
kMaxParams = 3,
|
||||
|
||||
kNumCommands = 55
|
||||
};
|
||||
|
||||
class DraciEngine;
|
||||
class Script;
|
||||
|
||||
typedef void (Script::*GPLHandler)(const Common::Array<int> &);
|
||||
typedef int (Script::*GPLOperatorHandler)(int, int) const;
|
||||
typedef int (Script::*GPLFunctionHandler)(int) const;
|
||||
|
||||
/**
|
||||
* Represents a single command in the GPL scripting language bytecode.
|
||||
* Each command is represented in the bytecode by a command number and a
|
||||
* subnumber.
|
||||
*/
|
||||
enum GPL2ParameterType {
|
||||
kGPL2Num = 1,
|
||||
kGPL2Str = 2,
|
||||
kGPL2Ident = 3,
|
||||
kGPL2Math = 4
|
||||
};
|
||||
|
||||
struct GPL2Command {
|
||||
byte _number;
|
||||
byte _subNumber;
|
||||
const char *_name;
|
||||
uint16 _numParams;
|
||||
GPL2ParameterType _paramTypes[kMaxParams];
|
||||
GPLHandler _handler;
|
||||
};
|
||||
|
||||
struct GPL2Operator {
|
||||
GPLOperatorHandler _handler;
|
||||
const char *_name;
|
||||
};
|
||||
|
||||
struct GPL2Function {
|
||||
GPLFunctionHandler _handler;
|
||||
const char *_name;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 Animation;
|
||||
class GameObject;
|
||||
|
||||
class Script {
|
||||
|
||||
public:
|
||||
Script(DraciEngine *vm) : _vm(vm), _jump(0), _endProgram(false) { setupCommandList(); }
|
||||
|
||||
void run(const GPL2Program &program, uint16 offset);
|
||||
void runWrapper(const GPL2Program &program, uint16 offset, bool disableCursor, bool releaseAnims);
|
||||
bool testExpression(const GPL2Program &program, uint16 offset) const;
|
||||
void endCurrentProgram(bool value) { _endProgram = value; }
|
||||
bool shouldEndProgram() const { return _endProgram; }
|
||||
|
||||
private:
|
||||
int _jump;
|
||||
bool _endProgram;
|
||||
|
||||
/** List of all GPL commands. Initialized in the constructor. */
|
||||
const GPL2Command *_commandList;
|
||||
const GPL2Operator *_operatorList;
|
||||
const GPL2Function *_functionList;
|
||||
|
||||
void c_If(const Common::Array<int> ¶ms);
|
||||
void c_Goto(const Common::Array<int> ¶ms);
|
||||
void c_Let(const Common::Array<int> ¶ms);
|
||||
void load(const Common::Array<int> ¶ms);
|
||||
void start(const Common::Array<int> ¶ms);
|
||||
void loadMusic(const Common::Array<int> ¶ms);
|
||||
void startMusic(const Common::Array<int> ¶ms);
|
||||
void stopMusic(const Common::Array<int> ¶ms);
|
||||
void mark(const Common::Array<int> ¶ms);
|
||||
void release(const Common::Array<int> ¶ms);
|
||||
void icoStat(const Common::Array<int> ¶ms);
|
||||
void objStat(const Common::Array<int> ¶ms);
|
||||
void objStatOn(const Common::Array<int> ¶ms);
|
||||
void execInit(const Common::Array<int> ¶ms);
|
||||
void execLook(const Common::Array<int> ¶ms);
|
||||
void execUse(const Common::Array<int> ¶ms);
|
||||
void stayOn(const Common::Array<int> ¶ms);
|
||||
void walkOn(const Common::Array<int> ¶ms);
|
||||
void walkOnPlay(const Common::Array<int> ¶ms);
|
||||
void play(const Common::Array<int> ¶ms);
|
||||
void startPlay(const Common::Array<int> ¶ms);
|
||||
void justTalk(const Common::Array<int> ¶ms);
|
||||
void justStay(const Common::Array<int> ¶ms);
|
||||
void newRoom(const Common::Array<int> ¶ms);
|
||||
void talk(const Common::Array<int> ¶ms);
|
||||
void loadMap(const Common::Array<int> ¶ms);
|
||||
void roomMap(const Common::Array<int> ¶ms);
|
||||
void disableQuickHero(const Common::Array<int> ¶ms);
|
||||
void enableQuickHero(const Common::Array<int> ¶ms);
|
||||
void disableSpeedText(const Common::Array<int> ¶ms);
|
||||
void enableSpeedText(const Common::Array<int> ¶ms);
|
||||
void dialogue(const Common::Array<int> ¶ms);
|
||||
void exitDialogue(const Common::Array<int> ¶ms);
|
||||
void resetDialogue(const Common::Array<int> ¶ms);
|
||||
void resetDialogueFrom(const Common::Array<int> ¶ms);
|
||||
void resetBlock(const Common::Array<int> ¶ms);
|
||||
void setPalette(const Common::Array<int> ¶ms);
|
||||
void blackPalette(const Common::Array<int> ¶ms);
|
||||
void fadePalette(const Common::Array<int> ¶ms);
|
||||
void fadePalettePlay(const Common::Array<int> ¶ms);
|
||||
void loadPalette(const Common::Array<int> ¶ms);
|
||||
void quitGame(const Common::Array<int> ¶ms);
|
||||
void pushNewRoom(const Common::Array<int> ¶ms);
|
||||
void popNewRoom(const Common::Array<int> ¶ms);
|
||||
|
||||
int operAnd(int op1, int op2) const;
|
||||
int operOr(int op1, int op2) const;
|
||||
int operXor(int op1, int op2) const;
|
||||
int operSub(int op1, int op2) const;
|
||||
int operAdd(int op1, int op2) const;
|
||||
int operDiv(int op1, int op2) const;
|
||||
int operMul(int op1, int op2) const;
|
||||
int operEqual(int op1, int op2) const;
|
||||
int operNotEqual(int op1, int op2) const;
|
||||
int operGreater(int op1, int op2) const;
|
||||
int operLess(int op1, int op2) const;
|
||||
int operGreaterOrEqual(int op1, int op2) const;
|
||||
int operLessOrEqual(int op1, int op2) const;
|
||||
int operMod(int op1, int op2) const;
|
||||
|
||||
int funcRandom(int n) const;
|
||||
int funcNot(int n) const;
|
||||
int funcIsIcoOn(int iconID) const;
|
||||
int funcIcoStat(int iconID) const;
|
||||
int funcActIco(int iconID) const;
|
||||
int funcIsIcoAct(int iconID) const;
|
||||
int funcIsObjOn(int objID) const;
|
||||
int funcIsObjOff(int objID) const;
|
||||
int funcIsObjAway(int objID) const;
|
||||
int funcActPhase(int objID) const;
|
||||
int funcObjStat(int objID) const;
|
||||
int funcLastBlock(int blockID) const;
|
||||
int funcAtBegin(int yesno) const;
|
||||
int funcBlockVar(int blockID) const;
|
||||
int funcHasBeen(int blockID) const;
|
||||
int funcMaxLine(int lines) const;
|
||||
|
||||
void setupCommandList();
|
||||
const GPL2Command *findCommand(byte num, byte subnum) const;
|
||||
int handleMathExpression(Common::ReadStream *reader) const;
|
||||
|
||||
DraciEngine *_vm;
|
||||
};
|
||||
|
||||
} // End of namespace Draci
|
||||
|
||||
#endif // DRACI_SCRIPT_H
|
||||
447
engines/draci/sound.cpp
Normal file
447
engines/draci/sound.cpp
Normal file
@@ -0,0 +1,447 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/archive.h"
|
||||
#include "common/config-manager.h"
|
||||
#include "common/debug.h"
|
||||
#include "common/file.h"
|
||||
#include "common/str.h"
|
||||
#include "common/substream.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/memstream.h"
|
||||
#include "common/compression/unzip.h"
|
||||
|
||||
#include "draci/sound.h"
|
||||
#include "draci/draci.h"
|
||||
#include "draci/game.h"
|
||||
|
||||
#include "audio/audiostream.h"
|
||||
#include "audio/mixer.h"
|
||||
#include "audio/decoders/raw.h"
|
||||
#include "audio/decoders/mp3.h"
|
||||
#include "audio/decoders/vorbis.h"
|
||||
#include "audio/decoders/flac.h"
|
||||
|
||||
namespace Draci {
|
||||
|
||||
void LegacySoundArchive::openArchive(const char *path) {
|
||||
// Close previously opened archive (if any)
|
||||
closeArchive();
|
||||
|
||||
debugCN(1, kDraciArchiverDebugLevel, "Loading samples %s: ", path);
|
||||
|
||||
_f = new Common::File();
|
||||
_f->open(path);
|
||||
if (_f->isOpen()) {
|
||||
debugC(1, kDraciArchiverDebugLevel, "Success");
|
||||
} else {
|
||||
debugC(1, kDraciArchiverDebugLevel, "Error");
|
||||
delete _f;
|
||||
_f = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
// Save path for reading in files later on
|
||||
_path = path;
|
||||
|
||||
// Read archive header
|
||||
debugC(1, kDraciArchiverDebugLevel, "Loading header");
|
||||
|
||||
uint totalLength = _f->readUint32LE();
|
||||
|
||||
const uint kMaxSamples = 4095; // The no-sound file is exactly 16K bytes long, so don't fail on short reads
|
||||
uint *sampleStarts = (uint *)malloc(kMaxSamples * sizeof(uint));
|
||||
if (!sampleStarts)
|
||||
error("[LegacySoundArchive::openArchive] Cannot allocate buffer for no-sound file");
|
||||
|
||||
for (uint i = 0; i < kMaxSamples; ++i) {
|
||||
sampleStarts[i] = _f->readUint32LE();
|
||||
}
|
||||
|
||||
// Fill the sample table
|
||||
for (_sampleCount = 0; _sampleCount < kMaxSamples - 1; ++_sampleCount) {
|
||||
int length = sampleStarts[_sampleCount + 1] - sampleStarts[_sampleCount];
|
||||
if (length <= 0 && sampleStarts[_sampleCount] >= totalLength) // heuristics to detect the last sample
|
||||
break;
|
||||
}
|
||||
if (_sampleCount > 0) {
|
||||
debugC(1, kDraciArchiverDebugLevel, "Archive info: %d samples, %d total length",
|
||||
_sampleCount, totalLength);
|
||||
_samples = new SoundSample[_sampleCount];
|
||||
for (uint i = 0; i < _sampleCount; ++i) {
|
||||
_samples[i]._offset = sampleStarts[i];
|
||||
_samples[i]._length = sampleStarts[i+1] - sampleStarts[i];
|
||||
_samples[i]._frequency = 0; // set in getSample()
|
||||
}
|
||||
if (_samples[_sampleCount-1]._offset + _samples[_sampleCount-1]._length != totalLength &&
|
||||
_samples[_sampleCount-1]._offset + _samples[_sampleCount-1]._length - _samples[0]._offset != totalLength) {
|
||||
// WORKAROUND: the stored length is stored with the header for sounds and without the header for dubbing. Crazy.
|
||||
debugC(1, kDraciArchiverDebugLevel, "Broken sound archive: %d != %d",
|
||||
_samples[_sampleCount-1]._offset + _samples[_sampleCount-1]._length,
|
||||
totalLength);
|
||||
closeArchive();
|
||||
|
||||
free(sampleStarts);
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
debugC(1, kDraciArchiverDebugLevel, "Archive info: empty");
|
||||
}
|
||||
|
||||
free(sampleStarts);
|
||||
|
||||
// Indicate that the archive has been successfully opened
|
||||
_opened = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief LegacySoundArchive close method
|
||||
*
|
||||
* Closes the currently opened archive. It can be called explicitly to
|
||||
* free up memory.
|
||||
*/
|
||||
void LegacySoundArchive::closeArchive() {
|
||||
clearCache();
|
||||
delete _f;
|
||||
_f = nullptr;
|
||||
delete[] _samples;
|
||||
_samples = nullptr;
|
||||
_sampleCount = 0;
|
||||
_path = "";
|
||||
_opened = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 LegacySoundArchive::clearCache() {
|
||||
// Delete all cached data
|
||||
for (uint i = 0; i < _sampleCount; ++i) {
|
||||
_samples[i].close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief On-demand sound sample loader
|
||||
* @param i Index of file inside an archive
|
||||
* @return Pointer to a SoundSample coresponding to the opened file or NULL (on failure)
|
||||
*
|
||||
* Loads individual samples from an archive to memory on demand.
|
||||
*/
|
||||
SoundSample *LegacySoundArchive::getSample(int i, uint freq) {
|
||||
// Check whether requested file exists
|
||||
if (i < 0 || i >= (int)_sampleCount) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
debugCN(2, kDraciArchiverDebugLevel, "Accessing sample %d from archive %s... ",
|
||||
i, _path);
|
||||
|
||||
// Check if file has already been opened and return that
|
||||
if (_samples[i]._data) {
|
||||
debugC(2, kDraciArchiverDebugLevel, "Cached");
|
||||
} else {
|
||||
// It would be nice to unify the approach with ZipSoundArchive
|
||||
// and allocate a MemoryReadStream with buffer stored inside it
|
||||
// that playSoundBuffer() would just play. Unfortunately,
|
||||
// streams are not thread-safe and the same sample couldn't
|
||||
// thus be played more than once at the same time (this holds
|
||||
// even if we create a SeekableSubReadStream from it as this
|
||||
// just uses the parent). The only thread-safe solution is to
|
||||
// share a read-only buffer and allocate separate
|
||||
// MemoryReadStream's on top of it.
|
||||
_samples[i]._data = new byte[_samples[i]._length];
|
||||
_samples[i]._format = RAW;
|
||||
|
||||
// Read in the file (without the file header)
|
||||
_f->seek(_samples[i]._offset);
|
||||
_f->read(_samples[i]._data, _samples[i]._length);
|
||||
|
||||
debugC(2, kDraciArchiverDebugLevel, "Read sample %d from archive %s",
|
||||
i, _path);
|
||||
}
|
||||
_samples[i]._frequency = freq ? freq : _defaultFreq;
|
||||
|
||||
return _samples + i;
|
||||
}
|
||||
|
||||
void ZipSoundArchive::openArchive(const char *path, const char *extension, SoundFormat format, int raw_frequency) {
|
||||
closeArchive();
|
||||
if ((format == RAW || format == RAW80) && !raw_frequency) {
|
||||
error("openArchive() expects frequency for RAW data");
|
||||
return;
|
||||
}
|
||||
|
||||
debugCN(1, kDraciArchiverDebugLevel, "Trying to open ZIP archive %s: ", path);
|
||||
_archive = Common::makeZipArchive(path);
|
||||
_path = path;
|
||||
_extension = extension;
|
||||
_format = format;
|
||||
_defaultFreq = raw_frequency;
|
||||
|
||||
if (_archive) {
|
||||
Common::ArchiveMemberList files;
|
||||
_archive->listMembers(files);
|
||||
_sampleCount = files.size();
|
||||
|
||||
// The sample files are in the form ####.mp3 but not all numbers are used so we need to
|
||||
// iterate the archive and find the last file
|
||||
for (auto &file : files) {
|
||||
Common::String filename = file->getName();
|
||||
filename.erase(filename.size() - 4); // remove .mp3 extension
|
||||
uint file_number = atoi(filename.c_str());
|
||||
if(file_number > _sampleCount) // finds the last file (numerically)
|
||||
_sampleCount = file_number;
|
||||
}
|
||||
debugC(1, kDraciArchiverDebugLevel, "Capacity %d", _sampleCount);
|
||||
} else {
|
||||
debugC(1, kDraciArchiverDebugLevel, "Failed");
|
||||
}
|
||||
}
|
||||
|
||||
void ZipSoundArchive::closeArchive() {
|
||||
clearCache();
|
||||
delete _archive;
|
||||
_archive = nullptr;
|
||||
_path = _extension = nullptr;
|
||||
_sampleCount = _defaultFreq = 0;
|
||||
_format = RAW;
|
||||
}
|
||||
|
||||
void ZipSoundArchive::clearCache() {
|
||||
// Just deallocate the link-list of (very short) headers for each
|
||||
// dubbed sentence played in the current location. If the callers have
|
||||
// not called .close() on any of the items, call them now.
|
||||
for (auto &sample : _cache) {
|
||||
sample.close();
|
||||
}
|
||||
_cache.clear();
|
||||
}
|
||||
|
||||
SoundSample *ZipSoundArchive::getSample(int i, uint freq) {
|
||||
if (i < 0 || i >= (int)_sampleCount) {
|
||||
return nullptr;
|
||||
}
|
||||
debugCN(2, kDraciArchiverDebugLevel, "Accessing sample %d.%s from archive %s (format %d@%d, capacity %d): ",
|
||||
i, _extension, _path, static_cast<int> (_format), _defaultFreq, _sampleCount);
|
||||
if (freq != 0 && (_format != RAW && _format != RAW80)) {
|
||||
error("Cannot resample a sound in compressed format");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// We cannot really cache anything, because createReadStreamForMember()
|
||||
// returns the data as a ReadStream, which is not thread-safe. We thus
|
||||
// read it again each time even if it has possibly been already cached
|
||||
// a while ago. This is not such a problem for dubbing as for regular
|
||||
// sound samples.
|
||||
SoundSample sample;
|
||||
sample._frequency = freq ? freq : _defaultFreq;
|
||||
sample._format = _format;
|
||||
// Read in the file (without the file header)
|
||||
Common::Path filename(Common::String::format("%d.%s", i+1, _extension));
|
||||
sample._stream = _archive->createReadStreamForMember(filename);
|
||||
if (!sample._stream) {
|
||||
debugC(2, kDraciArchiverDebugLevel, "Doesn't exist");
|
||||
return nullptr;
|
||||
} else {
|
||||
debugC(2, kDraciArchiverDebugLevel, "Read");
|
||||
_cache.push_back(sample);
|
||||
// Return a pointer that we own and which we will deallocate
|
||||
// including its contents.
|
||||
return &_cache.back();
|
||||
}
|
||||
}
|
||||
|
||||
Sound::Sound(Audio::Mixer *mixer) : _mixer(mixer), _muteSound(false), _muteVoice(false),
|
||||
_showSubtitles(true), _talkSpeed(kStandardSpeed) {
|
||||
|
||||
for (int i = 0; i < SOUND_HANDLES; i++)
|
||||
_handles[i].type = kFreeHandle;
|
||||
|
||||
setVolume();
|
||||
}
|
||||
|
||||
SndHandle *Sound::getHandle() {
|
||||
for (int i = 0; i < SOUND_HANDLES; i++) {
|
||||
if (_handles[i].type != kFreeHandle && !_mixer->isSoundHandleActive(_handles[i].handle)) {
|
||||
debugC(5, kDraciSoundDebugLevel, "Handle %d has finished playing", i);
|
||||
_handles[i].type = kFreeHandle;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < SOUND_HANDLES; i++) {
|
||||
if (_handles[i].type == kFreeHandle) {
|
||||
debugC(5, kDraciSoundDebugLevel, "Allocated handle %d", i);
|
||||
return &_handles[i];
|
||||
}
|
||||
}
|
||||
|
||||
error("Sound::getHandle(): Too many sound handles");
|
||||
|
||||
return nullptr; // for compilers that don't support NORETURN
|
||||
}
|
||||
|
||||
uint Sound::playSoundBuffer(Audio::SoundHandle *handle, const SoundSample &buffer, int volume,
|
||||
sndHandleType handleType, bool loop) {
|
||||
if (!buffer._stream && !buffer._data) {
|
||||
warning("Empty stream");
|
||||
return 0;
|
||||
}
|
||||
// Create a new SeekableReadStream which will be automatically disposed
|
||||
// after the sample stops playing. Do not dispose the original
|
||||
// data/stream though.
|
||||
// Beware that if the sample comes from an archive (i.e., is stored in
|
||||
// buffer._stream), then you must NOT play it more than once at the
|
||||
// same time, because streams are not thread-safe. Playing it
|
||||
// repeatedly is OK. Currently this is ensured by that archives are
|
||||
// only used for dubbing, which is only played from one place in
|
||||
// script.cpp, which blocks until the dubbed sentence has finished
|
||||
// playing.
|
||||
Common::SeekableReadStream *stream;
|
||||
const int skip = buffer._format == RAW80 ? 80 : 0;
|
||||
if (buffer._stream) {
|
||||
stream = new Common::SeekableSubReadStream(
|
||||
buffer._stream, skip, buffer._stream->size() /* end */, DisposeAfterUse::NO);
|
||||
} else {
|
||||
stream = new Common::MemoryReadStream(
|
||||
buffer._data + skip, buffer._length - skip /* length */, DisposeAfterUse::NO);
|
||||
}
|
||||
|
||||
Audio::SeekableAudioStream *reader = nullptr;
|
||||
switch (buffer._format) {
|
||||
case RAW:
|
||||
case RAW80:
|
||||
reader = Audio::makeRawStream(stream, buffer._frequency, Audio::FLAG_UNSIGNED, DisposeAfterUse::YES);
|
||||
break;
|
||||
#ifdef USE_MAD
|
||||
case MP3:
|
||||
reader = Audio::makeMP3Stream(stream, DisposeAfterUse::YES);
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_VORBIS
|
||||
case OGG:
|
||||
reader = Audio::makeVorbisStream(stream, DisposeAfterUse::YES);
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_FLAC
|
||||
case FLAC:
|
||||
reader = Audio::makeFLACStream(stream, DisposeAfterUse::YES);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
error("Unsupported compression format %d", static_cast<int> (buffer._format));
|
||||
delete stream;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const uint length = reader->getLength().msecs();
|
||||
const Audio::Mixer::SoundType soundType = (handleType == kVoiceHandle) ?
|
||||
Audio::Mixer::kSpeechSoundType : Audio::Mixer::kSFXSoundType;
|
||||
Audio::AudioStream *audio_stream = Audio::makeLoopingAudioStream(reader, loop ? 0 : 1);
|
||||
_mixer->playStream(soundType, handle, audio_stream, -1, volume);
|
||||
return length;
|
||||
}
|
||||
|
||||
uint Sound::playSound(const SoundSample *buffer, int volume, bool loop) {
|
||||
if (!buffer || _muteSound)
|
||||
return 0;
|
||||
SndHandle *handle = getHandle();
|
||||
|
||||
handle->type = kEffectHandle;
|
||||
return playSoundBuffer(&handle->handle, *buffer, 2 * volume, handle->type, loop);
|
||||
}
|
||||
|
||||
void Sound::pauseSound() {
|
||||
for (int i = 0; i < SOUND_HANDLES; i++)
|
||||
if (_handles[i].type == kEffectHandle)
|
||||
_mixer->pauseHandle(_handles[i].handle, true);
|
||||
}
|
||||
|
||||
void Sound::resumeSound() {
|
||||
for (int i = 0; i < SOUND_HANDLES; i++)
|
||||
if (_handles[i].type == kEffectHandle)
|
||||
_mixer->pauseHandle(_handles[i].handle, false);
|
||||
}
|
||||
|
||||
void Sound::stopSound() {
|
||||
for (int i = 0; i < SOUND_HANDLES; i++)
|
||||
if (_handles[i].type == kEffectHandle) {
|
||||
_mixer->stopHandle(_handles[i].handle);
|
||||
debugC(5, kDraciSoundDebugLevel, "Stopping effect handle %d", i);
|
||||
_handles[i].type = kFreeHandle;
|
||||
}
|
||||
}
|
||||
|
||||
uint Sound::playVoice(const SoundSample *buffer) {
|
||||
if (!buffer || _muteVoice)
|
||||
return 0;
|
||||
SndHandle *handle = getHandle();
|
||||
|
||||
handle->type = kVoiceHandle;
|
||||
return playSoundBuffer(&handle->handle, *buffer, Audio::Mixer::kMaxChannelVolume, handle->type, false);
|
||||
}
|
||||
|
||||
void Sound::pauseVoice() {
|
||||
for (int i = 0; i < SOUND_HANDLES; i++)
|
||||
if (_handles[i].type == kVoiceHandle)
|
||||
_mixer->pauseHandle(_handles[i].handle, true);
|
||||
}
|
||||
|
||||
void Sound::resumeVoice() {
|
||||
for (int i = 0; i < SOUND_HANDLES; i++)
|
||||
if (_handles[i].type == kVoiceHandle)
|
||||
_mixer->pauseHandle(_handles[i].handle, false);
|
||||
}
|
||||
|
||||
void Sound::stopVoice() {
|
||||
for (int i = 0; i < SOUND_HANDLES; i++)
|
||||
if (_handles[i].type == kVoiceHandle) {
|
||||
_mixer->stopHandle(_handles[i].handle);
|
||||
debugC(5, kDraciSoundDebugLevel, "Stopping voice handle %d", i);
|
||||
_handles[i].type = kFreeHandle;
|
||||
}
|
||||
}
|
||||
|
||||
void Sound::setVolume() {
|
||||
_showSubtitles = ConfMan.getBool("subtitles");
|
||||
_talkSpeed = ConfMan.getInt("talkspeed");
|
||||
|
||||
if (_mixer->isReady()) {
|
||||
_muteSound = ConfMan.getBool("sfx_mute");
|
||||
_muteVoice = ConfMan.getBool("speech_mute");
|
||||
} else {
|
||||
_muteSound = _muteVoice = true;
|
||||
}
|
||||
|
||||
if (ConfMan.getBool("mute")) {
|
||||
_muteSound = _muteVoice = true;
|
||||
}
|
||||
|
||||
_mixer->muteSoundType(Audio::Mixer::kSFXSoundType, _muteSound);
|
||||
_mixer->muteSoundType(Audio::Mixer::kSpeechSoundType, _muteVoice);
|
||||
|
||||
_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume"));
|
||||
_mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, ConfMan.getInt("speech_volume"));
|
||||
}
|
||||
|
||||
} // End of namespace Draci
|
||||
226
engines/draci/sound.h
Normal file
226
engines/draci/sound.h
Normal file
@@ -0,0 +1,226 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DRACI_SOUND_H
|
||||
#define DRACI_SOUND_H
|
||||
|
||||
#include "common/str.h"
|
||||
#include "common/file.h"
|
||||
#include "common/list.h"
|
||||
#include "audio/mixer.h"
|
||||
|
||||
namespace Common {
|
||||
class Archive;
|
||||
class SeekableReadStream;
|
||||
}
|
||||
|
||||
namespace Draci {
|
||||
|
||||
enum SoundFormat { RAW, RAW80, MP3, OGG, FLAC }; // RAW80 means skip the first 80 bytes
|
||||
|
||||
/**
|
||||
* Represents individual files inside the archive.
|
||||
*/
|
||||
struct SoundSample {
|
||||
uint _offset; // For internal use of LegacySoundArchive
|
||||
uint _length;
|
||||
|
||||
uint _frequency; // Only when _format == RAW or RAW80
|
||||
SoundFormat _format;
|
||||
|
||||
byte *_data; // At most one of these two pointer can be non-NULL
|
||||
Common::SeekableReadStream* _stream;
|
||||
|
||||
SoundSample() : _offset(0), _length(0), _frequency(0), _format(RAW), _data(NULL), _stream(NULL) { }
|
||||
// The standard copy constructor is good enough, since we only store numbers and pointers.
|
||||
// Don't call close() automaticall in the destructor, otherwise copying causes SIGSEGV.
|
||||
void close() {
|
||||
delete[] _data;
|
||||
delete _stream;
|
||||
_data = NULL;
|
||||
_stream = NULL;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An abstract wrapper around archives of sound samples or dubbing.
|
||||
*/
|
||||
class SoundArchive {
|
||||
public:
|
||||
SoundArchive() { }
|
||||
virtual ~SoundArchive() { }
|
||||
|
||||
/**
|
||||
* Returns the number of sound samples in the archive. Zero means that
|
||||
* a fake empty archive has been opened and the caller may consider
|
||||
* opening a different one, for example with compressed music.
|
||||
*/
|
||||
virtual uint size() const = 0;
|
||||
|
||||
/**
|
||||
* Checks whether there is an archive opened. Should be called before reading
|
||||
* from the archive to check whether opening of the archive has succeeded.
|
||||
*/
|
||||
virtual bool isOpen() const = 0;
|
||||
|
||||
/**
|
||||
* Removes cached samples from memory.
|
||||
*/
|
||||
virtual void clearCache() = 0;
|
||||
|
||||
/**
|
||||
* Caches a given sample into memory and returns a pointer into it. We
|
||||
* own the returned pointer, but the user may call .close() on it,
|
||||
* which deallocates the memory of the actual sample data. If freq is
|
||||
* nonzero, then the sample is played at a different frequency (only
|
||||
* works for uncompressed samples).
|
||||
*/
|
||||
virtual SoundSample *getSample(int i, uint freq) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reads CD.SAM (with dubbing) and CD2.SAM (with sound samples) from the
|
||||
* original game. Caches all read samples in a thread-safe manner.
|
||||
*/
|
||||
class LegacySoundArchive : public SoundArchive {
|
||||
public:
|
||||
LegacySoundArchive(const char *path, uint defaultFreq) :
|
||||
_path(NULL), _samples(NULL), _sampleCount(0), _defaultFreq(defaultFreq), _opened(false), _f(NULL) {
|
||||
openArchive(path);
|
||||
}
|
||||
~LegacySoundArchive() override { closeArchive(); }
|
||||
|
||||
void openArchive(const char *path);
|
||||
void closeArchive();
|
||||
|
||||
uint size() const override { return _sampleCount; }
|
||||
bool isOpen() const override { return _opened; }
|
||||
|
||||
void clearCache() override;
|
||||
SoundSample *getSample(int i, uint freq) override;
|
||||
|
||||
private:
|
||||
const char *_path; ///< Path to file
|
||||
SoundSample *_samples; ///< Internal array of files
|
||||
uint _sampleCount; ///< Number of files in archive
|
||||
uint _defaultFreq; ///< The default sampling frequency of the archived samples
|
||||
bool _opened; ///< True if the archive is opened, false otherwise
|
||||
Common::File *_f; ///< Opened file
|
||||
};
|
||||
|
||||
/**
|
||||
* Reads ZIP archives with uncompressed files containing lossy-compressed
|
||||
* samples in arbitrary format. Doesn't do any real caching and is
|
||||
* thread-safe.
|
||||
*/
|
||||
class ZipSoundArchive : public SoundArchive {
|
||||
public:
|
||||
ZipSoundArchive() : _archive(NULL), _path(NULL), _extension(NULL), _format(RAW), _sampleCount(0), _defaultFreq(0), _cache() { }
|
||||
~ZipSoundArchive() override { closeArchive(); }
|
||||
|
||||
void openArchive(const char *path, const char *extension, SoundFormat format, int raw_frequency = 0);
|
||||
void closeArchive();
|
||||
|
||||
uint size() const override { return _sampleCount; }
|
||||
bool isOpen() const override { return _archive != NULL; }
|
||||
|
||||
void clearCache() override;
|
||||
SoundSample *getSample(int i, uint freq) override;
|
||||
|
||||
private:
|
||||
Common::Archive *_archive;
|
||||
const char *_path;
|
||||
const char *_extension;
|
||||
SoundFormat _format;
|
||||
uint _sampleCount;
|
||||
uint _defaultFreq;
|
||||
|
||||
// Since we typically play at most 1 dubbing at a time, we could get
|
||||
// away with having just 1 record allocated and reusing it each time.
|
||||
// However, that would be thread-unsafe if two samples were played.
|
||||
// Although the player does not do that, it is nicer to allow for it in
|
||||
// principle. For each dubbed sentence, we allocate a new record in
|
||||
// the following link-list, which is cleared during each location
|
||||
// change. The dubbed samples themselves are manually deallocated
|
||||
// after they end.
|
||||
Common::List<SoundSample> _cache;
|
||||
};
|
||||
|
||||
#define SOUND_HANDLES 10
|
||||
|
||||
enum sndHandleType {
|
||||
kFreeHandle,
|
||||
kEffectHandle,
|
||||
kVoiceHandle
|
||||
};
|
||||
|
||||
struct SndHandle {
|
||||
Audio::SoundHandle handle;
|
||||
sndHandleType type;
|
||||
};
|
||||
|
||||
// Taken from engines/saga/sound.h and simplified (in particular, removed
|
||||
// decompression until we support compressed files too).
|
||||
class Sound {
|
||||
public:
|
||||
|
||||
Sound(Audio::Mixer *mixer);
|
||||
~Sound() {}
|
||||
|
||||
uint playSound(const SoundSample *buffer, int volume, bool loop);
|
||||
void pauseSound();
|
||||
void resumeSound();
|
||||
void stopSound();
|
||||
bool isMutedSound() const { return _muteSound; }
|
||||
|
||||
uint playVoice(const SoundSample *buffer);
|
||||
void pauseVoice();
|
||||
void resumeVoice();
|
||||
void stopVoice();
|
||||
bool isMutedVoice() const { return _muteVoice; }
|
||||
|
||||
void stopAll() { stopVoice(); stopSound(); }
|
||||
|
||||
void setVolume();
|
||||
|
||||
bool showSubtitles() const { return _showSubtitles; }
|
||||
int talkSpeed() const { return _talkSpeed; }
|
||||
|
||||
private:
|
||||
// Returns the length of the sound sample in miliseconds.
|
||||
uint playSoundBuffer(Audio::SoundHandle *handle, const SoundSample &buffer, int volume,
|
||||
sndHandleType handleType, bool loop);
|
||||
|
||||
SndHandle *getHandle();
|
||||
|
||||
Audio::Mixer *_mixer;
|
||||
|
||||
bool _muteSound;
|
||||
bool _muteVoice;
|
||||
bool _showSubtitles;
|
||||
int _talkSpeed;
|
||||
|
||||
SndHandle _handles[SOUND_HANDLES];
|
||||
};
|
||||
|
||||
} // End of namespace Draci
|
||||
|
||||
#endif // DRACI_SOUND_H
|
||||
350
engines/draci/sprite.cpp
Normal file
350
engines/draci/sprite.cpp
Normal file
@@ -0,0 +1,350 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/memstream.h"
|
||||
|
||||
#include "draci/draci.h"
|
||||
#include "draci/font.h"
|
||||
#include "draci/sprite.h"
|
||||
#include "draci/surface.h"
|
||||
|
||||
namespace Draci {
|
||||
|
||||
const Displacement kNoDisplacement = { 0, 0, 1.0, 1.0 };
|
||||
|
||||
/**
|
||||
* @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[(uint)(width * height)];
|
||||
byte *tmp = buf;
|
||||
memcpy(buf, img, (uint)(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(uint16 width, uint16 height, byte *raw_data, int x, int y, bool columnwise)
|
||||
: _ownsData(true), _data(raw_data), _mirror(false) {
|
||||
|
||||
_width = width;
|
||||
_height = height;
|
||||
|
||||
_scaledWidth = _width;
|
||||
_scaledHeight = _height;
|
||||
|
||||
_x = x;
|
||||
_y = y;
|
||||
|
||||
_delay = 0;
|
||||
|
||||
if (columnwise) {
|
||||
transformToRows(raw_data, _width, _height);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for loading sprites from a sprite-formatted buffer, one byte per
|
||||
* pixel.
|
||||
*/
|
||||
Sprite::Sprite(const byte *sprite_data, uint16 length, int x, int y, bool columnwise)
|
||||
: _ownsData(false), _data(nullptr), _mirror(false) {
|
||||
|
||||
Common::MemoryReadStream reader(sprite_data, length);
|
||||
_width = reader.readSint16LE();
|
||||
_height = reader.readSint16LE();
|
||||
|
||||
_scaledWidth = _width;
|
||||
_scaledHeight = _height;
|
||||
|
||||
_x = x;
|
||||
_y = y;
|
||||
|
||||
_delay = 0;
|
||||
|
||||
if (!columnwise) {
|
||||
_ownsData = false;
|
||||
_data = sprite_data + 4;
|
||||
} else {
|
||||
_ownsData = true;
|
||||
byte *data = new byte[_width * _height];
|
||||
memcpy(data, sprite_data + 4, _width * _height);
|
||||
transformToRows(data, _width, _height);
|
||||
_data = data;
|
||||
}
|
||||
}
|
||||
|
||||
Sprite::~Sprite() {
|
||||
if (_ownsData) {
|
||||
delete[] _data;
|
||||
}
|
||||
}
|
||||
|
||||
int Sprite::getPixel(int x, int y, const Displacement &displacement) const {
|
||||
Common::Rect rect = getRect(displacement);
|
||||
|
||||
int dy = y - rect.top;
|
||||
int dx = x - rect.left;
|
||||
|
||||
// Calculate scaling factors
|
||||
double scaleX = double(rect.width()) / _width;
|
||||
double scaleY = double(rect.height()) / _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::drawReScaled(Surface *surface, bool markDirty, const Displacement &displacement) const {
|
||||
const Common::Rect destRect(getRect(displacement));
|
||||
Common::Rect clippedDestRect(0, 0, surface->w, surface->h);
|
||||
clippedDestRect.clip(destRect);
|
||||
|
||||
// Calculate by how much we need to adjust the source rectangle to account for cropping
|
||||
const Common::Point croppedBy(clippedDestRect.left - destRect.left, clippedDestRect.top - destRect.top);
|
||||
|
||||
// Get pointers to source and destination buffers
|
||||
byte *dst = (byte *)surface->getBasePtr(clippedDestRect.left, clippedDestRect.top);
|
||||
|
||||
const int transparent = surface->getTransparentColor();
|
||||
|
||||
// Calculate how many rows and columns we need to draw
|
||||
const int rows = clippedDestRect.height();
|
||||
const int columns = clippedDestRect.width();
|
||||
|
||||
// Precalculate column indexes
|
||||
int *columnIndices = new int[columns];
|
||||
if (!_mirror) {
|
||||
for (int j = 0; j < columns; ++j) {
|
||||
columnIndices[j] = (j + croppedBy.x) * _width / destRect.width();
|
||||
}
|
||||
} else {
|
||||
// Draw the sprite mirrored if the _mirror flag is set
|
||||
for (int j = 0; j < columns; ++j) {
|
||||
columnIndices[j] = _width - 1 - (j + croppedBy.x) * _width / destRect.width();
|
||||
}
|
||||
}
|
||||
|
||||
// Blit the sprite to the surface
|
||||
for (int i = 0; i < rows; ++i) {
|
||||
// Compute the index of current row to be drawn
|
||||
const int row = (i + croppedBy.y) * _height / destRect.height();
|
||||
const byte *row_data = _data + row * _width;
|
||||
|
||||
for (int j = 0; j < columns; ++j) {
|
||||
|
||||
// Fetch index of current column to be drawn
|
||||
const byte src = row_data[columnIndices[j]];
|
||||
|
||||
// Don't blit if the pixel is transparent on the target surface
|
||||
if (src != transparent) {
|
||||
dst[j] = src;
|
||||
}
|
||||
}
|
||||
|
||||
// Advance to next row
|
||||
dst += surface->pitch;
|
||||
}
|
||||
|
||||
// Mark the sprite's rectangle dirty
|
||||
if (markDirty) {
|
||||
surface->markDirtyRect(clippedDestRect);
|
||||
}
|
||||
|
||||
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, int relX, int relY) const {
|
||||
const Common::Rect destRect(_x + relX, _y + relY, _x + relX + _width, _y + relY + _height);
|
||||
Common::Rect clippedDestRect(0, 0, surface->w, surface->h);
|
||||
clippedDestRect.clip(destRect);
|
||||
|
||||
// Calculate by how much we need to adjust the source rectangle to account for cropping
|
||||
const Common::Point croppedBy(clippedDestRect.left - destRect.left, clippedDestRect.top - destRect.top);
|
||||
|
||||
// Get pointers to source and destination buffers
|
||||
byte *dst = (byte *)surface->getBasePtr(clippedDestRect.left, clippedDestRect.top);
|
||||
const byte *src = _data + croppedBy.y * _width +
|
||||
(!_mirror ? croppedBy.x : _width - 1 - croppedBy.x);
|
||||
|
||||
const int transparent = surface->getTransparentColor();
|
||||
|
||||
// Calculate how many rows and columns we need to draw
|
||||
const int rows = clippedDestRect.height();
|
||||
const int columns = clippedDestRect.width();
|
||||
|
||||
// Blit the sprite to the surface
|
||||
for (int i = 0; i < rows; ++i) {
|
||||
if (!_mirror) {
|
||||
for (int j = 0; j < columns; ++j) {
|
||||
if (src[j] != transparent) {
|
||||
dst[j] = src[j];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int j = 0; j < columns; ++j) {
|
||||
if (src[-j] != transparent) {
|
||||
dst[j] = src[-j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Advance to next row
|
||||
dst += surface->pitch;
|
||||
src += _width;
|
||||
}
|
||||
|
||||
// Mark the sprite's rectangle dirty
|
||||
if (markDirty) {
|
||||
surface->markDirtyRect(clippedDestRect);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Common::Rect Sprite::getRect(const Displacement &displacement) const {
|
||||
return Common::Rect(_x + displacement.relX, _y + displacement.relY,
|
||||
_x + displacement.relX + lround(_scaledWidth * displacement.extraScaleX),
|
||||
_y + displacement.relY + lround(_scaledHeight * displacement.extraScaleY));
|
||||
}
|
||||
|
||||
Text::Text(const Common::String &str, const Font *font, byte fontColor,
|
||||
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;
|
||||
_color = fontColor;
|
||||
|
||||
_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::draw(Surface *surface, bool markDirty, int relX, int relY) const {
|
||||
_font->drawString(surface, _text, _x + relX, _y + relY, _color, _spacing, true);
|
||||
}
|
||||
|
||||
Common::Rect Text::getRect(const Displacement &displacement) const {
|
||||
// Texts are never scaled
|
||||
return Common::Rect(_x + displacement.relX, _y + displacement.relY, _x + displacement.relX + _width, _y + displacement.relY + _height);
|
||||
}
|
||||
|
||||
void Text::setFont(const Font *font) {
|
||||
if (font == _font) {
|
||||
return;
|
||||
}
|
||||
_font = font;
|
||||
|
||||
_width = _font->getStringWidth(_text, _spacing);
|
||||
_height = _font->getStringHeight(_text);
|
||||
}
|
||||
|
||||
void Text::repeatedlySplitLongLines(uint maxWidth) {
|
||||
while (_width > maxWidth) {
|
||||
splitLinesLongerThan(maxWidth);
|
||||
_width = _font->getStringWidth(_text, _spacing);
|
||||
_height = _font->getStringHeight(_text);
|
||||
}
|
||||
}
|
||||
|
||||
void Text::splitLinesLongerThan(uint maxWidth) {
|
||||
char *start = const_cast<char *> (_text.c_str()); // hacky
|
||||
while (1) {
|
||||
char *end = strchr(start, '|');
|
||||
if (end) {
|
||||
*end = 0;
|
||||
}
|
||||
uint lineWidth = _font->getStringWidth(start, _spacing);
|
||||
if (lineWidth > maxWidth) {
|
||||
int middle = end ? (end - start) / 2 : strlen(start) / 2;
|
||||
for (int i = 0; ; ++i) {
|
||||
if (start[middle + i] == ' ') {
|
||||
start[middle + i] = '|';
|
||||
break;
|
||||
}
|
||||
if (start[middle - i] == ' ') {
|
||||
start[middle - i] = '|';
|
||||
break;
|
||||
}
|
||||
}
|
||||
debugC(2, kDraciGeneralDebugLevel, "Long line of width %d split into %s", lineWidth, start);
|
||||
}
|
||||
if (end) {
|
||||
*end = '|';
|
||||
start = end + 1;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Draci
|
||||
171
engines/draci/sprite.h
Normal file
171
engines/draci/sprite.h
Normal file
@@ -0,0 +1,171 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DRACI_SPRITE_H
|
||||
#define DRACI_SPRITE_H
|
||||
|
||||
#include "common/scummsys.h"
|
||||
#include "common/rect.h"
|
||||
|
||||
namespace Draci {
|
||||
|
||||
enum DrawableType {
|
||||
kDrawableText,
|
||||
kDrawableSprite
|
||||
};
|
||||
|
||||
struct Displacement {
|
||||
int relX, relY;
|
||||
double extraScaleX, extraScaleY;
|
||||
};
|
||||
|
||||
extern const Displacement kNoDisplacement;
|
||||
|
||||
class Surface;
|
||||
class Font;
|
||||
|
||||
class Drawable {
|
||||
public:
|
||||
virtual void draw(Surface *surface, bool markDirty, int relX, int relY) const = 0;
|
||||
virtual void drawReScaled(Surface *surface, bool markDirty, const Displacement &displacement) 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(const Displacement &displacement) 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:
|
||||
// Takes ownership of raw_data.
|
||||
Sprite(uint16 width, uint16 height, byte *raw_data, int x, int y, bool columnwise);
|
||||
|
||||
// It doesn't take ownership of sprite_data. If columnwise is false,
|
||||
// it internally points to the original buffer (which has lifetime at
|
||||
// least as long as this sprite, assumming that it comes from a cached
|
||||
// BArchive file), otherwise it allocates its own buffer with the
|
||||
// transposed image.
|
||||
Sprite(const byte *sprite_data, uint16 length, int x, int y, bool columnwise);
|
||||
|
||||
~Sprite() override;
|
||||
|
||||
void draw(Surface *surface, bool markDirty, int relX, int relY) const override;
|
||||
void drawReScaled(Surface *surface, bool markDirty, const Displacement &displacement) const override;
|
||||
|
||||
void setMirrorOn() { _mirror = true; }
|
||||
void setMirrorOff() { _mirror = false; }
|
||||
|
||||
Common::Rect getRect(const Displacement &displacement) const override;
|
||||
|
||||
const byte *getBuffer() const { return _data; }
|
||||
int getPixel(int x, int y, const Displacement &displacement) const;
|
||||
|
||||
DrawableType getType() const override { return kDrawableSprite; }
|
||||
|
||||
private:
|
||||
bool _ownsData;
|
||||
const byte *_data; ///< Pointer to a buffer containing raw sprite data (row-wise)
|
||||
bool _mirror;
|
||||
};
|
||||
|
||||
class Text : public Drawable {
|
||||
|
||||
public:
|
||||
Text(const Common::String &str, const Font *font, byte fontColor,
|
||||
int x, int y, uint spacing);
|
||||
~Text() override {}
|
||||
|
||||
void setText(const Common::String &str);
|
||||
void setColor(byte fontColor) { _color = fontColor; }
|
||||
void setSpacing(uint spacing) { _spacing = spacing; }
|
||||
void setFont(const Font *font);
|
||||
|
||||
void repeatedlySplitLongLines(uint maxWidth);
|
||||
|
||||
uint getLength() const { return _length; }
|
||||
|
||||
void draw(Surface *surface, bool markDirty, int relX, int relY) const override;
|
||||
|
||||
// drawReScaled just calls draw so that Text can be accessed through a Drawable pointer.
|
||||
// Text scaling does not need to be handled.
|
||||
void drawReScaled(Surface *surface, bool markDirty, const Displacement &displacement) const override { draw(surface, markDirty, displacement.relX, displacement.relY); }
|
||||
Common::Rect getRect(const Displacement &displacement) const override;
|
||||
|
||||
DrawableType getType() const override { return kDrawableText; }
|
||||
private:
|
||||
void splitLinesLongerThan(uint maxWidth);
|
||||
|
||||
Common::String _text;
|
||||
uint _length;
|
||||
uint8 _color;
|
||||
uint _spacing;
|
||||
const Font *_font;
|
||||
};
|
||||
|
||||
} // End of namespace Draci
|
||||
|
||||
#endif // DRACI_SPRITE_H
|
||||
127
engines/draci/surface.cpp
Normal file
127
engines/draci/surface.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "draci/screen.h"
|
||||
#include "draci/surface.h"
|
||||
|
||||
namespace Draci {
|
||||
|
||||
Surface::Surface(int width, int height) {
|
||||
this->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
|
||||
this->markClean();
|
||||
_transparentColor = 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 Marks the whole surface dirty
|
||||
*/
|
||||
void Surface::markDirty() {
|
||||
_fullUpdate = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Marks the whole surface clean
|
||||
*/
|
||||
void Surface::markClean() {
|
||||
_fullUpdate = false;
|
||||
_dirtyRects.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Fills the surface with the specified color
|
||||
*/
|
||||
void Surface::fill(uint color) {
|
||||
byte *ptr = (byte *)getPixels();
|
||||
|
||||
memset(ptr, color, (uint)(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(int x, int width) const {
|
||||
int newX = x - width / 2;
|
||||
|
||||
if (newX + width > w)
|
||||
newX = w - width;
|
||||
|
||||
if (newX < 0)
|
||||
newX = 0;
|
||||
|
||||
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::putAboveY(int y, int height) const {
|
||||
int newY = y - height;
|
||||
|
||||
if (newY + height > h)
|
||||
newY = h - height;
|
||||
|
||||
if (newY < 0)
|
||||
newY = 0;
|
||||
|
||||
return newY;
|
||||
}
|
||||
|
||||
} // End of namespace Draci
|
||||
67
engines/draci/surface.h
Normal file
67
engines/draci/surface.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DRACI_SURFACE_H
|
||||
#define DRACI_SURFACE_H
|
||||
|
||||
#include "common/list.h"
|
||||
#include "common/rect.h"
|
||||
#include "graphics/surface.h"
|
||||
|
||||
namespace Draci {
|
||||
|
||||
class Surface : public Graphics::Surface {
|
||||
|
||||
public:
|
||||
Surface(int width, int height);
|
||||
~Surface();
|
||||
|
||||
void markDirtyRect(Common::Rect r);
|
||||
const Common::List<Common::Rect> *getDirtyRects() const { return &_dirtyRects; }
|
||||
void clearDirtyRects() { _dirtyRects.clear(); }
|
||||
void markDirty();
|
||||
void markClean();
|
||||
bool needsFullUpdate() const { return _fullUpdate; }
|
||||
uint getTransparentColor() const { return _transparentColor; }
|
||||
void setTransparentColor(uint color) { _transparentColor = color; }
|
||||
void fill(uint color);
|
||||
uint putAboveY(int y, int height) const;
|
||||
uint centerOnX(int x, int width) const;
|
||||
Common::Rect getDimensions() const { return Common::Rect(w, h); }
|
||||
|
||||
private:
|
||||
/** The current transparent color of the surface. See getTransparentColor() and
|
||||
* setTransparentColor().
|
||||
*/
|
||||
uint _transparentColor;
|
||||
|
||||
/** 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
|
||||
772
engines/draci/walking.cpp
Normal file
772
engines/draci/walking.cpp
Normal file
@@ -0,0 +1,772 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/memstream.h"
|
||||
|
||||
#include "draci/draci.h"
|
||||
#include "draci/animation.h"
|
||||
#include "draci/game.h"
|
||||
#include "draci/walking.h"
|
||||
#include "draci/sprite.h"
|
||||
|
||||
namespace Draci {
|
||||
|
||||
void WalkingMap::load(const 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 WalkingMap::getPixel(int x, int y) const {
|
||||
const byte *pMapByte = _data + _byteWidth * y + x / 8;
|
||||
return *pMapByte & (1 << x % 8);
|
||||
}
|
||||
|
||||
bool WalkingMap::isWalkable(const Common::Point &p) const {
|
||||
// Convert to map pixels
|
||||
return getPixel(p.x / _deltaX, p.y / _deltaY);
|
||||
}
|
||||
|
||||
Sprite *WalkingMap::newOverlayFromMap(byte color) const {
|
||||
// HACK: Create a visible overlay from the walking map so we can test it
|
||||
byte *wlk = new byte[_realWidth * _realHeight];
|
||||
memset(wlk, 255, _realWidth * _realHeight);
|
||||
|
||||
for (int i = 0; i < _mapWidth; ++i) {
|
||||
for (int j = 0; j < _mapHeight; ++j) {
|
||||
if (getPixel(i, j)) {
|
||||
drawOverlayRectangle(Common::Point(i, j), color, wlk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Sprite *ov = new Sprite(_realWidth, _realHeight, wlk, 0, 0, false);
|
||||
// ov has taken the ownership of wlk.
|
||||
|
||||
return ov;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
Common::Point WalkingMap::findNearestWalkable(int startX, int startY) const {
|
||||
// The dimension of the screen.
|
||||
const Common::Rect searchRect(0, 0, _realWidth, _realHeight);
|
||||
|
||||
// Consider circles with radii gradually rising from 0 to the length of
|
||||
// the longest edge on the screen. For each radius, probe all points
|
||||
// on the circle and return the first walkable one. Go through angles
|
||||
// [0, 45 degrees] and probe all 8 reflections of each point.
|
||||
for (int radius = 0; radius < searchRect.width() + searchRect.height(); radius += _deltaX) {
|
||||
|
||||
// The position of the point on the circle.
|
||||
int x = 0;
|
||||
int y = radius;
|
||||
|
||||
// Variables for computing the points on the circle
|
||||
int prediction = 1 - radius;
|
||||
int dx = 3;
|
||||
int dy = 2 * radius - 2;
|
||||
|
||||
// Walk until we reach the 45-degree angle.
|
||||
while (x <= y) {
|
||||
|
||||
// The place where, eventually, the result coordinates will be stored
|
||||
Common::Point finalPos;
|
||||
|
||||
// Auxiliary array of multiplicative coefficients for reflecting points.
|
||||
static const int kSigns[] = { 1, -1 };
|
||||
|
||||
// Check all 8 reflections of the basic point.
|
||||
for (uint i = 0; i < 2; ++i) {
|
||||
finalPos.y = startY + y * kSigns[i];
|
||||
|
||||
for (uint j = 0; j < 2; ++j) {
|
||||
finalPos.x = startX + x * kSigns[j];
|
||||
|
||||
// If the current point is walkable, return it
|
||||
if (searchRect.contains(finalPos.x, finalPos.y) && isWalkable(finalPos)) {
|
||||
return finalPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (uint i = 0; i < 2; ++i) {
|
||||
finalPos.y = startY + x * kSigns[i];
|
||||
|
||||
for (uint j = 0; j < 2; ++j) {
|
||||
finalPos.x = startX + y * kSigns[j];
|
||||
|
||||
// If the current point is walkable, return it
|
||||
if (searchRect.contains(finalPos.x, finalPos.y) && isWalkable(finalPos)) {
|
||||
return finalPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Walk along the circle to the next point: the
|
||||
// X-coordinate moves to the right, and the
|
||||
// Y-coordinate may move to the bottom if the predictor
|
||||
// says so.
|
||||
if (prediction >= 0) {
|
||||
prediction -= dy;
|
||||
dy -= 2 * _deltaX;
|
||||
y -= _deltaX;
|
||||
}
|
||||
prediction += dx;
|
||||
dx += 2 * _deltaX;
|
||||
x += _deltaX;
|
||||
}
|
||||
}
|
||||
|
||||
// The destination point is unreachable.
|
||||
return Common::Point(-1, -1);
|
||||
}
|
||||
|
||||
// We don't use Common::Point due to using static initialization.
|
||||
const int WalkingMap::kDirections[][2] = { {0, -1}, {0, +1}, {-1, 0}, {+1, 0} };
|
||||
|
||||
bool WalkingMap::findShortestPath(Common::Point p1, Common::Point p2, WalkingPath *path) const {
|
||||
// Round the positions to map squares.
|
||||
p1.x /= _deltaX;
|
||||
p2.x /= _deltaX;
|
||||
p1.y /= _deltaY;
|
||||
p2.y /= _deltaY;
|
||||
|
||||
// Allocate buffers for breadth-first search. The buffer of points for
|
||||
// exploration should be large enough.
|
||||
const int bufSize = 4 * _realHeight;
|
||||
int8 *cameFrom = new int8[_mapWidth * _mapHeight];
|
||||
Common::Point *toSearch = new Common::Point[bufSize];
|
||||
|
||||
// Insert the starting point as a single seed.
|
||||
int toRead = 0, toWrite = 0;
|
||||
memset(cameFrom, -1, _mapWidth * _mapHeight); // -1 = not found yet
|
||||
cameFrom[p1.y * _mapWidth + p1.x] = 0;
|
||||
toSearch[toWrite++] = p1;
|
||||
|
||||
// Search until we empty the whole buffer (not found) or find the
|
||||
// destination point.
|
||||
while (toRead != toWrite) {
|
||||
const Common::Point &here = toSearch[toRead];
|
||||
const int from = cameFrom[here.y * _mapWidth + here.x];
|
||||
if (here == p2) {
|
||||
break;
|
||||
}
|
||||
// Look into all 4 directions in a particular order depending
|
||||
// on the direction we came to this point from. This is to
|
||||
// ensure that among many paths of the same length, the one
|
||||
// with the smallest number of turns is preferred.
|
||||
for (int addDir = 0; addDir < 4; ++addDir) {
|
||||
const int probeDirection = (from + addDir) % 4;
|
||||
const int x = here.x + kDirections[probeDirection][0];
|
||||
const int y = here.y + kDirections[probeDirection][1];
|
||||
if (x < 0 || x >= _mapWidth || y < 0 || y >= _mapHeight) {
|
||||
continue;
|
||||
}
|
||||
// If this point is walkable and we haven't seen it
|
||||
// yet, record how we have reached it and insert it
|
||||
// into the round buffer for exploration.
|
||||
if (getPixel(x, y) && cameFrom[y * _mapWidth + x] == -1) {
|
||||
cameFrom[y * _mapWidth + x] = probeDirection;
|
||||
toSearch[toWrite++] = Common::Point(x, y);
|
||||
toWrite %= bufSize;
|
||||
}
|
||||
}
|
||||
++toRead;
|
||||
toRead %= bufSize;
|
||||
}
|
||||
|
||||
// The path doesn't exist.
|
||||
if (toRead == toWrite) {
|
||||
delete[] cameFrom;
|
||||
delete[] toSearch;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Trace the path back and store it. Count the path length, resize the
|
||||
// output array, and then track the pack from the end.
|
||||
path->clear();
|
||||
for (int pass = 0; pass < 2; ++pass) {
|
||||
Common::Point p = p2;
|
||||
int index = 0;
|
||||
while (1) {
|
||||
++index;
|
||||
if (pass == 1) {
|
||||
(*path)[path->size() - index] = p;
|
||||
}
|
||||
if (p == p1) {
|
||||
break;
|
||||
}
|
||||
const int from = cameFrom[p.y * _mapWidth + p.x];
|
||||
p.x -= kDirections[from][0];
|
||||
p.y -= kDirections[from][1];
|
||||
}
|
||||
if (pass == 0) {
|
||||
path->resize(index);
|
||||
}
|
||||
}
|
||||
|
||||
delete[] cameFrom;
|
||||
delete[] toSearch;
|
||||
return true;
|
||||
}
|
||||
|
||||
void WalkingMap::obliquePath(const WalkingPath& path, WalkingPath *obliquedPath) {
|
||||
// Prune the path to only contain vertices where the direction is changing.
|
||||
obliquedPath->clear();
|
||||
if (path.empty()) {
|
||||
return;
|
||||
}
|
||||
obliquedPath->push_back(path[0]);
|
||||
uint index = 1;
|
||||
while (index < path.size()) {
|
||||
// index1 points to the last vertex inserted into the
|
||||
// simplified path.
|
||||
uint index1 = index - 1;
|
||||
// Probe the vertical direction. Notice that the shortest path
|
||||
// never turns by 180 degrees and therefore it is sufficient to
|
||||
// test that the X coordinates are equal.
|
||||
while (index < path.size() && path[index].x == path[index1].x) {
|
||||
++index;
|
||||
}
|
||||
if (index - 1 > index1) {
|
||||
index1 = index - 1;
|
||||
obliquedPath->push_back(path[index1]);
|
||||
}
|
||||
// Probe the horizontal direction.
|
||||
while (index < path.size() && path[index].y == path[index1].y) {
|
||||
++index;
|
||||
}
|
||||
if (index - 1 > index1) {
|
||||
index1 = index - 1;
|
||||
obliquedPath->push_back(path[index1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Repeatedly oblique the path until it cannot be improved. This
|
||||
// process is finite, because after each success the number of vertices
|
||||
// goes down.
|
||||
while (managedToOblique(obliquedPath)) {}
|
||||
}
|
||||
|
||||
Sprite *WalkingMap::newOverlayFromPath(const WalkingPath &path, byte color) const {
|
||||
// HACK: Create a visible overlay from the walking map so we can test it
|
||||
byte *wlk = new byte[_realWidth * _realHeight];
|
||||
memset(wlk, 255, _realWidth * _realHeight);
|
||||
|
||||
for (uint segment = 1; segment < path.size(); ++segment) {
|
||||
const Common::Point &v1 = path[segment-1];
|
||||
const Common::Point &v2 = path[segment];
|
||||
const int steps = pointsBetween(v1, v2);
|
||||
// Draw only points in the interval [v1, v2). These half-open
|
||||
// half-closed intervals connect all the way to the last point.
|
||||
for (int step = 0; step < steps; ++step) {
|
||||
drawOverlayRectangle(interpolate(v1, v2, step, steps), color, wlk);
|
||||
}
|
||||
}
|
||||
// Draw the last point. This works also when the path has no segment,
|
||||
// but just one point.
|
||||
if (path.size() > 0) {
|
||||
const Common::Point &vLast = path[path.size()-1];
|
||||
drawOverlayRectangle(vLast, color, wlk);
|
||||
}
|
||||
|
||||
Sprite *ov = new Sprite(_realWidth, _realHeight, wlk, 0, 0, false);
|
||||
// ov has taken the ownership of wlk.
|
||||
|
||||
return ov;
|
||||
}
|
||||
|
||||
void WalkingMap::drawOverlayRectangle(const Common::Point &p, byte color, byte *buf) const {
|
||||
for (int i = 0; i < _deltaX; ++i) {
|
||||
for (int j = 0; j < _deltaY; ++j) {
|
||||
buf[(p.y * _deltaY + j) * _realWidth + (p.x * _deltaX + i)] = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int WalkingMap::pointsBetween(const Common::Point &p1, const Common::Point &p2) {
|
||||
return MAX(ABS(p2.x - p1.x), ABS(p2.y - p1.y));
|
||||
}
|
||||
|
||||
Common::Point WalkingMap::interpolate(const Common::Point &p1, const Common::Point &p2, int i, int n) {
|
||||
const int x = (p1.x * (n-i) + p2.x * i + n/2) / n;
|
||||
const int y = (p1.y * (n-i) + p2.y * i + n/2) / n;
|
||||
return Common::Point(x, y);
|
||||
}
|
||||
|
||||
bool WalkingMap::lineIsCovered(const Common::Point &p1, const Common::Point &p2) const {
|
||||
const int steps = pointsBetween(p1, p2);
|
||||
for (int step = 0; step <= steps; ++step) {
|
||||
Common::Point p = interpolate(p1, p2, step, steps);
|
||||
if (!getPixel(p.x, p.y)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WalkingMap::managedToOblique(WalkingPath *path) const {
|
||||
bool improved = false;
|
||||
|
||||
// Making the path oblique works as follows. If the path has at least
|
||||
// 3 remaining vertices, we try to oblique the L-shaped path between
|
||||
// them. First we try to connect the 1st and 3rd vertex directly (if
|
||||
// all points on the line between them are walkable) and then we try to
|
||||
// walk on both edges towards the 2nd vertex in parallel and try to
|
||||
// find a shortcut (replacing the 2nd vertex by this mid-point). If
|
||||
// either of those attempts succeeds, we have shortned the path. We
|
||||
// update the path vertices and continue with the next segment.
|
||||
for (uint head = 2; head < path->size(); ++head) {
|
||||
const Common::Point &v1 = (*path)[head-2];
|
||||
const Common::Point &v2 = (*path)[head-1];
|
||||
const Common::Point &v3 = (*path)[head];
|
||||
const int points12 = pointsBetween(v1, v2);
|
||||
const int points32 = pointsBetween(v3, v2);
|
||||
// Find the first point p on each edge [v1, v2] and [v3, v2]
|
||||
// such that the edge [p, the third vertex] is covered.
|
||||
// Ideally we would like p \in {v1, v3} and the closer the
|
||||
// better. The last point p = v2 should always succeed.
|
||||
int first12, first32;
|
||||
for (first12 = 0; first12 < points12; ++first12) {
|
||||
Common::Point midPoint = interpolate(v1, v2, first12, points12);
|
||||
if (lineIsCovered(midPoint, v3)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (first12 == 0) {
|
||||
// Can completely remove the vertex. Head stays the
|
||||
// same after -- and ++.
|
||||
path->remove_at(--head);
|
||||
improved = true;
|
||||
continue;
|
||||
}
|
||||
for (first32 = 0; first32 < points32; ++first32) {
|
||||
Common::Point midPoint = interpolate(v3, v2, first32, points32);
|
||||
if (lineIsCovered(midPoint, v1)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (first12 < points12 && first32 >= points32 + MIN(first12 - points12, 0)) {
|
||||
// There is such a point on the first edge and the
|
||||
// second edge has either not succeeded or we gain more
|
||||
// by cutting this edge than the other one.
|
||||
(*path)[head-1] = interpolate(v1, v2, first12, points12);
|
||||
// After replacing the 2nd vertex, let head move on.
|
||||
} else if (first32 < points32) {
|
||||
(*path)[head-1] = interpolate(v3, v2, first32, points32);
|
||||
}
|
||||
}
|
||||
return improved;
|
||||
}
|
||||
|
||||
void WalkingState::stopWalking() {
|
||||
_path.clear();
|
||||
_callback = nullptr;
|
||||
}
|
||||
|
||||
void WalkingState::startWalking(const Common::Point &p1, const Common::Point &p2,
|
||||
const Common::Point &mouse, SightDirection dir,
|
||||
const Common::Point &delta, const WalkingPath& path) {
|
||||
_path = path;
|
||||
_mouse = mouse;
|
||||
_dir = dir;
|
||||
|
||||
if (!_path.size()) {
|
||||
_path.push_back(p1);
|
||||
}
|
||||
if (_path.size() == 1 && p2 != p1) {
|
||||
// Although the first and last point belong to the same
|
||||
// rectangle and therefore the computed path is of length 1,
|
||||
// they are different pixels.
|
||||
_path.push_back(p2);
|
||||
}
|
||||
debugC(2, kDraciWalkingDebugLevel, "Starting walking [%d,%d] -> [%d,%d] with %d vertices",
|
||||
p1.x, p1.y, p2.x, p2.y, _path.size());
|
||||
|
||||
// The first and last point are available with pixel accurracy.
|
||||
_path[0] = p1;
|
||||
_path[_path.size() - 1] = p2;
|
||||
// The intermediate points are given with map granularity; convert them
|
||||
// to pixels.
|
||||
for (uint i = 1; i < _path.size() - 1; ++i) {
|
||||
_path[i].x *= delta.x;
|
||||
_path[i].y *= delta.y;
|
||||
}
|
||||
|
||||
// Remember the initial dragon's direction.
|
||||
const GameObject *dragon = _vm->_game->getObject(kDragonObject);
|
||||
_startingDirection = static_cast<Movement> (dragon->playingAnim());
|
||||
|
||||
// Going to start with the first segment.
|
||||
_segment = 0;
|
||||
_lastAnimPhase = -1;
|
||||
_turningFinished = false;
|
||||
turnForTheNextSegment();
|
||||
}
|
||||
|
||||
void WalkingState::setCallback(const GPL2Program *program, uint16 offset) {
|
||||
_callback = _callbackLast = program;
|
||||
_callbackOffset = _callbackOffsetLast = offset;
|
||||
}
|
||||
|
||||
void WalkingState::callback() {
|
||||
if (!_callback) {
|
||||
return;
|
||||
}
|
||||
debugC(2, kDraciWalkingDebugLevel, "Calling walking callback");
|
||||
|
||||
const GPL2Program &originalCallback = *_callback;
|
||||
_callback = nullptr;
|
||||
_vm->_script->runWrapper(originalCallback, _callbackOffset, true, false);
|
||||
_callbackLast = nullptr;
|
||||
_callbackOffset = 0;
|
||||
}
|
||||
|
||||
void WalkingState::callbackLast() {
|
||||
setCallback(_callbackLast, _callbackOffsetLast);
|
||||
}
|
||||
|
||||
bool WalkingState::continueWalkingOrClearPath() {
|
||||
const bool stillWalking = continueWalking();
|
||||
if (!stillWalking) {
|
||||
_path.clear();
|
||||
}
|
||||
return stillWalking;
|
||||
}
|
||||
|
||||
bool WalkingState::continueWalking() {
|
||||
const GameObject *dragon = _vm->_game->getObject(kDragonObject);
|
||||
const Movement movement = static_cast<Movement> (dragon->playingAnim());
|
||||
|
||||
if (_turningFinished) {
|
||||
// When a turning animation has finished, heroAnimationFinished() callback
|
||||
// gets called, which sets this flag to true. It's important
|
||||
// to not start walking right away in the callback, because
|
||||
// that would disturb the data structures of the animation
|
||||
// manager.
|
||||
_turningFinished = false;
|
||||
return walkOnNextEdge();
|
||||
}
|
||||
|
||||
// If the current segment is the last one, we have reached the
|
||||
// destination and are already facing in the right direction ===>
|
||||
// return false. The code should, however, get here only if the path
|
||||
// has just 1 vertex and startWalking() leaves the path open.
|
||||
// Finishing and nontrivial path will get caught earlier.
|
||||
if (_segment >= _path.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read the dragon's animation's current phase. Determine if it has
|
||||
// changed from the last time. If not, wait until it has.
|
||||
Animation *anim = dragon->_anim[movement];
|
||||
const int animPhase = anim->currentFrameNum();
|
||||
const bool wasUpdated = animPhase != _lastAnimPhase;
|
||||
if (!wasUpdated) {
|
||||
debugC(4, kDraciWalkingDebugLevel, "Waiting for an animation phase change: still %d", animPhase);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isTurningMovement(movement)) {
|
||||
// If the current animation is a turning animation, wait a bit more.
|
||||
debugC(3, kDraciWalkingDebugLevel, "Continuing turning for edge %d with phase %d", _segment, animPhase);
|
||||
_lastAnimPhase = animPhase;
|
||||
return true;
|
||||
}
|
||||
|
||||
// We are walking in the middle of an edge. The animation phase has
|
||||
// just changed.
|
||||
|
||||
// Read the position of the hero from the animation object, and adjust
|
||||
// it to the current edge.
|
||||
const Common::Point prevHero = _vm->_game->getHeroPosition();
|
||||
_vm->_game->positionHeroAsAnim(anim);
|
||||
const Common::Point curHero = _vm->_game->getHeroPosition();
|
||||
Common::Point adjustedHero = curHero;
|
||||
const bool reachedEnd = alignHeroToEdge(_path[_segment-1], _path[_segment], prevHero, &adjustedHero);
|
||||
if (reachedEnd && _segment >= _path.size() - 1) {
|
||||
// We don't want the dragon to jump around if we repeatedly
|
||||
// click on the same pixel. Let him always end where desired.
|
||||
debugC(2, kDraciWalkingDebugLevel, "Adjusting position to the final node");
|
||||
adjustedHero = _path[_segment];
|
||||
}
|
||||
|
||||
debugC(3, kDraciWalkingDebugLevel, "Continuing walking on edge %d: phase %d and position+=[%d,%d]->[%d,%d] adjusted to [%d,%d]",
|
||||
_segment-1, animPhase, curHero.x - prevHero.x, curHero.y - prevHero.y, curHero.x, curHero.y, adjustedHero.x, adjustedHero.y);
|
||||
|
||||
// Update the hero position to the adjusted one. The animation number
|
||||
// is not changing, so this will just move the sprite and return the
|
||||
// current frame number.
|
||||
_vm->_game->setHeroPosition(adjustedHero);
|
||||
_lastAnimPhase = _vm->_game->playHeroAnimation(movement);
|
||||
|
||||
// If the hero has reached the end of the edge, start transition to the
|
||||
// next phase. This will increment _segment, either immediately (if no
|
||||
// transition is needed) or in the callback (after the transition is
|
||||
// done). If the hero has arrived at a slightly different point due to
|
||||
// animated sprites, adjust the path so that the animation can smoothly
|
||||
// continue.
|
||||
if (reachedEnd) {
|
||||
if (adjustedHero != _path[_segment]) {
|
||||
debugC(2, kDraciWalkingDebugLevel, "Adjusting node %d of the path [%d,%d]->[%d,%d]",
|
||||
_segment, _path[_segment].x, _path[_segment].y, adjustedHero.x, adjustedHero.y);
|
||||
_path[_segment] = adjustedHero;
|
||||
}
|
||||
return turnForTheNextSegment();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WalkingState::alignHeroToEdge(const Common::Point &p1, const Common::Point &p2, const Common::Point &prevHero, Common::Point *hero) {
|
||||
const Movement movement = animationForDirection(p1, p2);
|
||||
const Common::Point p2Diff(p2.x - p1.x, p2.y - p1.y);
|
||||
if (p2Diff.x == 0 && p2Diff.y == 0) {
|
||||
debugC(2, kDraciWalkingDebugLevel, "Adjusted walking edge has zero length");
|
||||
// Due to changing the path vertices on the fly, this can happen.
|
||||
return true;
|
||||
}
|
||||
bool reachedEnd;
|
||||
if (movement == kMoveLeft || movement == kMoveRight) {
|
||||
if (p2Diff.x == 0) {
|
||||
error("Wrong value for horizontal movement");
|
||||
}
|
||||
reachedEnd = movement == kMoveLeft ? hero->x <= p2.x : hero->x >= p2.x;
|
||||
hero->y += hero->x * p2Diff.y / p2Diff.x - prevHero.x * p2Diff.y / p2Diff.x;
|
||||
} else {
|
||||
if (p2Diff.y == 0) {
|
||||
error("Wrong value for vertical movement");
|
||||
}
|
||||
reachedEnd = movement == kMoveUp ? hero->y <= p2.y : hero->y >= p2.y;
|
||||
hero->x += hero->y * p2Diff.x / p2Diff.y - prevHero.y * p2Diff.x / p2Diff.y;
|
||||
}
|
||||
return reachedEnd;
|
||||
}
|
||||
|
||||
bool WalkingState::turnForTheNextSegment() {
|
||||
const GameObject *dragon = _vm->_game->getObject(kDragonObject);
|
||||
const Movement currentAnim = static_cast<Movement> (dragon->playingAnim());
|
||||
const Movement wantAnim = directionForNextPhase();
|
||||
Movement transition = transitionBetweenAnimations(currentAnim, wantAnim);
|
||||
|
||||
debugC(2, kDraciWalkingDebugLevel, "Turning for edge %d", _segment);
|
||||
|
||||
if (transition == kMoveUndefined) {
|
||||
// Start the next segment right away as if the turning has just finished.
|
||||
return walkOnNextEdge();
|
||||
} else {
|
||||
// Otherwise start the transition and wait until the Animation
|
||||
// class calls heroAnimationFinished() as a callback, leading
|
||||
// to calling walkOnNextEdge() in the next phase.
|
||||
assert(isTurningMovement(transition));
|
||||
_lastAnimPhase = _vm->_game->playHeroAnimation(transition);
|
||||
Animation *anim = dragon->_anim[transition];
|
||||
anim->registerCallback(&Animation::tellWalkingState);
|
||||
|
||||
debugC(2, kDraciWalkingDebugLevel, "Starting turning animation %d with phase %d", transition, _lastAnimPhase);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void WalkingState::heroAnimationFinished() {
|
||||
debugC(2, kDraciWalkingDebugLevel, "Turning callback called");
|
||||
_turningFinished = true;
|
||||
|
||||
// We don't need to clear the callback to safer doNothing, because
|
||||
// nobody ever plays this animation directly. It is only played by
|
||||
// turnForTheNextSegment() and then the same callback needs to be
|
||||
// activated again.
|
||||
}
|
||||
|
||||
bool WalkingState::walkOnNextEdge() {
|
||||
// The hero is turned well for the next line segment or for facing the
|
||||
// target direction. It is also standing on the right spot thanks to
|
||||
// the entry condition for turnForTheNextSegment().
|
||||
|
||||
// Start the desired next animation and retrieve the current animation
|
||||
// phase.
|
||||
// Don't use any callbacks, because continueWalking() will decide the
|
||||
// end on its own and after walking is done callbacks shouldn't be
|
||||
// called either. It wouldn't make much sense anyway, since the
|
||||
// walking/staying/talking animations are cyclic.
|
||||
Movement nextAnim = directionForNextPhase();
|
||||
_lastAnimPhase = _vm->_game->playHeroAnimation(nextAnim);
|
||||
|
||||
debugC(2, kDraciWalkingDebugLevel, "Turned for edge %d, starting animation %d with phase %d", _segment, nextAnim, _lastAnimPhase);
|
||||
|
||||
if (++_segment < _path.size()) {
|
||||
// We are on an edge: track where the hero is on this edge.
|
||||
int length = WalkingMap::pointsBetween(_path[_segment-1], _path[_segment]);
|
||||
debugC(2, kDraciWalkingDebugLevel, "Next edge %d has length %d", _segment-1, length);
|
||||
return true;
|
||||
} else {
|
||||
// Otherwise we are done. continueWalking() will return false next time.
|
||||
debugC(2, kDraciWalkingDebugLevel, "We have walked the whole path");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Movement WalkingState::animationForDirection(const Common::Point &here, const Common::Point &there) {
|
||||
const int dx = there.x - here.x;
|
||||
const int dy = there.y - here.y;
|
||||
if (ABS(dx) >= ABS(dy)) {
|
||||
return dx >= 0 ? kMoveRight : kMoveLeft;
|
||||
} else {
|
||||
return dy >= 0 ? kMoveDown : kMoveUp;
|
||||
}
|
||||
}
|
||||
|
||||
Movement WalkingState::directionForNextPhase() const {
|
||||
if (_segment >= _path.size() - 1) {
|
||||
return animationForSightDirection(_dir, _path[_path.size()-1], _mouse, _path, _startingDirection);
|
||||
} else {
|
||||
return animationForDirection(_path[_segment], _path[_segment+1]);
|
||||
}
|
||||
}
|
||||
|
||||
Movement WalkingState::transitionBetweenAnimations(Movement previous, Movement next) {
|
||||
switch (next) {
|
||||
case kMoveUp:
|
||||
switch (previous) {
|
||||
case kMoveLeft:
|
||||
case kStopLeft:
|
||||
case kSpeakLeft:
|
||||
return kMoveLeftUp;
|
||||
case kMoveRight:
|
||||
case kStopRight:
|
||||
case kSpeakRight:
|
||||
return kMoveRightUp;
|
||||
default:
|
||||
return kMoveUndefined;
|
||||
}
|
||||
case kMoveDown:
|
||||
switch (previous) {
|
||||
case kMoveLeft:
|
||||
case kStopLeft:
|
||||
case kSpeakLeft:
|
||||
return kMoveLeftDown;
|
||||
case kMoveRight:
|
||||
case kStopRight:
|
||||
case kSpeakRight:
|
||||
return kMoveRightDown;
|
||||
default:
|
||||
return kMoveUndefined;
|
||||
}
|
||||
case kMoveLeft:
|
||||
switch (previous) {
|
||||
case kMoveDown:
|
||||
return kMoveDownLeft;
|
||||
case kMoveUp:
|
||||
return kMoveUpLeft;
|
||||
case kMoveRight:
|
||||
case kStopRight:
|
||||
case kSpeakRight:
|
||||
return kMoveRightLeft;
|
||||
default:
|
||||
return kMoveUndefined;
|
||||
}
|
||||
case kMoveRight:
|
||||
switch (previous) {
|
||||
case kMoveDown:
|
||||
return kMoveDownRight;
|
||||
case kMoveUp:
|
||||
return kMoveUpRight;
|
||||
case kMoveLeft:
|
||||
case kStopLeft:
|
||||
case kSpeakLeft:
|
||||
return kMoveLeftRight;
|
||||
default:
|
||||
return kMoveUndefined;
|
||||
}
|
||||
case kStopLeft:
|
||||
switch (previous) {
|
||||
case kMoveUp:
|
||||
return kMoveUpStopLeft;
|
||||
case kMoveRight:
|
||||
case kStopRight:
|
||||
case kSpeakRight:
|
||||
return kMoveRightLeft;
|
||||
default:
|
||||
return kMoveUndefined;
|
||||
}
|
||||
case kStopRight:
|
||||
switch (previous) {
|
||||
case kMoveUp:
|
||||
return kMoveUpStopRight;
|
||||
case kMoveLeft:
|
||||
case kStopLeft:
|
||||
case kSpeakLeft:
|
||||
return kMoveLeftRight;
|
||||
default:
|
||||
return kMoveUndefined;
|
||||
}
|
||||
default:
|
||||
return kMoveUndefined;
|
||||
}
|
||||
}
|
||||
|
||||
Movement WalkingState::animationForSightDirection(SightDirection dir, const Common::Point &hero, const Common::Point &mouse, const WalkingPath &path, Movement startingDirection) {
|
||||
switch (dir) {
|
||||
case kDirectionLeft:
|
||||
return kStopLeft;
|
||||
case kDirectionRight:
|
||||
return kStopRight;
|
||||
case kDirectionMouse:
|
||||
if (mouse.x < hero.x) {
|
||||
return kStopLeft;
|
||||
} else if (mouse.x > hero.x) {
|
||||
return kStopRight;
|
||||
}
|
||||
// fall through
|
||||
default: {
|
||||
// Find the last horizontal direction on the path.
|
||||
int i = path.size() - 1;
|
||||
while (i >= 0 && path[i].x == hero.x) {
|
||||
--i;
|
||||
}
|
||||
if (i >= 0) {
|
||||
return path[i].x < hero.x ? kStopRight : kStopLeft;
|
||||
} else {
|
||||
// Avoid changing the direction when no walking has
|
||||
// been done. Preserve the original direction.
|
||||
return (startingDirection == kMoveLeft || startingDirection == kStopLeft || startingDirection == kSpeakLeft)
|
||||
? kStopLeft : kStopRight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Draci
|
||||
201
engines/draci/walking.h
Normal file
201
engines/draci/walking.h
Normal file
@@ -0,0 +1,201 @@
|
||||
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DRACI_WALKING_H
|
||||
#define DRACI_WALKING_H
|
||||
|
||||
#include "common/array.h"
|
||||
#include "common/rect.h"
|
||||
|
||||
namespace Draci {
|
||||
|
||||
class Sprite;
|
||||
|
||||
typedef Common::Array<Common::Point> WalkingPath;
|
||||
|
||||
class WalkingMap {
|
||||
public:
|
||||
WalkingMap() : _realWidth(0), _realHeight(0), _deltaX(1), _deltaY(1),
|
||||
_mapWidth(0), _mapHeight(0), _byteWidth(0), _data(NULL) { }
|
||||
|
||||
void load(const byte *data, uint length);
|
||||
|
||||
bool getPixel(int x, int y) const;
|
||||
bool isWalkable(const Common::Point &p) const;
|
||||
|
||||
Sprite *newOverlayFromMap(byte color) const;
|
||||
Common::Point findNearestWalkable(int x, int y) const;
|
||||
|
||||
bool findShortestPath(Common::Point p1, Common::Point p2, WalkingPath *path) const;
|
||||
void obliquePath(const WalkingPath& path, WalkingPath *obliquedPath);
|
||||
Sprite *newOverlayFromPath(const WalkingPath &path, byte color) const;
|
||||
Common::Point getDelta() const { return Common::Point(_deltaX, _deltaY); }
|
||||
|
||||
static int pointsBetween(const Common::Point &p1, const Common::Point &p2);
|
||||
static Common::Point interpolate(const Common::Point &p1, const Common::Point &p2, int i, int n);
|
||||
|
||||
private:
|
||||
int _realWidth, _realHeight;
|
||||
int _deltaX, _deltaY;
|
||||
int _mapWidth, _mapHeight;
|
||||
int _byteWidth;
|
||||
|
||||
// We don't own the pointer. It points to the BArchive cache for this room.
|
||||
const byte *_data;
|
||||
|
||||
// 4 possible directions to walk from a pixel.
|
||||
static const int kDirections[][2];
|
||||
|
||||
void drawOverlayRectangle(const Common::Point &p, byte color, byte *buf) const;
|
||||
bool lineIsCovered(const Common::Point &p1, const Common::Point &p2) const;
|
||||
|
||||
// Returns true if the number of vertices on the path was decreased.
|
||||
bool managedToOblique(WalkingPath *path) const;
|
||||
};
|
||||
|
||||
/*
|
||||
* Enumerates the directions the dragon can look into when arrived.
|
||||
*/
|
||||
enum SightDirection {
|
||||
kDirectionLast, kDirectionMouse, kDirectionUnknown,
|
||||
kDirectionRight, kDirectionLeft, kDirectionIntelligent
|
||||
};
|
||||
|
||||
/**
|
||||
* Enumerates the animations for the dragon's movement.
|
||||
*/
|
||||
enum Movement {
|
||||
kMoveUndefined = -1,
|
||||
kMoveDown, kMoveUp, kMoveRight, kMoveLeft,
|
||||
|
||||
kFirstTurning,
|
||||
kMoveRightDown = kFirstTurning, kMoveRightUp, kMoveLeftDown, kMoveLeftUp,
|
||||
kMoveDownRight, kMoveUpRight, kMoveDownLeft, kMoveUpLeft,
|
||||
kMoveLeftRight, kMoveRightLeft, kMoveUpStopLeft, kMoveUpStopRight,
|
||||
kLastTurning = kMoveUpStopRight,
|
||||
|
||||
kSpeakRight, kSpeakLeft, kStopRight, kStopLeft,
|
||||
|
||||
kFirstTemporaryAnimation
|
||||
};
|
||||
|
||||
class DraciEngine;
|
||||
struct GPL2Program;
|
||||
|
||||
class WalkingState {
|
||||
public:
|
||||
explicit WalkingState(DraciEngine *vm) : _vm(vm) {
|
||||
_dir = kDirectionLast;
|
||||
_startingDirection = kMoveUndefined;
|
||||
_segment = 0;
|
||||
_lastAnimPhase = 0;
|
||||
_turningFinished = 0;
|
||||
_callbackOffset = 0;
|
||||
_callbackOffsetLast = 0;
|
||||
_callbackLast = 0;
|
||||
|
||||
stopWalking();
|
||||
}
|
||||
|
||||
~WalkingState() {}
|
||||
|
||||
void stopWalking();
|
||||
void startWalking(const Common::Point &p1, const Common::Point &p2,
|
||||
const Common::Point &mouse, SightDirection dir,
|
||||
const Common::Point &delta, const WalkingPath& path);
|
||||
const WalkingPath& getPath() const { return _path; }
|
||||
|
||||
void setCallback(const GPL2Program *program, uint16 offset);
|
||||
void callback();
|
||||
void callbackLast();
|
||||
|
||||
bool isActive() const { return _path.size() > 0; }
|
||||
|
||||
// Advances the hero along the path and changes animation accordingly.
|
||||
// Walking MUST be active when calling this method. When the hero has
|
||||
// arrived to the target, returns false, but leaves the callback
|
||||
// untouched (the caller must call it).
|
||||
// The second variant also clears the path when returning false.
|
||||
bool continueWalking();
|
||||
bool continueWalkingOrClearPath();
|
||||
|
||||
// Called when the hero's turning animation has finished.
|
||||
void heroAnimationFinished();
|
||||
|
||||
// Returns the hero's animation corresponding to looking into given
|
||||
// direction. The direction can be smart and in that case this
|
||||
// function needs to know the whole last path, the current position of
|
||||
// the hero, or the mouse position.
|
||||
static Movement animationForSightDirection(SightDirection dir, const Common::Point &hero, const Common::Point &mouse, const WalkingPath &path, Movement startingDirection);
|
||||
|
||||
private:
|
||||
DraciEngine *_vm;
|
||||
|
||||
WalkingPath _path;
|
||||
Common::Point _mouse;
|
||||
SightDirection _dir;
|
||||
Movement _startingDirection;
|
||||
|
||||
uint _segment; // Index of the path vertex we are currently going to / rotation on
|
||||
int _lastAnimPhase;
|
||||
bool _turningFinished;
|
||||
|
||||
const GPL2Program *_callback;
|
||||
const GPL2Program *_callbackLast;
|
||||
uint16 _callbackOffset;
|
||||
uint16 _callbackOffsetLast;
|
||||
|
||||
// Initiates turning of the dragon into the direction for the next
|
||||
// segment / after walking. Returns false when there is nothing left
|
||||
// to do and walking is done.
|
||||
bool turnForTheNextSegment();
|
||||
|
||||
// Starts walking on the next edge. Returns false if we are already at
|
||||
// the final vertex and walking is done.
|
||||
bool walkOnNextEdge();
|
||||
|
||||
// Return one of the 4 animations kMove{Down,Up,Right,Left}
|
||||
// corresponding to walking from here to there.
|
||||
static Movement animationForDirection(const Common::Point &here, const Common::Point &there);
|
||||
|
||||
// Returns the desired facing direction to begin the next phase of the
|
||||
// walk. It's either a direction for the given edge or the desired
|
||||
// final direction.
|
||||
Movement directionForNextPhase() const;
|
||||
|
||||
// Returns either animation that needs to be played between given two
|
||||
// animations (e.g., kMoveRightDown after kMoveRight and before
|
||||
// kMoveDown), or kMoveUndefined if none animation is to be played.
|
||||
static Movement transitionBetweenAnimations(Movement previous, Movement next);
|
||||
|
||||
static bool isTurningMovement(Movement m) {
|
||||
return m >= kFirstTurning && m <= kLastTurning;
|
||||
}
|
||||
|
||||
// Projects hero to the given edge. Returns true when hero has reached
|
||||
// at least p2. prevHero is passed so that we can compute how much to
|
||||
// adjust in the other-than-walking direction.
|
||||
static bool alignHeroToEdge(const Common::Point &p1, const Common::Point &p2, const Common::Point &prevHero, Common::Point *hero);
|
||||
};
|
||||
|
||||
} // End of namespace Draci
|
||||
|
||||
#endif // DRACI_WALKING_H
|
||||
Reference in New Issue
Block a user