diff options
author | Paul Gilbert | 2016-03-20 14:55:41 -0400 |
---|---|---|
committer | Paul Gilbert | 2016-03-20 14:55:41 -0400 |
commit | 3c852cc240221785598023de56e5a71a0d8806fa (patch) | |
tree | c2a64b811586e4e9d8a4d5baa04d5c4f9d23bd08 /engines | |
parent | ea54e6244e75c609e6886ba210f80fb22c479d3f (diff) | |
parent | 509a00109e79156e91c062f145ac3aa86ec8584e (diff) | |
download | scummvm-rg350-3c852cc240221785598023de56e5a71a0d8806fa.tar.gz scummvm-rg350-3c852cc240221785598023de56e5a71a0d8806fa.tar.bz2 scummvm-rg350-3c852cc240221785598023de56e5a71a0d8806fa.zip |
Merge branch 'master' into titanic
Diffstat (limited to 'engines')
484 files changed, 26382 insertions, 7988 deletions
diff --git a/engines/access/access.cpp b/engines/access/access.cpp index bc9bcb4b08..c12761af4a 100644 --- a/engines/access/access.cpp +++ b/engines/access/access.cpp @@ -436,20 +436,9 @@ void AccessEngine::copyBF1BF2() { } void AccessEngine::copyBF2Vid() { - const byte *srcP = (const byte *)_buffer2.getPixels(); - byte *destP = (byte *)_screen->getBasePtr(_screen->_windowXAdd, - _screen->_windowYAdd + _screen->_screenYOff); - - for (int yp = 0; yp < _screen->_vWindowLinesTall; ++yp) { - Common::copy(srcP, srcP + _screen->_vWindowBytesWide, destP); - srcP += _buffer2.pitch; - destP += _screen->pitch; - } - - // Add dirty rect for affected area - Common::Rect r(_screen->_vWindowBytesWide, _screen->_vWindowLinesTall); - r.moveTo(_screen->_windowXAdd, _screen->_windowYAdd + _screen->_screenYOff); - _screen->addDirtyRect(r); + _screen->blitFrom(_buffer2, + Common::Rect(0, 0, _screen->_vWindowBytesWide, _screen->_vWindowLinesTall), + Common::Point(_screen->_windowXAdd, _screen->_windowYAdd)); } void AccessEngine::playVideo(int videoNum, const Common::Point &pt) { diff --git a/engines/access/asurface.cpp b/engines/access/asurface.cpp index f693e6a3a0..2518ff6ad8 100644 --- a/engines/access/asurface.cpp +++ b/engines/access/asurface.cpp @@ -110,7 +110,7 @@ void ImageEntryList::addToList(ImageEntry &ie) { int ASurface::_clipWidth; int ASurface::_clipHeight; -ASurface::ASurface(): Graphics::Surface() { +ASurface::ASurface(): Graphics::ManagedSurface() { _leftSkip = _rightSkip = 0; _topSkip = _bottomSkip = 0; _lastBoundsX = _lastBoundsY = 0; @@ -122,65 +122,14 @@ ASurface::ASurface(): Graphics::Surface() { } ASurface::~ASurface() { - free(); _savedBlock.free(); } -void ASurface::create(uint16 width, uint16 height) { - Graphics::Surface::create(width, height, Graphics::PixelFormat::createFormatCLUT8()); -} - void ASurface::clearBuffer() { byte *pSrc = (byte *)getPixels(); Common::fill(pSrc, pSrc + w * h, 0); } -bool ASurface::clip(Common::Rect &r) { - int skip; - _leftSkip = _rightSkip = 0; - _topSkip = _bottomSkip = 0; - - if (r.left > _clipWidth || r.left < 0) { - if (r.left >= 0) - return true; - - skip = -r.left; - r.setWidth(r.width() - skip); - _leftSkip = skip; - r.moveTo(0, r.top); - } - - int right = r.right - 1; - if (right < 0) - return true; - else if (right > _clipWidth) { - skip = right - _clipWidth; - r.setWidth(r.width() - skip); - _rightSkip = skip; - } - - if (r.top > _clipHeight || r.top < 0) { - if (r.top >= 0) - return true; - - skip = -r.top; - r.setHeight(r.height() - skip); - _topSkip = skip; - r.moveTo(r.left, 0); - } - - int bottom = r.bottom - 1; - if (bottom < 0) - return true; - else if (bottom > _clipHeight) { - skip = bottom - _clipHeight; - _bottomSkip = skip; - r.setHeight(r.height() - skip); - } - - return false; -} - void ASurface::plotImage(SpriteResource *sprite, int frameNum, const Common::Point &pt) { SpriteFrame *frame = sprite->getFrame(frameNum); Common::Rect r(pt.x, pt.y, pt.x + frame->w, pt.y + frame->h); @@ -195,81 +144,7 @@ void ASurface::plotImage(SpriteResource *sprite, int frameNum, const Common::Poi } } -void ASurface::transBlitFrom(ASurface *src, const Common::Point &destPos) { - if (getPixels() == nullptr) - create(w, h); - - for (int yp = 0; yp < src->h; ++yp) { - const byte *srcP = (const byte *)src->getBasePtr(0, yp); - byte *destP = (byte *)getBasePtr(destPos.x, destPos.y + yp); - - for (int xp = 0; xp < this->w; ++xp, ++srcP, ++destP) { - if (*srcP != TRANSPARENCY) - *destP = *srcP; - } - } -} - -void ASurface::transBlitFrom(ASurface *src, const Common::Rect &bounds) { - const int SCALE_LIMIT = 0x100; - int scaleX = SCALE_LIMIT * bounds.width() / src->w; - int scaleY = SCALE_LIMIT * bounds.height() / src->h; - int scaleXCtr = 0, scaleYCtr = 0; - - for (int yCtr = 0, destY = bounds.top; yCtr < src->h; ++yCtr) { - // Handle skipping lines if Y scaling - scaleYCtr += scaleY; - if (scaleYCtr < SCALE_LIMIT) - continue; - scaleYCtr -= SCALE_LIMIT; - - // Handle off-screen lines - if (destY >= this->h) - break; - - if (destY >= 0) { - // Handle drawing the line - const byte *pSrc = (const byte *)src->getBasePtr(0, yCtr); - byte *pDest = (byte *)getBasePtr(bounds.left, destY); - scaleXCtr = 0; - int x = bounds.left; - - for (int xCtr = 0; xCtr < src->w; ++xCtr, ++pSrc) { - // Handle horizontal scaling - scaleXCtr += scaleX; - if (scaleXCtr < SCALE_LIMIT) - continue; - scaleXCtr -= SCALE_LIMIT; - - // Only handle on-screen pixels - if (x >= this->w) - break; - if (x >= 0 && *pSrc != 0) - *pDest = *pSrc; - - ++pDest; - ++x; - } - } - - ++destY; - } -} - -void ASurface::transBlitFrom(ASurface &src) { - blitFrom(src); -} - -void ASurface::blitFrom(const Graphics::Surface &src) { - assert(w >= src.w && h >= src.h); - for (int y = 0; y < src.h; ++y) { - const byte *srcP = (const byte *)src.getBasePtr(0, y); - byte *destP = (byte *)getBasePtr(0, y); - Common::copy(srcP, srcP + src.w, destP); - } -} - -void ASurface::copyBuffer(Graphics::Surface *src) { +void ASurface::copyBuffer(Graphics::ManagedSurface *src) { blitFrom(*src); } @@ -282,14 +157,11 @@ void ASurface::plotB(SpriteFrame *frame, const Common::Point &pt) { } void ASurface::sPlotF(SpriteFrame *frame, const Common::Rect &bounds) { - transBlitFrom(frame, bounds); + transBlitFrom(*frame, Common::Rect(0, 0, frame->w, frame->h), bounds, TRANSPARENCY, false); } void ASurface::sPlotB(SpriteFrame *frame, const Common::Rect &bounds) { - ASurface flippedFrame; - frame->flipHorizontal(flippedFrame); - - transBlitFrom(&flippedFrame, bounds); + transBlitFrom(*frame, Common::Rect(0, 0, frame->w, frame->h), bounds, TRANSPARENCY, true); } void ASurface::copyBlock(ASurface *src, const Common::Rect &bounds) { @@ -324,22 +196,22 @@ void ASurface::restoreBlock() { } void ASurface::drawRect() { - Graphics::Surface::fillRect(Common::Rect(_orgX1, _orgY1, _orgX2, _orgY2), _lColor); + Graphics::ManagedSurface::fillRect(Common::Rect(_orgX1, _orgY1, _orgX2, _orgY2), _lColor); } void ASurface::drawLine(int x1, int y1, int x2, int y2, int col) { - Graphics::Surface::drawLine(x1, y1, x2, y2, col); + Graphics::ManagedSurface::drawLine(x1, y1, x2, y2, col); } void ASurface::drawLine() { - Graphics::Surface::drawLine(_orgX1, _orgY1, _orgX2, _orgY1, _lColor); + Graphics::ManagedSurface::drawLine(_orgX1, _orgY1, _orgX2, _orgY1, _lColor); } void ASurface::drawBox() { - Graphics::Surface::drawLine(_orgX1, _orgY1, _orgX2, _orgY1, _lColor); - Graphics::Surface::drawLine(_orgX1, _orgY2, _orgX2, _orgY2, _lColor); - Graphics::Surface::drawLine(_orgX2, _orgY1, _orgX2, _orgY1, _lColor); - Graphics::Surface::drawLine(_orgX2, _orgY2, _orgX2, _orgY2, _lColor); + Graphics::ManagedSurface::drawLine(_orgX1, _orgY1, _orgX2, _orgY1, _lColor); + Graphics::ManagedSurface::drawLine(_orgX1, _orgY2, _orgX2, _orgY2, _lColor); + Graphics::ManagedSurface::drawLine(_orgX2, _orgY1, _orgX2, _orgY1, _lColor); + Graphics::ManagedSurface::drawLine(_orgX2, _orgY2, _orgX2, _orgY2, _lColor); } void ASurface::flipHorizontal(ASurface &dest) { @@ -373,4 +245,50 @@ void ASurface::moveBufferDown() { Common::copy_backward(p, p + (pitch * (h - TILE_HEIGHT)), p + (pitch * h)); } +bool ASurface::clip(Common::Rect &r) { + int skip; + _leftSkip = _rightSkip = 0; + _topSkip = _bottomSkip = 0; + + if (r.left > _clipWidth || r.left < 0) { + if (r.left >= 0) + return true; + + skip = -r.left; + r.setWidth(r.width() - skip); + _leftSkip = skip; + r.moveTo(0, r.top); + } + + int right = r.right - 1; + if (right < 0) + return true; + else if (right > _clipWidth) { + skip = right - _clipWidth; + r.setWidth(r.width() - skip); + _rightSkip = skip; + } + + if (r.top > _clipHeight || r.top < 0) { + if (r.top >= 0) + return true; + + skip = -r.top; + r.setHeight(r.height() - skip); + _topSkip = skip; + r.moveTo(r.left, 0); + } + + int bottom = r.bottom - 1; + if (bottom < 0) + return true; + else if (bottom > _clipHeight) { + skip = bottom - _clipHeight; + _bottomSkip = skip; + r.setHeight(r.height() - skip); + } + + return false; +} + } // End of namespace Access diff --git a/engines/access/asurface.h b/engines/access/asurface.h index dd05c8067b..ec18ec09c3 100644 --- a/engines/access/asurface.h +++ b/engines/access/asurface.h @@ -27,7 +27,7 @@ #include "common/array.h" #include "common/memstream.h" #include "common/rect.h" -#include "graphics/surface.h" +#include "graphics/managed_surface.h" #include "access/data.h" namespace Access { @@ -35,7 +35,7 @@ namespace Access { class SpriteResource; class SpriteFrame; -class ASurface : public Graphics::Surface { +class ASurface : virtual public Graphics::ManagedSurface { private: Graphics::Surface _savedBlock; @@ -61,14 +61,8 @@ public: virtual ~ASurface(); - void create(uint16 width, uint16 height); - - bool empty() const { return w == 0 || h == 0 || pixels == nullptr; } - void clearBuffer(); - bool clip(Common::Rect &r); - void plotImage(SpriteResource *sprite, int frameNum, const Common::Point &pt); /** @@ -102,18 +96,8 @@ public: virtual void drawLine(); virtual void drawBox(); - - virtual void transBlitFrom(ASurface *src, const Common::Point &destPos); - - virtual void transBlitFrom(ASurface *src, const Common::Rect &bounds); - virtual void transBlitFrom(ASurface &src); - - virtual void blitFrom(const Graphics::Surface &src); - - virtual void copyBuffer(Graphics::Surface *src); - - virtual void addDirtyRect(const Common::Rect &r) {} + virtual void copyBuffer(Graphics::ManagedSurface *src); void copyTo(ASurface *dest); @@ -126,6 +110,8 @@ public: void moveBufferUp(); void moveBufferDown(); + + bool clip(Common::Rect &r); }; class SpriteFrame : public ASurface { diff --git a/engines/access/detection.cpp b/engines/access/detection.cpp index 7494c9c047..2cd7e50f0f 100644 --- a/engines/access/detection.cpp +++ b/engines/access/detection.cpp @@ -146,7 +146,6 @@ SaveStateList AccessMetaEngine::listSaves(const char *target) const { Access::AccessSavegameHeader header; filenames = saveFileMan->listSavefiles(pattern); - sort(filenames.begin(), filenames.end()); // Sort to get the files in numerical order SaveStateList saveList; for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { @@ -167,6 +166,8 @@ SaveStateList AccessMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/access/detection_tables.h b/engines/access/detection_tables.h index 9556cd9f67..7d9509ca43 100644 --- a/engines/access/detection_tables.h +++ b/engines/access/detection_tables.h @@ -49,7 +49,7 @@ static const AccessGameDescription gameDescriptions[] = { AD_ENTRY1s("c00.ap", "aeb429ff015596144c0df06886c84825", 303753), Common::ES_ESP, Common::kPlatformDOS, - ADGF_NO_FLAGS, + ADGF_UNSTABLE, GUIO1(GUIO_NONE) }, GType_Amazon, diff --git a/engines/access/events.cpp b/engines/access/events.cpp index d62b05c33f..21ff0d0928 100644 --- a/engines/access/events.cpp +++ b/engines/access/events.cpp @@ -115,7 +115,7 @@ void EventsManager::setCursor(CursorType cursorId) { } } -void EventsManager::setCursorData(Graphics::Surface *src, const Common::Rect &r) { +void EventsManager::setCursorData(Graphics::ManagedSurface *src, const Common::Rect &r) { _invCursor.create(r.width(), r.height(), Graphics::PixelFormat::createFormatCLUT8()); _invCursor.copyRectToSurface(*src, 0, 0, r); } @@ -281,8 +281,7 @@ void EventsManager::nextFrame() { // Give time to the debugger _vm->_debugger->onFrame(); - // TODO: Refactor for dirty rects - _vm->_screen->updateScreen(); + _vm->_screen->update(); } void EventsManager::nextTimer() { diff --git a/engines/access/events.h b/engines/access/events.h index b8c5f0ee5e..5acbb71c9d 100644 --- a/engines/access/events.h +++ b/engines/access/events.h @@ -100,7 +100,7 @@ public: /** * Set the image for the inventory cursor */ - void setCursorData(Graphics::Surface *src, const Common::Rect &r); + void setCursorData(Graphics::ManagedSurface *src, const Common::Rect &r); /** * Return the current cursor Id diff --git a/engines/access/files.cpp b/engines/access/files.cpp index b9c0f7080d..48276ee477 100644 --- a/engines/access/files.cpp +++ b/engines/access/files.cpp @@ -130,13 +130,13 @@ void FileManager::openFile(Resource *res, const Common::String &filename) { error("Could not open file - %s", filename.c_str()); } -void FileManager::loadScreen(Graphics::Surface *dest, int fileNum, int subfile) { +void FileManager::loadScreen(Graphics::ManagedSurface *dest, int fileNum, int subfile) { Resource *res = loadFile(fileNum, subfile); handleScreen(dest, res); delete res; } -void FileManager::handleScreen(Graphics::Surface *dest, Resource *res) { +void FileManager::handleScreen(Graphics::ManagedSurface *dest, Resource *res) { _vm->_screen->loadRawPalette(res->_stream); if (_setPaletteFlag) _vm->_screen->setPalette(); @@ -147,20 +147,17 @@ void FileManager::handleScreen(Graphics::Surface *dest, Resource *res) { res->_size -= res->_stream->pos(); handleFile(res); - if (dest != _vm->_screen) - dest->w = _vm->_screen->w; + Graphics::Surface destSurface = dest->getSubArea(Common::Rect(0, 0, + _vm->_screen->w, _vm->_screen->h)); - if (dest->w == dest->pitch) { - res->_stream->read((byte *)dest->getPixels(), dest->w * dest->h); + if (destSurface.w == destSurface.pitch) { + res->_stream->read((byte *)destSurface.getPixels(), destSurface.w * destSurface.h); } else { - for (int y = 0; y < dest->h; ++y) { - byte *pDest = (byte *)dest->getBasePtr(0, y); - res->_stream->read(pDest, dest->w); + for (int y = 0; y < destSurface.h; ++y) { + byte *pDest = (byte *)destSurface.getBasePtr(0, y); + res->_stream->read(pDest, destSurface.w); } } - - if (dest == _vm->_screen) - _vm->_screen->addDirtyRect(Common::Rect(0, 0, dest->w, dest->h)); } void FileManager::loadScreen(int fileNum, int subfile) { diff --git a/engines/access/files.h b/engines/access/files.h index d081934e91..61fccc2431 100644 --- a/engines/access/files.h +++ b/engines/access/files.h @@ -26,7 +26,7 @@ #include "common/scummsys.h" #include "common/array.h" #include "common/file.h" -#include "graphics/surface.h" +#include "graphics/managed_surface.h" #include "access/decompress.h" namespace Access { @@ -81,7 +81,7 @@ private: /** * Handles loading a screen surface and palette with decoded resource */ - void handleScreen(Graphics::Surface *dest, Resource *res); + void handleScreen(Graphics::ManagedSurface *dest, Resource *res); /** * Open up a sub-file container file @@ -133,7 +133,7 @@ public: /** * Load a screen resource onto a designated surface */ - void loadScreen(Graphics::Surface *dest, int fileNum, int subfile); + void loadScreen(Graphics::ManagedSurface *dest, int fileNum, int subfile); }; } // End of namespace Access diff --git a/engines/access/font.cpp b/engines/access/font.cpp index 8af183f193..6ae65e43f0 100644 --- a/engines/access/font.cpp +++ b/engines/access/font.cpp @@ -151,13 +151,12 @@ void Font::drawString(ASurface *s, const Common::String &msg, const Common::Poin int Font::drawChar(ASurface *s, char c, Common::Point &pt) { Graphics::Surface &ch = _chars[c - ' ']; - - s->addDirtyRect(Common::Rect(pt.x, pt.y, pt.x + ch.w, pt.y + ch.h)); + Graphics::Surface dest = s->getSubArea(Common::Rect(pt.x, pt.y, pt.x + ch.w, pt.y + ch.h)); // Loop through the lines of the character for (int y = 0; y < ch.h; ++y) { byte *pSrc = (byte *)ch.getBasePtr(0, y); - byte *pDest = (byte *)s->getBasePtr(pt.x, pt.y + y); + byte *pDest = (byte *)dest.getBasePtr(0, y); // Loop through the horizontal pixels of the line for (int x = 0; x < ch.w; ++x, ++pSrc, ++pDest) { diff --git a/engines/access/screen.cpp b/engines/access/screen.cpp index aa15abd59a..9700640b71 100644 --- a/engines/access/screen.cpp +++ b/engines/access/screen.cpp @@ -69,8 +69,6 @@ void Screen::clearScreen() { clearBuffer(); if (_vesaMode) _vm->_clearSummaryFlag = true; - - addDirtyRect(Common::Rect(0, 0, this->w, this->h)); } void Screen::setDisplayScan() { @@ -89,28 +87,14 @@ void Screen::setPanel(int num) { _msVirtualOffset = _virtualOffsetsTable[num]; } -void Screen::updateScreen() { +void Screen::update() { if (_vm->_startup >= 0) { if (--_vm->_startup == -1) _fadeIn = true; return; } - - // Merge the dirty rects - mergeDirtyRects(); - - // Loop through copying dirty areas to the physical screen - Common::List<Common::Rect>::iterator i; - for (i = _dirtyRects.begin(); i != _dirtyRects.end(); ++i) { - const Common::Rect &r = *i; - const byte *srcP = (const byte *)getBasePtr(r.left, r.top); - g_system->copyRectToScreen(srcP, this->pitch, r.left, r.top, - r.width(), r.height()); - } - - // Signal the physical screen to update - g_system->updateScreen(); - _dirtyRects.clear(); + markAllDirty();//****DEBUG**** + Graphics::Screen::update(); } void Screen::setInitialPalettte() { @@ -153,7 +137,7 @@ void Screen::loadRawPalette(Common::SeekableReadStream *stream) { void Screen::updatePalette() { g_system->getPaletteManager()->setPalette(&_tempPalette[0], 0, PALETTE_COUNT); - updateScreen(); + update(); } void Screen::savePalette() { @@ -293,22 +277,7 @@ void Screen::drawBox() { ASurface::drawBox(); } -void Screen::transBlitFrom(ASurface *src, const Common::Point &destPos) { - addDirtyRect(Common::Rect(destPos.x, destPos.y, destPos.x + src->w, destPos.y + src->h)); - ASurface::transBlitFrom(src, destPos); -} - -void Screen::transBlitFrom(ASurface *src, const Common::Rect &bounds) { - addDirtyRect(bounds); - ASurface::transBlitFrom(src, bounds); -} - -void Screen::blitFrom(const Graphics::Surface &src) { - addDirtyRect(Common::Rect(0, 0, src.w, src.h)); - ASurface::blitFrom(src); -} - -void Screen::copyBuffer(Graphics::Surface *src) { +void Screen::copyBuffer(Graphics::ManagedSurface *src) { addDirtyRect(Common::Rect(0, 0, src->w, src->h)); ASurface::copyBuffer(src); } @@ -349,51 +318,7 @@ void Screen::cyclePaletteBackwards() { } void Screen::flashPalette(int count) { - warning("TODO: Implement flashPalette"); -} - -void Screen::addDirtyRect(const Common::Rect &r) { - _dirtyRects.push_back(r); - assert(r.isValidRect() && r.width() > 0 && r.height() > 0); -} - -void Screen::mergeDirtyRects() { - Common::List<Common::Rect>::iterator rOuter, rInner; - - // Ensure dirty rect list has at least two entries - rOuter = _dirtyRects.begin(); - for (int i = 0; i < 2; ++i, ++rOuter) { - if (rOuter == _dirtyRects.end()) - return; - } - - // Process the dirty rect list to find any rects to merge - for (rOuter = _dirtyRects.begin(); rOuter != _dirtyRects.end(); ++rOuter) { - rInner = rOuter; - while (++rInner != _dirtyRects.end()) { - - if ((*rOuter).intersects(*rInner)) { - // these two rectangles overlap or - // are next to each other - merge them - - unionRectangle(*rOuter, *rOuter, *rInner); - - // remove the inner rect from the list - _dirtyRects.erase(rInner); - - // move back to beginning of list - rInner = rOuter; - } - } - } + // No implementation needed in ScummVM } -bool Screen::unionRectangle(Common::Rect &destRect, const Common::Rect &src1, const Common::Rect &src2) { - destRect = src1; - destRect.extend(src2); - - return !destRect.isEmpty(); -} - - } // End of namespace Access diff --git a/engines/access/screen.h b/engines/access/screen.h index 6fa0fe3812..a022741f91 100644 --- a/engines/access/screen.h +++ b/engines/access/screen.h @@ -26,15 +26,13 @@ #include "common/scummsys.h" #include "common/rect.h" #include "common/stream.h" +#include "graphics/screen.h" #include "access/asurface.h" namespace Access { class AccessEngine; -#define PALETTE_COUNT 256 -#define PALETTE_SIZE (256 * 3) - struct ScreenSave { int _clipWidth; int _clipHeight; @@ -47,7 +45,7 @@ struct ScreenSave { int _screenYOff; }; -class Screen : public ASurface { +class Screen : public virtual ASurface, public virtual Graphics::Screen { private: AccessEngine *_vm; byte _tempPalette[PALETTE_SIZE]; @@ -66,10 +64,6 @@ private: Common::List<Common::Rect> _dirtyRects; void updatePalette(); - - void mergeDirtyRects(); - - bool unionRectangle(Common::Rect &destRect, const Common::Rect &src1, const Common::Rect &src2); public: int _vesaMode; int _startColor, _numColors; @@ -87,6 +81,11 @@ public: bool _screenChangeFlag; bool _fadeIn; public: + /** + * Updates the screen + */ + virtual void update(); + virtual void copyBlock(ASurface *src, const Common::Rect &bounds); virtual void restoreBlock(); @@ -95,15 +94,7 @@ public: virtual void drawBox(); - virtual void transBlitFrom(ASurface *src, const Common::Point &destPos); - - virtual void transBlitFrom(ASurface *src, const Common::Rect &bounds); - - virtual void blitFrom(const Graphics::Surface &src); - - virtual void copyBuffer(Graphics::Surface *src); - - virtual void addDirtyRect(const Common::Rect &r); + virtual void copyBuffer(Graphics::ManagedSurface *src); public: Screen(AccessEngine *vm); @@ -114,11 +105,6 @@ public: void setPanel(int num); /** - * Update the underlying screen - */ - void updateScreen(); - - /** * Fade out screen */ void forceFadeOut(); diff --git a/engines/access/scripts.cpp b/engines/access/scripts.cpp index 7c354e78e5..38313640f1 100644 --- a/engines/access/scripts.cpp +++ b/engines/access/scripts.cpp @@ -1005,10 +1005,7 @@ void Scripts::cmdFreeSound() { } while (!_vm->shouldQuit() && sound.isSFXPlaying()); // Free the sounds - while (sound._soundTable.size() > 0) { - delete sound._soundTable[0]._res; - sound._soundTable.remove_at(0); - } + sound.freeSounds(); } } diff --git a/engines/access/video.cpp b/engines/access/video.cpp index 5fc5f6762c..e3ff457c3b 100644 --- a/engines/access/video.cpp +++ b/engines/access/video.cpp @@ -157,7 +157,7 @@ void VideoPlayer::playVideo() { // If the video is playing on the screen surface, add a dirty rect if (_vidSurface == _vm->_screen) - _vm->_screen->addDirtyRect(_videoBounds); + _vm->_screen->markAllDirty(); getFrame(); if (++_videoFrame == _frameCount) { diff --git a/engines/access/video/movie_decoder.cpp b/engines/access/video/movie_decoder.cpp index 05ec25d54c..1406e549ad 100644 --- a/engines/access/video/movie_decoder.cpp +++ b/engines/access/video/movie_decoder.cpp @@ -719,7 +719,7 @@ bool AccessEngine::playMovie(const Common::String &filename, const Common::Point g_system->getPaletteManager()->setPalette(palette, 0, 256); } - _screen->updateScreen(); + _screen->update(); } } diff --git a/engines/advancedDetector.cpp b/engines/advancedDetector.cpp index 72629a833e..7a09f662d1 100644 --- a/engines/advancedDetector.cpp +++ b/engines/advancedDetector.cpp @@ -41,8 +41,8 @@ static GameDescriptor toGameDescriptor(const ADGameDescription &g, const PlainGa title = g.extra; extra = ""; } else { - while (sg->gameid) { - if (!scumm_stricmp(g.gameid, sg->gameid)) + while (sg->gameId) { + if (!scumm_stricmp(g.gameId, sg->gameId)) title = sg->description; sg++; } @@ -56,7 +56,7 @@ static GameDescriptor toGameDescriptor(const ADGameDescription &g, const PlainGa else if (g.flags & ADGF_TESTING) gsl = kTestingGame; - GameDescriptor gd(g.gameid, title, g.language, g.platform, 0, gsl); + GameDescriptor gd(g.gameId, title, g.language, g.platform, 0, gsl); gd.updateDesc(extra); return gd; } @@ -89,21 +89,38 @@ static Common::String generatePreferredTarget(const Common::String &id, const AD return res; } +static Common::String sanitizeName(const char *name) { + Common::String res; + + while (*name) { + if (Common::isAlnum(*name)) + res += tolower(*name); + name++; + } + + return res; +} + void AdvancedMetaEngine::updateGameDescriptor(GameDescriptor &desc, const ADGameDescription *realDesc) const { - if (_singleid != NULL) { + if (_singleId != NULL) { desc["preferredtarget"] = desc["gameid"]; - desc["gameid"] = _singleid; + desc["gameid"] = _singleId; } if (!desc.contains("preferredtarget")) desc["preferredtarget"] = desc["gameid"]; + if (realDesc->flags & ADGF_AUTOGENTARGET) { + if (*realDesc->extra) + desc["preferredtarget"] = sanitizeName(realDesc->extra); + } + desc["preferredtarget"] = generatePreferredTarget(desc["preferredtarget"], realDesc); if (_flags & kADFlagUseExtraAsHint) desc["extra"] = realDesc->extra; - desc.setGUIOptions(realDesc->guioptions + _guioptions); + desc.setGUIOptions(realDesc->guiOptions + _guiOptions); desc.appendGUIOptions(getGameGUIOptionsDescriptionLanguage(realDesc->language)); if (realDesc->flags & ADGF_ADDENGLISH) @@ -149,7 +166,7 @@ GameList AdvancedMetaEngine::detectGames(const Common::FSList &fslist) const { // Use fallback detector if there were no matches by other means const ADGameDescription *fallbackDesc = fallbackDetect(allFiles, fslist); if (fallbackDesc != 0) { - GameDescriptor desc(toGameDescriptor(*fallbackDesc, _gameids)); + GameDescriptor desc(toGameDescriptor(*fallbackDesc, _gameIds)); updateGameDescriptor(desc, fallbackDesc); detectedGames.push_back(desc); } @@ -157,7 +174,7 @@ GameList AdvancedMetaEngine::detectGames(const Common::FSList &fslist) const { // Otherwise use the found matches cleanupPirated(matches); for (uint i = 0; i < matches.size(); i++) { - GameDescriptor desc(toGameDescriptor(*matches[i], _gameids)); + GameDescriptor desc(toGameDescriptor(*matches[i], _gameIds)); updateGameDescriptor(desc, matches[i]); detectedGames.push_back(desc); } @@ -252,10 +269,10 @@ Common::Error AdvancedMetaEngine::createInstance(OSystem *syst, Engine **engine) if (cleanupPirated(matches)) return Common::kNoGameDataFoundError; - if (_singleid == NULL) { + if (_singleId == NULL) { // Find the first match with correct gameid. for (uint i = 0; i < matches.size(); i++) { - if (matches[i]->gameid == gameid) { + if (matches[i]->gameId == gameid) { agdDesc = matches[i]; break; } @@ -270,7 +287,7 @@ Common::Error AdvancedMetaEngine::createInstance(OSystem *syst, Engine **engine) if (agdDesc != 0) { // Seems we found a fallback match. But first perform a basic // sanity check: the gameid must match. - if (_singleid == NULL && agdDesc->gameid != gameid) + if (_singleId == NULL && agdDesc->gameId != gameid) agdDesc = 0; } } @@ -284,9 +301,9 @@ Common::Error AdvancedMetaEngine::createInstance(OSystem *syst, Engine **engine) if (agdDesc->flags & ADGF_ADDENGLISH) lang += " " + getGameGUIOptionsDescriptionLanguage(Common::EN_ANY); - Common::updateGameGUIOptions(agdDesc->guioptions + _guioptions, lang); + Common::updateGameGUIOptions(agdDesc->guiOptions + _guiOptions, lang); - GameDescriptor gameDescriptor = toGameDescriptor(*agdDesc, _gameids); + GameDescriptor gameDescriptor = toGameDescriptor(*agdDesc, _gameIds); bool showTestingWarning = false; @@ -407,7 +424,7 @@ ADGameDescList AdvancedMetaEngine::detectGame(const Common::FSNode &parent, cons // Check which files are included in some ADGameDescription *and* are present. // Compute MD5s and file sizes for these files. - for (descPtr = _gameDescriptors; ((const ADGameDescription *)descPtr)->gameid != 0; descPtr += _descItemSize) { + for (descPtr = _gameDescriptors; ((const ADGameDescription *)descPtr)->gameId != 0; descPtr += _descItemSize) { g = (const ADGameDescription *)descPtr; for (fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) { @@ -430,7 +447,7 @@ ADGameDescList AdvancedMetaEngine::detectGame(const Common::FSNode &parent, cons // MD5 based matching uint i; - for (i = 0, descPtr = _gameDescriptors; ((const ADGameDescription *)descPtr)->gameid != 0; descPtr += _descItemSize, ++i) { + for (i = 0, descPtr = _gameDescriptors; ((const ADGameDescription *)descPtr)->gameId != 0; descPtr += _descItemSize, ++i) { g = (const ADGameDescription *)descPtr; bool fileMissing = false; @@ -487,7 +504,7 @@ ADGameDescList AdvancedMetaEngine::detectGame(const Common::FSNode &parent, cons gotAnyMatchesWithAllFiles = true; if (!fileMissing) { - debug(2, "Found game: %s (%s %s/%s) (%d)", g->gameid, g->extra, + debug(2, "Found game: %s (%s %s/%s) (%d)", g->gameId, g->extra, getPlatformDescription(g->platform), getLanguageDescription(g->language), i); if (curFilesMatched > maxFilesMatched) { @@ -503,7 +520,7 @@ ADGameDescList AdvancedMetaEngine::detectGame(const Common::FSNode &parent, cons } } else { - debug(5, "Skipping game: %s (%s %s/%s) (%d)", g->gameid, g->extra, + debug(5, "Skipping game: %s (%s %s/%s) (%d)", g->gameId, g->extra, getPlatformDescription(g->platform), getLanguageDescription(g->language), i); } } @@ -543,7 +560,7 @@ const ADGameDescription *AdvancedMetaEngine::detectGameFilebased(const FileMap & } if (!fileMissing) { - debug(4, "Matched: %s", agdesc->gameid); + debug(4, "Matched: %s", agdesc->gameId); if (numMatchedFiles > maxNumMatchedFiles) { matchedDesc = agdesc; @@ -568,27 +585,27 @@ const ADGameDescription *AdvancedMetaEngine::detectGameFilebased(const FileMap & } GameList AdvancedMetaEngine::getSupportedGames() const { - if (_singleid != NULL) { + if (_singleId != NULL) { GameList gl; - const PlainGameDescriptor *g = _gameids; - while (g->gameid) { - if (0 == scumm_stricmp(_singleid, g->gameid)) { - gl.push_back(GameDescriptor(g->gameid, g->description)); + const PlainGameDescriptor *g = _gameIds; + while (g->gameId) { + if (0 == scumm_stricmp(_singleId, g->gameId)) { + gl.push_back(GameDescriptor(g->gameId, g->description)); return gl; } g++; } - error("Engine %s doesn't have its singleid specified in ids list", _singleid); + error("Engine %s doesn't have its singleid specified in ids list", _singleId); } - return GameList(_gameids); + return GameList(_gameIds); } -GameDescriptor AdvancedMetaEngine::findGame(const char *gameid) const { +GameDescriptor AdvancedMetaEngine::findGame(const char *gameId) const { // First search the list of supported gameids for a match. - const PlainGameDescriptor *g = findPlainGameDescriptor(gameid, _gameids); + const PlainGameDescriptor *g = findPlainGameDescriptor(gameId, _gameIds); if (g) return GameDescriptor(*g); @@ -596,14 +613,14 @@ GameDescriptor AdvancedMetaEngine::findGame(const char *gameid) const { return GameDescriptor(); } -AdvancedMetaEngine::AdvancedMetaEngine(const void *descs, uint descItemSize, const PlainGameDescriptor *gameids, const ADExtraGuiOptionsMap *extraGuiOptions) - : _gameDescriptors((const byte *)descs), _descItemSize(descItemSize), _gameids(gameids), +AdvancedMetaEngine::AdvancedMetaEngine(const void *descs, uint descItemSize, const PlainGameDescriptor *gameIds, const ADExtraGuiOptionsMap *extraGuiOptions) + : _gameDescriptors((const byte *)descs), _descItemSize(descItemSize), _gameIds(gameIds), _extraGuiOptions(extraGuiOptions) { _md5Bytes = 5000; - _singleid = NULL; + _singleId = NULL; _flags = 0; - _guioptions = GUIO_NONE; + _guiOptions = GUIO_NONE; _maxScanDepth = 1; _directoryGlobs = NULL; } diff --git a/engines/advancedDetector.h b/engines/advancedDetector.h index ad551698f6..ab3ec22bdc 100644 --- a/engines/advancedDetector.h +++ b/engines/advancedDetector.h @@ -81,6 +81,7 @@ typedef Common::HashMap<Common::String, ADFileProperties, Common::IgnoreCase_Has enum ADGameFlags { ADGF_NO_FLAGS = 0, + ADGF_AUTOGENTARGET = (1 << 20), // automatically generate gameid from extra ADGF_UNSTABLE = (1 << 21), // flag to designate not yet officially-supported games that are not fit for public testing ADGF_TESTING = (1 << 22), // flag to designate not yet officially-supported games that are fit for public testing ADGF_PIRATED = (1 << 23), ///< flag to designate well known pirated versions with cracks @@ -94,7 +95,7 @@ enum ADGameFlags { }; struct ADGameDescription { - const char *gameid; + const char *gameId; const char *extra; ADGameFileDescription filesDescriptions[14]; Common::Language language; @@ -107,7 +108,7 @@ struct ADGameDescription { */ uint32 flags; - const char *guioptions; + const char *guiOptions; }; /** @@ -191,7 +192,7 @@ protected: * A list of all gameids (and their corresponding descriptions) supported * by this engine. */ - const PlainGameDescriptor *_gameids; + const PlainGameDescriptor *_gameIds; /** * A map containing all the extra game GUI options the engine supports. @@ -219,7 +220,7 @@ protected: * address a more generic problem. We should find a better way to * disambiguate gameids. */ - const char *_singleid; + const char *_singleId; /** * A bitmask of flags which can be used to configure the behavior @@ -233,7 +234,7 @@ protected: * entry in addition to per-game options. Refer to GameGUIOption * enum for the list. */ - Common::String _guioptions; + Common::String _guiOptions; /** * Maximum depth of directories to look up. @@ -251,7 +252,7 @@ protected: const char * const *_directoryGlobs; public: - AdvancedMetaEngine(const void *descs, uint descItemSize, const PlainGameDescriptor *gameids, const ADExtraGuiOptionsMap *extraGuiOptions = 0); + AdvancedMetaEngine(const void *descs, uint descItemSize, const PlainGameDescriptor *gameIds, const ADExtraGuiOptionsMap *extraGuiOptions = 0); /** * Returns list of targets supported by the engine. @@ -259,7 +260,7 @@ public: */ virtual GameList getSupportedGames() const; - virtual GameDescriptor findGame(const char *gameid) const; + virtual GameDescriptor findGame(const char *gameId) const; virtual GameList detectGames(const Common::FSList &fslist) const; diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp index bcf84840d2..6e63cd3e71 100644 --- a/engines/agi/agi.cpp +++ b/engines/agi/agi.cpp @@ -283,18 +283,7 @@ void AgiBase::initRenderMode() { switch (platform) { case Common::kPlatformDOS: - switch (configRenderMode) { - case Common::kRenderCGA: - _renderMode = Common::kRenderCGA; - break; - // Hercules is not supported atm - //case Common::kRenderHercA: - //case Common::kRenderHercG: - // _renderMode = Common::kRenderHercG; - // break; - default: - break; - } + // Keep EGA break; case Common::kPlatformAmiga: _renderMode = Common::kRenderAmiga; @@ -323,6 +312,12 @@ void AgiBase::initRenderMode() { case Common::kRenderVGA: _renderMode = Common::kRenderVGA; break; + case Common::kRenderHercG: + _renderMode = Common::kRenderHercG; + break; + case Common::kRenderHercA: + _renderMode = Common::kRenderHercA; + break; case Common::kRenderAmiga: _renderMode = Common::kRenderAmiga; break; @@ -401,6 +396,11 @@ AgiEngine::AgiEngine(OSystem *syst, const AGIGameDescription *gameDesc) : AgiBas setupOpcodes(); _game._curLogic = NULL; + _veryFirstInitialCycle = true; + _instructionCounter = 0; + resetGetVarSecondsHeuristic(); + + _setVolumeBrokenFangame = false; // for further study see AgiEngine::setVolumeViaScripts() _lastSaveTime = 0; @@ -461,7 +461,7 @@ void AgiEngine::initialize() { _console = new Console(this); _words = new Words(this); _font = new GfxFont(this); - _gfx = new GfxMgr(this); + _gfx = new GfxMgr(this, _font); _sound = new SoundMgr(this, _mixer); _picture = new PictureMgr(this, _gfx); _sprites = new SpritesMgr(this, _gfx); @@ -470,6 +470,8 @@ void AgiEngine::initialize() { _inventory = new InventoryMgr(this, _gfx, _text, _systemUI); _font->init(); + _gfx->initVideo(); + _text->init(_systemUI); _game.gameFlags = 0; @@ -478,8 +480,6 @@ void AgiEngine::initialize() { _game.name[0] = '\0'; - _gfx->initVideo(); - _lastSaveTime = 0; debugC(2, kDebugLevelMain, "Detect game"); @@ -507,19 +507,6 @@ void AgiEngine::redrawScreen() { _text->promptRedraw(); } -// Adjust a given coordinate to the local game screen -// Used on mouse cursor coordinates before passing them to scripts -void AgiEngine::adjustPosToGameScreen(int16 &x, int16 &y) { - x = x / 2; // 320 -> 160 - y = y - _gfx->getRenderStartOffsetY(); // remove status bar line - if (y < 0) { - y = 0; - } - if (y >= SCRIPT_HEIGHT) { - y = SCRIPT_HEIGHT + 1; // 1 beyond - } -} - AgiEngine::~AgiEngine() { agiDeinit(); delete _loader; @@ -563,17 +550,28 @@ void AgiEngine::syncSoundSettings() { setVolumeViaSystemSetting(); } +// WORKAROUND: +// Sometimes Sierra printed some text on the screen and did a room change immediately afterwards expecting the +// interpreter to load the data for a bit of time. This of course doesn't happen in our AGI, so we try to +// detect such situations via heuristic and then delay the game for a bit. +// In those cases a wait mouse cursor will be shown. +// // Scenes that need this: // +// Gold Rush: +// - During Stagecoach path, after getting solving the steep hill "Congratulations!!!" (NewRoom) +// - when following your mule "Yet right on his tail!!!" (NewRoom/NewPicture - but room 123 stays the same) // Manhunter 1: // - intro text screen (DrawPic) // - MAD "zooming in..." during intro and other scenes, for example room 124 (NewRoom) // The NewRoom call is not done during the same cycle as the "zooming in..." print call. // Space Quest 1: // - right at the start of the game (NewRoom) +// - right at the end of the asteroids "That was mighty close!" (NewRoom) // Space Quest 2 // - right at the start of the game (NewRoom) // - after exiting the very first room, a message pops up, that isn't readable without it (NewRoom) +// - Climbing into shuttle on planet Labion. "You open the hatch and head on in." (NewRoom) // Games, that must not be triggered: @@ -590,7 +588,14 @@ void AgiEngine::nonBlockingText_Forget() { _game.nonBlockingTextShown = false; _game.nonBlockingTextCyclesLeft = 0; } -void AgiEngine::nonBlockingText_CycleDone() { + +void AgiEngine::artificialDelay_Reset() { + nonBlockingText_Forget(); + _artificialDelayCurrentRoom = -1; + _artificialDelayCurrentPicture = -1; +} + +void AgiEngine::artificialDelay_CycleDone() { if (_game.nonBlockingTextCyclesLeft) { _game.nonBlockingTextCyclesLeft--; @@ -601,30 +606,97 @@ void AgiEngine::nonBlockingText_CycleDone() { } } -void AgiEngine::loadingTrigger_NewRoom(int16 newRoomNr) { - if (_game.nonBlockingTextShown) { - _game.nonBlockingTextShown = false; +// WORKAROUND: +// On Apple IIgs, there are situations like for example the Police Quest 1 intro, where music is playing +// and then the scripts switch to a new room, expecting it to load for a bit of time. In ScummVM this results +// in music getting cut off, because our loading is basically done in an instant. This also happens in the +// original interpreter, when you use a faster CPU in emulation. +// +// That's why there is an additional table, where one can add such situations to it. +// These issues are basically impossible to detect, because sometimes music is also supposed to play throughout +// multiple rooms. +// +// Normally all text-based issues should get detected by the current heuristic. Do not add those in here. + +// script, description, signature patch +static const AgiArtificialDelayEntry artificialDelayTable[] = { + { GID_GOLDRUSH, Common::kPlatformApple2GS, ARTIFICIALDELAYTYPE_NEWROOM, 14, 21, 2200 }, // Stagecoach path: right after getting on it in Brooklyn + { GID_PQ1, Common::kPlatformApple2GS, ARTIFICIALDELAYTYPE_NEWPICTURE, 1, 2, 2200 }, // Intro: music track is supposed to finish before credits screen. Developers must have assumed that room loading would take that long. + { GID_MH1, Common::kPlatformApple2GS, ARTIFICIALDELAYTYPE_NEWPICTURE, 155, 183, 2200 }, // Happens, when hitting fingers at bar + { GID_AGIDEMO, Common::kPlatformUnknown, ARTIFICIALDELAYTYPE_END, -1, -1, 0 } +}; - int16 curRoomNr = getVar(VM_VAR_CURRENT_ROOM); +uint16 AgiEngine::artificialDelay_SearchTable(AgiArtificialDelayTriggerType triggerType, int16 orgNr, int16 newNr) { + if (getPlatform() != Common::kPlatformApple2GS) { + return 0; + } + + const AgiArtificialDelayEntry *delayEntry = artificialDelayTable; - if (newRoomNr != curRoomNr) { - if (!_game.automaticRestoreGame) { - // wait a bit, we detected non-blocking text - wait(2000, true); // 2 seconds, set busy + while (delayEntry->triggerType != ARTIFICIALDELAYTYPE_END) { + if (triggerType == delayEntry->triggerType) { + if ((orgNr == delayEntry->orgNr) && (newNr == delayEntry->newNr)) { + if ((getGameID() == delayEntry->gameId) && (getPlatform() == delayEntry->platform)) { + warning("artificial delay forced"); + return delayEntry->millisecondsDelay; + } } } + + delayEntry++; } + return 0; } -void AgiEngine::loadingTrigger_DrawPicture() { - if (_game.nonBlockingTextShown) { - _game.nonBlockingTextShown = false; +void AgiEngine::artificialDelayTrigger_NewRoom(int16 newRoomNr) { + uint16 millisecondsDelay = 0; - if (!_game.automaticRestoreGame) { - // wait a bit, we detected non-blocking text - wait(2000, true); // 2 seconds, set busy + //warning("artificial delay trigger: room %d -> new room %d", _artificialDelayCurrentRoom, newRoomNr); + + if (!_game.automaticRestoreGame) { + millisecondsDelay = artificialDelay_SearchTable(ARTIFICIALDELAYTYPE_NEWROOM, _artificialDelayCurrentRoom, newRoomNr); + + if (_game.nonBlockingTextShown) { + if (newRoomNr != _artificialDelayCurrentRoom) { + if (millisecondsDelay < 2000) { + // wait a bit, we detected non-blocking text + millisecondsDelay = 2000; // 2 seconds + } + } + } + + if (millisecondsDelay) { + wait(millisecondsDelay, true); // set busy mouse cursor + _game.nonBlockingTextShown = false; + } + } + + _artificialDelayCurrentRoom = newRoomNr; +} + +void AgiEngine::artificialDelayTrigger_DrawPicture(int16 newPictureNr) { + uint16 millisecondsDelay = 0; + + //warning("artificial delay trigger: picture %d -> new picture %d", _artificialDelayCurrentPicture, newPictureNr); + + if (!_game.automaticRestoreGame) { + millisecondsDelay = artificialDelay_SearchTable(ARTIFICIALDELAYTYPE_NEWPICTURE, _artificialDelayCurrentPicture, newPictureNr); + + if (_game.nonBlockingTextShown) { + if (newPictureNr != _artificialDelayCurrentPicture) { + if (millisecondsDelay < 2000) { + // wait a bit, we detected non-blocking text + millisecondsDelay = 2000; // 2 seconds, set busy + } + } + } + + if (millisecondsDelay) { + wait(millisecondsDelay, true); // set busy mouse cursor + _game.nonBlockingTextShown = false; } } + _artificialDelayCurrentPicture = newPictureNr; } } // End of namespace Agi diff --git a/engines/agi/agi.h b/engines/agi/agi.h index 93017af099..b288557f57 100644 --- a/engines/agi/agi.h +++ b/engines/agi/agi.h @@ -148,17 +148,17 @@ enum BooterDisks { // position and position.v. // enum AgiGameFeatures { - GF_AGIMOUSE = (1 << 0), + GF_AGIMOUSE = (1 << 0), // this disables "Click-to-walk mouse interface" GF_AGDS = (1 << 1), - GF_AGI256 = (1 << 2), - GF_AGI256_2 = (1 << 3), - GF_AGIPAL = (1 << 4), - GF_MACGOLDRUSH = (1 << 5), - GF_FANMADE = (1 << 6), - GF_MENUS = (1 << 7), - GF_ESCPAUSE = (1 << 8), + GF_AGI256 = (1 << 2), // marks fanmade AGI-256 games + GF_AGI256_2 = (1 << 3), // marks fanmade AGI-256-2 games + GF_AGIPAL = (1 << 4), // marks game using fanmade AGIPAL extension + GF_MACGOLDRUSH = (1 << 5), // use "grdir" instead of "dir" for volume loading + GF_FANMADE = (1 << 6), // marks fanmade games + GF_MENUS = (1 << 7), // not used anymore + GF_ESCPAUSE = (1 << 8), // not used anymore, we detect this internally GF_OLDAMIGAV20 = (1 << 9), - GF_CLIPCOORDS = (1 << 10), + GF_CLIPCOORDS = (1 << 10), // not used atm GF_2GSOLDSOUND = (1 << 11) }; @@ -704,6 +704,21 @@ public: } }; +enum AgiArtificialDelayTriggerType { + ARTIFICIALDELAYTYPE_NEWROOM = 0, + ARTIFICIALDELAYTYPE_NEWPICTURE = 1, + ARTIFICIALDELAYTYPE_END = -1 +}; + +struct AgiArtificialDelayEntry { + uint32 gameId; + Common::Platform platform; + AgiArtificialDelayTriggerType triggerType; + int16 orgNr; + int16 newNr; + uint16 millisecondsDelay; +}; + typedef void (*AgiCommand)(AgiGame *state, AgiEngine *vm, uint8 *p); class AgiEngine : public AgiBase { @@ -724,8 +739,6 @@ public: Common::Error loadGameState(int slot); Common::Error saveGameState(int slot, const Common::String &description); - void adjustPosToGameScreen(int16 &x, int16 &y); - private: int _keyQueue[KEY_QUEUE_SIZE]; int _keyQueueStart; @@ -746,7 +759,7 @@ public: SavedGameSlotIdArray getSavegameSlotIds(); Common::String getSavegameFilename(int16 slotId) const; - bool getSavegameInformation(int16 slotId, Common::String &saveDescription, uint32 &saveDate, uint16 &saveTime, bool &saveIsValid); + bool getSavegameInformation(int16 slotId, Common::String &saveDescription, uint32 &saveDate, uint32 &saveTime, bool &saveIsValid); int saveGame(const Common::String &fileName, const Common::String &descriptionString); int loadGame(const Common::String &fileName, bool checkId = true); @@ -847,6 +860,21 @@ public: int testIfCode(int); void executeAgiCommand(uint8, uint8 *); +private: + bool _veryFirstInitialCycle; /**< signals, that currently the very first cycle is executed (restarts, etc. do not count!) */ + uint32 _instructionCounter; /**< counts every instruction, that got executed, can wrap around */ + + bool _setVolumeBrokenFangame; + + void resetGetVarSecondsHeuristic(); + void getVarSecondsHeuristicTrigger(); + uint32 _getVarSecondsHeuristicLastInstructionCounter; /**< last time VM_VAR_SECONDS were read */ + uint16 _getVarSecondsHeuristicCounter; /**< how many times heuristic was triggered */ + + uint32 _playTimeInSecondsAdjust; /**< milliseconds to adjust for calculating current play time in seconds, see setVarSecondsTrigger() */ + + void setVarSecondsTrigger(byte newSeconds); + public: // Some submethods of testIfCode void skipInstruction(byte op); @@ -899,6 +927,8 @@ private: void checkMotion(ScreenObjEntry *screenObj); public: + void motionActivated(ScreenObjEntry *screenObj); + void cyclerActivated(ScreenObjEntry *screenObj); void checkAllMotions(); void moveObj(ScreenObjEntry *screenObj); void inDestination(ScreenObjEntry *screenObj); @@ -924,10 +954,18 @@ public: void nonBlockingText_IsShown(); void nonBlockingText_Forget(); - void nonBlockingText_CycleDone(); - void loadingTrigger_NewRoom(int16 newRoomNr); - void loadingTrigger_DrawPicture(); + void artificialDelay_Reset(); + void artificialDelay_CycleDone(); + + uint16 artificialDelay_SearchTable(AgiArtificialDelayTriggerType triggerType, int16 orgNr, int16 newNr); + + void artificialDelayTrigger_NewRoom(int16 newRoomNr); + void artificialDelayTrigger_DrawPicture(int16 newPictureNr); + +private: + int16 _artificialDelayCurrentRoom; + int16 _artificialDelayCurrentPicture; public: void redrawScreen(); diff --git a/engines/agi/appleIIgs_timedelay_overwrite.h b/engines/agi/appleIIgs_timedelay_overwrite.h new file mode 100644 index 0000000000..c24d7cb5bd --- /dev/null +++ b/engines/agi/appleIIgs_timedelay_overwrite.h @@ -0,0 +1,91 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef AGI_APPLEIIGS_DELAY_OVERWRITE_H +#define AGI_APPLEIIGS_DELAY_OVERWRITE_H + +namespace Agi { + +struct AgiAppleIIgsDelayOverwriteRoomEntry { + int16 fromRoom; + int16 toRoom; + int16 timeDelayOverwrite; // time delay here is like on PC, so 0 - unlimited, 1 - 20 cycles, 2 - 10 cycles + bool onlyWhenPlayerNotInControl; +}; + +struct AgiAppleIIgsDelayOverwriteGameEntry { + uint32 gameId; + int16 defaultTimeDelayOverwrite; // time delay here is like on PC, so 0 - unlimited, 1 - 20 cycles, 2 - 10 cycles + const AgiAppleIIgsDelayOverwriteRoomEntry *roomTable; +}; + +static const AgiAppleIIgsDelayOverwriteRoomEntry appleIIgsDelayOverwriteKQ4[] = { + { 120, 121, -1, true }, // Part of the intro: Graham gets his hat, throws it and breaks down, don't touch speed (3 is set) + { 128, 128, -1, true }, // Part of the intro: first actual room for gameplay, but during intro, don't touch speed (3 is set) + { 92, 92, -1, true }, // Part of caught by gargoyle w/ Lolotte cutscene (3 is set) + // room 54 sets the speed for a short time to 3 right after entering "clean" command. It doesn't seem to hurt that we switch it down to 2 + // room 92 is dual use, part of cutscenes, part of gameplay, that's why we only stop touching it, when player is not in control + { 135, 135, -1, true }, // Part of ending cutscene. Don't touch speed (3 is set) + { -1, -1, -1, false } +}; + +static const AgiAppleIIgsDelayOverwriteRoomEntry appleIIgsDelayOverwriteMH1[] = { + //{ 153, 153, 2, false }, // Intro w/ credits + //{ 104, 104, 2, false }, // Intro cutscene + //{ 117, 117, 2, false }, // Intro cutscene (ego waking up) + { 114, 114, -1, false }, // interactive MAD map + { 124, 125, -1, false }, // MAD during intro (tracking), seem to work properly at given speed + { 132, 133, -1, false }, // MAD day 2 intro (tracking) + { 137, 137, -1, false }, // Night Club 4th arcade game - game sets speed to 7 + { 115, 116, -1, false }, // MAD day 3 intro (tracking) + { 148, 148, -1, false }, // day 3: arcade sequence under pawn shop (game sets speed to 6) + { 103, 103, -1, false }, // MAD day 4 intro (tracking) + { 105, 105, -1, false }, // day 4 tracking mini game right at the start (game sets speed to 3) + { 107, 107, -1, false }, // MAD day 4 intro (tracking) + { 112, 112, -1, false }, // MAD day 4 intro (tracking) + { -1, -1, -1, false } +}; + +static const AgiAppleIIgsDelayOverwriteRoomEntry appleIIgsDelayOverwriteSQ2[] = { + { 1, 1, -1, false }, // Intro: space ship entering space port, don't touch speed + { -1, -1, -1, false } +}; + +static const AgiAppleIIgsDelayOverwriteGameEntry appleIIgsDelayOverwriteGameTable[] = { + { GID_BC, 2, nullptr }, // sets the speed at the start and doesn't modify it + { GID_GOLDRUSH, 2, nullptr }, + { GID_KQ1, 2, nullptr }, + // KQ2 seems to work fine at speed given by scripts + { GID_KQ3, 2, nullptr }, + { GID_KQ4, 2, appleIIgsDelayOverwriteKQ4 }, + { GID_LSL1, 2, nullptr }, // Switch Larry 1 to 10 cycles per second (that's around PC Larry 1's "normal" speed + { GID_MH1, 2, appleIIgsDelayOverwriteMH1 }, + { GID_MIXEDUP, 2, nullptr }, + { GID_PQ1, 2, nullptr }, + { GID_SQ1, 2, nullptr }, // completed, no issues using these settings + { GID_SQ2, 2, appleIIgsDelayOverwriteSQ2 }, + { GID_AGIDEMO, -1, nullptr } +}; + +} // End of namespace Agi + +#endif /* AGI_APPLEIIGS_DELAY_OVERWRITE_H */ diff --git a/engines/agi/checks.cpp b/engines/agi/checks.cpp index 1e1670f674..c67b6a5810 100644 --- a/engines/agi/checks.cpp +++ b/engines/agi/checks.cpp @@ -129,7 +129,7 @@ bool AgiEngine::checkPriority(ScreenObjEntry *screenObj) { screenPriority = _gfx->getPriority(curX, curY); if (screenPriority == 0) { // unconditional black. no go at all! - touchedControl = 0; + touchedControl = false; break; } diff --git a/engines/agi/console.cpp b/engines/agi/console.cpp index 6419e60219..9a4a357b44 100644 --- a/engines/agi/console.cpp +++ b/engines/agi/console.cpp @@ -33,27 +33,28 @@ namespace Agi { Console::Console(AgiEngine *vm) : GUI::Debugger() { _vm = vm; - registerCmd("debug", WRAP_METHOD(Console, Cmd_Debug)); - registerCmd("cont", WRAP_METHOD(Console, Cmd_Cont)); - registerCmd("agiver", WRAP_METHOD(Console, Cmd_Agiver)); - registerCmd("version", WRAP_METHOD(Console, Cmd_Version)); - registerCmd("flags", WRAP_METHOD(Console, Cmd_Flags)); - registerCmd("logic0", WRAP_METHOD(Console, Cmd_Logic0)); - registerCmd("objs", WRAP_METHOD(Console, Cmd_Objs)); - registerCmd("runopcode", WRAP_METHOD(Console, Cmd_RunOpcode)); - registerCmd("opcode", WRAP_METHOD(Console, Cmd_Opcode)); - registerCmd("step", WRAP_METHOD(Console, Cmd_Step)); - registerCmd("trigger", WRAP_METHOD(Console, Cmd_Trigger)); - registerCmd("vars", WRAP_METHOD(Console, Cmd_Vars)); - registerCmd("setvar", WRAP_METHOD(Console, Cmd_SetVar)); - registerCmd("setflag", WRAP_METHOD(Console, Cmd_SetFlag)); - registerCmd("setobj", WRAP_METHOD(Console, Cmd_SetObj)); - registerCmd("room", WRAP_METHOD(Console, Cmd_Room)); - registerCmd("bt", WRAP_METHOD(Console, Cmd_BT)); - registerCmd("show_map", WRAP_METHOD(Console, Cmd_ShowMap)); - registerCmd("screenobj", WRAP_METHOD(Console, Cmd_ScreenObj)); - registerCmd("vmvars", WRAP_METHOD(Console, Cmd_VmVars)); - registerCmd("vmflags", WRAP_METHOD(Console, Cmd_VmFlags)); + registerCmd("debug", WRAP_METHOD(Console, Cmd_Debug)); + registerCmd("cont", WRAP_METHOD(Console, Cmd_Cont)); + registerCmd("agiver", WRAP_METHOD(Console, Cmd_Agiver)); + registerCmd("version", WRAP_METHOD(Console, Cmd_Version)); + registerCmd("flags", WRAP_METHOD(Console, Cmd_Flags)); + registerCmd("logic0", WRAP_METHOD(Console, Cmd_Logic0)); + registerCmd("objs", WRAP_METHOD(Console, Cmd_Objs)); + registerCmd("runopcode", WRAP_METHOD(Console, Cmd_RunOpcode)); + registerCmd("opcode", WRAP_METHOD(Console, Cmd_Opcode)); + registerCmd("step", WRAP_METHOD(Console, Cmd_Step)); + registerCmd("trigger", WRAP_METHOD(Console, Cmd_Trigger)); + registerCmd("vars", WRAP_METHOD(Console, Cmd_Vars)); + registerCmd("setvar", WRAP_METHOD(Console, Cmd_SetVar)); + registerCmd("setflag", WRAP_METHOD(Console, Cmd_SetFlag)); + registerCmd("setobj", WRAP_METHOD(Console, Cmd_SetObj)); + registerCmd("room", WRAP_METHOD(Console, Cmd_Room)); + registerCmd("bt", WRAP_METHOD(Console, Cmd_BT)); + registerCmd("show_map", WRAP_METHOD(Console, Cmd_ShowMap)); + registerCmd("screenobj", WRAP_METHOD(Console, Cmd_ScreenObj)); + registerCmd("vmvars", WRAP_METHOD(Console, Cmd_VmVars)); + registerCmd("vmflags", WRAP_METHOD(Console, Cmd_VmFlags)); + registerCmd("disableautosave", WRAP_METHOD(Console, Cmd_DisableAutomaticSave)); } bool Console::Cmd_SetVar(int argc, const char **argv) { @@ -609,6 +610,18 @@ bool Console::Cmd_VmFlags(int argc, const char **argv) { return true; } +bool Console::Cmd_DisableAutomaticSave(int argc, const char **argv) { + if (!_vm->_game.automaticSave) { + debugPrintf("Automatic saving is currently not enabled\n"); + return true; + } + + _vm->_game.automaticSave = false; + + debugPrintf("Automatic saving DISABLED!\n"); + return true; +} + bool Console::parseInteger(const char *argument, int &result) { char *endPtr = 0; int idxLen = strlen(argument); diff --git a/engines/agi/console.h b/engines/agi/console.h index 41dc9ddabc..ccc17b31de 100644 --- a/engines/agi/console.h +++ b/engines/agi/console.h @@ -66,6 +66,7 @@ private: bool Cmd_ScreenObj(int argc, const char **argv); bool Cmd_VmVars(int argc, const char **argv); bool Cmd_VmFlags(int argc, const char **argv); + bool Cmd_DisableAutomaticSave(int argc, const char **argv); bool parseInteger(const char *argument, int &result); diff --git a/engines/agi/cycle.cpp b/engines/agi/cycle.cpp index 17c9c873cc..19aca6f2c4 100644 --- a/engines/agi/cycle.cpp +++ b/engines/agi/cycle.cpp @@ -30,6 +30,7 @@ #include "agi/keyboard.h" #include "agi/menu.h" #include "agi/systemui.h" +#include "agi/appleIIgs_timedelay_overwrite.h" namespace Agi { @@ -44,7 +45,7 @@ void AgiEngine::newRoom(int16 newRoomNr) { int i; // Loading trigger - loadingTrigger_NewRoom(newRoomNr); + artificialDelayTrigger_NewRoom(newRoomNr); debugC(4, kDebugLevelMain, "*** room %d ***", newRoomNr); _sound->stopSound(); @@ -141,6 +142,9 @@ void AgiEngine::interpretCycle() { oldScore = getVar(VM_VAR_SCORE); oldSound = getFlag(VM_FLAG_SOUND_ON); + // Reset script heuristic here + resetGetVarSecondsHeuristic(); + _game.exitAllLogics = false; while (runLogic(0) == 0 && !(shouldQuit() || _restartGame)) { setVar(VM_VAR_WORD_NOT_FOUND, 0); @@ -149,10 +153,12 @@ void AgiEngine::interpretCycle() { oldScore = getVar(VM_VAR_SCORE); setFlag(VM_FLAG_ENTERED_CLI, false); _game.exitAllLogics = false; - nonBlockingText_CycleDone(); + _veryFirstInitialCycle = false; + artificialDelay_CycleDone(); resetControllers(); } - nonBlockingText_CycleDone(); + _veryFirstInitialCycle = false; + artificialDelay_CycleDone(); resetControllers(); screenObjEgo->direction = getVar(VM_VAR_EGO_DIRECTION); @@ -219,9 +225,10 @@ uint16 AgiEngine::processAGIEvents() { // no inner loop active at the moment, regular processing if (key) { - setVar(VM_VAR_KEY, key & 0xFF); if (!handleController(key)) { if (key) { + // Only set VAR_KEY, when no controller/direction was detected + setVar(VM_VAR_KEY, key & 0xFF); if (_text->promptIsEnabled()) { _text->promptKeyPress(key); } @@ -288,6 +295,8 @@ uint16 AgiEngine::processAGIEvents() { int AgiEngine::playGame() { int ec = errOK; + const AgiAppleIIgsDelayOverwriteGameEntry *appleIIgsDelayOverwrite = nullptr; + const AgiAppleIIgsDelayOverwriteRoomEntry *appleIIgsDelayRoomOverwrite = nullptr; debugC(2, kDebugLevelMain, "initializing..."); debugC(2, kDebugLevelMain, "game version = 0x%x", getVersion()); @@ -306,7 +315,7 @@ int AgiEngine::playGame() { setFlag(VM_FLAG_LOGIC_ZERO_FIRST_TIME, true); // not in 2.917 setFlag(VM_FLAG_NEW_ROOM_EXEC, true); // needed for MUMG and SQ2! setFlag(VM_FLAG_SOUND_ON, true); // enable sound - setVar(VM_VAR_TIME_DELAY, 2); // "normal" speed + // do not set VM_VAR_TIME_DELAY, original AGI did not do it (in the data segment it was simply set to 0) _game.gfxMode = true; _text->promptRow_Set(22); @@ -335,7 +344,17 @@ int AgiEngine::playGame() { } } - nonBlockingText_Forget(); + artificialDelay_Reset(); + + if (getPlatform() == Common::kPlatformApple2GS) { + // Look up, if there is a time delay overwrite table for the current game + appleIIgsDelayOverwrite = appleIIgsDelayOverwriteGameTable; + while (appleIIgsDelayOverwrite->gameId != GID_AGIDEMO) { + if (appleIIgsDelayOverwrite->gameId == getGameID()) + break; // game found + appleIIgsDelayOverwrite++; + } + } do { processAGIEvents(); @@ -352,6 +371,46 @@ int AgiEngine::playGame() { // Normally that game runs at TIME_DELAY 1. // Maybe a script patch for this game would make sense. // TODO: needs further investigation + + int16 timeDelayOverwrite = -99; + + // Now check, if we got a time delay overwrite entry for current room + if (appleIIgsDelayOverwrite->roomTable) { + byte curRoom = getVar(VM_VAR_CURRENT_ROOM); + + appleIIgsDelayRoomOverwrite = appleIIgsDelayOverwrite->roomTable; + while (appleIIgsDelayRoomOverwrite->fromRoom >= 0) { + if ((appleIIgsDelayRoomOverwrite->fromRoom <= curRoom) && (appleIIgsDelayRoomOverwrite->toRoom >= curRoom)) { + if (appleIIgsDelayRoomOverwrite->onlyWhenPlayerNotInControl) { + if (_game.playerControl) { + // Player is actually currently in control? -> then skip this entry + appleIIgsDelayRoomOverwrite++; + continue; + } + } + timeDelayOverwrite = appleIIgsDelayRoomOverwrite->timeDelayOverwrite; + break; + } + appleIIgsDelayRoomOverwrite++; + } + + if (timeDelayOverwrite == -99) { + // use default time delay in case no room specific one was found + timeDelayOverwrite = appleIIgsDelayOverwrite->defaultTimeDelayOverwrite; + } + } else { + timeDelayOverwrite = appleIIgsDelayOverwrite->defaultTimeDelayOverwrite; + } + + if (timeDelayOverwrite >= 0) { + if (timeDelayOverwrite != timeDelay) { + // delayOverwrite is not the same as the delay taken from the scripts? overwrite it + //warning("AppleIIgs: time delay overwrite from %d to %d", timeDelay, timeDelayOverwrite); + + setVar(VM_VAR_TIME_DELAY, timeDelayOverwrite - 1); // adjust for Apple IIgs + timeDelay = timeDelayOverwrite; + } + } } if (_passedPlayTimeCycles >= timeDelay) { @@ -396,7 +455,7 @@ int AgiEngine::runGame() { if (_restartGame) { setFlag(VM_FLAG_RESTART_GAME, true); - setVar(VM_VAR_TIME_DELAY, 2); // "normal" speed + // do not set VM_VAR_TIME_DELAY, original AGI did not do it // Reset in-game timer inGameTimerReset(); @@ -438,7 +497,13 @@ int AgiEngine::runGame() { break; case Common::kRenderHercA: case Common::kRenderHercG: - setVar(VM_VAR_MONITOR, kAgiMonitorHercules); + // Set EGA for now. Some games place text differently, when this is set to kAgiMonitorHercules. + // Text placement was different for Hercules rendering (16x12 instead of 16x16). There also was + // not enough space left for the prompt at the bottom. This was caused by the Hercules resolution. + // We don't have this restriction and we also support the regular prompt for Hercules mode. + // In theory Sierra could have had special Hercules code inside their games. + // TODO: check this. + setVar(VM_VAR_MONITOR, kAgiMonitorEga); break; // Don't know if Amiga AGI games use a different value than kAgiMonitorEga // for vMonitor so I just use kAgiMonitorEga for them (As was done before too). diff --git a/engines/agi/detection.cpp b/engines/agi/detection.cpp index 6fca86db63..9f66d78d80 100644 --- a/engines/agi/detection.cpp +++ b/engines/agi/detection.cpp @@ -171,6 +171,26 @@ static const ADExtraGuiOptionsMap optionsList[] = { } }, + { + GAMEOPTION_USE_HERCULES_FONT, + { + _s("Use Hercules hires font"), + _s("Uses Hercules hires font, when font file is available."), + "herculesfont", + false + } + }, + + { + GAMEOPTION_COMMAND_PROMPT_WINDOW, + { + _s("Pause when entering commands"), + _s("Shows a command prompt window and pauses the game (like in SCI) instead of a real-time prompt."), + "commandpromptwindow", + false + } + }, + AD_EXTRA_GUI_OPTIONS_TERMINATOR }; @@ -182,8 +202,8 @@ class AgiMetaEngine : public AdvancedMetaEngine { public: AgiMetaEngine() : AdvancedMetaEngine(Agi::gameDescriptions, sizeof(Agi::AGIGameDescription), agiGames, optionsList) { - _singleid = "agi"; - _guioptions = GUIO1(GUIO_NOSPEECH); + _singleId = "agi"; + _guiOptions = GUIO1(GUIO_NOSPEECH); } virtual const char *getName() const { @@ -261,7 +281,6 @@ SaveStateList AgiMetaEngine::listSaves(const char *target) const { pattern += ".###"; filenames = saveFileMan->listSavefiles(pattern); - sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) SaveStateList saveList; for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { @@ -299,6 +318,8 @@ SaveStateList AgiMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } @@ -359,6 +380,9 @@ SaveStateDescriptor AgiMetaEngine::querySaveMetaInfos(const char *target, int sl uint32 saveDate = in->readUint32BE(); uint16 saveTime = in->readUint16BE(); + if (saveVersion >= 9) { + in->readByte(); // skip over seconds of saveTime (not needed here) + } if (saveVersion >= 6) { uint32 playTime = in->readUint32BE(); descriptor.setPlayTime(playTime * 1000); @@ -543,13 +567,13 @@ const ADGameDescription *AgiMetaEngine::fallbackDetect(const FileMap &allFilesXX // Override the gameid & extra values in g_fallbackDesc.desc. This only works // until the fallback detector is called again, and while the MetaEngine instance // is alive (as else the string storage is modified/deleted). - g_fallbackDesc.desc.gameid = _gameid.c_str(); + g_fallbackDesc.desc.gameId = _gameid.c_str(); g_fallbackDesc.desc.extra = _extra.c_str(); Common::String fallbackWarning; fallbackWarning = "Your game version has been detected using fallback matching as a\n"; - fallbackWarning += Common::String::format("variant of %s (%s).\n", g_fallbackDesc.desc.gameid, g_fallbackDesc.desc.extra); + fallbackWarning += Common::String::format("variant of %s (%s).\n", g_fallbackDesc.desc.gameId, g_fallbackDesc.desc.extra); fallbackWarning += "If this is an original and unmodified version or new made Fanmade game,\n"; fallbackWarning += "please report any, information previously printed by ScummVM to the team.\n"; diff --git a/engines/agi/detection_tables.h b/engines/agi/detection_tables.h index 8da255d9ee..0938e9d4d6 100644 --- a/engines/agi/detection_tables.h +++ b/engines/agi/detection_tables.h @@ -24,12 +24,14 @@ namespace Agi { #define GAMEOPTION_ORIGINAL_SAVELOAD GUIO_GAMEOPTIONS1 #define GAMEOPTION_AMIGA_ALTERNATIVE_PALETTE GUIO_GAMEOPTIONS2 -#define GAMEOPTION_DISABLE_MOUSE GUIO_GAMEOPTIONS3 +#define GAMEOPTION_DISABLE_MOUSE GUIO_GAMEOPTIONS3 +#define GAMEOPTION_USE_HERCULES_FONT GUIO_GAMEOPTIONS4 +#define GAMEOPTION_COMMAND_PROMPT_WINDOW GUIO_GAMEOPTIONS5 // TODO: properly implement GAMEOPTIONs -#define GAMEOPTIONS_DEFAULT GUIO2(GAMEOPTION_ORIGINAL_SAVELOAD,GAMEOPTION_DISABLE_MOUSE) -#define GAMEOPTIONS_AMIGA GUIO2(GAMEOPTION_ORIGINAL_SAVELOAD,GAMEOPTION_AMIGA_ALTERNATIVE_PALETTE) -#define GAMEOPTIONS_FANMADE_MOUSE GUIO1(GAMEOPTION_ORIGINAL_SAVELOAD) +#define GAMEOPTIONS_DEFAULT GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD,GAMEOPTION_DISABLE_MOUSE,GAMEOPTION_USE_HERCULES_FONT,GAMEOPTION_COMMAND_PROMPT_WINDOW) +#define GAMEOPTIONS_AMIGA GUIO4(GAMEOPTION_ORIGINAL_SAVELOAD,GAMEOPTION_AMIGA_ALTERNATIVE_PALETTE,GAMEOPTION_USE_HERCULES_FONT,GAMEOPTION_COMMAND_PROMPT_WINDOW) +#define GAMEOPTIONS_FANMADE_MOUSE GUIO3(GAMEOPTION_ORIGINAL_SAVELOAD,GAMEOPTION_USE_HERCULES_FONT,GAMEOPTION_COMMAND_PROMPT_WINDOW) #define GAME_LVFPN(id,extra,fname,md5,size,lang,ver,features,gid,platform,interp,guioptions) { \ { \ @@ -573,7 +575,14 @@ static const AGIGameDescription gameDescriptions[] = { GAME("sq2", "2.0D 1988-03-14 3.5\"", "85390bde8958c39830e1adbe9fff87f3", 0x2936, GID_SQ2), // Space Quest 2 (IIgs) 2.0A 7/25/88 (CE) - GAME_P("sq2", "2.0A 1988-07-25 (CE)", "5dfdac98dd3c01fcfb166529f917e911", 0x2936, GID_SQ2, Common::kPlatformApple2GS), + // We have to see this as AGI < 2.936, because otherwise a set.pri.base call would somewhat break + // priority in SQ2, when entering Vohaul's vault. + // The Apple IIgs AGI included with SQ2 is the same as the one included with KQ3. + // We currently consider KQ3 IIgs to be a 2.917-equivalent. + // The SQ2 IIgs AGI definitely has 177 kernel functions, but it seems that Sierra shuffled the last few around / added a few extras at the end. + // For KQ3 set.pri.base is called with parameters that seem to be sound resources, which means + // set.pri.base was possibly discard.sound. For KQ4 onwards it seems this was cleaned up. + GAME_P("sq2", "2.0A 1988-07-25 (CE)", "5dfdac98dd3c01fcfb166529f917e911", 0x2917, GID_SQ2, Common::kPlatformApple2GS), { // Space Quest 2 (Amiga) 2.0F @@ -849,6 +858,7 @@ static const AGIGameDescription gameDescriptions[] = { FANMADE("Tex McPhilip 2 - Road To Divinity (v1.5)", "7387e8df854440bc26620ca0ea43af9a"), FANMADE("Tex McPhilip 3 - A Destiny of Sin (Demo v0.25)", "992d12031a486ad84e592ff5d7c9d782"), FANMADE("The 13th Disciple (v1.00)", "887719ad59afce9a41ec057dbb73ad73"), + FANMADE("The 13th Disciple (v1.01)", "58e3ec1b9ac1a79901c472aaa59db832"), FANMADE("The Adventures of a Crazed Hermit", "6e3086cbb794d3299a9c5a9792295511"), FANMADE("The Gourd of the Beans", "246f4d94946afb547482d44a53616d06"), FANMADE("The Grateful Dead", "c2146631afacf8cb455ce24f3d2d46e7"), diff --git a/engines/agi/font.cpp b/engines/agi/font.cpp index 670c1bf575..f9605e4a3d 100644 --- a/engines/agi/font.cpp +++ b/engines/agi/font.cpp @@ -20,6 +20,7 @@ * */ +#include "common/config-manager.h" #include "agi/agi.h" #include "agi/font.h" #include "agi/text.h" @@ -31,6 +32,7 @@ GfxFont::GfxFont(AgiBase *vm) { _fontData = nullptr; _fontDataAllocated = nullptr; + _fontIsHires = false; } GfxFont::~GfxFont() { @@ -619,52 +621,71 @@ static const uint8 fontData_ExtendedRussian[] = { }; void GfxFont::init() { - switch (_vm->_renderMode) { - case Common::kRenderAmiga: - // Try user-file first, if that fails use our internal inaccurate topaz font - loadFontScummVMFile("agi-font-amiga.bin"); - if (!_fontData) { - loadFontAmigaPseudoTopaz(); + if (ConfMan.getBool("herculesfont")) { + // User wants, that we use Hercules hires font, try to load it + loadFontHercules(); + } else { + switch (_vm->_renderMode) { + case Common::kRenderHercA: + case Common::kRenderHercG: + // Render mode is Hercules, we try to load Hercules hires font + loadFontHercules(); + break; + default: + break; } - break; - case Common::kRenderApple2GS: - // Special font, stored in file AGIFONT - loadFontAppleIIgs(); - break; - case Common::kRenderAtariST: - // TODO: Atari ST uses another font - // Seems to be the standard Atari ST 8x8 system font - loadFontScummVMFile("agi-font-atarist.bin"); - if (!_fontData) { - loadFontAtariST("agi-font-atarist-system.fnt"); + } + + if (!_fontData) { + switch (_vm->_renderMode) { + case Common::kRenderAmiga: + // Try user-file first, if that fails use our internal inaccurate topaz font + loadFontScummVMFile("agi-font-amiga.bin"); if (!_fontData) { - // TODO: in case we find a recreation of the font, add it in here + loadFontAmigaPseudoTopaz(); } - } - break; - case Common::kRenderCGA: - case Common::kRenderEGA: - case Common::kRenderVGA: - switch (_vm->getGameID()) { - case GID_MICKEY: - // load mickey mouse font from interpreter file - loadFontMickey(); break; + case Common::kRenderApple2GS: + // Special font, stored in file AGIFONT + loadFontAppleIIgs(); + break; + case Common::kRenderAtariST: + // TODO: Atari ST uses another font + // Seems to be the standard Atari ST 8x8 system font + loadFontScummVMFile("agi-font-atarist.bin"); + if (!_fontData) { + loadFontAtariST("agi-font-atarist-system.fnt"); + if (!_fontData) { + // TODO: in case we find a recreation of the font, add it in here + } + } + break; + case Common::kRenderHercA: + case Common::kRenderHercG: + case Common::kRenderCGA: + case Common::kRenderEGA: + case Common::kRenderVGA: + switch (_vm->getGameID()) { + case GID_MICKEY: + // load mickey mouse font from interpreter file + loadFontMickey(); + break; + default: + loadFontScummVMFile("agi-font-dos.bin"); + break; + } + break; + default: - loadFontScummVMFile("agi-font-dos.bin"); break; } - break; - - default: - break; - } - if (!_fontData) { - // no font assigned? - // use regular PC-BIOS font (taken from Dos-Box with a few modifications) - _fontData = fontData_PCBIOS; - debug("AGI: Using PC-BIOS font"); + if (!_fontData) { + // no font assigned? + // use regular PC-BIOS font (taken from Dos-Box with a few modifications) + _fontData = fontData_PCBIOS; + debug("AGI: Using PC-BIOS font"); + } } if (_vm->getLanguage() == Common::RU_RUS) { @@ -678,6 +699,10 @@ const byte *GfxFont::getFontData() { return _fontData; } +bool GfxFont::isFontHires() { + return _fontIsHires; +} + void GfxFont::overwriteSaveRestoreDialogCharacter() { // overwrite character 0x1A with the standard Sierra arrow to the right character // required for the original save/restore dialogs @@ -686,6 +711,11 @@ void GfxFont::overwriteSaveRestoreDialogCharacter() { // Overwrite extended character set (0x80-0xFF) with Russian characters void GfxFont::overwriteExtendedWithRussianSet() { + if (_fontIsHires) { + // TODO: Implement overwriting hires font characters too + return; + } + if (!_fontDataAllocated) { // nothing allocated, we need to allocate space ourselves to be able to modify an internal font _fontDataAllocated = (uint8 *)calloc(256, 8); @@ -829,6 +859,11 @@ void GfxFont::loadFontAmigaPseudoTopaz() { assert((topazBitOffset & 7) == 0); topazByteOffset = topazBitOffset >> 3; + + // Security check, although we are working on static const data from within ScummVM + uint maxOffset = (topazByteOffset + ((topazHeight - 1) * topazModulo)); + assert(maxOffset < sizeof(fontData_AmigaPseudoTopaz)); + for (uint16 curHeight = 0; curHeight < topazHeight; curHeight++) { *fontData = topazData[topazByteOffset]; fontData++; @@ -1160,4 +1195,102 @@ void GfxFont::loadFontAtariST(Common::String fontFilename) { debug("AGI: Using Atari ST 8x8 system font"); } +// Loads a Sierra Hercules font file +void GfxFont::loadFontHercules() { + Common::File fontFile; + int32 fontFileSize = 0; + byte *fontData = nullptr; + byte *rawData = nullptr; + + uint16 rawDataPos = 0; + uint16 curCharNr = 0; + uint16 curCharLine = 0; + + if (fontFile.open("hgc_font")) { + // hgc_font file found, this is interleaved font data 16x12, should be 3072 bytes + // 24 bytes per character, 128 characters + fontFileSize = fontFile.size(); + if (fontFileSize == (128 * 24)) { + // size seems to be fine + fontData = (uint8 *)calloc(256, 32); + _fontDataAllocated = fontData; + + rawData = (byte *)calloc(128, 24); + fontFile.read(rawData, 128 * 24); + + // convert interleaved 16x12 -> non-interleaved 16x16 + for (curCharNr = 0; curCharNr < 128; curCharNr++) { + fontData += 4; // skip the first 2 lines + for (curCharLine = 0; curCharLine < 6; curCharLine++) { + fontData[0] = rawData[rawDataPos + 2 + 0]; + fontData[1] = rawData[rawDataPos + 2 + 1]; + fontData[2] = rawData[rawDataPos + 0 + 0]; + fontData[3] = rawData[rawDataPos + 0 + 1]; + rawDataPos += 4; + fontData += 4; + } + fontData += 4; // skip the last 2 lines + } + + free(rawData); + } else { + warning("Fontfile 'hgc_font': unexpected file size"); + } + fontFile.close(); + + } + + // It seems hgc_graf.ovl holds a low-res font. It makes no real sense to use it. + // This was only done to AGI3 games and those rendered differently (2 pixel lines -> 3 pixel lines instead of 4) + // User could copy hgc_font from another AGI game over to get the hires font working. +#if 0 + if (!_fontDataAllocated) { + if (fontFile.open("hgc_graf.ovl")) { + // hgc_graf.ovl file found, this is font data + code. non-interleaved font data, should be 3075 bytes + // 16 bytes per character, 128 characters, 2048 bytes of font data, starting offset 21 + fontFileSize = fontFile.size(); + if (fontFileSize == 3075) { + // size seems to be fine + fontData = (uint8 *)calloc(256, 32); + _fontDataAllocated = fontData; + + fontFile.seek(21); + rawData = (byte *)calloc(128, 16); + fontFile.read(rawData, 128 * 16); + + // repeat every line 2 times to get 16x16 pixels + for (curCharNr = 0; curCharNr < 128; curCharNr++) { + for (curCharLine = 0; curCharLine < 8; curCharLine++) { + fontData[0] = rawData[rawDataPos + 0]; + fontData[1] = rawData[rawDataPos + 1]; + fontData[2] = rawData[rawDataPos + 0]; + fontData[3] = rawData[rawDataPos + 1]; + rawDataPos += 2; + fontData += 4; + } + } + + free(rawData); + + } else { + warning("Fontfile 'hgc_graf.ovl': unexpected file size"); + } + fontFile.close(); + } + } +#endif + + if (_fontDataAllocated) { + // font loaded + _fontData = _fontDataAllocated; + _fontIsHires = true; + + debug("AGI: Using Hercules hires font"); + + } else { + // Continue, if no file was not found + warning("Could not open/use file 'hgc_font' for Hercules hires font"); + } +} + } // End of namespace Agi diff --git a/engines/agi/font.h b/engines/agi/font.h index 0bb1bbb18d..485b139858 100644 --- a/engines/agi/font.h +++ b/engines/agi/font.h @@ -36,6 +36,7 @@ private: public: void init(); const byte *getFontData(); + bool isFontHires(); private: void overwriteSaveRestoreDialogCharacter(); @@ -46,9 +47,11 @@ private: void loadFontAmigaPseudoTopaz(); void loadFontAppleIIgs(); void loadFontAtariST(Common::String fontFilename); + void loadFontHercules(); const uint8 *_fontData; // pointer to the currently used font uint8 *_fontDataAllocated; + bool _fontIsHires; }; } // End of namespace Agi diff --git a/engines/agi/global.cpp b/engines/agi/global.cpp index 0f10976988..23256f27fb 100644 --- a/engines/agi/global.cpp +++ b/engines/agi/global.cpp @@ -23,6 +23,7 @@ #include "common/config-manager.h" #include "agi/agi.h" +#include "agi/graphics.h" namespace Agi { @@ -53,14 +54,21 @@ void AgiBase::flipFlag(int16 flagNr) { void AgiEngine::setVar(int16 varNr, byte newValue) { _game.vars[varNr] = newValue; - if (varNr == VM_VAR_VOLUME) { + switch (varNr) { + case VM_VAR_SECONDS: + setVarSecondsTrigger(newValue); + break; + case VM_VAR_VOLUME: setVolumeViaScripts(newValue); + break; } } byte AgiEngine::getVar(int16 varNr) { switch (varNr) { case VM_VAR_SECONDS: + getVarSecondsHeuristicTrigger(); + // is supposed to fall through case VM_VAR_MINUTES: case VM_VAR_HOURS: case VM_VAR_DAYS: @@ -79,7 +87,32 @@ byte AgiEngine::getVar(int16 varNr) { // 15 - mute void AgiEngine::setVolumeViaScripts(byte newVolume) { newVolume = CLIP<byte>(newVolume, 0, 15); - newVolume = 15 - newVolume; // turn volume around + + if (_veryFirstInitialCycle) { + // WORKAROUND: + // The very first cycle is currently running and volume got changed + // This is surely the initial value. For plenty of fan games, a default of 15 is set + // Which actually means "mute" in AGI, but AGI on PC used PC speaker, which did not use + // volume setting. We do. So we detect such a situation and set a flag, so that the + // volume will get interpreted "correctly" for those fan games. + // Note: not all fan games are broken in that regard! + // See bug #7035 + if (getFeatures() & GF_FANMADE) { + // We only check for fan games, Sierra always did it properly of course + if (newVolume == 15) { + // Volume gets set to mute at the start? + // Probably broken fan game detected, set flag + debug("Broken volume in fan game detected, enabling workaround"); + _setVolumeBrokenFangame = true; + } + } + } + + if (!_setVolumeBrokenFangame) { + // In AGI 15 is mute, 0 is loudest + // Some fan games set this incorrectly as 15 for loudest, 0 for mute + newVolume = 15 - newVolume; // turn volume around + } int scummVMVolume = newVolume * Audio::Mixer::kMaxMixerVolume / 15; bool scummVMMute = false; @@ -135,10 +168,42 @@ void AgiEngine::setVolumeViaSystemSetting() { _game.vars[VM_VAR_VOLUME] = internalVolume; } +void AgiEngine::resetGetVarSecondsHeuristic() { + _getVarSecondsHeuristicLastInstructionCounter = 0; + _getVarSecondsHeuristicCounter = 0; +} + +// Called, when the scripts read VM_VAR_SECONDS +void AgiEngine::getVarSecondsHeuristicTrigger() { + uint32 counterDifference = _instructionCounter - _getVarSecondsHeuristicLastInstructionCounter; + + if (counterDifference <= 3) { + // Seconds were read within 3 instructions + _getVarSecondsHeuristicCounter++; + if (_getVarSecondsHeuristicCounter > 20) { + // More than 20 times in a row? This really seems to be an inner loop waiting for seconds to change + // This happens in at least: + // Police Quest 1 - Poker game (room 75, responsible script 81) + + // Wait a few milliseconds, get events and update screen + // We MUST NOT process AGI events in here + wait(10); + processScummVMEvents(); + _gfx->updateScreen(); + + _getVarSecondsHeuristicCounter = 0; + } + } else { + _getVarSecondsHeuristicCounter = 0; + } + _getVarSecondsHeuristicLastInstructionCounter = _instructionCounter; +} + // In-Game timer, used for timer VM Variables void AgiEngine::inGameTimerReset(uint32 newPlayTime) { _lastUsedPlayTimeInCycles = newPlayTime / 50; _lastUsedPlayTimeInSeconds = newPlayTime / 1000; + _playTimeInSecondsAdjust = 0; // no adjust for now setTotalPlayTime(newPlayTime); inGameTimerResetPassedCycles(); } @@ -158,6 +223,24 @@ uint32 AgiEngine::inGameTimerGetPassedCycles() { return _passedPlayTimeCycles; } +// Seconds got set by the game +// This happens in Mixed Up Mother Goose. The game syncs the songs to VM_VAR_SECONDS, but instead +// of only reading them, it sets it to 0 and then checks if it reached a certain second. +// The original interpreter didn't reset the internal cycles counter. Which means the timing was never accurate, +// because the cycles counter may just overflow right after setting the seconds, which means a second +// increase almost immediately happened. We even fix this issue by adjusting for it. +void AgiEngine::setVarSecondsTrigger(byte newSeconds) { + // Adjust in game timer, so that VM timer variables are accurate + inGameTimerUpdate(); + + // Adjust VM seconds again + _game.vars[VM_VAR_SECONDS] = newSeconds; + + // Calculate milliseconds adjust (see comment above) + uint32 curPlayTimeMilliseconds = inGameTimerGet(); + _playTimeInSecondsAdjust = curPlayTimeMilliseconds % 1000; +} + // This is called, when one of the timer variables is read // We calculate the latest variables, according to current official playtime // This is also called in the main loop, because the game needs to be sync'd to 20 cycles per second @@ -178,6 +261,14 @@ void AgiEngine::inGameTimerUpdate() { _lastUsedPlayTimeInCycles = curPlayTimeCycles; // Now calculate current play time in seconds + if (_playTimeInSecondsAdjust) { + // Apply adjust from setVarSecondsTrigger() + if (curPlayTimeMilliseconds >= _playTimeInSecondsAdjust) { + curPlayTimeMilliseconds -= _playTimeInSecondsAdjust; + } else { + curPlayTimeMilliseconds = 0; + } + } uint32 curPlayTimeSeconds = curPlayTimeMilliseconds / 1000; if (curPlayTimeSeconds == _lastUsedPlayTimeInSeconds) { @@ -185,26 +276,50 @@ void AgiEngine::inGameTimerUpdate() { return; } - uint32 secondsLeft = 0; - byte curDays = 0; - byte curHours = 0; - byte curMinutes = 0; - byte curSeconds = 0; - - curDays = curPlayTimeSeconds / 86400; - secondsLeft = curPlayTimeSeconds % 86400; - - curHours = secondsLeft / 3600; - secondsLeft = secondsLeft % 3600; - - curMinutes = secondsLeft / 60; - curSeconds = secondsLeft % 60; - - // directly set them, otherwise we would go into an endless loop - _game.vars[VM_VAR_SECONDS] = curSeconds; - _game.vars[VM_VAR_MINUTES] = curMinutes; - _game.vars[VM_VAR_HOURS] = curHours; - _game.vars[VM_VAR_DAYS] = curDays; + int32 playTimeSecondsDelta = curPlayTimeSeconds - _lastUsedPlayTimeInSeconds; + + if (playTimeSecondsDelta > 0) { + // Read and write to VM vars directly to avoid endless loop + uint32 secondsLeft = playTimeSecondsDelta; + byte curSeconds = _game.vars[VM_VAR_SECONDS]; + byte curMinutes = _game.vars[VM_VAR_MINUTES]; + byte curHours = _game.vars[VM_VAR_HOURS]; + byte curDays = _game.vars[VM_VAR_DAYS]; + + // Add delta to VM variables + if (secondsLeft >= 86400) { + curDays += secondsLeft / 86400; + secondsLeft = secondsLeft % 86400; + } + if (secondsLeft >= 3600) { + curHours += secondsLeft / 3600; + secondsLeft = secondsLeft % 3600; + } + if (secondsLeft >= 60) { + curMinutes += secondsLeft / 60; + secondsLeft = secondsLeft % 60; + } + curSeconds += secondsLeft; + + while (curSeconds > 59) { + curSeconds -= 60; + curMinutes++; + } + while (curMinutes > 59) { + curMinutes -= 60; + curHours++; + } + while (curHours > 23) { + curHours -= 24; + curDays++; + } + + // directly set them + _game.vars[VM_VAR_SECONDS] = curSeconds; + _game.vars[VM_VAR_MINUTES] = curMinutes; + _game.vars[VM_VAR_HOURS] = curHours; + _game.vars[VM_VAR_DAYS] = curDays; + } _lastUsedPlayTimeInSeconds = curPlayTimeSeconds; } diff --git a/engines/agi/graphics.cpp b/engines/agi/graphics.cpp index ba5895ccd1..6d3563a451 100644 --- a/engines/agi/graphics.cpp +++ b/engines/agi/graphics.cpp @@ -39,7 +39,7 @@ namespace Agi { #include "agi/font.h" -GfxMgr::GfxMgr(AgiBase *vm) : _vm(vm) { +GfxMgr::GfxMgr(AgiBase *vm, GfxFont *font) : _vm(vm), _font(font) { _agipalFileNum = 0; memset(&_paletteGfxMode, 0, sizeof(_paletteGfxMode)); @@ -50,7 +50,17 @@ GfxMgr::GfxMgr(AgiBase *vm) : _vm(vm) { initPriorityTable(); - _renderStartOffsetY = 0; + _renderStartVisualOffsetY = 0; + _renderStartDisplayOffsetY = 0; + + _upscaledHires = DISPLAY_UPSCALED_DISABLED; + _displayScreenWidth = DISPLAY_DEFAULT_WIDTH; + _displayScreenHeight = DISPLAY_DEFAULT_HEIGHT; + _displayFontWidth = 8; + _displayFontHeight = 8; + + _displayWidthMulAdjust = 0; // visualPos * (2+0) = displayPos + _displayHeightMulAdjust = 0; // visualPos * (1+0) = displayPos } /** @@ -59,6 +69,8 @@ GfxMgr::GfxMgr(AgiBase *vm) : _vm(vm) { * @see deinit_video() */ int GfxMgr::initVideo() { + bool forceHires = false; + // Set up palettes initPalette(_paletteTextMode, PALETTE_EGA); @@ -72,6 +84,14 @@ int GfxMgr::initVideo() { case Common::kRenderVGA: initPalette(_paletteGfxMode, PALETTE_VGA, 256, 8); break; + case Common::kRenderHercG: + initPalette(_paletteGfxMode, PALETTE_HERCULES_GREEN, 2, 8); + forceHires = true; + break; + case Common::kRenderHercA: + initPalette(_paletteGfxMode, PALETTE_HERCULES_AMBER, 2, 8); + forceHires = true; + break; case Common::kRenderAmiga: if (!ConfMan.getBool("altamigapalette")) { // Set the correct Amiga palette depending on AGI interpreter version @@ -87,7 +107,16 @@ int GfxMgr::initVideo() { } break; case Common::kRenderApple2GS: - initPalette(_paletteGfxMode, PALETTE_APPLE_II_GS, 16, 4); + switch (_vm->getGameID()) { + case GID_SQ1: + // Special one, only used for Space Quest 1 on Apple IIgs. Is the same as Amiga v1 palette + initPalette(_paletteGfxMode, PALETTE_APPLE_II_GS_SQ1, 16, 4); + break; + default: + // Regular "standard" Apple IIgs palette, used by everything else + initPalette(_paletteGfxMode, PALETTE_APPLE_II_GS, 16, 4); + break; + } break; case Common::kRenderAtariST: initPalette(_paletteGfxMode, PALETTE_ATARI_ST, 16, 3); @@ -116,31 +145,47 @@ int GfxMgr::initVideo() { break; } + //bool forcedUpscale = true; + + if (_font->isFontHires() || forceHires) { + // Upscaling enable + _upscaledHires = DISPLAY_UPSCALED_640x400; + _displayScreenWidth = 640; + _displayScreenHeight = 400; + _displayFontWidth = 16; + _displayFontHeight = 16; + + _displayWidthMulAdjust = 2; + _displayHeightMulAdjust = 1; + } + // set up mouse cursors switch (_vm->_renderMode) { case Common::kRenderEGA: case Common::kRenderCGA: case Common::kRenderVGA: - initMouseCursor(&_mouseCursor, MOUSECURSOR_SCI, 11, 16, 1, 1); + case Common::kRenderHercG: + case Common::kRenderHercA: + initMouseCursor(&_mouseCursor, MOUSECURSOR_SCI, 11, 16, 0, 0); initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_SCI_BUSY, 15, 16, 7, 8); break; case Common::kRenderAmiga: - initMouseCursor(&_mouseCursor, MOUSECURSOR_AMIGA, 8, 11, 1, 1); + initMouseCursor(&_mouseCursor, MOUSECURSOR_AMIGA, 8, 11, 0, 0); initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_AMIGA_BUSY, 13, 16, 7, 8); break; case Common::kRenderApple2GS: // had no special busy mouse cursor - initMouseCursor(&_mouseCursor, MOUSECURSOR_APPLE_II_GS, 9, 11, 1, 1); + initMouseCursor(&_mouseCursor, MOUSECURSOR_APPLE_II_GS, 9, 11, 0, 0); initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_SCI_BUSY, 15, 16, 7, 8); break; case Common::kRenderAtariST: - initMouseCursor(&_mouseCursor, MOUSECURSOR_ATARI_ST, 11, 16, 1, 1); + initMouseCursor(&_mouseCursor, MOUSECURSOR_ATARI_ST, 11, 16, 0, 0); initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_SCI_BUSY, 15, 16, 7, 8); break; case Common::kRenderMacintosh: // It looks like Atari ST + Macintosh used the same standard mouse cursor // TODO: Verify by checking actual hardware - initMouseCursor(&_mouseCursor, MOUSECURSOR_ATARI_ST, 11, 16, 1, 1); + initMouseCursor(&_mouseCursor, MOUSECURSOR_ATARI_ST, 11, 16, 0, 0); initMouseCursor(&_mouseCursorBusy, MOUSECURSOR_MACINTOSH_BUSY, 10, 14, 7, 8); break; default: @@ -149,15 +194,15 @@ int GfxMgr::initVideo() { } _pixels = SCRIPT_WIDTH * SCRIPT_HEIGHT; - _visualScreen = (byte *)calloc(_pixels, 1); + _gameScreen = (byte *)calloc(_pixels, 1); _priorityScreen = (byte *)calloc(_pixels, 1); - _activeScreen = _visualScreen; + _activeScreen = _gameScreen; //_activeScreen = _priorityScreen; - _displayPixels = DISPLAY_WIDTH * DISPLAY_HEIGHT; + _displayPixels = _displayScreenWidth * _displayScreenHeight; _displayScreen = (byte *)calloc(_displayPixels, 1); - initGraphics(DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_WIDTH > 320); + initGraphics(_displayScreenWidth, _displayScreenHeight, _displayScreenWidth > 320); setPalette(true); // set gfx-mode palette @@ -174,27 +219,151 @@ int GfxMgr::initVideo() { * @see init_video() */ int GfxMgr::deinitVideo() { + // Free mouse cursors in case they were allocated + if (_mouseCursor.bitmapDataAllocated) + free(_mouseCursor.bitmapDataAllocated); + if (_mouseCursorBusy.bitmapDataAllocated) + free(_mouseCursorBusy.bitmapDataAllocated); + free(_displayScreen); - free(_visualScreen); + free(_gameScreen); free(_priorityScreen); return errOK; } void GfxMgr::setRenderStartOffset(uint16 offsetY) { - if (offsetY >= (DISPLAY_HEIGHT - SCRIPT_HEIGHT)) + if (offsetY >= (VISUAL_HEIGHT - SCRIPT_HEIGHT)) error("invalid render start offset"); - _renderStartOffsetY = offsetY; + _renderStartVisualOffsetY = offsetY; + _renderStartDisplayOffsetY = offsetY * (1 + _displayHeightMulAdjust); +} +uint16 GfxMgr::getRenderStartDisplayOffsetY() { + return _renderStartDisplayOffsetY; +} + +// Translates a game screen coordinate to a display screen coordinate +// Game screen to 320x200 -> x * 2, y + renderStart +// Game screen to 640x400 -> x * 4, (y * 2) + renderStart +void GfxMgr::translateGamePosToDisplayScreen(int16 &x, int16 &y) { + x = x * (2 + _displayWidthMulAdjust); + y = y * (1 + _displayHeightMulAdjust) + _renderStartDisplayOffsetY; +} + +// Translates a visual coordinate to a display screen coordinate +// Visual to 320x200 -> x * 2, y +// Visual to 640x400 -> x * 4, y * 2 +void GfxMgr::translateVisualPosToDisplayScreen(int16 &x, int16 &y) { + x = x * (2 + _displayWidthMulAdjust); + y = y * (1 + _displayHeightMulAdjust); +} + +// Translates a display screen coordinate to a game screen coordinate +// Display screen to 320x200 -> x / 2, y - renderStart +// Display screen to 640x400 -> x / 4, (y / 2) - renderStart +void GfxMgr::translateDisplayPosToGameScreen(int16 &x, int16 &y) { + y -= _renderStartDisplayOffsetY; // remove status bar line + x = x / (2 + _displayWidthMulAdjust); + y = y / (1 + _displayHeightMulAdjust); + if (y < 0) + y = 0; + if (y >= SCRIPT_HEIGHT) + y = SCRIPT_HEIGHT + 1; // 1 beyond +} + +// Translates dimension from visual screen to display screen +void GfxMgr::translateVisualDimensionToDisplayScreen(int16 &width, int16 &height) { + width = width * (2 + _displayWidthMulAdjust); + height = height * (1 + _displayHeightMulAdjust); +} + +// Translates dimension from display screen to visual screen +void GfxMgr::translateDisplayDimensionToVisualScreen(int16 &width, int16 &height) { + width = width / (2 + _displayWidthMulAdjust); + height = height / (1 + _displayHeightMulAdjust); +} + +// Translates a rect from game screen to display screen +void GfxMgr::translateGameRectToDisplayScreen(int16 &x, int16 &y, int16 &width, int16 &height) { + translateGamePosToDisplayScreen(x, y); + translateVisualDimensionToDisplayScreen(width, height); } -uint16 GfxMgr::getRenderStartOffsetY() { - return _renderStartOffsetY; + +// Translates a rect from visual screen to display screen +void GfxMgr::translateVisualRectToDisplayScreen(int16 &x, int16 &y, int16 &width, int16 &height) { + translateVisualPosToDisplayScreen(x, y); + translateVisualDimensionToDisplayScreen(width, height); +} + +uint32 GfxMgr::getDisplayOffsetToGameScreenPos(int16 x, int16 y) { + translateGamePosToDisplayScreen(x, y); + return (y * _displayScreenWidth) + x; +} + +uint32 GfxMgr::getDisplayOffsetToVisualScreenPos(int16 x, int16 y) { + translateVisualPosToDisplayScreen(x, y); + return (y * _displayScreenWidth) + x; +} + +// Attention: uses display screen coordinates! +void GfxMgr::copyDisplayRectToScreen(int16 x, int16 y, int16 width, int16 height) { + g_system->copyRectToScreen(_displayScreen + y * _displayScreenWidth + x, _displayScreenWidth, x, y, width, height); +} +void GfxMgr::copyDisplayRectToScreen(int16 x, int16 adjX, int16 y, int16 adjY, int16 width, int16 adjWidth, int16 height, int16 adjHeight) { + switch (_upscaledHires) { + case DISPLAY_UPSCALED_DISABLED: + break; + case DISPLAY_UPSCALED_640x400: + adjX *= 2; adjY *= 2; + adjWidth *= 2; adjHeight *= 2; + break; + default: + assert(0); + break; + } + x += adjX; y += adjY; + width += adjWidth; height += adjHeight; + g_system->copyRectToScreen(_displayScreen + y * _displayScreenWidth + x, _displayScreenWidth, x, y, width, height); +} +void GfxMgr::copyDisplayRectToScreenUsingGamePos(int16 x, int16 y, int16 width, int16 height) { + translateGameRectToDisplayScreen(x, y, width, height); + g_system->copyRectToScreen(_displayScreen + (y * _displayScreenWidth) + x, _displayScreenWidth, x, y, width, height); +} +void GfxMgr::copyDisplayRectToScreenUsingVisualPos(int16 x, int16 y, int16 width, int16 height) { + translateVisualRectToDisplayScreen(x, y, width, height); + g_system->copyRectToScreen(_displayScreen + (y * _displayScreenWidth) + x, _displayScreenWidth, x, y, width, height); +} +void GfxMgr::copyDisplayToScreen() { + g_system->copyRectToScreen(_displayScreen, _displayScreenWidth, 0, 0, _displayScreenWidth, _displayScreenHeight); +} + +void GfxMgr::translateFontPosToDisplayScreen(int16 &x, int16 &y) { + x *= _displayFontWidth; + y *= _displayFontHeight; +} +void GfxMgr::translateDisplayPosToFontScreen(int16 &x, int16 &y) { + x /= _displayFontWidth; + y /= _displayFontHeight; +} +void GfxMgr::translateFontDimensionToDisplayScreen(int16 &width, int16 &height) { + width *= _displayFontWidth; + height *= _displayFontHeight; +} +void GfxMgr::translateFontRectToDisplayScreen(int16 &x, int16 &y, int16 &width, int16 &height) { + translateFontPosToDisplayScreen(x, y); + translateFontDimensionToDisplayScreen(width, height); +} +Common::Rect GfxMgr::getFontRectForDisplayScreen(int16 column, int16 row, int16 width, int16 height) { + Common::Rect displayRect(width * _displayFontWidth, height * _displayFontHeight); + displayRect.moveTo(column * _displayFontWidth, row * _displayFontHeight); + return displayRect; } void GfxMgr::debugShowMap(int mapNr) { switch (mapNr) { case 0: - _activeScreen = _visualScreen; + _activeScreen = _gameScreen; break; case 1: _activeScreen = _priorityScreen; @@ -203,11 +372,11 @@ void GfxMgr::debugShowMap(int mapNr) { break; } - render_Block(0, 167, SCRIPT_WIDTH, SCRIPT_HEIGHT); + render_Block(0, 0, SCRIPT_WIDTH, SCRIPT_HEIGHT); } void GfxMgr::clear(byte color, byte priority) { - memset(_visualScreen, color, _pixels); + memset(_gameScreen, color, _pixels); memset(_priorityScreen, priority, _pixels); } @@ -215,7 +384,7 @@ void GfxMgr::clearDisplay(byte color, bool copyToScreen) { memset(_displayScreen, color, _displayPixels); if (copyToScreen) { - g_system->copyRectToScreen(_displayScreen, DISPLAY_WIDTH, 0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT); + copyDisplayToScreen(); } } @@ -223,7 +392,7 @@ void GfxMgr::putPixel(int16 x, int16 y, byte drawMask, byte color, byte priority int offset = y * SCRIPT_WIDTH + x; if (drawMask & GFX_SCREEN_MASK_VISUAL) { - _visualScreen[offset] = color; + _gameScreen[offset] = color; } if (drawMask & GFX_SCREEN_MASK_PRIORITY) { _priorityScreen[offset] = priority; @@ -231,15 +400,72 @@ void GfxMgr::putPixel(int16 x, int16 y, byte drawMask, byte color, byte priority } void GfxMgr::putPixelOnDisplay(int16 x, int16 y, byte color) { - int offset = y * DISPLAY_WIDTH + x; + uint32 offset = 0; + + switch (_upscaledHires) { + case DISPLAY_UPSCALED_DISABLED: + offset = y * _displayScreenWidth + x; + + _displayScreen[offset] = color; + break; + case DISPLAY_UPSCALED_640x400: + offset = (y * _displayScreenWidth) + x; - _displayScreen[offset] = color; + _displayScreen[offset + 0] = color; + _displayScreen[offset + 1] = color; + _displayScreen[offset + _displayScreenWidth + 0] = color; + _displayScreen[offset + _displayScreenWidth + 1] = color; + break; + default: + break; + } +} + +void GfxMgr::putPixelOnDisplay(int16 x, int16 adjX, int16 y, int16 adjY, byte color) { + switch (_upscaledHires) { + case DISPLAY_UPSCALED_DISABLED: + break; + case DISPLAY_UPSCALED_640x400: + adjX *= 2; adjY *= 2; + break; + default: + assert(0); + break; + } + x += adjX; + y += adjY; + putPixelOnDisplay(x, y, color); +} + +void GfxMgr::putFontPixelOnDisplay(int16 baseX, int16 baseY, int16 addX, int16 addY, byte color, bool isHires) { + uint32 offset = 0; + + switch (_upscaledHires) { + case DISPLAY_UPSCALED_DISABLED: + offset = ((baseY + addY) * _displayScreenWidth) + (baseX + addX); + _displayScreen[offset] = color; + break; + case DISPLAY_UPSCALED_640x400: + if (isHires) { + offset = ((baseY + addY) * _displayScreenWidth) + (baseX + addX); + _displayScreen[offset] = color; + } else { + offset = ((baseY + addY * 2) * _displayScreenWidth) + (baseX + addX * 2); + _displayScreen[offset + 0] = color; + _displayScreen[offset + 1] = color; + _displayScreen[offset + _displayScreenWidth + 0] = color; + _displayScreen[offset + _displayScreenWidth + 1] = color; + } + break; + default: + break; + } } byte GfxMgr::getColor(int16 x, int16 y) { int offset = y * SCRIPT_WIDTH + x; - return _visualScreen[offset]; + return _gameScreen[offset]; } byte GfxMgr::getPriority(int16 x, int16 y) { @@ -279,12 +505,17 @@ byte GfxMgr::getCGAMixtureColor(byte color) { return CGA_MixtureColorTable[color & 0x0F]; } -// Attention: y-coordinate points to the LOWER left! +// Attention: in our implementation, y-coordinate is upper left. +// Sierra passed the lower left instead. We changed it to make upscaling easier. void GfxMgr::render_Block(int16 x, int16 y, int16 width, int16 height, bool copyToScreen) { if (!render_Clip(x, y, width, height)) return; switch (_vm->_renderMode) { + case Common::kRenderHercG: + case Common::kRenderHercA: + render_BlockHercules(x, y, width, height, copyToScreen); + break; case Common::kRenderCGA: render_BlockCGA(x, y, width, height, copyToScreen); break; @@ -295,17 +526,26 @@ void GfxMgr::render_Block(int16 x, int16 y, int16 width, int16 height, bool copy } if (copyToScreen) { - int16 upperY = y - height + 1 + _renderStartOffsetY; - g_system->copyRectToScreen(_displayScreen + upperY * DISPLAY_WIDTH + x * 2, DISPLAY_WIDTH, x * 2, upperY, width * 2, height); + copyDisplayRectToScreenUsingGamePos(x, y, width, height); } } bool GfxMgr::render_Clip(int16 &x, int16 &y, int16 &width, int16 &height, int16 clipAgainstWidth, int16 clipAgainstHeight) { if ((x >= clipAgainstWidth) || ((x + width - 1) < 0) || - (y < 0) || ((y - (height - 1)) >= clipAgainstHeight)) { + (y < 0) || ((y + (height - 1)) >= clipAgainstHeight)) { return false; } + if (y < 0) { + height += y; + y = 0; + } + + if ((y + height - 1) >= clipAgainstHeight) { + height = clipAgainstHeight - y; + } + +#if 0 if ((y - height + 1) < 0) height = y + 1; @@ -313,6 +553,7 @@ bool GfxMgr::render_Clip(int16 &x, int16 &y, int16 &width, int16 &height, int16 height -= y - (clipAgainstHeight - 1); y = clipAgainstHeight - 1; } +#endif if (x < 0) { width += x; @@ -326,52 +567,212 @@ bool GfxMgr::render_Clip(int16 &x, int16 &y, int16 &width, int16 &height, int16 } void GfxMgr::render_BlockEGA(int16 x, int16 y, int16 width, int16 height, bool copyToScreen) { - int offsetVisual = SCRIPT_WIDTH * y + x; - int offsetDisplay = (DISPLAY_WIDTH * (y + _renderStartOffsetY)) + x * 2; + uint32 offsetVisual = SCRIPT_WIDTH * y + x; + uint32 offsetDisplay = getDisplayOffsetToGameScreenPos(x, y); int16 remainingWidth = width; int16 remainingHeight = height; byte curColor = 0; + int16 displayWidth = width * (2 + _displayWidthMulAdjust); while (remainingHeight) { remainingWidth = width; - while (remainingWidth) { - curColor = _activeScreen[offsetVisual++]; - _displayScreen[offsetDisplay++] = curColor; - _displayScreen[offsetDisplay++] = curColor; - remainingWidth--; + + switch (_upscaledHires) { + case DISPLAY_UPSCALED_DISABLED: + while (remainingWidth) { + curColor = _activeScreen[offsetVisual++]; + _displayScreen[offsetDisplay++] = curColor; + _displayScreen[offsetDisplay++] = curColor; + remainingWidth--; + } + break; + case DISPLAY_UPSCALED_640x400: + while (remainingWidth) { + curColor = _activeScreen[offsetVisual++]; + memset(&_displayScreen[offsetDisplay], curColor, 4); + memset(&_displayScreen[offsetDisplay + _displayScreenWidth], curColor, 4); + offsetDisplay += 4; + remainingWidth--; + } + break; + default: + assert(0); + break; + } + + offsetVisual += SCRIPT_WIDTH - width; + offsetDisplay += _displayScreenWidth - displayWidth; + + switch (_upscaledHires) { + case DISPLAY_UPSCALED_640x400: + offsetDisplay += _displayScreenWidth;; + break; + default: + break; } - offsetVisual -= SCRIPT_WIDTH + width; - offsetDisplay -= DISPLAY_WIDTH + width * 2; remainingHeight--; } } void GfxMgr::render_BlockCGA(int16 x, int16 y, int16 width, int16 height, bool copyToScreen) { - int offsetVisual = SCRIPT_WIDTH * y + x; - int offsetDisplay = (DISPLAY_WIDTH * (y + _renderStartOffsetY)) + x * 2; + uint32 offsetVisual = SCRIPT_WIDTH * y + x; + uint32 offsetDisplay = getDisplayOffsetToGameScreenPos(x, y); + int16 remainingWidth = width; + int16 remainingHeight = height; + byte curColor = 0; + int16 displayWidth = width * (2 + _displayWidthMulAdjust); + + while (remainingHeight) { + remainingWidth = width; + + switch (_upscaledHires) { + case DISPLAY_UPSCALED_DISABLED: + while (remainingWidth) { + curColor = _activeScreen[offsetVisual++]; + _displayScreen[offsetDisplay++] = curColor & 0x03; // we process CGA mixture + _displayScreen[offsetDisplay++] = curColor >> 2; + remainingWidth--; + } + break; + case DISPLAY_UPSCALED_640x400: + while (remainingWidth) { + curColor = _activeScreen[offsetVisual++]; + _displayScreen[offsetDisplay + 0] = curColor & 0x03; // we process CGA mixture + _displayScreen[offsetDisplay + 1] = curColor >> 2; + _displayScreen[offsetDisplay + 2] = curColor & 0x03; + _displayScreen[offsetDisplay + 3] = curColor >> 2; + _displayScreen[offsetDisplay + _displayScreenWidth + 0] = curColor & 0x03; + _displayScreen[offsetDisplay + _displayScreenWidth + 1] = curColor >> 2; + _displayScreen[offsetDisplay + _displayScreenWidth + 2] = curColor & 0x03; + _displayScreen[offsetDisplay + _displayScreenWidth + 3] = curColor >> 2; + offsetDisplay += 4; + remainingWidth--; + } + break; + default: + assert(0); + break; + } + + offsetVisual += SCRIPT_WIDTH - width; + offsetDisplay += _displayScreenWidth - displayWidth; + + switch (_upscaledHires) { + case DISPLAY_UPSCALED_640x400: + offsetDisplay += _displayScreenWidth;; + break; + default: + break; + } + + remainingHeight--; + } +} + +static const uint8 herculesColorMapping[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x88, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, + 0x80, 0x10, 0x02, 0x20, 0x01, 0x08, 0x40, 0x04, + 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00, + 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, + 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, + 0x11, 0x22, 0x44, 0x88, 0x11, 0x22, 0x44, 0x88, + 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, + 0x22, 0x00, 0x88, 0x00, 0x22, 0x00, 0x88, 0x00, + 0xD7, 0xFF, 0x7D, 0xFF, 0xD7, 0xFF, 0x7D, 0xFF, + 0xDD, 0x55, 0x77, 0xAA, 0xDD, 0x55, 0x77, 0xAA, + 0x7F, 0xEF, 0xFD, 0xDF, 0xFE, 0xF7, 0xBF, 0xFB, + 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF, + 0x77, 0xBB, 0xDD, 0xEE, 0x77, 0xBB, 0xDD, 0xEE, + 0x77, 0xFF, 0xFF, 0xFF, 0xDD, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; + +// Sierra actually seems to have rendered the whole screen all the time +void GfxMgr::render_BlockHercules(int16 x, int16 y, int16 width, int16 height, bool copyToScreen) { + uint32 offsetVisual = SCRIPT_WIDTH * y + x; + uint32 offsetDisplay = getDisplayOffsetToGameScreenPos(x, y); int16 remainingWidth = width; int16 remainingHeight = height; byte curColor = 0; + int16 displayWidth = width * (2 + _displayWidthMulAdjust); + + assert(_upscaledHires == DISPLAY_UPSCALED_640x400); + + uint16 lookupOffset1 = (y * 2 & 0x07); + uint16 lookupOffset2 = 0; + bool getUpperNibble = false; + byte herculesColors1 = 0; + byte herculesColors2 = 0; while (remainingHeight) { remainingWidth = width; + + lookupOffset1 = (lookupOffset1 + 0) & 0x07; + lookupOffset2 = (lookupOffset1 + 1) & 0x07; + + getUpperNibble = (x & 1) ? false : true; while (remainingWidth) { - curColor = _activeScreen[offsetVisual++]; - _displayScreen[offsetDisplay++] = curColor & 0x03; // we process CGA mixture - _displayScreen[offsetDisplay++] = curColor >> 2; + curColor = _activeScreen[offsetVisual++] & 0x0F; + + if (getUpperNibble) { + herculesColors1 = herculesColorMapping[curColor * 8 + lookupOffset1] & 0x0F; + herculesColors2 = herculesColorMapping[curColor * 8 + lookupOffset2] & 0x0F; + } else { + herculesColors1 = herculesColorMapping[curColor * 8 + lookupOffset1] >> 4; + herculesColors2 = herculesColorMapping[curColor * 8 + lookupOffset2] >> 4; + } + getUpperNibble ^= true; + + _displayScreen[offsetDisplay + 0] = (herculesColors1 & 0x08) ? 1 : 0; + _displayScreen[offsetDisplay + 1] = (herculesColors1 & 0x04) ? 1 : 0; + _displayScreen[offsetDisplay + 2] = (herculesColors1 & 0x02) ? 1 : 0; + _displayScreen[offsetDisplay + 3] = (herculesColors1 & 0x01) ? 1 : 0; + + _displayScreen[offsetDisplay + _displayScreenWidth + 0] = (herculesColors2 & 0x08) ? 1 : 0; + _displayScreen[offsetDisplay + _displayScreenWidth + 1] = (herculesColors2 & 0x04) ? 1 : 0; + _displayScreen[offsetDisplay + _displayScreenWidth + 2] = (herculesColors2 & 0x02) ? 1 : 0; + _displayScreen[offsetDisplay + _displayScreenWidth + 3] = (herculesColors2 & 0x01) ? 1 : 0; + + offsetDisplay += 4; remainingWidth--; } - offsetVisual -= SCRIPT_WIDTH + width; - offsetDisplay -= DISPLAY_WIDTH + width * 2; + + lookupOffset1 += 2; + + offsetVisual += SCRIPT_WIDTH - width; + offsetDisplay += _displayScreenWidth - displayWidth; + offsetDisplay += _displayScreenWidth;; remainingHeight--; } } +// Table used for at least Manhunter 2, it renders 2 lines -> 3 lines instead of 4 +// Manhunter 1 is shipped with a broken Hercules font +// King's Quest 4 aborts right at the start, when Hercules rendering is active +#if 0 +static const uint8 herculesCoordinateOffset[] = { + 0x00, 0x01, 0x03, 0x04, 0x06, 0x07, 0x01, 0x02, + 0x04, 0x05, 0x07, 0x00, 0x02, 0x03, 0x05, 0x06 +}; + +static const uint8 herculesColorMapping[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x02, 0x00, 0x40, 0x00, 0x08, 0x00, + 0x80, 0x10, 0x02, 0x20, 0x01, 0x08, 0x40, 0x04, 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00, + 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, 0x88, 0x00, + 0x11, 0x22, 0x44, 0x88, 0x11, 0x22, 0x44, 0x88, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, + 0x22, 0x00, 0x88, 0x00, 0x22, 0x00, 0x88, 0x00, 0xD7, 0xFF, 0x7D, 0xFF, 0xD7, 0xFF, 0x7D, 0xFF, + 0xDD, 0x55, 0x77, 0xAA, 0xDD, 0x55, 0x77, 0xAA, 0x7F, 0xEF, 0xFD, 0xDF, 0xFE, 0xF7, 0xBF, 0xFB, + 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF, 0x77, 0xBB, 0xDD, 0xEE, 0x77, 0xBB, 0xDD, 0xEE, + 0x7F, 0xEF, 0xFB, 0xBF, 0xEF, 0xFE, 0xBF, 0xFD, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +}; +#endif + void GfxMgr::transition_Amiga() { uint16 screenPos = 1; - uint16 screenStepPos = 1; + uint32 screenStepPos = 1; int16 posY = 0, posX = 0; int16 stepCount = 0; @@ -393,15 +794,29 @@ void GfxMgr::transition_Amiga() { posY = screenStepPos / SCRIPT_WIDTH; posX = screenStepPos - (posY * SCRIPT_WIDTH); - posY += _renderStartOffsetY; // adjust to only update the main area, not the status bar - posX *= 2; // adjust for display screen - - screenStepPos = (screenStepPos * 2) + (_renderStartOffsetY * DISPLAY_WIDTH); // adjust here too for display screen - for (int16 multiPixel = 0; multiPixel < 4; multiPixel++) { - g_system->copyRectToScreen(_displayScreen + screenStepPos, DISPLAY_WIDTH, posX, posY, 2, 1); - screenStepPos += (0x1A40 * 2); // 6720d - posY += 42; + // Adjust to only update the game screen, not the status bar + translateGamePosToDisplayScreen(posX, posY); + + switch (_upscaledHires) { + case DISPLAY_UPSCALED_DISABLED: + for (int16 multiPixel = 0; multiPixel < 4; multiPixel++) { + screenStepPos = (posY * _displayScreenWidth) + posX; + g_system->copyRectToScreen(_displayScreen + screenStepPos, _displayScreenWidth, posX, posY, 2, 1); + posY += 42; + } + break; + case DISPLAY_UPSCALED_640x400: + for (int16 multiPixel = 0; multiPixel < 4; multiPixel++) { + screenStepPos = (posY * _displayScreenWidth) + posX; + g_system->copyRectToScreen(_displayScreen + screenStepPos, _displayScreenWidth, posX, posY, 4, 2); + posY += 42 * 2; + } + break; + default: + assert(0); + break; } + stepCount++; if (stepCount == 220) { // 30 times for the whole transition, so should take around 0.5 seconds @@ -424,7 +839,7 @@ void GfxMgr::transition_Amiga() { // Atari ST definitely had a hi-res transition using the full resolution unlike the Amiga transition. void GfxMgr::transition_AtariSt() { uint16 screenPos = 1; - uint16 screenStepPos = 1; + uint32 screenStepPos = 1; int16 posY = 0, posX = 0; int16 stepCount = 0; @@ -443,17 +858,31 @@ void GfxMgr::transition_AtariSt() { if ((screenPos < 13440) && (screenPos & 1)) { screenStepPos = screenPos >> 1; - posY = screenStepPos / DISPLAY_WIDTH; - posX = screenStepPos - (posY * DISPLAY_WIDTH); - - posY += _renderStartOffsetY; // adjust to only update the main area, not the status bar - - screenStepPos = screenStepPos + (_renderStartOffsetY * DISPLAY_WIDTH); // adjust here too for display screen - for (int16 multiPixel = 0; multiPixel < 8; multiPixel++) { - g_system->copyRectToScreen(_displayScreen + screenStepPos, DISPLAY_WIDTH, posX, posY, 1, 1); - screenStepPos += 0x1a40; // 6720d - posY += 21; + posY = screenStepPos / DISPLAY_DEFAULT_WIDTH; + posX = screenStepPos - (posY * DISPLAY_DEFAULT_WIDTH); + + switch (_upscaledHires) { + case DISPLAY_UPSCALED_DISABLED: + posY += _renderStartDisplayOffsetY; // adjust to only update the main area, not the status bar + for (int16 multiPixel = 0; multiPixel < 8; multiPixel++) { + screenStepPos = (posY * _displayScreenWidth) + posX; + g_system->copyRectToScreen(_displayScreen + screenStepPos, _displayScreenWidth, posX, posY, 1, 1); + posY += 21; + } + break; + case DISPLAY_UPSCALED_640x400: + posX *= 2; posY *= 2; + posY += _renderStartDisplayOffsetY; // adjust to only update the main area, not the status bar + for (int16 multiPixel = 0; multiPixel < 8; multiPixel++) { + screenStepPos = (posY * _displayScreenWidth) + posX; + g_system->copyRectToScreen(_displayScreen + screenStepPos, _displayScreenWidth, posX, posY, 2, 2); + posY += 21 * 2; + } + break; + default: + break; } + stepCount++; if (stepCount == 168) { // 40 times for the whole transition, so should take around 0.7 seconds @@ -484,7 +913,7 @@ void GfxMgr::block_save(int16 x, int16 y, int16 width, int16 height, byte *buffe //warning("block_save: %d, %d -> %d, %d", x, y, width, height); while (remainingHeight) { - memcpy(curBufferPtr, _visualScreen + offset, width); + memcpy(curBufferPtr, _gameScreen + offset, width); offset += SCRIPT_WIDTH; curBufferPtr += width; remainingHeight--; @@ -510,7 +939,7 @@ void GfxMgr::block_restore(int16 x, int16 y, int16 width, int16 height, byte *bu //warning("block_restore: %d, %d -> %d, %d", x, y, width, height); while (remainingHeight) { - memcpy(_visualScreen + offset, curBufferPtr, width); + memcpy(_gameScreen + offset, curBufferPtr, width); offset += SCRIPT_WIDTH; curBufferPtr += width; remainingHeight--; @@ -526,12 +955,8 @@ void GfxMgr::block_restore(int16 x, int16 y, int16 width, int16 height, byte *bu } } -// Attention: uses visual screen coordinates! -void GfxMgr::copyDisplayRectToScreen(int16 x, int16 y, int16 width, int16 height) { - g_system->copyRectToScreen(_displayScreen + y * DISPLAY_WIDTH + x, DISPLAY_WIDTH, x, y, width, height); -} - // coordinates are for visual screen, but are supposed to point somewhere inside the playscreen +// x, y is the upper left. Sierra passed them as lower left. We change that to make upscaling easier. // attention: Clipping is done here against 160x200 instead of 160x168 // Original interpreter didn't do any clipping, we do it for security. // Clipping against the regular script width/height must not be done, @@ -539,13 +964,13 @@ void GfxMgr::copyDisplayRectToScreen(int16 x, int16 y, int16 width, int16 height // Going beyond 160x168 will result in messageboxes not getting fully removed // In KQ4's case, the scripts clear the screen that's why it works. void GfxMgr::drawBox(int16 x, int16 y, int16 width, int16 height, byte backgroundColor, byte lineColor) { - if (!render_Clip(x, y, width, height, SCRIPT_WIDTH, DISPLAY_HEIGHT - _renderStartOffsetY)) + if (!render_Clip(x, y, width, height, VISUAL_WIDTH, VISUAL_HEIGHT - _renderStartVisualOffsetY)) return; // coordinate translation: visual-screen -> display-screen - x = x * 2; - y = y + _renderStartOffsetY; // drawDisplayRect paints anywhere on the whole screen, our coordinate is within playscreen - width = width * 2; // width was given as visual width, we need display width + translateVisualRectToDisplayScreen(x, y, width, height); + + y = y + _renderStartDisplayOffsetY; // drawDisplayRect paints anywhere on the whole screen, our coordinate is within playscreen // draw box background drawDisplayRect(x, y, width, height, backgroundColor); @@ -555,75 +980,87 @@ void GfxMgr::drawBox(int16 x, int16 y, int16 width, int16 height, byte backgroun case Common::kRenderApple2GS: case Common::kRenderAmiga: // Slightly different window frame, and actually using 1-pixel width, which is "hi-res" - drawDisplayRect(x + 2, y - 2, width - 4, 1, lineColor); - drawDisplayRect(x + width - 3, y - 2, 1, height - 4, lineColor); - drawDisplayRect(x + 2, y - height + 3, width - 4, 1, lineColor); - drawDisplayRect(x + 2, y - 2, 1, height - 4, lineColor); + drawDisplayRect(x, +2, y, +2, width, -4, 0, 1, lineColor); + drawDisplayRect(x + width, -3, y, +2, 0, 1, height, -4, lineColor); + drawDisplayRect(x, +2, y + height, -3, width, -4, 0, 1, lineColor); + drawDisplayRect(x, +2, y, +2, 0, 1, height, -4, lineColor); break; case Common::kRenderMacintosh: // 1 pixel between box and frame lines. Frame lines were black - drawDisplayRect(x + 1, y - 1, width - 2, 1, 0); - drawDisplayRect(x + width - 2, y - 1, 1, height - 2, 0); - drawDisplayRect(x + 1, y - height + 2, width - 2, 1, 0); - drawDisplayRect(x + 1, y - 1, 1, height - 2, 0); + drawDisplayRect(x, +1, y, +1, width, -2, 0, 1, 0); + drawDisplayRect(x + width, -2, y, +1, 0, 1, height, -2, 0); + drawDisplayRect(x, +1, y + height, -2, width, -2, 0, 1, 0); + drawDisplayRect(x, +1, y, +1, 0, 1, height, -2, 0); break; + case Common::kRenderHercA: + case Common::kRenderHercG: + lineColor = 0; // change linecolor to black + // supposed to fall through case Common::kRenderCGA: case Common::kRenderEGA: case Common::kRenderVGA: case Common::kRenderAtariST: default: - drawDisplayRect(x + 2, y - 1, width - 4, 1, lineColor); - drawDisplayRect(x + width - 4, y - 2, 2, height - 4, lineColor); - drawDisplayRect(x + 2, y - height + 2, width - 4, 1, lineColor); - drawDisplayRect(x + 2, y - 2, 2, height - 4, lineColor); + drawDisplayRect(x, +2, y, +1, width, -4, 0, 1, lineColor); + drawDisplayRect(x + width, -4, y, +2, 0, 2, height, -4, lineColor); + drawDisplayRect(x, +2, y + height, -2, width, -4, 0, 1, lineColor); + drawDisplayRect(x, +2, y, +2, 0, 2, height, -4, lineColor); break; } } -// coordinates for visual screen -void GfxMgr::drawRect(int16 x, int16 y, int16 width, int16 height, byte color) { - if (!render_Clip(x, y, width, height, SCRIPT_WIDTH, DISPLAY_HEIGHT - _renderStartOffsetY)) - return; - - // coordinate translation: visual-screen -> display-screen - x = x * 2; - y = y + _renderStartOffsetY; // drawDisplayRect paints anywhere on the whole screen, our coordinate is within playscreen - width = width * 2; // width was given as visual width, we need display width - - drawDisplayRect(x, y, width, height, color); -} - // coordinates are directly for display screen void GfxMgr::drawDisplayRect(int16 x, int16 y, int16 width, int16 height, byte color, bool copyToScreen) { switch (_vm->_renderMode) { case Common::kRenderCGA: drawDisplayRectCGA(x, y, width, height, color); break; + case Common::kRenderHercG: + case Common::kRenderHercA: + if (color) + color = 1; // change any color except black to green/amber + // supposed to fall through case Common::kRenderEGA: default: drawDisplayRectEGA(x, y, width, height, color); break; } if (copyToScreen) { - int16 upperY = y - height + 1; - g_system->copyRectToScreen(_displayScreen + upperY * DISPLAY_WIDTH + x, DISPLAY_WIDTH, x, upperY, width, height); + copyDisplayRectToScreen(x, y, width, height); } } +void GfxMgr::drawDisplayRect(int16 x, int16 adjX, int16 y, int16 adjY, int16 width, int16 adjWidth, int16 height, int16 adjHeight, byte color, bool copyToScreen) { + switch (_upscaledHires) { + case DISPLAY_UPSCALED_DISABLED: + x += adjX; y += adjY; + width += adjWidth; height += adjHeight; + break; + case DISPLAY_UPSCALED_640x400: + x += adjX * 2; y += adjY * 2; + width += adjWidth * 2; height += adjHeight * 2; + break; + default: + assert(0); + break; + } + drawDisplayRect(x, y, width, height, color, copyToScreen); +} + void GfxMgr::drawDisplayRectEGA(int16 x, int16 y, int16 width, int16 height, byte color) { - int offsetDisplay = (DISPLAY_WIDTH * y) + x; + uint32 offsetDisplay = (y * _displayScreenWidth) + x; int16 remainingHeight = height; while (remainingHeight) { memset(_displayScreen + offsetDisplay, color, width); - offsetDisplay -= DISPLAY_WIDTH; + offsetDisplay += _displayScreenWidth; remainingHeight--; } } void GfxMgr::drawDisplayRectCGA(int16 x, int16 y, int16 width, int16 height, byte color) { - int offsetDisplay = (DISPLAY_WIDTH * y) + x; + uint32 offsetDisplay = (y * _displayScreenWidth) + x; int16 remainingHeight = height; int16 remainingWidth = width; byte CGAMixtureColor = getCGAMixtureColor(color); @@ -644,18 +1081,20 @@ void GfxMgr::drawDisplayRectCGA(int16 x, int16 y, int16 width, int16 height, byt remainingWidth -= 2; } - offsetDisplay -= DISPLAY_WIDTH; + offsetDisplay += _displayScreenWidth; remainingHeight--; } } // row + column are text-coordinates void GfxMgr::drawCharacter(int16 row, int16 column, byte character, byte foreground, byte background, bool disabledLook) { - int16 x = column * FONT_DISPLAY_WIDTH; - int16 y = row * FONT_DISPLAY_HEIGHT; + int16 x = column; + int16 y = row; byte transformXOR = 0; byte transformOR = 0; + translateFontPosToDisplayScreen(x, y); + // Now figure out, if special handling needs to be done if (_vm->_game.gfxMode) { if (background & 0x08) { @@ -676,22 +1115,43 @@ void GfxMgr::drawStringOnDisplay(int16 x, int16 y, const char *text, byte foregr while (*text) { drawCharacterOnDisplay(x, y, *text, foregroundColor, backgroundColor); text++; - x += FONT_DISPLAY_WIDTH; + x += _displayFontWidth; + } +} + +void GfxMgr::drawStringOnDisplay(int16 x, int16 adjX, int16 y, int16 adjY, const char *text, byte foregroundColor, byte backgroundColor) { + switch (_upscaledHires) { + case DISPLAY_UPSCALED_DISABLED: + x += adjX; + y += adjY; + break; + case DISPLAY_UPSCALED_640x400: + x += adjX * 2; + y += adjY * 2; + break; + default: + assert(0); + break; } + drawStringOnDisplay(x, y, text, foregroundColor, backgroundColor); } void GfxMgr::drawCharacterOnDisplay(int16 x, int16 y, const byte character, byte foreground, byte background, byte transformXOR, byte transformOR) { int16 curX, curY; const byte *fontData; + bool fontIsHires = _font->isFontHires(); + int16 fontHeight = fontIsHires ? 16 : FONT_DISPLAY_HEIGHT; + int16 fontWidth = fontIsHires ? 16 : FONT_DISPLAY_WIDTH; + int16 fontBytesPerCharacter = fontIsHires ? 32 : FONT_BYTES_PER_CHARACTER; byte curByte = 0; uint16 curBit; // get font data of specified character - fontData = _vm->getFontData() + character * FONT_BYTES_PER_CHARACTER; + fontData = _font->getFontData() + character * fontBytesPerCharacter; curBit = 0; - for (curY = 0; curY < FONT_DISPLAY_HEIGHT; curY++) { - for (curX = 0; curX < FONT_DISPLAY_WIDTH; curX++) { + for (curY = 0; curY < fontHeight; curY++) { + for (curX = 0; curX < fontWidth; curX++) { if (!curBit) { curByte = *fontData; // do transformations in case they are needed (invert/disabled look) @@ -701,9 +1161,9 @@ void GfxMgr::drawCharacterOnDisplay(int16 x, int16 y, const byte character, byte curBit = 0x80; } if (curByte & curBit) { - putPixelOnDisplay(x + curX, y + curY, foreground); + putFontPixelOnDisplay(x, y, curX, curY, foreground, fontIsHires); } else { - putPixelOnDisplay(x + curX, y + curY, background); + putFontPixelOnDisplay(x, y, curX, curY, background, fontIsHires); } curBit = curBit >> 1; } @@ -711,18 +1171,20 @@ void GfxMgr::drawCharacterOnDisplay(int16 x, int16 y, const byte character, byte transformOR ^= 0xFF; } - copyDisplayRectToScreen(x, y, FONT_DISPLAY_WIDTH, FONT_DISPLAY_HEIGHT); + copyDisplayRectToScreen(x, y, _displayFontWidth, _displayFontHeight); } #define SHAKE_VERTICAL_PIXELS 4 -#define SHAKE_HORIZONTAL_PIXELS 8 +#define SHAKE_HORIZONTAL_PIXELS 4 // Sierra used some EGA port trickery to do it, we have to do it by copying pixels around void GfxMgr::shakeScreen(int16 repeatCount) { int shakeNr, shakeCount; uint8 *blackSpace; + int16 shakeHorizontalPixels = SHAKE_HORIZONTAL_PIXELS * (2 + _displayWidthMulAdjust); + int16 shakeVerticalPixels = SHAKE_VERTICAL_PIXELS * (1 + _displayHeightMulAdjust); - if ((blackSpace = (uint8 *)calloc(SHAKE_HORIZONTAL_PIXELS * DISPLAY_WIDTH, 1)) == NULL) + if ((blackSpace = (uint8 *)calloc(shakeVerticalPixels * _displayScreenWidth, 1)) == NULL) return; shakeCount = repeatCount * 8; // effectively 4 shakes per repeat @@ -732,12 +1194,12 @@ void GfxMgr::shakeScreen(int16 repeatCount) { for (shakeNr = 0; shakeNr < shakeCount; shakeNr++) { if (shakeNr & 1) { // move back - copyDisplayRectToScreen(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT); + copyDisplayToScreen(); } else { - g_system->copyRectToScreen(_displayScreen, DISPLAY_WIDTH, SHAKE_HORIZONTAL_PIXELS, SHAKE_VERTICAL_PIXELS, DISPLAY_WIDTH - SHAKE_HORIZONTAL_PIXELS, DISPLAY_HEIGHT - SHAKE_VERTICAL_PIXELS); + g_system->copyRectToScreen(_displayScreen, _displayScreenWidth, shakeHorizontalPixels, shakeVerticalPixels, _displayScreenWidth - shakeHorizontalPixels, _displayScreenHeight - shakeVerticalPixels); // additionally fill the remaining space with black - g_system->copyRectToScreen(blackSpace, DISPLAY_WIDTH, 0, 0, DISPLAY_WIDTH, SHAKE_VERTICAL_PIXELS); - g_system->copyRectToScreen(blackSpace, SHAKE_HORIZONTAL_PIXELS, 0, 0, SHAKE_HORIZONTAL_PIXELS, DISPLAY_HEIGHT); + g_system->copyRectToScreen(blackSpace, _displayScreenWidth, 0, 0, _displayScreenWidth, shakeVerticalPixels); + g_system->copyRectToScreen(blackSpace, shakeHorizontalPixels, 0, 0, shakeHorizontalPixels, _displayScreenHeight); } g_system->updateScreen(); g_system->delayMillis(66); // Sierra waited for 4 V'Syncs, which is around 66 milliseconds @@ -751,13 +1213,18 @@ void GfxMgr::updateScreen() { } void GfxMgr::initPriorityTable() { + _priorityTableSet = false; + + createDefaultPriorityTable(_priorityTable); +} + +void GfxMgr::createDefaultPriorityTable(uint8 *priorityTable) { int16 priority, step; int16 yPos = 0; - _priorityTableSet = false; for (priority = 1; priority < 15; priority++) { for (step = 0; step < 12; step++) { - _priorityTable[yPos++] = priority < 4 ? 4 : priority; + priorityTable[yPos++] = priority < 4 ? 4 : priority; } } } @@ -776,11 +1243,35 @@ void GfxMgr::setPriorityTable(int16 priorityBase) { } } +// used for saving +int16 GfxMgr::saveLoadGetPriority(int16 yPos) { + assert(yPos < SCRIPT_HEIGHT); + return _priorityTable[yPos]; +} +bool GfxMgr::saveLoadWasPriorityTableModified() { + return _priorityTableSet; +} + // used for restoring -void GfxMgr::setPriority(int16 yPos, int16 priority) { +void GfxMgr::saveLoadSetPriority(int16 yPos, int16 priority) { assert(yPos < SCRIPT_HEIGHT); _priorityTable[yPos] = priority; } +void GfxMgr::saveLoadSetPriorityTableModifiedBool(bool wasModified) { + _priorityTableSet = wasModified; +} +void GfxMgr::saveLoadFigureOutPriorityTableModifiedBool() { + uint8 defaultPriorityTable[SCRIPT_HEIGHT]; /**< priority table */ + + createDefaultPriorityTable(defaultPriorityTable); + + if (memcmp(defaultPriorityTable, _priorityTable, sizeof(_priorityTable)) == 0) { + // Match, it is the default table, so reset the flag + _priorityTableSet = false; + } else { + _priorityTableSet = true; + } +} /** * Convert sprite priority to y value. @@ -793,7 +1284,8 @@ int16 GfxMgr::priorityToY(int16 priority) { return (priority - 5) * 12 + 48; } - // dynamic priority bands were introduced in 3.002.086 (effectively AGI3) + // Dynamic priority bands were introduced in 2.425, but removed again until 2.936 (effectively last version of AGI2) + // They are available from 2.936 onwards. // It seems there was a glitch, that caused priority bands to not get calculated properly. // It was caused by this function starting with Y = 168 instead of 167, which meant it always // returned with 168 as result. @@ -804,6 +1296,8 @@ int16 GfxMgr::priorityToY(int16 priority) { // drawn first, followed by ego, which would then draw ego over the dwarf. // For more information see bug #1712585 (dwarf sprite priority) // + // This glitch is definitely present in 2.425, 2.936 and 3.002.086. + // // Priority bands were working properly in: 3.001.098 (Black Cauldron) uint16 agiVersion = _vm->getVersion(); @@ -925,7 +1419,38 @@ int GfxMgr::getAGIPalFileNum() { } void GfxMgr::initMouseCursor(MouseCursorData *mouseCursor, const byte *bitmapData, uint16 width, uint16 height, int hotspotX, int hotspotY) { - mouseCursor->bitmapData = bitmapData; + switch (_upscaledHires) { + case DISPLAY_UPSCALED_DISABLED: + mouseCursor->bitmapData = bitmapData; + break; + case DISPLAY_UPSCALED_640x400: { + mouseCursor->bitmapDataAllocated = (byte *)malloc(width * height * 4); + mouseCursor->bitmapData = mouseCursor->bitmapDataAllocated; + + // Upscale mouse cursor + byte *upscaledData = mouseCursor->bitmapDataAllocated; + + for (uint16 y = 0; y < height; y++) { + for (uint16 x = 0; x < width; x++) { + byte curColor = *bitmapData++; + upscaledData[x * 2 + 0] = curColor; + upscaledData[x * 2 + 1] = curColor; + upscaledData[x * 2 + (width * 2) + 0] = curColor; + upscaledData[x * 2 + (width * 2) + 1] = curColor; + } + upscaledData += width * 2 * 2; + } + + width *= 2; + height *= 2; + hotspotX *= 2; + hotspotY *= 2; + break; + } + default: + assert(0); + break; + } mouseCursor->width = width; mouseCursor->height = height; mouseCursor->hotspotX = hotspotX; diff --git a/engines/agi/graphics.h b/engines/agi/graphics.h index fb596626c6..1cb595cdfa 100644 --- a/engines/agi/graphics.h +++ b/engines/agi/graphics.h @@ -29,13 +29,15 @@ namespace Agi { #define SCRIPT_WIDTH 160 #define SCRIPT_HEIGHT 168 -#define DISPLAY_WIDTH 320 -#define DISPLAY_HEIGHT 200 - -#define GFX_WIDTH 320 -#define GFX_HEIGHT 200 -#define CHAR_COLS 8 -#define CHAR_LINES 8 +#define VISUAL_WIDTH 160 +#define VISUAL_HEIGHT 200 +#define DISPLAY_DEFAULT_WIDTH 320 +#define DISPLAY_DEFAULT_HEIGHT 200 + +enum GfxScreenUpscaledMode { + DISPLAY_UPSCALED_DISABLED = 0, + DISPLAY_UPSCALED_640x400 = 1 +}; class AgiEngine; @@ -47,6 +49,7 @@ enum GfxScreenMasks { struct MouseCursorData { const byte *bitmapData; + byte *bitmapDataAllocated; uint16 width; uint16 height; int hotspotX; @@ -56,6 +59,7 @@ struct MouseCursorData { class GfxMgr { private: AgiBase *_vm; + GfxFont *_font; uint8 _paletteGfxMode[256 * 3]; uint8 _paletteTextMode[256 * 3]; @@ -64,7 +68,7 @@ private: int _agipalFileNum; public: - GfxMgr(AgiBase *vm); + GfxMgr(AgiBase *vm, GfxFont *font); int initVideo(); int deinitVideo(); @@ -78,18 +82,58 @@ public: void setMouseCursor(bool busy = false); void setRenderStartOffset(uint16 offsetY); - uint16 getRenderStartOffsetY(); + uint16 getRenderStartDisplayOffsetY(); + + void translateGamePosToDisplayScreen(int16 &x, int16 &y); + void translateVisualPosToDisplayScreen(int16 &x, int16 &y); + void translateDisplayPosToGameScreen(int16 &x, int16 &y); + + void translateVisualDimensionToDisplayScreen(int16 &width, int16 &height); + void translateDisplayDimensionToVisualScreen(int16 &width, int16 &height); + + void translateGameRectToDisplayScreen(int16 &x, int16 &y, int16 &width, int16 &height); + void translateVisualRectToDisplayScreen(int16 &x, int16 &y, int16 &width, int16 &height); + void translateDisplayRectToVisualScreen(int16 &x, int16 &y, int16 &width, int16 &height); + + uint32 getDisplayOffsetToGameScreenPos(int16 x, int16 y); + uint32 getDisplayOffsetToVisualScreenPos(int16 x, int16 y); + + void copyDisplayRectToScreen(int16 x, int16 y, int16 width, int16 height); + void copyDisplayRectToScreen(int16 x, int16 adjX, int16 y, int16 adjY, int16 width, int16 adjWidth, int16 height, int16 adjHeight); + void copyDisplayRectToScreenUsingGamePos(int16 x, int16 y, int16 width, int16 height); + void copyDisplayRectToScreenUsingVisualPos(int16 x, int16 y, int16 width, int16 height); + void copyDisplayToScreen(); + + void translateFontPosToDisplayScreen(int16 &x, int16 &y); + void translateDisplayPosToFontScreen(int16 &x, int16 &y); + void translateFontDimensionToDisplayScreen(int16 &width, int16 &height); + void translateFontRectToDisplayScreen(int16 &x, int16 &y, int16 &width, int16 &height); + Common::Rect getFontRectForDisplayScreen(int16 column, int16 row, int16 width, int16 height); private: uint _pixels; - //uint16 _displayWidth; - //uint16 _displayHeight; uint _displayPixels; byte *_activeScreen; - byte *_visualScreen; // 160x168 - byte *_priorityScreen; // 160x168 - byte *_displayScreen; // 320x200 + byte *_gameScreen; // 160x168 - screen, where the actual game content is drawn to (actual graphics, not including status line, prompt, etc.) + byte *_priorityScreen; // 160x168 - screen contains priority information of the game screen + // the term "visual screen" is effectively the display screen, but at 160x200 resolution. Used for coordinate translation + byte *_displayScreen; // 320x200 or 640x400 - screen, that the game is rendered to and which is then copied to framebuffer + + uint16 _displayScreenWidth; + uint16 _displayScreenHeight; + + uint16 _displayFontWidth; + uint16 _displayFontHeight; + + uint16 _displayWidthMulAdjust; + uint16 _displayHeightMulAdjust; + + /** + * This variable defines, if upscaled hires is active and what upscaled mode + * is used. + */ + GfxScreenUpscaledMode _upscaledHires; bool _priorityTableSet; uint8 _priorityTable[SCRIPT_HEIGHT]; /**< priority table */ @@ -97,15 +141,32 @@ private: MouseCursorData _mouseCursor; MouseCursorData _mouseCursorBusy; - uint16 _renderStartOffsetY; + uint16 _renderStartVisualOffsetY; + uint16 _renderStartDisplayOffsetY; public: + uint16 getDisplayScreenWidth() { + return _displayScreenWidth; + } + uint16 getDisplayFontWidth() { + return _displayFontWidth; + } + uint16 getDisplayFontHeight() { + return _displayFontHeight; + } + + GfxScreenUpscaledMode getUpscaledHires() { + return _upscaledHires; + } + void debugShowMap(int mapNr); void clear(byte color, byte priority); void clearDisplay(byte color, bool copyToScreen = true); void putPixel(int16 x, int16 y, byte drawMask, byte color, byte priority); void putPixelOnDisplay(int16 x, int16 y, byte color); + void putPixelOnDisplay(int16 x, int16 adjX, int16 y, int16 adjY, byte color); + void putFontPixelOnDisplay(int16 baseX, int16 baseY, int16 addX, int16 addY, byte color, bool isHires); byte getColor(int16 x, int16 y); byte getPriority(int16 x, int16 y); @@ -119,6 +180,7 @@ public: private: void render_BlockEGA(int16 x, int16 y, int16 width, int16 height, bool copyToScreen); void render_BlockCGA(int16 x, int16 y, int16 width, int16 height, bool copyToScreen); + void render_BlockHercules(int16 x, int16 y, int16 width, int16 height, bool copyToScreen); public: void transition_Amiga(); @@ -127,11 +189,9 @@ public: void block_save(int16 x, int16 y, int16 width, int16 height, byte *bufferPtr); void block_restore(int16 x, int16 y, int16 width, int16 height, byte *bufferPtr); - void copyDisplayRectToScreen(int16 x, int16 y, int16 width, int16 height); - void drawBox(int16 x, int16 y, int16 width, int16 height, byte backgroundColor, byte lineColor); - void drawRect(int16 x, int16 y, int16 width, int16 height, byte color); void drawDisplayRect(int16 x, int16 y, int16 width, int16 height, byte color, bool copyToScreen = true); + void drawDisplayRect(int16 x, int16 adjX, int16 y, int16 adjY, int16 width, int16 adjWidth, int16 height, int16 adjHeight, byte color, bool copyToScreen = true); private: void drawDisplayRectEGA(int16 x, int16 y, int16 width, int16 height, byte color); void drawDisplayRectCGA(int16 x, int16 y, int16 width, int16 height, byte color); @@ -139,14 +199,21 @@ private: public: void drawCharacter(int16 row, int16 column, byte character, byte foreground, byte background, bool disabledLook); void drawStringOnDisplay(int16 x, int16 y, const char *text, byte foreground, byte background); + void drawStringOnDisplay(int16 x, int16 adjX, int16 y, int16 adjY, const char *text, byte foregroundColor, byte backgroundColor); void drawCharacterOnDisplay(int16 x, int16 y, byte character, byte foreground, byte background, byte transformXOR = 0, byte transformOR = 0); void shakeScreen(int16 repeatCount); void updateScreen(); void initPriorityTable(); + void createDefaultPriorityTable(uint8 *priorityTable); void setPriorityTable(int16 priorityBase); - void setPriority(int16 yPos, int16 priority); + bool saveLoadWasPriorityTableModified(); + int16 saveLoadGetPriority(int16 yPos); + void saveLoadSetPriorityTableModifiedBool(bool wasModified); + void saveLoadSetPriority(int16 yPos, int16 priority); + void saveLoadFigureOutPriorityTableModifiedBool(); + int16 priorityToY(int16 priority); int16 priorityFromY(int16 yPos); }; diff --git a/engines/agi/inv.cpp b/engines/agi/inv.cpp index 1df5282622..834fa9badc 100644 --- a/engines/agi/inv.cpp +++ b/engines/agi/inv.cpp @@ -34,6 +34,8 @@ InventoryMgr::InventoryMgr(AgiEngine *agi, GfxMgr *gfx, TextMgr *text, SystemUI _gfx = gfx; _text = text; _systemUI = systemUI; + + _activeItemNr = -1; } InventoryMgr::~InventoryMgr() { diff --git a/engines/agi/keyboard.cpp b/engines/agi/keyboard.cpp index 5a73afe6fb..3bc45af5d4 100644 --- a/engines/agi/keyboard.cpp +++ b/engines/agi/keyboard.cpp @@ -130,7 +130,7 @@ void AgiEngine::processScummVMEvents() { } break; case Common::EVENT_KEYDOWN: - if (event.kbd.hasFlags(Common::KBD_CTRL) && event.kbd.keycode == Common::KEYCODE_d) { + if (event.kbd.hasFlags(Common::KBD_CTRL | Common::KBD_SHIFT) && event.kbd.keycode == Common::KEYCODE_d) { _console->attach(); break; } @@ -256,7 +256,28 @@ void AgiEngine::processScummVMEvents() { // Original AGI actually created direction events in here // We don't do that, that's why we create a stationary event instead, which will // result in a direction change to 0 in handleController(). - keyEnqueue(AGI_KEY_STATIONARY); + switch (event.kbd.keycode) { + case Common::KEYCODE_LEFT: + case Common::KEYCODE_RIGHT: + case Common::KEYCODE_UP: + case Common::KEYCODE_DOWN: + case Common::KEYCODE_HOME: + case Common::KEYCODE_END: + case Common::KEYCODE_PAGEUP: + case Common::KEYCODE_PAGEDOWN: + case Common::KEYCODE_KP4: + case Common::KEYCODE_KP6: + case Common::KEYCODE_KP8: + case Common::KEYCODE_KP2: + case Common::KEYCODE_KP9: + case Common::KEYCODE_KP3: + case Common::KEYCODE_KP7: + case Common::KEYCODE_KP1: + keyEnqueue(AGI_KEY_STATIONARY); + break; + default: + break; + } } break; @@ -292,7 +313,8 @@ bool AgiEngine::handleMouseClicks(uint16 &key) { if (!cycleInnerLoopIsActive()) { // Only do this, when no inner loop is currently active - Common::Rect displayLineRect(DISPLAY_WIDTH, FONT_DISPLAY_HEIGHT); + Common::Rect displayLineRect = _gfx->getFontRectForDisplayScreen(0, 0, FONT_COLUMN_CHARACTERS, 1); +// Common::Rect displayLineRect(_gfx->getDisplayScreenWidth(), _gfx->getDisplayFontHeight()); if (displayLineRect.contains(_mouse.pos)) { // Mouse is inside first line of the screen @@ -307,7 +329,7 @@ bool AgiEngine::handleMouseClicks(uint16 &key) { // Prompt is currently enabled int16 promptRow = _text->promptRow_Get(); - displayLineRect.moveTo(0, promptRow * FONT_DISPLAY_HEIGHT); + displayLineRect.moveTo(0, promptRow * _gfx->getDisplayFontHeight()); if (displayLineRect.contains(_mouse.pos)) { // and user clicked within the line of the prompt @@ -330,9 +352,7 @@ bool AgiEngine::handleMouseClicks(uint16 &key) { _text->stringPos_Get(stringRow, stringColumn); stringMaxLen = _text->stringGetMaxLen(); - Common::Rect displayRect(stringMaxLen * FONT_DISPLAY_WIDTH, FONT_DISPLAY_HEIGHT); - displayRect.moveTo(stringColumn * FONT_DISPLAY_WIDTH, stringRow * FONT_DISPLAY_HEIGHT); - + Common::Rect displayRect = _gfx->getFontRectForDisplayScreen(stringColumn, stringRow, stringMaxLen, 1); if (displayRect.contains(_mouse.pos)) { // user clicked inside the input space showPredictiveDialog(); @@ -472,7 +492,7 @@ bool AgiEngine::handleController(uint16 key) { // in case you walked to the log by using the mouse, so don't!!! int16 egoDestinationX = _mouse.pos.x; int16 egoDestinationY = _mouse.pos.y; - adjustPosToGameScreen(egoDestinationX, egoDestinationY); + _gfx->translateDisplayPosToGameScreen(egoDestinationX, egoDestinationY); screenObjEgo->motionType = kMotionEgo; if (egoDestinationX < (screenObjEgo->xSize / 2)) { diff --git a/engines/agi/menu.cpp b/engines/agi/menu.cpp index 803efd757e..49c2d0eeab 100644 --- a/engines/agi/menu.cpp +++ b/engines/agi/menu.cpp @@ -49,8 +49,8 @@ GfxMenu::GfxMenu(AgiEngine *vm, GfxMgr *gfx, PictureMgr *picture, TextMgr *text) _drawnMenuNr = -1; _drawnMenuHeight = 0; _drawnMenuWidth = 0; - _drawnMenuRow = 0; - _drawnMenuColumn = 0; + _drawnMenuY = 0; + _drawnMenuX = 0; } GfxMenu::~GfxMenu() { @@ -64,6 +64,8 @@ GfxMenu::~GfxMenu() { } void GfxMenu::addMenu(const char *menuText) { + int16 curColumnEnd = _setupMenuColumn; + // already submitted? in that case no further changes possible if (_submitted) return; @@ -72,6 +74,18 @@ void GfxMenu::addMenu(const char *menuText) { menuEntry->text = menuText; menuEntry->textLen = menuEntry->text.size(); + + // Cut menu name in case menu bar is full + // Happens in at least the fan game Get Outta Space Quest + // Original interpreter had graphical issues in this case + // TODO: this whole code needs to get reworked anyway to support different types of menu bars depending on platform + curColumnEnd += menuEntry->textLen; + while ((menuEntry->textLen) && (curColumnEnd > 40)) { + menuEntry->text.deleteLastChar(); + menuEntry->textLen--; + curColumnEnd--; + } + menuEntry->row = 0; menuEntry->column = _setupMenuColumn; menuEntry->itemCount = 0; @@ -309,8 +323,9 @@ void GfxMenu::execute() { // Unless we are in "via mouse" mode. In that case check current mouse position if (viaMouse) { - int16 mouseRow = _vm->_mouse.pos.y / FONT_DISPLAY_HEIGHT; - int16 mouseColumn = _vm->_mouse.pos.x / FONT_DISPLAY_WIDTH; + int16 mouseRow = _vm->_mouse.pos.y; + int16 mouseColumn = _vm->_mouse.pos.x; + _gfx->translateDisplayPosToFontScreen(mouseColumn, mouseRow); mouseFindMenuSelection(mouseRow, mouseColumn, _drawnMenuNr, _mouseModeItemNr); } @@ -354,7 +369,7 @@ void GfxMenu::execute() { // WORKAROUND: Playarea starts right at the stop, so instead of clearing that part, render it from playarea // Required for at least Donald Duck // This was not done by original AGI, which means the upper pixel line were cleared in this case. - _gfx->render_Block(0, (1 * FONT_VISUAL_HEIGHT) - 1, SCRIPT_WIDTH, FONT_VISUAL_HEIGHT); + _gfx->render_Block(0, 0, SCRIPT_WIDTH, FONT_VISUAL_HEIGHT); } else { _text->clearLine(0, 0); } @@ -365,6 +380,10 @@ void GfxMenu::drawMenuName(int16 menuNr, bool inverted) { GuiMenuEntry *menuEntry = _array[menuNr]; bool disabledLook = false; + // Don't draw in case there is no text + if (!menuEntry->text.size()) + return; + if (!inverted) { _text->charAttrib_Set(0, _text->calculateTextBackground(15)); } else { @@ -409,10 +428,11 @@ void GfxMenu::drawMenu(int16 selectedMenuNr, int16 selectedMenuItemNr) { // calculate active menu dimensions _drawnMenuHeight = (menuEntry->itemCount + 2) * FONT_VISUAL_HEIGHT; _drawnMenuWidth = (menuEntry->maxItemTextLen * FONT_VISUAL_WIDTH) + 8; - _drawnMenuRow = (menuEntry->itemCount + 3 - _text->getWindowRowMin()) * FONT_VISUAL_HEIGHT - 1; - _drawnMenuColumn = (itemEntry->column - 1) * FONT_VISUAL_WIDTH; + _drawnMenuY = (1 - _text->getWindowRowMin()) * FONT_VISUAL_HEIGHT; + //(menuEntry->itemCount + 3 - _text->getWindowRowMin()) * FONT_VISUAL_HEIGHT - 1; + _drawnMenuX = (itemEntry->column - 1) * FONT_VISUAL_WIDTH; - _gfx->drawBox(_drawnMenuColumn, _drawnMenuRow, _drawnMenuWidth, _drawnMenuHeight, 15, 0); + _gfx->drawBox(_drawnMenuX, _drawnMenuY, _drawnMenuWidth, _drawnMenuHeight, 15, 0); while (itemCount) { if (itemNr == selectedMenuItemNr) { @@ -430,7 +450,7 @@ void GfxMenu::removeActiveMenu(int16 selectedMenuNr) { drawMenuName(selectedMenuNr, false); // overwrite actual menu items by rendering play screen - _gfx->render_Block(_drawnMenuColumn, _drawnMenuRow, _drawnMenuWidth, _drawnMenuHeight); + _gfx->render_Block(_drawnMenuX, _drawnMenuY, _drawnMenuWidth, _drawnMenuHeight); } void GfxMenu::keyPress(uint16 newKey) { @@ -531,8 +551,10 @@ void GfxMenu::keyPress(uint16 newKey) { // In "via mouse" mode, we check if user let go of the left mouse button and then select the item that way void GfxMenu::mouseEvent(uint16 newKey) { // Find out, where current mouse cursor actually is - int16 mouseRow = _vm->_mouse.pos.y / FONT_DISPLAY_HEIGHT; - int16 mouseColumn = _vm->_mouse.pos.x / FONT_DISPLAY_WIDTH; + int16 mouseRow = _vm->_mouse.pos.y; + int16 mouseColumn = _vm->_mouse.pos.x; + + _gfx->translateDisplayPosToFontScreen(mouseColumn, mouseRow); int16 activeMenuNr, activeItemNr; mouseFindMenuSelection(mouseRow, mouseColumn, activeMenuNr, activeItemNr); @@ -620,7 +642,7 @@ void GfxMenu::mouseFindMenuSelection(int16 mouseRow, int16 mouseColumn, int16 &a if (mouseRow == menuEntry->row) { // line match - if ((mouseColumn >= menuEntry->column) && (mouseColumn <= (menuEntry->column + menuEntry->textLen))) { + if ((mouseColumn >= menuEntry->column) && (mouseColumn < (menuEntry->column + menuEntry->textLen))) { // full match activeMenuNr = menuNr; activeMenuItemNr = -1; // no item selected @@ -642,7 +664,7 @@ void GfxMenu::mouseFindMenuSelection(int16 mouseRow, int16 mouseColumn, int16 &a if (mouseRow == itemEntry->row) { // line match - if ((mouseColumn >= itemEntry->column) && (mouseColumn <= (itemEntry->column + itemEntry->textLen))) { + if ((mouseColumn >= itemEntry->column) && (mouseColumn < (itemEntry->column + itemEntry->textLen))) { // full match if (itemEntry->enabled) { // Only see it, when it's currently enabled diff --git a/engines/agi/menu.h b/engines/agi/menu.h index a621d7f0f2..b47289180b 100644 --- a/engines/agi/menu.h +++ b/engines/agi/menu.h @@ -111,8 +111,8 @@ private: uint16 _drawnMenuHeight; uint16 _drawnMenuWidth; - int16 _drawnMenuRow; - int16 _drawnMenuColumn; + int16 _drawnMenuY; + int16 _drawnMenuX; // Following variables are used in "via mouse" mode int16 _mouseModeItemNr; diff --git a/engines/agi/motion.cpp b/engines/agi/motion.cpp index 7f49028701..f408ba35e6 100644 --- a/engines/agi/motion.cpp +++ b/engines/agi/motion.cpp @@ -62,6 +62,60 @@ void AgiEngine::changePos(ScreenObjEntry *screenObj) { } } +// WORKAROUND: +// A motion was just activated, check if "end.of.loop"/"reverse.loop" is currently active for the same screen object +// If this is the case, it would result in some random flag getting overwritten in original AGI after the loop was +// completed, because in original AGI loop_flag + wander_count/follow_stepSize/move_X shared the same memory location. +// This is basically an implementation error in the original interpreter. +// Happens in at least: +// - BC: right at the end when the witches disappear at least on Apple IIgs (room 12, screen object 13, view 84) +// - KQ1: when grabbing the eagle (room 22). +// - KQ2: happened somewhere in the game, LordHoto couldn't remember exactly where +void AgiEngine::motionActivated(ScreenObjEntry *screenObj) { + if (screenObj->flags & fCycling) { + // Cycling active too + switch (screenObj->cycle) { + case kCycleEndOfLoop: // "end.of.loop" + case kCycleRevLoop: // "reverse.loop" + // Disable it + screenObj->flags &= ~fCycling; + screenObj->cycle = kCycleNormal; + + warning("Motion activated for screen object %d, but cycler also active", screenObj->objectNr); + warning("This would have resulted in flag corruption in original AGI. Cycler disabled."); + break; + default: + break; + } + } +} + +// WORKAROUND: +// See comment for motionActivated() +// This way no flag would have been overwritten, but certain other variables of the motions. +void AgiEngine::cyclerActivated(ScreenObjEntry *screenObj) { + switch (screenObj->motionType) { + case kMotionWander: + // this would have resulted in wander_count to get corrupted + // We don't stop it. + break; + case kMotionFollowEgo: + // this would have resulted in follow_stepSize to get corrupted + // do not stop motion atm - screenObj->direction = 0; + // do not stop motion atm - screenObj->motionType = kMotionNormal; + break; + case kMotionMoveObj: + // this would have resulted in move_x to get corrupted + // do not stop motion atm - motionMoveObjStop(screenObj); + break; + default: + return; + break; + } + warning("Cycler activated for screen object %d, but motion also active", screenObj->objectNr); + warning("This would have resulted in corruption in original AGI. Motion disabled."); +} + void AgiEngine::motionWander(ScreenObjEntry *screenObj) { uint8 originalWanderCount = screenObj->wander_count; diff --git a/engines/agi/op_cmd.cpp b/engines/agi/op_cmd.cpp index 3f8630521d..fed07ea986 100644 --- a/engines/agi/op_cmd.cpp +++ b/engines/agi/op_cmd.cpp @@ -37,7 +37,6 @@ namespace Agi { -#define getGameID() state->_vm->getGameID() #define getFeatures() state->_vm->getFeatures() #define getVersion() state->_vm->getVersion() #define getLanguage() state->_vm->getLanguage() @@ -82,7 +81,7 @@ void cmdAssignN(AgiGame *state, AgiEngine *vm, uint8 *parameter) { // variable to the correct value here // Fixes bug #1942476 - "AGI: Fan(Get Outta SQ) - Score // is lost on restart" - if (getGameID() == GID_GETOUTTASQ && varNr == 7) + if (vm->getGameID() == GID_GETOUTTASQ && varNr == 7) vm->setVar(varNr, 8); } @@ -890,6 +889,19 @@ void cmdObjStatusF(AgiGame *state, AgiEngine *vm, uint8 *parameter) { // unk_181: Deactivate keypressed control (default control of ego) void cmdSetSimple(AgiGame *state, AgiEngine *vm, uint8 *parameter) { if (!(getFeatures() & (GF_AGI256 | GF_AGI256_2))) { + // set.simple is called by Larry 1 on Apple IIgs at the store, after answering the 555-6969 phone. + // load.sound(16) is called right before it. Interpreter is 2.440-like. + // it's called with parameter 16. + // Original interpreter doesn't seem to play any sound. + // TODO: Figure out what's going on. It can't be automatic saving of course. + // Also getting called in KQ1, when planting beans - parameter 12. + // And when killing the witch - parameter 40. + if ((getVersion() < 0x2425) || (getVersion() == 0x2440)) { + // was not available before 2.2425, but also not available in 2.440 + warning("set.simple called, although not available for current AGI version"); + return; + } + int16 stringNr = parameter[0]; const char *textPtr = nullptr; @@ -897,7 +909,10 @@ void cmdSetSimple(AgiGame *state, AgiEngine *vm, uint8 *parameter) { // Try to get description for automatic saves textPtr = state->strings[stringNr]; + strncpy(state->automaticSaveDescription, textPtr, sizeof(state->automaticSaveDescription)); + state->automaticSaveDescription[sizeof(state->automaticSaveDescription) - 1] = 0; + if (state->automaticSaveDescription[0]) { // We got it and it's set, so enable automatic saving state->automaticSave = true; @@ -917,21 +932,26 @@ void cmdSetSimple(AgiGame *state, AgiEngine *vm, uint8 *parameter) { spritesMgr->drawAllSpriteLists(); state->pictureShown = false; + // Loading trigger + vm->artificialDelayTrigger_DrawPicture(resourceNr); + // Show the picture. Similar to void cmdShow_pic(AgiGame *state, AgiEngine *vm, uint8 *p). vm->setFlag(VM_FLAG_OUTPUT_MODE, false); vm->_text->closeWindow(); vm->_picture->showPic(); state->pictureShown = true; - - // Loading trigger - vm->loadingTrigger_DrawPicture(); } } +// push.script was not available until 2.425, and also not available in 2.440 void cmdPopScript(AgiGame *state, AgiEngine *vm, uint8 *parameter) { - if (getVersion() >= 0x2915) { - debug(0, "pop.script"); + if ((getVersion() < 0x2425) || (getVersion() == 0x2440)) { + // was not available before 2.2425, but also not available in 2.440 + warning("pop.script called, although not available for current AGI version"); + return; } + + debug(0, "pop.script"); } void cmdDiscardSound(AgiGame *state, AgiEngine *vm, uint8 *parameter) { @@ -953,8 +973,18 @@ void cmdShowMouse(AgiGame *state, AgiEngine *vm, uint8 *parameter) { // but show.mouse is never called afterwards. Game running under emulator doesn't seem to hide the mouse cursor. // TODO: figure out, what exactly happens. Probably some hacked-in command and not related to mouse cursor for that game? void cmdHideMouse(AgiGame *state, AgiEngine *vm, uint8 *parameter) { - if (getVersion() < 0x3000) + if (getVersion() < 0x3000) { + // was not available before 3.086 + warning("hide.mouse, although not available for current AGI version"); + return; + } + + if ((vm->getGameID() == GID_MH1) && (vm->getPlatform() == Common::kPlatformApple2GS)) { + // Called right after beating arcade sequence on day 4 in the hospital Parameter is "1". + // Right before cutscene. show.mouse isn't called. Probably different function. + warning("hide.mouse called, disabled for MH1 Apple IIgs"); return; + } // WORKAROUND: Turns off current movement that's being caused with the mouse. // This fixes problems with too many popup boxes appearing in the Amiga @@ -972,14 +1002,18 @@ void cmdHideMouse(AgiGame *state, AgiEngine *vm, uint8 *parameter) { } void cmdAllowMenu(AgiGame *state, AgiEngine *vm, uint8 *parameter) { + if (getVersion() < 0x3098) { + // was not available before 3.098 + warning("allow.menu called, although not available for current AGI version"); + return; + } + uint16 allowed = parameter[0]; - if (getVersion() >= 0x3098) { - if (allowed) { - state->_vm->_menu->accessAllow(); - } else { - state->_vm->_menu->accessDeny(); - } + if (allowed) { + state->_vm->_menu->accessAllow(); + } else { + state->_vm->_menu->accessDeny(); } } @@ -997,15 +1031,21 @@ void cmdFenceMouse(AgiGame *state, AgiEngine *vm, uint8 *parameter) { // HoldKey was added in 2.425 // There was no way to disable this mode until 3.098 though void cmdHoldKey(AgiGame *state, AgiEngine *vm, uint8 *parameter) { - if (getVersion() < 0x2425) + if ((getVersion() < 0x2425) || (getVersion() == 0x2440)) { + // was not available before 2.425, but also not available in 2.440 + warning("hold.key called, although not available for current AGI version"); return; + } vm->_keyHoldMode = true; } void cmdReleaseKey(AgiGame *state, AgiEngine *vm, uint8 *parameter) { - if (getVersion() < 0x3098) + if (getVersion() < 0x3098) { + // was not available before 3.098 + warning("release.key called, although not available for current AGI version"); return; + } vm->_keyHoldMode = false; } @@ -1094,7 +1134,7 @@ void cmdDrawPicV1(AgiGame *state, AgiEngine *vm, uint8 *parameter) { vm->_text->promptClear(); // Loading trigger - vm->loadingTrigger_DrawPicture(); + vm->artificialDelayTrigger_DrawPicture(resourceNr); } void cmdDrawPic(AgiGame *state, AgiEngine *vm, uint8 *parameter) { @@ -1106,6 +1146,7 @@ void cmdDrawPic(AgiGame *state, AgiEngine *vm, uint8 *parameter) { spritesMgr->eraseSprites(); vm->_picture->decodePicture(resourceNr, true); + spritesMgr->buildAllSpriteLists(); spritesMgr->drawAllSpriteLists(); state->pictureShown = false; @@ -1123,11 +1164,11 @@ void cmdDrawPic(AgiGame *state, AgiEngine *vm, uint8 *parameter) { // above the ground), flag 103 is reset, thereby fixing this issue. Note // that this is a script bug and occurs in the original interpreter as well. // Fixes bug #3056: AGI: SQ1 (2.2 DOS ENG) bizzare exploding roger - if (getGameID() == GID_SQ1 && resourceNr == 20) + if (vm->getGameID() == GID_SQ1 && resourceNr == 20) vm->setFlag(103, false); // Loading trigger - vm->loadingTrigger_DrawPicture(); + vm->artificialDelayTrigger_DrawPicture(resourceNr); } void cmdShowPic(AgiGame *state, AgiEngine *vm, uint8 *parameter) { @@ -1179,7 +1220,7 @@ void cmdOverlayPic(AgiGame *state, AgiEngine *vm, uint8 *parameter) { state->pictureShown = false; // Loading trigger - vm->loadingTrigger_DrawPicture(); + vm->artificialDelayTrigger_DrawPicture(resourceNr); } void cmdShowPriScreen(AgiGame *state, AgiEngine *vm, uint8 *parameter) { @@ -1454,6 +1495,8 @@ void cmdReverseLoop(AgiGame *state, AgiEngine *vm, uint8 *parameter) { screenObj->flags |= (fDontupdate | fUpdate | fCycling); screenObj->loop_flag = loopFlag; state->_vm->setFlag(screenObj->loop_flag, false); + + vm->cyclerActivated(screenObj); } void cmdReverseLoopV1(AgiGame *state, AgiEngine *vm, uint8 *parameter) { @@ -1479,6 +1522,8 @@ void cmdEndOfLoop(AgiGame *state, AgiEngine *vm, uint8 *parameter) { screenObj->flags |= (fDontupdate | fUpdate | fCycling); screenObj->loop_flag = loopFlag; vm->setFlag(screenObj->loop_flag, false); + + vm->cyclerActivated(screenObj); } void cmdEndOfLoopV1(AgiGame *state, AgiEngine *vm, uint8 *parameter) { @@ -1591,6 +1636,8 @@ void cmdFollowEgo(AgiGame *state, AgiEngine *vm, uint8 *parameter) { vm->setFlag(screenObj->follow_flag, false); screenObj->flags |= fUpdate; } + + vm->motionActivated(screenObj); } void cmdMoveObj(AgiGame *state, AgiEngine *vm, uint8 *parameter) { @@ -1619,6 +1666,8 @@ void cmdMoveObj(AgiGame *state, AgiEngine *vm, uint8 *parameter) { screenObj->flags |= fUpdate; } + vm->motionActivated(screenObj); + if (objectNr == 0) state->playerControl = false; @@ -1647,6 +1696,8 @@ void cmdMoveObjF(AgiGame *state, AgiEngine *vm, uint8 *parameter) { vm->setFlag(screenObj->move_flag, false); screenObj->flags |= fUpdate; + vm->motionActivated(screenObj); + if (objectNr == 0) state->playerControl = false; @@ -1668,6 +1719,8 @@ void cmdWander(AgiGame *state, AgiEngine *vm, uint8 *parameter) { } else { screenObj->flags |= fUpdate; } + + vm->motionActivated(screenObj); } void cmdSetGameID(AgiGame *state, AgiEngine *vm, uint8 *parameter) { @@ -1857,7 +1910,7 @@ void cmdDistance(AgiGame *state, AgiEngine *vm, uint8 *parameter) { // wouldn't chase Rosella around anymore. If it had worked correctly the zombie // wouldn't have come up at all or it would have come up and gone back down // immediately. The latter approach is the one implemented here. - if (getGameID() == GID_KQ4 && (vm->getVar(VM_VAR_CURRENT_ROOM) == 16 || vm->getVar(VM_VAR_CURRENT_ROOM) == 18) && destVarNr >= 221 && destVarNr <= 223) { + if (vm->getGameID() == GID_KQ4 && (vm->getVar(VM_VAR_CURRENT_ROOM) == 16 || vm->getVar(VM_VAR_CURRENT_ROOM) == 18) && destVarNr >= 221 && destVarNr <= 223) { // Rooms 16 and 18 are graveyards where three zombies come up at night. They use logics 16 and 18. // Variables 221-223 are used to save the distance between each zombie and Rosella. // Variables 155, 156 and 162 are used to save the state of each zombie in room 16. @@ -2132,6 +2185,7 @@ void cmdPrintAtV(AgiGame *state, AgiEngine *vm, uint8 *parameter) { state->_vm->_text->printAt(textNr, textRow, textColumn, textWidth); } +// push.script was not available until 2.425, and also not available in 2.440 void cmdPushScript(AgiGame *state, AgiEngine *vm, uint8 *parameter) { // We run AGIMOUSE always as a side effect //if (getFeatures() & GF_AGIMOUSE || true) { @@ -2146,6 +2200,20 @@ void cmdPushScript(AgiGame *state, AgiEngine *vm, uint8 *parameter) { } void cmdSetPriBase(AgiGame *state, AgiEngine *vm, uint8 *parameter) { + if ((getVersion() != 0x2425) && (getVersion() < 0x2936)) { + // was only available in the 2.425 interpreter and from 2.936 (last AGI2 version) onwards + // Called during KQ3 (Apple IIgs): + // - picking up chicken (parameter = 50) + // - opening store/tavern door (parameter = 19) + // - when pirates say "Land Ho" (parameter = 16) + // - when killing the dragon (parameter = 4) + // Also called by SQ2 (Apple IIgs): + // - in Vohaul's lair (SQ2 currently gets this call through, which breaks some priority) + // TODO: Figure out what's going on + warning("set.pri.base called, although not available for current AGI version"); + return; + } + uint16 priorityBase = parameter[0]; debug(0, "Priority base set to %d", priorityBase); @@ -2159,7 +2227,7 @@ void cmdMousePosn(AgiGame *state, AgiEngine *vm, uint8 *parameter) { int16 mouseX = vm->_mouse.pos.x; int16 mouseY = vm->_mouse.pos.y; - state->_vm->adjustPosToGameScreen(mouseX, mouseY); + vm->_gfx->translateDisplayPosToGameScreen(mouseX, mouseY); vm->setVar(destVarNr1, mouseX); vm->setVar(destVarNr2, mouseY); @@ -2285,6 +2353,9 @@ int AgiEngine::runLogic(int16 logicNr) { } #endif + // Just a counter for every instruction, that got executed + _instructionCounter++; + _game.execStack.back().curIP = state->_curLogic->cIP; char st[101]; diff --git a/engines/agi/opcodes.cpp b/engines/agi/opcodes.cpp index 359d79ee4a..472917de77 100644 --- a/engines/agi/opcodes.cpp +++ b/engines/agi/opcodes.cpp @@ -343,18 +343,18 @@ AgiInstruction insV2[] = { { "div.n", "vn", &cmdDivN }, // B7 { "div.v", "vv", &cmdDivV }, // B8 { "close.window", "", &cmdCloseWindow }, // B9 - { "set.simple", "n", &cmdSetSimple }, // BA + { "set.simple", "n", &cmdSetSimple }, // BA AGI2.425+, *BUT* not included in AGI2.440 { "push.script", "", &cmdPushScript }, // BB { "pop.script", "", &cmdPopScript }, // BC { "hold.key", "", &cmdHoldKey }, // BD - { "set.pri.base", "n", &cmdSetPriBase }, // BE - { "discard.sound", "n", &cmdDiscardSound }, // BF - { "hide.mouse", "", &cmdHideMouse }, // 1 arg for AGI version 3.002.086 + { "set.pri.base", "n", &cmdSetPriBase }, // BE AGI2.936+ *AND* also inside AGI2.425 + { "discard.sound", "n", &cmdDiscardSound }, // BF was skip for PC + { "hide.mouse", "", &cmdHideMouse }, // C0 1 arg for AGI version 3.002.086 AGI3+ only starts here { "allow.menu", "n", &cmdAllowMenu }, // C1 { "show.mouse", "", &cmdShowMouse }, // C2 { "fence.mouse", "nnnn", &cmdFenceMouse }, // C3 { "mouse.posn", "vv", &cmdMousePosn }, // C4 - { "release.key", "", &cmdReleaseKey }, // 2 args for at least the Amiga GR (v2.05 1989-03-09) using AGI 2.316 + { "release.key", "", &cmdReleaseKey }, // C5 2 args for at least the Amiga GR (v2.05 1989-03-09) using AGI 2.316 { "adj.ego.move.to.xy", "", &cmdAdjEgoMoveToXY } // C6 }; diff --git a/engines/agi/palette.h b/engines/agi/palette.h index f66582b9b6..40c31da425 100644 --- a/engines/agi/palette.h +++ b/engines/agi/palette.h @@ -60,6 +60,22 @@ static const uint8 PALETTE_CGA[4 * 3] = { }; /** + * 2 color Hercules (green) palette. Using 8-bit RGB values. + */ +static const uint8 PALETTE_HERCULES_GREEN[2 * 3] = { + 0x00, 0x00, 0x00, // black + 0x00, 0xdc, 0x28 // green +}; + +/** + * 2 color Hercules (amber) palette. Using 8-bit RGB values. + */ +static const uint8 PALETTE_HERCULES_AMBER[2 * 3] = { + 0x00, 0x00, 0x00, // black + 0xdc, 0xb4, 0x00 // amber +}; + +/** * Atari ST AGI palette. * Used by all of the tested Atari ST AGI games * from Donald Duck's Playground (1986) to Manhunter II (1989). @@ -122,6 +138,9 @@ static const uint8 PALETTE_APPLE_II_GS[16 * 3] = { 0xF, 0xF, 0xF }; +// Re-use Amiga v1 palette for Apple IIgs Space Quest 1 +#define PALETTE_APPLE_II_GS_SQ1 PALETTE_AMIGA_V1 + /** * First generation Amiga & Apple IIGS AGI palette. * A 16-color, 12-bit RGB palette. diff --git a/engines/agi/picture.cpp b/engines/agi/picture.cpp index 36eb587f68..a80e811f44 100644 --- a/engines/agi/picture.cpp +++ b/engines/agi/picture.cpp @@ -394,9 +394,6 @@ void PictureMgr::drawPictureC64() { _patCode = getNextByte(); plotBrush(); break; - case 0xfb: - draw_LineShort(); - break; case 0xff: // end of data return; default: @@ -433,6 +430,9 @@ void PictureMgr::drawPictureV1() { _scrOn = true; _priOn = false; break; + case 0xfb: + draw_LineShort(); + break; case 0xff: // end of data return; default: @@ -1001,7 +1001,7 @@ void PictureMgr::clear() { void PictureMgr::showPic() { debugC(8, kDebugLevelMain, "Show picture!"); - _gfx->render_Block(0, 167, SCRIPT_WIDTH, SCRIPT_HEIGHT); + _gfx->render_Block(0, 0, SCRIPT_WIDTH, SCRIPT_HEIGHT); } /** @@ -1014,8 +1014,7 @@ void PictureMgr::showPic(int16 x, int16 y, int16 pic_width, int16 pic_height) { debugC(8, kDebugLevelMain, "Show picture!"); - // render block requires lower left coordinate! - _gfx->render_Block(x, pic_height + y - 1, pic_width, pic_height); + _gfx->render_Block(x, y, pic_width, pic_height); } void PictureMgr::showPicWithTransition() { @@ -1038,13 +1037,13 @@ void PictureMgr::showPicWithTransition() { case Common::kRenderAmiga: case Common::kRenderApple2GS: // Platform Amiga/Apple II GS -> render and do Amiga transition - _gfx->render_Block(0, 167, SCRIPT_WIDTH, SCRIPT_HEIGHT, false); + _gfx->render_Block(0, 0, SCRIPT_WIDTH, SCRIPT_HEIGHT, false); _gfx->transition_Amiga(); return; break; case Common::kRenderAtariST: // Platform Atari ST used a different transition, looks "high-res" (full 320x168) - _gfx->render_Block(0, 167, SCRIPT_WIDTH, SCRIPT_HEIGHT, false); + _gfx->render_Block(0, 0, SCRIPT_WIDTH, SCRIPT_HEIGHT, false); _gfx->transition_AtariSt(); return; default: @@ -1054,7 +1053,7 @@ void PictureMgr::showPicWithTransition() { } } - _gfx->render_Block(0, 167, SCRIPT_WIDTH, SCRIPT_HEIGHT); + _gfx->render_Block(0, 0, SCRIPT_WIDTH, SCRIPT_HEIGHT); } // preagi needed functions (for plotPattern) diff --git a/engines/agi/preagi.cpp b/engines/agi/preagi.cpp index 104b442f5d..bb5d3b8896 100644 --- a/engines/agi/preagi.cpp +++ b/engines/agi/preagi.cpp @@ -58,7 +58,7 @@ void PreAgiEngine::initialize() { initRenderMode(); _font = new GfxFont(this); - _gfx = new GfxMgr(this); + _gfx = new GfxMgr(this, _font); _picture = new PictureMgr(this, _gfx); _font->init(); @@ -112,7 +112,7 @@ void PreAgiEngine::clearScreen(int attr, bool overrideDefault) { } void PreAgiEngine::clearGfxScreen(int attr) { - _gfx->drawDisplayRect(0, 0, GFX_WIDTH - 1, IDI_MAX_ROW_PIC * 8 - 1, (attr & 0xF0) / 0x10); + _gfx->drawDisplayRect(0, 0, DISPLAY_DEFAULT_WIDTH - 1, IDI_MAX_ROW_PIC * 8 - 1, (attr & 0xF0) / 0x10); } // String functions diff --git a/engines/agi/preagi_mickey.cpp b/engines/agi/preagi_mickey.cpp index 8b1ae81b8e..620d5e0baf 100644 --- a/engines/agi/preagi_mickey.cpp +++ b/engines/agi/preagi_mickey.cpp @@ -957,7 +957,7 @@ void MickeyEngine::drawLogo() { } } - _gfx->copyDisplayRectToScreen(0, 0, DISPLAY_WIDTH, height); + _gfx->copyDisplayToScreen(); delete[] fileBuffer; } diff --git a/engines/agi/saveload.cpp b/engines/agi/saveload.cpp index 1bf0dbcc02..0658609cd0 100644 --- a/engines/agi/saveload.cpp +++ b/engines/agi/saveload.cpp @@ -45,23 +45,25 @@ #include "agi/systemui.h" #include "agi/words.h" -#define SAVEGAME_CURRENT_VERSION 8 +#define SAVEGAME_CURRENT_VERSION 11 // -// Version 0 (Sarien): view table has 64 entries -// Version 1 (Sarien): view table has 256 entries (needed in KQ3) -// Version 2 (ScummVM): first ScummVM version -// Version 3 (ScummVM): added AGIPAL save/load support -// Version 4 (ScummVM): added thumbnails and save creation date/time -// Version 5 (ScummVM): Added game md5 -// Version 6 (ScummVM): Added game played time -// Version 7 (ScummVM): Added controller key mappings -// required for some games for quick-loading from ScummVM main menu -// for games, that do not set all key mappings right at the start -// Added automatic save data (for command SetSimple) -// Version 8 (ScummVM): Added Hold-Key-Mode boolean -// required for at least Mixed Up Mother Goose -// gets set at the start of the game only +// Version 0 (Sarien): view table has 64 entries +// Version 1 (Sarien): view table has 256 entries (needed in KQ3) +// Version 2 (ScummVM): first ScummVM version +// Version 3 (ScummVM): added AGIPAL save/load support +// Version 4 (ScummVM): added thumbnails and save creation date/time +// Version 5 (ScummVM): Added game md5 +// Version 6 (ScummVM): Added game played time +// Version 7 (ScummVM): Added controller key mappings +// required for some games for quick-loading from ScummVM main menu +// for games, that do not set all key mappings right at the start +// Added automatic save data (for command SetSimple) +// Version 8 (ScummVM): Added Hold-Key-Mode boolean +// required for at least Mixed Up Mother Goose +// gets set at the start of the game only +// Version 9 (ScummVM): Added seconds to saved game time stamp +// Version 10 (ScummVM): Added priorityTableSet boolean namespace Agi { @@ -109,6 +111,8 @@ int AgiEngine::saveGame(const Common::String &fileName, const Common::String &de debugC(5, kDebugLevelMain | kDebugLevelSavegame, "Writing save date (%d)", saveDate); out->writeUint16BE(saveTime); debugC(5, kDebugLevelMain | kDebugLevelSavegame, "Writing save time (%d)", saveTime); + // Version 9+: save seconds of current time as well + out->writeByte(curTime.tm_sec & 0xFF); out->writeUint32BE(playTime); debugC(5, kDebugLevelMain | kDebugLevelSavegame, "Writing play time (%d)", playTime); @@ -171,9 +175,11 @@ int AgiEngine::saveGame(const Common::String &fileName, const Common::String &de out->writeSint16BE(0); } - // TODO: save if priority table was modified for (i = 0; i < SCRIPT_HEIGHT; i++) - out->writeByte(_gfx->priorityFromY(i)); + out->writeByte(_gfx->saveLoadGetPriority(i)); + + // Version 10+: Save, if priority table got modified (set.pri.base opcode) + out->writeSint16BE((int16)_gfx->saveLoadWasPriorityTableModified()); out->writeSint16BE((int16)_game.gfxMode); out->writeByte(_text->inputGetCursorChar()); @@ -260,6 +266,8 @@ int AgiEngine::saveGame(const Common::String &fileName, const Common::String &de out->writeByte(screenObj->motionType); out->writeByte(screenObj->cycle); + // Version 11+: loop_flag, was saved previously under vt.parm1 + out->writeByte(screenObj->loop_flag); out->writeByte(screenObj->priority); out->writeUint16BE(screenObj->flags); @@ -338,6 +346,7 @@ int AgiEngine::loadGame(const Common::String &fileName, bool checkId) { int16 parm[7]; Common::InSaveFile *in; bool totalPlayTimeWasSet = false; + byte oldLoopFlag = 0; debugC(3, kDebugLevelMain | kDebugLevelSavegame, "AgiEngine::loadGame(%s)", fileName.c_str()); @@ -384,7 +393,10 @@ int AgiEngine::loadGame(const Common::String &fileName, bool checkId) { Graphics::skipThumbnail(*in); in->readUint32BE(); // save date - in->readUint16BE(); // save time + in->readUint16BE(); // save time (hour + minute) + if (saveVersion >= 9) { + in->readByte(); // save time seconds + } if (saveVersion >= 6) { uint32 playTime = in->readUint32BE(); inGameTimerReset(playTime * 1000); @@ -494,7 +506,21 @@ int AgiEngine::loadGame(const Common::String &fileName, bool checkId) { } for (i = 0; i < SCRIPT_HEIGHT; i++) - _gfx->setPriority(i, in->readByte()); + _gfx->saveLoadSetPriority(i, in->readByte()); + + if (saveVersion >= 10) { + // Version 10+: priority table was modified by scripts + int16 priorityTableWasModified = in->readSint16BE(); + + if (priorityTableWasModified) { + _gfx->saveLoadSetPriorityTableModifiedBool(true); + } else { + _gfx->saveLoadSetPriorityTableModifiedBool(false); + } + } else { + // Try to figure it out by ourselves + _gfx->saveLoadFigureOutPriorityTableModifiedBool(); + } _text->closeWindow(); @@ -611,6 +637,10 @@ int AgiEngine::loadGame(const Common::String &fileName, bool checkId) { screenObj->motionType = (MotionType)in->readByte(); screenObj->cycle = (CycleType)in->readByte(); + if (saveVersion >= 11) { + // Version 11+: loop_flag, was previously vt.parm1 + screenObj->loop_flag = in->readByte(); + } screenObj->priority = in->readByte(); screenObj->flags = in->readUint16BE(); @@ -618,7 +648,7 @@ int AgiEngine::loadGame(const Common::String &fileName, bool checkId) { // this was done so that saved games compatibility isn't broken switch (screenObj->motionType) { case kMotionNormal: - in->readByte(); + oldLoopFlag = in->readByte(); in->readByte(); in->readByte(); in->readByte(); @@ -628,12 +658,14 @@ int AgiEngine::loadGame(const Common::String &fileName, bool checkId) { in->readByte(); in->readByte(); in->readByte(); + oldLoopFlag = screenObj->wander_count; break; case kMotionFollowEgo: screenObj->follow_stepSize = in->readByte(); screenObj->follow_flag = in->readByte(); screenObj->follow_count = in->readByte(); in->readByte(); + oldLoopFlag = screenObj->follow_stepSize; break; case kMotionEgo: case kMotionMoveObj: @@ -641,10 +673,21 @@ int AgiEngine::loadGame(const Common::String &fileName, bool checkId) { screenObj->move_y = in->readByte(); screenObj->move_stepSize = in->readByte(); screenObj->move_flag = in->readByte(); + oldLoopFlag = screenObj->move_x; break; default: error("unknown motion-type"); } + if (saveVersion < 11) { + if (saveVersion < 7) { + // Recreate loop_flag from motion-type (was previously vt.parm1) + // vt.parm1 was shared for multiple uses + screenObj->loop_flag = oldLoopFlag; + } else { + // for Version 7-10 we can't really do anything, it was not saved + screenObj->loop_flag = 0; // set it to 0 + } + } } for (i = vtEntries; i < SCREENOBJECTS_MAX; i++) { memset(&_game.screenObjTable[i], 0, sizeof(ScreenObjEntry)); @@ -693,7 +736,7 @@ int AgiEngine::loadGame(const Common::String &fileName, bool checkId) { _words->clearEgoWords(); // don't delay anything right after restoring a game - nonBlockingText_Forget(); + artificialDelay_Reset(); _sprites->eraseSprites(); _sprites->buildAllSpriteLists(); @@ -704,7 +747,7 @@ int AgiEngine::loadGame(const Common::String &fileName, bool checkId) { _text->promptRedraw(); // copy everything over (we should probably only copy over the remaining parts of the screen w/o play screen - _gfx->copyDisplayRectToScreen(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT); + _gfx->copyDisplayToScreen(); // Sync volume settings from ScummVM system settings, so that VM volume variable is overwritten setVolumeViaSystemSetting(); @@ -813,7 +856,7 @@ Common::String AgiEngine::getSavegameFilename(int16 slotId) const { return saveLoadSlot; } -bool AgiEngine::getSavegameInformation(int16 slotId, Common::String &saveDescription, uint32 &saveDate, uint16 &saveTime, bool &saveIsValid) { +bool AgiEngine::getSavegameInformation(int16 slotId, Common::String &saveDescription, uint32 &saveDate, uint32 &saveTime, bool &saveIsValid) { Common::InSaveFile *in; Common::String fileName = getSavegameFilename(slotId); char saveGameDescription[31]; @@ -875,7 +918,10 @@ bool AgiEngine::getSavegameInformation(int16 slotId, Common::String &saveDescrip Graphics::skipThumbnail(*in); saveDate = in->readUint32BE(); - saveTime = in->readUint16BE(); + saveTime = in->readUint16BE() << 8; + if (saveVersion >= 9) { + saveTime |= in->readByte(); // add seconds (only available since saved game version 9+) + } // save date is DDMMYYYY, we need a proper format byte saveDateDay = saveDate >> 24; diff --git a/engines/agi/sound_2gs.cpp b/engines/agi/sound_2gs.cpp index b33591343a..b1bcee3920 100644 --- a/engines/agi/sound_2gs.cpp +++ b/engines/agi/sound_2gs.cpp @@ -482,6 +482,8 @@ static bool convertWave(Common::SeekableReadStream &source, int8 *dest, uint len IIgsSample::IIgsSample(uint8 *data, uint32 len, int16 resourceNr) : AgiSound() { Common::MemoryReadStream stream(data, len, DisposeAfterUse::YES); + _sample = nullptr; + // Check that the header was read ok and that it's of the correct type if (_header.read(stream) && _header.type == AGI_SOUND_SAMPLE) { // An Apple IIGS AGI sample resource uint32 sampleStartPos = stream.pos(); diff --git a/engines/agi/sprite.cpp b/engines/agi/sprite.cpp index 74f20c1d22..8263ea12ac 100644 --- a/engines/agi/sprite.cpp +++ b/engines/agi/sprite.cpp @@ -106,6 +106,28 @@ void SpritesMgr::buildSpriteListAdd(uint16 givenOrderNr, ScreenObjEntry *screenO spriteEntry.yPos = (screenObj->yPos) - (screenObj->ySize) + 1; spriteEntry.xSize = screenObj->xSize; spriteEntry.ySize = screenObj->ySize; + + // Checking, if xPos/yPos/right/bottom are valid and do not go outside of playscreen (visual screen) + // Original AGI did not do this (but it then resulted in memory corruption) + if (spriteEntry.xPos < 0) { + warning("buildSpriteListAdd(): ignoring screen obj %d, b/c xPos (%d) < 0", screenObj->objectNr, spriteEntry.xPos); + return; + } + if (spriteEntry.yPos < 0) { + warning("buildSpriteListAdd(): ignoring screen obj %d, b/c yPos (%d) < 0", screenObj->objectNr, spriteEntry.yPos); + return; + } + int16 xRight = spriteEntry.xPos + spriteEntry.xSize; + if (xRight > SCRIPT_HEIGHT) { + warning("buildSpriteListAdd(): ignoring screen obj %d, b/c rightPos (%d) > %d", screenObj->objectNr, xRight, SCRIPT_WIDTH); + return; + } + int16 yBottom = spriteEntry.yPos + spriteEntry.ySize; + if (yBottom > SCRIPT_HEIGHT) { + warning("buildSpriteListAdd(): ignoring screen obj %d, b/c bottomPos (%d) > %d", screenObj->objectNr, yBottom, SCRIPT_HEIGHT); + return; + } + // warning("list-add: %d, %d, original yPos: %d, ySize: %d", spriteEntry.xPos, spriteEntry.yPos, screenObj->yPos, screenObj->ySize); spriteEntry.backgroundBuffer = (uint8 *)malloc(spriteEntry.xSize * spriteEntry.ySize * 2); // for visual + priority data assert(spriteEntry.backgroundBuffer); @@ -332,7 +354,8 @@ void SpritesMgr::showSprite(ScreenObjEntry *screenObj) { } // render this block - _gfx->render_Block(x, y, width, height); + int16 upperY = y - height + 1; + _gfx->render_Block(x, upperY, width, height); } void SpritesMgr::showSprites(SpriteList &spriteList) { @@ -394,7 +417,7 @@ void SpritesMgr::showObject(int16 viewNr) { screenObj.yPos_prev = SCRIPT_HEIGHT - 1; screenObj.yPos = screenObj.yPos_prev; screenObj.priority = 15; - screenObj.flags |= fFixedPriority; + screenObj.flags = fFixedPriority; // Original AGI did "| fFixedPriority" on uninitialized memory screenObj.objectNr = 255; // ??? backgroundBuffer = (uint8 *)malloc(screenObj.xSize * screenObj.ySize * 2); // for visual + priority data diff --git a/engines/agi/systemui.cpp b/engines/agi/systemui.cpp index 2dd6629103..aeb1ded4a2 100644 --- a/engines/agi/systemui.cpp +++ b/engines/agi/systemui.cpp @@ -38,10 +38,14 @@ SystemUI::SystemUI(AgiEngine *vm, GfxMgr *gfx, TextMgr *text) { _askForVerificationMouseLockedButtonNr = -1; _askForVerificationMouseActiveButtonNr = -1; + clearSavedGameSlots(); + _textStatusScore = "Score:%v3 of %v7"; _textStatusSoundOn = "Sound:on"; _textStatusSoundOff = "Sound:off"; + _textEnterCommand = "Enter input\n\n"; + _textPause = " Game paused.\nPress Enter to continue."; _textPauseButton = nullptr; @@ -212,6 +216,47 @@ const char *SystemUI::getInventoryTextReturnToGame() { return _textInventoryReturnToGame; } +bool SystemUI::askForCommand(Common::String &commandText) { + // Let user enter the command (this was originally only available for Hercules rendering, we allow it everywhere) + bool previousEditState = _text->inputGetEditStatus(); + byte previousEditCursor = _text->inputGetCursorChar(); + + _text->drawMessageBox(_textEnterCommand, 0, 36, true); + + _text->inputEditOn(); + + _text->charPos_Push(); + _text->charAttrib_Push(); + + _text->charPos_SetInsideWindow(2, 0); + _text->charAttrib_Set(15, 0); + _text->clearBlockInsideWindow(2, 0, 36, 0); // input line is supposed to be black + _text->inputSetCursorChar('_'); + + _text->stringSet(commandText.c_str()); // Set current command text (may be a command recall) + + _vm->cycleInnerLoopActive(CYCLE_INNERLOOP_GETSTRING); + _text->stringEdit(35); // only allow up to 35 characters + + _text->charAttrib_Pop(); + _text->charPos_Pop(); + _text->inputSetCursorChar(previousEditCursor); + if (!previousEditState) { + _text->inputEditOff(); + } + + _text->closeWindow(); + + if (!_text->stringWasEntered()) { + // User cancelled? exit now + return false; + } + + commandText.clear(); + commandText += (char *)_text->_inputString; + return true; +} + int16 SystemUI::figureOutAutomaticSaveGameSlot(const char *automaticSaveDescription) { int16 matchedGameSlotId = -1; int16 freshGameSlotId = -1; @@ -327,7 +372,7 @@ int16 SystemUI::askForRestoreGameSlot() { int16 restoreGameSlotNr = -1; // Fill saved game slot cache - readSavedGameSlots(true, true); // filter empty/corrupt slots, but including auto-save slot + readSavedGameSlots(true, true); // filter empty/corrupt slots, but include auto-save slot if (_savedGameArray.size() == 0) { // no saved games @@ -526,12 +571,12 @@ void SystemUI::readSavedGameSlots(bool filterNonexistant, bool withAutoSaveSlot) SystemUISavedGameEntry savedGameEntry; Common::String saveDescription; uint32 saveDate = 0; - uint16 saveTime = 0; + uint32 saveTime = 0; bool saveIsValid = false; int16 mostRecentSlotNr = -1; uint32 mostRecentSlotSaveDate = 0; - uint16 mostRecentSlotSaveTime = 0; + uint32 mostRecentSlotSaveTime = 0; clearSavedGameSlots(); @@ -638,6 +683,9 @@ void SystemUI::readSavedGameSlots(bool filterNonexistant, bool withAutoSaveSlot) void SystemUI::figureOutAutomaticSavedGameSlot(const char *automaticSaveDescription, int16 &matchedGameSlotId, int16 &freshGameSlotId) { bool foundFresh = false; + matchedGameSlotId = -1; + freshGameSlotId = -1; + for (uint16 slotNr = 0; slotNr < _savedGameArray.size(); slotNr++) { SystemUISavedGameEntry *savedGameEntry = &_savedGameArray[slotNr]; @@ -653,7 +701,7 @@ void SystemUI::figureOutAutomaticSavedGameSlot(const char *automaticSaveDescript // no new slot found yet if (!savedGameEntry->exists) { // and current slot doesn't exist - if (slotNr) { + if (savedGameEntry->slotId) { // and slot is not the auto-save slot -> remember this slot freshGameSlotId = savedGameEntry->slotId; foundFresh = true; @@ -746,21 +794,23 @@ bool SystemUI::askForVerification(const char *verifyText, const char *button1Tex // Buttons enabled, calculate button coordinates int16 msgBoxX = 0, msgBoxY = 0, msgBoxLowerY = 0; int16 msgBoxWidth = 0, msgBoxHeight = 0; + int16 fontHeight = _gfx->getDisplayFontHeight(); + int16 fontWidth = _gfx->getDisplayFontWidth(); _text->getMessageBoxInnerDisplayDimensions(msgBoxX, msgBoxY, msgBoxWidth, msgBoxHeight); - // Adjust Y coordinate to lower edge + // Calculate lower Y msgBoxLowerY = msgBoxY + (msgBoxHeight - 1); buttonEntry.active = false; if (button1Text) { buttonEntry.text = button1Text; - buttonEntry.textWidth = strlen(button1Text) * FONT_DISPLAY_WIDTH; + buttonEntry.textWidth = strlen(button1Text) * _gfx->getDisplayFontWidth(); buttonEntry.isDefault = true; _buttonArray.push_back(buttonEntry); } if (button2Text) { buttonEntry.text = button2Text; - buttonEntry.textWidth = strlen(button2Text) * FONT_DISPLAY_WIDTH; + buttonEntry.textWidth = strlen(button2Text) * _gfx->getDisplayFontWidth(); buttonEntry.isDefault = false; _buttonArray.push_back(buttonEntry); } @@ -768,37 +818,30 @@ bool SystemUI::askForVerification(const char *verifyText, const char *button1Tex // Render-Mode specific calculations switch (_vm->_renderMode) { case Common::kRenderApple2GS: - _buttonArray[0].rect = Common::Rect(14 + _buttonArray[0].textWidth, FONT_DISPLAY_HEIGHT + 6); - _buttonArray[0].rect.moveTo(msgBoxX + 2, msgBoxLowerY - (8 + FONT_DISPLAY_HEIGHT + 2)); - + _buttonArray[0].rect = createRect(msgBoxX, +2, msgBoxLowerY - fontHeight, -(8 + 2), _buttonArray[0].textWidth, +14, fontHeight, +6); + if (_buttonArray.size() > 1) { - int16 adjustedX = msgBoxX + msgBoxWidth - 10; - _buttonArray[1].rect = Common::Rect(14 + _buttonArray[1].textWidth, FONT_DISPLAY_HEIGHT + 6); - adjustedX -= _buttonArray[1].rect.width(); - _buttonArray[1].rect.moveTo(adjustedX, msgBoxLowerY - (8 + FONT_DISPLAY_HEIGHT + 2)); + int16 adjustedX = msgBoxX + msgBoxWidth - _buttonArray[1].textWidth; // - 10; + _buttonArray[1].rect = createRect(adjustedX, -(14 + 10), _buttonArray[0].rect.top, 0, _buttonArray[1].textWidth, +14, fontHeight, +6); } break; - case Common::kRenderAmiga: - _buttonArray[0].rect = Common::Rect(4 + _buttonArray[0].textWidth + 4, 2 + FONT_DISPLAY_HEIGHT + 2); - _buttonArray[0].rect.moveTo(msgBoxX, msgBoxLowerY - _buttonArray[0].rect.height()); + case Common::kRenderAmiga: { + _buttonArray[0].rect = createRect(msgBoxX, 0, msgBoxLowerY - fontHeight, -(2 + 2), _buttonArray[0].textWidth, +(4 + 4), fontHeight, +(2 + 2)); if (_buttonArray.size() > 1) { - int16 adjustedX = msgBoxX + msgBoxWidth; - _buttonArray[1].rect = Common::Rect(4 + _buttonArray[1].textWidth + 4, 2 + FONT_DISPLAY_HEIGHT + 2); - adjustedX -= _buttonArray[1].rect.width(); - _buttonArray[1].rect.moveTo(adjustedX, msgBoxLowerY - _buttonArray[1].rect.height()); + int16 adjustedX = msgBoxX + msgBoxWidth - _buttonArray[1].textWidth; + _buttonArray[1].rect = createRect(adjustedX, -(4 + 4), _buttonArray[0].rect.top, 0, _buttonArray[1].textWidth, +(4 + 4), fontHeight, +(2 + 2)); } break; + } case Common::kRenderAtariST: - _buttonArray[0].rect = Common::Rect(_buttonArray[0].textWidth, FONT_DISPLAY_HEIGHT); - _buttonArray[0].rect.moveTo(msgBoxX + (5 * FONT_DISPLAY_WIDTH), msgBoxLowerY - FONT_DISPLAY_HEIGHT); + _buttonArray[0].rect = createRect(msgBoxX + (5 * fontWidth), 0, msgBoxLowerY - fontHeight, 0, _buttonArray[0].textWidth, 0, fontHeight, 0); + if (_buttonArray.size() > 1) { - int16 adjustedX = msgBoxX + msgBoxWidth - (5 * FONT_DISPLAY_WIDTH); - _buttonArray[1].rect = Common::Rect(_buttonArray[1].textWidth, FONT_DISPLAY_HEIGHT); - adjustedX -= _buttonArray[1].rect.width(); - _buttonArray[1].rect.moveTo(adjustedX, msgBoxLowerY - _buttonArray[1].rect.height()); + int16 adjustedX = msgBoxX + msgBoxWidth - (5 * fontWidth + _buttonArray[1].textWidth); + _buttonArray[1].rect = createRect(adjustedX, 0, _buttonArray[0].rect.top, 0, _buttonArray[1].textWidth, 0, fontHeight, 0); } break; @@ -946,6 +989,25 @@ void SystemUI::askForVerificationKeyPress(uint16 newKey) { } } +Common::Rect SystemUI::createRect(int16 x, int16 adjX, int16 y, int16 adjY, int16 width, int16 adjWidth, int16 height, int16 adjHeight) { + switch (_gfx->getUpscaledHires()) { + case DISPLAY_UPSCALED_DISABLED: + break; + case DISPLAY_UPSCALED_640x400: + adjX *= 2; adjY *= 2; + adjWidth *= 2; adjHeight *= 2; + break; + default: + assert(0); + break; + } + x += adjX; y += adjY; + width += adjWidth; height += adjHeight; + Common::Rect newRect(width, height); + newRect.moveTo(x, y); + return newRect; +} + #define SYSTEMUI_BUTTONEDGE_APPLEIIGS_WIDTH 8 #define SYSTEMUI_BUTTONEDGE_APPLEIIGS_HEIGHT 5 @@ -993,20 +1055,20 @@ void SystemUI::drawButtonAppleIIgs(SystemUIButtonEntry *button) { } // draw base box for it - _gfx->drawDisplayRect(button->rect.left, button->rect.bottom - 1, button->rect.width(), button->rect.height(), backgroundColor, false); + _gfx->drawDisplayRect(button->rect.left, button->rect.top, button->rect.width(), button->rect.height(), backgroundColor, false); // draw inner lines - _gfx->drawDisplayRect(button->rect.left + 1, button->rect.top - 1, button->rect.width() - 2, 1, 0, false); // upper horizontal - _gfx->drawDisplayRect(button->rect.left - 2, button->rect.bottom - 2, 2, button->rect.height() - 2, 0, false); // left vertical - _gfx->drawDisplayRect(button->rect.right, button->rect.bottom - 2, 2, button->rect.height() - 2, 0, false); // right vertical - _gfx->drawDisplayRect(button->rect.left + 1, button->rect.bottom, button->rect.width() - 2, 1, 0, false); // lower horizontal + _gfx->drawDisplayRect(button->rect.left, +1, button->rect.top, -1, button->rect.width(), -2, 0, 1, 0, false); // lower horizontal + _gfx->drawDisplayRect(button->rect.left, -2, button->rect.top, +1, 0, 2, button->rect.height(), -2, 0, false); // left vertical + _gfx->drawDisplayRect(button->rect.right, 0, button->rect.top, +1, 0, 2, button->rect.height(), -2, 0, false); // right vertical + _gfx->drawDisplayRect(button->rect.left, +1, button->rect.bottom, 0, button->rect.width(), -2, 0, 1, 0, false); // upper horizontal if (button->isDefault) { // draw outer lines - _gfx->drawDisplayRect(button->rect.left, button->rect.top - 3, button->rect.width(), 1, 0, false); // upper horizontal - _gfx->drawDisplayRect(button->rect.left - 5, button->rect.bottom - 2, 2, button->rect.height() - 2, 0, false); // left vertical - _gfx->drawDisplayRect(button->rect.right + 3, button->rect.bottom - 2, 2, button->rect.height() - 2, 0, false); // right vertical - _gfx->drawDisplayRect(button->rect.left, button->rect.bottom + 2, button->rect.width(), 1, 0, false); // lower horizontal + _gfx->drawDisplayRect(button->rect.left, 0, button->rect.top, -3, button->rect.width(), 0, 0, 1, 0, false); // upper horizontal + _gfx->drawDisplayRect(button->rect.left, -5, button->rect.top, +2, 0, 2, button->rect.height(), -2, 0, false); // left vertical + _gfx->drawDisplayRect(button->rect.right, +3, button->rect.top, +2, 0, 2, button->rect.height(), -2, 0, false); // right vertical + _gfx->drawDisplayRect(button->rect.left, 0, button->rect.bottom, +2, button->rect.width(), 0, 0, 1, 0, false); // lower horizontal if (button->active) edgeBitmap = buttonEdgeAppleIIgsDefaultActive; @@ -1021,18 +1083,18 @@ void SystemUI::drawButtonAppleIIgs(SystemUIButtonEntry *button) { } // draw edge graphics - drawButtonAppleIIgsEdgePixels(button->rect.left - 5, button->rect.top - 3, edgeBitmap, false, false); - drawButtonAppleIIgsEdgePixels(button->rect.right + 4, button->rect.top - 3, edgeBitmap, true, false); - drawButtonAppleIIgsEdgePixels(button->rect.left - 5, button->rect.bottom + 2, edgeBitmap, false, true); - drawButtonAppleIIgsEdgePixels(button->rect.right + 4, button->rect.bottom + 2, edgeBitmap, true, true); + drawButtonAppleIIgsEdgePixels(button->rect.left, -5, button->rect.top, -3, edgeBitmap, false, false); + drawButtonAppleIIgsEdgePixels(button->rect.right, +4, button->rect.top, -3, edgeBitmap, true, false); + drawButtonAppleIIgsEdgePixels(button->rect.left, -5, button->rect.bottom, +2, edgeBitmap, false, true); + drawButtonAppleIIgsEdgePixels(button->rect.right, +4, button->rect.bottom, +2, edgeBitmap, true, true); // Button text - _gfx->drawStringOnDisplay(button->rect.left + 7, button->rect.top + 3, button->text, foregroundColor, backgroundColor); + _gfx->drawStringOnDisplay(button->rect.left, +7, button->rect.top, +3, button->text, foregroundColor, backgroundColor); - _gfx->copyDisplayRectToScreen(button->rect.left - 5, button->rect.top - 3, button->rect.width() + 10, button->rect.height() + 6); + _gfx->copyDisplayRectToScreen(button->rect.left, -5, button->rect.top, -3, button->rect.width(), +10, button->rect.height(), +6); } -void SystemUI::drawButtonAppleIIgsEdgePixels(int16 x, int16 y, byte *edgeBitmap, bool mirrored, bool upsideDown) { +void SystemUI::drawButtonAppleIIgsEdgePixels(int16 x, int16 adjX, int16 y, int16 adjY, byte *edgeBitmap, bool mirrored, bool upsideDown) { int8 directionY = upsideDown ? -1 : +1; int8 directionX = mirrored ? -1 : +1; int8 curY = 0; @@ -1050,9 +1112,9 @@ void SystemUI::drawButtonAppleIIgsEdgePixels(int16 x, int16 y, byte *edgeBitmap, while (widthLeft) { if (curBitmapByte & curBitmapBit) { - _gfx->putPixelOnDisplay(x + curX, y + curY, 0); + _gfx->putPixelOnDisplay(x, adjX + curX, y, adjY + curY, 0); } else { - _gfx->putPixelOnDisplay(x + curX, y + curY, 15); + _gfx->putPixelOnDisplay(x, adjX + curX, y, adjY + curY, 15); } curBitmapBit = curBitmapBit >> 1; @@ -1087,12 +1149,11 @@ void SystemUI::drawButtonAmiga(SystemUIButtonEntry *button) { } // draw base box for it - _gfx->drawDisplayRect(button->rect.left, button->rect.bottom - 1, button->rect.width(), button->rect.height(), backgroundColor, false); + _gfx->drawDisplayRect(button->rect.left, button->rect.top, button->rect.width(), button->rect.height(), backgroundColor, false); // Button text - _gfx->drawStringOnDisplay(button->rect.left + 4, button->rect.top + 2, button->text, foregroundColor, backgroundColor); + _gfx->drawStringOnDisplay(button->rect.left, +4, button->rect.top, +2, button->text, foregroundColor, backgroundColor); - // draw base box for it _gfx->copyDisplayRectToScreen(button->rect.left, button->rect.top, button->rect.width(), button->rect.height()); } diff --git a/engines/agi/systemui.h b/engines/agi/systemui.h index ceb78935eb..283de8794c 100644 --- a/engines/agi/systemui.h +++ b/engines/agi/systemui.h @@ -77,6 +77,8 @@ public: const char *getInventoryTextSelectItems(); const char *getInventoryTextReturnToGame(); + bool askForCommand(Common::String &commandText); + int16 figureOutAutomaticSaveGameSlot(const char *automaticSaveDescription); int16 figureOutAutomaticRestoreGameSlot(const char *automaticSaveDescription); @@ -107,9 +109,12 @@ private: private: SystemUIButtonArray _buttonArray; + Common::Rect createRect(int16 x, int16 adjX, int16 y, int16 adjY, int16 width, int16 adjWidth, int16 height, int16 adjHeight); + //void moveRect(int16 x, int16 adjX, int16 y, int16 adjY); + void drawButton(SystemUIButtonEntry *button); void drawButtonAppleIIgs(SystemUIButtonEntry *buttonEntry); - void drawButtonAppleIIgsEdgePixels(int16 x, int16 y, byte *edgeBitmap, bool mirrored, bool upsideDown); + void drawButtonAppleIIgsEdgePixels(int16 x, int16 adjX, int16 y, int16 adjY, byte *edgeBitmap, bool mirrored, bool upsideDown); void drawButtonAmiga(SystemUIButtonEntry *buttonEntry); void drawButtonAtariST(SystemUIButtonEntry *buttonEntry); @@ -127,6 +132,8 @@ private: const char *_textStatusSoundOn; const char *_textStatusSoundOff; + const char *_textEnterCommand; + const char *_textPause; const char *_textPauseButton; const char *_textRestart; diff --git a/engines/agi/text.cpp b/engines/agi/text.cpp index 965de69335..0cacce2421 100644 --- a/engines/agi/text.cpp +++ b/engines/agi/text.cpp @@ -20,6 +20,7 @@ * */ +#include "common/config-manager.h" #include "agi/agi.h" #include "agi/sprite.h" // for commit_both() #include "agi/graphics.h" @@ -66,6 +67,7 @@ TextMgr::TextMgr(AgiEngine *vm, Words *words, GfxMgr *gfx) { _inputStringRow = 0; _inputStringColumn = 0; + _inputStringEntered = false; _inputStringMaxLen = 0; _inputStringCursorPos = 0; _inputString[0] = 0; @@ -73,6 +75,12 @@ TextMgr::TextMgr(AgiEngine *vm, Words *words, GfxMgr *gfx) { configureScreen(2); _messageBoxCancelled = false; + + _optionCommandPromptWindow = false; + + if (ConfMan.getBool("commandpromptwindow")) { + _optionCommandPromptWindow = true; + } } TextMgr::~TextMgr() { @@ -87,7 +95,7 @@ void TextMgr::configureScreen(uint16 row_Min) { _window_Row_Max = row_Min + 21; // forward data to GfxMgr as well - _gfx->setRenderStartOffset(row_Min * FONT_DISPLAY_HEIGHT); + _gfx->setRenderStartOffset(row_Min * FONT_VISUAL_HEIGHT); } uint16 TextMgr::getWindowRowMin() { return _window_Row_Min; @@ -169,7 +177,7 @@ void TextMgr::charAttrib_Set(byte foreground, byte background) { if (background) { _textAttrib.combinedForeground = 3; _textAttrib.combinedBackground = 8; // enable invert of colors - } else if (foreground > 14) { + } else { if (foreground > 14) { _textAttrib.combinedForeground = 3; } else { @@ -178,6 +186,16 @@ void TextMgr::charAttrib_Set(byte foreground, byte background) { _textAttrib.combinedBackground = 0; } break; + case Common::kRenderHercA: + case Common::kRenderHercG: + if (background) { + _textAttrib.combinedForeground = 0; + _textAttrib.combinedBackground = 1; + } else { + _textAttrib.combinedForeground = 1; + _textAttrib.combinedBackground = 0; + } + break; default: // EGA-handling: if (background) { @@ -465,7 +483,8 @@ void TextMgr::drawMessageBox(const char *textPtr, int16 forcedHeight, int16 want _messageState.backgroundSize_Width = (_messageState.textSize_Width * FONT_VISUAL_WIDTH) + 10; _messageState.backgroundSize_Height = (_messageState.textSize_Height * FONT_VISUAL_HEIGHT) + 10; _messageState.backgroundPos_x = (_messageState.textPos.column * FONT_VISUAL_WIDTH) - 5; - _messageState.backgroundPos_y = (_messageState.textPos_Edge.row - _window_Row_Min + 1) * FONT_VISUAL_HEIGHT + 4; + _messageState.backgroundPos_y = (startingRow * FONT_VISUAL_HEIGHT) - 5; + // original AGI used lowerY here, calculated using (_messageState.textPos_Edge.row - _window_Row_Min + 1) * FONT_VISUAL_HEIGHT + 4; // Hardcoded colors: white background and red lines _gfx->drawBox(_messageState.backgroundPos_x, _messageState.backgroundPos_y, _messageState.backgroundSize_Width, _messageState.backgroundSize_Height, 15, 4); @@ -486,10 +505,11 @@ void TextMgr::getMessageBoxInnerDisplayDimensions(int16 &x, int16 &y, int16 &wid if (!_messageState.window_Active) return; - y = _messageState.textPos.row * FONT_DISPLAY_HEIGHT; - x = _messageState.textPos.column * FONT_DISPLAY_WIDTH; - width = _messageState.textSize_Width * FONT_DISPLAY_WIDTH; - height = _messageState.textSize_Height * FONT_DISPLAY_HEIGHT; + y = _messageState.textPos.row; + x = _messageState.textPos.column; + width = _messageState.textSize_Width; + height = _messageState.textSize_Height; + _gfx->translateFontRectToDisplayScreen(x, y, width, height); } bool TextMgr::isMouseWithinMessageBox() { @@ -498,10 +518,10 @@ bool TextMgr::isMouseWithinMessageBox() { int16 mouseX = _vm->_mouse.pos.x; if (_messageState.window_Active) { - _vm->adjustPosToGameScreen(mouseX, mouseY); + _gfx->translateDisplayPosToGameScreen(mouseX, mouseY); - if ((mouseX >= _messageState.backgroundPos_x) && (mouseX <= (_messageState.backgroundPos_x + _messageState.backgroundSize_Width))) { - if ((mouseY >= _messageState.backgroundPos_y - _messageState.backgroundSize_Height) && (mouseY <= (_messageState.backgroundPos_y))) { + if ((mouseX >= _messageState.backgroundPos_x) && (mouseX < (_messageState.backgroundPos_x + _messageState.backgroundSize_Width))) { + if ((mouseY >= _messageState.backgroundPos_y) && (mouseY < (_messageState.backgroundPos_y + _messageState.backgroundSize_Height))) { return true; } } @@ -580,12 +600,12 @@ void TextMgr::clearBlock(int16 row_Upper, int16 column_Upper, int16 row_Lower, i charPos_Clip(row_Upper, column_Upper); charPos_Clip(row_Lower, column_Lower); - int16 x = column_Upper * FONT_DISPLAY_WIDTH; - int16 y = row_Upper * FONT_DISPLAY_HEIGHT; - int16 width = (column_Lower + 1 - column_Upper) * FONT_DISPLAY_WIDTH; - int16 height = (row_Lower + 1 - row_Upper) * FONT_DISPLAY_HEIGHT; + int16 x = column_Upper; + int16 y = row_Upper; + int16 width = (column_Lower + 1 - column_Upper); + int16 height = (row_Lower + 1 - row_Upper); + _gfx->translateFontRectToDisplayScreen(x, y, width, height); - y = y + height - 1; // drawDisplayRect wants lower Y-coordinate _gfx->drawDisplayRect(x, y, width, height, color); } @@ -658,6 +678,30 @@ void TextMgr::promptKeyPress(uint16 newKey) { int16 maxChars = 0; int16 scriptsInputLen = _vm->getVar(VM_VAR_MAX_INPUT_CHARACTERS); + bool acceptableInput = false; + + // FEATURE: Sierra didn't check for valid characters (filtered out umlauts etc.) + // In text-mode this sort of worked at least with the DOS interpreter + // but as soon as invalid characters were used in graphics mode they weren't properly shown + switch (_vm->getLanguage()) { + case Common::RU_RUS: + if (newKey >= 0x20) + acceptableInput = true; + break; + default: + if ((newKey >= 0x20) && (newKey <= 0x7f)) + acceptableInput = true; + break; + } + + if (_optionCommandPromptWindow) { + // Forward to command prompt window, using last command + if (acceptableInput) { + promptCommandWindow(false, newKey); + } + return; + } + if (_messageState.dialogue_Open) { maxChars = TEXT_STRING_MAX_SIZE - 4; } else { @@ -702,22 +746,6 @@ void TextMgr::promptKeyPress(uint16 newKey) { } default: if (maxChars > _promptCursorPos) { - bool acceptableInput = false; - - // FEATURE: Sierra didn't check for valid characters (filtered out umlauts etc.) - // In text-mode this sort of worked at least with the DOS interpreter - // but as soon as invalid characters were used in graphics mode they weren't properly shown - switch (_vm->getLanguage()) { - case Common::RU_RUS: - if (newKey >= 0x20) - acceptableInput = true; - break; - default: - if ((newKey >= 0x20) && (newKey <= 0x7f)) - acceptableInput = true; - break; - } - if (acceptableInput) { _prompt[_promptCursorPos] = newKey; _promptCursorPos++; @@ -734,6 +762,11 @@ void TextMgr::promptKeyPress(uint16 newKey) { } void TextMgr::promptCancelLine() { + if (_optionCommandPromptWindow) { + // Abort, in case command prompt window is active + return; + } + while (_promptCursorPos) { promptKeyPress(0x08); // Backspace until prompt is empty } @@ -742,6 +775,12 @@ void TextMgr::promptCancelLine() { void TextMgr::promptEchoLine() { int16 previousLen = strlen((char *)_promptPrevious); + if (_optionCommandPromptWindow) { + // Forward to command prompt window, using last command + promptCommandWindow(true, 0); + return; + } + if (_promptCursorPos < previousLen) { inputEditOn(); @@ -758,6 +797,11 @@ void TextMgr::promptRedraw() { char *textPtr = nullptr; if (_promptEnabled) { + if (_optionCommandPromptWindow) { + // Abort, in case command prompt window is active + return; + } + inputEditOn(); clearLine(_promptRow, _textAttrib.background); charPos_Set(_promptRow, 0); @@ -775,6 +819,10 @@ void TextMgr::promptRedraw() { // for AGI1 void TextMgr::promptClear() { + if (_optionCommandPromptWindow) { + // Abort, in case command prompt window is active + return; + } clearLine(_promptRow, _textAttrib.background); } @@ -784,6 +832,35 @@ void TextMgr::promptRememberForAutoComplete(bool entered) { #endif } +void TextMgr::promptCommandWindow(bool recallLastCommand, uint16 newKey) { + Common::String commandText; + + if (recallLastCommand) { + commandText += Common::String((char *)_promptPrevious); + } + if (newKey) { + if (newKey != ' ') { + // Only add char, when it's not a space. + // Original AGI did not filter space, but it makes no sense to start with a space. + // Space would get filtered anyway during dictionary parsing. + commandText += newKey; + } + } + + if (_systemUI->askForCommand(commandText)) { + if (commandText.size()) { + // Something actually was entered? + strncpy((char *)&_prompt, commandText.c_str(), sizeof(_prompt)); + promptRememberForAutoComplete(true); + memcpy(&_promptPrevious, &_prompt, sizeof(_prompt)); + // parse text + _vm->_words->parseUsingDictionary((char *)&_prompt); + + _prompt[0] = 0; + } + } +} + bool TextMgr::stringWasEntered() { return _inputStringEntered; } @@ -941,8 +1018,18 @@ char *TextMgr::stringWordWrap(const char *originalText, int16 maxWidth, int16 *c //memset(resultWrappedBuffer, 0, sizeof(resultWrappedBuffer)); for debugging + // Good testcases: + // King's Quest 1 intro: the scrolling text is filled up with spaces, so that old lines are erased + // Apple IIgs restart system UI: spaces used to make the window larger + // Gold Rush Stagecoach path room 60: " Lake Michigan!", with max length 9 -> should get split into " Lake" / "Michigan!" + while (originalText[curReadPos]) { // Try to find out length of next word + + // If first character is a space, skip it, so that we process at least this space + if (originalText[curReadPos] == ' ') + curReadPos++; + while (originalText[curReadPos]) { if (originalText[curReadPos] == ' ') break; @@ -957,6 +1044,15 @@ char *TextMgr::stringWordWrap(const char *originalText, int16 maxWidth, int16 *c if (wordLen >= lineWidthLeft) { // Not enough space left + + // If first character right after the new line is a space, skip over it + if (wordLen) { + if (originalText[wordStartPos] == ' ') { + wordStartPos++; + wordLen--; + } + } + if (wordLen > maxWidth) { // Word way too long, split it in half curReadPos = curReadPos - (wordLen - maxWidth); @@ -973,14 +1069,6 @@ char *TextMgr::stringWordWrap(const char *originalText, int16 maxWidth, int16 *c // Reached absolute maximum? -> exit now if (boxHeight >= HEIGHT_MAX) break; - - // If first character right after the new line is a space, skip over it - if (wordLen) { - if (originalText[wordStartPos] == ' ') { - wordStartPos++; - wordLen--; - } - } } // Copy current word over @@ -1005,10 +1093,6 @@ char *TextMgr::stringWordWrap(const char *originalText, int16 maxWidth, int16 *c } wordStartPos = curReadPos; - - // Last word ended with a space, skip this space for reading the next word - if (wordEndChar == ' ') - curReadPos++; } resultWrappedBuffer[curWritePos] = 0; diff --git a/engines/agi/text.h b/engines/agi/text.h index 72d012b917..f0aeab7762 100644 --- a/engines/agi/text.h +++ b/engines/agi/text.h @@ -55,7 +55,7 @@ struct MessageState_Struct { uint16 printed_Height; int16 backgroundPos_x; - int16 backgroundPos_y; + int16 backgroundPos_y; // original AGI used lowerY here, we use upperY so that upscaling is easier int16 backgroundSize_Width; int16 backgroundSize_Height; }; @@ -163,6 +163,8 @@ public: bool _inputEditEnabled; byte _inputCursorChar; + bool _optionCommandPromptWindow; + bool _promptEnabled; int16 _promptRow; int16 _promptCursorPos; @@ -189,6 +191,8 @@ public: void promptClear(); // for AGI1 void promptRememberForAutoComplete(bool entered = false); // for auto-completion + void promptCommandWindow(bool recallLastCommand, uint16 newKey); + int16 _inputStringRow; int16 _inputStringColumn; bool _inputStringEntered; diff --git a/engines/agi/view.cpp b/engines/agi/view.cpp index ac3e60ee6d..a13e40e60d 100644 --- a/engines/agi/view.cpp +++ b/engines/agi/view.cpp @@ -111,7 +111,6 @@ int AgiEngine::decodeView(byte *resourceData, uint16 resourceSize, int16 viewNr) byte *celCompressedData = nullptr; uint16 celCompressedSize = 0; -// byte *rawBitmap = nullptr; debugC(5, kDebugLevelResources, "decode_view(%d)", viewNr); @@ -431,14 +430,28 @@ void AgiEngine::unloadView(int16 viewNr) { * @param viewNr number of AGI view resource */ void AgiEngine::setView(ScreenObjEntry *screenObj, int16 viewNr) { - screenObj->viewData = &_game.views[viewNr]; + if (!(_game.dirView[viewNr].flags & RES_LOADED)) { + // View resource currently not loaded, this is probably a game bug + // Load the resource now to fix the issue, and give out a warning + // This happens in at least Larry 1 for Apple IIgs right after getting beaten up by taxi driver + // Original interpreter bombs out in this situation saying "view not loaded, Press ESC to quit" + warning("setView() called on screen object %d to use view %d, but view not loaded", screenObj->objectNr, viewNr); + warning("probably game script bug, trying to load view into memory"); + if (agiLoadResource(RESOURCETYPE_VIEW, viewNr) != errOK) { + // loading failed, we better error() out now + error("setView() called to set view %d for screen object %d, which is not loaded atm and loading failed", viewNr, screenObj->objectNr); + return; + }; + } + + screenObj->viewResource = &_game.views[viewNr]; screenObj->currentViewNr = viewNr; - screenObj->loopCount = screenObj->viewData->loopCount; + screenObj->loopCount = screenObj->viewResource->loopCount; screenObj->viewReplaced = true; if (getVersion() < 0x2000) { - screenObj->stepSize = screenObj->viewData->headerStepSize; - screenObj->cycleTime = screenObj->viewData->headerCycleTime; + screenObj->stepSize = screenObj->viewResource->headerStepSize; + screenObj->cycleTime = screenObj->viewResource->headerCycleTime; screenObj->cycleTimeCount = 0; } if (screenObj->currentLoopNr >= screenObj->loopCount) { @@ -454,7 +467,11 @@ void AgiEngine::setView(ScreenObjEntry *screenObj, int16 viewNr) { * @param loopNr number of loop */ void AgiEngine::setLoop(ScreenObjEntry *screenObj, int16 loopNr) { - assert(screenObj->viewData != NULL); + if (!(_game.dirView[screenObj->currentViewNr].flags & RES_LOADED)) { + error("setLoop() called on screen object %d, which has no loaded view resource assigned to it", screenObj->objectNr); + return; + } + assert(screenObj->viewResource); if (screenObj->loopCount == 0) { warning("setLoop() called on screen object %d, which has no loops (view %d)", screenObj->objectNr, screenObj->currentViewNr); @@ -465,7 +482,11 @@ void AgiEngine::setLoop(ScreenObjEntry *screenObj, int16 loopNr) { // requested loop not existant // instead of error()ing out, we instead clip it // At least required for possibly Manhunter 1 according to previous comment when leaving the arcade machine - // TODO: check MH1 + // TODO: Check MH1 + // TODO: This causes an issue in KQ1, when bowing to the king in room 53 + // Ego will face away from the king, because the scripts set the loop first and then the view + // Loop is corrected by us, because at that time it's invalid. Was already present in 1.7.0 + // We should probably script-patch it out. int16 requestedLoopNr = loopNr; loopNr = screenObj->loopCount - 1; @@ -493,7 +514,16 @@ void AgiEngine::setLoop(ScreenObjEntry *screenObj, int16 loopNr) { * @param celNr number of cel */ void AgiEngine::setCel(ScreenObjEntry *screenObj, int16 celNr) { - assert(screenObj->viewData != NULL); + if (!(_game.dirView[screenObj->currentViewNr].flags & RES_LOADED)) { + error("setCel() called on screen object %d, which has no loaded view resource assigned to it", screenObj->objectNr); + return; + } + assert(screenObj->viewResource); + + if (screenObj->loopCount == 0) { + warning("setLoop() called on screen object %d, which has no loops (view %d)", screenObj->objectNr, screenObj->currentViewNr); + return; + } AgiViewLoop *curViewLoop = &_game.views[screenObj->currentViewNr].loop[screenObj->currentLoopNr]; diff --git a/engines/agi/view.h b/engines/agi/view.h index 04021260a3..e59916da78 100644 --- a/engines/agi/view.h +++ b/engines/agi/view.h @@ -47,10 +47,6 @@ struct AgiView { byte *description; int16 loopCount; AgiViewLoop *loop; - - //struct ViewLoop *loop; - //bool agi256_2; - //byte *resourceData; }; enum MotionType { @@ -98,7 +94,7 @@ struct ScreenObjEntry { int16 yPos; uint8 currentViewNr; bool viewReplaced; - struct AgiView *viewData; + struct AgiView *viewResource; uint8 currentLoopNr; uint8 loopCount; struct AgiViewLoop *loopData; diff --git a/engines/agi/words.cpp b/engines/agi/words.cpp index 32fa4cbff4..7072f003c2 100644 --- a/engines/agi/words.cpp +++ b/engines/agi/words.cpp @@ -293,7 +293,7 @@ int16 Words::findWordInDictionary(const Common::String &userInputLowcased, uint1 return wordId; } -void Words::parseUsingDictionary(char *rawUserInput) { +void Words::parseUsingDictionary(const char *rawUserInput) { Common::String userInput; Common::String userInputLowcased; const char *userInputPtr = nullptr; diff --git a/engines/agi/words.h b/engines/agi/words.h index c7bf4829c3..96dafae275 100644 --- a/engines/agi/words.h +++ b/engines/agi/words.h @@ -57,7 +57,7 @@ public: void unloadDictionary(); void clearEgoWords(); - void parseUsingDictionary(char *rawUserInput); + void parseUsingDictionary(const char *rawUserInput); private: void cleanUpInput(const char *userInput, Common::String &cleanInput); diff --git a/engines/agos/configure.engine b/engines/agos/configure.engine index 3ae1fb16f2..cd7fcf9d78 100644 --- a/engines/agos/configure.engine +++ b/engines/agos/configure.engine @@ -1,4 +1,4 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] add_engine agos "AGOS" yes "agos2" "AGOS 1 games" -add_engine agos2 "AGOS 2 games" yes +add_engine agos2 "AGOS 2 games" yes "" "" "highres" diff --git a/engines/agos/detection.cpp b/engines/agos/detection.cpp index 94446159b9..2c89522089 100644 --- a/engines/agos/detection.cpp +++ b/engines/agos/detection.cpp @@ -94,13 +94,13 @@ using namespace AGOS; class AgosMetaEngine : public AdvancedMetaEngine { public: AgosMetaEngine() : AdvancedMetaEngine(AGOS::gameDescriptions, sizeof(AGOS::AGOSGameDescription), agosGames) { - _guioptions = GUIO1(GUIO_NOLAUNCHLOAD); + _guiOptions = GUIO1(GUIO_NOLAUNCHLOAD); _maxScanDepth = 2; _directoryGlobs = directoryGlobs; } - virtual GameDescriptor findGame(const char *gameid) const { - return Engines::findGameID(gameid, _gameids, obsoleteGameIDsTable); + virtual GameDescriptor findGame(const char *gameId) const { + return Engines::findGameID(gameId, _gameIds, obsoleteGameIDsTable); } virtual const char *getName() const { @@ -186,7 +186,6 @@ SaveStateList AgosMetaEngine::listSaves(const char *target) const { pattern += ".###"; filenames = saveFileMan->listSavefiles(pattern); - sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) SaveStateList saveList; for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { @@ -203,6 +202,8 @@ SaveStateList AgosMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/agos/sound.cpp b/engines/agos/sound.cpp index 762f60bd91..13b5bf761c 100644 --- a/engines/agos/sound.cpp +++ b/engines/agos/sound.cpp @@ -217,7 +217,7 @@ static void convertVolume(int &vol) { } static void convertPan(int &pan) { - // DirectSound was orginally used, which specifies volume + // DirectSound was originally used, which specifies volume // and panning differently than ScummVM does, using a logarithmic scale // rather than a linear one. // diff --git a/engines/avalanche/POTFILES b/engines/avalanche/POTFILES new file mode 100644 index 0000000000..5b0bb910ed --- /dev/null +++ b/engines/avalanche/POTFILES @@ -0,0 +1 @@ +engines/avalanche/parser.cpp diff --git a/engines/avalanche/configure.engine b/engines/avalanche/configure.engine index 28d6a558db..9b913ff053 100644 --- a/engines/avalanche/configure.engine +++ b/engines/avalanche/configure.engine @@ -1,3 +1,3 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] -add_engine avalanche "Lord Avalot d'Argent" no +add_engine avalanche "Lord Avalot d'Argent" no "" "" "highres" diff --git a/engines/avalanche/detection.cpp b/engines/avalanche/detection.cpp index 484d148161..e35c5d2cac 100644 --- a/engines/avalanche/detection.cpp +++ b/engines/avalanche/detection.cpp @@ -40,7 +40,7 @@ uint32 AvalancheEngine::getFeatures() const { } const char *AvalancheEngine::getGameId() const { - return _gameDescription->desc.gameid; + return _gameDescription->desc.gameId; } static const PlainGameDescriptor avalancheGames[] = { @@ -110,7 +110,6 @@ SaveStateList AvalancheMetaEngine::listSaves(const char *target) const { pattern += ".###"; filenames = saveFileMan->listSavefiles(pattern); - sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) SaveStateList saveList; for (Common::StringArray::const_iterator filename = filenames.begin(); filename != filenames.end(); ++filename) { @@ -152,6 +151,8 @@ SaveStateList AvalancheMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/avalanche/parser.cpp b/engines/avalanche/parser.cpp index 220186ca5e..7c6d254099 100644 --- a/engines/avalanche/parser.cpp +++ b/engines/avalanche/parser.cpp @@ -30,6 +30,7 @@ #include "avalanche/nim.h" #include "gui/saveload.h" +#include "common/translation.h" namespace Avalanche { @@ -1883,7 +1884,7 @@ void Parser::doThat() { break; case kVerbCodeLoad: { - GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser("Restore game:", "Restore", false); + GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false); int16 savegameId = dialog->runModalWithCurrentTarget(); delete dialog; @@ -1895,7 +1896,7 @@ void Parser::doThat() { } break; case kVerbCodeSave: { - GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser("Save game:", "Save", true); + GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true); int16 savegameId = dialog->runModalWithCurrentTarget(); Common::String savegameDescription = dialog->getResultString(); delete dialog; diff --git a/engines/bbvs/bbvs.cpp b/engines/bbvs/bbvs.cpp index d40d5e482f..6ae663479d 100644 --- a/engines/bbvs/bbvs.cpp +++ b/engines/bbvs/bbvs.cpp @@ -137,6 +137,21 @@ BbvsEngine::~BbvsEngine() { } void BbvsEngine::newGame() { + memset(_easterEggInput, 0, sizeof(_easterEggInput)); + _gameTicks = 0; + _playVideoNumber = 0; + memset(_inventoryItemStatus, 0, sizeof(_inventoryItemStatus)); + memset(_gameVars, 0, sizeof(_gameVars)); + memset(_sceneVisited, 0, sizeof(_sceneVisited)); + + _mouseX = 160; + _mouseY = 120; + _mouseButtons = 0; + + _currVerbNum = kVerbLook; + _currTalkObjectIndex = -1; + _currSceneNum = 0; + _currInventoryItem = -1; _newSceneNum = 32; } @@ -162,24 +177,10 @@ Common::Error BbvsEngine::run() { _sound = new SoundMan(); allocSnapshot(); - memset(_easterEggInput, 0, sizeof(_easterEggInput)); - _gameTicks = 0; - _playVideoNumber = 0; - _bootSaveSlot = -1; - - memset(_inventoryItemStatus, 0, sizeof(_inventoryItemStatus)); - memset(_gameVars, 0, sizeof(_gameVars)); - memset(_sceneVisited, 0, sizeof(_sceneVisited)); - - _mouseX = 160; - _mouseY = 120; - _mouseButtons = 0; + newGame(); - _currVerbNum = kVerbLook; - _currInventoryItem = -1; - _currTalkObjectIndex = -1; - _currSceneNum = 0; + _bootSaveSlot = -1; _newSceneNum = 31; if (ConfMan.hasKey("save_slot")) diff --git a/engines/bbvs/detection.cpp b/engines/bbvs/detection.cpp index eb894e9f13..7c0045ee73 100644 --- a/engines/bbvs/detection.cpp +++ b/engines/bbvs/detection.cpp @@ -43,7 +43,7 @@ static const ADGameDescription gameDescriptions[] = { AD_ENTRY1s("vspr0001.vnm", "7ffe9b9e7ca322db1d48e86f5130578e", 1166628), Common::EN_ANY, Common::kPlatformWindows, - ADGF_NO_FLAGS | ADGF_TESTING, + ADGF_NO_FLAGS, GUIO0() }, { @@ -52,7 +52,7 @@ static const ADGameDescription gameDescriptions[] = { AD_ENTRY1s("vspr0001.vnm", "91c76b1048f93208cd7b1a05ebccb408", 1176976), Common::RU_RUS, Common::kPlatformWindows, - GF_GUILANGSWITCH | ADGF_TESTING, + GF_GUILANGSWITCH | ADGF_NO_FLAGS, GUIO0() }, @@ -69,7 +69,7 @@ static const char * const directoryGlobs[] = { class BbvsMetaEngine : public AdvancedMetaEngine { public: BbvsMetaEngine() : AdvancedMetaEngine(Bbvs::gameDescriptions, sizeof(ADGameDescription), bbvsGames) { - _singleid = "bbvs"; + _singleId = "bbvs"; _maxScanDepth = 3; _directoryGlobs = directoryGlobs; } @@ -116,7 +116,6 @@ SaveStateList BbvsMetaEngine::listSaves(const char *target) const { pattern += ".###"; Common::StringArray filenames; filenames = saveFileMan->listSavefiles(pattern.c_str()); - Common::sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) SaveStateList saveList; for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { // Obtain the last 3 digits of the filename, since they correspond to the save slot @@ -131,6 +130,8 @@ SaveStateList BbvsMetaEngine::listSaves(const char *target) const { } } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/bbvs/dialogs.cpp b/engines/bbvs/dialogs.cpp index ef7f3c9320..c8470f8eef 100644 --- a/engines/bbvs/dialogs.cpp +++ b/engines/bbvs/dialogs.cpp @@ -102,7 +102,7 @@ void MainMenu::reflowLayout() { _w = 2 * buttonWidth + buttonPadding; _h = 3 * buttonHeight + 3 * buttonPadding; _x = (screenW - _w) / 2; - _y = screenH - _h; + _y = screenH - _h - 2; int x = 0, y = 0; diff --git a/engines/cge/POTFILES b/engines/cge/POTFILES index 55430683c3..f8aef4932d 100644 --- a/engines/cge/POTFILES +++ b/engines/cge/POTFILES @@ -1,2 +1,3 @@ engines/cge/detection.cpp +engines/cge/events.cpp diff --git a/engines/cge/detection.cpp b/engines/cge/detection.cpp index 47c5f56b6e..52168dc934 100644 --- a/engines/cge/detection.cpp +++ b/engines/cge/detection.cpp @@ -115,7 +115,7 @@ static const ADExtraGuiOptionsMap optionsList[] = { class CGEMetaEngine : public AdvancedMetaEngine { public: CGEMetaEngine() : AdvancedMetaEngine(CGE::gameDescriptions, sizeof(ADGameDescription), CGEGames, optionsList) { - _singleid = "soltys"; + _singleId = "soltys"; } virtual const char *getName() const { @@ -199,7 +199,6 @@ SaveStateList CGEMetaEngine::listSaves(const char *target) const { pattern += ".###"; filenames = saveFileMan->listSavefiles(pattern); - sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) SaveStateList saveList; for (Common::StringArray::const_iterator filename = filenames.begin(); filename != filenames.end(); ++filename) { @@ -235,6 +234,8 @@ SaveStateList CGEMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/cge/events.cpp b/engines/cge/events.cpp index 24b3a270cf..c2f8982592 100644 --- a/engines/cge/events.cpp +++ b/engines/cge/events.cpp @@ -30,6 +30,7 @@ #include "gui/message.h" #include "common/config-manager.h" #include "common/events.h" +#include "common/translation.h" #include "engines/advancedDetector.h" #include "cge/events.h" #include "cge/events.h" @@ -70,7 +71,7 @@ bool Keyboard::getKey(Common::Event &event) { return false; case Common::KEYCODE_F5: if (_vm->canSaveGameStateCurrently()) { - GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser("Save game:", "Save", true); + GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true); int16 savegameId = dialog->runModalWithCurrentTarget(); Common::String savegameDescription = dialog->getResultString(); delete dialog; @@ -81,7 +82,7 @@ bool Keyboard::getKey(Common::Event &event) { return false; case Common::KEYCODE_F7: if (_vm->canLoadGameStateCurrently()) { - GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser("Restore game:", "Restore", false); + GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false); int16 savegameId = dialog->runModalWithCurrentTarget(); delete dialog; diff --git a/engines/cge2/POTFILES b/engines/cge2/POTFILES index 1e904763ec..3bff3258fd 100644 --- a/engines/cge2/POTFILES +++ b/engines/cge2/POTFILES @@ -1 +1,2 @@ engines/cge2/detection.cpp +engines/cge2/events.cpp diff --git a/engines/cge2/detection.cpp b/engines/cge2/detection.cpp index a1867b0dfe..01119bb1fd 100644 --- a/engines/cge2/detection.cpp +++ b/engines/cge2/detection.cpp @@ -111,7 +111,7 @@ static const ADExtraGuiOptionsMap optionsList[] = { class CGE2MetaEngine : public AdvancedMetaEngine { public: CGE2MetaEngine() : AdvancedMetaEngine(gameDescriptions, sizeof(ADGameDescription), CGE2Games, optionsList) { - _singleid = "sfinx"; + _singleId = "sfinx"; } virtual const char *getName() const { @@ -199,7 +199,6 @@ SaveStateList CGE2MetaEngine::listSaves(const char *target) const { pattern += ".###"; filenames = saveFileMan->listSavefiles(pattern); - sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) SaveStateList saveList; for (Common::StringArray::const_iterator filename = filenames.begin(); filename != filenames.end(); ++filename) { @@ -235,6 +234,8 @@ SaveStateList CGE2MetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/cge2/events.cpp b/engines/cge2/events.cpp index 85743c8011..96cecc8e23 100644 --- a/engines/cge2/events.cpp +++ b/engines/cge2/events.cpp @@ -30,6 +30,7 @@ #include "gui/message.h" #include "common/config-manager.h" #include "common/events.h" +#include "common/translation.h" #include "engines/advancedDetector.h" #include "cge2/events.h" #include "cge2/text.h" @@ -63,7 +64,7 @@ bool Keyboard::getKey(Common::Event &event) { return false; case Common::KEYCODE_F5: if (_vm->canSaveGameStateCurrently()) { - GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser("Save game:", "Save", true); + GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true); int16 savegameId = dialog->runModalWithCurrentTarget(); Common::String savegameDescription = dialog->getResultString(); delete dialog; @@ -74,7 +75,7 @@ bool Keyboard::getKey(Common::Event &event) { return false; case Common::KEYCODE_F7: if (_vm->canLoadGameStateCurrently()) { - GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser("Restore game:", "Restore", false); + GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false); int16 savegameId = dialog->runModalWithCurrentTarget(); delete dialog; diff --git a/engines/cge2/vga13h.cpp b/engines/cge2/vga13h.cpp index eb111c3255..54f5c00d93 100644 --- a/engines/cge2/vga13h.cpp +++ b/engines/cge2/vga13h.cpp @@ -141,7 +141,7 @@ Sprite::Sprite(CGE2Engine *vm) memset(_actionCtrl, 0, sizeof(_actionCtrl)); memset(_file, 0, sizeof(_file)); memset(&_flags, 0, sizeof(_flags)); - _flags._frnt = 1; + _flags._frnt = true; } Sprite::Sprite(CGE2Engine *vm, BitmapPtr shpP, int cnt) @@ -152,7 +152,7 @@ Sprite::Sprite(CGE2Engine *vm, BitmapPtr shpP, int cnt) memset(_actionCtrl, 0, sizeof(_actionCtrl)); memset(_file, 0, sizeof(_file)); memset(&_flags, 0, sizeof(_flags)); - _flags._frnt = 1; + _flags._frnt = true; setShapeList(shpP, cnt); } diff --git a/engines/cine/anim.cpp b/engines/cine/anim.cpp index c6099447d8..6ecf07fe15 100644 --- a/engines/cine/anim.cpp +++ b/engines/cine/anim.cpp @@ -535,7 +535,7 @@ int loadSpl(const char *resourceName, int16 idx) { entry = idx < 0 ? emptyAnimSpace() : idx; assert(entry >= 0); - g_cine->_animDataTable[entry].load(dataPtr + 0x16, ANIM_RAW, g_cine->_partBuffer[foundFileIdx].unpackedSize - 0x16, 1, foundFileIdx, 0, currentPartName); + g_cine->_animDataTable[entry].load(dataPtr, ANIM_RAW, g_cine->_partBuffer[foundFileIdx].unpackedSize, 1, foundFileIdx, 0, currentPartName); free(dataPtr); return entry + 1; diff --git a/engines/cine/detection.cpp b/engines/cine/detection.cpp index dac7add16b..ec01e8734d 100644 --- a/engines/cine/detection.cpp +++ b/engines/cine/detection.cpp @@ -80,12 +80,12 @@ static const ADExtraGuiOptionsMap optionsList[] = { class CineMetaEngine : public AdvancedMetaEngine { public: CineMetaEngine() : AdvancedMetaEngine(Cine::gameDescriptions, sizeof(Cine::CINEGameDescription), cineGames, optionsList) { - _singleid = "cine"; - _guioptions = GUIO2(GUIO_NOSPEECH, GAMEOPTION_ORIGINAL_SAVELOAD); + _singleId = "cine"; + _guiOptions = GUIO2(GUIO_NOSPEECH, GAMEOPTION_ORIGINAL_SAVELOAD); } - virtual GameDescriptor findGame(const char *gameid) const { - return Engines::findGameID(gameid, _gameids, obsoleteGameIDsTable); + virtual GameDescriptor findGame(const char *gameId) const { + return Engines::findGameID(gameId, _gameIds, obsoleteGameIDsTable); } virtual const char *getName() const { @@ -137,7 +137,6 @@ SaveStateList CineMetaEngine::listSaves(const char *target) const { Common::String pattern = target; pattern += ".#"; Common::StringArray filenames = saveFileMan->listSavefiles(pattern); - sort(filenames.begin(), filenames.end()); Common::StringArray::const_iterator file; Common::String filename = target; @@ -169,6 +168,8 @@ SaveStateList CineMetaEngine::listSaves(const char *target) const { delete in; + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/cine/saveload.cpp b/engines/cine/saveload.cpp index 1f4f286694..dfd3a1f4bc 100644 --- a/engines/cine/saveload.cpp +++ b/engines/cine/saveload.cpp @@ -691,6 +691,11 @@ bool CineEngine::loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFor } if (strlen(bgName)) { + if (g_cine->getGameType() == GType_FW && (g_cine->getFeatures() & GF_CD)) { + char buffer[20]; + removeExtention(buffer, bgName); + g_sound->setBgMusic(atoi(buffer + 1)); + } loadBg(bgName); } diff --git a/engines/cine/script_fw.cpp b/engines/cine/script_fw.cpp index 6ad38f4433..86eb709d5a 100644 --- a/engines/cine/script_fw.cpp +++ b/engines/cine/script_fw.cpp @@ -1858,7 +1858,9 @@ int FWScript::o1_playSample() { if (g_cine->getGameType() == Cine::GType_OS && size == 0) { return 0; } - g_sound->stopMusic(); + // The DOS CD version of Future Wars uses CD audio for music + if (!(g_cine->getGameType() == Cine::GType_FW && (g_cine->getFeatures() & GF_CD))) + g_sound->stopMusic(); if (size == 0xFFFF) { g_sound->playSound(channel, 0, data, 0, 0, 0, volume, 0); } else { diff --git a/engines/composer/configure.engine b/engines/composer/configure.engine index 71a79acb5d..17120a3a3d 100644 --- a/engines/composer/configure.engine +++ b/engines/composer/configure.engine @@ -1,3 +1,3 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] -add_engine composer "Magic Composer" yes +add_engine composer "Magic Composer" yes "" "" "highres" diff --git a/engines/composer/detection.cpp b/engines/composer/detection.cpp index 036c1e88d8..a3ab18ae54 100644 --- a/engines/composer/detection.cpp +++ b/engines/composer/detection.cpp @@ -38,7 +38,7 @@ int ComposerEngine::getGameType() const { } const char *ComposerEngine::getGameId() const { - return _gameDescription->desc.gameid; + return _gameDescription->desc.gameId; } uint32 ComposerEngine::getFeatures() const { @@ -81,12 +81,12 @@ static const ComposerGameDescription gameDescriptions[] = { GType_ComposerV1 }, - // Magic Tales: Baba Yaga and the Magic Geese Mac - from bug #3466402 + // Magic Tales: Baba Yaga and the Magic Geese Mac - from bug #3466402, #7025 { { "babayaga", "", - AD_ENTRY1("Baba Yaga", "ae3a4445f42fe10253da7ee4ea0d37"), + AD_ENTRY1s("Baba Yaga", "ae3a4445f42fe10253da7ee4ea0d37d6", 44321), Common::EN_ANY, Common::kPlatformMacintosh, ADGF_NO_FLAGS, @@ -386,7 +386,7 @@ static const char *directoryGlobs[] = { class ComposerMetaEngine : public AdvancedMetaEngine { public: ComposerMetaEngine() : AdvancedMetaEngine(Composer::gameDescriptions, sizeof(Composer::ComposerGameDescription), composerGames) { - _singleid = "composer"; + _singleId = "composer"; _maxScanDepth = 2; _directoryGlobs = directoryGlobs; } diff --git a/engines/cruise/detection.cpp b/engines/cruise/detection.cpp index 4d0ed2bf91..6f5d236173 100644 --- a/engines/cruise/detection.cpp +++ b/engines/cruise/detection.cpp @@ -35,7 +35,7 @@ struct CRUISEGameDescription { }; const char *CruiseEngine::getGameId() const { - return _gameDescription->desc.gameid; + return _gameDescription->desc.gameId; } Common::Language CruiseEngine::getLanguage() const { @@ -196,8 +196,8 @@ static const CRUISEGameDescription gameDescriptions[] = { class CruiseMetaEngine : public AdvancedMetaEngine { public: CruiseMetaEngine() : AdvancedMetaEngine(Cruise::gameDescriptions, sizeof(Cruise::CRUISEGameDescription), cruiseGames) { - _singleid = "cruise"; - _guioptions = GUIO2(GUIO_NOSPEECH, GUIO_NOMIDI); + _singleId = "cruise"; + _guiOptions = GUIO2(GUIO_NOSPEECH, GUIO_NOMIDI); } virtual const char *getName() const { @@ -231,7 +231,6 @@ SaveStateList CruiseMetaEngine::listSaves(const char *target) const { Common::String pattern("cruise.s##"); filenames = saveFileMan->listSavefiles(pattern); - sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) SaveStateList saveList; for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { @@ -250,6 +249,8 @@ SaveStateList CruiseMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/draci/detection.cpp b/engines/draci/detection.cpp index a32dae3216..65427bd8cd 100644 --- a/engines/draci/detection.cpp +++ b/engines/draci/detection.cpp @@ -84,7 +84,7 @@ const ADGameDescription gameDescriptions[] = { class DraciMetaEngine : public AdvancedMetaEngine { public: DraciMetaEngine() : AdvancedMetaEngine(Draci::gameDescriptions, sizeof(ADGameDescription), draciGames) { - _singleid = "draci"; + _singleId = "draci"; } virtual const char *getName() const { @@ -120,7 +120,6 @@ SaveStateList DraciMetaEngine::listSaves(const char *target) const { Common::String pattern("draci.s##"); filenames = saveFileMan->listSavefiles(pattern); - sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) SaveStateList saveList; for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { @@ -143,6 +142,8 @@ SaveStateList DraciMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/drascula/detection.cpp b/engines/drascula/detection.cpp index fbf58faaa1..ffec393a0a 100644 --- a/engines/drascula/detection.cpp +++ b/engines/drascula/detection.cpp @@ -310,8 +310,8 @@ SaveStateDescriptor loadMetaData(Common::ReadStream *s, int slot, bool setPlayTi class DrasculaMetaEngine : public AdvancedMetaEngine { public: DrasculaMetaEngine() : AdvancedMetaEngine(Drascula::gameDescriptions, sizeof(Drascula::DrasculaGameDescription), drasculaGames) { - _singleid = "drascula"; - _guioptions = GUIO1(GUIO_NOMIDI); + _singleId = "drascula"; + _guiOptions = GUIO1(GUIO_NOMIDI); } virtual const char *getName() const { @@ -355,7 +355,6 @@ SaveStateList DrasculaMetaEngine::listSaves(const char *target) const { pattern += ".###"; filenames = saveFileMan->listSavefiles(pattern); - sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) SaveStateList saveList; int slotNum = 0; @@ -378,6 +377,8 @@ SaveStateList DrasculaMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/dreamweb/detection.cpp b/engines/dreamweb/detection.cpp index 4fc624c2f3..764171bcb0 100644 --- a/engines/dreamweb/detection.cpp +++ b/engines/dreamweb/detection.cpp @@ -70,8 +70,8 @@ public: AdvancedMetaEngine(DreamWeb::gameDescriptions, sizeof(DreamWeb::DreamWebGameDescription), dreamWebGames, gameGuiOptions) { - _singleid = "dreamweb"; - _guioptions = GUIO1(GUIO_NOMIDI); + _singleId = "dreamweb"; + _guiOptions = GUIO1(GUIO_NOMIDI); } virtual const char *getName() const { @@ -128,7 +128,6 @@ bool DreamWebMetaEngine::createInstance(OSystem *syst, Engine **engine, const AD SaveStateList DreamWebMetaEngine::listSaves(const char *target) const { Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); Common::StringArray files = saveFileMan->listSavefiles("DREAMWEB.D##"); - Common::sort(files.begin(), files.end()); SaveStateList saveList; for (uint i = 0; i < files.size(); ++i) { @@ -146,6 +145,8 @@ SaveStateList DreamWebMetaEngine::listSaves(const char *target) const { saveList.push_back(sd); } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/engine.cpp b/engines/engine.cpp index 475cc77064..d3b9b113cf 100644 --- a/engines/engine.cpp +++ b/engines/engine.cpp @@ -292,7 +292,7 @@ void splashScreen() { Common::Event event; while (time0 + 600 > g_system->getMillis()) { g_system->updateScreen(); - g_system->getEventManager()->pollEvent(event); + (void)g_system->getEventManager()->pollEvent(event); g_system->delayMillis(10); } g_system->hideOverlay(); diff --git a/engines/fullpipe/configure.engine b/engines/fullpipe/configure.engine index a9042449db..611d0188dc 100644 --- a/engines/fullpipe/configure.engine +++ b/engines/fullpipe/configure.engine @@ -1,3 +1,3 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] -add_engine fullpipe "Full Pipe" no "" "" "16bit" +add_engine fullpipe "Full Pipe" no "" "" "16bit highres" diff --git a/engines/fullpipe/detection.cpp b/engines/fullpipe/detection.cpp index ccd55935e6..6f92f19f24 100644 --- a/engines/fullpipe/detection.cpp +++ b/engines/fullpipe/detection.cpp @@ -32,7 +32,7 @@ namespace Fullpipe { const char *FullpipeEngine::getGameId() const { - return _gameDescription->gameid; + return _gameDescription->gameId; } } @@ -76,7 +76,7 @@ static const ADGameDescription gameDescriptions[] = { class FullpipeMetaEngine : public AdvancedMetaEngine { public: FullpipeMetaEngine() : AdvancedMetaEngine(Fullpipe::gameDescriptions, sizeof(ADGameDescription), fullpipeGames) { - _singleid = "fullpipe"; + _singleId = "fullpipe"; } virtual const char *getName() const { @@ -111,7 +111,6 @@ SaveStateList FullpipeMetaEngine::listSaves(const char *target) const { Common::String pattern("fullpipe.s##"); filenames = saveFileMan->listSavefiles(pattern); - sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) SaveStateList saveList; for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { @@ -130,6 +129,8 @@ SaveStateList FullpipeMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/game.cpp b/engines/game.cpp index 85ad6fe2e8..7ff51a99cc 100644 --- a/engines/game.cpp +++ b/engines/game.cpp @@ -26,8 +26,8 @@ const PlainGameDescriptor *findPlainGameDescriptor(const char *gameid, const PlainGameDescriptor *list) { const PlainGameDescriptor *g = list; - while (g->gameid) { - if (0 == scumm_stricmp(gameid, g->gameid)) + while (g->gameId) { + if (0 == scumm_stricmp(gameid, g->gameId)) return g; g++; } @@ -40,7 +40,7 @@ GameDescriptor::GameDescriptor() { } GameDescriptor::GameDescriptor(const PlainGameDescriptor &pgd, Common::String guioptions) { - setVal("gameid", pgd.gameid); + setVal("gameid", pgd.gameId); setVal("description", pgd.description); if (!guioptions.empty()) diff --git a/engines/game.h b/engines/game.h index a9bec8f9e0..e01e5c6885 100644 --- a/engines/game.h +++ b/engines/game.h @@ -36,7 +36,7 @@ * consisting of PlainGameDescriptors. */ struct PlainGameDescriptor { - const char *gameid; + const char *gameId; const char *description; }; @@ -108,7 +108,7 @@ public: GameList() {} GameList(const GameList &list) : Common::Array<GameDescriptor>(list) {} GameList(const PlainGameDescriptor *g) { - while (g->gameid) { + while (g->gameId) { push_back(GameDescriptor(*g)); g++; } diff --git a/engines/gob/detection/detection.cpp b/engines/gob/detection/detection.cpp index 3b26f63c39..b0aa78f416 100644 --- a/engines/gob/detection/detection.cpp +++ b/engines/gob/detection/detection.cpp @@ -33,7 +33,7 @@ class GobMetaEngine : public AdvancedMetaEngine { public: GobMetaEngine(); - virtual GameDescriptor findGame(const char *gameid) const; + virtual GameDescriptor findGame(const char *gameId) const; virtual const ADGameDescription *fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const; @@ -55,12 +55,12 @@ private: GobMetaEngine::GobMetaEngine() : AdvancedMetaEngine(Gob::gameDescriptions, sizeof(Gob::GOBGameDescription), gobGames) { - _singleid = "gob"; - _guioptions = GUIO1(GUIO_NOLAUNCHLOAD); + _singleId = "gob"; + _guiOptions = GUIO1(GUIO_NOLAUNCHLOAD); } -GameDescriptor GobMetaEngine::findGame(const char *gameid) const { - return Engines::findGameID(gameid, _gameids, obsoleteGameIDsTable); +GameDescriptor GobMetaEngine::findGame(const char *gameId) const { + return Engines::findGameID(gameId, _gameIds, obsoleteGameIDsTable); } const ADGameDescription *GobMetaEngine::fallbackDetect(const FileMap &allFiles, const Common::FSList &fslist) const { diff --git a/engines/gob/script.cpp b/engines/gob/script.cpp index d6e9841e0a..9298f159a4 100644 --- a/engines/gob/script.cpp +++ b/engines/gob/script.cpp @@ -215,7 +215,7 @@ char *Script::readString(int32 length) { } byte Script::peekByte(int32 offset) { - byte v; + byte v = 0; peek(&v, 1, offset); diff --git a/engines/gob/sound/adlib.cpp b/engines/gob/sound/adlib.cpp index 1e024d5a50..2a9a716058 100644 --- a/engines/gob/sound/adlib.cpp +++ b/engines/gob/sound/adlib.cpp @@ -283,7 +283,12 @@ void AdLib::initOPL() { _voiceOn [i] = 0; } - _opl->reset(); + /* NOTE: We used to completely reset the OPL here, via _opl->reset(). However, + * with the OPL timer change in 73e8ac2a, reset() must not be called while + * the callback is still active. With the Gob AdLib rewrite in 03ef6689, + * this reset shouldn't be necessary anymore either, since this function + * here cleans everything properly anyway. If suddenly a certain piece of + * music in a Gob game sounds weird, we need to re-examine that. */ initOperatorVolumes(); resetFreqs(); diff --git a/engines/groovie/configure.engine b/engines/groovie/configure.engine index 212a49bec8..f283731a58 100644 --- a/engines/groovie/configure.engine +++ b/engines/groovie/configure.engine @@ -1,4 +1,4 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] -add_engine groovie "Groovie" yes "groovie2" "7th Guest" +add_engine groovie "Groovie" yes "groovie2" "7th Guest" "highres" add_engine groovie2 "Groovie 2 games" no "" "" "jpeg 16bit" diff --git a/engines/groovie/detection.cpp b/engines/groovie/detection.cpp index a249b66858..b12e264a57 100644 --- a/engines/groovie/detection.cpp +++ b/engines/groovie/detection.cpp @@ -322,7 +322,7 @@ static const ADExtraGuiOptionsMap optionsList[] = { class GroovieMetaEngine : public AdvancedMetaEngine { public: GroovieMetaEngine() : AdvancedMetaEngine(gameDescriptions, sizeof(GroovieGameDescription), groovieGames, optionsList) { - _singleid = "groovie"; + _singleId = "groovie"; // Use kADFlagUseExtraAsHint in order to distinguish the 11th hour from // its "Making of" as well as the Clandestiny Trailer; they all share @@ -333,7 +333,7 @@ public: // to the detection entries. In the latter case, this TODO should be // replaced with an according explanation. _flags = kADFlagUseExtraAsHint; - _guioptions = GUIO3(GUIO_NOSUBTITLES, GUIO_NOSFX, GUIO_NOASPECT); + _guiOptions = GUIO3(GUIO_NOSUBTITLES, GUIO_NOSFX, GUIO_NOASPECT); // Need MIDI directory to detect 11H Mac Installed _maxScanDepth = 2; diff --git a/engines/groovie/groovie.cpp b/engines/groovie/groovie.cpp index b42cf09245..2021cef6e8 100644 --- a/engines/groovie/groovie.cpp +++ b/engines/groovie/groovie.cpp @@ -69,7 +69,7 @@ GroovieEngine::GroovieEngine(OSystem *syst, const GroovieGameDescription *gd) : // Initialize the custom debug levels DebugMan.addDebugChannel(kDebugVideo, "Video", "Debug video and audio playback"); - DebugMan.addDebugChannel(kDebugResource, "Resource", "Debug resouce management"); + DebugMan.addDebugChannel(kDebugResource, "Resource", "Debug resource management"); DebugMan.addDebugChannel(kDebugScript, "Script", "Debug the scripts"); DebugMan.addDebugChannel(kDebugUnknown, "Unknown", "Report values of unknown data in files"); DebugMan.addDebugChannel(kDebugHotspots, "Hotspots", "Show the hotspots"); diff --git a/engines/hopkins/configure.engine b/engines/hopkins/configure.engine index c38ecd4cd2..cd9f50a5f9 100644 --- a/engines/hopkins/configure.engine +++ b/engines/hopkins/configure.engine @@ -1,3 +1,3 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] -add_engine hopkins "Hopkins FBI" yes "" "" "16bit" +add_engine hopkins "Hopkins FBI" yes "" "" "16bit highres" diff --git a/engines/hopkins/detection.cpp b/engines/hopkins/detection.cpp index 670efb8898..cc1e84f5f8 100644 --- a/engines/hopkins/detection.cpp +++ b/engines/hopkins/detection.cpp @@ -153,7 +153,6 @@ SaveStateList HopkinsMetaEngine::listSaves(const char *target) const { Common::String pattern = Common::String::format("%s.0##", target); filenames = saveFileMan->listSavefiles(pattern); - sort(filenames.begin(), filenames.end()); // Sort to get the files in numerical order Hopkins::hopkinsSavegameHeader header; @@ -178,6 +177,8 @@ SaveStateList HopkinsMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/hugo/POTFILES b/engines/hugo/POTFILES new file mode 100644 index 0000000000..ff61e12ca5 --- /dev/null +++ b/engines/hugo/POTFILES @@ -0,0 +1 @@ +engines/hugo/file.cpp diff --git a/engines/hugo/detection.cpp b/engines/hugo/detection.cpp index a005e649d4..4e4746c002 100644 --- a/engines/hugo/detection.cpp +++ b/engines/hugo/detection.cpp @@ -41,7 +41,7 @@ uint32 HugoEngine::getFeatures() const { } const char *HugoEngine::getGameId() const { - return _gameDescription->desc.gameid; + return _gameDescription->desc.gameId; } @@ -180,7 +180,6 @@ SaveStateList HugoMetaEngine::listSaves(const char *target) const { pattern += "-##.SAV"; filenames = saveFileMan->listSavefiles(pattern); - sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) SaveStateList saveList; char slot[3]; @@ -217,6 +216,8 @@ SaveStateList HugoMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/hugo/file.cpp b/engines/hugo/file.cpp index e2633977a8..7a3538ea63 100644 --- a/engines/hugo/file.cpp +++ b/engines/hugo/file.cpp @@ -32,6 +32,7 @@ #include "common/savefile.h" #include "common/textconsole.h" #include "common/config-manager.h" +#include "common/translation.h" #include "graphics/surface.h" #include "graphics/thumbnail.h" @@ -294,7 +295,7 @@ bool FileManager::saveGame(const int16 slot, const Common::String &descrip) { Common::String savegameDescription; if (slot == -1) { - GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser("Save game:", "Save", true); + GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true); savegameId = dialog->runModalWithCurrentTarget(); savegameDescription = dialog->getResultString(); delete dialog; @@ -396,7 +397,7 @@ bool FileManager::restoreGame(const int16 slot) { int16 savegameId; if (slot == -1) { - GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser("Restore game:", "Restore", false); + GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false); savegameId = dialog->runModalWithCurrentTarget(); delete dialog; } else { diff --git a/engines/kyra/debugger.cpp b/engines/kyra/debugger.cpp index 4a90722a35..6683f973ca 100644 --- a/engines/kyra/debugger.cpp +++ b/engines/kyra/debugger.cpp @@ -542,7 +542,7 @@ bool Debugger_EoB::cmdSaveOriginal(int argc, const char **argv) { debugPrintf("Failure.\n"); } } else { - debugPrintf("Syntax: save_original\n (Saves game in original file format to a file which can be used with the orginal game executable.)\n\n"); + debugPrintf("Syntax: save_original\n (Saves game in original file format to a file which can be used with the original game executable.)\n\n"); } return true; @@ -562,7 +562,7 @@ bool Debugger_EoB::cmdSaveOriginal(int argc, const char **argv) { return true; } - debugPrintf("Syntax: save_original <slot>\n (Saves game in original file format to a file which can be used with the orginal game executable.\n A save slot between 0 and 5 must be specified.)\n\n"); + debugPrintf("Syntax: save_original <slot>\n (Saves game in original file format to a file which can be used with the original game executable.\n A save slot between 0 and 5 must be specified.)\n\n"); return true; } diff --git a/engines/kyra/detection.cpp b/engines/kyra/detection.cpp index a4786ef583..989a45b420 100644 --- a/engines/kyra/detection.cpp +++ b/engines/kyra/detection.cpp @@ -246,7 +246,6 @@ SaveStateList KyraMetaEngine::listSaves(const char *target) const { Common::StringArray filenames; filenames = saveFileMan->listSavefiles(pattern); - Common::sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) SaveStateList saveList; for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { @@ -269,6 +268,8 @@ SaveStateList KyraMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/kyra/detection_tables.h b/engines/kyra/detection_tables.h index 2ee0262ef2..773d491423 100644 --- a/engines/kyra/detection_tables.h +++ b/engines/kyra/detection_tables.h @@ -1546,7 +1546,7 @@ const KYRAGameDescription adGameDescs[] = { }, Common::EN_ANY, Common::kPlatformDOS, - ADGF_TESTING, + ADGF_NO_FLAGS, GUIO7(GUIO_NOSPEECH, GUIO_MIDIADLIB, GUIO_MIDIPCSPK, GUIO_RENDERVGA, GUIO_RENDEREGA, GUIO_RENDERCGA, GAMEOPTION_EOB_HPGRAPHS) }, EOB_FLAGS @@ -1562,7 +1562,7 @@ const KYRAGameDescription adGameDescs[] = { }, Common::DE_DEU, Common::kPlatformDOS, - ADGF_TESTING, + ADGF_NO_FLAGS, GUIO7(GUIO_NOSPEECH, GUIO_MIDIADLIB, GUIO_MIDIPCSPK, GUIO_RENDERVGA, GUIO_RENDEREGA, GUIO_RENDERCGA, GAMEOPTION_EOB_HPGRAPHS) }, EOB_FLAGS @@ -1576,10 +1576,10 @@ const KYRAGameDescription adGameDescs[] = { { "EOBDATA3.PAK", 0, "3ed915ab5b94d60dbfe1b55379889c51", -1 }, { 0, 0, 0, 0 } }, - Common::IT_ITA, - Common::kPlatformDOS, - ADGF_TESTING, - GUIO7(GUIO_NOSPEECH, GUIO_MIDIADLIB, GUIO_MIDIPCSPK, GUIO_RENDERVGA, GUIO_RENDEREGA, GUIO_RENDERCGA, GAMEOPTION_EOB_HPGRAPHS) + Common::IT_ITA, + Common::kPlatformDOS, + ADGF_NO_FLAGS, + GUIO7(GUIO_NOSPEECH, GUIO_MIDIADLIB, GUIO_MIDIPCSPK, GUIO_RENDERVGA, GUIO_RENDEREGA, GUIO_RENDERCGA, GAMEOPTION_EOB_HPGRAPHS) }, EOB_FLAGS }, @@ -1594,7 +1594,7 @@ const KYRAGameDescription adGameDescs[] = { }, Common::EN_ANY, Common::kPlatformDOS, - ADGF_TESTING, + ADGF_NO_FLAGS, GUIO6(GUIO_NOSPEECH, GUIO_MIDIADLIB, GUIO_MIDIPCSPK, GUIO_RENDERVGA, GUIO_RENDEREGA, GAMEOPTION_EOB_HPGRAPHS) }, EOB2_FLAGS @@ -1610,7 +1610,7 @@ const KYRAGameDescription adGameDescs[] = { }, Common::DE_DEU, Common::kPlatformDOS, - ADGF_TESTING, + ADGF_NO_FLAGS, GUIO6(GUIO_NOSPEECH, GUIO_MIDIADLIB, GUIO_MIDIPCSPK, GUIO_RENDERVGA, GUIO_RENDEREGA, GAMEOPTION_EOB_HPGRAPHS) }, EOB2_FLAGS diff --git a/engines/kyra/gui_lol.cpp b/engines/kyra/gui_lol.cpp index cfdf762bba..5ff0b6a109 100644 --- a/engines/kyra/gui_lol.cpp +++ b/engines/kyra/gui_lol.cpp @@ -2219,7 +2219,7 @@ int GUI_LoL::runMenu(Menu &menu) { int fW = (d->w << 3) - wW; int fC = 0; - // LoL doesnt't have default higlighted items. No item should be + // LoL doesn't have default higlighted items. No item should be // highlighted when entering a new menu. // Instead, the respevtive struct entry is used to determine whether // a menu has scroll buttons or slider bars. diff --git a/engines/kyra/gui_v1.cpp b/engines/kyra/gui_v1.cpp index a75c14b071..92891f71b2 100644 --- a/engines/kyra/gui_v1.cpp +++ b/engines/kyra/gui_v1.cpp @@ -200,7 +200,7 @@ void GUI_v1::processHighlights(Menu &menu) { int mouseY = p.y; if (_vm->game() == GI_LOL && menu.highlightedItem != 255) { - // LoL doesnt't have default highlighted items. + // LoL doesn't have default highlighted items. // We use a highlightedItem value of 255 for this. // With LoL no highlighting should take place unless the diff --git a/engines/kyra/sound.cpp b/engines/kyra/sound.cpp index 3c3aff6ea9..6c0d529f96 100644 --- a/engines/kyra/sound.cpp +++ b/engines/kyra/sound.cpp @@ -37,7 +37,7 @@ namespace Kyra { Sound::Sound(KyraEngine_v1 *vm, Audio::Mixer *mixer) : _vm(vm), _mixer(mixer), _soundChannels(), _musicEnabled(1), - _sfxEnabled(true) { + _sfxEnabled(true) { } Sound::~Sound() { @@ -165,7 +165,7 @@ bool Sound::allVoiceChannelsPlaying() const { #pragma mark - MixedSoundDriver::MixedSoundDriver(KyraEngine_v1 *vm, Audio::Mixer *mixer, Sound *music, Sound *sfx) - : Sound(vm, mixer), _music(music), _sfx(sfx) { + : Sound(vm, mixer), _music(music), _sfx(sfx) { } MixedSoundDriver::~MixedSoundDriver() { @@ -289,16 +289,16 @@ void KyraEngine_v1::snd_playWanderScoreViaMap(int command, int restart) { //} if (_flags.platform == Common::kPlatformDOS || _flags.platform == Common::kPlatformMacintosh) { - assert(command*2+1 < _trackMapSize); - if (_curMusicTheme != _trackMap[command*2]) { - if (_trackMap[command*2] != -1 && _trackMap[command*2] != -2) - snd_playTheme(_trackMap[command*2], -1); + assert(command * 2 + 1 < _trackMapSize); + if (_curMusicTheme != _trackMap[command * 2]) { + if (_trackMap[command * 2] != -1 && _trackMap[command * 2] != -2) + snd_playTheme(_trackMap[command * 2], -1); } if (command != 1) { if (_lastMusicCommand != command) { _sound->haltTrack(); - _sound->playTrack(_trackMap[command*2+1]); + _sound->playTrack(_trackMap[command * 2 + 1]); } } else { _sound->beginFadeOut(); @@ -307,8 +307,8 @@ void KyraEngine_v1::snd_playWanderScoreViaMap(int command, int restart) { if (command == -1) { _sound->haltTrack(); } else { - assert(command*2+1 < _trackMapSize); - if (_trackMap[command*2] != -2 && command != _lastMusicCommand) { + assert(command * 2 + 1 < _trackMapSize); + if (_trackMap[command * 2] != -2 && command != _lastMusicCommand) { _sound->haltTrack(); _sound->playTrack(command); } diff --git a/engines/lab/configure.engine b/engines/lab/configure.engine index 18091b1b84..3be9545aa6 100644 --- a/engines/lab/configure.engine +++ b/engines/lab/configure.engine @@ -1,3 +1,3 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] -add_engine lab "Labyrinth of Time" no +add_engine lab "Labyrinth of Time" yes diff --git a/engines/lab/detection.cpp b/engines/lab/detection.cpp index d01dff2843..1fd3ca8944 100644 --- a/engines/lab/detection.cpp +++ b/engines/lab/detection.cpp @@ -48,7 +48,7 @@ static const ADGameDescription labDescriptions[] = { }, Common::EN_ANY, Common::kPlatformDOS, - ADGF_TESTING, + ADGF_NO_FLAGS, GUIO0() }, { @@ -61,7 +61,7 @@ static const ADGameDescription labDescriptions[] = { }, Common::EN_ANY, Common::kPlatformDOS, - Lab::GF_LOWRES | ADGF_TESTING, + Lab::GF_LOWRES | ADGF_NO_FLAGS, GUIO0() }, { @@ -75,7 +75,7 @@ static const ADGameDescription labDescriptions[] = { }, Common::EN_ANY, Common::kPlatformWindows, - ADGF_TESTING, + ADGF_NO_FLAGS, GUIO0() }, { @@ -115,7 +115,7 @@ uint32 LabEngine::getFeatures() const { class LabMetaEngine : public AdvancedMetaEngine { public: LabMetaEngine() : AdvancedMetaEngine(labDescriptions, sizeof(ADGameDescription), lab_setting) { - _singleid = "lab"; + _singleId = "lab"; _maxScanDepth = 4; _directoryGlobs = directoryGlobs; @@ -169,7 +169,6 @@ SaveStateList LabMetaEngine::listSaves(const char *target) const { Common::StringArray filenames; filenames = saveFileMan->listSavefiles(pattern.c_str()); - Common::sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..)*/ SaveStateList saveList; @@ -187,6 +186,8 @@ SaveStateList LabMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } @@ -196,26 +197,7 @@ int LabMetaEngine::getMaximumSaveSlot() const { void LabMetaEngine::removeSaveState(const char *target, int slot) const { Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); - Common::String filename = Common::String::format("%s.%03u", target, slot); - - saveFileMan->removeSavefile(filename.c_str()); - - Common::StringArray filenames; - Common::String pattern = target; - pattern += ".###"; - filenames = saveFileMan->listSavefiles(pattern.c_str()); - Common::sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) - - for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { - // Obtain the last 3 digits of the filename, since they correspond to the save slot - int slotNum = atoi(file->c_str() + file->size() - 3); - - // Rename every slot greater than the deleted slot, - if (slotNum > slot) { - saveFileMan->renameSavefile(file->c_str(), filename.c_str()); - filename = Common::String::format("%s.%03u", target, ++slot); - } - } + saveFileMan->removeSavefile(Common::String::format("%s.%03u", target, slot)); } SaveStateDescriptor LabMetaEngine::querySaveMetaInfos(const char *target, int slot) const { diff --git a/engines/lab/lab.cpp b/engines/lab/lab.cpp index 0a4cf4ec50..9b0ebfc4e5 100644 --- a/engines/lab/lab.cpp +++ b/engines/lab/lab.cpp @@ -214,12 +214,6 @@ void LabEngine::updateEvents() { Common::Error LabEngine::loadGameState(int slot) { bool result = loadGame(slot); - _curFileName = " "; - _closeDataPtr = nullptr; - _mainDisplay = true; - _followingCrumbs = false; - _event->simulateEvent(); - _graphics->_longWinInFront = false; return (result) ? Common::kNoError : Common::kUserCanceled; } @@ -229,11 +223,11 @@ Common::Error LabEngine::saveGameState(int slot, const Common::String &desc) { } bool LabEngine::canLoadGameStateCurrently() { - return !_anim->isPlaying() && !_introPlaying; + return !_introPlaying; } bool LabEngine::canSaveGameStateCurrently() { - return !_anim->isPlaying() && !_introPlaying; + return !_introPlaying; } } // End of namespace Lab diff --git a/engines/lab/processroom.cpp b/engines/lab/processroom.cpp index 44c8d65d7c..5093e8ef85 100644 --- a/engines/lab/processroom.cpp +++ b/engines/lab/processroom.cpp @@ -238,6 +238,8 @@ void LabEngine::doActions(const ActionList &actionList) { ActionList::const_iterator action; for (action = actionList.begin(); action != actionList.end(); ++action) { updateEvents(); + if (_quitLab || shouldQuit()) + return; switch (action->_actionType) { case kActionPlaySound: @@ -381,6 +383,8 @@ void LabEngine::doActions(const ActionList &actionList) { while (_system->getMillis() < targetMillis) { updateEvents(); + if (_quitLab || shouldQuit()) + return; _anim->diffNextFrame(); } } @@ -409,6 +413,8 @@ void LabEngine::doActions(const ActionList &actionList) { case kActionWaitSound: // used in scene 44 (heart of the labyrinth / ending) while (_music->isSoundEffectActive()) { updateEvents(); + if (_quitLab || shouldQuit()) + return; _anim->diffNextFrame(); waitTOF(); } diff --git a/engines/lab/savegame.cpp b/engines/lab/savegame.cpp index 16c4044839..656595e3e5 100644 --- a/engines/lab/savegame.cpp +++ b/engines/lab/savegame.cpp @@ -157,6 +157,11 @@ bool LabEngine::saveGame(int slot, const Common::String desc) { file->finalize(); delete file; + _mainDisplay = true; + _alternate = false; + _event->simulateEvent(); + _graphics->screenUpdate(); + return true; } @@ -202,6 +207,17 @@ bool LabEngine::loadGame(int slot) { delete file; + _curFileName = " "; + _closeDataPtr = nullptr; + _followingCrumbs = false; + _graphics->_longWinInFront = false; + _event->initMouse(); + + _mainDisplay = true; + _alternate = false; + _event->simulateEvent(); + _graphics->screenUpdate(); + return true; } @@ -238,11 +254,6 @@ bool LabEngine::saveRestoreGame() { delete dialog; } - _alternate = false; - _mainDisplay = true; - _event->initMouse(); - _graphics->screenUpdate(); - return isOK; } diff --git a/engines/lastexpress/configure.engine b/engines/lastexpress/configure.engine index 807b1a088b..66bac55dea 100644 --- a/engines/lastexpress/configure.engine +++ b/engines/lastexpress/configure.engine @@ -1,3 +1,3 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] -add_engine lastexpress "The Last Express" no "" "" "16bit" +add_engine lastexpress "The Last Express" no "" "" "16bit highres" diff --git a/engines/lastexpress/detection.cpp b/engines/lastexpress/detection.cpp index d790582104..52224e4ea5 100644 --- a/engines/lastexpress/detection.cpp +++ b/engines/lastexpress/detection.cpp @@ -199,8 +199,8 @@ static const ADGameDescription gameDescriptions[] = { class LastExpressMetaEngine : public AdvancedMetaEngine { public: LastExpressMetaEngine() : AdvancedMetaEngine(gameDescriptions, sizeof(ADGameDescription), lastExpressGames) { - _singleid = "lastexpress"; - _guioptions = GUIO2(GUIO_NOSUBTITLES, GUIO_NOSFX); + _singleId = "lastexpress"; + _guiOptions = GUIO2(GUIO_NOSUBTITLES, GUIO_NOSFX); } const char *getName() const { diff --git a/engines/lure/detection.cpp b/engines/lure/detection.cpp index 902e8afd65..690a358bc3 100644 --- a/engines/lure/detection.cpp +++ b/engines/lure/detection.cpp @@ -193,12 +193,12 @@ class LureMetaEngine : public AdvancedMetaEngine { public: LureMetaEngine() : AdvancedMetaEngine(Lure::gameDescriptions, sizeof(Lure::LureGameDescription), lureGames) { _md5Bytes = 1024; - _singleid = "lure"; + _singleId = "lure"; // Use kADFlagUseExtraAsHint to distinguish between EGA and VGA versions // of italian Lure when their datafiles sit in the same directory. _flags = kADFlagUseExtraAsHint; - _guioptions = GUIO1(GUIO_NOSPEECH); + _guiOptions = GUIO1(GUIO_NOSPEECH); } virtual const char *getName() const { @@ -245,7 +245,6 @@ SaveStateList LureMetaEngine::listSaves(const char *target) const { Common::String pattern = "lure.###"; filenames = saveFileMan->listSavefiles(pattern); - sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) SaveStateList saveList; for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { @@ -262,6 +261,8 @@ SaveStateList LureMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/made/detection.cpp b/engines/made/detection.cpp index 7adc3e1f98..636c2d147c 100644 --- a/engines/made/detection.cpp +++ b/engines/made/detection.cpp @@ -521,7 +521,7 @@ static MadeGameDescription g_fallbackDesc = { class MadeMetaEngine : public AdvancedMetaEngine { public: MadeMetaEngine() : AdvancedMetaEngine(Made::gameDescriptions, sizeof(Made::MadeGameDescription), madeGames) { - _singleid = "made"; + _singleId = "made"; } virtual const char *getName() const { diff --git a/engines/mads/debugger.cpp b/engines/mads/debugger.cpp index 740c19abad..b9731b1d31 100644 --- a/engines/mads/debugger.cpp +++ b/engines/mads/debugger.cpp @@ -158,7 +158,7 @@ bool Debugger::Cmd_ShowCodes(int argc, const char **argv) { Scene &scene = _vm->_game->_scene; // Copy the depth/walk surface to the background and flag for screen refresh - scene._depthSurface.copyTo(&scene._backgroundSurface); + scene._depthSurface.blitFrom(scene._backgroundSurface); scene._spriteSlots.fullRefresh(); // Draw the locations of scene nodes onto the background diff --git a/engines/mads/detection.cpp b/engines/mads/detection.cpp index 0357bd4bcc..b3ba60b6d0 100644 --- a/engines/mads/detection.cpp +++ b/engines/mads/detection.cpp @@ -192,7 +192,6 @@ SaveStateList MADSMetaEngine::listSaves(const char *target) const { MADS::MADSSavegameHeader header; filenames = saveFileMan->listSavefiles(pattern); - sort(filenames.begin(), filenames.end()); // Sort to get the files in numerical order SaveStateList saveList; for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { @@ -213,6 +212,8 @@ SaveStateList MADSMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/mads/dialogs.cpp b/engines/mads/dialogs.cpp index d9a1e53964..fa656a90c1 100644 --- a/engines/mads/dialogs.cpp +++ b/engines/mads/dialogs.cpp @@ -54,21 +54,19 @@ Dialog::~Dialog() { void Dialog::save() { _savedSurface = new MSurface(_width, _height); - _vm->_screen.copyTo(_savedSurface, + _savedSurface->blitFrom(*_vm->_screen, Common::Rect(_position.x, _position.y, _position.x + _width, _position.y + _height), Common::Point()); - _vm->_screen.copyRectToScreen(getBounds()); +// _vm->_screen->copyRectToScreen(getBounds()); } void Dialog::restore() { if (_savedSurface) { - _savedSurface->copyTo(&_vm->_screen, _position); + _vm->_screen->blitFrom(*_savedSurface, _position); delete _savedSurface; _savedSurface = nullptr; - _vm->_screen.copyRectToScreen(getBounds()); - Common::copy(&_dialogPalette[0], &_dialogPalette[8 * 3], &_vm->_palette->_mainPalette[248 * 3]); _vm->_palette->setPalette(&_vm->_palette->_mainPalette[248 * 3], 248, 8); @@ -87,16 +85,16 @@ void Dialog::draw() { // Draw the dialog // Fill entire content of dialog Common::Rect bounds = getBounds(); - _vm->_screen.fillRect(bounds, TEXTDIALOG_BACKGROUND); + _vm->_screen->fillRect(bounds, TEXTDIALOG_BACKGROUND); // Draw the outer edge lines - _vm->_screen.hLine(_position.x + 1, _position.y + _height - 2, + _vm->_screen->hLine(_position.x + 1, _position.y + _height - 2, _position.x + _width - 2, TEXTDIALOG_EDGE); - _vm->_screen.hLine(_position.x, _position.y + _height - 1, + _vm->_screen->hLine(_position.x, _position.y + _height - 1, _position.x + _width - 1, TEXTDIALOG_EDGE); - _vm->_screen.vLine(_position.x + _width - 2, _position.y + 2, + _vm->_screen->vLine(_position.x + _width - 2, _position.y + 2, _position.y + _height - 2, TEXTDIALOG_EDGE); - _vm->_screen.vLine(_position.x + _width - 1, _position.y + 1, + _vm->_screen->vLine(_position.x + _width - 1, _position.y + 1, _position.y + _height - 1, TEXTDIALOG_EDGE); // Draw the gravelly dialog content @@ -125,8 +123,9 @@ void Dialog::calculateBounds() { void Dialog::drawContent(const Common::Rect &r, int seed, byte color1, byte color2) { uint16 currSeed = seed ? seed : 0xB78E; + Graphics::Surface dest = _vm->_screen->getSubArea(r); for (int yp = 0; yp < r.height(); ++yp) { - byte *destP = _vm->_screen.getBasePtr(r.left, r.top + yp); + byte *destP = (byte *)dest.getBasePtr(0, yp); for (int xp = 0; xp < r.width(); ++xp) { uint16 seedAdjust = currSeed; @@ -326,7 +325,7 @@ void TextDialog::draw() { for (int lineNum = 0; lineNum <= _numLines; ++lineNum) { if (_lineXp[lineNum] == -1) { // Draw a line across the entire dialog - _vm->_screen.hLine(_position.x + 2, + _vm->_screen->hLine(_position.x + 2, lineYp + (_font->getHeight() + 1) / 2, _position.x + _width - 4, TEXTDIALOG_BLACK); } else { @@ -336,21 +335,19 @@ void TextDialog::draw() { if (_lineXp[lineNum] & 0x40) ++yp; - _font->writeString(&_vm->_screen, _lines[lineNum], + _font->writeString(_vm->_screen, _lines[lineNum], Common::Point(xp, yp), 1); if (_lineXp[lineNum] & 0x80) { // Draw an underline under the text int lineWidth = _font->getWidth(_lines[lineNum], 1); - _vm->_screen.hLine(xp, yp + _font->getHeight(), xp + lineWidth, + _vm->_screen->hLine(xp, yp + _font->getHeight(), xp + lineWidth, TEXTDIALOG_BLACK); } } lineYp += _font->getHeight() + 1; } - - _vm->_screen.copyRectToScreen(getBounds()); } void TextDialog::calculateBounds() { @@ -360,10 +357,10 @@ void TextDialog::calculateBounds() { if (_position.y == -1) _position.y = 100 - (_height / 2); - if ((_position.x + _width) > _vm->_screen.getWidth()) - _position.x = _vm->_screen.getWidth() - (_position.x + _width); - if ((_position.y + _height) > _vm->_screen.getHeight()) - _position.y = _vm->_screen.getHeight() - (_position.y + _height); + if ((_position.x + _width) > _vm->_screen->w) + _position.x = _vm->_screen->w - (_position.x + _width); + if ((_position.y + _height) > _vm->_screen->h) + _position.y = _vm->_screen->h - (_position.y + _height); } void TextDialog::drawWithInput() { @@ -452,7 +449,7 @@ FullScreenDialog::FullScreenDialog(MADSEngine *vm) : _vm(vm) { } FullScreenDialog::~FullScreenDialog() { - _vm->_screen.resetClipBounds(); + _vm->_screen->resetClipBounds(); _vm->_game->_scene.restrictScene(); } @@ -491,16 +488,13 @@ void FullScreenDialog::display() { game._trigger = 0; // Clear the screen and draw the upper and lower horizontal lines - _vm->_screen.empty(); + _vm->_screen->clear(); _vm->_palette->setLowRange(); - _vm->_screen.hLine(0, 20, MADS_SCREEN_WIDTH, 2); - _vm->_screen.hLine(0, 179, MADS_SCREEN_WIDTH, 2); - _vm->_screen.resetClipBounds(); - _vm->_screen.copyRectToScreen(Common::Rect(0, 0, MADS_SCREEN_WIDTH, MADS_SCREEN_HEIGHT)); + _vm->_screen->hLine(0, 20, MADS_SCREEN_WIDTH, 2); + _vm->_screen->hLine(0, 179, MADS_SCREEN_WIDTH, 2); // Restrict the screen to the area between the two lines - _vm->_screen.setClipBounds(Common::Rect(0, DIALOG_TOP, MADS_SCREEN_WIDTH, - DIALOG_TOP + MADS_SCENE_HEIGHT)); + _vm->_screen->setClipBounds(Common::Rect(0, DIALOG_TOP, MADS_SCREEN_WIDTH, DIALOG_TOP + MADS_SCENE_HEIGHT)); _vm->_game->_scene.restrictScene(); if (_screenId > 0) diff --git a/engines/mads/dragonsphere/dragonsphere_scenes.cpp b/engines/mads/dragonsphere/dragonsphere_scenes.cpp index 938931e80d..a18d03d143 100644 --- a/engines/mads/dragonsphere/dragonsphere_scenes.cpp +++ b/engines/mads/dragonsphere/dragonsphere_scenes.cpp @@ -218,7 +218,7 @@ void SceneInfoDragonsphere::loadCodes(MSurface &depthSurface, int variant) { } void SceneInfoDragonsphere::loadCodes(MSurface &depthSurface, Common::SeekableReadStream *stream) { - byte *destP = depthSurface.getData(); + byte *destP = (byte *)depthSurface.getPixels(); byte *walkMap = new byte[stream->size()]; stream->read(walkMap, stream->size()); diff --git a/engines/mads/events.cpp b/engines/mads/events.cpp index 7463704c4b..de83260b0f 100644 --- a/engines/mads/events.cpp +++ b/engines/mads/events.cpp @@ -98,7 +98,7 @@ void EventsManager::changeCursor() { // Check for hotspot indication pixels along the right-hand and bottom // row. Put together, these give the cursor's hotspot x,y int hotspotX = 0, hotspotY = 0; - byte *cursorData = cursor->getData(); + const byte *cursorData = (const byte *)cursor->getPixels(); for (int idx = 0; idx < cursor->w; ++idx) { if (cursorData[(cursor->h - 1) * cursor->w + idx] != transIndex) hotspotX = idx; @@ -110,7 +110,7 @@ void EventsManager::changeCursor() { // Reduce the cursor data to remove the last column from each row, since // the cursor routines don't have a pitch option byte *destCursor = new byte[(cursor->w - 1) * (cursor->h - 1)]; - byte *srcP = cursorData; + const byte *srcP = cursorData; byte *destP = destCursor; for (int idx = 0; idx < (cursor->h - 1); ++idx) { @@ -217,7 +217,7 @@ bool EventsManager::checkForNextFrameCounter() { _vm->_debugger->onFrame(); // Display the frame - _vm->_screen.updateScreen(); + _vm->_screen->update(); // Signal the ScummVM debugger _vm->_debugger->onFrame(); diff --git a/engines/mads/font.cpp b/engines/mads/font.cpp index 3e6d23fe6f..3828c3df8e 100644 --- a/engines/mads/font.cpp +++ b/engines/mads/font.cpp @@ -167,16 +167,13 @@ int Font::writeString(MSurface *surface, const Common::String &msg, const Common return x; int bottom = y + height - 1; - if (bottom > surface->getHeight() - 1) { - height -= MIN(height, bottom - (surface->getHeight() - 1)); + if (bottom > surface->h - 1) { + height -= MIN(height, bottom - (surface->h - 1)); } if (height <= 0) return x; - byte *destPtr = surface->getBasePtr(x, y); - uint8 *oldDestPtr = destPtr; - int xPos = x; const char *text = msg.c_str(); @@ -185,10 +182,11 @@ int Font::writeString(MSurface *surface, const Common::String &msg, const Common int charWidth = _charWidths[(byte)theChar]; if (charWidth > 0) { - if (xPos + charWidth > xEnd) return xPos; + Graphics::Surface dest = surface->getSubArea( + Common::Rect(xPos, y, xPos + charWidth, y + height)); uint8 *charData = &_charData[_charOffs[(byte)theChar]]; int bpp = getBpp(charWidth); @@ -196,6 +194,8 @@ int Font::writeString(MSurface *surface, const Common::String &msg, const Common charData += bpp * skipY; for (int i = 0; i < height; i++) { + byte *destPtr = (byte *)dest.getBasePtr(0, i); + for (int j = 0; j < bpp; j++) { if (*charData & 0xc0) *destPtr = _fontColors[(*charData & 0xc0) >> 6]; @@ -211,22 +211,13 @@ int Font::writeString(MSurface *surface, const Common::String &msg, const Common destPtr++; charData++; } - - destPtr += surface->getWidth() - bpp * 4; - } - - destPtr = oldDestPtr + charWidth + spaceWidth; - oldDestPtr = destPtr; - } xPos += charWidth + spaceWidth; - } return xPos; - } int Font::getWidth(const Common::String &msg, int spaceWidth) { diff --git a/engines/mads/game.cpp b/engines/mads/game.cpp index 8ebea2a3b2..0a6741ba7a 100644 --- a/engines/mads/game.cpp +++ b/engines/mads/game.cpp @@ -498,7 +498,7 @@ void Game::loadGame(int slotNumber) { _scene._currentSceneId = -2; _sectionNumber = _scene._nextSceneId / 100; _scene._frameStartTime = _vm->_events->getFrameCounter(); - _vm->_screen._shakeCountdown = -1; + _vm->_screen->_shakeCountdown = -1; // Default the selected inventory item to the first one, if the player has any _scene._userInterface._selectedInvIndex = _objects._inventoryList.size() > 0 ? 0 : -1; @@ -600,7 +600,8 @@ void Game::createThumbnail() { uint8 thumbPalette[PALETTE_SIZE]; _vm->_palette->grabPalette(thumbPalette, 0, PALETTE_COUNT); _saveThumb = new Graphics::Surface(); - ::createThumbnail(_saveThumb, _vm->_screen.getData(), MADS_SCREEN_WIDTH, MADS_SCREEN_HEIGHT, thumbPalette); + ::createThumbnail(_saveThumb, (const byte *)_vm->_screen->getPixels(), + MADS_SCREEN_WIDTH, MADS_SCREEN_HEIGHT, thumbPalette); } void Game::syncTimers(SyncType slaveType, int slaveId, SyncType masterType, int masterId) { diff --git a/engines/mads/mads.cpp b/engines/mads/mads.cpp index 963a35c24c..29bcd10094 100644 --- a/engines/mads/mads.cpp +++ b/engines/mads/mads.cpp @@ -94,22 +94,15 @@ void MADSEngine::initialize() { _palette = new Palette(this); Font::init(this); _font = new Font(); - _screen.init(); + _screen = new Screen(); _sound = new SoundManager(this, _mixer); _audio = new AudioPlayer(_mixer, getGameID()); _game = Game::init(this); - - switch (getGameID()) { - case GType_RexNebular: - _gameConv = nullptr; - break; - default: - _gameConv = new GameConversations(this); - } + _gameConv = new GameConversations(this); loadOptions(); - _screen.empty(); + _screen->clear(); } void MADSEngine::loadOptions() { diff --git a/engines/mads/mads.h b/engines/mads/mads.h index eb808de32f..52f71f7c79 100644 --- a/engines/mads/mads.h +++ b/engines/mads/mads.h @@ -100,7 +100,7 @@ public: GameConversations * _gameConv; Palette *_palette; Resources *_resources; - ScreenSurface _screen; + Screen *_screen; SoundManager *_sound; AudioPlayer *_audio; bool _easyMouse; diff --git a/engines/mads/menu_views.cpp b/engines/mads/menu_views.cpp index 10d5a2179a..9050ca6081 100644 --- a/engines/mads/menu_views.cpp +++ b/engines/mads/menu_views.cpp @@ -253,7 +253,7 @@ void TextView::processCommand() { SceneInfo *sceneInfo = SceneInfo::init(_vm); sceneInfo->_width = MADS_SCREEN_WIDTH; sceneInfo->_height = MADS_SCENE_HEIGHT; - _spareScreens[spareIndex].setSize(MADS_SCREEN_WIDTH, MADS_SCENE_HEIGHT); + _spareScreens[spareIndex].create(MADS_SCREEN_WIDTH, MADS_SCENE_HEIGHT); sceneInfo->loadMadsV1Background(screenId, "", SCENEFLAG_TRANSLATE, _spareScreens[spareIndex]); @@ -346,9 +346,11 @@ void TextView::doFrame() { // If a screen transition is in progress and it's time for another column, handle it if (_spareScreen) { - byte *srcP = _spareScreen->getBasePtr(_translationX, 0); - byte *bgP = scene._backgroundSurface.getBasePtr(_translationX, 0); - byte *screenP = (byte *)_vm->_screen.getBasePtr(_translationX, 0); + const byte *srcP = (const byte *)_spareScreen->getBasePtr(_translationX, 0); + byte *bgP = (byte *)scene._backgroundSurface.getBasePtr(_translationX, 0); + + Graphics::Surface dest = _vm->_screen->getSubArea(Common::Rect(_translationX, 0, _translationX + 1, 0)); + byte *screenP = (byte *)dest.getBasePtr(0, 0); for (int y = 0; y < MADS_SCENE_HEIGHT; ++y, srcP += MADS_SCREEN_WIDTH, bgP += MADS_SCREEN_WIDTH, screenP += MADS_SCREEN_WIDTH) { @@ -356,10 +358,6 @@ void TextView::doFrame() { *screenP = *srcP; } - // Flag the column of the screen is modified - _vm->_screen.copyRectToScreen(Common::Rect(_translationX, 0, - _translationX + 1, MADS_SCENE_HEIGHT)); - // Keep moving the column to copy to the right if (++_translationX == MADS_SCREEN_WIDTH) { // Surface transition is complete @@ -571,6 +569,7 @@ void AnimationView::doFrame() { void AnimationView::loadNextResource() { Scene &scene = _vm->_game->_scene; Palette &palette = *_vm->_palette; + Screen &screen = *_vm->_screen; ResourceEntry &resEntry = _resources[_resourceIndex]; Common::Array<PaletteCycle> paletteCycles; @@ -587,12 +586,15 @@ void AnimationView::loadNextResource() { // Handle the bars at the top/bottom if (resEntry._showWhiteBars) { // For animations the screen has been clipped to the middle 156 rows. - // So although it's slightly messy, bypass our screen class entirely, - // and draw the horizontal lines directly on the physiacl screen surface - Graphics::Surface *s = g_system->lockScreen(); - s->hLine(0, 20, MADS_SCREEN_WIDTH, 253); - s->hLine(0, 179, MADS_SCREEN_WIDTH, 253); - g_system->unlockScreen(); + // So although it's slightly messy, temporarily reset clip bounds + // so we can redraw the white lines + Common::Rect clipBounds = screen.getClipBounds(); + screen.resetClipBounds(); + + screen.hLine(0, 20, MADS_SCREEN_WIDTH, 253); + screen.hLine(0, 179, MADS_SCREEN_WIDTH, 253); + + screen.setClipBounds(clipBounds); } // Load the new animation diff --git a/engines/mads/msurface.cpp b/engines/mads/msurface.cpp index f768624278..40c69c0f08 100644 --- a/engines/mads/msurface.cpp +++ b/engines/mads/msurface.cpp @@ -32,37 +32,6 @@ namespace MADS { MADSEngine *MSurface::_vm = nullptr; -MSurface::MSurface() { - pixels = nullptr; - _freeFlag = false; -} - -MSurface::MSurface(int width, int height) { - pixels = nullptr; - _freeFlag = false; - setSize(width, height); -} - -MSurface::~MSurface() { - if (_freeFlag) - Graphics::Surface::free(); -} - -void MSurface::setSize(int width, int height) { - if (_freeFlag) - Graphics::Surface::free(); - Graphics::Surface::create(width, height, Graphics::PixelFormat::createFormatCLUT8()); - _freeFlag = true; -} - -void MSurface::setPixels(byte *pData, int horizSize, int vertSize) { - _freeFlag = false; - pixels = pData; - w = pitch = horizSize; - h = vertSize; - format.bytesPerPixel = 1; -} - int MSurface::scaleValue(int value, int scale, int err) { int scaled = 0; while (value--) { @@ -76,7 +45,6 @@ int MSurface::scaleValue(int value, int scale, int err) { } void MSurface::drawSprite(const Common::Point &pt, SpriteInfo &info, const Common::Rect &clipRect) { - enum { kStatusSkip, kStatusScale, @@ -116,8 +84,8 @@ void MSurface::drawSprite(const Common::Point &pt, SpriteInfo &info, const Commo return; int heightAmt = scaledHeight; - byte *src = info.sprite->getData(); - byte *dst = getBasePtr(x - info.hotX - clipX, y - info.hotY - clipY); + const byte *src = (const byte *)info.sprite->getPixels(); + byte *dst = (byte *)getBasePtr(x - info.hotX - clipX, y - info.hotY - clipY); int status = kStatusSkip; byte *scaledLineBuf = new byte[scaledWidth]; @@ -138,7 +106,7 @@ void MSurface::drawSprite(const Common::Point &pt, SpriteInfo &info, const Commo byte *lineDst = scaledLineBuf; int curErrX = errX; int width = scaledWidth; - byte *tempSrc = src; + const byte *tempSrc = src; int startX = clipX; while (width > 0) { byte pixel = *tempSrc++; @@ -201,63 +169,136 @@ void MSurface::drawSprite(const Common::Point &pt, SpriteInfo &info, const Commo } delete[] scaledLineBuf; - } -void MSurface::empty() { - Common::fill(getBasePtr(0, 0), getBasePtr(0, h), 0); +void MSurface::scrollX(int xAmount) { + if (xAmount == 0) + return; + + byte buffer[80]; + int direction = (xAmount > 0) ? -1 : 1; + int xSize = ABS(xAmount); + assert(xSize <= 80); + + byte *srcP = (byte *)getBasePtr(0, 0); + + for (int y = 0; y < this->h; ++y, srcP += pitch) { + if (direction < 0) { + // Copy area to be overwritten + Common::copy(srcP, srcP + xSize, &buffer[0]); + // Shift the remainder of the line over the given area + Common::copy(srcP + xSize, srcP + this->w, srcP); + // Move buffered area to the end of the line + Common::copy(&buffer[0], &buffer[xSize], srcP + this->w - xSize); + } else { + // Copy area to be overwritten + Common::copy_backward(srcP + this->w - xSize, srcP + this->w, &buffer[80]); + // Shift the remainder of the line over the given area + Common::copy_backward(srcP, srcP + this->w - xSize, srcP + this->w); + // Move buffered area to the start of the line + Common::copy_backward(&buffer[80 - xSize], &buffer[80], srcP + xSize); + } + } + + markAllDirty(); } -void MSurface::copyFrom(MSurface *src, const Common::Rect &srcBounds, - const Common::Point &destPos, int transparentColor) { - // Validation of the rectangle and position - int destX = destPos.x, destY = destPos.y; - if ((destX >= w) || (destY >= h)) +void MSurface::scrollY(int yAmount) { + if (yAmount == 0) return; - Common::Rect copyRect = srcBounds; - if (destX < 0) { - copyRect.left += -destX; - destX = 0; - } else if (destX + copyRect.width() > w) { - copyRect.right -= destX + copyRect.width() - w; - } - if (destY < 0) { - copyRect.top += -destY; - destY = 0; - } else if (destY + copyRect.height() > h) { - copyRect.bottom -= destY + copyRect.height() - h; + int direction = (yAmount > 0) ? 1 : -1; + int ySize = ABS(yAmount); + assert(ySize < (this->h / 2)); + assert(this->w == pitch); + + int blockSize = ySize * this->w; + byte *tempData = new byte[blockSize]; + byte *pixelsP = (byte *)getBasePtr(0, 0); + + if (direction > 0) { + // Buffer the lines to be overwritten + byte *srcP = (byte *)getBasePtr(0, this->h - ySize); + Common::copy(srcP, srcP + (pitch * ySize), tempData); + // Vertically shift all the lines + Common::copy_backward(pixelsP, pixelsP + (pitch * (this->h - ySize)), + pixelsP + (pitch * this->h)); + // Transfer the buffered lines top the top of the screen + Common::copy(tempData, tempData + blockSize, pixelsP); + } else { + // Buffer the lines to be overwritten + Common::copy(pixelsP, pixelsP + (pitch * ySize), tempData); + // Vertically shift all the lines + Common::copy(pixelsP + (pitch * ySize), pixelsP + (pitch * this->h), pixelsP); + // Transfer the buffered lines to the bottom of the screen + Common::copy(tempData, tempData + blockSize, pixelsP + (pitch * (this->h - ySize))); } - if (!copyRect.isValidRect()) - return; + markAllDirty(); + delete[] tempData; +} - // Copy the specified area +void MSurface::translate(Common::Array<RGB6> &palette) { + for (int y = 0; y < this->h; ++y) { + byte *pDest = (byte *)getBasePtr(0, y); + + for (int x = 0; x < this->w; ++x, ++pDest) { + if (*pDest < 255) // scene 752 has some palette indices of 255 + *pDest = palette[*pDest]._palIndex; + } + } - byte *data = src->getData(); - byte *srcPtr = data + (src->getWidth() * copyRect.top + copyRect.left); - byte *destPtr = (byte *)pixels + (destY * getWidth()) + destX; + markAllDirty(); +} - for (int rowCtr = 0; rowCtr < copyRect.height(); ++rowCtr) { - if (transparentColor == -1) { - // No transparency, so copy line over - Common::copy(srcPtr, srcPtr + copyRect.width(), destPtr); - } else { - // Copy each byte one at a time checking for the transparency color - for (int xCtr = 0; xCtr < copyRect.width(); ++xCtr) - if (srcPtr[xCtr] != transparentColor) destPtr[xCtr] = srcPtr[xCtr]; +void MSurface::translate(byte map[PALETTE_COUNT]) { + for (int y = 0; y < this->h; ++y) { + byte *pDest = (byte *)getBasePtr(0, y); + + for (int x = 0; x < this->w; ++x, ++pDest) { + *pDest = map[*pDest]; } + } + + markAllDirty(); +} + +MSurface *MSurface::flipHorizontal() const { + MSurface *dest = new MSurface(this->w, this->h); + + for (int y = 0; y < this->h; ++y) { + const byte *srcP = getBasePtr(this->w - 1, y); + byte *destP = dest->getBasePtr(0, y); - srcPtr += src->getWidth(); - destPtr += getWidth(); + for (int x = 0; x < this->w; ++x) + *destP++ = *srcP--; + } + + return dest; +} + +void MSurface::copyRectTranslate(MSurface &srcSurface, const byte *paletteMap, + const Common::Point &destPos, const Common::Rect &srcRect) { + // Loop through the lines + for (int yCtr = 0; yCtr < srcRect.height(); ++yCtr) { + const byte *srcP = (const byte *)srcSurface.getBasePtr(srcRect.left, srcRect.top + yCtr); + byte *destP = (byte *)getBasePtr(destPos.x, destPos.y + yCtr); + + // Copy the line over + for (int xCtr = 0; xCtr < srcRect.width(); ++xCtr, ++srcP, ++destP) { + *destP = paletteMap[*srcP]; + } } + + addDirtyRect(Common::Rect(destPos.x, destPos.y, destPos.x + srcRect.width(), + destPos.y + srcRect.height())); } -void MSurface::copyFrom(MSurface *src, const Common::Point &destPos, int depth, +void MSurface::copyFrom(MSurface &src, const Common::Point &destPos, int depth, DepthSurface *depthSurface, int scale, bool flipped, int transparentColor) { int destX = destPos.x, destY = destPos.y; - int frameWidth = src->getWidth(); - int frameHeight = src->getHeight(); + int frameWidth = src.w; + int frameHeight = src.h; int direction = flipped ? -1 : 1; int highestDim = MAX(frameWidth, frameHeight); @@ -271,7 +312,8 @@ void MSurface::copyFrom(MSurface *src, const Common::Point &destPos, int depth, distCtr += scale; if (distCtr < 100) { lineDist[distIndex] = false; - } else { + } + else { lineDist[distIndex] = true; distCtr -= 100; @@ -290,18 +332,20 @@ void MSurface::copyFrom(MSurface *src, const Common::Point &destPos, int depth, // Special case for quicker drawing of non-scaled images if (scale == 100 || scale == -1) { // Copy the specified area - Common::Rect copyRect(0, 0, src->getWidth(), src->getHeight()); + Common::Rect copyRect(0, 0, src.w, src.h); if (destX < 0) { copyRect.left += -destX; destX = 0; - } else if (destX + copyRect.width() > w) { + } + else if (destX + copyRect.width() > w) { copyRect.right -= destX + copyRect.width() - w; } if (destY < 0) { copyRect.top += -destY; destY = 0; - } else if (destY + copyRect.height() > h) { + } + else if (destY + copyRect.height() > h) { copyRect.bottom -= destY + copyRect.height() - h; } @@ -311,9 +355,9 @@ void MSurface::copyFrom(MSurface *src, const Common::Point &destPos, int depth, if (flipped) copyRect.moveTo(0, copyRect.top); - byte *data = src->getData(); - byte *srcPtr = data + (src->getWidth() * copyRect.top + copyRect.left); - byte *destPtr = (byte *)pixels + (destY * pitch) + destX; + byte *data = src.getPixels(); + byte *srcPtr = data + (src.w * copyRect.top + copyRect.left); + byte *destPtr = (byte *)getPixels() + (destY * pitch) + destX; if (flipped) srcPtr += copyRect.width() - 1; @@ -329,18 +373,18 @@ void MSurface::copyFrom(MSurface *src, const Common::Point &destPos, int depth, destPtr[xCtr] = *srcP; } - srcPtr += src->getWidth(); - destPtr += getWidth(); + srcPtr += src.w; + destPtr += this->w; } return; } // Start of draw logic for scaled sprites - const byte *srcPixelsP = src->getData(); + const byte *srcPixelsP = src.getPixels(); - int destRight = this->getWidth() - 1; - int destBottom = this->getHeight() - 1; + int destRight = this->w - 1; + int destBottom = this->h - 1; // Check x bounding area int spriteLeft = 0; @@ -387,7 +431,7 @@ void MSurface::copyFrom(MSurface *src, const Common::Point &destPos, int depth, spriteLeft = spriteLeft * direction; // Loop through the lines of the sprite - for (int yp = 0, sprY = -1; yp < frameHeight; ++yp, srcPixelsP += src->pitch) { + for (int yp = 0, sprY = -1; yp < frameHeight; ++yp, srcPixelsP += src.pitch) { if (!lineDist[yp]) // Not a display line, so skip it continue; @@ -411,8 +455,8 @@ void MSurface::copyFrom(MSurface *src, const Common::Point &destPos, int depth, continue; // Get depth of current output pixel in depth surface - Common::Point pt((destP - (byte *)this->pixels) % this->pitch, - (destP - (byte *)this->pixels) / this->pitch); + Common::Point pt((destP - (byte *)getPixels()) % this->pitch, + (destP - (byte *)getPixels()) / this->pitch); int pixelDepth = (depthSurface == nullptr) ? 15 : depthSurface->getDepth(pt); if ((*srcP != transparentColor) && (depth <= pixelDepth)) @@ -424,119 +468,8 @@ void MSurface::copyFrom(MSurface *src, const Common::Point &destPos, int depth, // Move to the next destination line destPixelsP += this->pitch; } -} - -void MSurface::scrollX(int xAmount) { - if (xAmount == 0) - return; - - byte buffer[80]; - int direction = (xAmount > 0) ? -1 : 1; - int xSize = ABS(xAmount); - assert(xSize <= 80); - - byte *srcP = getBasePtr(0, 0); - - for (int y = 0; y < this->h; ++y, srcP += pitch) { - if (direction < 0) { - // Copy area to be overwritten - Common::copy(srcP, srcP + xSize, &buffer[0]); - // Shift the remainder of the line over the given area - Common::copy(srcP + xSize, srcP + this->w, srcP); - // Move buffered area to the end of the line - Common::copy(&buffer[0], &buffer[xSize], srcP + this->w - xSize); - } else { - // Copy area to be overwritten - Common::copy_backward(srcP + this->w - xSize, srcP + this->w, &buffer[80]); - // Shift the remainder of the line over the given area - Common::copy_backward(srcP, srcP + this->w - xSize, srcP + this->w); - // Move buffered area to the start of the line - Common::copy_backward(&buffer[80 - xSize], &buffer[80], srcP + xSize); - } - } -} - -void MSurface::scrollY(int yAmount) { - if (yAmount == 0) - return; - - int direction = (yAmount > 0) ? 1 : -1; - int ySize = ABS(yAmount); - assert(ySize < (this->h / 2)); - assert(this->w == pitch); - - int blockSize = ySize * this->w; - byte *tempData = new byte[blockSize]; - byte *pixelsP = getBasePtr(0, 0); - - if (direction > 0) { - // Buffer the lines to be overwritten - byte *srcP = (byte *)getBasePtr(0, this->h - ySize); - Common::copy(srcP, srcP + (pitch * ySize), tempData); - // Vertically shift all the lines - Common::copy_backward(pixelsP, pixelsP + (pitch * (this->h - ySize)), - pixelsP + (pitch * this->h)); - // Transfer the buffered lines top the top of the screen - Common::copy(tempData, tempData + blockSize, pixelsP); - } else { - // Buffer the lines to be overwritten - Common::copy(pixelsP, pixelsP + (pitch * ySize), tempData); - // Vertically shift all the lines - Common::copy(pixelsP + (pitch * ySize), pixelsP + (pitch * this->h), pixelsP); - // Transfer the buffered lines to the bottom of the screen - Common::copy(tempData, tempData + blockSize, pixelsP + (pitch * (this->h - ySize))); - } - - delete[] tempData; -} - -void MSurface::translate(Common::Array<RGB6> &palette) { - for (int y = 0; y < this->h; ++y) { - byte *pDest = getBasePtr(0, y); - - for (int x = 0; x < this->w; ++x, ++pDest) { - if (*pDest < 255) // scene 752 has some palette indices of 255 - *pDest = palette[*pDest]._palIndex; - } - } -} - -void MSurface::translate(byte map[PALETTE_COUNT]) { - for (int y = 0; y < this->h; ++y) { - byte *pDest = getBasePtr(0, y); - - for (int x = 0; x < this->w; ++x, ++pDest) { - *pDest = map[*pDest]; - } - } -} - -MSurface *MSurface::flipHorizontal() const { - MSurface *dest = new MSurface(this->w, this->h); - - for (int y = 0; y < this->h; ++y) { - const byte *srcP = getBasePtr(this->w - 1, y); - byte *destP = dest->getBasePtr(0, y); - - for (int x = 0; x < this->w; ++x) - *destP++ = *srcP--; - } - - return dest; -} - -void MSurface::copyRectTranslate(MSurface &srcSurface, const byte *paletteMap, - const Common::Point &destPos, const Common::Rect &srcRect) { - // Loop through the lines - for (int yCtr = 0; yCtr < srcRect.height(); ++yCtr) { - const byte *srcP = srcSurface.getBasePtr(srcRect.left, srcRect.top + yCtr); - byte *destP = getBasePtr(destPos.x, destPos.y + yCtr); - // Copy the line over - for (int xCtr = 0; xCtr < srcRect.width(); ++xCtr, ++srcP, ++destP) { - *destP = paletteMap[*srcP]; - } - } + addDirtyRect(Common::Rect(destX, destY, destX + frameWidth, destY + frameHeight)); } /*------------------------------------------------------------------------*/ @@ -544,26 +477,26 @@ void MSurface::copyRectTranslate(MSurface &srcSurface, const byte *paletteMap, int DepthSurface::getDepth(const Common::Point &pt) { if (_depthStyle == 2) { int bits = (3 - (pt.x % 4)) * 2; - byte v = *getBasePtr(pt.x >> 2, pt.y); + byte v = *(const byte *)getBasePtr(pt.x >> 2, pt.y); return v >> bits; } else { if (pt.x < 0 || pt.y < 0 || pt.x >= this->w || pt.y >= this->h) return 0; - return *getBasePtr(pt.x, pt.y) & 0xF; + return *(const byte *)getBasePtr(pt.x, pt.y) & 0xF; } } int DepthSurface::getDepthHighBit(const Common::Point &pt) { if (_depthStyle == 2) { int bits = (3 - (pt.x % 4)) * 2; - byte v = *getBasePtr(pt.x >> 2, pt.y); + byte v = *(const byte *)getBasePtr(pt.x >> 2, pt.y); return (v >> bits) & 2; } else { if (pt.x < 0 || pt.y < 0 || pt.x >= this->w || pt.y >= this->h) return 0; - return *getBasePtr(pt.x, pt.y) & 0x80; + return *(const byte *)getBasePtr(pt.x, pt.y) & 0x80; } } diff --git a/engines/mads/msurface.h b/engines/mads/msurface.h index 80891afb83..e92770900d 100644 --- a/engines/mads/msurface.h +++ b/engines/mads/msurface.h @@ -25,7 +25,7 @@ #include "common/scummsys.h" #include "common/rect.h" -#include "graphics/surface.h" +#include "graphics/managed_surface.h" #include "mads/palette.h" namespace MADS { @@ -50,22 +50,21 @@ struct SpriteInfo { /* * MADS graphics surface */ -class MSurface : public Graphics::Surface { +class MSurface : virtual public Graphics::ManagedSurface { +private: + /** + * Helper method for calculating new dimensions when scaling a sprite + */ + int scaleValue(int value, int scale, int err); protected: static MADSEngine *_vm; - bool _freeFlag; public: /** - * Sets the engine refrence used all surfaces + * Sets the engine reference used all surfaces */ static void setVm(MADSEngine *vm) { _vm = vm; } /** - * Helper method for calculating new dimensions when scaling a sprite - */ - static int scaleValue(int value, int scale, int err); - - /** * Base method for descendents to load their contents */ virtual void load(const Common::String &resName) {} @@ -73,126 +72,50 @@ public: /** * Basic constructor */ - MSurface(); + MSurface() : Graphics::ManagedSurface() {} /** * Constructor for a surface with fixed dimensions */ - MSurface(int width, int height); + MSurface(int width, int height) : Graphics::ManagedSurface(width, height) {} /** * Destructor */ - virtual ~MSurface(); - - /** - * Reinitializes a surface to have a given set of dimensions - */ - void setSize(int width, int height); - - /** - * Sets the pixels the surface is associated with - * @remarks The surface will not free the data block - */ - void setPixels(byte *pData, int horizSize, int vertSize); - - /** - * Draws an arbitrary line on the screen using a specified color - * @param startPos Starting position - * @param endPos Ending position - * @param color Color to use - */ - void line(const Common::Point &startPos, const Common::Point &endPos, byte color); - - /** - * Draws a sprite - * @param pt Position to draw sprite at - * @param info General sprite details - * @param clipRect Clipping rectangle to constrain sprite drawing within - */ - void drawSprite(const Common::Point &pt, SpriteInfo &info, const Common::Rect &clipRect); - - /** - * Returns the width of the surface - */ - int getWidth() const { return w; } - - /** - * Returns the height of the surface - */ - int getHeight() const { return h; } + virtual ~MSurface() {} /** - * Returns the size of the surface as a Rect + * Return a rect containing the bounds of the surface */ - Common::Rect getBounds() const { - return Common::Rect(0, 0, w, h); - } + Common::Rect getBounds() { return Common::Rect(0, 0, this->w, this->h); } /** - * Returns a pointer to the surface data + * Return the pixels for the surface */ - byte *getData() { return (byte *)Graphics::Surface::getPixels(); } + inline byte *getPixels() { return (byte *)Graphics::ManagedSurface::getPixels(); } /** - * Returns a pointer to a given position within the surface + * Return the pixels for the surface */ - byte *getBasePtr(int x, int y) { return (byte *)Graphics::Surface::getBasePtr(x, y); } + inline const void *getPixels() const { return (const byte *)Graphics::ManagedSurface::getPixels(); } /** - * Returns a pointer to a given position within the surface - */ - const byte *getBasePtr(int x, int y) const { return (const byte *)Graphics::Surface::getBasePtr(x, y); } - - /** - * Clears the surface - */ - void empty(); - - /** - * Copys a sub-section of another surface into the current one. - * @param src Source surface - * @param srcBounds Area of source surface to copy - * @param destPos Destination position to draw in current surface - * @param transparentColor Transparency palette index - */ - void copyFrom(MSurface *src, const Common::Rect &srcBounds, const Common::Point &destPos, - int transparentColor = -1); - - /** - * Copys a sub-section of another surface into the current one. - * @param src Source surface - * @param destPos Destination position to draw in current surface - * @param depth Depth of sprite - * @param depthSurface Depth surface to use with sprite depth - * @param scale Scale for image - * @param flipped Flag for whether image is to be flipped - * @param transparentColor Transparency palette index - */ - void copyFrom(MSurface *src, const Common::Point &destPos, int depth, DepthSurface *depthSurface, - int scale, bool flipped, int transparentColor = -1); - - /** - * Copies the surface to a given destination surface + * Return a pointer to a given position on the surface */ - void copyTo(MSurface *dest, int transparentColor = -1) { - dest->copyFrom(this, Common::Rect(w, h), Common::Point(), transparentColor); - } + byte *getBasePtr(int x, int y) { return (byte *)Graphics::ManagedSurface::getBasePtr(x, y); } /** - * Copies the surface to a given destination surface + * Return a pointer to a given position on the surface */ - void copyTo(MSurface *dest, const Common::Point &pt, int transparentColor = -1) { - dest->copyFrom(this, Common::Rect(w, h), pt, transparentColor); - } + inline const byte *getBasePtr(int x, int y) const { return (const byte *)Graphics::ManagedSurface::getBasePtr(x, y); } /** - * Copies the surface to a given destination surface + * Draws a sprite + * @param pt Position to draw sprite at + * @param info General sprite details + * @param clipRect Clipping rectangle to constrain sprite drawing within */ - void copyTo(MSurface *dest, const Common::Rect &srcBounds, const Common::Point &destPos, - int transparentColor = -1) { - dest->copyFrom(this, srcBounds, destPos, transparentColor); - } + void drawSprite(const Common::Point &pt, SpriteInfo &info, const Common::Rect &clipRect); /** * Scroll the screen horizontally by a given amount @@ -227,6 +150,19 @@ public: */ void copyRectTranslate(MSurface &srcSurface, const byte *paletteMap, const Common::Point &destPos, const Common::Rect &srcRect); + + /** + * Copys a sub-section of another surface into the current one. + * @param src Source surface + * @param destPos Destination position to draw in current surface + * @param depth Depth of sprite + * @param depthSurface Depth surface to use with sprite depth + * @param scale Scale for image + * @param flipped Flag for whether image is to be flipped + * @param transparentColor Transparency palette index + */ + void copyFrom(MSurface &src, const Common::Point &destPos, int depth, DepthSurface *depthSurface, + int scale, bool flipped, int transparentColor = -1); }; class DepthSurface : public MSurface { @@ -239,7 +175,7 @@ public: /** * Constructor */ - DepthSurface() : _depthStyle(0) {} + DepthSurface() : MSurface(), _depthStyle(0) {} /** * Returns the depth at a given position diff --git a/engines/mads/nebular/dialogs_nebular.cpp b/engines/mads/nebular/dialogs_nebular.cpp index 58e60fe323..2af80f517e 100644 --- a/engines/mads/nebular/dialogs_nebular.cpp +++ b/engines/mads/nebular/dialogs_nebular.cpp @@ -438,11 +438,10 @@ void CopyProtectionDialog::show() { Common::KeyState curKey; const Common::Rect inputArea(110, 165, 210, 175); MSurface *origInput = new MSurface(inputArea.width(), inputArea.height()); - _vm->_screen.frameRect(inputArea, TEXTDIALOG_BLACK); - _vm->_screen.copyTo(origInput, inputArea, Common::Point(0, 0)); - _font->setColors(TEXTDIALOG_FE, TEXTDIALOG_FE, TEXTDIALOG_FE, TEXTDIALOG_FE); - _vm->_screen.copyRectToScreen(inputArea); - _vm->_screen.updateScreen(); + _vm->_screen->frameRect(inputArea, TEXTDIALOG_BLACK); + origInput->blitFrom(*_vm->_screen, inputArea, Common::Point(0, 0)); + _font->setColors(TEXTDIALOG_FE, TEXTDIALOG_FE, TEXTDIALOG_FE, TEXTDIALOG_FE); + _vm->_screen->update(); bool firstTime = true; @@ -470,11 +469,10 @@ void CopyProtectionDialog::show() { _textInput = _hogEntry._word[0]; } - _vm->_screen.copyFrom(origInput, Common::Rect(0, 0, inputArea.width(), inputArea.height()), Common::Point(inputArea.left, inputArea.top)); - _font->writeString(&_vm->_screen, _textInput, + _vm->_screen->blitFrom(*origInput, Common::Point(inputArea.left, inputArea.top)); + _font->writeString(_vm->_screen, _textInput, Common::Point(inputArea.left + 2, inputArea.top + 1), 1); - _vm->_screen.copyRectToScreen(inputArea); - _vm->_screen.updateScreen(); + _vm->_screen->update(); } origInput->free(); @@ -537,7 +535,7 @@ void PictureDialog::save() { // Save the entire screen _savedSurface = new MSurface(MADS_SCREEN_WIDTH, MADS_SCREEN_HEIGHT); - _vm->_screen.copyTo(_savedSurface); + _savedSurface->blitFrom(*_vm->_screen); // Save palette information Common::copy(&palette._mainPalette[0], &palette._mainPalette[PALETTE_SIZE], &_palette[0]); @@ -568,7 +566,7 @@ void PictureDialog::save() { // Remap the greyed out screen to use the small greyscale range // at the top end of the palette - _vm->_screen.translate(map); + _vm->_screen->translate(map); // Load the inventory picture Common::String setName = Common::String::format("*OB%.3d.SS", _objectId); @@ -578,13 +576,12 @@ void PictureDialog::save() { // Get the inventory frame, and adjust the dialog position to allow for it MSprite *frame = asset->getFrame(0); _position.y = frame->h + 12; - if ((_position.y + _height) > _vm->_screen.getHeight()) - _position.y -= (_position.y + _height) - _vm->_screen.getHeight(); + if ((_position.y + _height) > _vm->_screen->h) + _position.y -= (_position.y + _height) - _vm->_screen->h; // Draw the inventory picture - frame->copyTo(&_vm->_screen, Common::Point(160 - frame->w / 2, 6), + _vm->_screen->transBlitFrom(*frame, Common::Point(160 - frame->w / 2, 6), frame->getTransparencyIndex()); - _vm->_screen.copyRectToScreen(_vm->_screen.getBounds()); // Adjust the dialog colors to use TEXTDIALOG_CONTENT1 -= 10; @@ -598,13 +595,11 @@ void PictureDialog::save() { void PictureDialog::restore() { if (_savedSurface) { - _savedSurface->copyTo(&_vm->_screen); + _vm->_screen->blitFrom(*_savedSurface); _savedSurface->free(); delete _savedSurface; _savedSurface = nullptr; - _vm->_screen.copyRectToScreen(_vm->_screen.getBounds()); - // Restore palette information Palette &palette = *_vm->_palette; Common::copy(&_palette[0], &_palette[PALETTE_SIZE], &palette._mainPalette[0]); @@ -691,7 +686,6 @@ void GameDialog::display() { } GameDialog::~GameDialog() { - _vm->_screen.resetClipBounds(); _vm->_game->_scene._currentSceneId = RETURNING_FROM_DIALOG; } diff --git a/engines/mads/nebular/game_nebular.cpp b/engines/mads/nebular/game_nebular.cpp index 9c0acf1a47..1db5eaea00 100644 --- a/engines/mads/nebular/game_nebular.cpp +++ b/engines/mads/nebular/game_nebular.cpp @@ -827,49 +827,51 @@ void GameNebular::step() { if (_player._visible && _player._stepEnabled && !_player._moving && (_player._facing == _player._turnToFacing)) { if (_scene._frameStartTime >= (uint32)_globals[kWalkerTiming]) { - int randomVal = _vm->getRandomNumber(29999); - if (_globals[kSexOfRex] == REX_MALE) { - switch (_player._facing) { - case FACING_SOUTHWEST: - case FACING_SOUTHEAST: - case FACING_NORTHWEST: - case FACING_NORTHEAST: - if (randomVal < 200) { - _player.addWalker(-1, 0); - _player.addWalker(1, 0); - } - break; - - case FACING_WEST: - case FACING_EAST: - if (randomVal < 500) { - for (int count = 0; count < 10; ++count) { + if (_player._stopWalkers.empty()) { + int randomVal = _vm->getRandomNumber(29999); + if (_globals[kSexOfRex] == REX_MALE) { + switch (_player._facing) { + case FACING_SOUTHWEST: + case FACING_SOUTHEAST: + case FACING_NORTHWEST: + case FACING_NORTHEAST: + if (randomVal < 200) { + _player.addWalker(-1, 0); _player.addWalker(1, 0); } - } - break; - - case FACING_SOUTH: - if (randomVal < 500) { - for (int count = 0; count < 10; ++count) { - _player.addWalker((randomVal < 250) ? 1 : 2, 0); + break; + + case FACING_WEST: + case FACING_EAST: + if (randomVal < 500) { + for (int count = 0; count < 10; ++count) { + _player.addWalker(1, 0); + } } - } else if (randomVal < 750) { - for (int count = 0; count < 5; ++count) { - _player.addWalker(1, 0); + break; + + case FACING_SOUTH: + if (randomVal < 500) { + for (int count = 0; count < 10; ++count) { + _player.addWalker((randomVal < 250) ? 1 : 2, 0); + } + } else if (randomVal < 750) { + for (int count = 0; count < 5; ++count) { + _player.addWalker(1, 0); + } + + _player.addWalker(0, 0); + _player.addWalker(0, 0); + + for (int count = 0; count < 5; ++count) { + _player.addWalker(2, 0); + } } + break; - _player.addWalker(0, 0); - _player.addWalker(0, 0); - - for (int count = 0; count < 5; ++count) { - _player.addWalker(2, 0); - } + default: + break; } - break; - - default: - break; } } diff --git a/engines/mads/nebular/menu_nebular.cpp b/engines/mads/nebular/menu_nebular.cpp index 0520294b29..cd81efe0f0 100644 --- a/engines/mads/nebular/menu_nebular.cpp +++ b/engines/mads/nebular/menu_nebular.cpp @@ -384,8 +384,8 @@ void AdvertView::show() { // Load the advert background onto the screen SceneInfo *sceneInfo = SceneInfo::init(_vm); sceneInfo->load(screenId, 0, Common::String(), 0, _vm->_game->_scene._depthSurface, - _vm->_screen); - _vm->_screen.copyRectToScreen(_vm->_screen.getBounds()); + *_vm->_screen); + _vm->_screen->markAllDirty(); _vm->_palette->setFullPalette(_vm->_palette->_mainPalette); delete sceneInfo; diff --git a/engines/mads/nebular/nebular_scenes.cpp b/engines/mads/nebular/nebular_scenes.cpp index da419a70a2..40228b4b7d 100644 --- a/engines/mads/nebular/nebular_scenes.cpp +++ b/engines/mads/nebular/nebular_scenes.cpp @@ -323,8 +323,8 @@ void SceneInfoNebular::loadCodes(MSurface &depthSurface, int variant) { } void SceneInfoNebular::loadCodes(MSurface &depthSurface, Common::SeekableReadStream *stream) { - byte *destP = depthSurface.getData(); - byte *endP = depthSurface.getBasePtr(0, depthSurface.h); + byte *destP = (byte *)depthSurface.getPixels(); + byte *endP = (byte *)depthSurface.getBasePtr(0, depthSurface.h); byte runLength = stream->readByte(); while (destP < endP && runLength > 0) { diff --git a/engines/mads/nebular/nebular_scenes3.cpp b/engines/mads/nebular/nebular_scenes3.cpp index 0fb13a706c..7323ee893d 100644 --- a/engines/mads/nebular/nebular_scenes3.cpp +++ b/engines/mads/nebular/nebular_scenes3.cpp @@ -2818,7 +2818,7 @@ void Scene318::step() { if (_internCounter >= 3600) { _vm->_sound->command(59); - _vm->_screen._shakeCountdown = 20; + _vm->_screen->_shakeCountdown = 20; _internWalkingFl = true; } } @@ -3288,22 +3288,22 @@ void Scene319::step() { if (_animMode == 2) { if (_animFrame == 13) - _vm->_screen._shakeCountdown = 40; + _vm->_screen->_shakeCountdown = 40; if (_animFrame == 16) - _vm->_screen._shakeCountdown = 1; + _vm->_screen->_shakeCountdown = 1; } if (_animMode == 3) { if (_animFrame == 11) - _vm->_screen._shakeCountdown = 60; + _vm->_screen->_shakeCountdown = 60; if (_animFrame == 18) - _vm->_screen._shakeCountdown = 1; + _vm->_screen->_shakeCountdown = 1; } if ((_animMode == 4) && (_animFrame == 16)) - _vm->_screen._shakeCountdown = 80; + _vm->_screen->_shakeCountdown = 80; if ((nextFrame >= 0) && (nextFrame != _scene->_animation[0]->getCurrentFrame())) { _scene->_animation[0]->setCurrentFrame(nextFrame); @@ -3326,7 +3326,7 @@ void Scene319::step() { _animFrame = _scene->_animation[0]->getCurrentFrame(); _slacheTalkingFl = true; - _vm->_screen._shakeCountdown = 1; + _vm->_screen->_shakeCountdown = 1; for (int i = 0; i <= 1; i++) { int oldIdx = _globals._sequenceIndexes[i]; @@ -3350,7 +3350,7 @@ void Scene319::step() { _vm->_palette->setColorValues(0, 0, 0); _vm->_palette->fadeOut(_vm->_palette->_mainPalette, nullptr, 18, 228, 248, 0, 1, 16); - _vm->_screen._shakeCountdown = 1; + _vm->_screen->_shakeCountdown = 1; _scene->_reloadSceneFlag = true; break; @@ -3731,7 +3731,7 @@ void Scene320::step() { case 417: case 457: - _vm->_screen._shakeCountdown = 40; + _vm->_screen->_shakeCountdown = 40; _vm->_sound->command(59); break; diff --git a/engines/mads/nebular/nebular_scenes5.cpp b/engines/mads/nebular/nebular_scenes5.cpp index 3778a59eb3..ea3574b0d1 100644 --- a/engines/mads/nebular/nebular_scenes5.cpp +++ b/engines/mads/nebular/nebular_scenes5.cpp @@ -1603,7 +1603,7 @@ void Scene508::enter() { _globals._sequenceIndexes[3] = _scene->_sequences.startCycle(_globals._spriteIndexes[3], false, -2); _scene->_sequences.setDepth(_globals._sequenceIndexes[3], 8); _globals._sequenceIndexes[5] = _scene->_sequences.startCycle(_globals._spriteIndexes[5], false, -2); - int idx = _scene->_dynamicHotspots.add(NOUN_LASER_BEAM, VERB_WALKTO, _globals._sequenceIndexes[5], Common::Rect(0, 0, 0, 0)); + int idx = _scene->_dynamicHotspots.add(NOUN_SPINACH_PATCH_DOLL, VERB_WALKTO, _globals._sequenceIndexes[5], Common::Rect(0, 0, 0, 0)); _scene->_dynamicHotspots.setPosition(idx, Common::Point(57, 116), FACING_NORTHEAST); _scene->_hotspots.activate(NOUN_HOLE, false); _scene->_hotspots.activate(NOUN_LASER_BEAM, false); diff --git a/engines/mads/phantom/phantom_scenes.cpp b/engines/mads/phantom/phantom_scenes.cpp index f7a7153fbe..7ef627ceeb 100644 --- a/engines/mads/phantom/phantom_scenes.cpp +++ b/engines/mads/phantom/phantom_scenes.cpp @@ -191,7 +191,7 @@ void SceneInfoPhantom::loadCodes(MSurface &depthSurface, int variant) { } void SceneInfoPhantom::loadCodes(MSurface &depthSurface, Common::SeekableReadStream *stream) { - byte *destP = depthSurface.getData(); + byte *destP = (byte *)depthSurface.getPixels(); byte *walkMap = new byte[stream->size()]; stream->read(walkMap, stream->size()); diff --git a/engines/mads/rails.cpp b/engines/mads/rails.cpp index ee0ca98cd3..46d9e0ebd3 100644 --- a/engines/mads/rails.cpp +++ b/engines/mads/rails.cpp @@ -149,7 +149,7 @@ int Rails::scanPath(const Common::Point &srcPos, const Common::Point &destPos) { ++xDiff; ++yDiff; - const byte *srcP = _depthSurface->getBasePtr(srcPos.x, srcPos.y); + const byte *srcP = (const byte *)_depthSurface->getBasePtr(srcPos.x, srcPos.y); int index = xAmount; // Outer loop diff --git a/engines/mads/scene.cpp b/engines/mads/scene.cpp index 83ab1151a9..66f56f9407 100644 --- a/engines/mads/scene.cpp +++ b/engines/mads/scene.cpp @@ -89,8 +89,7 @@ Scene::~Scene() { } void Scene::restrictScene() { - _sceneSurface.init(MADS_SCREEN_WIDTH, MADS_SCENE_HEIGHT, MADS_SCREEN_WIDTH, - _vm->_screen.getPixels(), Graphics::PixelFormat::createFormatCLUT8()); + _sceneSurface.create(*_vm->_screen, Common::Rect(0, 0, MADS_SCREEN_WIDTH, MADS_SCENE_HEIGHT)); } void Scene::clearVocab() { @@ -517,7 +516,7 @@ void Scene::drawElements(ScreenTransition transitionType, bool surfaceFlag) { if (_posAdjust != Common::Point(0, 0)) warning("Adjust used %d %d", _posAdjust.x, _posAdjust.y); // Copy background for the dirty areas to the screen - _dirtyAreas.copy(&_backgroundSurface, &_vm->_screen, _posAdjust); + _dirtyAreas.copy(&_backgroundSurface, _vm->_screen, _posAdjust); // Handle dirty areas for foreground objects _spriteSlots.setDirtyAreas(); @@ -528,11 +527,11 @@ void Scene::drawElements(ScreenTransition transitionType, bool surfaceFlag) { _spriteSlots.drawSprites(&_sceneSurface); // Draw text elements onto the view - _textDisplay.draw(&_vm->_screen); + _textDisplay.draw(_vm->_screen); if (transitionType) { // Fading in the screen - _vm->_screen.transition(transitionType, surfaceFlag); + _vm->_screen->transition(transitionType, surfaceFlag); _vm->_sound->startQueuedCommands(); } else { // Copy dirty areas to the screen diff --git a/engines/mads/scene_data.cpp b/engines/mads/scene_data.cpp index 7b0e64c1fe..5323178ec7 100644 --- a/engines/mads/scene_data.cpp +++ b/engines/mads/scene_data.cpp @@ -242,13 +242,13 @@ void SceneInfo::load(int sceneId, int variant, const Common::String &resName, int height = _height; if (!bgSurface.getPixels() || (bgSurface.w != width) || (bgSurface.h != height)) { - bgSurface.setSize(width, height); + bgSurface.create(width, height); } if (_depthStyle == 2) width >>= 2; if (!depthSurface.getPixels()) { - depthSurface.setSize(width, height); + depthSurface.create(width, height); } loadCodes(depthSurface, variant); @@ -288,7 +288,7 @@ void SceneInfo::load(int sceneId, int variant, const Common::String &resName, assert(asset && _depthStyle != 2); MSprite *spr = asset->getFrame(si._frameNumber); - bgSurface.copyFrom(spr, si._position, si._depth, &depthSurface, + bgSurface.copyFrom(*spr, si._position, si._depth, &depthSurface, si._scale, false, spr->getTransparencyIndex()); } @@ -455,7 +455,7 @@ void SceneInfo::loadMadsV2Background(int sceneId, const Common::String &resName, newHeight = tileCount * tileHeight; if (bgSurface.w != newWidth || bgSurface.h != newHeight) - bgSurface.setSize(newWidth, newHeight); + bgSurface.create(newWidth, newHeight); // -------------------------------------------------------------------------------- @@ -477,7 +477,7 @@ void SceneInfo::loadMadsV2Background(int sceneId, const Common::String &resName, //debugCN(kDebugGraphics, "Tile: %i, compressed size: %i\n", i, compressedTileDataSize); - newTile->empty(); + newTile->clear(); byte *compressedTileData = new byte[compressedTileDataSize]; @@ -503,7 +503,8 @@ void SceneInfo::loadMadsV2Background(int sceneId, const Common::String &resName, TileSetIterator tile = tileSet.begin(); for (int i = 0; i < tileIndex; i++) ++tile; - ((*tile).get())->copyTo(&bgSurface, Common::Point(x * tileWidth, y * tileHeight)); + + bgSurface.blitFrom(*(*tile).get(), Common::Point(x * tileWidth, y * tileHeight)); ((*tile).get())->free(); } } diff --git a/engines/mads/screen.cpp b/engines/mads/screen.cpp index 90fbbe7e2a..05f9de61e2 100644 --- a/engines/mads/screen.cpp +++ b/engines/mads/screen.cpp @@ -69,7 +69,6 @@ void DirtyArea::setArea(int width, int height, int maxWidth, int maxHeight) { _active = true; } - void DirtyArea::setSpriteSlot(const SpriteSlot *spriteSlot) { int width, height; Scene &scene = _vm->_game->_scene; @@ -215,12 +214,13 @@ void DirtyAreas::copy(MSurface *srcSurface, MSurface *destSurface, const Common: Common::Point destPos(srcBounds.left, srcBounds.top); if ((*this)[i]._active && bounds.isValidRect()) { - srcSurface->copyTo(destSurface, bounds, destPos); + destSurface->blitFrom(*srcSurface, bounds, destPos); } } } void DirtyAreas::copyToScreen() { +/* for (uint i = 0; i < size(); ++i) { const Common::Rect &bounds = (*this)[i]._bounds; @@ -229,9 +229,10 @@ void DirtyAreas::copyToScreen() { continue; if ((*this)[i]._active && (*this)[i]._bounds.isValidRect()) { - _vm->_screen.copyRectToScreen(bounds); + _vm->_screen->copyRectToScreen(bounds); } } + */ } void DirtyAreas::reset() { @@ -554,38 +555,17 @@ void ScreenObjects::synchronize(Common::Serializer &s) { /*------------------------------------------------------------------------*/ -ScreenSurface::ScreenSurface() { +Screen::Screen(): Graphics::Screen(), MSurface() { + // Create the screen surface separately on another surface, since the screen + // surface will be subject to change as the clipping area is altered + _rawSurface.create(MADS_SCREEN_WIDTH, MADS_SCREEN_HEIGHT); + resetClipBounds(); + _shakeCountdown = -1; _random = 0x4D2; - _surfacePixels = nullptr; -} - -void ScreenSurface::init() { - // Set the size for the screen - setSize(MADS_SCREEN_WIDTH, MADS_SCREEN_HEIGHT); - - // Store a copy of the raw pixels pointer for the screen, since the surface - // itself may be later changed to only a subset of the screen - _surfacePixels = (byte *)getPixels(); - _freeFlag = false; -} - -ScreenSurface::~ScreenSurface() { - ::free(_surfacePixels); } -void ScreenSurface::copyRectToScreen(const Common::Rect &bounds) { - const byte *buf = getBasePtr(bounds.left, bounds.top); - - Common::Rect destBounds = bounds; - destBounds.translate(_clipBounds.left, _clipBounds.top); - - if (bounds.width() != 0 && bounds.height() != 0) - g_system->copyRectToScreen(buf, this->pitch, destBounds.left, destBounds.top, - destBounds.width(), destBounds.height()); -} - -void ScreenSurface::updateScreen() { +void Screen::update() { if (_shakeCountdown >= 0) { _random = _random * 5 + 1; int offset = (_random >> 8) & 3; @@ -596,27 +576,42 @@ void ScreenSurface::updateScreen() { // offset width shown at the very right. The offset changes to give // an effect of shaking the screen offset *= 4; - const byte *buf = getBasePtr(offset, 0); - g_system->copyRectToScreen(buf, this->pitch, 0, 0, - this->pitch - offset, this->h); + const byte *buf = (const byte *)getBasePtr(offset, 0); + g_system->copyRectToScreen(buf, this->pitch, 0, 0, this->pitch - offset, this->h); if (offset > 0) - g_system->copyRectToScreen(this->pixels, this->pitch, + g_system->copyRectToScreen(getPixels(), this->pitch, this->pitch - offset, 0, offset, this->h); + return; } - g_system->updateScreen(); + // Reset any clip bounds if active whilst the screen is updated + Common::Rect clipBounds = getClipBounds(); + resetClipBounds(); + + // Update the screen + Graphics::Screen::update(); + + // Revert back to whatever clipping is active + setClipBounds(clipBounds); } -void ScreenSurface::transition(ScreenTransition transitionType, bool surfaceFlag) { +void Screen::transition(ScreenTransition transitionType, bool surfaceFlag) { Palette &pal = *_vm->_palette; Scene &scene = _vm->_game->_scene; byte palData[PALETTE_SIZE]; + // The original loads the new scene to the screen surface for some of the + // transition types like fade out/in, so we need to clear the dirty rects so + // it doesn't prematurely get blitted to the physical screen before fade out + Common::Rect clipBounds = getClipBounds(); + clearDirtyRects(); + switch (transitionType) { case kTransitionFadeIn: - case kTransitionFadeOutIn: + case kTransitionFadeOutIn: { Common::fill(&pal._colorValues[0], &pal._colorValues[3], 0); Common::fill(&pal._colorFlags[0], &pal._colorFlags[3], false); + resetClipBounds(); if (transitionType == kTransitionFadeOutIn) { // Fade out @@ -628,9 +623,11 @@ void ScreenSurface::transition(ScreenTransition transitionType, bool surfaceFlag Common::fill(&palData[0], &palData[PALETTE_SIZE], 0); pal.setFullPalette(palData); - copyRectToScreen(getBounds()); + markAllDirty(); + update(); pal.fadeIn(palData, pal._mainPalette, 0, 256, 0, 1, 1, 16); break; + } case kTransitionBoxInBottomLeft: case kTransitionBoxInBottomRight: @@ -666,19 +663,13 @@ void ScreenSurface::transition(ScreenTransition transitionType, bool surfaceFlag // Quick transitions break; } -} -void ScreenSurface::setClipBounds(const Common::Rect &r) { - _clipBounds = r; - setPixels(_surfacePixels + pitch * r.top + r.left, r.width(), r.height()); - this->pitch = MADS_SCREEN_WIDTH; + // Reset clipping + markAllDirty(); + setClipBounds(clipBounds); } -void ScreenSurface::resetClipBounds() { - setClipBounds(Common::Rect(0, 0, MADS_SCREEN_WIDTH, MADS_SCREEN_HEIGHT)); -} - -void ScreenSurface::panTransition(MSurface &newScreen, byte *palData, int entrySide, +void Screen::panTransition(MSurface &newScreen, byte *palData, int entrySide, const Common::Point &srcPos, const Common::Point &destPos, ThroughBlack throughBlack, bool setPalette, int numTicks) { EventsManager &events = *_vm->_events; @@ -735,8 +726,6 @@ void ScreenSurface::panTransition(MSurface &newScreen, byte *palData, int entryS srcPos.x + xAt + 1, srcPos.y + size.y)); } - copyRectToScreen(Common::Rect(xAt, destPos.y, xAt + 1, destPos.y + size.y)); - // Slight delay events.pollEvents(); g_system->delayMillis(1); @@ -747,16 +736,18 @@ void ScreenSurface::panTransition(MSurface &newScreen, byte *palData, int entryS } if (throughBlack == THROUGH_BLACK2) { + /* Common::Rect r(srcPos.x, srcPos.y, srcPos.x + size.x, srcPos.y + size.y); copyRectToSurface(newScreen, destPos.x, destPos.y, r); copyRectToScreen(r); + */ } } /** * Translates the current screen from the old palette to the new palette */ -void ScreenSurface::swapForeground(byte newPalette[PALETTE_SIZE], byte *paletteMap) { +void Screen::swapForeground(byte newPalette[PALETTE_SIZE], byte *paletteMap) { Palette &palette = *_vm->_palette; byte oldPalette[PALETTE_SIZE]; byte oldMap[PALETTE_COUNT]; @@ -775,7 +766,7 @@ void ScreenSurface::swapForeground(byte newPalette[PALETTE_SIZE], byte *paletteM destP += 2 * RGB_SIZE; } - Common::Rect oldClip = _clipBounds; + Common::Rect oldClip = getClipBounds(); resetClipBounds(); copyRectTranslate(*this, oldMap, Common::Point(0, 0), @@ -790,7 +781,7 @@ void ScreenSurface::swapForeground(byte newPalette[PALETTE_SIZE], byte *paletteM * Palettes consist of 128 RGB entries for the foreground and background * respectively, with the two interleaved together. So the start */ -void ScreenSurface::swapPalette(const byte *palData, byte swapTable[PALETTE_COUNT], +void Screen::swapPalette(const byte *palData, byte swapTable[PALETTE_COUNT], bool foreground) { int start = foreground ? 1 : 0; const byte *dynamicList = &palData[start * RGB_SIZE]; @@ -815,5 +806,12 @@ void ScreenSurface::swapPalette(const byte *palData, byte swapTable[PALETTE_COUN } } +void Screen::setClipBounds(const Common::Rect &r) { + create(_rawSurface, r); +} + +void Screen::resetClipBounds() { + setClipBounds(Common::Rect(0, 0, MADS_SCREEN_WIDTH, MADS_SCREEN_HEIGHT)); +} } // End of namespace MADS diff --git a/engines/mads/screen.h b/engines/mads/screen.h index d910e88633..626080580e 100644 --- a/engines/mads/screen.h +++ b/engines/mads/screen.h @@ -25,6 +25,7 @@ #include "common/scummsys.h" #include "common/array.h" +#include "graphics/screen.h" #include "mads/msurface.h" #include "mads/action.h" @@ -207,11 +208,10 @@ public: void synchronize(Common::Serializer &s); }; -class ScreenSurface : public MSurface { +class Screen : virtual public Graphics::Screen, virtual public MSurface { private: uint16 _random; - byte *_surfacePixels; - Common::Rect _clipBounds; + MSurface _rawSurface; void panTransition(MSurface &newScreen, byte *palData, int entrySide, const Common::Point &srcPos, const Common::Point &destPos, @@ -226,36 +226,40 @@ public: /** * Constructor */ - ScreenSurface(); + Screen(); /** * Destructor */ - ~ScreenSurface(); + virtual ~Screen() {} /** - * Initialize the surface + * Updates the physical screen with contents of the internal surface */ - void init(); + virtual void update(); /** - * Copys an area of the screen surface to the ScmmVM physical screen buffer - * @param bounds Area of screen surface to copy + * Transition to a new screen with a given effect */ - void copyRectToScreen(const Common::Rect &bounds); + void transition(ScreenTransition transitionType, bool surfaceFlag); /** - * Updates the screen with the contents of the surface + * Set the screen drawing area to a sub-section of the real screen */ - void updateScreen(); - - void transition(ScreenTransition transitionType, bool surfaceFlag); - void setClipBounds(const Common::Rect &r); + /** + * Reset back to drawing on the entirety of the screen + */ void resetClipBounds(); - const Common::Rect &getClipBounds() { return _clipBounds; } + /** + * Return the current drawing/clip area + */ + const Common::Rect getClipBounds() const { + const Common::Point pt = getOffsetFromOwner(); + return Common::Rect(pt.x, pt.y, pt.x + this->w, pt.y + this->h); + } }; } // End of namespace MADS diff --git a/engines/mads/sequence.cpp b/engines/mads/sequence.cpp index 50b37de7ea..2afe089d4a 100644 --- a/engines/mads/sequence.cpp +++ b/engines/mads/sequence.cpp @@ -237,8 +237,8 @@ bool SequenceList::loadSprites(int seqIndex) { if ((seqEntry._flags != 0) || (seqEntry._dynamicHotspotIndex >= 0)) { SpriteAsset &spriteSet = *scene._sprites[seqEntry._spritesIndex]; MSprite *frame = spriteSet.getFrame(seqEntry._frameIndex - 1); - int width = frame->getWidth() * seqEntry._scale / 200; - int height = frame->getHeight() * seqEntry._scale / 100; + int width = frame->w * seqEntry._scale / 200; + int height = frame->h * seqEntry._scale / 100; Common::Point pt = spriteSlot._position; // Handle sprite movement, if present diff --git a/engines/mads/sprites.cpp b/engines/mads/sprites.cpp index 0a1c0b710d..fc8ddf22d2 100644 --- a/engines/mads/sprites.cpp +++ b/engines/mads/sprites.cpp @@ -59,10 +59,10 @@ MSprite::MSprite() : MSurface() { } MSprite::MSprite(Common::SeekableReadStream *source, const Common::Array<RGB6> &palette, - const Common::Rect &bounds) - : MSurface(bounds.width(), bounds.height()), - _offset(Common::Point(bounds.left, bounds.top)), _transparencyIndex(TRANSPARENT_COLOR_INDEX) { + const Common::Rect &bounds): MSurface(), _transparencyIndex(TRANSPARENT_COLOR_INDEX), + _offset(Common::Point(bounds.left, bounds.top)) { // Load the sprite data + create(bounds.width(), bounds.height()); loadSprite(source, palette); } @@ -74,8 +74,8 @@ void MSprite::loadSprite(Common::SeekableReadStream *source, byte *outp, *lineStart; bool newLine = false; - outp = getData(); - lineStart = getData(); + outp = getPixels(); + lineStart = getPixels(); int spriteSize = this->w * this->h; byte transIndex = getTransparencyIndex(); Common::fill(outp, outp + spriteSize, transIndex); @@ -84,7 +84,7 @@ void MSprite::loadSprite(Common::SeekableReadStream *source, byte cmd1, cmd2, count, pixel; if (newLine) { - outp = lineStart + getWidth(); + outp = lineStart + this->w; lineStart = outp; newLine = false; } @@ -126,7 +126,7 @@ void MSprite::loadSprite(Common::SeekableReadStream *source, // Do a final iteration over the sprite to convert it's pixels to // the final positions in the main palette spriteSize = this->w * this->h; - for (outp = getData(); spriteSize > 0; --spriteSize, ++outp) { + for (outp = getPixels(); spriteSize > 0; --spriteSize, ++outp) { if (*outp != transIndex) *outp = palette[*outp]._palIndex; } @@ -257,12 +257,12 @@ void SpriteSlots::drawBackground() { } if (spriteSlot._depth <= 1) { - frame->copyTo(&scene._backgroundSurface, pt, frame->getTransparencyIndex()); + scene._backgroundSurface.transBlitFrom(*frame, pt, frame->getTransparencyIndex()); } else if (scene._depthStyle == 0) { - scene._backgroundSurface.copyFrom(frame, pt, spriteSlot._depth, &scene._depthSurface, + scene._backgroundSurface.copyFrom(*frame, pt, spriteSlot._depth, &scene._depthSurface, -1, false, frame->getTransparencyIndex()); } else { - frame->copyTo(&scene._backgroundSurface, pt, frame->getTransparencyIndex()); + scene._backgroundSurface.transBlitFrom(*frame, pt, frame->getTransparencyIndex()); } } } @@ -319,7 +319,7 @@ void SpriteSlots::drawSprites(MSurface *s) { if ((slot._scale < 100) && (slot._scale != -1)) { // Scaled drawing - s->copyFrom(sprite, slot._position, slot._depth, &scene._depthSurface, + s->copyFrom(*sprite, slot._position, slot._depth, &scene._depthSurface, slot._scale, flipped, sprite->getTransparencyIndex()); } else { int xp, yp; @@ -334,7 +334,7 @@ void SpriteSlots::drawSprites(MSurface *s) { if (slot._depth > 1) { // Draw the frame with depth processing - s->copyFrom(sprite, Common::Point(xp, yp), slot._depth, &scene._depthSurface, + s->copyFrom(*sprite, Common::Point(xp, yp), slot._depth, &scene._depthSurface, -1, flipped, sprite->getTransparencyIndex()); } else { MSurface *spr = sprite; @@ -344,7 +344,7 @@ void SpriteSlots::drawSprites(MSurface *s) { } // No depth, so simply draw the image - spr->copyTo(s, Common::Point(xp, yp), sprite->getTransparencyIndex()); + s->transBlitFrom(*spr, Common::Point(xp, yp), sprite->getTransparencyIndex()); // Free sprite if it was a flipped one if (flipped) { diff --git a/engines/mads/user_interface.cpp b/engines/mads/user_interface.cpp index e4b09ff54c..8f7cb0a24b 100644 --- a/engines/mads/user_interface.cpp +++ b/engines/mads/user_interface.cpp @@ -112,7 +112,7 @@ void UISlots::draw(bool updateFlag, bool delFlag) { Common::Point(dirtyArea._bounds.left, dirtyArea._bounds.top)); } else { // Copy area - userInterface._surface.copyTo(&userInterface, dirtyArea._bounds, + userInterface.blitFrom(userInterface._surface, dirtyArea._bounds, Common::Point(dirtyArea._bounds.left, dirtyArea._bounds.top)); } } @@ -155,7 +155,7 @@ void UISlots::draw(bool updateFlag, bool delFlag) { if (slot._segmentId == IMG_SPINNING_OBJECT) { MSprite *sprite = asset->getFrame(frameNumber - 1); - sprite->copyTo(&userInterface, slot._position, + userInterface.transBlitFrom(*sprite, slot._position, sprite->getTransparencyIndex()); } else { MSprite *sprite = asset->getFrame(frameNumber - 1); @@ -185,7 +185,7 @@ void UISlots::draw(bool updateFlag, bool delFlag) { // Flag area of screen as needing update Common::Rect r = dirtyArea._bounds; r.translate(0, scene._interfaceY); - _vm->_screen.copyRectToScreen(r); + //_vm->_screen->copyRectToScreen(r); } } } @@ -339,10 +339,10 @@ UserInterface::UserInterface(MADSEngine *vm) : _vm(vm), _dirtyAreas(vm), Common::fill(&_categoryIndexes[0], &_categoryIndexes[7], 0); // Map the user interface to the bottom of the game's screen surface - byte *pData = _vm->_screen.getBasePtr(0, MADS_SCENE_HEIGHT); - setPixels(pData, MADS_SCREEN_WIDTH, MADS_INTERFACE_HEIGHT); + create(*_vm->_screen, Common::Rect(0, MADS_SCENE_HEIGHT, MADS_SCREEN_WIDTH, + MADS_SCREEN_HEIGHT)); - _surface.setSize(MADS_SCREEN_WIDTH, MADS_INTERFACE_HEIGHT); + _surface.create(MADS_SCREEN_WIDTH, MADS_INTERFACE_HEIGHT); } void UserInterface::load(const Common::String &resName) { @@ -367,7 +367,7 @@ void UserInterface::load(const Common::String &resName) { // Read in the surface data Common::SeekableReadStream *pixelsStream = madsPack.getItemStream(1); - pixelsStream->read(_surface.getData(), MADS_SCREEN_WIDTH * MADS_INTERFACE_HEIGHT); + pixelsStream->read(_surface.getPixels(), MADS_SCREEN_WIDTH * MADS_INTERFACE_HEIGHT); delete pixelsStream; } @@ -390,7 +390,7 @@ void UserInterface::setup(InputMode inputMode) { resName += ".INT"; load(resName); - _surface.copyTo(this); + blitFrom(_surface); } _vm->_game->_screenObjects._inputMode = inputMode; @@ -455,9 +455,9 @@ void UserInterface::mergeFrom(MSurface *src, const Common::Rect &srcBounds, // Copy the specified area - byte *data = src->getData(); - byte *srcPtr = data + (src->getWidth() * copyRect.top + copyRect.left); - byte *destPtr = (byte *)this->pixels + (destY * getWidth()) + destX; + byte *data = src->getPixels(); + byte *srcPtr = data + (src->w * copyRect.top + copyRect.left); + byte *destPtr = (byte *)getPixels() + (destY * this->w) + destX; for (int rowCtr = 0; rowCtr < copyRect.height(); ++rowCtr) { // Process each line of the area @@ -468,8 +468,8 @@ void UserInterface::mergeFrom(MSurface *src, const Common::Rect &srcBounds, destPtr[xCtr] = srcPtr[xCtr]; } - srcPtr += src->getWidth(); - destPtr += getWidth(); + srcPtr += src->w; + destPtr += this->w; } } @@ -593,7 +593,7 @@ void UserInterface::scrollbarChanged() { _uiSlots.add(r); _uiSlots.draw(false, false); drawScroller(); - updateRect(r); +// updateRect(r); } void UserInterface::writeVocab(ScrCategory category, int id) { @@ -1012,7 +1012,7 @@ void UserInterface::selectObject(int invIndex) { _uiSlots.add(bounds); _uiSlots.draw(false, false); drawItemVocabList(); - updateRect(bounds); + //updateRect(bounds); } } @@ -1036,7 +1036,7 @@ void UserInterface::updateSelection(ScrCategory category, int newIndex, int *idx _uiSlots.add(bounds); _uiSlots.draw(false, false); drawInventoryList(); - updateRect(bounds); + //updateRect(bounds); _inventoryChanged = false; if (invList.size() < 2) { @@ -1052,25 +1052,19 @@ void UserInterface::updateSelection(ScrCategory category, int newIndex, int *idx if (oldIndex >= 0) { writeVocab(category, oldIndex); - if (getBounds(category, oldIndex, bounds)) - updateRect(bounds); +/* if (getBounds(category, oldIndex, bounds)) + updateRect(bounds); */ } if (newIndex >= 0) { writeVocab(category, newIndex); - if (getBounds(category, newIndex, bounds)) - updateRect(bounds); +/* if (getBounds(category, newIndex, bounds)) + updateRect(bounds); */ } } } -void UserInterface::updateRect(const Common::Rect &bounds) { - Common::Rect r = bounds; - r.translate(0, MADS_SCENE_HEIGHT); - _vm->_screen.copyRectToScreen(r); -} - void UserInterface::scrollerChanged() { warning("TODO: scrollerChanged"); } diff --git a/engines/mads/user_interface.h b/engines/mads/user_interface.h index 60cc1f736d..9232dc1bb1 100644 --- a/engines/mads/user_interface.h +++ b/engines/mads/user_interface.h @@ -190,8 +190,6 @@ private: * Draw a UI textual element */ void writeVocab(ScrCategory category, int id); - - void updateRect(const Common::Rect &bounds); public: MSurface _surface; UISlots _uiSlots; diff --git a/engines/metaengine.h b/engines/metaengine.h index 41f3ec4cba..e7bfebab71 100644 --- a/engines/metaengine.h +++ b/engines/metaengine.h @@ -96,6 +96,9 @@ public: /** * Return a list of all save states associated with the given target. * + * The returned list is guaranteed to be sorted by slot numbers. That + * means smaller slot numbers are always stored before bigger slot numbers. + * * The caller has to ensure that this (Meta)Engine is responsible * for the specified target (by using findGame on it respectively * on the associated gameid from the relevant ConfMan entry, if present). diff --git a/engines/mohawk/configure.engine b/engines/mohawk/configure.engine index 47402c4560..ccb9499ef0 100644 --- a/engines/mohawk/configure.engine +++ b/engines/mohawk/configure.engine @@ -1,6 +1,6 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] -add_engine mohawk "Mohawk" yes "cstime myst riven" "Living Books" +add_engine mohawk "Mohawk" yes "cstime myst riven" "Living Books" "highres" add_engine cstime "Where in Time is Carmen Sandiego?" no add_engine riven "Riven: The Sequel to Myst" no "" "" "16bit" -add_engine myst "Myst" no +add_engine myst "Myst" yes diff --git a/engines/mohawk/console.cpp b/engines/mohawk/console.cpp index 9b5bae78be..fd79e53b07 100644 --- a/engines/mohawk/console.cpp +++ b/engines/mohawk/console.cpp @@ -63,6 +63,8 @@ MystConsole::MystConsole(MohawkEngine_Myst *vm) : GUI::Debugger(), _vm(vm) { registerCmd("disableInitOpcodes", WRAP_METHOD(MystConsole, Cmd_DisableInitOpcodes)); registerCmd("cache", WRAP_METHOD(MystConsole, Cmd_Cache)); registerCmd("resources", WRAP_METHOD(MystConsole, Cmd_Resources)); + registerCmd("quickTest", WRAP_METHOD(MystConsole, Cmd_QuickTest)); + registerVar("show_resource_rects", &_vm->_showResourceRects); } MystConsole::~MystConsole() { @@ -119,7 +121,7 @@ static const uint16 default_start_card[12] = { 10000, 2000, 5038, - 2, // TODO: Should be 1? + 1, 1, 6122, 4134, @@ -329,6 +331,44 @@ bool MystConsole::Cmd_Resources(int argc, const char **argv) { return true; } +bool MystConsole::Cmd_QuickTest(int argc, const char **argv) { + // Go through all the ages, all the views and click random stuff + for (uint i = 0; i < ARRAYSIZE(mystStackNames); i++) { + if (i == 2 || i == 5 || i == 9 || i == 10) continue; + debug("Loading stack %s", mystStackNames[i]); + _vm->changeToStack(i, default_start_card[i], 0, 0); + + Common::Array<uint16> ids = _vm->getResourceIDList(ID_VIEW); + for (uint j = 0; j < ids.size(); j++) { + if (ids[j] == 4632) continue; + + debug("Loading card %d", ids[j]); + _vm->changeToCard(ids[j], kTransitionCopy); + + _vm->_video->updateMovies(); + _vm->_scriptParser->runPersistentScripts(); + _vm->_system->updateScreen(); + + int16 resIndex = _vm->_rnd->getRandomNumber(_vm->_resources.size()) - 1; + if (resIndex >= 0 && _vm->_resources[resIndex]->isEnabled()) { + _vm->_resources[resIndex]->handleMouseDown(); + _vm->_resources[resIndex]->handleMouseUp(); + } + + _vm->_video->updateMovies(); + _vm->_scriptParser->runPersistentScripts(); + _vm->_system->updateScreen(); + + if (_vm->getCurStack() != i) { + // Clicking may have linked us to another age + _vm->changeToStack(i, default_start_card[i], 0, 0); + } + } + } + + return true; +} + #endif // ENABLE_MYST #ifdef ENABLE_RIVEN diff --git a/engines/mohawk/console.h b/engines/mohawk/console.h index af01c0d1e0..dc40049a89 100644 --- a/engines/mohawk/console.h +++ b/engines/mohawk/console.h @@ -55,6 +55,7 @@ private: bool Cmd_DisableInitOpcodes(int argc, const char **argv); bool Cmd_Cache(int argc, const char **argv); bool Cmd_Resources(int argc, const char **argv); + bool Cmd_QuickTest(int argc, const char **argv); }; #endif diff --git a/engines/mohawk/detection.cpp b/engines/mohawk/detection.cpp index 926c296257..a64d7ff7df 100644 --- a/engines/mohawk/detection.cpp +++ b/engines/mohawk/detection.cpp @@ -26,6 +26,7 @@ #include "common/savefile.h" #include "common/system.h" #include "common/textconsole.h" +#include "common/translation.h" #include "mohawk/livingbooks.h" @@ -35,6 +36,7 @@ #ifdef ENABLE_MYST #include "mohawk/myst.h" +#include "mohawk/myst_state.h" #endif #ifdef ENABLE_RIVEN @@ -52,7 +54,7 @@ struct MohawkGameDescription { }; const char* MohawkEngine::getGameId() const { - return _gameDescription->desc.gameid; + return _gameDescription->desc.gameId; } uint32 MohawkEngine::getFeatures() const { @@ -159,10 +161,24 @@ static const char *directoryGlobs[] = { 0 }; +static const ADExtraGuiOptionsMap optionsList[] = { + { + GAMEOPTION_PLAY_MYST_FLYBY, + { + _s("Play the Myst fly by movie"), + _s("The Myst fly by movie was not played by the original engine."), + "playmystflyby", + false + } + }, + + AD_EXTRA_GUI_OPTIONS_TERMINATOR +}; + class MohawkMetaEngine : public AdvancedMetaEngine { public: - MohawkMetaEngine() : AdvancedMetaEngine(Mohawk::gameDescriptions, sizeof(Mohawk::MohawkGameDescription), mohawkGames) { - _singleid = "mohawk"; + MohawkMetaEngine() : AdvancedMetaEngine(Mohawk::gameDescriptions, sizeof(Mohawk::MohawkGameDescription), mohawkGames, optionsList) { + _singleId = "mohawk"; _maxScanDepth = 2; _directoryGlobs = directoryGlobs; } @@ -184,13 +200,18 @@ public: virtual SaveStateList listSaves(const char *target) const; virtual int getMaximumSaveSlot() const { return 999; } virtual void removeSaveState(const char *target, int slot) const; + virtual SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const; }; bool MohawkMetaEngine::hasFeature(MetaEngineFeature f) const { return (f == kSupportsListSaves) || (f == kSupportsLoadingDuringStartup) - || (f == kSupportsDeleteSave); + || (f == kSupportsDeleteSave) + || (f == kSavesSupportMetaInfo) + || (f == kSavesSupportThumbnail) + || (f == kSavesSupportCreationDate) + || (f == kSavesSupportPlayTime); } SaveStateList MohawkMetaEngine::listSaves(const char *target) const { @@ -198,12 +219,15 @@ SaveStateList MohawkMetaEngine::listSaves(const char *target) const { SaveStateList saveList; // Loading games is only supported in Myst/Riven currently. +#ifdef ENABLE_MYST if (strstr(target, "myst")) { - filenames = g_system->getSavefileManager()->listSavefiles("*.mys"); + filenames = Mohawk::MystGameState::generateSaveGameList(); for (uint32 i = 0; i < filenames.size(); i++) saveList.push_back(SaveStateDescriptor(i, filenames[i])); - } else if (strstr(target, "riven")) { + } else +#endif + if (strstr(target, "riven")) { filenames = g_system->getSavefileManager()->listSavefiles("*.rvn"); for (uint32 i = 0; i < filenames.size(); i++) @@ -215,15 +239,35 @@ SaveStateList MohawkMetaEngine::listSaves(const char *target) const { void MohawkMetaEngine::removeSaveState(const char *target, int slot) const { // Removing saved games is only supported in Myst/Riven currently. +#ifdef ENABLE_MYST if (strstr(target, "myst")) { - Common::StringArray filenames = g_system->getSavefileManager()->listSavefiles("*.mys"); - g_system->getSavefileManager()->removeSavefile(filenames[slot].c_str()); - } else if (strstr(target, "riven")) { + Common::StringArray filenames = Mohawk::MystGameState::generateSaveGameList(); + Mohawk::MystGameState::deleteSave(filenames[slot]); + } else +#endif + if (strstr(target, "riven")) { Common::StringArray filenames = g_system->getSavefileManager()->listSavefiles("*.rvn"); g_system->getSavefileManager()->removeSavefile(filenames[slot].c_str()); } } +SaveStateDescriptor MohawkMetaEngine::querySaveMetaInfos(const char *target, int slot) const { +#ifdef ENABLE_MYST + if (strstr(target, "myst")) { + Common::StringArray filenames = Mohawk::MystGameState::generateSaveGameList(); + + if (slot >= (int) filenames.size()) { + return SaveStateDescriptor(); + } + + return Mohawk::MystGameState::querySaveMetaInfos(filenames[slot]); + } else +#endif + { + return SaveStateDescriptor(); + } +} + bool MohawkMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const { const Mohawk::MohawkGameDescription *gd = (const Mohawk::MohawkGameDescription *)desc; diff --git a/engines/mohawk/detection_tables.h b/engines/mohawk/detection_tables.h index 97d2932d57..7941a0d51a 100644 --- a/engines/mohawk/detection_tables.h +++ b/engines/mohawk/detection_tables.h @@ -22,6 +22,13 @@ namespace Mohawk { +#define GAMEOPTION_PLAY_MYST_FLYBY GUIO_GAMEOPTIONS1 + +#define GUI_OPTIONS_MYST GUIO3(GUIO_NOASPECT, GUIO_NOSUBTITLES, GUIO_NOMIDI) +#define GUI_OPTIONS_MYST_ME GUIO4(GUIO_NOASPECT, GUIO_NOSUBTITLES, GUIO_NOMIDI, GAMEOPTION_PLAY_MYST_FLYBY) +#define GUI_OPTIONS_MYST_DEMO GUIO4(GUIO_NOASPECT, GUIO_NOSUBTITLES, GUIO_NOMIDI, GUIO_NOLAUNCHLOAD) +#define GUI_OPTIONS_MYST_MAKING_OF GUIO4(GUIO_NOASPECT, GUIO_NOSUBTITLES, GUIO_NOMIDI, GUIO_NOLAUNCHLOAD) + static const MohawkGameDescription gameDescriptions[] = { // Myst // English Windows 3.11 @@ -34,7 +41,7 @@ static const MohawkGameDescription gameDescriptions[] = { Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + GUI_OPTIONS_MYST }, GType_MYST, 0, @@ -52,7 +59,7 @@ static const MohawkGameDescription gameDescriptions[] = { Common::EN_ANY, Common::kPlatformWindows, ADGF_DEMO | ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + GUI_OPTIONS_MYST_DEMO }, GType_MYST, GF_DEMO, @@ -70,7 +77,7 @@ static const MohawkGameDescription gameDescriptions[] = { Common::DE_DEU, Common::kPlatformWindows, ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + GUI_OPTIONS_MYST }, GType_MYST, 0, @@ -88,7 +95,7 @@ static const MohawkGameDescription gameDescriptions[] = { Common::DE_DEU, Common::kPlatformWindows, ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + GUI_OPTIONS_MYST }, GType_MYST, 0, @@ -106,7 +113,7 @@ static const MohawkGameDescription gameDescriptions[] = { Common::ES_ESP, Common::kPlatformWindows, ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + GUI_OPTIONS_MYST }, GType_MYST, 0, @@ -124,7 +131,7 @@ static const MohawkGameDescription gameDescriptions[] = { Common::IT_ITA, Common::kPlatformWindows, ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + GUI_OPTIONS_MYST }, GType_MYST, 0, @@ -142,7 +149,7 @@ static const MohawkGameDescription gameDescriptions[] = { Common::JA_JPN, Common::kPlatformWindows, ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + GUI_OPTIONS_MYST }, GType_MYST, 0, @@ -160,7 +167,7 @@ static const MohawkGameDescription gameDescriptions[] = { Common::FR_FRA, Common::kPlatformWindows, ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + GUI_OPTIONS_MYST }, GType_MYST, 0, @@ -178,7 +185,7 @@ static const MohawkGameDescription gameDescriptions[] = { Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + GUI_OPTIONS_MYST_MAKING_OF }, GType_MAKINGOF, 0, @@ -196,7 +203,7 @@ static const MohawkGameDescription gameDescriptions[] = { Common::JA_JPN, Common::kPlatformWindows, ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + GUI_OPTIONS_MYST_MAKING_OF }, GType_MAKINGOF, 0, @@ -214,7 +221,7 @@ static const MohawkGameDescription gameDescriptions[] = { Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + GUI_OPTIONS_MYST_ME }, GType_MYST, GF_ME, @@ -232,7 +239,7 @@ static const MohawkGameDescription gameDescriptions[] = { Common::DE_DEU, Common::kPlatformWindows, ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + GUI_OPTIONS_MYST_ME }, GType_MYST, GF_ME, @@ -250,7 +257,7 @@ static const MohawkGameDescription gameDescriptions[] = { Common::FR_FRA, Common::kPlatformWindows, ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + GUI_OPTIONS_MYST_ME }, GType_MYST, GF_ME, @@ -268,7 +275,7 @@ static const MohawkGameDescription gameDescriptions[] = { Common::PL_POL, Common::kPlatformWindows, ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + GUI_OPTIONS_MYST_ME }, GType_MYST, GF_ME, @@ -2698,7 +2705,7 @@ static const MohawkGameDescription fallbackDescs[] = { Common::UNK_LANG, Common::kPlatformWindows, ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + GUI_OPTIONS_MYST }, GType_MYST, 0, @@ -2713,7 +2720,7 @@ static const MohawkGameDescription fallbackDescs[] = { Common::UNK_LANG, Common::kPlatformWindows, ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + GUI_OPTIONS_MYST_MAKING_OF }, GType_MAKINGOF, 0, @@ -2728,7 +2735,7 @@ static const MohawkGameDescription fallbackDescs[] = { Common::UNK_LANG, Common::kPlatformWindows, ADGF_UNSTABLE, - GUIO1(GUIO_NOASPECT) + GUI_OPTIONS_MYST_ME }, GType_MYST, GF_ME, diff --git a/engines/mohawk/dialogs.cpp b/engines/mohawk/dialogs.cpp index ffc455286f..6c6ae9e77f 100644 --- a/engines/mohawk/dialogs.cpp +++ b/engines/mohawk/dialogs.cpp @@ -24,6 +24,7 @@ #include "mohawk/dialogs.h" #include "gui/gui-manager.h" +#include "gui/saveload.h" #include "gui/ThemeEngine.h" #include "gui/widget.h" #include "common/system.h" @@ -82,35 +83,47 @@ enum { kWaterCmd = 'WATR', kDropCmd = 'DROP', kMapCmd = 'SMAP', - kMenuCmd = 'MENU' + kMenuCmd = 'MENU', + kSaveCmd = 'SAVE', + kLoadCmd = 'LOAD', + kQuitCmd = 'QUIT' }; #ifdef ENABLE_MYST -MystOptionsDialog::MystOptionsDialog(MohawkEngine_Myst* vm) : GUI::OptionsDialog("", 120, 120, 360, 200), _vm(vm) { +MystOptionsDialog::MystOptionsDialog(MohawkEngine_Myst* vm) : GUI::Dialog(0, 0, 360, 200), _vm(vm) { // I18N: Option for fast scene switching - _zipModeCheckbox = new GUI::CheckboxWidget(this, 15, 10, 300, 15, _("~Z~ip Mode Activated"), 0, kZipCmd); - _transitionsCheckbox = new GUI::CheckboxWidget(this, 15, 30, 300, 15, _("~T~ransitions Enabled"), 0, kTransCmd); + _zipModeCheckbox = new GUI::CheckboxWidget(this, 15, 10, 220, 15, _("~Z~ip Mode Activated"), 0, kZipCmd); + _transitionsCheckbox = new GUI::CheckboxWidget(this, 15, 30, 220, 15, _("~T~ransitions Enabled"), 0, kTransCmd); // I18N: Drop book page _dropPageButton = new GUI::ButtonWidget(this, 15, 60, 100, 25, _("~D~rop Page"), 0, kDropCmd); // Myst ME only has maps if (_vm->getFeatures() & GF_ME) - _showMapButton = new GUI::ButtonWidget(this, 15, 95, 100, 25, _("~S~how Map"), 0, kMapCmd); + _showMapButton = new GUI::ButtonWidget(this, 15, 95, 100, 25, _("Show ~M~ap"), 0, kMapCmd); else _showMapButton = 0; // Myst demo only has a menu if (_vm->getFeatures() & GF_DEMO) - _returnToMenuButton = new GUI::ButtonWidget(this, 15, 95, 100, 25, _("~M~ain Menu"), 0, kMenuCmd); + _returnToMenuButton = new GUI::ButtonWidget(this, 15, 95, 100, 25, _("Main Men~u~"), 0, kMenuCmd); else _returnToMenuButton = 0; + _loadButton = new GUI::ButtonWidget(this, 245, 25, 100, 25, _("~L~oad"), 0, kLoadCmd); + _saveButton = new GUI::ButtonWidget(this, 245, 60, 100, 25, _("~S~ave"), 0, kSaveCmd); + new GUI::ButtonWidget(this, 245, 95, 100, 25, _("~Q~uit"), 0, kQuitCmd); + new GUI::ButtonWidget(this, 95, 160, 120, 25, _("~O~K"), 0, GUI::kOKCmd); new GUI::ButtonWidget(this, 225, 160, 120, 25, _("~C~ancel"), 0, GUI::kCloseCmd); + + _loadDialog = new GUI::SaveLoadChooser(_("Load game:"), _("Load"), false); + _saveDialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true); } MystOptionsDialog::~MystOptionsDialog() { + delete _loadDialog; + delete _saveDialog; } void MystOptionsDialog::open() { @@ -133,6 +146,44 @@ void MystOptionsDialog::open() { _zipModeCheckbox->setState(_vm->_gameState->_globals.zipMode); _transitionsCheckbox->setState(_vm->_gameState->_globals.transitions); + + _loadButton->setEnabled(_vm->canLoadGameStateCurrently()); + _saveButton->setEnabled(_vm->canSaveGameStateCurrently()); +} + +void MystOptionsDialog::save() { + int slot = _saveDialog->runModalWithCurrentTarget(); + + if (slot >= 0) { + Common::String result(_saveDialog->getResultString()); + if (result.empty()) { + // If the user was lazy and entered no save name, come up with a default name. + result = _saveDialog->createDefaultSaveDescription(slot); + } + + _vm->saveGameState(slot, result); + close(); + } +} + +void MystOptionsDialog::load() { + int slot = _loadDialog->runModalWithCurrentTarget(); + + if (slot >= 0) { + _vm->loadGameState(slot); + close(); + } +} + +void MystOptionsDialog::reflowLayout() { + const int screenW = g_system->getOverlayWidth(); + const int screenH = g_system->getOverlayHeight(); + + // Center the dialog + _x = (screenW - getWidth()) / 2; + _y = (screenH - getHeight()) / 2; + + Dialog::reflowLayout(); } void MystOptionsDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) { @@ -144,18 +195,39 @@ void MystOptionsDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, ui case kMapCmd: _vm->_needsShowMap = true; close(); - break; + break; case kMenuCmd: _vm->_needsShowDemoMenu = true; close(); - break; + break; + case kLoadCmd: + load(); + break; + case kSaveCmd: + save(); + break; + case kQuitCmd: { + if (_vm->getGameType() != GType_MAKINGOF) { + _vm->_needsShowCredits = true; + } else { + Common::Event eventQ; + eventQ.type = Common::EVENT_QUIT; + g_system->getEventManager()->pushEvent(eventQ); + } + close(); + } + break; case GUI::kOKCmd: _vm->_gameState->_globals.zipMode = _zipModeCheckbox->getState(); _vm->_gameState->_globals.transitions = _transitionsCheckbox->getState(); - GUI::OptionsDialog::handleCommand(sender, cmd, data); + setResult(1); + close(); + break; + case GUI::kCloseCmd: + close(); break; default: - GUI::OptionsDialog::handleCommand(sender, cmd, data); + GUI::Dialog::handleCommand(sender, cmd, data); } } diff --git a/engines/mohawk/dialogs.h b/engines/mohawk/dialogs.h index 7470cd3acd..bc25c72a43 100644 --- a/engines/mohawk/dialogs.h +++ b/engines/mohawk/dialogs.h @@ -32,6 +32,10 @@ #include "gui/widget.h" #include "gui/widgets/list.h" +namespace GUI { +class SaveLoadChooser; +} + namespace Mohawk { class MohawkEngine; @@ -70,20 +74,32 @@ public: class MohawkEngine_Myst; -class MystOptionsDialog : public GUI::OptionsDialog { +class MystOptionsDialog : public GUI::Dialog { public: MystOptionsDialog(MohawkEngine_Myst *vm); ~MystOptionsDialog(); void open(); + virtual void reflowLayout() override; virtual void handleCommand(GUI::CommandSender*, uint32, uint32); private: MohawkEngine_Myst *_vm; + GUI::CheckboxWidget *_zipModeCheckbox; GUI::CheckboxWidget *_transitionsCheckbox; + GUI::ButtonWidget *_dropPageButton; GUI::ButtonWidget *_showMapButton; GUI::ButtonWidget *_returnToMenuButton; + + GUI::ButtonWidget *_loadButton; + GUI::ButtonWidget *_saveButton; + + GUI::SaveLoadChooser *_loadDialog; + GUI::SaveLoadChooser *_saveDialog; + + void save(); + void load(); }; #endif diff --git a/engines/mohawk/myst.cpp b/engines/mohawk/myst.cpp index b6a6c27329..c16fab9131 100644 --- a/engines/mohawk/myst.cpp +++ b/engines/mohawk/myst.cpp @@ -66,38 +66,22 @@ MohawkEngine_Myst::MohawkEngine_Myst(OSystem *syst, const MohawkGameDescription DebugMan.addDebugChannel(kDebugHelp, "Help", "Track Help File (HELP) Parsing"); DebugMan.addDebugChannel(kDebugCache, "Cache", "Track Resource Cache Accesses"); - // Engine tweaks - // Disabling this makes engine behavior as per - // original, including bugs, missing bits etc. :) - _tweaksEnabled = true; - _currentCursor = 0; _mainCursor = kDefaultMystCursor; _showResourceRects = false; _curCard = 0; _needsUpdate = false; + _canSafelySaveLoad = false; _curResource = -1; - _hoverResource = 0; - _dragResource = 0; - - _gfx = NULL; - _console = NULL; - _scriptParser = NULL; - _gameState = NULL; - _loadDialog = NULL; - _optionsDialog = NULL; - - _cursorHintCount = 0; - _cursorHints = NULL; - - _prevStack = NULL; - - _view.conditionalImageCount = 0; - _view.conditionalImages = NULL; - _view.soundList = NULL; - _view.soundListVolume = NULL; - _view.scriptResCount = 0; - _view.scriptResources = NULL; + _hoverResource = nullptr; + + _gfx = nullptr; + _console = nullptr; + _scriptParser = nullptr; + _gameState = nullptr; + _optionsDialog = nullptr; + + _prevStack = nullptr; } MohawkEngine_Myst::~MohawkEngine_Myst() { @@ -107,20 +91,12 @@ MohawkEngine_Myst::~MohawkEngine_Myst() { delete _console; delete _scriptParser; delete _gameState; - delete _loadDialog; delete _optionsDialog; delete _prevStack; delete _rnd; - delete[] _cursorHints; - - delete[] _view.conditionalImages; - delete[] _view.scriptResources; - for (uint32 i = 0; i < _resources.size(); i++) delete _resources[i]; - - _resources.clear(); } // Uses cached data objects in preference to disk access @@ -138,7 +114,11 @@ Common::SeekableReadStream *MohawkEngine_Myst::getResource(uint32 tag, uint16 id } error("Could not find a \'%s\' resource with ID %04x", tag2str(tag), id); - return NULL; + return nullptr; +} + +Common::Array<uint16> MohawkEngine_Myst::getResourceIDList(uint32 type) const { + return _mhk[0]->getResourceIDList(type); } void MohawkEngine_Myst::cachePreload(uint32 tag, uint16 id) { @@ -242,7 +222,6 @@ Common::Error MohawkEngine_Myst::run() { _gfx = new MystGraphics(this); _console = new MystConsole(this); _gameState = new MystGameState(this, _saveFileMan); - _loadDialog = new GUI::SaveLoadChooser(_("Load game:"), _("Load"), false); _optionsDialog = new MystOptionsDialog(this); _cursor = new MystCursorManager(this); _rnd = new Common::RandomSource("myst"); @@ -251,9 +230,9 @@ Common::Error MohawkEngine_Myst::run() { _cursor->showCursor(); // Load game from launcher/command line if requested - if (ConfMan.hasKey("save_slot") && canLoadGameStateCurrently()) { + if (ConfMan.hasKey("save_slot") && hasGameSaveSupport()) { uint32 gameToLoad = ConfMan.getInt("save_slot"); - Common::StringArray savedGamesList = _gameState->generateSaveGameList(); + Common::StringArray savedGamesList = MystGameState::generateSaveGameList(); if (gameToLoad > savedGamesList.size()) error ("Could not find saved game"); _gameState->load(savedGamesList[gameToLoad]); @@ -284,7 +263,7 @@ Common::Error MohawkEngine_Myst::run() { _needsUpdate = _video->updateMovies(); _scriptParser->runPersistentScripts(); - while (_eventMan->pollEvent(event)) { + while (pollEvent(event)) { switch (event.type) { case Common::EVENT_MOUSEMOVE: { _needsUpdate = true; @@ -324,17 +303,15 @@ Common::Error MohawkEngine_Myst::run() { case Common::KEYCODE_SPACE: pauseGame(); break; - case Common::KEYCODE_F4: - _showResourceRects = !_showResourceRects; - if (_showResourceRects) - drawResourceRects(); - break; case Common::KEYCODE_F5: _needsPageDrop = false; _needsShowMap = false; _needsShowDemoMenu = false; + _needsShowCredits = false; + _canSafelySaveLoad = true; runDialog(*_optionsDialog); + _canSafelySaveLoad = false; if (_needsPageDrop) { dropPage(); @@ -350,6 +327,12 @@ Common::Error MohawkEngine_Myst::run() { changeToStack(kDemoStack, 2002, 0, 0); _needsShowDemoMenu = false; } + + if (_needsShowCredits) { + _cursor->hideCursor(); + changeToStack(kCreditsStack, 10000, 0, 0); + _needsShowCredits = false; + } break; default: break; @@ -372,6 +355,15 @@ Common::Error MohawkEngine_Myst::run() { return Common::kNoError; } +bool MohawkEngine_Myst::pollEvent(Common::Event &event) { + // Saving / Loading is allowed from the GMM only when the main event loop is running + _canSafelySaveLoad = true; + bool eventReturned = _eventMan->pollEvent(event); + _canSafelySaveLoad = false; + + return eventReturned; +} + bool MohawkEngine_Myst::skippableWait(uint32 duration) { uint32 end = _system->getMillis() + duration; bool skipped = false; @@ -413,6 +405,7 @@ void MohawkEngine_Myst::changeToStack(uint16 stack, uint16 card, uint16 linkSrcS // Fill screen with black and empty cursor _cursor->setCursor(0); + _currentCursor = 0; if (getFeatures() & GF_ME) _system->fillScreen(_system->getScreenFormat().RGBToColor(0, 0, 0)); @@ -502,7 +495,7 @@ void MohawkEngine_Myst::changeToStack(uint16 stack, uint16 card, uint16 linkSrcS if (getFeatures() & GF_ME) { // Play Flyby Entry Movie on Masterpiece Edition. - const char *flyby = 0; + const char *flyby = nullptr; switch (_curStack) { case kSeleniticStack: @@ -512,8 +505,9 @@ void MohawkEngine_Myst::changeToStack(uint16 stack, uint16 card, uint16 linkSrcS flyby = "stoneship flyby"; break; // Myst Flyby Movie not used in Original Masterpiece Edition Engine + // We play it when first arriving on Myst, and if the user has chosen so. case kMystStack: - if (_tweaksEnabled) + if (ConfMan.getBool("playmystflyby") && card == 4134) flyby = "myst flyby"; break; case kMechanicalStack: @@ -539,12 +533,12 @@ void MohawkEngine_Myst::changeToStack(uint16 stack, uint16 card, uint16 linkSrcS uint16 MohawkEngine_Myst::getCardBackgroundId() { uint16 imageToDraw = 0; - if (_view.conditionalImageCount == 0) + if (_view.conditionalImages.size() == 0) imageToDraw = _view.mainImage; else { - for (uint16 i = 0; i < _view.conditionalImageCount; i++) { + for (uint16 i = 0; i < _view.conditionalImages.size(); i++) { uint16 varValue = _scriptParser->getVar(_view.conditionalImages[i].var); - if (varValue < _view.conditionalImages[i].numStates) + if (varValue < _view.conditionalImages[i].values.size()) imageToDraw = _view.conditionalImages[i].values[varValue]; } } @@ -586,36 +580,7 @@ void MohawkEngine_Myst::changeToCard(uint16 card, TransitionType transition) { drawCardBackground(); // Handle sound - int16 soundAction = 0; - uint16 soundActionVolume = 0; - - if (_view.sound == kMystSoundActionConditional) { - uint16 soundVarValue = _scriptParser->getVar(_view.soundVar); - if (soundVarValue >= _view.soundCount) - warning("Conditional sound variable outside range"); - else { - soundAction = _view.soundList[soundVarValue]; - soundActionVolume = _view.soundListVolume[soundVarValue]; - } - } else { - soundAction = _view.sound; - soundActionVolume = _view.soundVolume; - } - - if (soundAction == kMystSoundActionContinue) - debug(2, "Continuing with current sound"); - else if (soundAction == kMystSoundActionChangeVolume) { - debug(2, "Continuing with current sound, changing volume"); - _sound->changeBackgroundVolumeMyst(soundActionVolume); - } else if (soundAction == kMystSoundActionStop) { - debug(2, "Stopping sound"); - _sound->stopBackgroundMyst(); - } else if (soundAction > 0) { - debug(2, "Playing new sound %d", soundAction); - _sound->replaceBackgroundMyst(soundAction, soundActionVolume); - } else { - error("Unknown sound action %d", soundAction); - } + applySoundBlock(_view.soundBlock); if (_view.flags & kMystZipDestination) _gameState->addZipDest(_curStack, card); @@ -637,15 +602,16 @@ void MohawkEngine_Myst::changeToCard(uint16 card, TransitionType transition) { // Make sure the screen is updated if (transition != kNoTransition) { - if (!_gameState->_globals.transitions) - transition = kTransitionCopy; - - _gfx->runTransition(transition, Common::Rect(544, 333), 10, 0); + if (_gameState->_globals.transitions) { + _gfx->runTransition(transition, Common::Rect(544, 333), 10, 0); + } else { + _gfx->copyBackBufferToScreen(Common::Rect(544, 333)); + _needsUpdate = true; + } } // Make sure we have the right cursor showing - _dragResource = 0; - _hoverResource = 0; + _hoverResource = nullptr; _curResource = -1; checkCurrentResource(); @@ -671,13 +637,13 @@ void MohawkEngine_Myst::checkCurrentResource() { // Tell previous resource the mouse is no longer hovering it if (_hoverResource && !_hoverResource->contains(mouse)) { _hoverResource->handleMouseLeave(); - _hoverResource = 0; + _hoverResource = nullptr; } for (uint16 i = 0; i < _resources.size(); i++) if (_resources[i]->contains(mouse)) { - if (_hoverResource != _resources[i] && _resources[i]->type == kMystHoverArea) { - _hoverResource = static_cast<MystResourceType13 *>(_resources[i]); + if (_hoverResource != _resources[i] && _resources[i]->type == kMystAreaHover) { + _hoverResource = static_cast<MystAreaHover *>(_resources[i]); _hoverResource->handleMouseEnter(); } @@ -694,17 +660,17 @@ void MohawkEngine_Myst::checkCurrentResource() { checkCursorHints(); } -MystResource *MohawkEngine_Myst::updateCurrentResource() { +MystArea *MohawkEngine_Myst::updateCurrentResource() { checkCurrentResource(); if (_curResource >= 0) return _resources[_curResource]; else - return 0; + return nullptr; } void MohawkEngine_Myst::loadCard() { - debugC(kDebugView, "Loading Card View:"); + debugC(kDebugView, "Loading Card View: %d", _curCard); Common::SeekableReadStream *viewStream = getResource(ID_VIEW, _curCard); @@ -713,21 +679,23 @@ void MohawkEngine_Myst::loadCard() { debugC(kDebugView, "Flags: 0x%04X", _view.flags); // The Image Block (Reminiscent of Riven PLST resources) - _view.conditionalImageCount = viewStream->readUint16LE(); - debugC(kDebugView, "Conditional Image Count: %d", _view.conditionalImageCount); - if (_view.conditionalImageCount != 0) { - _view.conditionalImages = new MystCondition[_view.conditionalImageCount]; - for (uint16 i = 0; i < _view.conditionalImageCount; i++) { + uint16 conditionalImageCount = viewStream->readUint16LE(); + debugC(kDebugView, "Conditional Image Count: %d", conditionalImageCount); + if (conditionalImageCount != 0) { + for (uint16 i = 0; i < conditionalImageCount; i++) { + MystCondition conditionalImage; + debugC(kDebugView, "\tImage %d:", i); - _view.conditionalImages[i].var = viewStream->readUint16LE(); - debugC(kDebugView, "\t\tVar: %d", _view.conditionalImages[i].var); - _view.conditionalImages[i].numStates = viewStream->readUint16LE(); - debugC(kDebugView, "\t\tNumber of States: %d", _view.conditionalImages[i].numStates); - _view.conditionalImages[i].values = new uint16[_view.conditionalImages[i].numStates]; - for (uint16 j = 0; j < _view.conditionalImages[i].numStates; j++) { - _view.conditionalImages[i].values[j] = viewStream->readUint16LE(); - debugC(kDebugView, "\t\tState %d -> Value %d", j, _view.conditionalImages[i].values[j]); + conditionalImage.var = viewStream->readUint16LE(); + debugC(kDebugView, "\t\tVar: %d", conditionalImage.var); + uint16 numStates = viewStream->readUint16LE(); + debugC(kDebugView, "\t\tNumber of States: %d", numStates); + for (uint16 j = 0; j < numStates; j++) { + conditionalImage.values.push_back(viewStream->readUint16LE()); + debugC(kDebugView, "\t\tState %d -> Value %d", j, conditionalImage.values[j]); } + + _view.conditionalImages.push_back(conditionalImage); } _view.mainImage = 0; } else { @@ -736,87 +704,58 @@ void MohawkEngine_Myst::loadCard() { } // The Sound Block (Reminiscent of Riven SLST resources) - _view.sound = viewStream->readSint16LE(); - debugCN(kDebugView, "Sound Control: %d = ", _view.sound); - if (_view.sound > 0) { - debugC(kDebugView, "Play new Sound, change volume"); - debugC(kDebugView, "\tSound: %d", _view.sound); - _view.soundVolume = viewStream->readUint16LE(); - debugC(kDebugView, "\tVolume: %d", _view.soundVolume); - } else if (_view.sound == kMystSoundActionContinue) - debugC(kDebugView, "Continue current sound"); - else if (_view.sound == kMystSoundActionChangeVolume) { - debugC(kDebugView, "Continue current sound, change volume"); - _view.soundVolume = viewStream->readUint16LE(); - debugC(kDebugView, "\tVolume: %d", _view.soundVolume); - } else if (_view.sound == kMystSoundActionStop) { - debugC(kDebugView, "Stop sound"); - } else if (_view.sound == kMystSoundActionConditional) { - debugC(kDebugView, "Conditional sound list"); - _view.soundVar = viewStream->readUint16LE(); - debugC(kDebugView, "\tVar: %d", _view.soundVar); - _view.soundCount = viewStream->readUint16LE(); - debugC(kDebugView, "\tCount: %d", _view.soundCount); - _view.soundList = new int16[_view.soundCount]; - _view.soundListVolume = new uint16[_view.soundCount]; - - for (uint16 i = 0; i < _view.soundCount; i++) { - _view.soundList[i] = viewStream->readSint16LE(); - debugC(kDebugView, "\t\tCondition %d: Action %d", i, _view.soundList[i]); - if (_view.soundList[i] == kMystSoundActionChangeVolume || _view.soundList[i] >= 0) { - _view.soundListVolume[i] = viewStream->readUint16LE(); - debugC(kDebugView, "\t\tCondition %d: Volume %d", i, _view.soundListVolume[i]); - } - } - } else { - debugC(kDebugView, "Unknown"); - warning("Unknown sound control value in card"); - } + _view.soundBlock = readSoundBlock(viewStream); // Resources that scripts can call upon - _view.scriptResCount = viewStream->readUint16LE(); - debugC(kDebugView, "Script Resource Count: %d", _view.scriptResCount); - if (_view.scriptResCount != 0) { - _view.scriptResources = new MystView::ScriptResource[_view.scriptResCount]; - for (uint16 i = 0; i < _view.scriptResCount; i++) { - debugC(kDebugView, "\tResource %d:", i); - _view.scriptResources[i].type = viewStream->readUint16LE(); - debugC(kDebugView, "\t\t Type: %d", _view.scriptResources[i].type); - - switch (_view.scriptResources[i].type) { - case 1: - debugC(kDebugView, "\t\t\t\t= Image"); - break; - case 2: - debugC(kDebugView, "\t\t\t\t= Sound"); - break; - case 3: - debugC(kDebugView, "\t\t\t\t= Resource List"); - break; - default: - debugC(kDebugView, "\t\t\t\t= Unknown"); - break; - } + uint16 scriptResCount = viewStream->readUint16LE(); + debugC(kDebugView, "Script Resource Count: %d", scriptResCount); + for (uint16 i = 0; i < scriptResCount; i++) { + MystView::ScriptResource scriptResource; + + debugC(kDebugView, "\tResource %d:", i); + scriptResource.type = (MystView::ScriptResourceType) viewStream->readUint16LE(); + debugC(kDebugView, "\t\t Type: %d", scriptResource.type); + + switch (scriptResource.type) { + case MystView::kResourceImage: + debugC(kDebugView, "\t\t\t\t= Image"); + break; + case MystView::kResourceSound: + debugC(kDebugView, "\t\t\t\t= Sound"); + break; + case MystView::kResourceSwitch: + debugC(kDebugView, "\t\t\t\t= Resource Switch"); + break; + case MystView::kResourceImageNoCache: + debugC(kDebugView, "\t\t\t\t= Image - Caching disabled"); + break; + case MystView::kResourceSoundNoCache: + debugC(kDebugView, "\t\t\t\t= Sound - Caching disabled"); + break; + default: + debugC(kDebugView, "\t\t\t\t= Unknown"); + warning("Unknown script resource type '%d' in card '%d'", scriptResource.type, _curCard); + break; + } - if (_view.scriptResources[i].type == 3) { - _view.scriptResources[i].var = viewStream->readUint16LE(); - debugC(kDebugView, "\t\t Var: %d", _view.scriptResources[i].var); - _view.scriptResources[i].count = viewStream->readUint16LE(); - debugC(kDebugView, "\t\t Resource List Count: %d", _view.scriptResources[i].count); - _view.scriptResources[i].u0 = viewStream->readUint16LE(); - debugC(kDebugView, "\t\t u0: %d", _view.scriptResources[i].u0); - _view.scriptResources[i].resource_list = new int16[_view.scriptResources[i].count]; - - for (uint16 j = 0; j < _view.scriptResources[i].count; j++) { - _view.scriptResources[i].resource_list[j] = viewStream->readSint16LE(); - debugC(kDebugView, "\t\t Resource List %d: %d", j, _view.scriptResources[i].resource_list[j]); - } - } else { - _view.scriptResources[i].resource_list = NULL; - _view.scriptResources[i].id = viewStream->readUint16LE(); - debugC(kDebugView, "\t\t Id: %d", _view.scriptResources[i].id); + if (scriptResource.type == MystView::kResourceSwitch) { + scriptResource.switchVar = viewStream->readUint16LE(); + debugC(kDebugView, "\t\t Var: %d", scriptResource.switchVar); + uint16 count = viewStream->readUint16LE(); + debugC(kDebugView, "\t\t Resource List Count: %d", count); + scriptResource.switchResourceType = (MystView::ScriptResourceType) viewStream->readUint16LE(); + debugC(kDebugView, "\t\t u0: %d", scriptResource.switchResourceType); + + for (uint16 j = 0; j < count; j++) { + scriptResource.switchResourceIds.push_back(viewStream->readSint16LE()); + debugC(kDebugView, "\t\t Resource List %d: %d", j, scriptResource.switchResourceIds[j]); } + } else { + scriptResource.id = viewStream->readUint16LE(); + debugC(kDebugView, "\t\t Id: %d", scriptResource.id); } + + _view.scriptResources.push_back(scriptResource); } // Identifiers for other resources. 0 if non existent. There is always an RLST. @@ -831,7 +770,6 @@ void MohawkEngine_Myst::loadCard() { delete viewStream; // Precache Card Resources - // TODO: Deal with Mac ME External Picture File uint32 cacheImageType; if (getFeatures() & GF_ME) cacheImageType = ID_PICT; @@ -839,63 +777,58 @@ void MohawkEngine_Myst::loadCard() { cacheImageType = ID_WDIB; // Precache Image Block data - if (_view.conditionalImageCount != 0) { - for (uint16 i = 0; i < _view.conditionalImageCount; i++) - for (uint16 j = 0; j < _view.conditionalImages[i].numStates; j++) - cachePreload(cacheImageType, _view.conditionalImages[i].values[j]); - } else + if (_view.conditionalImages.size() != 0) { + for (uint16 i = 0; i < _view.conditionalImages.size(); i++) { + uint16 value = _scriptParser->getVar(_view.conditionalImages[i].var); + cachePreload(cacheImageType, _view.conditionalImages[i].values[value]); + } + } else { cachePreload(cacheImageType, _view.mainImage); + } // Precache Sound Block data - if (_view.sound > 0) - cachePreload(ID_MSND, _view.sound); - else if (_view.sound == kMystSoundActionConditional) { - for (uint16 i = 0; i < _view.soundCount; i++) { - if (_view.soundList[i] > 0) - cachePreload(ID_MSND, _view.soundList[i]); + if (_view.soundBlock.sound > 0) + cachePreload(ID_MSND, _view.soundBlock.sound); + else if (_view.soundBlock.sound == kMystSoundActionConditional) { + uint16 value = _scriptParser->getVar(_view.soundBlock.soundVar); + if (_view.soundBlock.soundList[value].action > 0) { + cachePreload(ID_MSND, _view.soundBlock.soundList[value].action); } } // Precache Script Resources - if (_view.scriptResCount != 0) { - for (uint16 i = 0; i < _view.scriptResCount; i++) { - switch (_view.scriptResources[i].type) { - case 1: - cachePreload(cacheImageType, _view.scriptResources[i].id); - break; - case 2: - cachePreload(ID_MSND, _view.scriptResources[i].id); - break; - case 3: - warning("TODO: Precaching of Script Resource List not supported"); - break; - default: - warning("Unknown Resource in Script Resource List Precaching"); - break; - } + for (uint16 i = 0; i < _view.scriptResources.size(); i++) { + MystView::ScriptResourceType type; + int16 id; + if (_view.scriptResources[i].type == MystView::kResourceSwitch) { + type = _view.scriptResources[i].switchResourceType; + uint16 value = _scriptParser->getVar(_view.scriptResources[i].switchVar); + id = _view.scriptResources[i].switchResourceIds[value]; + } else { + type = _view.scriptResources[i].type; + id = _view.scriptResources[i].id; + } + + if (id < 0) continue; + + switch (type) { + case MystView::kResourceImage: + cachePreload(cacheImageType, id); + break; + case MystView::kResourceSound: + cachePreload(ID_MSND, id); + break; + default: + // The other resource types should not be cached + break; } } } void MohawkEngine_Myst::unloadCard() { - for (uint16 i = 0; i < _view.conditionalImageCount; i++) - delete[] _view.conditionalImages[i].values; - - delete[] _view.conditionalImages; - _view.conditionalImageCount = 0; - _view.conditionalImages = NULL; - - delete[] _view.soundList; - _view.soundList = NULL; - delete[] _view.soundListVolume; - _view.soundListVolume = NULL; - - for (uint16 i = 0; i < _view.scriptResCount; i++) - delete[] _view.scriptResources[i].resource_list; - - delete[] _view.scriptResources; - _view.scriptResources = NULL; - _view.scriptResCount = 0; + _view.conditionalImages.clear(); + _view.soundBlock.soundList.clear(); + _view.scriptResources.clear(); } void MohawkEngine_Myst::runInitScript() { @@ -968,14 +901,12 @@ void MohawkEngine_Myst::loadHelp(uint16 id) { debugC(kDebugHelp, "\thelpText: \"%s\"", helpText.c_str()); delete[] u0; + + delete helpStream; } void MohawkEngine_Myst::loadCursorHints() { - for (uint16 i = 0; i < _cursorHintCount; i++) - delete[] _cursorHints[i].variableHint.values; - _cursorHintCount = 0; - delete[] _cursorHints; - _cursorHints = NULL; + _cursorHints.clear(); if (!_view.hint) { debugC(kDebugHint, "No HINT Present"); @@ -985,33 +916,33 @@ void MohawkEngine_Myst::loadCursorHints() { debugC(kDebugHint, "Loading Cursor Hints:"); Common::SeekableReadStream *hintStream = getResource(ID_HINT, _curCard); - _cursorHintCount = hintStream->readUint16LE(); - debugC(kDebugHint, "Cursor Hint Count: %d", _cursorHintCount); - _cursorHints = new MystCursorHint[_cursorHintCount]; + uint16 cursorHintCount = hintStream->readUint16LE(); + debugC(kDebugHint, "Cursor Hint Count: %d", cursorHintCount); + + for (uint16 i = 0; i < cursorHintCount; i++) { + MystCursorHint hint; - for (uint16 i = 0; i < _cursorHintCount; i++) { debugC(kDebugHint, "Cursor Hint %d:", i); - _cursorHints[i].id = hintStream->readUint16LE(); - debugC(kDebugHint, "\tId: %d", _cursorHints[i].id); - _cursorHints[i].cursor = hintStream->readSint16LE(); - debugC(kDebugHint, "\tCursor: %d", _cursorHints[i].cursor); + hint.id = hintStream->readUint16LE(); + debugC(kDebugHint, "\tId: %d", hint.id); + hint.cursor = hintStream->readSint16LE(); + debugC(kDebugHint, "\tCursor: %d", hint.cursor); - if (_cursorHints[i].cursor == -1) { + if (hint.cursor == -1) { debugC(kDebugHint, "\tConditional Cursor Hints:"); - _cursorHints[i].variableHint.var = hintStream->readUint16LE(); - debugC(kDebugHint, "\tVar: %d", _cursorHints[i].variableHint.var); - _cursorHints[i].variableHint.numStates = hintStream->readUint16LE(); - debugC(kDebugHint, "\tNumber of States: %d", _cursorHints[i].variableHint.numStates); - _cursorHints[i].variableHint.values = new uint16[_cursorHints[i].variableHint.numStates]; - for (uint16 j = 0; j < _cursorHints[i].variableHint.numStates; j++) { - _cursorHints[i].variableHint.values[j] = hintStream->readUint16LE(); - debugC(kDebugHint, "\t\t State %d: Cursor %d", j, _cursorHints[i].variableHint.values[j]); + hint.variableHint.var = hintStream->readUint16LE(); + debugC(kDebugHint, "\tVar: %d", hint.variableHint.var); + uint16 numStates = hintStream->readUint16LE(); + debugC(kDebugHint, "\tNumber of States: %d", numStates); + for (uint16 j = 0; j < numStates; j++) { + hint.variableHint.values.push_back(hintStream->readUint16LE()); + debugC(kDebugHint, "\t\t State %d: Cursor %d", j, hint.variableHint.values[j]); } } else { - _cursorHints[i].variableHint.var = 0; - _cursorHints[i].variableHint.numStates = 0; - _cursorHints[i].variableHint.values = NULL; + hint.variableHint.var = 0; } + + _cursorHints.push_back(hint); } delete hintStream; @@ -1033,12 +964,12 @@ void MohawkEngine_Myst::checkCursorHints() { } // Check all the cursor hints to see if we're in a hotspot that contains a hint. - for (uint16 i = 0; i < _cursorHintCount; i++) + for (uint16 i = 0; i < _cursorHints.size(); i++) if (_cursorHints[i].id == _curResource && _resources[_cursorHints[i].id]->isEnabled()) { if (_cursorHints[i].cursor == -1) { uint16 var_value = _scriptParser->getVar(_cursorHints[i].variableHint.var); - if (var_value >= _cursorHints[i].variableHint.numStates) + if (var_value >= _cursorHints[i].variableHint.values.size()) warning("Variable %d Out of Range in variable HINT Resource %d", _cursorHints[i].variableHint.var, i); else { _currentCursor = _cursorHints[i].variableHint.values[var_value]; @@ -1076,50 +1007,50 @@ void MohawkEngine_Myst::drawResourceImages() { _resources[i]->drawDataToScreen(); } -void MohawkEngine_Myst::redrawResource(MystResourceType8 *resource, bool update) { - resource->drawConditionalDataToScreen(_scriptParser->getVar(resource->getType8Var()), update); +void MohawkEngine_Myst::redrawResource(MystAreaImageSwitch *resource, bool update) { + resource->drawConditionalDataToScreen(_scriptParser->getVar(resource->getImageSwitchVar()), update); } void MohawkEngine_Myst::redrawArea(uint16 var, bool update) { for (uint16 i = 0; i < _resources.size(); i++) - if (_resources[i]->type == kMystConditionalImage && _resources[i]->getType8Var() == var) - redrawResource(static_cast<MystResourceType8 *>(_resources[i]), update); + if (_resources[i]->type == kMystAreaImageSwitch && _resources[i]->getImageSwitchVar() == var) + redrawResource(static_cast<MystAreaImageSwitch *>(_resources[i]), update); } -MystResource *MohawkEngine_Myst::loadResource(Common::SeekableReadStream *rlstStream, MystResource *parent) { - MystResource *resource = 0; +MystArea *MohawkEngine_Myst::loadResource(Common::SeekableReadStream *rlstStream, MystArea *parent) { + MystArea *resource = nullptr; ResourceType type = static_cast<ResourceType>(rlstStream->readUint16LE()); debugC(kDebugResource, "\tType: %d", type); - debugC(kDebugResource, "\tSub_Record: %d", (parent == NULL) ? 0 : 1); + debugC(kDebugResource, "\tSub_Record: %d", (parent == nullptr) ? 0 : 1); switch (type) { - case kMystAction: - resource = new MystResourceType5(this, rlstStream, parent); + case kMystAreaAction: + resource = new MystAreaAction(this, rlstStream, parent); break; - case kMystVideo: - resource = new MystResourceType6(this, rlstStream, parent); + case kMystAreaVideo: + resource = new MystAreaVideo(this, rlstStream, parent); break; - case kMystSwitch: - resource = new MystResourceType7(this, rlstStream, parent); + case kMystAreaActionSwitch: + resource = new MystAreaActionSwitch(this, rlstStream, parent); break; - case kMystConditionalImage: - resource = new MystResourceType8(this, rlstStream, parent); + case kMystAreaImageSwitch: + resource = new MystAreaImageSwitch(this, rlstStream, parent); break; - case kMystSlider: - resource = new MystResourceType10(this, rlstStream, parent); + case kMystAreaSlider: + resource = new MystAreaSlider(this, rlstStream, parent); break; - case kMystDragArea: - resource = new MystResourceType11(this, rlstStream, parent); + case kMystAreaDrag: + resource = new MystAreaDrag(this, rlstStream, parent); break; case kMystVideoInfo: - resource = new MystResourceType12(this, rlstStream, parent); + resource = new MystVideoInfo(this, rlstStream, parent); break; - case kMystHoverArea: - resource = new MystResourceType13(this, rlstStream, parent); + case kMystAreaHover: + resource = new MystAreaHover(this, rlstStream, parent); break; default: - resource = new MystResource(this, rlstStream, parent); + resource = new MystArea(this, rlstStream, parent); break; } @@ -1145,34 +1076,42 @@ void MohawkEngine_Myst::loadResources() { for (uint16 i = 0; i < resourceCount; i++) { debugC(kDebugResource, "Resource #%d:", i); - _resources.push_back(loadResource(rlstStream, NULL)); + _resources.push_back(loadResource(rlstStream, nullptr)); } delete rlstStream; } Common::Error MohawkEngine_Myst::loadGameState(int slot) { - if (_gameState->load(_gameState->generateSaveGameList()[slot])) + if (_gameState->load(MystGameState::generateSaveGameList()[slot])) return Common::kNoError; return Common::kUnknownError; } Common::Error MohawkEngine_Myst::saveGameState(int slot, const Common::String &desc) { - Common::StringArray saveList = _gameState->generateSaveGameList(); + Common::StringArray saveList = MystGameState::generateSaveGameList(); if ((uint)slot < saveList.size()) - _gameState->deleteSave(saveList[slot]); + MystGameState::deleteSave(saveList[slot]); + + return _gameState->save(desc) ? Common::kNoError : Common::kUnknownError; +} - return _gameState->save(Common::String(desc)) ? Common::kNoError : Common::kUnknownError; +bool MohawkEngine_Myst::hasGameSaveSupport() const { + return !(getFeatures() & GF_DEMO) && getGameType() != GType_MAKINGOF; } bool MohawkEngine_Myst::canLoadGameStateCurrently() { // No loading in the demo/makingof - return !(getFeatures() & GF_DEMO) && getGameType() != GType_MAKINGOF; + return _canSafelySaveLoad && hasGameSaveSupport(); } bool MohawkEngine_Myst::canSaveGameStateCurrently() { + if (!_canSafelySaveLoad) { + return false; + } + // There's a limited number of stacks the game can save in switch (_curStack) { case kChannelwoodStack: @@ -1225,4 +1164,82 @@ void MohawkEngine_Myst::dropPage() { checkCursorHints(); } +MystSoundBlock MohawkEngine_Myst::readSoundBlock(Common::ReadStream *stream) const { + MystSoundBlock soundBlock; + soundBlock.sound = stream->readSint16LE(); + debugCN(kDebugView, "Sound Control: %d = ", soundBlock.sound); + + if (soundBlock.sound > 0) { + debugC(kDebugView, "Play new Sound, change volume"); + debugC(kDebugView, "\tSound: %d", soundBlock.sound); + soundBlock.soundVolume = stream->readUint16LE(); + debugC(kDebugView, "\tVolume: %d", soundBlock.soundVolume); + } else if (soundBlock.sound == kMystSoundActionContinue) + debugC(kDebugView, "Continue current sound"); + else if (soundBlock.sound == kMystSoundActionChangeVolume) { + debugC(kDebugView, "Continue current sound, change volume"); + soundBlock.soundVolume = stream->readUint16LE(); + debugC(kDebugView, "\tVolume: %d", soundBlock.soundVolume); + } else if (soundBlock.sound == kMystSoundActionStop) { + debugC(kDebugView, "Stop sound"); + } else if (soundBlock.sound == kMystSoundActionConditional) { + debugC(kDebugView, "Conditional sound list"); + soundBlock.soundVar = stream->readUint16LE(); + debugC(kDebugView, "\tVar: %d", soundBlock.soundVar); + uint16 soundCount = stream->readUint16LE(); + debugC(kDebugView, "\tCount: %d", soundCount); + + for (uint16 i = 0; i < soundCount; i++) { + MystSoundBlock::SoundItem sound; + + sound.action = stream->readSint16LE(); + debugC(kDebugView, "\t\tCondition %d: Action %d", i, sound.action); + if (sound.action == kMystSoundActionChangeVolume || sound.action >= 0) { + sound.volume = stream->readUint16LE(); + debugC(kDebugView, "\t\tCondition %d: Volume %d", i, sound.volume); + } + + soundBlock.soundList.push_back(sound); + } + } else { + debugC(kDebugView, "Unknown"); + warning("Unknown sound control value '%d' in card '%d'", soundBlock.sound, _curCard); + } + + return soundBlock; +} + +void MohawkEngine_Myst::applySoundBlock(const MystSoundBlock &block) { + int16 soundAction = 0; + uint16 soundActionVolume = 0; + + if (block.sound == kMystSoundActionConditional) { + uint16 soundVarValue = _scriptParser->getVar(block.soundVar); + if (soundVarValue >= block.soundList.size()) + warning("Conditional sound variable outside range"); + else { + soundAction = block.soundList[soundVarValue].action; + soundActionVolume = block.soundList[soundVarValue].volume; + } + } else { + soundAction = block.sound; + soundActionVolume = block.soundVolume; + } + + if (soundAction == kMystSoundActionContinue) + debug(2, "Continuing with current sound"); + else if (soundAction == kMystSoundActionChangeVolume) { + debug(2, "Continuing with current sound, changing volume"); + _sound->changeBackgroundVolumeMyst(soundActionVolume); + } else if (soundAction == kMystSoundActionStop) { + debug(2, "Stopping sound"); + _sound->stopBackgroundMyst(); + } else if (soundAction > 0) { + debug(2, "Playing new sound %d", soundAction); + _sound->replaceBackgroundMyst(soundAction, soundActionVolume); + } else { + error("Unknown sound action %d", soundAction); + } +} + } // End of namespace Mohawk diff --git a/engines/mohawk/myst.h b/engines/mohawk/myst.h index 4d86642652..0b249e5499 100644 --- a/engines/mohawk/myst.h +++ b/engines/mohawk/myst.h @@ -28,10 +28,9 @@ #include "mohawk/resource_cache.h" #include "mohawk/myst_scripts.h" +#include "common/events.h" #include "common/random.h" -#include "gui/saveload.h" - namespace Mohawk { class MohawkEngine_Myst; @@ -41,9 +40,9 @@ class MystScriptParser; class MystConsole; class MystGameState; class MystOptionsDialog; -class MystResource; -class MystResourceType8; -class MystResourceType13; +class MystArea; +class MystAreaImageSwitch; +class MystAreaHover; // Engine Debug Flags enum { @@ -96,8 +95,19 @@ const uint16 kMasterpieceOnly = 0xFFFF; struct MystCondition { uint16 var; - uint16 numStates; - uint16 *values; + Common::Array<uint16> values; +}; + +struct MystSoundBlock { + struct SoundItem { + int16 action; + uint16 volume; + }; + + int16 sound; + uint16 soundVolume; + uint16 soundVar; + Common::Array<SoundItem> soundList; }; // View Sound Action Type @@ -118,29 +128,29 @@ struct MystView { uint16 flags; // Image Data - uint16 conditionalImageCount; - MystCondition *conditionalImages; + Common::Array<MystCondition> conditionalImages; uint16 mainImage; // Sound Data - int16 sound; - uint16 soundVolume; - uint16 soundVar; - uint16 soundCount; - int16 *soundList; - uint16 *soundListVolume; + MystSoundBlock soundBlock; // Script Resources - uint16 scriptResCount; + enum ScriptResourceType { + kResourceImage = 1, + kResourceSound = 2, + kResourceSwitch = 3, + kResourceImageNoCache = 4, + kResourceSoundNoCache = 5 + }; + struct ScriptResource { - uint16 type; - uint16 id; // Not used by type 3 - // TODO: Type 3 has more. Maybe use a union? - uint16 var; // Used by type 3 only - uint16 count; // Used by type 3 only - uint16 u0; // Used by type 3 only - int16 *resource_list; // Used by type 3 only - } *scriptResources; + ScriptResourceType type; + uint16 id; + uint16 switchVar; + ScriptResourceType switchResourceType; + Common::Array<int16> switchResourceIds; + }; + Common::Array<ScriptResource> scriptResources; // Resource ID's uint16 rlst; @@ -158,18 +168,17 @@ struct MystCursorHint { class MohawkEngine_Myst : public MohawkEngine { protected: - Common::Error run(); + Common::Error run() override; public: MohawkEngine_Myst(OSystem *syst, const MohawkGameDescription *gamedesc); virtual ~MohawkEngine_Myst(); - Common::SeekableReadStream *getResource(uint32 tag, uint16 id); + Common::SeekableReadStream *getResource(uint32 tag, uint16 id) override; + Common::Array<uint16> getResourceIDList(uint32 type) const; Common::String wrapMovieFilename(const Common::String &movieName, uint16 stack); - void reloadSaveList(); - void changeToStack(uint16 stack, uint16 card, uint16 linkSrcSound, uint16 linkDstSound); void changeToCard(uint16 card, TransitionType transition); uint16 getCurCard() { return _curCard; } @@ -177,46 +186,50 @@ public: void setMainCursor(uint16 cursor); uint16 getMainCursor() { return _mainCursor; } void checkCursorHints(); - MystResource *updateCurrentResource(); + MystArea *updateCurrentResource(); bool skippableWait(uint32 duration); - bool _tweaksEnabled; + MystSoundBlock readSoundBlock(Common::ReadStream *stream) const; + void applySoundBlock(const MystSoundBlock &block); + bool _needsUpdate; bool _needsPageDrop; bool _needsShowMap; bool _needsShowDemoMenu; + bool _needsShowCredits; + + bool _showResourceRects; - MystView _view; MystGraphics *_gfx; MystGameState *_gameState; MystScriptParser *_scriptParser; - Common::Array<MystResource *> _resources; - MystResource *_dragResource; + Common::Array<MystArea *> _resources; Common::RandomSource *_rnd; - bool _showResourceRects; - MystResource *loadResource(Common::SeekableReadStream *rlstStream, MystResource *parent); + MystArea *loadResource(Common::SeekableReadStream *rlstStream, MystArea *parent); void setResourceEnabled(uint16 resourceId, bool enable); void redrawArea(uint16 var, bool update = true); - void redrawResource(MystResourceType8 *resource, bool update = true); + void redrawResource(MystAreaImageSwitch *resource, bool update = true); void drawResourceImages(); void drawCardBackground(); uint16 getCardBackgroundId(); + template<class T> + T *getViewResource(uint index); + void setCacheState(bool state) { _cache.enabled = state; } bool getCacheState() { return _cache.enabled; } - GUI::Debugger *getDebugger() { return _console; } + GUI::Debugger *getDebugger() override { return _console; } - bool canLoadGameStateCurrently(); - bool canSaveGameStateCurrently(); - Common::Error loadGameState(int slot); - Common::Error saveGameState(int slot, const Common::String &desc); - bool hasFeature(EngineFeature f) const; + bool canLoadGameStateCurrently() override; + bool canSaveGameStateCurrently() override; + Common::Error loadGameState(int slot) override; + Common::Error saveGameState(int slot, const Common::String &desc) override; + bool hasFeature(EngineFeature f) const override; private: MystConsole *_console; - GUI::SaveLoadChooser *_loadDialog; MystOptionsDialog *_optionsDialog; MystScriptParser *_prevStack; ResourceCache _cache; @@ -224,9 +237,18 @@ private: uint16 _curStack; uint16 _curCard; + MystView _view; bool _runExitScript; + /** + * Saving / Loading is only allowed from the main event loop + */ + bool _canSafelySaveLoad; + bool hasGameSaveSupport() const; + + bool pollEvent(Common::Event &event); + void dropPage(); void loadCard(); @@ -240,15 +262,25 @@ private: void drawResourceRects(); void checkCurrentResource(); int16 _curResource; - MystResourceType13 *_hoverResource; + MystAreaHover *_hoverResource; - uint16 _cursorHintCount; - MystCursorHint *_cursorHints; + Common::Array<MystCursorHint> _cursorHints; void loadCursorHints(); uint16 _currentCursor; uint16 _mainCursor; // Also defines the current page being held (white, blue, red, or none) }; +template<class T> +T *MohawkEngine_Myst::getViewResource(uint index) { + T *resource = dynamic_cast<T *>(_resources[index]); + + if (!resource) { + error("View resource '%d' has unexpected type", index); + } + + return resource; +} + } // End of namespace Mohawk #endif diff --git a/engines/mohawk/myst_areas.cpp b/engines/mohawk/myst_areas.cpp index 7a9596d8e0..4b9cf546fa 100644 --- a/engines/mohawk/myst_areas.cpp +++ b/engines/mohawk/myst_areas.cpp @@ -32,11 +32,11 @@ namespace Mohawk { -MystResource::MystResource(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent) { +MystArea::MystArea(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystArea *parent) { _vm = vm; _parent = parent; - if (parent == NULL) { + if (parent == nullptr) { _flags = rlstStream->readUint16LE(); _rect.left = rlstStream->readSint16LE(); _rect.top = rlstStream->readSint16LE(); @@ -66,10 +66,10 @@ MystResource::MystResource(MohawkEngine_Myst *vm, Common::SeekableReadStream *rl debugC(kDebugResource, "\tdest: %d", _dest); } -MystResource::~MystResource() { +MystArea::~MystArea() { } -void MystResource::handleMouseUp() { +void MystArea::handleMouseUp() { if (_dest == 0) { warning("Movement type resource with null destination at position (%d, %d), (%d, %d)", _rect.left, _rect.top, _rect.right, _rect.bottom); return; @@ -78,13 +78,13 @@ void MystResource::handleMouseUp() { uint16 opcode; switch (type) { - case kMystForwardArea: + case kMystAreaForward: opcode = 6; break; - case kMystLeftArea: + case kMystAreaLeft: opcode = 8; break; - case kMystRightArea: + case kMystAreaRight: opcode = 7; break; default: @@ -96,27 +96,27 @@ void MystResource::handleMouseUp() { _vm->_scriptParser->runOpcode(opcode, 0); } -bool MystResource::canBecomeActive() { +bool MystArea::canBecomeActive() { return !unreachableZipDest() && (isEnabled() || (_flags & kMystUnknownFlag)); } -bool MystResource::unreachableZipDest() { +bool MystArea::unreachableZipDest() { return (_flags & kMystZipModeEnableFlag) && !_vm->_gameState->isReachableZipDest(_vm->getCurStack() , _dest); } -bool MystResource::isEnabled() { +bool MystArea::isEnabled() { return _flags & kMystHotspotEnableFlag; } -void MystResource::setEnabled(bool enabled) { +void MystArea::setEnabled(bool enabled) { if (enabled) _flags |= kMystHotspotEnableFlag; else _flags &= ~kMystHotspotEnableFlag; } -const Common::String MystResource::describe() { +const Common::String MystArea::describe() { Common::String desc = Common::String::format("type: %2d rect: (%3d %3d %3d %3d)", type, _rect.left, _rect.top, _rect.width(), _rect.height()); @@ -126,7 +126,7 @@ const Common::String MystResource::describe() { return desc; } -void MystResource::drawBoundingRect() { +void MystArea::drawBoundingRect() { if (_rect.isValidRect()) { if (!canBecomeActive()) _vm->_gfx->drawRect(_rect, kRectUnreachable); @@ -137,18 +137,19 @@ void MystResource::drawBoundingRect() { } } -MystResourceType5::MystResourceType5(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent) : MystResource(vm, rlstStream, parent) { +MystAreaAction::MystAreaAction(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystArea *parent) : + MystArea(vm, rlstStream, parent) { debugC(kDebugResource, "\tResource Type 5 Script:"); _script = vm->_scriptParser->readScript(rlstStream, kMystScriptNormal); } -void MystResourceType5::handleMouseUp() { +void MystAreaAction::handleMouseUp() { _vm->_scriptParser->runScript(_script, this); } -const Common::String MystResourceType5::describe() { - Common::String desc = MystResource::describe(); +const Common::String MystAreaAction::describe() { + Common::String desc = MystArea::describe(); if (_script->size() != 0) { desc += " ops:"; @@ -161,7 +162,7 @@ const Common::String MystResourceType5::describe() { } // In Myst/Making of Myst, the paths are hardcoded ala Windows style without extension. Convert them. -Common::String MystResourceType6::convertMystVideoName(Common::String name) { +Common::String MystAreaVideo::convertMystVideoName(Common::String name) { Common::String temp; for (uint32 i = 1; i < name.size(); i++) { @@ -174,7 +175,8 @@ Common::String MystResourceType6::convertMystVideoName(Common::String name) { return temp + ".mov"; } -MystResourceType6::MystResourceType6(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent) : MystResourceType5(vm, rlstStream, parent) { +MystAreaVideo::MystAreaVideo(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystArea *parent) : + MystAreaAction(vm, rlstStream, parent) { char c = 0; do { @@ -197,16 +199,7 @@ MystResourceType6::MystResourceType6(MohawkEngine_Myst *vm, Common::SeekableRead _direction = rlstStream->readSint16LE(); _playBlocking = rlstStream->readUint16LE(); _loop = rlstStream->readUint16LE(); - _u3 = rlstStream->readUint16LE(); - - // TODO: Out of bound values should clip the movie - if (_left < 0) - _left = 0; - if (_top < 0) - _top = 0; - - if (_u3 != 0) - warning("Type 6 _u3 != 0"); + _playRate = rlstStream->readUint16LE(); debugC(kDebugResource, "\tvideoFile: \"%s\"", _videoFile.c_str()); debugC(kDebugResource, "\tleft: %d", _left); @@ -215,15 +208,15 @@ MystResourceType6::MystResourceType6(MohawkEngine_Myst *vm, Common::SeekableRead debugC(kDebugResource, "\tdirection: %d", _direction); debugC(kDebugResource, "\tplayBlocking: %d", _playBlocking); debugC(kDebugResource, "\tplayOnCardChange: %d", _playOnCardChange); - debugC(kDebugResource, "\tu3: %d", _u3); + debugC(kDebugResource, "\tplayRate: %d", _playRate); } -VideoHandle MystResourceType6::playMovie() { +VideoHandle MystAreaVideo::playMovie() { // Check if the video is already running VideoHandle handle = _vm->_video->findVideoHandle(_videoFile); // If the video is not running, play it - if (!handle || handle->endOfVideo()) { + if (!handle) { handle = _vm->_video->playMovie(_videoFile); if (!handle) error("Failed to open '%s'", _videoFile.c_str()); @@ -231,13 +224,23 @@ VideoHandle MystResourceType6::playMovie() { handle->moveTo(_left, _top); handle->setLooping(_loop != 0); + Common::Rational rate; + if (_playRate != 0) { + rate = Common::Rational(_playRate, 100); + } else { + rate = 1; + } + if (_direction == -1) { + rate = -rate; handle->seek(handle->getDuration()); - handle->setRate(-1); } + + handle->setRate(rate); } else { // Resume the video handle->pause(false); + handle->start(); } if (_playBlocking) { @@ -248,186 +251,156 @@ VideoHandle MystResourceType6::playMovie() { return handle; } -void MystResourceType6::handleCardChange() { +VideoHandle MystAreaVideo::getMovieHandle() { + // If the video is already in the manager, just return the handle + VideoHandle handle = _vm->_video->findVideoHandle(_videoFile); + if (!handle) { + // If the video has not been loaded yet, do it but don't start playing it + handle = _vm->_video->playMovie(_videoFile); + if (!handle) + error("Failed to open '%s'", _videoFile.c_str()); + handle->stop(); + } + + return handle; +} + +void MystAreaVideo::handleCardChange() { if (_playOnCardChange) playMovie(); } -bool MystResourceType6::isPlaying() { +bool MystAreaVideo::isPlaying() { VideoHandle handle = _vm->_video->findVideoHandle(_videoFile); return handle && !handle->endOfVideo(); } -void MystResourceType6::pauseMovie(bool pause) { +void MystAreaVideo::pauseMovie(bool pause) { VideoHandle handle = _vm->_video->findVideoHandle(_videoFile); if (handle && !handle->endOfVideo()) handle->pause(pause); } -MystResourceType7::MystResourceType7(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent) : MystResource(vm, rlstStream, parent) { - _var7 = rlstStream->readUint16LE(); - _numSubResources = rlstStream->readUint16LE(); - debugC(kDebugResource, "\tvar7: %d", _var7); - debugC(kDebugResource, "\tnumSubResources: %d", _numSubResources); +MystAreaActionSwitch::MystAreaActionSwitch(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystArea *parent) : + MystArea(vm, rlstStream, parent) { + _actionSwitchVar = rlstStream->readUint16LE(); + uint16 numSubResources = rlstStream->readUint16LE(); + debugC(kDebugResource, "\tactionSwitchVar: %d", _actionSwitchVar); + debugC(kDebugResource, "\tnumSubResources: %d", numSubResources); - for (uint16 i = 0; i < _numSubResources; i++) + for (uint16 i = 0; i < numSubResources; i++) _subResources.push_back(vm->loadResource(rlstStream, this)); } -MystResourceType7::~MystResourceType7() { +MystAreaActionSwitch::~MystAreaActionSwitch() { for (uint32 i = 0; i < _subResources.size(); i++) delete _subResources[i]; _subResources.clear(); } -// TODO: All these functions to switch subresource are very similar. -// Find way to share code (function pointer pass?) -void MystResourceType7::drawDataToScreen() { - if (_var7 == 0xFFFF) { - if (_numSubResources == 1) - _subResources[0]->drawDataToScreen(); - else if (_numSubResources != 0) - warning("Type 7 Resource with _numSubResources of %d, but no control variable", _numSubResources); +void MystAreaActionSwitch::doSwitch(AreaHandler handler) { + if (_actionSwitchVar == 0xFFFF) { + if (_subResources.size() == 1) + (_subResources[0]->*handler)(); + else if (_subResources.size() != 0) + warning("Action switch resource with _numSubResources of %d, but no control variable", _subResources.size()); } else { - uint16 varValue = _vm->_scriptParser->getVar(_var7); + uint16 varValue = _vm->_scriptParser->getVar(_actionSwitchVar); - if (_numSubResources == 1 && varValue != 0) - _subResources[0]->drawDataToScreen(); - else if (_numSubResources != 0) { - if (varValue < _numSubResources) - _subResources[varValue]->drawDataToScreen(); + if (_subResources.size() == 1 && varValue != 0) + (_subResources[0]->*handler)(); + else if (_subResources.size() != 0) { + if (varValue < _subResources.size()) + (_subResources[varValue]->*handler)(); else - warning("Type 7 Resource Var %d: %d exceeds number of sub resources %d", _var7, varValue, _numSubResources); + warning("Action switch resource Var %d: %d exceeds number of sub resources %d", _actionSwitchVar, varValue, _subResources.size()); } } } -void MystResourceType7::handleCardChange() { - if (_var7 == 0xFFFF) { - if (_numSubResources == 1) - _subResources[0]->handleCardChange(); - else if (_numSubResources != 0) - warning("Type 7 Resource with _numSubResources of %d, but no control variable", _numSubResources); - } else { - uint16 varValue = _vm->_scriptParser->getVar(_var7); - - if (_numSubResources == 1 && varValue != 0) - _subResources[0]->handleCardChange(); - else if (_numSubResources != 0) { - if (varValue < _numSubResources) - _subResources[varValue]->handleCardChange(); - else - warning("Type 7 Resource Var %d: %d exceeds number of sub resources %d", _var7, varValue, _numSubResources); - } - } +void MystAreaActionSwitch::drawDataToScreen() { + doSwitch(&MystArea::drawDataToScreen); } -void MystResourceType7::handleMouseUp() { - if (_var7 == 0xFFFF) { - if (_numSubResources == 1) - _subResources[0]->handleMouseUp(); - else if (_numSubResources != 0) - warning("Type 7 Resource with _numSubResources of %d, but no control variable", _numSubResources); - } else { - uint16 varValue = _vm->_scriptParser->getVar(_var7); - - if (_numSubResources == 1 && varValue != 0) - _subResources[0]->handleMouseUp(); - else if (_numSubResources != 0) { - if (varValue < _numSubResources) - _subResources[varValue]->handleMouseUp(); - else - warning("Type 7 Resource Var %d: %d exceeds number of sub resources %d", _var7, varValue, _numSubResources); - } - } +void MystAreaActionSwitch::handleCardChange() { + doSwitch(&MystArea::handleCardChange); } -void MystResourceType7::handleMouseDown() { - if (_var7 == 0xFFFF) { - if (_numSubResources == 1) - _subResources[0]->handleMouseDown(); - else if (_numSubResources != 0) - warning("Type 7 Resource with _numSubResources of %d, but no control variable", _numSubResources); - } else { - uint16 varValue = _vm->_scriptParser->getVar(_var7); - - if (_numSubResources == 1 && varValue != 0) - _subResources[0]->handleMouseDown(); - else if (_numSubResources != 0) { - if (varValue < _numSubResources) - _subResources[varValue]->handleMouseDown(); - else - warning("Type 7 Resource Var %d: %d exceeds number of sub resources %d", _var7, varValue, _numSubResources); - } - } +void MystAreaActionSwitch::handleMouseUp() { + doSwitch(&MystArea::handleMouseUp); } -MystResourceType8::MystResourceType8(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent) : MystResourceType7(vm, rlstStream, parent) { - _var8 = rlstStream->readUint16LE(); - _numSubImages = rlstStream->readUint16LE(); - debugC(kDebugResource, "\tvar8: %d", _var8); - debugC(kDebugResource, "\tnumSubImages: %d", _numSubImages); +void MystAreaActionSwitch::handleMouseDown() { + doSwitch(&MystArea::handleMouseDown); +} - _subImages = new MystResourceType8::SubImage[_numSubImages]; +MystAreaImageSwitch::MystAreaImageSwitch(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystArea *parent) : + MystAreaActionSwitch(vm, rlstStream, parent) { + _imageSwitchVar = rlstStream->readUint16LE(); + uint16 numSubImages = rlstStream->readUint16LE(); + debugC(kDebugResource, "\tvar8: %d", _imageSwitchVar); + debugC(kDebugResource, "\tnumSubImages: %d", numSubImages); - for (uint16 i = 0; i < _numSubImages; i++) { + for (uint16 i = 0; i < numSubImages; i++) { debugC(kDebugResource, "\tSubimage %d:", i); - _subImages[i].wdib = rlstStream->readUint16LE(); - _subImages[i].rect.left = rlstStream->readSint16LE(); + SubImage subImage; + subImage.wdib = rlstStream->readUint16LE(); + subImage.rect.left = rlstStream->readSint16LE(); - if (_subImages[i].rect.left != -1) { - _subImages[i].rect.top = rlstStream->readSint16LE(); - _subImages[i].rect.right = rlstStream->readSint16LE(); - _subImages[i].rect.bottom = rlstStream->readSint16LE(); + if (subImage.rect.left != -1) { + subImage.rect.top = rlstStream->readSint16LE(); + subImage.rect.right = rlstStream->readSint16LE(); + subImage.rect.bottom = rlstStream->readSint16LE(); } else { // Use the hotspot rect as the source rect since the subimage is fullscreen // Convert to bitmap coordinates (upside down) - _subImages[i].rect.left = _rect.left; - _subImages[i].rect.top = 333 - _rect.bottom; - _subImages[i].rect.right = _rect.right; - _subImages[i].rect.bottom = 333 - _rect.top; + subImage.rect.left = _rect.left; + subImage.rect.top = 333 - _rect.bottom; + subImage.rect.right = _rect.right; + subImage.rect.bottom = 333 - _rect.top; } - debugC(kDebugResource, "\twdib: %d", _subImages[i].wdib); - debugC(kDebugResource, "\tleft: %d", _subImages[i].rect.left); - debugC(kDebugResource, "\ttop: %d", _subImages[i].rect.top); - debugC(kDebugResource, "\tright: %d", _subImages[i].rect.right); - debugC(kDebugResource, "\tbottom: %d", _subImages[i].rect.bottom); + debugC(kDebugResource, "\twdib: %d", subImage.wdib); + debugC(kDebugResource, "\tleft: %d", subImage.rect.left); + debugC(kDebugResource, "\ttop: %d", subImage.rect.top); + debugC(kDebugResource, "\tright: %d", subImage.rect.right); + debugC(kDebugResource, "\tbottom: %d", subImage.rect.bottom); + + _subImages.push_back(subImage); } } -MystResourceType8::~MystResourceType8() { - delete[] _subImages; +MystAreaImageSwitch::~MystAreaImageSwitch() { } -void MystResourceType8::drawDataToScreen() { - // Need to call overidden Type 7 function to ensure +void MystAreaImageSwitch::drawDataToScreen() { + // Need to call overridden function to ensure // switch section is processed correctly. - MystResourceType7::drawDataToScreen(); + MystAreaActionSwitch::drawDataToScreen(); bool drawSubImage = false; int16 subImageId = 0; - if (_var8 == 0xFFFF) { - if (_numSubImages == 1) { + if (_imageSwitchVar == 0xFFFF) { + if (_subImages.size() == 1) { subImageId = 0; drawSubImage = true; - } else if (_numSubImages != 0) - warning("Type 8 Resource with _numSubImages of %d, but no control variable", _numSubImages); + } else if (_subImages.size() != 0) + warning("Image Switch resource with _numSubImages of %d, but no control variable", _subImages.size()); } else { - uint16 varValue = _vm->_scriptParser->getVar(_var8); + uint16 varValue = _vm->_scriptParser->getVar(_imageSwitchVar); - if (_numSubImages == 1 && varValue != 0) { + if (_subImages.size() == 1 && varValue != 0) { subImageId = 0; drawSubImage = true; - } else if (_numSubImages != 0) { - if (varValue < _numSubImages) { + } else if (_subImages.size() != 0) { + if (varValue < _subImages.size()) { subImageId = varValue; drawSubImage = true; } else - warning("Type 8 Image Var %d: %d exceeds number of subImages %d", _var8, varValue, _numSubImages); + warning("Image Switch Var %d: %d exceeds number of subImages %d", _imageSwitchVar, varValue, _subImages.size()); } } @@ -442,20 +415,21 @@ void MystResourceType8::drawDataToScreen() { } } -void MystResourceType8::drawConditionalDataToScreen(uint16 state, bool update) { +//TODO: Merge with the method above? +void MystAreaImageSwitch::drawConditionalDataToScreen(uint16 state, bool update) { bool drawSubImage = false; int16 subImageId = 0; - if (_numSubImages == 1 && state != 0) { + if (_subImages.size() == 1 && state != 0) { subImageId = 0; drawSubImage = true; - } else if (_numSubImages != 0) { - if (state < _numSubImages) { + } else if (_subImages.size() != 0) { + if (state < _subImages.size()) { subImageId = state; drawSubImage = true; } else - warning("Type 8 Image Var %d: %d exceeds number of subImages %d", _var8, state, _numSubImages); + warning("Image Switch Var %d: %d exceeds number of subImages %d", _imageSwitchVar, state, _subImages.size()); } @@ -476,18 +450,26 @@ void MystResourceType8::drawConditionalDataToScreen(uint16 state, bool update) { } } -uint16 MystResourceType8::getType8Var() { - return _var8; +uint16 MystAreaImageSwitch::getImageSwitchVar() { + return _imageSwitchVar; +} + +MystAreaImageSwitch::SubImage MystAreaImageSwitch::getSubImage(uint index) const { + return _subImages[index]; } -const Common::String MystResourceType8::describe() { +void MystAreaImageSwitch::setSubImageRect(uint index, const Common::Rect &rect) { + _subImages[index].rect = rect; +} + +const Common::String MystAreaImageSwitch::describe() { Common::String desc = Common::String::format("%s var: %2d", - MystResourceType7::describe().c_str(), _var8); + MystAreaActionSwitch::describe().c_str(), _imageSwitchVar); - if (_numSubImages > 0) { + if (_subImages.size() > 0) { desc += " subImgs:"; - for (uint i = 0; i < _numSubImages; i++) + for (uint i = 0; i < _subImages.size(); i++) desc += Common::String::format(" %d", (int16)_subImages[i].wdib); } @@ -496,7 +478,8 @@ const Common::String MystResourceType8::describe() { // No MystResourceType9! -MystResourceType10::MystResourceType10(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent) : MystResourceType11(vm, rlstStream, parent) { +MystAreaSlider::MystAreaSlider(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystArea *parent) : + MystAreaDrag(vm, rlstStream, parent) { _dragSound = rlstStream->readUint16LE(); debugC(kDebugResource, "\tdrag sound : %d", _dragSound); @@ -505,23 +488,23 @@ MystResourceType10::MystResourceType10(MohawkEngine_Myst *vm, Common::SeekableRe _sliderHeight = _rect.bottom - _rect.top; } -MystResourceType10::~MystResourceType10() { +MystAreaSlider::~MystAreaSlider() { } -void MystResourceType10::setStep(uint16 step) { +void MystAreaSlider::setStep(uint16 step) { _rect.top = _minV + _stepV * step - _sliderHeight / 2; _rect.bottom = _rect.top + _sliderHeight; _subImages[0].rect.top = 333 - _rect.bottom - 1; _subImages[0].rect.bottom = 333 - _rect.top - 1; } -void MystResourceType10::setPosition(uint16 pos) { +void MystAreaSlider::setPosition(uint16 pos) { Common::Point mouse; mouse.y = pos; updatePosition(mouse); } -Common::Rect MystResourceType10::boundingBox() { +Common::Rect MystAreaSlider::boundingBox() { Common::Rect bb; bb.top = _rect.top; @@ -544,7 +527,7 @@ Common::Rect MystResourceType10::boundingBox() { return bb; } -void MystResourceType10::restoreBackground() { +void MystAreaSlider::restoreBackground() { // Restore background Common::Rect src = boundingBox(); Common::Rect dest = boundingBox(); @@ -553,14 +536,11 @@ void MystResourceType10::restoreBackground() { _vm->_gfx->copyImageSectionToScreen(_vm->getCardBackgroundId(), src, dest); } -void MystResourceType10::handleMouseDown() { - // Tell the engine we are dragging a resource - _vm->_dragResource = this; - +void MystAreaSlider::handleMouseDown() { const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos(); updatePosition(mouse); - MystResourceType11::handleMouseDown(); + MystAreaDrag::handleMouseDown(); // Restore background restoreBackground(); @@ -569,7 +549,7 @@ void MystResourceType10::handleMouseDown() { drawConditionalDataToScreen(2); } -void MystResourceType10::handleMouseUp() { +void MystAreaSlider::handleMouseUp() { const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos(); updatePosition(mouse); @@ -593,19 +573,16 @@ void MystResourceType10::handleMouseUp() { value = _pos.x; } - _vm->_scriptParser->setVarValue(_var8, value); - - MystResourceType11::handleMouseUp(); + _vm->_scriptParser->setVarValue(_imageSwitchVar, value); - // No longer in drag mode - _vm->_dragResource = 0; + MystAreaDrag::handleMouseUp(); } -void MystResourceType10::handleMouseDrag() { +void MystAreaSlider::handleMouseDrag() { const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos(); updatePosition(mouse); - MystResourceType11::handleMouseDrag(); + MystAreaDrag::handleMouseDrag(); // Restore background restoreBackground(); @@ -614,7 +591,7 @@ void MystResourceType10::handleMouseDrag() { drawConditionalDataToScreen(2); } -void MystResourceType10::updatePosition(const Common::Point &mouse) { +void MystAreaSlider::updatePosition(const Common::Point &mouse) { bool positionChanged = false; Common::Point mouseClipped; @@ -667,7 +644,8 @@ void MystResourceType10::updatePosition(const Common::Point &mouse) { _vm->_sound->replaceSoundMyst(_dragSound); } -MystResourceType11::MystResourceType11(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent) : MystResourceType8(vm, rlstStream, parent) { +MystAreaDrag::MystAreaDrag(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystArea *parent) : + MystAreaImageSwitch(vm, rlstStream, parent) { _flagHV = rlstStream->readUint16LE(); _minH = rlstStream->readUint16LE(); _maxH = rlstStream->readUint16LE(); @@ -694,16 +672,15 @@ MystResourceType11::MystResourceType11(MohawkEngine_Myst *vm, Common::SeekableRe debugCN(kDebugResource, "Type 11 _mouseDragOpcode: %d\n", _mouseDragOpcode); debugCN(kDebugResource, "Type 11 _mouseUpOpcode: %d\n", _mouseUpOpcode); - for (byte i = 0; i < 3; i++) { + for (byte i = 0; i < ARRAYSIZE(_lists); i++) { debugC(kDebugResource, "\tList %d:", i); - _lists[i].listCount = rlstStream->readUint16LE(); - debugC(kDebugResource, "\t%d values", _lists[i].listCount); + uint16 listCount = rlstStream->readUint16LE(); + debugC(kDebugResource, "\t%d values", listCount); - _lists[i].list = new uint16[_lists[i].listCount]; - for (uint16 j = 0; j < _lists[i].listCount; j++) { - _lists[i].list[j] = rlstStream->readUint16LE(); - debugC(kDebugResource, "\tValue %d: %d", j, _lists[i].list[j]); + for (uint16 j = 0; j < listCount; j++) { + _lists[i].push_back(rlstStream->readUint16LE()); + debugC(kDebugResource, "\tValue %d: %d", j, _lists[i][j]); } } @@ -717,44 +694,42 @@ MystResourceType11::MystResourceType11(MohawkEngine_Myst *vm, Common::SeekableRe _stepV = (_maxV - _minV) / (_stepsV - 1); } -MystResourceType11::~MystResourceType11() { - for (byte i = 0; i < 3; i++) - delete[] _lists[i].list; +MystAreaDrag::~MystAreaDrag() { } -void MystResourceType11::handleMouseDown() { +void MystAreaDrag::handleMouseDown() { const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos(); setPositionClipping(mouse, _pos); _vm->_scriptParser->setInvokingResource(this); - _vm->_scriptParser->runOpcode(_mouseDownOpcode, _var8); + _vm->_scriptParser->runOpcode(_mouseDownOpcode, _imageSwitchVar); } -void MystResourceType11::handleMouseUp() { +void MystAreaDrag::handleMouseUp() { const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos(); setPositionClipping(mouse, _pos); _vm->_scriptParser->setInvokingResource(this); - _vm->_scriptParser->runOpcode(_mouseUpOpcode, _var8); + _vm->_scriptParser->runOpcode(_mouseUpOpcode, _imageSwitchVar); } -void MystResourceType11::handleMouseDrag() { +void MystAreaDrag::handleMouseDrag() { const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos(); setPositionClipping(mouse, _pos); _vm->_scriptParser->setInvokingResource(this); - _vm->_scriptParser->runOpcode(_mouseDragOpcode, _var8); + _vm->_scriptParser->runOpcode(_mouseDragOpcode, _imageSwitchVar); } -const Common::String MystResourceType11::describe() { +const Common::String MystAreaDrag::describe() { return Common::String::format("%s down: %s drag: %s up: %s", - MystResourceType8::describe().c_str(), + MystAreaImageSwitch::describe().c_str(), _vm->_scriptParser->getOpcodeDesc(_mouseDownOpcode).c_str(), _vm->_scriptParser->getOpcodeDesc(_mouseDragOpcode).c_str(), _vm->_scriptParser->getOpcodeDesc(_mouseUpOpcode).c_str()); } -void MystResourceType11::setPositionClipping(const Common::Point &mouse, Common::Point &dest) { +void MystAreaDrag::setPositionClipping(const Common::Point &mouse, Common::Point &dest) { if (_flagHV & 2) dest.y = CLIP<uint16>(mouse.y, _minV, _maxV); @@ -762,19 +737,20 @@ void MystResourceType11::setPositionClipping(const Common::Point &mouse, Common: dest.x = CLIP<uint16>(mouse.x, _minH, _maxH); } -uint16 MystResourceType11::getList1(uint16 index) { - return (index < _lists[0].listCount) ? _lists[0].list[index] : 0; +uint16 MystAreaDrag::getList1(uint16 index) { + return (index < _lists[0].size()) ? _lists[0][index] : 0; } -uint16 MystResourceType11::getList2(uint16 index) { - return (index < _lists[1].listCount) ? _lists[1].list[index] : 0; +uint16 MystAreaDrag::getList2(uint16 index) { + return (index < _lists[1].size()) ? _lists[1][index] : 0; } -uint16 MystResourceType11::getList3(uint16 index) { - return (index < _lists[2].listCount) ? _lists[2].list[index] : 0; +uint16 MystAreaDrag::getList3(uint16 index) { + return (index < _lists[2].size()) ? _lists[2][index] : 0; } -MystResourceType12::MystResourceType12(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent) : MystResourceType11(vm, rlstStream, parent) { +MystVideoInfo::MystVideoInfo(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystArea *parent) : + MystAreaDrag(vm, rlstStream, parent) { _numFrames = rlstStream->readUint16LE(); _firstFrame = rlstStream->readUint16LE(); uint16 frameWidth = rlstStream->readUint16LE(); @@ -795,16 +771,16 @@ MystResourceType12::MystResourceType12(MohawkEngine_Myst *vm, Common::SeekableRe debugC(kDebugResource, "\t_frameRect.bottom: %d", _frameRect.bottom); } -MystResourceType12::~MystResourceType12() { +MystVideoInfo::~MystVideoInfo() { } -void MystResourceType12::drawFrame(uint16 frame) { +void MystVideoInfo::drawFrame(uint16 frame) { _currentFrame = _firstFrame + frame; _vm->_gfx->copyImageToScreen(_currentFrame, _frameRect); _vm->_system->updateScreen(); } -bool MystResourceType12::pullLeverV() { +bool MystVideoInfo::pullLeverV() { const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos(); // Make the handle follow the mouse @@ -820,7 +796,7 @@ bool MystResourceType12::pullLeverV() { return step == maxStep; } -void MystResourceType12::releaseLeverV() { +void MystVideoInfo::releaseLeverV() { const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos(); // Get current lever frame @@ -836,7 +812,8 @@ void MystResourceType12::releaseLeverV() { } } -MystResourceType13::MystResourceType13(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent) : MystResource(vm, rlstStream, parent) { +MystAreaHover::MystAreaHover(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystArea *parent) : + MystArea(vm, rlstStream, parent) { _enterOpcode = rlstStream->readUint16LE(); _leaveOpcode = rlstStream->readUint16LE(); @@ -844,27 +821,27 @@ MystResourceType13::MystResourceType13(MohawkEngine_Myst *vm, Common::SeekableRe debugC(kDebugResource, "\t_leaveOpcode: %d", _leaveOpcode); } -void MystResourceType13::handleMouseEnter() { +void MystAreaHover::handleMouseEnter() { // Pass along the enter opcode to the script parser // The variable to use is stored in the dest field _vm->_scriptParser->runOpcode(_enterOpcode, _dest); } -void MystResourceType13::handleMouseLeave() { +void MystAreaHover::handleMouseLeave() { // Pass along the leave opcode (with no parameters) to the script parser // The variable to use is stored in the dest field _vm->_scriptParser->runOpcode(_leaveOpcode, _dest); } -void MystResourceType13::handleMouseUp() { +void MystAreaHover::handleMouseUp() { // Type 13 Resources do nothing on Mouse Clicks. // This is required to override the inherited default - // i.e. MystResource::handleMouseUp + // i.e. MystArea::handleMouseUp } -const Common::String MystResourceType13::describe() { +const Common::String MystAreaHover::describe() { return Common::String::format("%s enter: %s leave: %s", - MystResource::describe().c_str(), + MystArea::describe().c_str(), _vm->_scriptParser->getOpcodeDesc(_enterOpcode).c_str(), _vm->_scriptParser->getOpcodeDesc(_leaveOpcode).c_str()); } diff --git a/engines/mohawk/myst_areas.h b/engines/mohawk/myst_areas.h index 97ec882497..b19a2df9e2 100644 --- a/engines/mohawk/myst_areas.h +++ b/engines/mohawk/myst_areas.h @@ -32,19 +32,19 @@ namespace Mohawk { // Myst Resource Types enum ResourceType { - kMystForwardArea = 0, - kMystLeftArea = 1, - kMystRightArea = 2, - kMystDownArea = 3, - kMystUpArea = 4, - kMystAction = 5, - kMystVideo = 6, - kMystSwitch = 7, - kMystConditionalImage = 8, - kMystSlider = 10, - kMystDragArea = 11, + kMystAreaForward = 0, + kMystAreaLeft = 1, + kMystAreaRight = 2, + kMystAreaDown = 3, + kMystAreaUp = 4, + kMystAreaAction = 5, + kMystAreaVideo = 6, + kMystAreaActionSwitch = 7, + kMystAreaImageSwitch = 8, + kMystAreaSlider = 10, + kMystAreaDrag = 11, kMystVideoInfo = 12, - kMystHoverArea = 13 + kMystAreaHover = 13 }; // Myst Resource Flags @@ -56,16 +56,14 @@ enum { kMystZipModeEnableFlag = (1 << 3) }; -class MystResource { +class MystArea { public: - MystResource(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent); - virtual ~MystResource(); + MystArea(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystArea *parent); + virtual ~MystArea(); + virtual const Common::String describe(); void drawBoundingRect(); - MystResource *_parent; - ResourceType type; - bool contains(Common::Point point) { return _rect.contains(point); } virtual void drawDataToScreen() {} virtual void handleCardChange() {} @@ -75,7 +73,7 @@ public: void setEnabled(bool enabled); bool isDrawSubimages() { return _flags & kMystSubimageEnableFlag; } uint16 getDest() { return _dest; } - virtual uint16 getType8Var() { return 0xFFFF; } + virtual uint16 getImageSwitchVar() { return 0xFFFF; } bool unreachableZipDest(); bool canBecomeActive(); @@ -84,6 +82,8 @@ public: virtual void handleMouseDown() {} virtual void handleMouseDrag() {} + MystArea *_parent; + ResourceType type; protected: MohawkEngine_Myst *_vm; @@ -92,21 +92,25 @@ protected: uint16 _dest; }; -class MystResourceType5 : public MystResource { +class MystAreaAction : public MystArea { public: - MystResourceType5(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent); - void handleMouseUp(); - const Common::String describe(); + MystAreaAction(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystArea *parent); + + void handleMouseUp() override; + const Common::String describe() override; protected: MystScript _script; }; -class MystResourceType6 : public MystResourceType5 { +class MystAreaVideo : public MystAreaAction { public: - MystResourceType6(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent); + MystAreaVideo(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystArea *parent); + VideoHandle playMovie(); - void handleCardChange(); + VideoHandle getMovieHandle(); + + void handleCardChange() override; bool isPlaying(); void setDirection(int16 direction) { _direction = direction; } void setBlocking(bool blocking) { _playBlocking = blocking; } @@ -114,6 +118,7 @@ public: protected: static Common::String convertMystVideoName(Common::String name); + Common::String _videoFile; int16 _left; int16 _top; @@ -121,58 +126,63 @@ protected: int16 _direction; // 1 => forward, -1 => backwards uint16 _playBlocking; uint16 _playOnCardChange; - uint16 _u3; + uint16 _playRate; // percents }; -class MystResourceType7 : public MystResource { +class MystAreaActionSwitch : public MystArea { public: - MystResourceType7(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent); - virtual ~MystResourceType7(); + MystAreaActionSwitch(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystArea *parent); + virtual ~MystAreaActionSwitch(); - virtual void drawDataToScreen(); - virtual void handleCardChange(); + virtual void drawDataToScreen() override; + virtual void handleCardChange() override; - virtual void handleMouseUp(); - virtual void handleMouseDown(); + virtual void handleMouseUp() override; + virtual void handleMouseDown() override; - MystResource *getSubResource(uint16 index) { return _subResources[index]; } + MystArea *getSubResource(uint16 index) { return _subResources[index]; } protected: - uint16 _var7; - uint16 _numSubResources; - Common::Array<MystResource *> _subResources; + typedef void (MystArea::*AreaHandler)(); + + void doSwitch(AreaHandler handler); + + uint16 _actionSwitchVar; + Common::Array<MystArea *> _subResources; }; -class MystResourceType8 : public MystResourceType7 { +class MystAreaImageSwitch : public MystAreaActionSwitch { public: - MystResourceType8(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent); - virtual ~MystResourceType8(); - virtual const Common::String describe(); - - virtual void drawDataToScreen(); - void drawConditionalDataToScreen(uint16 state, bool update = true); - uint16 getType8Var(); + MystAreaImageSwitch(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystArea *parent); + virtual ~MystAreaImageSwitch(); struct SubImage { uint16 wdib; Common::Rect rect; - } *_subImages; + }; + + virtual const Common::String describe() override; + virtual void drawDataToScreen() override; + void drawConditionalDataToScreen(uint16 state, bool update = true); + uint16 getImageSwitchVar() override; + + SubImage getSubImage(uint index) const; + void setSubImageRect(uint index, const Common::Rect &rect); protected: - uint16 _var8; - uint16 _numSubImages; + uint16 _imageSwitchVar; + Common::Array<SubImage> _subImages; }; -// No MystResourceType9! - -class MystResourceType11 : public MystResourceType8 { +class MystAreaDrag : public MystAreaImageSwitch { public: - MystResourceType11(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent); - virtual ~MystResourceType11(); - const Common::String describe(); + MystAreaDrag(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystArea *parent); + virtual ~MystAreaDrag(); - void handleMouseDown(); - void handleMouseUp(); - void handleMouseDrag(); + const Common::String describe() override; + + virtual void handleMouseDown() override; + virtual void handleMouseUp() override; + virtual void handleMouseDrag() override; uint16 getList1(uint16 index); uint16 getList2(uint16 index); @@ -183,6 +193,8 @@ public: Common::Point _pos; protected: + typedef Common::Array<uint16> ValueList; + void setPositionClipping(const Common::Point &mouse, Common::Point &dest); uint16 _flagHV; @@ -197,21 +209,17 @@ protected: uint16 _mouseDownOpcode; uint16 _mouseDragOpcode; uint16 _mouseUpOpcode; - struct { - uint16 listCount; - uint16 *list; - } _lists[3]; - + ValueList _lists[3]; }; -class MystResourceType10 : public MystResourceType11 { +class MystAreaSlider : public MystAreaDrag { public: - MystResourceType10(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent); - virtual ~MystResourceType10(); + MystAreaSlider(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystArea *parent); + virtual ~MystAreaSlider(); - void handleMouseDown(); - void handleMouseUp(); - void handleMouseDrag(); + void handleMouseDown() override; + void handleMouseUp() override; + void handleMouseDrag() override; void setStep(uint16 step); void setPosition(uint16 pos); void restoreBackground(); @@ -225,10 +233,11 @@ protected: uint16 _sliderHeight; }; -class MystResourceType12 : public MystResourceType11 { +class MystVideoInfo : public MystAreaDrag { public: - MystResourceType12(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent); - virtual ~MystResourceType12(); + MystVideoInfo(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystArea *parent); + virtual ~MystVideoInfo(); + void drawFrame(uint16 frame); bool pullLeverV(); void releaseLeverV(); @@ -243,12 +252,13 @@ private: uint16 _currentFrame; }; -class MystResourceType13 : public MystResource { +class MystAreaHover : public MystArea { public: - MystResourceType13(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystResource *parent); - const Common::String describe(); + MystAreaHover(MohawkEngine_Myst *vm, Common::SeekableReadStream *rlstStream, MystArea *parent); + + const Common::String describe() override; - void handleMouseUp(); + void handleMouseUp() override; void handleMouseEnter(); void handleMouseLeave(); diff --git a/engines/mohawk/myst_graphics.cpp b/engines/mohawk/myst_graphics.cpp index 49f97cca63..5db9697a78 100644 --- a/engines/mohawk/myst_graphics.cpp +++ b/engines/mohawk/myst_graphics.cpp @@ -40,7 +40,7 @@ MystGraphics::MystGraphics(MohawkEngine_Myst* vm) : GraphicsManager(), _vm(vm) { if (_vm->getFeatures() & GF_ME) { // High color - initGraphics(_viewport.width(), _viewport.height(), true, NULL); + initGraphics(_viewport.width(), _viewport.height(), true, nullptr); if (_vm->_system->getScreenFormat().bytesPerPixel == 1) error("Myst ME requires greater than 256 colors to run"); @@ -73,7 +73,7 @@ MohawkSurface *MystGraphics::decodeImage(uint16 id) { // if it's a PICT or WDIB resource. If it's Myst ME it's most likely a PICT, and if it's // original it's definitely a WDIB. However, Myst ME throws us another curve ball in // that PICT resources can contain WDIB's instead of PICT's. - Common::SeekableReadStream *dataStream = NULL; + Common::SeekableReadStream *dataStream = nullptr; if (_vm->getFeatures() & GF_ME && _vm->hasResource(ID_PICT, id)) { // The PICT resource exists. However, it could still contain a MystBitmap @@ -95,7 +95,7 @@ MohawkSurface *MystGraphics::decodeImage(uint16 id) { dataStream->seek(0); } - MohawkSurface *mhkSurface = 0; + MohawkSurface *mhkSurface = nullptr; if (isPict) { Image::PICTDecoder pict; @@ -103,6 +103,8 @@ MohawkSurface *MystGraphics::decodeImage(uint16 id) { if (!pict.loadStream(*dataStream)) error("Could not decode Myst ME PICT"); + delete dataStream; + mhkSurface = new MohawkSurface(pict.getSurface()->convertTo(_pixelFormat)); } else { mhkSurface = _bmpDecoder->decodeImage(dataStream); @@ -225,9 +227,8 @@ void MystGraphics::copyBackBufferToScreen(Common::Rect r) { void MystGraphics::runTransition(TransitionType type, Common::Rect rect, uint16 steps, uint16 delay) { - // Do not artificially delay during transitions - int oldEnableDrawingTimeSimulation = _enableDrawingTimeSimulation; - _enableDrawingTimeSimulation = 0; + // Transitions are barely visible without adding delays between the draw calls + enableDrawingTimeSimulation(true); switch (type) { case kTransitionLeftToRight: { @@ -288,7 +289,10 @@ void MystGraphics::runTransition(TransitionType type, Common::Rect rect, uint16 debugC(kDebugView, "Dissolve"); for (int16 step = 0; step < 8; step++) { - simulatePreviousDrawDelay(rect); + // Only one eighth of the rect pixels are updated by a draw step, + // delay by one eighth of the regular time + simulatePreviousDrawDelay(Common::Rect(rect.width() / 8, rect.height())); + transitionDissolve(rect, step); } } @@ -367,7 +371,7 @@ void MystGraphics::runTransition(TransitionType type, Common::Rect rect, uint16 error("Unknown transition %d", type); } - _enableDrawingTimeSimulation = oldEnableDrawingTimeSimulation; + enableDrawingTimeSimulation(false); } void MystGraphics::transitionDissolve(Common::Rect rect, uint step) { @@ -639,8 +643,10 @@ void MystGraphics::simulatePreviousDrawDelay(const Common::Rect &dest) { // Do not draw anything new too quickly after the previous draw call // so that images stay at least a little while on screen // This is enabled only for scripted draw calls - if (time < _nextAllowedDrawTime) + if (time < _nextAllowedDrawTime) { + debugC(kDebugView, "Delaying draw call by %d ms", _nextAllowedDrawTime - time); _vm->_system->delayMillis(_nextAllowedDrawTime - time); + } } // Next draw call allowed at DELAY + AERA * COEFF milliseconds from now diff --git a/engines/mohawk/myst_graphics.h b/engines/mohawk/myst_graphics.h index 6281c94cc8..93e388cb83 100644 --- a/engines/mohawk/myst_graphics.h +++ b/engines/mohawk/myst_graphics.h @@ -61,8 +61,8 @@ public: const byte *getPalette() const { return _palette; } protected: - MohawkSurface *decodeImage(uint16 id); - MohawkEngine *getVM() { return (MohawkEngine *)_vm; } + MohawkSurface *decodeImage(uint16 id) override; + MohawkEngine *getVM() override { return (MohawkEngine *)_vm; } private: MohawkEngine_Myst *_vm; diff --git a/engines/mohawk/myst_scripts.cpp b/engines/mohawk/myst_scripts.cpp index 487d0f45fe..04e7c5a9b7 100644 --- a/engines/mohawk/myst_scripts.cpp +++ b/engines/mohawk/myst_scripts.cpp @@ -29,6 +29,7 @@ #include "mohawk/video.h" #include "common/system.h" +#include "common/memstream.h" #include "common/textconsole.h" #include "gui/message.h" @@ -38,7 +39,7 @@ MystScriptEntry::MystScriptEntry() { type = kMystScriptNone; var = 0; argc = 0; - argv = 0; + argv = nullptr; resourceId = 0; u1 = 0; } @@ -81,7 +82,7 @@ MystScriptParser::MystScriptParser(MohawkEngine_Myst *vm) : _vm(vm), _globals(vm->_gameState->_globals) { setupCommonOpcodes(); - _invokingResource = NULL; + _invokingResource = nullptr; _savedCardId = 0; _savedCursorId = 0; _tempVar = 0; @@ -154,7 +155,7 @@ void MystScriptParser::setupCommonOpcodes() { #undef OPCODE -void MystScriptParser::runScript(MystScript script, MystResource *invokingResource) { +void MystScriptParser::runScript(MystScript script, MystArea *invokingResource) { debugC(kDebugScript, "Script Size: %d", script->size()); // Scripted drawing takes more time to simulate older hardware @@ -260,15 +261,6 @@ bool MystScriptParser::setVarValue(uint16 var, uint16 value) { return false; } -// NOTE: Check to be used on Opcodes where var is thought -// not to be used. This emits a warning if var is nonzero. -// It is possible that the opcode does use var 0 in this case, -// but this will catch the majority of missed cases. -void MystScriptParser::varUnusedCheck(uint16 op, uint16 var) { - if (var != 0) - warning("Opcode %d: Unused Var %d", op, var); -} - void MystScriptParser::animatedUpdate(uint16 argc, uint16 *argv, uint16 delay) { uint16 argsRead = 0; @@ -331,7 +323,7 @@ void MystScriptParser::o_changeCardSwitch4(uint16 op, uint16 var, uint16 argc, u if (value) _vm->changeToCard(argv[value -1 ], kTransitionDissolve); - else if (_invokingResource != NULL) + else if (_invokingResource != nullptr) _vm->changeToCard(_invokingResource->getDest(), kTransitionDissolve); else warning("Missing invokingResource in altDest call"); @@ -344,7 +336,7 @@ void MystScriptParser::o_changeCardSwitchLtR(uint16 op, uint16 var, uint16 argc, if (value) _vm->changeToCard(argv[value -1 ], kTransitionLeftToRight); - else if (_invokingResource != NULL) + else if (_invokingResource != nullptr) _vm->changeToCard(_invokingResource->getDest(), kTransitionLeftToRight); else warning("Missing invokingResource in altDest call"); @@ -357,7 +349,7 @@ void MystScriptParser::o_changeCardSwitchRtL(uint16 op, uint16 var, uint16 argc, if (value) _vm->changeToCard(argv[value -1 ], kTransitionRightToLeft); - else if (_invokingResource != NULL) + else if (_invokingResource != nullptr) _vm->changeToCard(_invokingResource->getDest(), kTransitionRightToLeft); else warning("Missing invokingResource in altDest call"); @@ -398,7 +390,7 @@ void MystScriptParser::o_redrawCard(uint16 op, uint16 var, uint16 argc, uint16 * void MystScriptParser::o_goToDest(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Change To Dest of Invoking Resource", op); - if (_invokingResource != NULL) + if (_invokingResource != nullptr) _vm->changeToCard(_invokingResource->getDest(), kTransitionCopy); else warning("Opcode %d: Missing invokingResource", op); @@ -407,7 +399,7 @@ void MystScriptParser::o_goToDest(uint16 op, uint16 var, uint16 argc, uint16 *ar void MystScriptParser::o_goToDestForward(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Change To Dest of Invoking Resource", op); - if (_invokingResource != NULL) + if (_invokingResource != nullptr) _vm->changeToCard(_invokingResource->getDest(), kTransitionDissolve); else warning("Opcode %d: Missing invokingResource", op); @@ -416,7 +408,7 @@ void MystScriptParser::o_goToDestForward(uint16 op, uint16 var, uint16 argc, uin void MystScriptParser::o_goToDestLeft(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Change To Dest of Invoking Resource", op); - if (_invokingResource != NULL) + if (_invokingResource != nullptr) _vm->changeToCard(_invokingResource->getDest(), kTransitionPartToRight); else warning("Opcode %d: Missing invokingResource", op); @@ -425,7 +417,7 @@ void MystScriptParser::o_goToDestLeft(uint16 op, uint16 var, uint16 argc, uint16 void MystScriptParser::o_goToDestRight(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Change To Dest of Invoking Resource", op); - if (_invokingResource != NULL) + if (_invokingResource != nullptr) _vm->changeToCard(_invokingResource->getDest(), kTransitionPartToLeft); else warning("Opcode %d: Missing invokingResource", op); @@ -434,7 +426,7 @@ void MystScriptParser::o_goToDestRight(uint16 op, uint16 var, uint16 argc, uint1 void MystScriptParser::o_goToDestUp(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Change To Dest of Invoking Resource", op); - if (_invokingResource != NULL) + if (_invokingResource != nullptr) _vm->changeToCard(_invokingResource->getDest(), kTransitionTopToBottom); else warning("Opcode %d: Missing invokingResource", op); @@ -442,7 +434,10 @@ void MystScriptParser::o_goToDestUp(uint16 op, uint16 var, uint16 argc, uint16 * void MystScriptParser::o_triggerMovie(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Trigger Type 6 Resource Movie..", op); - // TODO: If movie has sound, pause background music + // The original has code to pause the background music before playing the movie, + // if the movie has a sound track, as well as code to resume it afterwards. But since + // the movie has not yet been loaded at this point, it is impossible to know + // if the movie actually has a sound track. The code is never executed. int16 direction = 1; if (argc == 1) @@ -451,11 +446,9 @@ void MystScriptParser::o_triggerMovie(uint16 op, uint16 var, uint16 argc, uint16 debugC(kDebugScript, "\tDirection: %d", direction); // Trigger resource 6 movie overriding play direction - MystResourceType6 *resource = static_cast<MystResourceType6 *>(_invokingResource); + MystAreaVideo *resource = getInvokingResource<MystAreaVideo>(); resource->setDirection(direction); resource->playMovie(); - - // TODO: If movie has sound, resume background music } void MystScriptParser::o_toggleVarNoRedraw(uint16 op, uint16 var, uint16 argc, uint16 *argv) { @@ -468,7 +461,7 @@ void MystScriptParser::o_drawAreaState(uint16 op, uint16 var, uint16 argc, uint1 debugC(kDebugScript, "Opcode %d: drawAreaState, state: %d", op, argv[0]); debugC(kDebugScript, "\tVar: %d", var); - MystResourceType8 *parent = static_cast<MystResourceType8 *>(_invokingResource->_parent); + MystAreaImageSwitch *parent = static_cast<MystAreaImageSwitch *>(getInvokingResource<MystArea>()->_parent); parent->drawConditionalDataToScreen(argv[0]); } @@ -517,6 +510,11 @@ void MystScriptParser::o_changeCardPop(uint16 op, uint16 var, uint16 argc, uint1 debugC(kDebugScript, "Opcode %d: Return To Stored Card Id", op); debugC(kDebugScript, "\tCardId: %d", _savedCardId); + if (_savedCardId == 0) { + warning("No pushed card to go back to"); + return; + } + TransitionType transition = static_cast<TransitionType>(argv[0]); _vm->changeToCard(_savedCardId, transition); @@ -531,7 +529,7 @@ void MystScriptParser::o_enableAreas(uint16 op, uint16 var, uint16 argc, uint16 for (uint16 i = 0; i < count; i++) { debugC(kDebugScript, "Enable hotspot index %d", argv[i + 1]); - MystResource *resource = 0; + MystArea *resource = nullptr; if (argv[i + 1] == 0xFFFF) resource = _invokingResource; else @@ -556,7 +554,7 @@ void MystScriptParser::o_disableAreas(uint16 op, uint16 var, uint16 argc, uint16 for (uint16 i = 0; i < count; i++) { debugC(kDebugScript, "Disable hotspot index %d", argv[i + 1]); - MystResource *resource = 0; + MystArea *resource = nullptr; if (argv[i + 1] == 0xFFFF) resource = _invokingResource; else @@ -587,7 +585,7 @@ void MystScriptParser::o_toggleAreasActivation(uint16 op, uint16 var, uint16 arg for (uint16 i = 0; i < count; i++) { debugC(kDebugScript, "Enable/Disable hotspot index %d", argv[i + 1]); - MystResource *resource = 0; + MystArea *resource = nullptr; if (argv[i + 1] == 0xFFFF) resource = _invokingResource; else @@ -682,82 +680,15 @@ void MystScriptParser::o_copyImageToBackBuffer(uint16 op, uint16 var, uint16 arg _vm->_gfx->copyImageSectionToBackBuffer(imageId, srcRect, dstRect); } -// TODO: Implement common engine function for read and processing of sound blocks -// for use by this opcode and VIEW sound block. -// TODO: Though the playSound and PlaySoundBlocking opcodes play sounds immediately, -// this opcode changes the main background sound playing.. -// Current behavior here and with VIEW sound block is not right as demonstrated -// by Channelwood Card 3280 (Tank Valve) and water flow sound behavior in pipe -// on cards leading from shed... void MystScriptParser::o_changeBackgroundSound(uint16 op, uint16 var, uint16 argc, uint16 *argv) { - int16 *soundList = NULL; - uint16 *soundListVolume = NULL; - // Used on Stoneship Card 2080 // Used on Channelwood Card 3225 with argc = 8 i.e. Conditional Sound List - if (argc == 1 || argc == 2 || argc == 8) { - debugC(kDebugScript, "Opcode %d: Process Sound Block", op); - uint16 decodeIdx = 0; - - int16 soundAction = argv[decodeIdx++]; - uint16 soundVolume = 65535; - if (soundAction == kMystSoundActionChangeVolume || soundAction > 0) { - soundVolume = argv[decodeIdx++]; - } else if (soundAction == kMystSoundActionConditional) { - debugC(kDebugScript, "Conditional sound list"); - uint16 condVar = argv[decodeIdx++]; - uint16 condVarValue = getVar(condVar); - uint16 condCount = argv[decodeIdx++]; - - debugC(kDebugScript, "\tcondVar: %d = %d", condVar, condVarValue); - debugC(kDebugScript, "\tcondCount: %d", condCount); - - soundList = new int16[condCount]; - soundListVolume = new uint16[condCount]; - - if (condVarValue >= condCount) - warning("Opcode %d: Conditional sound variable outside range", op); - else { - for (uint16 i = 0; i < condCount; i++) { - soundList[i] = argv[decodeIdx++]; - debugC(kDebugScript, "\t\tCondition %d: Action %d", i, soundList[i]); - if (soundList[i] == kMystSoundActionChangeVolume || soundList[i] > 0) { - soundListVolume[i] = argv[decodeIdx++]; - } else - soundListVolume[i] = 65535; - debugC(kDebugScript, "\t\tCondition %d: Volume %d", i, soundListVolume[i]); - } - - soundAction = soundList[condVarValue]; - soundVolume = soundListVolume[condVarValue]; - } - } + debugC(kDebugScript, "Opcode %d: Process Sound Block", op); - if (soundAction == kMystSoundActionContinue) - debugC(kDebugScript, "Continue current sound"); - else if (soundAction == kMystSoundActionChangeVolume) { - debugC(kDebugScript, "Continue current sound, change volume"); - debugC(kDebugScript, "\tVolume: %d", soundVolume); - _vm->_sound->changeBackgroundVolumeMyst(soundVolume); - } else if (soundAction == kMystSoundActionStop) { - debugC(kDebugScript, "Stop sound"); - _vm->_sound->stopBackgroundMyst(); - } else if (soundAction > 0) { - debugC(kDebugScript, "Play new Sound, change volume"); - debugC(kDebugScript, "\tSound: %d", soundAction); - debugC(kDebugScript, "\tVolume: %d", soundVolume); - _vm->_sound->replaceBackgroundMyst(soundAction, soundVolume); - } else { - debugC(kDebugScript, "Unknown"); - warning("Unknown sound control value in opcode %d", op); - } - } else - warning("Unknown arg count in opcode %d", op); + Common::MemoryReadStream stream = Common::MemoryReadStream((const byte *) argv, argc * sizeof(uint16)); - delete[] soundList; - soundList = NULL; - delete[] soundListVolume; - soundListVolume = NULL; + MystSoundBlock soundBlock = _vm->readSoundBlock(&stream); + _vm->applySoundBlock(soundBlock); } void MystScriptParser::o_soundPlaySwitch(uint16 op, uint16 var, uint16 argc, uint16 *argv) { diff --git a/engines/mohawk/myst_scripts.h b/engines/mohawk/myst_scripts.h index 7d8165c762..69052b10f5 100644 --- a/engines/mohawk/myst_scripts.h +++ b/engines/mohawk/myst_scripts.h @@ -34,7 +34,7 @@ namespace Mohawk { #define DECLARE_OPCODE(x) void x(uint16 op, uint16 var, uint16 argc, uint16 *argv) class MohawkEngine_Myst; -class MystResource; +class MystArea; enum MystScriptType { kMystScriptNone, @@ -63,11 +63,11 @@ public: MystScriptParser(MohawkEngine_Myst *vm); virtual ~MystScriptParser(); - void runScript(MystScript script, MystResource *invokingResource = NULL); - void runOpcode(uint16 op, uint16 var = 0, uint16 argc = 0, uint16 *argv = NULL); + void runScript(MystScript script, MystArea *invokingResource = nullptr); + void runOpcode(uint16 op, uint16 var = 0, uint16 argc = 0, uint16 *argv = nullptr); const Common::String getOpcodeDesc(uint16 op); MystScript readScript(Common::SeekableReadStream *stream, MystScriptType type); - void setInvokingResource(MystResource *resource) { _invokingResource = resource; } + void setInvokingResource(MystArea *resource) { _invokingResource = resource; } virtual void disablePersistentScripts() = 0; virtual void runPersistentScripts() = 0; @@ -151,8 +151,6 @@ protected: Common::Array<MystOpcode *> _opcodes; - MystResource *_invokingResource; - uint16 _savedCardId; uint16 _savedMapCardId; uint16 _savedCursorId; @@ -163,9 +161,25 @@ protected: static const uint16 _startCard[]; void setupCommonOpcodes(); - void varUnusedCheck(uint16 op, uint16 var); + + template<class T> + T *getInvokingResource() const; + +private: + MystArea *_invokingResource; }; +template<class T> +T *MystScriptParser::getInvokingResource() const { + T *resource = dynamic_cast<T *>(_invokingResource); + + if (!resource) { + error("Invoking resource has unexpected type"); + } + + return resource; +} + } // End of namespace Mohawk #undef DECLARE_OPCODE diff --git a/engines/mohawk/myst_stacks/channelwood.cpp b/engines/mohawk/myst_stacks/channelwood.cpp index 0718f8e683..21c3042359 100644 --- a/engines/mohawk/myst_stacks/channelwood.cpp +++ b/engines/mohawk/myst_stacks/channelwood.cpp @@ -363,7 +363,7 @@ void Channelwood::o_drawImageChangeCardAndVolume(uint16 op, uint16 var, uint16 a void Channelwood::o_waterTankValveOpen(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Do Water Tank Valve Open Animation", op); - Common::Rect rect = _invokingResource->getRect(); + Common::Rect rect = getInvokingResource<MystArea>()->getRect(); for (uint i = 0; i < 2; i++) for (uint16 imageId = 3601; imageId >= 3595; imageId--) { @@ -377,7 +377,7 @@ void Channelwood::o_waterTankValveOpen(uint16 op, uint16 var, uint16 argc, uint1 void Channelwood::o_leverStartMove(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Generic lever start move", op); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); lever->drawFrame(0); _vm->_cursor->setCursor(700); _leverPulled = false; @@ -386,7 +386,7 @@ void Channelwood::o_leverStartMove(uint16 op, uint16 var, uint16 argc, uint16 *a void Channelwood::o_leverMove(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Generic lever move", op); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); if (lever->pullLeverV()) { if (!_leverPulled) { @@ -401,7 +401,7 @@ void Channelwood::o_leverMove(uint16 op, uint16 var, uint16 argc, uint16 *argv) void Channelwood::o_leverMoveFail(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Generic lever move", op); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); if (lever->pullLeverV()) { if (!_leverPulled) { @@ -419,7 +419,7 @@ void Channelwood::o_leverEndMove(uint16 op, uint16 var, uint16 argc, uint16 *arg debugC(kDebugScript, "Opcode %d: Generic lever end move", op); // Get current lever frame - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); // Release lever lever->releaseLeverV(); @@ -439,7 +439,7 @@ void Channelwood::o_leverEndMoveResumeBackground(uint16 op, uint16 var, uint16 a void Channelwood::o_leverEndMoveWithSound(uint16 op, uint16 var, uint16 argc, uint16 *argv) { o_leverEndMove(op, var, argc, argv); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); uint16 soundId = lever->getList3(0); if (soundId) _vm->_sound->replaceSoundMyst(soundId); @@ -461,7 +461,7 @@ void Channelwood::o_leverElev3EndMove(uint16 op, uint16 var, uint16 argc, uint16 void Channelwood::o_pumpLeverMove(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Pump lever move", op); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); if (lever->pullLeverV()) { uint16 soundId = lever->getList2(0); @@ -475,7 +475,7 @@ void Channelwood::o_pumpLeverMove(uint16 op, uint16 var, uint16 argc, uint16 *ar void Channelwood::o_pumpLeverEndMove(uint16 op, uint16 var, uint16 argc, uint16 *argv) { o_leverEndMove(op, var, argc, argv); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); uint16 soundId = lever->getList3(0); if (soundId) _vm->_sound->replaceBackgroundMyst(soundId, 36864); @@ -484,7 +484,7 @@ void Channelwood::o_pumpLeverEndMove(uint16 op, uint16 var, uint16 argc, uint16 void Channelwood::o_stairsDoorToggle(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Play stairs door video", op); - MystResourceType6 *movie = static_cast<MystResourceType6 *>(_invokingResource); + MystAreaVideo *movie = getInvokingResource<MystAreaVideo>(); if (_state.stairsUpperDoorState) { // Close door, play the open movie backwards @@ -500,7 +500,7 @@ void Channelwood::o_stairsDoorToggle(uint16 op, uint16 var, uint16 argc, uint16 void Channelwood::o_valveHandleMove1(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Valve handle move", op); - MystResourceType12 *handle = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *handle = getInvokingResource<MystVideoInfo>(); const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos(); if (handle->getRect().contains(mouse)) { @@ -516,7 +516,7 @@ void Channelwood::o_valveHandleMove1(uint16 op, uint16 var, uint16 argc, uint16 void Channelwood::o_valveHandleMoveStart1(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Valve handle move start", op); - MystResourceType12 *handle = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *handle = getInvokingResource<MystVideoInfo>(); uint16 soundId = handle->getList1(0); if (soundId) _vm->_sound->replaceSoundMyst(soundId); @@ -528,7 +528,7 @@ void Channelwood::o_valveHandleMoveStart1(uint16 op, uint16 var, uint16 argc, ui void Channelwood::o_valveHandleMoveStop(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Valve handle move stop", op); - MystResourceType12 *handle = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *handle = getInvokingResource<MystVideoInfo>(); // Update state with valve position if (_tempVar <= 5) @@ -551,7 +551,7 @@ void Channelwood::o_valveHandleMoveStop(uint16 op, uint16 var, uint16 argc, uint void Channelwood::o_valveHandleMove2(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Valve handle move", op); - MystResourceType12 *handle = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *handle = getInvokingResource<MystVideoInfo>(); const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos(); if (handle->getRect().contains(mouse)) { @@ -567,7 +567,7 @@ void Channelwood::o_valveHandleMove2(uint16 op, uint16 var, uint16 argc, uint16 void Channelwood::o_valveHandleMoveStart2(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Valve handle move start", op); - MystResourceType12 *handle = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *handle = getInvokingResource<MystVideoInfo>(); uint16 soundId = handle->getList1(0); if (soundId) _vm->_sound->replaceSoundMyst(soundId); @@ -579,7 +579,7 @@ void Channelwood::o_valveHandleMoveStart2(uint16 op, uint16 var, uint16 argc, ui void Channelwood::o_valveHandleMove3(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Valve handle move", op); - MystResourceType12 *handle = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *handle = getInvokingResource<MystVideoInfo>(); const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos(); if (handle->getRect().contains(mouse)) { @@ -595,7 +595,7 @@ void Channelwood::o_valveHandleMove3(uint16 op, uint16 var, uint16 argc, uint16 void Channelwood::o_valveHandleMoveStart3(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Valve handle move start", op); - MystResourceType12 *handle = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *handle = getInvokingResource<MystVideoInfo>(); uint16 soundId = handle->getList1(0); if (soundId) _vm->_sound->replaceSoundMyst(soundId); @@ -621,24 +621,32 @@ void Channelwood::o_hologramMonitor(uint16 op, uint16 var, uint16 argc, uint16 * switch (button) { case 0: handle = _vm->_video->playMovie(_vm->wrapMovieFilename("monalgh", kChannelwoodStack)); + if (!handle) + error("Failed to open monalgh movie"); + handle->moveTo(227, 70); break; case 1: handle = _vm->_video->playMovie(_vm->wrapMovieFilename("monamth", kChannelwoodStack)); + if (!handle) + error("Failed to open monamth movie"); + handle->moveTo(227, 70); break; case 2: handle = _vm->_video->playMovie(_vm->wrapMovieFilename("monasirs", kChannelwoodStack)); + if (!handle) + error("Failed to open monasirs movie"); + handle->moveTo(227, 70); break; case 3: handle = _vm->_video->playMovie(_vm->wrapMovieFilename("monsmsg", kChannelwoodStack)); + if (!handle) + error("Failed to open monsmsg movie"); + handle->moveTo(226, 68); break; default: warning("Opcode %d Control Variable Out of Range", op); break; } - - // Move the video to the right location - if (handle) - handle->moveTo(227, 70); } } @@ -680,13 +688,13 @@ void Channelwood::o_hologramTemple(uint16 op, uint16 var, uint16 argc, uint16 *a void Channelwood::o_executeMouseUp(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Execute mouse up", op); - MystResourceType5 *resource = static_cast<MystResourceType5 *>(_vm->_resources[argv[0]]); + MystArea *resource = _vm->getViewResource<MystArea>(argv[0]); resource->handleMouseUp(); } void Channelwood::o_waterTankValveClose(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Do Water Tank Valve Close Animation", op); - Common::Rect rect = _invokingResource->getRect(); + Common::Rect rect = getInvokingResource<MystArea>()->getRect(); for (uint i = 0; i < 2; i++) for (uint16 imageId = 3595; imageId <= 3601; imageId++) { @@ -747,13 +755,14 @@ void Channelwood::o_soundReplace(uint16 op, uint16 var, uint16 argc, uint16 *arg uint16 soundId = argv[0]; - // TODO: If is foreground playing - _vm->_sound->replaceSoundMyst(soundId); + if (!_vm->_sound->isPlaying()) { + _vm->_sound->replaceSoundMyst(soundId); + } } void Channelwood::o_lever_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Generic lever init", op); - _leverAction = static_cast<MystResourceType5 *>(_invokingResource); + _leverAction = getInvokingResource<MystArea>(); } void Channelwood::o_pipeValve_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { diff --git a/engines/mohawk/myst_stacks/channelwood.h b/engines/mohawk/myst_stacks/channelwood.h index bd5d7ffe94..ac875e52d8 100644 --- a/engines/mohawk/myst_stacks/channelwood.h +++ b/engines/mohawk/myst_stacks/channelwood.h @@ -40,16 +40,16 @@ public: Channelwood(MohawkEngine_Myst *vm); ~Channelwood(); - void disablePersistentScripts(); - void runPersistentScripts(); + void disablePersistentScripts() override; + void runPersistentScripts() override; private: void setupOpcodes(); - uint16 getVar(uint16 var); - void toggleVar(uint16 var); - bool setVarValue(uint16 var, uint16 value); + uint16 getVar(uint16 var) override; + void toggleVar(uint16 var) override; + bool setVarValue(uint16 var, uint16 value) override; - virtual uint16 getMap() { return 9932; } + virtual uint16 getMap() override { return 9932; } DECLARE_OPCODE(o_bridgeToggle); DECLARE_OPCODE(o_pipeExtend); @@ -94,7 +94,7 @@ private: uint16 _doorOpened; // 68 bool _leverPulled; - MystResourceType5 *_leverAction; // 72 + MystArea *_leverAction; // 72 bool pipeChangeValve(bool open, uint16 mask); }; diff --git a/engines/mohawk/myst_stacks/credits.cpp b/engines/mohawk/myst_stacks/credits.cpp index b9ff8b26aa..b4a2076528 100644 --- a/engines/mohawk/myst_stacks/credits.cpp +++ b/engines/mohawk/myst_stacks/credits.cpp @@ -37,6 +37,7 @@ namespace MystStacks { Credits::Credits(MohawkEngine_Myst *vm) : MystScriptParser(vm) { setupOpcodes(); + _curImage = 0; } Credits::~Credits() { @@ -66,8 +67,10 @@ void Credits::runPersistentScripts() { _curImage++; // After the 6th image has shown, it's time to quit - if (_curImage == 7) + if (_curImage == 7) { _vm->quitGame(); + return; + } // Draw next image _vm->drawCardBackground(); diff --git a/engines/mohawk/myst_stacks/credits.h b/engines/mohawk/myst_stacks/credits.h index 3c0f969203..c2c20372bd 100644 --- a/engines/mohawk/myst_stacks/credits.h +++ b/engines/mohawk/myst_stacks/credits.h @@ -40,12 +40,12 @@ public: Credits(MohawkEngine_Myst *vm); ~Credits(); - void disablePersistentScripts(); - void runPersistentScripts(); + void disablePersistentScripts() override; + void runPersistentScripts() override; private: void setupOpcodes(); - uint16 getVar(uint16 var); + uint16 getVar(uint16 var) override; DECLARE_OPCODE(o_runCredits); diff --git a/engines/mohawk/myst_stacks/demo.h b/engines/mohawk/myst_stacks/demo.h index f19b9a6c2c..64a392502f 100644 --- a/engines/mohawk/myst_stacks/demo.h +++ b/engines/mohawk/myst_stacks/demo.h @@ -40,8 +40,8 @@ public: Demo(MohawkEngine_Myst *vm); ~Demo(); - void disablePersistentScripts(); - void runPersistentScripts(); + void disablePersistentScripts() override; + void runPersistentScripts() override; private: void setupOpcodes(); diff --git a/engines/mohawk/myst_stacks/dni.h b/engines/mohawk/myst_stacks/dni.h index 3dc4645bd3..1a5f0911f9 100644 --- a/engines/mohawk/myst_stacks/dni.h +++ b/engines/mohawk/myst_stacks/dni.h @@ -40,12 +40,12 @@ public: Dni(MohawkEngine_Myst *vm); ~Dni(); - void disablePersistentScripts(); - void runPersistentScripts(); + void disablePersistentScripts() override; + void runPersistentScripts() override; private: void setupOpcodes(); - uint16 getVar(uint16 var); + uint16 getVar(uint16 var) override; void atrus_run(); void loopVideo_run(); diff --git a/engines/mohawk/myst_stacks/intro.cpp b/engines/mohawk/myst_stacks/intro.cpp index dc66984398..1d733d8100 100644 --- a/engines/mohawk/myst_stacks/intro.cpp +++ b/engines/mohawk/myst_stacks/intro.cpp @@ -170,7 +170,7 @@ void Intro::mystLinkBook_run() { void Intro::o_mystLinkBook_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Myst link book init", op); - _linkBookMovie = static_cast<MystResourceType6 *>(_invokingResource); + _linkBookMovie = getInvokingResource<MystAreaVideo>(); _startTime = 1; _linkBookRunning = true; } diff --git a/engines/mohawk/myst_stacks/intro.h b/engines/mohawk/myst_stacks/intro.h index a6c4a594d2..0095706795 100644 --- a/engines/mohawk/myst_stacks/intro.h +++ b/engines/mohawk/myst_stacks/intro.h @@ -29,7 +29,7 @@ namespace Mohawk { -class MystResourceType6; +class MystAreaVideo; struct MystScriptEntry; namespace MystStacks { @@ -41,12 +41,12 @@ public: Intro(MohawkEngine_Myst *vm); ~Intro(); - void disablePersistentScripts(); - void runPersistentScripts(); + void disablePersistentScripts() override; + void runPersistentScripts() override; private: void setupOpcodes(); - uint16 getVar(uint16 var); + uint16 getVar(uint16 var) override; DECLARE_OPCODE(o_useLinkBook); @@ -60,7 +60,7 @@ private: uint16 _introStep; bool _linkBookRunning; - MystResourceType6 *_linkBookMovie; + MystAreaVideo *_linkBookMovie; }; } // End of namespace MystStacks diff --git a/engines/mohawk/myst_stacks/makingof.h b/engines/mohawk/myst_stacks/makingof.h index 79ef913bcf..41f91bc3fa 100644 --- a/engines/mohawk/myst_stacks/makingof.h +++ b/engines/mohawk/myst_stacks/makingof.h @@ -40,8 +40,8 @@ public: MakingOf(MohawkEngine_Myst *vm); ~MakingOf(); - void disablePersistentScripts(); - void runPersistentScripts(); + void disablePersistentScripts() override; + void runPersistentScripts() override; private: void setupOpcodes(); diff --git a/engines/mohawk/myst_stacks/mechanical.cpp b/engines/mohawk/myst_stacks/mechanical.cpp index ffcaa226c6..3324c9a22d 100644 --- a/engines/mohawk/myst_stacks/mechanical.cpp +++ b/engines/mohawk/myst_stacks/mechanical.cpp @@ -40,6 +40,9 @@ Mechanical::Mechanical(MohawkEngine_Myst *vm) : setupOpcodes(); _elevatorGoingMiddle = false; + _elevatorPosition = 0; + + _crystalLit = 0; _mystStaircaseState = false; _fortressPosition = 0; @@ -277,7 +280,7 @@ void Mechanical::o_throneEnablePassage(uint16 op, uint16 var, uint16 argc, uint1 void Mechanical::o_birdCrankStart(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Mechanical bird crank start", op); - MystResourceType11 *crank = static_cast<MystResourceType11 *>(_invokingResource); + MystAreaDrag *crank = getInvokingResource<MystAreaDrag>(); uint16 crankSoundId = crank->getList2(0); _vm->_sound->replaceSoundMyst(crankSoundId, Audio::Mixer::kMaxChannelVolume, true); @@ -285,16 +288,16 @@ void Mechanical::o_birdCrankStart(uint16 op, uint16 var, uint16 argc, uint16 *ar _birdSingEndTime = 0; _birdCrankStartTime = _vm->_system->getMillis(); - MystResourceType6 *crankMovie = static_cast<MystResourceType6 *>(crank->getSubResource(0)); + MystAreaVideo *crankMovie = static_cast<MystAreaVideo *>(crank->getSubResource(0)); crankMovie->playMovie(); } void Mechanical::o_birdCrankStop(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Mechanical bird crank stop", op); - MystResourceType11 *crank = static_cast<MystResourceType11 *>(_invokingResource); + MystAreaDrag *crank = getInvokingResource<MystAreaDrag>(); - MystResourceType6 *crankMovie = static_cast<MystResourceType6 *>(crank->getSubResource(0)); + MystAreaVideo *crankMovie = static_cast<MystAreaVideo *>(crank->getSubResource(0)); crankMovie->pauseMovie(true); uint16 crankSoundId = crank->getList2(1); @@ -334,7 +337,7 @@ void Mechanical::o_fortressStaircaseMovie(uint16 op, uint16 var, uint16 argc, ui void Mechanical::o_elevatorRotationStart(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Elevator rotation lever start", op); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); lever->drawFrame(0); _elevatorRotationLeverMoving = true; @@ -349,7 +352,7 @@ void Mechanical::o_elevatorRotationMove(uint16 op, uint16 var, uint16 argc, uint debugC(kDebugScript, "Opcode %d: Elevator rotation lever move", op); const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos(); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); // Make the handle follow the mouse int16 maxStep = lever->getNumFrames() - 1; @@ -367,7 +370,7 @@ void Mechanical::o_elevatorRotationStop(uint16 op, uint16 var, uint16 argc, uint debugC(kDebugScript, "Opcode %d: Elevator rotation lever stop", op); const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos(); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); // Get current lever frame int16 maxStep = lever->getNumFrames() - 1; @@ -416,7 +419,7 @@ void Mechanical::o_fortressRotationSpeedStart(uint16 op, uint16 var, uint16 argc _vm->_cursor->setCursor(700); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); lever->drawFrame(0); } @@ -424,7 +427,7 @@ void Mechanical::o_fortressRotationSpeedMove(uint16 op, uint16 var, uint16 argc, debugC(kDebugScript, "Opcode %d Fortress rotation speed lever move", op); const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos(); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); // Make the handle follow the mouse int16 maxStep = lever->getNumFrames() - 1; @@ -441,7 +444,7 @@ void Mechanical::o_fortressRotationSpeedMove(uint16 op, uint16 var, uint16 argc, void Mechanical::o_fortressRotationSpeedStop(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d Fortress rotation speed lever stop", op); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); // Release lever for (int i = _fortressRotationSpeed; i >= 0; i--) { @@ -459,7 +462,7 @@ void Mechanical::o_fortressRotationBrakeStart(uint16 op, uint16 var, uint16 argc _vm->_cursor->setCursor(700); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); lever->drawFrame(_fortressRotationBrake); } @@ -467,7 +470,7 @@ void Mechanical::o_fortressRotationBrakeMove(uint16 op, uint16 var, uint16 argc, debugC(kDebugScript, "Opcode %d Fortress rotation brake lever move", op); const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos(); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); // Make the handle follow the mouse int16 maxStep = lever->getNumFrames() - 1; @@ -484,7 +487,7 @@ void Mechanical::o_fortressRotationBrakeMove(uint16 op, uint16 var, uint16 argc, void Mechanical::o_fortressRotationBrakeStop(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d Fortress rotation brake lever stop", op); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); lever->drawFrame(_fortressRotationBrake); _vm->checkCursorHints(); @@ -495,7 +498,7 @@ void Mechanical::o_fortressSimulationSpeedStart(uint16 op, uint16 var, uint16 ar _vm->_cursor->setCursor(700); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); lever->drawFrame(0); } @@ -503,7 +506,7 @@ void Mechanical::o_fortressSimulationSpeedMove(uint16 op, uint16 var, uint16 arg debugC(kDebugScript, "Opcode %d Fortress rotation simulator speed lever move", op); const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos(); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); // Make the handle follow the mouse int16 maxStep = lever->getNumFrames() - 1; @@ -520,7 +523,7 @@ void Mechanical::o_fortressSimulationSpeedMove(uint16 op, uint16 var, uint16 arg void Mechanical::o_fortressSimulationSpeedStop(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d Fortress rotation simulator speed lever stop", op); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); // Release lever for (int i = _fortressSimulationSpeed; i >= 0; i--) { @@ -538,7 +541,7 @@ void Mechanical::o_fortressSimulationBrakeStart(uint16 op, uint16 var, uint16 ar _vm->_cursor->setCursor(700); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); lever->drawFrame(_fortressSimulationBrake); } @@ -546,7 +549,7 @@ void Mechanical::o_fortressSimulationBrakeMove(uint16 op, uint16 var, uint16 arg debugC(kDebugScript, "Opcode %d Fortress rotation simulator brake lever move", op); const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos(); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); // Make the handle follow the mouse int16 maxStep = lever->getNumFrames() - 1; @@ -563,7 +566,7 @@ void Mechanical::o_fortressSimulationBrakeMove(uint16 op, uint16 var, uint16 arg void Mechanical::o_fortressSimulationBrakeStop(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d Fortress rotation simulator brake lever stop", op); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); lever->drawFrame(_fortressSimulationBrake); _vm->checkCursorHints(); @@ -664,7 +667,7 @@ void Mechanical::o_elevatorTopMovie(uint16 op, uint16 var, uint16 argc, uint16 * void Mechanical::o_fortressRotationSetPosition(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Set fortress position", op); - VideoHandle gears = _fortressRotationGears->playMovie(); + VideoHandle gears = _fortressRotationGears->getMovieHandle(); uint32 moviePosition = Audio::Timestamp(gears->getTime(), 600).totalNumberOfFrames(); // Myst ME short movie workaround, explained in o_fortressRotation_init @@ -737,7 +740,7 @@ void Mechanical::o_throne_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) // Used on Card 6238 (Sirrus' Throne) and Card 6027 (Achenar's Throne) debugC(kDebugScript, "Opcode %d: Brother throne init", op); - _invokingResource->setEnabled(getVar(var)); + getInvokingResource<MystArea>()->setEnabled(getVar(var)); } void Mechanical::o_fortressStaircase_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { @@ -763,13 +766,13 @@ void Mechanical::o_bird_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { _birdSinging = false; _birdSingEndTime = 0; - _bird = static_cast<MystResourceType6 *>(_invokingResource); + _bird = getInvokingResource<MystAreaVideo>(); } void Mechanical::o_snakeBox_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Snake box init", op); - _snakeBox = static_cast<MystResourceType6 *>(_invokingResource); + _snakeBox = getInvokingResource<MystAreaVideo>(); } void Mechanical::elevatorRotation_run() { @@ -798,7 +801,7 @@ void Mechanical::o_elevatorRotation_init(uint16 op, uint16 var, uint16 argc, uin } void Mechanical::fortressRotation_run() { - VideoHandle gears = _fortressRotationGears->playMovie(); + VideoHandle gears = _fortressRotationGears->getMovieHandle(); double oldRate = gears->getRate().toDouble(); uint32 moviePosition = Audio::Timestamp(gears->getTime(), 600).totalNumberOfFrames(); @@ -872,7 +875,7 @@ void Mechanical::fortressRotation_run() { void Mechanical::o_fortressRotation_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Fortress rotation init", op); - _fortressRotationGears = static_cast<MystResourceType6 *>(_invokingResource); + _fortressRotationGears = getInvokingResource<MystAreaVideo>(); VideoHandle gears = _fortressRotationGears->playMovie(); gears->setLooping(true); @@ -938,13 +941,22 @@ void Mechanical::fortressSimulation_run() { holo->setLooping(true); holo->setRate(0); + // HACK: Support negative rates with edit lists + _fortressSimulationHoloRate = 0; + // END HACK + _vm->_cursor->showCursor(); _fortressSimulationInit = false; } else { - VideoHandle holo = _fortressSimulationHolo->playMovie(); + VideoHandle holo = _fortressSimulationHolo->getMovieHandle(); double oldRate = holo->getRate().toDouble(); + + // HACK: Support negative rates with edit lists + oldRate = _fortressSimulationHoloRate; + // END HACK + uint32 moviePosition = Audio::Timestamp(holo->getTime(), 600).totalNumberOfFrames(); int32 positionInQuarter = 900 - (moviePosition + 900) % 1800; @@ -978,7 +990,26 @@ void Mechanical::fortressSimulation_run() { newRate = CLIP<double>(newRate, -2.5, 2.5); - holo->setRate(Common::Rational((int)(newRate * 1000.0), 1000)); + // HACK: Support negative rates with edit lists + + // Our current QuickTime implementation does not support negative + // playback rates for movies using edit lists. + // The fortress rotation simulator movie this code handles is the + // only movie in the game requiring that feature. + + // This hack approximates the next frame to display when the rate + // is negative, and seeks to it. It's not intended to be precise. + + _fortressSimulationHoloRate = newRate; + + if (_fortressSimulationHoloRate < 0) { + double newMoviePosition = moviePosition + _fortressSimulationHoloRate * 10; + holo->setRate(0); + holo->seek(Audio::Timestamp(0, (uint)newMoviePosition, 600)); + } else { + holo->setRate(Common::Rational((int)(newRate * 1000.0), 1000)); + } + // END HACK _gearsWereRunning = true; } else if (_gearsWereRunning) { @@ -986,6 +1017,11 @@ void Mechanical::fortressSimulation_run() { uint16 simulationPosition = (moviePosition + 900) / 1800 % 4; holo->setRate(0); + + // HACK: Support negative rates with edit lists + _fortressSimulationHoloRate = 0; + // END HACK + holo->seek(Audio::Timestamp(0, 1800 * simulationPosition, 600)); _vm->_sound->playSoundBlocking( _fortressRotationSounds[simulationPosition]); @@ -997,7 +1033,7 @@ void Mechanical::fortressSimulation_run() { void Mechanical::o_fortressSimulation_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Fortress rotation simulator init", op); - _fortressSimulationHolo = static_cast<MystResourceType6 *>(_invokingResource); + _fortressSimulationHolo = getInvokingResource<MystAreaVideo>(); _fortressSimulationStartSound1 = argv[0]; _fortressSimulationStartSound2 = argv[1]; @@ -1019,7 +1055,7 @@ void Mechanical::o_fortressSimulation_init(uint16 op, uint16 var, uint16 argc, u void Mechanical::o_fortressSimulationStartup_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Fortress rotation simulator startup init", op); - _fortressSimulationStartup = static_cast<MystResourceType6 *>(_invokingResource); + _fortressSimulationStartup = getInvokingResource<MystAreaVideo>(); } } // End of namespace MystStacks diff --git a/engines/mohawk/myst_stacks/mechanical.h b/engines/mohawk/myst_stacks/mechanical.h index 6360b2be2d..aae02df433 100644 --- a/engines/mohawk/myst_stacks/mechanical.h +++ b/engines/mohawk/myst_stacks/mechanical.h @@ -40,16 +40,16 @@ public: Mechanical(MohawkEngine_Myst *vm); ~Mechanical(); - void disablePersistentScripts(); - void runPersistentScripts(); + void disablePersistentScripts() override; + void runPersistentScripts() override; private: void setupOpcodes(); - uint16 getVar(uint16 var); - void toggleVar(uint16 var); - bool setVarValue(uint16 var, uint16 value); + uint16 getVar(uint16 var) override; + void toggleVar(uint16 var) override; + bool setVarValue(uint16 var, uint16 value) override; - virtual uint16 getMap() { return 9931; } + virtual uint16 getMap() override { return 9931; } void birdSing_run(); void elevatorRotation_run(); @@ -109,7 +109,7 @@ private: uint16 _fortressRotationBrake; // 80 uint16 _fortressPosition; // 82 uint16 _fortressRotationSounds[4]; // 86 to 92 - MystResourceType6 *_fortressRotationGears; // 172 + MystAreaVideo *_fortressRotationGears; // 172 bool _fortressRotationShortMovieWorkaround; uint32 _fortressRotationShortMovieCount; @@ -121,8 +121,12 @@ private: uint16 _fortressSimulationBrake; // 98 uint16 _fortressSimulationStartSound1; // 102 uint16 _fortressSimulationStartSound2; // 100 - MystResourceType6 *_fortressSimulationHolo; // 160 - MystResourceType6 *_fortressSimulationStartup; // 164 + MystAreaVideo *_fortressSimulationHolo; // 160 + MystAreaVideo *_fortressSimulationStartup; // 164 + + // HACK: Support negative rates with edit lists + double _fortressSimulationHoloRate; + // END HACK uint16 _elevatorGoingDown; // 112 @@ -143,10 +147,10 @@ private: bool _birdSinging; // 144 uint32 _birdCrankStartTime; // 136 uint32 _birdSingEndTime; // 140 - MystResourceType6 *_bird; // 152 + MystAreaVideo *_bird; // 152 - MystResourceType6 *_snakeBox; // 156 + MystAreaVideo *_snakeBox; // 156 }; } // End of namespace MystStacks diff --git a/engines/mohawk/myst_stacks/myst.cpp b/engines/mohawk/myst_stacks/myst.cpp index 9ad1635757..9d23d2fb10 100644 --- a/engines/mohawk/myst_stacks/myst.cpp +++ b/engines/mohawk/myst_stacks/myst.cpp @@ -50,14 +50,16 @@ Myst::Myst(MohawkEngine_Myst *vm) : _libraryBookcaseChanged = false; _dockVaultState = 0; _cabinDoorOpened = 0; + _cabinHandleDown = 0; _cabinMatchState = 2; + _cabinGaugeMovieEnabled = false; _matchBurning = false; - _tree = 0; - _treeAlcove = 0; + _tree = nullptr; + _treeAlcove = nullptr; _treeStopped = false; _treeMinPosition = 0; _imagerValidationStep = 0; - _observatoryCurrentSlider = 0; + _observatoryCurrentSlider = nullptr; _butterfliesMoviePlayed = false; _state.treeLastMoveTime = _vm->_system->getMillis(); } @@ -626,7 +628,7 @@ uint16 Myst::getVar(uint16 var) { case 307: // Cabin Boiler Fully Pressurized return _state.cabinPilotLightLit == 1 && _state.cabinValvePosition > 12; case 308: // Cabin handle position - return 0; // Not implemented in the original + return _cabinHandleDown; default: return MystScriptParser::getVar(var); } @@ -764,6 +766,9 @@ bool Myst::setVarValue(uint16 var, uint16 value) { case 304: // Myst Library Image Present on Tower Rotation Map _towerRotationMapInitialized = value; break; + case 308: // Cabin handle position + _cabinHandleDown = value; + break; case 309: // Tree stopped _treeStopped = value; break; @@ -860,14 +865,14 @@ void Myst::o_fireplaceToggleButton(uint16 op, uint16 var, uint16 argc, uint16 *a if (line & bitmask) { // Unset button for (uint i = 4795; i >= 4779; i--) { - _vm->_gfx->copyImageToScreen(i, _invokingResource->getRect()); + _vm->_gfx->copyImageToScreen(i, getInvokingResource<MystArea>()->getRect()); _vm->_system->updateScreen(); } _fireplaceLines[var - 17] &= ~bitmask; } else { // Set button for (uint i = 4779; i <= 4795; i++) { - _vm->_gfx->copyImageToScreen(i, _invokingResource->getRect()); + _vm->_gfx->copyImageToScreen(i, getInvokingResource<MystArea>()->getRect()); _vm->_system->updateScreen(); } _fireplaceLines[var - 17] |= bitmask; @@ -1251,7 +1256,7 @@ void Myst::o_imagerPlayButton(uint16 op, uint16 var, uint16 argc, uint16 *argv) void Myst::o_imagerEraseButton(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Imager erase button", op); - _imagerRedButton = static_cast<MystResourceType8 *>(_invokingResource->_parent); + _imagerRedButton = static_cast<MystAreaImageSwitch *>(getInvokingResource<MystArea>()->_parent); for (uint i = 0; i < 4; i++) _imagerSound[i] = argv[i]; _imagerValidationCard = argv[4]; @@ -1351,7 +1356,7 @@ void Myst::o_towerElevatorAnimation(uint16 op, uint16 var, uint16 argc, uint16 * void Myst::o_generatorButtonPressed(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Generator button pressed", op); - MystResource *button = _invokingResource->_parent; + MystArea *button = getInvokingResource<MystArea>()->_parent; generatorRedrawRocket(); @@ -1376,7 +1381,7 @@ void Myst::o_generatorButtonPressed(uint16 op, uint16 var, uint16 argc, uint16 * if (_generatorVoltage) _vm->_sound->replaceSoundMyst(6297); else { - _vm->_sound->replaceSoundMyst(7297); // TODO: Replace with play sound and replace background 4297 + _vm->_sound->replaceSoundMyst(7297); _vm->_sound->replaceBackgroundMyst(4297); } @@ -1385,7 +1390,7 @@ void Myst::o_generatorButtonPressed(uint16 op, uint16 var, uint16 argc, uint16 * } // Redraw button - _vm->redrawArea(button->getType8Var()); + _vm->redrawArea(button->getImageSwitchVar()); // Blow breaker if (_state.generatorVoltage > 59) @@ -1398,8 +1403,8 @@ void Myst::generatorRedrawRocket() { _vm->redrawArea(97); } -void Myst::generatorButtonValue(MystResource *button, uint16 &mask, uint16 &value) { - switch (button->getType8Var()) { +void Myst::generatorButtonValue(MystArea *button, uint16 &mask, uint16 &value) { + switch (button->getImageSwitchVar()) { case 52: // Generator Switch #1 mask = 1; value = 10; @@ -1466,7 +1471,7 @@ void Myst::o_cabinSafeHandleStartMove(uint16 op, uint16 var, uint16 argc, uint16 debugC(kDebugScript, "Opcode %d: Cabin safe handle start move", op); // Used on Card 4100 - MystResourceType12 *handle = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *handle = getInvokingResource<MystVideoInfo>(); handle->drawFrame(0); _vm->_cursor->setCursor(700); _tempVar = 0; @@ -1476,7 +1481,7 @@ void Myst::o_cabinSafeHandleMove(uint16 op, uint16 var, uint16 argc, uint16 *arg debugC(kDebugScript, "Opcode %d: Cabin safe handle move", op); // Used on Card 4100 - MystResourceType12 *handle = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *handle = getInvokingResource<MystVideoInfo>(); if (handle->pullLeverV()) { // Sound not played yet @@ -1506,7 +1511,7 @@ void Myst::o_cabinSafeHandleEndMove(uint16 op, uint16 var, uint16 argc, uint16 * debugC(kDebugScript, "Opcode %d: Cabin safe handle end move", op); // Used on Card 4100 - MystResourceType12 *handle = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *handle = getInvokingResource<MystVideoInfo>(); handle->drawFrame(0); _vm->checkCursorHints(); } @@ -1804,7 +1809,7 @@ void Myst::o_observatoryTimeSliderMove(uint16 op, uint16 var, uint16 argc, uint1 void Myst::o_circuitBreakerStartMove(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Circuit breaker start move", op); - MystResourceType12 *breaker = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *breaker = getInvokingResource<MystVideoInfo>(); breaker->drawFrame(0); _vm->_cursor->setCursor(700); _tempVar = 0; @@ -1813,7 +1818,7 @@ void Myst::o_circuitBreakerStartMove(uint16 op, uint16 var, uint16 argc, uint16 void Myst::o_circuitBreakerMove(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Circuit breaker move", op); - MystResourceType12 *breaker = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *breaker = getInvokingResource<MystVideoInfo>(); const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos(); int16 maxStep = breaker->getStepsV() - 1; @@ -1828,7 +1833,7 @@ void Myst::o_circuitBreakerMove(uint16 op, uint16 var, uint16 argc, uint16 *argv // Breaker switched if (step == maxStep) { // Choose breaker - if (breaker->getType8Var() == 93) { + if (breaker->getImageSwitchVar() == 93) { // Voltage is still too high or not broken if (_state.generatorVoltage > 59 || _state.generatorBreakers != 1) { uint16 soundId = breaker->getList2(1); @@ -1864,8 +1869,8 @@ void Myst::o_circuitBreakerMove(uint16 op, uint16 var, uint16 argc, uint16 *argv void Myst::o_circuitBreakerEndMove(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Circuit breaker end move", op); - MystResourceType12 *breaker = static_cast<MystResourceType12 *>(_invokingResource); - _vm->redrawArea(breaker->getType8Var()); + MystVideoInfo *breaker = getInvokingResource<MystVideoInfo>(); + _vm->redrawArea(breaker->getImageSwitchVar()); _vm->checkCursorHints(); } @@ -2138,7 +2143,7 @@ void Myst::tree_run() { // Check if alcove is accessible treeSetAlcoveAccessible(); - if (_cabinGaugeMovie) { + if (_cabinGaugeMovieEnabled) { Common::Rational rate = boilerComputeGaugeRate(pressure, delay); boilerResetGauge(rate); } @@ -2187,22 +2192,22 @@ void Myst::o_rocketSoundSliderEndMove(uint16 op, uint16 var, uint16 argc, uint16 if (_state.generatorVoltage == 59 && !_state.generatorBreakers && _rocketSliderSound) _vm->_sound->stopSound(); - if (_invokingResource == _rocketSlider1) + if (getInvokingResource<MystArea>() == _rocketSlider1) _state.rocketSliderPosition[0] = _rocketSlider1->_pos.y; - else if (_invokingResource == _rocketSlider2) + else if (getInvokingResource<MystArea>() == _rocketSlider2) _state.rocketSliderPosition[1] = _rocketSlider2->_pos.y; - else if (_invokingResource == _rocketSlider3) + else if (getInvokingResource<MystArea>() == _rocketSlider3) _state.rocketSliderPosition[2] = _rocketSlider3->_pos.y; - else if (_invokingResource == _rocketSlider4) + else if (getInvokingResource<MystArea>() == _rocketSlider4) _state.rocketSliderPosition[3] = _rocketSlider4->_pos.y; - else if (_invokingResource == _rocketSlider5) + else if (getInvokingResource<MystArea>() == _rocketSlider5) _state.rocketSliderPosition[4] = _rocketSlider5->_pos.y; _vm->_sound->resumeBackgroundMyst(); } void Myst::rocketSliderMove() { - MystResourceType10 *slider = static_cast<MystResourceType10 *>(_invokingResource); + MystAreaSlider *slider = getInvokingResource<MystAreaSlider>(); if (_state.generatorVoltage == 59 && !_state.generatorBreakers) { uint16 soundId = rocketSliderGetSound(slider->_pos.y); @@ -2262,7 +2267,7 @@ void Myst::rocketCheckSolution() { if (solved) { // Reset lever position - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); lever->drawFrame(0); // Book appearing @@ -2299,17 +2304,17 @@ void Myst::rocketCheckSolution() { void Myst::o_rocketPianoStart(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Rocket piano start move", op); - MystResourceType11 *key = static_cast<MystResourceType11 *>(_invokingResource); + MystAreaDrag *key = getInvokingResource<MystAreaDrag>(); // What the hell?? - Common::Rect src = key->_subImages[1].rect; - Common::Rect rect = key->_subImages[0].rect; + Common::Rect src = key->getSubImage(1).rect; + Common::Rect rect = key->getSubImage(0).rect; Common::Rect dest = rect; dest.top = 332 - rect.bottom; dest.bottom = 332 - rect.top; // Draw pressed piano key - _vm->_gfx->copyImageSectionToScreen(key->_subImages[1].wdib, src, dest); + _vm->_gfx->copyImageSectionToScreen(key->getSubImage(1).wdib, src, dest); _vm->_system->updateScreen(); // Play note @@ -2326,29 +2331,29 @@ void Myst::o_rocketPianoMove(uint16 op, uint16 var, uint16 argc, uint16 *argv) { Common::Rect piano = Common::Rect(85, 123, 460, 270); // Unpress previous key - MystResourceType11 *key = static_cast<MystResourceType11 *>(_invokingResource); + MystAreaDrag *key = getInvokingResource<MystAreaDrag>(); - Common::Rect src = key->_subImages[0].rect; + Common::Rect src = key->getSubImage(0).rect; Common::Rect dest = src; dest.top = 332 - src.bottom; dest.bottom = 332 - src.top; // Draw unpressed piano key - _vm->_gfx->copyImageSectionToScreen(key->_subImages[0].wdib, src, dest); + _vm->_gfx->copyImageSectionToScreen(key->getSubImage(0).wdib, src, dest); if (piano.contains(mouse)) { - MystResource *resource = _vm->updateCurrentResource(); - if (resource && resource->type == kMystDragArea) { + MystArea *resource = _vm->updateCurrentResource(); + if (resource && resource->type == kMystAreaDrag) { // Press new key - key = static_cast<MystResourceType11 *>(resource); - src = key->_subImages[1].rect; - Common::Rect rect = key->_subImages[0].rect; + key = static_cast<MystAreaDrag *>(resource); + src = key->getSubImage(1).rect; + Common::Rect rect = key->getSubImage(0).rect; dest = rect; dest.top = 332 - rect.bottom; dest.bottom = 332 - rect.top; // Draw pressed piano key - _vm->_gfx->copyImageSectionToScreen(key->_subImages[1].wdib, src, dest); + _vm->_gfx->copyImageSectionToScreen(key->getSubImage(1).wdib, src, dest); // Play note if (_state.generatorVoltage == 59 && !_state.generatorBreakers) { @@ -2368,15 +2373,15 @@ void Myst::o_rocketPianoMove(uint16 op, uint16 var, uint16 argc, uint16 *argv) { void Myst::o_rocketPianoStop(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Rocket piano end move", op); - MystResourceType8 *key = static_cast<MystResourceType8 *>(_invokingResource); + MystAreaImageSwitch *key = getInvokingResource<MystAreaImageSwitch>(); - Common::Rect &src = key->_subImages[0].rect; + Common::Rect src = key->getSubImage(0).rect; Common::Rect dest = src; dest.top = 332 - src.bottom; dest.bottom = 332 - src.top; // Draw unpressed piano key - _vm->_gfx->copyImageSectionToScreen(key->_subImages[0].wdib, src, dest); + _vm->_gfx->copyImageSectionToScreen(key->getSubImage(0).wdib, src, dest); _vm->_system->updateScreen(); _vm->_sound->stopSound(); @@ -2386,7 +2391,7 @@ void Myst::o_rocketPianoStop(uint16 op, uint16 var, uint16 argc, uint16 *argv) { void Myst::o_rocketLeverStartMove(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Rocket lever start move", op); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); _vm->_cursor->setCursor(700); _rocketLeverPosition = 0; @@ -2406,7 +2411,7 @@ void Myst::o_rocketOpenBook(uint16 op, uint16 var, uint16 argc, uint16 *argv) { void Myst::o_rocketLeverMove(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Rocket lever move", op); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos(); // Make the lever follow the mouse @@ -2435,7 +2440,7 @@ void Myst::o_rocketLeverMove(uint16 op, uint16 var, uint16 argc, uint16 *argv) { void Myst::o_rocketLeverEndMove(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Rocket lever end move", op); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); _vm->checkCursorHints(); _rocketLeverPosition = 0; @@ -2712,7 +2717,7 @@ void Myst::clockWheel_run() { } void Myst::clockWheelStartTurn(uint16 wheel) { - MystResourceType11 *resource = static_cast<MystResourceType11 *>(_invokingResource); + MystAreaDrag *resource = getInvokingResource<MystAreaDrag>(); uint16 soundId = resource->getList1(0); if (soundId) @@ -2835,10 +2840,10 @@ void Myst::o_observatoryChangeSettingStop(uint16 op, uint16 var, uint16 argc, ui _observatoryIncrement = 0; // Restore button and slider - _vm->_gfx->copyBackBufferToScreen(_invokingResource->getRect()); + _vm->_gfx->copyBackBufferToScreen(getInvokingResource<MystArea>()->getRect()); if (_observatoryCurrentSlider) { _vm->redrawResource(_observatoryCurrentSlider); - _observatoryCurrentSlider = 0; + _observatoryCurrentSlider = nullptr; } _vm->_sound->resumeBackgroundMyst(); } @@ -2874,7 +2879,7 @@ void Myst::o_imagerEraseStop(uint16 op, uint16 var, uint16 argc, uint16 *argv) { void Myst::o_clockLeverStartMove(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Clock lever start move", op); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); lever->drawFrame(0); _vm->_cursor->setCursor(700); _clockMiddleGearMovedAlone = false; @@ -2885,7 +2890,7 @@ void Myst::o_clockLeverMove(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Clock left lever move", op); if (!_clockLeverPulled) { - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); // If lever pulled if (lever->pullLeverV()) { @@ -2977,7 +2982,7 @@ void Myst::o_clockLeverEndMove(uint16 op, uint16 var, uint16 argc, uint16 *argv) _vm->_sound->replaceSoundMyst(8113); // Release lever - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); lever->releaseLeverV(); // Check if puzzle is solved @@ -3021,7 +3026,7 @@ void Myst::clockGearsCheckSolution() { void Myst::o_clockResetLeverStartMove(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Clock reset lever start move", op); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); lever->drawFrame(0); _vm->_cursor->setCursor(700); } @@ -3029,7 +3034,7 @@ void Myst::o_clockResetLeverStartMove(uint16 op, uint16 var, uint16 argc, uint16 void Myst::o_clockResetLeverMove(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Clock reset lever move", op); - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); // If pulled if (lever->pullLeverV() && _clockWeightPosition != 0) @@ -3125,7 +3130,7 @@ void Myst::o_clockResetLeverEndMove(uint16 op, uint16 var, uint16 argc, uint16 * debugC(kDebugScript, "Opcode %d: Clock reset lever end move", op); // Get current lever frame - MystResourceType12 *lever = static_cast<MystResourceType12 *>(_invokingResource); + MystVideoInfo *lever = getInvokingResource<MystVideoInfo>(); lever->releaseLeverV(); @@ -3189,8 +3194,8 @@ void Myst::towerRotationMap_run() { void Myst::o_towerRotationMap_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { _towerRotationMapRunning = true; - _towerRotationMapTower = static_cast<MystResourceType11 *>(_invokingResource); - _towerRotationMapLabel = static_cast<MystResourceType8 *>(_vm->_resources[argv[0]]); + _towerRotationMapTower = getInvokingResource<MystAreaImageSwitch>(); + _towerRotationMapLabel = _vm->getViewResource<MystAreaImageSwitch>(argv[0]); _tempVar = 0; _startTime = 0; _towerRotationMapClicked = false; @@ -3202,7 +3207,7 @@ void Myst::towerRotationDrawBuildings() { // Draw other resources for (uint i = 1; i <= 10; i++) { - MystResourceType8 *resource = static_cast<MystResourceType8 *>(_vm->_resources[i]); + MystAreaImageSwitch *resource = _vm->getViewResource<MystAreaImageSwitch>(i); _vm->redrawResource(resource, false); } } @@ -3313,7 +3318,7 @@ void Myst::o_forechamberDoor_init(uint16 op, uint16 var, uint16 argc, uint16 *ar void Myst::o_shipAccess_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { // Enable acces to the ship if (_state.shipFloating) { - _invokingResource->setEnabled(true); + getInvokingResource<MystArea>()->setEnabled(true); } } @@ -3322,7 +3327,7 @@ void Myst::o_butterflies_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) // Used for Card 4256 (Butterfly Movie Activation) if (!_butterfliesMoviePlayed) { - MystResourceType6 *butterflies = static_cast<MystResourceType6 *>(_invokingResource); + MystAreaVideo *butterflies = getInvokingResource<MystAreaVideo>(); butterflies->playMovie(); _butterfliesMoviePlayed = true; @@ -3333,8 +3338,8 @@ void Myst::o_imager_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Imager init", op); debugC(kDebugScript, "Var: %d", var); - MystResourceType7 *select = static_cast<MystResourceType7 *>(_invokingResource); - _imagerMovie = static_cast<MystResourceType6 *>(select->getSubResource(getVar(var))); + MystAreaActionSwitch *select = getInvokingResource<MystAreaActionSwitch>(); + _imagerMovie = static_cast<MystAreaVideo *>(select->getSubResource(getVar(var))); _imagerRunning = true; } @@ -3377,8 +3382,8 @@ void Myst::libraryBookcaseTransform_run(void) { void Myst::o_libraryBookcaseTransform_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { if (_libraryBookcaseChanged) { - MystResourceType7 *resource = static_cast<MystResourceType7 *>(_invokingResource); - _libraryBookcaseMovie = static_cast<MystResourceType6 *>(resource->getSubResource(getVar(0))); + MystAreaActionSwitch *resource = getInvokingResource<MystAreaActionSwitch>(); + _libraryBookcaseMovie = static_cast<MystAreaVideo *>(resource->getSubResource(getVar(0))); _libraryBookcaseSoundId = argv[0]; _libraryBookcaseMoving = true; } @@ -3471,17 +3476,17 @@ void Myst::o_observatory_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) _tempVar = 0; _observatoryNotInitialized = true; - _observatoryVisualizer = static_cast<MystResourceType8 *>(_invokingResource); - _observatoryGoButton = static_cast<MystResourceType8 *>(_vm->_resources[argv[0]]); + _observatoryVisualizer = getInvokingResource<MystAreaImageSwitch>(); + _observatoryGoButton = _vm->getViewResource<MystAreaImageSwitch>(argv[0]); if (observatoryIsDDMMYYYY2400()) { - _observatoryDaySlider = static_cast<MystResourceType10 *>(_vm->_resources[argv[1]]); - _observatoryMonthSlider = static_cast<MystResourceType10 *>(_vm->_resources[argv[2]]); + _observatoryDaySlider = _vm->getViewResource<MystAreaSlider>(argv[1]); + _observatoryMonthSlider = _vm->getViewResource<MystAreaSlider>(argv[2]); } else { - _observatoryMonthSlider = static_cast<MystResourceType10 *>(_vm->_resources[argv[1]]); - _observatoryDaySlider = static_cast<MystResourceType10 *>(_vm->_resources[argv[2]]); + _observatoryMonthSlider = _vm->getViewResource<MystAreaSlider>(argv[1]); + _observatoryDaySlider = _vm->getViewResource<MystAreaSlider>(argv[2]); } - _observatoryYearSlider = static_cast<MystResourceType10 *>(_vm->_resources[argv[3]]); - _observatoryTimeSlider = static_cast<MystResourceType10 *>(_vm->_resources[argv[4]]); + _observatoryYearSlider = _vm->getViewResource<MystAreaSlider>(argv[3]); + _observatoryTimeSlider = _vm->getViewResource<MystAreaSlider>(argv[4]); // Set date selection sliders position _observatoryDaySlider->setPosition(_state.observatoryDaySlider); @@ -3503,18 +3508,14 @@ bool Myst::observatoryIsDDMMYYYY2400() { } void Myst::observatoryUpdateVisualizer(uint16 x, uint16 y) { - Common::Rect &visu0 = _observatoryVisualizer->_subImages[0].rect; - Common::Rect &visu1 = _observatoryVisualizer->_subImages[1].rect; - - visu0.left = x; - visu0.right = visu0.left + 105; - visu0.bottom = 512 - y; - visu0.top = visu0.bottom - 106; + Common::Rect visu; + visu.left = x; + visu.right = visu.left + 105; + visu.bottom = 512 - y; + visu.top = visu.bottom - 106; - visu1.left = visu0.left; - visu1.top = visu0.top; - visu1.right = visu0.right; - visu1.bottom = visu0.bottom; + _observatoryVisualizer->setSubImageRect(0, visu); + _observatoryVisualizer->setSubImageRect(1, visu); } void Myst::observatorySetTargetToSetting() { @@ -3618,13 +3619,13 @@ void Myst::gullsFly2_run() { void Myst::o_treeCard_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Enter tree card", op); - _tree = static_cast<MystResourceType8 *>(_invokingResource); + _tree = getInvokingResource<MystAreaImageSwitch>(); } void Myst::o_treeEntry_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Enter tree card with entry", op); - _treeAlcove = static_cast<MystResourceType5 *>(_invokingResource); + _treeAlcove = getInvokingResource<MystArea>(); _treeMinAccessiblePosition = argv[0]; _treeMaxAccessiblePosition = argv[1]; @@ -3703,16 +3704,18 @@ void Myst::boilerGaugeInit() { frame = Audio::Timestamp(0, 0, 600); _vm->_video->drawVideoFrame(_cabinGaugeMovie, frame); + + _cabinGaugeMovieEnabled = true; } void Myst::o_rocketSliders_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Rocket sliders init", op); - _rocketSlider1 = static_cast<MystResourceType10 *>(_vm->_resources[argv[0]]); - _rocketSlider2 = static_cast<MystResourceType10 *>(_vm->_resources[argv[1]]); - _rocketSlider3 = static_cast<MystResourceType10 *>(_vm->_resources[argv[2]]); - _rocketSlider4 = static_cast<MystResourceType10 *>(_vm->_resources[argv[3]]); - _rocketSlider5 = static_cast<MystResourceType10 *>(_vm->_resources[argv[4]]); + _rocketSlider1 = _vm->getViewResource<MystAreaSlider>(argv[0]); + _rocketSlider2 = _vm->getViewResource<MystAreaSlider>(argv[1]); + _rocketSlider3 = _vm->getViewResource<MystAreaSlider>(argv[2]); + _rocketSlider4 = _vm->getViewResource<MystAreaSlider>(argv[3]); + _rocketSlider5 = _vm->getViewResource<MystAreaSlider>(argv[4]); // Initialize sliders position for (uint i = 0; i < 5; i++) @@ -3828,13 +3831,13 @@ void Myst::o_bookAddSpecialPage_exit(uint16 op, uint16 var, uint16 argc, uint16 void Myst::o_treeCard_exit(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Exit tree card", op); - _tree = 0; + _tree = nullptr; } void Myst::o_treeEntry_exit(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Exit tree card with entry", op); - _treeAlcove = 0; + _treeAlcove = nullptr; } void Myst::o_boiler_exit(uint16 op, uint16 var, uint16 argc, uint16 *argv) { @@ -3842,6 +3845,8 @@ void Myst::o_boiler_exit(uint16 op, uint16 var, uint16 argc, uint16 *argv) { _cabinGaugeMovie = VideoHandle(); _cabinFireMovie = VideoHandle(); + + _cabinGaugeMovieEnabled = false; } void Myst::o_generatorControlRoom_exit(uint16 op, uint16 var, uint16 argc, uint16 *argv) { diff --git a/engines/mohawk/myst_stacks/myst.h b/engines/mohawk/myst_stacks/myst.h index a83609f640..6e2f7cc1c8 100644 --- a/engines/mohawk/myst_stacks/myst.h +++ b/engines/mohawk/myst_stacks/myst.h @@ -40,21 +40,20 @@ public: Myst(MohawkEngine_Myst *vm); ~Myst(); - virtual void disablePersistentScripts(); - virtual void runPersistentScripts(); + virtual void disablePersistentScripts() override; + virtual void runPersistentScripts() override; protected: void setupOpcodes(); - uint16 getVar(uint16 var); - void toggleVar(uint16 var); - bool setVarValue(uint16 var, uint16 value); + uint16 getVar(uint16 var) override; + void toggleVar(uint16 var) override; + bool setVarValue(uint16 var, uint16 value) override; - virtual uint16 getMap() { return 9934; } + virtual uint16 getMap() override { return 9934; } void towerRotationMap_run(); virtual void libraryBookcaseTransform_run(); void generatorControlRoom_run(); - void opcode_212_run(); void libraryCombinationBook_run(); void clockWheel_run(); void matchBurn_run(); @@ -192,11 +191,11 @@ protected: bool _generatorControlRoomRunning; uint16 _generatorVoltage; // 58 - MystResourceType10 *_rocketSlider1; // 248 - MystResourceType10 *_rocketSlider2; // 252 - MystResourceType10 *_rocketSlider3; // 256 - MystResourceType10 *_rocketSlider4; // 260 - MystResourceType10 *_rocketSlider5; // 264 + MystAreaSlider *_rocketSlider1; // 248 + MystAreaSlider *_rocketSlider2; // 252 + MystAreaSlider *_rocketSlider3; // 256 + MystAreaSlider *_rocketSlider4; // 260 + MystAreaSlider *_rocketSlider5; // 264 uint16 _rocketSliderSound; // 294 uint16 _rocketLeverPosition; // 296 VideoHandle _rocketLinkBook; @@ -214,7 +213,7 @@ protected: uint32 _gullsNextTime; // 216 bool _libraryBookcaseMoving; - MystResourceType6 *_libraryBookcaseMovie; // 104 + MystAreaVideo *_libraryBookcaseMovie; // 104 uint16 _libraryBookcaseSoundId; // 284 bool _libraryBookcaseChanged; // 288 uint16 _libraryBookSound1; // 298 @@ -223,13 +222,13 @@ protected: uint16 _courtyardBoxSound; // 302 bool _imagerValidationRunning; - MystResourceType8 *_imagerRedButton; // 304 + MystAreaImageSwitch *_imagerRedButton; // 304 uint16 _imagerSound[4]; // 308 to 314 uint16 _imagerValidationCard; // 316 uint16 _imagerValidationStep; // 318 bool _imagerRunning; - MystResourceType6 *_imagerMovie; // 64 + MystAreaVideo *_imagerMovie; // 64 uint16 _fireplaceLines[6]; // 74 to 84 @@ -248,8 +247,8 @@ protected: bool _towerRotationBlinkLabel; uint16 _towerRotationBlinkLabelCount; uint16 _towerRotationMapInitialized; // 292 - MystResourceType11 *_towerRotationMapTower; // 108 - MystResourceType8 *_towerRotationMapLabel; // 112 + MystAreaImageSwitch *_towerRotationMapTower; // 108 + MystAreaImageSwitch *_towerRotationMapLabel; // 112 uint16 _towerRotationSpeed; // 124 bool _towerRotationMapClicked; // 132 bool _towerRotationOverSpot; // 136 @@ -257,10 +256,13 @@ protected: bool _matchBurning; uint16 _matchGoOutCnt; uint16 _cabinDoorOpened; // 56 + uint16 _cabinHandleDown; // 344 uint16 _cabinMatchState; // 60 uint32 _matchGoOutTime; // 144 VideoHandle _cabinFireMovie; // 240 + + bool _cabinGaugeMovieEnabled; VideoHandle _cabinGaugeMovie; // 244 bool _boilerPressureIncreasing; @@ -269,8 +271,8 @@ protected: bool _basementPressureDecreasing; bool _treeStopped; // 236 - MystResourceType8 *_tree; // 220 - MystResourceType5 *_treeAlcove; // 224 + MystAreaImageSwitch *_tree; // 220 + MystArea *_treeAlcove; // 224 uint16 _treeMinPosition; // 228 uint16 _treeMinAccessiblePosition; // 230 uint16 _treeMaxAccessiblePosition; // 232 @@ -280,21 +282,21 @@ protected: bool _observatoryDayChanging; bool _observatoryYearChanging; bool _observatoryTimeChanging; - MystResourceType8 *_observatoryVisualizer; // 184 - MystResourceType8 *_observatoryGoButton; // 188 - MystResourceType10 *_observatoryDaySlider; // 192 - MystResourceType10 *_observatoryMonthSlider; // 196 - MystResourceType10 *_observatoryYearSlider; // 200 - MystResourceType10 *_observatoryTimeSlider; // 204 + MystAreaImageSwitch *_observatoryVisualizer; // 184 + MystAreaImageSwitch *_observatoryGoButton; // 188 + MystAreaSlider *_observatoryDaySlider; // 192 + MystAreaSlider *_observatoryMonthSlider; // 196 + MystAreaSlider *_observatoryYearSlider; // 200 + MystAreaSlider *_observatoryTimeSlider; // 204 uint32 _observatoryLastTime; // 208 bool _observatoryNotInitialized; // 212 int16 _observatoryIncrement; // 346 - MystResourceType10 *_observatoryCurrentSlider; // 348 + MystAreaSlider *_observatoryCurrentSlider; // 348 bool _greenBookRunning; void generatorRedrawRocket(); - void generatorButtonValue(MystResource *button, uint16 &offset, uint16 &value); + void generatorButtonValue(MystArea *button, uint16 &offset, uint16 &value); void rocketSliderMove(); uint16 rocketSliderGetSound(uint16 pos); diff --git a/engines/mohawk/myst_stacks/preview.cpp b/engines/mohawk/myst_stacks/preview.cpp index 4cae4aaca7..42458758f5 100644 --- a/engines/mohawk/myst_stacks/preview.cpp +++ b/engines/mohawk/myst_stacks/preview.cpp @@ -239,13 +239,13 @@ void Preview::o_library_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Library init", op); // Used for Card 3002 (Myst Island Overview) - _library = static_cast<MystResourceType8 *>(_invokingResource); + _library = getInvokingResource<MystAreaImageSwitch>(); } void Preview::o_libraryBookcaseTransformDemo_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { if (_libraryBookcaseChanged) { - MystResourceType7 *resource = static_cast<MystResourceType7 *>(_invokingResource); - _libraryBookcaseMovie = static_cast<MystResourceType6 *>(resource->getSubResource(getVar(303))); + MystAreaActionSwitch *resource = getInvokingResource<MystAreaActionSwitch>(); + _libraryBookcaseMovie = static_cast<MystAreaVideo *>(resource->getSubResource(getVar(303))); _libraryBookcaseSoundId = argv[0]; _libraryBookcaseMoving = true; } diff --git a/engines/mohawk/myst_stacks/preview.h b/engines/mohawk/myst_stacks/preview.h index 0959e935f5..9d833b35e2 100644 --- a/engines/mohawk/myst_stacks/preview.h +++ b/engines/mohawk/myst_stacks/preview.h @@ -40,8 +40,8 @@ public: Preview(MohawkEngine_Myst *vm); ~Preview(); - void disablePersistentScripts(); - void runPersistentScripts(); + void disablePersistentScripts() override; + void runPersistentScripts() override; private: void setupOpcodes(); @@ -56,7 +56,7 @@ private: DECLARE_OPCODE(o_library_init); uint16 _libraryState; // 4 - MystResourceType8 *_library; // 32 + MystAreaImageSwitch *_library; // 32 bool _speechRunning; uint _speechStep; @@ -67,7 +67,7 @@ private: void speech_run(); void speechUpdateCue(); - void libraryBookcaseTransform_run(); + void libraryBookcaseTransform_run() override; }; } // End of namespace MystStacks diff --git a/engines/mohawk/myst_stacks/selenitic.cpp b/engines/mohawk/myst_stacks/selenitic.cpp index 8b95c7fa53..5402e5a581 100644 --- a/engines/mohawk/myst_stacks/selenitic.cpp +++ b/engines/mohawk/myst_stacks/selenitic.cpp @@ -39,9 +39,12 @@ namespace MystStacks { Selenitic::Selenitic(MohawkEngine_Myst *vm) : MystScriptParser(vm), _state(vm->_gameState->_selenitic) { setupOpcodes(); - _invokingResource = NULL; _mazeRunnerPosition = 288; _mazeRunnerDirection = 8; + _mazeRunnerDoorOpened = false; + + _soundReceiverDirection = 0; + _soundReceiverStartTime = 0; } Selenitic::~Selenitic() { @@ -669,14 +672,20 @@ void Selenitic::soundReceiverUpdate() { } void Selenitic::soundReceiverDrawView() { + soundReceiverSetSubimageRect(); + soundReceiverDrawAngle(); +} + +void Selenitic::soundReceiverSetSubimageRect() const { uint32 left = ((*_soundReceiverPosition) * 1800) / 3600; - _soundReceiverViewer->_subImages->rect.left = left; - _soundReceiverViewer->_subImages->rect.right = left + 136; + Common::Rect rect = _soundReceiverViewer->getSubImage(0).rect; - _soundReceiverViewer->drawConditionalDataToScreen(0); + rect.left = left; + rect.right = left + 136; - soundReceiverDrawAngle(); + _soundReceiverViewer->setSubImageRect(0, rect); + _soundReceiverViewer->drawConditionalDataToScreen(0); } void Selenitic::soundReceiverDrawAngle() { @@ -770,7 +779,7 @@ uint16 Selenitic::soundLockCurrentSound(uint16 position, bool pixels) { return 0; } -MystResourceType10 *Selenitic::soundLockSliderFromVar(uint16 var) { +MystAreaSlider *Selenitic::soundLockSliderFromVar(uint16 var) { switch (var) { case 20: return _soundLockSlider1; @@ -784,13 +793,13 @@ MystResourceType10 *Selenitic::soundLockSliderFromVar(uint16 var) { return _soundLockSlider5; } - return 0; + return nullptr; } void Selenitic::o_soundLockMove(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Sound lock move", op); - MystResourceType10 *slider = soundLockSliderFromVar(var); + MystAreaSlider *slider = soundLockSliderFromVar(var); uint16 soundId = soundLockCurrentSound(slider->_pos.y, true); if (_soundLockSoundId != soundId) { @@ -802,7 +811,7 @@ void Selenitic::o_soundLockMove(uint16 op, uint16 var, uint16 argc, uint16 *argv void Selenitic::o_soundLockStartMove(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Sound lock start move", op); - MystResourceType10 *slider = soundLockSliderFromVar(var); + MystAreaSlider *slider = soundLockSliderFromVar(var); _vm->_cursor->setCursor(700); _vm->_sound->pauseBackgroundMyst(); @@ -814,7 +823,7 @@ void Selenitic::o_soundLockStartMove(uint16 op, uint16 var, uint16 argc, uint16 void Selenitic::o_soundLockEndMove(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Sound lock end move", op); - MystResourceType10 *slider = soundLockSliderFromVar(var); + MystAreaSlider *slider = soundLockSliderFromVar(var); uint16 *value = &_state.soundLockSliderPositions[0]; switch (var) { @@ -858,7 +867,7 @@ void Selenitic::o_soundLockEndMove(uint16 op, uint16 var, uint16 argc, uint16 *a _vm->_sound->resumeBackgroundMyst(); } -void Selenitic::soundLockCheckSolution(MystResourceType10 *slider, uint16 value, uint16 solution, bool &solved) { +void Selenitic::soundLockCheckSolution(MystAreaSlider *slider, uint16 value, uint16 solution, bool &solved) { slider->drawConditionalDataToScreen(2); _vm->_sound->replaceSoundMyst(soundLockCurrentSound(value / 12, false)); _vm->_system->delayMillis(1500); @@ -926,15 +935,15 @@ void Selenitic::o_soundReceiverEndMove(uint16 op, uint16 var, uint16 argc, uint1 } void Selenitic::o_mazeRunnerCompass_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { - _mazeRunnerCompass = static_cast<MystResourceType8 *>(_invokingResource); + _mazeRunnerCompass = getInvokingResource<MystAreaImageSwitch>(); } void Selenitic::o_mazeRunnerWindow_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { - _mazeRunnerWindow = static_cast<MystResourceType8 *>(_invokingResource); + _mazeRunnerWindow = getInvokingResource<MystAreaImageSwitch>(); } void Selenitic::o_mazeRunnerLight_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { - _mazeRunnerLight = static_cast<MystResourceType8 *>(_invokingResource); + _mazeRunnerLight = getInvokingResource<MystAreaImageSwitch>(); } void Selenitic::soundReceiver_run() { @@ -942,10 +951,13 @@ void Selenitic::soundReceiver_run() { if (_soundReceiverDirection) { uint32 currentTime = _vm->_system->getMillis(); - if (_soundReceiverSpeed == 50 && currentTime > _soundReceiverStartTime + 500) - soundReceiverIncreaseSpeed(); - else if (currentTime > _soundReceiverStartTime + 1000) - soundReceiverIncreaseSpeed(); + if (_soundReceiverSpeed == 50 && currentTime > _soundReceiverStartTime + 500) { + soundReceiverIncreaseSpeed(); + _soundReceiverStartTime = currentTime; + } else if (currentTime > _soundReceiverStartTime + 1000) { + soundReceiverIncreaseSpeed(); + _soundReceiverStartTime = currentTime; + } if (currentTime > _soundReceiverStartTime + 100) soundReceiverUpdate(); @@ -1056,24 +1068,26 @@ void Selenitic::o_soundReceiver_init(uint16 op, uint16 var, uint16 argc, uint16 // Used for Card 1245 (Sound Receiver) _soundReceiverRunning = true; - _soundReceiverRightButton = static_cast<MystResourceType8 *>(_vm->_resources[0]); - _soundReceiverLeftButton = static_cast<MystResourceType8 *>(_vm->_resources[1]); - _soundReceiverSigmaButton = static_cast<MystResourceType8 *>(_vm->_resources[2]); - _soundReceiverSources[4] = static_cast<MystResourceType8 *>(_vm->_resources[3]); - _soundReceiverSources[3] = static_cast<MystResourceType8 *>(_vm->_resources[4]); - _soundReceiverSources[2] = static_cast<MystResourceType8 *>(_vm->_resources[5]); - _soundReceiverSources[1] = static_cast<MystResourceType8 *>(_vm->_resources[6]); - _soundReceiverSources[0] = static_cast<MystResourceType8 *>(_vm->_resources[7]); - _soundReceiverViewer = static_cast<MystResourceType8 *>(_vm->_resources[8]); - _soundReceiverAngle1 = static_cast<MystResourceType8 *>(_vm->_resources[10]); - _soundReceiverAngle2 = static_cast<MystResourceType8 *>(_vm->_resources[11]); - _soundReceiverAngle3 = static_cast<MystResourceType8 *>(_vm->_resources[12]); - _soundReceiverAngle4 = static_cast<MystResourceType8 *>(_vm->_resources[13]); + _soundReceiverRightButton = _vm->getViewResource<MystAreaImageSwitch>(0); + _soundReceiverLeftButton = _vm->getViewResource<MystAreaImageSwitch>(1); + _soundReceiverSigmaButton = _vm->getViewResource<MystAreaImageSwitch>(2); + _soundReceiverSources[4] = _vm->getViewResource<MystAreaImageSwitch>(3); + _soundReceiverSources[3] = _vm->getViewResource<MystAreaImageSwitch>(4); + _soundReceiverSources[2] = _vm->getViewResource<MystAreaImageSwitch>(5); + _soundReceiverSources[1] = _vm->getViewResource<MystAreaImageSwitch>(6); + _soundReceiverSources[0] = _vm->getViewResource<MystAreaImageSwitch>(7); + _soundReceiverViewer = _vm->getViewResource<MystAreaImageSwitch>(8); + _soundReceiverAngle1 = _vm->getViewResource<MystAreaImageSwitch>(10); + _soundReceiverAngle2 = _vm->getViewResource<MystAreaImageSwitch>(11); + _soundReceiverAngle3 = _vm->getViewResource<MystAreaImageSwitch>(12); + _soundReceiverAngle4 = _vm->getViewResource<MystAreaImageSwitch>(13); uint16 currentSource = _state.soundReceiverCurrentSource; _soundReceiverPosition = &_state.soundReceiverPositions[currentSource]; _soundReceiverCurrentSource = _soundReceiverSources[currentSource]; + soundReceiverSetSubimageRect(); + _soundReceiverSigmaPressed = false; } @@ -1081,31 +1095,31 @@ void Selenitic::o_soundLock_init(uint16 op, uint16 var, uint16 argc, uint16 *arg debugC(kDebugScript, "Opcode %d: Sound lock init", op); for (uint i = 0; i < _vm->_resources.size(); i++) { - if (_vm->_resources[i]->type == kMystSlider) { - switch (_vm->_resources[i]->getType8Var()) { + if (_vm->_resources[i]->type == kMystAreaSlider) { + switch (_vm->_resources[i]->getImageSwitchVar()) { case 20: - _soundLockSlider1 = static_cast<MystResourceType10 *>(_vm->_resources[i]); + _soundLockSlider1 = _vm->getViewResource<MystAreaSlider>(i); _soundLockSlider1->setStep(_state.soundLockSliderPositions[0]); break; case 21: - _soundLockSlider2 = static_cast<MystResourceType10 *>(_vm->_resources[i]); + _soundLockSlider2 = _vm->getViewResource<MystAreaSlider>(i); _soundLockSlider2->setStep(_state.soundLockSliderPositions[1]); break; case 22: - _soundLockSlider3 = static_cast<MystResourceType10 *>(_vm->_resources[i]); + _soundLockSlider3 = _vm->getViewResource<MystAreaSlider>(i); _soundLockSlider3->setStep(_state.soundLockSliderPositions[2]); break; case 23: - _soundLockSlider4 = static_cast<MystResourceType10 *>(_vm->_resources[i]); + _soundLockSlider4 = _vm->getViewResource<MystAreaSlider>(i); _soundLockSlider4->setStep(_state.soundLockSliderPositions[3]); break; case 24: - _soundLockSlider5 = static_cast<MystResourceType10 *>(_vm->_resources[i]); + _soundLockSlider5 = _vm->getViewResource<MystAreaSlider>(i); _soundLockSlider5->setStep(_state.soundLockSliderPositions[4]); break; } - } else if (_vm->_resources[i]->type == kMystConditionalImage && _vm->_resources[i]->getType8Var() == 28) { - _soundLockButton = static_cast<MystResourceType8 *>(_vm->_resources[i]); + } else if (_vm->_resources[i]->type == kMystAreaImageSwitch && _vm->_resources[i]->getImageSwitchVar() == 28) { + _soundLockButton = _vm->getViewResource<MystAreaImageSwitch>(i); } } @@ -1113,11 +1127,11 @@ void Selenitic::o_soundLock_init(uint16 op, uint16 var, uint16 argc, uint16 *arg } void Selenitic::o_mazeRunnerRight_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { - _mazeRunnerRightButton = static_cast<MystResourceType8 *>(_invokingResource); + _mazeRunnerRightButton = getInvokingResource<MystAreaImageSwitch>(); } void Selenitic::o_mazeRunnerLeft_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { - _mazeRunnerLeftButton = static_cast<MystResourceType8 *>(_invokingResource); + _mazeRunnerLeftButton = getInvokingResource<MystAreaImageSwitch>(); } const uint16 Selenitic::_mazeRunnerMap[300][4] = { diff --git a/engines/mohawk/myst_stacks/selenitic.h b/engines/mohawk/myst_stacks/selenitic.h index c669d01012..fc9649755d 100644 --- a/engines/mohawk/myst_stacks/selenitic.h +++ b/engines/mohawk/myst_stacks/selenitic.h @@ -29,7 +29,7 @@ namespace Mohawk { -class MystResourceType8; +class MystAreaImageSwitch; struct MystScriptEntry; namespace MystStacks { @@ -41,16 +41,16 @@ public: Selenitic(MohawkEngine_Myst *vm); ~Selenitic(); - void disablePersistentScripts(); - void runPersistentScripts(); + void disablePersistentScripts() override; + void runPersistentScripts() override; private: void setupOpcodes(); - uint16 getVar(uint16 var); - void toggleVar(uint16 var); - bool setVarValue(uint16 var, uint16 value); + uint16 getVar(uint16 var) override; + void toggleVar(uint16 var) override; + bool setVarValue(uint16 var, uint16 value) override; - virtual uint16 getMap() { return 9930; } + virtual uint16 getMap() override { return 9930; } DECLARE_OPCODE(o_mazeRunnerMove); DECLARE_OPCODE(o_mazeRunnerSoundRepeat); @@ -80,43 +80,44 @@ private: bool _soundReceiverRunning; bool _soundReceiverSigmaPressed; // 6 - MystResourceType8 *_soundReceiverSources[5]; // 92 -> 108 - MystResourceType8 *_soundReceiverCurrentSource; // 112 + MystAreaImageSwitch *_soundReceiverSources[5]; // 92 -> 108 + MystAreaImageSwitch *_soundReceiverCurrentSource; // 112 uint16 *_soundReceiverPosition; // 116 uint16 _soundReceiverDirection; // 120 uint16 _soundReceiverSpeed; // 122 uint32 _soundReceiverStartTime; //124 - MystResourceType8 *_soundReceiverViewer; // 128 - MystResourceType8 *_soundReceiverRightButton; // 132 - MystResourceType8 *_soundReceiverLeftButton; // 136 - MystResourceType8 *_soundReceiverAngle1; // 140 - MystResourceType8 *_soundReceiverAngle2; // 144 - MystResourceType8 *_soundReceiverAngle3; // 148 - MystResourceType8 *_soundReceiverAngle4; // 152 - MystResourceType8 *_soundReceiverSigmaButton; // 156 + MystAreaImageSwitch *_soundReceiverViewer; // 128 + MystAreaImageSwitch *_soundReceiverRightButton; // 132 + MystAreaImageSwitch *_soundReceiverLeftButton; // 136 + MystAreaImageSwitch *_soundReceiverAngle1; // 140 + MystAreaImageSwitch *_soundReceiverAngle2; // 144 + MystAreaImageSwitch *_soundReceiverAngle3; // 148 + MystAreaImageSwitch *_soundReceiverAngle4; // 152 + MystAreaImageSwitch *_soundReceiverSigmaButton; // 156 static const uint16 _mazeRunnerMap[300][4]; static const uint8 _mazeRunnerVideos[300][4]; uint16 _mazeRunnerPosition; // 56 uint16 _mazeRunnerDirection; // 58 - MystResourceType8 *_mazeRunnerWindow; // 68 - MystResourceType8 *_mazeRunnerCompass; // 72 - MystResourceType8 *_mazeRunnerLight; // 76 - MystResourceType8 *_mazeRunnerRightButton; // 80 - MystResourceType8 *_mazeRunnerLeftButton; // 84 + MystAreaImageSwitch *_mazeRunnerWindow; // 68 + MystAreaImageSwitch *_mazeRunnerCompass; // 72 + MystAreaImageSwitch *_mazeRunnerLight; // 76 + MystAreaImageSwitch *_mazeRunnerRightButton; // 80 + MystAreaImageSwitch *_mazeRunnerLeftButton; // 84 bool _mazeRunnerDoorOpened; // 160 uint16 _soundLockSoundId; - MystResourceType10 *_soundLockSlider1; // 164 - MystResourceType10 *_soundLockSlider2; // 168 - MystResourceType10 *_soundLockSlider3; // 172 - MystResourceType10 *_soundLockSlider4; // 176 - MystResourceType10 *_soundLockSlider5; // 180 - MystResourceType8 *_soundLockButton; // 184 + MystAreaSlider *_soundLockSlider1; // 164 + MystAreaSlider *_soundLockSlider2; // 168 + MystAreaSlider *_soundLockSlider3; // 172 + MystAreaSlider *_soundLockSlider4; // 176 + MystAreaSlider *_soundLockSlider5; // 180 + MystAreaImageSwitch *_soundLockButton; // 184 void soundReceiverLeftRight(uint direction); void soundReceiverUpdate(); + void soundReceiverSetSubimageRect() const; void soundReceiverDrawView(); void soundReceiverDrawAngle(); void soundReceiverIncreaseSpeed(); @@ -125,8 +126,8 @@ private: void soundReceiverSolution(uint16 source, uint16 &solution, bool &enabled); uint16 soundLockCurrentSound(uint16 position, bool pixels); - MystResourceType10 *soundLockSliderFromVar(uint16 var); - void soundLockCheckSolution(MystResourceType10 *slider, uint16 value, uint16 solution, bool &solved); + MystAreaSlider *soundLockSliderFromVar(uint16 var); + void soundLockCheckSolution(MystAreaSlider *slider, uint16 value, uint16 solution, bool &solved); bool mazeRunnerForwardAllowed(uint16 position); void mazeRunnerUpdateCompass(); diff --git a/engines/mohawk/myst_stacks/slides.h b/engines/mohawk/myst_stacks/slides.h index fb7868a03c..a0c9ae5821 100644 --- a/engines/mohawk/myst_stacks/slides.h +++ b/engines/mohawk/myst_stacks/slides.h @@ -40,8 +40,8 @@ public: Slides(MohawkEngine_Myst *vm); ~Slides(); - void disablePersistentScripts(); - void runPersistentScripts(); + void disablePersistentScripts() override; + void runPersistentScripts() override; private: void setupOpcodes(); diff --git a/engines/mohawk/myst_stacks/stoneship.cpp b/engines/mohawk/myst_stacks/stoneship.cpp index 1113ceeac9..293c0f96f4 100644 --- a/engines/mohawk/myst_stacks/stoneship.cpp +++ b/engines/mohawk/myst_stacks/stoneship.cpp @@ -50,6 +50,8 @@ Stoneship::Stoneship(MohawkEngine_Myst *vm) : _chestDrawersOpen = 0; _chestAchenarBottomDrawerClosed = 1; + _brotherDoorOpen = 0; + // Drop key if (_state.trapdoorKeyState == 1) _state.trapdoorKeyState = 2; @@ -402,9 +404,9 @@ void Stoneship::o_pumpTurnOff(uint16 op, uint16 var, uint16 argc, uint16 *argv) } for (uint i = 0; i < _vm->_resources.size(); i++) { - MystResource *resource = _vm->_resources[i]; - if (resource->type == kMystConditionalImage && resource->getType8Var() == buttonVar) { - static_cast<MystResourceType8 *>(resource)->drawConditionalDataToScreen(0, true); + MystArea *resource = _vm->_resources[i]; + if (resource->type == kMystAreaImageSwitch && resource->getImageSwitchVar() == buttonVar) { + static_cast<MystAreaImageSwitch *>(resource)->drawConditionalDataToScreen(0, true); break; } } @@ -437,9 +439,9 @@ void Stoneship::o_cabinBookMovie(uint16 op, uint16 var, uint16 argc, uint16 *arg void Stoneship::o_drawerOpenSirius(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Open drawer", op); - MystResourceType8 *drawer = static_cast<MystResourceType8 *>(_vm->_resources[argv[0]]); + MystAreaImageSwitch *drawer = _vm->getViewResource<MystAreaImageSwitch>(argv[0]); - if (drawer->getType8Var() == 35) { + if (drawer->getImageSwitchVar() == 35) { drawer->drawConditionalDataToScreen(getVar(102), 0); } else { drawer->drawConditionalDataToScreen(0, 0); @@ -466,7 +468,7 @@ void Stoneship::o_telescopeStart(uint16 op, uint16 var, uint16 argc, uint16 *arg void Stoneship::o_telescopeMove(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Telescope move", op); - MystResourceType11 *display = static_cast<MystResourceType11 *>(_invokingResource); + MystAreaDrag *display = getInvokingResource<MystAreaDrag>(); const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos(); // Compute telescope position @@ -489,7 +491,7 @@ void Stoneship::o_telescopeStop(uint16 op, uint16 var, uint16 argc, uint16 *argv void Stoneship::o_generatorStart(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Generator start", op); - MystResourceType11 *handle = static_cast<MystResourceType11 *>(_invokingResource); + MystAreaDrag *handle = getInvokingResource<MystAreaDrag>(); uint16 soundId = handle->getList1(0); if (soundId) @@ -504,7 +506,7 @@ void Stoneship::o_generatorStart(uint16 op, uint16 var, uint16 argc, uint16 *arg _batteryNextTime = _vm->_system->getMillis() + 1000; // Start handle movie - MystResourceType6 *movie = static_cast<MystResourceType6 *>(handle->getSubResource(0)); + MystAreaVideo *movie = static_cast<MystAreaVideo *>(handle->getSubResource(0)); movie->playMovie(); soundId = handle->getList2(0); @@ -530,8 +532,8 @@ void Stoneship::o_generatorStop(uint16 op, uint16 var, uint16 argc, uint16 *argv } // Pause handle movie - MystResourceType11 *handle = static_cast<MystResourceType11 *>(_invokingResource); - MystResourceType6 *movie = static_cast<MystResourceType6 *>(handle->getSubResource(0)); + MystAreaDrag *handle = getInvokingResource<MystAreaDrag>(); + MystAreaVideo *movie = static_cast<MystAreaVideo *>(handle->getSubResource(0)); movie->pauseMovie(true); uint16 soundId = handle->getList3(0); @@ -582,7 +584,7 @@ void Stoneship::batteryDeplete_run() { void Stoneship::o_drawerOpenAchenar(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Open drawer", op); - MystResourceType8 *drawer = static_cast<MystResourceType8 *>(_vm->_resources[argv[0]]); + MystAreaImageSwitch *drawer = _vm->getViewResource<MystAreaImageSwitch>(argv[0]); drawer->drawConditionalDataToScreen(0, 0); _vm->_gfx->runTransition(kTransitionTopToBottom, drawer->getRect(), 25, 5); } @@ -617,7 +619,7 @@ void Stoneship::o_hologramSelectionStart(uint16 op, uint16 var, uint16 argc, uin void Stoneship::o_hologramSelectionMove(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Hologram move", op); - MystResourceType11 *handle = static_cast<MystResourceType11 *>(_invokingResource); + MystAreaDrag *handle = getInvokingResource<MystAreaDrag>(); const Common::Point &mouse = _vm->_system->getEventManager()->getMousePos(); if (handle->getRect().contains(mouse)) { @@ -806,7 +808,7 @@ void Stoneship::o_cloudOrbLeave(uint16 op, uint16 var, uint16 argc, uint16 *argv _cloudOrbMovie->pauseMovie(true); _vm->_sound->replaceSoundMyst(_cloudOrbStopSound); - _vm->_gfx->runTransition(kTransitionTopToBottom, _invokingResource->getRect(), 4, 0); + _vm->_gfx->runTransition(kTransitionTopToBottom, getInvokingResource<MystArea>()->getRect(), 4, 0); } void Stoneship::o_drawerCloseOpened(uint16 op, uint16 var, uint16 argc, uint16 *argv) { @@ -822,20 +824,20 @@ void Stoneship::drawerClose(uint16 drawer) { _vm->drawCardBackground(); _vm->drawResourceImages(); - MystResource *res = _vm->_resources[drawer]; + MystArea *res = _vm->_resources[drawer]; _vm->_gfx->runTransition(kTransitionBottomToTop, res->getRect(), 25, 5); } void Stoneship::o_hologramDisplay_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Hologram display init", op); - _hologramDisplay = static_cast<MystResourceType6 *>(_invokingResource); + _hologramDisplay = getInvokingResource<MystAreaVideo>(); _hologramDisplayPos = 0; } void Stoneship::o_hologramSelection_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Hologram selection init", op); - _hologramSelection = static_cast<MystResourceType6 *>(_invokingResource); + _hologramSelection = getInvokingResource<MystAreaVideo>(); } void Stoneship::batteryGaugeUpdate() { @@ -856,7 +858,7 @@ void Stoneship::o_battery_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) // Used for Card 2160 (Lighthouse Battery Pack Closeup) debugC(kDebugScript, "Opcode %d: Battery init", op); - _batteryGauge = static_cast<MystResourceType8 *>(_invokingResource); + _batteryGauge = getInvokingResource<MystAreaImageSwitch>(); batteryGaugeUpdate(); } @@ -1014,7 +1016,7 @@ void Stoneship::o_achenarDrawers_init(uint16 op, uint16 var, uint16 argc, uint16 void Stoneship::o_cloudOrb_init(uint16 op, uint16 var, uint16 argc, uint16 *argv) { debugC(kDebugScript, "Opcode %d: Cloud orb init", op); - _cloudOrbMovie = static_cast<MystResourceType6 *>(_invokingResource); + _cloudOrbMovie = getInvokingResource<MystAreaVideo>(); _cloudOrbSound = argv[0]; _cloudOrbStopSound = argv[1]; } diff --git a/engines/mohawk/myst_stacks/stoneship.h b/engines/mohawk/myst_stacks/stoneship.h index 4e1b42f26b..776641a787 100644 --- a/engines/mohawk/myst_stacks/stoneship.h +++ b/engines/mohawk/myst_stacks/stoneship.h @@ -40,16 +40,16 @@ public: Stoneship(MohawkEngine_Myst *vm); ~Stoneship(); - void disablePersistentScripts(); - void runPersistentScripts(); + void disablePersistentScripts() override; + void runPersistentScripts() override; private: void setupOpcodes(); - uint16 getVar(uint16 var); - void toggleVar(uint16 var); - bool setVarValue(uint16 var, uint16 value); + uint16 getVar(uint16 var) override; + void toggleVar(uint16 var) override; + bool setVarValue(uint16 var, uint16 value) override; - virtual uint16 getMap() { return 9933; } + virtual uint16 getMap() override { return 9933; } DECLARE_OPCODE(o_pumpTurnOff); DECLARE_OPCODE(o_brotherDoorOpen); @@ -98,7 +98,7 @@ private: bool _batteryGaugeRunning; uint16 _batteryLastCharge; // 92 - MystResourceType8 *_batteryGauge; // 96 + MystAreaImageSwitch *_batteryGauge; // 96 void batteryGaugeUpdate(); void batteryGauge_run(); @@ -113,8 +113,8 @@ private: void drawerClose(uint16 drawer); uint16 _hologramTurnedOn; // 80 - MystResourceType6 *_hologramDisplay; // 84 - MystResourceType6 *_hologramSelection; // 88 + MystAreaVideo *_hologramDisplay; // 84 + MystAreaVideo *_hologramSelection; // 88 uint16 _hologramDisplayPos; bool _tunnelRunning; @@ -135,7 +135,7 @@ private: void telescope_run(); void telescopeLighthouseDraw(); - MystResourceType6 *_cloudOrbMovie; // 136 + MystAreaVideo *_cloudOrbMovie; // 136 uint16 _cloudOrbSound; // 140 uint16 _cloudOrbStopSound; // 142 diff --git a/engines/mohawk/myst_state.cpp b/engines/mohawk/myst_state.cpp index 3e54017df8..06cd69b23c 100644 --- a/engines/mohawk/myst_state.cpp +++ b/engines/mohawk/myst_state.cpp @@ -26,11 +26,41 @@ #include "common/debug.h" #include "common/serializer.h" +#include "common/system.h" #include "common/textconsole.h" #include "common/util.h" +#include "graphics/thumbnail.h" + namespace Mohawk { +MystSaveMetadata::MystSaveMetadata() { + saveDay = 0; + saveMonth = 0; + saveYear = 0; + saveHour = 0; + saveMinute = 0; + totalPlayTime = 0; +} + +bool MystSaveMetadata::sync(Common::Serializer &s) { + static const Common::Serializer::Version kCurrentVersion = 1; + + if (!s.syncVersion(kCurrentVersion)) { + return false; + } + + s.syncAsByte(saveDay); + s.syncAsByte(saveMonth); + s.syncAsUint16LE(saveYear); + s.syncAsByte(saveHour); + s.syncAsByte(saveMinute); + s.syncString(saveDescription); + s.syncAsUint32LE(totalPlayTime); + + return true; +} + MystGameState::MystGameState(MohawkEngine_Myst *vm, Common::SaveFileManager *saveFileMan) : _vm(vm), _saveFileMan(saveFileMan) { // Most of the variables are zero at game start. memset(&_globals, 0, sizeof(_globals)); @@ -77,28 +107,15 @@ MystGameState::~MystGameState() { } Common::StringArray MystGameState::generateSaveGameList() { - return _saveFileMan->listSavefiles("*.mys"); + return g_system->getSavefileManager()->listSavefiles("*.mys"); } bool MystGameState::load(const Common::String &filename) { - Common::InSaveFile *loadFile = _saveFileMan->openForLoading(filename); - if (!loadFile) - return false; - - debugC(kDebugSaveLoad, "Loading game from '%s'", filename.c_str()); - - // First, let's make sure we're using a saved game file from this version of Myst - // By checking length of file... - int32 size = loadFile->size(); - if (size != 664 && size != 601) { - warning("Incompatible saved game version"); - delete loadFile; + if (!loadState(filename)) { return false; } - Common::Serializer s(loadFile, 0); - syncGameState(s, size == 664); - delete loadFile; + loadMetadata(filename); // Set Channelwood elevator state to down, because we start on the lower level _channelwood.elevatorState = 0; @@ -119,19 +136,76 @@ bool MystGameState::load(const Common::String &filename) { return true; } -bool MystGameState::save(const Common::String &fname) { - Common::String filename(fname); - // Make sure we have the right extension - if (!filename.hasSuffix(".mys") && !filename.hasSuffix(".MYS")) - filename += ".mys"; +bool MystGameState::loadState(const Common::String &filename) { + Common::InSaveFile *loadFile = _saveFileMan->openForLoading(filename); + if (!loadFile) { + return false; + } + + debugC(kDebugSaveLoad, "Loading game from '%s'", filename.c_str()); + + // First, let's make sure we're using a saved game file from this version of Myst + // By checking length of file... + int32 size = loadFile->size(); + if (size != 664 && size != 601) { + warning("Incompatible saved game version"); + delete loadFile; + return false; + } + + Common::Serializer s(loadFile, nullptr); + syncGameState(s, size == 664); + delete loadFile; + + return true; +} + +void MystGameState::loadMetadata(const Common::String &filename) { + // Open the metadata file + Common::InSaveFile *metadataFile = openMetadataFile(filename); + if (!metadataFile) { + return; + } + + debugC(kDebugSaveLoad, "Loading metadata from '%s'", filename.c_str()); + + Common::Serializer m(metadataFile, nullptr); + + // Read the metadata file + if (_metadata.sync(m)) { + _vm->setTotalPlayTime(_metadata.totalPlayTime); + } + delete metadataFile; +} + +bool MystGameState::save(const Common::String &filename) { + // Make sure the description does not have an extension + Common::String desc = filename; + if (filename.hasSuffix(".mys") || filename.hasSuffix(".MYS")) { + desc = removeExtension(filename); + } + + if (!saveState(desc)) { + return false; + } + + updateMetadateForSaving(desc); + + return saveMetadata(desc); +} + +bool MystGameState::saveState(const Common::String &desc) { + // Make sure we have the right extension + Common::String filename = desc + ".mys"; Common::OutSaveFile *saveFile = _saveFileMan->openForSaving(filename); - if (!saveFile) + if (!saveFile) { return false; + } debugC(kDebugSaveLoad, "Saving game to '%s'", filename.c_str()); - Common::Serializer s(0, saveFile); + Common::Serializer s(nullptr, saveFile); syncGameState(s, _vm->getFeatures() & GF_ME); saveFile->finalize(); delete saveFile; @@ -139,6 +213,88 @@ bool MystGameState::save(const Common::String &fname) { return true; } +void MystGameState::updateMetadateForSaving(const Common::String &desc) { + // Update save creation info + TimeDate t; + g_system->getTimeAndDate(t); + _metadata.saveYear = t.tm_year + 1900; + _metadata.saveMonth = t.tm_mon + 1; + _metadata.saveDay = t.tm_mday; + _metadata.saveHour = t.tm_hour; + _metadata.saveMinute = t.tm_min; + _metadata.saveDescription = desc; + _metadata.totalPlayTime = _vm->getTotalPlayTime(); +} + +bool MystGameState::saveMetadata(const Common::String &desc) { + // Write the metadata to a separate file so that the save files + // are still compatible with the original engine + Common::String metadataFilename = desc + ".mym"; + Common::OutSaveFile *metadataFile = _saveFileMan->openForSaving(metadataFilename); + if (!metadataFile) { + return false; + } + + // Save the metadata + Common::Serializer m(nullptr, metadataFile); + _metadata.sync(m); + + // Append a thumbnail + Graphics::saveThumbnail(*metadataFile); + + metadataFile->finalize(); + delete metadataFile; + + return true; +} + +SaveStateDescriptor MystGameState::querySaveMetaInfos(const Common::String filename) { + SaveStateDescriptor desc; + desc.setDescription(filename); + + // Open the metadata file + Common::InSaveFile *metadataFile = openMetadataFile(filename); + if (!metadataFile) { + return desc; + } + + Common::Serializer m(metadataFile, nullptr); + + // Read the metadata file + Mohawk::MystSaveMetadata metadata; + if (!metadata.sync(m)) { + delete metadataFile; + return desc; + } + + // Set the save description + desc.setDescription(metadata.saveDescription); + desc.setSaveDate(metadata.saveYear, metadata.saveMonth, metadata.saveDay); + desc.setSaveTime(metadata.saveHour, metadata.saveMinute); + desc.setPlayTime(metadata.totalPlayTime); + desc.setThumbnail(Graphics::loadThumbnail(*metadataFile)); + + delete metadataFile; + + return desc; +} + +Common::InSaveFile *MystGameState::openMetadataFile(const Common::String &filename) { + // Remove the extension + Common::String baseName = removeExtension(filename); + + // Open the metadata file + return g_system->getSavefileManager()->openForLoading(baseName + ".mym"); +} + +Common::String MystGameState::removeExtension(const Common::String &filename) { + Common::String baseName = filename; + for (uint i = 0; i < 4; i++) { + baseName.deleteLastChar(); + } + return baseName; +} + void MystGameState::syncGameState(Common::Serializer &s, bool isME) { // Globals first s.syncAsUint16LE(_globals.u0); @@ -317,11 +473,14 @@ void MystGameState::syncGameState(Common::Serializer &s, bool isME) { void MystGameState::deleteSave(const Common::String &saveName) { debugC(kDebugSaveLoad, "Deleting save file \'%s\'", saveName.c_str()); - _saveFileMan->removeSavefile(saveName.c_str()); + Common::String basename = removeExtension(saveName); + + g_system->getSavefileManager()->removeSavefile(saveName); + g_system->getSavefileManager()->removeSavefile(basename + ".mym"); } void MystGameState::addZipDest(uint16 stack, uint16 view) { - ZipDests *zipDests = 0; + ZipDests *zipDests = nullptr; // The demo has no zip dest storage if (_vm->getFeatures() & GF_DEMO) diff --git a/engines/mohawk/myst_state.h b/engines/mohawk/myst_state.h index b07a0f2469..50359a5b52 100644 --- a/engines/mohawk/myst_state.h +++ b/engines/mohawk/myst_state.h @@ -27,6 +27,8 @@ #include "common/file.h" #include "common/str.h" +#include "engines/savestate.h" + namespace Common { class Serializer; } @@ -35,15 +37,33 @@ namespace Mohawk { class MohawkEngine_Myst; +struct MystSaveMetadata { + uint8 saveDay; + uint8 saveMonth; + uint16 saveYear; + + uint8 saveHour; + uint8 saveMinute; + + uint32 totalPlayTime; + + Common::String saveDescription; + + MystSaveMetadata(); + bool sync(Common::Serializer &s); +}; + class MystGameState { public: MystGameState(MohawkEngine_Myst*, Common::SaveFileManager*); ~MystGameState(); - Common::StringArray generateSaveGameList(); - bool load(const Common::String &); - bool save(const Common::String &); - void deleteSave(const Common::String &); + static Common::StringArray generateSaveGameList(); + static SaveStateDescriptor querySaveMetaInfos(const Common::String filename); + + bool load(const Common::String &filename); + bool save(const Common::String &filename); + static void deleteSave(const Common::String &saveName); void addZipDest(uint16 stack, uint16 view); bool isReachableZipDest(uint16 stack, uint16 view); @@ -268,8 +288,17 @@ public: uint32 generatorDepletionTime; } _stoneship; + MystSaveMetadata _metadata; + private: void syncGameState(Common::Serializer &s, bool isME); + static Common::InSaveFile *openMetadataFile(const Common::String &filename); + bool loadState(const Common::String &filename); + void loadMetadata(const Common::String &filename); + bool saveState(const Common::String &desc); + void updateMetadateForSaving(const Common::String &desc); + bool saveMetadata(const Common::String &desc); + static Common::String removeExtension(const Common::String &filename); // The values in these regions are lists of VIEW resources // which correspond to visited zip destinations diff --git a/engines/mohawk/sound.cpp b/engines/mohawk/sound.cpp index 198627e012..a2c08d4a92 100644 --- a/engines/mohawk/sound.cpp +++ b/engines/mohawk/sound.cpp @@ -21,6 +21,7 @@ */ #include "common/debug.h" +#include "common/events.h" #include "common/system.h" #include "common/util.h" #include "common/textconsole.h" @@ -163,8 +164,26 @@ Audio::SoundHandle *Sound::replaceSoundMyst(uint16 id, byte volume, bool loop) { void Sound::playSoundBlocking(uint16 id, byte volume) { Audio::SoundHandle *handle = playSound(id, volume); - while (_vm->_mixer->isSoundHandleActive(*handle)) + while (_vm->_mixer->isSoundHandleActive(*handle) && !_vm->shouldQuit()) { + Common::Event event; + while (_vm->_system->getEventManager()->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_KEYDOWN: + switch (event.kbd.keycode) { + case Common::KEYCODE_SPACE: + _vm->pauseGame(); + break; + default: + break; + } + default: + break; + } + } + + // Cut down on CPU usage _vm->_system->delayMillis(10); + } } void Sound::playMidi(uint16 id) { @@ -625,7 +644,7 @@ uint16 Sound::convertMystID(uint16 id) { return id; } -Audio::SoundHandle *Sound::replaceBackgroundMyst(uint16 id, uint16 volume) { +void Sound::replaceBackgroundMyst(uint16 id, uint16 volume) { debug(0, "Replacing background sound with %d", id); // TODO: The original engine does fading @@ -641,8 +660,11 @@ Audio::SoundHandle *Sound::replaceBackgroundMyst(uint16 id, uint16 volume) { // Check if sound is already playing if (_mystBackgroundSound.type == kUsedHandle && _vm->_mixer->isSoundHandleActive(_mystBackgroundSound.handle) - && _vm->getResourceName(ID_MSND, convertMystID(_mystBackgroundSound.id)).hasPrefix(prefix)) - return &_mystBackgroundSound.handle; + && _vm->getResourceName(ID_MSND, convertMystID(_mystBackgroundSound.id)).hasPrefix(prefix)) { + // The sound is already playing, just change the volume + changeBackgroundVolumeMyst(volume); + return; + } // Stop old background sound stopBackgroundMyst(); @@ -659,10 +681,7 @@ Audio::SoundHandle *Sound::replaceBackgroundMyst(uint16 id, uint16 volume) { Audio::AudioStream *audStream = Audio::makeLoopingAudioStream(rewindStream, 0); _vm->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_mystBackgroundSound.handle, audStream, -1, volume >> 8); - return &_mystBackgroundSound.handle; } - - return NULL; } void Sound::stopBackgroundMyst() { diff --git a/engines/mohawk/sound.h b/engines/mohawk/sound.h index 75c9492d96..c62e6e9874 100644 --- a/engines/mohawk/sound.h +++ b/engines/mohawk/sound.h @@ -136,7 +136,7 @@ public: // Myst-specific sound functions Audio::SoundHandle *replaceSoundMyst(uint16 id, byte volume = Audio::Mixer::kMaxChannelVolume, bool loop = false); - Audio::SoundHandle *replaceBackgroundMyst(uint16 id, uint16 volume = 0xFFFF); + void replaceBackgroundMyst(uint16 id, uint16 volume = 0xFFFF); void pauseBackgroundMyst(); void resumeBackgroundMyst(); void stopBackgroundMyst(); diff --git a/engines/mohawk/video.cpp b/engines/mohawk/video.cpp index ff4a69cd28..eec543235e 100644 --- a/engines/mohawk/video.cpp +++ b/engines/mohawk/video.cpp @@ -146,7 +146,7 @@ VideoHandle::VideoHandle(const VideoHandle &handle) : _ptr(handle._ptr) { VideoManager::VideoManager(MohawkEngine* vm) : _vm(vm) { // Set dithering enabled, if required - _enableDither = _vm->getGameType() == GType_MYST && !(_vm->getFeatures() & GF_ME); + _enableDither = (_vm->getGameType() == GType_MYST || _vm->getGameType() == GType_MAKINGOF) && !(_vm->getFeatures() & GF_ME); } VideoManager::~VideoManager() { @@ -184,7 +184,7 @@ void VideoManager::playMovieBlocking(const Common::String &fileName, uint16 x, u } ptr->start(); - waitUntilMovieEnds(ptr); + waitUntilMovieEnds(VideoHandle(ptr)); } void VideoManager::playMovieBlockingCentered(const Common::String &fileName, bool clearScreen) { @@ -200,7 +200,7 @@ void VideoManager::playMovieBlockingCentered(const Common::String &fileName, boo ptr->center(); ptr->start(); - waitUntilMovieEnds(ptr); + waitUntilMovieEnds(VideoHandle(ptr)); } void VideoManager::waitUntilMovieEnds(VideoHandle videoHandle) { @@ -278,7 +278,7 @@ VideoHandle VideoManager::playMovie(const Common::String &fileName) { return VideoHandle(); ptr->start(); - return ptr; + return VideoHandle(ptr); } VideoHandle VideoManager::playMovie(uint16 id) { @@ -287,7 +287,7 @@ VideoHandle VideoManager::playMovie(uint16 id) { return VideoHandle(); ptr->start(); - return ptr; + return VideoHandle(ptr); } bool VideoManager::updateMovies() { @@ -317,50 +317,13 @@ bool VideoManager::updateMovies() { // Check if we need to draw a frame if (video->needsUpdate()) { - const Graphics::Surface *frame = video->decodeNextFrame(); - Graphics::Surface *convertedFrame = 0; - - if (frame && (*it)->isEnabled()) { - Graphics::PixelFormat pixelFormat = _vm->_system->getScreenFormat(); - - if (frame->format != pixelFormat) { - // We don't support downconverting to 8bpp without having - // support in the codec. Set _enableDither if shows up. - if (pixelFormat.bytesPerPixel == 1) { - warning("Cannot convert high color video frame to 8bpp"); - (*it)->close(); - it = _videos.erase(it); - continue; - } - - // Convert to the current screen format - convertedFrame = frame->convertTo(pixelFormat, video->getPalette()); - frame = convertedFrame; - } else if (pixelFormat.bytesPerPixel == 1 && video->hasDirtyPalette()) { - // Set the palette when running in 8bpp mode only - // Don't do this for Myst, which has its own per-stack handling - if (_vm->getGameType() != GType_MYST) - _vm->_system->getPaletteManager()->setPalette(video->getPalette(), 0, 256); - } - - // Clip the width/height to make sure we stay on the screen (Myst does this a few times) - uint16 width = MIN<int32>(video->getWidth(), _vm->_system->getWidth() - (*it)->getX()); - uint16 height = MIN<int32>(video->getHeight(), _vm->_system->getHeight() - (*it)->getY()); - _vm->_system->copyRectToScreen(frame->getPixels(), frame->pitch, (*it)->getX(), (*it)->getY(), width, height); - - // We've drawn something to the screen, make sure we update it + if (drawNextFrame(*it)) { updateScreen = true; - - // Delete 8bpp conversion surface - if (convertedFrame) { - convertedFrame->free(); - delete convertedFrame; - } } } // Check the video time - _vm->doVideoTimer(*it, false); + _vm->doVideoTimer(VideoHandle(*it), false); // Remember to increase the iterator it++; @@ -370,6 +333,74 @@ bool VideoManager::updateMovies() { return updateScreen; } +bool VideoManager::drawNextFrame(VideoEntryPtr videoEntry) { + Video::VideoDecoder *video = videoEntry->_video; + const Graphics::Surface *frame = video->decodeNextFrame(); + + if (!frame || !videoEntry->isEnabled()) { + return false; + } + + Graphics::Surface *convertedFrame = 0; + Graphics::PixelFormat pixelFormat = _vm->_system->getScreenFormat(); + + if (frame->format != pixelFormat) { + // We don't support downconverting to 8bpp without having + // support in the codec. Set _enableDither if shows up. + if (pixelFormat.bytesPerPixel == 1) { + warning("Cannot convert high color video frame to 8bpp"); + return false; + } + + // Convert to the current screen format + convertedFrame = frame->convertTo(pixelFormat, video->getPalette()); + frame = convertedFrame; + } else if (pixelFormat.bytesPerPixel == 1 && video->hasDirtyPalette()) { + // Set the palette when running in 8bpp mode only + // Don't do this for Myst, which has its own per-stack handling + if (_vm->getGameType() != GType_MYST) + _vm->_system->getPaletteManager()->setPalette(video->getPalette(), 0, 256); + } + + // Clip the video to make sure it stays on the screen (Myst does this a few times) + Common::Rect targetRect = Common::Rect(video->getWidth(), video->getHeight()); + targetRect.translate(videoEntry->getX(), videoEntry->getY()); + + Common::Rect frameRect = Common::Rect(video->getWidth(), video->getHeight()); + + if (targetRect.left < 0) { + frameRect.left -= targetRect.left; + targetRect.left = 0; + } + + if (targetRect.top < 0) { + frameRect.top -= targetRect.top; + targetRect.top = 0; + } + + if (targetRect.right > _vm->_system->getWidth()) { + frameRect.right -= targetRect.right - _vm->_system->getWidth(); + targetRect.right = _vm->_system->getWidth(); + } + + if (targetRect.bottom > _vm->_system->getHeight()) { + frameRect.bottom -= targetRect.bottom - _vm->_system->getHeight(); + targetRect.bottom = _vm->_system->getHeight(); + } + + _vm->_system->copyRectToScreen(frame->getBasePtr(frameRect.left, frameRect.top), frame->pitch, + targetRect.left, targetRect.top, targetRect.width(), targetRect.height()); + + // Delete 8bpp conversion surface + if (convertedFrame) { + convertedFrame->free(); + delete convertedFrame; + } + + // We've drawn something to the screen, make sure we update it + return true; +} + void VideoManager::activateMLST(uint16 mlstId, uint16 card) { Common::SeekableReadStream *mlstStream = _vm->getResource(ID_MLST, card); uint16 recordCount = mlstStream->readUint16BE(); @@ -430,7 +461,7 @@ VideoHandle VideoManager::playMovieRiven(uint16 id) { ptr->start(); } - return ptr; + return VideoHandle(ptr); } } @@ -445,7 +476,7 @@ void VideoManager::playMovieBlockingRiven(uint16 id) { ptr->moveTo(_mlstRecords[i].left, _mlstRecords[i].top); ptr->setVolume(_mlstRecords[i].volume); ptr->start(); - waitUntilMovieEnds(ptr); + waitUntilMovieEnds(VideoHandle(ptr)); return; } } @@ -522,7 +553,7 @@ VideoHandle VideoManager::findVideoHandleRiven(uint16 id) { if (_mlstRecords[i].code == id) for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++) if ((*it)->getID() == _mlstRecords[i].movieID) - return *it; + return VideoHandle(*it); return VideoHandle(); } @@ -533,7 +564,7 @@ VideoHandle VideoManager::findVideoHandle(uint16 id) { for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++) if ((*it)->getID() == id) - return *it; + return VideoHandle(*it); return VideoHandle(); } @@ -544,7 +575,7 @@ VideoHandle VideoManager::findVideoHandle(const Common::String &fileName) { for (VideoList::iterator it = _videos.begin(); it != _videos.end(); it++) if ((*it)->getFileName().equalsIgnoreCase(fileName)) - return *it; + return VideoHandle(*it); return VideoHandle(); } @@ -558,12 +589,10 @@ bool VideoManager::isVideoPlaying() { } void VideoManager::drawVideoFrame(VideoHandle handle, const Audio::Timestamp &time) { - // FIXME: This should be done separately from the "playing" - // videos eventually. assert(handle); handle->seek(time); - updateMovies(); - handle->close(); + drawNextFrame(handle._ptr); + handle->stop(); } VideoManager::VideoList::iterator VideoManager::findEntry(VideoEntryPtr ptr) { diff --git a/engines/mohawk/video.h b/engines/mohawk/video.h index 106a32f8e2..d0edab9def 100644 --- a/engines/mohawk/video.h +++ b/engines/mohawk/video.h @@ -293,7 +293,7 @@ private: /** * Constructor for internal VideoManager use */ - VideoHandle(VideoEntryPtr ptr); + explicit VideoHandle(VideoEntryPtr ptr); /** * The video entry this is associated with @@ -350,6 +350,8 @@ private: VideoList::iterator findEntry(VideoEntryPtr ptr); void removeEntry(VideoEntryPtr ptr); + bool drawNextFrame(VideoEntryPtr videoEntry); + // Dithering control bool _enableDither; void checkEnableDither(VideoEntryPtr &entry); diff --git a/engines/mortevielle/configure.engine b/engines/mortevielle/configure.engine index a7fb2ccda6..0fe89acc99 100644 --- a/engines/mortevielle/configure.engine +++ b/engines/mortevielle/configure.engine @@ -1,3 +1,3 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] -add_engine mortevielle "Mortevielle" yes +add_engine mortevielle "Mortevielle" yes "" "" "highres" diff --git a/engines/mortevielle/detection.cpp b/engines/mortevielle/detection.cpp index b6c27e6b12..6791707dd5 100644 --- a/engines/mortevielle/detection.cpp +++ b/engines/mortevielle/detection.cpp @@ -55,7 +55,7 @@ public: MortevielleMetaEngine() : AdvancedMetaEngine(Mortevielle::MortevielleGameDescriptions, sizeof(Mortevielle::MortevielleGameDescription), MortevielleGame) { _md5Bytes = 512; - _singleid = "mortevielle"; + _singleId = "mortevielle"; // Use kADFlagUseExtraAsHint to distinguish between original and improved versions // (i.e. use or not of the game data file). _flags = kADFlagUseExtraAsHint; diff --git a/engines/neverhood/configure.engine b/engines/neverhood/configure.engine index 46910e293e..f04e6c69f6 100644 --- a/engines/neverhood/configure.engine +++ b/engines/neverhood/configure.engine @@ -1,3 +1,3 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] -add_engine neverhood "Neverhood" yes +add_engine neverhood "Neverhood" yes "" "" "highres" diff --git a/engines/neverhood/detection.cpp b/engines/neverhood/detection.cpp index 4d545e136c..cfddc2d6b4 100644 --- a/engines/neverhood/detection.cpp +++ b/engines/neverhood/detection.cpp @@ -41,7 +41,7 @@ struct NeverhoodGameDescription { }; const char *NeverhoodEngine::getGameId() const { - return _gameDescription->desc.gameid; + return _gameDescription->desc.gameId; } uint32 NeverhoodEngine::getFeatures() const { @@ -181,8 +181,8 @@ static const ExtraGuiOption neverhoodExtraGuiOption3 = { class NeverhoodMetaEngine : public AdvancedMetaEngine { public: NeverhoodMetaEngine() : AdvancedMetaEngine(Neverhood::gameDescriptions, sizeof(Neverhood::NeverhoodGameDescription), neverhoodGames) { - _singleid = "neverhood"; - _guioptions = GUIO2(GUIO_NOSUBTITLES, GUIO_NOMIDI); + _singleId = "neverhood"; + _guiOptions = GUIO2(GUIO_NOSUBTITLES, GUIO_NOMIDI); } virtual const char *getName() const { @@ -245,7 +245,6 @@ SaveStateList NeverhoodMetaEngine::listSaves(const char *target) const { Common::StringArray filenames; filenames = saveFileMan->listSavefiles(pattern.c_str()); - Common::sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) SaveStateList saveList; for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); file++) { @@ -262,6 +261,8 @@ SaveStateList NeverhoodMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/parallaction/detection.cpp b/engines/parallaction/detection.cpp index daeee1fee4..4c52990874 100644 --- a/engines/parallaction/detection.cpp +++ b/engines/parallaction/detection.cpp @@ -220,7 +220,7 @@ static const PARALLACTIONGameDescription gameDescriptions[] = { class ParallactionMetaEngine : public AdvancedMetaEngine { public: ParallactionMetaEngine() : AdvancedMetaEngine(Parallaction::gameDescriptions, sizeof(Parallaction::PARALLACTIONGameDescription), parallactionGames) { - _guioptions = GUIO1(GUIO_NOLAUNCHLOAD); + _guiOptions = GUIO1(GUIO_NOLAUNCHLOAD); } virtual const char *getName() const { @@ -273,7 +273,6 @@ SaveStateList ParallactionMetaEngine::listSaves(const char *target) const { Common::String pattern(ConfMan.getDomain(target)->getVal("gameid") + ".0##"); Common::StringArray filenames = saveFileMan->listSavefiles(pattern); - sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) SaveStateList saveList; for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { @@ -290,6 +289,8 @@ SaveStateList ParallactionMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/parallaction/saveload.cpp b/engines/parallaction/saveload.cpp index 72e9400acd..6d598d9557 100644 --- a/engines/parallaction/saveload.cpp +++ b/engines/parallaction/saveload.cpp @@ -194,7 +194,7 @@ int SaveLoad::selectSaveFile(Common::String &selectedName, bool saveMode, const bool SaveLoad::loadGame() { Common::String null; - int _di = selectSaveFile(null, false, "Load file", "Load"); + int _di = selectSaveFile(null, false, _("Load file"), _("Load")); if (_di == -1) { return false; } @@ -209,7 +209,7 @@ bool SaveLoad::loadGame() { bool SaveLoad::saveGame() { Common::String saveName; - int slot = selectSaveFile(saveName, true, "Save file", "Save"); + int slot = selectSaveFile(saveName, true, _("Save file"), _("Save")); if (slot == -1) { return false; } diff --git a/engines/pegasus/configure.engine b/engines/pegasus/configure.engine index ed7e295287..650d57a602 100644 --- a/engines/pegasus/configure.engine +++ b/engines/pegasus/configure.engine @@ -1,3 +1,3 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] -add_engine pegasus "The Journeyman Project: Pegasus Prime" yes "" "" "16bit" +add_engine pegasus "The Journeyman Project: Pegasus Prime" yes "" "" "16bit highres" diff --git a/engines/pegasus/detection.cpp b/engines/pegasus/detection.cpp index 721c382d4f..161a133c8b 100644 --- a/engines/pegasus/detection.cpp +++ b/engines/pegasus/detection.cpp @@ -134,7 +134,7 @@ static const PegasusGameDescription gameDescriptions[] = { class PegasusMetaEngine : public AdvancedMetaEngine { public: PegasusMetaEngine() : AdvancedMetaEngine(Pegasus::gameDescriptions, sizeof(Pegasus::PegasusGameDescription), pegasusGames) { - _singleid = "pegasus"; + _singleId = "pegasus"; } virtual const char *getName() const { diff --git a/engines/prince/configure.engine b/engines/prince/configure.engine index 50740d9f41..827579b00d 100644 --- a/engines/prince/configure.engine +++ b/engines/prince/configure.engine @@ -1,3 +1,3 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] -add_engine prince "The Prince and The Coward" no +add_engine prince "The Prince and The Coward" no "" "" "highres" diff --git a/engines/prince/detection.cpp b/engines/prince/detection.cpp index 3fe7993fdb..1c6f63aff3 100644 --- a/engines/prince/detection.cpp +++ b/engines/prince/detection.cpp @@ -29,7 +29,7 @@ int PrinceEngine::getGameType() const { } const char *PrinceEngine::getGameId() const { - return _gameDescription->desc.gameid; + return _gameDescription->desc.gameId; } uint32 PrinceEngine::getFeatures() const { diff --git a/engines/prince/detection.h b/engines/prince/detection.h index 7e5bdd6b7b..3076253cf5 100644 --- a/engines/prince/detection.h +++ b/engines/prince/detection.h @@ -104,7 +104,7 @@ const static char *directoryGlobs[] = { class PrinceMetaEngine : public AdvancedMetaEngine { public: PrinceMetaEngine() : AdvancedMetaEngine(Prince::gameDescriptions, sizeof(Prince::PrinceGameDescription), princeGames) { - _singleid = "prince"; + _singleId = "prince"; _maxScanDepth = 2; _directoryGlobs = directoryGlobs; } diff --git a/engines/prince/graphics.cpp b/engines/prince/graphics.cpp index 3482d79f69..d5178efc17 100644 --- a/engines/prince/graphics.cpp +++ b/engines/prince/graphics.cpp @@ -279,7 +279,7 @@ void GraphicsMan::drawTransparentWithTransDrawNode(Graphics::Surface *screen, Dr // first and last row at the same time (height = 1) - no anti-alias continue; } - // new color value based on orginal screen surface color and sprite's edge pixel color + // new color value based on original screen surface color and sprite's edge pixel color *dst2 = transTableData[*dst2 * 256 + value]; } } diff --git a/engines/queen/detection.cpp b/engines/queen/detection.cpp index 96075a0f0c..81e0767836 100644 --- a/engines/queen/detection.cpp +++ b/engines/queen/detection.cpp @@ -185,6 +185,19 @@ static const QueenGameDescription gameDescriptions[] = { }, }, + // DOS Floppy - Russian + { + { + "queen", + "Floppy", + AD_ENTRY1s("queen.1", "0d1c10d5c3a1bd90bc0b3859a3258093", 22677657), + Common::RU_RUS, + Common::kPlatformDOS, + ADGF_NO_FLAGS, + GUIO1(GUIO_NOSPEECH) + }, + }, + // DOS CD - French { { @@ -388,7 +401,7 @@ static const QueenGameDescription gameDescriptions[] = { class QueenMetaEngine : public AdvancedMetaEngine { public: QueenMetaEngine() : AdvancedMetaEngine(Queen::gameDescriptions, sizeof(Queen::QueenGameDescription), queenGames, optionsList) { - _singleid = "queen"; + _singleId = "queen"; } virtual const char *getName() const { @@ -430,25 +443,25 @@ const ADGameDescription *QueenMetaEngine::fallbackDetect(const FileMap &allFiles } Queen::DetectedGameVersion version; if (Queen::Resource::detectVersion(&version, &dataFile)) { - desc.gameid = "queen"; + desc.gameId = "queen"; desc.language = version.language; desc.platform = version.platform; desc.flags = ADGF_NO_FLAGS; - desc.guioptions = GUIO0(); + desc.guiOptions = GUIO0(); if (version.features & Queen::GF_DEMO) { desc.extra = "Demo"; desc.flags = ADGF_DEMO; - desc.guioptions = GUIO_NOSPEECH; + desc.guiOptions = GUIO_NOSPEECH; } else if (version.features & Queen::GF_INTERVIEW) { desc.extra = "Interview"; desc.flags = ADGF_DEMO; - desc.guioptions = GUIO_NOSPEECH; + desc.guiOptions = GUIO_NOSPEECH; } else if (version.features & Queen::GF_FLOPPY) { desc.extra = "Floppy"; - desc.guioptions = GUIO_NOSPEECH; + desc.guiOptions = GUIO_NOSPEECH; } else if (version.features & Queen::GF_TALKIE) { desc.extra = "Talkie"; - desc.guioptions = GAMEOPTION_ALT_INTRO; + desc.guiOptions = GAMEOPTION_ALT_INTRO; } return (const ADGameDescription *)&desc; } @@ -464,7 +477,6 @@ SaveStateList QueenMetaEngine::listSaves(const char *target) const { Common::String pattern("queen.s##"); filenames = saveFileMan->listSavefiles(pattern); - sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) SaveStateList saveList; for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { @@ -483,6 +495,8 @@ SaveStateList QueenMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/saga/configure.engine b/engines/saga/configure.engine index 99e2ab367b..adb904a6dd 100644 --- a/engines/saga/configure.engine +++ b/engines/saga/configure.engine @@ -1,5 +1,5 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] add_engine saga "SAGA" yes "ihnm saga2" "ITE" -add_engine ihnm "IHNM" yes -add_engine saga2 "SAGA 2 games" no +add_engine ihnm "IHNM" yes "" "" "highres" +add_engine saga2 "SAGA 2 games" no "" "" "highres" diff --git a/engines/saga/detection.cpp b/engines/saga/detection.cpp index 13ca63525a..0677e84d67 100644 --- a/engines/saga/detection.cpp +++ b/engines/saga/detection.cpp @@ -102,11 +102,11 @@ static const Engines::ObsoleteGameID obsoleteGameIDsTable[] = { class SagaMetaEngine : public AdvancedMetaEngine { public: SagaMetaEngine() : AdvancedMetaEngine(Saga::gameDescriptions, sizeof(Saga::SAGAGameDescription), sagaGames) { - _singleid = "saga"; + _singleId = "saga"; } - virtual GameDescriptor findGame(const char *gameid) const { - return Engines::findGameID(gameid, _gameids, obsoleteGameIDsTable); + virtual GameDescriptor findGame(const char *gameId) const { + return Engines::findGameID(gameId, _gameIds, obsoleteGameIDsTable); } virtual const char *getName() const { @@ -183,7 +183,6 @@ SaveStateList SagaMetaEngine::listSaves(const char *target) const { pattern += ".s##"; filenames = saveFileMan->listSavefiles(pattern); - sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) SaveStateList saveList; int slotNum = 0; @@ -203,6 +202,8 @@ SaveStateList SagaMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/saga/interface.cpp b/engines/saga/interface.cpp index 44581f26fc..ad940aaf8b 100644 --- a/engines/saga/interface.cpp +++ b/engines/saga/interface.cpp @@ -332,6 +332,9 @@ void Interface::saveReminderCallback(void *refCon) { } void Interface::updateSaveReminder() { + // CHECKME: This is potentially called from a different thread because it is + // called from a timer callback. However, it does not seem to take any + // precautions to avoid race conditions. if (_active && _panelMode == kPanelMain) { _saveReminderState = _saveReminderState % _vm->getDisplayInfo().saveReminderNumSprites + 1; drawStatusBar(); @@ -1859,8 +1862,10 @@ void Interface::drawStatusBar() { int stringWidth; int color; // The default colors in the Spanish version of IHNM are shifted by one - // Fixes bug #1848016 - "IHNM: Wrong Subtitles Color (Spanish)" - int offset = (_vm->getLanguage() == Common::ES_ESP) ? 1 : 0; + // Fixes bug #1848016 - "IHNM: Wrong Subtitles Color (Spanish)". This + // also applies to the German and French versions (bug #7064 - "IHNM: + // text mistake in german version"). + int offset = (_vm->getLanguage() == Common::ES_ESP || _vm->getLanguage() == Common::DE_DEU || _vm->getLanguage() == Common::FR_FRA) ? 1 : 0; // Disable the status text in IHNM when the chapter is 8 if (_vm->getGameId() == GID_IHNM && _vm->_scene->currentChapterNumber() == 8) diff --git a/engines/saga/music.cpp b/engines/saga/music.cpp index 663f5991d0..cd48ebaa4d 100644 --- a/engines/saga/music.cpp +++ b/engines/saga/music.cpp @@ -224,6 +224,9 @@ void Music::musicVolumeGaugeCallback(void *refCon) { } void Music::musicVolumeGauge() { + // CHECKME: This is potentially called from a different thread because it is + // called from a timer callback. However, it does not seem to take any + // precautions to avoid race conditions. int volume; _currentVolumePercent += 10; diff --git a/engines/saga/puzzle.cpp b/engines/saga/puzzle.cpp index 705834f1b7..099bf79e6b 100644 --- a/engines/saga/puzzle.cpp +++ b/engines/saga/puzzle.cpp @@ -399,6 +399,9 @@ void Puzzle::hintTimerCallback(void *refCon) { } void Puzzle::solicitHint() { + // CHECKME: This is potentially called from a different thread because it is + // called from a timer callback. However, it does not seem to take any + // precautions to avoid race conditions. int i; _vm->_actor->setSpeechColor(1, kITEColorBlack); diff --git a/engines/saga/render.cpp b/engines/saga/render.cpp index 1f23a388d0..b932e228ad 100644 --- a/engines/saga/render.cpp +++ b/engines/saga/render.cpp @@ -276,6 +276,9 @@ void Render::fpsTimerCallback(void *refCon) { } void Render::fpsTimer() { + // CHECKME: This is potentially called from a different thread because it is + // called from a timer callback. However, it does not seem to take any + // precautions to avoid race conditions. _fps = _renderedFrameCount; _renderedFrameCount = 0; } diff --git a/engines/saga/saga.cpp b/engines/saga/saga.cpp index 532b59d3c7..77a21e7f93 100644 --- a/engines/saga/saga.cpp +++ b/engines/saga/saga.cpp @@ -578,9 +578,11 @@ ColorId SagaEngine::KnownColor2ColorId(KnownColor knownColor) { } #ifdef ENABLE_IHNM } else if (getGameId() == GID_IHNM) { - // The default colors in the Spanish version of IHNM are shifted by one - // Fixes bug #1848016 - "IHNM: Wrong Subtitles Color (Spanish)" - int offset = (getLanguage() == Common::ES_ESP) ? 1 : 0; + // The default colors in the Spanish, version of IHNM are shifted by one + // Fixes bug #1848016 - "IHNM: Wrong Subtitles Color (Spanish)". This + // also applies to the German and French versions (bug #7064 - "IHNM: + // text mistake in german version"). + int offset = (getLanguage() == Common::ES_ESP || getLanguage() == Common::DE_DEU || getLanguage() == Common::FR_FRA) ? 1 : 0; switch (knownColor) { case(kKnownColorTransparent): diff --git a/engines/savestate.h b/engines/savestate.h index 54eff0f8cb..21ade602fa 100644 --- a/engines/savestate.h +++ b/engines/savestate.h @@ -27,7 +27,6 @@ #include "common/str.h" #include "common/ptr.h" - namespace Graphics { struct Surface; } @@ -205,5 +204,13 @@ private: /** List of savestates. */ typedef Common::Array<SaveStateDescriptor> SaveStateList; +/** + * Comparator object to compare SaveStateDescriptor's based on slot. + */ +struct SaveStateDescriptorSlotComparator { + bool operator()(const SaveStateDescriptor &x, const SaveStateDescriptor &y) const { + return x.getSaveSlot() < y.getSaveSlot(); + } +}; #endif diff --git a/engines/sci/configure.engine b/engines/sci/configure.engine index d1c45a4654..f8a519002e 100644 --- a/engines/sci/configure.engine +++ b/engines/sci/configure.engine @@ -1,4 +1,4 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] add_engine sci "SCI" yes "sci32" "SCI 0-1.1 games" -add_engine sci32 "SCI32 games" no +add_engine sci32 "SCI32 games" no "" "" "highres" diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp index 438c725324..a092e0676d 100644 --- a/engines/sci/console.cpp +++ b/engines/sci/console.cpp @@ -137,8 +137,12 @@ Console::Console(SciEngine *engine) : GUI::Debugger(), registerCmd("wl", WRAP_METHOD(Console, cmdWindowList)); // alias registerCmd("plane_list", WRAP_METHOD(Console, cmdPlaneList)); registerCmd("pl", WRAP_METHOD(Console, cmdPlaneList)); // alias + registerCmd("visible_plane_list", WRAP_METHOD(Console, cmdVisiblePlaneList)); + registerCmd("vpl", WRAP_METHOD(Console, cmdVisiblePlaneList)); // alias registerCmd("plane_items", WRAP_METHOD(Console, cmdPlaneItemList)); registerCmd("pi", WRAP_METHOD(Console, cmdPlaneItemList)); // alias + registerCmd("visible_plane_items", WRAP_METHOD(Console, cmdVisiblePlaneItemList)); + registerCmd("vpi", WRAP_METHOD(Console, cmdVisiblePlaneItemList)); // alias registerCmd("saved_bits", WRAP_METHOD(Console, cmdSavedBits)); registerCmd("show_saved_bits", WRAP_METHOD(Console, cmdShowSavedBits)); // Segments @@ -190,6 +194,7 @@ Console::Console(SciEngine *engine) : GUI::Debugger(), registerCmd("send", WRAP_METHOD(Console, cmdSend)); registerCmd("go", WRAP_METHOD(Console, cmdGo)); registerCmd("logkernel", WRAP_METHOD(Console, cmdLogKernel)); + registerCmd("vocab994", WRAP_METHOD(Console, cmdMapVocab994)); // Breakpoints registerCmd("bp_list", WRAP_METHOD(Console, cmdBreakpointList)); registerCmd("bplist", WRAP_METHOD(Console, cmdBreakpointList)); // alias @@ -330,6 +335,7 @@ bool Console::cmdHelp(int argc, const char **argv) { debugPrintf("debugflag_list - Lists the available debug flags and their status\n"); debugPrintf("debugflag_enable - Enables a debug flag\n"); debugPrintf("debugflag_disable - Disables a debug flag\n"); + debugPrintf("debuglevel - Shows or sets debug level\n"); debugPrintf("\n"); debugPrintf("Commands\n"); debugPrintf("--------\n"); @@ -380,7 +386,9 @@ bool Console::cmdHelp(int argc, const char **argv) { debugPrintf(" animate_list / al - Shows the current list of objects in kAnimate's draw list (SCI0 - SCI1.1)\n"); debugPrintf(" window_list / wl - Shows a list of all the windows (ports) in the draw list (SCI0 - SCI1.1)\n"); debugPrintf(" plane_list / pl - Shows a list of all the planes in the draw list (SCI2+)\n"); + debugPrintf(" visible_plane_list / vpl - Shows a list of all the planes in the visible draw list (SCI2+)\n"); debugPrintf(" plane_items / pi - Shows a list of all items for a plane (SCI2+)\n"); + debugPrintf(" visible_plane_items / vpi - Shows a list of all items for a plane in the visible draw list (SCI2+)\n"); debugPrintf(" saved_bits - List saved bits on the hunk\n"); debugPrintf(" show_saved_bits - Display saved bits\n"); debugPrintf("\n"); @@ -487,6 +495,9 @@ bool Console::cmdGetVersion(int argc, const char **argv) { debugPrintf("SCI2.1 kernel table: %s\n", (_engine->_features->detectSci21KernelType() == SCI_VERSION_2) ? "modified SCI2 (old)" : "SCI2.1 (new)"); #endif debugPrintf("View type: %s\n", viewTypeDesc[g_sci->getResMan()->getViewType()]); + if (getSciVersion() <= SCI_VERSION_1_1) { + debugPrintf("kAnimate fastCast enabled: %s\n", g_sci->_gfxAnimate->isFastCastEnabled() ? "yes" : "no"); + } debugPrintf("Uses palette merging: %s\n", g_sci->_gfxPalette16->isMerging() ? "yes" : "no"); debugPrintf("Uses 16 bit color matching: %s\n", g_sci->_gfxPalette16->isUsing16bitColorMatch() ? "yes" : "no"); debugPrintf("Resource volume version: %s\n", g_sci->getResMan()->getVolVersionDesc()); @@ -994,7 +1005,7 @@ bool Console::cmdHexgrep(int argc, const char **argv) { for (; resNumber <= resMax; resNumber++) { script = _engine->getResMan()->findResource(ResourceId(restype, resNumber), 0); if (script) { - unsigned int seeker = 0, seekerold = 0; + uint32 seeker = 0, seekerold = 0; uint32 comppos = 0; int output_script_name = 0; @@ -1500,7 +1511,7 @@ bool Console::cmdSaid(int argc, const char **argv) { } // TODO: Maybe turn this into a proper said spec compiler - unsigned int len = 0; + uint32 len = 0; for (p++; p < argc; p++) { if (strcmp(argv[p], ",") == 0) { spec[len++] = 0xf0; @@ -1537,7 +1548,7 @@ bool Console::cmdSaid(int argc, const char **argv) { spec[len++] = 0xfe; spec[len++] = 0xf6; } else { - unsigned int s = strtol(argv[p], 0, 16); + uint32 s = strtol(argv[p], 0, 16); if (s >= 0xf0 && s <= 0xff) { spec[len++] = s; } else { @@ -1766,6 +1777,21 @@ bool Console::cmdPlaneList(int argc, const char **argv) { return true; } +bool Console::cmdVisiblePlaneList(int argc, const char **argv) { +#ifdef ENABLE_SCI32 + if (_engine->_gfxFrameout) { + debugPrintf("Visible plane list:\n"); + _engine->_gfxFrameout->printVisiblePlaneList(this); + } else { + debugPrintf("This SCI version does not have a list of planes\n"); + } +#else + debugPrintf("SCI32 isn't included in this compiled executable\n"); +#endif + return true; +} + + bool Console::cmdPlaneItemList(int argc, const char **argv) { if (argc != 2) { debugPrintf("Shows the list of items for a plane\n"); @@ -1794,6 +1820,34 @@ bool Console::cmdPlaneItemList(int argc, const char **argv) { return true; } +bool Console::cmdVisiblePlaneItemList(int argc, const char **argv) { + if (argc != 2) { + debugPrintf("Shows the list of items for a plane\n"); + debugPrintf("Usage: %s <plane address>\n", argv[0]); + return true; + } + + reg_t planeObject = NULL_REG; + + if (parse_reg_t(_engine->_gamestate, argv[1], &planeObject, false)) { + debugPrintf("Invalid address passed.\n"); + debugPrintf("Check the \"addresses\" command on how to use addresses\n"); + return true; + } + +#ifdef ENABLE_SCI32 + if (_engine->_gfxFrameout) { + debugPrintf("Visible plane item list:\n"); + _engine->_gfxFrameout->printVisiblePlaneItemList(this, planeObject); + } else { + debugPrintf("This SCI version does not have a list of plane items\n"); + } +#else + debugPrintf("SCI32 isn't included in this compiled executable\n"); +#endif + return true; +} + bool Console::cmdSavedBits(int argc, const char **argv) { SegManager *segman = _engine->_gamestate->_segMan; SegmentId id = segman->findSegmentByType(SEG_TYPE_HUNK); @@ -3901,6 +3955,55 @@ bool Console::cmdSfx01Track(int argc, const char **argv) { return true; } +bool Console::cmdMapVocab994(int argc, const char **argv) { + EngineState *s = _engine->_gamestate; // for the several defines in this function + reg_t reg; + + if (argc != 4) { + debugPrintf("Attempts to map a range of vocab.994 entries to a given class\n"); + debugPrintf("Usage: %s <class addr> <first> <last>\n", argv[0]); + return true; + } + + if (parse_reg_t(_engine->_gamestate, argv[1], ®, false)) { + debugPrintf("Invalid address passed.\n"); + debugPrintf("Check the \"addresses\" command on how to use addresses\n"); + return true; + } + + Resource *resource = _engine->_resMan->findResource(ResourceId(kResourceTypeVocab, 994), 0); + const Object *obj = s->_segMan->getObject(reg); + uint16 *data = (uint16 *) resource->data; + uint32 first = atoi(argv[2]); + uint32 last = atoi(argv[3]); + Common::Array<bool> markers; + + markers.resize(_engine->getKernel()->getSelectorNamesSize()); + if (!obj->isClass() && getSciVersion() != SCI_VERSION_3) + obj = s->_segMan->getObject(obj->getSuperClassSelector()); + + first = MIN(first, (uint32) (resource->size / 2 - 2)); + last = MIN(last, (uint32) (resource->size / 2 - 2)); + + for (uint32 i = first; i <= last; ++i) { + uint16 ofs = data[i]; + + if (obj && ofs < obj->getVarCount()) { + uint16 varSelector = obj->getVarSelector(ofs); + debugPrintf("%d: property at index %04x of %s is %s %s\n", i, ofs, + s->_segMan->derefString(obj->getNameSelector()), + _engine->getKernel()->getSelectorName(varSelector).c_str(), + markers[varSelector] ? "(repeat!)" : ""); + markers[varSelector] = true; + } + else { + debugPrintf("%d: property at index %04x doesn't match up with %s\n", i, ofs, + s->_segMan->derefString(obj->getNameSelector())); + } + } + + return true; +} bool Console::cmdQuit(int argc, const char **argv) { if (argc != 2) { } @@ -4339,7 +4442,8 @@ int Console::printObject(reg_t pos) { debugPrintf(" "); if (var_container && i < var_container->getVarCount()) { uint16 varSelector = var_container->getVarSelector(i); - debugPrintf("[%03x] %s = ", varSelector, _engine->getKernel()->getSelectorName(varSelector).c_str()); + // Times two commented out for now for easy parsing of vocab.994 + debugPrintf("(%04x) [%03x] %s = ", i /* *2 */, varSelector, _engine->getKernel()->getSelectorName(varSelector).c_str()); } else debugPrintf("p#%x = ", i); diff --git a/engines/sci/console.h b/engines/sci/console.h index 8b10912fbe..cf85def950 100644 --- a/engines/sci/console.h +++ b/engines/sci/console.h @@ -96,7 +96,9 @@ private: bool cmdAnimateList(int argc, const char **argv); bool cmdWindowList(int argc, const char **argv); bool cmdPlaneList(int argc, const char **argv); + bool cmdVisiblePlaneList(int argc, const char **argv); bool cmdPlaneItemList(int argc, const char **argv); + bool cmdVisiblePlaneItemList(int argc, const char **argv); bool cmdSavedBits(int argc, const char **argv); bool cmdShowSavedBits(int argc, const char **argv); // Segments @@ -137,6 +139,7 @@ private: bool cmdSend(int argc, const char **argv); bool cmdGo(int argc, const char **argv); bool cmdLogKernel(int argc, const char **argv); + bool cmdMapVocab994(int argc, const char **argv); // Breakpoints bool cmdBreakpointList(int argc, const char **argv); bool cmdBreakpointDelete(int argc, const char **argv); diff --git a/engines/sci/decompressor.cpp b/engines/sci/decompressor.cpp index e65ff148de..ca2298e67e 100644 --- a/engines/sci/decompressor.cpp +++ b/engines/sci/decompressor.cpp @@ -257,7 +257,7 @@ int DecompressorLZW::unpackLZW1(Common::ReadStream *src, byte *dest, uint32 nPac init(src, dest, nPacked, nUnpacked); byte *stak = (byte *)malloc(0x1014); - unsigned int tokensSize = 0x1004 * sizeof(Tokenlist); + uint32 tokensSize = 0x1004 * sizeof(Tokenlist); Tokenlist *tokens = (Tokenlist *)malloc(tokensSize); if (!stak || !tokens) { free(stak); diff --git a/engines/sci/detection.cpp b/engines/sci/detection.cpp index f4f104010f..c920ef10e2 100644 --- a/engines/sci/detection.cpp +++ b/engines/sci/detection.cpp @@ -98,8 +98,11 @@ static const PlainGameDescriptor s_sciGameTitles[] = { {"lsl6", "Leisure Suit Larry 6: Shape Up or Slip Out!"}, {"pepper", "Pepper's Adventure in Time"}, {"slater", "Slater & Charlie Go Camping"}, + {"gk1demo", "Gabriel Knight: Sins of the Fathers"}, + {"qfg4demo", "Quest for Glory IV: Shadows of Darkness"}, + {"pq4demo", "Police Quest IV: Open Season"}, // === SCI2 games ========================================================= - {"gk1", "Gabriel Knight: Sins of the Fathers"}, // demo is SCI11, full version SCI32 + {"gk1", "Gabriel Knight: Sins of the Fathers"}, {"pq4", "Police Quest IV: Open Season"}, // floppy is SCI2, CD SCI2.1 {"qfg4", "Quest for Glory IV: Shadows of Darkness"}, // floppy is SCI2, CD SCI2.1 // === SCI2.1 games ======================================================== @@ -146,6 +149,7 @@ static const GameIdStrToEnum s_gameIdStrToEnum[] = { { "fairytales", GID_FAIRYTALES }, { "freddypharkas", GID_FREDDYPHARKAS }, { "funseeker", GID_FUNSEEKER }, + { "gk1demo", GID_GK1DEMO }, { "gk1", GID_GK1 }, { "gk2", GID_GK2 }, { "hoyle1", GID_HOYLE1 }, @@ -183,12 +187,14 @@ static const GameIdStrToEnum s_gameIdStrToEnum[] = { { "pq2", GID_PQ2 }, { "pq3", GID_PQ3 }, { "pq4", GID_PQ4 }, + { "pq4demo", GID_PQ4DEMO }, { "pqswat", GID_PQSWAT }, { "qfg1", GID_QFG1 }, { "qfg1vga", GID_QFG1VGA }, { "qfg2", GID_QFG2 }, { "qfg3", GID_QFG3 }, { "qfg4", GID_QFG4 }, + { "qfg4demo", GID_QFG4DEMO }, { "rama", GID_RAMA }, { "sci-fanmade", GID_FANMADE }, // FIXME: Do we really need/want this? { "shivers", GID_SHIVERS }, @@ -356,7 +362,7 @@ Common::String convertSierraGameId(Common::String sierraId, uint32 *gameFlags, R // qfg4 demo has less than 50 scripts if (resources.size() < 50) - return "qfg4"; + return "qfg4demo"; // Otherwise it's qfg3 return "qfg3"; @@ -473,7 +479,7 @@ static char s_fallbackGameIdBuf[256]; class SciMetaEngine : public AdvancedMetaEngine { public: SciMetaEngine() : AdvancedMetaEngine(Sci::SciGameDescriptions, sizeof(ADGameDescription), s_sciGameTitles, optionsList) { - _singleid = "sci"; + _singleId = "sci"; } virtual const char *getName() const { @@ -526,8 +532,8 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles, s_fallbackDesc.language = Common::EN_ANY; s_fallbackDesc.flags = ADGF_NO_FLAGS; s_fallbackDesc.platform = Common::kPlatformDOS; // default to PC platform - s_fallbackDesc.gameid = "sci"; - s_fallbackDesc.guioptions = GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI); + s_fallbackDesc.gameId = "sci"; + s_fallbackDesc.guiOptions = GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI); if (allFiles.contains("resource.map") || allFiles.contains("Data1") || allFiles.contains("resmap.001") || allFiles.contains("resmap.001")) { @@ -578,7 +584,7 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles, ResourceManager resMan; resMan.addAppropriateSourcesForDetection(fslist); - resMan.initForDetection(); + resMan.init(); // TODO: Add error handling. #ifndef ENABLE_SCI32 @@ -610,7 +616,7 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles, Common::String gameId = convertSierraGameId(sierraGameId, &s_fallbackDesc.flags, resMan); strncpy(s_fallbackGameIdBuf, gameId.c_str(), sizeof(s_fallbackGameIdBuf) - 1); s_fallbackGameIdBuf[sizeof(s_fallbackGameIdBuf) - 1] = 0; // Make sure string is NULL terminated - s_fallbackDesc.gameid = s_fallbackGameIdBuf; + s_fallbackDesc.gameId = s_fallbackGameIdBuf; // Try to determine the game language // Load up text 0 and start looking for "#" characters @@ -653,7 +659,7 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles, const bool isCD = (s_fallbackDesc.flags & ADGF_CD); if (!isCD) - s_fallbackDesc.guioptions = GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI); + s_fallbackDesc.guiOptions = GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI); if (gameId.hasSuffix("sci")) { s_fallbackDesc.extra = "SCI"; @@ -686,7 +692,7 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles, bool SciMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const { const GameIdStrToEnum *g = s_gameIdStrToEnum; for (; g->gameidStr; ++g) { - if (0 == strcmp(desc->gameid, g->gameidStr)) { + if (0 == strcmp(desc->gameId, g->gameidStr)) { *engine = new SciEngine(syst, desc, g->gameidEnum); return true; } @@ -727,7 +733,6 @@ SaveStateList SciMetaEngine::listSaves(const char *target) const { pattern += ".###"; filenames = saveFileMan->listSavefiles(pattern); - sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) SaveStateList saveList; int slotNr = 0; @@ -760,6 +765,8 @@ SaveStateList SciMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h index 25623215ed..968eb784b1 100644 --- a/engines/sci/detection_tables.h +++ b/engines/sci/detection_tables.h @@ -684,21 +684,23 @@ static const struct ADGameDescription SciGameDescriptions[] = { AD_LISTEND}, Common::EN_ANY, Common::kPlatformDOS, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, - // Gabriel Knight - English DOS CD Demo + // Gabriel Knight - English DOS Demo // SCI interpreter version 1.001.092 - {"gk1", "CD Demo", { + // Note: we are not using ADGF_DEMO here, to avoid a game ID like gk1demo-demo + {"gk1demo", "Demo", { {"resource.map", 0, "39645952ae0ed8072c7e838f31b75464", 2490}, {"resource.000", 0, "eb3ed7477ca4110813fe1fcf35928561", 1718450}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO, GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + Common::EN_ANY, Common::kPlatformDOS, 0, GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, - // Gabriel Knight - English DOS CD Demo (from DrMcCoy) + // Gabriel Knight - English DOS Demo (from DrMcCoy) // SCI interpreter version 1.001.092 - {"gk1", "CD Demo", { + // Note: we are not using ADGF_DEMO here, to avoid a game ID like gk1demo-demo + {"gk1demo", "Demo", { {"resource.map", 0, "8cad2a256f41463030cbb7ea1bfb2857", 2490}, {"resource.000", 0, "eb3ed7477ca4110813fe1fcf35928561", 1718450}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO, GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + Common::EN_ANY, Common::kPlatformDOS, 0, GUIO3(GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, #ifdef ENABLE_SCI32 // Gabriel Knight - English DOS Floppy @@ -941,6 +943,15 @@ static const struct ADGameDescription SciGameDescriptions[] = { AD_LISTEND}, Common::EN_ANY, Common::kPlatformDOS, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // Hoyle 2 - English DOS (supplied by m_kiewitz) + // SCI interpreter version 0.000.668, Ver 1.000.014, 2x5.25" + {"hoyle2", "", { + {"resource.map", 0, "8cef06c93d17d96f44aacd5902d84b30", 2100}, + {"resource.001", 0, "8f2dd70abe01112eca464cda818b5eb6", 98289}, + {"resource.002", 0, "8f2dd70abe01112eca464cda818b5eb6", 197326}, + AD_LISTEND}, + Common::EN_ANY, Common::kPlatformDOS, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // Hoyle 2 - English DOS (supplied by misterhands in bug report #6598) // Game v1.000.016, interpreter 0.000.668, INT #12.5.90 {"hoyle2", "", { @@ -1531,13 +1542,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resource.000", 0, "71afd220d46bde1109c58e6acc0f3a01", 469094}, {"resource.001", 0, "72a569f46f1abf2d9d2b1526ad3799c3", 12808839}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformFMTowns, 0, GUIO2(GUIO_NOASPECT, GUIO_MIDITOWNS) }, - {"kq5", "", { - {"resource.map", 0, "20c7cd248ff1a349ed354568eebd972b", 12733}, - {"resource.000", 0, "71afd220d46bde1109c58e6acc0f3a01", 469094}, - {"resource.001", 0, "72a569f46f1abf2d9d2b1526ad3799c3", 12808839}, - AD_LISTEND}, - Common::JA_JPN, Common::kPlatformFMTowns, 0, GUIO2(GUIO_NOASPECT, GUIO_MIDITOWNS) }, + Common::JA_JPN, Common::kPlatformFMTowns, ADGF_ADDENGLISH, GUIO3(GUIO_NOASPECT, GAMEOPTION_ORIGINAL_SAVELOAD, GUIO_MIDITOWNS) }, // King's Quest 5 - Japanese PC-98 Floppy 0.000.015 (supplied by omer_mor in bug report #3073583) {"kq5", "", { @@ -1646,14 +1651,6 @@ static const struct ADGameDescription SciGameDescriptions[] = { #ifdef ENABLE_SCI32 - // King's Quest 7 - English Windows (from abevi) - // VERSION 1.65c - {"kq7", "", { - {"resource.000", 0, "4948e4e1506f1e1c4e1d47abfa06b7f8", 204385195}, - {"resource.map", 0, "40ccafb2195301504eba2e4f4f2c7f3d", 18925}, - AD_LISTEND}, - Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, - // King's Quest 7 - English Windows (from the King's Quest Collection) // Executable scanning reports "2.100.002", VERSION file reports "1.4" {"kq7", "", { @@ -1662,6 +1659,33 @@ static const struct ADGameDescription SciGameDescriptions[] = { AD_LISTEND}, Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // King's Quest 7 - English Windows-interpreter-only (supplied by m_kiewitz) + // SCI interpreter version 2.100.002, VERSION file reports "1.51" + {"kq7", "", { + {"resource.map", 0, "838b9ff132bd6962026fee832e8a7ddb", 18697}, + {"resource.000", 0, "eb63ea3a2c2469dc2d777d351c626404", 206626576}, + {"resource.aud", 0, "c2a988a16053eb98c7b73a75139902a0", 217716879}, + AD_LISTEND}, + Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + + // King's Quest 7 - German Windows-interpreter-only (supplied by markcoolio in bug report #2727402) + // SCI interpreter version 2.100.002, VERSION file reports "1.51" + // same as English 1.51, only resource.aud/resource.sfx are different + {"kq7", "", { + {"resource.map", 0, "838b9ff132bd6962026fee832e8a7ddb", 18697}, + {"resource.000", 0, "eb63ea3a2c2469dc2d777d351c626404", 206626576}, + {"resource.aud", 0, "3f17bcaf8a9ff6a6c2d4de1a2078fdcc", 258119621}, + AD_LISTEND}, + Common::DE_DEU, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + + // King's Quest 7 - English Windows (from abevi) + // VERSION 1.65c + {"kq7", "", { + {"resource.000", 0, "4948e4e1506f1e1c4e1d47abfa06b7f8", 204385195}, + {"resource.map", 0, "40ccafb2195301504eba2e4f4f2c7f3d", 18925}, + AD_LISTEND}, + Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // King's Quest 7 - English DOS (from FRG) // SCI interpreter version 2.100.002, VERSION file reports "2.00b" {"kq7", "", { @@ -1678,14 +1702,6 @@ static const struct ADGameDescription SciGameDescriptions[] = { AD_LISTEND}, Common::EN_ANY, Common::kPlatformWindows, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, - // King's Quest 7 - German Windows (supplied by markcoolio in bug report #2727402) - // SCI interpreter version 2.100.002 - {"kq7", "", { - {"resource.map", 0, "838b9ff132bd6962026fee832e8a7ddb", 18697}, - {"resource.000", 0, "eb63ea3a2c2469dc2d777d351c626404", 206626576}, - AD_LISTEND}, - Common::DE_DEU, Common::kPlatformDOS, ADGF_UNSTABLE | ADGF_CD, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, - // King's Quest 7 - Spanish DOS (from jvprat) // Executable scanning reports "2.100.002", VERSION file reports "2.00" {"kq7", "", { @@ -2286,6 +2302,14 @@ static const struct ADGameDescription SciGameDescriptions[] = { AD_LISTEND}, Common::EN_ANY, Common::kPlatformDOS, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // Larry 6 - French DOS Floppy - LOWRES (provided by theco33) + // SCI interpreter version 1.001.113 + {"lsl6", "", { + {"resource.map", 0, "1e07144d3b06a3269236880170978acb", 6943}, + {"resource.000", 0, "7884a8db9253e29e6b37a2651fd90ba3", 5749882}, + AD_LISTEND}, + Common::FR_FRA, Common::kPlatformDOS, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // Larry 6 - English/German/French DOS CD - LOWRES // SCI interpreter version 1.001.115 {"lsl6", "", { @@ -2593,12 +2617,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resource.map", 0, "b11e971ccd2040bebba59dfb409a08ef", 5772}, {"resource.001", 0, "d49625d9b8005ec01c852f8322a82867", 4330713}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformFMTowns, 0, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, - {"mothergoose256", "", { - {"resource.map", 0, "b11e971ccd2040bebba59dfb409a08ef", 5772}, - {"resource.001", 0, "d49625d9b8005ec01c852f8322a82867", 4330713}, - AD_LISTEND}, - Common::JA_JPN, Common::kPlatformFMTowns, 0, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + Common::JA_JPN, Common::kPlatformFMTowns, ADGF_ADDENGLISH, GUIO4(GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, #ifdef ENABLE_SCI32 // Mixed-Up Mother Goose Deluxe - English Windows/DOS CD (supplied by markcoolio in bug report #2723810) @@ -2627,6 +2646,28 @@ static const struct ADGameDescription SciGameDescriptions[] = { Common::EN_ANY, Common::kPlatformDOS, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, #ifdef ENABLE_SCI32 + // Phantasmagoria - English DOS/Windows (from csnover) + // Windows executable scanning reports "2.100.002" - "Aug 06 1995" + // DOS executable scanning reports "2.100.002" - "May 24 1995" + // VERSION file reports "1.000.000" + {"phantasmagoria", "", { + {"resmap.001", 0, "43c395f312a190e67b90b2c1e93a79e2", 11518}, + {"ressci.001", 0, "3aae6559aa1df273bc542d5ac6330d75", 65844612}, + {"resmap.002", 0, "94f142cfe8ec4107b6a42876cb603ed0", 12058}, + {"ressci.002", 0, "3aae6559aa1df273bc542d5ac6330d75", 71588691}, + {"resmap.003", 0, "39e9abd4501b5b6168dd07379c0be753", 12334}, + {"ressci.003", 0, "3aae6559aa1df273bc542d5ac6330d75", 73651084}, + {"resmap.004", 0, "434f9704658229fef322c863d2422a9a", 12556}, + {"ressci.004", 0, "3aae6559aa1df273bc542d5ac6330d75", 75811935}, + {"resmap.005", 0, "3ff9b4f7301800825c0ed008e091205e", 12604}, + {"ressci.005", 0, "3aae6559aa1df273bc542d5ac6330d75", 78814934}, + {"resmap.006", 0, "27ad413313e2a3ec3c53250e7ff5b2d1", 12532}, + {"ressci.006", 0, "3aae6559aa1df273bc542d5ac6330d75", 77901360}, + {"resmap.007", 0, "aa8175cfc93242af6f5e65bdceaafc0d", 7972}, + //{"ressci.007", 0, "3aae6559aa1df273bc542d5ac6330d75", 25859038}, + AD_LISTEND}, + Common::EN_ANY, Common::kPlatformDOS, ADGF_UNSTABLE, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + // Phantasmagoria - English DOS (from jvprat) // Executable scanning reports "2.100.002", VERSION file reports "1.100.000UK" {"phantasmagoria", "", { @@ -2875,6 +2916,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { Common::EN_ANY, Common::kPlatformDOS, 0, GUIO5(GUIO_NOSPEECH, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, // Police Quest 2 - Japanese PC-98 (also includes english language) + // Executable scanning reports "x.yyy.zzz" // SCI interpreter version unknown {"pq2", "", { {"resource.map", 0, "883804c616dca1d82373bf9fda3a71d2", 4656}, @@ -2882,7 +2924,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resource.002", 0, "05fdee43a228dd6ea4d1a92ccae3f788", 637662}, {"resource.003", 0, "05fdee43a228dd6ea4d1a92ccae3f788", 684395}, AD_LISTEND}, - Common::JA_JPN, Common::kPlatformPC98, ADGF_ADDENGLISH, GUIO5(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + Common::JA_JPN, Common::kPlatformPC98, ADGF_ADDENGLISH, GUIO6(GUIO_NOSPEECH, GUIO_NOASPECT, GAMEOPTION_EGA_UNDITHER, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, // Police Quest 3 - English Amiga // Executable scanning reports "1.004.024" @@ -2969,11 +3011,12 @@ static const struct ADGameDescription SciGameDescriptions[] = { // Police Quest 4 - English DOS Non-Interactive Demo (from FRG) // SCI interpreter version 1.001.096 - {"pq4", "Demo", { + // Note: we are not using ADGF_DEMO here, to avoid a game ID like pq4demo-demo + {"pq4demo", "Demo", { {"resource.map", 0, "be56f87a1c4a13062a30a362df860c2f", 1472}, {"resource.000", 0, "527d5684016e6816157cd15d9071b11b", 1121310}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + Common::EN_ANY, Common::kPlatformDOS, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, #ifdef ENABLE_SCI32 // Police Quest 4 - English DOS CD (from the Police Quest Collection) @@ -3381,11 +3424,12 @@ static const struct ADGameDescription SciGameDescriptions[] = { // Quest for Glory 4 - English DOS Non-Interactive Demo (from FRG) // SCI interpreter version 1.001.069 (just a guess) - {"qfg4", "Demo", { + // Note: we are not using ADGF_DEMO here, to avoid a game ID like qfg4demo-demo + {"qfg4demo", "Demo", { {"resource.map", 0, "1ba7c7ae1efb315326d45cb931569b1b", 922}, {"resource.000", 0, "41ba03f0b188b029132daa3ece0d3e14", 623154}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformDOS, ADGF_DEMO, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, + Common::EN_ANY, Common::kPlatformDOS, 0, GUIO4(GUIO_NOSPEECH, GAMEOPTION_PREFER_DIGITAL_SFX, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_FB01_MIDI) }, #ifdef ENABLE_SCI32 // Quest for Glory 4 1.1 Floppy - English DOS (supplied by markcool in bug report #2723852) diff --git a/engines/sci/engine/kernel.cpp b/engines/sci/engine/kernel.cpp index 0df4701334..796dea2382 100644 --- a/engines/sci/engine/kernel.cpp +++ b/engines/sci/engine/kernel.cpp @@ -853,7 +853,7 @@ void Kernel::loadKernelNames(GameFeatures *features) { _kernelNames[0x26] = "Portrait"; else if (g_sci->getPlatform() == Common::kPlatformMacintosh) _kernelNames[0x84] = "ShowMovie"; - } else if (g_sci->getGameId() == GID_QFG4 && g_sci->isDemo()) { + } else if (g_sci->getGameId() == GID_QFG4DEMO) { _kernelNames[0x7b] = "RemapColors"; // QFG4 Demo has this SCI2 function instead of StrSplit } diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h index f62c43840f..62566a74b2 100644 --- a/engines/sci/engine/kernel.h +++ b/engines/sci/engine/kernel.h @@ -75,6 +75,10 @@ struct SciWorkaroundEntry; // from workarounds.h * vocab.997. This results in much more readable code. Thus, this vocabulary isn't * used at all. * + * 993.voc (unneeded) - Contains the SCI3 equivalent of vocab.994; like its predecessor, + * the raw selector numbers can be deduced and used instead. In fact, one version of this + * file has turned out to cover all versiona of SCI3. + * * SCI0 parser vocabularies: * - vocab.901 / 901.voc - suffix vocabulary * - vocab.900 / 900.voc - parse tree branches @@ -408,7 +412,7 @@ reg_t kPlatform(EngineState *s, int argc, reg_t *argv); reg_t kTextColors(EngineState *s, int argc, reg_t *argv); reg_t kTextFonts(EngineState *s, int argc, reg_t *argv); reg_t kShow(EngineState *s, int argc, reg_t *argv); -reg_t kRemapColors(EngineState *s, int argc, reg_t *argv); +reg_t kRemapColors16(EngineState *s, int argc, reg_t *argv); reg_t kDummy(EngineState *s, int argc, reg_t *argv); reg_t kEmpty(EngineState *s, int argc, reg_t *argv); reg_t kStub(EngineState *s, int argc, reg_t *argv); @@ -441,24 +445,54 @@ reg_t kStringLower(EngineState *s, int argc, reg_t *argv); reg_t kStringTrn(EngineState *s, int argc, reg_t *argv); reg_t kStringTrnExclude(EngineState *s, int argc, reg_t *argv); +reg_t kScrollWindowCreate(EngineState *s, int argc, reg_t *argv); +reg_t kScrollWindowAdd(EngineState *s, int argc, reg_t *argv); +reg_t kScrollWindowWhere(EngineState *s, int argc, reg_t *argv); +reg_t kScrollWindowShow(EngineState *s, int argc, reg_t *argv); +reg_t kScrollWindowDestroy(EngineState *s, int argc, reg_t *argv); + reg_t kMulDiv(EngineState *s, int argc, reg_t *argv); -reg_t kCantBeHere32(EngineState *s, int argc, reg_t *argv); -reg_t kRemapColors32(EngineState *s, int argc, reg_t *argv); -// "Screen items" in SCI32 are views + +reg_t kRemapColors(EngineState *s, int argc, reg_t *argv); +reg_t kRemapOff(EngineState *s, int argc, reg_t *argv); +reg_t kRemapByRange(EngineState *s, int argc, reg_t *argv); +reg_t kRemapByPercent(EngineState *s, int argc, reg_t *argv); +reg_t kRemapToGray(EngineState *s, int argc, reg_t *argv); +reg_t kRemapToPercentGray(EngineState *s, int argc, reg_t *argv); +reg_t kRemapSetNoMatchRange(EngineState *s, int argc, reg_t *argv); + reg_t kAddScreenItem(EngineState *s, int argc, reg_t *argv); reg_t kUpdateScreenItem(EngineState *s, int argc, reg_t *argv); reg_t kDeleteScreenItem(EngineState *s, int argc, reg_t *argv); -// Text + reg_t kCreateTextBitmap(EngineState *s, int argc, reg_t *argv); -reg_t kDisposeTextBitmap(EngineState *s, int argc, reg_t *argv); -// "Planes" in SCI32 are pictures +reg_t kBitmap(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapCreate(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapDestroy(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapDrawLine(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapDrawView(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapDrawText(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapDrawColor(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapDrawBitmap(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapInvert(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapSetDisplace(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapCreateFromView(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapCopyPixels(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapClone(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapGetInfo(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapScale(EngineState *s, int argc, reg_t *argv); +reg_t kBitmapCreateFromUnknown(EngineState *s, int argc, reg_t *argv); + reg_t kAddPlane(EngineState *s, int argc, reg_t *argv); reg_t kDeletePlane(EngineState *s, int argc, reg_t *argv); reg_t kUpdatePlane(EngineState *s, int argc, reg_t *argv); +reg_t kMovePlaneItems(EngineState *s, int argc, reg_t *argv); reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv); reg_t kSetPalStyleRange(EngineState *s, int argc, reg_t *argv); reg_t kGetHighPlanePri(EngineState *s, int argc, reg_t *argv); reg_t kFrameOut(EngineState *s, int argc, reg_t *argv); +reg_t kCelHigh32(EngineState *s, int argc, reg_t *argv); +reg_t kCelWide32(EngineState *s, int argc, reg_t *argv); reg_t kIsOnMe(EngineState *s, int argc, reg_t *argv); // kOnMe for SCI2, kIsOnMe for SCI2.1 reg_t kInPolygon(EngineState *s, int argc, reg_t *argv); @@ -473,6 +507,7 @@ reg_t kEditText(EngineState *s, int argc, reg_t *argv); reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv); reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv); reg_t kSetScroll(EngineState *s, int argc, reg_t *argv); + reg_t kPalCycle(EngineState *s, int argc, reg_t *argv); reg_t kPaletteSetFade(EngineState *s, int argc, reg_t *argv); reg_t kPalVarySetVary(EngineState *s, int argc, reg_t *argv); @@ -488,6 +523,8 @@ reg_t kPalVaryMergeStart(EngineState *s, int argc, reg_t *argv); // SCI2.1 Kernel Functions reg_t kMorphOn(EngineState *s, int argc, reg_t *argv); reg_t kText(EngineState *s, int argc, reg_t *argv); +reg_t kTextSize32(EngineState *s, int argc, reg_t *argv); +reg_t kTextWidth(EngineState *s, int argc, reg_t *argv); reg_t kSave(EngineState *s, int argc, reg_t *argv); reg_t kAutoSave(EngineState *s, int argc, reg_t *argv); reg_t kList(EngineState *s, int argc, reg_t *argv); @@ -505,9 +542,9 @@ reg_t kGetSierraProfileInt(EngineState *s, int argc, reg_t *argv); reg_t kCelInfo(EngineState *s, int argc, reg_t *argv); reg_t kSetLanguage(EngineState *s, int argc, reg_t *argv); reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv); +reg_t kSetFontHeight(EngineState *s, int argc, reg_t *argv); reg_t kSetFontRes(EngineState *s, int argc, reg_t *argv); reg_t kFont(EngineState *s, int argc, reg_t *argv); -reg_t kBitmap(EngineState *s, int argc, reg_t *argv); reg_t kAddLine(EngineState *s, int argc, reg_t *argv); reg_t kUpdateLine(EngineState *s, int argc, reg_t *argv); reg_t kDeleteLine(EngineState *s, int argc, reg_t *argv); diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h index fce3230a18..3463d05e77 100644 --- a/engines/sci/engine/kernel_tables.h +++ b/engines/sci/engine/kernel_tables.h @@ -60,14 +60,19 @@ struct SciKernelMapSubEntry { #define SCI_SUBOPENTRY_TERMINATOR { SCI_VERSION_NONE, SCI_VERSION_NONE, 0, NULL, NULL, NULL, NULL } -#define SIG_SCIALL SCI_VERSION_NONE, SCI_VERSION_NONE -#define SIG_SCI0 SCI_VERSION_NONE, SCI_VERSION_01 -#define SIG_SCI1 SCI_VERSION_1_EGA_ONLY, SCI_VERSION_1_LATE -#define SIG_SCI11 SCI_VERSION_1_1, SCI_VERSION_1_1 -#define SIG_SINCE_SCI11 SCI_VERSION_1_1, SCI_VERSION_NONE -#define SIG_SINCE_SCI21 SCI_VERSION_2_1_EARLY, SCI_VERSION_3 -#define SIG_UNTIL_SCI21MID SCI_VERSION_2, SCI_VERSION_2_1_MIDDLE -#define SIG_SINCE_SCI21LATE SCI_VERSION_2_1_LATE, SCI_VERSION_3 +#define SIG_SCIALL SCI_VERSION_NONE, SCI_VERSION_NONE +#define SIG_SCI0 SCI_VERSION_NONE, SCI_VERSION_01 +#define SIG_SCI1 SCI_VERSION_1_EGA_ONLY, SCI_VERSION_1_LATE +#define SIG_SCI11 SCI_VERSION_1_1, SCI_VERSION_1_1 +#define SIG_SINCE_SCI11 SCI_VERSION_1_1, SCI_VERSION_NONE +#define SIG_SCI2 SCI_VERSION_2, SCI_VERSION_2 +#define SIG_SCI21EARLY SCI_VERSION_2_1_EARLY, SCI_VERSION_2_1_EARLY +#define SIG_UNTIL_SCI21EARLY SCI_VERSION_2, SCI_VERSION_2_1_EARLY +#define SIG_UNTIL_SCI21MID SCI_VERSION_2, SCI_VERSION_2_1_MIDDLE +#define SIG_SINCE_SCI21 SCI_VERSION_2_1_EARLY, SCI_VERSION_3 +#define SIG_SINCE_SCI21MID SCI_VERSION_2_1_MIDDLE, SCI_VERSION_3 +#define SIG_SINCE_SCI21LATE SCI_VERSION_2_1_LATE, SCI_VERSION_3 +#define SIG_SCI3 SCI_VERSION_3, SCI_VERSION_3 #define SIG_SCI16 SCI_VERSION_NONE, SCI_VERSION_1_1 #define SIG_SCI32 SCI_VERSION_2, SCI_VERSION_NONE @@ -210,7 +215,7 @@ static const SciKernelMapSubEntry kPalVary_subops[] = { { SIG_SCI16, 6, MAP_CALL(PalVaryPauseResume), "i", NULL }, #ifdef ENABLE_SCI32 { SIG_SCI32, 0, MAP_CALL(PalVarySetVary), "i(i)(i)(ii)", NULL }, - { SIG_SCI32, 1, MAP_CALL(PalVarySetPercent), "(i)(i)", NULL }, + { SIG_SCI32, 1, MAP_CALL(PalVarySetPercent), "(i)(i)", kPalVarySetPercent_workarounds }, { SIG_SCI32, 2, MAP_CALL(PalVaryGetPercent), "", NULL }, { SIG_SCI32, 3, MAP_CALL(PalVaryOff), "", NULL }, { SIG_SCI32, 4, MAP_CALL(PalVaryMergeTarget), "i", NULL }, @@ -283,6 +288,42 @@ static const SciKernelMapSubEntry kSave_subops[] = { }; // version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kFont_subops[] = { + { SIG_SINCE_SCI21MID, 0, MAP_CALL(SetFontHeight), "i", NULL }, + { SIG_SINCE_SCI21MID, 1, MAP_CALL(SetFontRes), "ii", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; + +// version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kText_subops[] = { + { SIG_SINCE_SCI21MID, 0, MAP_CALL(TextSize32), "r[r0]i(i)(i)", NULL }, + { SIG_SINCE_SCI21MID, 1, MAP_CALL(TextWidth), "ri", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; + +// version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kBitmap_subops[] = { + { SIG_SINCE_SCI21, 0, MAP_CALL(BitmapCreate), "iiii(i)(i)(i)", NULL }, + { SIG_SINCE_SCI21, 1, MAP_CALL(BitmapDestroy), "r", NULL }, + { SIG_SINCE_SCI21, 2, MAP_CALL(BitmapDrawLine), "riiiii(i)(i)", NULL }, + { SIG_SINCE_SCI21, 3, MAP_CALL(BitmapDrawView), "riii(i)(i)(0)(i)(i)", NULL }, + { SIG_SINCE_SCI21, 4, MAP_CALL(BitmapDrawText), "rriiiiiiiiiii", NULL }, + { SIG_SINCE_SCI21, 5, MAP_CALL(BitmapDrawColor), "riiiii", NULL }, + { SIG_SINCE_SCI21, 6, MAP_CALL(BitmapDrawBitmap), "rr(i)(i)(i)", NULL }, + { SIG_SINCE_SCI21, 7, MAP_CALL(BitmapInvert), "riiiiii", NULL }, + { SIG_SINCE_SCI21MID, 8, MAP_CALL(BitmapSetDisplace), "rii", NULL }, + { SIG_SINCE_SCI21MID, 9, MAP_CALL(BitmapCreateFromView), "iii(i)(i)(i)([r0])", NULL }, + { SIG_SINCE_SCI21MID, 10, MAP_CALL(BitmapCopyPixels), "rr", NULL }, + { SIG_SINCE_SCI21MID, 11, MAP_CALL(BitmapClone), "r", NULL }, + { SIG_SINCE_SCI21LATE, 12, MAP_CALL(BitmapGetInfo), "r(i)(i)", NULL }, + { SIG_SINCE_SCI21LATE, 13, MAP_CALL(BitmapScale), "r...ii", NULL }, + { SIG_SCI3, 14, MAP_CALL(BitmapCreateFromUnknown), "......", NULL }, + { SIG_SCI3, 15, MAP_EMPTY(Bitmap), "(.*)", NULL }, + { SIG_SCI3, 16, MAP_EMPTY(Bitmap), "(.*)", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; + +// version, subId, function-mapping, signature, workarounds static const SciKernelMapSubEntry kList_subops[] = { { SIG_SINCE_SCI21, 0, MAP_CALL(NewList), "", NULL }, { SIG_SINCE_SCI21, 1, MAP_CALL(DisposeList), "l", NULL }, @@ -311,6 +352,17 @@ static const SciKernelMapSubEntry kList_subops[] = { }; // version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kRemapColors_subops[] = { + { SIG_SCI32, 0, MAP_CALL(RemapOff), "(i)", NULL }, + { SIG_SCI32, 1, MAP_CALL(RemapByRange), "iiii(i)", NULL }, + { SIG_SCI32, 2, MAP_CALL(RemapByPercent), "ii(i)", NULL }, + { SIG_SCI32, 3, MAP_CALL(RemapToGray), "ii(i)", NULL }, + { SIG_SCI32, 4, MAP_CALL(RemapToPercentGray), "iii(i)", NULL }, + { SIG_SCI32, 5, MAP_CALL(RemapSetNoMatchRange), "ii", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; + +// version, subId, function-mapping, signature, workarounds static const SciKernelMapSubEntry kString_subops[] = { { SIG_SCI32, 0, MAP_CALL(StringNew), "i(i)", NULL }, { SIG_SCI32, 1, MAP_CALL(StringSize), "[or]", NULL }, @@ -352,6 +404,31 @@ static const SciKernelMapSubEntry kString_subops[] = { SCI_SUBOPENTRY_TERMINATOR }; +// version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kScrollWindow_subops[] = { + { SIG_SCI32, 0, MAP_CALL(ScrollWindowCreate), "oi", NULL }, + { SIG_SCI32, 1, MAP_CALL(ScrollWindowAdd), "o.ii.(.)", NULL }, + { SIG_SCI32, 2, MAP_DUMMY(ScrollWindowClear), "o", NULL }, + { SIG_SCI32, 3, MAP_DUMMY(ScrollWindowPageUp), "o", NULL }, + { SIG_SCI32, 4, MAP_DUMMY(ScrollWindowPageDown), "o", NULL }, + { SIG_SCI32, 5, MAP_DUMMY(ScrollWindowUpArrow), "o", NULL }, + { SIG_SCI32, 6, MAP_DUMMY(ScrollWindowDownArrow), "o", NULL }, + { SIG_SCI32, 7, MAP_DUMMY(ScrollWindowHome), "o", NULL }, + { SIG_SCI32, 8, MAP_DUMMY(ScrollWindowEnd), "o", NULL }, + { SIG_SCI32, 9, MAP_DUMMY(ScrollWindowResize), "o.", NULL }, + { SIG_SCI32, 10, MAP_CALL(ScrollWindowWhere), "oi", NULL }, + { SIG_SCI32, 11, MAP_DUMMY(ScrollWindowGo), "o..", NULL }, + { SIG_SCI32, 12, MAP_DUMMY(ScrollWindowInsert), "o.....", NULL }, + { SIG_SCI32, 13, MAP_DUMMY(ScrollWindowDelete), "o.", NULL }, + { SIG_SCI32, 14, MAP_DUMMY(ScrollWindowModify), "o.....(.)", NULL }, + { SIG_SCI32, 15, MAP_DUMMY(ScrollWindowHide), "o", NULL }, + { SIG_SCI32, 16, MAP_CALL(ScrollWindowShow), "o", NULL }, + { SIG_SCI32, 17, MAP_CALL(ScrollWindowDestroy), "o", NULL }, + { SIG_SCI32, 18, MAP_DUMMY(ScrollWindowText), "o", NULL }, + { SIG_SCI32, 19, MAP_DUMMY(ScrollWindowReconstruct), "o.", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; + #endif struct SciKernelMapEntry { @@ -380,12 +457,16 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(AvoidPath), SIG_EVERYWHERE, "ii(.*)", NULL, NULL }, { MAP_CALL(BaseSetter), SIG_EVERYWHERE, "o", NULL, NULL }, { MAP_CALL(CanBeHere), SIG_EVERYWHERE, "o(l)", NULL, NULL }, + { MAP_CALL(CantBeHere), SIG_SCI16, SIGFOR_ALL, "o(l)", NULL, NULL }, #ifdef ENABLE_SCI32 - { "CantBeHere", kCantBeHere32, SIG_SCI32, SIGFOR_ALL, "ol", NULL, NULL }, + { MAP_CALL(CantBeHere), SIG_SCI32, SIGFOR_ALL, "ol", NULL, NULL }, +#endif + { MAP_CALL(CelHigh), SIG_SCI16, SIGFOR_ALL, "ii(i)", NULL, kCelHigh_workarounds }, + { MAP_CALL(CelWide), SIG_SCI16, SIGFOR_ALL, "ii(i)", NULL, kCelWide_workarounds }, +#ifdef ENABLE_SCI32 + { "CelHigh", kCelHigh32, SIG_SCI32, SIGFOR_ALL, "iii", NULL, NULL }, + { "CelWide", kCelWide32, SIG_SCI32, SIGFOR_ALL, "iii", NULL, NULL }, #endif - { MAP_CALL(CantBeHere), SIG_EVERYWHERE, "o(l)", NULL, NULL }, - { MAP_CALL(CelHigh), SIG_EVERYWHERE, "ii(i)", NULL, kCelHigh_workarounds }, - { MAP_CALL(CelWide), SIG_EVERYWHERE, "ii(i)", NULL, kCelWide_workarounds }, { MAP_CALL(CheckFreeSpace), SIG_SCI32, SIGFOR_ALL, "r.*", NULL, NULL }, { MAP_CALL(CheckFreeSpace), SIG_SCI11, SIGFOR_ALL, "r(i)", NULL, NULL }, { MAP_CALL(CheckFreeSpace), SIG_EVERYWHERE, "r", NULL, NULL }, @@ -484,9 +565,9 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(PriCoord), SIG_EVERYWHERE, "i", NULL, NULL }, { MAP_CALL(Random), SIG_EVERYWHERE, "i(i)(i)", NULL, NULL }, { MAP_CALL(ReadNumber), SIG_EVERYWHERE, "r", NULL, kReadNumber_workarounds }, - { MAP_CALL(RemapColors), SIG_SCI11, SIGFOR_ALL, "i(i)(i)(i)(i)", NULL, NULL }, + { "RemapColors", kRemapColors16, SIG_SCI11, SIGFOR_ALL, "i(i)(i)(i)(i)", NULL, NULL }, #ifdef ENABLE_SCI32 - { "RemapColors", kRemapColors32, SIG_SCI32, SIGFOR_ALL, "i(i)(i)(i)(i)(i)", NULL, NULL }, + { MAP_CALL(RemapColors), SIG_SCI32, SIGFOR_ALL, "i(i)(i)(i)(i)(i)", kRemapColors_subops, NULL }, #endif { MAP_CALL(ResCheck), SIG_EVERYWHERE, "ii(iiii)", NULL, NULL }, { MAP_CALL(RespondsTo), SIG_EVERYWHERE, ".i", NULL, NULL }, @@ -502,7 +583,10 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(SetDebug), SIG_EVERYWHERE, "(i*)", NULL, NULL }, { MAP_CALL(SetJump), SIG_EVERYWHERE, "oiii", NULL, NULL }, { MAP_CALL(SetMenu), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, - { MAP_CALL(SetNowSeen), SIG_EVERYWHERE, "o(i)", NULL, NULL }, + { MAP_CALL(SetNowSeen), SIG_SCI16, SIGFOR_ALL, "o(i)", NULL, NULL }, +#ifdef ENABLE_SCI32 + { MAP_CALL(SetNowSeen), SIG_SCI32, SIGFOR_ALL, "o", NULL, NULL }, +#endif { MAP_CALL(SetPort), SIG_EVERYWHERE, "i(iiiii)(i)", NULL, kSetPort_workarounds }, { MAP_CALL(SetQuitStr), SIG_EVERYWHERE, "r", NULL, NULL }, { MAP_CALL(SetSynonyms), SIG_EVERYWHERE, "o", NULL, NULL }, @@ -520,10 +604,10 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(StrEnd), SIG_EVERYWHERE, "r", NULL, NULL }, { MAP_CALL(StrLen), SIG_EVERYWHERE, "[r0]", NULL, kStrLen_workarounds }, { MAP_CALL(StrSplit), SIG_EVERYWHERE, "rr[r0]", NULL, NULL }, - { MAP_CALL(TextColors), SIG_EVERYWHERE, "(i*)", NULL, NULL }, - { MAP_CALL(TextFonts), SIG_EVERYWHERE, "(i*)", NULL, NULL }, - { MAP_CALL(TextSize), SIG_SCIALL, SIGFOR_MAC, "r[r0]i(i)(r0)(i)", NULL, NULL }, - { MAP_CALL(TextSize), SIG_EVERYWHERE, "r[r0]i(i)(r0)", NULL, NULL }, + { MAP_CALL(TextColors), SIG_SCI16, SIGFOR_ALL, "(i*)", NULL, NULL }, + { MAP_CALL(TextFonts), SIG_SCI16, SIGFOR_ALL, "(i*)", NULL, NULL }, + { MAP_CALL(TextSize), SIG_SCI16, SIGFOR_MAC, "r[r0]i(i)(r0)(i)", NULL, NULL }, + { MAP_CALL(TextSize), SIG_SCI16, SIGFOR_ALL, "r[r0]i(i)(r0)", NULL, NULL }, { MAP_CALL(TimesCos), SIG_EVERYWHERE, "ii", NULL, NULL }, { "CosMult", kTimesCos, SIG_EVERYWHERE, "ii", NULL, NULL }, { MAP_CALL(TimesCot), SIG_EVERYWHERE, "ii", NULL, NULL }, @@ -555,14 +639,18 @@ static SciKernelMapEntry s_kernelMap[] = { #ifdef ENABLE_SCI32 // SCI2 Kernel Functions // TODO: whoever knows his way through those calls, fix the signatures. + { "TextSize", kTextSize32, SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "r[r0]i(i)", NULL, NULL }, + { MAP_DUMMY(TextColors), SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "(.*)", NULL, NULL }, + { MAP_DUMMY(TextFonts), SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "(.*)", NULL, NULL }, + { MAP_CALL(AddPlane), SIG_EVERYWHERE, "o", NULL, NULL }, { MAP_CALL(AddScreenItem), SIG_EVERYWHERE, "o", NULL, NULL }, { MAP_CALL(Array), SIG_EVERYWHERE, "(.*)", NULL, NULL }, { MAP_CALL(CreateTextBitmap), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, { MAP_CALL(DeletePlane), SIG_EVERYWHERE, "o", NULL, NULL }, { MAP_CALL(DeleteScreenItem), SIG_EVERYWHERE, "o", NULL, NULL }, - { MAP_CALL(DisposeTextBitmap), SIG_EVERYWHERE, "r", NULL, NULL }, - { MAP_CALL(FrameOut), SIG_EVERYWHERE, "", NULL, NULL }, + { "DisposeTextBitmap", kBitmapDestroy, SIG_SCI2, SIGFOR_ALL, "r", NULL, NULL }, + { MAP_CALL(FrameOut), SIG_EVERYWHERE, "(i)", NULL, NULL }, { MAP_CALL(GetHighPlanePri), SIG_EVERYWHERE, "", NULL, NULL }, { MAP_CALL(InPolygon), SIG_EVERYWHERE, "iio", NULL, NULL }, { MAP_CALL(IsHiRes), SIG_EVERYWHERE, "", NULL, NULL }, @@ -623,9 +711,9 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_DUMMY(MarkMemory), SIG_EVERYWHERE, "(.*)", NULL, NULL }, { MAP_DUMMY(GetHighItemPri), SIG_EVERYWHERE, "(.*)", NULL, NULL }, { MAP_DUMMY(ShowStylePercent), SIG_EVERYWHERE, "(.*)", NULL, NULL }, - { MAP_DUMMY(InvertRect), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_DUMMY(InvertRect), SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "(.*)", NULL, NULL }, { MAP_DUMMY(InputText), SIG_EVERYWHERE, "(.*)", NULL, NULL }, - { MAP_DUMMY(TextWidth), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(TextWidth), SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "ri", NULL, NULL }, { MAP_DUMMY(PointSize), SIG_EVERYWHERE, "(.*)", NULL, NULL }, // SCI2.1 Kernel Functions @@ -636,18 +724,18 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(PlayVMD), SIG_EVERYWHERE, "(.*)", NULL, NULL }, { MAP_CALL(Robot), SIG_EVERYWHERE, "(.*)", NULL, NULL }, { MAP_CALL(Save), SIG_EVERYWHERE, "i(.*)", kSave_subops, NULL }, - { MAP_CALL(Text), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(Text), SIG_SINCE_SCI21MID, SIGFOR_ALL, "i(.*)", kText_subops, NULL }, { MAP_CALL(AddPicAt), SIG_EVERYWHERE, "oiii", NULL, NULL }, { MAP_CALL(GetWindowsOption), SIG_EVERYWHERE, "i", NULL, NULL }, { MAP_CALL(WinHelp), SIG_EVERYWHERE, "(.*)", NULL, NULL }, { MAP_CALL(GetConfig), SIG_EVERYWHERE, "ro", NULL, NULL }, { MAP_CALL(GetSierraProfileInt), SIG_EVERYWHERE, "rri", NULL, NULL }, - { MAP_CALL(CelInfo), SIG_EVERYWHERE, "iiiiii", NULL, NULL }, - { MAP_CALL(SetLanguage), SIG_EVERYWHERE, "r", NULL, NULL }, - { MAP_CALL(ScrollWindow), SIG_EVERYWHERE, "io(.*)", NULL, NULL }, - { MAP_CALL(SetFontRes), SIG_EVERYWHERE, "ii", NULL, NULL }, - { MAP_CALL(Font), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, - { MAP_CALL(Bitmap), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(CelInfo), SIG_SINCE_SCI21MID, SIGFOR_ALL, "iiiiii", NULL, NULL }, + { MAP_CALL(SetLanguage), SIG_SINCE_SCI21MID, SIGFOR_ALL, "r", NULL, NULL }, + { MAP_CALL(ScrollWindow), SIG_EVERYWHERE, "i(.*)", kScrollWindow_subops, NULL }, + { MAP_CALL(SetFontRes), SIG_SCI21EARLY, SIGFOR_ALL, "ii", NULL, NULL }, + { MAP_CALL(Font), SIG_SINCE_SCI21MID, SIGFOR_ALL, "i(.*)", kFont_subops, NULL }, + { MAP_CALL(Bitmap), SIG_EVERYWHERE, "(.*)", kBitmap_subops, NULL }, { MAP_CALL(AddLine), SIG_EVERYWHERE, "oiiiiiiiii", NULL, NULL }, { MAP_CALL(UpdateLine), SIG_EVERYWHERE, "[r0]oiiiiiiiii", NULL, NULL }, { MAP_CALL(DeleteLine), SIG_EVERYWHERE, "[r0]o", NULL, NULL }, @@ -699,9 +787,9 @@ static SciKernelMapEntry s_kernelMap[] = { // <lskovlun> The idea, if I understand correctly, is that the engine generates events // of a special HotRect type continuously when the mouse is on that rectangle - // MovePlaneItems - used by SQ6 to scroll through the inventory via the up/down buttons - // SetPalStyleRange - 2 integer parameters, start and end. All styles from start-end - // (inclusive) are set to 0 + // Used by SQ6 to scroll through the inventory via the up/down buttons + { MAP_CALL(MovePlaneItems), SIG_SINCE_SCI21, SIGFOR_ALL, "oii(i)", NULL, NULL }, + { MAP_CALL(SetPalStyleRange), SIG_EVERYWHERE, "ii", NULL, NULL }, { MAP_CALL(MorphOn), SIG_EVERYWHERE, "", NULL, NULL }, @@ -1004,7 +1092,6 @@ static const char *const sci2_default_knames[] = { /*0x89*/ "TextWidth", // for debugging(?), only in SCI2, not used in any SCI2 game /*0x8a*/ "PointSize", // for debugging(?), only in SCI2, not used in any SCI2 game - // GK2 Demo (and similar) only kernel functions /*0x8b*/ "AddLine", /*0x8c*/ "DeleteLine", /*0x8d*/ "UpdateLine", diff --git a/engines/sci/engine/kevent.cpp b/engines/sci/engine/kevent.cpp index bb595e9960..534d9ce713 100644 --- a/engines/sci/engine/kevent.cpp +++ b/engines/sci/engine/kevent.cpp @@ -83,11 +83,12 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) { } // For a real event we use its associated mouse position - mousePos = curEvent.mousePos; #ifdef ENABLE_SCI32 - if (getSciVersion() >= SCI_VERSION_2_1_EARLY) - g_sci->_gfxCoordAdjuster->fromDisplayToScript(mousePos.y, mousePos.x); + if (getSciVersion() >= SCI_VERSION_2) + mousePos = curEvent.mousePosSci; + else #endif + mousePos = curEvent.mousePos; // Limit the mouse cursor position, if necessary g_sci->_gfxCursor->refreshPosition(); @@ -101,7 +102,25 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) { // question. Check GfxCursor::setPosition(), for a more detailed // explanation and a list of cursor position workarounds. if (s->_cursorWorkaroundRect.contains(mousePos.x, mousePos.y)) { - s->_cursorWorkaroundActive = false; + // For OpenPandora and possibly other platforms, that support analog-stick control + touch screen + // control at the same time: in case the cursor is currently at the coordinate set by the scripts, + // we will count down instead of immediately disabling the workaround. + // On OpenPandora the cursor position is set, but it's overwritten shortly afterwards by the + // touch screen. In this case we would sometimes disable the workaround, simply because the touch + // screen hasn't yet overwritten the position and thus the workaround would not work anymore. + // On OpenPandora it would sometimes work and sometimes not without this. + if (s->_cursorWorkaroundPoint == mousePos) { + // Cursor is still at the same spot as set by the scripts + if (s->_cursorWorkaroundPosCount > 0) { + s->_cursorWorkaroundPosCount--; + } else { + // Was for quite a bit of time at that spot, so disable workaround now + s->_cursorWorkaroundActive = false; + } + } else { + // Cursor has moved, but is within the rect -> disable workaround immediately + s->_cursorWorkaroundActive = false; + } } else { mousePos.x = s->_cursorWorkaroundPoint.x; mousePos.y = s->_cursorWorkaroundPoint.y; diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp index 979fa95a42..335763a35f 100644 --- a/engines/sci/engine/kfile.cpp +++ b/engines/sci/engine/kfile.cpp @@ -37,7 +37,6 @@ #include "sci/engine/state.h" #include "sci/engine/kernel.h" #include "sci/engine/savegame.h" -#include "sci/graphics/menu.h" #include "sci/sound/audio.h" #include "sci/console.h" @@ -602,6 +601,16 @@ reg_t kFileIOExists(EngineState *s, int argc, reg_t *argv) { bool exists = false; + if (g_sci->getGameId() == GID_PEPPER) { + // HACK: Special case for Pepper's Adventure in Time + // The game checks like crazy for the file CDAUDIO when entering the game menu. + // On at least Windows that makes the engine slow down to a crawl and takes at least 1 second. + // Should get solved properly by changing the code below. This here is basically for 1.8.0 release. + // TODO: Fix this properly. + if (name == "CDAUDIO") + return NULL_REG; + } + // Check for regular file exists = Common::File::exists(name); @@ -907,50 +916,8 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) { gamestate_restore(s, in); delete in; - switch (g_sci->getGameId()) { - case GID_MOTHERGOOSE: - // WORKAROUND: Mother Goose SCI0 - // Script 200 / rm200::newRoom will set global C5h directly right after creating a child to the - // current number of children plus 1. - // We can't trust that global, that's why we set the actual savedgame id right here directly after - // restoring a saved game. - // If we didn't, the game would always save to a new slot - s->variables[VAR_GLOBAL][0xC5].setOffset(SAVEGAMEID_OFFICIALRANGE_START + savegameId); - break; - case GID_MOTHERGOOSE256: - // WORKAROUND: Mother Goose SCI1/SCI1.1 does some weird things for - // saving a previously restored game. - // We set the current savedgame-id directly and remove the script - // code concerning this via script patch. - s->variables[VAR_GLOBAL][0xB3].setOffset(SAVEGAMEID_OFFICIALRANGE_START + savegameId); - break; - case GID_JONES: - // HACK: The code that enables certain menu items isn't called when a game is restored from the - // launcher, or the "Restore game" option in the game's main menu - bugs #6537 and #6723. - // These menu entries are disabled when the game is launched, and are enabled when a new game is - // started. The code for enabling these entries is is all in script 1, room1::init, but that code - // path is never followed in these two cases (restoring game from the menu, or restoring a game - // from the ScummVM launcher). Thus, we perform the calls to enable the menus ourselves here. - // These two are needed when restoring from the launcher - // FIXME: The original interpreter saves and restores the menu state, so these attributes - // are automatically reset there. We may want to do the same. - g_sci->_gfxMenu->kernelSetAttribute(257 >> 8, 257 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Sierra -> About Jones - g_sci->_gfxMenu->kernelSetAttribute(258 >> 8, 258 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Sierra -> Help - // The rest are normally enabled from room1::init - g_sci->_gfxMenu->kernelSetAttribute(769 >> 8, 769 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Options -> Delete current player - g_sci->_gfxMenu->kernelSetAttribute(513 >> 8, 513 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Game -> Save Game - g_sci->_gfxMenu->kernelSetAttribute(515 >> 8, 515 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Game -> Restore Game - g_sci->_gfxMenu->kernelSetAttribute(1025 >> 8, 1025 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Status -> Statistics - g_sci->_gfxMenu->kernelSetAttribute(1026 >> 8, 1026 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Status -> Goals - break; - case GID_PQ2: - // HACK: Same as above - enable the save game menu option when loading in PQ2 (bug #6875). - // It gets disabled in the game's death screen. - g_sci->_gfxMenu->kernelSetAttribute(2, 1, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Game -> Save Game - break; - default: - break; - } + gamestate_afterRestoreFixUp(s, savegameId); + } else { s->r_acc = TRUE_REG; warning("Savegame #%d not found", savegameId); diff --git a/engines/sci/engine/kgraphics.cpp b/engines/sci/engine/kgraphics.cpp index 0b945c1eec..73236b98ed 100644 --- a/engines/sci/engine/kgraphics.cpp +++ b/engines/sci/engine/kgraphics.cpp @@ -45,6 +45,7 @@ #include "sci/graphics/paint16.h" #include "sci/graphics/picture.h" #include "sci/graphics/ports.h" +#include "sci/graphics/remap.h" #include "sci/graphics/screen.h" #include "sci/graphics/text16.h" #include "sci/graphics/view.h" @@ -359,12 +360,7 @@ reg_t kTextSize(EngineState *s, int argc, reg_t *argv) { uint16 languageSplitter = 0; Common::String splitText = g_sci->strSplitLanguage(text.c_str(), &languageSplitter, sep); -#ifdef ENABLE_SCI32 - if (g_sci->_gfxText32) - g_sci->_gfxText32->kernelTextSize(splitText.c_str(), font_nr, maxwidth, &textWidth, &textHeight); - else -#endif - g_sci->_gfxText16->kernelTextSize(splitText.c_str(), languageSplitter, font_nr, maxwidth, &textWidth, &textHeight); + g_sci->_gfxText16->kernelTextSize(splitText.c_str(), languageSplitter, font_nr, maxwidth, &textWidth, &textHeight); // One of the game texts in LB2 German contains loads of spaces in // its end. We trim the text here, otherwise the graphics code will @@ -445,8 +441,15 @@ reg_t kCantBeHere(EngineState *s, int argc, reg_t *argv) { reg_t curObject = argv[0]; reg_t listReference = (argc > 1) ? argv[1] : NULL_REG; - reg_t canBeHere = g_sci->_gfxCompare->kernelCanBeHere(curObject, listReference); - return canBeHere; +#ifdef ENABLE_SCI32 + if (getSciVersion() >= SCI_VERSION_2) { + return g_sci->_gfxCompare->kernelCantBeHere32(curObject, listReference); + } else { +#endif + return g_sci->_gfxCompare->kernelCanBeHere(curObject, listReference); +#ifdef ENABLE_SCI32 + } +#endif } reg_t kIsItSkip(EngineState *s, int argc, reg_t *argv) { @@ -492,7 +495,7 @@ reg_t kNumLoops(EngineState *s, int argc, reg_t *argv) { loopCount = g_sci->_gfxCache->kernelViewGetLoopCount(viewId); - debugC(kDebugLevelGraphics, "NumLoops(view.%d) = %d", viewId, loopCount); + debugC(9, kDebugLevelGraphics, "NumLoops(view.%d) = %d", viewId, loopCount); return make_reg(0, loopCount); } @@ -505,7 +508,7 @@ reg_t kNumCels(EngineState *s, int argc, reg_t *argv) { celCount = g_sci->_gfxCache->kernelViewGetCelCount(viewId, loopNo); - debugC(kDebugLevelGraphics, "NumCels(view.%d, %d) = %d", viewId, loopNo, celCount); + debugC(9, kDebugLevelGraphics, "NumCels(view.%d, %d) = %d", viewId, loopNo, celCount); return make_reg(0, celCount); } @@ -576,9 +579,17 @@ reg_t kBaseSetter(EngineState *s, int argc, reg_t *argv) { } reg_t kSetNowSeen(EngineState *s, int argc, reg_t *argv) { - g_sci->_gfxCompare->kernelSetNowSeen(argv[0]); - - return s->r_acc; +#ifdef ENABLE_SCI32 + if (getSciVersion() >= SCI_VERSION_2) { + g_sci->_gfxFrameout->kernelSetNowSeen(argv[0]); + return NULL_REG; + } else { +#endif + g_sci->_gfxCompare->kernelSetNowSeen(argv[0]); + return s->r_acc; +#ifdef ENABLE_SCI32 + } +#endif } reg_t kPalette(EngineState *s, int argc, reg_t *argv) { @@ -1247,22 +1258,22 @@ reg_t kShow(EngineState *s, int argc, reg_t *argv) { } // Early variant of the SCI32 kRemapColors kernel function, used in the demo of QFG4 -reg_t kRemapColors(EngineState *s, int argc, reg_t *argv) { +reg_t kRemapColors16(EngineState *s, int argc, reg_t *argv) { uint16 operation = argv[0].toUint16(); switch (operation) { case 0: { // remap by percent uint16 percent = argv[1].toUint16(); - g_sci->_gfxPalette16->resetRemapping(); - g_sci->_gfxPalette16->setRemappingPercent(254, percent); + g_sci->_gfxRemap16->resetRemapping(); + g_sci->_gfxRemap16->setRemappingPercent(254, percent); } break; case 1: { // remap by range uint16 from = argv[1].toUint16(); uint16 to = argv[2].toUint16(); uint16 base = argv[3].toUint16(); - g_sci->_gfxPalette16->resetRemapping(); - g_sci->_gfxPalette16->setRemappingRange(254, from, to, base); + g_sci->_gfxRemap16->resetRemapping(); + g_sci->_gfxRemap16->setRemappingRange(254, from, to, base); } break; case 2: // turn remapping off (unused) diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp index 8d41393a9e..7850a10006 100644 --- a/engines/sci/engine/kgraphics32.cpp +++ b/engines/sci/engine/kgraphics32.cpp @@ -45,15 +45,17 @@ #include "sci/graphics/paint16.h" #include "sci/graphics/picture.h" #include "sci/graphics/ports.h" +#include "sci/graphics/remap.h" #include "sci/graphics/screen.h" #include "sci/graphics/text16.h" #include "sci/graphics/view.h" #ifdef ENABLE_SCI32 -#include "sci/graphics/palette32.h" +#include "sci/graphics/celobj32.h" #include "sci/graphics/controls32.h" #include "sci/graphics/font.h" // TODO: remove once kBitmap is moved in a separate class -#include "sci/graphics/text32.h" #include "sci/graphics/frameout.h" +#include "sci/graphics/palette32.h" +#include "sci/graphics/text32.h" #endif namespace Sci { @@ -62,63 +64,67 @@ namespace Sci { extern void showScummVMDialog(const Common::String &message); reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv) { - // Returns 0 if the screen width or height is less than 640 or 400, - // respectively. - if (g_system->getWidth() < 640 || g_system->getHeight() < 400) + const Buffer &buffer = g_sci->_gfxFrameout->getCurrentBuffer(); + if (buffer.screenWidth < 640 || buffer.screenHeight < 400) return make_reg(0, 0); return make_reg(0, 1); } -// SCI32 variant, can't work like sci16 variants -reg_t kCantBeHere32(EngineState *s, int argc, reg_t *argv) { - // TODO -// reg_t curObject = argv[0]; -// reg_t listReference = (argc > 1) ? argv[1] : NULL_REG; - - return NULL_REG; -} - reg_t kAddScreenItem(EngineState *s, int argc, reg_t *argv) { - if (g_sci->_gfxFrameout->findScreenItem(argv[0]) == NULL) - g_sci->_gfxFrameout->kernelAddScreenItem(argv[0]); - else - g_sci->_gfxFrameout->kernelUpdateScreenItem(argv[0]); + debugC(6, kDebugLevelGraphics, "kAddScreenItem %x:%x (%s)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0])); + g_sci->_gfxFrameout->kernelAddScreenItem(argv[0]); return s->r_acc; } reg_t kUpdateScreenItem(EngineState *s, int argc, reg_t *argv) { + debugC(7, kDebugLevelGraphics, "kUpdateScreenItem %x:%x (%s)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0])); g_sci->_gfxFrameout->kernelUpdateScreenItem(argv[0]); return s->r_acc; } reg_t kDeleteScreenItem(EngineState *s, int argc, reg_t *argv) { + debugC(6, kDebugLevelGraphics, "kDeleteScreenItem %x:%x (%s)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0])); g_sci->_gfxFrameout->kernelDeleteScreenItem(argv[0]); return s->r_acc; } reg_t kAddPlane(EngineState *s, int argc, reg_t *argv) { + debugC(6, kDebugLevelGraphics, "kAddPlane %x:%x (%s)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0])); g_sci->_gfxFrameout->kernelAddPlane(argv[0]); return s->r_acc; } +reg_t kUpdatePlane(EngineState *s, int argc, reg_t *argv) { + debugC(7, kDebugLevelGraphics, "kUpdatePlane %x:%x (%s)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0])); + g_sci->_gfxFrameout->kernelUpdatePlane(argv[0]); + return s->r_acc; +} + reg_t kDeletePlane(EngineState *s, int argc, reg_t *argv) { + debugC(6, kDebugLevelGraphics, "kDeletePlane %x:%x (%s)", PRINT_REG(argv[0]), s->_segMan->getObjectName(argv[0])); g_sci->_gfxFrameout->kernelDeletePlane(argv[0]); return s->r_acc; } -reg_t kUpdatePlane(EngineState *s, int argc, reg_t *argv) { - g_sci->_gfxFrameout->kernelUpdatePlane(argv[0]); +reg_t kMovePlaneItems(EngineState *s, int argc, reg_t *argv) { + const reg_t plane = argv[0]; + const int16 deltaX = argv[1].toSint16(); + const int16 deltaY = argv[2].toSint16(); + const bool scrollPics = argc > 3 ? argv[3].toUint16() : false; + + g_sci->_gfxFrameout->kernelMovePlaneItems(plane, deltaX, deltaY, scrollPics); return s->r_acc; } reg_t kAddPicAt(EngineState *s, int argc, reg_t *argv) { reg_t planeObj = argv[0]; GuiResourceId pictureId = argv[1].toUint16(); - int16 pictureX = argv[2].toSint16(); - int16 pictureY = argv[3].toSint16(); + int16 x = argv[2].toSint16(); + int16 y = argv[3].toSint16(); + bool mirrorX = argc > 4 ? argv[4].toSint16() : false; - g_sci->_gfxFrameout->kernelAddPicAt(planeObj, pictureId, pictureX, pictureY); + g_sci->_gfxFrameout->kernelAddPicAt(planeObj, pictureId, x, y, mirrorX); return s->r_acc; } @@ -127,44 +133,16 @@ reg_t kGetHighPlanePri(EngineState *s, int argc, reg_t *argv) { } reg_t kFrameOut(EngineState *s, int argc, reg_t *argv) { -/* TODO: Transcribed from SCI engine disassembly. - GraphicsMgr &graphicsMgr = g_sci->_graphicsMgr; - if (graphicsMgr.palMorphNeeded) { - graphicsMgr.PalMorphFrameOut(&g_PalStyleRanges, false); - } - else { - // TODO: Not sure if this is a pointer or not yet. - if (g_ScrollState != nullptr) { - kFrameOutDoScroll(); - } - - bool showBits = true; - if (argc == 1) { - showBits = (bool) argv[0].toUint16(); - } - - rect SOL_Rect = { .left = 0, .top = 0, .right = UINT32_MAX, .bottom = UINT32_MAX }; - graphicsMgr.FrameOut(showBits, &rect); - } -*/ - g_sci->_gfxFrameout->kernelFrameout(); - return NULL_REG; + bool showBits = argc > 0 ? argv[0].toUint16() : true; + g_sci->_gfxFrameout->kernelFrameOut(showBits); + s->speedThrottler(16); + s->_throttleTrigger = true; + return s->r_acc; } reg_t kSetPalStyleRange(EngineState *s, int argc, reg_t *argv) { -/* TODO: Transcribed from SCI engine disassembly. - uint16 start = argv[0].toUint16(); - uint16 end = argv[1].toUint16(); - if (end <= start) { - uint16 index = start; - while (index <= end) { - g_PalStyleRanges[index] = 0; - } - } -*/ - - kStub(s, argc, argv); - return NULL_REG; + g_sci->_gfxFrameout->kernelSetPalStyleRange(argv[0].toUint16(), argv[1].toUint16()); + return s->r_acc; } reg_t kObjectIntersect(EngineState *s, int argc, reg_t *argv) { @@ -173,81 +151,92 @@ reg_t kObjectIntersect(EngineState *s, int argc, reg_t *argv) { return make_reg(0, objRect1.intersects(objRect2)); } -// Tests if the coordinate is on the passed object reg_t kIsOnMe(EngineState *s, int argc, reg_t *argv) { - uint16 x = argv[0].toUint16(); - uint16 y = argv[1].toUint16(); - reg_t targetObject = argv[2]; - uint16 illegalBits = argv[3].getOffset(); - Common::Rect nsRect = g_sci->_gfxCompare->getNSRect(targetObject); - - uint16 itemX = readSelectorValue(s->_segMan, targetObject, SELECTOR(x)); - uint16 itemY = readSelectorValue(s->_segMan, targetObject, SELECTOR(y)); - // If top and left are negative, we need to adjust coordinates by the item's x and y - if (nsRect.left < 0) - nsRect.translate(itemX, 0); - if (nsRect.top < 0) - nsRect.translate(0, itemY); - - // we assume that x, y are local coordinates - - bool contained = nsRect.contains(x, y); - if (contained && illegalBits) { - // If illegalbits are set, we check the color of the pixel that got clicked on - // for now, we return false if the pixel is transparent - // although illegalBits may get differently set, don't know yet how this really works out - uint16 viewId = readSelectorValue(s->_segMan, targetObject, SELECTOR(view)); - int16 loopNo = readSelectorValue(s->_segMan, targetObject, SELECTOR(loop)); - int16 celNo = readSelectorValue(s->_segMan, targetObject, SELECTOR(cel)); - if (g_sci->_gfxCompare->kernelIsItSkip(viewId, loopNo, celNo, Common::Point(x - nsRect.left, y - nsRect.top))) - contained = false; - } - return make_reg(0, contained); + int16 x = argv[0].toSint16(); + int16 y = argv[1].toSint16(); + reg_t object = argv[2]; + bool checkPixel = argv[3].toSint16(); + + return g_sci->_gfxFrameout->kernelIsOnMe(object, Common::Point(x, y), checkPixel); } reg_t kCreateTextBitmap(EngineState *s, int argc, reg_t *argv) { - switch (argv[0].toUint16()) { - case 0: { - if (argc != 4) { - warning("kCreateTextBitmap(0): expected 4 arguments, got %i", argc); - return NULL_REG; - } - reg_t object = argv[3]; - Common::String text = s->_segMan->getString(readSelector(s->_segMan, object, SELECTOR(text))); - debugC(kDebugLevelStrings, "kCreateTextBitmap case 0 (%04x:%04x, %04x:%04x, %04x:%04x)", - PRINT_REG(argv[1]), PRINT_REG(argv[2]), PRINT_REG(argv[3])); - debugC(kDebugLevelStrings, "%s", text.c_str()); - int16 maxWidth = argv[1].toUint16(); - int16 maxHeight = argv[2].toUint16(); - g_sci->_gfxCoordAdjuster->fromScriptToDisplay(maxHeight, maxWidth); - // These values can be larger than the screen in the SQ6 demo, room 100 - // TODO: Find out why. For now, don't show any text in that room. - if (g_sci->getGameId() == GID_SQ6 && g_sci->isDemo() && s->currentRoomNumber() == 100) - return NULL_REG; - return g_sci->_gfxText32->createTextBitmap(object, maxWidth, maxHeight); - } - case 1: { - if (argc != 2) { - warning("kCreateTextBitmap(1): expected 2 arguments, got %i", argc); - return NULL_REG; - } - reg_t object = argv[1]; - Common::String text = s->_segMan->getString(readSelector(s->_segMan, object, SELECTOR(text))); - debugC(kDebugLevelStrings, "kCreateTextBitmap case 1 (%04x:%04x)", PRINT_REG(argv[1])); - debugC(kDebugLevelStrings, "%s", text.c_str()); - return g_sci->_gfxText32->createTextBitmap(object); - } - default: - warning("CreateTextBitmap(%d)", argv[0].toUint16()); + SegManager *segMan = s->_segMan; + + int16 subop = argv[0].toUint16(); + + int16 width = 0; + int16 height = 0; + reg_t object; + + if (subop == 0) { + width = argv[1].toUint16(); + height = argv[2].toUint16(); + object = argv[3]; + } else if (subop == 1) { + object = argv[1]; + } else { + warning("Invalid kCreateTextBitmap subop %d", subop); return NULL_REG; } + + Common::String text = segMan->getString(readSelector(segMan, object, SELECTOR(text))); + int16 foreColor = readSelectorValue(segMan, object, SELECTOR(fore)); + int16 backColor = readSelectorValue(segMan, object, SELECTOR(back)); + int16 skipColor = readSelectorValue(segMan, object, SELECTOR(skip)); + GuiResourceId fontId = (GuiResourceId)readSelectorValue(segMan, object, SELECTOR(font)); + int16 borderColor = readSelectorValue(segMan, object, SELECTOR(borderColor)); + int16 dimmed = readSelectorValue(segMan, object, SELECTOR(dimmed)); + + Common::Rect rect( + readSelectorValue(segMan, object, SELECTOR(textLeft)), + readSelectorValue(segMan, object, SELECTOR(textTop)), + readSelectorValue(segMan, object, SELECTOR(textRight)) + 1, + readSelectorValue(segMan, object, SELECTOR(textBottom)) + 1 + ); + + if (subop == 0) { + TextAlign alignment = (TextAlign)readSelectorValue(segMan, object, SELECTOR(mode)); + return g_sci->_gfxText32->createFontBitmap(width, height, rect, text, foreColor, backColor, skipColor, fontId, alignment, borderColor, dimmed, true); + } else { + CelInfo32 celInfo; + celInfo.type = kCelTypeView; + celInfo.resourceId = readSelectorValue(segMan, object, SELECTOR(view)); + celInfo.loopNo = readSelectorValue(segMan, object, SELECTOR(loop)); + celInfo.celNo = readSelectorValue(segMan, object, SELECTOR(cel)); + return g_sci->_gfxText32->createFontBitmap(celInfo, rect, text, foreColor, backColor, fontId, skipColor, borderColor, dimmed); + } +} + +reg_t kText(EngineState *s, int argc, reg_t *argv) { + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); } -reg_t kDisposeTextBitmap(EngineState *s, int argc, reg_t *argv) { - g_sci->_gfxText32->disposeTextBitmap(argv[0]); +reg_t kTextSize32(EngineState *s, int argc, reg_t *argv) { + g_sci->_gfxText32->setFont(argv[2].toUint16()); + + reg_t *rect = s->_segMan->derefRegPtr(argv[0], 4); + + Common::String text = s->_segMan->getString(argv[1]); + int16 maxWidth = argc > 3 ? argv[3].toSint16() : 0; + bool doScaling = argc > 4 ? argv[4].toSint16() : true; + + Common::Rect textRect = g_sci->_gfxText32->getTextSize(text, maxWidth, doScaling); + rect[0] = make_reg(0, textRect.left); + rect[1] = make_reg(0, textRect.top); + rect[2] = make_reg(0, textRect.right - 1); + rect[3] = make_reg(0, textRect.bottom - 1); return s->r_acc; } +reg_t kTextWidth(EngineState *s, int argc, reg_t *argv) { + g_sci->_gfxText32->setFont(argv[1].toUint16()); + Common::String text = s->_segMan->getString(argv[0]); + return make_reg(0, g_sci->_gfxText32->getStringWidth(text)); +} + reg_t kWinHelp(EngineState *s, int argc, reg_t *argv) { switch (argv[0].toUint16()) { case 1: @@ -266,98 +255,137 @@ reg_t kWinHelp(EngineState *s, int argc, reg_t *argv) { } /** - * Used for scene transitions, replacing (but reusing parts of) the old - * transition code. + * Causes an immediate plane transition with an optional transition + * effect */ reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv) { - // Can be called with 7 or 8 parameters - // The style defines which transition to perform. Related to the transition - // tables inside graphics/transitions.cpp - uint16 showStyle = argv[0].toUint16(); // 0 - 15 - reg_t planeObj = argv[1]; // the affected plane - Common::String planeObjName = s->_segMan->getObjectName(planeObj); - uint16 seconds = argv[2].toUint16(); // seconds that the transition lasts - uint16 backColor = argv[3].toUint16(); // target back color(?). When fading out, it's 0x0000. When fading in, it's 0xffff - int16 priority = argv[4].toSint16(); // always 0xc8 (200) when fading in/out - uint16 animate = argv[5].toUint16(); // boolean, animate or not while the transition lasts - uint16 refFrame = argv[6].toUint16(); // refFrame, always 0 when fading in/out + ShowStyleType type = (ShowStyleType)argv[0].toUint16(); + reg_t planeObj = argv[1]; + int16 seconds = argv[2].toSint16(); + // NOTE: This value seems to indicate whether the transition is an + // “exit” transition (0) or an “enter” transition (-1) for fade + // transitions. For other types of transitions, it indicates a palette + // index value to use when filling the screen. + int16 back = argv[3].toSint16(); + int16 priority = argv[4].toSint16(); + int16 animate = argv[5].toSint16(); + // TODO: Rename to frameOutNow? + int16 refFrame = argv[6].toSint16(); + int16 blackScreen; + reg_t pFadeArray; int16 divisions; - // If the game has the pFadeArray selector, another parameter is used here, - // before the optional last parameter - bool hasFadeArray = g_sci->getKernel()->findSelector("pFadeArray") > 0; - if (hasFadeArray) { - // argv[7] - divisions = (argc >= 9) ? argv[8].toSint16() : -1; // divisions (transition steps?) - } else { - divisions = (argc >= 8) ? argv[7].toSint16() : -1; // divisions (transition steps?) + // SCI 2–2.1early + if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) { + blackScreen = 0; + pFadeArray = NULL_REG; + divisions = argc > 7 ? argv[7].toSint16() : -1; } - - if (showStyle > 15) { - warning("kSetShowStyle: Illegal style %d for plane %04x:%04x", showStyle, PRINT_REG(planeObj)); - return s->r_acc; + // SCI 2.1mid–2.1late + else if (getSciVersion() < SCI_VERSION_3) { + blackScreen = 0; + pFadeArray = argc > 7 ? argv[7] : NULL_REG; + divisions = argc > 8 ? argv[8].toSint16() : -1; + } + // SCI 3 + else { + blackScreen = argv[7].toSint16(); + pFadeArray = argc > 8 ? argv[8] : NULL_REG; + divisions = argc > 9 ? argv[9].toSint16() : -1; } - // GK1 calls fadeout (13) / fadein (14) with the following parameters: - // seconds: 1 - // backColor: 0 / -1 - // fade: 200 - // animate: 0 - // refFrame: 0 - // divisions: 0 / 20 +// TODO: Reuse later for SCI2 and SCI3 implementation and then discard +// warning("kSetShowStyle: effect %d, plane: %04x:%04x (%s), sec: %d, " +// "dir: %d, prio: %d, animate: %d, ref frame: %d, black screen: %d, " +// "pFadeArray: %04x:%04x (%s), divisions: %d", +// type, PRINT_REG(planeObj), s->_segMan->getObjectName(planeObj), seconds, +// back, priority, animate, refFrame, blackScreen, +// PRINT_REG(pFadeArray), s->_segMan->getObjectName(pFadeArray), divisions); - // TODO: Check if the plane is in the list of planes to draw + // NOTE: The order of planeObj and showStyle are reversed + // because this is how SCI3 called the corresponding method + // on the KernelMgr + g_sci->_gfxFrameout->kernelSetShowStyle(argc, planeObj, type, seconds, back, priority, animate, refFrame, pFadeArray, divisions, blackScreen); - Common::String effectName = "unknown"; + return s->r_acc; +} - switch (showStyle) { - case 0: // no transition / show - effectName = "show"; +reg_t kCelHigh32(EngineState *s, int argc, reg_t *argv) { + GuiResourceId resourceId = argv[0].toUint16(); + int16 loopNo = argv[1].toSint16(); + int16 celNo = argv[2].toSint16(); + CelObjView celObj(resourceId, loopNo, celNo); + return make_reg(0, mulru(celObj._height, Ratio(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight, celObj._scaledHeight))); +} + +reg_t kCelWide32(EngineState *s, int argc, reg_t *argv) { + GuiResourceId resourceId = argv[0].toUint16(); + int16 loopNo = argv[1].toSint16(); + int16 celNo = argv[2].toSint16(); + CelObjView celObj(resourceId, loopNo, celNo); + return make_reg(0, mulru(celObj._width, Ratio(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth, celObj._scaledWidth))); +} + +reg_t kCelInfo(EngineState *s, int argc, reg_t *argv) { + // Used by Shivers 1, room 23601 to determine what blocks on the red door puzzle board + // are occupied by pieces already + + CelObjView view(argv[1].toUint16(), argv[2].toSint16(), argv[3].toSint16()); + + int16 result = 0; + + switch (argv[0].toUint16()) { + case 0: + result = view._displace.x; break; - case 13: // fade out - effectName = "fade out"; - // TODO + case 1: + result = view._displace.y; break; - case 14: // fade in - effectName = "fade in"; - // TODO + case 2: + case 3: + // null operation break; - default: - // TODO + case 4: + result = view.readPixel(argv[4].toSint16(), argv[5].toSint16(), view._mirrorX); break; } - warning("kSetShowStyle: effect %d (%s) - plane: %04x:%04x (%s), sec: %d, " - "back: %d, prio: %d, animate: %d, ref frame: %d, divisions: %d", - showStyle, effectName.c_str(), PRINT_REG(planeObj), planeObjName.c_str(), - seconds, backColor, priority, animate, refFrame, divisions); - return s->r_acc; + return make_reg(0, result); } -reg_t kCelInfo(EngineState *s, int argc, reg_t *argv) { - // Used by Shivers 1, room 23601 to determine what blocks on the red door puzzle board - // are occupied by pieces already +reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) { + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} - switch (argv[0].toUint16()) { // subops 0 - 4 - // 0 - return the view - // 1 - return the loop - // 2, 3 - nop - case 4: { - GuiResourceId viewId = argv[1].toSint16(); - int16 loopNo = argv[2].toSint16(); - int16 celNo = argv[3].toSint16(); - int16 x = argv[4].toUint16(); - int16 y = argv[5].toUint16(); - byte color = g_sci->_gfxCache->kernelViewGetColorAtCoordinate(viewId, loopNo, celNo, x, y); - return make_reg(0, color); - } - default: { - kStub(s, argc, argv); - return s->r_acc; - } - } +reg_t kScrollWindowCreate(EngineState *s, int argc, reg_t *argv) { + debug("kScrollWindowCreate"); + kStub(s, argc, argv); + return argv[0]; +} + +reg_t kScrollWindowAdd(EngineState *s, int argc, reg_t *argv) { + debug("kScrollWindowAdd"); + return kStubNull(s, argc, argv); } +reg_t kScrollWindowWhere(EngineState *s, int argc, reg_t *argv) { + debug("kScrollWindowWhere"); + return kStubNull(s, argc, argv); +} + +reg_t kScrollWindowShow(EngineState *s, int argc, reg_t *argv) { + debug("kScrollWindowShow"); + return kStubNull(s, argc, argv); +} + +reg_t kScrollWindowDestroy(EngineState *s, int argc, reg_t *argv) { + debug("kScrollWindowDestroy"); + return kStubNull(s, argc, argv); +} + +#if 0 reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) { // Used by SQ6 and LSL6 hires for the text area in the bottom of the // screen. The relevant scripts also exist in Phantasmagoria 1, but they're @@ -465,226 +493,233 @@ reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) { return s->r_acc; } +#endif + +reg_t kFont(EngineState *s, int argc, reg_t *argv) { + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} + +reg_t kSetFontHeight(EngineState *s, int argc, reg_t *argv) { + // TODO: Setting font may have just been for side effect + // of setting the fontHeight on the font manager, in + // which case we could just get the font directly ourselves. + g_sci->_gfxText32->setFont(argv[0].toUint16()); + g_sci->_gfxText32->_scaledHeight = (g_sci->_gfxText32->_font->getHeight() * g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight + g_sci->_gfxText32->_scaledHeight - 1) / g_sci->_gfxText32->_scaledHeight; + return make_reg(0, g_sci->_gfxText32->_scaledHeight); +} reg_t kSetFontRes(EngineState *s, int argc, reg_t *argv) { - // TODO: This defines the resolution that the fonts are supposed to be displayed - // in. Currently, this is only used for showing high-res fonts in GK1 Mac, but - // should be extended to handle other font resolutions such as those + g_sci->_gfxText32->_scaledWidth = argv[0].toUint16(); + g_sci->_gfxText32->_scaledHeight = argv[1].toUint16(); + return s->r_acc; +} - int xResolution = argv[0].toUint16(); - //int yResolution = argv[1].toUint16(); +reg_t kBitmap(EngineState *s, int argc, reg_t *argv) { + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} - g_sci->_gfxScreen->setFontIsUpscaled(xResolution == 640 && - g_sci->_gfxScreen->getUpscaledHires() != GFX_SCREEN_UPSCALED_DISABLED); +reg_t kBitmapCreate(EngineState *s, int argc, reg_t *argv) { + int16 width = argv[0].toSint16(); + int16 height = argv[1].toSint16(); + int16 skipColor = argv[2].toSint16(); + int16 backColor = argv[3].toSint16(); + int16 scaledWidth = argc > 4 ? argv[4].toSint16() : g_sci->_gfxText32->_scaledWidth; + int16 scaledHeight = argc > 5 ? argv[5].toSint16() : g_sci->_gfxText32->_scaledHeight; + bool useRemap = argc > 6 ? argv[6].toSint16() : false; - return s->r_acc; + BitmapResource bitmap(s->_segMan, width, height, skipColor, 0, 0, scaledWidth, scaledHeight, 0, useRemap); + memset(bitmap.getPixels(), backColor, width * height); + return bitmap.getObject(); } -reg_t kFont(EngineState *s, int argc, reg_t *argv) { - // Handle font settings for SCI2.1 +reg_t kBitmapDestroy(EngineState *s, int argc, reg_t *argv) { + s->_segMan->freeHunkEntry(argv[0]); + return s->r_acc; +} - switch (argv[0].toUint16()) { - case 1: - // Set font resolution - return kSetFontRes(s, argc - 1, argv + 1); - default: - warning("kFont: unknown subop %d", argv[0].toUint16()); +reg_t kBitmapDrawLine(EngineState *s, int argc, reg_t *argv) { + // bitmapMemId, (x1, y1, x2, y2) OR (x2, y2, x1, y1), line color, unknown int, unknown int + return kStubNull(s, argc + 1, argv - 1); +} + +reg_t kBitmapDrawView(EngineState *s, int argc, reg_t *argv) { + // viewId, loopNo, celNo, displace x, displace y, unused, view x, view y + + // called e.g. from TiledBitmap::resize() in Torin's Passage, script 64869 + // The tiled view seems to always have 2 loops. + // These loops need to have 1 cel in loop 0 and 8 cels in loop 1. + + return kStubNull(s, argc + 1, argv - 1); + +#if 0 + // tiled surface + // 6 params, called e.g. from TiledBitmap::resize() in Torin's Passage, + // script 64869 + reg_t hunkId = argv[1]; // obtained from kBitmap(0) + // The tiled view seems to always have 2 loops. + // These loops need to have 1 cel in loop 0 and 8 cels in loop 1. + uint16 viewNum = argv[2].toUint16(); // vTiles selector + uint16 loop = argv[3].toUint16(); + uint16 cel = argv[4].toUint16(); + uint16 x = argv[5].toUint16(); + uint16 y = argv[6].toUint16(); + + byte *memoryPtr = s->_segMan->getHunkPointer(hunkId); + // Get totalWidth, totalHeight + uint16 totalWidth = READ_LE_UINT16(memoryPtr); + uint16 totalHeight = READ_LE_UINT16(memoryPtr + 2); + byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE; + + GfxView *view = g_sci->_gfxCache->getView(viewNum); + uint16 tileWidth = view->getWidth(loop, cel); + uint16 tileHeight = view->getHeight(loop, cel); + const byte *tileBitmap = view->getBitmap(loop, cel); + uint16 width = MIN<uint16>(totalWidth - x, tileWidth); + uint16 height = MIN<uint16>(totalHeight - y, tileHeight); + + for (uint16 curY = 0; curY < height; curY++) { + for (uint16 curX = 0; curX < width; curX++) { + bitmap[(curY + y) * totalWidth + (curX + x)] = tileBitmap[curY * tileWidth + curX]; + } } +#endif +} + +reg_t kBitmapDrawText(EngineState *s, int argc, reg_t *argv) { + // called e.g. from TextButton::createBitmap() in Torin's Passage, script 64894 + + BitmapResource bitmap(argv[0]); + Common::String text = s->_segMan->getString(argv[1]); + Common::Rect textRect( + argv[2].toSint16(), + argv[3].toSint16(), + argv[4].toSint16() + 1, + argv[5].toSint16() + 1 + ); + int16 foreColor = argv[6].toSint16(); + int16 backColor = argv[7].toSint16(); + int16 skipColor = argv[8].toSint16(); + GuiResourceId fontId = (GuiResourceId)argv[9].toUint16(); + TextAlign alignment = (TextAlign)argv[10].toSint16(); + int16 borderColor = argv[11].toSint16(); + bool dimmed = argv[12].toUint16(); + + // NOTE: Technically the engine checks these things: + // textRect.bottom > 0 + // textRect.right > 0 + // textRect.left < bitmap.width + // textRect.top < bitmap.height + // Then clips. But this seems stupid. + textRect.clip(Common::Rect(bitmap.getWidth(), bitmap.getHeight())); + + reg_t textBitmapObject = g_sci->_gfxText32->createFontBitmap(textRect.width(), textRect.height(), Common::Rect(textRect.width(), textRect.height()), text, foreColor, backColor, skipColor, fontId, alignment, borderColor, dimmed, false); + Buffer bitmapBuffer(bitmap.getWidth(), bitmap.getHeight(), bitmap.getPixels()); + CelObjMem textCel(textBitmapObject); + textCel.draw(bitmapBuffer, textRect, Common::Point(textRect.left, textRect.top), false); + s->_segMan->freeHunkEntry(textBitmapObject); return s->r_acc; } -// TODO: Eventually, all of the kBitmap operations should be put -// in a separate class +reg_t kBitmapDrawColor(EngineState *s, int argc, reg_t *argv) { + // called e.g. from TextView::init() and TextView::draw() in Torin's Passage, script 64890 -#define BITMAP_HEADER_SIZE 46 + BitmapResource bitmap(argv[0]); + Common::Rect fillRect( + argv[1].toSint16(), + argv[2].toSint16(), + argv[3].toSint16() + 1, + argv[4].toSint16() + 1 + ); -reg_t kBitmap(EngineState *s, int argc, reg_t *argv) { - // Used for bitmap operations in SCI2.1 and SCI3. - // This is the SCI2.1 version, the functionality seems to have changed in SCI3. + Buffer buffer(bitmap.getWidth(), bitmap.getHeight(), bitmap.getPixels()); + buffer.fillRect(fillRect, argv[5].toSint16()); + return s->r_acc; +} - switch (argv[0].toUint16()) { - case 0: // init bitmap surface - { - // 6 params, called e.g. from TextView::init() in Torin's Passage, - // script 64890 and TransView::init() in script 64884 - uint16 width = argv[1].toUint16(); - uint16 height = argv[2].toUint16(); - //uint16 skip = argv[3].toUint16(); - uint16 back = argv[4].toUint16(); // usually equals skip - //uint16 width2 = (argc >= 6) ? argv[5].toUint16() : 0; - //uint16 height2 = (argc >= 7) ? argv[6].toUint16() : 0; - //uint16 transparentFlag = (argc >= 8) ? argv[7].toUint16() : 0; - - // TODO: skip, width2, height2, transparentFlag - // (used for transparent bitmaps) - int entrySize = width * height + BITMAP_HEADER_SIZE; - reg_t memoryId = s->_segMan->allocateHunkEntry("Bitmap()", entrySize); - byte *memoryPtr = s->_segMan->getHunkPointer(memoryId); - memset(memoryPtr, 0, BITMAP_HEADER_SIZE); // zero out the bitmap header - memset(memoryPtr + BITMAP_HEADER_SIZE, back, width * height); - // Save totalWidth, totalHeight - // TODO: Save the whole bitmap header, like SSCI does - WRITE_LE_UINT16(memoryPtr, width); - WRITE_LE_UINT16(memoryPtr + 2, height); - return memoryId; - } - break; - case 1: // dispose text bitmap surface - return kDisposeTextBitmap(s, argc - 1, argv + 1); - case 2: // dispose bitmap surface, with extra param - // 2 params, called e.g. from MenuItem::dispose in Torin's Passage, - // script 64893 - warning("kBitmap(2), unk1 %d, bitmap ptr %04x:%04x", argv[1].toUint16(), PRINT_REG(argv[2])); - break; - case 3: // tiled surface - { - // 6 params, called e.g. from TiledBitmap::resize() in Torin's Passage, - // script 64869 - reg_t hunkId = argv[1]; // obtained from kBitmap(0) - // The tiled view seems to always have 2 loops. - // These loops need to have 1 cel in loop 0 and 8 cels in loop 1. - uint16 viewNum = argv[2].toUint16(); // vTiles selector - uint16 loop = argv[3].toUint16(); - uint16 cel = argv[4].toUint16(); - uint16 x = argv[5].toUint16(); - uint16 y = argv[6].toUint16(); - - byte *memoryPtr = s->_segMan->getHunkPointer(hunkId); - // Get totalWidth, totalHeight - uint16 totalWidth = READ_LE_UINT16(memoryPtr); - uint16 totalHeight = READ_LE_UINT16(memoryPtr + 2); - byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE; - - GfxView *view = g_sci->_gfxCache->getView(viewNum); - uint16 tileWidth = view->getWidth(loop, cel); - uint16 tileHeight = view->getHeight(loop, cel); - const byte *tileBitmap = view->getBitmap(loop, cel); - uint16 width = MIN<uint16>(totalWidth - x, tileWidth); - uint16 height = MIN<uint16>(totalHeight - y, tileHeight); - - for (uint16 curY = 0; curY < height; curY++) { - for (uint16 curX = 0; curX < width; curX++) { - bitmap[(curY + y) * totalWidth + (curX + x)] = tileBitmap[curY * tileWidth + curX]; - } - } +reg_t kBitmapDrawBitmap(EngineState *s, int argc, reg_t *argv) { + // target bitmap, source bitmap, x, y, unknown boolean - } - break; - case 4: // add text to bitmap - { - // 13 params, called e.g. from TextButton::createBitmap() in Torin's Passage, - // script 64894 - reg_t hunkId = argv[1]; // obtained from kBitmap(0) - Common::String text = s->_segMan->getString(argv[2]); - uint16 textX = argv[3].toUint16(); - uint16 textY = argv[4].toUint16(); - //reg_t unk5 = argv[5]; - //reg_t unk6 = argv[6]; - //reg_t unk7 = argv[7]; // skip? - //reg_t unk8 = argv[8]; // back? - //reg_t unk9 = argv[9]; - uint16 fontId = argv[10].toUint16(); - //uint16 mode = argv[11].toUint16(); - uint16 dimmed = argv[12].toUint16(); - //warning("kBitmap(4): bitmap ptr %04x:%04x, font %d, mode %d, dimmed %d - text: \"%s\"", - // PRINT_REG(bitmapPtr), font, mode, dimmed, text.c_str()); - uint16 foreColor = 255; // TODO - - byte *memoryPtr = s->_segMan->getHunkPointer(hunkId); - // Get totalWidth, totalHeight - uint16 totalWidth = READ_LE_UINT16(memoryPtr); - uint16 totalHeight = READ_LE_UINT16(memoryPtr + 2); - byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE; - - GfxFont *font = g_sci->_gfxCache->getFont(fontId); - - int16 charCount = 0; - uint16 curX = textX, curY = textY; - const char *txt = text.c_str(); - - while (*txt) { - charCount = g_sci->_gfxText32->GetLongest(txt, totalWidth, font); - if (charCount == 0) - break; - - for (int i = 0; i < charCount; i++) { - unsigned char curChar = txt[i]; - font->drawToBuffer(curChar, curY, curX, foreColor, dimmed, bitmap, totalWidth, totalHeight); - curX += font->getCharWidth(curChar); - } - - curX = textX; - curY += font->getHeight(); - txt += charCount; - while (*txt == ' ') - txt++; // skip over breaking spaces - } + return kStubNull(s, argc + 1, argv - 1); +} - } - break; - case 5: // fill with color - { - // 6 params, called e.g. from TextView::init() and TextView::draw() - // in Torin's Passage, script 64890 - reg_t hunkId = argv[1]; // obtained from kBitmap(0) - uint16 x = argv[2].toUint16(); - uint16 y = argv[3].toUint16(); - uint16 fillWidth = argv[4].toUint16(); // width - 1 - uint16 fillHeight = argv[5].toUint16(); // height - 1 - uint16 back = argv[6].toUint16(); - - byte *memoryPtr = s->_segMan->getHunkPointer(hunkId); - // Get totalWidth, totalHeight - uint16 totalWidth = READ_LE_UINT16(memoryPtr); - uint16 totalHeight = READ_LE_UINT16(memoryPtr + 2); - uint16 width = MIN<uint16>(totalWidth - x, fillWidth); - uint16 height = MIN<uint16>(totalHeight - y, fillHeight); - byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE; - - for (uint16 curY = 0; curY < height; curY++) { - for (uint16 curX = 0; curX < width; curX++) { - bitmap[(curY + y) * totalWidth + (curX + x)] = back; - } - } +reg_t kBitmapInvert(EngineState *s, int argc, reg_t *argv) { + // bitmap, left, top, right, bottom, foreColor, backColor - } - break; - default: - kStub(s, argc, argv); - break; - } + return kStubNull(s, argc + 1, argv - 1); +} +reg_t kBitmapSetDisplace(EngineState *s, int argc, reg_t *argv) { + BitmapResource bitmap(argv[0]); + bitmap.setDisplace(Common::Point(argv[1].toSint16(), argv[2].toSint16())); return s->r_acc; } -// Used for edit boxes in save/load dialogs. It's a rewritten version of kEditControl, -// but it handles events on its own, using an internal loop, instead of using SCI -// scripts for event management like kEditControl does. Called by script 64914, -// DEdit::hilite(). -reg_t kEditText(EngineState *s, int argc, reg_t *argv) { - reg_t controlObject = argv[0]; +reg_t kBitmapCreateFromView(EngineState *s, int argc, reg_t *argv) { + // viewId, loopNo, celNo, skipColor, backColor, useRemap, source overlay bitmap - if (!controlObject.isNull()) { - g_sci->_gfxControls32->kernelTexteditChange(controlObject); - } + return kStub(s, argc + 1, argv - 1); +} - return s->r_acc; +reg_t kBitmapCopyPixels(EngineState *s, int argc, reg_t *argv) { + // target bitmap, source bitmap + + return kStubNull(s, argc + 1, argv - 1); +} + +reg_t kBitmapClone(EngineState *s, int argc, reg_t *argv) { + // bitmap + + return kStub(s, argc + 1, argv - 1); +} + +reg_t kBitmapGetInfo(EngineState *s, int argc, reg_t *argv) { + // bitmap + + // argc 1 = get width + // argc 2 = pixel at row 0 col n + // argc 3 = pixel at row n col n + return kStub(s, argc + 1, argv - 1); +} + +reg_t kBitmapScale(EngineState *s, int argc, reg_t *argv) { + // TODO: SCI3 + return kStubNull(s, argc + 1, argv - 1); +} + +reg_t kBitmapCreateFromUnknown(EngineState *s, int argc, reg_t *argv) { + // TODO: SCI3 + return kStub(s, argc + 1, argv - 1); +} + +reg_t kEditText(EngineState *s, int argc, reg_t *argv) { + return g_sci->_gfxControls32->kernelEditText(argv[0]); } reg_t kAddLine(EngineState *s, int argc, reg_t *argv) { + return kStubNull(s, argc, argv); // return 0:0 for now, so that follow up calls won't create signature mismatches +#if 0 reg_t plane = argv[0]; Common::Point startPoint(argv[1].toUint16(), argv[2].toUint16()); Common::Point endPoint(argv[3].toUint16(), argv[4].toUint16()); - // argv[5] is unknown (a number, usually 200) + byte priority = (byte)argv[5].toUint16(); byte color = (byte)argv[6].toUint16(); - byte priority = (byte)argv[7].toUint16(); - byte control = (byte)argv[8].toUint16(); - // argv[9] is unknown (usually a small number, 1 or 2). Thickness, perhaps? - return g_sci->_gfxFrameout->addPlaneLine(plane, startPoint, endPoint, color, priority, control); + byte style = (byte)argv[7].toUint16(); // 0: solid, 1: dashed, 2: pattern + byte pattern = (byte)argv[8].toUint16(); + byte thickness = (byte)argv[9].toUint16(); +// return g_sci->_gfxFrameout->addPlaneLine(plane, startPoint, endPoint, color, priority, 0); + return s->r_acc; +#endif } reg_t kUpdateLine(EngineState *s, int argc, reg_t *argv) { + return kStub(s, argc, argv); + +#if 0 reg_t hunkId = argv[0]; reg_t plane = argv[1]; Common::Point startPoint(argv[2].toUint16(), argv[3].toUint16()); @@ -694,14 +729,18 @@ reg_t kUpdateLine(EngineState *s, int argc, reg_t *argv) { byte priority = (byte)argv[8].toUint16(); byte control = (byte)argv[9].toUint16(); // argv[10] is unknown (usually a small number, 1 or 2). Thickness, perhaps? - g_sci->_gfxFrameout->updatePlaneLine(plane, hunkId, startPoint, endPoint, color, priority, control); +// g_sci->_gfxFrameout->updatePlaneLine(plane, hunkId, startPoint, endPoint, color, priority, control); return s->r_acc; +#endif } reg_t kDeleteLine(EngineState *s, int argc, reg_t *argv) { + return kStub(s, argc, argv); +#if 0 reg_t hunkId = argv[0]; reg_t plane = argv[1]; - g_sci->_gfxFrameout->deletePlaneLine(plane, hunkId); +// g_sci->_gfxFrameout->deletePlaneLine(plane, hunkId); return s->r_acc; +#endif } reg_t kSetScroll(EngineState *s, int argc, reg_t *argv) { @@ -730,13 +769,8 @@ reg_t kSetScroll(EngineState *s, int argc, reg_t *argv) { // Used by SQ6, script 900, the datacorder reprogramming puzzle (from room 270) reg_t kMorphOn(EngineState *s, int argc, reg_t *argv) { - // TODO: g_sci->_gfxManager->palMorphIsOn = true - // This function sets the palMorphIsOn flag which causes kFrameOut to use - // an alternative FrameOut function (GraphicsMgr::PalMorphFrameOut instead - // of GraphicsMgr::FrameOut). At the end of the frame, kFrameOut sets the - // palMorphIsOn flag back to false. - kStub(s, argc, argv); - return NULL_REG; + g_sci->_gfxFrameout->_palMorphIsOn = true; + return s->r_acc; } reg_t kPaletteSetFade(EngineState *s, int argc, reg_t *argv) { @@ -744,7 +778,7 @@ reg_t kPaletteSetFade(EngineState *s, int argc, reg_t *argv) { uint16 toColor = argv[1].toUint16(); uint16 percent = argv[2].toUint16(); g_sci->_gfxPalette32->setFade(percent, fromColor, toColor); - return NULL_REG; + return s->r_acc; } reg_t kPalVarySetVary(EngineState *s, int argc, reg_t *argv) { @@ -762,14 +796,14 @@ reg_t kPalVarySetVary(EngineState *s, int argc, reg_t *argv) { } g_sci->_gfxPalette32->kernelPalVarySet(paletteId, percent, time, fromColor, toColor); - return NULL_REG; + return s->r_acc; } reg_t kPalVarySetPercent(EngineState *s, int argc, reg_t *argv) { int time = argc > 0 ? argv[0].toSint16() * 60 : 0; int16 percent = argc > 1 ? argv[1].toSint16() : 0; g_sci->_gfxPalette32->setVaryPercent(percent, time, -1, -1); - return NULL_REG; + return s->r_acc; } reg_t kPalVaryGetPercent(EngineState *s, int argc, reg_t *argv) { @@ -778,7 +812,7 @@ reg_t kPalVaryGetPercent(EngineState *s, int argc, reg_t *argv) { reg_t kPalVaryOff(EngineState *s, int argc, reg_t *argv) { g_sci->_gfxPalette32->varyOff(); - return NULL_REG; + return s->r_acc; } reg_t kPalVaryMergeTarget(EngineState *s, int argc, reg_t *argv) { @@ -790,7 +824,7 @@ reg_t kPalVaryMergeTarget(EngineState *s, int argc, reg_t *argv) { reg_t kPalVarySetTime(EngineState *s, int argc, reg_t *argv) { int time = argv[0].toSint16() * 60; g_sci->_gfxPalette32->setVaryTime(time); - return NULL_REG; + return s->r_acc; } reg_t kPalVarySetTarget(EngineState *s, int argc, reg_t *argv) { @@ -875,76 +909,57 @@ reg_t kPalCycle(EngineState *s, int argc, reg_t *argv) { return s->r_acc; } -reg_t kRemapColors32(EngineState *s, int argc, reg_t *argv) { - uint16 operation = argv[0].toUint16(); +reg_t kRemapColors(EngineState *s, int argc, reg_t *argv) { + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} - switch (operation) { - case 0: { // turn remapping off - // WORKAROUND: Game scripts in QFG4 erroneously turn remapping off in room - // 140 (the character point allocation screen) and never turn it back on, - // even if it's clearly used in that screen. - if (g_sci->getGameId() == GID_QFG4 && s->currentRoomNumber() == 140) - return s->r_acc; +reg_t kRemapOff(EngineState *s, int argc, reg_t *argv) { + byte color = (argc >= 1) ? argv[0].toUint16() : 0; + g_sci->_gfxRemap32->remapOff(color); + return s->r_acc; +} - int16 base = (argc >= 2) ? argv[1].toSint16() : 0; - if (base > 0) - warning("kRemapColors(0) called with base %d", base); - g_sci->_gfxPalette32->resetRemapping(); - } - break; - case 1: { // remap by range - uint16 color = argv[1].toUint16(); - uint16 from = argv[2].toUint16(); - uint16 to = argv[3].toUint16(); - uint16 base = argv[4].toUint16(); - uint16 unk5 = (argc >= 6) ? argv[5].toUint16() : 0; - if (unk5 > 0) - warning("kRemapColors(1) called with 6 parameters, unknown parameter is %d", unk5); - g_sci->_gfxPalette32->setRemappingRange(color, from, to, base); - } - break; - case 2: { // remap by percent - uint16 color = argv[1].toUint16(); - uint16 percent = argv[2].toUint16(); // 0 - 100 - if (argc >= 4) - warning("RemapByPercent called with 4 parameters, unknown parameter is %d", argv[3].toUint16()); - g_sci->_gfxPalette32->setRemappingPercent(color, percent); - } - break; - case 3: { // remap to gray - // Example call: QFG4 room 490 (Baba Yaga's hut) - params are color 253, 75% and 0. - // In this room, it's used for the cloud before Baba Yaga appears. - int16 color = argv[1].toSint16(); - int16 percent = argv[2].toSint16(); // 0 - 100 - if (argc >= 4) - warning("RemapToGray called with 4 parameters, unknown parameter is %d", argv[3].toUint16()); - g_sci->_gfxPalette32->setRemappingPercentGray(color, percent); - } - break; - case 4: { // remap to percent gray - // Example call: QFG4 rooms 530/535 (swamp) - params are 253, 100%, 200 - int16 color = argv[1].toSint16(); - int16 percent = argv[2].toSint16(); // 0 - 100 - // argv[3] is unknown (a number, e.g. 200) - start color, perhaps? - if (argc >= 5) - warning("RemapToGrayPercent called with 5 parameters, unknown parameter is %d", argv[4].toUint16()); - g_sci->_gfxPalette32->setRemappingPercentGray(color, percent); - } - break; - case 5: { // don't map to range - //int16 mapping = argv[1].toSint16(); - uint16 intensity = argv[2].toUint16(); - // HACK for PQ4 - if (g_sci->getGameId() == GID_PQ4) - g_sci->_gfxPalette32->kernelSetIntensity(0, 255, intensity, true); +reg_t kRemapByRange(EngineState *s, int argc, reg_t *argv) { + byte color = argv[0].toUint16(); + byte from = argv[1].toUint16(); + byte to = argv[2].toUint16(); + byte base = argv[3].toUint16(); + // The last parameter, depth, is unused + g_sci->_gfxRemap32->setRemappingRange(color, from, to, base); + return s->r_acc; +} - kStub(s, argc, argv); - } - break; - default: - break; - } +reg_t kRemapByPercent(EngineState *s, int argc, reg_t *argv) { + byte color = argv[0].toUint16(); + byte percent = argv[1].toUint16(); + // The last parameter, depth, is unused + g_sci->_gfxRemap32->setRemappingPercent(color, percent); + return s->r_acc; +} + +reg_t kRemapToGray(EngineState *s, int argc, reg_t *argv) { + byte color = argv[0].toUint16(); + byte gray = argv[1].toUint16(); + // The last parameter, depth, is unused + g_sci->_gfxRemap32->setRemappingToGray(color, gray); + return s->r_acc; +} + +reg_t kRemapToPercentGray(EngineState *s, int argc, reg_t *argv) { + byte color = argv[0].toUint16(); + byte gray = argv[1].toUint16(); + byte percent = argv[2].toUint16(); + // The last parameter, depth, is unused + g_sci->_gfxRemap32->setRemappingToPercentGray(color, gray, percent); + return s->r_acc; +} +reg_t kRemapSetNoMatchRange(EngineState *s, int argc, reg_t *argv) { + byte from = argv[0].toUint16(); + byte count = argv[1].toUint16(); + g_sci->_gfxRemap32->setNoMatchRange(from, count); return s->r_acc; } diff --git a/engines/sci/engine/kmisc.cpp b/engines/sci/engine/kmisc.cpp index 9d47a37bca..f4bb4ff85b 100644 --- a/engines/sci/engine/kmisc.cpp +++ b/engines/sci/engine/kmisc.cpp @@ -268,7 +268,10 @@ reg_t kMemory(EngineState *s, int argc, reg_t *argv) { switch (argv[0].toUint16()) { case K_MEMORY_ALLOCATE_CRITICAL: { int byteCount = argv[1].toUint16(); - // WORKAROUND: + // Sierra themselves allocated at least 2 bytes more than requested. + // Probably as a safety margin. And they also made size even. + // + // This behavior is required by at least these: // - pq3 (multilingual) room 202 // when plotting crimes, allocates the returned bytes from kStrLen // on "W" and "E" and wants to put a string in there, which doesn't @@ -276,18 +279,22 @@ reg_t kMemory(EngineState *s, int argc, reg_t *argv) { // - lsl5 (multilingual) room 280 // allocates memory according to a previous kStrLen for the name of // the airport ladies (bug #3093818), which isn't enough - - // We always allocate 1 byte more, because of this - byteCount++; + byteCount += 2 + (byteCount & 1); if (!s->_segMan->allocDynmem(byteCount, "kMemory() critical", &s->r_acc)) { error("Critical heap allocation failed"); } break; } - case K_MEMORY_ALLOCATE_NONCRITICAL: - s->_segMan->allocDynmem(argv[1].toUint16(), "kMemory() non-critical", &s->r_acc); + case K_MEMORY_ALLOCATE_NONCRITICAL: { + int byteCount = argv[1].toUint16(); + + // See above + byteCount += 2 + (byteCount & 1); + + s->_segMan->allocDynmem(byteCount, "kMemory() non-critical", &s->r_acc); break; + } case K_MEMORY_FREE : if (!s->_segMan->freeDynmem(argv[1])) { if (g_sci->getGameId() == GID_QFG1VGA) { diff --git a/engines/sci/engine/kpathing.cpp b/engines/sci/engine/kpathing.cpp index 5b2245e84d..7ac744f584 100644 --- a/engines/sci/engine/kpathing.cpp +++ b/engines/sci/engine/kpathing.cpp @@ -1943,14 +1943,14 @@ static int liesBefore(const Vertex *v, const Common::Point &p1, const Common::Po // indexp1/vertexp1 on the polygon being merged. // It ends with the point intersection2, being the analogous intersection. struct Patch { - unsigned int indexw1; - unsigned int indexp1; + uint32 indexw1; + uint32 indexp1; const Vertex *vertexw1; const Vertex *vertexp1; Common::Point intersection1; - unsigned int indexw2; - unsigned int indexp2; + uint32 indexw2; + uint32 indexp2; const Vertex *vertexw2; const Vertex *vertexp2; Common::Point intersection2; @@ -1960,7 +1960,7 @@ struct Patch { // Check if the given vertex on the work polygon is bypassed by this patch. -static bool isVertexCovered(const Patch &p, unsigned int wi) { +static bool isVertexCovered(const Patch &p, uint32 wi) { // / v (outside) // ---w1--1----p----w2--2---- @@ -2402,7 +2402,7 @@ reg_t kMergePoly(EngineState *s, int argc, reg_t *argv) { // Copy work.vertices into arrayRef Vertex *vertex; - unsigned int n = 0; + uint32 n = 0; CLIST_FOREACH(vertex, &work.vertices) { if (vertex == work.vertices._head || vertex->v != vertex->_prev->v) writePoint(arrayRef, n++, vertex->v); diff --git a/engines/sci/engine/ksound.cpp b/engines/sci/engine/ksound.cpp index 032191e4c1..398a623286 100644 --- a/engines/sci/engine/ksound.cpp +++ b/engines/sci/engine/ksound.cpp @@ -206,8 +206,15 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) { // athrxx: It seems from disasm that the original KQ5 FM-Towns loads a default language (Japanese) audio map at the beginning // right after loading the video and audio drivers. The -1 language argument in here simply means that the original will stick // with Japanese. Instead of doing that we switch to the language selected in the launcher. - if (g_sci->getPlatform() == Common::kPlatformFMTowns && language == -1) - language = (g_sci->getLanguage() == Common::JA_JPN) ? K_LANG_JAPANESE : K_LANG_ENGLISH; + if (g_sci->getPlatform() == Common::kPlatformFMTowns && language == -1) { + // FM-Towns calls us to get the current language / also set the default language + // This doesn't just happen right at the start, but also when the user clicks on the Sierra logo in the game menu + // It uses the result of this call to either show "English Voices" or "Japanese Voices". + + // Language should have been set by setLauncherLanguage() already (or could have been modified by the scripts). + // Get this language setting, so that the chosen language will get set for resource manager. + language = g_sci->getSciLanguage(); + } debugC(kDebugLevelSound, "kDoAudio: set language to %d", language); @@ -233,19 +240,38 @@ reg_t kDoAudio(EngineState *s, int argc, reg_t *argv) { #endif } - // 3 new subops in Pharkas. kDoAudio in Pharkas sits at seg026:038C + // 3 new subops in Pharkas CD (including CD demo). kDoAudio in Pharkas sits at seg026:038C case 11: // Not sure where this is used yet warning("kDoAudio: Unhandled case 11, %d extra arguments passed", argc - 1); break; case 12: - // Seems to be some sort of audio sync, used in Pharkas. Silenced the - // warning due to the high level of spam it produces. (takes no params) - //warning("kDoAudio: Unhandled case 12, %d extra arguments passed", argc - 1); + // SSCI calls this function with no parameters from + // the TalkRandCycle class and branches on the return + // value like a boolean. The conjectured purpose of + // this function is to ensure that the talker's mouth + // does not move if there is read jitter (slow CD + // drive, scratched CD). The old behavior here of not + // doing anything caused a nonzero value to be left in + // the accumulator by chance. This is equivalent, but + // more explicit. + + return make_reg(0, 1); break; case 13: - // Used in Pharkas whenever a speech sample starts (takes no params) - //warning("kDoAudio: Unhandled case 13, %d extra arguments passed", argc - 1); + // SSCI returns a serial number for the played audio + // here, used in the PointsSound class. The reason is severalfold: + + // 1. SSCI does not support multiple wave effects at once + // 2. FPFP may disable its icon bar during the points sound. + // 3. Each new sound preempts any sound already playing. + // 4. If the points sound is interrupted before completion, + // the icon bar could remain disabled. + + // Since points (1) and (3) do not apply to us, we can simply + // return a constant here. This is equivalent to the + // old behavior, as above. + return make_reg(0, 1); break; case 17: // Seems to be some sort of audio sync, used in SQ6. Silenced the diff --git a/engines/sci/engine/kstring.cpp b/engines/sci/engine/kstring.cpp index 310e38dbd1..1c08bf597c 100644 --- a/engines/sci/engine/kstring.cpp +++ b/engines/sci/engine/kstring.cpp @@ -661,19 +661,6 @@ reg_t kStrSplit(EngineState *s, int argc, reg_t *argv) { #ifdef ENABLE_SCI32 -reg_t kText(EngineState *s, int argc, reg_t *argv) { - switch (argv[0].toUint16()) { - case 0: - return kTextSize(s, argc - 1, argv + 1); - default: - // TODO: Other subops here too, perhaps kTextColors and kTextFonts - warning("kText(%d)", argv[0].toUint16()); - break; - } - - return s->r_acc; -} - // TODO: there is an unused second argument, happens at least in LSL6 right during the intro reg_t kStringNew(EngineState *s, int argc, reg_t *argv) { reg_t stringHandle; @@ -778,11 +765,14 @@ reg_t kStringCopy(EngineState *s, int argc, reg_t *argv) { } // The original engine ignores bad copies too - if (index2 > string2Size) + if (index2 >= string2Size) return NULL_REG; // A count of -1 means fill the rest of the array - uint32 count = argv[4].toSint16() == -1 ? string2Size - index2 + 1 : argv[4].toUint16(); + uint32 count = string2Size - index2; + if (argv[4].toSint16() != -1) { + count = MIN(count, (uint32)argv[4].toUint16()); + } // reg_t strAddress = argv[0]; SciString *string1 = s->_segMan->lookupString(argv[0]); diff --git a/engines/sci/engine/object.cpp b/engines/sci/engine/object.cpp index 0626c084c1..0566d6955f 100644 --- a/engines/sci/engine/object.cpp +++ b/engines/sci/engine/object.cpp @@ -255,6 +255,8 @@ void Object::initSelectorsSci3(const byte *buf) { if (g_sci->getKernel()->getSelectorNamesSize() % 32) ++groups; + _mustSetViewVisible.resize(groups); + methods = properties = 0; // Selectors are divided into groups of 32, of which the first @@ -270,7 +272,9 @@ void Object::initSelectorsSci3(const byte *buf) { // This object actually has selectors belonging to this group int typeMask = READ_SCI11ENDIAN_UINT32(seeker); - for (int bit = 2; bit < 32; ++bit) { + _mustSetViewVisible[groupNr] = (typeMask & 1); + + for (int bit = 2; bit < 32; ++bit) { int value = READ_SCI11ENDIAN_UINT16(seeker + bit * 2); if (typeMask & (1 << bit)) { // Property ++properties; @@ -281,7 +285,8 @@ void Object::initSelectorsSci3(const byte *buf) { } } - } + } else + _mustSetViewVisible[groupNr] = false; } _variables.resize(properties); diff --git a/engines/sci/engine/object.h b/engines/sci/engine/object.h index 0ae7ed2cab..a7be170f4f 100644 --- a/engines/sci/engine/object.h +++ b/engines/sci/engine/object.h @@ -41,8 +41,21 @@ enum { }; enum infoSelectorFlags { - kInfoFlagClone = 0x0001, - kInfoFlagClass = 0x8000 + kInfoFlagClone = 0x0001, +#ifdef ENABLE_SCI32 + /** + * When set, indicates to game scripts that a screen + * item can be updated. + */ + kInfoFlagViewVisible = 0x0008, // TODO: "dirty" ? + + /** + * When set, the object has an associated screen item in + * the rendering tree. + */ + kInfoFlagViewInserted = 0x0010, +#endif + kInfoFlagClass = 0x8000 }; enum ObjectOffsets { @@ -120,7 +133,24 @@ public: _infoSelectorSci3 = info; } - // No setter for the -info- selector +#ifdef ENABLE_SCI32 + void setInfoSelectorFlag(infoSelectorFlags flag) { + if (getSciVersion() < SCI_VERSION_3) { + _variables[_offset + 2] |= flag; + } else { + _infoSelectorSci3 |= flag; + } + } + + // NOTE: In real engine, -info- is treated as byte size + void clearInfoSelectorFlag(infoSelectorFlags flag) { + if (getSciVersion() < SCI_VERSION_3) { + _variables[_offset + 2] &= ~flag; + } else { + _infoSelectorSci3 &= ~flag; + } + } +#endif reg_t getNameSelector() const { if (getSciVersion() < SCI_VERSION_3) @@ -232,6 +262,8 @@ public: bool initBaseObject(SegManager *segMan, reg_t addr, bool doInitSuperClass = true); void syncBaseObject(const byte *ptr) { _baseObj = ptr; } + bool mustSetViewVisibleSci3(int selector) const { return _mustSetViewVisible[selector/32]; } + private: void initSelectorsSci3(const byte *buf); @@ -248,6 +280,7 @@ private: reg_t _superClassPosSci3; /**< reg_t pointing to superclass for SCI3 */ reg_t _speciesSelectorSci3; /**< reg_t containing species "selector" for SCI3 */ reg_t _infoSelectorSci3; /**< reg_t containing info "selector" for SCI3 */ + Common::Array<bool> _mustSetViewVisible; /** cached bit of info to make lookup fast, SCI3 only */ }; diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp index 165c147c1c..fcb65157d8 100644 --- a/engines/sci/engine/savegame.cpp +++ b/engines/sci/engine/savegame.cpp @@ -39,6 +39,7 @@ #include "sci/engine/vm_types.h" #include "sci/engine/script.h" // for SCI_OBJ_EXPORTS and SCI_OBJ_SYNONYMS #include "sci/graphics/helpers.h" +#include "sci/graphics/menu.h" #include "sci/graphics/palette.h" #include "sci/graphics/ports.h" #include "sci/graphics/screen.h" @@ -957,7 +958,8 @@ bool gamestate_save(EngineState *s, Common::WriteStream *fh, const Common::Strin extern void showScummVMDialog(const Common::String &message); void gamestate_delayedrestore(EngineState *s) { - Common::String fileName = g_sci->getSavegameName(s->_delayedRestoreGameId); + int savegameId = s->_delayedRestoreGameId; // delayedRestoreGameId gets destroyed within gamestate_restore()! + Common::String fileName = g_sci->getSavegameName(savegameId); Common::SeekableReadStream *in = g_sci->getSaveFileManager()->openForLoading(fileName); if (in) { @@ -965,6 +967,7 @@ void gamestate_delayedrestore(EngineState *s) { gamestate_restore(s, in); delete in; if (s->r_acc != make_reg(0, 1)) { + gamestate_afterRestoreFixUp(s, savegameId); return; } } @@ -972,6 +975,73 @@ void gamestate_delayedrestore(EngineState *s) { error("Restoring gamestate '%s' failed", fileName.c_str()); } +void gamestate_afterRestoreFixUp(EngineState *s, int savegameId) { + switch (g_sci->getGameId()) { + case GID_MOTHERGOOSE: + // WORKAROUND: Mother Goose SCI0 + // Script 200 / rm200::newRoom will set global C5h directly right after creating a child to the + // current number of children plus 1. + // We can't trust that global, that's why we set the actual savedgame id right here directly after + // restoring a saved game. + // If we didn't, the game would always save to a new slot + s->variables[VAR_GLOBAL][0xC5].setOffset(SAVEGAMEID_OFFICIALRANGE_START + savegameId); + break; + case GID_MOTHERGOOSE256: + // WORKAROUND: Mother Goose SCI1/SCI1.1 does some weird things for + // saving a previously restored game. + // We set the current savedgame-id directly and remove the script + // code concerning this via script patch. + s->variables[VAR_GLOBAL][0xB3].setOffset(SAVEGAMEID_OFFICIALRANGE_START + savegameId); + break; + case GID_JONES: + // HACK: The code that enables certain menu items isn't called when a game is restored from the + // launcher, or the "Restore game" option in the game's main menu - bugs #6537 and #6723. + // These menu entries are disabled when the game is launched, and are enabled when a new game is + // started. The code for enabling these entries is is all in script 1, room1::init, but that code + // path is never followed in these two cases (restoring game from the menu, or restoring a game + // from the ScummVM launcher). Thus, we perform the calls to enable the menus ourselves here. + // These two are needed when restoring from the launcher + // FIXME: The original interpreter saves and restores the menu state, so these attributes + // are automatically reset there. We may want to do the same. + g_sci->_gfxMenu->kernelSetAttribute(257 >> 8, 257 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Sierra -> About Jones + g_sci->_gfxMenu->kernelSetAttribute(258 >> 8, 258 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Sierra -> Help + // The rest are normally enabled from room1::init + g_sci->_gfxMenu->kernelSetAttribute(769 >> 8, 769 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Options -> Delete current player + g_sci->_gfxMenu->kernelSetAttribute(513 >> 8, 513 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Game -> Save Game + g_sci->_gfxMenu->kernelSetAttribute(515 >> 8, 515 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Game -> Restore Game + g_sci->_gfxMenu->kernelSetAttribute(1025 >> 8, 1025 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Status -> Statistics + g_sci->_gfxMenu->kernelSetAttribute(1026 >> 8, 1026 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Status -> Goals + break; + case GID_KQ6: + if (g_sci->isCD()) { + // WORKAROUND: + // For the CD version of King's Quest 6, set global depending on current hires/lowres state + // The game sets a global at the start depending on it and some things check that global + // instead of checking platform like for example the game action menu. + // This never happened in the original interpreter, because the original DOS interpreter + // was only capable of lowres graphics and the original Windows 3.11 interpreter was only capable + // of hires graphics. Saved games were not compatible between those two. + // Which means saving during lowres mode, then going into hires mode and restoring that saved game, + // will result in some graphics being incorrect (lowres). + // That's why we are setting the global after restoring a saved game depending on hires/lowres state. + // The CD demo of KQ6 does the same and uses the exact same global. + if ((g_sci->getPlatform() == Common::kPlatformWindows) || (g_sci->forceHiresGraphics())) { + s->variables[VAR_GLOBAL][0xA9].setOffset(1); + } else { + s->variables[VAR_GLOBAL][0xA9].setOffset(0); + } + } + break; + case GID_PQ2: + // HACK: Same as above - enable the save game menu option when loading in PQ2 (bug #6875). + // It gets disabled in the game's death screen. + g_sci->_gfxMenu->kernelSetAttribute(2, 1, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Game -> Save Game + break; + default: + break; + } +} + void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) { SavegameMetadata meta; @@ -1013,13 +1083,31 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) { if (g_sci->_gfxPorts) g_sci->_gfxPorts->reset(); // clear screen - if (g_sci->_gfxScreen) - g_sci->_gfxScreen->clearForRestoreGame(); + if (getSciVersion() <= SCI_VERSION_1_1) { + // Only do clearing the screen for SCI16 + // Both SCI16 + SCI32 did not clear the screen. + // We basically do it for SCI16, because of KQ6. + // When hires portraits are shown and the user restores during that time, the portraits + // wouldn't get fully removed. In original SCI, the user wasn't able to restore during that time, + // so this is basically a workaround, so that ScummVM features work properly. + // For SCI32, behavior was verified in DOSBox, that SCI32 does not clear and also not redraw the screen. + // It only redraws elements that have changed in comparison to the state before the restore. + // If we cleared the screen for SCI32, we would have issues because of this behavior. + if (g_sci->_gfxScreen) + g_sci->_gfxScreen->clearForRestoreGame(); + } #ifdef ENABLE_SCI32 - // Also clear any SCI32 planes/screen items currently showing so they - // don't show up after the load. - if (getSciVersion() >= SCI_VERSION_2) - g_sci->_gfxFrameout->clear(); + // Delete current planes/elements of actively loaded VM, only when our ScummVM dialogs are patched in + // We MUST NOT delete all planes/screen items. At least Space Quest 6 has a few in memory like for example + // the options plane, which are not re-added and are in memory all the time right from the start of the + // game. Sierra SCI32 did not clear planes, only scripts cleared the ones inside planes::elements. + if (getSciVersion() >= SCI_VERSION_2) { + if (!s->_delayedRestoreFromLauncher) { + // Only do it, when we are restoring regulary and not from launcher + // As it could result in option planes etc. on the screen (happens in gk1) + g_sci->_gfxFrameout->syncWithScripts(false); + } + } #endif s->reset(true); @@ -1044,6 +1132,13 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) { if (g_sci->_gfxPorts) g_sci->_gfxPorts->saveLoadWithSerializer(ser); + // SCI32: + // Current planes/screen elements of freshly loaded VM are re-added by scripts in [gameID]::replay + // We don't have to do that in here. + // But we may have to do it ourselves in case we ever implement some soft-error handling in case + // a saved game can't be restored. That way we can restore the game screen. + // see _gfxFrameout->syncWithScripts() + Vocabulary *voc = g_sci->getVocabulary(); if (ser.getVersion() >= 30 && voc) voc->saveLoadWithSerializer(ser); @@ -1061,6 +1156,8 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) { // signal restored game to game scripts s->gameIsRestarting = GAMEISRESTARTING_RESTORE; + + s->_delayedRestoreFromLauncher = false; } bool get_savegame_metadata(Common::SeekableReadStream *stream, SavegameMetadata *meta) { diff --git a/engines/sci/engine/savegame.h b/engines/sci/engine/savegame.h index bb555434c9..459e992e24 100644 --- a/engines/sci/engine/savegame.h +++ b/engines/sci/engine/savegame.h @@ -87,6 +87,9 @@ bool gamestate_save(EngineState *s, Common::WriteStream *save, const Common::Str // does a delayed saved game restore, used by ScummVM game menu - see detection.cpp / SciEngine::loadGameState() void gamestate_delayedrestore(EngineState *s); +// does a few fixups right after restoring a saved game +void gamestate_afterRestoreFixUp(EngineState *s, int savegameId); + /** * Restores a game state from a directory. * @param s An older state from the same game diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp index 6915e12a0e..8039c5f282 100644 --- a/engines/sci/engine/script_patches.cpp +++ b/engines/sci/engine/script_patches.cpp @@ -101,6 +101,8 @@ static const char *const selectorNameTable[] = { "startText", // King's Quest 6 CD / Laura Bow 2 CD for audio+text support "startAudio", // King's Quest 6 CD / Laura Bow 2 CD for audio+text support "modNum", // King's Quest 6 CD / Laura Bow 2 CD for audio+text support + "cycler", // Space Quest 4 / system selector + "setLoop", // Laura Bow 1 Colonel's Bequest NULL }; @@ -127,7 +129,9 @@ enum ScriptPatcherSelectors { SELECTOR_timesShownID, SELECTOR_startText, SELECTOR_startAudio, - SELECTOR_modNum + SELECTOR_modNum, + SELECTOR_cycler, + SELECTOR_setLoop }; // =========================================================================== @@ -404,6 +408,59 @@ static const SciScriptPatcherEntry fanmadeSignatures[] = { }; // =========================================================================== + +// WORKAROUND +// Freddy Pharkas intro screen +// Sierra used inner loops for the scaling of the 2 title views. +// Those inner loops don't call kGameIsRestarting, which is why +// we do not update the screen and we also do not throttle. +// +// This patch fixes this and makes it work. +// Applies to at least: English PC-CD +// Responsible method: sTownScript::changeState(1), sTownScript::changeState(3) (script 110) +static const uint16 freddypharkasSignatureIntroScaling[] = { + 0x38, SIG_ADDTOOFFSET(+2), // pushi (setLoop) (009b for PC CD) + 0x78, // push1 + PATCH_ADDTOOFFSET(1), // push0 for first code, push1 for second code + 0x38, SIG_ADDTOOFFSET(+2), // pushi (setStep) (0143 for PC CD) + 0x7a, // push2 + 0x39, 0x05, // pushi 05 + 0x3c, // dup + 0x72, SIG_ADDTOOFFSET(+2), // lofsa (view) + SIG_MAGICDWORD, + 0x4a, 0x1e, // send 1e + 0x35, 0x0a, // ldi 0a + 0xa3, 0x02, // sal local[2] + // start of inner loop + 0x8b, 0x02, // lsl local[2] + SIG_ADDTOOFFSET(+43), // skip almost all of inner loop + 0xa3, 0x02, // sal local[2] + 0x33, 0xcf, // jmp [inner loop start] + SIG_END +}; + +static const uint16 freddypharkasPatchIntroScaling[] = { + // remove setLoop(), objects in heap are already prepared, saves 5 bytes + 0x38, + PATCH_GETORIGINALBYTE(+6), + PATCH_GETORIGINALBYTE(+7), // pushi (setStep) + 0x7a, // push2 + 0x39, 0x05, // pushi 05 + 0x3c, // dup + 0x72, + PATCH_GETORIGINALBYTE(+13), + PATCH_GETORIGINALBYTE(+14), // lofsa (view) + 0x4a, 0x18, // send 18 - adjusted + 0x35, 0x0a, // ldi 0a + 0xa3, 0x02, // sal local[2] + // start of new inner loop + 0x39, 0x00, // pushi 00 + 0x43, 0x2c, 0x00, // callk GameIsRestarting <-- add this so that our speed throttler is triggered + SIG_ADDTOOFFSET(+47), // skip almost all of inner loop + 0x33, 0xca, // jmp [inner loop start] + PATCH_END +}; + // script 0 of freddy pharkas/CD PointsSound::check waits for a signal and if // no signal received will call kDoSound(0xD) which is a dummy in sierra sci // and ScummVM and will use acc (which is not set by the dummy) to trigger @@ -522,6 +579,7 @@ static const uint16 freddypharkasPatchMacInventory[] = { static const SciScriptPatcherEntry freddypharkasSignatures[] = { { true, 0, "CD: score early disposal", 1, freddypharkasSignatureScoreDisposal, freddypharkasPatchScoreDisposal }, { true, 15, "Mac: broken inventory", 1, freddypharkasSignatureMacInventory, freddypharkasPatchMacInventory }, + { true, 110, "intro scaling workaround", 2, freddypharkasSignatureIntroScaling, freddypharkasPatchIntroScaling }, { true, 235, "CD: canister pickup hang", 3, freddypharkasSignatureCanisterHang, freddypharkasPatchCanisterHang }, { true, 320, "ladder event issue", 2, freddypharkasSignatureLadderEvent, freddypharkasPatchLadderEvent }, SCI_SIGNATUREENTRY_TERMINATOR @@ -755,6 +813,32 @@ static const uint16 kq5PatchWitchCageInit[] = { PATCH_END }; +// The multilingual releases of KQ5 hang right at the end during the magic battle with Mordack. +// It seems additional code was added to wait for signals, but the signals are never set and thus +// the game hangs. We disable that code, so that the battle works again. +// This also happened in the original interpreter. +// We must not change similar code, that happens before. + +// Applies to at least: French PC floppy, German PC floppy, Spanish PC floppy +// Responsible method: stingScript::changeState, dragonScript::changeState, snakeScript::changeState +static const uint16 kq5SignatureMultilingualEndingGlitch[] = { + SIG_MAGICDWORD, + 0x89, 0x57, // lsg global[57h] + 0x35, 0x00, // ldi 0 + 0x1a, // eq? + 0x18, // not + 0x30, SIG_UINT16(0x0011), // bnt [skip signal check] + SIG_ADDTOOFFSET(+8), // skip globalSound::prevSignal get code + 0x36, // push + 0x35, 0x0a, // ldi 0Ah + SIG_END +}; + +static const uint16 kq5PatchMultilingualEndingGlitch[] = { + PATCH_ADDTOOFFSET(+6), + 0x32, // change BNT into JMP + PATCH_END +}; // In the final battle, the DOS version uses signals in the music to handle // timing, while in the Windows version another method is used and the GM @@ -785,9 +869,10 @@ static const uint16 kq5PatchWinGMSignals[] = { // script, description, signature patch static const SciScriptPatcherEntry kq5Signatures[] = { - { true, 0, "CD: harpy volume change", 1, kq5SignatureCdHarpyVolume, kq5PatchCdHarpyVolume }, - { true, 200, "CD: witch cage init", 1, kq5SignatureWitchCageInit, kq5PatchWitchCageInit }, - { false, 124, "Win: GM Music signal checks", 4, kq5SignatureWinGMSignals, kq5PatchWinGMSignals }, + { true, 0, "CD: harpy volume change", 1, kq5SignatureCdHarpyVolume, kq5PatchCdHarpyVolume }, + { true, 200, "CD: witch cage init", 1, kq5SignatureWitchCageInit, kq5PatchWitchCageInit }, + { true, 124, "Multilingual: Ending glitching out", 3, kq5SignatureMultilingualEndingGlitch, kq5PatchMultilingualEndingGlitch }, + { false, 124, "Win: GM Music signal checks", 4, kq5SignatureWinGMSignals, kq5PatchWinGMSignals }, SCI_SIGNATUREENTRY_TERMINATOR }; @@ -1201,8 +1286,10 @@ static const uint16 kq6CDPatchAudioTextSupportJumpAlways[] = { }; // Fixes "Girl In The Tower" to get played in dual mode as well +// Also changes credits to use CD audio for dual mode. +// // Applies to at least: PC-CD -// Patched method: rm740::cue +// Patched method: rm740::cue (script 740), sCredits::init (script 52) static const uint16 kq6CDSignatureAudioTextSupportGirlInTheTower[] = { SIG_MAGICDWORD, 0x89, 0x5a, // lsg global[5a] @@ -1329,6 +1416,7 @@ static const SciScriptPatcherEntry kq6Signatures[] = { { false, 928, "CD: audio + text support KQ6 4", 1, kq6CDSignatureAudioTextSupport4, kq6CDPatchAudioTextSupport4 }, { false, 1009, "CD: audio + text support KQ6 Guards", 2, kq6CDSignatureAudioTextSupportGuards, kq6CDPatchAudioTextSupportGuards }, { false, 1027, "CD: audio + text support KQ6 Stepmother", 1, kq6CDSignatureAudioTextSupportStepmother, kq6CDPatchAudioTextSupportJumpAlways }, + { false, 52, "CD: audio + text support KQ6 Girl In The Tower", 1, kq6CDSignatureAudioTextSupportGirlInTheTower, kq6CDPatchAudioTextSupportGirlInTheTower }, { false, 740, "CD: audio + text support KQ6 Girl In The Tower", 1, kq6CDSignatureAudioTextSupportGirlInTheTower, kq6CDPatchAudioTextSupportGirlInTheTower }, { false, 370, "CD: audio + text support KQ6 Azure & Ariel", 6, kq6CDSignatureAudioTextSupportAzureAriel, kq6CDPatchAudioTextSupportAzureAriel }, { false, 903, "CD: audio + text support KQ6 menu", 1, kq6CDSignatureAudioTextMenuSupport, kq6CDPatchAudioTextMenuSupport }, @@ -1536,6 +1624,43 @@ static const SciScriptPatcherEntry larry6Signatures[] = { }; // =========================================================================== +// Laura Bow 1 - Colonel's Bequest +// +// This is basically just a broken easter egg in Colonel's Bequest. +// A plane can show up in room 4, but that only happens really rarely. +// Anyway the Sierra developer seems to have just entered the wrong loop, +// which is why the statue view is used instead (loop 0). +// We fix it to use the correct loop. +// +// This is only broken in the PC version. It was fixed for Amiga + Atari ST. +// +// Credits to OmerMor, for finding it. + +// Applies to at least: English PC Floppy +// Responsible method: room4::init +static const uint16 laurabow1SignatureEasterEggViewFix[] = { + 0x78, // push1 + 0x76, // push0 + SIG_MAGICDWORD, + 0x38, SIG_SELECTOR16(setLoop), // pushi "setLoop" + 0x78, // push1 + 0x39, 0x03, // pushi 3 (loop 3, view only has 3 loops) + SIG_END +}; + +static const uint16 laurabow1PatchEasterEggViewFix[] = { + PATCH_ADDTOOFFSET(+7), + 0x02, // change loop to 2 + PATCH_END +}; + +// script, description, signature patch +static const SciScriptPatcherEntry laurabow1Signatures[] = { + { true, 4, "easter egg view fix", 1, laurabow1SignatureEasterEggViewFix, laurabow1PatchEasterEggViewFix }, + SCI_SIGNATUREENTRY_TERMINATOR +}; + +// =========================================================================== // Laura Bow 2 // // Moving away the painting in the room with the hidden safe is problematic @@ -1840,15 +1965,103 @@ static const SciScriptPatcherEntry laurabow2Signatures[] = { // MG::replay somewhat calculates the savedgame-id used when saving again // this doesn't work right and we remove the code completely. // We set the savedgame-id directly right after restoring in kRestoreGame. +// We also draw the background picture in here instead. +// This Mixed Up Mother Goose draws the background picture before restoring, +// instead of doing it properly in MG::replay. This fixes graphic issues, +// when restoring from GMM. +// +// Applies to at least: English SCI1 CD, English SCI1.1 floppy, Japanese FM-Towns +// Responsible method: MG::replay (script 0) static const uint16 mothergoose256SignatureReplay[] = { + 0x7a, // push2 + 0x78, // push1 + 0x5b, 0x00, 0xbe, // lea global[BEh] + 0x36, // push + 0x43, 0x70, 0x04, // callk MemorySegment + 0x7a, // push2 + 0x5b, 0x00, 0xbe, // lea global[BEh] + 0x36, // push + 0x76, // push0 + 0x43, 0x62, 0x04, // callk StrAt + 0xa1, 0xaa, // sag global[AAh] + 0x7a, // push2 + 0x5b, 0x00, 0xbe, // lea global[BEh] + 0x36, // push + 0x78, // push1 + 0x43, 0x62, 0x04, // callk StrAt + 0x36, // push + 0x35, 0x20, // ldi 20 + 0x04, // sub + 0xa1, SIG_ADDTOOFFSET(+1), // sag global[57h] -> FM-Towns [9Dh] + // 35 bytes + 0x39, 0x03, // pushi 03 + 0x89, SIG_ADDTOOFFSET(+1), // lsg global[1Dh] -> FM-Towns [1Eh] + 0x76, // push0 + 0x7a, // push2 + 0x5b, 0x00, 0xbe, // lea global[BEh] + 0x36, // push + 0x7a, // push2 + 0x43, 0x62, 0x04, // callk StrAt + 0x36, // push + 0x35, 0x01, // ldi 01 + 0x04, // sub + 0x36, // push + 0x43, 0x62, 0x06, // callk StrAt + // 22 bytes + 0x7a, // push2 + 0x5b, 0x00, 0xbe, // lea global[BE] + 0x36, // push + 0x39, 0x03, // pushi 03 + 0x43, 0x62, 0x04, // callk StrAt + // 10 bytes 0x36, // push 0x35, SIG_MAGICDWORD, 0x20, // ldi 20 0x04, // sub 0xa1, 0xb3, // sag global[b3] + // 6 bytes SIG_END }; static const uint16 mothergoose256PatchReplay[] = { + 0x39, 0x06, // pushi 06 + 0x76, // push0 + 0x76, // push0 + 0x38, PATCH_UINT16(200), // pushi 200d + 0x38, PATCH_UINT16(320), // pushi 320d + 0x76, // push0 + 0x76, // push0 + 0x43, 0x15, 0x0c, // callk SetPort -> set picture port to full screen + // 15 bytes + 0x39, 0x04, // pushi 04 + 0x3c, // dup + 0x76, // push0 + 0x38, PATCH_UINT16(255), // pushi 255d + 0x76, // push0 + 0x43, 0x6f, 0x08, // callk Palette -> set intensity to 0 for all colors + // 11 bytes + 0x7a, // push2 + 0x38, PATCH_UINT16(800), // pushi 800 + 0x76, // push0 + 0x43, 0x08, 0x04, // callk DrawPic -> draw picture 800 + // 8 bytes + 0x39, 0x06, // pushi 06 + 0x39, 0x0c, // pushi 0Ch + 0x76, // push0 + 0x76, // push0 + 0x38, PATCH_UINT16(200), // push 200 + 0x38, PATCH_UINT16(320), // push 320 + 0x78, // push1 + 0x43, 0x6c, 0x0c, // callk Graph -> send everything to screen + // 16 bytes + 0x39, 0x06, // pushi 06 + 0x76, // push0 + 0x76, // push0 + 0x38, PATCH_UINT16(156), // pushi 156d + 0x38, PATCH_UINT16(258), // pushi 258d + 0x39, 0x03, // pushi 03 + 0x39, 0x04, // pushi 04 + 0x43, 0x15, 0x0c, // callk SetPort -> set picture port back + // 17 bytes 0x34, PATCH_UINT16(0x0000), // ldi 0000 (dummy) 0x34, PATCH_UINT16(0x0000), // ldi 0000 (dummy) PATCH_END @@ -1856,6 +2069,9 @@ static const uint16 mothergoose256PatchReplay[] = { // when saving, it also checks if the savegame ID is below 13. // we change this to check if below 113 instead +// +// Applies to at least: English SCI1 CD, English SCI1.1 floppy, Japanese FM-Towns +// Responsible method: Game::save (script 994 for SCI1), MG::save (script 0 for SCI1.1) static const uint16 mothergoose256SignatureSaveLimit[] = { 0x89, SIG_MAGICDWORD, 0xb3, // lsg global[b3] 0x35, 0x0d, // ldi 0d @@ -1879,6 +2095,50 @@ static const SciScriptPatcherEntry mothergoose256Signatures[] = { // =========================================================================== // Police Quest 1 VGA + +// When briefing is about to start in room 15, other officers will get into the room too. +// When one of those officers gets into the way of ego, they will tell the player to sit down. +// But control will be disabled right at that point. Ego may then go to his seat by himself, +// or more often than not will just stand there. The player is unable to do anything. +// +// Sergeant Dooley will then enter the room. Tell the player to sit down 3 times and after +// that it's game over. +// +// Because the Sergeant is telling the player to sit down, one has to assume that the player +// is meant to still be in control. Which is why this script patch removes disabling of player control. +// +// The script also tries to make ego walk to the chair, but it fails because it gets stuck with other +// actors. So I guess the safest way is to remove all of that and let the player do it manually. +// +// The responsible method seems to use a few hardcoded texts, which is why I have to assume that it's +// not used anywhere else. I also checked all scripts and couldn't find any other calls to it. +// +// This of course also happens when using the original interpreter. +// +// Scripts work like this: manX::doit (script 134) triggers gab::changeState, which then triggers rm015::notify +// +// Applies to at least: English floppy +// Responsible method: gab::changeState (script 152) +// Fixes bug: #5865 +static const uint16 pq1vgaSignatureBriefingGettingStuck[] = { + 0x76, // push0 + 0x45, 0x02, 0x00, // call export 2 of script 0 (disable control) + 0x38, SIG_ADDTOOFFSET(+2), // pushi notify + 0x76, // push0 + 0x81, 0x02, // lag global[2] (get current room) + 0x4a, 0x04, // send 04 + SIG_MAGICDWORD, + 0x8b, 0x02, // lsl local[2] + 0x35, 0x01, // ldi 01 + 0x02, // add + SIG_END +}; + +static const uint16 pq1vgaPatchBriefingGettingStuck[] = { + 0x33, 0x0a, // jmp to lsl local[2], skip over export 2 and ::notify + PATCH_END // rm015::notify would try to make ego walk to the chair +}; + // When at the police station, you can put or get your gun from your locker. // The script, that handles this, is buggy. It disposes the gun as soon as // you click, but then waits 2 seconds before it also closes the locker. @@ -1966,10 +2226,11 @@ static const uint16 pq1vgaPatchMapSaveRestoreBug[] = { PATCH_END }; -// script, description, signature patch +// script, description, signature patch static const SciScriptPatcherEntry pq1vgaSignatures[] = { - { true, 341, "put gun in locker bug", 1, pq1vgaSignaturePutGunInLockerBug, pq1vgaPatchPutGunInLockerBug }, - { true, 500, "map save/restore bug", 2, pq1vgaSignatureMapSaveRestoreBug, pq1vgaPatchMapSaveRestoreBug }, + { true, 152, "getting stuck while briefing is about to start", 1, pq1vgaSignatureBriefingGettingStuck, pq1vgaPatchBriefingGettingStuck }, + { true, 341, "put gun in locker bug", 1, pq1vgaSignaturePutGunInLockerBug, pq1vgaPatchPutGunInLockerBug }, + { true, 500, "map save/restore bug", 2, pq1vgaSignatureMapSaveRestoreBug, pq1vgaPatchMapSaveRestoreBug }, SCI_SIGNATUREENTRY_TERMINATOR }; @@ -2673,14 +2934,75 @@ static const uint16 qfg3PatchChiefPriority[] = { PATCH_END }; +// Partly WORKAROUND: +// During combat, the game is not properly throttled. That's because the game uses +// an inner loop for combat and does not iterate through the main loop. +// It also doesn't call kGameIsRestarting. This may get fixed properly at some point +// by rewriting the speed throttler. +// +// Additionally Sierra set the cycle speed of the hero to 0. Which explains +// why the actions of the hero are so incredibly fast. This issue also happened +// in the original interpreter, when the computer was too powerful. +// +// Applies to at least: English, French, German, Italian, Spanish PC floppy +// Responsible method: combatControls::dispatchEvent (script 550) + WarriorObj in heap +// Fixes bug #6247 +static const uint16 qfg3SignatureCombatSpeedThrottling1[] = { + 0x31, 0x0d, // bnt [skip code] + SIG_MAGICDWORD, + 0x89, 0xd2, // lsg global[D2h] + 0x35, 0x00, // ldi 0 + 0x1e, // gt? + 0x31, 0x06, // bnt [skip code] + 0xe1, 0xd2, // -ag global[D2h] (jump skips over this) + 0x81, 0x58, // lag global[58h] + 0xa3, 0x01, // sal local[01] + SIG_END +}; + +static const uint16 qfg3PatchCombatSpeedThrottling1[] = { + 0x80, 0xd2, // lsg global[D2h] + 0x14, // or + 0x31, 0x06, // bnt [skip code] - saves 4 bytes + 0xe1, 0xd2, // -ag global[D2h] + 0x81, 0x58, // lag global[58h] + 0xa3, 0x01, // sal local[01] (jump skips over this) + // our code + 0x76, // push0 + 0x43, 0x2c, 0x00, // callk GameIsRestarting <-- add this so that our speed throttler is triggered + PATCH_END +}; + +static const uint16 qfg3SignatureCombatSpeedThrottling2[] = { + SIG_MAGICDWORD, + SIG_UINT16(12), // priority 12 + SIG_UINT16(0), // underbits 0 + SIG_UINT16(0x4010), // signal 4010h + SIG_ADDTOOFFSET(+18), + SIG_UINT16(0), // scaleSignal 0 + SIG_UINT16(128), // scaleX + SIG_UINT16(128), // scaleY + SIG_UINT16(128), // maxScale + SIG_UINT16(0), // cycleSpeed + SIG_END +}; + +static const uint16 qfg3PatchCombatSpeedThrottling2[] = { + PATCH_ADDTOOFFSET(+32), + PATCH_UINT16(5), // set cycleSpeed to 5 + PATCH_END +}; + // script, description, signature patch static const SciScriptPatcherEntry qfg3Signatures[] = { - { true, 944, "import dialog continuous calls", 1, qfg3SignatureImportDialog, qfg3PatchImportDialog }, - { true, 440, "dialog crash when asking about Woo", 1, qfg3SignatureWooDialog, qfg3PatchWooDialog }, - { true, 440, "dialog crash when asking about Woo", 1, qfg3SignatureWooDialogAlt, qfg3PatchWooDialogAlt }, - { true, 52, "export character save bug", 2, qfg3SignatureExportChar, qfg3PatchExportChar }, - { true, 54, "import character from QfG1 bug", 1, qfg3SignatureImportQfG1Char, qfg3PatchImportQfG1Char }, - { true, 640, "chief in hut priority fix", 1, qfg3SignatureChiefPriority, qfg3PatchChiefPriority }, + { true, 944, "import dialog continuous calls", 1, qfg3SignatureImportDialog, qfg3PatchImportDialog }, + { true, 440, "dialog crash when asking about Woo", 1, qfg3SignatureWooDialog, qfg3PatchWooDialog }, + { true, 440, "dialog crash when asking about Woo", 1, qfg3SignatureWooDialogAlt, qfg3PatchWooDialogAlt }, + { true, 52, "export character save bug", 2, qfg3SignatureExportChar, qfg3PatchExportChar }, + { true, 54, "import character from QfG1 bug", 1, qfg3SignatureImportQfG1Char, qfg3PatchImportQfG1Char }, + { true, 640, "chief in hut priority fix", 1, qfg3SignatureChiefPriority, qfg3PatchChiefPriority }, + { true, 550, "combat speed throttling script", 1, qfg3SignatureCombatSpeedThrottling1, qfg3PatchCombatSpeedThrottling1 }, + { true, 550, "combat speed throttling heap", 1, qfg3SignatureCombatSpeedThrottling2, qfg3PatchCombatSpeedThrottling2 }, SCI_SIGNATUREENTRY_TERMINATOR }; @@ -2836,6 +3158,66 @@ static const uint16 sq4CdPatchGetPointsForChangingBackClothes[] = { PATCH_END }; + +// For Space Quest 4 CD, Sierra added a pick up animation for Roger, when he picks up the rope. +// +// When the player is detected by the zombie right at the start of the game, while picking up the rope, +// scripts bomb out. This also happens, when using the original interpreter. +// +// This is caused by code, that's supposed to make Roger face the arriving drone. +// We fix it, by checking if ego::cycler is actually set before calling that code. +// +// Applies to at least: English PC CD +// Responsible method: droidShoots::changeState(3) +// Fixes bug: #6076 +static const uint16 sq4CdSignatureGettingShotWhileGettingRope[] = { + 0x35, 0x02, // ldi 02 + 0x65, 0x1a, // aTop cycles + 0x32, SIG_UINT16(0x02fa), // jmp [end] + SIG_MAGICDWORD, + 0x3c, // dup + 0x35, 0x02, // ldi 02 + 0x1a, // eq? + 0x31, 0x0b, // bnt [state 3 check] + 0x76, // push0 + 0x45, 0x02, 0x00, // call export 2 of script 0 -> disable controls + 0x35, 0x02, // ldi 02 + 0x65, 0x1a, // aTop cycles + 0x32, SIG_UINT16(0x02e9), // jmp [end] + 0x3c, // dup + 0x35, 0x03, // ldi 03 + 0x1a, // eq? + 0x31, 0x1e, // bnt [state 4 check] + 0x76, // push0 + 0x45, 0x02, 0x00, // call export 2 of script 0 -> disable controls again?? + 0x7a, // push2 + 0x89, 0x00, // lsg global[0] + 0x72, SIG_UINT16(0x0242), // lofsa deathDroid + 0x36, // push + 0x45, 0x0d, 0x04, // call export 13 of script 0 -> set heading of ego to face droid + SIG_END +}; + +static const uint16 sq4CdPatchGettingShotWhileGettingRope[] = { + PATCH_ADDTOOFFSET(+11), + // this makes state 2 only do the 2 cycles wait, controls should always be disabled already at this point + 0x2f, 0xf3, // bt [previous state aTop cycles code] + // Now we check for state 3, this change saves us 11 bytes + 0x3c, // dup + 0x35, 0x03, // ldi 03 + 0x1a, // eq? + 0x31, 0x29, // bnt [state 4 check] + // new state 3 code + 0x76, // push0 + 0x45, 0x02, 0x00, // call export 2 of script 0 (disable controls, actually not needed) + 0x38, PATCH_SELECTOR16(cycler), // pushi cycler + 0x76, // push0 + 0x81, 0x00, // lag global[0] + 0x4a, 0x04, // send 04 (get ego::cycler) + 0x30, PATCH_UINT16(10), // bnt [jump over heading call] + PATCH_END +}; + // The scripts in SQ4CD support simultaneous playing of speech and subtitles, // but this was not available as an option. The following two patches enable // this functionality in the game's GUI options dialog. @@ -2932,6 +3314,7 @@ static const SciScriptPatcherEntry sq4Signatures[] = { { true, 700, "Floppy: throw stuff at sequel police bug", 1, sq4FloppySignatureThrowStuffAtSequelPoliceBug, sq4FloppyPatchThrowStuffAtSequelPoliceBug }, { true, 45, "CD: walk in from below for room 45 fix", 1, sq4CdSignatureWalkInFromBelowRoom45, sq4CdPatchWalkInFromBelowRoom45 }, { true, 396, "CD: get points for changing back clothes fix",1, sq4CdSignatureGetPointsForChangingBackClothes, sq4CdPatchGetPointsForChangingBackClothes }, + { true, 701, "CD: getting shot, while getting rope", 1, sq4CdSignatureGettingShotWhileGettingRope, sq4CdPatchGettingShotWhileGettingRope }, { true, 0, "CD: Babble icon speech and subtitles fix", 1, sq4CdSignatureBabbleIcon, sq4CdPatchBabbleIcon }, { true, 818, "CD: Speech and subtitles option", 1, sq4CdSignatureTextOptions, sq4CdPatchTextOptions }, { true, 818, "CD: Speech and subtitles option button", 1, sq4CdSignatureTextOptionsButton, sq4CdPatchTextOptionsButton }, @@ -3374,20 +3757,20 @@ bool ScriptPatcher::verifySignature(uint32 byteOffset, const uint16 *signatureDa } // will return -1 if no match was found, otherwise an offset to the start of the signature match -int32 ScriptPatcher::findSignature(const SciScriptPatcherEntry *patchEntry, SciScriptPatcherRuntimeEntry *runtimeEntry, const byte *scriptData, const uint32 scriptSize) { +int32 ScriptPatcher::findSignature(uint32 magicDWord, int magicOffset, const uint16 *signatureData, const char *patchDescription, const byte *scriptData, const uint32 scriptSize) { if (scriptSize < 4) // we need to find a DWORD, so less than 4 bytes is not okay return -1; - const uint32 magicDWord = runtimeEntry->magicDWord; // is platform-specific BE/LE form, so that the later match will work + // magicDWord is in platform-specific BE/LE form, so that the later match will work, this was done for performance const uint32 searchLimit = scriptSize - 3; uint32 DWordOffset = 0; // first search for the magic DWORD while (DWordOffset < searchLimit) { if (magicDWord == READ_UINT32(scriptData + DWordOffset)) { // magic DWORD found, check if actual signature matches - uint32 offset = DWordOffset + runtimeEntry->magicOffset; + uint32 offset = DWordOffset + magicOffset; - if (verifySignature(offset, patchEntry->signatureData, patchEntry->description, scriptData, scriptSize)) + if (verifySignature(offset, signatureData, patchDescription, scriptData, scriptSize)) return offset; } DWordOffset++; @@ -3396,22 +3779,146 @@ int32 ScriptPatcher::findSignature(const SciScriptPatcherEntry *patchEntry, SciS return -1; } -// This method calculates the magic DWORD for each entry in the signature table -// and it also initializes the selector table for selectors used in the signatures/patches of the current game -void ScriptPatcher::initSignature(const SciScriptPatcherEntry *patchTable) { - const SciScriptPatcherEntry *curEntry = patchTable; - SciScriptPatcherRuntimeEntry *curRuntimeEntry; +int32 ScriptPatcher::findSignature(const SciScriptPatcherEntry *patchEntry, const SciScriptPatcherRuntimeEntry *runtimeEntry, const byte *scriptData, const uint32 scriptSize) { + return findSignature(runtimeEntry->magicDWord, runtimeEntry->magicOffset, patchEntry->signatureData, patchEntry->description, scriptData, scriptSize); +} + +// Attention: Magic DWord is returns using platform specific byte order. This is done on purpose for performance. +void ScriptPatcher::calculateMagicDWordAndVerify(const char *signatureDescription, const uint16 *signatureData, bool magicDWordIncluded, uint32 &calculatedMagicDWord, int &calculatedMagicDWordOffset) { Selector curSelector = -1; - int step; int magicOffset; byte magicDWord[4]; int magicDWordLeft = 0; - const uint16 *curData; uint16 curWord; uint16 curCommand; uint32 curValue; byte byte1 = 0; byte byte2 = 0; + + memset(magicDWord, 0, sizeof(magicDWord)); + + curWord = *signatureData; + magicOffset = 0; + while (curWord != SIG_END) { + curCommand = curWord & SIG_COMMANDMASK; + curValue = curWord & SIG_VALUEMASK; + switch (curCommand) { + case SIG_MAGICDWORD: { + if (magicDWordIncluded) { + if ((calculatedMagicDWord) || (magicDWordLeft)) + error("Script-Patcher: Magic-DWORD specified multiple times in signature\nFaulty patch: '%s'", signatureDescription); + magicDWordLeft = 4; + calculatedMagicDWordOffset = magicOffset; + } else { + error("Script-Patcher: Magic-DWORD sequence found in patch data\nFaulty patch: '%s'", signatureDescription); + } + break; + } + case SIG_CODE_ADDTOOFFSET: { + magicOffset -= curValue; + if (magicDWordLeft) + error("Script-Patcher: Magic-DWORD contains AddToOffset command\nFaulty patch: '%s'", signatureDescription); + break; + } + case SIG_CODE_UINT16: + case SIG_CODE_SELECTOR16: { + // UINT16 or 1 + switch (curCommand) { + case SIG_CODE_UINT16: { + signatureData++; curWord = *signatureData; + if (curWord & SIG_COMMANDMASK) + error("Script-Patcher: signature entry inconsistent\nFaulty patch: '%s'", signatureDescription); + if (!_isMacSci11) { + byte1 = curValue; + byte2 = curWord & SIG_BYTEMASK; + } else { + byte1 = curWord & SIG_BYTEMASK; + byte2 = curValue; + } + break; + } + case SIG_CODE_SELECTOR16: { + curSelector = _selectorIdTable[curValue]; + if (curSelector == -1) { + curSelector = g_sci->getKernel()->findSelector(selectorNameTable[curValue]); + _selectorIdTable[curValue] = curSelector; + } + if (!_isMacSci11) { + byte1 = curSelector & 0x00FF; + byte2 = curSelector >> 8; + } else { + byte1 = curSelector >> 8; + byte2 = curSelector & 0x00FF; + } + break; + } + } + magicOffset -= 2; + if (magicDWordLeft) { + // Remember current word for Magic DWORD + magicDWord[4 - magicDWordLeft] = byte1; + magicDWordLeft--; + if (magicDWordLeft) { + magicDWord[4 - magicDWordLeft] = byte2; + magicDWordLeft--; + } + if (!magicDWordLeft) { + // Magic DWORD is now known, convert to platform specific byte order + calculatedMagicDWord = READ_LE_UINT32(magicDWord); + } + } + break; + } + case SIG_CODE_BYTE: + case SIG_CODE_SELECTOR8: { + if (curCommand == SIG_CODE_SELECTOR8) { + curSelector = _selectorIdTable[curValue]; + if (curSelector == -1) { + curSelector = g_sci->getKernel()->findSelector(selectorNameTable[curValue]); + _selectorIdTable[curValue] = curSelector; + if (curSelector != -1) { + if (curSelector & 0xFF00) + error("Script-Patcher: 8 bit selector required, game uses 16 bit selector\nFaulty patch: '%s'", signatureDescription); + } + } + curValue = curSelector; + } + magicOffset--; + if (magicDWordLeft) { + // Remember current byte for Magic DWORD + magicDWord[4 - magicDWordLeft] = (byte)curValue; + magicDWordLeft--; + if (!magicDWordLeft) { + calculatedMagicDWord = READ_LE_UINT32(magicDWord); + } + } + break; + } + case PATCH_CODE_GETORIGINALBYTEADJUST: { + signatureData++; // skip over extra uint16 + break; + } + default: + break; + } + signatureData++; + curWord = *signatureData; + } + + if (magicDWordLeft) + error("Script-Patcher: Magic-DWORD beyond End-Of-Signature\nFaulty patch: '%s'", signatureDescription); + if (magicDWordIncluded) { + if (!calculatedMagicDWord) { + error("Script-Patcher: Magic-DWORD not specified in signature\nFaulty patch: '%s'", signatureDescription); + } + } +} + +// This method calculates the magic DWORD for each entry in the signature table +// and it also initializes the selector table for selectors used in the signatures/patches of the current game +void ScriptPatcher::initSignature(const SciScriptPatcherEntry *patchTable) { + const SciScriptPatcherEntry *curEntry = patchTable; + SciScriptPatcherRuntimeEntry *curRuntimeEntry; int patchEntryCount = 0; // Count entries and allocate runtime data @@ -3425,120 +3932,14 @@ void ScriptPatcher::initSignature(const SciScriptPatcherEntry *patchTable) { curRuntimeEntry = _runtimeTable; while (curEntry->signatureData) { // process signature - memset(magicDWord, 0, sizeof(magicDWord)); - curRuntimeEntry->active = curEntry->defaultActive; curRuntimeEntry->magicDWord = 0; curRuntimeEntry->magicOffset = 0; - for (step = 0; step < 2; step++) { - switch (step) { - case 0: curData = curEntry->signatureData; break; - case 1: curData = curEntry->patchData; break; - } - - curWord = *curData; - magicOffset = 0; - while (curWord != SIG_END) { - curCommand = curWord & SIG_COMMANDMASK; - curValue = curWord & SIG_VALUEMASK; - switch (curCommand) { - case SIG_MAGICDWORD: { - if (step == 0) { - if ((curRuntimeEntry->magicDWord) || (magicDWordLeft)) - error("Script-Patcher: Magic-DWORD specified multiple times in signature\nFaulty patch: '%s'", curEntry->description); - magicDWordLeft = 4; - curRuntimeEntry->magicOffset = magicOffset; - } - break; - } - case SIG_CODE_ADDTOOFFSET: { - magicOffset -= curValue; - if (magicDWordLeft) - error("Script-Patcher: Magic-DWORD contains AddToOffset command\nFaulty patch: '%s'", curEntry->description); - break; - } - case SIG_CODE_UINT16: - case SIG_CODE_SELECTOR16: { - // UINT16 or 1 - switch (curCommand) { - case SIG_CODE_UINT16: { - curData++; curWord = *curData; - if (curWord & SIG_COMMANDMASK) - error("Script-Patcher: signature entry inconsistent\nFaulty patch: '%s'", curEntry->description); - if (!_isMacSci11) { - byte1 = curValue; - byte2 = curWord & SIG_BYTEMASK; - } else { - byte1 = curWord & SIG_BYTEMASK; - byte2 = curValue; - } - break; - } - case SIG_CODE_SELECTOR16: { - curSelector = _selectorIdTable[curValue]; - if (curSelector == -1) { - curSelector = g_sci->getKernel()->findSelector(selectorNameTable[curValue]); - _selectorIdTable[curValue] = curSelector; - } - if (!_isMacSci11) { - byte1 = curSelector & 0x00FF; - byte2 = curSelector >> 8; - } else { - byte1 = curSelector >> 8; - byte2 = curSelector & 0x00FF; - } - break; - } - } - magicOffset -= 2; - if (magicDWordLeft) { - // Remember current word for Magic DWORD - magicDWord[4 - magicDWordLeft] = byte1; - magicDWordLeft--; - if (magicDWordLeft) { - magicDWord[4 - magicDWordLeft] = byte2; - magicDWordLeft--; - } - if (!magicDWordLeft) { - curRuntimeEntry->magicDWord = READ_LE_UINT32(magicDWord); - } - } - break; - } - case SIG_CODE_BYTE: - case SIG_CODE_SELECTOR8: { - if (curCommand == SIG_CODE_SELECTOR8) { - curSelector = _selectorIdTable[curValue]; - if (curSelector == -1) { - curSelector = g_sci->getKernel()->findSelector(selectorNameTable[curValue]); - _selectorIdTable[curValue] = curSelector; - if (curSelector != -1) { - if (curSelector & 0xFF00) - error("Script-Patcher: 8 bit selector required, game uses 16 bit selector\nFaulty patch: '%s'", curEntry->description); - } - } - curValue = curSelector; - } - magicOffset--; - if (magicDWordLeft) { - // Remember current byte for Magic DWORD - magicDWord[4 - magicDWordLeft] = (byte)curValue; - magicDWordLeft--; - if (!magicDWordLeft) { - curRuntimeEntry->magicDWord = READ_LE_UINT32(magicDWord); - } - } - } - } - curData++; - curWord = *curData; - } - } - if (magicDWordLeft) - error("Script-Patcher: Magic-DWORD beyond End-Of-Signature\nFaulty patch: '%s'", curEntry->description); - if (!curRuntimeEntry->magicDWord) - error("Script-Patcher: Magic-DWORD not specified in signature\nFaulty patch: '%s'", curEntry->description); + // We verify the signature data and remember the calculated magic DWord from the signature data + calculateMagicDWordAndVerify(curEntry->description, curEntry->signatureData, true, curRuntimeEntry->magicDWord, curRuntimeEntry->magicOffset); + // We verify the patch data + calculateMagicDWordAndVerify(curEntry->description, curEntry->patchData, false, curRuntimeEntry->magicDWord, curRuntimeEntry->magicOffset); curEntry++; curRuntimeEntry++; } @@ -3596,6 +3997,9 @@ void ScriptPatcher::processScript(uint16 scriptNr, byte *scriptData, const uint3 case GID_KQ6: signatureTable = kq6Signatures; break; + case GID_LAURABOW: + signatureTable = laurabow1Signatures; + break; case GID_LAURABOW2: signatureTable = laurabow2Signatures; break; diff --git a/engines/sci/engine/script_patches.h b/engines/sci/engine/script_patches.h index d15fce321b..645e0946b3 100644 --- a/engines/sci/engine/script_patches.h +++ b/engines/sci/engine/script_patches.h @@ -90,13 +90,32 @@ public: ScriptPatcher(); ~ScriptPatcher(); + // Calculates the magic DWord for fast search and verifies signature/patch data + // Returns the magic DWord in platform-specific byte-order. This is done on purpose for performance. + void calculateMagicDWordAndVerify(const char *signatureDescription, const uint16 *signatureData, bool magicDWordIncluded, uint32 &calculatedMagicDWord, int &calculatedMagicDWordOffset); + + // Called when a script is loaded to check for signature matches and apply patches in such cases void processScript(uint16 scriptNr, byte *scriptData, const uint32 scriptSize); + + // Verifies, if a given signature matches the given script data (pointed to by additional byte offset) bool verifySignature(uint32 byteOffset, const uint16 *signatureData, const char *signatureDescription, const byte *scriptData, const uint32 scriptSize); + // searches for a given signature inside script data + // returns -1 in case it was not found or an offset to the matching data + int32 findSignature(uint32 magicDWord, int magicOffset, const uint16 *signatureData, const char *patchDescription, const byte *scriptData, const uint32 scriptSize); + private: + // Initializes a patch table and creates run time information for it (for enabling/disabling), also calculates magic DWORD) void initSignature(const SciScriptPatcherEntry *patchTable); + + // Enables a patch inside the patch table (used for optional patches like CD+Text support for KQ6 & LB2) void enablePatch(const SciScriptPatcherEntry *patchTable, const char *searchDescription); - int32 findSignature(const SciScriptPatcherEntry *patchEntry, SciScriptPatcherRuntimeEntry *runtimeEntry, const byte *scriptData, const uint32 scriptSize); + + // Searches for a given signature entry inside script data + // returns -1 in case it was not found or an offset to the matching data + int32 findSignature(const SciScriptPatcherEntry *patchEntry, const SciScriptPatcherRuntimeEntry *runtimeEntry, const byte *scriptData, const uint32 scriptSize); + + // Applies a patch to a given script + offset (overwrites parts) void applyPatch(const SciScriptPatcherEntry *patchEntry, byte *scriptData, const uint32 scriptSize, int32 signatureOffset); Selector *_selectorIdTable; diff --git a/engines/sci/engine/scriptdebug.cpp b/engines/sci/engine/scriptdebug.cpp index f0157a6569..7d70f30d55 100644 --- a/engines/sci/engine/scriptdebug.cpp +++ b/engines/sci/engine/scriptdebug.cpp @@ -499,7 +499,7 @@ void Kernel::dumpScriptClass(char *data, int seeker, int objsize) { void Kernel::dissectScript(int scriptNumber, Vocabulary *vocab) { int objectctr[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - unsigned int _seeker = 0; + uint32 _seeker = 0; Resource *script = _resMan->findResource(ResourceId(kResourceTypeScript, scriptNumber), 0); if (!script) { @@ -510,7 +510,7 @@ void Kernel::dissectScript(int scriptNumber, Vocabulary *vocab) { while (_seeker < script->size) { int objType = (int16)READ_SCI11ENDIAN_UINT16(script->data + _seeker); int objsize; - unsigned int seeker = _seeker + 4; + uint32 seeker = _seeker + 4; if (!objType) { debugN("End of script object (#0) encountered.\n"); diff --git a/engines/sci/engine/seg_manager.cpp b/engines/sci/engine/seg_manager.cpp index 23b1fdae23..8090b1861d 100644 --- a/engines/sci/engine/seg_manager.cpp +++ b/engines/sci/engine/seg_manager.cpp @@ -837,10 +837,13 @@ byte *SegManager::allocDynmem(int size, const char *descr, reg_t *addr) { d._size = size; - if (size == 0) + // Original SCI only zeroed out heap memory on initialize + // They didn't do it again for every allocation + if (size) { + d._buf = (byte *)calloc(size, 1); + } else { d._buf = NULL; - else - d._buf = (byte *)malloc(size); + } d._description = descr; diff --git a/engines/sci/engine/segment.h b/engines/sci/engine/segment.h index de7f60ac16..2699bc2e5b 100644 --- a/engines/sci/engine/segment.h +++ b/engines/sci/engine/segment.h @@ -203,7 +203,7 @@ struct List { struct Hunk { void *mem; - unsigned int size; + uint32 size; const char *type; }; diff --git a/engines/sci/engine/selector.cpp b/engines/sci/engine/selector.cpp index 910f1f885f..ac621f58ae 100644 --- a/engines/sci/engine/selector.cpp +++ b/engines/sci/engine/selector.cpp @@ -179,6 +179,8 @@ void Kernel::mapSelectors() { FIND_SELECTOR(fore); FIND_SELECTOR(back); FIND_SELECTOR(skip); + FIND_SELECTOR(borderColor); + FIND_SELECTOR(width); FIND_SELECTOR(fixPriority); FIND_SELECTOR(mirrored); FIND_SELECTOR(visible); @@ -187,6 +189,17 @@ void Kernel::mapSelectors() { FIND_SELECTOR(inLeft); FIND_SELECTOR(inBottom); FIND_SELECTOR(inRight); + FIND_SELECTOR(textTop); + FIND_SELECTOR(textLeft); + FIND_SELECTOR(textBottom); + FIND_SELECTOR(textRight); + FIND_SELECTOR(title); + FIND_SELECTOR(titleFont); + FIND_SELECTOR(titleFore); + FIND_SELECTOR(titleBack); + FIND_SELECTOR(magnifier); + FIND_SELECTOR(frameOut); + FIND_SELECTOR(casts); #endif } @@ -199,6 +212,16 @@ reg_t readSelector(SegManager *segMan, reg_t object, Selector selectorId) { return *address.getPointer(segMan); } +#ifdef ENABLE_SCI32 +void updateInfoFlagViewVisible(Object *obj, int index) { + // TODO: Make this correct for all SCI versions + // Selectors 26 through 44 are selectors for View script objects in SQ6 + if (index >= 26 && index <= 44 && getSciVersion() >= SCI_VERSION_2) { + obj->setInfoSelectorFlag(kInfoFlagViewVisible); + } +} +#endif + void writeSelector(SegManager *segMan, reg_t object, Selector selectorId, reg_t value) { ObjVarRef address; @@ -211,8 +234,12 @@ void writeSelector(SegManager *segMan, reg_t object, Selector selectorId, reg_t if (lookupSelector(segMan, object, selectorId, &address, NULL) != kSelectorVariable) error("Selector '%s' of object at %04x:%04x could not be" " written to", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object)); - else + else { *address.getPointer(segMan) = value; +#ifdef ENABLE_SCI32 + updateInfoFlagViewVisible(segMan->getObject(object), selectorId); +#endif + } } void invokeSelector(EngineState *s, reg_t object, int selectorId, diff --git a/engines/sci/engine/selector.h b/engines/sci/engine/selector.h index b3dd393708..f2d06d1cf4 100644 --- a/engines/sci/engine/selector.h +++ b/engines/sci/engine/selector.h @@ -146,6 +146,8 @@ struct SelectorCache { Selector back; Selector skip; Selector dimmed; + Selector borderColor; + Selector width; Selector fixPriority; Selector mirrored; @@ -153,6 +155,12 @@ struct SelectorCache { Selector useInsetRect; Selector inTop, inLeft, inBottom, inRight; + Selector textTop, textLeft, textBottom, textRight; + Selector title, titleFont, titleFore, titleBack; + + Selector magnifier; + Selector frameOut; + Selector casts; // needed for sync'ing screen items/planes with scripts, when our save/restore code is patched in (see GfxFrameout::syncWithScripts) #endif }; @@ -191,6 +199,16 @@ void writeSelector(SegManager *segMan, reg_t object, Selector selectorId, reg_t void invokeSelector(EngineState *s, reg_t object, int selectorId, int k_argc, StackPtr k_argp, int argc = 0, const reg_t *argv = 0); +#ifdef ENABLE_SCI32 +/** + * SCI32 set kInfoFlagViewVisible in the -info- selector if a certain + * range of properties was written to. + * This function checks if index is in the right range, and sets the flag + * on obj.-info- if it is. + */ +void updateInfoFlagViewVisible(Object *obj, int index); +#endif + } // End of namespace Sci #endif // SCI_ENGINE_KERNEL_H diff --git a/engines/sci/engine/state.cpp b/engines/sci/engine/state.cpp index d53e6b48c8..fda78317b5 100644 --- a/engines/sci/engine/state.cpp +++ b/engines/sci/engine/state.cpp @@ -95,6 +95,7 @@ void EngineState::reset(bool isRestoring) { // reset delayed restore game functionality _delayedRestoreGame = false; _delayedRestoreGameId = 0; + _delayedRestoreFromLauncher = false; executionStackBase = 0; _executionStackPosChanged = false; diff --git a/engines/sci/engine/state.h b/engines/sci/engine/state.h index 0f04e32fe5..cf9a753f5c 100644 --- a/engines/sci/engine/state.h +++ b/engines/sci/engine/state.h @@ -138,10 +138,12 @@ public: // see detection.cpp / SciEngine::loadGameState() bool _delayedRestoreGame; // boolean, that triggers delayed restore (triggered by ScummVM menu) int _delayedRestoreGameId; // the saved game id, that it supposed to get restored (triggered by ScummVM menu) + bool _delayedRestoreFromLauncher; // is set, when the the delayed restore game was triggered from launcher uint _chosenQfGImportItem; // Remembers the item selected in QfG import rooms bool _cursorWorkaroundActive; // Refer to GfxCursor::setPosition() + int16 _cursorWorkaroundPosCount; // When the cursor is reported to be at the previously set coordinate, we won't disable the workaround unless it happened for this many times Common::Point _cursorWorkaroundPoint; Common::Rect _cursorWorkaroundRect; diff --git a/engines/sci/engine/vm.cpp b/engines/sci/engine/vm.cpp index 3729fc5236..64e6c045db 100644 --- a/engines/sci/engine/vm.cpp +++ b/engines/sci/engine/vm.cpp @@ -258,6 +258,10 @@ static void _exec_varselectors(EngineState *s) { if (xs.argc) { // write? *var = xs.variables_argp[1]; +#ifdef ENABLE_SCI32 + updateInfoFlagViewVisible(s->_segMan->getObject(xs.addr.varp.obj), xs.addr.varp.varindex); +#endif + } else // No, read s->r_acc = *var; } @@ -1095,6 +1099,9 @@ void run_vm(EngineState *s) { case op_aTop: // 0x32 (50) // Accumulator To Property validate_property(s, obj, opparams[0]) = s->r_acc; +#ifdef ENABLE_SCI32 + updateInfoFlagViewVisible(obj, opparams[0]>>1); +#endif break; case op_pTos: // 0x33 (51) @@ -1105,6 +1112,9 @@ void run_vm(EngineState *s) { case op_sTop: // 0x34 (52) // Stack To Property validate_property(s, obj, opparams[0]) = POP32(); +#ifdef ENABLE_SCI32 + updateInfoFlagViewVisible(obj, opparams[0]>>1); +#endif break; case op_ipToa: // 0x35 (53) @@ -1119,7 +1129,9 @@ void run_vm(EngineState *s) { opProperty += 1; else opProperty -= 1; - +#ifdef ENABLE_SCI32 + updateInfoFlagViewVisible(obj, opparams[0]>>1); +#endif if (opcode == op_ipToa || opcode == op_dpToa) s->r_acc = opProperty; else diff --git a/engines/sci/engine/vm_types.cpp b/engines/sci/engine/vm_types.cpp index cf008c45e1..d74e2b194c 100644 --- a/engines/sci/engine/vm_types.cpp +++ b/engines/sci/engine/vm_types.cpp @@ -210,12 +210,30 @@ reg_t reg_t::operator^(const reg_t right) const { return lookForWorkaround(right, "bitwise XOR"); } +#ifdef ENABLE_SCI32 +reg_t reg_t::operator&(int16 right) const { + return *this & make_reg(0, right); +} + +reg_t reg_t::operator|(int16 right) const { + return *this | make_reg(0, right); +} + +reg_t reg_t::operator^(int16 right) const { + return *this ^ make_reg(0, right); +} +#endif + int reg_t::cmp(const reg_t right, bool treatAsUnsigned) const { if (getSegment() == right.getSegment()) { // can compare things in the same segment if (treatAsUnsigned || !isNumber()) return toUint16() - right.toUint16(); else return toSint16() - right.toSint16(); +#ifdef ENABLE_SCI32 + } else if (getSciVersion() >= SCI_VERSION_2) { + return sci32Comparison(right); +#endif } else if (pointerComparisonWithInteger(right)) { return 1; } else if (right.pointerComparisonWithInteger(*this)) { @@ -224,6 +242,26 @@ int reg_t::cmp(const reg_t right, bool treatAsUnsigned) const { return lookForWorkaround(right, "comparison").toSint16(); } +#ifdef ENABLE_SCI32 +int reg_t::sci32Comparison(const reg_t right) const { + // In SCI32, MemIDs are normally indexes into the memory manager's handle + // list, but the engine reserves indexes at and above 20000 for objects + // that were created inside the engine (as opposed to inside the VM). The + // engine compares these as a tiebreaker for graphics objects that are at + // the same priority, and it is necessary to at least minimally handle + // this situation. + // This is obviously a bogus comparision, but then, this entire thing is + // bogus. For the moment, it just needs to be deterministic. + if (isNumber() && !right.isNumber()) { + return 1; + } else if (right.isNumber() && !isNumber()) { + return -1; + } + + return getOffset() - right.getOffset(); +} +#endif + bool reg_t::pointerComparisonWithInteger(const reg_t right) const { // This function handles the case where a script tries to compare a pointer // to a number. Normally, we would not want to allow that. However, SCI0 - diff --git a/engines/sci/engine/vm_types.h b/engines/sci/engine/vm_types.h index af78bd0b84..e60f52e85c 100644 --- a/engines/sci/engine/vm_types.h +++ b/engines/sci/engine/vm_types.h @@ -136,6 +136,19 @@ struct reg_t { reg_t operator|(const reg_t right) const; reg_t operator^(const reg_t right) const; +#ifdef ENABLE_SCI32 + reg_t operator&(int16 right) const; + reg_t operator|(int16 right) const; + reg_t operator^(int16 right) const; + + void operator&=(const reg_t &right) { *this = *this & right; } + void operator|=(const reg_t &right) { *this = *this | right; } + void operator^=(const reg_t &right) { *this = *this ^ right; } + void operator&=(int16 right) { *this = *this & right; } + void operator|=(int16 right) { *this = *this | right; } + void operator^=(int16 right) { *this = *this ^ right; } +#endif + private: /** * Compares two reg_t's. @@ -147,6 +160,10 @@ private: int cmp(const reg_t right, bool treatAsUnsigned) const; reg_t lookForWorkaround(const reg_t right, const char *operation) const; bool pointerComparisonWithInteger(const reg_t right) const; + +#ifdef ENABLE_SCI32 + int sci32Comparison(const reg_t right) const; +#endif }; static inline reg_t make_reg(SegmentId segment, uint16 offset) { diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp index aab32032f7..3832f4cf04 100644 --- a/engines/sci/engine/workarounds.cpp +++ b/engines/sci/engine/workarounds.cpp @@ -378,6 +378,7 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = { { GID_SQ6, -1, 64950, -1, "Feature", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // called when pressing "Start game" in the main menu, when entering the Orion's Belt bar (room 300), and perhaps other places { GID_SQ6, -1, 64964, 0, "DPath", "init", NULL, 1, { WORKAROUND_FAKE, 0 } }, // during the game { GID_TORIN, -1, 64017, 0, "oFlags", "clear", NULL, 0, { WORKAROUND_FAKE, 0 } }, // entering Torin's home in the French version + { GID_TORIN, 10000, 64029, 0, "oMessager", "nextMsg", NULL, 3, { WORKAROUND_FAKE, 0 } }, // start of chapter one SCI_WORKAROUNDENTRY_TERMINATOR }; @@ -630,7 +631,7 @@ const SciWorkaroundEntry kGraphUpdateBox_workarounds[] = { // gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround const SciWorkaroundEntry kIsObject_workarounds[] = { - { GID_GK1, 50, 999, 0, "List", "eachElementDo", NULL, 0, { WORKAROUND_FAKE, 0 } }, // GK1 demo, when asking Grace for messages it gets called with an invalid parameter (type "error") - bug #4950 + { GID_GK1DEMO, 50, 999, 0, "List", "eachElementDo", NULL, 0, { WORKAROUND_FAKE, 0 } }, // GK1 demo, when asking Grace for messages it gets called with an invalid parameter (type "error") - bug #4950 { GID_ISLANDBRAIN, -1, 999, 0, "List", "eachElementDo", NULL, 0, { WORKAROUND_FAKE, 0 } }, // when going to the game options, choosing "Info" and selecting anything from the list, gets called with an invalid parameter (type "error") - bug #4989 { GID_QFG3, -1, 999, 0, "List", "eachElementDo", NULL, 0, { WORKAROUND_FAKE, 0 } }, // when asking for something, gets called with type error parameter SCI_WORKAROUNDENTRY_TERMINATOR @@ -656,6 +657,12 @@ const SciWorkaroundEntry kNewWindow_workarounds[] = { }; // gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround +const SciWorkaroundEntry kPalVarySetPercent_workarounds[] = { + { GID_GK1, 370, 370, 0, "graceComeOut", "changeState", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // there's an extra parameter in GK1, when changing chapters. This extra parameter seems to be a bug or just unimplemented functionality, as there's no visible change from the original in the chapter change room + SCI_WORKAROUNDENTRY_TERMINATOR +}; + +// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround const SciWorkaroundEntry kReadNumber_workarounds[] = { { GID_CNICK_LAURABOW,100, 101, 0, "dominoes.opt", "doit", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // When dominoes.opt is present, the game scripts call kReadNumber with an extra integer parameter - bug #6425 { GID_HOYLE3, 100, 101, 0, "dominoes.opt", "doit", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // When dominoes.opt is present, the game scripts call kReadNumber with an extra integer parameter - bug #6425 @@ -664,7 +671,7 @@ const SciWorkaroundEntry kReadNumber_workarounds[] = { // gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround const SciWorkaroundEntry kPaletteUnsetFlag_workarounds[] = { - { GID_QFG4, 100, 100, 0, "doMovie", "changeState", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // after the Sierra logo, no flags are passed, thus the call is meaningless - bug #4947 + { GID_QFG4DEMO, 100, 100, 0, "doMovie", "changeState", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // after the Sierra logo, no flags are passed, thus the call is meaningless - bug #4947 SCI_WORKAROUNDENTRY_TERMINATOR }; diff --git a/engines/sci/engine/workarounds.h b/engines/sci/engine/workarounds.h index 46059a175c..8f519a8c9c 100644 --- a/engines/sci/engine/workarounds.h +++ b/engines/sci/engine/workarounds.h @@ -89,6 +89,7 @@ extern const SciWorkaroundEntry kIsObject_workarounds[]; extern const SciWorkaroundEntry kMemory_workarounds[]; extern const SciWorkaroundEntry kMoveCursor_workarounds[]; extern const SciWorkaroundEntry kNewWindow_workarounds[]; +extern const SciWorkaroundEntry kPalVarySetPercent_workarounds[]; extern const SciWorkaroundEntry kReadNumber_workarounds[]; extern const SciWorkaroundEntry kPaletteUnsetFlag_workarounds[]; extern const SciWorkaroundEntry kSetCursor_workarounds[]; diff --git a/engines/sci/event.cpp b/engines/sci/event.cpp index f8285c26f2..34f1618514 100644 --- a/engines/sci/event.cpp +++ b/engines/sci/event.cpp @@ -29,6 +29,9 @@ #include "sci/console.h" #include "sci/engine/state.h" #include "sci/engine/kernel.h" +#ifdef ENABLE_SCI32 +#include "sci/graphics/frameout.h" +#endif #include "sci/graphics/screen.h" namespace Sci { @@ -44,7 +47,7 @@ static const ScancodeRow scancodeAltifyRows[] = { { 0x2c, "ZXCVBNM,./" } }; -static const byte codepagemap_88591toDOS[0x80] = { +static const byte codePageMap88591ToDOS[0x80] = { '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', // 0x8x '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', '?', // 0x9x '?', 0xad, 0x9b, 0x9c, '?', 0x9d, '?', 0x9e, '?', '?', 0xa6, 0xae, 0xaa, '?', '?', '?', // 0xAx @@ -133,8 +136,13 @@ static int altify(int ch) { } SciEvent EventManager::getScummVMEvent() { - SciEvent input = { SCI_EVENT_NONE, 0, 0, Common::Point(0, 0) }; - SciEvent noEvent = { SCI_EVENT_NONE, 0, 0, Common::Point(0, 0) }; +#ifdef ENABLE_SCI32 + SciEvent input = { SCI_EVENT_NONE, 0, 0, Common::Point(), Common::Point() }; + SciEvent noEvent = { SCI_EVENT_NONE, 0, 0, Common::Point(), Common::Point() }; +#else + SciEvent input = { SCI_EVENT_NONE, 0, 0, Common::Point() }; + SciEvent noEvent = { SCI_EVENT_NONE, 0, 0, Common::Point() }; +#endif Common::EventManager *em = g_system->getEventManager(); Common::Event ev; @@ -155,7 +163,20 @@ SciEvent EventManager::getScummVMEvent() { // via pollEvent. // We also adjust the position based on the scaling of the screen. Common::Point mousePos = em->getMousePos(); - g_sci->_gfxScreen->adjustBackUpscaledCoordinates(mousePos.y, mousePos.x); + +#if ENABLE_SCI32 + if (getSciVersion() >= SCI_VERSION_2) { + Buffer &screen = g_sci->_gfxFrameout->getCurrentBuffer(); + + Common::Point mousePosSci = mousePos; + mulru(mousePosSci, Ratio(screen.scriptWidth, screen.screenWidth), Ratio(screen.scriptHeight, screen.screenHeight)); + noEvent.mousePosSci = input.mousePosSci = mousePosSci; + } else { +#endif + g_sci->_gfxScreen->adjustBackUpscaledCoordinates(mousePos.y, mousePos.x); +#if ENABLE_SCI32 + } +#endif noEvent.mousePos = input.mousePos = mousePos; @@ -173,7 +194,7 @@ SciEvent EventManager::getScummVMEvent() { return input; } - int scummVMKeyFlags; + int scummVMKeyFlags; switch (ev.type) { case Common::EVENT_KEYDOWN: @@ -265,7 +286,7 @@ SciEvent EventManager::getScummVMEvent() { return noEvent; // Convert 8859-1 characters to DOS (cp850/437) for // multilingual SCI01 games - input.character = codepagemap_88591toDOS[input.character & 0x7f]; + input.character = codePageMap88591ToDOS[input.character & 0x7f]; } if (scummVMKeycode == Common::KEYCODE_TAB) { input.character = SCI_KEY_TAB; @@ -302,6 +323,11 @@ SciEvent EventManager::getScummVMEvent() { input.character = altify(input.character); if (getSciVersion() <= SCI_VERSION_1_MIDDLE && (scummVMKeyFlags & Common::KBD_CTRL) && input.character > 0 && input.character < 27) input.character += 96; // 0x01 -> 'a' +#ifdef ENABLE_SCI32 + if (getSciVersion() >= SCI_VERSION_2 && (scummVMKeyFlags & Common::KBD_CTRL) && input.character == 'c') { + input.character = SCI_KEY_ETX; + } +#endif // If no actual key was pressed (e.g. if only a modifier key was pressed), // ignore the event @@ -328,8 +354,12 @@ void EventManager::updateScreen() { } } -SciEvent EventManager::getSciEvent(unsigned int mask) { - SciEvent event = { SCI_EVENT_NONE, 0, 0, Common::Point(0, 0) }; +SciEvent EventManager::getSciEvent(uint32 mask) { +#ifdef ENABLE_SCI32 + SciEvent event = { SCI_EVENT_NONE, 0, 0, Common::Point(), Common::Point() }; +#else + SciEvent event = { SCI_EVENT_NONE, 0, 0, Common::Point() }; +#endif EventManager::updateScreen(); @@ -342,7 +372,7 @@ SciEvent EventManager::getSciEvent(unsigned int mask) { // Search for matching event in queue Common::List<SciEvent>::iterator iter = _events.begin(); - while (iter != _events.end() && !((*iter).type & mask)) + while (iter != _events.end() && !(iter->type & mask)) ++iter; if (iter != _events.end()) { @@ -364,17 +394,17 @@ SciEvent EventManager::getSciEvent(unsigned int mask) { void SciEngine::sleep(uint32 msecs) { uint32 time; - const uint32 wakeup_time = g_system->getMillis() + msecs; + const uint32 wakeUpTime = g_system->getMillis() + msecs; while (true) { // let backend process events and update the screen _eventMan->getSciEvent(SCI_EVENT_PEEK); time = g_system->getMillis(); - if (time + 10 < wakeup_time) { + if (time + 10 < wakeUpTime) { g_system->delayMillis(10); } else { - if (time < wakeup_time) - g_system->delayMillis(wakeup_time - time); + if (time < wakeUpTime) + g_system->delayMillis(wakeUpTime - time); break; } diff --git a/engines/sci/event.h b/engines/sci/event.h index 76c884aba4..15a94b3e73 100644 --- a/engines/sci/event.h +++ b/engines/sci/event.h @@ -39,11 +39,18 @@ struct SciEvent { uint16 character; /** - * The mouse position at the time the event was created. - * - * These are display coordinates! + * The mouse position at the time the event was created, + * in display coordinates. */ Common::Point mousePos; + +#ifdef ENABLE_SCI32 + /** + * The mouse position at the time the event was created, + * in script coordinates. + */ + Common::Point mousePosSci; +#endif }; /*Values for type*/ @@ -59,6 +66,9 @@ struct SciEvent { #define SCI_EVENT_ANY 0x7fff /* Keycodes of special keys: */ +#ifdef ENABLE_SCI32 +#define SCI_KEY_ETX 3 +#endif #define SCI_KEY_ESC 27 #define SCI_KEY_BACKSPACE 8 #define SCI_KEY_ENTER 13 @@ -121,7 +131,7 @@ public: ~EventManager(); void updateScreen(); - SciEvent getSciEvent(unsigned int mask); + SciEvent getSciEvent(uint32 mask); private: SciEvent getScummVMEvent(); diff --git a/engines/sci/graphics/animate.cpp b/engines/sci/graphics/animate.cpp index 7957ed6a55..98278397b7 100644 --- a/engines/sci/graphics/animate.cpp +++ b/engines/sci/graphics/animate.cpp @@ -28,6 +28,7 @@ #include "sci/sci.h" #include "sci/event.h" #include "sci/engine/kernel.h" +#include "sci/engine/script_patches.h" #include "sci/engine/state.h" #include "sci/engine/selector.h" #include "sci/engine/vm.h" @@ -44,8 +45,8 @@ namespace Sci { -GfxAnimate::GfxAnimate(EngineState *state, GfxCache *cache, GfxPorts *ports, GfxPaint16 *paint16, GfxScreen *screen, GfxPalette *palette, GfxCursor *cursor, GfxTransitions *transitions) - : _s(state), _cache(cache), _ports(ports), _paint16(paint16), _screen(screen), _palette(palette), _cursor(cursor), _transitions(transitions) { +GfxAnimate::GfxAnimate(EngineState *state, ScriptPatcher *scriptPatcher, GfxCache *cache, GfxPorts *ports, GfxPaint16 *paint16, GfxScreen *screen, GfxPalette *palette, GfxCursor *cursor, GfxTransitions *transitions) + : _s(state), _scriptPatcher(scriptPatcher), _cache(cache), _ports(ports), _paint16(paint16), _screen(screen), _palette(palette), _cursor(cursor), _transitions(transitions) { init(); } @@ -55,16 +56,77 @@ GfxAnimate::~GfxAnimate() { void GfxAnimate::init() { _lastCastData.clear(); - _ignoreFastCast = false; - // fastCast object is not found in any SCI games prior SCI1 - if (getSciVersion() <= SCI_VERSION_01) - _ignoreFastCast = true; - // Also if fastCast object exists at gamestartup, we can assume that the interpreter doesnt do kAnimate aborts - // (found in Larry 1) - if (getSciVersion() > SCI_VERSION_0_EARLY) { - if (!_s->_segMan->findObjectByName("fastCast").isNull()) - _ignoreFastCast = true; + _fastCastEnabled = false; + if (getSciVersion() == SCI_VERSION_1_1) { + // Seems to have been available for all SCI1.1 games + _fastCastEnabled = true; + } else if (getSciVersion() >= SCI_VERSION_1_EARLY) { + // fastCast only exists for some games between SCI1 early and SCI1 late + // Try to detect it by code signature + // It's extremely important, that we only enable it for games that actually need it + if (detectFastCast()) { + _fastCastEnabled = true; + } + } +} + +// Signature for fastCast detection +static const uint16 fastCastSignature[] = { + SIG_MAGICDWORD, + 0x35, 0x00, // ldi 00 + 0xa1, 84, // sag global[84d] + SIG_END +}; + +// Fast cast in games: + +// SCI1 Early: +// KQ5 - no fastcast, LSL1 (demo) - no fastcast, Mixed Up Fairy Tales - *has fastcast*, XMas Card 1990 - no fastcast, +// SQ4Floppy - no fastcast, Mixed Up Mother Goose - no fastcast +// +// SCI1 Middle: +// LSL5 demo - no fastfast, Conquest of the Longbow demo - no fastcast, LSL1 - no fastcast, +// Astro Chicken II - no fastcast +// +// SCI1 Late: +// Castle of Dr. Brain demo - has fastcast, Castle of Dr. Brain - has fastcast, +// Conquests of the Longbow - has fastcast, Space Quest 1 EGA - has fastcast, +// King's Quest 5 multilingual - *NO* fastcast, Police Quest 3 demo - *NO* fastcast, +// LSL5 multilingual - has fastcast, Police Quest 3 - has fastcast, +// EcoQuest 1 - has fastcast, Mixed Up Fairy Tales demo - has fastcast, +// Space Quest 4 multilingual - *NO* fastcast +// +// SCI1.1 +// Quest for Glory 3 demo - has fastcast, Police Quest 1 - hast fastcast, Quest for Glory 1 - has fastcast +// Laura Bow 2 Floppy - has fastcast, Mixed Up Mother Goose - has fastcast, Quest for Glory 3 - has fastcast +// Island of Dr. Brain - has fastcast, King's Quest 6 - has fastcast, Space Quest 5 - has fastcast +// Hoyle 4 - has fastcast, Laura Bow 2 CD - has fastcast, Freddy Pharkas CD - has fastcast +bool GfxAnimate::detectFastCast() { + SegManager *segMan = _s->_segMan; + const reg_t gameVMObject = g_sci->getGameObject(); + reg_t gameSuperVMObject = segMan->getObject(gameVMObject)->getSuperClassSelector(); + uint32 magicDWord = 0; // platform-specific BE/LE for performance + int magicDWordOffset = 0; + + if (gameSuperVMObject.isNull()) { + gameSuperVMObject = gameVMObject; // Just in case. According to sci.cpp this may happen in KQ5CD, when loading saved games before r54510 + } + + Script *objectScript = segMan->getScript(gameSuperVMObject.getSegment()); + byte *scriptData = const_cast<byte *>(objectScript->getBuf(0)); + uint32 scriptSize = objectScript->getBufSize(); + + _scriptPatcher->calculateMagicDWordAndVerify("fast cast detection", fastCastSignature, true, magicDWord, magicDWordOffset); + + // Signature is found for multilingual King's Quest 5 too, but it looks as if the fast cast global is never set + // within that game. Which means even though we detect it as having the capability, it's never actually used. + // The original multilingual KQ5 interpreter did have this feature disabled. + // Sierra probably used latest system scripts and that's why we detect it. + if (_scriptPatcher->findSignature(magicDWord, magicDWordOffset, fastCastSignature, "fast cast detection", scriptData, scriptSize) >= 0) { + // Signature found, game seems to use fast cast for kAnimate + return true; } + return false; } void GfxAnimate::disposeLastCast() { @@ -80,12 +142,14 @@ bool GfxAnimate::invoke(List *list, int argc, reg_t *argv) { while (curNode) { curObject = curNode->value; - if (!_ignoreFastCast) { + if (_fastCastEnabled) { // Check if the game has a fastCast object set // if we don't abort kAnimate processing, at least in kq5 there will be animation cels drawn into speech boxes. if (!_s->variables[VAR_GLOBAL][84].isNull()) { - if (!strcmp(_s->_segMan->getObjectName(_s->variables[VAR_GLOBAL][84]), "fastCast")) - return false; + // This normally points to an object called "fastCast", + // but for example in Eco Quest 1 it may also point to an object called "EventHandler" (see bug #5170) + // Original SCI only checked, if this global was not 0. + return false; } } diff --git a/engines/sci/graphics/animate.h b/engines/sci/graphics/animate.h index 6c1822c903..ac7078093c 100644 --- a/engines/sci/graphics/animate.h +++ b/engines/sci/graphics/animate.h @@ -87,9 +87,13 @@ class GfxView; */ class GfxAnimate { public: - GfxAnimate(EngineState *state, GfxCache *cache, GfxPorts *ports, GfxPaint16 *paint16, GfxScreen *screen, GfxPalette *palette, GfxCursor *cursor, GfxTransitions *transitions); + GfxAnimate(EngineState *state, ScriptPatcher *scriptPatcher, GfxCache *cache, GfxPorts *ports, GfxPaint16 *paint16, GfxScreen *screen, GfxPalette *palette, GfxCursor *cursor, GfxTransitions *transitions); virtual ~GfxAnimate(); + bool isFastCastEnabled() { + return _fastCastEnabled; + } + void disposeLastCast(); bool invoke(List *list, int argc, reg_t *argv); void makeSortedList(List *list); @@ -110,6 +114,7 @@ public: private: void init(); + bool detectFastCast(); void addToPicSetPicNotValid(); void animateShowPic(); @@ -119,6 +124,7 @@ private: void setNsRect(GfxView *view, AnimateList::iterator it); EngineState *_s; + ScriptPatcher *_scriptPatcher; GfxCache *_cache; GfxPorts *_ports; GfxPaint16 *_paint16; @@ -130,7 +136,7 @@ private: AnimateList _list; AnimateArray _lastCastData; - bool _ignoreFastCast; + bool _fastCastEnabled; }; } // End of namespace Sci diff --git a/engines/sci/graphics/cache.cpp b/engines/sci/graphics/cache.cpp index 59af8334eb..fb1f557ad6 100644 --- a/engines/sci/graphics/cache.cpp +++ b/engines/sci/graphics/cache.cpp @@ -102,8 +102,4 @@ int16 GfxCache::kernelViewGetCelCount(GuiResourceId viewId, int16 loopNo) { return getView(viewId)->getCelCount(loopNo); } -byte GfxCache::kernelViewGetColorAtCoordinate(GuiResourceId viewId, int16 loopNo, int16 celNo, int16 x, int16 y) { - return getView(viewId)->getColorAtCoordinate(loopNo, celNo, x, y); -} - } // End of namespace Sci diff --git a/engines/sci/graphics/cache.h b/engines/sci/graphics/cache.h index 33fa4fe399..61952718a9 100644 --- a/engines/sci/graphics/cache.h +++ b/engines/sci/graphics/cache.h @@ -49,8 +49,6 @@ public: int16 kernelViewGetLoopCount(GuiResourceId viewId); int16 kernelViewGetCelCount(GuiResourceId viewId, int16 loopNo); - byte kernelViewGetColorAtCoordinate(GuiResourceId viewId, int16 loopNo, int16 celNo, int16 x, int16 y); - private: void purgeFontCache(); void purgeViewCache(); diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp new file mode 100644 index 0000000000..693bc5f196 --- /dev/null +++ b/engines/sci/graphics/celobj32.cpp @@ -0,0 +1,1050 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "sci/resource.h" +#include "sci/engine/seg_manager.h" +#include "sci/engine/state.h" +#include "sci/graphics/celobj32.h" +#include "sci/graphics/frameout.h" +#include "sci/graphics/palette32.h" +#include "sci/graphics/picture.h" +#include "sci/graphics/remap.h" +#include "sci/graphics/text32.h" +#include "sci/graphics/view.h" + +namespace Sci { +#pragma mark CelScaler +CelScaler *CelObj::_scaler = nullptr; + +void CelScaler::activateScaleTables(const Ratio &scaleX, const Ratio &scaleY) { + const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth; + const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight; + + for (int i = 0; i < ARRAYSIZE(_scaleTables); ++i) { + if (_scaleTables[i].scaleX == scaleX && _scaleTables[i].scaleY == scaleY) { + _activeIndex = i; + return; + } + } + + int i = 1 - _activeIndex; + _activeIndex = i; + CelScalerTable &table = _scaleTables[i]; + + if (table.scaleX != scaleX) { + assert(screenWidth <= ARRAYSIZE(table.valuesX)); + buildLookupTable(table.valuesX, scaleX, screenWidth); + table.scaleX = scaleX; + } + + if (table.scaleY != scaleY) { + assert(screenHeight <= ARRAYSIZE(table.valuesY)); + buildLookupTable(table.valuesY, scaleY, screenHeight); + table.scaleY = scaleY; + } +} + +void CelScaler::buildLookupTable(int *table, const Ratio &ratio, const int size) { + int value = 0; + int remainder = 0; + int num = ratio.getNumerator(); + for (int i = 0; i < size; ++i) { + *table++ = value; + remainder += ratio.getDenominator(); + if (remainder >= num) { + value += remainder / num; + remainder %= num; + } + } +} + +const CelScalerTable *CelScaler::getScalerTable(const Ratio &scaleX, const Ratio &scaleY) { + activateScaleTables(scaleX, scaleY); + return &_scaleTables[_activeIndex]; +} + +#pragma mark - +#pragma mark CelObj + +void CelObj::init() { + CelObj::deinit(); + _nextCacheId = 1; + _scaler = new CelScaler(); + _cache = new CelCache; + _cache->resize(100); +} + +void CelObj::deinit() { + delete _scaler; + _scaler = nullptr; + if (_cache != nullptr) { + for (CelCache::iterator it = _cache->begin(); it != _cache->end(); ++it) { + delete it->celObj; + } + } + delete _cache; + _cache = nullptr; +} + +#pragma mark - +#pragma mark CelObj - Scalers + +template<bool FLIP, typename READER> +struct SCALER_NoScale { +#ifndef NDEBUG + const byte *_rowEdge; +#endif + const byte *_row; + READER _reader; + const int16 _lastIndex; + const int16 _sourceX; + const int16 _sourceY; + + SCALER_NoScale(const CelObj &celObj, const int16 maxWidth, const Common::Point &scaledPosition) : + _reader(celObj, FLIP ? celObj._width : maxWidth), + _lastIndex(celObj._width - 1), + _sourceX(scaledPosition.x), + _sourceY(scaledPosition.y) {} + + inline void setTarget(const int16 x, const int16 y) { + _row = _reader.getRow(y - _sourceY); + + if (FLIP) { +#ifndef NDEBUG + _rowEdge = _row - 1; +#endif + _row += _lastIndex - (x - _sourceX); + assert(_row > _rowEdge); + } else { +#ifndef NDEBUG + _rowEdge = _row + _lastIndex + 1; +#endif + _row += x - _sourceX; + assert(_row < _rowEdge); + } + } + + inline byte read() { + assert(_row != _rowEdge); + + if (FLIP) { + return *_row--; + } else { + return *_row++; + } + } +}; + +template<bool FLIP, typename READER> +struct SCALER_Scale { +#ifndef NDEBUG + int16 _maxX; +#endif + const byte *_row; + READER _reader; + int16 _x; + static int16 _valuesX[1024]; + static int16 _valuesY[1024]; + + SCALER_Scale(const CelObj &celObj, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio scaleX, const Ratio scaleY) : +#ifndef NDEBUG + _maxX(targetRect.right - 1), +#endif + // The maximum width of the scaled object may not be as + // wide as the source data it requires if downscaling, + // so just always make the reader decompress an entire + // line of source data when scaling + _reader(celObj, celObj._width) { + // In order for scaling ratios to apply equally across objects that + // start at different positions on the screen, the pixels that are + // read from the source bitmap must all use the same pattern of + // division. In other words, cels must follow the same scaling pattern + // as if they were drawn starting at an even multiple of the scaling + // ratio, even if they were not. + // + // To get the correct source pixel when reading out through the scaler, + // the engine creates a lookup table for each axis that translates + // directly from target positions to the indexes of source pixels using + // the global cadence for the given scaling ratio. + + const CelScalerTable *table = CelObj::_scaler->getScalerTable(scaleX, scaleY); + + const int16 unscaledX = (scaledPosition.x / scaleX).toInt(); + if (FLIP) { + int lastIndex = celObj._width - 1; + for (int16 x = targetRect.left; x < targetRect.right; ++x) { + _valuesX[x] = lastIndex - (table->valuesX[x] - unscaledX); + } + } else { + for (int16 x = targetRect.left; x < targetRect.right; ++x) { + _valuesX[x] = table->valuesX[x] - unscaledX; + } + } + + const int16 unscaledY = (scaledPosition.y / scaleY).toInt(); + for (int16 y = targetRect.top; y < targetRect.bottom; ++y) { + _valuesY[y] = table->valuesY[y] - unscaledY; + } + } + + inline void setTarget(const int16 x, const int16 y) { + _row = _reader.getRow(_valuesY[y]); + _x = x; + assert(_x >= 0 && _x <= _maxX); + } + + inline byte read() { + assert(_x >= 0 && _x <= _maxX); + return _row[_valuesX[_x++]]; + } +}; + +template<bool FLIP, typename READER> +int16 SCALER_Scale<FLIP, READER>::_valuesX[1024]; +template<bool FLIP, typename READER> +int16 SCALER_Scale<FLIP, READER>::_valuesY[1024]; + +#pragma mark - +#pragma mark CelObj - Resource readers + +struct READER_Uncompressed { +private: +#ifndef NDEBUG + const int16 _sourceHeight; +#endif + byte *_pixels; + const int16 _sourceWidth; + +public: + READER_Uncompressed(const CelObj &celObj, const int16) : +#ifndef NDEBUG + _sourceHeight(celObj._height), +#endif + _sourceWidth(celObj._width) { + byte *resource = celObj.getResPointer(); + _pixels = resource + READ_SCI11ENDIAN_UINT32(resource + celObj._celHeaderOffset + 24); + } + + inline const byte *getRow(const int16 y) const { + assert(y >= 0 && y < _sourceHeight); + return _pixels + y * _sourceWidth; + } +}; + +struct READER_Compressed { +private: + byte *_resource; + byte _buffer[1024]; + uint32 _controlOffset; + uint32 _dataOffset; + uint32 _uncompressedDataOffset; + int16 _y; + const int16 _sourceHeight; + const uint8 _transparentColor; + const int16 _maxWidth; + +public: + READER_Compressed(const CelObj &celObj, const int16 maxWidth) : + _resource(celObj.getResPointer()), + _y(-1), + _sourceHeight(celObj._height), + _transparentColor(celObj._transparentColor), + _maxWidth(maxWidth) { + assert(maxWidth <= celObj._width); + + byte *celHeader = _resource + celObj._celHeaderOffset; + _dataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 24); + _uncompressedDataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 28); + _controlOffset = READ_SCI11ENDIAN_UINT32(celHeader + 32); + } + + inline const byte *getRow(const int16 y) { + assert(y >= 0 && y < _sourceHeight); + if (y != _y) { + // compressed data segment for row + byte *row = _resource + _dataOffset + READ_SCI11ENDIAN_UINT32(_resource + _controlOffset + y * 4); + + // uncompressed data segment for row + byte *literal = _resource + _uncompressedDataOffset + READ_SCI11ENDIAN_UINT32(_resource + _controlOffset + _sourceHeight * 4 + y * 4); + + uint8 length; + for (int16 i = 0; i < _maxWidth; i += length) { + byte controlByte = *row++; + length = controlByte; + + // Run-length encoded + if (controlByte & 0x80) { + length &= 0x3F; + assert(i + length < (int)sizeof(_buffer)); + + // Fill with skip color + if (controlByte & 0x40) { + memset(_buffer + i, _transparentColor, length); + // Next value is fill color + } else { + memset(_buffer + i, *literal, length); + ++literal; + } + // Uncompressed + } else { + assert(i + length < (int)sizeof(_buffer)); + memcpy(_buffer + i, literal, length); + literal += length; + } + } + _y = y; + } + + return _buffer; + } +}; + +#pragma mark - +#pragma mark CelObj - Remappers + +struct MAPPER_NoMD { + inline void draw(byte *target, const byte pixel, const uint8 skipColor) const { + if (pixel != skipColor) { + *target = pixel; + } + } +}; +struct MAPPER_NoMDNoSkip { + inline void draw(byte *target, const byte pixel, const uint8) const { + *target = pixel; + } +}; + +struct MAPPER_Map { + inline void draw(byte *target, const byte pixel, const uint8 skipColor) const { + if (pixel != skipColor) { + if (pixel < g_sci->_gfxRemap32->getStartColor()) { + *target = pixel; + } else { + if (g_sci->_gfxRemap32->remapEnabled(pixel)) + *target = g_sci->_gfxRemap32->remapColor(pixel, *target); + } + } + } +}; + +void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect) const { + const Common::Point &scaledPosition = screenItem._scaledPosition; + const Ratio &scaleX = screenItem._ratioX; + const Ratio &scaleY = screenItem._ratioY; + + if (_remap) { + // NOTE: In the original code this check was `g_Remap_numActiveRemaps && _remap`, + // but since we are already in a `_remap` branch, there is no reason to check it + // again + if (g_sci->_gfxRemap32->getRemapCount()) { + if (scaleX.isOne() && scaleY.isOne()) { + if (_compressionType == kCelCompressionNone) { + if (_drawMirrored) { + drawUncompHzFlipMap(target, targetRect, scaledPosition); + } else { + drawUncompNoFlipMap(target, targetRect, scaledPosition); + } + } else { + if (_drawMirrored) { + drawHzFlipMap(target, targetRect, scaledPosition); + } else { + drawNoFlipMap(target, targetRect, scaledPosition); + } + } + } else { + if (_compressionType == kCelCompressionNone) { + scaleDrawUncompMap(target, scaleX, scaleY, targetRect, scaledPosition); + } else { + scaleDrawMap(target, scaleX, scaleY, targetRect, scaledPosition); + } + } + } else { + if (scaleX.isOne() && scaleY.isOne()) { + if (_compressionType == kCelCompressionNone) { + if (_drawMirrored) { + drawUncompHzFlip(target, targetRect, scaledPosition); + } else { + drawUncompNoFlip(target, targetRect, scaledPosition); + } + } else { + if (_drawMirrored) { + drawHzFlip(target, targetRect, scaledPosition); + } else { + drawNoFlip(target, targetRect, scaledPosition); + } + } + } else { + if (_compressionType == kCelCompressionNone) { + scaleDrawUncomp(target, scaleX, scaleY, targetRect, scaledPosition); + } else { + scaleDraw(target, scaleX, scaleY, targetRect, scaledPosition); + } + } + } + } else { + if (scaleX.isOne() && scaleY.isOne()) { + if (_compressionType == kCelCompressionNone) { + if (_transparent) { + if (_drawMirrored) { + drawUncompHzFlipNoMD(target, targetRect, scaledPosition); + } else { + drawUncompNoFlipNoMD(target, targetRect, scaledPosition); + } + } else { + if (_drawMirrored) { + drawUncompHzFlipNoMDNoSkip(target, targetRect, scaledPosition); + } else { + drawUncompNoFlipNoMDNoSkip(target, targetRect, scaledPosition); + } + } + } else { + if (_drawMirrored) { + drawHzFlipNoMD(target, targetRect, scaledPosition); + } else { + drawNoFlipNoMD(target, targetRect, scaledPosition); + } + } + } else { + if (_compressionType == kCelCompressionNone) { + scaleDrawUncompNoMD(target, scaleX, scaleY, targetRect, scaledPosition); + } else { + scaleDrawNoMD(target, scaleX, scaleY, targetRect, scaledPosition); + } + } + } +} + +void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, bool mirrorX) { + _drawMirrored = mirrorX; + draw(target, screenItem, targetRect); +} + +void CelObj::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) { + _drawMirrored = mirrorX; + Ratio square; + drawTo(target, targetRect, scaledPosition, square, square); +} + +void CelObj::drawTo(Buffer &target, Common::Rect const &targetRect, Common::Point const &scaledPosition, Ratio const &scaleX, Ratio const &scaleY) const { + if (_remap) { + if (scaleX.isOne() && scaleY.isOne()) { + if (_compressionType == kCelCompressionNone) { + if (_drawMirrored) { + drawUncompHzFlipMap(target, targetRect, scaledPosition); + } else { + drawUncompNoFlipMap(target, targetRect, scaledPosition); + } + } else { + if (_drawMirrored) { + drawHzFlipMap(target, targetRect, scaledPosition); + } else { + drawNoFlipMap(target, targetRect, scaledPosition); + } + } + } else { + if (_compressionType == kCelCompressionNone) { + scaleDrawUncompMap(target, scaleX, scaleY, targetRect, scaledPosition); + } else { + scaleDrawMap(target, scaleX, scaleY, targetRect, scaledPosition); + } + } + } else { + if (scaleX.isOne() && scaleY.isOne()) { + if (_compressionType == kCelCompressionNone) { + if (_drawMirrored) { + drawUncompHzFlipNoMD(target, targetRect, scaledPosition); + } else { + drawUncompNoFlipNoMD(target, targetRect, scaledPosition); + } + } else { + if (_drawMirrored) { + drawHzFlipNoMD(target, targetRect, scaledPosition); + } else { + drawNoFlipNoMD(target, targetRect, scaledPosition); + } + } + } else { + if (_compressionType == kCelCompressionNone) { + scaleDrawUncompNoMD(target, scaleX, scaleY, targetRect, scaledPosition); + } else { + scaleDrawNoMD(target, scaleX, scaleY, targetRect, scaledPosition); + } + } + } +} + +uint8 CelObj::readPixel(uint16 x, const uint16 y, bool mirrorX) const { + if (mirrorX) { + x = _width - x - 1; + } + + if (_compressionType == kCelCompressionNone) { + READER_Uncompressed reader(*this, x + 1); + return reader.getRow(y)[x]; + } else { + READER_Compressed reader(*this, x + 1); + return reader.getRow(y)[x]; + } +} + +void CelObj::submitPalette() const { + if (_hunkPaletteOffset) { + Palette palette; + + byte *res = getResPointer(); + // NOTE: In SCI engine this uses HunkPalette::Init. + // TODO: Use a better size value + g_sci->_gfxPalette32->createFromData(res + _hunkPaletteOffset, 999999, &palette); + g_sci->_gfxPalette32->submit(palette); + } +} + +#pragma mark - +#pragma mark CelObj - Caching +int CelObj::_nextCacheId = 1; +CelCache *CelObj::_cache = nullptr; + +int CelObj::searchCache(const CelInfo32 &celInfo, int *nextInsertIndex) const { + int oldestId = _nextCacheId + 1; + int oldestIndex = -1; + + for (int i = 0, len = _cache->size(); i < len; ++i) { + CelCacheEntry &entry = (*_cache)[i]; + + if (entry.celObj != nullptr) { + if (entry.celObj->_info == celInfo) { + entry.id = ++_nextCacheId; + return i; + } + + if (oldestId > entry.id) { + oldestId = entry.id; + oldestIndex = i; + } + } else if (oldestIndex == -1) { + oldestIndex = i; + } + } + + // NOTE: Unlike the original SCI engine code, the out-param + // here is only updated if there was not a cache hit. + *nextInsertIndex = oldestIndex; + return -1; +} + +void CelObj::putCopyInCache(const int cacheIndex) const { + if (cacheIndex == -1) { + error("Invalid cache index"); + } + + CelCacheEntry &entry = (*_cache)[cacheIndex]; + + if (entry.celObj != nullptr) { + delete entry.celObj; + } + + entry.celObj = duplicate(); + entry.id = ++_nextCacheId; +} + +#pragma mark - +#pragma mark CelObj - Drawing + +template<typename MAPPER, typename SCALER> +struct RENDERER { + MAPPER &_mapper; + SCALER &_scaler; + const uint8 _skipColor; + + RENDERER(MAPPER &mapper, SCALER &scaler, const uint8 skipColor) : + _mapper(mapper), + _scaler(scaler), + _skipColor(skipColor) {} + + inline void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + byte *targetPixel = (byte *)target.getPixels() + target.screenWidth * targetRect.top + targetRect.left; + + const int16 skipStride = target.screenWidth - targetRect.width(); + const int16 targetWidth = targetRect.width(); + const int16 targetHeight = targetRect.height(); + for (int16 y = 0; y < targetHeight; ++y) { + _scaler.setTarget(targetRect.left, targetRect.top + y); + + for (int16 x = 0; x < targetWidth; ++x) { + _mapper.draw(targetPixel++, _scaler.read(), _skipColor); + } + + targetPixel += skipStride; + } + } +}; + +template<typename MAPPER, typename SCALER> +void CelObj::render(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + + MAPPER mapper; + SCALER scaler(*this, targetRect.left - scaledPosition.x + targetRect.width(), scaledPosition); + RENDERER<MAPPER, SCALER> renderer(mapper, scaler, _transparentColor); + renderer.draw(target, targetRect, scaledPosition); +} + +template<typename MAPPER, typename SCALER> +void CelObj::render(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio &scaleX, const Ratio &scaleY) const { + + MAPPER mapper; + SCALER scaler(*this, targetRect, scaledPosition, scaleX, scaleY); + RENDERER<MAPPER, SCALER> renderer(mapper, scaler, _transparentColor); + renderer.draw(target, targetRect, scaledPosition); +} + +void dummyFill(Buffer &target, const Common::Rect &targetRect) { + target.fillRect(targetRect, 250); +} + +void CelObj::drawHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + debug("drawHzFlip"); + dummyFill(target, targetRect); +} + +void CelObj::drawNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + debug("drawNoFlip"); + dummyFill(target, targetRect); +} + +void CelObj::drawUncompNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + debug("drawUncompNoFlip"); + dummyFill(target, targetRect); +} + +void CelObj::drawUncompHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + debug("drawUncompHzFlip"); + dummyFill(target, targetRect); +} + +void CelObj::scaleDraw(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + debug("scaleDraw"); + dummyFill(target, targetRect); +} + +void CelObj::scaleDrawUncomp(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + debug("scaleDrawUncomp"); + dummyFill(target, targetRect); +} + +void CelObj::drawHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + render<MAPPER_Map, SCALER_NoScale<true, READER_Compressed> >(target, targetRect, scaledPosition); +} + +void CelObj::drawNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + render<MAPPER_Map, SCALER_NoScale<false, READER_Compressed> >(target, targetRect, scaledPosition); +} + +void CelObj::drawUncompNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + render<MAPPER_Map, SCALER_NoScale<false, READER_Uncompressed> >(target, targetRect, scaledPosition); +} + +void CelObj::drawUncompHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + render<MAPPER_Map, SCALER_NoScale<true, READER_Uncompressed> >(target, targetRect, scaledPosition); +} + +void CelObj::scaleDrawMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + if (_drawMirrored) + render<MAPPER_Map, SCALER_Scale<true, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY); + else + render<MAPPER_Map, SCALER_Scale<false, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY); +} + +void CelObj::scaleDrawUncompMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + if (_drawMirrored) + render<MAPPER_Map, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY); + else + render<MAPPER_Map, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY); +} + +void CelObj::drawNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + render<MAPPER_NoMD, SCALER_NoScale<false, READER_Compressed> >(target, targetRect, scaledPosition); +} + +void CelObj::drawHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + render<MAPPER_NoMD, SCALER_NoScale<true, READER_Compressed> >(target, targetRect, scaledPosition); +} + +void CelObj::drawUncompNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + render<MAPPER_NoMD, SCALER_NoScale<false, READER_Uncompressed> >(target, targetRect, scaledPosition); +} + +void CelObj::drawUncompNoFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + render<MAPPER_NoMDNoSkip, SCALER_NoScale<false, READER_Uncompressed> >(target, targetRect, scaledPosition); +} + +void CelObj::drawUncompHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + render<MAPPER_NoMD, SCALER_NoScale<true, READER_Uncompressed> >(target, targetRect, scaledPosition); +} + +void CelObj::drawUncompHzFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + render<MAPPER_NoMDNoSkip, SCALER_NoScale<true, READER_Uncompressed> >(target, targetRect, scaledPosition); +} + +void CelObj::scaleDrawNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + if (_drawMirrored) + render<MAPPER_NoMD, SCALER_Scale<true, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY); + else + render<MAPPER_NoMD, SCALER_Scale<false, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY); +} + +void CelObj::scaleDrawUncompNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + if (_drawMirrored) + render<MAPPER_NoMD, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY); + else + render<MAPPER_NoMD, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY); +} + +#pragma mark - +#pragma mark CelObjView +CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo) { + _info.type = kCelTypeView; + _info.resourceId = viewId; + _info.loopNo = loopNo; + _info.celNo = celNo; + _mirrorX = false; + _compressionType = kCelCompressionInvalid; + _transparent = true; + + int cacheInsertIndex; + int cacheIndex = searchCache(_info, &cacheInsertIndex); + if (cacheIndex != -1) { + CelCacheEntry &entry = (*_cache)[cacheIndex]; + *this = *dynamic_cast<CelObjView *>(entry.celObj); + entry.id = ++_nextCacheId; + return; + } + + // TODO: The next code should be moved to a common file that + // generates view resource metadata for both SCI16 and SCI32 + // implementations + + Resource *resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, viewId), false); + + // NOTE: SCI2.1/SQ6 just silently returns here. + if (!resource) { + warning("View resource %d not loaded", viewId); + return; + } + + byte *data = resource->data; + + _scaledWidth = READ_SCI11ENDIAN_UINT16(data + 14); + _scaledHeight = READ_SCI11ENDIAN_UINT16(data + 16); + + if (_scaledWidth == 0 || _scaledHeight == 0) { + byte sizeFlag = data[5]; + if (sizeFlag == 0) { + _scaledWidth = 320; + _scaledHeight = 200; + } else if (sizeFlag == 1) { + _scaledWidth = 640; + _scaledHeight = 480; + } else if (sizeFlag == 2) { + _scaledWidth = 640; + _scaledHeight = 400; + } + } + + uint16 loopCount = data[2]; + if (_info.loopNo >= loopCount) { + _info.loopNo = loopCount - 1; + } + + // NOTE: This is the actual check, in the actual location, + // from SCI engine. + if (loopNo < 0) { + error("Loop is less than 0!"); + } + + const uint16 viewHeaderSize = READ_SCI11ENDIAN_UINT16(data); + const uint8 loopHeaderSize = data[12]; + const uint8 viewHeaderFieldSize = 2; + + byte *loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * _info.loopNo); + + if ((int8)loopHeader[0] != -1) { + if (loopHeader[1] == 1) { + _mirrorX = true; + } + + loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * (int8)loopHeader[0]); + } + + uint8 celCount = loopHeader[2]; + if (_info.celNo >= celCount) { + _info.celNo = celCount - 1; + } + + _hunkPaletteOffset = READ_SCI11ENDIAN_UINT32(data + 8); + _celHeaderOffset = READ_SCI11ENDIAN_UINT32(loopHeader + 12) + (data[13] * _info.celNo); + + byte *celHeader = data + _celHeaderOffset; + + _width = READ_SCI11ENDIAN_UINT16(celHeader); + _height = READ_SCI11ENDIAN_UINT16(celHeader + 2); + _displace.x = _width / 2 - (int16)READ_SCI11ENDIAN_UINT16(celHeader + 4); + _displace.y = _height - (int16)READ_SCI11ENDIAN_UINT16(celHeader + 6) - 1; + _transparentColor = celHeader[8]; + _compressionType = (CelCompressionType)celHeader[9]; + + if (_compressionType != kCelCompressionNone && _compressionType != kCelCompressionRLE) { + error("Compression type not supported - V: %d L: %d C: %d", _info.resourceId, _info.loopNo, _info.celNo); + } + + if (celHeader[10] & 128) { + // NOTE: This is correct according to SCI2.1/SQ6/DOS; + // the engine re-reads the byte value as a word value + uint16 flags = READ_SCI11ENDIAN_UINT16(celHeader + 10); + _transparent = flags & 1 ? true : false; + _remap = flags & 2 ? true : false; + } else if (_compressionType == kCelCompressionNone) { + _remap = analyzeUncompressedForRemap(); + } else { + _remap = analyzeForRemap(); + } + + putCopyInCache(cacheInsertIndex); +} + +bool CelObjView::analyzeUncompressedForRemap() const { + byte *pixels = getResPointer() + READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24); + for (int i = 0; i < _width * _height; ++i) { + byte pixel = pixels[i]; + if (pixel >= g_sci->_gfxRemap32->getStartColor() && pixel <= g_sci->_gfxRemap32->getEndColor() && pixel != _transparentColor) { + return true; + } + } + return false; +} + +bool CelObjView::analyzeForRemap() const { + READER_Compressed reader(*this, _width); + for (int y = 0; y < _height; y++) { + const byte *curRow = reader.getRow(y); + for (int x = 0; x < _width; x++) { + byte pixel = curRow[x]; + if (pixel >= g_sci->_gfxRemap32->getStartColor() && pixel <= g_sci->_gfxRemap32->getEndColor() && pixel != _transparentColor) { + return true; + } + } + } + return false; +} + +void CelObjView::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, bool mirrorX, const Ratio &scaleX, const Ratio &scaleY) { + _drawMirrored = mirrorX; + drawTo(target, targetRect, scaledPosition, scaleX, scaleY); +} + +CelObjView *CelObjView::duplicate() const { + return new CelObjView(*this); +} + +byte *CelObjView::getResPointer() const { + return g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, _info.resourceId), false)->data; +} + +#pragma mark - +#pragma mark CelObjPic +CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) { + _info.type = kCelTypePic; + _info.resourceId = picId; + _info.loopNo = 0; + _info.celNo = celNo; + _mirrorX = false; + _compressionType = kCelCompressionInvalid; + _transparent = true; + _remap = false; + + int cacheInsertIndex; + int cacheIndex = searchCache(_info, &cacheInsertIndex); + if (cacheIndex != -1) { + CelCacheEntry &entry = (*_cache)[cacheIndex]; + *this = *dynamic_cast<CelObjPic *>(entry.celObj); + entry.id = ++_nextCacheId; + return; + } + + Resource *resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, picId), false); + + // NOTE: SCI2.1/SQ6 just silently returns here. + if (!resource) { + warning("Pic resource %d not loaded", picId); + return; + } + + byte *data = resource->data; + + _celCount = data[2]; + + if (_info.celNo >= _celCount) { + error("Cel number %d greater than cel count %d", _info.celNo, _celCount); + } + + _celHeaderOffset = READ_SCI11ENDIAN_UINT16(data) + (READ_SCI11ENDIAN_UINT16(data + 4) * _info.celNo); + _hunkPaletteOffset = READ_SCI11ENDIAN_UINT32(data + 6); + + byte *celHeader = data + _celHeaderOffset; + + _width = READ_SCI11ENDIAN_UINT16(celHeader); + _height = READ_SCI11ENDIAN_UINT16(celHeader + 2); + _displace.x = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 4); + _displace.y = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 6); + _transparentColor = celHeader[8]; + _compressionType = (CelCompressionType)celHeader[9]; + _priority = READ_SCI11ENDIAN_UINT16(celHeader + 36); + _relativePosition.x = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 38); + _relativePosition.y = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 40); + + uint16 sizeFlag1 = READ_SCI11ENDIAN_UINT16(data + 10); + uint16 sizeFlag2 = READ_SCI11ENDIAN_UINT16(data + 12); + + if (sizeFlag2) { + _scaledWidth = sizeFlag1; + _scaledHeight = sizeFlag2; + } else if (sizeFlag1 == 0) { + _scaledWidth = 320; + _scaledHeight = 200; + } else if (sizeFlag1 == 1) { + _scaledWidth = 640; + _scaledHeight = 480; + } else if (sizeFlag1 == 2) { + _scaledWidth = 640; + _scaledHeight = 400; + } + + if (celHeader[10] & 128) { + // NOTE: This is correct according to SCI2.1/SQ6/DOS; + // the engine re-reads the byte value as a word value + uint16 flags = READ_SCI11ENDIAN_UINT16(celHeader + 10); + _transparent = flags & 1 ? true : false; + _remap = flags & 2 ? true : false; + } else { + _transparent = _compressionType != kCelCompressionNone ? true : analyzeUncompressedForSkip(); + + if (_compressionType != kCelCompressionNone && _compressionType != kCelCompressionRLE) { + error("Compression type not supported - P: %d C: %d", picId, celNo); + } + } + + putCopyInCache(cacheInsertIndex); +} + +bool CelObjPic::analyzeUncompressedForSkip() const { + byte *resource = getResPointer(); + byte *pixels = resource + READ_SCI11ENDIAN_UINT32(resource + _celHeaderOffset + 24); + for (int i = 0; i < _width * _height; ++i) { + uint8 pixel = pixels[i]; + if (pixel == _transparentColor) { + return true; + } + } + + return false; +} + +void CelObjPic::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) { + Ratio square; + _drawMirrored = mirrorX; + drawTo(target, targetRect, scaledPosition, square, square); +} + +CelObjPic *CelObjPic::duplicate() const { + return new CelObjPic(*this); +} + +byte *CelObjPic::getResPointer() const { + return g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, _info.resourceId), false)->data; +} + +#pragma mark - +#pragma mark CelObjMem +CelObjMem::CelObjMem(const reg_t bitmapObject) { + _info.type = kCelTypeMem; + _info.bitmap = bitmapObject; + _mirrorX = false; + _compressionType = kCelCompressionNone; + _celHeaderOffset = 0; + _transparent = true; + + BitmapResource bitmap(bitmapObject); + _width = bitmap.getWidth(); + _height = bitmap.getHeight(); + _displace = bitmap.getDisplace(); + _transparentColor = bitmap.getSkipColor(); + _scaledWidth = bitmap.getScaledWidth(); + _scaledHeight = bitmap.getScaledHeight(); + _hunkPaletteOffset = bitmap.getHunkPaletteOffset(); + _remap = bitmap.getRemap(); +} + +CelObjMem *CelObjMem::duplicate() const { + return new CelObjMem(*this); +} + +byte *CelObjMem::getResPointer() const { + return g_sci->getEngineState()->_segMan->getHunkPointer(_info.bitmap); +} + +#pragma mark - +#pragma mark CelObjColor +CelObjColor::CelObjColor(const uint8 color, const int16 width, const int16 height) { + _info.type = kCelTypeColor; + _info.color = color; + _displace.x = 0; + _displace.y = 0; + _scaledWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + _scaledHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + _hunkPaletteOffset = 0; + _mirrorX = false; + _remap = false; + _width = width; + _height = height; +} + +void CelObjColor::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, const bool mirrorX) { + // TODO: The original engine sets this flag but why? One cannot + // draw a solid color mirrored. + _drawMirrored = mirrorX; + draw(target, targetRect); +} +void CelObjColor::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, bool mirrorX) { + error("Unsupported method"); +} +void CelObjColor::draw(Buffer &target, const Common::Rect &targetRect) const { + target.fillRect(targetRect, _info.color); +} + +CelObjColor *CelObjColor::duplicate() const { + return new CelObjColor(*this); +} + +byte *CelObjColor::getResPointer() const { + error("Unsupported method"); +} +} // End of namespace Sci diff --git a/engines/sci/graphics/celobj32.h b/engines/sci/graphics/celobj32.h new file mode 100644 index 0000000000..0bb4b03ae2 --- /dev/null +++ b/engines/sci/graphics/celobj32.h @@ -0,0 +1,581 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SCI_GRAPHICS_CELOBJ32_H +#define SCI_GRAPHICS_CELOBJ32_H + +#include "common/rational.h" +#include "common/rect.h" +#include "sci/resource.h" +#include "sci/engine/vm_types.h" + +namespace Sci { +typedef Common::Rational Ratio; + +enum CelType { + kCelTypeView = 0, + kCelTypePic = 1, + kCelTypeMem = 2, + kCelTypeColor = 3 +}; + +enum CelCompressionType { + kCelCompressionNone = 0, + kCelCompressionRLE = 138, + kCelCompressionInvalid = 1000 +}; + +/** + * A CelInfo32 object describes the basic properties of a + * cel object. + */ +struct CelInfo32 { + /** + * The type of the cel object. + */ + CelType type; + + /** + * For cel objects that draw from resources, the ID of + * the resource to load. + */ + GuiResourceId resourceId; + + /** + * For CelObjView, the loop number to draw from the + * view resource. + */ + int16 loopNo; + + /** + * For CelObjView and CelObjPic, the cel number to draw + * from the view or pic resource. + */ + int16 celNo; + + /** + * For CelObjMem, a segment register pointing to a heap + * resource containing headered bitmap data. + */ + reg_t bitmap; + + /** + * For CelObjColor, the fill color. + */ + uint8 color; + + // NOTE: In at least SCI2.1/SQ6, color is left + // uninitialised. + CelInfo32() : + type(kCelTypeMem), + resourceId(0), + loopNo(0), + celNo(0), + bitmap(NULL_REG) {} + + // NOTE: This is the equivalence criteria used by + // CelObj::searchCache in at least SCI2.1/SQ6. Notably, + // it does not check the color field. + inline bool operator==(const CelInfo32 &other) { + return ( + type == other.type && + resourceId == other.resourceId && + loopNo == other.loopNo && + celNo == other.celNo && + bitmap == other.bitmap + ); + } + + inline bool operator!=(const CelInfo32 &other) { + return !(*this == other); + } +}; + +class CelObj; +struct CelCacheEntry { + /** + * A monotonically increasing cache ID used to identify + * the least recently used item in the cache for + * replacement. + */ + int id; + CelObj *celObj; + CelCacheEntry() : id(0), celObj(nullptr) {} +}; + +typedef Common::Array<CelCacheEntry> CelCache; + +#pragma mark - +#pragma mark CelScaler + +struct CelScalerTable { + /** + * A lookup table of indexes that should be used to find + * the correct column to read from the source bitmap + * when drawing a scaled version of the source bitmap. + */ + int valuesX[1024]; + + /** + * The ratio used to generate the x-values. + */ + Ratio scaleX; + + /** + * A lookup table of indexes that should be used to find + * the correct row to read from a source bitmap when + * drawing a scaled version of the source bitmap. + */ + int valuesY[1024]; + + /** + * The ratio used to generate the y-values. + */ + Ratio scaleY; +}; + +class CelScaler { + /** + * Cached scale tables. + */ + CelScalerTable _scaleTables[2]; + + /** + * The index of the most recently used scale table. + */ + int _activeIndex; + + /** + * Activates a scale table for the given X and Y ratios. + * If there is no table that matches the given ratios, + * the least most recently used table will be replaced + * and activated. + */ + void activateScaleTables(const Ratio &scaleX, const Ratio &scaleY); + + /** + * Builds a pixel lookup table in `table` for the given + * ratio. The table will be filled up to the specified + * size, which should be large enough to draw across the + * entire target buffer. + */ + void buildLookupTable(int *table, const Ratio &ratio, const int size); + +public: + CelScaler() : + _scaleTables(), + _activeIndex(0) { + CelScalerTable &table = _scaleTables[0]; + table.scaleX = Ratio(); + table.scaleY = Ratio(); + for (int i = 0; i < ARRAYSIZE(table.valuesX); ++i) { + table.valuesX[i] = i; + table.valuesY[i] = i; + } + for (int i = 1; i < ARRAYSIZE(_scaleTables); ++i) { + _scaleTables[i] = _scaleTables[0]; + } + } + + /** + * Retrieves scaler tables for the given X and Y ratios. + */ + const CelScalerTable *getScalerTable(const Ratio &scaleX, const Ratio &scaleY); +}; + +#pragma mark - +#pragma mark CelObj + +class ScreenItem; +/** + * A cel object is the lowest-level rendering primitive in + * the SCI engine and draws itself directly to a target + * pixel buffer. + */ +class CelObj { +protected: + /** + * When true, this cel will be horizontally mirrored + * when it is drawn. This is an internal flag that is + * set by draw methods based on the combination of the + * cel's `_mirrorX` property and the owner screen item's + * `_mirrorX` property. + */ + bool _drawMirrored; + +public: + static CelScaler *_scaler; + + /** + * The basic identifying information for this cel. This + * information effectively acts as a composite key for + * a cel object, and any cel object can be recreated + * from this data alone. + */ + CelInfo32 _info; + + /** + * The offset to the cel header for this cel within the + * raw resource data. + */ + uint32 _celHeaderOffset; + + /** + * The offset to the embedded palette for this cel + * within the raw resource data. + */ + uint32 _hunkPaletteOffset; + + /** + * The natural dimensions of the cel. + */ + uint16 _width, _height; + + /** + * TODO: Documentation + */ + Common::Point _displace; + + /** + * The dimensions of the original coordinate system for + * the cel. Used to scale cels from their native size + * to the correct size on screen. + * + * @note This is set to scriptWidth/Height for + * CelObjColor. For other cel objects, the value comes + * from the raw resource data. For text bitmaps, this is + * the width/height of the coordinate system used to + * generate the text, which also defaults to + * scriptWidth/Height but seems to typically be changed + * to more closely match the native screen resolution. + */ + uint16 _scaledWidth, _scaledHeight; + + /** + * The skip (transparent) color for the cel. When + * compositing, any pixels matching this color will not + * be copied to the buffer. + */ + uint8 _transparentColor; + + /** + * Whether or not this cel has any transparent regions. + * This is used for optimised drawing of non-transparent + * cels. + */ + bool _transparent; // TODO: probably "skip"? + + /** + * The compression type for the pixel data for this cel. + */ + CelCompressionType _compressionType; + + /** + * Whether or not this cel should be palette-remapped? + */ + bool _remap; + + /** + * If true, the cel contains pre-mirrored picture data. + * This value comes directly from the resource data and + * is XORed with the `_mirrorX` property of the owner + * screen item when rendering. + */ + bool _mirrorX; + + /** + * Initialises static CelObj members. + */ + static void init(); + + /** + * Frees static CelObj members. + */ + static void deinit(); + + virtual ~CelObj() {}; + + /** + * Draws the cel to the target buffer using the priority + * and positioning information from the given screen + * item. The mirroring of the cel will be unchanged from + * any previous call to draw. + */ + void draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect) const; + + /** + * Draws the cel to the target buffer using the priority + * and positioning information from the given screen + * item and the given mirror flag. + * + * @note In SCI engine, this function was a virtual + * function, but CelObjView, CelObjPic, and CelObjMem + * all used the same function and the compiler + * deduplicated the copies; we deduplicate the source by + * putting the implementation on CelObj instead of + * copying it to 3/4 of the subclasses. + */ + virtual void draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, const bool mirrorX); + + /** + * Draws the cel to the target buffer using the + * positioning and mirroring information from the + * provided arguments. + * + * @note In SCI engine, this function was a virtual + * function, but CelObjView, CelObjPic, and CelObjMem + * all used the same function and the compiler + * deduplicated the copies; we deduplicate the source by + * putting the implementation on CelObj instead of + * copying it to 3/4 of the subclasses. + */ + virtual void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX); + + /** + * Draws the cel to the target buffer using the given + * position and scaling parameters. The mirroring of the + * cel will be unchanged from any previous call to draw. + */ + void drawTo(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio &scaleX, const Ratio &scaleY) const; + + /** + * Creates a copy of this cel on the free store and + * returns a pointer to the new object. The new cel will + * point to a shared copy of bitmap/resource data. + */ + virtual CelObj *duplicate() const = 0; + + /** + * Retrieves a pointer to the raw resource data for this + * cel. This method cannot be used with a CelObjColor. + */ + virtual byte *getResPointer() const = 0; + + /** + * Reads the pixel at the given coordinates. This method + * is valid only for CelObjView and CelObjPic. + */ + virtual uint8 readPixel(uint16 x, uint16 y, bool mirrorX) const; + + /** + * Submits the palette from this cel to the palette + * manager for integration into the master screen + * palette. + */ + void submitPalette() const; + +#pragma mark - +#pragma mark CelObj - Drawing +private: + template<typename MAPPER, typename SCALER> + void render(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + + template<typename MAPPER, typename SCALER> + void render(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio &scaleX, const Ratio &scaleY) const; + + void drawHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void drawNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void drawUncompNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void drawUncompHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void scaleDraw(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void scaleDrawUncomp(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + + void drawHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void drawNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void drawUncompNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void drawUncompHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void scaleDrawMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void scaleDrawUncompMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + // NOTE: The original includes versions of the above functions with priority parameters, which were not actually used in SCI32 + + void drawHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void drawNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void drawUncompNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void drawUncompNoFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void drawUncompHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void drawUncompHzFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void scaleDrawNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void scaleDrawUncompNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + // NOTE: The original includes versions of the above functions with priority parameters, which were not actually used in SCI32 + +#pragma mark - +#pragma mark CelObj - Caching +protected: + /** + * A monotonically increasing cache ID used to identify + * the least recently used item in the cache for + * replacement. + */ + static int _nextCacheId; + + /** + * A cache of cel objects used to avoid reinitialisation + * overhead for cels with the same CelInfo32. + */ + // NOTE: At least SQ6 uses a fixed cache size of 100. + static CelCache *_cache; + + /** + * Searches the cel cache for a CelObj matching the + * provided CelInfo32. If not found, -1 is returned. + * nextInsertIndex will receive the index of the oldest + * item in the cache, which can be used to replace + * the oldest item with a newer item. + */ + int searchCache(const CelInfo32 &celInfo, int *nextInsertIndex) const; + + /** + * Puts a copy of this CelObj into the cache at the + * given cache index. + */ + void putCopyInCache(int index) const; +}; + +#pragma mark - +#pragma mark CelObjView + +/** + * A CelObjView is the drawing primitive for a View type + * resource. Each CelObjView corresponds to a single cel + * within a single loop of a view. + */ +class CelObjView : public CelObj { +private: + /** + * Analyses resources without baked-in remap flags + * to determine whether or not they should be remapped. + */ + bool analyzeUncompressedForRemap() const; + + /** + * Analyses compressed resources without baked-in remap + * flags to determine whether or not they should be + * remapped. + */ + bool analyzeForRemap() const; + +public: + CelObjView(GuiResourceId viewId, int16 loopNo, int16 celNo); + virtual ~CelObjView() override {}; + + using CelObj::draw; + + /** + * Draws the cel to the target buffer using the + * positioning, mirroring, and scaling information from + * the provided arguments. + */ + void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, bool mirrorX, const Ratio &scaleX, const Ratio &scaleY); + + virtual CelObjView *duplicate() const override; + virtual byte *getResPointer() const override; +}; + +#pragma mark - +#pragma mark CelObjPic + +/** + * A CelObjPic is the drawing primitive for a Picture type + * resource. Each CelObjPic corresponds to a single cel + * within a picture. + */ +class CelObjPic : public CelObj { +private: + /** + * Analyses uncompressed resources without baked-in skip + * flags to determine whether or not they can use fast + * blitting. + */ + bool analyzeUncompressedForSkip() const; + +public: + /** + * The number of cels in the original picture resource. + */ + uint8 _celCount; + + /** + * The position of this cel relative to the top-left + * corner of the picture. + */ + Common::Point _relativePosition; + + /** + * The z-buffer priority for this cel. Higher prorities + * are drawn on top of lower priorities. + */ + int16 _priority; + + CelObjPic(GuiResourceId pictureId, int16 celNo); + virtual ~CelObjPic() override {}; + + using CelObj::draw; + virtual void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) override; + + virtual CelObjPic *duplicate() const override; + virtual byte *getResPointer() const override; +}; + +#pragma mark - +#pragma mark CelObjMem + +/** + * A CelObjMem is the drawing primitive for arbitrary + * bitmaps generated in memory. Generated bitmaps in SCI32 + * include text & vector drawings and per-pixel screen + * transitions like dissolves. + */ +class CelObjMem : public CelObj { +public: + CelObjMem(reg_t bitmap); + virtual ~CelObjMem() override {}; + + virtual CelObjMem *duplicate() const override; + virtual byte *getResPointer() const override; +}; + +#pragma mark - +#pragma mark CelObjColor + +/** + * A CelObjColor is the drawing primitive for fast, + * low-memory, flat color fills. + */ +class CelObjColor : public CelObj { +public: + CelObjColor(uint8 color, int16 width, int16 height); + virtual ~CelObjColor() override {}; + + using CelObj::draw; + /** + * Block fills the target buffer with the cel color. + */ + void draw(Buffer &target, const Common::Rect &targetRect) const; + virtual void draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, const bool mirrorX) override; + virtual void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) override; + + virtual CelObjColor *duplicate() const override; + virtual byte *getResPointer() const override; +}; +} // End of namespace Sci + +#endif diff --git a/engines/sci/graphics/compare.cpp b/engines/sci/graphics/compare.cpp index 716a366b7c..729eeeaf81 100644 --- a/engines/sci/graphics/compare.cpp +++ b/engines/sci/graphics/compare.cpp @@ -67,7 +67,7 @@ uint16 GfxCompare::isOnControl(uint16 screenMask, const Common::Rect &rect) { return result; } -reg_t GfxCompare::canBeHereCheckRectList(reg_t checkObject, const Common::Rect &checkRect, List *list) { +reg_t GfxCompare::canBeHereCheckRectList(const reg_t checkObject, const Common::Rect &checkRect, const List *list, const uint16 signalFlags) const { reg_t curAddress = list->first; Node *curNode = _segMan->lookupNode(curAddress); reg_t curObject; @@ -78,7 +78,7 @@ reg_t GfxCompare::canBeHereCheckRectList(reg_t checkObject, const Common::Rect & curObject = curNode->value; if (curObject != checkObject) { signal = readSelectorValue(_segMan, curObject, SELECTOR(signal)); - if (!(signal & (kSignalIgnoreActor | kSignalRemoveView | kSignalNoUpdate))) { + if (!(signal & signalFlags)) { curRect.left = readSelectorValue(_segMan, curObject, SELECTOR(brLeft)); curRect.top = readSelectorValue(_segMan, curObject, SELECTOR(brTop)); curRect.right = readSelectorValue(_segMan, curObject, SELECTOR(brRight)); @@ -112,11 +112,6 @@ void GfxCompare::kernelSetNowSeen(reg_t objectReference) { GfxView *view = NULL; Common::Rect celRect(0, 0); GuiResourceId viewId = (GuiResourceId)readSelectorValue(_segMan, objectReference, SELECTOR(view)); - - // HACK: Ignore invalid views for now (perhaps unimplemented text views?) - if (viewId == 0xFFFF) // invalid view - return; - int16 loopNo = readSelectorValue(_segMan, objectReference, SELECTOR(loop)); int16 celNo = readSelectorValue(_segMan, objectReference, SELECTOR(cel)); int16 x = (int16)readSelectorValue(_segMan, objectReference, SELECTOR(x)); @@ -126,26 +121,8 @@ void GfxCompare::kernelSetNowSeen(reg_t objectReference) { z = (int16)readSelectorValue(_segMan, objectReference, SELECTOR(z)); view = _cache->getView(viewId); - -#ifdef ENABLE_SCI32 - if (view->isSci2Hires()) - view->adjustToUpscaledCoordinates(y, x); - else if ((getSciVersion() >= SCI_VERSION_2_1_EARLY) && (getSciVersion() <= SCI_VERSION_2_1_LATE)) - _coordAdjuster->fromScriptToDisplay(y, x); -#endif - view->getCelRect(loopNo, celNo, x, y, z, celRect); -#ifdef ENABLE_SCI32 - if (view->isSci2Hires()) { - view->adjustBackUpscaledCoordinates(celRect.top, celRect.left); - view->adjustBackUpscaledCoordinates(celRect.bottom, celRect.right); - } else if ((getSciVersion() >= SCI_VERSION_2_1_EARLY) && (getSciVersion() <= SCI_VERSION_2_1_LATE)) { - _coordAdjuster->fromDisplayToScript(celRect.top, celRect.left); - _coordAdjuster->fromDisplayToScript(celRect.bottom, celRect.right); - } -#endif - if (lookupSelector(_segMan, objectReference, SELECTOR(nsTop), NULL, NULL) == kSelectorVariable) { setNSRect(objectReference, celRect); } @@ -153,32 +130,57 @@ void GfxCompare::kernelSetNowSeen(reg_t objectReference) { reg_t GfxCompare::kernelCanBeHere(reg_t curObject, reg_t listReference) { Common::Rect checkRect; - Common::Rect adjustedRect; - uint16 signal, controlMask; uint16 result; checkRect.left = readSelectorValue(_segMan, curObject, SELECTOR(brLeft)); checkRect.top = readSelectorValue(_segMan, curObject, SELECTOR(brTop)); checkRect.right = readSelectorValue(_segMan, curObject, SELECTOR(brRight)); checkRect.bottom = readSelectorValue(_segMan, curObject, SELECTOR(brBottom)); + uint16 signal = readSelectorValue(_segMan, curObject, SELECTOR(signal)); if (!checkRect.isValidRect()) { // can occur in Iceman and Mother Goose - HACK? TODO: is this really occuring in sierra sci? check this warning("kCan(t)BeHere - invalid rect %d, %d -> %d, %d", checkRect.left, checkRect.top, checkRect.right, checkRect.bottom); return NULL_REG; // this means "can be here" } - adjustedRect = _coordAdjuster->onControl(checkRect); - - signal = readSelectorValue(_segMan, curObject, SELECTOR(signal)); - controlMask = readSelectorValue(_segMan, curObject, SELECTOR(illegalBits)); + Common::Rect adjustedRect = _coordAdjuster->onControl(checkRect); + uint16 controlMask = readSelectorValue(_segMan, curObject, SELECTOR(illegalBits)); result = isOnControl(GFX_SCREEN_MASK_CONTROL, adjustedRect) & controlMask; if ((!result) && (signal & (kSignalIgnoreActor | kSignalRemoveView)) == 0) { List *list = _segMan->lookupList(listReference); if (!list) error("kCanBeHere called with non-list as parameter"); - return canBeHereCheckRectList(curObject, checkRect, list); + return canBeHereCheckRectList(curObject, checkRect, list, kSignalIgnoreActor | kSignalRemoveView | kSignalNoUpdate); } + + return make_reg(0, result); +} + +reg_t GfxCompare::kernelCantBeHere32(const reg_t curObject, const reg_t listReference) const { + // Most of SCI32 graphics code converts rects from the VM to exclusive + // rects before operating on them, but this call leverages SCI16 engine + // code that operates on inclusive rects, so the rect's bottom-right + // point is not modified like in other SCI32 kernel calls + Common::Rect checkRect( + readSelectorValue(_segMan, curObject, SELECTOR(brLeft)), + readSelectorValue(_segMan, curObject, SELECTOR(brTop)), + readSelectorValue(_segMan, curObject, SELECTOR(brRight)), + readSelectorValue(_segMan, curObject, SELECTOR(brBottom)) + ); + + uint16 result = 0; + uint16 signal = readSelectorValue(_segMan, curObject, SELECTOR(signal)); + const uint16 signalFlags = kSignalIgnoreActor | kSignalHidden; + + if ((signal & signalFlags) == 0) { + List *list = _segMan->lookupList(listReference); + if (!list) { + error("kCantBeHere called with non-list as parameter"); + } + result = !canBeHereCheckRectList(curObject, checkRect, list, signalFlags).isNull(); + } + return make_reg(0, result); } @@ -201,15 +203,9 @@ void GfxCompare::kernelBaseSetter(reg_t object) { GuiResourceId viewId = readSelectorValue(_segMan, object, SELECTOR(view)); int16 loopNo = readSelectorValue(_segMan, object, SELECTOR(loop)); int16 celNo = readSelectorValue(_segMan, object, SELECTOR(cel)); - - // HACK: Ignore invalid views for now (perhaps unimplemented text views?) - if (viewId == 0xFFFF) // invalid view - return; - uint16 scaleSignal = 0; - if (getSciVersion() >= SCI_VERSION_1_1) { + if (getSciVersion() >= SCI_VERSION_1_1) scaleSignal = readSelectorValue(_segMan, object, SELECTOR(scaleSignal)); - } Common::Rect celRect; diff --git a/engines/sci/graphics/compare.h b/engines/sci/graphics/compare.h index 88b44aeeb1..c7005980d0 100644 --- a/engines/sci/graphics/compare.h +++ b/engines/sci/graphics/compare.h @@ -40,6 +40,7 @@ public: uint16 kernelOnControl(byte screenMask, const Common::Rect &rect); void kernelSetNowSeen(reg_t objectReference); reg_t kernelCanBeHere(reg_t curObject, reg_t listReference); + reg_t kernelCantBeHere32(const reg_t curObject, const reg_t listReference) const; bool kernelIsItSkip(GuiResourceId viewId, int16 loopNo, int16 celNo, Common::Point position); void kernelBaseSetter(reg_t object); Common::Rect getNSRect(reg_t object); @@ -58,7 +59,7 @@ private: * *different* from checkObject, has a brRect which is contained inside * checkRect. */ - reg_t canBeHereCheckRectList(reg_t checkObject, const Common::Rect &checkRect, List *list); + reg_t canBeHereCheckRectList(const reg_t checkObject, const Common::Rect &checkRect, const List *list, const uint16 signalFlags) const; }; } // End of namespace Sci diff --git a/engines/sci/graphics/controls16.cpp b/engines/sci/graphics/controls16.cpp index e2e250cf9d..b4bd92699a 100644 --- a/engines/sci/graphics/controls16.cpp +++ b/engines/sci/graphics/controls16.cpp @@ -151,7 +151,7 @@ void GfxControls16::kernelTexteditChange(reg_t controlObject, reg_t eventObject) Common::Rect rect; if (textReference.isNull()) - error("kEditControl called on object that doesnt have a text reference"); + error("kEditControl called on object that doesn't have a text reference"); text = _segMan->getString(textReference); uint16 oldCursorPos = cursorPos; diff --git a/engines/sci/graphics/controls32.cpp b/engines/sci/graphics/controls32.cpp index 1bd497ce98..a877d8c276 100644 --- a/engines/sci/graphics/controls32.cpp +++ b/engines/sci/graphics/controls32.cpp @@ -23,9 +23,11 @@ #include "common/system.h" #include "sci/sci.h" +#include "sci/console.h" #include "sci/event.h" #include "sci/engine/kernel.h" #include "sci/engine/seg_manager.h" +#include "sci/engine/state.h" #include "sci/graphics/cache.h" #include "sci/graphics/compare.h" #include "sci/graphics/controls32.h" @@ -34,171 +36,321 @@ #include "sci/graphics/text32.h" namespace Sci { +GfxControls32::GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *text) : + _segMan(segMan), + _gfxCache(cache), + _gfxText32(text), + _overwriteMode(false), + _nextCursorFlashTick(0) {} -GfxControls32::GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *text) - : _segMan(segMan), _cache(cache), _text(text) { -} +reg_t GfxControls32::kernelEditText(const reg_t controlObject) { + SegManager *segMan = _segMan; -GfxControls32::~GfxControls32() { -} + TextEditor editor; + reg_t textObject = readSelector(_segMan, controlObject, SELECTOR(text)); + editor.text = _segMan->getString(textObject); + editor.foreColor = readSelectorValue(_segMan, controlObject, SELECTOR(fore)); + editor.backColor = readSelectorValue(_segMan, controlObject, SELECTOR(back)); + editor.skipColor = readSelectorValue(_segMan, controlObject, SELECTOR(skip)); + editor.fontId = readSelectorValue(_segMan, controlObject, SELECTOR(font)); + editor.maxLength = readSelectorValue(_segMan, controlObject, SELECTOR(width)); + editor.bitmap = readSelector(_segMan, controlObject, SELECTOR(bitmap)); + editor.cursorCharPosition = 0; + editor.cursorIsDrawn = false; + editor.borderColor = readSelectorValue(_segMan, controlObject, SELECTOR(borderColor)); -void GfxControls32::kernelTexteditChange(reg_t controlObject) { - SciEvent curEvent; - uint16 maxChars = 40; //readSelectorValue(_segMan, controlObject, SELECTOR(max)); // TODO - reg_t textReference = readSelector(_segMan, controlObject, SELECTOR(text)); - GfxFont *font = _cache->getFont(readSelectorValue(_segMan, controlObject, SELECTOR(font))); - Common::String text; - uint16 textSize; - bool textChanged = false; - bool textAddChar = false; - Common::Rect rect; + reg_t titleObject = readSelector(_segMan, controlObject, SELECTOR(title)); + + int16 titleHeight = 0; + GuiResourceId titleFontId = readSelectorValue(_segMan, controlObject, SELECTOR(titleFont)); + if (!titleObject.isNull()) { + GfxFont *titleFont = _gfxCache->getFont(titleFontId); + titleHeight += _gfxText32->scaleUpHeight(titleFont->getHeight()) + 1; + if (editor.borderColor != -1) { + titleHeight += 2; + } + } - if (textReference.isNull()) - error("kEditControl called on object that doesnt have a text reference"); - text = _segMan->getString(textReference); + int16 width = 0; + int16 height = titleHeight; - // TODO: Finish this - warning("kEditText ('%s')", text.c_str()); - return; + GfxFont *editorFont = _gfxCache->getFont(editor.fontId); + height += _gfxText32->scaleUpHeight(editorFont->getHeight()) + 1; + _gfxText32->setFont(editor.fontId); + int16 emSize = _gfxText32->getCharWidth('M', true); + width += editor.maxLength * emSize + 1; + if (editor.borderColor != -1) { + width += 4; + height += 2; + } - uint16 cursorPos = 0; - //uint16 oldCursorPos = cursorPos; - bool captureEvents = true; - EventManager* eventMan = g_sci->getEventManager(); + Common::Rect editorPlaneRect(width, height); + editorPlaneRect.translate(readSelectorValue(_segMan, controlObject, SELECTOR(x)), readSelectorValue(_segMan, controlObject, SELECTOR(y))); - while (captureEvents) { - curEvent = g_sci->getEventManager()->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_PEEK); + reg_t planeObj = readSelector(_segMan, controlObject, SELECTOR(plane)); + Plane *sourcePlane = g_sci->_gfxFrameout->getVisiblePlanes().findByObject(planeObj); + if (sourcePlane == nullptr) { + error("Could not find plane %04x:%04x", PRINT_REG(planeObj)); + } + editorPlaneRect.translate(sourcePlane->_gameRect.left, sourcePlane->_gameRect.top); - if (curEvent.type == SCI_EVENT_NONE) { - eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event + editor.textRect = Common::Rect(2, titleHeight + 2, width - 1, height - 1); + editor.width = width; + + if (editor.bitmap.isNull()) { + TextAlign alignment = (TextAlign)readSelectorValue(_segMan, controlObject, SELECTOR(mode)); + + if (titleObject.isNull()) { + bool dimmed = readSelectorValue(_segMan, controlObject, SELECTOR(dimmed)); + editor.bitmap = _gfxText32->createFontBitmap(width, height, editor.textRect, editor.text, editor.foreColor, editor.backColor, editor.skipColor, editor.fontId, alignment, editor.borderColor, dimmed, true); } else { - textSize = text.size(); + Common::String title = _segMan->getString(titleObject); + int16 titleBackColor = readSelectorValue(_segMan, controlObject, SELECTOR(titleBack)); + int16 titleForeColor = readSelectorValue(_segMan, controlObject, SELECTOR(titleFore)); + editor.bitmap = _gfxText32->createTitledBitmap(width, height, editor.textRect, editor.text, editor.foreColor, editor.backColor, editor.skipColor, editor.fontId, alignment, editor.borderColor, title, titleForeColor, titleBackColor, titleFontId, true); + } + } + + drawCursor(editor); - switch (curEvent.type) { - case SCI_EVENT_MOUSE_PRESS: - // TODO: Implement mouse support for cursor change + Plane *plane = new Plane(editorPlaneRect, kPlanePicTransparent); + plane->changePic(); + g_sci->_gfxFrameout->addPlane(*plane); + + CelInfo32 celInfo; + celInfo.type = kCelTypeMem; + celInfo.bitmap = editor.bitmap; + + ScreenItem *screenItem = new ScreenItem(plane->_object, celInfo, Common::Point(), ScaleInfo()); + plane->_screenItemList.add(screenItem); + + // frameOut must be called after the screen item is + // created, and before it is updated at the end of the + // event loop, otherwise it has both created and updated + // flags set which crashes the engine (it runs updates + // before creations) + g_sci->_gfxFrameout->frameOut(true); + + EventManager *eventManager = g_sci->getEventManager(); + bool clearTextOnInput = true; + bool textChanged = false; + for (;;) { + // We peek here because the last event needs to be allowed to + // dispatch a second time to the normal event handling system. + // In the actual engine, the event is always consumed and then + // the last event just gets posted back to the event manager for + // reprocessing, but instead, we only remove the event from the + // queue *after* we have determined it is not a defocusing event + const SciEvent event = eventManager->getSciEvent(SCI_EVENT_ANY | SCI_EVENT_PEEK); + + bool focused = true; + // Original engine did not have a QUIT event but we have to handle it + if (event.type == SCI_EVENT_QUIT) { + focused = false; + break; + } else if (event.type == SCI_EVENT_MOUSE_PRESS && !editorPlaneRect.contains(event.mousePosSci)) { + focused = false; + } else if (event.type == SCI_EVENT_KEYBOARD) { + switch (event.character) { + case SCI_KEY_ESC: + case SCI_KEY_UP: + case SCI_KEY_DOWN: + case SCI_KEY_TAB: + case SCI_KEY_SHIFT_TAB: + case SCI_KEY_ENTER: + focused = false; break; - case SCI_EVENT_KEYBOARD: - switch (curEvent.character) { - case SCI_KEY_BACKSPACE: - if (cursorPos > 0) { - cursorPos--; text.deleteChar(cursorPos); - textChanged = true; - } - eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event - break; - case SCI_KEY_DELETE: - if (cursorPos < textSize) { - text.deleteChar(cursorPos); - textChanged = true; - } - eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event - break; - case SCI_KEY_HOME: // HOME - cursorPos = 0; textChanged = true; - eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event - break; - case SCI_KEY_END: // END - cursorPos = textSize; textChanged = true; - eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event - break; - case SCI_KEY_LEFT: // LEFT - if (cursorPos > 0) { - cursorPos--; textChanged = true; - } - eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event - break; - case SCI_KEY_RIGHT: // RIGHT - if (cursorPos + 1 <= textSize) { - cursorPos++; textChanged = true; - } - eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event - break; - case 3: // returned in SCI1 late and newer when Control - C is pressed - if (curEvent.modifiers & SCI_KEYMOD_CTRL) { - // Control-C erases the whole line - cursorPos = 0; text.clear(); - textChanged = true; + } + } + + if (!focused) { + break; + } + + // Consume the event now that we know it is not one of the + // defocusing events above + eventManager->getSciEvent(SCI_EVENT_ANY); + + // NOTE: In the original engine, the font and bitmap were + // reset here on each iteration through the loop, but it + // doesn't seem like this should be necessary since + // control is not yielded back to the VM until input is + // received, which means there is nothing that could modify + // the GfxText32's state with a different font in the + // meantime + + bool shouldDeleteChar = false; + bool shouldRedrawText = false; + uint16 lastCursorPosition = editor.cursorCharPosition; + if (event.type == SCI_EVENT_KEYBOARD) { + switch (event.character) { + case SCI_KEY_LEFT: + clearTextOnInput = false; + if (editor.cursorCharPosition > 0) { + --editor.cursorCharPosition; + } + break; + + case SCI_KEY_RIGHT: + clearTextOnInput = false; + if (editor.cursorCharPosition < editor.text.size()) { + ++editor.cursorCharPosition; + } + break; + + case SCI_KEY_HOME: + clearTextOnInput = false; + editor.cursorCharPosition = 0; + break; + + case SCI_KEY_END: + clearTextOnInput = false; + editor.cursorCharPosition = editor.text.size(); + break; + + case SCI_KEY_INSERT: + clearTextOnInput = false; + // Redrawing also changes the cursor rect to + // reflect the new insertion mode + shouldRedrawText = true; + _overwriteMode = !_overwriteMode; + break; + + case SCI_KEY_DELETE: + clearTextOnInput = false; + if (editor.cursorCharPosition < editor.text.size()) { + shouldDeleteChar = true; + } + break; + + case SCI_KEY_BACKSPACE: + clearTextOnInput = false; + shouldDeleteChar = true; + if (editor.cursorCharPosition > 0) { + --editor.cursorCharPosition; + } + break; + + case SCI_KEY_ETX: + editor.text.clear(); + editor.cursorCharPosition = 0; + shouldRedrawText = true; + break; + + default: { + if (event.character >= 20 && event.character < 257) { + if (clearTextOnInput) { + clearTextOnInput = false; + editor.text.clear(); } - eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event - break; - case SCI_KEY_UP: - case SCI_KEY_DOWN: - case SCI_KEY_ENTER: - case SCI_KEY_ESC: - case SCI_KEY_TAB: - case SCI_KEY_SHIFT_TAB: - captureEvents = false; - break; - default: - if ((curEvent.modifiers & SCI_KEYMOD_CTRL) && curEvent.character == 'c') { - // Control-C in earlier SCI games (SCI0 - SCI1 middle) - // Control-C erases the whole line - cursorPos = 0; text.clear(); - textChanged = true; - } else if (curEvent.character > 31 && curEvent.character < 256 && textSize < maxChars) { - // insert pressed character - textAddChar = true; - textChanged = true; + + if ( + (_overwriteMode && editor.cursorCharPosition < editor.maxLength) || + (editor.text.size() < editor.maxLength && _gfxText32->getCharWidth(event.character, true) + _gfxText32->getStringWidth(editor.text) < editor.textRect.width()) + ) { + if (_overwriteMode && editor.cursorCharPosition < editor.text.size()) { + editor.text.setChar(event.character, editor.cursorCharPosition); + } else { + editor.text.insertChar(event.character, editor.cursorCharPosition); + } + + ++editor.cursorCharPosition; + shouldRedrawText = true; } - eventMan->getSciEvent(SCI_EVENT_KEYBOARD); // consume the event - break; } - break; + } } } - if (textChanged) { - rect = g_sci->_gfxCompare->getNSRect(controlObject); + if (shouldDeleteChar) { + shouldRedrawText = true; + if (editor.cursorCharPosition < editor.text.size()) { + editor.text.deleteChar(editor.cursorCharPosition); + } + } - if (textAddChar) { - const char *textPtr = text.c_str(); + if (shouldRedrawText) { + eraseCursor(editor); + _gfxText32->erase(editor.textRect, true); + _gfxText32->drawTextBox(editor.text); + drawCursor(editor); + textChanged = true; + screenItem->_updated = g_sci->_gfxFrameout->getScreenCount(); + } else if (editor.cursorCharPosition != lastCursorPosition) { + eraseCursor(editor); + drawCursor(editor); + screenItem->_updated = g_sci->_gfxFrameout->getScreenCount(); + } else { + flashCursor(editor); + screenItem->_updated = g_sci->_gfxFrameout->getScreenCount(); + } - // We check if we are really able to add the new char - uint16 textWidth = 0; - while (*textPtr) - textWidth += font->getCharWidth((byte)*textPtr++); - textWidth += font->getCharWidth(curEvent.character); + g_sci->_gfxFrameout->frameOut(true); + g_sci->getSciDebugger()->onFrame(); + g_sci->getEngineState()->speedThrottler(16); + g_sci->getEngineState()->_throttleTrigger = true; + } - // Does it fit? - if (textWidth >= rect.width()) { - return; - } + g_sci->_gfxFrameout->deletePlane(*plane); + if (readSelectorValue(segMan, controlObject, SELECTOR(frameOut))) { + g_sci->_gfxFrameout->frameOut(true); + } - text.insertChar(curEvent.character, cursorPos++); + _segMan->freeHunkEntry(editor.bitmap); - // Note: the following checkAltInput call might make the text - // too wide to fit, but SSCI fails to check that too. - } + if (textChanged) { + editor.text.trim(); + SciString *string = _segMan->lookupString(textObject); + string->fromString(editor.text); + } + + return make_reg(0, textChanged); +} - reg_t hunkId = readSelector(_segMan, controlObject, SELECTOR(bitmap)); - Common::Rect nsRect = g_sci->_gfxCompare->getNSRect(controlObject); - //texteditCursorErase(); // TODO: Cursor +void GfxControls32::drawCursor(TextEditor &editor) { + if (!editor.cursorIsDrawn) { + editor.cursorRect.left = editor.textRect.left + _gfxText32->getTextWidth(editor.text, 0, editor.cursorCharPosition); - // Write back string - _segMan->strcpy(textReference, text.c_str()); - // Modify the buffer and show it - _text->createTextBitmap(controlObject, 0, 0, hunkId); + const int16 scaledFontHeight = _gfxText32->scaleUpHeight(_gfxText32->_font->getHeight()); - _text->drawTextBitmap(0, 0, nsRect, controlObject); - //texteditCursorDraw(rect, text.c_str(), cursorPos); // TODO: Cursor - g_system->updateScreen(); + // NOTE: The original code branched on borderColor here but + // the two branches appeared to be identical, differing only + // because the compiler decided to be differently clever + // when optimising multiplication in each branch + if (_overwriteMode) { + editor.cursorRect.top = editor.textRect.top; + editor.cursorRect.setHeight(scaledFontHeight); } else { - // TODO: Cursor - /* - if (g_system->getMillis() >= _texteditBlinkTime) { - _paint16->invertRect(_texteditCursorRect); - _paint16->bitsShow(_texteditCursorRect); - _texteditCursorVisible = !_texteditCursorVisible; - texteditSetBlinkTime(); - } - */ + editor.cursorRect.top = editor.textRect.top + scaledFontHeight - 1; + editor.cursorRect.setHeight(1); } - textAddChar = false; - textChanged = false; - g_sci->sleep(10); - } // while + const char currentChar = editor.cursorCharPosition < editor.text.size() ? editor.text[editor.cursorCharPosition] : ' '; + editor.cursorRect.setWidth(_gfxText32->getCharWidth(currentChar, true)); + + _gfxText32->invertRect(editor.bitmap, editor.width, editor.cursorRect, editor.foreColor, editor.backColor, true); + + editor.cursorIsDrawn = true; + } + + _nextCursorFlashTick = g_sci->getTickCount() + 30; +} + +void GfxControls32::eraseCursor(TextEditor &editor) { + if (editor.cursorIsDrawn) { + _gfxText32->invertRect(editor.bitmap, editor.width, editor.cursorRect, editor.foreColor, editor.backColor, true); + editor.cursorIsDrawn = false; + } + + _nextCursorFlashTick = g_sci->getTickCount() + 30; } +void GfxControls32::flashCursor(TextEditor &editor) { + if (g_sci->getTickCount() > _nextCursorFlashTick) { + _gfxText32->invertRect(editor.bitmap, editor.width, editor.cursorRect, editor.foreColor, editor.backColor, true); + + editor.cursorIsDrawn = !editor.cursorIsDrawn; + _nextCursorFlashTick = g_sci->getTickCount() + 30; + } +} } // End of namespace Sci diff --git a/engines/sci/graphics/controls32.h b/engines/sci/graphics/controls32.h index 5af7c20f16..1bb7679ddd 100644 --- a/engines/sci/graphics/controls32.h +++ b/engines/sci/graphics/controls32.h @@ -29,20 +29,95 @@ class GfxCache; class GfxScreen; class GfxText32; +struct TextEditor { + /** + * The bitmap where the editor is rendered. + */ + reg_t bitmap; + + /** + * The width of the editor, in bitmap pixels. + */ + int16 width; + + /** + * The text in the editor. + */ + Common::String text; + + /** + * The rect where text should be drawn into the editor, + * in bitmap pixels. + */ + Common::Rect textRect; + + /** + * The color of the border. -1 indicates no border. + */ + int16 borderColor; + + /** + * The text color. + */ + uint8 foreColor; + + /** + * The background color. + */ + uint8 backColor; + + /** + * The transparent color. + */ + uint8 skipColor; + + /** + * The font used to render the text in the editor. + */ + GuiResourceId fontId; + + /** + * The current position of the cursor within the editor. + */ + uint16 cursorCharPosition; + + /** + * Whether or not the cursor is currently drawn to the + * screen. + */ + bool cursorIsDrawn; + + /** + * The rectangle for drawing the input cursor, in bitmap + * pixels. + */ + Common::Rect cursorRect; + + /** + * The maximum allowed text length, in characters. + */ + uint16 maxLength; +}; + /** * Controls class, handles drawing of controls in SCI32 (SCI2, SCI2.1, SCI3) games */ class GfxControls32 { public: GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *text); - ~GfxControls32(); - void kernelTexteditChange(reg_t controlObject); + reg_t kernelEditText(const reg_t controlObject); private: SegManager *_segMan; - GfxCache *_cache; - GfxText32 *_text; + GfxCache *_gfxCache; + GfxText32 *_gfxText32; + + bool _overwriteMode; + uint32 _nextCursorFlashTick; + void drawCursor(TextEditor &editor); + void eraseCursor(TextEditor &editor); + void flashCursor(TextEditor &editor); }; } // End of namespace Sci diff --git a/engines/sci/graphics/cursor.cpp b/engines/sci/graphics/cursor.cpp index 1a58de073c..f5dd473959 100644 --- a/engines/sci/graphics/cursor.cpp +++ b/engines/sci/graphics/cursor.cpp @@ -336,6 +336,9 @@ void GfxCursor::setPosition(Common::Point pos) { && ((workaround->newPositionX == pos.x) && (workaround->newPositionY == pos.y))) { EngineState *s = g_sci->getEngineState(); s->_cursorWorkaroundActive = true; + // At least on OpenPandora it seems that the cursor is actually set, but a bit afterwards + // touch screen controls will overwrite the position. More information see kGetEvent in kevent.cpp. + s->_cursorWorkaroundPosCount = 5; // should be enough for OpenPandora s->_cursorWorkaroundPoint = pos; s->_cursorWorkaroundRect = Common::Rect(workaround->rectLeft, workaround->rectTop, workaround->rectRight, workaround->rectBottom); return; @@ -453,6 +456,15 @@ void GfxCursor::kernelClearZoomZone() { void GfxCursor::kernelSetZoomZone(byte multiplier, Common::Rect zone, GuiResourceId viewNum, int loopNum, int celNum, GuiResourceId picNum, byte zoomColor) { kernelClearZoomZone(); + // This function is a stub in the Mac version of Freddy Pharkas. + // This function was only used in two games (LB2 and Pharkas), but there + // was no version of LB2 for the Macintosh platform. + // CHECKME: This wasn't verified against disassembly, one might want + // to check against it, in case there's some leftover code in the stubbed + // function (although it does seem that this was completely removed). + if (g_sci->getPlatform() == Common::kPlatformMacintosh) + return; + _zoomMultiplier = multiplier; if (_zoomMultiplier != 1 && _zoomMultiplier != 2 && _zoomMultiplier != 4) diff --git a/engines/sci/graphics/cursor.h b/engines/sci/graphics/cursor.h index c2d7998eb3..8d125c45b3 100644 --- a/engines/sci/graphics/cursor.h +++ b/engines/sci/graphics/cursor.h @@ -77,8 +77,18 @@ public: */ void kernelSetMoveZone(Common::Rect zone); - void kernelClearZoomZone(); + /** + * Creates a dynamic zoom cursor, that is used to zoom on specific parts of the screen, + * using a separate larger picture. This was only used by two SCI1.1 games, Laura Bow 2 + * (for examining the glyphs), and Freddy Pharkas (for examining the prescription with + * the whisky glass). + * + * In the Mac version of Freddy Pharkas, this was removed completely, and the scene has + * been redesigned to work without this functionality. There was no version of LB2 for + * the Macintosh platform. + */ void kernelSetZoomZone(byte multiplier, Common::Rect zone, GuiResourceId viewNum, int loopNum, int celNum, GuiResourceId picNum, byte zoomColor); + void kernelClearZoomZone(); void kernelSetPos(Common::Point pos); void kernelMoveCursor(Common::Point pos); diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp index cb81fe8d61..6454a1eb32 100644 --- a/engines/sci/graphics/frameout.cpp +++ b/engines/sci/graphics/frameout.cpp @@ -21,6 +21,7 @@ */ #include "common/algorithm.h" +#include "common/config-manager.h" #include "common/events.h" #include "common/keyboard.h" #include "common/list.h" @@ -46,964 +47,1484 @@ #include "sci/graphics/paint32.h" #include "sci/graphics/palette32.h" #include "sci/graphics/picture.h" +#include "sci/graphics/remap.h" #include "sci/graphics/text32.h" +#include "sci/graphics/plane32.h" +#include "sci/graphics/screen_item32.h" #include "sci/graphics/frameout.h" #include "sci/video/robot_decoder.h" namespace Sci { -// TODO/FIXME: This is all guesswork - -enum SciSpeciaPlanelPictureCodes { - kPlaneTranslucent = 0xfffe, // -2 - kPlanePlainColored = 0xffff // -1 +static int dissolveSequences[2][20] = { + /* SCI2.1early- */ { 3, 6, 12, 20, 48, 96, 184, 272, 576, 1280, 3232, 6912, 13568, 24576, 46080 }, + /* SCI2.1mid+ */ { 0, 0, 3, 6, 12, 20, 48, 96, 184, 272, 576, 1280, 3232, 6912, 13568, 24576, 46080, 73728, 132096, 466944 } +}; +static int16 divisionsDefaults[2][16] = { + /* SCI2.1early- */ { 1, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 40, 40, 101, 101 }, + /* SCI2.1mid+ */ { 1, 20, 20, 20, 20, 10, 10, 10, 10, 20, 20, 6, 10, 101, 101, 2 } +}; +static int16 unknownCDefaults[2][16] = { + /* SCI2.1early- */ { 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 0, 0, 0, 0 }, + /* SCI2.1mid+ */ { 0, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 0, 0, 7, 7, 0 } }; -GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette32 *palette, GfxPaint32 *paint32) - : _segMan(segMan), _resMan(resMan), _cache(cache), _screen(screen), _palette(palette), _paint32(paint32), _isHiRes(false) { - - _coordAdjuster = (GfxCoordAdjuster32 *)coordAdjuster; - _curScrollText = -1; - _showScrollText = false; - _maxScrollTexts = 0; +GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette32 *palette, GfxPaint32 *paint32) : + _isHiRes(false), + _cache(cache), + _palette(palette), + _resMan(resMan), + _screen(screen), + _segMan(segMan), + _paint32(paint32), + _showStyles(nullptr), + // TODO: Stop using _gfxScreen + _currentBuffer(screen->getDisplayWidth(), screen->getDisplayHeight(), nullptr), + _remapOccurred(false), + _frameNowVisible(false), + _screenRect(screen->getDisplayWidth(), screen->getDisplayHeight()), + _overdrawThreshold(0), + _palMorphIsOn(false) { + + _currentBuffer.setPixels(calloc(1, screen->getDisplayWidth() * screen->getDisplayHeight())); + + for (int i = 0; i < 236; i += 2) { + _styleRanges[i] = 0; + _styleRanges[i + 1] = -1; + } + for (int i = 236; i < ARRAYSIZE(_styleRanges); ++i) { + _styleRanges[i] = 0; + } // TODO: Make hires detection work uniformly across all SCI engine // versions (this flag is normally passed by SCI::MakeGraphicsMgr - // to the GraphicsMgr constructor depending upon video configuration) + // to the GraphicsMgr constructor depending upon video configuration, + // so should be handled upstream based on game configuration instead + // of here) if (getSciVersion() >= SCI_VERSION_2_1_EARLY && _resMan->detectHires()) { _isHiRes = true; } + + if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) { + _dissolveSequenceSeeds = dissolveSequences[0]; + _defaultDivisions = divisionsDefaults[0]; + _defaultUnknownC = unknownCDefaults[0]; + } else { + _dissolveSequenceSeeds = dissolveSequences[1]; + _defaultDivisions = divisionsDefaults[1]; + _defaultUnknownC = unknownCDefaults[1]; + } + + switch (g_sci->getGameId()) { + case GID_GK2: + case GID_LIGHTHOUSE: + case GID_LSL7: + case GID_PHANTASMAGORIA2: + case GID_PQSWAT: + case GID_TORIN: + case GID_RAMA: + _currentBuffer.scriptWidth = 640; + _currentBuffer.scriptHeight = 480; + break; + default: + // default script width for other games is 320x200 + break; + } + + // TODO: Nothing in the renderer really uses this. Currently, + // the cursor renderer does, and kLocalToGlobal/kGlobalToLocal + // do, but in the real engine (1) the cursor is handled in + // frameOut, and (2) functions do a very simple lookup of the + // plane and arithmetic with the plane's gameRect. In + // principle, CoordAdjuster could be reused for + // convertGameRectToPlaneRect, but it is not super clear yet + // what the benefit would be to do that. + _coordAdjuster = (GfxCoordAdjuster32 *)coordAdjuster; + + // TODO: Script resolution is hard-coded per game; + // also this must be set or else the engine will crash + _coordAdjuster->setScriptsResolution(_currentBuffer.scriptWidth, _currentBuffer.scriptHeight); } GfxFrameout::~GfxFrameout() { clear(); + CelObj::deinit(); + free(_currentBuffer.getPixels()); } +void GfxFrameout::run() { + CelObj::init(); + Plane::init(); + ScreenItem::init(); + + // NOTE: This happens in SCI::InitPlane in the actual engine, + // and is a background fill plane to ensure hidden planes + // (planes with a priority of -1) are never drawn + Plane *initPlane = new Plane(Common::Rect(_currentBuffer.scriptWidth, _currentBuffer.scriptHeight)); + initPlane->_priority = 0; + _planes.add(initPlane); +} + +// SCI32 actually did not clear anything at all it seems on restore. The scripts actually cleared up +// planes + screen items right before restoring. And after restoring they sync'd its internal planes list +// as well. void GfxFrameout::clear() { - deletePlaneItems(NULL_REG); _planes.clear(); - deletePlanePictures(NULL_REG); - clearScrollTexts(); -} - -void GfxFrameout::clearScrollTexts() { - _scrollTexts.clear(); - _curScrollText = -1; -} - -void GfxFrameout::addScrollTextEntry(Common::String &text, reg_t kWindow, uint16 x, uint16 y, bool replace) { - //reg_t bitmapHandle = g_sci->_gfxText32->createScrollTextBitmap(text, kWindow); - // HACK: We set the container dimensions manually - reg_t bitmapHandle = g_sci->_gfxText32->createScrollTextBitmap(text, kWindow, 480, 70); - ScrollTextEntry textEntry; - textEntry.bitmapHandle = bitmapHandle; - textEntry.kWindow = kWindow; - textEntry.x = x; - textEntry.y = y; - if (!replace || _scrollTexts.size() == 0) { - if (_scrollTexts.size() > _maxScrollTexts) { - _scrollTexts.remove_at(0); - _curScrollText--; - } - _scrollTexts.push_back(textEntry); - _curScrollText++; - } else { - _scrollTexts.pop_back(); - _scrollTexts.push_back(textEntry); - } + _visiblePlanes.clear(); + _showList.clear(); } -void GfxFrameout::showCurrentScrollText() { - if (!_showScrollText || _curScrollText < 0) +// This is what Game::restore does, only needed when our ScummVM dialogs are patched in +// It actually does one pass before actual restore deleting screen items + planes +// And after restore it does another pass adding screen items + planes. +// Attention: at least Space Quest 6's option plane seems to stay in memory right from the start and is not re-created. +void GfxFrameout::syncWithScripts(bool addElements) { + EngineState *engineState = g_sci->getEngineState(); + SegManager *segMan = engineState->_segMan; + + // In case original save/restore dialogs are active, don't do anything + if (ConfMan.getBool("originalsaveload")) return; - uint16 size = (uint16)_scrollTexts.size(); - if (size > 0) { - assert(_curScrollText < size); - ScrollTextEntry textEntry = _scrollTexts[_curScrollText]; - g_sci->_gfxText32->drawScrollTextBitmap(textEntry.kWindow, textEntry.bitmapHandle, textEntry.x, textEntry.y); - } -} - -extern void showScummVMDialog(const Common::String &message); - -void GfxFrameout::kernelAddPlane(reg_t object) { - PlaneEntry newPlane; - - if (_planes.empty()) { - // There has to be another way for sierra sci to do this or maybe script resolution is compiled into - // interpreter (TODO) - uint16 scriptWidth = readSelectorValue(_segMan, object, SELECTOR(resX)); - uint16 scriptHeight = readSelectorValue(_segMan, object, SELECTOR(resY)); - - // Phantasmagoria 2 doesn't specify a script width/height - if (g_sci->getGameId() == GID_PHANTASMAGORIA2) { - scriptWidth = 640; - scriptHeight = 480; - } - - assert(scriptWidth > 0 && scriptHeight > 0); - _coordAdjuster->setScriptsResolution(scriptWidth, scriptHeight); - } - - // Import of QfG character files dialog is shown in QFG4. - // Display additional popup information before letting user use it. - // For the SCI0-SCI1.1 version of this, check kDrawControl(). - if (g_sci->inQfGImportRoom() && !strcmp(_segMan->getObjectName(object), "DSPlane")) { - showScummVMDialog("Characters saved inside ScummVM are shown " - "automatically. Character files saved in the original " - "interpreter need to be put inside ScummVM's saved games " - "directory and a prefix needs to be added depending on which " - "game it was saved in: 'qfg1-' for Quest for Glory 1, 'qfg2-' " - "for Quest for Glory 2, 'qfg3-' for Quest for Glory 3. " - "Example: 'qfg2-thief.sav'."); - } - - newPlane.object = object; - newPlane.priority = readSelectorValue(_segMan, object, SELECTOR(priority)); - newPlane.lastPriority = -1; // hidden - newPlane.planeOffsetX = 0; - newPlane.planeOffsetY = 0; - newPlane.pictureId = kPlanePlainColored; - newPlane.planePictureMirrored = false; - newPlane.planeBack = 0; - _planes.push_back(newPlane); - - kernelUpdatePlane(object); -} - -void GfxFrameout::kernelUpdatePlane(reg_t object) { - for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) { - if (it->object == object) { - // Read some information - it->priority = readSelectorValue(_segMan, object, SELECTOR(priority)); - GuiResourceId lastPictureId = it->pictureId; - it->pictureId = readSelectorValue(_segMan, object, SELECTOR(picture)); - if (lastPictureId != it->pictureId) { - // picture got changed, load new picture - deletePlanePictures(object); - // Draw the plane's picture if it's not a translucent/plane colored frame - if ((it->pictureId != kPlanePlainColored) && (it->pictureId != kPlaneTranslucent)) { - // SQ6 gives us a bad picture number for the control menu - if (_resMan->testResource(ResourceId(kResourceTypePic, it->pictureId))) - addPlanePicture(object, it->pictureId, 0); - } - } - it->planeRect.top = readSelectorValue(_segMan, object, SELECTOR(top)); - it->planeRect.left = readSelectorValue(_segMan, object, SELECTOR(left)); - it->planeRect.bottom = readSelectorValue(_segMan, object, SELECTOR(bottom)); - it->planeRect.right = readSelectorValue(_segMan, object, SELECTOR(right)); - - _coordAdjuster->fromScriptToDisplay(it->planeRect.top, it->planeRect.left); - _coordAdjuster->fromScriptToDisplay(it->planeRect.bottom, it->planeRect.right); - - // We get negative left in kq7 in scrolling rooms - if (it->planeRect.left < 0) { - it->planeOffsetX = -it->planeRect.left; - it->planeRect.left = 0; - } else { - it->planeOffsetX = 0; - } + // Get planes list object + reg_t planesListObject = engineState->variables[VAR_GLOBAL][10]; + reg_t planesListElements = readSelector(segMan, planesListObject, SELECTOR(elements)); - if (it->planeRect.top < 0) { - it->planeOffsetY = -it->planeRect.top; - it->planeRect.top = 0; - } else { - it->planeOffsetY = 0; - } + List *planesList = segMan->lookupList(planesListElements); + reg_t planesNodeObject = planesList->first; - // We get bad plane-bottom in sq6 - if (it->planeRect.right > _screen->getWidth()) - it->planeRect.right = _screen->getWidth(); - if (it->planeRect.bottom > _screen->getHeight()) - it->planeRect.bottom = _screen->getHeight(); - - it->planeClipRect = Common::Rect(it->planeRect.width(), it->planeRect.height()); - it->upscaledPlaneRect = it->planeRect; - it->upscaledPlaneClipRect = it->planeClipRect; - if (_screen->getUpscaledHires()) { - _screen->adjustToUpscaledCoordinates(it->upscaledPlaneRect.top, it->upscaledPlaneRect.left); - _screen->adjustToUpscaledCoordinates(it->upscaledPlaneRect.bottom, it->upscaledPlaneRect.right); - _screen->adjustToUpscaledCoordinates(it->upscaledPlaneClipRect.top, it->upscaledPlaneClipRect.left); - _screen->adjustToUpscaledCoordinates(it->upscaledPlaneClipRect.bottom, it->upscaledPlaneClipRect.right); - } + // Go through all elements of planes::elements + while (!planesNodeObject.isNull()) { + Node *planesNode = segMan->lookupNode(planesNodeObject); + reg_t planeObject = planesNode->value; + + if (addElements) { + // Add this plane object + kernelAddPlane(planeObject); + } + + reg_t planeCastsObject = readSelector(segMan, planeObject, SELECTOR(casts)); + reg_t setListElements = readSelector(segMan, planeCastsObject, SELECTOR(elements)); + + // Now go through all elements of plane::casts::elements + List *planeCastsList = segMan->lookupList(setListElements); + reg_t planeCastsNodeObject = planeCastsList->first; + + while (!planeCastsNodeObject.isNull()) { + Node *castsNode = segMan->lookupNode(planeCastsNodeObject); + reg_t castsObject = castsNode->value; + + reg_t castsListElements = readSelector(segMan, castsObject, SELECTOR(elements)); + + List *castsList = segMan->lookupList(castsListElements); + reg_t castNodeObject = castsList->first; - it->planePictureMirrored = readSelectorValue(_segMan, object, SELECTOR(mirrored)); - it->planeBack = readSelectorValue(_segMan, object, SELECTOR(back)); + while (!castNodeObject.isNull()) { + Node *castNode = segMan->lookupNode(castNodeObject); + reg_t castObject = castNode->value; - sortPlanes(); + // read selector "-info-" of this object + // TODO: Seems to have been changed for SCI3 + // Do NOT use getInfoSelector in here. SCI3 games did not use infoToa, but an actual selector. + // Maybe that selector is just a straight copy, but it needs to get verified/checked. + uint16 castInfoSelector = readSelectorValue(segMan, castObject, SELECTOR(_info_)); - // Update the items in the plane - for (FrameoutList::iterator listIterator = _screenItems.begin(); listIterator != _screenItems.end(); listIterator++) { - reg_t itemPlane = readSelector(_segMan, (*listIterator)->object, SELECTOR(plane)); - if (object == itemPlane) { - kernelUpdateScreenItem((*listIterator)->object); + if (castInfoSelector & kInfoFlagViewInserted) { + if (addElements) { + // Flag set, so add this screen item + kernelAddScreenItem(castObject); + } else { + // Flag set, so delete this screen item + kernelDeleteScreenItem(castObject); + } } + + castNodeObject = castNode->succ; } - return; + planeCastsNodeObject = castsNode->succ; + } + + if (!addElements) { + // Delete this plane object + kernelDeletePlane(planeObject); } + + planesNodeObject = planesNode->succ; } - error("kUpdatePlane called on plane that wasn't added before"); } -void GfxFrameout::kernelDeletePlane(reg_t object) { - deletePlaneItems(object); - deletePlanePictures(object); +#pragma mark - +#pragma mark Screen items + +void GfxFrameout::kernelAddScreenItem(const reg_t object) { + const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane)); - for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) { - if (it->object == object) { - _planes.erase(it); - Common::Rect planeRect; - planeRect.top = readSelectorValue(_segMan, object, SELECTOR(top)); - planeRect.left = readSelectorValue(_segMan, object, SELECTOR(left)); - planeRect.bottom = readSelectorValue(_segMan, object, SELECTOR(bottom)); - planeRect.right = readSelectorValue(_segMan, object, SELECTOR(right)); + _segMan->getObject(object)->setInfoSelectorFlag(kInfoFlagViewInserted); + + Plane *plane = _planes.findByObject(planeObject); + if (plane == nullptr) { + error("kAddScreenItem: Plane %04x:%04x not found for screen item %04x:%04x", PRINT_REG(planeObject), PRINT_REG(object)); + } + + ScreenItem *screenItem = plane->_screenItemList.findByObject(object); + if (screenItem != nullptr) { + screenItem->update(object); + } else { + screenItem = new ScreenItem(object); + plane->_screenItemList.add(screenItem); + } +} - _coordAdjuster->fromScriptToDisplay(planeRect.top, planeRect.left); - _coordAdjuster->fromScriptToDisplay(planeRect.bottom, planeRect.right); +void GfxFrameout::kernelUpdateScreenItem(const reg_t object) { + const reg_t magnifierObject = readSelector(_segMan, object, SELECTOR(magnifier)); + if (magnifierObject.isNull()) { + const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane)); + Plane *plane = _planes.findByObject(planeObject); + if (plane == nullptr) { + error("kUpdateScreenItem: Plane %04x:%04x not found for screen item %04x:%04x", PRINT_REG(planeObject), PRINT_REG(object)); + } - // Blackout removed plane rect - _paint32->fillRect(planeRect, 0); - return; + ScreenItem *screenItem = plane->_screenItemList.findByObject(object); + if (screenItem == nullptr) { + error("kUpdateScreenItem: Screen item %04x:%04x not found in plane %04x:%04x", PRINT_REG(object), PRINT_REG(planeObject)); } + + screenItem->update(object); + } else { + error("Magnifier view is not known to be used by any game. Please submit a bug report with details about the game you were playing and what you were doing that triggered this error. Thanks!"); } } -void GfxFrameout::addPlanePicture(reg_t object, GuiResourceId pictureId, uint16 startX, uint16 startY) { - if (pictureId == kPlanePlainColored || pictureId == kPlaneTranslucent) // sanity check +void GfxFrameout::kernelDeleteScreenItem(const reg_t object) { + _segMan->getObject(object)->clearInfoSelectorFlag(kInfoFlagViewInserted); + + const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane)); + Plane *plane = _planes.findByObject(planeObject); + if (plane == nullptr) { + return; + } + + ScreenItem *screenItem = plane->_screenItemList.findByObject(object); + if (screenItem == nullptr) { return; + } - PlanePictureEntry newPicture; - newPicture.object = object; - newPicture.pictureId = pictureId; - newPicture.picture = new GfxPicture(_resMan, _coordAdjuster, 0, _screen, _palette, pictureId, false); - newPicture.startX = startX; - newPicture.startY = startY; - newPicture.pictureCels = 0; - _planePictures.push_back(newPicture); + if (screenItem->_created == 0) { + screenItem->_created = 0; + screenItem->_updated = 0; + screenItem->_deleted = getScreenCount(); + } else { + plane->_screenItemList.erase(screenItem); + plane->_screenItemList.pack(); + } } -void GfxFrameout::deletePlanePictures(reg_t object) { - PlanePictureList::iterator it = _planePictures.begin(); +#pragma mark - +#pragma mark Planes - while (it != _planePictures.end()) { - if (it->object == object || object.isNull()) { - delete it->pictureCels; - delete it->picture; - it = _planePictures.erase(it); - } else { - ++it; - } +void GfxFrameout::kernelAddPlane(const reg_t object) { + Plane *plane = _planes.findByObject(object); + if (plane != nullptr) { + plane->update(object); + updatePlane(*plane); + } else { + plane = new Plane(object); + addPlane(*plane); } } -// Provides the same functionality as kGraph(DrawLine) -reg_t GfxFrameout::addPlaneLine(reg_t object, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control) { - for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) { - if (it->object == object) { - PlaneLineEntry line; - line.hunkId = _segMan->allocateHunkEntry("PlaneLine()", 1); // we basically use this for a unique ID - line.startPoint = startPoint; - line.endPoint = endPoint; - line.color = color; - line.priority = priority; - line.control = control; - it->lines.push_back(line); - return line.hunkId; - } +void GfxFrameout::kernelUpdatePlane(const reg_t object) { + Plane *plane = _planes.findByObject(object); + if (plane == nullptr) { + error("kUpdatePlane: Plane %04x:%04x not found", PRINT_REG(object)); } - return NULL_REG; + plane->update(object); + updatePlane(*plane); } -void GfxFrameout::updatePlaneLine(reg_t object, reg_t hunkId, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control) { - // Check if we're asked to update a line that was never added - if (hunkId.isNull()) - return; +void GfxFrameout::kernelDeletePlane(const reg_t object) { + Plane *plane = _planes.findByObject(object); + if (plane == nullptr) { + error("kDeletePlane: Plane %04x:%04x not found", PRINT_REG(object)); + } - for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) { - if (it->object == object) { - for (PlaneLineList::iterator it2 = it->lines.begin(); it2 != it->lines.end(); ++it2) { - if (it2->hunkId == hunkId) { - it2->startPoint = startPoint; - it2->endPoint = endPoint; - it2->color = color; - it2->priority = priority; - it2->control = control; - return; - } - } - } + if (plane->_created) { + // NOTE: The original engine calls some `AbortPlane` function that + // just ends up doing this anyway so we skip the extra indirection + _planes.erase(plane); + } else { + plane->_created = 0; + plane->_deleted = g_sci->_gfxFrameout->getScreenCount(); } } -void GfxFrameout::deletePlaneLine(reg_t object, reg_t hunkId) { - // Check if we're asked to delete a line that was never added (happens during the intro of LSL6) - if (hunkId.isNull()) - return; +void GfxFrameout::deletePlane(Plane &planeToFind) { + Plane *plane = _planes.findByObject(planeToFind._object); + if (plane == nullptr) { + error("deletePlane: Plane %04x:%04x not found", PRINT_REG(planeToFind._object)); + } - for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) { - if (it->object == object) { - for (PlaneLineList::iterator it2 = it->lines.begin(); it2 != it->lines.end(); ++it2) { - if (it2->hunkId == hunkId) { - _segMan->freeHunkEntry(hunkId); - it2 = it->lines.erase(it2); - return; - } - } - } + if (plane->_created) { + _planes.erase(plane); + } else { + plane->_created = 0; + plane->_moved = 0; + plane->_deleted = getScreenCount(); } } -// Adapted from GfxAnimate::applyGlobalScaling() -void GfxFrameout::applyGlobalScaling(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 celHeight) { - // Global scaling uses global var 2 and some other stuff to calculate scaleX/scaleY - int16 maxScale = readSelectorValue(_segMan, itemEntry->object, SELECTOR(maxScale)); - int16 maxCelHeight = (maxScale * celHeight) >> 7; - reg_t globalVar2 = g_sci->getEngineState()->variables[VAR_GLOBAL][2]; // current room object - int16 vanishingY = readSelectorValue(_segMan, globalVar2, SELECTOR(vanishingY)); +void GfxFrameout::kernelMovePlaneItems(const reg_t object, const int16 deltaX, const int16 deltaY, const bool scrollPics) { + Plane *plane = _planes.findByObject(object); + if (plane == nullptr) { + error("kMovePlaneItems: Plane %04x:%04x not found", PRINT_REG(object)); + } - int16 fixedPortY = planeRect.bottom - vanishingY; - int16 fixedEntryY = itemEntry->y - vanishingY; - if (!fixedEntryY) - fixedEntryY = 1; + plane->scrollScreenItems(deltaX, deltaY, scrollPics); - if ((celHeight == 0) || (fixedPortY == 0)) - error("global scaling panic"); + for (ScreenItemList::iterator it = plane->_screenItemList.begin(); it != plane->_screenItemList.end(); ++it) { + ScreenItem &screenItem = **it; - itemEntry->scaleY = (maxCelHeight * fixedEntryY) / fixedPortY; - itemEntry->scaleY = (itemEntry->scaleY * maxScale) / celHeight; + // If object is a number, the screen item from the + // engine, not a script, and should be ignored + if (screenItem._object.isNumber()) { + continue; + } - // Make sure that the calculated value is sane - if (itemEntry->scaleY < 1 /*|| itemEntry->scaleY > 128*/) - itemEntry->scaleY = 128; + if (deltaX != 0) { + writeSelectorValue(_segMan, screenItem._object, SELECTOR(x), readSelectorValue(_segMan, screenItem._object, SELECTOR(x)) + deltaX); + } - itemEntry->scaleX = itemEntry->scaleY; + if (deltaY != 0) { + writeSelectorValue(_segMan, screenItem._object, SELECTOR(y), readSelectorValue(_segMan, screenItem._object, SELECTOR(y)) + deltaY); + } + } +} - // and set objects scale selectors - //writeSelectorValue(_segMan, itemEntry->object, SELECTOR(scaleX), itemEntry->scaleX); - //writeSelectorValue(_segMan, itemEntry->object, SELECTOR(scaleY), itemEntry->scaleY); +int16 GfxFrameout::kernelGetHighPlanePri() { + return _planes.getTopSciPlanePriority(); } -void GfxFrameout::kernelAddScreenItem(reg_t object) { - // Ignore invalid items - if (!_segMan->isObject(object)) { - warning("kernelAddScreenItem: Attempt to add an invalid object (%04x:%04x)", PRINT_REG(object)); - return; +void GfxFrameout::addPlane(Plane &plane) { + if (_planes.findByObject(plane._object) == nullptr) { + plane.clipScreenRect(_screenRect); + _planes.add(&plane); + } else { + plane._deleted = 0; + if (plane._created == 0) { + plane._moved = g_sci->_gfxFrameout->getScreenCount(); + } + _planes.sort(); } +} + +void GfxFrameout::updatePlane(Plane &plane) { + // NOTE: This assertion comes from SCI engine code. + assert(_planes.findByObject(plane._object) == &plane); - FrameoutEntry *itemEntry = new FrameoutEntry(); - memset(itemEntry, 0, sizeof(FrameoutEntry)); - itemEntry->object = object; - itemEntry->givenOrderNr = _screenItems.size(); - itemEntry->visible = true; - _screenItems.push_back(itemEntry); + Plane *visiblePlane = _visiblePlanes.findByObject(plane._object); + plane.sync(visiblePlane, _screenRect); + // NOTE: updateScreenRect was originally called a second time here, + // but it is already called at the end of the Plane::Update call + // in the original engine anyway. - kernelUpdateScreenItem(object); + _planes.sort(); } -void GfxFrameout::kernelUpdateScreenItem(reg_t object) { - // Ignore invalid items - if (!_segMan->isObject(object)) { - warning("kernelUpdateScreenItem: Attempt to update an invalid object (%04x:%04x)", PRINT_REG(object)); - return; - } +#pragma mark - +#pragma mark Pics - FrameoutEntry *itemEntry = findScreenItem(object); - if (!itemEntry) { - warning("kernelUpdateScreenItem: invalid object %04x:%04x", PRINT_REG(object)); - return; +void GfxFrameout::kernelAddPicAt(const reg_t planeObject, const GuiResourceId pictureId, const int16 x, const int16 y, const bool mirrorX) { + Plane *plane = _planes.findByObject(planeObject); + if (plane == nullptr) { + error("kAddPicAt: Plane %04x:%04x not found", PRINT_REG(planeObject)); } + plane->addPic(pictureId, Common::Point(x, y), mirrorX); +} - itemEntry->viewId = readSelectorValue(_segMan, object, SELECTOR(view)); - itemEntry->loopNo = readSelectorValue(_segMan, object, SELECTOR(loop)); - itemEntry->celNo = readSelectorValue(_segMan, object, SELECTOR(cel)); - itemEntry->x = readSelectorValue(_segMan, object, SELECTOR(x)); - itemEntry->y = readSelectorValue(_segMan, object, SELECTOR(y)); - itemEntry->z = readSelectorValue(_segMan, object, SELECTOR(z)); - itemEntry->priority = readSelectorValue(_segMan, object, SELECTOR(priority)); - if (readSelectorValue(_segMan, object, SELECTOR(fixPriority)) == 0) - itemEntry->priority = itemEntry->y; +#pragma mark - +#pragma mark Rendering - itemEntry->signal = readSelectorValue(_segMan, object, SELECTOR(signal)); - itemEntry->scaleSignal = readSelectorValue(_segMan, object, SELECTOR(scaleSignal)); +void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &rect) { +// TODO: Robot +// if (_robot != nullptr) { +// _robot.doRobot(); +// } - if (itemEntry->scaleSignal & kScaleSignalDoScaling32) { - itemEntry->scaleX = readSelectorValue(_segMan, object, SELECTOR(scaleX)); - itemEntry->scaleY = readSelectorValue(_segMan, object, SELECTOR(scaleY)); - } else { - itemEntry->scaleX = 128; - itemEntry->scaleY = 128; - } - itemEntry->visible = true; + // NOTE: The original engine allocated these as static arrays of 100 + // pointers to ScreenItemList / RectList + ScreenItemListList screenItemLists; + EraseListList eraseLists; - // Check if the entry can be hidden - if (lookupSelector(_segMan, object, SELECTOR(visible), NULL, NULL) != kSelectorNone) - itemEntry->visible = readSelectorValue(_segMan, object, SELECTOR(visible)); -} + screenItemLists.resize(_planes.size()); + eraseLists.resize(_planes.size()); -void GfxFrameout::kernelDeleteScreenItem(reg_t object) { - FrameoutEntry *itemEntry = findScreenItem(object); - // If the item could not be found, it may already have been deleted - if (!itemEntry) - return; + if (g_sci->_gfxRemap32->getRemapCount() > 0 && _remapOccurred) { + remapMarkRedraw(); + } - _screenItems.remove(itemEntry); - delete itemEntry; -} + calcLists(screenItemLists, eraseLists, rect); -void GfxFrameout::deletePlaneItems(reg_t planeObject) { - FrameoutList::iterator listIterator = _screenItems.begin(); + for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) { + list->sort(); + } - while (listIterator != _screenItems.end()) { - bool objectMatches = false; - if (!planeObject.isNull()) { - reg_t itemPlane = readSelector(_segMan, (*listIterator)->object, SELECTOR(plane)); - objectMatches = (planeObject == itemPlane); - } else { - objectMatches = true; + for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) { + for (DrawList::iterator drawItem = list->begin(); drawItem != list->end(); ++drawItem) { + (*drawItem)->screenItem->getCelObj().submitPalette(); } + } - if (objectMatches) { - FrameoutEntry *itemEntry = *listIterator; - listIterator = _screenItems.erase(listIterator); - delete itemEntry; - } else { - ++listIterator; - } + _remapOccurred = _palette->updateForFrame(); + + // NOTE: SCI engine set this to false on each loop through the + // planelist iterator below. Since that is a waste, we only set + // it once. + _frameNowVisible = false; + + for (PlaneList::size_type i = 0; i < _planes.size(); ++i) { + drawEraseList(eraseLists[i], *_planes[i]); + drawScreenItemList(screenItemLists[i]); } -} -FrameoutEntry *GfxFrameout::findScreenItem(reg_t object) { - for (FrameoutList::iterator listIterator = _screenItems.begin(); listIterator != _screenItems.end(); listIterator++) { - FrameoutEntry *itemEntry = *listIterator; - if (itemEntry->object == object) - return itemEntry; +// TODO: Robot +// if (_robot != nullptr) { +// _robot->frameAlmostVisible(); +// } + + _palette->updateHardware(); + + if (shouldShowBits) { + showBits(); } - return NULL; -} + _frameNowVisible = true; -int16 GfxFrameout::kernelGetHighPlanePri() { - sortPlanes(); - return readSelectorValue(g_sci->getEngineState()->_segMan, _planes.back().object, SELECTOR(priority)); +// TODO: Robot +// if (_robot != nullptr) { +// robot->frameNowVisible(); +// } } -void GfxFrameout::kernelAddPicAt(reg_t planeObj, GuiResourceId pictureId, int16 pictureX, int16 pictureY) { - addPlanePicture(planeObj, pictureId, pictureX, pictureY); -} +// Determine the parts of 'r' that aren't overlapped by 'other'. +// Returns -1 if r and other have no intersection. +// Returns number of returned parts (in outRects) otherwise. +// (In particular, this returns 0 if r is contained in other.) +int splitRects(Common::Rect r, const Common::Rect &other, Common::Rect(&outRects)[4]) { + if (!r.intersects(other)) { + return -1; + } -bool sortHelper(const FrameoutEntry* entry1, const FrameoutEntry* entry2) { - if (entry1->priority == entry2->priority) { - if (entry1->y == entry2->y) - return (entry1->givenOrderNr < entry2->givenOrderNr); - return (entry1->y < entry2->y); + int count = 0; + if (r.top < other.top) { + Common::Rect &t = outRects[count++]; + t = r; + t.bottom = other.top; + r.top = other.top; } - return (entry1->priority < entry2->priority); -} -bool planeSortHelper(const PlaneEntry &entry1, const PlaneEntry &entry2) { - if (entry1.priority < 0) - return true; + if (r.bottom > other.bottom) { + Common::Rect &t = outRects[count++]; + t = r; + t.top = other.bottom; + r.bottom = other.bottom; + } - if (entry2.priority < 0) - return false; + if (r.left < other.left) { + Common::Rect &t = outRects[count++]; + t = r; + t.right = other.left; + r.left = other.left; + } - return entry1.priority < entry2.priority; + if (r.right > other.right) { + Common::Rect &t = outRects[count++]; + t = r; + t.left = other.right; + } + + return count; } -void GfxFrameout::sortPlanes() { - // First, remove any invalid planes - for (PlaneList::iterator it = _planes.begin(); it != _planes.end();) { - if (!_segMan->isObject(it->object)) - it = _planes.erase(it); - else - it++; +void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &calcRect) { + RectList rectlist; + Common::Rect outRects[4]; + + int deletedPlaneCount = 0; + bool addedToRectList = false; + int planeCount = _planes.size(); + bool foundTransparentPlane = false; + + if (!calcRect.isEmpty()) { + addedToRectList = true; + rectlist.add(calcRect); } - // Sort the rest of them - Common::sort(_planes.begin(), _planes.end(), planeSortHelper); -} + for (int outerPlaneIndex = 0; outerPlaneIndex < planeCount; ++outerPlaneIndex) { + Plane *outerPlane = _planes[outerPlaneIndex]; -void GfxFrameout::showVideo() { - bool skipVideo = false; - RobotDecoder *videoDecoder = g_sci->_robotDecoder; - uint16 x = videoDecoder->getPos().x; - uint16 y = videoDecoder->getPos().y; - uint16 screenWidth = _screen->getWidth(); - uint16 screenHeight = _screen->getHeight(); - uint16 outputWidth; - uint16 outputHeight; + if (outerPlane->_type == kPlaneTypeTransparent) { + foundTransparentPlane = true; + } - if (videoDecoder->hasDirtyPalette()) - g_system->getPaletteManager()->setPalette(videoDecoder->getPalette(), 0, 256); + Plane *visiblePlane = _visiblePlanes.findByObject(outerPlane->_object); - while (!g_engine->shouldQuit() && !videoDecoder->endOfVideo() && !skipVideo) { - if (videoDecoder->needsUpdate()) { - const Graphics::Surface *frame = videoDecoder->decodeNextFrame(); - if (frame) { - // We need to clip here - // At least Phantasmagoria shows a 640x390 video on a 630x450 screen during the intro - outputWidth = frame->w > screenWidth ? screenWidth : frame->w; - outputHeight = frame->h > screenHeight ? screenHeight : frame->h; - g_system->copyRectToScreen(frame->getPixels(), frame->pitch, x, y, outputWidth, outputHeight); + if (outerPlane->_deleted) { + if (visiblePlane != nullptr) { + if (!visiblePlane->_screenRect.isEmpty()) { + addedToRectList = true; + rectlist.add(visiblePlane->_screenRect); + } + } + ++deletedPlaneCount; + } else if (visiblePlane != nullptr) { + if (outerPlane->_updated) { + --outerPlane->_updated; + + int splitcount = splitRects(visiblePlane->_screenRect, outerPlane->_screenRect, outRects); + if (splitcount) { + if (splitcount == -1) { + if (!visiblePlane->_screenRect.isEmpty()) { + rectlist.add(visiblePlane->_screenRect); + } + } else { + for (int i = 0; i < splitcount; ++i) { + rectlist.add(outRects[i]); + } + } - if (videoDecoder->hasDirtyPalette()) - g_system->getPaletteManager()->setPalette(videoDecoder->getPalette(), 0, 256); + addedToRectList = true; + } - g_system->updateScreen(); + if (!outerPlane->_redrawAllCount) { + int splitCount = splitRects(outerPlane->_screenRect, visiblePlane->_screenRect, outRects); + if (splitCount) { + for (int i = 0; i < splitCount; ++i) { + rectlist.add(outRects[i]); + } + addedToRectList = true; + } + } } } - Common::Event event; - while (g_system->getEventManager()->pollEvent(event)) { - if ((event.type == Common::EVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_ESCAPE) || event.type == Common::EVENT_LBUTTONUP) - skipVideo = true; - } + if (addedToRectList) { + for (RectList::iterator rect = rectlist.begin(); rect != rectlist.end(); ++rect) { + for (int innerPlaneIndex = _planes.size() - 1; innerPlaneIndex >= 0; --innerPlaneIndex) { + Plane *innerPlane = _planes[innerPlaneIndex]; + + if (!innerPlane->_deleted && innerPlane->_type != kPlaneTypeTransparent && innerPlane->_screenRect.intersects(**rect)) { + if (innerPlane->_redrawAllCount == 0) { + eraseLists[innerPlaneIndex].add(innerPlane->_screenRect.findIntersectingRect(**rect)); + } + + int splitCount = splitRects(**rect, innerPlane->_screenRect, outRects); + for (int i = 0; i < splitCount; ++i) { + rectlist.add(outRects[i]); + } + + rectlist.erase(rect); + break; + } + } + } - g_system->delayMillis(10); + rectlist.pack(); + } } -} -void GfxFrameout::createPlaneItemList(reg_t planeObject, FrameoutList &itemList) { - // Copy screen items of the current frame to the list of items to be drawn - for (FrameoutList::iterator listIterator = _screenItems.begin(); listIterator != _screenItems.end(); listIterator++) { - reg_t itemPlane = readSelector(_segMan, (*listIterator)->object, SELECTOR(plane)); - if (planeObject == itemPlane) { - kernelUpdateScreenItem((*listIterator)->object); // TODO: Why is this necessary? - itemList.push_back(*listIterator); + // clean up deleted planes + if (deletedPlaneCount) { + for (int planeIndex = planeCount - 1; planeIndex >= 0; --planeIndex) { + Plane *plane = _planes[planeIndex]; + + if (plane->_deleted) { + --plane->_deleted; + if (plane->_deleted <= 0) { + PlaneList::iterator visiblePlaneIt = Common::find_if(_visiblePlanes.begin(), _visiblePlanes.end(), FindByObject<Plane *>(plane->_object)); + if (visiblePlaneIt != _visiblePlanes.end()) { + _visiblePlanes.erase(visiblePlaneIt); + } + + _planes.remove_at(planeIndex); + eraseLists.remove_at(planeIndex); + drawLists.remove_at(planeIndex); + } + + if (--deletedPlaneCount <= 0) { + break; + } + } } } - for (PlanePictureList::iterator pictureIt = _planePictures.begin(); pictureIt != _planePictures.end(); pictureIt++) { - if (pictureIt->object == planeObject) { - GfxPicture *planePicture = pictureIt->picture; - // Allocate memory for picture cels - pictureIt->pictureCels = new FrameoutEntry[planePicture->getSci32celCount()]; + planeCount = _planes.size(); + for (int outerIndex = 0; outerIndex < planeCount; ++outerIndex) { + // "outer" just refers to the outer loop + Plane *outerPlane = _planes[outerIndex]; + if (outerPlane->_priorityChanged) { + --outerPlane->_priorityChanged; + + Plane *visibleOuterPlane = _visiblePlanes.findByObject(outerPlane->_object); + + rectlist.add(outerPlane->_screenRect.findIntersectingRect(visibleOuterPlane->_screenRect)); + + for (int innerIndex = planeCount - 1; innerIndex >= 0; --innerIndex) { + // "inner" just refers to the inner loop + Plane *innerPlane = _planes[innerIndex]; + Plane *visibleInnerPlane = _visiblePlanes.findByObject(innerPlane->_object); + + int rectCount = rectlist.size(); + for (int rectIndex = 0; rectIndex < rectCount; ++rectIndex) { + int splitCount = splitRects(*rectlist[rectIndex], _planes[innerIndex]->_screenRect, outRects); + + if (splitCount == 0) { + if (visibleInnerPlane != nullptr) { + // same priority, or relative priority between inner/outer changed + if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane->_priority - innerPlane->_priority) <= 0) { + if (outerPlane->_priority <= innerPlane->_priority) { + eraseLists[innerIndex].add(*rectlist[rectIndex]); + } else { + eraseLists[outerIndex].add(*rectlist[rectIndex]); + } + } + } + + rectlist.erase_at(rectIndex); + } else if (splitCount != -1) { + for (int i = 0; i < splitCount; ++i) { + rectlist.add(outRects[i]); + } + + if (visibleInnerPlane != nullptr) { + // same priority, or relative priority between inner/outer changed + if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane->_priority - innerPlane->_priority) <= 0) { + *rectlist[rectIndex] = outerPlane->_screenRect.findIntersectingRect(innerPlane->_screenRect); + if (outerPlane->_priority <= innerPlane->_priority) { + eraseLists[innerIndex].add(*rectlist[rectIndex]); + } + else { + eraseLists[outerIndex].add(*rectlist[rectIndex]); + } + } + } + rectlist.erase_at(rectIndex); + } + } + rectlist.pack(); + } + } + } - // Add following cels to the itemlist - FrameoutEntry *picEntry = pictureIt->pictureCels; - int planePictureCels = planePicture->getSci32celCount(); - for (int pictureCelNr = 0; pictureCelNr < planePictureCels; pictureCelNr++) { - picEntry->celNo = pictureCelNr; - picEntry->object = NULL_REG; - picEntry->picture = planePicture; - picEntry->y = planePicture->getSci32celY(pictureCelNr); - picEntry->x = planePicture->getSci32celX(pictureCelNr); - picEntry->picStartX = pictureIt->startX; - picEntry->picStartY = pictureIt->startY; - picEntry->visible = true; + for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) { + Plane *plane = _planes[planeIndex]; + Plane *visiblePlane = nullptr; - picEntry->priority = planePicture->getSci32celPriority(pictureCelNr); + PlaneList::iterator visiblePlaneIt = Common::find_if(_visiblePlanes.begin(), _visiblePlanes.end(), FindByObject<Plane *>(plane->_object)); + if (visiblePlaneIt != _visiblePlanes.end()) { + visiblePlane = *visiblePlaneIt; + } - itemList.push_back(picEntry); - picEntry++; + if (plane->_redrawAllCount) { + plane->redrawAll(visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]); + } else { + if (visiblePlane == nullptr) { + error("Missing visible plane for source plane %04x:%04x", PRINT_REG(plane->_object)); } + + plane->calcLists(*visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]); + } + + if (plane->_created) { + _visiblePlanes.add(new Plane(*plane)); + --plane->_created; + } else if (plane->_moved) { + assert(visiblePlaneIt != _visiblePlanes.end()); + **visiblePlaneIt = *plane; + --plane->_moved; } } - // Now sort our itemlist - Common::sort(itemList.begin(), itemList.end(), sortHelper); -} + if (foundTransparentPlane) { + for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) { + for (int i = planeIndex + 1; i < planeCount; ++i) { + if (_planes[i]->_type == kPlaneTypeTransparent) { + _planes[i]->filterUpEraseRects(drawLists[i], eraseLists[planeIndex]); + } + } -bool GfxFrameout::isPictureOutOfView(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 planeOffsetX, int16 planeOffsetY) { - // Out of view horizontally (sanity checks) - int16 pictureCelStartX = itemEntry->picStartX + itemEntry->x; - int16 pictureCelEndX = pictureCelStartX + itemEntry->picture->getSci32celWidth(itemEntry->celNo); - int16 planeStartX = planeOffsetX; - int16 planeEndX = planeStartX + planeRect.width(); - if (pictureCelEndX < planeStartX) - return true; - if (pictureCelStartX > planeEndX) - return true; + if (_planes[planeIndex]->_type == kPlaneTypeTransparent) { + for (int i = planeIndex - 1; i >= 0; --i) { + _planes[i]->filterDownEraseRects(drawLists[i], eraseLists[i], eraseLists[planeIndex]); + } - // Out of view vertically (sanity checks) - int16 pictureCelStartY = itemEntry->picStartY + itemEntry->y; - int16 pictureCelEndY = pictureCelStartY + itemEntry->picture->getSci32celHeight(itemEntry->celNo); - int16 planeStartY = planeOffsetY; - int16 planeEndY = planeStartY + planeRect.height(); - if (pictureCelEndY < planeStartY) - return true; - if (pictureCelStartY > planeEndY) - return true; + if (eraseLists[planeIndex].size() > 0) { + error("Transparent plane's erase list not absorbed"); + } + } - return false; + for (int i = planeIndex + 1; i < planeCount; ++i) { + if (_planes[i]->_type == kPlaneTypeTransparent) { + _planes[i]->filterUpDrawRects(drawLists[i], drawLists[planeIndex]); + } + } + } + } } -void GfxFrameout::drawPicture(FrameoutEntry *itemEntry, int16 planeOffsetX, int16 planeOffsetY, bool planePictureMirrored) { - int16 pictureOffsetX = planeOffsetX; - int16 pictureX = itemEntry->x; - if ((planeOffsetX) || (itemEntry->picStartX)) { - if (planeOffsetX <= itemEntry->picStartX) { - pictureX += itemEntry->picStartX - planeOffsetX; - pictureOffsetX = 0; - } else { - pictureOffsetX = planeOffsetX - itemEntry->picStartX; - } +void GfxFrameout::drawEraseList(const RectList &eraseList, const Plane &plane) { + if (plane._type != kPlaneTypeColored) { + return; } - int16 pictureOffsetY = planeOffsetY; - int16 pictureY = itemEntry->y; - if ((planeOffsetY) || (itemEntry->picStartY)) { - if (planeOffsetY <= itemEntry->picStartY) { - pictureY += itemEntry->picStartY - planeOffsetY; - pictureOffsetY = 0; - } else { - pictureOffsetY = planeOffsetY - itemEntry->picStartY; + for (RectList::const_iterator it = eraseList.begin(); it != eraseList.end(); ++it) { + mergeToShowList(**it, _showList, _overdrawThreshold); + _currentBuffer.fillRect(**it, plane._back); + } +} + +void GfxFrameout::drawScreenItemList(const DrawList &screenItemList) { + for (DrawList::const_iterator it = screenItemList.begin(); it != screenItemList.end(); ++it) { + DrawItem &drawItem = **it; + mergeToShowList(drawItem.rect, _showList, _overdrawThreshold); + ScreenItem &screenItem = *drawItem.screenItem; + // TODO: Remove +// debug("Drawing item %04x:%04x to %d %d %d %d", PRINT_REG(screenItem._object), PRINT_RECT(drawItem.rect)); + CelObj &celObj = *screenItem._celObj; + celObj.draw(_currentBuffer, screenItem, drawItem.rect, screenItem._mirrorX ^ celObj._mirrorX); + } +} + +void GfxFrameout::mergeToShowList(const Common::Rect &drawRect, RectList &showList, const int overdrawThreshold) { + Common::Rect merged(drawRect); + + bool didDelete = true; + RectList::size_type count = showList.size(); + while (didDelete && count) { + didDelete = false; + + for (RectList::size_type i = 0; i < count; ++i) { + Common::Rect existing = *showList[i]; + Common::Rect candidate; + candidate.left = MIN(merged.left, existing.left); + candidate.top = MIN(merged.top, existing.top); + candidate.right = MAX(merged.right, existing.right); + candidate.bottom = MAX(merged.bottom, existing.bottom); + + if (candidate.height() * candidate.width() - merged.width() * merged.height() - existing.width() * existing.height() <= overdrawThreshold) { + merged = candidate; + showList.erase_at(i); + didDelete = true; + } } + + count = showList.pack(); } - itemEntry->picture->drawSci32Vga(itemEntry->celNo, pictureX, itemEntry->y, pictureOffsetX, pictureOffsetY, planePictureMirrored); - // warning("picture cel %d %d", itemEntry->celNo, itemEntry->priority); + showList.add(merged); } -/* TODO: This is the proper implementation of GraphicsMgr::FrameOut transcribed from SQ6 SCI engine disassembly. -static DrawList* g_drawLists[100]; -static RectList* g_rectLists[100]; -void GfxFrameout::FrameOut(bool shouldShowBits, SOL_Rect *rect) { - if (robot) { - robot.doRobot(); +void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry *showStyle) { + Palette sourcePalette(*_palette->getNextPalette()); + alterVmap(sourcePalette, sourcePalette, -1, styleRanges); + + int16 prevRoom = g_sci->getEngineState()->variables[VAR_GLOBAL][12].toSint16(); + + Common::Rect rect(_screen->getDisplayWidth(), _screen->getDisplayHeight()); + _showList.add(rect); + showBits(); + + Common::Rect calcRect(0, 0); + + // NOTE: The original engine allocated these as static arrays of 100 + // pointers to ScreenItemList / RectList + ScreenItemListList screenItemLists; + EraseListList eraseLists; + + screenItemLists.resize(_planes.size()); + eraseLists.resize(_planes.size()); + + if (g_sci->_gfxRemap32->getRemapCount() > 0 && _remapOccurred) { + remapMarkRedraw(); } - auto planeCount = screen.planeList.planeCount; - if (planeCount > 0) { - for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) { - Plane plane = *screen.planeList[planeIndex]; + calcLists(screenItemLists, eraseLists, calcRect); + for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) { + list->sort(); + } - DrawList* drawList = new DrawList(); - g_drawLists[planeIndex] = drawList; - RectList* rectList = new RectList(); - g_rectLists[planeIndex] = rectList; + for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) { + for (DrawList::iterator drawItem = list->begin(); drawItem != list->end(); ++drawItem) { + (*drawItem)->screenItem->getCelObj().submitPalette(); } } - - if (g_Remap_numActiveRemaps > 0 && remapNeeded) { - screen.RemapMarkRedraw(); + + _remapOccurred = _palette->updateForFrame(); + _frameNowVisible = false; + + for (PlaneList::size_type i = 0; i < _planes.size(); ++i) { + drawEraseList(eraseLists[i], *_planes[i]); + drawScreenItemList(screenItemLists[i]); } - - CalcLists(&g_drawLists, &g_rectLists, rect); - // SCI engine stores reference *after* CalcLists - planeCount = screen.planeList.planeCount; - if (planeCount > 0) { - for (int drawListIndex = 0; drawListIndex < planeCount; ++i) { - DrawList* drawList = g_drawLists[drawListIndex]; - drawList->Sort(); - } + Palette nextPalette(*_palette->getNextPalette()); - for (int drawListIndex = 0; drawListIndex < planeCount; ++i) { - DrawList* drawList = g_drawLists[drawListIndex]; - if (drawList == nullptr || drawList->count == 0) { - continue; + if (prevRoom < 1000) { + for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) { + if (styleRanges[i] == -1 || styleRanges[i] == 0) { + sourcePalette.colors[i] = nextPalette.colors[i]; + sourcePalette.colors[i].used = true; } - - for (int screenItemIndex = 0, screenItemCount = drawList->count; screenItemIndex < screenItemCount; ++screenItemIndex) { - ScreenItem* screenItem = drawList->items[screenItemIndex]; - screenItem->GetCelObj()->SubmitPalette(); + } + } else { + for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) { + if (styleRanges[i] == -1 || (styleRanges[i] == 0 && i > 71 && i < 104)) { + sourcePalette.colors[i] = nextPalette.colors[i]; + sourcePalette.colors[i].used = true; } } } - // UpdateForFrame is where all palette mutations occur (cycles, varies, etc.) - bool remapNeeded = GPalette().UpdateForFrame(); - if (planeCount > 0) { - frameNowVisible = false; + _palette->submit(sourcePalette); + _palette->updateFFrame(); + _palette->updateHardware(); + alterVmap(nextPalette, sourcePalette, 1, _styleRanges); - for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) { - Plane* plane = screen.planeList[planeIndex]; + if (showStyle && showStyle->type != kShowStyleUnknown) { +// TODO: SCI2.1mid transition effects +// processEffects(); + warning("Transition %d not implemented!", showStyle->type); + } else { + showBits(); + } + + _frameNowVisible = true; - DrawEraseList(g_rectLists[planeIndex], plane); - DrawScreenItemsList(g_drawLists[planeIndex]); + for (PlaneList::iterator plane = _planes.begin(); plane != _planes.end(); ++plane) { + (*plane)->_redrawAllCount = getScreenCount(); + } + + if (g_sci->_gfxRemap32->getRemapCount() > 0 && _remapOccurred) { + remapMarkRedraw(); + } + + calcLists(screenItemLists, eraseLists, calcRect); + for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) { + list->sort(); + } + + for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) { + for (DrawList::iterator drawItem = list->begin(); drawItem != list->end(); ++drawItem) { + (*drawItem)->screenItem->getCelObj().submitPalette(); } } - if (robot) { - robot.FrameAlmostVisible(); + _remapOccurred = _palette->updateForFrame(); + // NOTE: During this second loop, `_frameNowVisible = false` is + // inside the next loop in SCI2.1mid + _frameNowVisible = false; + + for (PlaneList::size_type i = 0; i < _planes.size(); ++i) { + drawEraseList(eraseLists[i], *_planes[i]); + drawScreenItemList(screenItemLists[i]); } - GPalette().UpdateHardware(); + _palette->submit(nextPalette); + _palette->updateFFrame(); + _palette->updateHardware(); + showBits(); - if (shouldShowBits) { - ShowBits(); + _frameNowVisible = true; +} + +// TODO: What does the bit masking for the show rects do, +// and does it cause an off-by-one error in rect calculations +// since SOL_Rect is BR inclusive and Common::Rect is BR +// exclusive? +void GfxFrameout::showBits() { + for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) { + Common::Rect rounded(**rect); + // NOTE: SCI engine used BR-inclusive rects so used slightly + // different masking here to ensure that the width of rects + // was always even. + rounded.left &= ~1; + rounded.right = (rounded.right + 1) & ~1; + + // TODO: + // _cursor->GonnaPaint(rounded); } - frameNowVisible = true; + // TODO: + // _cursor->PaintStarting(); + + for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) { + Common::Rect rounded(**rect); + // NOTE: SCI engine used BR-inclusive rects so used slightly + // different masking here to ensure that the width of rects + // was always even. + rounded.left &= ~1; + rounded.right = (rounded.right + 1) & ~1; - if (robot) { - robot.FrameNowVisible(); + byte *sourceBuffer = (byte *)_currentBuffer.getPixels() + rounded.top * _currentBuffer.screenWidth + rounded.left; + + g_system->copyRectToScreen(sourceBuffer, _currentBuffer.screenWidth, rounded.left, rounded.top, rounded.width(), rounded.height()); } - if (planeCount > 0) { - for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) { - if (g_rectLists[planeIndex] != nullptr) { - delete g_rectLists[planeIndex]; + // TODO: + // _cursor->DonePainting(); + + _showList.clear(); +} + +void GfxFrameout::alterVmap(const Palette &palette1, const Palette &palette2, const int8 style, const int8 *const styleRanges) { + uint8 clut[256]; + + for (int paletteIndex = 0; paletteIndex < ARRAYSIZE(palette1.colors); ++paletteIndex) { + int outerR = palette1.colors[paletteIndex].r; + int outerG = palette1.colors[paletteIndex].g; + int outerB = palette1.colors[paletteIndex].b; + + if (styleRanges[paletteIndex] == style) { + int minDiff = 262140; + int minDiffIndex; + + for (int i = 0; i < 236; ++i) { + if (styleRanges[i] != style) { + int r = palette1.colors[i].r; + int g = palette1.colors[i].g; + int b = palette1.colors[i].b; + int diffSquared = (outerR - r) * (outerR - r) + (outerG - g) * (outerG - g) + (outerB - b) * (outerB - b); + if (diffSquared < minDiff) { + minDiff = diffSquared; + minDiffIndex = i; + } + } } - if (g_drawLists[planeIndex] != nullptr) { - delete g_drawLists[planeIndex]; + + clut[paletteIndex] = minDiffIndex; + } + + if (style == 1 && styleRanges[paletteIndex] == 0) { + int minDiff = 262140; + int minDiffIndex; + + for (int i = 0; i < 236; ++i) { + int r = palette2.colors[i].r; + int g = palette2.colors[i].g; + int b = palette2.colors[i].b; + + int diffSquared = (outerR - r) * (outerR - r) + (outerG - g) * (outerG - g) + (outerB - b) * (outerB - b); + if (diffSquared < minDiff) { + minDiff = diffSquared; + minDiffIndex = i; + } } + + clut[paletteIndex] = minDiffIndex; + } + } + + // NOTE: This is currBuffer->ptr in SCI engine + byte *pixels = (byte *)_currentBuffer.getPixels(); + + for (int pixelIndex = 0, numPixels = _currentBuffer.screenWidth * _currentBuffer.screenHeight; pixelIndex < numPixels; ++pixelIndex) { + byte currentValue = pixels[pixelIndex]; + int8 styleRangeValue = styleRanges[currentValue]; + if (styleRangeValue == -1 && styleRangeValue == style) { + currentValue = pixels[pixelIndex] = clut[currentValue]; + // NOTE: In original engine this assignment happens outside of the + // condition, but if the branch is not followed the value is just + // going to be the same as it was before + styleRangeValue = styleRanges[currentValue]; + } + + if ( + (styleRangeValue == 1 && styleRangeValue == style) || + (styleRangeValue == 0 && style == 1) + ) { + pixels[pixelIndex] = clut[currentValue]; } } } -void GfxFrameout::CalcLists(DrawList **drawLists, RectList **rectLists, SOL_Rect *rect) { - screen.CalcLists(&visibleScreen, drawLists, rectLists, rect); -} -*/ -void GfxFrameout::kernelFrameout() { - if (g_sci->_robotDecoder->isVideoLoaded()) { - showVideo(); + +void GfxFrameout::kernelSetPalStyleRange(const uint8 fromColor, const uint8 toColor) { + if (toColor > fromColor) { return; } - _palette->updateForFrame(); + for (int i = fromColor; i < toColor; ++i) { + _styleRanges[i] = 0; + } +} - // TODO: Tons of drawing stuff should be here, see commented out implementation above +inline ShowStyleEntry * GfxFrameout::findShowStyleForPlane(const reg_t planeObj) const { + ShowStyleEntry *entry = _showStyles; + while (entry != nullptr) { + if (entry->plane == planeObj) { + break; + } + entry = entry->next; + } - _palette->updateHardware(); + return entry; +} - for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); it++) { - reg_t planeObject = it->object; +inline ShowStyleEntry *GfxFrameout::deleteShowStyleInternal(ShowStyleEntry *const showStyle) { + ShowStyleEntry *lastEntry = nullptr; - // Draw any plane lines, if they exist - // These are drawn on invisible planes as well. (e.g. "invisiblePlane" in LSL6 hires) - // FIXME: Lines aren't always drawn (e.g. when the narrator speaks in LSL6 hires). - // Perhaps something is painted over them? - for (PlaneLineList::iterator it2 = it->lines.begin(); it2 != it->lines.end(); ++it2) { - Common::Point startPoint = it2->startPoint; - Common::Point endPoint = it2->endPoint; - _coordAdjuster->kernelLocalToGlobal(startPoint.x, startPoint.y, it->object); - _coordAdjuster->kernelLocalToGlobal(endPoint.x, endPoint.y, it->object); - _screen->drawLine(startPoint, endPoint, it2->color, it2->priority, it2->control); + for (ShowStyleEntry *testEntry = _showStyles; testEntry != nullptr; testEntry = testEntry->next) { + if (testEntry == showStyle) { + break; } + lastEntry = testEntry; + } - int16 planeLastPriority = it->lastPriority; + if (lastEntry == nullptr) { + _showStyles = showStyle->next; + lastEntry = _showStyles; + } else { + lastEntry->next = showStyle->next; + } - // Update priority here, sq6 sets it w/o UpdatePlane - int16 planePriority = it->priority = readSelectorValue(_segMan, planeObject, SELECTOR(priority)); + delete[] showStyle->fadeColorRanges; + delete showStyle; - it->lastPriority = planePriority; - if (planePriority < 0) { // Plane currently not meant to be shown - // If plane was shown before, delete plane rect - if (planePriority != planeLastPriority) - _paint32->fillRect(it->planeRect, 0); - continue; - } + // TODO: Verify that this is the correct entry to return + // for the loop in processShowStyles to work correctly + return lastEntry; +} - // There is a race condition lurking in SQ6, which causes the game to hang in the intro, when teleporting to Polysorbate LX. - // Since I first wrote the patch, the race has stopped occurring for me though. - // I'll leave this for investigation later, when someone can reproduce. - //if (it->pictureId == kPlanePlainColored) // FIXME: This is what SSCI does, and fixes the intro of LSL7, but breaks the dialogs in GK1 (adds black boxes) - if (it->pictureId == kPlanePlainColored && (it->planeBack || g_sci->getGameId() != GID_GK1)) - _paint32->fillRect(it->planeRect, it->planeBack); +// TODO: 10-argument version is only in SCI3; argc checks are currently wrong for this version +// and need to be fixed in future +// TODO: SQ6 does not use 'priority' (exists since SCI2) or 'blackScreen' (exists since SCI3); +// check to see if other versions use or if they are just always ignored +void GfxFrameout::kernelSetShowStyle(const uint16 argc, const reg_t planeObj, const ShowStyleType type, const int16 seconds, const int16 back, const int16 priority, const int16 animate, const int16 frameOutNow, reg_t pFadeArray, int16 divisions, const int16 blackScreen) { + + bool hasDivisions = false; + bool hasFadeArray = false; + + // KQ7 2.0b uses a mismatched version of the Styler script (SCI2.1early script + // for SCI2.1mid engine), so the calls it makes to kSetShowStyle are wrong and + // put `divisions` where `pFadeArray` is supposed to be + if (getSciVersion() == SCI_VERSION_2_1_MIDDLE && g_sci->getGameId() == GID_KQ7) { + hasDivisions = argc > 7; + hasFadeArray = false; + divisions = argc > 7 ? pFadeArray.toSint16() : -1; + pFadeArray = NULL_REG; + } else if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) { + hasDivisions = argc > 7; + hasFadeArray = false; + } else if (getSciVersion() < SCI_VERSION_3) { + hasDivisions = argc > 8; + hasFadeArray = argc > 7; + } else { + hasDivisions = argc > 9; + hasFadeArray = argc > 8; + } - _coordAdjuster->pictureSetDisplayArea(it->planeRect); - // Invoking drewPicture() with an invalid picture ID in SCI32 results in - // invalidating the palVary palette when a palVary effect is active. This - // is quite obvious in QFG4, where the day time palette is incorrectly - // shown when exiting the caves, and the correct night time palette - // flashes briefly each time that kPalVaryInit is called. - if (it->pictureId != 0xFFFF) - _palette->drewPicture(it->pictureId); + bool isFadeUp; + int16 color; + if (back != -1) { + isFadeUp = false; + color = back; + } else { + isFadeUp = true; + color = 0; + } - FrameoutList itemList; + if ((getSciVersion() < SCI_VERSION_2_1_MIDDLE && type == 15) || type > 15) { + error("Illegal show style %d for plane %04x:%04x", type, PRINT_REG(planeObj)); + } - createPlaneItemList(planeObject, itemList); + Plane *plane = _planes.findByObject(planeObj); + if (plane == nullptr) { + error("Plane %04x:%04x is not present in active planes list", PRINT_REG(planeObj)); + } - for (FrameoutList::iterator listIterator = itemList.begin(); listIterator != itemList.end(); listIterator++) { - FrameoutEntry *itemEntry = *listIterator; + bool createNewEntry = true; + ShowStyleEntry *entry = findShowStyleForPlane(planeObj); + if (entry != nullptr) { + // TODO: SCI2.1early has different criteria for show style reuse + bool useExisting = true; - if (!itemEntry->visible) - continue; + if (useExisting) { + useExisting = entry->divisions == (hasDivisions ? divisions : _defaultDivisions[type]) && entry->unknownC == _defaultUnknownC[type]; + } - if (itemEntry->object.isNull()) { - // Picture cel data - _coordAdjuster->fromScriptToDisplay(itemEntry->y, itemEntry->x); - _coordAdjuster->fromScriptToDisplay(itemEntry->picStartY, itemEntry->picStartX); + if (useExisting) { + createNewEntry = false; + isFadeUp = true; + entry->currentStep = 0; + } else { + isFadeUp = true; + color = entry->color; + deleteShowStyleInternal(entry/*, true*/); + entry = nullptr; + } + } - if (!isPictureOutOfView(itemEntry, it->planeRect, it->planeOffsetX, it->planeOffsetY)) - drawPicture(itemEntry, it->planeOffsetX, it->planeOffsetY, it->planePictureMirrored); - } else { - GfxView *view = (itemEntry->viewId != 0xFFFF) ? _cache->getView(itemEntry->viewId) : NULL; - int16 dummyX = 0; - - if (view && view->isSci2Hires()) { - view->adjustToUpscaledCoordinates(itemEntry->y, itemEntry->x); - view->adjustToUpscaledCoordinates(itemEntry->z, dummyX); - } else if (getSciVersion() >= SCI_VERSION_2_1_EARLY) { - _coordAdjuster->fromScriptToDisplay(itemEntry->y, itemEntry->x); - _coordAdjuster->fromScriptToDisplay(itemEntry->z, dummyX); + if (type > 0) { + if (createNewEntry) { + entry = new ShowStyleEntry; + // NOTE: SCI2.1 engine tests if allocation returned a null pointer + // but then only avoids setting currentStep if this is so. Since + // this is a nonsensical approach, we do not do that here + entry->currentStep = 0; + entry->unknownC = _defaultUnknownC[type]; + entry->processed = false; + entry->divisions = hasDivisions ? divisions : _defaultDivisions[type]; + entry->plane = planeObj; + + entry->fadeColorRanges = nullptr; + if (hasFadeArray) { + // NOTE: SCI2.1mid engine does no check to verify that an array is + // successfully retrieved, and SegMan will cause a fatal error + // if we try to use a memory segment that is not an array + SciArray<reg_t> *table = _segMan->lookupArray(pFadeArray); + + uint32 rangeCount = table->getSize(); + entry->fadeColorRangesCount = rangeCount; + + // NOTE: SCI engine code always allocates memory even if the range + // table has no entries, but this does not really make sense, so + // we avoid the allocation call in this case + if (rangeCount > 0) { + entry->fadeColorRanges = new uint16[rangeCount]; + for (size_t i = 0; i < rangeCount; ++i) { + entry->fadeColorRanges[i] = table->getValue(i).toUint16(); + } } + } else { + entry->fadeColorRangesCount = 0; + } + } - // Adjust according to current scroll position - itemEntry->x -= it->planeOffsetX; - itemEntry->y -= it->planeOffsetY; - - uint16 useInsetRect = readSelectorValue(_segMan, itemEntry->object, SELECTOR(useInsetRect)); - if (useInsetRect) { - itemEntry->celRect.top = readSelectorValue(_segMan, itemEntry->object, SELECTOR(inTop)); - itemEntry->celRect.left = readSelectorValue(_segMan, itemEntry->object, SELECTOR(inLeft)); - itemEntry->celRect.bottom = readSelectorValue(_segMan, itemEntry->object, SELECTOR(inBottom)); - itemEntry->celRect.right = readSelectorValue(_segMan, itemEntry->object, SELECTOR(inRight)); - if (view && view->isSci2Hires()) { - view->adjustToUpscaledCoordinates(itemEntry->celRect.top, itemEntry->celRect.left); - view->adjustToUpscaledCoordinates(itemEntry->celRect.bottom, itemEntry->celRect.right); - } - itemEntry->celRect.translate(itemEntry->x, itemEntry->y); - // TODO: maybe we should clip the cels rect with this, i'm not sure - // the only currently known usage is game menu of gk1 - } else if (view) { - // Process global scaling, if needed. - // TODO: Seems like SCI32 always processes global scaling for scaled objects - // TODO: We can only process symmetrical scaling for now (i.e. same value for scaleX/scaleY) - if ((itemEntry->scaleSignal & kScaleSignalDoScaling32) && - !(itemEntry->scaleSignal & kScaleSignalDisableGlobalScaling32) && - (itemEntry->scaleX == itemEntry->scaleY) && - itemEntry->scaleX != 128) - applyGlobalScaling(itemEntry, it->planeRect, view->getHeight(itemEntry->loopNo, itemEntry->celNo)); - - if ((itemEntry->scaleX == 128) && (itemEntry->scaleY == 128)) - view->getCelRect(itemEntry->loopNo, itemEntry->celNo, - itemEntry->x, itemEntry->y, itemEntry->z, itemEntry->celRect); - else - view->getCelScaledRect(itemEntry->loopNo, itemEntry->celNo, - itemEntry->x, itemEntry->y, itemEntry->z, itemEntry->scaleX, - itemEntry->scaleY, itemEntry->celRect); - - Common::Rect nsRect = itemEntry->celRect; - // Translate back to actual coordinate within scrollable plane - nsRect.translate(it->planeOffsetX, it->planeOffsetY); - - if (g_sci->getGameId() == GID_PHANTASMAGORIA2) { - // HACK: Some (?) objects in Phantasmagoria 2 have no NS rect. Skip them for now. - // TODO: Remove once we figure out how Phantasmagoria 2 draws objects on screen. - if (lookupSelector(_segMan, itemEntry->object, SELECTOR(nsLeft), NULL, NULL) != kSelectorVariable) - continue; - } + // NOTE: The original engine had no nullptr check and would just crash + // if it got to here + if (entry == nullptr) { + error("Cannot edit non-existing ShowStyle entry"); + } - if (view && view->isSci2Hires()) { - view->adjustBackUpscaledCoordinates(nsRect.top, nsRect.left); - view->adjustBackUpscaledCoordinates(nsRect.bottom, nsRect.right); - g_sci->_gfxCompare->setNSRect(itemEntry->object, nsRect); - } else if (getSciVersion() >= SCI_VERSION_2_1_EARLY && _isHiRes) { - _coordAdjuster->fromDisplayToScript(nsRect.top, nsRect.left); - _coordAdjuster->fromDisplayToScript(nsRect.bottom, nsRect.right); - g_sci->_gfxCompare->setNSRect(itemEntry->object, nsRect); - } + entry->fadeUp = isFadeUp; + entry->color = color; + entry->nextTick = g_sci->getTickCount(); + entry->type = type; + entry->animate = animate; + entry->delay = (seconds * 60 + entry->divisions - 1) / entry->divisions; - // TODO: For some reason, the top left nsRect coordinates get - // swapped in the GK1 inventory screen, investigate why. - // This is also needed for GK1 rooms 710 and 720 (catacombs, inner and - // outer circle), for handling the tiles and talking to Wolfgang. - // HACK: Fix the coordinates by explicitly setting them here for GK1. - // Also check bug #6729, for another case where this is needed. - if (g_sci->getGameId() == GID_GK1) - g_sci->_gfxCompare->setNSRect(itemEntry->object, nsRect); - } + if (entry->delay == 0) { + if (entry->fadeColorRanges != nullptr) { + delete[] entry->fadeColorRanges; + } + delete entry; + error("ShowStyle has no duration"); + } - // Don't attempt to draw sprites that are outside the visible - // screen area. An example is the random people walking in - // Jackson Square in GK1. - if (itemEntry->celRect.bottom < 0 || itemEntry->celRect.top >= _screen->getDisplayHeight() || - itemEntry->celRect.right < 0 || itemEntry->celRect.left >= _screen->getDisplayWidth()) - continue; - - Common::Rect clipRect, translatedClipRect; - clipRect = itemEntry->celRect; - - if (view && view->isSci2Hires()) { - clipRect.clip(it->upscaledPlaneClipRect); - translatedClipRect = clipRect; - translatedClipRect.translate(it->upscaledPlaneRect.left, it->upscaledPlaneRect.top); - } else { - // QFG4 passes invalid rectangles when a battle is starting - if (!clipRect.isValidRect()) - continue; - clipRect.clip(it->planeClipRect); - translatedClipRect = clipRect; - translatedClipRect.translate(it->planeRect.left, it->planeRect.top); - } + if (frameOutNow) { + Common::Rect frameOutRect(0, 0); + frameOut(false, frameOutRect); + } + + if (createNewEntry) { + // TODO: Implement SCI2.1early and SCI3 + entry->next = _showStyles; + _showStyles = entry; + } + } +} - if (view) { - if (!clipRect.isEmpty()) { - if ((itemEntry->scaleX == 128) && (itemEntry->scaleY == 128)) - view->draw(itemEntry->celRect, clipRect, translatedClipRect, - itemEntry->loopNo, itemEntry->celNo, 255, 0, view->isSci2Hires()); - else - view->drawScaled(itemEntry->celRect, clipRect, translatedClipRect, - itemEntry->loopNo, itemEntry->celNo, 255, itemEntry->scaleX, itemEntry->scaleY); +// NOTE: Different version of SCI engine support different show styles +// SCI2 implements 0, 1/3/5/7/9, 2/4/6/8/10, 11, 12, 13, 14 +// SCI2.1 implements 0, 1/2/3/4/5/6/7/8/9/10/11/12/15, 13, 14 +// SCI3 implements 0, 1/3/5/7/9, 2/4/6/8/10, 11, 12/15, 13, 14 +// TODO: Sierra code needs to be replaced with code that uses the +// computed entry->delay property instead of just counting divisors, +// as the latter is machine-speed-dependent and leads to wrong +// transition speeds +void GfxFrameout::processShowStyles() { + uint32 now = g_sci->getTickCount(); + + bool continueProcessing; + + // TODO: Change to bool? Engine uses inc to set the value to true, + // but there does not seem to be any reason to actually count how + // many times it was set + int doFrameOut; + do { + continueProcessing = false; + doFrameOut = 0; + ShowStyleEntry *showStyle = _showStyles; + while (showStyle != nullptr) { + bool retval = false; + + if (!showStyle->animate) { + ++doFrameOut; + } + + if (showStyle->nextTick < now || !showStyle->animate) { + // TODO: Different versions of SCI use different processors! + // This is the SQ6/KQ7/SCI2.1mid table. + switch (showStyle->type) { + case kShowStyleNone: { + retval = processShowStyleNone(showStyle); + break; + } + case kShowStyleHShutterOut: + case kShowStyleVShutterOut: + case kShowStyleWipeLeft: + case kShowStyleWipeUp: + case kShowStyleIrisOut: + case kShowStyleHShutterIn: + case kShowStyleVShutterIn: + case kShowStyleWipeRight: + case kShowStyleWipeDown: + case kShowStyleIrisIn: + case kShowStyle11: + case kShowStyle12: + case kShowStyleUnknown: { + retval = processShowStyleMorph(showStyle); + break; + } + case kShowStyleFadeOut: { + retval = processShowStyleFade(-1, showStyle); + break; + } + case kShowStyleFadeIn: { + retval = processShowStyleFade(1, showStyle); + break; } } + } - // Draw text, if it exists - if (lookupSelector(_segMan, itemEntry->object, SELECTOR(text), NULL, NULL) == kSelectorVariable) { - g_sci->_gfxText32->drawTextBitmap(itemEntry->x, itemEntry->y, it->planeRect, itemEntry->object); - } + if (!retval) { + continueProcessing = true; } - } - for (PlanePictureList::iterator pictureIt = _planePictures.begin(); pictureIt != _planePictures.end(); pictureIt++) { - if (pictureIt->object == planeObject) { - delete[] pictureIt->pictureCels; - pictureIt->pictureCels = 0; + if (retval && showStyle->processed) { + showStyle = deleteShowStyleInternal(showStyle); + } else { + showStyle = showStyle->next; } } - } - showCurrentScrollText(); + if (doFrameOut) { + frameOut(true); + + // TODO: Transitions without the “animate” flag are too + // fast, but the throttle value is arbitrary. Someone on + // real hardware probably needs to test what the actual + // speed of these transitions should be + EngineState *state = g_sci->getEngineState(); + state->speedThrottler(33); + state->_throttleTrigger = true; + } + } while(continueProcessing && doFrameOut); +} - _screen->copyToScreen(); +bool GfxFrameout::processShowStyleNone(ShowStyleEntry *const showStyle) { + if (showStyle->fadeUp) { + _palette->setFade(100, 0, 255); + } else { + _palette->setFade(0, 0, 255); + } - g_sci->getEngineState()->_throttleTrigger = true; + showStyle->processed = true; + return true; } -void GfxFrameout::printPlaneList(Console *con) { - for (PlaneList::const_iterator it = _planes.begin(); it != _planes.end(); ++it) { - PlaneEntry p = *it; - Common::String curPlaneName = _segMan->getObjectName(p.object); - Common::Rect r = p.upscaledPlaneRect; - Common::Rect cr = p.upscaledPlaneClipRect; - - con->debugPrintf("%04x:%04x (%s): prio %d, lastprio %d, offsetX %d, offsetY %d, pic %d, mirror %d, back %d\n", - PRINT_REG(p.object), curPlaneName.c_str(), - (int16)p.priority, (int16)p.lastPriority, - p.planeOffsetX, p.planeOffsetY, p.pictureId, - p.planePictureMirrored, p.planeBack); - con->debugPrintf(" rect: (%d, %d, %d, %d), clip rect: (%d, %d, %d, %d)\n", - r.left, r.top, r.right, r.bottom, - cr.left, cr.top, cr.right, cr.bottom); - - if (p.pictureId != 0xffff && p.pictureId != 0xfffe) { - con->debugPrintf("Pictures:\n"); - - for (PlanePictureList::iterator pictureIt = _planePictures.begin(); pictureIt != _planePictures.end(); pictureIt++) { - if (pictureIt->object == p.object) { - con->debugPrintf(" Picture %d: x %d, y %d\n", pictureIt->pictureId, pictureIt->startX, pictureIt->startY); - } +bool GfxFrameout::processShowStyleMorph(ShowStyleEntry *const showStyle) { + palMorphFrameOut(_styleRanges, showStyle); + showStyle->processed = true; + return true; +} + +// TODO: Normalise use of 'entry' vs 'showStyle' +bool GfxFrameout::processShowStyleFade(const int direction, ShowStyleEntry *const showStyle) { + bool unchanged = true; + if (showStyle->currentStep < showStyle->divisions) { + int percent; + if (direction <= 0) { + percent = showStyle->divisions - showStyle->currentStep - 1; + } else { + percent = showStyle->currentStep; + } + + percent *= 100; + percent /= showStyle->divisions - 1; + + if (showStyle->fadeColorRangesCount > 0) { + for (int i = 0, len = showStyle->fadeColorRangesCount; i < len; i += 2) { + _palette->setFade(percent, showStyle->fadeColorRanges[i], showStyle->fadeColorRanges[i + 1]); } + } else { + _palette->setFade(percent, 0, 255); } + + ++showStyle->currentStep; + showStyle->nextTick += showStyle->delay; + unchanged = false; + } + + if (showStyle->currentStep >= showStyle->divisions && unchanged) { + if (direction > 0) { + showStyle->processed = true; + } + + return true; + } + + return false; +} + +void GfxFrameout::kernelFrameOut(const bool shouldShowBits) { + if (_showStyles != nullptr) { + processShowStyles(); + } else if (_palMorphIsOn) { + palMorphFrameOut(_styleRanges, nullptr); + _palMorphIsOn = false; + } else { +// TODO: Window scroll +// if (g_PlaneScroll) { +// processScrolls(); +// } + + frameOut(shouldShowBits); + } +} + +#pragma mark - +#pragma mark Mouse cursor + +reg_t GfxFrameout::kernelIsOnMe(const reg_t object, const Common::Point &position, bool checkPixel) const { + const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane)); + Plane *plane = _visiblePlanes.findByObject(planeObject); + if (plane == nullptr) { + return make_reg(0, 0); + } + + ScreenItem *screenItem = plane->_screenItemList.findByObject(object); + if (screenItem == nullptr) { + return make_reg(0, 0); } + + // NOTE: The original engine passed a copy of the ScreenItem into isOnMe + // as a hack around the fact that the screen items in `_visiblePlanes` + // did not have their `_celObj` pointers cleared when their CelInfo was + // updated by `Plane::decrementScreenItemArrayCounts`. We handle this + // this more intelligently by clearing `_celObj` in the copy assignment + // operator, which is only ever called by `decrementScreenItemArrayCounts` + // anyway. + return make_reg(0, isOnMe(*screenItem, *plane, position, checkPixel)); } -void GfxFrameout::printPlaneItemList(Console *con, reg_t planeObject) { - for (FrameoutList::iterator listIterator = _screenItems.begin(); listIterator != _screenItems.end(); listIterator++) { - FrameoutEntry *e = *listIterator; - reg_t itemPlane = readSelector(_segMan, e->object, SELECTOR(plane)); +bool GfxFrameout::isOnMe(const ScreenItem &screenItem, const Plane &plane, const Common::Point &position, const bool checkPixel) const { + + Common::Point scaledPosition(position); + mulru(scaledPosition, Ratio(_currentBuffer.screenWidth, _currentBuffer.scriptWidth), Ratio(_currentBuffer.screenHeight, _currentBuffer.scriptHeight)); + scaledPosition.x += plane._planeRect.left; + scaledPosition.y += plane._planeRect.top; + + if (!screenItem._screenRect.contains(scaledPosition)) { + return false; + } + + if (checkPixel) { + CelObj &celObj = screenItem.getCelObj(); + + bool mirrorX = screenItem._mirrorX ^ celObj._mirrorX; + + scaledPosition.x -= screenItem._scaledPosition.x; + scaledPosition.y -= screenItem._scaledPosition.y; - if (planeObject == itemPlane) { - Common::String curItemName = _segMan->getObjectName(e->object); - Common::Rect icr = e->celRect; - GuiResourceId picId = e->picture ? e->picture->getResourceId() : 0; + mulru(scaledPosition, Ratio(celObj._scaledWidth, _currentBuffer.screenWidth), Ratio(celObj._scaledHeight, _currentBuffer.screenHeight)); - con->debugPrintf("%d: %04x:%04x (%s), view %d, loop %d, cel %d, x %d, y %d, z %d, " - "signal %d, scale signal %d, scaleX %d, scaleY %d, rect (%d, %d, %d, %d), " - "pic %d, picX %d, picY %d, visible %d\n", - e->givenOrderNr, PRINT_REG(e->object), curItemName.c_str(), - e->viewId, e->loopNo, e->celNo, e->x, e->y, e->z, - e->signal, e->scaleSignal, e->scaleX, e->scaleY, - icr.left, icr.top, icr.right, icr.bottom, - picId, e->picStartX, e->picStartY, e->visible); + if (screenItem._scale.signal != kScaleSignalNone && screenItem._scale.x && screenItem._scale.y) { + scaledPosition.x = scaledPosition.x * 128 / screenItem._scale.x; + scaledPosition.y = scaledPosition.y * 128 / screenItem._scale.y; } + + uint8 pixel = celObj.readPixel(scaledPosition.x, scaledPosition.y, mirrorX); + return pixel != celObj._transparentColor; } + + return true; +} + +void GfxFrameout::kernelSetNowSeen(const reg_t screenItemObject) const { + const reg_t planeObject = readSelector(_segMan, screenItemObject, SELECTOR(plane)); + + Plane *plane = _planes.findByObject(planeObject); + if (plane == nullptr) { + error("kSetNowSeen: Plane %04x:%04x not found for screen item %04x:%04x", PRINT_REG(planeObject), PRINT_REG(screenItemObject)); + } + + ScreenItem *screenItem = plane->_screenItemList.findByObject(screenItemObject); + if (screenItem == nullptr) { + error("kSetNowSeen: Screen item %04x:%04x not found in plane %04x:%04x", PRINT_REG(screenItemObject), PRINT_REG(planeObject)); + } + + Common::Rect result = screenItem->getNowSeenRect(*plane); + writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsLeft), result.left); + writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsTop), result.top); + writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsRight), result.right - 1); + writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsBottom), result.bottom - 1); +} + +void GfxFrameout::remapMarkRedraw() { + for (PlaneList::const_iterator it = _planes.begin(); it != _planes.end(); ++it) { + Plane *p = *it; + p->remapMarkRedraw(); + } +} + +#pragma mark - +#pragma mark Debugging + +void GfxFrameout::printPlaneListInternal(Console *con, const PlaneList &planeList) const { + for (PlaneList::const_iterator it = planeList.begin(); it != planeList.end(); ++it) { + Plane *p = *it; + p->printDebugInfo(con); + } +} + +void GfxFrameout::printPlaneList(Console *con) const { + printPlaneListInternal(con, _planes); +} + +void GfxFrameout::printVisiblePlaneList(Console *con) const { + printPlaneListInternal(con, _visiblePlanes); +} + +void GfxFrameout::printPlaneItemListInternal(Console *con, const ScreenItemList &screenItemList) const { + ScreenItemList::size_type i = 0; + for (ScreenItemList::const_iterator sit = screenItemList.begin(); sit != screenItemList.end(); sit++) { + ScreenItem *screenItem = *sit; + con->debugPrintf("%2d: ", i++); + screenItem->printDebugInfo(con); + } +} + +void GfxFrameout::printPlaneItemList(Console *con, const reg_t planeObject) const { + Plane *p = _planes.findByObject(planeObject); + + if (p == nullptr) { + con->debugPrintf("Plane does not exist"); + return; + } + + printPlaneItemListInternal(con, p->_screenItemList); +} + +void GfxFrameout::printVisiblePlaneItemList(Console *con, const reg_t planeObject) const { + Plane *p = _visiblePlanes.findByObject(planeObject); + + if (p == nullptr) { + con->debugPrintf("Plane does not exist"); + return; + } + + printPlaneItemListInternal(con, p->_screenItemList); } } // End of namespace Sci diff --git a/engines/sci/graphics/frameout.h b/engines/sci/graphics/frameout.h index d1a706e8de..8ed95a00de 100644 --- a/engines/sci/graphics/frameout.h +++ b/engines/sci/graphics/frameout.h @@ -23,86 +23,132 @@ #ifndef SCI_GRAPHICS_FRAMEOUT_H #define SCI_GRAPHICS_FRAMEOUT_H -namespace Sci { +#include "sci/graphics/plane32.h" +#include "sci/graphics/screen_item32.h" -class GfxPicture; +namespace Sci { +// TODO: Don't do this this way +int splitRects(Common::Rect r, const Common::Rect &other, Common::Rect(&outRects)[4]); -struct PlaneLineEntry { - reg_t hunkId; - Common::Point startPoint; - Common::Point endPoint; - byte color; - byte priority; - byte control; +// TODO: Verify display styles and adjust names appropriately for +// types 1 through 12 & 15 (others are correct) +// Names should be: +// * VShutterIn, VShutterOut +// * HShutterIn, HShutterOut +// * WipeLeft, WipeRight, WipeDown, WipeUp +// * PixelDissolve +// * ShutDown and Kill? (and Plain and Fade?) +enum ShowStyleType /* : uint8 */ { + kShowStyleNone = 0, + kShowStyleHShutterOut = 1, + kShowStyleHShutterIn = 2, + kShowStyleVShutterOut = 3, + kShowStyleVShutterIn = 4, + kShowStyleWipeLeft = 5, + kShowStyleWipeRight = 6, + kShowStyleWipeUp = 7, + kShowStyleWipeDown = 8, + kShowStyleIrisOut = 9, + kShowStyleIrisIn = 10, + kShowStyle11 = 11, + kShowStyle12 = 12, + kShowStyleFadeOut = 13, + kShowStyleFadeIn = 14, + // TODO: Only in SCI3 + kShowStyleUnknown = 15 }; -typedef Common::List<PlaneLineEntry> PlaneLineList; - -struct PlaneEntry { - reg_t object; - int16 priority; - int16 lastPriority; - int16 planeOffsetX; - int16 planeOffsetY; - GuiResourceId pictureId; - Common::Rect planeRect; - Common::Rect planeClipRect; - Common::Rect upscaledPlaneRect; - Common::Rect upscaledPlaneClipRect; - bool planePictureMirrored; - byte planeBack; - PlaneLineList lines; -}; +/** + * Show styles represent transitions applied to draw planes. + * One show style per plane can be active at a time. + */ +struct ShowStyleEntry { + /** + * The ID of the plane this show style belongs to. + * In SCI2.1mid (at least SQ6), per-plane transitions + * were removed and a single plane ID is used. + */ + reg_t plane; -typedef Common::List<PlaneEntry> PlaneList; - -struct FrameoutEntry { - uint16 givenOrderNr; - reg_t object; - GuiResourceId viewId; - int16 loopNo; - int16 celNo; - int16 x, y, z; - int16 priority; - uint16 signal; - uint16 scaleSignal; - int16 scaleX; - int16 scaleY; - Common::Rect celRect; - GfxPicture *picture; - int16 picStartX; - int16 picStartY; - bool visible; -}; + /** + * The type of the transition. + */ + ShowStyleType type; -typedef Common::List<FrameoutEntry *> FrameoutList; + // TODO: This name is probably incorrect + bool fadeUp; -struct PlanePictureEntry { - reg_t object; - int16 startX; - int16 startY; - GuiResourceId pictureId; - GfxPicture *picture; - FrameoutEntry *pictureCels; // temporary -}; + /** + * The number of steps for the show style. + */ + int16 divisions; -typedef Common::List<PlanePictureEntry> PlanePictureList; + // NOTE: This property exists from SCI2 through at least + // SCI2.1mid but is never used in the actual processing + // of the styles? + int unknownC; -struct ScrollTextEntry { - reg_t bitmapHandle; - reg_t kWindow; - uint16 x; - uint16 y; -}; + /** + * The color used by transitions that draw CelObjColor + * screen items. -1 for transitions that do not draw + * screen items. + */ + int16 color; + + // TODO: Probably uint32 + // TODO: This field probably should be used in order to + // provide time-accurate processing of show styles. In the + // actual SCI engine (at least 2–2.1mid) it appears that + // style transitions are drawn “as fast as possible”, one + // step per loop, even though this delay field exists + int delay; + + // TODO: Probably bool, but never seems to be true? + int animate; -typedef Common::Array<ScrollTextEntry> ScrollTextList; + /** + * The wall time at which the next step of the animation + * should execute. + */ + uint32 nextTick; -enum ViewScaleSignals32 { - kScaleSignalDoScaling32 = 0x0001, // enables scaling when drawing that cel (involves scaleX and scaleY) - kScaleSignalUnk1 = 0x0002, // unknown - kScaleSignalDisableGlobalScaling32 = 0x0004 + /** + * During playback of the show style, the current step + * (out of divisions). + */ + int currentStep; + + /** + * The next show style. + */ + ShowStyleEntry *next; + + /** + * Whether or not this style has finished running and + * is ready for disposal. + */ + bool processed; + + // + // Engine specific properties for SCI2.1mid through SCI3 + // + + /** + * The number of entries in the fadeColorRanges array. + */ + uint8 fadeColorRangesCount; + + /** + * A pointer to an dynamically sized array of palette + * indexes, in the order [ fromColor, toColor, ... ]. + * Only colors within this range are transitioned. + */ + uint16 *fadeColorRanges; }; +typedef Common::Array<DrawList> ScreenItemListList; +typedef Common::Array<RectList> EraseListList; + class GfxCache; class GfxCoordAdjuster32; class GfxPaint32; @@ -114,69 +160,293 @@ class GfxScreen; * Roughly equivalent to GraphicsMgr in the actual SCI engine. */ class GfxFrameout { +private: + bool _isHiRes; + GfxCache *_cache; + GfxCoordAdjuster32 *_coordAdjuster; + GfxPalette32 *_palette; + ResourceManager *_resMan; + GfxScreen *_screen; + SegManager *_segMan; + GfxPaint32 *_paint32; + public: GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette32 *palette, GfxPaint32 *paint32); ~GfxFrameout(); - void kernelAddPlane(reg_t object); - void kernelUpdatePlane(reg_t object); - void kernelDeletePlane(reg_t object); - void applyGlobalScaling(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 celHeight); - void kernelAddScreenItem(reg_t object); - void kernelUpdateScreenItem(reg_t object); - void kernelDeleteScreenItem(reg_t object); - void deletePlaneItems(reg_t planeObject); - FrameoutEntry *findScreenItem(reg_t object); - int16 kernelGetHighPlanePri(); - void kernelAddPicAt(reg_t planeObj, GuiResourceId pictureId, int16 pictureX, int16 pictureY); - void kernelFrameout(); - - void addPlanePicture(reg_t object, GuiResourceId pictureId, uint16 startX, uint16 startY = 0); - void deletePlanePictures(reg_t object); - reg_t addPlaneLine(reg_t object, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control); - void updatePlaneLine(reg_t object, reg_t hunkId, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control); - void deletePlaneLine(reg_t object, reg_t hunkId); void clear(); + void syncWithScripts(bool addElements); // this is what Game::restore does, only needed when our ScummVM dialogs are patched in + void run(); - // Scroll text functions - void addScrollTextEntry(Common::String &text, reg_t kWindow, uint16 x, uint16 y, bool replace); - void showCurrentScrollText(); - void initScrollText(uint16 maxItems) { _maxScrollTexts = maxItems; } - void clearScrollTexts(); - void firstScrollText() { if (_scrollTexts.size() > 0) _curScrollText = 0; } - void lastScrollText() { if (_scrollTexts.size() > 0) _curScrollText = _scrollTexts.size() - 1; } - void prevScrollText() { if (_curScrollText > 0) _curScrollText--; } - void nextScrollText() { if (_curScrollText + 1 < (uint16)_scrollTexts.size()) _curScrollText++; } - void toggleScrollText(bool show) { _showScrollText = show; } +#pragma mark - +#pragma mark Screen items +private: + void deleteScreenItem(ScreenItem *screenItem, const reg_t plane); + void remapMarkRedraw(); - void printPlaneList(Console *con); - void printPlaneItemList(Console *con, reg_t planeObject); +public: + void kernelAddScreenItem(const reg_t object); + void kernelUpdateScreenItem(const reg_t object); + void kernelDeleteScreenItem(const reg_t object); + void kernelSetNowSeen(const reg_t screenItemObject) const; +#pragma mark - +#pragma mark Planes private: - bool _isHiRes; + /** + * The list of planes (i.e. layers) that have been added + * to the screen. + * + * @note This field is on `GraphicsMgr.screen` in SCI + * engine. + */ + PlaneList _planes; - void showVideo(); - void createPlaneItemList(reg_t planeObject, FrameoutList &itemList); - bool isPictureOutOfView(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 planeOffsetX, int16 planeOffsetY); - void drawPicture(FrameoutEntry *itemEntry, int16 planeOffsetX, int16 planeOffsetY, bool planePictureMirrored); + /** + * Updates an existing plane with properties from the + * given VM object. + */ + void updatePlane(Plane &plane); - SegManager *_segMan; - ResourceManager *_resMan; - GfxCoordAdjuster32 *_coordAdjuster; - GfxCache *_cache; - GfxPalette32 *_palette; - GfxScreen *_screen; - GfxPaint32 *_paint32; +public: + /** + * Creates and adds a new plane to the plane list, or + * cancels deletion and updates an already-existing + * plane if a plane matching the given plane VM object + * already exists within the current plane list. + * + * @note This method is on Screen in SCI engine, but it + * is only ever called on `GraphicsMgr.screen`. + */ + void addPlane(Plane &plane); - FrameoutList _screenItems; - PlaneList _planes; - PlanePictureList _planePictures; - ScrollTextList _scrollTexts; - int16 _curScrollText; - bool _showScrollText; - uint16 _maxScrollTexts; + /** + * Deletes a plane within the current plane list. + * + * @note This method is on Screen in SCI engine, but it + * is only ever called on `GraphicsMgr.screen`. + */ + void deletePlane(Plane &plane); - void sortPlanes(); + const PlaneList &getPlanes() const { + return _planes; + } + const PlaneList &getVisiblePlanes() const { + return _visiblePlanes; + } + void kernelAddPlane(const reg_t object); + void kernelUpdatePlane(const reg_t object); + void kernelDeletePlane(const reg_t object); + void kernelMovePlaneItems(const reg_t object, const int16 deltaX, const int16 deltaY, const bool scrollPics); + int16 kernelGetHighPlanePri(); + +#pragma mark - +#pragma mark Pics +public: + void kernelAddPicAt(const reg_t planeObject, const GuiResourceId pictureId, const int16 pictureX, const int16 pictureY, const bool mirrorX); + +#pragma mark - + + // TODO: Remap-related? + void kernelSetPalStyleRange(const uint8 fromColor, const uint8 toColor); + +#pragma mark - +#pragma mark Transitions +private: + int *_dissolveSequenceSeeds; + int16 *_defaultDivisions; + int16 *_defaultUnknownC; + + /** + * TODO: Documentation + */ + ShowStyleEntry *_showStyles; + + inline ShowStyleEntry *findShowStyleForPlane(const reg_t planeObj) const; + inline ShowStyleEntry *deleteShowStyleInternal(ShowStyleEntry *const showStyle); + void processShowStyles(); + bool processShowStyleNone(ShowStyleEntry *showStyle); + bool processShowStyleMorph(ShowStyleEntry *showStyle); + bool processShowStyleFade(const int direction, ShowStyleEntry *showStyle); + +public: + // NOTE: This signature is taken from SCI3 Phantasmagoria 2 + // and is valid for all implementations of SCI32 + void kernelSetShowStyle(const uint16 argc, const reg_t planeObj, const ShowStyleType type, const int16 seconds, const int16 direction, const int16 priority, const int16 animate, const int16 frameOutNow, reg_t pFadeArray, int16 divisions, const int16 blackScreen); + +#pragma mark - +#pragma mark Rendering +private: + /** + * TODO: Documentation + */ + int8 _styleRanges[256]; + + /** + * The internal display pixel buffer. During frameOut, + * this buffer is drawn into according to the draw and + * erase rects calculated by `calcLists`, then drawn out + * to the hardware surface according to the `_showList` + * rects (which are also calculated by `calcLists`). + */ + Buffer _currentBuffer; + + /** + * TODO: Documentation + */ + bool _remapOccurred; + + /** + * Whether or not the data in the current buffer is what + * is visible to the user. During rendering updates, + * this flag is set to false. + */ + bool _frameNowVisible; + + /** + * TODO: Document + * TODO: Depending upon if the engine ever modifies this + * rect, it may be stupid to store it separately instead + * of just getting width/height from GfxScreen. + * + * @note This field is on `GraphicsMgr.screen` in SCI + * engine. + */ + Common::Rect _screenRect; + + /** + * A list of rectangles, in display coordinates, that + * represent portions of the internal screen buffer that + * should be drawn to the hardware display surface. + * + * @note This field is on `GraphicsMgr.screen` in SCI + * engine. + */ + RectList _showList; + + /** + * The amount of extra overdraw that is acceptable when + * merging two show list rectangles together into a + * single larger rectangle. + * + * @note This field is on `GraphicsMgr.screen` in SCI + * engine. + */ + int _overdrawThreshold; + + /** + * A list of planes that are currently drawn to the + * hardware display surface. Used to calculate + * differences in plane properties between the last + * frame and current frame. + * + * @note This field is on `GraphicsMgr.visibleScreen` in + * SCI engine. + */ + PlaneList _visiblePlanes; + + /** + * Calculates the location and dimensions of dirty rects + * over the entire screen for rendering the next frame. + * The draw and erase lists in `drawLists` and + * `eraseLists` each represent one plane on the screen. + */ + void calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &calcRect); + + /** + * Erases the areas in the given erase list from the + * visible screen buffer by filling them with the color + * from the corresponding plane. This is an optimisation + * for colored-type planes only; other plane types have + * to be redrawn from pixel data. + */ + void drawEraseList(const RectList &eraseList, const Plane &plane); + + /** + * Draws all screen items from the given draw list to + * the visible screen buffer. + */ + void drawScreenItemList(const DrawList &screenItemList); + + /** + * Adds a new rectangle to the list of regions to write + * out to the hardware. The provided rect may be merged + * into an existing rectangle to reduce the number of + * blit operations. + */ + void mergeToShowList(const Common::Rect &drawRect, RectList &showList, const int overdrawThreshold); + + /** + * TODO: Documentation + */ + void palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry *showStyle); + + /** + * Writes the internal frame buffer out to hardware and + * clears the show list. + */ + void showBits(); + +public: + /** + * Whether palMorphFrameOut should be used instead of + * frameOut for rendering. Used by kMorphOn to + * explicitly enable palMorphFrameOut for one frame. + */ + bool _palMorphIsOn; + + inline Buffer &getCurrentBuffer() { + return _currentBuffer; + } + + void kernelFrameOut(const bool showBits); + + /** + * Updates the internal screen buffer for the next + * frame. If `shouldShowBits` is true, also sends the + * buffer to hardware. + */ + void frameOut(const bool shouldShowBits, const Common::Rect &rect = Common::Rect()); + + /** + * Modifies the raw pixel data for the next frame with + * new palette indexes based on matched style ranges. + */ + void alterVmap(const Palette &palette1, const Palette &palette2, const int8 style, const int8 *const styleRanges); + + // NOTE: This function is used within ScreenItem subsystem and assigned + // to various booleanish fields that seem to represent the state of the + // screen item (created, updated, deleted). In GK1/DOS, Phant1/m68k, + // SQ6/DOS, SQ6/Win, and Phant2/Win, this function simply returns 1. If + // you know of any game/environment where this function returns some + // value other than 1, or if you used to work at Sierra and can explain + // why this is a thing (and if anyone needs to care about it), please + // open a ticket!! + inline int getScreenCount() const { + return 1; + }; + +#pragma mark - +#pragma mark Mouse cursor +private: + /** + * Determines whether or not the point given by + * `position` is inside of the given screen item. + */ + bool isOnMe(const ScreenItem &screenItem, const Plane &plane, const Common::Point &position, const bool checkPixel) const; + +public: + reg_t kernelIsOnMe(const reg_t object, const Common::Point &position, const bool checkPixel) const; + +#pragma mark - +#pragma mark Debugging +public: + void printPlaneList(Console *con) const; + void printVisiblePlaneList(Console *con) const; + void printPlaneListInternal(Console *con, const PlaneList &planeList) const; + void printPlaneItemList(Console *con, const reg_t planeObject) const; + void printVisiblePlaneItemList(Console *con, const reg_t planeObject) const; + void printPlaneItemListInternal(Console *con, const ScreenItemList &screenItemList) const; }; } // End of namespace Sci diff --git a/engines/sci/graphics/helpers.h b/engines/sci/graphics/helpers.h index e5b9f2aaed..19dddd74b8 100644 --- a/engines/sci/graphics/helpers.h +++ b/engines/sci/graphics/helpers.h @@ -26,6 +26,11 @@ #include "common/endian.h" // for READ_LE_UINT16 #include "common/rect.h" #include "common/serializer.h" +#ifdef ENABLE_SCI32 +#include "common/rational.h" +#include "graphics/pixelformat.h" +#include "graphics/surface.h" +#endif #include "sci/engine/vm_types.h" namespace Sci { @@ -45,6 +50,9 @@ typedef int16 TextAlignment; #define PORTS_FIRSTWINDOWID 2 #define PORTS_FIRSTSCRIPTWINDOWID 3 +#ifdef ENABLE_SCI32 +#define PRINT_RECT(x) (x).left,(x).top,(x).right,(x).bottom +#endif struct Port { uint16 id; @@ -118,6 +126,102 @@ struct Window : public Port, public Common::Serializable { } }; +#ifdef ENABLE_SCI32 +/** + * Multiplies a rectangle by two ratios with default + * rounding. Modifies the rect directly. + */ +inline void mul(Common::Rect &rect, const Common::Rational &ratioX, const Common::Rational &ratioY) { + rect.left = (rect.left * ratioX).toInt(); + rect.top = (rect.top * ratioY).toInt(); + rect.right = (rect.right * ratioX).toInt(); + rect.bottom = (rect.bottom * ratioY).toInt(); +} + +/** + * Multiplies a rectangle by two ratios with default + * rounding. Modifies the rect directly. Uses inclusive + * rectangle rounding. + */ +inline void mulinc(Common::Rect &rect, const Common::Rational &ratioX, const Common::Rational &ratioY) { + rect.left = (rect.left * ratioX).toInt(); + rect.top = (rect.top * ratioY).toInt(); + rect.right = ((rect.right - 1) * ratioX).toInt() + 1; + rect.bottom = ((rect.bottom - 1) * ratioY).toInt() + 1; +} + +/** + * Multiplies a number by a rational number, rounding up to + * the nearest whole number. + */ +inline int mulru(const int value, const Common::Rational &ratio, const int extra = 0) { + int num = (value + extra) * ratio.getNumerator(); + int result = num / ratio.getDenominator(); + if (num > ratio.getDenominator() && num % ratio.getDenominator()) { + ++result; + } + return result - extra; +} + +/** + * Multiplies a point by two rational numbers for X and Y, + * rounding up to the nearest whole number. Modifies the + * point directly. + */ +inline void mulru(Common::Point &point, const Common::Rational &ratioX, const Common::Rational &ratioY) { + point.x = mulru(point.x, ratioX); + point.y = mulru(point.y, ratioY); +} + +/** + * Multiplies a point by two rational numbers for X and Y, + * rounding up to the nearest whole number. Modifies the + * rect directly. + */ +inline void mulru(Common::Rect &rect, const Common::Rational &ratioX, const Common::Rational &ratioY, const int extra) { + rect.left = mulru(rect.left, ratioX); + rect.top = mulru(rect.top, ratioY); + rect.right = mulru(rect.right - 1, ratioX, extra) + 1; + rect.bottom = mulru(rect.bottom - 1, ratioY, extra) + 1; +} + +struct Buffer : public Graphics::Surface { + uint16 screenWidth; + uint16 screenHeight; + uint16 scriptWidth; + uint16 scriptHeight; + + Buffer(const uint16 width, const uint16 height, uint8 *const pix) : + screenWidth(width), + screenHeight(height), + // TODO: These values are not correct for all games. Script + // dimensions were hard-coded per game in the original + // interpreter. Search all games for their internal script + // dimensions and set appropriately. (This code does not + // appear to exist at all in SCI3, which uses 640x480.) + scriptWidth(320), + scriptHeight(200) { + init(width, height, width, pix, Graphics::PixelFormat::createFormatCLUT8()); + } + + void clear(const uint8 value) { + memset(pixels, value, w * h); + } + + inline uint8 *getAddress(const uint16 x, const uint16 y) { + return (uint8 *)getBasePtr(x, y); + } + + inline uint8 *getAddressSimRes(const uint16 x, const uint16 y) { + return (uint8*)pixels + (y * w * screenHeight / scriptHeight) + (x * screenWidth / scriptWidth); + } + + bool isNull() { + return pixels == nullptr; + } +}; +#endif + struct Color { byte used; byte r, g, b; @@ -146,7 +250,7 @@ struct Palette { } } - return false; + return true; } inline bool operator!=(const Palette &other) const { return !(*this == other); diff --git a/engines/sci/graphics/lists32.h b/engines/sci/graphics/lists32.h new file mode 100644 index 0000000000..4f74c77325 --- /dev/null +++ b/engines/sci/graphics/lists32.h @@ -0,0 +1,192 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SCI_GRAPHICS_LISTS32_H +#define SCI_GRAPHICS_LISTS32_H + +#include "common/array.h" + +namespace Sci { + +/** + * StablePointerArray holds pointers in a fixed-size array + * that maintains position of erased items until `pack` is + * called. It is used by DrawList, RectList, and + * ScreenItemList. StablePointerArray takes ownership of + * all pointers that are passed to it and deletes them when + * calling `erase` or when destroying the + * StablePointerArray. + */ +template<class T, uint N> +class StablePointerArray { + uint _size; + T *_items[N]; + +public: + typedef T **iterator; + typedef T *const *const_iterator; + typedef T *value_type; + typedef uint size_type; + + StablePointerArray() : _size(0), _items() {} + StablePointerArray(const StablePointerArray &other) : _size(other._size) { + for (size_type i = 0; i < _size; ++i) { + if (other._items[i] == nullptr) { + _items[i] = nullptr; + } else { + _items[i] = new T(*other._items[i]); + } + } + } + ~StablePointerArray() { + for (size_type i = 0; i < _size; ++i) { + delete _items[i]; + } + } + + void operator=(const StablePointerArray &other) { + clear(); + _size = other._size; + for (size_type i = 0; i < _size; ++i) { + if (other._items[i] == nullptr) { + _items[i] = nullptr; + } else { + _items[i] = new T(*other._items[i]); + } + } + } + + T *const &operator[](size_type index) const { + assert(index < _size); + return _items[index]; + } + + T *&operator[](size_type index) { + assert(index < _size); + return _items[index]; + } + + /** + * Adds a new pointer to the array. + */ + void add(T *item) { + assert(_size < N); + _items[_size++] = item; + } + + iterator begin() { + return _items; + } + + const_iterator begin() const { + return _items; + } + + void clear() { + for (size_type i = 0; i < _size; ++i) { + delete _items[i]; + _items[i] = nullptr; + } + + _size = 0; + } + + iterator end() { + return _items + _size; + } + + const_iterator end() const { + return _items + _size; + } + + /** + * Erases the object pointed to by the given iterator. + */ + void erase(T *item) { + for (iterator it = begin(); it != end(); ++it) { + if (*it == item) { + delete *it; + *it = nullptr; + break; + } + } + } + + /** + * Erases the object pointed to by the given iterator. + */ + void erase(iterator &it) { + assert(it >= _items && it < _items + _size); + delete *it; + *it = nullptr; + } + + /** + * Erases the object pointed to at the given index. + */ + void erase_at(size_type index) { + assert(index < _size); + + delete _items[index]; + _items[index] = nullptr; + } + + /** + * Removes freed pointers from the pointer list. + */ + size_type pack() { + iterator freePtr = begin(); + size_type newSize = 0; + + for (iterator it = begin(), last = end(); it != last; ++it) { + if (*it != nullptr) { + *freePtr = *it; + ++freePtr; + ++newSize; + } + } + + _size = newSize; + return newSize; + } + + /** + * The number of populated slots in the array. The size + * of the array will only go down once `pack` is called. + */ + size_type size() const { + return _size; + } +}; + +template<typename T> +class FindByObject { + const reg_t &_object; +public: + FindByObject(const reg_t &object) : _object(object) {} + bool operator()(const T entry) const { + return entry && entry->_object == _object; + } +}; + +} // End of namespace Sci +#endif diff --git a/engines/sci/graphics/palette.cpp b/engines/sci/graphics/palette.cpp index 106924ecd3..1514ad838f 100644 --- a/engines/sci/graphics/palette.cpp +++ b/engines/sci/graphics/palette.cpp @@ -32,6 +32,7 @@ #include "sci/graphics/cache.h" #include "sci/graphics/maciconbar.h" #include "sci/graphics/palette.h" +#include "sci/graphics/remap.h" #include "sci/graphics/screen.h" #include "sci/graphics/view.h" @@ -103,9 +104,6 @@ GfxPalette::GfxPalette(ResourceManager *resMan, GfxScreen *screen) default: error("GfxPalette: Unknown view type"); } - - _remapOn = false; - resetRemapping(); } GfxPalette::~GfxPalette() { @@ -310,7 +308,7 @@ void GfxPalette::set(Palette *newPalette, bool force, bool forceRealMerge) { uint32 systime = _sysPalette.timestamp; if (force || newPalette->timestamp != systime) { - // SCI1.1+ doesnt do real merging anymore, but simply copying over the used colors from other palettes + // SCI1.1+ doesn't do real merging anymore, but simply copying over the used colors from other palettes // There are some games with inbetween SCI1.1 interpreters, use real merging for them (e.g. laura bow 2 demo) if ((forceRealMerge) || (_useMerging)) _sysPaletteChanged |= merge(newPalette, force, forceRealMerge); @@ -336,79 +334,6 @@ void GfxPalette::set(Palette *newPalette, bool force, bool forceRealMerge) { } } -byte GfxPalette::remapColor(byte remappedColor, byte screenColor) { - assert(_remapOn); - if (_remappingType[remappedColor] == kRemappingByRange) - return _remappingByRange[screenColor]; - else if (_remappingType[remappedColor] == kRemappingByPercent) - return _remappingByPercent[screenColor]; - else - error("remapColor(): Color %d isn't remapped", remappedColor); - - return 0; // should never reach here -} - -void GfxPalette::resetRemapping() { - _remapOn = false; - _remappingPercentToSet = 0; - - for (int i = 0; i < 256; i++) { - _remappingType[i] = kRemappingNone; - _remappingByPercent[i] = i; - _remappingByRange[i] = i; - } -} - -void GfxPalette::setRemappingPercent(byte color, byte percent) { - _remapOn = true; - - // We need to defer the setup of the remapping table every time the screen - // palette is changed, so that kernelFindColor() can find the correct - // colors. Set it once here, in case the palette stays the same and update - // it on each palette change by copySysPaletteToScreen(). - _remappingPercentToSet = percent; - - for (int i = 0; i < 256; i++) { - byte r = _sysPalette.colors[i].r * _remappingPercentToSet / 100; - byte g = _sysPalette.colors[i].g * _remappingPercentToSet / 100; - byte b = _sysPalette.colors[i].b * _remappingPercentToSet / 100; - _remappingByPercent[i] = kernelFindColor(r, g, b); - } - - _remappingType[color] = kRemappingByPercent; -} - -void GfxPalette::setRemappingPercentGray(byte color, byte percent) { - _remapOn = true; - - // We need to defer the setup of the remapping table every time the screen - // palette is changed, so that kernelFindColor() can find the correct - // colors. Set it once here, in case the palette stays the same and update - // it on each palette change by copySysPaletteToScreen(). - _remappingPercentToSet = percent; - - // Note: This is not what the original does, but the results are the same visually - for (int i = 0; i < 256; i++) { - byte rComponent = (byte)(_sysPalette.colors[i].r * _remappingPercentToSet * 0.30 / 100); - byte gComponent = (byte)(_sysPalette.colors[i].g * _remappingPercentToSet * 0.59 / 100); - byte bComponent = (byte)(_sysPalette.colors[i].b * _remappingPercentToSet * 0.11 / 100); - byte luminosity = rComponent + gComponent + bComponent; - _remappingByPercent[i] = kernelFindColor(luminosity, luminosity, luminosity); - } - - _remappingType[color] = kRemappingByPercent; -} - -void GfxPalette::setRemappingRange(byte color, byte from, byte to, byte base) { - _remapOn = true; - - for (int i = from; i <= to; i++) { - _remappingByRange[i] = i + base; - } - - _remappingType[color] = kRemappingByRange; -} - bool GfxPalette::insert(Palette *newPalette, Palette *destPalette) { bool paletteChanged = false; @@ -589,15 +514,8 @@ void GfxPalette::copySysPaletteToScreen() { } } - // Check if we need to reset remapping by percent with the new colors. - if (_remappingPercentToSet) { - for (int i = 0; i < 256; i++) { - byte r = _sysPalette.colors[i].r * _remappingPercentToSet / 100; - byte g = _sysPalette.colors[i].g * _remappingPercentToSet / 100; - byte b = _sysPalette.colors[i].b * _remappingPercentToSet / 100; - _remappingByPercent[i] = kernelFindColor(r, g, b); - } - } + if (g_sci->_gfxRemap16) + g_sci->_gfxRemap16->updateRemapping(); g_system->getPaletteManager()->setPalette(bpal, 0, 256); } diff --git a/engines/sci/graphics/palette.h b/engines/sci/graphics/palette.h index 61a8d36cb9..7335dc59d0 100644 --- a/engines/sci/graphics/palette.h +++ b/engines/sci/graphics/palette.h @@ -35,12 +35,6 @@ class GfxScreen; #define SCI_PALETTE_MATCH_PERFECT 0x8000 #define SCI_PALETTE_MATCH_COLORMASK 0xFF -enum ColorRemappingType { - kRemappingNone = 0, - kRemappingByRange = 1, - kRemappingByPercent = 2 -}; - /** * Palette class, handles palette operations like changing intensity, setting up the palette, merging different palettes */ @@ -64,15 +58,6 @@ public: void getSys(Palette *pal); uint16 getTotalColorCount() const { return _totalScreenColors; } - void resetRemapping(); - void setRemappingPercent(byte color, byte percent); - void setRemappingPercentGray(byte color, byte percent); - void setRemappingRange(byte color, byte from, byte to, byte base); - bool isRemapped(byte color) const { - return _remapOn && (_remappingType[color] != kRemappingNone); - } - byte remapColor(byte remappedColor, byte screenColor); - void setOnScreen(); void copySysPaletteToScreen(); @@ -138,12 +123,6 @@ protected: int _palVarySignal; uint16 _totalScreenColors; - bool _remapOn; - ColorRemappingType _remappingType[256]; - byte _remappingByPercent[256]; - byte _remappingByRange[256]; - uint16 _remappingPercentToSet; - void loadMacIconBarPalette(); byte *_macClut; }; diff --git a/engines/sci/graphics/palette32.cpp b/engines/sci/graphics/palette32.cpp index e330b5620b..6844011675 100644 --- a/engines/sci/graphics/palette32.cpp +++ b/engines/sci/graphics/palette32.cpp @@ -28,29 +28,41 @@ #include "sci/event.h" #include "sci/resource.h" #include "sci/graphics/palette32.h" +#include "sci/graphics/remap.h" #include "sci/graphics/screen.h" namespace Sci { - + GfxPalette32::GfxPalette32(ResourceManager *resMan, GfxScreen *screen) : GfxPalette(resMan, screen), + // Palette versioning + _version(1), + _versionUpdated(false), + _sourcePalette(_sysPalette), + _nextPalette(_sysPalette), + // Clut _clutTable(nullptr), - // Palette cycling - _cyclers(), _cycleMap(), // Palette varying - _sourcePalette(_sysPalette), _nextPalette(_sysPalette), - _varyTime(0), _varyDirection(0), _varyTargetPercent(0), - _varyTargetPalette(nullptr), _varyStartPalette(nullptr), - _varyFromColor(0), _varyToColor(255), _varyNumTimesPaused(0), - _varyPercent(_varyTargetPercent), _varyLastTick(0), - // Palette versioning - _version(1), _versionUpdated(false) { - memset(_fadeTable, 100, sizeof(_fadeTable)); - + _varyStartPalette(nullptr), + _varyTargetPalette(nullptr), + _varyFromColor(0), + _varyToColor(255), + _varyLastTick(0), + _varyTime(0), + _varyDirection(0), + _varyTargetPercent(0), + _varyNumTimesPaused(0), + // Palette cycling + _cyclers(), + _cycleMap() { + _varyPercent = _varyTargetPercent; + for (int i = 0, len = ARRAYSIZE(_fadeTable); i < len; ++i) { + _fadeTable[i] = 100; + } // NOTE: In SCI engine, the palette manager constructor loads // the default palette, but in ScummVM this initialisation // is performed by SciEngine::run; see r49523 for details - } +} GfxPalette32::~GfxPalette32() { unloadClut(); @@ -59,13 +71,17 @@ GfxPalette32::~GfxPalette32() { } inline void mergePaletteInternal(Palette *const to, const Palette *const from) { - for (int i = 0; i < ARRAYSIZE(to->colors); ++i) { + for (int i = 0, len = ARRAYSIZE(to->colors); i < len; ++i) { if (from->colors[i].used) { to->colors[i] = from->colors[i]; } } } +const Palette *GfxPalette32::getNextPalette() const { + return &_nextPalette; +} + void GfxPalette32::submit(Palette &palette) { // TODO: The resource manager in SCI32 retains raw data of palettes from // the ResourceManager (ResourceMgr) through SegManager (MemoryMgr), and @@ -205,17 +221,22 @@ int16 GfxPalette32::matchColor(const byte r, const byte g, const byte b, const i return bestIndex; } -void GfxPalette32::updateForFrame() { +bool GfxPalette32::updateForFrame() { applyAll(); _versionUpdated = false; - // TODO: Implement remapping - // g_sci->_gfxFrameout->remapAllTables(_nextPalette != _sysPalette); + return g_sci->_gfxRemap32->remapAllTables(_nextPalette != _sysPalette); +} + +void GfxPalette32::updateFFrame() { + for (int i = 0; i < ARRAYSIZE(_nextPalette.colors); ++i) { + _nextPalette.colors[i] = _sourcePalette.colors[i]; + } + _versionUpdated = false; + g_sci->_gfxRemap32->remapAllTables(_nextPalette != _sysPalette); } void GfxPalette32::updateHardware() { if (_sysPalette == _nextPalette) { - // TODO: This condition has never been encountered yet - debug("Skipping hardware update because palettes are identical"); return; } @@ -246,9 +267,8 @@ void GfxPalette32::applyAll() { applyFade(); } -// -// Clut -// +#pragma mark - +#pragma mark Colour look-up bool GfxPalette32::loadClut(uint16 clutId) { // loadClut() will load a color lookup table from a clu file and set @@ -300,9 +320,8 @@ void GfxPalette32::unloadClut() { _clutTable = nullptr; } -// -// Palette vary -// +#pragma mark - +#pragma mark Varying inline bool GfxPalette32::createPaletteFromResourceInternal(const GuiResourceId paletteId, Palette *const out) const { Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, paletteId), false); @@ -409,7 +428,7 @@ void GfxPalette32::setVaryPercent(const int16 percent, const int time, const int } int16 GfxPalette32::getVaryPercent() const { - return abs(_varyPercent); + return ABS(_varyPercent); } void GfxPalette32::varyOff() { @@ -542,9 +561,8 @@ void GfxPalette32::applyVary() { } } -// -// Palette cycling -// +#pragma mark - +#pragma mark Cycling inline void GfxPalette32::clearCycleMap(const uint16 fromColor, const uint16 numColorsToClear) { bool *mapEntry = _cycleMap + fromColor; @@ -677,14 +695,8 @@ void GfxPalette32::cycleAllOn() { } void GfxPalette32::cycleAllPause() { - // TODO: The SCI SQ6 cycleAllPause function does not seem to perform - // nullptr checking?? This would definitely cause null pointer - // dereference in SCI code. I have not seen anything actually call - // this function yet, so it is possible it is just unused and broken - // in SCI SQ6. This assert exists so that if this function is called, - // it is noticed and can be rechecked in the actual engine. - // Obviously this code *does* do nullptr checks instead of crashing. :) - assert(0); + // NOTE: The original engine did not check for null pointers in the + // palette cyclers pointer array. for (int i = 0, len = ARRAYSIZE(_cyclers); i < len; ++i) { PalCycler *cycler = _cyclers[i]; if (cycler != nullptr) { @@ -768,11 +780,16 @@ void GfxPalette32::applyCycles() { } } -// -// Palette fading -// +#pragma mark - +#pragma mark Fading -void GfxPalette32::setFade(uint8 percent, uint8 fromColor, uint16 numColorsToFade) { +// NOTE: There are some game scripts (like SQ6 Sierra logo and main menu) that call +// setFade with numColorsToFade set to 256, but other parts of the engine like +// processShowStyleNone use 255 instead of 256. It is not clear if this is because +// the last palette entry is intentionally left unmodified, or if this is a bug +// in the engine. It certainly seems confused because all other places that accept +// color ranges typically receive values in the range of 0–255. +void GfxPalette32::setFade(uint16 percent, uint8 fromColor, uint16 numColorsToFade) { if (fromColor > numColorsToFade) { return; } @@ -794,9 +811,10 @@ void GfxPalette32::applyFade() { Color &color = _nextPalette.colors[i]; - color.r = (int16)color.r * _fadeTable[i] / 100; - color.g = (int16)color.g * _fadeTable[i] / 100; - color.b = (int16)color.b * _fadeTable[i] / 100; + color.r = MIN(255, (uint16)color.r * _fadeTable[i] / 100); + color.g = MIN(255, (uint16)color.g * _fadeTable[i] / 100); + color.b = MIN(255, (uint16)color.b * _fadeTable[i] / 100); } } -} + +} // End of namespace Sci diff --git a/engines/sci/graphics/palette32.h b/engines/sci/graphics/palette32.h index 8744687e4c..a5450776dc 100644 --- a/engines/sci/graphics/palette32.h +++ b/engines/sci/graphics/palette32.h @@ -25,6 +25,7 @@ #include "sci/graphics/palette.h" +namespace Sci { enum PalCyclerDirection { PalCycleBackward = 0, PalCycleForward = 1 @@ -71,188 +72,213 @@ struct PalCycler { uint16 numTimesPaused; }; -namespace Sci { - class GfxPalette32 : public GfxPalette { - public: - GfxPalette32(ResourceManager *resMan, GfxScreen *screen); - ~GfxPalette32(); - - protected: - /** - * The palette revision version. Increments once per game - * loop that changes the source palette. TODO: Possibly - * other areas also change _version, double-check once it - * is all implemented. - */ - uint32 _version; - - /** - * Whether or not the palette manager version was updated - * during this loop. - */ - bool _versionUpdated; - - /** - * The unmodified source palette loaded by kPalette. Additional - * palette entries may be mixed into the source palette by - * CelObj objects, which contain their own palettes. - */ - Palette _sourcePalette; - - /** - * The palette to be used when the hardware is next updated. - * On update, _nextPalette is transferred to _sysPalette. - */ - Palette _nextPalette; - - // SQ6 defines 10 cyclers - PalCycler *_cyclers[10]; - - /** - * The cycle map is used to detect overlapping cyclers. - * According to SCI engine code, when two cyclers overlap, - * a fatal error has occurred and the engine will display - * an error and then exit. - */ - bool _cycleMap[256]; - inline void clearCycleMap(uint16 fromColor, uint16 numColorsToClear); - inline void setCycleMap(uint16 fromColor, uint16 numColorsToClear); - inline PalCycler *getCycler(uint16 fromColor); - - /** - * The fade table records the expected intensity level of each pixel - * in the palette that will be displayed on the next frame. - */ - byte _fadeTable[256]; - - /** - * An optional lookup table used to remap RGB565 colors to a palette - * index. Used by Phantasmagoria 2 in 8-bit color environments. - */ - byte *_clutTable; - - /** - * An optional palette used to describe the source colors used - * in a palette vary operation. If this palette is not specified, - * sourcePalette is used instead. - */ - Palette *_varyStartPalette; - - /** - * An optional palette used to describe the target colors used - * in a palette vary operation. - */ - Palette *_varyTargetPalette; - - /** - * The minimum palette index that has been varied from the - * source palette. 0–255 - */ - uint8 _varyFromColor; - - /** - * The maximum palette index that is has been varied from the - * source palette. 0-255 - */ - uint8 _varyToColor; - - /** - * The tick at the last time the palette vary was updated. - */ - uint32 _varyLastTick; - - /** - * TODO: Document - * The velocity of change in percent? - */ - int _varyTime; - - /** - * TODO: Better documentation - * The direction of change, -1, 0, or 1. - */ - int16 _varyDirection; - - /** - * The amount, in percent, that the vary color is currently - * blended into the source color. - */ - int16 _varyPercent; - - /** - * The target amount that a vary color will be blended into - * the source color. - */ - int16 _varyTargetPercent; - - /** - * The number of time palette varying has been paused. - */ - uint16 _varyNumTimesPaused; - - /** - * Submits a palette to display. Entries marked as “used” in the - * submitted palette are merged into the existing entries of - * _sourcePalette. - */ - void submit(Palette &palette); - - public: - virtual void saveLoadWithSerializer(Common::Serializer &s) override; - - bool kernelSetFromResource(GuiResourceId resourceId, bool force) override; - int16 kernelFindColor(uint16 r, uint16 g, uint16 b) override; - void set(Palette *newPalette, bool force, bool forceRealMerge = false) override; - int16 matchColor(const byte matchRed, const byte matchGreen, const byte matchBlue, const int defaultDifference, int &lastCalculatedDifference, const bool *const matchTable); - - void updateForFrame(); - void updateHardware(); - void applyAll(); - - bool loadClut(uint16 clutId); - byte matchClutColor(uint16 color); - void unloadClut(); - - void kernelPalVarySet(const GuiResourceId paletteId, const int16 percent, const int time, const int16 fromColor, const int16 toColor); - void kernelPalVaryMergeTarget(const GuiResourceId paletteId); - void kernelPalVarySetTarget(const GuiResourceId paletteId); - void kernelPalVarySetStart(const GuiResourceId paletteId); - void kernelPalVaryMergeStart(const GuiResourceId paletteId); - virtual void kernelPalVaryPause(bool pause) override; - - void setVary(const Palette *const targetPalette, const int16 percent, const int time, const int16 fromColor, const int16 toColor); - void setVaryPercent(const int16 percent, const int time, const int16 fromColor, const int16 fromColorAlternate); - int16 getVaryPercent() const; - void varyOff(); - void mergeTarget(const Palette *const palette); - void varyPause(); - void varyOn(); - void setVaryTime(const int time); - void setTarget(const Palette *const palette); - void setStart(const Palette *const palette); - void mergeStart(const Palette *const palette); - private: - bool createPaletteFromResourceInternal(const GuiResourceId paletteId, Palette *const out) const; - Palette getPaletteFromResourceInternal(const GuiResourceId paletteId) const; - void setVaryTimeInternal(const int16 percent, const int time); - public: - void applyVary(); - - void setCycle(const uint8 fromColor, const uint8 toColor, const int16 direction, const int16 delay); - void doCycle(const uint8 fromColor, const int16 speed); - void cycleOn(const uint8 fromColor); - void cyclePause(const uint8 fromColor); - void cycleAllOn(); - void cycleAllPause(); - void cycleOff(const uint8 fromColor); - void cycleAllOff(); - void applyAllCycles(); - void applyCycles(); - - void setFade(const uint8 percent, const uint8 fromColor, const uint16 toColor); - void fadeOff(); - void applyFade(); - }; -} +class GfxPalette32 : public GfxPalette { +public: + GfxPalette32(ResourceManager *resMan, GfxScreen *screen); + ~GfxPalette32(); + +private: + // NOTE: currentPalette in SCI engine is called _sysPalette + // here. + + /** + * The palette revision version. Increments once per game + * loop that changes the source palette. TODO: Possibly + * other areas also change _version, double-check once it + * is all implemented. + */ + uint32 _version; + + /** + * Whether or not the palette manager version was updated + * during this loop. + */ + bool _versionUpdated; + + /** + * The unmodified source palette loaded by kPalette. Additional + * palette entries may be mixed into the source palette by + * CelObj objects, which contain their own palettes. + */ + Palette _sourcePalette; + + /** + * The palette to be used when the hardware is next updated. + * On update, _nextPalette is transferred to _sysPalette. + */ + Palette _nextPalette; + + bool createPaletteFromResourceInternal(const GuiResourceId paletteId, Palette *const out) const; + Palette getPaletteFromResourceInternal(const GuiResourceId paletteId) const; + +public: + virtual void saveLoadWithSerializer(Common::Serializer &s) override; + const Palette *getNextPalette() const; + + bool kernelSetFromResource(GuiResourceId resourceId, bool force) override; + int16 kernelFindColor(uint16 r, uint16 g, uint16 b) override; + void set(Palette *newPalette, bool force, bool forceRealMerge = false) override; + int16 matchColor(const byte matchRed, const byte matchGreen, const byte matchBlue, const int defaultDifference, int &lastCalculatedDifference, const bool *const matchTable); + + /** + * Submits a palette to display. Entries marked as “used” in the + * submitted palette are merged into the existing entries of + * _sourcePalette. + */ + void submit(Palette &palette); + + bool updateForFrame(); + void updateFFrame(); + void updateHardware(); + void applyAll(); + +#pragma mark - +#pragma mark color look-up +private: + /** + * An optional lookup table used to remap RGB565 colors to a palette + * index. Used by Phantasmagoria 2 in 8-bit color environments. + */ + byte *_clutTable; + +public: + bool loadClut(uint16 clutId); + byte matchClutColor(uint16 color); + void unloadClut(); + +#pragma mark - +#pragma mark Varying +private: + /** + * An optional palette used to describe the source colors used + * in a palette vary operation. If this palette is not specified, + * sourcePalette is used instead. + */ + Palette *_varyStartPalette; + + /** + * An optional palette used to describe the target colors used + * in a palette vary operation. + */ + Palette *_varyTargetPalette; + + /** + * The minimum palette index that has been varied from the + * source palette. 0–255 + */ + uint8 _varyFromColor; + + /** + * The maximum palette index that is has been varied from the + * source palette. 0-255 + */ + uint8 _varyToColor; + + /** + * The tick at the last time the palette vary was updated. + */ + uint32 _varyLastTick; + + /** + * The amount of time to elapse, in ticks, between each cycle + * of a palette vary animation. + */ + int _varyTime; + + /** + * The direction of change: -1, 0, or 1. + */ + int16 _varyDirection; + + /** + * The amount, in percent, that the vary color is currently + * blended into the source color. + */ + int16 _varyPercent; + + /** + * The target amount that a vary color will be blended into + * the source color. + */ + int16 _varyTargetPercent; + + /** + * The number of time palette varying has been paused. + */ + uint16 _varyNumTimesPaused; + +public: + void kernelPalVarySet(const GuiResourceId paletteId, const int16 percent, const int time, const int16 fromColor, const int16 toColor); + void kernelPalVaryMergeTarget(const GuiResourceId paletteId); + void kernelPalVarySetTarget(const GuiResourceId paletteId); + void kernelPalVarySetStart(const GuiResourceId paletteId); + void kernelPalVaryMergeStart(const GuiResourceId paletteId); + virtual void kernelPalVaryPause(bool pause) override; + + void setVary(const Palette *const targetPalette, const int16 percent, const int time, const int16 fromColor, const int16 toColor); + void setVaryPercent(const int16 percent, const int time, const int16 fromColor, const int16 fromColorAlternate); + int16 getVaryPercent() const; + void varyOff(); + void mergeTarget(const Palette *const palette); + void varyPause(); + void varyOn(); + void setVaryTime(const int time); + void setTarget(const Palette *const palette); + void setStart(const Palette *const palette); + void mergeStart(const Palette *const palette); + void setVaryTimeInternal(const int16 percent, const int time); + void applyVary(); + +#pragma mark - +#pragma mark Cycling +private: + // SQ6 defines 10 cyclers + PalCycler *_cyclers[10]; + + /** + * The cycle map is used to detect overlapping cyclers. + * According to SCI engine code, when two cyclers overlap, + * a fatal error has occurred and the engine will display + * an error and then exit. + */ + bool _cycleMap[256]; + inline void clearCycleMap(uint16 fromColor, uint16 numColorsToClear); + inline void setCycleMap(uint16 fromColor, uint16 numColorsToClear); + inline PalCycler *getCycler(uint16 fromColor); + +public: + void setCycle(const uint8 fromColor, const uint8 toColor, const int16 direction, const int16 delay); + void doCycle(const uint8 fromColor, const int16 speed); + void cycleOn(const uint8 fromColor); + void cyclePause(const uint8 fromColor); + void cycleAllOn(); + void cycleAllPause(); + void cycleOff(const uint8 fromColor); + void cycleAllOff(); + void applyAllCycles(); + void applyCycles(); + const bool *getCyclemap() { return _cycleMap; } + +#pragma mark - +#pragma mark Fading +private: + /** + * The fade table records the expected intensity level of each pixel + * in the palette that will be displayed on the next frame. + */ + uint16 _fadeTable[256]; + +public: + /** + * Sets the intensity level for a range of palette + * entries. An intensity of zero indicates total + * darkness. Intensity may be set to over 100 percent. + */ + void setFade(const uint16 percent, const uint8 fromColor, const uint16 toColor); + void fadeOff(); + void applyFade(); +}; + +} // End of namespace Sci #endif diff --git a/engines/sci/graphics/picture.cpp b/engines/sci/graphics/picture.cpp index d7ef84dc1e..2eab391afd 100644 --- a/engines/sci/graphics/picture.cpp +++ b/engines/sci/graphics/picture.cpp @@ -209,12 +209,12 @@ void GfxPicture::drawSci32Vga(int16 celNo, int16 drawX, int16 drawY, int16 pictu } // Header - // [headerSize:WORD] [celCount:BYTE] [Unknown:BYTE] [Unknown:WORD] [paletteOffset:DWORD] [Unknown:DWORD] + // 0[headerSize:WORD] 2[celCount:BYTE] 3[Unknown:BYTE] 4[celHeaderSize:WORD] 6[paletteOffset:DWORD] 10[Unknown:WORD] 12[Unknown:WORD] // cel-header follow afterwards, each is 42 bytes // Cel-Header - // [width:WORD] [height:WORD] [displaceX:WORD] [displaceY:WORD] [clearColor:BYTE] [compressed:BYTE] + // 0[width:WORD] 2[height:WORD] 4[displaceX:WORD] 6[displaceY:WORD] 8[clearColor:BYTE] 9[compressed:BYTE] // offset 10-23 is unknown - // [rleOffset:DWORD] [literalOffset:DWORD] [Unknown:WORD] [Unknown:WORD] [priority:WORD] [relativeXpos:WORD] [relativeYpos:WORD] + // 24[rleOffset:DWORD] 28[literalOffset:DWORD] 32[Unknown:WORD] 34[Unknown:WORD] 36[priority:WORD] 38[relativeXpos:WORD] 40[relativeYpos:WORD] cel_headerPos += 42 * celNo; diff --git a/engines/sci/graphics/picture.h b/engines/sci/graphics/picture.h index 2404f99b41..942fa0f107 100644 --- a/engines/sci/graphics/picture.h +++ b/engines/sci/graphics/picture.h @@ -38,6 +38,9 @@ enum { class GfxPorts; class GfxScreen; class GfxPalette; +class GfxCoordAdjuster; +class ResourceManager; +class Resource; /** * Picture class, handles loading and displaying of picture resources diff --git a/engines/sci/graphics/plane32.cpp b/engines/sci/graphics/plane32.cpp new file mode 100644 index 0000000000..d05e4f79e1 --- /dev/null +++ b/engines/sci/graphics/plane32.cpp @@ -0,0 +1,907 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "sci/console.h" +#include "sci/engine/kernel.h" +#include "sci/engine/selector.h" +#include "sci/engine/state.h" +#include "sci/graphics/frameout.h" +#include "sci/graphics/lists32.h" +#include "sci/graphics/plane32.h" +#include "sci/graphics/remap.h" +#include "sci/graphics/screen.h" +#include "sci/graphics/screen_item32.h" + +namespace Sci { +#pragma mark DrawList +void DrawList::add(ScreenItem *screenItem, const Common::Rect &rect) { + DrawItem *drawItem = new DrawItem; + drawItem->screenItem = screenItem; + drawItem->rect = rect; + DrawListBase::add(drawItem); +} + +#pragma mark - +#pragma mark Plane +uint16 Plane::_nextObjectId = 20000; + +Plane::Plane(const Common::Rect &gameRect, PlanePictureCodes pictureId) : +_width(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth), +_height(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight), +_pictureId(pictureId), +_mirrored(false), +_back(0), +_priorityChanged(0), +_object(make_reg(0, _nextObjectId++)), +_redrawAllCount(g_sci->_gfxFrameout->getScreenCount()), +_created(g_sci->_gfxFrameout->getScreenCount()), +_updated(0), +_deleted(0), +_moved(0), +_gameRect(gameRect) { + convertGameRectToPlaneRect(); + _priority = MAX(10000, g_sci->_gfxFrameout->getPlanes().getTopPlanePriority() + 1); + setType(); + _screenRect = _planeRect; +} + +Plane::Plane(reg_t object) : +_width(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth), +_height(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight), +_priorityChanged(false), +_object(object), +_redrawAllCount(g_sci->_gfxFrameout->getScreenCount()), +_created(g_sci->_gfxFrameout->getScreenCount()), +_updated(0), +_deleted(0), +_moved(0) { + SegManager *segMan = g_sci->getEngineState()->_segMan; + _vanishingPoint.x = readSelectorValue(segMan, object, SELECTOR(vanishingX)); + _vanishingPoint.y = readSelectorValue(segMan, object, SELECTOR(vanishingY)); + + _gameRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft)); + _gameRect.top = readSelectorValue(segMan, object, SELECTOR(inTop)); + _gameRect.right = readSelectorValue(segMan, object, SELECTOR(inRight)) + 1; + _gameRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom)) + 1; + convertGameRectToPlaneRect(); + + _back = readSelectorValue(segMan, object, SELECTOR(back)); + _priority = readSelectorValue(segMan, object, SELECTOR(priority)); + _pictureId = readSelectorValue(segMan, object, SELECTOR(picture)); + setType(); + + _mirrored = readSelectorValue(segMan, object, SELECTOR(mirrored)); + _screenRect = _planeRect; + changePic(); +} + +Plane::Plane(const Plane &other) : +_pictureId(other._pictureId), +_mirrored(other._mirrored), +_field_34(other._field_34), _field_38(other._field_38), +_field_3C(other._field_3C), _field_40(other._field_40), +_back(other._back), +_object(other._object), +_priority(other._priority), +_planeRect(other._planeRect), +_gameRect(other._gameRect), +_screenRect(other._screenRect), +_screenItemList(other._screenItemList) {} + +void Plane::operator=(const Plane &other) { + _gameRect = other._gameRect; + _planeRect = other._planeRect; + _vanishingPoint = other._vanishingPoint; + _pictureId = other._pictureId; + _type = other._type; + _mirrored = other._mirrored; + _priority = other._priority; + _back = other._back; + _width = other._width; + _field_34 = other._field_34; + _height = other._height; + _screenRect = other._screenRect; + _field_3C = other._field_3C; + _priorityChanged = other._priorityChanged; +} + +void Plane::init() { + _nextObjectId = 20000; +} + +void Plane::convertGameRectToPlaneRect() { + const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth; + const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight; + const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + + const Ratio ratioX = Ratio(screenWidth, scriptWidth); + const Ratio ratioY = Ratio(screenHeight, scriptHeight); + + _planeRect = _gameRect; + mulru(_planeRect, ratioX, ratioY, 1); +} + +void Plane::printDebugInfo(Console *con) const { + Common::String name; + + if (_object.isNumber()) { + name = "-scummvm-"; + } else { + name = g_sci->getEngineState()->_segMan->getObjectName(_object); + } + + con->debugPrintf("%04x:%04x (%s): type %d, prio %d, pic %d, mirror %d, back %d\n", + PRINT_REG(_object), + name.c_str(), + _type, + _priority, + _pictureId, + _mirrored, + _back + ); + con->debugPrintf(" game rect: (%d, %d, %d, %d), plane rect: (%d, %d, %d, %d)\n screen rect: (%d, %d, %d, %d)\n", + PRINT_RECT(_gameRect), + PRINT_RECT(_planeRect), + PRINT_RECT(_screenRect) + ); + con->debugPrintf(" # screen items: %d\n", _screenItemList.size()); +} + +#pragma mark - +#pragma mark Plane - Pic + +void Plane::addPicInternal(const GuiResourceId pictureId, const Common::Point *position, const bool mirrorX) { + + uint16 celCount = 1000; + for (uint16 celNo = 0; celNo < celCount; ++celNo) { + CelObjPic *celObj = new CelObjPic(pictureId, celNo); + if (celCount == 1000) { + celCount = celObj->_celCount; + } + + ScreenItem *screenItem = new ScreenItem(_object, celObj->_info); + screenItem->_pictureId = pictureId; + screenItem->_mirrorX = mirrorX; + screenItem->_priority = celObj->_priority; + screenItem->_fixPriority = true; + if (position != nullptr) { + screenItem->_position = *position + celObj->_relativePosition; + } else { + screenItem->_position = celObj->_relativePosition; + } + _screenItemList.add(screenItem); + + delete screenItem->_celObj; + screenItem->_celObj = celObj; + } +} + +void Plane::addPic(const GuiResourceId pictureId, const Common::Point &position, const bool mirrorX) { + deletePic(pictureId); + addPicInternal(pictureId, &position, mirrorX); + // NOTE: In SCI engine this method returned the pictureId of the + // plane, but this return value was never used +} + +void Plane::changePic() { + _pictureChanged = false; + + if (_type != kPlaneTypePicture) { + return; + } + + addPicInternal(_pictureId, nullptr, _mirrored); +} + +void Plane::deletePic(const GuiResourceId pictureId) { + for (ScreenItemList::iterator it = _screenItemList.begin(); it != _screenItemList.end(); ++it) { + ScreenItem *screenItem = *it; + if (screenItem->_pictureId == pictureId) { + screenItem->_created = 0; + screenItem->_updated = 0; + screenItem->_deleted = g_sci->_gfxFrameout->getScreenCount(); + } + } +} + +void Plane::deletePic(const GuiResourceId oldPictureId, const GuiResourceId newPictureId) { + deletePic(oldPictureId); + _pictureId = newPictureId; +} + +void Plane::deleteAllPics() { + for (ScreenItemList::iterator it = _screenItemList.begin(); it != _screenItemList.end(); ++it) { + ScreenItem *screenItem = *it; + if (screenItem != nullptr && screenItem->_celInfo.type == kCelTypePic) { + if (screenItem->_created == 0) { + screenItem->_created = 0; + screenItem->_updated = 0; + screenItem->_deleted = g_sci->_gfxFrameout->getScreenCount(); + } else { + _screenItemList.erase(it); + } + } + } + + _screenItemList.pack(); +} + +#pragma mark - +#pragma mark Plane - Rendering + +void Plane::breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList) const { + int index = planeList.findIndexByObject(_object); + + for (DrawList::size_type i = 0; i < drawList.size(); ++i) { + for (PlaneList::size_type j = index + 1; j < planeList.size(); ++j) { + if (planeList[j]->_type != kPlaneTypeTransparent) { + Common::Rect ptr[4]; + int count = splitRects(drawList[i]->rect, planeList[j]->_screenRect, ptr); + if (count != -1) { + for (int k = count - 1; k >= 0; --k) { + drawList.add(drawList[i]->screenItem, ptr[k]); + } + + drawList.erase_at(i); + break; + } + } + } + } + drawList.pack(); +} + +void Plane::breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeList) const { + int index = planeList.findIndexByObject(_object); + + for (RectList::size_type i = 0; i < eraseList.size(); ++i) { + for (PlaneList::size_type j = index + 1; j < planeList.size(); ++j) { + if (planeList[j]->_type != kPlaneTypeTransparent) { + Common::Rect ptr[4]; + + int count = splitRects(*eraseList[i], planeList[j]->_screenRect, ptr); + if (count != -1) { + for (int k = count - 1; k >= 0; --k) { + eraseList.add(ptr[k]); + } + + eraseList.erase_at(i); + break; + } + } + } + } + eraseList.pack(); +} + +void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList) { + ScreenItemList::size_type planeItemCount = _screenItemList.size(); + ScreenItemList::size_type visiblePlaneItemCount = visiblePlane._screenItemList.size(); + + for (ScreenItemList::size_type i = 0; i < planeItemCount; ++i) { + ScreenItem *vitem = nullptr; + // NOTE: The original engine used an array without bounds checking + // so could just get the visible screen item directly; we need to + // verify that the index is actually within the valid range for + // the visible plane before accessing the item to avoid a range + // error. + if (i < visiblePlaneItemCount) { + vitem = visiblePlane._screenItemList[i]; + } + ScreenItem *item = _screenItemList[i]; + + if (i < _screenItemList.size() && item != nullptr) { + if (item->_deleted) { + // add item's rect to erase list + if ( + i < visiblePlane._screenItemList.size() && + vitem != nullptr && + !vitem->_screenRect.isEmpty() + ) { + if (g_sci->_gfxRemap32->getRemapCount()) { + mergeToRectList(vitem->_screenRect, eraseList); + } else { + eraseList.add(vitem->_screenRect); + } + } + } else if (item->_created) { + // add item to draw list + item->calcRects(*this); + + if(!item->_screenRect.isEmpty()) { + if (g_sci->_gfxRemap32->getRemapCount()) { + drawList.add(item, item->_screenRect); + mergeToRectList(item->_screenRect, eraseList); + } else { + drawList.add(item, item->_screenRect); + } + } + } else if (item->_updated) { + // add old rect to erase list, new item to draw list + item->calcRects(*this); + if (g_sci->_gfxRemap32->getRemapCount()) { + // if item and vitem don't overlap, ... + if (item->_screenRect.isEmpty() || + i >= visiblePlaneItemCount || + vitem == nullptr || + vitem->_screenRect.isEmpty() || + !vitem->_screenRect.intersects(item->_screenRect) + ) { + // add item to draw list, and old rect to erase list + if (!item->_screenRect.isEmpty()) { + drawList.add(item, item->_screenRect); + mergeToRectList(item->_screenRect, eraseList); + } + if ( + i < visiblePlaneItemCount && + vitem != nullptr && + !vitem->_screenRect.isEmpty() + ) { + mergeToRectList(vitem->_screenRect, eraseList); + } + } else { + // otherwise, add bounding box of old+new to erase list, + // and item to draw list + + // TODO: This was changed from disasm, verify please! + Common::Rect extendedScreenRect = vitem->_screenRect; + extendedScreenRect.extend(item->_screenRect); + + drawList.add(item, item->_screenRect); + mergeToRectList(extendedScreenRect, eraseList); + } + } else { + // if no active remaps, just add item to draw list and old rect + // to erase list + if (!item->_screenRect.isEmpty()) { + drawList.add(item, item->_screenRect); + } + if ( + i < visiblePlaneItemCount && + vitem != nullptr && + !vitem->_screenRect.isEmpty() + ) { + eraseList.add(vitem->_screenRect); + } + } + } + } + } + + // Remove parts of eraselist/drawlist that are covered by other planes + breakEraseListByPlanes(eraseList, planeList); + breakDrawListByPlanes(drawList, planeList); + + // We store the current size of the drawlist, as we want to loop + // over the currently inserted entries later. + DrawList::size_type drawListSizePrimary = drawList.size(); + + if (/* TODO: dword_C6288 */ false) { // "high resolution pictures"???? + _screenItemList.sort(); + bool encounteredPic = false; + bool v81 = false; + + for (RectList::size_type i = 0; i < eraseList.size(); ++i) { + const Common::Rect *rect = eraseList[i]; + + for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) { + ScreenItem *item = _screenItemList[j]; + + if (j < _screenItemList.size() && item != nullptr) { + if (rect->intersects(item->_screenRect)) { + const Common::Rect intersection = rect->findIntersectingRect(item->_screenRect); + if (!item->_deleted) { + if (encounteredPic) { + if (item->_celInfo.type == kCelTypePic) { + if (v81 || item->_celInfo.celNo == 0) { + drawList.add(item, intersection); + } + } else { + if (!item->_updated && !item->_created) { + drawList.add(item, intersection); + } + v81 = true; + } + } else { + if (!item->_updated && !item->_created) { + drawList.add(item, intersection); + } + if (item->_celInfo.type == kCelTypePic) { + encounteredPic = true; + } + } + } + } + } + } + } + + _screenItemList.unsort(); + } else { + // add all items overlapping the erase list to the draw list + for (RectList::size_type i = 0; i < eraseList.size(); ++i) { + for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) { + ScreenItem *item = _screenItemList[j]; + if ( + item != nullptr && + !item->_created && !item->_updated && !item->_deleted && + eraseList[i]->intersects(item->_screenRect) + ) { + drawList.add(item, eraseList[i]->findIntersectingRect(item->_screenRect)); + } + } + } + } + + if (g_sci->_gfxRemap32->getRemapCount() == 0) { // no remaps active? + // Add all items that overlap with items in the drawlist and have higher + // priority. + + // We only loop over "primary" items in the draw list, skipping + // those that were added because of the erase list in the previous loop, + // or those to be added in this loop. + for (DrawList::size_type i = 0; i < drawListSizePrimary; ++i) { + DrawItem *dli = drawList[i]; + + for (ScreenItemList::size_type j = 0; j < planeItemCount; ++j) { + ScreenItem *sli = _screenItemList[j]; + + if ( + i < drawList.size() && dli != nullptr && + j < _screenItemList.size() && sli != nullptr && + !sli->_created && !sli->_updated && !sli->_deleted + ) { + ScreenItem *item = dli->screenItem; + + if ( + (sli->_priority > item->_priority || (sli->_priority == item->_priority && sli->_object > item->_object)) && + dli->rect.intersects(sli->_screenRect) + ) { + drawList.add(sli, dli->rect.findIntersectingRect(sli->_screenRect)); + } + } + } + } + } + + decrementScreenItemArrayCounts(&visiblePlane, false); + _screenItemList.pack(); + visiblePlane._screenItemList.pack(); +} + +void Plane::decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate) { + // The size of the screenItemList may change, so it is + // critical to re-check the size on each iteration + for (ScreenItemList::size_type i = 0; i < _screenItemList.size(); ++i) { + ScreenItem *item = _screenItemList[i]; + + if (item != nullptr) { + // update item in visiblePlane if item is updated + if ( + item->_updated || + ( + forceUpdate && + visiblePlane != nullptr && + visiblePlane->_screenItemList.findByObject(item->_object) != nullptr + ) + ) { + *visiblePlane->_screenItemList[i] = *_screenItemList[i]; + } + + if (item->_updated) { + item->_updated--; + } + + // create new item in visiblePlane if item was added + if (item->_created) { + item->_created--; + if (visiblePlane != nullptr) { + visiblePlane->_screenItemList.add(new ScreenItem(*item)); + } + } + + // delete item from both planes if it was deleted + if (item->_deleted) { + item->_deleted--; + if (!item->_deleted) { + visiblePlane->_screenItemList.erase_at(i); + _screenItemList.erase_at(i); + } + } + } + } +} + +void Plane::filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &transparentEraseList) const { + if (_type == kPlaneTypeTransparent) { + for (RectList::size_type i = 0; i < transparentEraseList.size(); ++i) { + const Common::Rect *r = transparentEraseList[i]; + for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) { + ScreenItem *item = _screenItemList[j]; + if (item != nullptr) { + if (r->intersects(item->_screenRect)) { + mergeToDrawList(j, *r, drawList); + } + } + } + } + } else { + for (RectList::size_type i = 0; i < transparentEraseList.size(); ++i) { + Common::Rect *r = transparentEraseList[i]; + if (r->intersects(_screenRect)) { + r->clip(_screenRect); + mergeToRectList(*r, eraseList); + + for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) { + ScreenItem *item = _screenItemList[j]; + + if (item != nullptr) { + if (r->intersects(item->_screenRect)) { + mergeToDrawList(j, *r, drawList); + } + } + } + + Common::Rect ptr[4]; + const Common::Rect *r2 = transparentEraseList[i]; + int count = splitRects(*r2, *r, ptr); + for (int k = count - 1; k >= 0; --k) { + transparentEraseList.add(ptr[k]); + } + transparentEraseList.erase_at(i); + } + } + + transparentEraseList.pack(); + } +} + +void Plane::filterUpDrawRects(DrawList &transparentDrawList, const DrawList &drawList) const { + for (DrawList::size_type i = 0; i < drawList.size(); ++i) { + const Common::Rect &r = drawList[i]->rect; + + for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) { + ScreenItem *item = _screenItemList[j]; + if (item != nullptr) { + if (r.intersects(item->_screenRect)) { + mergeToDrawList(j, r, transparentDrawList); + } + } + } + } +} + +void Plane::filterUpEraseRects(DrawList &drawList, RectList &eraseList) const { + for (RectList::size_type i = 0; i < eraseList.size(); ++i) { + const Common::Rect &r = *eraseList[i]; + for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) { + ScreenItem *item = _screenItemList[j]; + + if (item != nullptr) { + if (r.intersects(item->_screenRect)) { + mergeToDrawList(j, r, drawList); + } + } + } + } +} + +void Plane::mergeToDrawList(const ScreenItemList::size_type index, const Common::Rect &rect, DrawList &drawList) const { + RectList rects; + + ScreenItem *item = _screenItemList[index]; + Common::Rect r = item->_screenRect; + r.clip(rect); + rects.add(r); + + for (RectList::size_type i = 0; i < rects.size(); ++i) { + r = *rects[i]; + + for (DrawList::size_type j = 0; j < drawList.size(); ++j) { + const DrawItem *drawitem = drawList[j]; + if (item->_object == drawitem->screenItem->_object) { + if (drawitem->rect.contains(r)) { + rects.erase_at(i); + break; + } + + Common::Rect outRects[4]; + const int count = splitRects(r, drawitem->rect, outRects); + if (count != -1) { + for (int k = count - 1; k >= 0; --k) { + rects.add(outRects[k]); + } + + rects.erase_at(i); + + // proceed to the next rect + r = *rects[++i]; + } + } + } + } + + rects.pack(); + + for (RectList::size_type i = 0; i < rects.size(); ++i) { + drawList.add(item, *rects[i]); + } +} + +void Plane::mergeToRectList(const Common::Rect &rect, RectList &rectList) const { + RectList temp; + temp.add(rect); + + for (RectList::size_type i = 0; i < temp.size(); ++i) { + Common::Rect r = *temp[i]; + + for (RectList::size_type j = 0; j < rectList.size(); ++j) { + const Common::Rect *innerRect = rectList[j]; + if (innerRect->contains(r)) { + temp.erase_at(i); + break; + } + + Common::Rect out[4]; + const int count = splitRects(r, *innerRect, out); + if (count != -1) { + for (int k = count - 1; k >= 0; --k) { + temp.add(out[k]); + } + + temp.erase_at(i); + + // proceed to the next rect + r = *temp[++i]; + } + } + } + + temp.pack(); + + for (RectList::size_type i = 0; i < temp.size(); ++i) { + rectList.add(*temp[i]); + } +} + +void Plane::redrawAll(Plane *visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList) { + for (ScreenItemList::const_iterator screenItemPtr = _screenItemList.begin(); screenItemPtr != _screenItemList.end(); ++screenItemPtr) { + if (*screenItemPtr != nullptr) { + ScreenItem &screenItem = **screenItemPtr; + if (!screenItem._deleted) { + screenItem.calcRects(*this); + if (!screenItem._screenRect.isEmpty()) { + drawList.add(&screenItem, screenItem._screenRect); + } + } + } + } + + eraseList.clear(); + + if (!_screenRect.isEmpty() && _type != kPlaneTypePicture && _type != kPlaneTypeOpaque) { + eraseList.add(_screenRect); + } + breakEraseListByPlanes(eraseList, planeList); + breakDrawListByPlanes(drawList, planeList); + --_redrawAllCount; + decrementScreenItemArrayCounts(visiblePlane, true); + _screenItemList.pack(); + if (visiblePlane != nullptr) { + visiblePlane->_screenItemList.pack(); + } +} + +void Plane::setType() { + if (_pictureId == kPlanePicOpaque) { + _type = kPlaneTypeOpaque; + } else if (_pictureId == kPlanePicTransparent) { + _type = kPlaneTypeTransparent; + } else if (_pictureId == kPlanePicColored) { + _type = kPlaneTypeColored; + } else { + _type = kPlaneTypePicture; + } +} + +void Plane::sync(const Plane *other, const Common::Rect &screenRect) { + if (other == nullptr) { + if (_pictureChanged) { + deleteAllPics(); + setType(); + changePic(); + _redrawAllCount = g_sci->_gfxFrameout->getScreenCount(); + } else { + setType(); + } + } else { + if ( + _planeRect.top != other->_planeRect.top || + _planeRect.left != other->_planeRect.left || + _planeRect.right > other->_planeRect.right || + _planeRect.bottom > other->_planeRect.bottom + ) { + _redrawAllCount = g_sci->_gfxFrameout->getScreenCount(); + _updated = g_sci->_gfxFrameout->getScreenCount(); + } else if (_planeRect != other->_planeRect) { + _updated = g_sci->_gfxFrameout->getScreenCount(); + } + + if (_priority != other->_priority) { + _priorityChanged = g_sci->_gfxFrameout->getScreenCount(); + } + + if (_pictureId != other->_pictureId || _mirrored != other->_mirrored || _pictureChanged) { + deleteAllPics(); + setType(); + changePic(); + _redrawAllCount = g_sci->_gfxFrameout->getScreenCount(); + } + + if (_back != other->_back) { + _redrawAllCount = g_sci->_gfxFrameout->getScreenCount(); + } + } + + _deleted = 0; + if (_created == 0) { + _moved = g_sci->_gfxFrameout->getScreenCount(); + } + + convertGameRectToPlaneRect(); + _width = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + _height = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + _screenRect = _planeRect; + // NOTE: screenRect originally was retrieved through globals + // instead of being passed into the function + clipScreenRect(screenRect); +} + +void Plane::update(const reg_t object) { + SegManager *segMan = g_sci->getEngineState()->_segMan; + _vanishingPoint.x = readSelectorValue(segMan, object, SELECTOR(vanishingX)); + _vanishingPoint.y = readSelectorValue(segMan, object, SELECTOR(vanishingY)); + _gameRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft)); + _gameRect.top = readSelectorValue(segMan, object, SELECTOR(inTop)); + _gameRect.right = readSelectorValue(segMan, object, SELECTOR(inRight)) + 1; + _gameRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom)) + 1; + convertGameRectToPlaneRect(); + + _priority = readSelectorValue(segMan, object, SELECTOR(priority)); + GuiResourceId pictureId = readSelectorValue(segMan, object, SELECTOR(picture)); + if (_pictureId != pictureId) { + _pictureId = pictureId; + _pictureChanged = true; + } + + _mirrored = readSelectorValue(segMan, object, SELECTOR(mirrored)); + _back = readSelectorValue(segMan, object, SELECTOR(back)); +} + +void Plane::scrollScreenItems(const int16 deltaX, const int16 deltaY, const bool scrollPics) { + _redrawAllCount = g_sci->_gfxFrameout->getScreenCount(); + + for (ScreenItemList::iterator it = _screenItemList.begin(); it != _screenItemList.end(); ++it) { + if (*it != nullptr) { + ScreenItem &screenItem = **it; + if (!screenItem._deleted && (screenItem._celInfo.type != kCelTypePic || scrollPics)) { + screenItem._position.x += deltaX; + screenItem._position.y += deltaY; + } + } + } +} + +void Plane::remapMarkRedraw() { + for (ScreenItemList::const_iterator screenItemPtr = _screenItemList.begin(); screenItemPtr != _screenItemList.end(); ++screenItemPtr) { + if (*screenItemPtr != nullptr) { + ScreenItem &screenItem = **screenItemPtr; + if (screenItem.getCelObj()._remap && !screenItem._deleted && !screenItem._created) { + screenItem._updated = g_sci->_gfxFrameout->getScreenCount(); + } + } + } +} + +#pragma mark - +#pragma mark PlaneList +void PlaneList::add(Plane *plane) { + for (iterator it = begin(); it != end(); ++it) { + if ((*it)->_priority > plane->_priority) { + insert(it, plane); + return; + } + } + + push_back(plane); +} + +void PlaneList::clear() { + for (iterator it = begin(); it != end(); ++it) { + delete *it; + } + + PlaneListBase::clear(); +} + +void PlaneList::erase(Plane *plane) { + for (iterator it = begin(); it != end(); ++it) { + if (*it == plane) { + erase(it); + break; + } + } +} + +PlaneList::iterator PlaneList::erase(iterator it) { + delete *it; + return PlaneListBase::erase(it); +} + +int PlaneList::findIndexByObject(const reg_t object) const { + for (size_type i = 0; i < size(); ++i) { + if ((*this)[i] != nullptr && (*this)[i]->_object == object) { + return i; + } + } + + return -1; +} + +Plane *PlaneList::findByObject(const reg_t object) const { + const_iterator planeIt = Common::find_if(begin(), end(), FindByObject<Plane *>(object)); + + if (planeIt == end()) { + return nullptr; + } + + return *planeIt; +} + +int16 PlaneList::getTopPlanePriority() const { + if (size() > 0) { + return (*this)[size() - 1]->_priority; + } + + return 0; +} + +int16 PlaneList::getTopSciPlanePriority() const { + int16 priority = 0; + + for (const_iterator it = begin(); it != end(); ++it) { + if ((*it)->_priority >= 10000) { + break; + } + + priority = (*it)->_priority; + } + + return priority; +} + +void PlaneList::remove_at(size_type index) { + delete PlaneListBase::remove_at(index); +} + +} // End of namespace Sci diff --git a/engines/sci/graphics/plane32.h b/engines/sci/graphics/plane32.h new file mode 100644 index 0000000000..770a6fa445 --- /dev/null +++ b/engines/sci/graphics/plane32.h @@ -0,0 +1,485 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SCI_GRAPHICS_PLANE32_H +#define SCI_GRAPHICS_PLANE32_H + +#include "common/array.h" +#include "common/rect.h" +#include "sci/engine/vm_types.h" +#include "sci/graphics/helpers.h" +#include "sci/graphics/lists32.h" +#include "sci/graphics/screen_item32.h" + +namespace Sci { +enum PlaneType { + kPlaneTypeColored = 0, + kPlaneTypePicture = 1, + kPlaneTypeTransparent = 2, + kPlaneTypeOpaque = 3 +}; + +enum PlanePictureCodes { + // NOTE: Any value at or below 65532 means the plane + // is a kPlaneTypePicture. + kPlanePic = 65532, + kPlanePicOpaque = 65533, + kPlanePicTransparent = 65534, + kPlanePicColored = 65535 +}; + +#pragma mark - +#pragma mark RectList + +typedef StablePointerArray<Common::Rect, 200> RectListBase; +class RectList : public RectListBase { +public: + void add(const Common::Rect &rect) { + RectListBase::add(new Common::Rect(rect)); + } +}; + +#pragma mark - +#pragma mark DrawList + +struct DrawItem { + ScreenItem *screenItem; + Common::Rect rect; + + inline bool operator<(const DrawItem &other) const { + return *screenItem < *other.screenItem; + } +}; + +typedef StablePointerArray<DrawItem, 250> DrawListBase; +class DrawList : public DrawListBase { +private: + inline static bool sortHelper(const DrawItem *a, const DrawItem *b) { + return *a < *b; + } +public: + void add(ScreenItem *screenItem, const Common::Rect &rect); + inline void sort() { + pack(); + Common::sort(begin(), end(), sortHelper); + } +}; + +class PlaneList; + +#pragma mark - +#pragma mark Plane + +/** + * A plane is a grouped layer of screen items. + */ +class Plane { +private: + /** + * A serial used for planes that are generated inside + * the graphics engine, rather than the interpreter. + */ + static uint16 _nextObjectId; + + /** + * The dimensions of the plane, in game script + * coordinates. + * TODO: These are never used and are always + * scriptWidth x scriptHeight in SCI engine? The actual + * dimensions of the plane are always in + * gameRect/planeRect. + */ + int16 _width, _height; + + /** + * For planes that are used to render picture data, the + * resource ID of the picture to be displayed. This + * value may also be one of the special + * PlanePictureCodes, in which case the plane becomes a + * non-picture plane. + */ + GuiResourceId _pictureId; + + /** + * Whether or not the contents of picture planes should + * be drawn horizontally mirrored. Only applies to + * planes of type kPlaneTypePicture. + */ + bool _mirrored; + + /** + * Whether the picture ID for this plane has changed. + * This flag is set when the plane is created or updated + * from a VM object, and is cleared when the plane is + * synchronised to another plane (which calls + * changePic). + */ + bool _pictureChanged; + + // TODO: Are these ever actually used? + int _field_34, _field_38; // probably a point or ratio + int _field_3C, _field_40; // probably a point or ratio + + /** + * Converts the dimensions of the game rect used by + * scripts to the dimensions of the plane rect used to + * render content to the screen. Coordinates with + * remainders are rounded up to the next whole pixel. + */ + void convertGameRectToPlaneRect(); + + /** + * Sets the type of the plane according to its assigned + * picture resource ID. + */ + void setType(); + +public: + /** + * The color to use when erasing the plane. Only + * applies to planes of type kPlaneTypeColored. + */ + byte _back; + + /** + * Whether the priority of this plane has changed. + * This flag is set when the plane is updated from + * another plane and cleared when draw list calculation + * occurs. + */ + int _priorityChanged; + + /** + * A handle to the VM object corresponding to this + * plane. Some planes are generated purely within the + * graphics engine and have a numeric object value. + */ + reg_t _object; + + /** + * The rendering priority of the plane. Higher + * priorities are drawn above lower priorities. + */ + int16 _priority; + + /** + * Whether or not all screen items in this plane should + * be redrawn on the next frameout, instead of just + * the screen items marked as updated. This is set when + * visual changes to the plane itself are made that + * affect the rendering of the entire plane, and cleared + * once those changes are rendered by `redrawAll`. + */ + int _redrawAllCount; + + PlaneType _type; + + /** + * Flags indicating the state of the plane. + * - `created` is set when the plane is first created, + * either from a VM object or from within the engine + * itself + * - `updated` is set when the plane is updated from + * another plane and the two planes' `planeRect`s do + * not match + * - `deleted` is set when the plane is deleted by a + * kernel call + * - `moved` is set when the plane is synchronised from + * another plane and is not already in the "created" + * state + */ + int _created, _updated, _deleted, _moved; + + /** + * The vanishing point for the plane. Used when + * calculating the correct scaling of the plane's screen + * items according to their position. + */ + Common::Point _vanishingPoint; + + /** + * The position & dimensions of the plane in screen + * coordinates. This rect is not clipped to the screen, + * so may include coordinates that are offscreen. + */ + Common::Rect _planeRect; + + /** + * The position & dimensions of the plane in game script + * coordinates. + */ + Common::Rect _gameRect; + + /** + * The position & dimensions of the plane in screen + * coordinates. This rect is clipped to the screen. + */ + Common::Rect _screenRect; + + /** + * The list of screen items grouped within this plane. + */ + ScreenItemList _screenItemList; + +public: + /** + * Initialises static Plane members. + */ + static void init(); + + // NOTE: This constructor signature originally did not accept a + // picture ID, but some calls to construct planes with this signature + // immediately set the picture ID and then called setType again, so + // it made more sense to just make the picture ID a parameter instead. + Plane(const Common::Rect &gameRect, PlanePictureCodes pictureId = kPlanePicColored); + + Plane(const reg_t object); + + Plane(const Plane &other); + + void operator=(const Plane &other); + + inline bool operator<(const Plane &other) const { + if (_priority < other._priority) { + return true; + } + + if (_priority == other._priority) { + return _object < other._object; + } + + return false; + } + + /** + * Clips the screen rect of this plane to fit within the + * given screen rect. + */ + inline void clipScreenRect(const Common::Rect &screenRect) { + if (_screenRect.intersects(screenRect)) { + _screenRect.clip(screenRect); + } else { + _screenRect.left = 0; + _screenRect.top = 0; + _screenRect.right = 0; + _screenRect.bottom = 0; + } + } + + void printDebugInfo(Console *con) const; + + /** + * Compares the properties of the current plane against + * the properties of the `other` plane (which is the + * corresponding plane from the visible plane list) to + * discover which properties have been changed on this + * plane by a call to `update(reg_t)`. + * + * @note This method was originally called UpdatePlane + * in SCI engine. + */ + void sync(const Plane *other, const Common::Rect &screenRect); + + /** + * Updates the plane to match the state of the plane + * object from the virtual machine. + * + * @note This method was originally called UpdatePlane + * in SCI engine. + */ + void update(const reg_t object); + + /** + * Modifies the position of all non-pic screen items + * by the given delta. If `scrollPics` is true, pic + * items are also repositioned. + */ + void scrollScreenItems(const int16 deltaX, const int16 deltaY, const bool scrollPics); + +#pragma mark - +#pragma mark Plane - Pic +private: + /** + * Adds all cels from the specified picture resource to + * the plane as screen items. If a position is provided, + * the screen items will be given that position; + * otherwise, the default relative positions for each + * cel will be taken from the picture resource data. + */ + inline void addPicInternal(const GuiResourceId pictureId, const Common::Point *position, const bool mirrorX); + + /** + * Marks all screen items to be deleted that are within + * this plane and match the given picture ID. + */ + void deletePic(const GuiResourceId pictureId); + + /** + * Marks all screen items to be deleted that are within + * this plane and match the given picture ID, then sets + * the picture ID of the plane to the new picture ID + * without adding any screen items. + */ + void deletePic(const GuiResourceId oldPictureId, const GuiResourceId newPictureId); + + /** + * Marks all screen items to be deleted that are within + * this plane and are picture cels. + */ + void deleteAllPics(); + +public: + /** + * Marks all existing screen items matching the current + * picture to be deleted, then adds all cels from the + * new picture resource to the plane at the given + * position. + */ + void addPic(const GuiResourceId pictureId, const Common::Point &position, const bool mirrorX); + + /** + * If the plane is a picture plane, re-adds all cels + * from its picture resource to the plane. Otherwise, + * just clears the _pictureChanged flag. + */ + void changePic(); + +#pragma mark - +#pragma mark Plane - Rendering +private: + /** + * Splits all rects in the given draw list at the edges + * of all non-transparent planes above the current + * plane. + */ + void breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList) const; + + /** + * Splits all rects in the given erase list rects at the + * edges of all non-transparent planes above the current + * plane. + */ + void breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeList) const; + + /** + * Synchronises changes to screen items from the current + * plane to the visible plane and deletes screen items + * from the current plane that have been marked as + * deleted. If `forceUpdate` is true, all screen items + * on the visible plane will be updated, even if they + * are not marked as having changed. + */ + void decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate); + + /** + * Merges the screen item from this plane at the given + * index into the given draw list, clipped to the given + * rect. TODO: Finish documenting + */ + void mergeToDrawList(const DrawList::size_type index, const Common::Rect &rect, DrawList &drawList) const; + + /** + * Adds the given rect into the given rect list, + * merging it with other rects already inside the list, + * if possible, to avoid overdraw. TODO: Finish + * documenting + */ + void mergeToRectList(const Common::Rect &rect, RectList &rectList) const; + +public: + /** + * Calculates the location and dimensions of dirty rects + * of the screen items in this plane and adds them to + * the given draw and erase lists, and synchronises this + * plane's list of screen items to the given visible + * plane. + */ + void calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList); + + /** + * TODO: Documentation + */ + void filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &transparentEraseList) const; + + /** + * TODO: Documentation + */ + void filterUpEraseRects(DrawList &drawList, RectList &eraseList) const; + + /** + * TODO: Documentation + */ + void filterUpDrawRects(DrawList &transparentDrawList, const DrawList &drawList) const; + + /** + * Updates all of the plane's non-deleted screen items + * and adds them to the given draw and erase lists. + */ + void redrawAll(Plane *visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList); + + void remapMarkRedraw(); +}; + +#pragma mark - +#pragma mark PlaneList + +typedef Common::Array<Plane *> PlaneListBase; +class PlaneList : public PlaneListBase { +private: + inline static bool sortHelper(const Plane *a, const Plane *b) { + return *a < *b; + } + + using PlaneListBase::push_back; + +public: + // A method for finding the index of a plane inside a + // PlaneList is used because entries in the main plane + // list and visible plane list of GfxFrameout are + // synchronised by index + int findIndexByObject(const reg_t object) const; + Plane *findByObject(const reg_t object) const; + + /** + * Gets the priority of the top plane in the plane list. + */ + int16 getTopPlanePriority() const; + + /** + * Gets the priority of the top plane in the plane list + * created by a game script. + */ + int16 getTopSciPlanePriority() const; + + void add(Plane *plane); + void clear(); + iterator erase(iterator it); + void erase(Plane *plane); + inline void sort() { + Common::sort(begin(), end(), sortHelper); + } + void remove_at(size_type index); +}; + +} // End of namespace Sci + +#endif diff --git a/engines/sci/graphics/remap.cpp b/engines/sci/graphics/remap.cpp new file mode 100644 index 0000000000..e331eaf971 --- /dev/null +++ b/engines/sci/graphics/remap.cpp @@ -0,0 +1,386 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "sci/sci.h" +#include "sci/resource.h" +#include "sci/graphics/palette.h" +#include "sci/graphics/palette32.h" +#include "sci/graphics/remap.h" +#include "sci/graphics/screen.h" + +namespace Sci { + +#pragma mark - +#pragma mark SCI16 remapping (QFG4 demo) + +GfxRemap::GfxRemap(GfxPalette *palette) + : _palette(palette) { + _remapOn = false; + resetRemapping(); +} + +GfxRemap::~GfxRemap() { +} + +byte GfxRemap::remapColor(byte remappedColor, byte screenColor) { + assert(_remapOn); + if (_remappingType[remappedColor] == kRemappingByRange) + return _remappingByRange[screenColor]; + else if (_remappingType[remappedColor] == kRemappingByPercent) + return _remappingByPercent[screenColor]; + else + error("remapColor(): Color %d isn't remapped", remappedColor); + + return 0; // should never reach here +} + +void GfxRemap::resetRemapping() { + _remapOn = false; + _remappingPercentToSet = 0; + + for (int i = 0; i < 256; i++) { + _remappingType[i] = kRemappingNone; + _remappingByPercent[i] = i; + _remappingByRange[i] = i; + } +} + +void GfxRemap::setRemappingPercent(byte color, byte percent) { + _remapOn = true; + + // We need to defer the setup of the remapping table every time the screen + // palette is changed, so that kernelFindColor() can find the correct + // colors. Set it once here, in case the palette stays the same and update + // it on each palette change by copySysPaletteToScreen(). + _remappingPercentToSet = percent; + + for (int i = 0; i < 256; i++) { + byte r = _palette->_sysPalette.colors[i].r * _remappingPercentToSet / 100; + byte g = _palette->_sysPalette.colors[i].g * _remappingPercentToSet / 100; + byte b = _palette->_sysPalette.colors[i].b * _remappingPercentToSet / 100; + _remappingByPercent[i] = _palette->kernelFindColor(r, g, b); + } + + _remappingType[color] = kRemappingByPercent; +} + +void GfxRemap::setRemappingRange(byte color, byte from, byte to, byte base) { + _remapOn = true; + + for (int i = from; i <= to; i++) { + _remappingByRange[i] = i + base; + } + + _remappingType[color] = kRemappingByRange; +} + +void GfxRemap::updateRemapping() { + // Check if we need to reset remapping by percent with the new colors. + if (_remappingPercentToSet) { + for (int i = 0; i < 256; i++) { + byte r = _palette->_sysPalette.colors[i].r * _remappingPercentToSet / 100; + byte g = _palette->_sysPalette.colors[i].g * _remappingPercentToSet / 100; + byte b = _palette->_sysPalette.colors[i].b * _remappingPercentToSet / 100; + _remappingByPercent[i] = _palette->kernelFindColor(r, g, b); + } + } +} + +#pragma mark - +#pragma mark SCI32 remapping + +#ifdef ENABLE_SCI32 + +GfxRemap32::GfxRemap32(GfxPalette32 *palette) : _palette(palette) { + for (int i = 0; i < REMAP_COLOR_COUNT; i++) + _remaps[i] = RemapParams(0, 0, 0, 0, 100, kRemappingNone); + _noMapStart = _noMapCount = 0; + _update = false; + _remapCount = 0; + + // The remap range was 245 - 254 in SCI2, but was changed to 235 - 244 in SCI21 middle + _remapEndColor = (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) ? 244 : 254; +} + +void GfxRemap32::remapOff(byte color) { + if (!color) { + for (int i = 0; i < REMAP_COLOR_COUNT; i++) + _remaps[i] = RemapParams(0, 0, 0, 0, 100, kRemappingNone); + + _remapCount = 0; + } else { + assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); + const byte index = _remapEndColor - color; + _remaps[index] = RemapParams(0, 0, 0, 0, 100, kRemappingNone); + _remapCount--; + } + + _update = true; +} + +void GfxRemap32::setRemappingRange(byte color, byte from, byte to, byte base) { + assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); + _remaps[_remapEndColor - color] = RemapParams(from, to, base, 0, 100, kRemappingByRange); + initColorArrays(_remapEndColor - color); + _remapCount++; + _update = true; +} + +void GfxRemap32::setRemappingPercent(byte color, byte percent) { + assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); + _remaps[_remapEndColor - color] = RemapParams(0, 0, 0, 0, percent, kRemappingByPercent); + initColorArrays(_remapEndColor - color); + _remapCount++; + _update = true; +} + +void GfxRemap32::setRemappingToGray(byte color, byte gray) { + assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); + _remaps[_remapEndColor - color] = RemapParams(0, 0, 0, gray, 100, kRemappingToGray); + initColorArrays(_remapEndColor - color); + _remapCount++; + _update = true; +} + +void GfxRemap32::setRemappingToPercentGray(byte color, byte gray, byte percent) { + assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); + _remaps[_remapEndColor - color] = RemapParams(0, 0, 0, gray, percent, kRemappingToPercentGray); + initColorArrays(_remapEndColor - color); + _remapCount++; + _update = true; +} + +void GfxRemap32::setNoMatchRange(byte from, byte count) { + _noMapStart = from; + _noMapCount = count; +} + +bool GfxRemap32::remapEnabled(byte color) const { + assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); + const byte index = _remapEndColor - color; + return (_remaps[index].type != kRemappingNone); +} + +byte GfxRemap32::remapColor(byte color, byte target) { + assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); + const byte index = _remapEndColor - color; + if (_remaps[index].type != kRemappingNone) + return _remaps[index].remap[target]; + else + return target; +} + +void GfxRemap32::initColorArrays(byte index) { + Palette *curPalette = &_palette->_sysPalette; + RemapParams *curRemap = &_remaps[index]; + + memcpy(curRemap->curColor, curPalette->colors, NON_REMAPPED_COLOR_COUNT * sizeof(Color)); + memcpy(curRemap->targetColor, curPalette->colors, NON_REMAPPED_COLOR_COUNT * sizeof(Color)); +} + +bool GfxRemap32::updateRemap(byte index, bool palChanged) { + int result; + RemapParams *curRemap = &_remaps[index]; + const Palette *curPalette = &_palette->_sysPalette; + const Palette *nextPalette = _palette->getNextPalette(); + bool changed = false; + + if (!_update && !palChanged) + return false; + + Common::fill(_targetChanged, _targetChanged + NON_REMAPPED_COLOR_COUNT, false); + + switch (curRemap->type) { + case kRemappingNone: + return false; + case kRemappingByRange: + for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++) { + if (curRemap->from <= i && i <= curRemap->to) + result = i + curRemap->base; + else + result = i; + + if (curRemap->remap[i] != result) { + changed = true; + curRemap->remap[i] = result; + } + + curRemap->colorChanged[i] = true; + } + return changed; + case kRemappingByPercent: + for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) { + // NOTE: This method uses nextPalette instead of curPalette + Color color = nextPalette->colors[i]; + + if (curRemap->curColor[i] != color) { + curRemap->colorChanged[i] = true; + curRemap->curColor[i] = color; + } + + if (curRemap->percent != curRemap->oldPercent || curRemap->colorChanged[i]) { + byte red = CLIP<byte>(color.r * curRemap->percent / 100, 0, 255); + byte green = CLIP<byte>(color.g * curRemap->percent / 100, 0, 255); + byte blue = CLIP<byte>(color.b * curRemap->percent / 100, 0, 255); + byte used = curRemap->targetColor[i].used; + + Color newColor = { used, red, green, blue }; + if (curRemap->targetColor[i] != newColor) { + _targetChanged[i] = true; + curRemap->targetColor[i] = newColor; + } + } + } + + changed = applyRemap(index); + Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false); + curRemap->oldPercent = curRemap->percent; + return changed; + case kRemappingToGray: + for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) { + Color color = curPalette->colors[i]; + + if (curRemap->curColor[i] != color) { + curRemap->colorChanged[i] = true; + curRemap->curColor[i] = color; + } + + if (curRemap->gray != curRemap->oldGray || curRemap->colorChanged[i]) { + byte lumosity = ((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8; + byte red = CLIP<byte>(color.r - ((color.r - lumosity) * curRemap->gray / 100), 0, 255); + byte green = CLIP<byte>(color.g - ((color.g - lumosity) * curRemap->gray / 100), 0, 255); + byte blue = CLIP<byte>(color.b - ((color.b - lumosity) * curRemap->gray / 100), 0, 255); + byte used = curRemap->targetColor[i].used; + + Color newColor = { used, red, green, blue }; + if (curRemap->targetColor[i] != newColor) { + _targetChanged[i] = true; + curRemap->targetColor[i] = newColor; + } + } + } + + changed = applyRemap(index); + Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false); + curRemap->oldGray = curRemap->gray; + return changed; + case kRemappingToPercentGray: + for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) { + Color color = curPalette->colors[i]; + + if (curRemap->curColor[i] != color) { + curRemap->colorChanged[i] = true; + curRemap->curColor[i] = color; + } + + if (curRemap->percent != curRemap->oldPercent || curRemap->gray != curRemap->oldGray || curRemap->colorChanged[i]) { + byte lumosity = ((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8; + lumosity = lumosity * curRemap->percent / 100; + byte red = CLIP<byte>(color.r - ((color.r - lumosity) * curRemap->gray / 100), 0, 255); + byte green = CLIP<byte>(color.g - ((color.g - lumosity) * curRemap->gray / 100), 0, 255); + byte blue = CLIP<byte>(color.b - ((color.b - lumosity) * curRemap->gray / 100), 0, 255); + byte used = curRemap->targetColor[i].used; + + Color newColor = { used, red, green, blue }; + if (curRemap->targetColor[i] != newColor) { + _targetChanged[i] = true; + curRemap->targetColor[i] = newColor; + } + } + } + + changed = applyRemap(index); + Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false); + curRemap->oldPercent = curRemap->percent; + curRemap->oldGray = curRemap->gray; + return changed; + default: + return false; + } +} + +static int colorDistance(Color a, Color b) { + int rDiff = (a.r - b.r) * (a.r - b.r); + int gDiff = (a.g - b.g) * (a.g - b.g); + int bDiff = (a.b - b.b) * (a.b - b.b); + return rDiff + gDiff + bDiff; +} + +bool GfxRemap32::applyRemap(byte index) { + RemapParams *curRemap = &_remaps[index]; + const bool *cycleMap = _palette->getCyclemap(); + bool unmappedColors[NON_REMAPPED_COLOR_COUNT]; + Color newColors[NON_REMAPPED_COLOR_COUNT]; + bool changed = false; + + Common::fill(unmappedColors, unmappedColors + NON_REMAPPED_COLOR_COUNT, false); + if (_noMapCount) + Common::fill(unmappedColors + _noMapStart, unmappedColors + _noMapStart + _noMapCount, true); + + for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++) { + if (cycleMap[i]) + unmappedColors[i] = true; + } + + int curColor = 0; + for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) { + if (curRemap->colorChanged[i] && !unmappedColors[i]) + newColors[curColor++] = curRemap->curColor[i]; + } + + for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) { + Color targetColor = curRemap->targetColor[i]; + bool colorChanged = curRemap->colorChanged[curRemap->remap[i]]; + + if (!_targetChanged[i] && !colorChanged) + continue; + + if (_targetChanged[i] && colorChanged) + if (curRemap->distance[i] < 100 && colorDistance(targetColor, curRemap->curColor[curRemap->remap[i]]) <= curRemap->distance[i]) + continue; + + int diff = 0; + int16 result = _palette->matchColor(targetColor.r, targetColor.g, targetColor.b, curRemap->distance[i], diff, unmappedColors); + if (result != -1 && curRemap->remap[i] != result) { + changed = true; + curRemap->remap[i] = result; + curRemap->distance[i] = diff; + } + } + + return changed; +} + +bool GfxRemap32::remapAllTables(bool palChanged) { + bool changed = false; + + for (int i = 0; i < REMAP_COLOR_COUNT; i++) { + changed |= updateRemap(i, palChanged); + } + + _update = false; + return changed; +} + +#endif + +} // End of namespace Sci diff --git a/engines/sci/graphics/remap.h b/engines/sci/graphics/remap.h new file mode 100644 index 0000000000..d012568f7f --- /dev/null +++ b/engines/sci/graphics/remap.h @@ -0,0 +1,154 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SCI_GRAPHICS_REMAP_H +#define SCI_GRAPHICS_REMAP_H + +#include "common/array.h" +#include "sci/graphics/helpers.h" + +namespace Sci { + +class GfxScreen; + +enum ColorRemappingType { + kRemappingNone = 0, + kRemappingByRange = 1, + kRemappingByPercent = 2, + kRemappingToGray = 3, + kRemappingToPercentGray = 4 +}; + +#define REMAP_COLOR_COUNT 9 +#define NON_REMAPPED_COLOR_COUNT 236 + +/** + * Remap class, handles color remapping + */ +class GfxRemap { +public: + GfxRemap(GfxPalette *_palette); + ~GfxRemap(); + + void resetRemapping(); + void setRemappingPercent(byte color, byte percent); + void setRemappingRange(byte color, byte from, byte to, byte base); + bool isRemapped(byte color) const { + return _remapOn && (_remappingType[color] != kRemappingNone); + } + byte remapColor(byte remappedColor, byte screenColor); + void updateRemapping(); + +private: + GfxScreen *_screen; + GfxPalette *_palette; + + bool _remapOn; + ColorRemappingType _remappingType[256]; + byte _remappingByPercent[256]; + byte _remappingByRange[256]; + uint16 _remappingPercentToSet; +}; + +#ifdef ENABLE_SCI32 + +struct RemapParams { + byte from; + byte to; + byte base; + byte gray; + byte oldGray; + byte percent; + byte oldPercent; + ColorRemappingType type; + Color curColor[256]; + Color targetColor[256]; + byte distance[256]; + byte remap[256]; + bool colorChanged[256]; + + RemapParams() { + from = to = base = gray = oldGray = percent = oldPercent = 0; + type = kRemappingNone; + + // curColor and targetColor are initialized in GfxRemap32::initColorArrays + memset(curColor, 0, 256 * sizeof(Color)); + memset(targetColor, 0, 256 * sizeof(Color)); + memset(distance, 0, 256); + for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++) + remap[i] = i; + Common::fill(colorChanged, colorChanged + ARRAYSIZE(colorChanged), true); + } + + RemapParams(byte from_, byte to_, byte base_, byte gray_, byte percent_, ColorRemappingType type_) { + from = from_; + to = to_; + base = base_; + gray = oldGray = gray_; + percent = oldPercent = percent_; + type = type_; + + // curColor and targetColor are initialized in GfxRemap32::initColorArrays + memset(curColor, 0, 256 * sizeof(Color)); + memset(targetColor, 0, 256 * sizeof(Color)); + memset(distance, 0, 256); + for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++) + remap[i] = i; + Common::fill(colorChanged, colorChanged + ARRAYSIZE(colorChanged), true); + } +}; + +class GfxRemap32 { +public: + GfxRemap32(GfxPalette32 *palette); + ~GfxRemap32() {} + + void remapOff(byte color); + void setRemappingRange(byte color, byte from, byte to, byte base); + void setRemappingPercent(byte color, byte percent); + void setRemappingToGray(byte color, byte gray); + void setRemappingToPercentGray(byte color, byte gray, byte percent); + void setNoMatchRange(byte from, byte count); + bool remapEnabled(byte color) const; + byte remapColor(byte color, byte target); + bool remapAllTables(bool palChanged); + int getRemapCount() const { return _remapCount; } + int getStartColor() const { return _remapEndColor - REMAP_COLOR_COUNT + 1; } + int getEndColor() const { return _remapEndColor; } +private: + GfxPalette32 *_palette; + RemapParams _remaps[REMAP_COLOR_COUNT]; + bool _update; + byte _noMapStart, _noMapCount; + bool _targetChanged[NON_REMAPPED_COLOR_COUNT]; + byte _remapEndColor; + int _remapCount; + + void initColorArrays(byte index); + bool applyRemap(byte index); + bool updateRemap(byte index, bool palChanged); +}; +#endif + +} // End of namespace Sci + +#endif diff --git a/engines/sci/graphics/screen.cpp b/engines/sci/graphics/screen.cpp index 4cd6344600..c977a93817 100644 --- a/engines/sci/graphics/screen.cpp +++ b/engines/sci/graphics/screen.cpp @@ -214,36 +214,6 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) { error("Unknown SCI1.1 Mac game"); } else initGraphics(_displayWidth, _displayHeight, _displayWidth > 320); - - // Initialize code pointers - _vectorAdjustCoordinatePtr = &GfxScreen::vectorAdjustCoordinateNOP; - _vectorAdjustLineCoordinatesPtr = &GfxScreen::vectorAdjustLineCoordinatesNOP; - _vectorIsFillMatchPtr = &GfxScreen::vectorIsFillMatchNormal; - _vectorPutPixelPtr = &GfxScreen::putPixelNormal; - _vectorPutLinePixelPtr = &GfxScreen::putPixel; - _vectorGetPixelPtr = &GfxScreen::getPixelNormal; - _putPixelPtr = &GfxScreen::putPixelNormal; - _getPixelPtr = &GfxScreen::getPixelNormal; - - switch (_upscaledHires) { - case GFX_SCREEN_UPSCALED_480x300: - _vectorAdjustCoordinatePtr = &GfxScreen::vectorAdjustCoordinate480x300Mac; - _vectorAdjustLineCoordinatesPtr = &GfxScreen::vectorAdjustLineCoordinates480x300Mac; - // vectorPutPixel -> we already adjust coordinates for vector code, that's why we can set pixels directly - // vectorGetPixel -> see vectorPutPixel - _vectorPutLinePixelPtr = &GfxScreen::vectorPutLinePixel480x300Mac; - _putPixelPtr = &GfxScreen::putPixelAllUpscaled; - _getPixelPtr = &GfxScreen::getPixelUpscaled; - break; - case GFX_SCREEN_UPSCALED_640x400: - case GFX_SCREEN_UPSCALED_640x440: - case GFX_SCREEN_UPSCALED_640x480: - _vectorPutPixelPtr = &GfxScreen::putPixelDisplayUpscaled; - _putPixelPtr = &GfxScreen::putPixelDisplayUpscaled; - break; - case GFX_SCREEN_UPSCALED_DISABLED: - break; - } } GfxScreen::~GfxScreen() { @@ -270,17 +240,26 @@ void GfxScreen::copyToScreen() { } void GfxScreen::copyFromScreen(byte *buffer) { - // TODO this ignores the pitch Graphics::Surface *screen = g_system->lockScreen(); - memcpy(buffer, screen->getPixels(), _displayPixels); + + if (screen->pitch == _displayWidth) { + memcpy(buffer, screen->getPixels(), _displayPixels); + } else { + const byte *src = (const byte *)screen->getPixels(); + uint height = _displayHeight; + + while (height--) { + memcpy(buffer, src, _displayWidth); + buffer += _displayWidth; + src += screen->pitch; + } + } + g_system->unlockScreen(); } void GfxScreen::kernelSyncWithFramebuffer() { - // TODO this ignores the pitch - Graphics::Surface *screen = g_system->lockScreen(); - memcpy(_displayScreen, screen->getPixels(), _displayPixels); - g_system->unlockScreen(); + copyFromScreen(_displayScreen); } void GfxScreen::copyRectToScreen(const Common::Rect &rect) { @@ -325,40 +304,68 @@ byte GfxScreen::getDrawingMask(byte color, byte prio, byte control) { return flag; } -void GfxScreen::vectorAdjustCoordinateNOP(int16 *x, int16 *y) { +void GfxScreen::vectorAdjustLineCoordinates(int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control) { + switch (_upscaledHires) { + case GFX_SCREEN_UPSCALED_480x300: { + int16 displayLeft = (*left * 3) / 2; + int16 displayRight = (*right * 3) / 2; + int16 displayTop = (*top * 3) / 2; + int16 displayBottom = (*bottom * 3) / 2; + + if (displayLeft < displayRight) { + // one more pixel to the left, one more pixel to the right + if (displayLeft > 0) + vectorPutLinePixel(displayLeft - 1, displayTop, drawMask, color, priority, control); + vectorPutLinePixel(displayRight + 1, displayBottom, drawMask, color, priority, control); + } else if (displayLeft > displayRight) { + if (displayRight > 0) + vectorPutLinePixel(displayRight - 1, displayBottom, drawMask, color, priority, control); + vectorPutLinePixel(displayLeft + 1, displayTop, drawMask, color, priority, control); + } + *left = displayLeft; + *top = displayTop; + *right = displayRight; + *bottom = displayBottom; + break; + } + default: + break; + } } -void GfxScreen::vectorAdjustCoordinate480x300Mac(int16 *x, int16 *y) { - *x = _upscaledWidthMapping[*x]; - *y = _upscaledHeightMapping[*y]; +// This is called from vector drawing to put a pixel at a certain location +void GfxScreen::vectorPutLinePixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) { + if (_upscaledHires == GFX_SCREEN_UPSCALED_480x300) { + vectorPutLinePixel480x300(x, y, drawMask, color, priority, control); + return; + } + + // For anything else forward to the regular putPixel + putPixel(x, y, drawMask, color, priority, control); } -void GfxScreen::vectorAdjustLineCoordinatesNOP(int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control) { +// Special 480x300 Mac putPixel for vector line drawing, also draws an additional pixel below the actual one +void GfxScreen::vectorPutLinePixel480x300(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) { + int offset = y * _width + x; + + if (drawMask & GFX_SCREEN_MASK_VISUAL) { + // also set pixel below actual pixel + _visualScreen[offset] = color; + _visualScreen[offset + _width] = color; + _displayScreen[offset] = color; + _displayScreen[offset + _displayWidth] = color; + } + if (drawMask & GFX_SCREEN_MASK_PRIORITY) { + _priorityScreen[offset] = priority; + _priorityScreen[offset + _width] = priority; + } + if (drawMask & GFX_SCREEN_MASK_CONTROL) { + _controlScreen[offset] = control; + _controlScreen[offset + _width] = control; + } } -void GfxScreen::vectorAdjustLineCoordinates480x300Mac(int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control) { - int16 displayLeft = _upscaledWidthMapping[*left]; - int16 displayRight = _upscaledWidthMapping[*right]; - int16 displayTop = _upscaledHeightMapping[*top]; - int16 displayBottom = _upscaledHeightMapping[*bottom]; - - if (displayLeft < displayRight) { - // one more pixel to the left, one more pixel to the right - if (displayLeft > 0) - vectorPutLinePixel(displayLeft - 1, displayTop, drawMask, color, priority, control); - vectorPutLinePixel(displayRight + 1, displayBottom, drawMask, color, priority, control); - } else if (displayLeft > displayRight) { - if (displayRight > 0) - vectorPutLinePixel(displayRight - 1, displayBottom, drawMask, color, priority, control); - vectorPutLinePixel(displayLeft + 1, displayTop, drawMask, color, priority, control); - } - *left = displayLeft; - *top = displayTop; - *right = displayRight; - *bottom = displayBottom; -} - -byte GfxScreen::vectorIsFillMatchNormal(int16 x, int16 y, byte screenMask, byte checkForColor, byte checkForPriority, byte checkForControl, bool isEGA) { +byte GfxScreen::vectorIsFillMatch(int16 x, int16 y, byte screenMask, byte checkForColor, byte checkForPriority, byte checkForControl, bool isEGA) { int offset = y * _width + x; byte match = 0; @@ -388,132 +395,6 @@ byte GfxScreen::vectorIsFillMatchNormal(int16 x, int16 y, byte screenMask, byte return match; } -// Special 480x300 Mac putPixel for vector line drawing, also draws an additional pixel below the actual one -void GfxScreen::vectorPutLinePixel480x300Mac(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) { - int offset = y * _width + x; - - if (drawMask & GFX_SCREEN_MASK_VISUAL) { - _visualScreen[offset] = color; - _visualScreen[offset + _width] = color; - _displayScreen[offset] = color; - // also set pixel below actual pixel - _displayScreen[offset + _displayWidth] = color; - } - if (drawMask & GFX_SCREEN_MASK_PRIORITY) { - _priorityScreen[offset] = priority; - _priorityScreen[offset + _width] = priority; - } - if (drawMask & GFX_SCREEN_MASK_CONTROL) { - _controlScreen[offset] = control; - _controlScreen[offset + _width] = control; - } -} - -// Directly sets a pixel on various screens, display is not upscaled -void GfxScreen::putPixelNormal(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) { - int offset = y * _width + x; - - if (drawMask & GFX_SCREEN_MASK_VISUAL) { - _visualScreen[offset] = color; - _displayScreen[offset] = color; - } - if (drawMask & GFX_SCREEN_MASK_PRIORITY) - _priorityScreen[offset] = priority; - if (drawMask & GFX_SCREEN_MASK_CONTROL) - _controlScreen[offset] = control; -} - -// Directly sets a pixel on various screens, display IS upscaled -void GfxScreen::putPixelDisplayUpscaled(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) { - int offset = y * _width + x; - - if (drawMask & GFX_SCREEN_MASK_VISUAL) { - _visualScreen[offset] = color; - putScaledPixelOnScreen(_displayScreen, x, y, color); - } - if (drawMask & GFX_SCREEN_MASK_PRIORITY) - _priorityScreen[offset] = priority; - if (drawMask & GFX_SCREEN_MASK_CONTROL) - _controlScreen[offset] = control; -} - -// Directly sets a pixel on various screens, ALL screens ARE upscaled -void GfxScreen::putPixelAllUpscaled(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) { - if (drawMask & GFX_SCREEN_MASK_VISUAL) { - putScaledPixelOnScreen(_visualScreen, x, y, color); - putScaledPixelOnScreen(_displayScreen, x, y, color); - } - if (drawMask & GFX_SCREEN_MASK_PRIORITY) - putScaledPixelOnScreen(_priorityScreen, x, y, priority); - if (drawMask & GFX_SCREEN_MASK_CONTROL) - putScaledPixelOnScreen(_controlScreen, x, y, control); -} - -/** - * This is used to put font pixels onto the screen - we adjust differently, so that we won't - * do triple pixel lines in any case on upscaled hires. That way the font will not get distorted - * Sierra SCI didn't do this - */ -void GfxScreen::putFontPixel(int16 startingY, int16 x, int16 y, byte color) { - int16 actualY = startingY + y; - if (_fontIsUpscaled) { - // Do not scale ourselves, but put it on the display directly - putPixelOnDisplay(x, actualY, color); - } else { - int offset = actualY * _width + x; - - _visualScreen[offset] = color; - switch (_upscaledHires) { - case GFX_SCREEN_UPSCALED_DISABLED: - _displayScreen[offset] = color; - break; - case GFX_SCREEN_UPSCALED_640x400: - case GFX_SCREEN_UPSCALED_640x440: - case GFX_SCREEN_UPSCALED_640x480: { - // to 1-> 4 pixels upscaling for all of those, so that fonts won't look weird - int displayOffset = (_upscaledHeightMapping[startingY] + y * 2) * _displayWidth + x * 2; - _displayScreen[displayOffset] = color; - _displayScreen[displayOffset + 1] = color; - displayOffset += _displayWidth; - _displayScreen[displayOffset] = color; - _displayScreen[displayOffset + 1] = color; - break; - } - default: - putScaledPixelOnScreen(_displayScreen, x, actualY, color); - break; - } - } -} - -/** - * This will just change a pixel directly on displayscreen. It is supposed to be - * only used on upscaled-Hires games where hires content needs to get drawn ONTO - * the upscaled display screen (like japanese fonts, hires portraits, etc.). - */ -void GfxScreen::putPixelOnDisplay(int16 x, int16 y, byte color) { - int offset = y * _displayWidth + x; - _displayScreen[offset] = color; -} - -//void GfxScreen::putScaledPixelOnDisplay(int16 x, int16 y, byte color) { -//} - -void GfxScreen::putScaledPixelOnScreen(byte *screen, int16 x, int16 y, byte data) { - int displayOffset = _upscaledHeightMapping[y] * _displayWidth + _upscaledWidthMapping[x]; - int heightOffsetBreak = (_upscaledHeightMapping[y + 1] - _upscaledHeightMapping[y]) * _displayWidth; - int heightOffset = 0; - int widthOffsetBreak = _upscaledWidthMapping[x + 1] - _upscaledWidthMapping[x]; - do { - int widthOffset = 0; - do { - screen[displayOffset + heightOffset + widthOffset] = data; - widthOffset++; - } while (widthOffset != widthOffsetBreak); - heightOffset += _displayWidth; - } while (heightOffset != heightOffsetBreak); -} - /** * Sierra's Bresenham line drawing. * WARNING: Do not replace this with Graphics::drawLine(), as this causes issues @@ -595,16 +476,6 @@ void GfxScreen::putKanjiChar(Graphics::FontSJIS *commonFont, int16 x, int16 y, u commonFont->drawChar(displayPtr, chr, _displayWidth, 1, color, 0, -1, -1); } -byte GfxScreen::getPixelNormal(byte *screen, int16 x, int16 y) { - return screen[y * _width + x]; -} - -byte GfxScreen::getPixelUpscaled(byte *screen, int16 x, int16 y) { - int16 mappedX = _upscaledWidthMapping[x]; - int16 mappedY = _upscaledHeightMapping[y]; - return screen[mappedY * _width + mappedX]; -} - int GfxScreen::bitsGetDataSize(Common::Rect rect, byte mask) { int byteCount = sizeof(rect) + sizeof(mask); int pixels = rect.width() * rect.height(); @@ -615,7 +486,7 @@ int GfxScreen::bitsGetDataSize(Common::Rect rect, byte mask) { } else { int rectHeight = _upscaledHeightMapping[rect.bottom] - _upscaledHeightMapping[rect.top]; int rectWidth = _upscaledWidthMapping[rect.right] - _upscaledWidthMapping[rect.left]; - byteCount += rectHeight * rect.width() * rectWidth; // _displayScreen (upscaled hires) + byteCount += rectHeight * rectWidth; // _displayScreen (upscaled hires) } } if (mask & GFX_SCREEN_MASK_PRIORITY) { @@ -629,7 +500,6 @@ int GfxScreen::bitsGetDataSize(Common::Rect rect, byte mask) { error("bitsGetDataSize() called w/o being in upscaled hires mode"); byteCount += pixels; // _displayScreen (coordinates actually are given to us for hires displayScreen) } - return byteCount; } @@ -796,7 +666,7 @@ void GfxScreen::dither(bool addToFlag) { *displayPtr = color; break; default: - putScaledPixelOnScreen(_displayScreen, x, y, color); + putScaledPixelOnDisplay(x, y, color); break; } *visualPtr = color; @@ -828,7 +698,7 @@ void GfxScreen::dither(bool addToFlag) { *displayPtr = ditheredColor; break; default: - putScaledPixelOnScreen(_displayScreen, x, y, ditheredColor); + putScaledPixelOnDisplay(x, y, ditheredColor); break; } color = ((x^y) & 1) ? color >> 4 : color & 0x0F; diff --git a/engines/sci/graphics/screen.h b/engines/sci/graphics/screen.h index 1c946ef02f..65416252f6 100644 --- a/engines/sci/graphics/screen.h +++ b/engines/sci/graphics/screen.h @@ -76,6 +76,11 @@ public: byte getColorWhite() { return _colorWhite; } byte getColorDefaultVectorData() { return _colorDefaultVectorData; } +#ifdef ENABLE_SCI32 + byte *getDisplayScreen() { return _displayScreen; } + byte *getPriorityScreen() { return _priorityScreen; } +#endif + void clearForRestoreGame(); void copyToScreen(); void copyFromScreen(byte *buffer); @@ -84,51 +89,16 @@ public: void copyDisplayRectToScreen(const Common::Rect &rect); void copyRectToScreen(const Common::Rect &rect, int16 x, int16 y); - // calls to code pointers - void inline vectorAdjustCoordinate (int16 *x, int16 *y) { - (this->*_vectorAdjustCoordinatePtr)(x, y); - } - void inline vectorAdjustLineCoordinates (int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control) { - (this->*_vectorAdjustLineCoordinatesPtr)(left, top, right, bottom, drawMask, color, priority, control); - } - byte inline vectorIsFillMatch (int16 x, int16 y, byte screenMask, byte t_color, byte t_pri, byte t_con, bool isEGA) { - return (this->*_vectorIsFillMatchPtr)(x, y, screenMask, t_color, t_pri, t_con, isEGA); - } - void inline vectorPutPixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) { - (this->*_vectorPutPixelPtr)(x, y, drawMask, color, priority, control); - } - void inline vectorPutLinePixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) { - (this->*_vectorPutLinePixelPtr)(x, y, drawMask, color, priority, control); - } - byte inline vectorGetVisual(int16 x, int16 y) { - return (this->*_vectorGetPixelPtr)(_visualScreen, x, y); - } - byte inline vectorGetPriority(int16 x, int16 y) { - return (this->*_vectorGetPixelPtr)(_priorityScreen, x, y); - } - byte inline vectorGetControl(int16 x, int16 y) { - return (this->*_vectorGetPixelPtr)(_controlScreen, x, y); - } - - - void inline putPixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) { - (this->*_putPixelPtr)(x, y, drawMask, color, priority, control); - } + // Vector drawing +private: + void vectorPutLinePixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control); + void vectorPutLinePixel480x300(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control); - byte inline getVisual(int16 x, int16 y) { - return (this->*_getPixelPtr)(_visualScreen, x, y); - } - byte inline getPriority(int16 x, int16 y) { - return (this->*_getPixelPtr)(_priorityScreen, x, y); - } - byte inline getControl(int16 x, int16 y) { - return (this->*_getPixelPtr)(_controlScreen, x, y); - } +public: + void vectorAdjustLineCoordinates(int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control); + byte vectorIsFillMatch(int16 x, int16 y, byte screenMask, byte checkForColor, byte checkForPriority, byte checkForControl, bool isEGA); byte getDrawingMask(byte color, byte prio, byte control); - //void putPixel(int16 x, int16 y, byte drawMask, byte color, byte prio, byte control); - void putFontPixel(int16 startingY, int16 x, int16 y, byte color); - void putPixelOnDisplay(int16 x, int16 y, byte color); void drawLine(Common::Point startPoint, Common::Point endPoint, byte color, byte prio, byte control); void drawLine(int16 left, int16 top, int16 right, int16 bottom, byte color, byte prio, byte control) { drawLine(Common::Point(left, top), Common::Point(right, bottom), color, prio, control); @@ -206,8 +176,8 @@ private: byte *_controlScreen; /** - * This screen is the one that is actually displayed to the user. It may be - * 640x400 for japanese SCI1 games. SCI0 games may be undithered in here. + * This screen is the one, where pixels are copied out of into the frame buffer. + * It may be 640x400 for japanese SCI1 games. SCI0 games may be undithered in here. * Only read from this buffer for Save/ShowBits usage. */ byte *_displayScreen; @@ -215,8 +185,8 @@ private: ResourceManager *_resMan; /** - * Pointer to the currently active screen (changing it only required for - * debug purposes). + * Pointer to the currently active screen (changing only required for + * debug purposes, to show for example the priority screen). */ byte *_activeScreen; @@ -239,38 +209,241 @@ private: */ bool _fontIsUpscaled; - // dynamic code - void (GfxScreen::*_vectorAdjustCoordinatePtr) (int16 *x, int16 *y); - void vectorAdjustCoordinateNOP (int16 *x, int16 *y); - void vectorAdjustCoordinate480x300Mac (int16 *x, int16 *y); - void (GfxScreen::*_vectorAdjustLineCoordinatesPtr) (int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control); - void vectorAdjustLineCoordinatesNOP (int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control); - void vectorAdjustLineCoordinates480x300Mac (int16 *left, int16 *top, int16 *right, int16 *bottom, byte drawMask, byte color, byte priority, byte control); - - byte (GfxScreen::*_vectorIsFillMatchPtr) (int16 x, int16 y, byte screenMask, byte t_color, byte t_pri, byte t_con, bool isEGA); - byte vectorIsFillMatchNormal (int16 x, int16 y, byte screenMask, byte t_color, byte t_pri, byte t_con, bool isEGA); - byte vectorIsFillMatch480x300Mac (int16 x, int16 y, byte screenMask, byte t_color, byte t_pri, byte t_con, bool isEGA); + // pixel related code, in header so that it can be inlined for performance +public: + void putPixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) { + if (_upscaledHires == GFX_SCREEN_UPSCALED_480x300) { + putPixel480x300(x, y, drawMask, color, priority, control); + return; + } + + // Set pixel for visual, priority and control map directly, those are not upscaled + int offset = y * _width + x; + + if (drawMask & GFX_SCREEN_MASK_VISUAL) { + _visualScreen[offset] = color; + + int displayOffset = 0; + + switch (_upscaledHires) { + case GFX_SCREEN_UPSCALED_DISABLED: + displayOffset = offset; + _displayScreen[displayOffset] = color; + break; + + case GFX_SCREEN_UPSCALED_640x400: + case GFX_SCREEN_UPSCALED_640x440: + case GFX_SCREEN_UPSCALED_640x480: + putScaledPixelOnDisplay(x, y, color); + break; + default: + break; + } + } + if (drawMask & GFX_SCREEN_MASK_PRIORITY) { + _priorityScreen[offset] = priority; + } + if (drawMask & GFX_SCREEN_MASK_CONTROL) { + _controlScreen[offset] = control; + } + } - void (GfxScreen::*_vectorPutPixelPtr) (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control); - void vectorPutPixel480x300Mac (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control); + void putPixel480x300(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) { + int offset = ((y * 3) / 2 * _width) + ((x * 3) / 2); + + // All maps are upscaled + // TODO: figure out, what Sierra exactly did on Mac for these games + if (drawMask & GFX_SCREEN_MASK_VISUAL) { + putPixel480x300Worker(x, y, offset, _visualScreen, color); + putPixel480x300Worker(x, y, offset, _displayScreen, color); + } + if (drawMask & GFX_SCREEN_MASK_PRIORITY) { + putPixel480x300Worker(x, y, offset, _priorityScreen, priority); + } + if (drawMask & GFX_SCREEN_MASK_CONTROL) { + putPixel480x300Worker(x, y, offset, _controlScreen, control); + } + } + void putPixel480x300Worker(int16 x, int16 y, int offset, byte *screen, byte byteToSet) { + screen[offset] = byteToSet; + if (x & 1) + screen[offset + 1] = byteToSet; + if (y & 1) + screen[offset + _width] = byteToSet; + if ((x & 1) && (y & 1)) + screen[offset + _width + 1] = byteToSet; + } - void (GfxScreen::*_vectorPutLinePixelPtr) (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control); - void vectorPutLinePixel480x300Mac (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control); + // This is called from vector drawing to put a pixel at a certain location + void vectorPutPixel(int16 x, int16 y, byte drawMask, byte color, byte priority, byte control) { + switch (_upscaledHires) { + case GFX_SCREEN_UPSCALED_640x400: + case GFX_SCREEN_UPSCALED_640x440: + case GFX_SCREEN_UPSCALED_640x480: + // For regular upscaled modes forward to the regular putPixel + putPixel(x, y, drawMask, color, priority, control); + return; + break; + + default: + break; + } + + // For non-upscaled mode and 480x300 Mac put pixels directly + int offset = y * _width + x; + + if (drawMask & GFX_SCREEN_MASK_VISUAL) { + _visualScreen[offset] = color; + _displayScreen[offset] = color; + } + if (drawMask & GFX_SCREEN_MASK_PRIORITY) { + _priorityScreen[offset] = priority; + } + if (drawMask & GFX_SCREEN_MASK_CONTROL) { + _controlScreen[offset] = control; + } + } - byte (GfxScreen::*_vectorGetPixelPtr) (byte *screen, int16 x, int16 y); + /** + * This will just change a pixel directly on displayscreen. It is supposed to be + * only used on upscaled-Hires games where hires content needs to get drawn ONTO + * the upscaled display screen (like japanese fonts, hires portraits, etc.). + */ + void putPixelOnDisplay(int16 x, int16 y, byte color) { + int offset = y * _displayWidth + x; + _displayScreen[offset] = color; + } - void (GfxScreen::*_putPixelPtr) (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control); - void putPixelNormal (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control); - void putPixelDisplayUpscaled (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control); - void putPixelAllUpscaled (int16 x, int16 y, byte drawMask, byte color, byte priority, byte control); + // Upscales a pixel and puts it on display screen only + void putScaledPixelOnDisplay(int16 x, int16 y, byte color) { + int displayOffset = 0; + + switch (_upscaledHires) { + case GFX_SCREEN_UPSCALED_640x400: + displayOffset = (y * 2) * _displayWidth + x * 2; // straight 1 pixel -> 2 mapping + + _displayScreen[displayOffset] = color; + _displayScreen[displayOffset + 1] = color; + _displayScreen[displayOffset + _displayWidth] = color; + _displayScreen[displayOffset + _displayWidth + 1] = color; + break; + + case GFX_SCREEN_UPSCALED_640x440: { + int16 startY = (y * 11) / 5; + int16 endY = ((y + 1) * 11) / 5; + displayOffset = (startY * _displayWidth) + x * 2; + + for (int16 curY = startY; curY < endY; curY++) { + _displayScreen[displayOffset] = color; + _displayScreen[displayOffset + 1] = color; + displayOffset += _displayWidth; + } + break; + } + case GFX_SCREEN_UPSCALED_640x480: { + int16 startY = (y * 12) / 5; + int16 endY = ((y + 1) * 12) / 5; + displayOffset = (startY * _displayWidth) + x * 2; + + for (int16 curY = startY; curY < endY; curY++) { + _displayScreen[displayOffset] = color; + _displayScreen[displayOffset + 1] = color; + displayOffset += _displayWidth; + } + break; + } + default: + break; + } + } - byte (GfxScreen::*_getPixelPtr) (byte *screen, int16 x, int16 y); - byte getPixelNormal (byte *screen, int16 x, int16 y); - byte getPixelUpscaled (byte *screen, int16 x, int16 y); + /** + * This is used to put font pixels onto the screen - we adjust differently, so that we won't + * do triple pixel lines in any case on upscaled hires. That way the font will not get distorted + * Sierra SCI didn't do this + */ + void putFontPixel(int16 startingY, int16 x, int16 y, byte color) { + int16 actualY = startingY + y; + if (_fontIsUpscaled) { + // Do not scale ourselves, but put it on the display directly + putPixelOnDisplay(x, actualY, color); + } else { + int offset = actualY * _width + x; + + _visualScreen[offset] = color; + switch (_upscaledHires) { + case GFX_SCREEN_UPSCALED_DISABLED: + _displayScreen[offset] = color; + break; + case GFX_SCREEN_UPSCALED_640x400: + case GFX_SCREEN_UPSCALED_640x440: + case GFX_SCREEN_UPSCALED_640x480: { + // to 1-> 4 pixels upscaling for all of those, so that fonts won't look weird + int displayOffset = (_upscaledHeightMapping[startingY] + y * 2) * _displayWidth + x * 2; + _displayScreen[displayOffset] = color; + _displayScreen[displayOffset + 1] = color; + displayOffset += _displayWidth; + _displayScreen[displayOffset] = color; + _displayScreen[displayOffset + 1] = color; + break; + } + default: + putScaledPixelOnDisplay(x, actualY, color); + break; + } + } + } - // pixel helper - void putScaledPixelOnScreen(byte *screen, int16 x, int16 y, byte color); + byte getPixel(byte *screen, int16 x, int16 y) { + switch (_upscaledHires) { + case GFX_SCREEN_UPSCALED_480x300: { + int offset = ((y * 3) / 2) * _width + ((y * 3) / 2); + + return screen[offset]; + break; + } + default: + break; + } + return screen[y * _width + x]; + } + + byte getVisual(int16 x, int16 y) { + return getPixel(_visualScreen, x, y); + } + byte getPriority(int16 x, int16 y) { + return getPixel(_priorityScreen, x, y); + } + byte getControl(int16 x, int16 y) { + return getPixel(_controlScreen, x, y); + } + + // Vector related public code - in here, so that it can be inlined + byte vectorGetPixel(byte *screen, int16 x, int16 y) { + return screen[y * _width + x]; + } + + byte vectorGetVisual(int16 x, int16 y) { + return vectorGetPixel(_visualScreen, x, y); + } + byte vectorGetPriority(int16 x, int16 y) { + return vectorGetPixel(_priorityScreen, x, y); + } + byte vectorGetControl(int16 x, int16 y) { + return vectorGetPixel(_controlScreen, x, y); + } + + void vectorAdjustCoordinate(int16 *x, int16 *y) { + switch (_upscaledHires) { + case GFX_SCREEN_UPSCALED_480x300: + *x = (*x * 3) / 2; + *y = (*y * 3) / 2; + break; + default: + break; + } + } }; } // End of namespace Sci diff --git a/engines/sci/graphics/screen_item32.cpp b/engines/sci/graphics/screen_item32.cpp new file mode 100644 index 0000000000..c3fdbb6845 --- /dev/null +++ b/engines/sci/graphics/screen_item32.cpp @@ -0,0 +1,647 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "sci/console.h" +#include "sci/resource.h" +#include "sci/engine/kernel.h" +#include "sci/engine/selector.h" +#include "sci/engine/state.h" +#include "sci/graphics/celobj32.h" +#include "sci/graphics/frameout.h" +#include "sci/graphics/screen_item32.h" +#include "sci/graphics/view.h" + +namespace Sci { +#pragma mark ScreenItem + +uint16 ScreenItem::_nextObjectId = 20000; + +ScreenItem::ScreenItem(const reg_t object) : +_celObj(nullptr), +_object(object), +_pictureId(-1), +_created(g_sci->_gfxFrameout->getScreenCount()), +_updated(0), +_deleted(0), +_mirrorX(false) { + SegManager *segMan = g_sci->getEngineState()->_segMan; + + setFromObject(segMan, object, true, true); + _plane = readSelector(segMan, object, SELECTOR(plane)); +} + +ScreenItem::ScreenItem(const reg_t plane, const CelInfo32 &celInfo) : +_plane(plane), +_useInsetRect(false), +_z(0), +_celInfo(celInfo), +_celObj(nullptr), +_fixPriority(false), +_position(0, 0), +_object(make_reg(0, _nextObjectId++)), +_pictureId(-1), +_created(g_sci->_gfxFrameout->getScreenCount()), +_updated(0), +_deleted(0), +_mirrorX(false) {} + +ScreenItem::ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect) : +_plane(plane), +_useInsetRect(false), +_z(0), +_celInfo(celInfo), +_celObj(nullptr), +_fixPriority(false), +_position(rect.left, rect.top), +_object(make_reg(0, _nextObjectId++)), +_pictureId(-1), +_created(g_sci->_gfxFrameout->getScreenCount()), +_updated(0), +_deleted(0), +_mirrorX(false) { + if (celInfo.type == kCelTypeColor) { + _insetRect = rect; + } +} + +ScreenItem::ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Point &position, const ScaleInfo &scaleInfo) : +_plane(plane), +_scale(scaleInfo), +_useInsetRect(false), +_z(0), +_celInfo(celInfo), +_celObj(nullptr), +_fixPriority(false), +_position(position), +_object(make_reg(0, _nextObjectId++)), +_pictureId(-1), +_created(g_sci->_gfxFrameout->getScreenCount()), +_updated(0), +_deleted(0), +_mirrorX(false) {} + +ScreenItem::ScreenItem(const ScreenItem &other) : +_plane(other._plane), +_scale(other._scale), +_useInsetRect(other._useInsetRect), +_celInfo(other._celInfo), +_celObj(nullptr), +_object(other._object), +_mirrorX(other._mirrorX), +_scaledPosition(other._scaledPosition), +_screenRect(other._screenRect) { + if (other._useInsetRect) { + _insetRect = other._insetRect; + } +} + +void ScreenItem::operator=(const ScreenItem &other) { + // NOTE: The original engine did not check for differences in `_celInfo` + // to clear `_celObj` here; instead, it unconditionally set `_celInfo`, + // didn't clear `_celObj`, and did hacky stuff in `kIsOnMe` to avoid + // testing a mismatched `_celObj`. See `GfxFrameout::kernelIsOnMe` for + // more detail. + if (_celInfo != other._celInfo) { + _celInfo = other._celInfo; + delete _celObj; + _celObj = nullptr; + } + + _screenRect = other._screenRect; + _mirrorX = other._mirrorX; + _useInsetRect = other._useInsetRect; + if (other._useInsetRect) { + _insetRect = other._insetRect; + } + _scale = other._scale; + _scaledPosition = other._scaledPosition; +} + +ScreenItem::~ScreenItem() { + delete _celObj; +} + +void ScreenItem::init() { + _nextObjectId = 20000; +} + +void ScreenItem::setFromObject(SegManager *segMan, const reg_t object, const bool updateCel, const bool updateBitmap) { + _position.x = readSelectorValue(segMan, object, SELECTOR(x)); + _position.y = readSelectorValue(segMan, object, SELECTOR(y)); + _scale.x = readSelectorValue(segMan, object, SELECTOR(scaleX)); + _scale.y = readSelectorValue(segMan, object, SELECTOR(scaleY)); + _scale.max = readSelectorValue(segMan, object, SELECTOR(maxScale)); + _scale.signal = (ScaleSignals32)(readSelectorValue(segMan, object, SELECTOR(scaleSignal)) & 3); + + if (updateCel) { + _celInfo.resourceId = (GuiResourceId)readSelectorValue(segMan, object, SELECTOR(view)); + _celInfo.loopNo = readSelectorValue(segMan, object, SELECTOR(loop)); + _celInfo.celNo = readSelectorValue(segMan, object, SELECTOR(cel)); + + if (_celInfo.resourceId <= kPlanePic) { + // TODO: Enhance GfxView or ResourceManager to allow + // metadata for resources to be retrieved once, from a + // single location + Resource *view = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, _celInfo.resourceId), false); + if (!view) { + error("Failed to load resource %d", _celInfo.resourceId); + } + + // NOTE: +2 because the header size field itself is excluded from + // the header size in the data + const uint16 headerSize = READ_SCI11ENDIAN_UINT16(view->data) + 2; + const uint8 loopCount = view->data[2]; + const uint8 loopSize = view->data[12]; + + if (_celInfo.loopNo >= loopCount) { + const int maxLoopNo = loopCount - 1; + _celInfo.loopNo = maxLoopNo; + writeSelectorValue(segMan, object, SELECTOR(loop), maxLoopNo); + } + + byte *loopData = view->data + headerSize + (_celInfo.loopNo * loopSize); + const int8 seekEntry = loopData[0]; + if (seekEntry != -1) { + loopData = view->data + headerSize + (seekEntry * loopSize); + } + const uint8 celCount = loopData[2]; + if (_celInfo.celNo >= celCount) { + const int maxCelNo = celCount - 1; + _celInfo.celNo = maxCelNo; + writeSelectorValue(segMan, object, SELECTOR(cel), maxCelNo); + } + } + } + + if (updateBitmap) { + const reg_t bitmap = readSelector(segMan, object, SELECTOR(bitmap)); + if (!bitmap.isNull()) { + _celInfo.bitmap = bitmap; + _celInfo.type = kCelTypeMem; + } else { + _celInfo.bitmap = NULL_REG; + _celInfo.type = kCelTypeView; + } + } + + if (updateCel || updateBitmap) { + delete _celObj; + _celObj = nullptr; + } + + if (readSelectorValue(segMan, object, SELECTOR(fixPriority))) { + _fixPriority = true; + _priority = readSelectorValue(segMan, object, SELECTOR(priority)); + } else { + _fixPriority = false; + writeSelectorValue(segMan, object, SELECTOR(priority), _position.y); + } + + _z = readSelectorValue(segMan, object, SELECTOR(z)); + _position.y -= _z; + + if (readSelectorValue(segMan, object, SELECTOR(useInsetRect))) { + _useInsetRect = true; + _insetRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft)); + _insetRect.top = readSelectorValue(segMan, object, SELECTOR(inTop)); + _insetRect.right = readSelectorValue(segMan, object, SELECTOR(inRight)) + 1; + _insetRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom)) + 1; + } else { + _useInsetRect = false; + } + + segMan->getObject(object)->clearInfoSelectorFlag(kInfoFlagViewVisible); +} + +void ScreenItem::calcRects(const Plane &plane) { + const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth; + const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight; + + const CelObj &celObj = getCelObj(); + + Common::Rect celRect(celObj._width, celObj._height); + if (_useInsetRect) { + if (_insetRect.intersects(celRect)) { + _insetRect.clip(celRect); + } else { + _insetRect = Common::Rect(); + } + } else { + _insetRect = celRect; + } + + Ratio scaleX, scaleY; + + if (_scale.signal & kScaleSignalDoScaling32) { + if (_scale.signal & kScaleSignalUseVanishingPoint) { + int num = _scale.max * (_position.y - plane._vanishingPoint.y) / (scriptWidth - plane._vanishingPoint.y); + scaleX = Ratio(num, 128); + scaleY = Ratio(num, 128); + } else { + scaleX = Ratio(_scale.x, 128); + scaleY = Ratio(_scale.y, 128); + } + } + + if (scaleX.getNumerator() && scaleY.getNumerator()) { + _screenItemRect = _insetRect; + + const Ratio celToScreenX(screenWidth, celObj._scaledWidth); + const Ratio celToScreenY(screenHeight, celObj._scaledHeight); + + // Cel may use a coordinate system that is not the same size as the + // script coordinate system (usually this means high-resolution + // pictures with low-resolution scripts) + if (celObj._scaledWidth != scriptWidth || celObj._scaledHeight != scriptHeight) { + if (_useInsetRect) { + const Ratio scriptToCelX(celObj._scaledWidth, scriptWidth); + const Ratio scriptToCelY(celObj._scaledHeight, scriptHeight); + mulru(_screenItemRect, scriptToCelX, scriptToCelY, 0); + + if (_screenItemRect.intersects(celRect)) { + _screenItemRect.clip(celRect); + } else { + _screenItemRect = Common::Rect(); + } + } + + int displaceX = celObj._displace.x; + int displaceY = celObj._displace.y; + + if (_mirrorX != celObj._mirrorX && _celInfo.type != kCelTypePic) { + displaceX = celObj._width - celObj._displace.x - 1; + } + + if (!scaleX.isOne() || !scaleY.isOne()) { + mulinc(_screenItemRect, scaleX, scaleY); + displaceX = (displaceX * scaleX).toInt(); + displaceY = (displaceY * scaleY).toInt(); + } + + mulinc(_screenItemRect, celToScreenX, celToScreenY); + displaceX = (displaceX * celToScreenX).toInt(); + displaceY = (displaceY * celToScreenY).toInt(); + + const Ratio scriptToScreenX = Ratio(screenWidth, scriptWidth); + const Ratio scriptToScreenY = Ratio(screenHeight, scriptHeight); + + if (/* TODO: dword_C6288 */ false && _celInfo.type == kCelTypePic) { + _scaledPosition.x = _position.x; + _scaledPosition.y = _position.y; + } else { + _scaledPosition.x = (_position.x * scriptToScreenX).toInt() - displaceX; + _scaledPosition.y = (_position.y * scriptToScreenY).toInt() - displaceY; + } + + _screenItemRect.translate(_scaledPosition.x, _scaledPosition.y); + + if (_mirrorX != celObj._mirrorX && _celInfo.type == kCelTypePic) { + Common::Rect temp(_insetRect); + + if (!scaleX.isOne()) { + mulinc(temp, scaleX, Ratio()); + } + + mulinc(temp, celToScreenX, Ratio()); + + CelObjPic *celObjPic = dynamic_cast<CelObjPic *>(_celObj); + temp.translate((celObjPic->_relativePosition.x * scriptToScreenX).toInt() - displaceX, 0); + + // TODO: This is weird. + int deltaX = plane._planeRect.width() - temp.right - 1 - temp.left; + + _scaledPosition.x += deltaX; + _screenItemRect.translate(deltaX, 0); + } + + _scaledPosition.x += plane._planeRect.left; + _scaledPosition.y += plane._planeRect.top; + _screenItemRect.translate(plane._planeRect.left, plane._planeRect.top); + + _ratioX = scaleX * celToScreenX; + _ratioY = scaleY * celToScreenY; + } else { + int displaceX = celObj._displace.x; + if (_mirrorX != celObj._mirrorX && _celInfo.type != kCelTypePic) { + displaceX = celObj._width - celObj._displace.x - 1; + } + + if (!scaleX.isOne() || !scaleY.isOne()) { + mulinc(_screenItemRect, scaleX, scaleY); + // TODO: This was in the original code, baked into the + // multiplication though it is not immediately clear + // why this is the only one that reduces the BR corner + _screenItemRect.right -= 1; + _screenItemRect.bottom -= 1; + } + + _scaledPosition.x = _position.x - (displaceX * scaleX).toInt(); + _scaledPosition.y = _position.y - (celObj._displace.y * scaleY).toInt(); + _screenItemRect.translate(_scaledPosition.x, _scaledPosition.y); + + if (_mirrorX != celObj._mirrorX && _celInfo.type == kCelTypePic) { + Common::Rect temp(_insetRect); + + if (!scaleX.isOne()) { + mulinc(temp, scaleX, Ratio()); + temp.right -= 1; + } + + CelObjPic *celObjPic = dynamic_cast<CelObjPic *>(_celObj); + temp.translate(celObjPic->_relativePosition.x - (displaceX * scaleX).toInt(), celObjPic->_relativePosition.y - (celObj._displace.y * scaleY).toInt()); + + // TODO: This is weird. + int deltaX = plane._gameRect.width() - temp.right - 1 - temp.left; + + _scaledPosition.x += deltaX; + _screenItemRect.translate(deltaX, 0); + } + + _scaledPosition.x += plane._gameRect.left; + _scaledPosition.y += plane._gameRect.top; + _screenItemRect.translate(plane._gameRect.left, plane._gameRect.top); + + if (celObj._scaledWidth != screenWidth || celObj._scaledHeight != screenHeight) { + mulru(_scaledPosition, celToScreenX, celToScreenY); + mulru(_screenItemRect, celToScreenX, celToScreenY, 1); + } + + _ratioX = scaleX * celToScreenX; + _ratioY = scaleY * celToScreenY; + } + + _screenRect = _screenItemRect; + + if (_screenRect.intersects(plane._screenRect)) { + _screenRect.clip(plane._screenRect); + } else { + _screenRect.right = 0; + _screenRect.bottom = 0; + _screenRect.left = 0; + _screenRect.top = 0; + } + + if (!_fixPriority) { + _priority = _z + _position.y; + } + } else { + _screenRect.left = 0; + _screenRect.top = 0; + _screenRect.right = 0; + _screenRect.bottom = 0; + } +} + +CelObj &ScreenItem::getCelObj() const { + if (_celObj == nullptr) { + switch (_celInfo.type) { + case kCelTypeView: + _celObj = new CelObjView(_celInfo.resourceId, _celInfo.loopNo, _celInfo.celNo); + break; + case kCelTypePic: + error("Internal error, pic screen item with no cel."); + break; + case kCelTypeMem: + _celObj = new CelObjMem(_celInfo.bitmap); + break; + case kCelTypeColor: + _celObj = new CelObjColor(_celInfo.color, _insetRect.width(), _insetRect.height()); + break; + } + } + + return *_celObj; +} + +void ScreenItem::printDebugInfo(Console *con) const { + con->debugPrintf("%04x:%04x (%s), prio %d, x %d, y %d, z: %d, scaledX: %d, scaledY: %d flags: %d\n", + _object.getSegment(), _object.getOffset(), + g_sci->getEngineState()->_segMan->getObjectName(_object), + _priority, + _position.x, + _position.y, + _z, + _scaledPosition.x, + _scaledPosition.y, + _created | (_updated << 1) | (_deleted << 2) + ); + con->debugPrintf(" screen rect (%d, %d, %d, %d)\n", PRINT_RECT(_screenRect)); + if (_useInsetRect) { + con->debugPrintf(" inset rect: (%d, %d, %d, %d)\n", PRINT_RECT(_insetRect)); + } + + Common::String celType; + switch (_celInfo.type) { + case kCelTypePic: + celType = "pic"; + break; + case kCelTypeView: + celType = "view"; + break; + case kCelTypeColor: + celType = "color"; + break; + case kCelTypeMem: + celType = "mem"; + break; + } + + con->debugPrintf(" type: %s, res %d, loop %d, cel %d, bitmap %04x:%04x, color: %d\n", + celType.c_str(), + _celInfo.resourceId, + _celInfo.loopNo, + _celInfo.celNo, + PRINT_REG(_celInfo.bitmap), + _celInfo.color + ); + if (_celObj != nullptr) { + con->debugPrintf(" width %d, height %d, scaledWidth %d, scaledHeight %d\n", + _celObj->_width, + _celObj->_height, + _celObj->_scaledWidth, + _celObj->_scaledHeight + ); + } +} + +void ScreenItem::update(const reg_t object) { + SegManager *segMan = g_sci->getEngineState()->_segMan; + + const GuiResourceId view = readSelectorValue(segMan, object, SELECTOR(view)); + const int16 loopNo = readSelectorValue(segMan, object, SELECTOR(loop)); + const int16 celNo = readSelectorValue(segMan, object, SELECTOR(cel)); + + const bool updateCel = ( + _celInfo.resourceId != view || + _celInfo.loopNo != loopNo || + _celInfo.celNo != celNo + ); + + const bool updateBitmap = !readSelector(segMan, object, SELECTOR(bitmap)).isNull(); + + setFromObject(segMan, object, updateCel, updateBitmap); + + if (!_created) { + _updated = g_sci->_gfxFrameout->getScreenCount(); + } + + _deleted = 0; +} + +// TODO: This code is quite similar to calcRects, so try to deduplicate +// if possible +Common::Rect ScreenItem::getNowSeenRect(const Plane &plane) const { + CelObj &celObj = getCelObj(); + + Common::Rect celObjRect(celObj._width, celObj._height); + Common::Rect nsRect; + + if (_useInsetRect) { + // TODO: This is weird. Checking to see if the inset rect is + // fully inside the bounds of the celObjRect, and then + // clipping to the celObjRect, is pretty useless. + if (_insetRect.right > 0 && _insetRect.bottom > 0 && _insetRect.left < celObj._width && _insetRect.top < celObj._height) { + nsRect = _insetRect; + nsRect.clip(celObjRect); + } else { + nsRect = Common::Rect(); + } + } else { + nsRect = celObjRect; + } + + const uint16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + const uint16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + + Ratio scaleX, scaleY; + if (_scale.signal & kScaleSignalDoScaling32) { + if (_scale.signal & kScaleSignalUseVanishingPoint) { + int num = _scale.max * (_position.y - plane._vanishingPoint.y) / (scriptWidth - plane._vanishingPoint.y); + scaleX = Ratio(num, 128); + scaleY = Ratio(num, 128); + } else { + scaleX = Ratio(_scale.x, 128); + scaleY = Ratio(_scale.y, 128); + } + } + + if (scaleX.getNumerator() == 0 || scaleY.getNumerator() == 0) { + return Common::Rect(); + } + + int16 displaceX = celObj._displace.x; + int16 displaceY = celObj._displace.y; + + if (_mirrorX != celObj._mirrorX && _celInfo.type != kCelTypePic) { + displaceX = celObj._width - displaceX - 1; + } + + if (celObj._scaledWidth != scriptWidth || celObj._scaledHeight != scriptHeight) { + if (_useInsetRect) { + Ratio scriptToCelX(celObj._scaledWidth, scriptWidth); + Ratio scriptToCelY(celObj._scaledHeight, scriptHeight); + mulru(nsRect, scriptToCelX, scriptToCelY, 0); + + // TODO: This is weird. Checking to see if the inset rect is + // fully inside the bounds of the celObjRect, and then + // clipping to the celObjRect, is pretty useless. + if (nsRect.right > 0 && nsRect.bottom > 0 && nsRect.left < celObj._width && nsRect.top < celObj._height) { + nsRect.clip(celObjRect); + } else { + nsRect = Common::Rect(); + } + } + + if (!scaleX.isOne() || !scaleY.isOne()) { + mulinc(nsRect, scaleX, scaleY); + // TODO: This was in the original code, baked into the + // multiplication though it is not immediately clear + // why this is the only one that reduces the BR corner + nsRect.right -= 1; + nsRect.bottom -= 1; + } + + Ratio celToScriptX(scriptWidth, celObj._scaledWidth); + Ratio celToScriptY(scriptHeight, celObj._scaledHeight); + + displaceX = (displaceX * scaleX * celToScriptX).toInt(); + displaceY = (displaceY * scaleY * celToScriptY).toInt(); + + mulinc(nsRect, celToScriptX, celToScriptY); + nsRect.translate(_position.x - displaceX, _position.y - displaceY); + } else { + if (!scaleX.isOne() || !scaleY.isOne()) { + mulinc(nsRect, scaleX, scaleY); + // TODO: This was in the original code, baked into the + // multiplication though it is not immediately clear + // why this is the only one that reduces the BR corner + nsRect.right -= 1; + nsRect.bottom -= 1; + } + + displaceX = (displaceX * scaleX).toInt(); + displaceY = (displaceY * scaleY).toInt(); + nsRect.translate(_position.x - displaceX, _position.y - displaceY); + + if (_mirrorX != celObj._mirrorX && _celInfo.type != kCelTypePic) { + nsRect.translate(plane._gameRect.width() - nsRect.width(), 0); + } + } + + return nsRect; +} + +#pragma mark - +#pragma mark ScreenItemList +ScreenItem *ScreenItemList::findByObject(const reg_t object) const { + const_iterator screenItemIt = Common::find_if(begin(), end(), FindByObject<ScreenItem *>(object)); + + if (screenItemIt == end()) { + return nullptr; + } + + return *screenItemIt; +} +void ScreenItemList::sort() { + // TODO: SCI engine used _unsorted as an array of indexes into the + // list itself and then performed the same swap operations on the + // _unsorted array as the _storage array during sorting, but the + // only reason to do this would be if some of the pointers in the + // list were replaced so the pointer values themselves couldn’t + // simply be recorded and then restored later. It is not yet + // verified whether this simplification of the sort/unsort is + // safe. + for (size_type i = 0; i < size(); ++i) { + _unsorted[i] = (*this)[i]; + } + + Common::sort(begin(), end(), sortHelper); +} +void ScreenItemList::unsort() { + for (size_type i = 0; i < size(); ++i) { + (*this)[i] = _unsorted[i]; + } +} + +} // End of namespace Sci diff --git a/engines/sci/graphics/screen_item32.h b/engines/sci/graphics/screen_item32.h new file mode 100644 index 0000000000..977d80ebad --- /dev/null +++ b/engines/sci/graphics/screen_item32.h @@ -0,0 +1,288 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SCI_GRAPHICS_SCREEN_ITEM32_H +#define SCI_GRAPHICS_SCREEN_ITEM32_H + +#include "common/rect.h" +#include "sci/graphics/celobj32.h" +#include "sci/graphics/lists32.h" + +namespace Sci { + +enum ScaleSignals32 { + kScaleSignalNone = 0, + kScaleSignalDoScaling32 = 1, // enables scaling when drawing that cel (involves scaleX and scaleY) + kScaleSignalUseVanishingPoint = 2, + // TODO: Is this actually a thing? I have not seen it and + // the original engine masks &3 where it uses scale signals. + kScaleSignalDisableGlobalScaling32 = 4 +}; + +struct ScaleInfo { + int x, y, max; + ScaleSignals32 signal; + ScaleInfo() : x(128), y(128), max(100), signal(kScaleSignalNone) {} +}; + +class CelObj; +class Plane; +class SegManager; + +#pragma mark - +#pragma mark ScreenItem + +/** + * A ScreenItem is the engine-side representation of a + * game script View. + */ +class ScreenItem { +private: + /** + * A serial used for screen items that are generated + * inside the graphics engine, rather than the + * interpreter. + */ + static uint16 _nextObjectId; + + /** + * The parent plane of this screen item. + */ + reg_t _plane; + +public: + /** + * Scaling data used to calculate the final screen + * dimensions of the screen item as well as the scaling + * ratios used when drawing the item to screen. + */ + ScaleInfo _scale; + +private: + /** + * The position & dimensions of the screen item in + * screen coordinates. This rect includes the offset + * of the parent plane, but is not clipped to the + * screen, so may include coordinates that are + * offscreen. + */ + Common::Rect _screenItemRect; + + /** + * TODO: Document + */ + bool _useInsetRect; + + /** + * TODO: Documentation + * The insetRect is also used to describe the fill + * rectangle of a screen item that is drawn using + * CelObjColor. + */ + Common::Rect _insetRect; + + /** + * The z-index of the screen item in pseudo-3D space. + * Higher values are drawn on top of lower values. + */ + int _z; + + /** + * Sets the common properties of a screen item that must + * be set both during creation and update of a screen + * item. + */ + void setFromObject(SegManager *segMan, const reg_t object, const bool updateCel, const bool updateBitmap); + +public: + /** + * A descriptor for the cel object represented by the + * screen item. + */ + CelInfo32 _celInfo; + + /** + * The cel object used to actually render the screen + * item. This member is populated by calling + * `getCelObj`. + */ + mutable CelObj *_celObj; + + /** + * If set, the priority for this screen item is fixed + * in place. Otherwise, the priority of the screen item + * is calculated from its y-position + z-index. + */ + bool _fixPriority; + + /** + * The rendering priority of the screen item, relative + * only to the other screen items within the same plane. + * Higher priorities are drawn above lower priorities. + */ + int16 _priority; + + /** + * The top-left corner of the screen item, in game + * script coordinates, relative to the parent plane. + */ + Common::Point _position; + + /** + * The associated View script object that was + * used to create the ScreenItem, or a numeric + * value in the case of a ScreenItem that was + * generated outside of the VM. + */ + reg_t _object; + + /** + * For screen items representing picture resources, + * the resource ID of the picture. + */ + GuiResourceId _pictureId; + + /** + * Flags indicating the state of the screen item. + * - `created` is set when the screen item is first + * created, either from a VM object or from within the + * engine itself + * - `updated` is set when `created` is not already set + * and the screen item is updated from a VM object + * - `deleted` is set by the parent plane, if the parent + * plane is a pic type and its picture resource ID has + * changed + */ + int _created, _updated, _deleted; // ? + + /** + * For screen items that represent picture cels, this + * value is set to match the `_mirrorX` property of the + * parent plane and indicates that the cel should be + * drawn horizontally mirrored. For final drawing, it is + * XORed with the `_mirrorX` property of the cel object. + * The cel object's `_mirrorX` property comes from the + * resource data itself. + */ + bool _mirrorX; + + /** + * The scaling ratios to use when drawing this screen + * item. These values are calculated according to the + * scale info whenever the screen item is updated. + */ + Ratio _ratioX, _ratioY; + + /** + * The top-left corner of the screen item, in screen + * coordinates. + */ + Common::Point _scaledPosition; + + /** + * The position & dimensions of the screen item in + * screen coordinates. This rect includes the offset of + * the parent plane and is clipped to the screen. + */ + Common::Rect _screenRect; + + /** + * Initialises static Plane members. + */ + static void init(); + + ScreenItem(const reg_t screenItem); + ScreenItem(const reg_t plane, const CelInfo32 &celInfo); + ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect); + ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Point &position, const ScaleInfo &scaleInfo); + ScreenItem(const ScreenItem &other); + ~ScreenItem(); + void operator=(const ScreenItem &); + + inline bool operator<(const ScreenItem &other) const { + if (_priority < other._priority) { + return true; + } + + if (_priority == other._priority) { + if (_position.y + _z < other._position.y + other._z) { + return true; + } + + if (_position.y + _z == other._position.y + other._z) { + return _object < other._object; + } + } + + return false; + } + + /** + * Calculates the dimensions and scaling parameters for + * the screen item, using the given plane as the parent + * plane for screen rect positioning. + * + * @note This method was called Update in SCI engine. + */ + void calcRects(const Plane &plane); + + /** + * Retrieves the corresponding cel object for this + * screen item. If a cel object does not already exist, + * one will be created and assigned. + */ + CelObj &getCelObj() const; + + void printDebugInfo(Console *con) const; + + /** + * Updates the properties of the screen item from a + * VM object. + */ + void update(const reg_t object); + + /** + * Gets the "now seen" rect for the screen item, which + * represents the current size and position of the + * screen item on the screen in script coordinates. + */ + Common::Rect getNowSeenRect(const Plane &plane) const; +}; + +#pragma mark - +#pragma mark ScreenItemList + +typedef StablePointerArray<ScreenItem, 250> ScreenItemListBase; +class ScreenItemList : public ScreenItemListBase { + static bool inline sortHelper(const ScreenItem *a, const ScreenItem *b) { + return *a < *b; + } +public: + ScreenItem *_unsorted[250]; + + ScreenItem *findByObject(const reg_t object) const; + void sort(); + void unsort(); +}; +} // End of namespace Sci + +#endif diff --git a/engines/sci/graphics/text32.cpp b/engines/sci/graphics/text32.cpp index 56ce73e8fa..99ffc6e328 100644 --- a/engines/sci/graphics/text32.cpp +++ b/engines/sci/graphics/text32.cpp @@ -29,363 +29,629 @@ #include "sci/engine/selector.h" #include "sci/engine/state.h" #include "sci/graphics/cache.h" +#include "sci/graphics/celobj32.h" #include "sci/graphics/compare.h" #include "sci/graphics/font.h" +#include "sci/graphics/frameout.h" #include "sci/graphics/screen.h" #include "sci/graphics/text32.h" namespace Sci { -#define BITMAP_HEADER_SIZE 46 +int16 GfxText32::_defaultFontId = 0; + +GfxText32::GfxText32(SegManager *segMan, GfxCache *fonts) : + _segMan(segMan), + _cache(fonts), + _scaledWidth(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth), + _scaledHeight(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight), + // Not a typo, the original engine did not initialise height, only width + _width(0), + _text(""), + _field_20(0), + _field_2C(2), + _field_30(0), + _field_34(0), + _field_38(0), + _field_3C(0), + _bitmap(NULL_REG) { + _fontId = _defaultFontId; + _font = _cache->getFont(_defaultFontId); + } -#define SCI_TEXT32_ALIGNMENT_RIGHT -1 -#define SCI_TEXT32_ALIGNMENT_CENTER 1 -#define SCI_TEXT32_ALIGNMENT_LEFT 0 +reg_t GfxText32::createFontBitmap(int16 width, int16 height, const Common::Rect &rect, const Common::String &text, const uint8 foreColor, const uint8 backColor, const uint8 skipColor, const GuiResourceId fontId, const TextAlign alignment, const int16 borderColor, const bool dimmed, const bool doScaling) { -GfxText32::GfxText32(SegManager *segMan, GfxCache *fonts, GfxScreen *screen) - : _segMan(segMan), _cache(fonts), _screen(screen) { -} + _field_22 = 0; + _borderColor = borderColor; + _text = text; + _textRect = rect; + _width = width; + _height = height; + _foreColor = foreColor; + _backColor = backColor; + _skipColor = skipColor; + _alignment = alignment; + _dimmed = dimmed; -GfxText32::~GfxText32() { -} + setFont(fontId); -reg_t GfxText32::createScrollTextBitmap(Common::String text, reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t prevHunk) { - return createTextBitmapInternal(text, textObject, maxWidth, maxHeight, prevHunk); + if (doScaling) { + int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; -} -reg_t GfxText32::createTextBitmap(reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t prevHunk) { - reg_t stringObject = readSelector(_segMan, textObject, SELECTOR(text)); - // The object in the text selector of the item can be either a raw string - // or a Str object. In the latter case, we need to access the object's data - // selector to get the raw string. - if (_segMan->isHeapObject(stringObject)) - stringObject = readSelector(_segMan, stringObject, SELECTOR(data)); + Ratio scaleX(_scaledWidth, scriptWidth); + Ratio scaleY(_scaledHeight, scriptHeight); - Common::String text = _segMan->getString(stringObject); + _width = (_width * scaleX).toInt(); + _height = (_height * scaleY).toInt(); + mulinc(_textRect, scaleX, scaleY); + } - return createTextBitmapInternal(text, textObject, maxWidth, maxHeight, prevHunk); -} + // _textRect represents where text is drawn inside the + // bitmap; clipRect is the entire bitmap + Common::Rect bitmapRect(_width, _height); -reg_t GfxText32::createTextBitmapInternal(Common::String &text, reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t prevHunk) { - // HACK: The character offsets of the up and down arrow buttons are off by one - // in GK1, for some unknown reason. Fix them here. - if (text.size() == 1 && (text[0] == 29 || text[0] == 30)) { - text.setChar(text[0] + 1, 0); - } - GuiResourceId fontId = readSelectorValue(_segMan, textObject, SELECTOR(font)); - GfxFont *font = _cache->getFont(fontId); - bool dimmed = readSelectorValue(_segMan, textObject, SELECTOR(dimmed)); - int16 alignment = readSelectorValue(_segMan, textObject, SELECTOR(mode)); - uint16 foreColor = readSelectorValue(_segMan, textObject, SELECTOR(fore)); - uint16 backColor = readSelectorValue(_segMan, textObject, SELECTOR(back)); - - Common::Rect nsRect = g_sci->_gfxCompare->getNSRect(textObject); - uint16 width = nsRect.width() + 1; - uint16 height = nsRect.height() + 1; - - // Limit rectangle dimensions, if requested - if (maxWidth > 0) - width = maxWidth; - if (maxHeight > 0) - height = maxHeight; - - // Upscale the coordinates/width if the fonts are already upscaled - if (_screen->fontIsUpscaled()) { - width = width * _screen->getDisplayWidth() / _screen->getWidth(); - height = height * _screen->getDisplayHeight() / _screen->getHeight(); + if (_textRect.intersects(bitmapRect)) { + _textRect.clip(bitmapRect); + } else { + _textRect = Common::Rect(); } - int entrySize = width * height + BITMAP_HEADER_SIZE; - reg_t memoryId = NULL_REG; - if (prevHunk.isNull()) { - memoryId = _segMan->allocateHunkEntry("TextBitmap()", entrySize); + BitmapResource bitmap(_segMan, _width, _height, _skipColor, 0, 0, _scaledWidth, _scaledHeight, 0, false); + _bitmap = bitmap.getObject(); - // Scroll text objects have no bitmap selector! - ObjVarRef varp; - if (lookupSelector(_segMan, textObject, SELECTOR(bitmap), &varp, NULL) == kSelectorVariable) - writeSelector(_segMan, textObject, SELECTOR(bitmap), memoryId); - } else { - memoryId = prevHunk; + erase(bitmapRect, false); + + if (_borderColor > -1) { + drawFrame(bitmapRect, 1, _borderColor, false); } - byte *memoryPtr = _segMan->getHunkPointer(memoryId); - if (prevHunk.isNull()) - memset(memoryPtr, 0, BITMAP_HEADER_SIZE); + drawTextBox(); + return _bitmap; +} - byte *bitmap = memoryPtr + BITMAP_HEADER_SIZE; - memset(bitmap, backColor, width * height); +reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect &rect, const Common::String &text, const int16 foreColor, const int16 backColor, const GuiResourceId fontId, const int16 skipColor, const int16 borderColor, const bool dimmed) { + _field_22 = 0; + _borderColor = borderColor; + _text = text; + _textRect = rect; + _foreColor = foreColor; + _dimmed = dimmed; - // Save totalWidth, totalHeight - WRITE_LE_UINT16(memoryPtr, width); - WRITE_LE_UINT16(memoryPtr + 2, height); + setFont(fontId); - int16 charCount = 0; - uint16 curX = 0, curY = 0; - const char *txt = text.c_str(); - int16 textWidth, textHeight, totalHeight = 0, offsetX = 0, offsetY = 0; - uint16 start = 0; + int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; - // Calculate total text height - while (*txt) { - charCount = GetLongest(txt, width, font); - if (charCount == 0) - break; + int borderSize = 1; + mulinc(_textRect, Ratio(_scaledWidth, scriptWidth), Ratio(_scaledHeight, scriptHeight)); - Width(txt, 0, (int16)strlen(txt), fontId, textWidth, textHeight, true); + CelObjView view(celInfo.resourceId, celInfo.loopNo, celInfo.celNo); + _skipColor = view._transparentColor; + _width = view._width * _scaledWidth / view._scaledWidth; + _height = view._height * _scaledHeight / view._scaledHeight; - totalHeight += textHeight; - txt += charCount; - while (*txt == ' ') - txt++; // skip over breaking spaces + Common::Rect bitmapRect(_width, _height); + if (_textRect.intersects(bitmapRect)) { + _textRect.clip(bitmapRect); + } else { + _textRect = Common::Rect(); } - txt = text.c_str(); - - // Draw text in buffer - while (*txt) { - charCount = GetLongest(txt, width, font); - if (charCount == 0) - break; - Width(txt, start, charCount, fontId, textWidth, textHeight, true); - - switch (alignment) { - case SCI_TEXT32_ALIGNMENT_RIGHT: - offsetX = width - textWidth; - break; - case SCI_TEXT32_ALIGNMENT_CENTER: - // Center text both horizontally and vertically - offsetX = (width - textWidth) / 2; - offsetY = (height - totalHeight) / 2; - break; - case SCI_TEXT32_ALIGNMENT_LEFT: - offsetX = 0; - break; - - default: - warning("Invalid alignment %d used in TextBox()", alignment); - } + BitmapResource bitmap(_segMan, _width, _height, _skipColor, 0, 0, _scaledWidth, _scaledHeight, 0, false); + _bitmap = bitmap.getObject(); + Buffer buffer(_width, _height, bitmap.getPixels()); + + // NOTE: The engine filled the bitmap pixels with 11 here, which is silly + // because then it just erased the bitmap using the skip color. So we don't + // fill the bitmap redundantly here. - byte curChar; - - for (int i = 0; i < charCount; i++) { - curChar = txt[i]; - - switch (curChar) { - case 0x0A: - case 0x0D: - case 0: - break; - case 0x7C: - warning("Code processing isn't implemented in SCI32"); - break; - default: - font->drawToBuffer(curChar, curY + offsetY, curX + offsetX, foreColor, dimmed, bitmap, width, height); - curX += font->getCharWidth(curChar); - break; + _backColor = _skipColor; + erase(bitmapRect, false); + _backColor = backColor; + + view.draw(buffer, bitmapRect, Common::Point(0, 0), false, Ratio(_scaledWidth, view._scaledWidth), Ratio(_scaledHeight, view._scaledHeight)); + + if (_backColor != skipColor && _foreColor != skipColor) { + erase(_textRect, false); + } + + if (text.size() > 0) { + if (_foreColor == skipColor) { + error("TODO: Implement transparent text"); + } else { + if (borderColor != -1) { + drawFrame(bitmapRect, borderSize, _borderColor, false); } - } - curX = 0; - curY += font->getHeight(); - txt += charCount; - while (*txt == ' ') - txt++; // skip over breaking spaces + drawTextBox(); + } } - return memoryId; + return _bitmap; } -void GfxText32::disposeTextBitmap(reg_t hunkId) { - _segMan->freeHunkEntry(hunkId); +reg_t GfxText32::createTitledBitmap(const int16 width, const int16 height, const Common::Rect &textRect, const Common::String &text, const int16 foreColor, const int16 backColor, const int16 skipColor, const GuiResourceId fontId, const TextAlign alignment, const int16 borderColor, Common::String &title, const int16 titleForeColor, const int16 titleBackColor, const GuiResourceId titleFontId, const bool doScaling) { + warning("TODO: createTitledBitmap incomplete !"); + return createFontBitmap(width, height, textRect, text, foreColor, backColor, skipColor, fontId, alignment, borderColor, false, doScaling); } -void GfxText32::drawTextBitmap(int16 x, int16 y, Common::Rect planeRect, reg_t textObject) { - reg_t hunkId = readSelector(_segMan, textObject, SELECTOR(bitmap)); - drawTextBitmapInternal(x, y, planeRect, textObject, hunkId); +void GfxText32::setFont(const GuiResourceId fontId) { + // NOTE: In SCI engine this calls FontMgr::BuildFontTable and then a font + // table is built on the FontMgr directly; instead, because we already have + // font resources, this code just grabs a font out of GfxCache. + if (fontId != _fontId) { + _fontId = fontId == -1 ? _defaultFontId : fontId; + _font = _cache->getFont(_fontId); + } } -void GfxText32::drawScrollTextBitmap(reg_t textObject, reg_t hunkId, uint16 x, uint16 y) { - /*reg_t plane = readSelector(_segMan, textObject, SELECTOR(plane)); - Common::Rect planeRect; - planeRect.top = readSelectorValue(_segMan, plane, SELECTOR(top)); - planeRect.left = readSelectorValue(_segMan, plane, SELECTOR(left)); - planeRect.bottom = readSelectorValue(_segMan, plane, SELECTOR(bottom)); - planeRect.right = readSelectorValue(_segMan, plane, SELECTOR(right)); +void GfxText32::drawFrame(const Common::Rect &rect, const int16 size, const uint8 color, const bool doScaling) { + Common::Rect targetRect = doScaling ? scaleRect(rect) : rect; - drawTextBitmapInternal(x, y, planeRect, textObject, hunkId);*/ + byte *bitmap = _segMan->getHunkPointer(_bitmap); + byte *pixels = bitmap + READ_SCI11ENDIAN_UINT32(bitmap + 28) + rect.top * _width + rect.left; - // HACK: we pretty much ignore the plane rect and x, y... - drawTextBitmapInternal(0, 0, Common::Rect(20, 390, 600, 460), textObject, hunkId); + // NOTE: Not fully disassembled, but this should be right + int16 rectWidth = targetRect.width(); + int16 sidesHeight = targetRect.height() - size * 2; + int16 centerWidth = rectWidth - size * 2; + int16 stride = _width - rectWidth; + + for (int16 y = 0; y < size; ++y) { + memset(pixels, color, rectWidth); + pixels += _width; + } + for (int16 y = 0; y < sidesHeight; ++y) { + for (int16 x = 0; x < size; ++x) { + *pixels++ = color; + } + pixels += centerWidth; + for (int16 x = 0; x < size; ++x) { + *pixels++ = color; + } + pixels += stride; + } + for (int16 y = 0; y < size; ++y) { + memset(pixels, color, rectWidth); + pixels += _width; + } } -void GfxText32::drawTextBitmapInternal(int16 x, int16 y, Common::Rect planeRect, reg_t textObject, reg_t hunkId) { - int16 backColor = (int16)readSelectorValue(_segMan, textObject, SELECTOR(back)); - // Sanity check: Check if the hunk is set. If not, either the game scripts - // didn't set it, or an old saved game has been loaded, where it wasn't set. - if (hunkId.isNull()) - return; +void GfxText32::drawChar(const char charIndex) { + byte *bitmap = _segMan->getHunkPointer(_bitmap); + byte *pixels = bitmap + READ_SCI11ENDIAN_UINT32(bitmap + 28); - // Negative coordinates indicate that text shouldn't be displayed - if (x < 0 || y < 0) - return; + _font->drawToBuffer(charIndex, _drawPosition.y, _drawPosition.x, _foreColor, _dimmed, pixels, _width, _height); + _drawPosition.x += _font->getCharWidth(charIndex); +} - byte *memoryPtr = _segMan->getHunkPointer(hunkId); +uint16 GfxText32::getCharWidth(const char charIndex, const bool doScaling) const { + uint16 width = _font->getCharWidth(charIndex); + if (doScaling) { + width = scaleUpWidth(width); + } + return width; +} - if (!memoryPtr) { - // Happens when restoring in some SCI32 games (e.g. SQ6). - // Commented out to reduce console spam - //warning("Attempt to draw an invalid text bitmap"); +void GfxText32::drawTextBox() { + if (_text.size() == 0) { return; } - byte *surface = memoryPtr + BITMAP_HEADER_SIZE; + const char *text = _text.c_str(); + const char *sourceText = text; + int16 textRectWidth = _textRect.width(); + _drawPosition.y = _textRect.top; + uint charIndex = 0; + if (getLongest(&charIndex, textRectWidth) == 0) { + error("DrawTextBox GetLongest=0"); + } + + charIndex = 0; + uint nextCharIndex = 0; + while (*text != '\0') { + _drawPosition.x = _textRect.left; + + uint length = getLongest(&nextCharIndex, textRectWidth); + int16 textWidth = getTextWidth(charIndex, length); + + if (_alignment == kTextAlignCenter) { + _drawPosition.x += (textRectWidth - textWidth) / 2; + } else if (_alignment == kTextAlignRight) { + _drawPosition.x += textRectWidth - textWidth; + } + + drawText(charIndex, length); + charIndex = nextCharIndex; + text = sourceText + charIndex; + _drawPosition.y += _font->getHeight(); + } +} + +void GfxText32::drawTextBox(const Common::String &text) { + _text = text; + drawTextBox(); +} + +void GfxText32::drawText(const uint index, uint length) { + assert(index + length <= _text.size()); + + // NOTE: This draw loop implementation is somewhat different than the + // implementation in the actual engine, but should be accurate. Primarily + // the changes revolve around eliminating some extra temporaries and + // fixing the logic to match. + const char *text = _text.c_str() + index; + while (length-- > 0) { + char currentChar = *text++; + + if (currentChar == '|') { + const char controlChar = *text++; + --length; + + if (length == 0) { + return; + } + + if (controlChar == 'a' || controlChar == 'c' || controlChar == 'f') { + uint16 value = 0; + + while (length > 0) { + const char valueChar = *text; + if (valueChar < '0' || valueChar > '9') { + break; + } + + ++text; + --length; + value = 10 * value + (valueChar - '0'); + } + + if (length == 0) { + return; + } + + if (controlChar == 'a') { + _alignment = (TextAlign)value; + } else if (controlChar == 'c') { + _foreColor = value; + } else if (controlChar == 'f') { + setFont(value); + } + } + + while (length > 0 && *text != '|') { + ++text; + --length; + } + } else { + drawChar(currentChar); + } + } +} + +void GfxText32::invertRect(const reg_t bitmap, int16 bitmapStride, const Common::Rect &rect, const uint8 foreColor, const uint8 backColor, const bool doScaling) { + Common::Rect targetRect = rect; + if (doScaling) { + bitmapStride = bitmapStride * _scaledWidth / g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + targetRect = scaleRect(rect); + } + + byte *bitmapData = _segMan->getHunkPointer(bitmap); - int curByte = 0; - int16 skipColor = (int16)readSelectorValue(_segMan, textObject, SELECTOR(skip)); - uint16 textX = planeRect.left + x; - uint16 textY = planeRect.top + y; - // Get totalWidth, totalHeight - uint16 width = READ_LE_UINT16(memoryPtr); - uint16 height = READ_LE_UINT16(memoryPtr + 2); + // NOTE: SCI code is super weird here; it seems to be trying to look at the + // entire size of the bitmap including the header, instead of just the pixel + // data size. We just look at the pixel size. This function generally is an + // odd duck since the stride dimension for a bitmap is built in to the bitmap + // header, so perhaps it was once an unheadered bitmap format and this + // function was never updated to match? Or maybe they exploit the + // configurable stride length somewhere else to do stair stepping inverts... + uint32 invertSize = targetRect.height() * bitmapStride + targetRect.width(); + uint32 bitmapSize = READ_SCI11ENDIAN_UINT32(bitmapData + 12); - // Upscale the coordinates/width if the fonts are already upscaled - if (_screen->fontIsUpscaled()) { - textX = textX * _screen->getDisplayWidth() / _screen->getWidth(); - textY = textY * _screen->getDisplayHeight() / _screen->getHeight(); + if (invertSize >= bitmapSize) { + error("InvertRect too big: %u >= %u", invertSize, bitmapSize); } - bool translucent = (skipColor == -1 && backColor == -1); + // NOTE: Actual engine just added the bitmap header size hardcoded here + byte *pixel = bitmapData + READ_SCI11ENDIAN_UINT32(bitmapData + 28) + bitmapStride * targetRect.top + targetRect.left; - for (int curY = 0; curY < height; curY++) { - for (int curX = 0; curX < width; curX++) { - byte pixel = surface[curByte++]; - if ((!translucent && pixel != skipColor && pixel != backColor) || - (translucent && pixel != 0xFF)) - _screen->putFontPixel(textY, curX + textX, curY, pixel); + int16 stride = bitmapStride - targetRect.width(); + int16 targetHeight = targetRect.height(); + int16 targetWidth = targetRect.width(); + + for (int16 y = 0; y < targetHeight; ++y) { + for (int16 x = 0; x < targetWidth; ++x) { + if (*pixel == foreColor) { + *pixel = backColor; + } else if (*pixel == backColor) { + *pixel = foreColor; + } + + ++pixel; } + + pixel += stride; } } -int16 GfxText32::GetLongest(const char *text, int16 maxWidth, GfxFont *font) { - uint16 curChar = 0; - int16 maxChars = 0, curCharCount = 0; - uint16 width = 0; - - while (width <= maxWidth) { - curChar = (*(const byte *)text++); - - switch (curChar) { - // We need to add 0xD, 0xA and 0xD 0xA to curCharCount and then exit - // which means, we split text like - // 'Mature, experienced software analyst available.' 0xD 0xA - // 'Bug installation a proven speciality. "No version too clean."' (normal game text, this is from lsl2) - // and 0xA '-------' 0xA (which is the official sierra subtitle separator) - // Sierra did it the same way. - case 0xD: - // Check, if 0xA is following, if so include it as well - if ((*(const unsigned char *)text) == 0xA) - curCharCount++; - // it's meant to pass through here - case 0xA: - curCharCount++; - // and it's also meant to pass through here - case 0: - return curCharCount; - case ' ': - maxChars = curCharCount; // return count up to (but not including) breaking space - break; +uint GfxText32::getLongest(uint *charIndex, const int16 width) { + assert(width > 0); + + uint testLength = 0; + uint length = 0; + + const uint initialCharIndex = *charIndex; + + // The index of the next word after the last word break + uint lastWordBreakIndex = *charIndex; + + const char *text = _text.c_str() + *charIndex; + + char currentChar; + while ((currentChar = *text++) != '\0') { + // NOTE: In the original engine, the font, color, and alignment were + // reset here to their initial values + + // The text to render contains a line break; stop at the line break + if (currentChar == '\r' || currentChar == '\n') { + // Skip the rest of the line break if it is a Windows-style + // \r\n or non-standard \n\r + // NOTE: In the original engine, the `text` pointer had not been + // advanced yet so the indexes used to access characters were + // one higher + if ( + (currentChar == '\r' && text[0] == '\n') || + (currentChar == '\n' && text[0] == '\r' && text[1] != '\n') + ) { + ++*charIndex; + } + + // We are at the end of a line but the last word in the line made + // it too wide to fit in the text area; return up to the previous + // word + if (length && getTextWidth(initialCharIndex, testLength) > width) { + *charIndex = lastWordBreakIndex; + return length; + } + + // Skip the line break and return all text seen up to now + // NOTE: In original engine, the font, color, and alignment were + // reset, then getTextWidth was called to use its side-effects to + // set font, color, and alignment according to the text from + // `initialCharIndex` to `testLength` + ++*charIndex; + return testLength; + } else if (currentChar == ' ') { + // The last word in the line made it too wide to fit in the text area; + // return up to the previous word, then collapse the whitespace + // between that word and its next sibling word into the line break + if (getTextWidth(initialCharIndex, testLength) > width) { + *charIndex = lastWordBreakIndex; + const char *nextChar = _text.c_str() + lastWordBreakIndex; + while (*nextChar++ == ' ') { + ++*charIndex; + } + + // NOTE: In original engine, the font, color, and alignment were + // set here to the values that were seen at the last space character + return length; + } + + // NOTE: In the original engine, the values of _fontId, _foreColor, + // and _alignment were stored for use in the return path mentioned + // just above here + + // We found a word break that was within the text area, memorise it + // and continue processing. +1 on the character index because it has + // not been incremented yet so currently points to the word break + // and not the word after the break + length = testLength; + lastWordBreakIndex = *charIndex + 1; + } + + // In the middle of a line, keep processing + ++*charIndex; + ++testLength; + + // NOTE: In the original engine, the font, color, and alignment were + // reset here to their initial values + + // The text to render contained no word breaks yet but is already too + // wide for the text area; just split the word in half at the point + // where it overflows + if (length == 0 && getTextWidth(initialCharIndex, testLength) > width) { + *charIndex = --testLength + lastWordBreakIndex; + return testLength; } - if (width + font->getCharWidth(curChar) > maxWidth) - break; - width += font->getCharWidth(curChar); - curCharCount++; } - return maxChars; + // The complete text to render was a single word, or was narrower than + // the text area, so return the entire line + if (length == 0 || getTextWidth(initialCharIndex, testLength) <= width) { + // NOTE: In original engine, the font, color, and alignment were + // reset, then getTextWidth was called to use its side-effects to + // set font, color, and alignment according to the text from + // `initialCharIndex` to `testLength` + return testLength; + } + + // The last word in the line made it wider than the text area, so return + // up to the penultimate word + *charIndex = lastWordBreakIndex; + return length; } -void GfxText32::kernelTextSize(const char *text, int16 font, int16 maxWidth, int16 *textWidth, int16 *textHeight) { - Common::Rect rect(0, 0, 0, 0); - Size(rect, text, font, maxWidth); - *textWidth = rect.width(); - *textHeight = rect.height(); +int16 GfxText32::getTextWidth(const uint index, uint length) const { + int16 width = 0; + + const char *text = _text.c_str() + index; + + GfxFont *font = _font; + + char currentChar = *text++; + while (length > 0 && currentChar != '\0') { + // Control codes are in the format `|<code><value>|` + if (currentChar == '|') { + // NOTE: Original engine code changed the global state of the + // FontMgr here upon encountering any color, alignment, or + // font control code. + // To avoid requiring all callers to manually restore these + // values on every call, we ignore control codes other than + // font change (since alignment and color do not change the + // width of characters), and simply update the font pointer + // on stack instead of the member property font. + currentChar = *text++; + --length; + + if (length > 0 && currentChar == 'f') { + GuiResourceId fontId = 0; + do { + currentChar = *text++; + --length; + + fontId = fontId * 10 + currentChar - '0'; + } while (length > 0 && currentChar >= '0' && currentChar <= '9'); + + if (length > 0) { + font = _cache->getFont(fontId); + } + } + + // Forward through any more unknown control character data + while (length > 0 && currentChar != '|') { + ++text; + --length; + } + } else { + width += font->getCharWidth(currentChar); + } + + currentChar = *text++; + --length; + } + + return width; } -void GfxText32::StringWidth(const char *str, GuiResourceId fontId, int16 &textWidth, int16 &textHeight) { - Width(str, 0, (int16)strlen(str), fontId, textWidth, textHeight, true); +int16 GfxText32::getTextWidth(const Common::String &text, const uint index, const uint length) { + _text = text; + return scaleUpWidth(getTextWidth(index, length)); } -void GfxText32::Width(const char *text, int16 from, int16 len, GuiResourceId fontId, int16 &textWidth, int16 &textHeight, bool restoreFont) { - byte curChar; - textWidth = 0; textHeight = 0; - - GfxFont *font = _cache->getFont(fontId); - - if (font) { - text += from; - while (len--) { - curChar = (*(const byte *)text++); - switch (curChar) { - case 0x0A: - case 0x0D: - textHeight = MAX<int16> (textHeight, font->getHeight()); - break; - case 0x7C: - warning("Code processing isn't implemented in SCI32"); - break; - default: - textHeight = MAX<int16> (textHeight, font->getHeight()); - textWidth += font->getCharWidth(curChar); - break; +Common::Rect GfxText32::getTextSize(const Common::String &text, int16 maxWidth, bool doScaling) { + // NOTE: Like most of the text rendering code, this function was pretty + // weird in the original engine. The initial result rectangle was actually + // a 1x1 rectangle (0, 0, 0, 0), which was then "fixed" after the main + // text size loop finished running by subtracting 1 from the right and + // bottom edges. Like other functions in SCI32, this has been converted + // to use exclusive rects with inclusive rounding. + + Common::Rect result; + + int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + + maxWidth = maxWidth * _scaledWidth / scriptWidth; + + _text = text; + + if (maxWidth >= 0) { + if (maxWidth == 0) { + // TODO: This was hardcoded to 192, but guessing + // that it was originally 60% of the scriptWidth + // before the compiler took over. + // Verify this by looking at a game that uses a + // scriptWidth other than 320, like LSL7 + maxWidth = _scaledWidth * (scriptWidth * 0.6) / scriptWidth; + } + + result.right = maxWidth; + + int16 textWidth = 0; + if (_text.size() > 0) { + const char *rawText = _text.c_str(); + const char *sourceText = rawText; + uint charIndex = 0; + uint nextCharIndex = 0; + while (*rawText != '\0') { + uint length = getLongest(&nextCharIndex, result.width()); + textWidth = MAX(textWidth, getTextWidth(charIndex, length)); + charIndex = nextCharIndex; + rawText = sourceText + charIndex; + // TODO: Due to getLongest and getTextWidth not having side + // effects, it is possible that the currently loaded font's + // height is wrong for this line if it was changed inline + result.bottom += _font->getHeight(); } } + + if (textWidth < maxWidth) { + result.right = textWidth; + } + } else { + result.right = getTextWidth(0, 10000); + // NOTE: In the original engine code, the bottom was not decremented + // by 1, which means that the rect was actually a pixel taller than + // the height of the font. This was not the case in the other branch, + // which decremented the bottom by 1 at the end of the loop. + result.bottom = _font->getHeight() + 1; } + + if (doScaling) { + // NOTE: The original engine code also scaled top/left but these are + // always zero so there is no reason to do that. + result.right = ((result.right - 1) * scriptWidth + _scaledWidth - 1) / _scaledWidth + 1; + result.bottom = ((result.bottom - 1) * scriptHeight + _scaledHeight - 1) / _scaledHeight + 1; + } + + return result; } -int16 GfxText32::Size(Common::Rect &rect, const char *text, GuiResourceId fontId, int16 maxWidth) { - int16 charCount; - int16 maxTextWidth = 0, textWidth; - int16 totalHeight = 0, textHeight; +void GfxText32::erase(const Common::Rect &rect, const bool doScaling) { + Common::Rect targetRect = doScaling ? scaleRect(rect) : rect; - // Adjust maxWidth if we're using an upscaled font - if (_screen->fontIsUpscaled()) - maxWidth = maxWidth * _screen->getDisplayWidth() / _screen->getWidth(); + byte *bitmap = _segMan->getHunkPointer(_bitmap); + byte *pixels = bitmap + READ_SCI11ENDIAN_UINT32(bitmap + 28); - rect.top = rect.left = 0; - GfxFont *font = _cache->getFont(fontId); + // NOTE: There is an extra optimisation within the SCI code to + // do a single memset if the scaledRect is the same size as + // the bitmap, not implemented here. + Buffer buffer(_width, _height, pixels); + buffer.fillRect(targetRect, _backColor); +} - if (maxWidth < 0) { // force output as single line - StringWidth(text, fontId, textWidth, textHeight); - rect.bottom = textHeight; - rect.right = textWidth; - } else { - // rect.right=found widest line with RTextWidth and GetLongest - // rect.bottom=num. lines * GetPointSize - rect.right = (maxWidth ? maxWidth : 192); - const char *curPos = text; - while (*curPos) { - charCount = GetLongest(curPos, rect.right, font); - if (charCount == 0) - break; - Width(curPos, 0, charCount, fontId, textWidth, textHeight, false); - maxTextWidth = MAX(textWidth, maxTextWidth); - totalHeight += textHeight; - curPos += charCount; - while (*curPos == ' ') - curPos++; // skip over breaking spaces - } - rect.bottom = totalHeight; - rect.right = maxWidth ? maxWidth : MIN(rect.right, maxTextWidth); +int16 GfxText32::getStringWidth(const Common::String &text) { + return getTextWidth(text, 0, 10000); +} + +int16 GfxText32::getTextCount(const Common::String &text, const uint index, const Common::Rect &textRect, const bool doScaling) { + const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + + Common::Rect scaledRect(textRect); + if (doScaling) { + mulinc(scaledRect, Ratio(_scaledWidth, scriptWidth), Ratio(_scaledHeight, scriptHeight)); } - // Adjust the width/height if we're using an upscaled font - // for the scripts - if (_screen->fontIsUpscaled()) { - rect.right = rect.right * _screen->getWidth() / _screen->getDisplayWidth(); - rect.bottom = rect.bottom * _screen->getHeight() / _screen->getDisplayHeight(); + Common::String oldText = _text; + _text = text; + + uint charIndex = index; + int16 maxWidth = scaledRect.width(); + int16 lineCount = (scaledRect.height() - 2) / _font->getHeight(); + while (lineCount--) { + getLongest(&charIndex, maxWidth); } - return rect.right; + _text = oldText; + return charIndex - index; } +int16 GfxText32::getTextCount(const Common::String &text, const uint index, const GuiResourceId fontId, const Common::Rect &textRect, const bool doScaling) { + setFont(fontId); + return getTextCount(text, index, textRect, doScaling); +} + + } // End of namespace Sci diff --git a/engines/sci/graphics/text32.h b/engines/sci/graphics/text32.h index 7ba7df50e4..5768ea0c59 100644 --- a/engines/sci/graphics/text32.h +++ b/engines/sci/graphics/text32.h @@ -23,34 +23,455 @@ #ifndef SCI_GRAPHICS_TEXT32_H #define SCI_GRAPHICS_TEXT32_H +#include "sci/graphics/celobj32.h" +#include "sci/graphics/frameout.h" + namespace Sci { +enum TextAlign { + kTextAlignLeft = 0, + kTextAlignCenter = 1, + kTextAlignRight = 2 +}; + +enum BitmapFlags { + kBitmapRemap = 2 +}; + +#define BITMAP_PROPERTY(size, property, offset)\ +inline uint##size get##property() const {\ + return READ_SCI11ENDIAN_UINT##size(_bitmap + (offset));\ +}\ +inline void set##property(uint##size value) {\ + WRITE_SCI11ENDIAN_UINT##size(_bitmap + (offset), (value));\ +} + /** - * Text32 class, handles text calculation and displaying of text for SCI2, SCI21 and SCI3 games + * A convenience class for creating and modifying in-memory + * bitmaps. */ -class GfxText32 { +class BitmapResource { + byte *_bitmap; + reg_t _object; + + /** + * Gets the size of the bitmap header for the current + * engine version. + */ + static inline uint16 getBitmapHeaderSize() { + // TODO: These values are accurate for each engine, but there may be no reason + // to not simply just always use size 40, since SCI2.1mid does not seem to + // actually store any data above byte 40, and SCI2 did not allow bitmaps with + // scaling resolutions other than the default (320x200). Perhaps SCI3 used + // the extra bytes, or there is some reason why they tried to align the header + // size with other headers like pic headers? +// uint32 bitmapHeaderSize; +// if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) { +// bitmapHeaderSize = 46; +// } else if (getSciVersion() == SCI_VERSION_2_1_EARLY) { +// bitmapHeaderSize = 40; +// } else { +// bitmapHeaderSize = 36; +// } +// return bitmapHeaderSize; + return 46; + } + + /** + * Gets the byte size of a bitmap with the given width + * and height. + */ + static inline uint32 getBitmapSize(const uint16 width, const uint16 height) { + return width * height + getBitmapHeaderSize(); + } + public: - GfxText32(SegManager *segMan, GfxCache *fonts, GfxScreen *screen); - ~GfxText32(); - reg_t createTextBitmap(reg_t textObject, uint16 maxWidth = 0, uint16 maxHeight = 0, reg_t prevHunk = NULL_REG); - reg_t createScrollTextBitmap(Common::String text, reg_t textObject, uint16 maxWidth = 0, uint16 maxHeight = 0, reg_t prevHunk = NULL_REG); - void drawTextBitmap(int16 x, int16 y, Common::Rect planeRect, reg_t textObject); - void drawScrollTextBitmap(reg_t textObject, reg_t hunkId, uint16 x, uint16 y); - void disposeTextBitmap(reg_t hunkId); - int16 GetLongest(const char *text, int16 maxWidth, GfxFont *font); + /** + * Create a bitmap resource for an existing bitmap. + * Ownership of the bitmap is retained by the caller. + */ + inline BitmapResource(reg_t bitmap) : + _bitmap(g_sci->getEngineState()->_segMan->getHunkPointer(bitmap)), + _object(bitmap) { + if (_bitmap == nullptr || getUncompressedDataOffset() != getBitmapHeaderSize()) { + error("Invalid Text bitmap %04x:%04x", PRINT_REG(bitmap)); + } + } - void kernelTextSize(const char *text, int16 font, int16 maxWidth, int16 *textWidth, int16 *textHeight); + /** + * Allocates and initialises a new bitmap in the given + * segment manager. + */ + inline BitmapResource(SegManager *segMan, const int16 width, const int16 height, const uint8 skipColor, const int16 displaceX, const int16 displaceY, const int16 scaledWidth, const int16 scaledHeight, const uint32 hunkPaletteOffset, const bool remap) { -private: - reg_t createTextBitmapInternal(Common::String &text, reg_t textObject, uint16 maxWidth, uint16 maxHeight, reg_t hunkId); - void drawTextBitmapInternal(int16 x, int16 y, Common::Rect planeRect, reg_t textObject, reg_t hunkId); - int16 Size(Common::Rect &rect, const char *text, GuiResourceId fontId, int16 maxWidth); - void Width(const char *text, int16 from, int16 len, GuiResourceId orgFontId, int16 &textWidth, int16 &textHeight, bool restoreFont); - void StringWidth(const char *str, GuiResourceId orgFontId, int16 &textWidth, int16 &textHeight); + _object = segMan->allocateHunkEntry("Bitmap()", getBitmapSize(width, height)); + _bitmap = segMan->getHunkPointer(_object); + + const uint16 bitmapHeaderSize = getBitmapHeaderSize(); + + setWidth(width); + setHeight(height); + setDisplace(Common::Point(displaceX, displaceY)); + setSkipColor(skipColor); + _bitmap[9] = 0; + WRITE_SCI11ENDIAN_UINT16(_bitmap + 10, 0); + setRemap(remap); + setDataSize(width * height); + WRITE_SCI11ENDIAN_UINT32(_bitmap + 16, 0); + setHunkPaletteOffset(hunkPaletteOffset); + setDataOffset(bitmapHeaderSize); + setUncompressedDataOffset(bitmapHeaderSize); + setControlOffset(0); + setScaledWidth(scaledWidth); + setScaledHeight(scaledHeight); + } + + reg_t getObject() const { + return _object; + } + + BITMAP_PROPERTY(16, Width, 0); + BITMAP_PROPERTY(16, Height, 2); + + inline Common::Point getDisplace() const { + return Common::Point( + (int16)READ_SCI11ENDIAN_UINT16(_bitmap + 4), + (int16)READ_SCI11ENDIAN_UINT16(_bitmap + 6) + ); + } + + inline void setDisplace(const Common::Point &displace) { + WRITE_SCI11ENDIAN_UINT16(_bitmap + 4, (uint16)displace.x); + WRITE_SCI11ENDIAN_UINT16(_bitmap + 6, (uint16)displace.y); + } + + inline uint8 getSkipColor() const { + return _bitmap[8]; + } + + inline void setSkipColor(const uint8 skipColor) { + _bitmap[8] = skipColor; + } + + inline bool getRemap() const { + return READ_SCI11ENDIAN_UINT16(_bitmap + 10) & kBitmapRemap; + } + + inline void setRemap(const bool remap) { + uint16 flags = READ_SCI11ENDIAN_UINT16(_bitmap + 10); + if (remap) { + flags |= kBitmapRemap; + } else { + flags &= ~kBitmapRemap; + } + WRITE_SCI11ENDIAN_UINT16(_bitmap + 10, flags); + } + BITMAP_PROPERTY(32, DataSize, 12); + + inline uint32 getHunkPaletteOffset() const { + return READ_SCI11ENDIAN_UINT32(_bitmap + 20); + } + + void setHunkPaletteOffset(uint32 hunkPaletteOffset) { + if (hunkPaletteOffset) { + hunkPaletteOffset += getBitmapHeaderSize(); + } + + WRITE_SCI11ENDIAN_UINT32(_bitmap + 20, hunkPaletteOffset); + } + + BITMAP_PROPERTY(32, DataOffset, 24); + + // NOTE: This property is used as a "magic number" for + // validating that a block of memory is a valid bitmap, + // and so is always set to the size of the header. + BITMAP_PROPERTY(32, UncompressedDataOffset, 28); + + // NOTE: This property always seems to be zero + BITMAP_PROPERTY(32, ControlOffset, 32); + + inline uint16 getScaledWidth() const { + if (getDataOffset() >= 40) { + return READ_SCI11ENDIAN_UINT16(_bitmap + 36); + } + + // SCI2 bitmaps did not have scaling ability + return 320; + } + + inline void setScaledWidth(uint16 scaledWidth) { + if (getDataOffset() >= 40) { + WRITE_SCI11ENDIAN_UINT16(_bitmap + 36, scaledWidth); + } + } + + inline uint16 getScaledHeight() const { + if (getDataOffset() >= 40) { + return READ_SCI11ENDIAN_UINT16(_bitmap + 38); + } + + // SCI2 bitmaps did not have scaling ability + return 200; + } + + inline void setScaledHeight(uint16 scaledHeight) { + if (getDataOffset() >= 40) { + WRITE_SCI11ENDIAN_UINT16(_bitmap + 38, scaledHeight); + } + } + + inline byte *getPixels() { + return _bitmap + getUncompressedDataOffset(); + } +}; + +class GfxFont; + +/** + * This class handles text calculation and rendering for + * SCI32 games. The text calculation system in SCI32 is + * nearly the same as SCI16, which means this class behaves + * similarly. Notably, GfxText32 maintains drawing + * parameters across multiple calls. + */ +class GfxText32 { +private: SegManager *_segMan; GfxCache *_cache; - GfxScreen *_screen; + + /** + * The resource ID of the default font used by the game. + * + * @todo Check all SCI32 games to learn what their + * default font is. + */ + static int16 _defaultFontId; + + /** + * The width and height of the currently active text + * bitmap, in text-system coordinates. + * + * @note These are unsigned in the actual engine. + */ + int16 _width, _height; + + /** + * The color used to draw text. + */ + uint8 _foreColor; + + /** + * The background color of the text box. + */ + uint8 _backColor; + + /** + * The transparent color of the text box. Used when + * compositing the bitmap onto the screen. + */ + uint8 _skipColor; + + /** + * The rect where the text is drawn within the bitmap. + * This rect is clipped to the dimensions of the bitmap. + */ + Common::Rect _textRect; + + /** + * The text being drawn to the currently active text + * bitmap. + */ + Common::String _text; + + /** + * The font being used to draw the text. + */ + GuiResourceId _fontId; + + /** + * The color of the text box border. + */ + int16 _borderColor; + + /** + * TODO: Document + */ + bool _dimmed; + + /** + * The text alignment for the drawn text. + */ + TextAlign _alignment; + + int16 _field_20; + + /** + * TODO: Document + */ + int16 _field_22; + + int _field_2C, _field_30, _field_34, _field_38; + + int16 _field_3C; + + /** + * The position of the text draw cursor. + */ + Common::Point _drawPosition; + + void drawFrame(const Common::Rect &rect, const int16 size, const uint8 color, const bool doScaling); + + void drawChar(const char charIndex); + void drawText(const uint index, uint length); + + /** + * Gets the length of the longest run of text available + * within the currently loaded text, starting from the + * given `charIndex` and running for up to `maxWidth` + * pixels. Returns the number of characters that can be + * written, and mutates the value pointed to by + * `charIndex` to point to the index of the next + * character to render. + */ + uint getLongest(uint *charIndex, const int16 maxWidth); + + /** + * Gets the pixel width of a substring of the currently + * loaded text, without scaling. + */ + int16 getTextWidth(const uint index, uint length) const; + + inline Common::Rect scaleRect(const Common::Rect &rect) { + Common::Rect scaledRect(rect); + int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + Ratio scaleX(_scaledWidth, scriptWidth); + Ratio scaleY(_scaledHeight, scriptHeight); + mulinc(scaledRect, scaleX, scaleY); + return scaledRect; + } + +public: + GfxText32(SegManager *segMan, GfxCache *fonts); + + /** + * The memory handle of the currently active bitmap. + */ + reg_t _bitmap; + + /** + * The size of the x-dimension of the coordinate system + * used by the text renderer. + */ + int16 _scaledWidth; + + /** + * The size of the y-dimension of the coordinate system + * used by the text renderer. + */ + int16 _scaledHeight; + + /** + * The currently active font resource used to write text + * into the bitmap. + * + * @note SCI engine builds the font table directly + * inside of FontMgr; we use GfxFont instead. + */ + GfxFont *_font; + + /** + * Creates a plain font bitmap with a flat color + * background. + */ + reg_t createFontBitmap(int16 width, int16 height, const Common::Rect &rect, const Common::String &text, const uint8 foreColor, const uint8 backColor, const uint8 skipColor, const GuiResourceId fontId, TextAlign alignment, const int16 borderColor, bool dimmed, const bool doScaling); + + /** + * Creates a font bitmap with a view background. + */ + reg_t createFontBitmap(const CelInfo32 &celInfo, const Common::Rect &rect, const Common::String &text, const int16 foreColor, const int16 backColor, const GuiResourceId fontId, const int16 skipColor, const int16 borderColor, const bool dimmed); + + /** + * Creates a font bitmap with a title. + */ + reg_t createTitledBitmap(const int16 width, const int16 height, const Common::Rect &textRect, const Common::String &text, const int16 foreColor, const int16 backColor, const int16 skipColor, const GuiResourceId fontId, const TextAlign alignment, const int16 borderColor, Common::String &title, const int16 titleForeColor, const int16 titleBackColor, const GuiResourceId titleFontId, const bool doScaling); + + inline int scaleUpWidth(int value) const { + const int scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + return (value * scriptWidth + _scaledWidth - 1) / _scaledWidth; + } + + inline int scaleUpHeight(int value) const { + const int scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + return (value * scriptHeight + _scaledHeight - 1) / _scaledHeight; + } + + /** + * Draws the text to the bitmap. + */ + void drawTextBox(); + + /** + * Draws the given text to the bitmap. + * + * @note The original engine holds a reference to a + * shared string which lets the text be updated from + * outside of the font manager. Instead, we give this + * extra signature to send the text to draw. + * + * TODO: Use shared string instead? + */ + void drawTextBox(const Common::String &text); + + /** + * Erases the given rect by filling with the background + * color. + */ + void erase(const Common::Rect &rect, const bool doScaling); + + void invertRect(const reg_t bitmap, const int16 bitmapStride, const Common::Rect &rect, const uint8 foreColor, const uint8 backColor, const bool doScaling); + + /** + * Sets the font to be used for rendering and + * calculation of text dimensions. + */ + void setFont(const GuiResourceId fontId); + + /** + * Gets the width of a character. + */ + uint16 getCharWidth(const char charIndex, const bool doScaling) const; + + /** + * Retrieves the width and height of a block of text. + */ + Common::Rect getTextSize(const Common::String &text, const int16 maxWidth, bool doScaling); + + /** + * Gets the pixel width of a substring of the currently + * loaded text, with scaling. + */ + int16 getTextWidth(const Common::String &text, const uint index, const uint length); + + /** + * Retrieves the width of a line of text. + */ + int16 getStringWidth(const Common::String &text); + + /** + * Gets the number of characters of `text`, starting + * from `index`, that can be safely rendered into + * `textRect`. + */ + int16 getTextCount(const Common::String &text, const uint index, const Common::Rect &textRect, const bool doScaling); + + /** + * Gets the number of characters of `text`, starting + * from `index`, that can be safely rendered into + * `textRect` using the given font. + */ + int16 getTextCount(const Common::String &text, const uint index, const GuiResourceId fontId, const Common::Rect &textRect, const bool doScaling); }; } // End of namespace Sci diff --git a/engines/sci/graphics/view.cpp b/engines/sci/graphics/view.cpp index 2ee18b5c9a..1939e66179 100644 --- a/engines/sci/graphics/view.cpp +++ b/engines/sci/graphics/view.cpp @@ -25,6 +25,7 @@ #include "sci/engine/state.h" #include "sci/graphics/screen.h" #include "sci/graphics/palette.h" +#include "sci/graphics/remap.h" #include "sci/graphics/coordadjuster.h" #include "sci/graphics/view.h" @@ -833,19 +834,6 @@ void GfxView::draw(const Common::Rect &rect, const Common::Rect &clipRect, const bitmap += (clipRect.top - rect.top) * celWidth + (clipRect.left - rect.left); - // WORKAROUND: EcoQuest French and German draw the fish and anemone sprites - // with priority 15 in scene 440. Afterwards, a dialog is shown on top of - // these sprites with priority 15 as well. This is undefined behavior - // actually, as the sprites and dialog share the same priority, so in our - // implementation the sprites get drawn incorrectly on top of the dialog. - // Perhaps this worked by mistake in SSCI because of subtle differences in - // how sprites are drawn. We compensate for this by resetting the priority - // of all sprites that have a priority of 15 in scene 440 to priority 14, - // so that the speech bubble can be drawn correctly on top of them. Fixes - // bug #3040625. - if (g_sci->getGameId() == GID_ECOQUEST && g_sci->getEngineState()->currentRoomNumber() == 440 && priority == 15) - priority = 14; - if (!_EGAmapping) { for (y = 0; y < height; y++, bitmap += celWidth) { for (x = 0; x < width; x++) { @@ -855,12 +843,11 @@ void GfxView::draw(const Common::Rect &rect, const Common::Rect &clipRect, const const int y2 = clipRectTranslated.top + y; if (!upscaledHires) { if (priority >= _screen->getPriority(x2, y2)) { - if (!_palette->isRemapped(palette->mapping[color])) { - _screen->putPixel(x2, y2, drawMask, palette->mapping[color], priority, 0); - } else { - byte remappedColor = _palette->remapColor(palette->mapping[color], _screen->getVisual(x2, y2)); - _screen->putPixel(x2, y2, drawMask, remappedColor, priority, 0); - } + byte outputColor = palette->mapping[color]; + // SCI16 remapping (QFG4 demo) + if (g_sci->_gfxRemap16 && g_sci->_gfxRemap16->isRemapped(outputColor)) + outputColor = g_sci->_gfxRemap16->remapColor(outputColor, _screen->getVisual(x2, y2)); + _screen->putPixel(x2, y2, drawMask, outputColor, priority, 0); } } else { // UpscaledHires means view is hires and is supposed to @@ -970,12 +957,11 @@ void GfxView::drawScaled(const Common::Rect &rect, const Common::Rect &clipRect, const int x2 = clipRectTranslated.left + x; const int y2 = clipRectTranslated.top + y; if (color != clearKey && priority >= _screen->getPriority(x2, y2)) { - if (!_palette->isRemapped(palette->mapping[color])) { - _screen->putPixel(x2, y2, drawMask, palette->mapping[color], priority, 0); - } else { - byte remappedColor = _palette->remapColor(palette->mapping[color], _screen->getVisual(x2, y2)); - _screen->putPixel(x2, y2, drawMask, remappedColor, priority, 0); - } + byte outputColor = palette->mapping[color]; + // SCI16 remapping (QFG4 demo) + if (g_sci->_gfxRemap16 && g_sci->_gfxRemap16->isRemapped(outputColor)) + outputColor = g_sci->_gfxRemap16->remapColor(outputColor, _screen->getVisual(x2, y2)); + _screen->putPixel(x2, y2, drawMask, outputColor, priority, 0); } } } @@ -989,13 +975,4 @@ void GfxView::adjustBackUpscaledCoordinates(int16 &y, int16 &x) { _screen->adjustBackUpscaledCoordinates(y, x, _sci2ScaleRes); } -byte GfxView::getColorAtCoordinate(int16 loopNo, int16 celNo, int16 x, int16 y) { - const CelInfo *celInfo = getCelInfo(loopNo, celNo); - const byte *bitmap = getBitmap(loopNo, celNo); - const int16 celWidth = celInfo->width; - - bitmap += (celWidth * y); - return bitmap[x]; -} - } // End of namespace Sci diff --git a/engines/sci/graphics/view.h b/engines/sci/graphics/view.h index d8803db208..91590208c1 100644 --- a/engines/sci/graphics/view.h +++ b/engines/sci/graphics/view.h @@ -85,8 +85,6 @@ public: void adjustToUpscaledCoordinates(int16 &y, int16 &x); void adjustBackUpscaledCoordinates(int16 &y, int16 &x); - byte getColorAtCoordinate(int16 loopNo, int16 celNo, int16 x, int16 y); - private: void initData(GuiResourceId resourceId); void unpackCel(int16 loopNo, int16 celNo, byte *outPtr, uint32 pixelCount); diff --git a/engines/sci/module.mk b/engines/sci/module.mk index 08e5ea84d8..a02147e4d0 100644 --- a/engines/sci/module.mk +++ b/engines/sci/module.mk @@ -57,6 +57,7 @@ MODULE_OBJS := \ graphics/picture.o \ graphics/portrait.o \ graphics/ports.o \ + graphics/remap.o \ graphics/screen.o \ graphics/text16.o \ graphics/transitions.o \ @@ -81,10 +82,13 @@ MODULE_OBJS := \ ifdef ENABLE_SCI32 MODULE_OBJS += \ engine/kgraphics32.o \ + graphics/celobj32.o \ graphics/controls32.o \ graphics/frameout.o \ graphics/paint32.o \ + graphics/plane32.o \ graphics/palette32.o \ + graphics/screen_item32.o \ graphics/text32.o \ video/robot_decoder.o endif diff --git a/engines/sci/parser/vocabulary.cpp b/engines/sci/parser/vocabulary.cpp index 828a57abeb..a09ba8f3ce 100644 --- a/engines/sci/parser/vocabulary.cpp +++ b/engines/sci/parser/vocabulary.cpp @@ -121,7 +121,7 @@ bool Vocabulary::loadParserWords() { } } - unsigned int seeker; + uint32 seeker; if (resourceType == kVocabularySCI1) seeker = 255 * 2; // vocab.900 starts with 255 16-bit pointers which we don't use else @@ -202,7 +202,7 @@ bool Vocabulary::loadSuffixes() { if (!resource) return false; // No vocabulary found - unsigned int seeker = 1; + uint32 seeker = 1; while ((seeker < resource->size - 1) && (resource->data[seeker + 1] != 0xff)) { suffix_t suffix; @@ -288,7 +288,7 @@ bool Vocabulary::loadAltInputs() { AltInput t; t._input = data; - unsigned int l = strlen(data); + uint32 l = strlen(data); t._inputLength = l; data += l + 1; @@ -325,15 +325,15 @@ bool Vocabulary::checkAltInput(Common::String& text, uint16& cursorPos) { return false; bool ret = false; - unsigned int loopCount = 0; + uint32 loopCount = 0; bool changed; do { changed = false; const char* t = text.c_str(); - unsigned int tlen = text.size(); + uint32 tlen = text.size(); - for (unsigned int p = 0; p < tlen && !changed; ++p) { + for (uint32 p = 0; p < tlen && !changed; ++p) { unsigned char s = t[p]; if (s >= _altInputs.size() || _altInputs[s].empty()) continue; @@ -351,7 +351,7 @@ bool Vocabulary::checkAltInput(Common::String& text, uint16& cursorPos) { cursorPos = p + strlen(i->_replacement); } - for (unsigned int j = 0; j < i->_inputLength; ++j) + for (uint32 j = 0; j < i->_inputLength; ++j) text.deleteChar(p); const char *r = i->_replacement; while (*r) diff --git a/engines/sci/parser/vocabulary.h b/engines/sci/parser/vocabulary.h index f4adee6e55..59558ce18a 100644 --- a/engines/sci/parser/vocabulary.h +++ b/engines/sci/parser/vocabulary.h @@ -156,7 +156,7 @@ typedef Common::Array<synonym_t> SynonymList; struct AltInput { const char *_input; const char *_replacement; - unsigned int _inputLength; + uint32 _inputLength; bool _prefix; }; diff --git a/engines/sci/resource.cpp b/engines/sci/resource.cpp index 54ef4b3363..6a5af1a6d6 100644 --- a/engines/sci/resource.cpp +++ b/engines/sci/resource.cpp @@ -307,7 +307,7 @@ bool Resource::loadPatch(Common::SeekableReadStream *file) { error("Can't allocate %d bytes needed for loading %s", res->size + res->_headerSize, res->_id.toString().c_str()); } - unsigned int really_read; + uint32 really_read; if (res->_headerSize > 0) { really_read = file->read(res->_header, res->_headerSize); if (really_read != res->_headerSize) @@ -565,12 +565,11 @@ Resource *ResourceManager::testResource(ResourceId id) { } int ResourceManager::addAppropriateSources() { - Common::ArchiveMemberList files; - if (Common::File::exists("resource.map")) { // SCI0-SCI2 file naming scheme ResourceSource *map = addExternalMap("resource.map"); + Common::ArchiveMemberList files; SearchMan.listMatchingMembers(files, "resource.0??"); for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) { @@ -587,20 +586,20 @@ int ResourceManager::addAppropriateSources() { #endif } else if (Common::MacResManager::exists("Data1")) { // Mac SCI1.1+ file naming scheme - SearchMan.listMatchingMembers(files, "Data?*"); + Common::StringArray files; + Common::MacResManager::listFiles(files, "Data?"); - for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) { - Common::String filename = (*x)->getName(); - addSource(new MacResourceForkResourceSource(filename, atoi(filename.c_str() + 4))); + for (Common::StringArray::const_iterator x = files.begin(); x != files.end(); ++x) { + addSource(new MacResourceForkResourceSource(*x, atoi(x->c_str() + 4))); } #ifdef ENABLE_SCI32 // There can also be a "Patches" resource fork with patches - if (Common::File::exists("Patches")) + if (Common::MacResManager::exists("Patches")) addSource(new MacResourceForkResourceSource("Patches", 100)); } else { // SCI2.1-SCI3 file naming scheme - Common::ArchiveMemberList mapFiles; + Common::ArchiveMemberList mapFiles, files; SearchMan.listMatchingMembers(mapFiles, "resmap.0??"); SearchMan.listMatchingMembers(files, "ressci.0??"); @@ -865,6 +864,7 @@ ResourceManager::ResourceManager() { } void ResourceManager::init() { + _maxMemoryLRU = 256 * 1024; // 256KiB _memoryLocked = 0; _memoryLRU = 0; _LRU.clear(); @@ -918,6 +918,14 @@ void ResourceManager::init() { debugC(1, kDebugLevelResMan, "resMan: Detected %s", getSciVersionDesc(getSciVersion())); + // Resources in SCI32 games are significantly larger than SCI16 + // games and can cause immediate exhaustion of the LRU resource + // cache, leading to constant decompression of picture resources + // and making the renderer very slow. + if (getSciVersion() >= SCI_VERSION_2) { + _maxMemoryLRU = 2048 * 1024; // 2MiB + } + switch (_viewType) { case kViewEga: debugC(1, kDebugLevelResMan, "resMan: Detected EGA graphic resources"); @@ -935,35 +943,14 @@ void ResourceManager::init() { debugC(1, kDebugLevelResMan, "resMan: Detected SCI1.1 VGA graphic resources"); break; default: -#ifdef ENABLE_SCI32 - error("resMan: Couldn't determine view type"); -#else - if (getSciVersion() >= SCI_VERSION_2) { - // SCI support isn't built in, thus the view type won't be determined for - // SCI2+ games. This will be handled further up, so throw no error here - } else { - error("resMan: Couldn't determine view type"); - } -#endif + // Throw a warning, but do not error out here, because this is called from the + // fallback detector, and the user could be pointing to a folder with a non-SCI + // game, but with SCI-like file names (e.g. Pinball Creep) + warning("resMan: Couldn't determine view type"); + break; } } -void ResourceManager::initForDetection() { - assert(!g_sci); - - _memoryLocked = 0; - _memoryLRU = 0; - _LRU.clear(); - _resMap.clear(); - _audioMapSCI1 = NULL; - - _mapVersion = detectMapVersion(); - _volVersion = detectVolVersion(); - - scanNewSources(); - detectSciVersion(); -} - ResourceManager::~ResourceManager() { // freeing resources ResourceMap::iterator itr = _resMap.begin(); @@ -998,9 +985,9 @@ void ResourceManager::addToLRU(Resource *res) { _LRU.push_front(res); _memoryLRU += res->size; #if SCI_VERBOSE_RESMAN - debug("Adding %s.%03d (%d bytes) to lru control: %d bytes total", - getResourceTypeName(res->type), res->number, res->size, - mgr->_memoryLRU); + debug("Adding %s (%d bytes) to lru control: %d bytes total", + res->_id.toString().c_str(), res->size, + _memoryLRU); #endif res->_status = kResStatusEnqueued; } @@ -1023,13 +1010,13 @@ void ResourceManager::printLRU() { } void ResourceManager::freeOldResources() { - while (MAX_MEMORY < _memoryLRU) { + while (_maxMemoryLRU < _memoryLRU) { assert(!_LRU.empty()); Resource *goner = *_LRU.reverse_begin(); removeFromLRU(goner); goner->unalloc(); #ifdef SCI_VERBOSE_RESMAN - debug("resMan-debug: LRU: Freeing %s.%03d (%d bytes)", getResourceTypeName(goner->type), goner->number, goner->size); + debug("resMan-debug: LRU: Freeing %s (%d bytes)", goner->_id.toString().c_str(), goner->size); #endif } } @@ -2474,7 +2461,9 @@ bool ResourceManager::hasOldScriptHeader() { Resource *res = findResource(ResourceId(kResourceTypeScript, 0), 0); if (!res) { - error("resMan: Failed to find script.000"); + // Script 0 missing -> corrupted / non-SCI resource files. + // Don't error out here, because this might have been called + // from the fallback detector return false; } @@ -2679,7 +2668,9 @@ Common::String ResourceManager::findSierraGameId() { return ""; // Seek to the name selector of the first export - byte *seeker = heap->data + READ_UINT16(heap->data + gameObjectOffset + nameSelector * 2); + byte *offsetPtr = heap->data + gameObjectOffset + nameSelector * 2; + uint16 offset = !isSci11Mac() ? READ_LE_UINT16(offsetPtr) : READ_BE_UINT16(offsetPtr); + byte *seeker = heap->data + offset; Common::String sierraId; sierraId += (const char *)seeker; diff --git a/engines/sci/resource.h b/engines/sci/resource.h index eb5b508254..ef474d97c2 100644 --- a/engines/sci/resource.h +++ b/engines/sci/resource.h @@ -315,11 +315,6 @@ public: void init(); /** - * Similar to the function above, only called from the fallback detector - */ - void initForDetection(); - - /** * Adds all of the resource files for a game */ int addAppropriateSources(); @@ -426,9 +421,7 @@ protected: // Note: maxMemory will not be interpreted as a hard limit, only as a restriction // for resources which are not explicitly locked. However, a warning will be // issued whenever this limit is exceeded. - enum { - MAX_MEMORY = 256 * 1024 // 256KB - }; + int _maxMemoryLRU; ViewType _viewType; // Used to determine if the game has EGA or VGA graphics Common::List<ResourceSource *> _sources; diff --git a/engines/sci/resource_audio.cpp b/engines/sci/resource_audio.cpp index 6869e6379e..5717a09121 100644 --- a/engines/sci/resource_audio.cpp +++ b/engines/sci/resource_audio.cpp @@ -139,7 +139,7 @@ bool Resource::loadFromAudioVolumeSCI1(Common::SeekableReadStream *file) { error("Can't allocate %d bytes needed for loading %s", size, _id.toString().c_str()); } - unsigned int really_read = file->read(data, size); + uint32 really_read = file->read(data, size); if (really_read != size) warning("Read %d bytes from %s but expected %d", really_read, _id.toString().c_str(), size); @@ -688,6 +688,12 @@ SoundResource::SoundResource(uint32 resourceNr, ResourceManager *resMan, SciVers channel->data = resource->data + dataOffset; channel->size = READ_LE_UINT16(data + 4); + + if (dataOffset + channel->size > resource->size) { + warning("Invalid size inside sound resource %d: track %d, channel %d", resourceNr, trackNr, channelNr); + channel->size = resource->size - dataOffset; + } + channel->curPos = 0; channel->number = *channel->data; diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp index 1232b6559b..e14d12b918 100644 --- a/engines/sci/sci.cpp +++ b/engines/sci/sci.cpp @@ -58,6 +58,7 @@ #include "sci/graphics/picture.h" #include "sci/graphics/ports.h" #include "sci/graphics/palette.h" +#include "sci/graphics/remap.h" #include "sci/graphics/screen.h" #include "sci/graphics/text16.h" #include "sci/graphics/transitions.h" @@ -163,6 +164,7 @@ SciEngine::~SciEngine() { delete _gfxText32; delete _robotDecoder; delete _gfxFrameout; + delete _gfxRemap32; #endif delete _gfxMenu; delete _gfxControls16; @@ -175,6 +177,7 @@ SciEngine::~SciEngine() { delete _gfxPorts; delete _gfxCache; delete _gfxPalette16; + delete _gfxRemap16; delete _gfxCursor; delete _gfxScreen; @@ -238,13 +241,7 @@ Common::Error SciEngine::run() { // Only DOS+Windows switch (_gameId) { case GID_KQ6: - if (isCD()) - _forceHiresGraphics = ConfMan.getBool("enable_high_resolution_graphics"); - break; case GID_GK1: - if ((isCD()) && (!isDemo())) - _forceHiresGraphics = ConfMan.getBool("enable_high_resolution_graphics"); - break; case GID_PQ4: if (isCD()) _forceHiresGraphics = ConfMan.getBool("enable_high_resolution_graphics"); @@ -316,6 +313,7 @@ Common::Error SciEngine::run() { if (directSaveSlotLoading >= 0) { _gamestate->_delayedRestoreGame = true; _gamestate->_delayedRestoreGameId = directSaveSlotLoading; + _gamestate->_delayedRestoreFromLauncher = true; // Jones only initializes its menus when restarting/restoring, thus set // the gameIsRestarting flag here before initializing. Fixes bug #6536. @@ -529,7 +527,7 @@ void SciEngine::patchGameSaveRestore() { byte kernelIdSave = 0; switch (_gameId) { - case GID_HOYLE1: // gets confused, although the game doesnt support saving/restoring at all + case GID_HOYLE1: // gets confused, although the game doesn't support saving/restoring at all case GID_HOYLE2: // gets confused, see hoyle1 case GID_JONES: // gets confused, when we patch us in, the game is only able to save to 1 slot, so hooking is not required case GID_MOTHERGOOSE: // mother goose EGA saves/restores directly and has no save/restore dialogs @@ -576,17 +574,29 @@ void SciEngine::patchGameSaveRestore() { } } + const Object *patchObjectSave = nullptr; + + if (getSciVersion() < SCI_VERSION_2) { + // Patch gameobject ::save for now for SCI0 - SCI1.1 + // TODO: It seems this was never adjusted to superclass, but adjusting it now may cause + // issues with some game. Needs to get checked and then possibly changed. + patchObjectSave = gameObject; + } else { + // Patch superclass ::save for SCI32 + patchObjectSave = gameSuperObject; + } + // Search for gameobject ::save, if there is one patch that one too - uint16 gameObjectMethodCount = gameObject->getMethodCount(); - for (uint16 methodNr = 0; methodNr < gameObjectMethodCount; methodNr++) { - uint16 selectorId = gameObject->getFuncSelector(methodNr); + uint16 patchObjectMethodCount = patchObjectSave->getMethodCount(); + for (uint16 methodNr = 0; methodNr < patchObjectMethodCount; methodNr++) { + uint16 selectorId = patchObjectSave->getFuncSelector(methodNr); Common::String methodName = _kernel->getSelectorName(selectorId); if (methodName == "save") { if (_gameId != GID_FAIRYTALES) { // Fairy Tales saves automatically without a dialog if (kernelIdSave != kernelIdRestore) - patchGameSaveRestoreCode(segMan, gameObject->getFunction(methodNr), kernelIdSave); + patchGameSaveRestoreCode(segMan, patchObjectSave->getFunction(methodNr), kernelIdSave); else - patchGameSaveRestoreCodeSci21(segMan, gameObject->getFunction(methodNr), kernelIdSave, false); + patchGameSaveRestoreCodeSci21(segMan, patchObjectSave->getFunction(methodNr), kernelIdSave, false); } break; } @@ -653,6 +663,7 @@ void SciEngine::initGraphics() { _gfxPaint = 0; _gfxPaint16 = 0; _gfxPalette16 = 0; + _gfxRemap16 = 0; _gfxPorts = 0; _gfxText16 = 0; _gfxTransitions = 0; @@ -663,6 +674,7 @@ void SciEngine::initGraphics() { _gfxFrameout = 0; _gfxPaint32 = 0; _gfxPalette32 = 0; + _gfxRemap32 = 0; #endif if (hasMacIconBar()) @@ -672,9 +684,12 @@ void SciEngine::initGraphics() { if (getSciVersion() >= SCI_VERSION_2) { _gfxPalette32 = new GfxPalette32(_resMan, _gfxScreen); _gfxPalette16 = _gfxPalette32; + _gfxRemap32 = new GfxRemap32(_gfxPalette32); } else { #endif _gfxPalette16 = new GfxPalette(_resMan, _gfxScreen); + if (getGameId() == GID_QFG4DEMO) + _gfxRemap16 = new GfxRemap(_gfxPalette16); #ifdef ENABLE_SCI32 } #endif @@ -690,10 +705,11 @@ void SciEngine::initGraphics() { _gfxCompare = new GfxCompare(_gamestate->_segMan, _gfxCache, _gfxScreen, _gfxCoordAdjuster); _gfxPaint32 = new GfxPaint32(_resMan, _gfxCoordAdjuster, _gfxScreen, _gfxPalette32); _gfxPaint = _gfxPaint32; - _gfxText32 = new GfxText32(_gamestate->_segMan, _gfxCache, _gfxScreen); - _gfxControls32 = new GfxControls32(_gamestate->_segMan, _gfxCache, _gfxText32); _robotDecoder = new RobotDecoder(getPlatform() == Common::kPlatformMacintosh); _gfxFrameout = new GfxFrameout(_gamestate->_segMan, _resMan, _gfxCoordAdjuster, _gfxCache, _gfxScreen, _gfxPalette32, _gfxPaint32); + _gfxText32 = new GfxText32(_gamestate->_segMan, _gfxCache); + _gfxControls32 = new GfxControls32(_gamestate->_segMan, _gfxCache, _gfxText32); + _gfxFrameout->run(); } else { #endif // SCI0-SCI1.1 graphic objects creation @@ -704,7 +720,7 @@ void SciEngine::initGraphics() { _gfxTransitions = new GfxTransitions(_gfxScreen, _gfxPalette16); _gfxPaint16 = new GfxPaint16(_resMan, _gamestate->_segMan, _gfxCache, _gfxPorts, _gfxCoordAdjuster, _gfxScreen, _gfxPalette16, _gfxTransitions, _audio); _gfxPaint = _gfxPaint16; - _gfxAnimate = new GfxAnimate(_gamestate, _gfxCache, _gfxPorts, _gfxPaint16, _gfxScreen, _gfxPalette16, _gfxCursor, _gfxTransitions); + _gfxAnimate = new GfxAnimate(_gamestate, _scriptPatcher, _gfxCache, _gfxPorts, _gfxPaint16, _gfxScreen, _gfxPalette16, _gfxCursor, _gfxTransitions); _gfxText16 = new GfxText16(_gfxCache, _gfxPorts, _gfxPaint16, _gfxScreen); _gfxControls16 = new GfxControls16(_gamestate->_segMan, _gfxPorts, _gfxPaint16, _gfxText16, _gfxScreen); _gfxMenu = new GfxMenu(_eventMan, _gamestate->_segMan, _gfxPorts, _gfxPaint16, _gfxText16, _gfxScreen, _gfxCursor); @@ -820,7 +836,7 @@ Console *SciEngine::getSciDebugger() { } const char *SciEngine::getGameIdStr() const { - return _gameDescription->gameid; + return _gameDescription->gameId; } Common::Language SciEngine::getLanguage() const { @@ -897,12 +913,30 @@ int SciEngine::inQfGImportRoom() const { void SciEngine::setLauncherLanguage() { if (_gameDescription->flags & ADGF_ADDENGLISH) { // If game is multilingual - if (Common::parseLanguage(ConfMan.get("language")) == Common::EN_ANY) { + Common::Language chosenLanguage = Common::parseLanguage(ConfMan.get("language")); + uint16 languageToSet = 0; + + switch (chosenLanguage) { + case Common::EN_ANY: // and English was selected as language - if (SELECTOR(printLang) != -1) // set text language to English - writeSelectorValue(_gamestate->_segMan, _gameObjectAddress, SELECTOR(printLang), K_LANG_ENGLISH); - if (SELECTOR(parseLang) != -1) // and set parser language to English as well - writeSelectorValue(_gamestate->_segMan, _gameObjectAddress, SELECTOR(parseLang), K_LANG_ENGLISH); + languageToSet = K_LANG_ENGLISH; + break; + case Common::JA_JPN: { + // Set Japanese for FM-Towns games + // KQ5 on FM-Towns has no initial language set + if (g_sci->getPlatform() == Common::kPlatformFMTowns) { + languageToSet = K_LANG_JAPANESE; + } + } + default: + break; + } + + if (languageToSet) { + if (SELECTOR(printLang) != -1) // set text language + writeSelectorValue(_gamestate->_segMan, _gameObjectAddress, SELECTOR(printLang), languageToSet); + if (SELECTOR(parseLang) != -1) // and set parser language as well + writeSelectorValue(_gamestate->_segMan, _gameObjectAddress, SELECTOR(parseLang), languageToSet); } } } diff --git a/engines/sci/sci.h b/engines/sci/sci.h index 5c86d92355..7df3d38163 100644 --- a/engines/sci/sci.h +++ b/engines/sci/sci.h @@ -71,6 +71,8 @@ class GfxPaint16; class GfxPaint32; class GfxPalette; class GfxPalette32; +class GfxRemap; +class GfxRemap32; class GfxPorts; class GfxScreen; class GfxText16; @@ -128,6 +130,7 @@ enum SciGameId { GID_FAIRYTALES, GID_FREDDYPHARKAS, GID_FUNSEEKER, + GID_GK1DEMO, // We have a separate ID for GK1 demo, because it's actually a completely different game (SCI1.1 vs SCI2/SCI2.1) GID_GK1, GID_GK2, GID_HOYLE1, @@ -165,12 +168,14 @@ enum SciGameId { GID_PQ2, GID_PQ3, GID_PQ4, + GID_PQ4DEMO, // We have a separate ID for PQ4 demo, because it's actually a completely different game (SCI1.1 vs SCI2/SCI2.1) GID_PQSWAT, GID_QFG1, GID_QFG1VGA, GID_QFG2, GID_QFG3, GID_QFG4, + GID_QFG4DEMO, // We have a separate ID for QFG4 demo, because it's actually a completely different game (SCI1.1 vs SCI2/SCI2.1) GID_RAMA, GID_SHIVERS, //GID_SHIVERS2, // Not SCI @@ -201,8 +206,8 @@ enum SciVersion { SCI_VERSION_1_LATE, // Dr. Brain 1, EcoQuest 1, Longbow, PQ3, SQ1, LSL5, KQ5 CD SCI_VERSION_1_1, // Dr. Brain 2, EcoQuest 1 CD, EcoQuest 2, KQ6, QFG3, SQ4CD, XMAS 1992 and many more SCI_VERSION_2, // GK1, PQ4 floppy, QFG4 floppy - SCI_VERSION_2_1_EARLY, // GK2 demo, KQ7, LSL6 hires, PQ4, QFG4 floppy - SCI_VERSION_2_1_MIDDLE, // GK2, KQ7, MUMG Deluxe, Phantasmagoria 1, PQ4CD, PQ:SWAT, QFG4CD, Shivers 1, SQ6, Torin + SCI_VERSION_2_1_EARLY, // GK2 demo, KQ7 1.4/1.51, LSL6 hires, PQ4CD, QFG4 floppy + SCI_VERSION_2_1_MIDDLE, // GK2, KQ7 2.00b, MUMG Deluxe, Phantasmagoria 1, PQ:SWAT, QFG4CD, Shivers 1, SQ6, Torin SCI_VERSION_2_1_LATE, // demos of LSL7, Lighthouse, RAMA SCI_VERSION_3 // LSL7, Lighthouse, RAMA, Phantasmagoria 2 }; @@ -282,7 +287,7 @@ public: inline EngineState *getEngineState() const { return _gamestate; } inline Vocabulary *getVocabulary() const { return _vocabulary; } inline EventManager *getEventManager() const { return _eventMan; } - inline reg_t getGameObject() const { return _gameObjectAddress; } + inline reg_t getGameObject() const { return _gameObjectAddress; } // Gets the game object VM address Common::RandomSource &getRNG() { return _rng; } @@ -349,6 +354,8 @@ public: GfxMenu *_gfxMenu; // Menu for 16-bit gfx GfxPalette *_gfxPalette16; GfxPalette32 *_gfxPalette32; // Palette for 32-bit gfx + GfxRemap *_gfxRemap16; // Remapping for the QFG4 demo + GfxRemap32 *_gfxRemap32; // Remapping for 32-bit gfx GfxPaint *_gfxPaint; GfxPaint16 *_gfxPaint16; // Painting in 16-bit gfx GfxPaint32 *_gfxPaint32; // Painting in 32-bit gfx diff --git a/engines/sci/sound/drivers/amigamac.cpp b/engines/sci/sound/drivers/amigamac.cpp index 5ce49086ca..0f93b19e7c 100644 --- a/engines/sci/sound/drivers/amigamac.cpp +++ b/engines/sci/sound/drivers/amigamac.cpp @@ -497,7 +497,7 @@ MidiDriver_AmigaMac::InstrumentSample *MidiDriver_AmigaMac::readInstrumentSCI0(C } instrument->samples = (int8 *) malloc(size + 1); - if (file.read(instrument->samples, size) < (unsigned int)size) { + if (file.read(instrument->samples, size) < (uint32)size) { warning("Amiga/Mac driver: failed to read instrument samples"); free(instrument->samples); delete instrument; diff --git a/engines/sci/sound/soundcmd.cpp b/engines/sci/sound/soundcmd.cpp index ee5903fda2..e7b25eb1fc 100644 --- a/engines/sci/sound/soundcmd.cpp +++ b/engines/sci/sound/soundcmd.cpp @@ -44,7 +44,7 @@ SoundCommandParser::SoundCommandParser(ResourceManager *resMan, SegManager *segM // resource number, but it's totally unrelated to the menu music). // The GK1 demo (very late SCI1.1) does the same thing // TODO: Check the QFG4 demo - _useDigitalSFX = (getSciVersion() >= SCI_VERSION_2 || g_sci->getGameId() == GID_GK1 || ConfMan.getBool("prefer_digitalsfx")); + _useDigitalSFX = (getSciVersion() >= SCI_VERSION_2 || g_sci->getGameId() == GID_GK1DEMO || ConfMan.getBool("prefer_digitalsfx")); _music = new SciMusic(_soundVersion, _useDigitalSFX); _music->init(); diff --git a/engines/sci/util.cpp b/engines/sci/util.cpp index c72d3beb19..ccec41a1ab 100644 --- a/engines/sci/util.cpp +++ b/engines/sci/util.cpp @@ -69,4 +69,13 @@ void WRITE_SCI11ENDIAN_UINT16(void *ptr, uint16 val) { WRITE_LE_UINT16(ptr, val); } +#ifdef ENABLE_SCI32 +void WRITE_SCI11ENDIAN_UINT32(void *ptr, uint32 val) { + if (g_sci->getPlatform() == Common::kPlatformMacintosh && getSciVersion() >= SCI_VERSION_1_1) + WRITE_BE_UINT32(ptr, val); + else + WRITE_LE_UINT32(ptr, val); +} +#endif + } // End of namespace Sci diff --git a/engines/sci/util.h b/engines/sci/util.h index 378030939c..b0fee5151e 100644 --- a/engines/sci/util.h +++ b/engines/sci/util.h @@ -37,6 +37,9 @@ void WRITE_SCIENDIAN_UINT16(void *ptr, uint16 val); uint16 READ_SCI11ENDIAN_UINT16(const void *ptr); uint32 READ_SCI11ENDIAN_UINT32(const void *ptr); void WRITE_SCI11ENDIAN_UINT16(void *ptr, uint16 val); +#ifdef ENABLE_SCI32 +void WRITE_SCI11ENDIAN_UINT32(void *ptr, uint32 val); +#endif // Wrappers for reading integer values in resources that are // LE in SCI1.1 Mac, but BE in SCI32 Mac diff --git a/engines/scumm/actor.cpp b/engines/scumm/actor.cpp index 0d7ea39ec2..3a69b5f03c 100644 --- a/engines/scumm/actor.cpp +++ b/engines/scumm/actor.cpp @@ -861,7 +861,7 @@ L2C36:; stopActorMoving(); return; } - // 2C98: Yes, an exact copy of what just occured.. the original does this, so im doing it... + // 2C98: Yes, an exact copy of what just occurred.. the original does this, so im doing it... // Just to keep me sane when going over it :) if (A == 0xFF) { setActorFromTmp(); diff --git a/engines/scumm/charset-fontdata.cpp b/engines/scumm/charset-fontdata.cpp index 23e89b1878..a1e92a9950 100644 --- a/engines/scumm/charset-fontdata.cpp +++ b/engines/scumm/charset-fontdata.cpp @@ -591,35 +591,40 @@ CharsetRendererV2::CharsetRendererV2(ScummEngine *vm, Common::Language language) _fontHeight = 8; _curId = 0; - const byte *replacementData = NULL; + const byte *replacementMap = NULL, *replacementData = NULL; int replacementChars = 0; switch (language) { case Common::DE_DEU: if (_vm->_game.version == 0) { - replacementData = germanCharsetDataV0; + replacementMap = germanCharsetDataV0; replacementChars = sizeof(germanCharsetDataV0) / 2; } else { - replacementData = germanCharsetDataV2; + replacementMap = germanCharsetDataV2; replacementChars = sizeof(germanCharsetDataV2) / 2; } + replacementData = specialCharsetData; break; case Common::FR_FRA: - replacementData = frenchCharsetDataV2; + replacementMap = frenchCharsetDataV2; replacementChars = sizeof(frenchCharsetDataV2) / 2; + replacementData = specialCharsetData; break; case Common::IT_ITA: - replacementData = italianCharsetDataV2; + replacementMap = italianCharsetDataV2; replacementChars = sizeof(italianCharsetDataV2) / 2; + replacementData = specialCharsetData; break; case Common::ES_ESP: - replacementData = spanishCharsetDataV2; + replacementMap = spanishCharsetDataV2; replacementChars = sizeof(spanishCharsetDataV2) / 2; + replacementData = specialCharsetData; break; case Common::RU_RUS: if (((_vm->_game.id == GID_MANIAC) || (_vm->_game.id == GID_ZAK)) && (_vm->_game.version == 2)) { - replacementData = russCharsetDataV2; + replacementMap = russCharsetDataV2; replacementChars = sizeof(russCharsetDataV2) / 2; + replacementData = russianCharsetDataV2; } else { _fontPtr = russianCharsetDataV2; } @@ -629,20 +634,16 @@ CharsetRendererV2::CharsetRendererV2(ScummEngine *vm, Common::Language language) break; } - if (replacementData) { + if (replacementMap && replacementData) { _fontPtr = new byte[sizeof(englishCharsetDataV2)]; _deleteFontPtr = true; memcpy(const_cast<byte *>(_fontPtr), englishCharsetDataV2, sizeof(englishCharsetDataV2)); for (int i = 0; i < replacementChars; i++) { - int ch1 = replacementData[2 * i]; - int ch2 = replacementData[2 * i + 1]; + int ch1 = replacementMap[2 * i]; + int ch2 = replacementMap[2 * i + 1]; - if (((_vm->_game.id == GID_MANIAC) || (_vm->_game.id == GID_ZAK)) && (_vm->_game.version == 2)) { - memcpy(const_cast<byte *>(_fontPtr) + 8 * ch1, russianCharsetDataV2 + 8 * ch2, 8); - } else { - memcpy(const_cast<byte *>(_fontPtr) + 8 * ch1, specialCharsetData + 8 * ch2, 8); - } + memcpy(const_cast<byte *>(_fontPtr) + 8 * ch1, replacementData + 8 * ch2, 8); } } else _deleteFontPtr = false; diff --git a/engines/scumm/configure.engine b/engines/scumm/configure.engine index e1de788061..e8962a371e 100644 --- a/engines/scumm/configure.engine +++ b/engines/scumm/configure.engine @@ -2,4 +2,4 @@ # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] add_engine scumm "SCUMM" yes "scumm_7_8 he" "v0-v6 games" add_engine scumm_7_8 "v7 & v8 games" yes -add_engine he "HE71+ games" yes +add_engine he "HE71+ games" yes "" "" "highres" diff --git a/engines/scumm/detection.cpp b/engines/scumm/detection.cpp index e5bbad15e6..9264a6443b 100644 --- a/engines/scumm/detection.cpp +++ b/engines/scumm/detection.cpp @@ -1271,7 +1271,6 @@ SaveStateList ScummMetaEngine::listSaves(const char *target) const { pattern += ".s##"; filenames = saveFileMan->listSavefiles(pattern); - sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) SaveStateList saveList; for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { @@ -1288,6 +1287,8 @@ SaveStateList ScummMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/scumm/he/sound_he.cpp b/engines/scumm/he/sound_he.cpp index a78aff96f8..b806a9f3cc 100644 --- a/engines/scumm/he/sound_he.cpp +++ b/engines/scumm/he/sound_he.cpp @@ -636,7 +636,7 @@ void SoundHE::playHESound(int soundID, int heOffset, int heChannel, int heFlags) if (heFlags & 1) { _heChannel[heChannel].timer = 0; } else { - _heChannel[heChannel].timer = size * 1000 / rate; + _heChannel[heChannel].timer = size * 1000 / (rate * blockAlign); } _mixer->stopHandle(_heSoundChannels[heChannel]); @@ -658,7 +658,7 @@ void SoundHE::playHESound(int soundID, int heOffset, int heChannel, int heFlags) _heChannel[heChannel].rate = rate; if (_heChannel[heChannel].timer) - _heChannel[heChannel].timer = size * 1000 / rate; + _heChannel[heChannel].timer = size * 1000 / (rate * blockAlign); // makeADPCMStream returns a stream in native endianness, but RawMemoryStream // defaults to big endian. If we're on a little endian system, set the LE flag. diff --git a/engines/scumm/imuse_digi/dimuse_track.cpp b/engines/scumm/imuse_digi/dimuse_track.cpp index b7abdd074e..28ad64670c 100644 --- a/engines/scumm/imuse_digi/dimuse_track.cpp +++ b/engines/scumm/imuse_digi/dimuse_track.cpp @@ -352,9 +352,9 @@ Track *IMuseDigital::cloneToFadeOutTrack(Track *track, int fadeDelay) { // leaving bug number for now #1635361 ImuseDigiSndMgr::SoundDesc *soundDesc = _sound->cloneSound(track->soundDesc); if (!soundDesc) { - // it fail load open old song after switch to diffrent CDs + // it fail load open old song after switch to different CDs // so gave up - error("Game not supported while playing on 2 diffrent CDs"); + error("Game not supported while playing on 2 different CDs"); } track->soundDesc = soundDesc; diff --git a/engines/scumm/players/player_ad.cpp b/engines/scumm/players/player_ad.cpp index 4d4be2c3c2..55bbeeef98 100644 --- a/engines/scumm/players/player_ad.cpp +++ b/engines/scumm/players/player_ad.cpp @@ -50,7 +50,7 @@ Player_AD::Player_AD(ScummEngine *scumm) writeReg(0x01, 0x20); _engineMusicTimer = 0; - _soundPlaying = -1; + _musicResource = -1; _curOffset = 0; @@ -104,8 +104,8 @@ void Player_AD::startSound(int sound) { stopMusic(); // Lock the new music resource - _soundPlaying = sound; - _vm->_res->lock(rtSound, _soundPlaying); + _musicResource = sound; + _vm->_res->lock(rtSound, _musicResource); // Start the new music resource _musicData = res; @@ -150,7 +150,7 @@ void Player_AD::startSound(int sound) { void Player_AD::stopSound(int sound) { Common::StackLock lock(_mutex); - if (sound == _soundPlaying) { + if (sound == _musicResource) { stopMusic(); } else { for (int i = 0; i < ARRAYSIZE(_sfx); ++i) { @@ -178,7 +178,17 @@ int Player_AD::getMusicTimer() { } int Player_AD::getSoundStatus(int sound) const { - return (sound == _soundPlaying); + if (sound == _musicResource) { + return true; + } + + for (int i = 0; i < ARRAYSIZE(_sfx); ++i) { + if (_sfx[i].resource == sound) { + return true; + } + } + + return false; } void Player_AD::saveLoadWithSerializer(Serializer *ser) { @@ -193,7 +203,7 @@ void Player_AD::saveLoadWithSerializer(Serializer *ser) { if (ser->getVersion() >= VER(96)) { int32 res[4] = { - _soundPlaying, _sfx[0].resource, _sfx[1].resource, _sfx[2].resource + _musicResource, _sfx[0].resource, _sfx[1].resource, _sfx[2].resource }; // The first thing we save is a list of sound resources being played @@ -461,13 +471,13 @@ void Player_AD::startMusic() { } void Player_AD::stopMusic() { - if (_soundPlaying == -1) { + if (_musicResource == -1) { return; } // Unlock the music resource if present - _vm->_res->unlock(rtSound, _soundPlaying); - _soundPlaying = -1; + _vm->_res->unlock(rtSound, _musicResource); + _musicResource = -1; // Stop the music playback _curOffset = 0; @@ -510,7 +520,7 @@ void Player_AD::updateMusic() { // important to note that we need to parse a command directly // at the new position, i.e. there is no time value we need to // parse. - if (_soundPlaying == -1) { + if (_musicResource == -1) { return; } else { continue; diff --git a/engines/scumm/players/player_ad.h b/engines/scumm/players/player_ad.h index 63fda3cc7c..9cd1a06261 100644 --- a/engines/scumm/players/player_ad.h +++ b/engines/scumm/players/player_ad.h @@ -68,7 +68,7 @@ private: OPL::OPL *_opl2; - int _soundPlaying; + int _musicResource; int32 _engineMusicTimer; struct SfxSlot; diff --git a/engines/scumm/players/player_v4a.cpp b/engines/scumm/players/player_v4a.cpp index bd8ce3f7ad..58f53a6d7f 100644 --- a/engines/scumm/players/player_v4a.cpp +++ b/engines/scumm/players/player_v4a.cpp @@ -174,7 +174,7 @@ int Player_V4A::getMusicTimer() { return 2000; if (_musicId) { // The titlesong (and a few others) is running with ~70 ticks per second and the scale seems to be based on that. - // The Game itself doesnt get the timing from the Tfmx Player however, so we just use the elapsed time + // The Game itself doesn't get the timing from the Tfmx Player however, so we just use the elapsed time // 357 ~ 1000 * 25 * (1 / 70) return _mixer->getSoundElapsedTime(_musicHandle) / 357; } @@ -183,7 +183,7 @@ int Player_V4A::getMusicTimer() { int Player_V4A::getSoundStatus(int nr) const { // For music the game queues a variable the Tfmx Player sets through a special command. - // For sfx there seems to be no way to queue them, and the game doesnt try to. + // For sfx there seems to be no way to queue them, and the game doesn't try to. return (nr == _musicId) ? _signal : 0; } diff --git a/engines/scumm/saveload.cpp b/engines/scumm/saveload.cpp index e5673c1803..f3df24ff51 100644 --- a/engines/scumm/saveload.cpp +++ b/engines/scumm/saveload.cpp @@ -1303,7 +1303,7 @@ void ScummEngine::saveOrLoad(Serializer *s) { if (hasTownsData) { // Skip FM-Towns specific data - for (int i = 69 * sizeof(uint8) + 44 * sizeof(int16); i; i--) + for (i = 69 * sizeof(uint8) + 44 * sizeof(int16); i; i--) s->loadByte(); } diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp index 24d676a1ff..89d2d3dc72 100644 --- a/engines/scumm/scumm.cpp +++ b/engines/scumm/scumm.cpp @@ -2611,8 +2611,12 @@ bool ScummEngine::startManiac() { Common::String path = dom.getVal("path"); if (path.hasPrefix(currentPath)) { - path.erase(0, currentPath.size() + 1); - if (path.equalsIgnoreCase("maniac")) { + path.erase(0, currentPath.size()); + // Do a case-insensitive non-path-mode match of the remainder. + // While strictly speaking it's too broad, this matchString + // ignores the presence or absence of trailing path separators + // in either currentPath or path. + if (path.matchString("*maniac*", true, false)) { maniacTarget = iter->_key; break; } diff --git a/engines/scumm/scumm_v4.h b/engines/scumm/scumm_v4.h index 28f619ceaa..a008023ff9 100644 --- a/engines/scumm/scumm_v4.h +++ b/engines/scumm/scumm_v4.h @@ -35,7 +35,7 @@ class ScummEngine_v4 : public ScummEngine_v5 { public: /** - * Prepared savegame used by the orginal save/load dialog. + * Prepared savegame used by the original save/load dialog. * Must be valid as long as the savescreen is active. As we are not * notified when the savescreen is closed, memory is only freed on a game * reset, at the destruction of the engine or when the original save/load diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp index 84d2b37f96..4d70ee8482 100644 --- a/engines/scumm/sound.cpp +++ b/engines/scumm/sound.cpp @@ -240,7 +240,7 @@ void Sound::playSound(int soundID) { // mentioned in the bug report above; in case it is, I put a check here. assert(soundID == 39); - // The samplerate is copied from the sound resouce 39 of the PC CD/VGA + // The samplerate is copied from the sound resource 39 of the PC CD/VGA // version of Monkey Island. // Read info from the header diff --git a/engines/sherlock/POTFILES b/engines/sherlock/POTFILES new file mode 100644 index 0000000000..b9f1c1249f --- /dev/null +++ b/engines/sherlock/POTFILES @@ -0,0 +1,3 @@ +engines/sherlock/detection.cpp +engines/sherlock/scalpel/scalpel.cpp +engines/sherlock/tattoo/widget_files.cpp diff --git a/engines/sherlock/animation.cpp b/engines/sherlock/animation.cpp index 681e71d0f6..4442c1da85 100644 --- a/engines/sherlock/animation.cpp +++ b/engines/sherlock/animation.cpp @@ -23,6 +23,8 @@ #include "sherlock/animation.h" #include "sherlock/sherlock.h" #include "sherlock/scalpel/scalpel_screen.h" +#include "sherlock/scalpel/3do/scalpel_3do_screen.h" + #include "common/algorithm.h" namespace Sherlock { @@ -89,7 +91,7 @@ bool Animation::play(const Common::String &filename, bool intro, int minDelay, i // Draw the sprite. Note that we explicitly use the raw frame below, rather than the ImageFrame, // since we don't want the offsets in the image file to be used, just the explicit position we specify - screen.transBlitFrom(images[imageFrame]._frame, pt); + screen.SHtransBlitFrom(images[imageFrame]._frame, pt); } else { // At this point, either the sprites for the frame has been complete, or there weren't any sprites // at all to draw for the frame @@ -201,7 +203,7 @@ bool Animation::play3DO(const Common::String &filename, bool intro, int minDelay // Draw the sprite. Note that we explicitly use the raw frame below, rather than the ImageFrame, // since we don't want the offsets in the image file to be used, just the explicit position we specify - screen._backBuffer1.transBlitFrom(images[imageFrame]._frame, pt); + screen._backBuffer1.SHtransBlitFrom(images[imageFrame]._frame, pt); if (!fadeActive) screen.slamArea(pt.x, pt.y, images[imageFrame]._frame.w, images[imageFrame]._frame.h); } else { diff --git a/engines/sherlock/events.cpp b/engines/sherlock/events.cpp index 4b0b7dfb3f..6cfee5d822 100644 --- a/engines/sherlock/events.cpp +++ b/engines/sherlock/events.cpp @@ -143,7 +143,7 @@ void Events::setCursor(CursorId cursorId, const Common::Point &cursorPos, const // Form a single surface containing both frames Surface s(r.width(), r.height()); - s.fill(TRANSPARENCY); + s.clear(TRANSPARENCY); // Draw the passed image Common::Point drawPos; @@ -151,11 +151,11 @@ void Events::setCursor(CursorId cursorId, const Common::Point &cursorPos, const drawPos.x = -cursorPt.x; if (cursorPt.y < 0) drawPos.y = -cursorPt.y; - s.blitFrom(surface, Common::Point(drawPos.x, drawPos.y)); + s.SHblitFrom(surface, Common::Point(drawPos.x, drawPos.y)); // Draw the cursor image drawPos = Common::Point(MAX(cursorPt.x, (int16)0), MAX(cursorPt.y, (int16)0)); - s.transBlitFrom(cursorImg, Common::Point(drawPos.x, drawPos.y)); + s.SHtransBlitFrom(cursorImg, Common::Point(drawPos.x, drawPos.y)); // Set up hotspot position for cursor, adjusting for cursor image's position within the surface Common::Point hotspot; @@ -163,7 +163,7 @@ void Events::setCursor(CursorId cursorId, const Common::Point &cursorPos, const hotspot = Common::Point(8, 8); hotspot += drawPos; // Set the cursor - setCursor(s.getRawSurface(), hotspot.x, hotspot.y); + setCursor(s, hotspot.x, hotspot.y); } void Events::animateCursorIfNeeded() { diff --git a/engines/sherlock/fixed_text.cpp b/engines/sherlock/fixed_text.cpp index cbee944120..4679fe58b8 100644 --- a/engines/sherlock/fixed_text.cpp +++ b/engines/sherlock/fixed_text.cpp @@ -27,6 +27,183 @@ namespace Sherlock { +static const char *const fixedJournalTextEN[] = { + // Holmes asked/said... + "Holmes asked me, ", + "Holmes asked the Inspector, ", + "Holmes asked %s, ", + "Holmes said to me, ", + "Holmes said to the Inspector, ", + "Holmes said to %s, ", + // I asked/said... + "I replied, ", + "The reply was, ", + // Holmes/I/The Inspector/Person asked/said (without "Then" prefix) + "Holmes asked, ", + "Holmes said, ", + "I asked, ", + "I said, ", + "The Inspector asked, ", + "The Inspector said, ", + "%s asked, ", + "%s said, ", + // Then Holmes/I/The Inspector/Person asked/said + "Then Holmes asked, ", + "Then Holmes said, ", + "Then I asked, ", + "Then I said, ", + "Then the Inspector asked, ", + "Then the Inspector said, ", + "Then %s asked, ", + "Then %s said, " +}; + +static const char *const fixedJournalTextDE[] = { + // Holmes asked/said... + "Holmes fragte mich, ", + "Holmes fragte Inspektor Lestrade, ", + "Holmes fragte %s, ", + "Holmes sagte mir, ", + "Holmes sagte Inspektor Lestrade, ", + "Holmes sagte %s, ", + // I asked/said... + "Ich antwortete, ", + "Die Antwort lautete, ", + // Holmes/I/The Inspector/Person asked/said (without "Then" prefix) + "Holmes fragte, ", // original: "fragte Holmes" + "Holmes sagte, ", // original: "sagte Holmes" + "Ich fragte, ", // original: "fragte Ich" + "Ich sagte, ", // original: "sagte Ich" + "Der Inspektor fragte, ", + "Der Inspektor sagte, ", + "%s fragte, ", + "%s sagte, ", + // Then Holmes/I/The Inspector/Person asked/said + "Dann fragte Holmes, ", + "Dann sagte Holmes, ", + "Dann fragte ich, ", // original: "Dann sagte Ich" + "Dann sagte ich, ", // original: "Dann sagte Ich" + "Dann fragte der Inspektor, ", + "Dann sagte der Inspektor, ", + "Dann fragte %s, ", + "Dann sagte %s, " +}; + +// Only used for Sherlock Holmes 2, so special characters should use the SH2 charset +// small a w/ accent grave: 0x85 / octal 205 +// small e w/ accent acute: 0x82 / octal 202 +// small e w/ accent grave: 0x8A / octal 212 +// small e w/ circonflexe: 0x88 / octal 210 +// small cedilla: 0x87 / octal 207 +static const char *const fixedJournalTextFR[] = { + // Holmes asked/said... + "Holmes me demanda, ", // original: "Holmes m'a demand\202, " + "Holmes demanda \205 l'inspecteur, ", // original: "Holmes a demand\202 \205 l'inspecteur, " + "Holmes demanda \205 %s, ", // original: "Holmes a demand\202 \205 %s, " + "Holmes me dit, ", // original: "Holmes m'a dit, " + "Holmes dit \205 l'inspecteur, ", // original: "Holmes a dit \205 l'inspecteur, " + "Holmes dit \205 %s, ", // original: "Holmes a dit \205 %s, " + // I asked/said... + "Je r\202pondis, ", // original: "J'ai r\202pondu, ", + "La r\202ponse fut, ", + // Holmes/I/The Inspector/Person asked/said (without "Then" prefix) + "Holmes demanda, ", // original: "Holmes a demand\202, " + "Holmes dit, ", + "Je demandai, ", // original: "J'ai demand\202, " + "Je dis, ", // original: "J'ai dit, " + "L'inspecteur demanda, ", // original: ""L'inspecteur a demand\202, " + "L'inspecteur dit, ", + "%s demanda, ", // original: "%s a demand\202, " + "%s dit, ", + // Then Holmes/I/The Inspector/Person asked/said + "Alors Holmes demanda, ", // original: it seems "puis"/"then" was not used/removed. They instead added a space character, so sentences looked weird + "Alors Holmes dit, ", + "Alors je demandai, ", + "Alors je dis, ", + "Alors l'inspecteur demanda, ", + "Alors l'inspecteur dit, ", + "Alors %s demanda, ", + "Alors %s dit, " +}; + +// Sherlock Holmes 1+2: +// small e w/ accent bottom to top : 0x82 / octal 202 +// big E w/ accent bottom to top : 0x90 / octal 220 +// small a w/ accent bottom to top : 0xA0 / octal 240 +// small i w/ accent bottom to top : 0xA1 / octal 241 +// small o w/ accent bottom to top : 0xA2 / octal 242 +// small u w/ accent bottom to top : 0xA3 / octal 243 +// small n w/ wavy line : 0xA4 / octal 244 +// big N w/ wavy line : 0xA5 / octal 245 +// small a w/ under line : 0xA6 / octal 246 +// small o w/ under line : 0xA7 / octal 247 +// inverted question mark : 0xA8 / octal 250 +static const char *const fixedJournalTextES[] = { + // Holmes asked/said... + "Holmes me pregunt\242, ", // original: "Holmes me pidi\242, ", + "Holmes pregunt\242 al Inspector, ", // original: "el inspector"? + "Holmes pregunt\242 %s, ", + "Holmes me dijo, ", + "Holmes dijo al Inspector, ", // original: "el inspector"? + "Holmes dijo a %s, ", // original: "Holmes dijo a %s, " + // I asked/said... + "Yo content\202, ", // original: "Yo respond\241, ", + "La respuesta fue, ", + // Holmes/I/The Inspector/Person asked/said (without "Then" prefix) + "Holmes pregunt\242, ", + "Holmes dijo, ", + "Yo pregunt\202, ", // original: "Yo pregunt\242, ", + "Yo dije, ", + "El Inspector pregunt\242, ", + "El Inspector dijo, ", + "%s pregunt\242, ", + "%s dijo, ", + // Then Holmes/I/The Inspector/Person asked/said + "Despu\202s Holmes pregunt\242, ", // original: added "Entonces" instead of "Despues" + "Despu\202s Holmes dijo, ", + "Despu\202s yo pregunt\202, ", // "pregunt\242, " + "Despu\202s yo dije, ", + "Despu\202s el Inspector pregunt\242, ", + "Despu\202s el Inspector dijo, ", + "Despu\202s %s pregunt\242, ", + "Despu\202s %s dijo, " +}; + +FixedText::FixedText(SherlockEngine *vm) { + _vm = vm; + + // Figure out which fixed texts to use + Common::Language curLanguage = _vm->getLanguage(); + + switch (curLanguage) { + case Common::EN_ANY: + // Used by Sherlock Holmes 1+2 + _fixedJournalTextArray = fixedJournalTextEN; + _fixedObjectPickedUpText = "Picked up %s"; + break; + case Common::DE_DEU: + // Used by Sherlock Holmes 1+2 + _fixedJournalTextArray = fixedJournalTextDE; + _fixedObjectPickedUpText = "%s eingesteckt"; + break; + case Common::FR_FRA: + // Used by Sherlock Holmes 2 + _fixedJournalTextArray = fixedJournalTextFR; + _fixedObjectPickedUpText = ""; // Not used, because there is no French Sherlock Holmes 1 + break; + case Common::ES_ESP: + // Used by Sherlock Holmes 1+2 + _fixedJournalTextArray = fixedJournalTextES; + _fixedObjectPickedUpText = "Cogido/a %s"; + break; + default: + // Default to English + _fixedJournalTextArray = fixedJournalTextEN; + _fixedObjectPickedUpText = "Picked up %s"; + break; + } +} + FixedText *FixedText::init(SherlockEngine *vm) { if (vm->getGameID() == GType_SerratedScalpel) return new Scalpel::ScalpelFixedText(vm); @@ -34,5 +211,12 @@ FixedText *FixedText::init(SherlockEngine *vm) { return new Tattoo::TattooFixedText(vm); } +const char *FixedText::getJournalText(int fixedJournalTextId) { + return _fixedJournalTextArray[fixedJournalTextId]; +} + +const char *FixedText::getObjectPickedUpText() { + return _fixedObjectPickedUpText; +} } // End of namespace Sherlock diff --git a/engines/sherlock/fixed_text.h b/engines/sherlock/fixed_text.h index 40444f4052..3eae60e1c3 100644 --- a/engines/sherlock/fixed_text.h +++ b/engines/sherlock/fixed_text.h @@ -39,13 +39,44 @@ enum FixedTextActionId { kFixedTextAction_Use }; +enum FixedJournalTextId { + // Holmes asked/said... + kFixedJournalText_HolmesAskedMe = 0, + kFixedJournalText_HolmesAskedTheInspector, + kFixedJournalText_HolmesAskedPerson, + kFixedJournalText_HolmesSaidToMe, + kFixedJournalText_HolmesSaidToTheInspector, + kFixedJournalText_HolmesSaidToPerson, + // I asked/said + kFixedJournalText_IReplied, + kFixedJournalText_TheReplyWas, + // Holmes/I/The Inspector/Person asked/said (without "Then" prefix) + kFixedJournalText_HolmesAsked, + kFixedJournalText_HolmesSaid, + kFixedJournalText_IAsked, + kFixedJournalText_ISaid, + kFixedJournalText_TheInspectorAsked, + kFixedJournalText_TheInspectorSaid, + kFixedJournalText_PersonAsked, + kFixedJournalText_PersonSaid, + // Then Holmes/I/The Inspector/Person asked/said + kFixedJournalText_ThenHolmesAsked, + kFixedJournalText_ThenHolmesSaid, + kFixedJournalText_ThenIAsked, + kFixedJournalText_ThenISaid, + kFixedJournalText_ThenTheInspectorAsked, + kFixedJournalText_ThenTheInspectorSaid, + kFixedJournalText_ThenPersonAsked, + kFixedJournalText_ThenPersonSaid +}; + class SherlockEngine; class FixedText { protected: SherlockEngine *_vm; - FixedText(SherlockEngine *vm) : _vm(vm) {} + FixedText(SherlockEngine *vm); public: static FixedText *init(SherlockEngine *vm); virtual ~FixedText() {} @@ -59,6 +90,20 @@ public: * Get action message */ virtual const Common::String getActionMessage(FixedTextActionId actionId, int messageIndex) = 0; + + /** + * Gets journal text + */ + const char *getJournalText(int fixedJournalTextId); + + /** + * Gets object "Picked Up" text + */ + const char *getObjectPickedUpText(); + +private: + const char *const *_fixedJournalTextArray; + const char *_fixedObjectPickedUpText; }; } // End of namespace Sherlock diff --git a/engines/sherlock/fonts.cpp b/engines/sherlock/fonts.cpp index 8e36c3908a..5a14881f1c 100644 --- a/engines/sherlock/fonts.cpp +++ b/engines/sherlock/fonts.cpp @@ -43,7 +43,7 @@ void Fonts::setVm(SherlockEngine *vm) { _charCount = 0; } -void Fonts::free() { +void Fonts::freeFont() { delete _font; } @@ -213,7 +213,7 @@ void Fonts::writeString(Surface *surface, const Common::String &str, if (curChar < _charCount) { ImageFrame &frame = (*_font)[curChar]; - surface->transBlitFrom(frame, Common::Point(charPos.x, charPos.y + _yOffsets[curChar]), false, overrideColor); + surface->SHtransBlitFrom(frame, Common::Point(charPos.x, charPos.y + _yOffsets[curChar]), false, overrideColor); charPos.x += frame._frame.w + 1; } else { warning("Invalid character encountered - %d", (int)curChar); diff --git a/engines/sherlock/fonts.h b/engines/sherlock/fonts.h index a527cc73c0..3594d466c2 100644 --- a/engines/sherlock/fonts.h +++ b/engines/sherlock/fonts.h @@ -57,7 +57,7 @@ public: /** * Frees the font manager */ - static void free(); + static void freeFont(); /** * Set the font to use for writing text on the screen diff --git a/engines/sherlock/image_file.h b/engines/sherlock/image_file.h index da260ab30b..778332b726 100644 --- a/engines/sherlock/image_file.h +++ b/engines/sherlock/image_file.h @@ -46,6 +46,11 @@ struct ImageFrame { Graphics::Surface _frame; /** + * Converts an ImageFrame record to a surface for convenience in passing to drawing methods + */ + operator const Graphics::Surface &() { return _frame; } + + /** * Decompress a single frame for the sprite */ void decompressFrame(const byte *src, bool isRoseTattoo); diff --git a/engines/sherlock/journal.cpp b/engines/sherlock/journal.cpp index a30734c4f5..8763af31dd 100644 --- a/engines/sherlock/journal.cpp +++ b/engines/sherlock/journal.cpp @@ -299,6 +299,7 @@ bool Journal::drawJournal(int direction, int howFar) { } void Journal::loadJournalFile(bool alreadyLoaded) { + FixedText &fixedText = *_vm->_fixedText; People &people = *_vm->_people; Screen &screen = *_vm->_screen; Talk &talk = *_vm->_talk; @@ -381,20 +382,34 @@ void Journal::loadJournalFile(bool alreadyLoaded) { // If Holmes has something to say first, then take care of it if (!replyOnly) { // Handle the grammar - journalString += "Holmes "; + bool asked = false; + if (talk[journalEntry._statementNum]._statement.hasSuffix("?")) - journalString += "asked "; - else - journalString += "said to "; + asked = true; if (talk._talkTo == 1) { - journalString += "me"; + if (asked) + journalString += fixedText.getJournalText(kFixedJournalText_HolmesAskedMe); + else + journalString += fixedText.getJournalText(kFixedJournalText_HolmesSaidToTheInspector); + } else if ((talk._talkTo == 2 && IS_SERRATED_SCALPEL) || (talk._talkTo == 18 && IS_ROSE_TATTOO)) { - journalString += "the Inspector"; + if (asked) + journalString += fixedText.getJournalText(kFixedJournalText_HolmesAskedTheInspector); + else + journalString += fixedText.getJournalText(kFixedJournalText_HolmesSaidToMe); + } else { - journalString += people._characters[talk._talkTo]._name; + const char *text = nullptr; + if (asked) + text = fixedText.getJournalText(kFixedJournalText_HolmesAskedPerson); + else + text = fixedText.getJournalText(kFixedJournalText_HolmesSaidToPerson); + + journalString += Common::String::format(text, people._characters[talk._talkTo]._name); } - journalString += ", \""; + + journalString += "\""; // Add the statement journalString += statement._statement; @@ -457,29 +472,46 @@ void Journal::loadJournalFile(bool alreadyLoaded) { journalString += "\"\n"; if (talk._talkTo == 1) - journalString += "I replied, \""; + journalString += fixedText.getJournalText(kFixedJournalText_IReplied); else - journalString += "The reply was, \""; + journalString += fixedText.getJournalText(kFixedJournalText_TheReplyWas); } else { - if (talk._talkTo == 1) - journalString += "I"; - else if (talk._talkTo == inspectorId) - journalString += "The Inspector"; - else - journalString += people._characters[talk._talkTo]._name; - const byte *strP = replyP + 1; byte v; do { v = *strP++; } while (v && (v < opcodes[0]) && (v != '.') && (v != '!') && (v != '?')); + bool asked = false; + if (v == '?') - journalString += " asked, \""; - else - journalString += " said, \""; + asked = true; + + if (talk._talkTo == 1) { + if (asked) + journalString += fixedText.getJournalText(kFixedJournalText_IAsked); + else + journalString += fixedText.getJournalText(kFixedJournalText_ISaid); + + } else if (talk._talkTo == inspectorId) { + if (asked) + journalString += fixedText.getJournalText(kFixedJournalText_TheInspectorAsked); + else + journalString += fixedText.getJournalText(kFixedJournalText_TheInspectorSaid); + + } else { + const char *text = nullptr; + if (asked) + text = fixedText.getJournalText(kFixedJournalText_PersonAsked); + else + text = fixedText.getJournalText(kFixedJournalText_PersonSaid); + + journalString += Common::String::format(text, people._characters[talk._talkTo]._name); + } } + journalString += "\""; + startOfReply = false; } @@ -502,11 +534,13 @@ void Journal::loadJournalFile(bool alreadyLoaded) { justChangedSpeaker = true; } + bool addPrefixThen = false; + if (!startOfReply) { if (!commentFlag && !commentJustPrinted) journalString += "\"\n"; - journalString += "Then "; + addPrefixThen = true; // "Then" should get added to the sentence commentFlag = false; } else if (!replyOnly) { journalString += "\"\n"; @@ -517,15 +551,6 @@ void Journal::loadJournalFile(bool alreadyLoaded) { if (IS_ROSE_TATTOO) replyP++; - if (c == 0) - journalString += "Holmes"; - else if (c == 1) - journalString += "I"; - else if (c == inspectorId) - journalString += "the Inspector"; - else - journalString += people._characters[c]._name; - if (IS_SERRATED_SCALPEL && _vm->getLanguage() == Common::DE_DEU) Scalpel::ScalpelTalk::skipBadText(replyP); @@ -535,10 +560,70 @@ void Journal::loadJournalFile(bool alreadyLoaded) { v = *strP++; } while (v && v < opcodes[0] && v != '.' && v != '!' && v != '?'); + bool asked = false; + if (v == '?') - journalString += " asked, \""; - else - journalString += " said, \""; + asked = true; + + if (c == 0) { + // Holmes + if (addPrefixThen) { + if (asked) + journalString += fixedText.getJournalText(kFixedJournalText_ThenHolmesAsked); + else + journalString += fixedText.getJournalText(kFixedJournalText_ThenHolmesSaid); + } else { + if (asked) + journalString += fixedText.getJournalText(kFixedJournalText_HolmesAsked); + else + journalString += fixedText.getJournalText(kFixedJournalText_HolmesSaid); + } + } else if (c == 1) { + // I (Watson) + if (addPrefixThen) { + if (asked) + journalString += fixedText.getJournalText(kFixedJournalText_ThenIAsked); + else + journalString += fixedText.getJournalText(kFixedJournalText_ThenISaid); + } else { + if (asked) + journalString += fixedText.getJournalText(kFixedJournalText_IAsked); + else + journalString += fixedText.getJournalText(kFixedJournalText_ISaid); + } + } else if (c == inspectorId) { + // The Inspector + if (addPrefixThen) { + if (asked) + journalString += fixedText.getJournalText(kFixedJournalText_ThenTheInspectorAsked); + else + journalString += fixedText.getJournalText(kFixedJournalText_ThenTheInspectorSaid); + } else { + if (asked) + journalString += fixedText.getJournalText(kFixedJournalText_TheInspectorAsked); + else + journalString += fixedText.getJournalText(kFixedJournalText_TheInspectorSaid); + } + } else { + // Person + const char *text = nullptr; + if (addPrefixThen) { + if (asked) + text = fixedText.getJournalText(kFixedJournalText_ThenPersonAsked); + else + text = fixedText.getJournalText(kFixedJournalText_ThenPersonSaid); + } else { + if (asked) + text = fixedText.getJournalText(kFixedJournalText_PersonAsked); + else + text = fixedText.getJournalText(kFixedJournalText_PersonSaid); + } + + journalString += Common::String::format(text, people._characters[c]._name); + } + + journalString += "\""; + } else { if (IS_SERRATED_SCALPEL) { // Control code, so move past it and any parameters @@ -718,8 +803,7 @@ bool Journal::isPrintable(byte ch) const { if (ch < opcodes[0]) return true; - if (_vm->getGameID() == GType_SerratedScalpel && _vm->getLanguage() == Common::DE_DEU - && ch >= 0xe0) + if (_vm->getLanguage() == Common::DE_DEU && ch == 0xe1) // accept German Sharp-S character return true; return false; diff --git a/engines/sherlock/module.mk b/engines/sherlock/module.mk index 7fa7896691..0d17d0b249 100644 --- a/engines/sherlock/module.mk +++ b/engines/sherlock/module.mk @@ -3,6 +3,7 @@ MODULE := engines/sherlock MODULE_OBJS = \ scalpel/scalpel.o \ scalpel/3do/movie_decoder.o \ + scalpel/3do/scalpel_3do_screen.o \ scalpel/drivers/adlib.o \ scalpel/drivers/mt32.o \ scalpel/tsage/logo.o \ @@ -30,6 +31,7 @@ MODULE_OBJS = \ tattoo/tattoo_people.o \ tattoo/tattoo_resources.o \ tattoo/tattoo_scene.o \ + tattoo/tattoo_screen.o \ tattoo/tattoo_talk.o \ tattoo/tattoo_user_interface.o \ tattoo/widget_base.o \ diff --git a/engines/sherlock/objects.cpp b/engines/sherlock/objects.cpp index e70b707404..644c0c74c9 100644 --- a/engines/sherlock/objects.cpp +++ b/engines/sherlock/objects.cpp @@ -365,8 +365,8 @@ bool BaseObject::checkEndOfSequence() { if (seq == 99) { --_frameNumber; - screen._backBuffer1.transBlitFrom(*_imageFrame, _position); - screen._backBuffer2.transBlitFrom(*_imageFrame, _position); + screen._backBuffer1.SHtransBlitFrom(*_imageFrame, _position); + screen._backBuffer2.SHtransBlitFrom(*_imageFrame, _position); _type = INVALID; } else if (IS_ROSE_TATTOO && _talkSeq && seq == 0) { setObjTalkSequence(_talkSeq); @@ -1061,6 +1061,11 @@ void Object::load(Common::SeekableReadStream &s, bool isRoseTattoo) { for (int idx = 0; idx < 6; ++idx) _use[idx].load(s, true); + // WORKAROUND: Fix German version using hatpin/pin in pillow in Pratt's loft + if (_use[1]._target == "Nadel" && _use[1]._verb == "Untersuche" + && _use[2]._target == "Nadel" && _use[2]._verb == "Untersuche") + _use[1]._target = "Alte Nadel"; + _quickDraw = s.readByte(); _scaleVal = s.readUint16LE(); _requiredFlag[1] = s.readSint16LE(); @@ -1338,7 +1343,7 @@ void Object::adjustObject() { frame = 0; int imgNum = _sequences[frame]; - if (imgNum > _maxFrames) + if (imgNum > _maxFrames || imgNum == 0) imgNum = 1; _imageFrame = &(*_images)[imgNum - 1]; @@ -1424,8 +1429,19 @@ int Object::pickUpObject(FixedTextActionId fixedTextActionId) { ui.clearInfo(); Common::String itemName = _description; - itemName.setChar(tolower(itemName[0]), 0); - screen.print(Common::Point(0, INFO_LINE + 1), COL_INFO_FOREGROUND, "Picked up %s", itemName.c_str()); + + // It's an item, make it lowercase + switch (_vm->getLanguage()) { + case Common::DE_DEU: + // don't do this for German version + break; + default: + // do it for English + Spanish version + itemName.setChar(tolower(itemName[0]), 0); + break; + } + + screen.print(Common::Point(0, INFO_LINE + 1), COL_INFO_FOREGROUND, fixedText.getObjectPickedUpText(), itemName.c_str()); ui._menuCounter = 25; } } diff --git a/engines/sherlock/scalpel/3do/scalpel_3do_screen.cpp b/engines/sherlock/scalpel/3do/scalpel_3do_screen.cpp new file mode 100644 index 0000000000..f848394864 --- /dev/null +++ b/engines/sherlock/scalpel/3do/scalpel_3do_screen.cpp @@ -0,0 +1,286 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "sherlock/scalpel/scalpel_screen.h" +#include "sherlock/scalpel/scalpel.h" +#include "sherlock/scalpel/3do/scalpel_3do_screen.h" + +namespace Sherlock { + +namespace Scalpel { + +Scalpel3DOScreen::Scalpel3DOScreen(SherlockEngine *vm): ScalpelScreen(vm) { +} + +void Scalpel3DOScreen::SHblitFrom(const Graphics::Surface &src, const Common::Point &pt, const Common::Rect &srcBounds) { + if (!_vm->_isScreenDoubled) { + ScalpelScreen::SHblitFrom(src, pt, srcBounds); + return; + } + + Common::Rect srcRect = srcBounds; + Common::Rect destRect(pt.x, pt.y, pt.x + srcRect.width(), pt.y + srcRect.height()); + + if (!srcRect.isValidRect() || !clip(srcRect, destRect)) + return; + + // Add dirty area remapped to the 640x200 surface + addDirtyRect(Common::Rect(destRect.left * 2, destRect.top * 2, destRect.right * 2, destRect.bottom * 2)); + + // Transfer the area, doubling each pixel + for (int yp = 0; yp < srcRect.height(); ++yp) { + const uint16 *srcP = (const uint16 *)src.getBasePtr(srcRect.left, srcRect.top + yp); + uint16 *destP = (uint16 *)getBasePtr(destRect.left * 2, (destRect.top + yp) * 2); + + for (int xp = srcRect.left; xp < srcRect.right; ++xp, ++srcP, destP += 2) { + *destP = *srcP; + *(destP + 1) = *srcP; + *(destP + 640) = *srcP; + *(destP + 640 + 1) = *srcP; + } + } +} + +void Scalpel3DOScreen::transBlitFromUnscaled(const Graphics::Surface &src, const Common::Point &pt, + bool flipped, int overrideColor) { + error("TODO: Refactor"); +#if 0 + if (!_vm->_isScreenDoubled) { + ScalpelScreen::transBlitFromUnscaled(src, pt, flipped, overrideColor); + return; + } + + Common::Rect drawRect(0, 0, src.w, src.h); + Common::Rect destRect(pt.x, pt.y, pt.x + src.w, pt.y + src.h); + + // Clip the display area to on-screen + if (!clip(drawRect, destRect)) + // It's completely off-screen + return; + + if (flipped) + drawRect = Common::Rect(src.w - drawRect.right, src.h - drawRect.bottom, + src.w - drawRect.left, src.h - drawRect.top); + + Common::Point destPt(destRect.left, destRect.top); + addDirtyRect(Common::Rect(destPt.x * 2, destPt.y * 2, (destPt.x + drawRect.width()) * 2, + (destPt.y + drawRect.height()) * 2)); + + assert(src.format.bytesPerPixel == 2 && _surface.format.bytesPerPixel == 2); + + for (int yp = 0; yp < drawRect.height(); ++yp) { + const uint16 *srcP = (const uint16 *)src.getBasePtr( + flipped ? drawRect.right - 1 : drawRect.left, drawRect.top + yp); + uint16 *destP = (uint16 *)getBasePtr(destPt.x * 2, (destPt.y + yp) * 2); + + for (int xp = 0; xp < drawRect.width(); ++xp, destP += 2) { + // RGB 0, 0, 0 -> transparent on 3DO + if (*srcP) { + *destP = *srcP; + *(destP + 1) = *srcP; + *(destP + 640) = *srcP; + *(destP + 640 + 1) = *srcP; + } + + srcP = flipped ? srcP - 1 : srcP + 1; + } + } +#endif +} + +void Scalpel3DOScreen::fillRect(const Common::Rect &r, uint color) { + if (_vm->_isScreenDoubled) + ScalpelScreen::fillRect(Common::Rect(r.left * 2, r.top * 2, r.right * 2, r.bottom * 2), color); + else + ScalpelScreen::fillRect(r, color); +} + +void Scalpel3DOScreen::fadeIntoScreen3DO(int speed) { + Events &events = *_vm->_events; + uint16 *currentScreenBasePtr = (uint16 *)getPixels(); + uint16 *targetScreenBasePtr = (uint16 *)_backBuffer->getPixels(); + uint16 currentScreenPixel = 0; + uint16 targetScreenPixel = 0; + + uint16 currentScreenPixelRed = 0; + uint16 currentScreenPixelGreen = 0; + uint16 currentScreenPixelBlue = 0; + + uint16 targetScreenPixelRed = 0; + uint16 targetScreenPixelGreen = 0; + uint16 targetScreenPixelBlue = 0; + + uint16 screenWidth = SHERLOCK_SCREEN_WIDTH; + uint16 screenHeight = SHERLOCK_SCREEN_HEIGHT; + uint16 screenX = 0; + uint16 screenY = 0; + uint16 pixelsChanged = 0; + + clearDirtyRects(); + + do { + pixelsChanged = 0; + uint16 *currentScreenPtr = currentScreenBasePtr; + uint16 *targetScreenPtr = targetScreenBasePtr; + + for (screenY = 0; screenY < screenHeight; screenY++) { + for (screenX = 0; screenX < screenWidth; screenX++) { + currentScreenPixel = *currentScreenPtr; + targetScreenPixel = *targetScreenPtr; + + if (currentScreenPixel != targetScreenPixel) { + // pixel doesn't match, adjust accordingly + currentScreenPixelRed = currentScreenPixel & 0xF800; + currentScreenPixelGreen = currentScreenPixel & 0x07E0; + currentScreenPixelBlue = currentScreenPixel & 0x001F; + targetScreenPixelRed = targetScreenPixel & 0xF800; + targetScreenPixelGreen = targetScreenPixel & 0x07E0; + targetScreenPixelBlue = targetScreenPixel & 0x001F; + + if (currentScreenPixelRed != targetScreenPixelRed) { + if (currentScreenPixelRed < targetScreenPixelRed) { + currentScreenPixelRed += 0x0800; + } else { + currentScreenPixelRed -= 0x0800; + } + } + if (currentScreenPixelGreen != targetScreenPixelGreen) { + // Adjust +2/-2 because we are running RGB555 at RGB565 + if (currentScreenPixelGreen < targetScreenPixelGreen) { + currentScreenPixelGreen += 0x0040; + } else { + currentScreenPixelGreen -= 0x0040; + } + } + if (currentScreenPixelBlue != targetScreenPixelBlue) { + if (currentScreenPixelBlue < targetScreenPixelBlue) { + currentScreenPixelBlue += 0x0001; + } else { + currentScreenPixelBlue -= 0x0001; + } + } + + uint16 v = currentScreenPixelRed | currentScreenPixelGreen | currentScreenPixelBlue; + *currentScreenPtr = v; + if (_vm->_isScreenDoubled) { + *(currentScreenPtr + 1) = v; + *(currentScreenPtr + 640) = v; + *(currentScreenPtr + 640 + 1) = v; + } + + pixelsChanged++; + } + + currentScreenPtr += _vm->_isScreenDoubled ? 2 : 1; + targetScreenPtr++; + } + + if (_vm->_isScreenDoubled) + currentScreenPtr += 640; + } + + // Too much considered dirty at the moment + if (_vm->_isScreenDoubled) + addDirtyRect(Common::Rect(0, 0, screenWidth * 2, screenHeight * 2)); + else + addDirtyRect(Common::Rect(0, 0, screenWidth, screenHeight)); + + events.pollEvents(); + events.delay(10 * speed); + } while ((pixelsChanged) && (!_vm->shouldQuit())); +} + +void Scalpel3DOScreen::blitFrom3DOcolorLimit(uint16 limitColor) { + uint16 *currentScreenPtr = (uint16 *)getPixels(); + uint16 *targetScreenPtr = (uint16 *)_backBuffer->getPixels(); + uint16 currentScreenPixel = 0; + + uint16 screenWidth = SHERLOCK_SCREEN_WIDTH; + uint16 screenHeight = SHERLOCK_SCREEN_HEIGHT; + uint16 screenX = 0; + uint16 screenY = 0; + + uint16 currentScreenPixelRed = 0; + uint16 currentScreenPixelGreen = 0; + uint16 currentScreenPixelBlue = 0; + + uint16 limitPixelRed = limitColor & 0xF800; + uint16 limitPixelGreen = limitColor & 0x07E0; + uint16 limitPixelBlue = limitColor & 0x001F; + + for (screenY = 0; screenY < screenHeight; screenY++) { + for (screenX = 0; screenX < screenWidth; screenX++) { + currentScreenPixel = *targetScreenPtr; + + currentScreenPixelRed = currentScreenPixel & 0xF800; + currentScreenPixelGreen = currentScreenPixel & 0x07E0; + currentScreenPixelBlue = currentScreenPixel & 0x001F; + + if (currentScreenPixelRed < limitPixelRed) + currentScreenPixelRed = limitPixelRed; + if (currentScreenPixelGreen < limitPixelGreen) + currentScreenPixelGreen = limitPixelGreen; + if (currentScreenPixelBlue < limitPixelBlue) + currentScreenPixelBlue = limitPixelBlue; + + uint16 v = currentScreenPixelRed | currentScreenPixelGreen | currentScreenPixelBlue; + *currentScreenPtr = v; + if (_vm->_isScreenDoubled) { + *(currentScreenPtr + 1) = v; + *(currentScreenPtr + 640) = v; + *(currentScreenPtr + 640 + 1) = v; + } + + currentScreenPtr += _vm->_isScreenDoubled ? 2 : 1; + targetScreenPtr++; + } + + if (_vm->_isScreenDoubled) + currentScreenPtr += 640; + } + + // Too much considered dirty at the moment + if (_vm->_isScreenDoubled) + addDirtyRect(Common::Rect(0, 0, screenWidth * 2, screenHeight * 2)); + else + addDirtyRect(Common::Rect(0, 0, screenWidth, screenHeight)); +} + +uint16 Scalpel3DOScreen::width() const { + return _vm->_isScreenDoubled ? this->w / 2 : this->w; +} + +uint16 Scalpel3DOScreen::height() const { + return _vm->_isScreenDoubled ? this->h / 2 : this->h; +} + +void Scalpel3DOScreen::rawBlitFrom(const Graphics::Surface &src, const Common::Point &pt) { + Common::Rect srcRect(0, 0, src.w, src.h); + Common::Rect destRect(pt.x, pt.y, pt.x + src.w, pt.y + src.h); + + addDirtyRect(destRect); + copyRectToSurface(src, destRect.left, destRect.top, srcRect); +} + +} // End of namespace Scalpel + +} // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/3do/scalpel_3do_screen.h b/engines/sherlock/scalpel/3do/scalpel_3do_screen.h new file mode 100644 index 0000000000..422f588b17 --- /dev/null +++ b/engines/sherlock/scalpel/3do/scalpel_3do_screen.h @@ -0,0 +1,76 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_SCALPEL_3DO_SCREEN_H +#define SHERLOCK_SCALPEL_3DO_SCREEN_H + +#include "sherlock/scalpel/scalpel_screen.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Scalpel { + +class Scalpel3DOScreen : public ScalpelScreen { +protected: + /** + * Draws a sub-section of a surface at a given position within this surface + * Overriden for the 3DO to automatically double the size of everything to the underlying 640x400 surface + */ + virtual void SHblitFrom(const Graphics::Surface &src, const Common::Point &pt, const Common::Rect &srcBounds); + + /** + * Draws a surface at a given position within this surface with transparency + */ + virtual void transBlitFromUnscaled(const Graphics::Surface &src, const Common::Point &pt, bool flipped, + int overrideColor); +public: + Scalpel3DOScreen(SherlockEngine *vm); + virtual ~Scalpel3DOScreen() {} + + /** + * Draws a sub-section of a surface at a given position within this surface + */ + void rawBlitFrom(const Graphics::Surface &src, const Common::Point &pt); + + /** + * Fade backbuffer 1 into screen (3DO RGB!) + */ + void fadeIntoScreen3DO(int speed); + + void blitFrom3DOcolorLimit(uint16 color); + + /** + * Fill a given area of the surface with a given color + */ + virtual void fillRect(const Common::Rect &r, uint color); + + virtual uint16 width() const; + virtual uint16 height() const; +}; + +} // End of namespace Scalpel + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/scalpel/scalpel.cpp b/engines/sherlock/scalpel/scalpel.cpp index b17f38b218..cbb202095f 100644 --- a/engines/sherlock/scalpel/scalpel.cpp +++ b/engines/sherlock/scalpel/scalpel.cpp @@ -29,6 +29,7 @@ #include "sherlock/scalpel/scalpel_people.h" #include "sherlock/scalpel/scalpel_scene.h" #include "sherlock/scalpel/scalpel_screen.h" +#include "sherlock/scalpel/3do/scalpel_3do_screen.h" #include "sherlock/scalpel/tsage/logo.h" #include "sherlock/sherlock.h" #include "sherlock/music.h" @@ -371,8 +372,8 @@ bool ScalpelEngine::showCityCutscene() { if (finished) { ImageFile titleImages_LondonNovember("title2.vgs", true); - _screen->_backBuffer1.blitFrom(*_screen); - _screen->_backBuffer2.blitFrom(*_screen); + _screen->_backBuffer1.SHblitFrom(*_screen); + _screen->_backBuffer2.SHblitFrom(*_screen); Common::Point londonPosition; @@ -386,19 +387,19 @@ bool ScalpelEngine::showCityCutscene() { } // London, England - _screen->_backBuffer1.transBlitFrom(titleImages_LondonNovember[0], londonPosition); + _screen->_backBuffer1.SHtransBlitFrom(titleImages_LondonNovember[0], londonPosition); _screen->randomTransition(); finished = _events->delay(1000, true); // November, 1888 if (finished) { - _screen->_backBuffer1.transBlitFrom(titleImages_LondonNovember[1], Common::Point(100, 100)); + _screen->_backBuffer1.SHtransBlitFrom(titleImages_LondonNovember[1], Common::Point(100, 100)); _screen->randomTransition(); finished = _events->delay(5000, true); } // Transition out the title - _screen->_backBuffer1.blitFrom(_screen->_backBuffer2); + _screen->_backBuffer1.SHblitFrom(_screen->_backBuffer2); _screen->randomTransition(); } @@ -407,8 +408,8 @@ bool ScalpelEngine::showCityCutscene() { if (finished) { ImageFile titleImages_SherlockHolmesTitle("title.vgs", true); - _screen->_backBuffer1.blitFrom(*_screen); - _screen->_backBuffer2.blitFrom(*_screen); + _screen->_backBuffer1.SHblitFrom(*_screen); + _screen->_backBuffer2.SHblitFrom(*_screen); Common::Point lostFilesPosition; Common::Point sherlockHolmesPosition; @@ -427,17 +428,17 @@ bool ScalpelEngine::showCityCutscene() { } // The Lost Files of - _screen->_backBuffer1.transBlitFrom(titleImages_SherlockHolmesTitle[0], lostFilesPosition); + _screen->_backBuffer1.SHtransBlitFrom(titleImages_SherlockHolmesTitle[0], lostFilesPosition); // Sherlock Holmes - _screen->_backBuffer1.transBlitFrom(titleImages_SherlockHolmesTitle[1], sherlockHolmesPosition); + _screen->_backBuffer1.SHtransBlitFrom(titleImages_SherlockHolmesTitle[1], sherlockHolmesPosition); // copyright - _screen->_backBuffer1.transBlitFrom(titleImages_SherlockHolmesTitle[2], copyrightPosition); + _screen->_backBuffer1.SHtransBlitFrom(titleImages_SherlockHolmesTitle[2], copyrightPosition); _screen->verticalTransition(); finished = _events->delay(4000, true); if (finished) { - _screen->_backBuffer1.blitFrom(_screen->_backBuffer2); + _screen->_backBuffer1.SHblitFrom(_screen->_backBuffer2); _screen->randomTransition(); finished = _events->delay(2000); } @@ -461,7 +462,7 @@ bool ScalpelEngine::showCityCutscene() { // English, width 175, height 38 alleyPosition = Common::Point(72, 51); } - _screen->transBlitFrom(titleImages_SherlockHolmesTitle[3], alleyPosition); + _screen->SHtransBlitFrom(titleImages_SherlockHolmesTitle[3], alleyPosition); _screen->fadeIn(palette, 3); // Wait until the track got looped and the first few notes were played @@ -537,7 +538,7 @@ bool ScalpelEngine::showAlleyCutscene() { earlyTheFollowingMorningPosition = Common::Point(35, 52); } - _screen->transBlitFrom(titleImages_EarlyTheFollowingMorning[0], earlyTheFollowingMorningPosition); + _screen->SHtransBlitFrom(titleImages_EarlyTheFollowingMorning[0], earlyTheFollowingMorningPosition); // fast fade-in _screen->fadeIn(palette, 1); @@ -641,23 +642,23 @@ bool ScalpelEngine::scrollCredits() { delete stream; // Save a copy of the screen background for use in drawing each credit frame - _screen->_backBuffer1.blitFrom(*_screen); + _screen->_backBuffer1.SHblitFrom(*_screen); // Loop for showing the credits for(int idx = 0; idx < 600 && !_events->kbHit() && !shouldQuit(); ++idx) { // Copy the entire screen background before writing text - _screen->blitFrom(_screen->_backBuffer1); + _screen->SHblitFrom(_screen->_backBuffer1); // Write the text appropriate for the next frame if (idx < 400) - _screen->transBlitFrom(creditsImages[0], Common::Point(10, 200 - idx), false, 0); + _screen->SHtransBlitFrom(creditsImages[0], Common::Point(10, 200 - idx), false, 0); if (idx > 200) - _screen->transBlitFrom(creditsImages[1], Common::Point(10, 400 - idx), false, 0); + _screen->SHtransBlitFrom(creditsImages[1], Common::Point(10, 400 - idx), false, 0); // Don't show credit text on the top and bottom ten rows of the screen - _screen->blitFrom(_screen->_backBuffer1, Common::Point(0, 0), Common::Rect(0, 0, _screen->w(), 10)); - _screen->blitFrom(_screen->_backBuffer1, Common::Point(0, _screen->h() - 10), - Common::Rect(0, _screen->h() - 10, _screen->w(), _screen->h())); + _screen->SHblitFrom(_screen->_backBuffer1, Common::Point(0, 0), Common::Rect(0, 0, _screen->width(), 10)); + _screen->SHblitFrom(_screen->_backBuffer1, Common::Point(0, _screen->height() - 10), + Common::Rect(0, _screen->height() - 10, _screen->width(), _screen->height())); _events->delay(100); } @@ -670,7 +671,7 @@ bool ScalpelEngine::show3DOSplash() { // 3DO EA Splash screen ImageFile3DO titleImage_3DOSplash("3DOSplash.cel", kImageFile3DOType_Cel); - _screen->transBlitFrom(titleImage_3DOSplash[0]._frame, Common::Point(0, -20)); + _screen->SHtransBlitFrom(titleImage_3DOSplash[0]._frame, Common::Point(0, -20)); bool finished = _events->delay(3000, true); if (finished) { @@ -706,7 +707,7 @@ bool ScalpelEngine::showCityCutscene3DO() { _sound->playAiff("prologue/sounds/rain.aiff", 15, true); // Fade screen to grey - screen._backBuffer1.fill(0xCE59); // RGB565: 25, 50, 25 (grey) + screen._backBuffer1.clear(0xCE59); // RGB565: 25, 50, 25 (grey) screen.fadeIntoScreen3DO(2); } @@ -715,16 +716,16 @@ bool ScalpelEngine::showCityCutscene3DO() { } if (finished) { - screen._backBuffer1.fill(0); // fill backbuffer with black to avoid issues during fade from white + screen._backBuffer1.clear(0); // fill backbuffer with black to avoid issues during fade from white finished = _animation->play3DO("26open1", true, 1, true, 2); } if (finished) { - screen._backBuffer2.blitFrom(screen._backBuffer1); + screen._backBuffer2.SHblitFrom(screen._backBuffer1); // "London, England" ImageFile3DO titleImage_London("title2a.cel", kImageFile3DOType_Cel); - screen._backBuffer1.transBlitFrom(titleImage_London[0]._frame, Common::Point(30, 50)); + screen._backBuffer1.SHtransBlitFrom(titleImage_London[0]._frame, Common::Point(30, 50)); screen.fadeIntoScreen3DO(1); finished = _events->delay(1500, true); @@ -732,7 +733,7 @@ bool ScalpelEngine::showCityCutscene3DO() { if (finished) { // "November, 1888" ImageFile3DO titleImage_November("title2b.cel", kImageFile3DOType_Cel); - screen._backBuffer1.transBlitFrom(titleImage_November[0]._frame, Common::Point(100, 100)); + screen._backBuffer1.SHtransBlitFrom(titleImage_November[0]._frame, Common::Point(100, 100)); screen.fadeIntoScreen3DO(1); finished = _music->waitUntilMSec(14700, 0, 0, 5000); @@ -740,8 +741,8 @@ bool ScalpelEngine::showCityCutscene3DO() { if (finished) { // Restore screen - _screen->_backBuffer1.blitFrom(screen._backBuffer2); - _screen->blitFrom(screen._backBuffer1); + _screen->_backBuffer1.SHblitFrom(screen._backBuffer2); + _screen->SHblitFrom(screen._backBuffer1); } } @@ -751,7 +752,7 @@ bool ScalpelEngine::showCityCutscene3DO() { if (finished) { // "Sherlock Holmes" (title) ImageFile3DO titleImage_SherlockHolmesTitle("title1ab.cel", kImageFile3DOType_Cel); - screen._backBuffer1.transBlitFrom(titleImage_SherlockHolmesTitle[0]._frame, Common::Point(34, 5)); + screen._backBuffer1.SHtransBlitFrom(titleImage_SherlockHolmesTitle[0]._frame, Common::Point(34, 5)); // Blend in screen.fadeIntoScreen3DO(2); @@ -761,7 +762,7 @@ bool ScalpelEngine::showCityCutscene3DO() { if (finished) { ImageFile3DO titleImage_Copyright("title1c.cel", kImageFile3DOType_Cel); - screen.transBlitFrom(titleImage_Copyright[0]._frame, Common::Point(20, 190)); + screen.SHtransBlitFrom(titleImage_Copyright[0]._frame, Common::Point(20, 190)); finished = _events->delay(3500, true); } } @@ -780,7 +781,7 @@ bool ScalpelEngine::showCityCutscene3DO() { if (finished) { // "In the alley behind the Regency Theatre..." ImageFile3DO titleImage_InTheAlley("title1d.cel", kImageFile3DOType_Cel); - screen._backBuffer1.transBlitFrom(titleImage_InTheAlley[0]._frame, Common::Point(72, 51)); + screen._backBuffer1.SHtransBlitFrom(titleImage_InTheAlley[0]._frame, Common::Point(72, 51)); // Fade in screen.fadeIntoScreen3DO(4); @@ -819,7 +820,7 @@ bool ScalpelEngine::showAlleyCutscene3DO() { ImageFile3DO titleImage_ScreamingVictim("scream.cel", kImageFile3DOType_Cel); screen.clear(); - screen.transBlitFrom(titleImage_ScreamingVictim[0]._frame, Common::Point(0, 0)); + screen.SHtransBlitFrom(titleImage_ScreamingVictim[0]._frame, Common::Point(0, 0)); // Play "scream.aiff" if (_sound->_voices) @@ -848,7 +849,7 @@ bool ScalpelEngine::showAlleyCutscene3DO() { if (finished) { // "Early the following morning on Baker Street..." ImageFile3DO titleImage_EarlyTheFollowingMorning("title3.cel", kImageFile3DOType_Cel); - screen._backBuffer1.transBlitFrom(titleImage_EarlyTheFollowingMorning[0]._frame, Common::Point(35, 51)); + screen._backBuffer1.SHtransBlitFrom(titleImage_EarlyTheFollowingMorning[0]._frame, Common::Point(35, 51)); // Fade in screen.fadeIntoScreen3DO(4); @@ -908,7 +909,7 @@ bool ScalpelEngine::showOfficeCutscene3DO() { ImageFile3DO titleImage_CoffeeNote("note.cel", kImageFile3DOType_Cel); _screen->clear(); - _screen->transBlitFrom(titleImage_CoffeeNote[0]._frame, Common::Point(0, 0)); + _screen->SHtransBlitFrom(titleImage_CoffeeNote[0]._frame, Common::Point(0, 0)); if (_sound->_voices) { finished = _sound->playSound("prologue/sounds/note.aiff", WAIT_KBD_OR_FINISH); @@ -937,7 +938,7 @@ bool ScalpelEngine::showOfficeCutscene3DO() { // TODO: Brighten the image, possibly by doing a partial fade // to white. - _screen->_backBuffer2.blitFrom(_screen->_backBuffer1); + _screen->_backBuffer2.SHblitFrom(_screen->_backBuffer1); for (int nr = 1; finished && nr <= 4; nr++) { char filename[15]; @@ -945,8 +946,8 @@ bool ScalpelEngine::showOfficeCutscene3DO() { ImageFile3DO *creditsImage = new ImageFile3DO(filename, kImageFile3DOType_Cel); ImageFrame *creditsFrame = &(*creditsImage)[0]; for (int i = 0; finished && i < 200 + creditsFrame->_height; i++) { - _screen->blitFrom(_screen->_backBuffer2); - _screen->transBlitFrom(creditsFrame->_frame, Common::Point((320 - creditsFrame->_width) / 2, 200 - i)); + _screen->SHblitFrom(_screen->_backBuffer2); + _screen->SHtransBlitFrom(creditsFrame->_frame, Common::Point((320 - creditsFrame->_width) / 2, 200 - i)); if (!_events->delay(70, true)) finished = false; } @@ -998,7 +999,7 @@ void ScalpelEngine::showLBV(const Common::String &filename) { delete stream; _screen->setPalette(images._palette); - _screen->_backBuffer1.blitFrom(images[0]); + _screen->_backBuffer1.SHblitFrom(images[0]); _screen->verticalTransition(); } @@ -1156,7 +1157,7 @@ void ScalpelEngine::eraseBrumwellMirror() { // If player is in range of the mirror, then restore background from the secondary back buffer if (Common::Rect(70, 100, 200, 200).contains(pt)) { - _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(137, 18), + _screen->_backBuffer1.SHblitFrom(_screen->_backBuffer2, Common::Point(137, 18), Common::Rect(137, 18, 184, 74)); } } @@ -1218,20 +1219,20 @@ void ScalpelEngine::doBrumwellMirror() { bool flipped = people[HOLMES]._sequenceNumber == WALK_LEFT || people[HOLMES]._sequenceNumber == STOP_LEFT || people[HOLMES]._sequenceNumber == WALK_UPRIGHT || people[HOLMES]._sequenceNumber == STOP_UPRIGHT || people[HOLMES]._sequenceNumber == WALK_DOWNLEFT || people[HOLMES]._sequenceNumber == STOP_DOWNLEFT; - _screen->_backBuffer1.transBlitFrom(imageFrame, pt + Common::Point(38, -imageFrame._frame.h - 25), flipped); + _screen->_backBuffer1.SHtransBlitFrom(imageFrame, pt + Common::Point(38, -imageFrame._frame.h - 25), flipped); // Redraw the mirror borders to prevent the drawn image of Holmes from appearing outside of the mirror - _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(114, 18), + _screen->_backBuffer1.SHblitFrom(_screen->_backBuffer2, Common::Point(114, 18), Common::Rect(114, 18, 137, 114)); - _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(137, 70), + _screen->_backBuffer1.SHblitFrom(_screen->_backBuffer2, Common::Point(137, 70), Common::Rect(137, 70, 142, 114)); - _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(142, 71), + _screen->_backBuffer1.SHblitFrom(_screen->_backBuffer2, Common::Point(142, 71), Common::Rect(142, 71, 159, 114)); - _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(159, 72), + _screen->_backBuffer1.SHblitFrom(_screen->_backBuffer2, Common::Point(159, 72), Common::Rect(159, 72, 170, 116)); - _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(170, 73), + _screen->_backBuffer1.SHblitFrom(_screen->_backBuffer2, Common::Point(170, 73), Common::Rect(170, 73, 184, 114)); - _screen->_backBuffer1.blitFrom(_screen->_backBuffer2, Common::Point(184, 18), + _screen->_backBuffer1.SHblitFrom(_screen->_backBuffer2, Common::Point(184, 18), Common::Rect(184, 18, 212, 114)); } } @@ -1272,7 +1273,7 @@ void ScalpelEngine::showScummVMRestoreDialog() { bool ScalpelEngine::play3doMovie(const Common::String &filename, const Common::Point &pos, bool isPortrait) { Scalpel3DOScreen &screen = *(Scalpel3DOScreen *)_screen; Scalpel3DOMovieDecoder *videoDecoder = new Scalpel3DOMovieDecoder(); - Graphics::Surface tempSurface; + Graphics::ManagedSurface tempSurface; Common::Point framePos(pos.x, pos.y); ImageFile3DO *frameImageFile = nullptr; @@ -1307,7 +1308,7 @@ bool ScalpelEngine::play3doMovie(const Common::String &filename, const Common::P // If we're to show the movie at half-size, we'll need a temporary intermediate surface if (halfSize) - tempSurface.create(width / 2, height / 2, _screen->getPixelFormat()); + tempSurface.create(width / 2, height / 2); while (!shouldQuit() && !videoDecoder->endOfVideo() && !skipVideo) { if (videoDecoder->needsUpdate()) { @@ -1371,19 +1372,19 @@ bool ScalpelEngine::play3doMovie(const Common::String &filename, const Common::P } // Point the drawing frame to the temporary surface - frame = &tempSurface; + frame = &tempSurface.rawSurface(); } if (isPortrait && !frameShown) { // Draw the frame (not the frame of the video, but a frame around the video) itself - _screen->transBlitFrom(frameImage->_frame, framePos); + _screen->SHtransBlitFrom(frameImage->_frame, framePos); frameShown = true; } if (isPortrait && !halfSize) { screen.rawBlitFrom(*frame, Common::Point(pos.x * 2, pos.y * 2)); } else { - _screen->blitFrom(*frame, pos); + _screen->SHblitFrom(*frame, pos); } _screen->update(); @@ -1413,9 +1414,9 @@ bool ScalpelEngine::play3doMovie(const Common::String &filename, const Common::P } // Restore scene - screen._backBuffer1.blitFrom(screen._backBuffer2); + screen._backBuffer1.SHblitFrom(screen._backBuffer2); _scene->updateBackground(); - screen.slamArea(0, 0, screen.w(), CONTROLS_Y); + screen.slamArea(0, 0, screen.width(), CONTROLS_Y); return !skipVideo; } diff --git a/engines/sherlock/scalpel/scalpel_darts.cpp b/engines/sherlock/scalpel/scalpel_darts.cpp index 87f4566837..c5ba8032f3 100644 --- a/engines/sherlock/scalpel/scalpel_darts.cpp +++ b/engines/sherlock/scalpel/scalpel_darts.cpp @@ -102,7 +102,7 @@ void Darts::playDarts() { score -= lastDart; _roundScore += lastDart; - screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(DART_INFO_X, DART_INFO_Y - 1), + screen._backBuffer1.SHblitFrom(screen._backBuffer2, Common::Point(DART_INFO_X, DART_INFO_Y - 1), Common::Rect(DART_INFO_X, DART_INFO_Y - 1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); screen.print(Common::Point(DART_INFO_X, DART_INFO_Y), DART_COL_FORE, "Dart # %d", idx + 1); screen.print(Common::Point(DART_INFO_X, DART_INFO_Y + 10), DART_COL_FORE, "Scored %d points", lastDart); @@ -154,7 +154,7 @@ void Darts::playDarts() { events.wait(20); } - screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(DART_INFO_X, DART_INFO_Y - 1), + screen._backBuffer1.SHblitFrom(screen._backBuffer2, Common::Point(DART_INFO_X, DART_INFO_Y - 1), Common::Rect(DART_INFO_X, DART_INFO_Y - 1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); } @@ -166,8 +166,8 @@ void Darts::playDarts() { done |= _vm->shouldQuit(); if (!done) { - screen._backBuffer2.blitFrom((*_dartImages)[0], Common::Point(0, 0)); - screen._backBuffer1.blitFrom(screen._backBuffer2); + screen._backBuffer2.SHblitFrom((*_dartImages)[0], Common::Point(0, 0)); + screen._backBuffer1.SHblitFrom(screen._backBuffer2); screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); } } while (!done); @@ -185,7 +185,7 @@ void Darts::loadDarts() { _dartImages = new ImageFile("darts.vgs"); screen.setPalette(_dartImages->_palette); - screen._backBuffer1.blitFrom((*_dartImages)[0], Common::Point(0, 0)); + screen._backBuffer1.SHblitFrom((*_dartImages)[0], Common::Point(0, 0)); screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); } @@ -245,7 +245,7 @@ void Darts::showNames(int playerNum) { screen.slamArea(STATUS_INFO_X + 50, STATUS_INFO_Y + 10, 81, 12); // Make a copy of the back buffer to the secondary one - screen._backBuffer2.blitFrom(screen._backBuffer1); + screen._backBuffer2.SHblitFrom(screen._backBuffer1); } void Darts::showStatus(int playerNum) { @@ -253,7 +253,7 @@ void Darts::showStatus(int playerNum) { byte color; // Copy scoring screen from secondary back buffer. This will erase any previously displayed status/score info - screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 10), + screen._backBuffer1.SHblitFrom(screen._backBuffer2, Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 10), Common::Rect(STATUS_INFO_X, STATUS_INFO_Y + 10, SHERLOCK_SCREEN_WIDTH, STATUS_INFO_Y + 48)); color = (playerNum == 0) ? PLAYER_COLOR : DART_COL_FORE; @@ -292,7 +292,7 @@ int Darts::throwDart(int dartNum, int computer) { if (_vm->shouldQuit()) return 0; - screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(DART_INFO_X, DART_INFO_Y - 1), + screen._backBuffer1.SHblitFrom(screen._backBuffer2, Common::Point(DART_INFO_X, DART_INFO_Y - 1), Common::Rect(DART_INFO_X, DART_INFO_Y - 1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); screen.slamRect(Common::Rect(DART_INFO_X, DART_INFO_Y - 1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); @@ -309,9 +309,9 @@ int Darts::throwDart(int dartNum, int computer) { // Copy the bars to the secondary back buffer so that they remain fixed at their selected values // whilst the dart is being animated at being thrown at the board - screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(DARTBARHX - 1, DARTHORIZY - 1), + screen._backBuffer2.SHblitFrom(screen._backBuffer1, Common::Point(DARTBARHX - 1, DARTHORIZY - 1), Common::Rect(DARTBARHX - 1, DARTHORIZY - 1, DARTBARHX + DARTBARSIZE + 3, DARTHORIZY + 10)); - screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(DARTBARVX - 1, DARTHEIGHTY - 1), + screen._backBuffer2.SHblitFrom(screen._backBuffer1, Common::Point(DARTBARVX - 1, DARTHEIGHTY - 1), Common::Rect(DARTBARVX - 1, DARTHEIGHTY - 1, DARTBARVX + 11, DARTHEIGHTY + DARTBARSIZE + 3)); // Convert height and width to relative range of -50 to 50, where 0,0 is the exact centre of the board @@ -344,7 +344,7 @@ void Darts::drawDartThrow(const Common::Point &pt) { // Draw the dart Common::Point drawPos(pos.x - frame._width / 2, pos.y - frame._height); - screen._backBuffer1.transBlitFrom(frame, drawPos); + screen._backBuffer1.SHtransBlitFrom(frame, drawPos); screen.slamArea(drawPos.x, drawPos.y, frame._width, frame._height); // Handle erasing old dart frame area @@ -352,14 +352,14 @@ void Darts::drawDartThrow(const Common::Point &pt) { screen.slamRect(oldDrawBounds); oldDrawBounds = Common::Rect(drawPos.x, drawPos.y, drawPos.x + frame._width, drawPos.y + frame._height); - screen._backBuffer1.blitFrom(screen._backBuffer2, drawPos, oldDrawBounds); + screen._backBuffer1.SHblitFrom(screen._backBuffer2, drawPos, oldDrawBounds); events.wait(2); } // Draw dart in final "stuck to board" form - screen._backBuffer1.transBlitFrom((*_dartImages)[22], Common::Point(oldDrawBounds.left, oldDrawBounds.top)); - screen._backBuffer2.transBlitFrom((*_dartImages)[22], Common::Point(oldDrawBounds.left, oldDrawBounds.top)); + screen._backBuffer1.SHtransBlitFrom((*_dartImages)[22], Common::Point(oldDrawBounds.left, oldDrawBounds.top)); + screen._backBuffer2.SHtransBlitFrom((*_dartImages)[22], Common::Point(oldDrawBounds.left, oldDrawBounds.top)); screen.slamRect(oldDrawBounds); } @@ -368,8 +368,8 @@ void Darts::erasePowerBars() { screen._backBuffer1.fillRect(Common::Rect(DARTBARHX, DARTHORIZY, DARTBARHX + DARTBARSIZE, DARTHORIZY + 10), BLACK); screen._backBuffer1.fillRect(Common::Rect(DARTBARVX, DARTHEIGHTY, DARTBARVX + 10, DARTHEIGHTY + DARTBARSIZE), BLACK); - screen._backBuffer1.transBlitFrom((*_dartImages)[2], Common::Point(DARTBARHX - 1, DARTHORIZY - 1)); - screen._backBuffer1.transBlitFrom((*_dartImages)[3], Common::Point(DARTBARVX - 1, DARTHEIGHTY - 1)); + screen._backBuffer1.SHtransBlitFrom((*_dartImages)[2], Common::Point(DARTBARHX - 1, DARTHORIZY - 1)); + screen._backBuffer1.SHtransBlitFrom((*_dartImages)[3], Common::Point(DARTBARVX - 1, DARTHEIGHTY - 1)); screen.slamArea(DARTBARHX - 1, DARTHORIZY - 1, DARTBARSIZE + 3, 11); screen.slamArea(DARTBARVX - 1, DARTHEIGHTY - 1, 11, DARTBARSIZE + 3); } @@ -398,11 +398,11 @@ int Darts::doPowerBar(const Common::Point &pt, byte color, int goToPower, bool i if (isVertical) { screen._backBuffer1.hLine(pt.x, pt.y + DARTBARSIZE - 1 - idx, pt.x + 8, color); - screen._backBuffer1.transBlitFrom((*_dartImages)[3], Common::Point(pt.x - 1, pt.y - 1)); + screen._backBuffer1.SHtransBlitFrom((*_dartImages)[3], Common::Point(pt.x - 1, pt.y - 1)); screen.slamArea(pt.x, pt.y + DARTBARSIZE - 1 - idx, 8, 2); } else { screen._backBuffer1.vLine(pt.x + idx, pt.y, pt.y + 8, color); - screen._backBuffer1.transBlitFrom((*_dartImages)[2], Common::Point(pt.x - 1, pt.y - 1)); + screen._backBuffer1.SHtransBlitFrom((*_dartImages)[2], Common::Point(pt.x - 1, pt.y - 1)); screen.slamArea(pt.x + idx, pt.y, 1, 8); } diff --git a/engines/sherlock/scalpel/scalpel_fixed_text.cpp b/engines/sherlock/scalpel/scalpel_fixed_text.cpp index 8932fb29c5..d76136a1db 100644 --- a/engines/sherlock/scalpel/scalpel_fixed_text.cpp +++ b/engines/sherlock/scalpel/scalpel_fixed_text.cpp @@ -105,6 +105,14 @@ static const char *const fixedTextEN[] = { "Tarot Cards", "An ornate key", "A pawn ticket", + // SH1: User Interface + "No, thank you.", + "You can't do that.", + "Done...", + "Use ", + " on %s", + "Give ", + " to %s", // SH1: People names "Sherlock Holmes", "Dr. Watson", @@ -250,6 +258,14 @@ static const char *const fixedTextDE[] = { "Ein Tarot-Kartenspiel", // original interpreter: "Ein Tarock-Kartenspiel" [sic] "Ein verzierter Schl\201ssel", "Ein Pfandschein", + // SH1: User Interface + "Nein, vielen Dank.", + "Nein, das geht wirklich nicht.", // original: "Nein, das geht wirklich nicht" + "Fertig...", + "Benutze ", + " mit %s", + "Gib ", // original: "Gebe " + " an %s", // original: " zu %s" // SH1: People names "Sherlock Holmes", "Dr. Watson", @@ -316,6 +332,7 @@ static const char *const fixedTextDE[] = { // up-side down exclamation mark - 0xAD / octal 255 // up-side down question mark - 0xA8 / octal 250 // n with a wave on top - 0xA4 / octal 244 +// more characters see engines/sherlock/fixed_text.cpp static const char *const fixedTextES[] = { // Game hotkeys "VMHTACIUDNFO", @@ -394,6 +411,14 @@ static const char *const fixedTextES[] = { "Unas cartas de Tarot", "Una llave muy vistosa", "Una papeleta de empe\244o", + // SH1: User Interface + "No, gracias.", + "No puedes hacerlo.", // original: "No puedes hacerlo" + "Hecho...", + "Usar ", + " sobre %s", + "Dar ", + " a %s", // SH1: People names "Sherlock Holmes", "Dr. Watson", @@ -517,7 +542,7 @@ static const char *const fixedTextDE_ActionMove[] = { "L\204\341t sich nicht bewegen", "Festged\201belt in der Erde...", "Oha, VIEL zu schwer", - "Der andere Kiste ist im Weg" // [sic] + "Die andere Kiste ist im Weg" // original: "Der andere Kiste ist im Weg" }; static const char *const fixedTextES_ActionMove[] = { @@ -616,8 +641,6 @@ static const FixedTextActionEntry fixedTextES_Actions[] = { // ========================================= -// TODO: -// It seems there was a French version of Sherlock Holmes 2 static const FixedTextLanguageEntry fixedTextLanguages[] = { { Common::DE_DEU, fixedTextDE, fixedTextDE_Actions }, { Common::ES_ESP, fixedTextES, fixedTextES_Actions }, diff --git a/engines/sherlock/scalpel/scalpel_fixed_text.h b/engines/sherlock/scalpel/scalpel_fixed_text.h index 841d602408..d9b3bbed79 100644 --- a/engines/sherlock/scalpel/scalpel_fixed_text.h +++ b/engines/sherlock/scalpel/scalpel_fixed_text.h @@ -107,6 +107,14 @@ enum FixedTextId { kFixedText_InitInventory_Tarot, kFixedText_InitInventory_OrnateKey, kFixedText_InitInventory_PawnTicket, + // SH1: User Interface + kFixedText_UserInterface_NoThankYou, + kFixedText_UserInterface_YouCantDoThat, + kFixedText_UserInterface_Done, + kFixedText_UserInterface_Use, + kFixedText_UserInterface_UseOn, + kFixedText_UserInterface_Give, + kFixedText_UserInterface_GiveTo, // People names kFixedText_People_SherlockHolmes, kFixedText_People_DrWatson, diff --git a/engines/sherlock/scalpel/scalpel_inventory.cpp b/engines/sherlock/scalpel/scalpel_inventory.cpp index c3e20295fd..e8d4d3b934 100644 --- a/engines/sherlock/scalpel/scalpel_inventory.cpp +++ b/engines/sherlock/scalpel/scalpel_inventory.cpp @@ -201,7 +201,7 @@ void ScalpelInventory::highlight(int index, byte color) { ImageFrame &frame = (*_invShapes[slot])[0]; bb.fillRect(Common::Rect(8 + slot * 52, 165, (slot + 1) * 52, 194), color); - bb.transBlitFrom(frame, Common::Point(6 + slot * 52 + ((47 - frame._width) / 2), + bb.SHtransBlitFrom(frame, Common::Point(6 + slot * 52 + ((47 - frame._width) / 2), 163 + ((33 - frame._height) / 2))); screen.slamArea(8 + slot * 52, 165, 44, 30); } @@ -217,12 +217,12 @@ void ScalpelInventory::refreshInv() { ui._infoFlag = true; ui.clearInfo(); - screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(0, CONTROLS_Y), + screen._backBuffer2.SHblitFrom(screen._backBuffer1, Common::Point(0, CONTROLS_Y), Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); ui.examine(); if (!talk._talkToAbort) { - screen._backBuffer2.blitFrom((*ui._controlPanel)[0], Common::Point(0, CONTROLS_Y)); + screen._backBuffer2.SHblitFrom((*ui._controlPanel)[0], Common::Point(0, CONTROLS_Y)); loadInv(); } } @@ -264,7 +264,7 @@ void ScalpelInventory::putInv(InvSlamMode slamIt) { // Draw the item image ImageFrame &frame = (*_invShapes[itemNum])[0]; - bb.transBlitFrom(frame, Common::Point(6 + itemNum * 52 + ((47 - frame._width) / 2), + bb.SHtransBlitFrom(frame, Common::Point(6 + itemNum * 52 + ((47 - frame._width) / 2), 163 + ((33 - frame._height) / 2))); } diff --git a/engines/sherlock/scalpel/scalpel_map.cpp b/engines/sherlock/scalpel/scalpel_map.cpp index 0924581e38..ba14b5b300 100644 --- a/engines/sherlock/scalpel/scalpel_map.cpp +++ b/engines/sherlock/scalpel/scalpel_map.cpp @@ -167,13 +167,13 @@ int ScalpelMap::show() { setupSprites(); if (!IS_3DO) { - screen._backBuffer1.blitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y)); - screen._backBuffer1.blitFrom((*bigMap)[1], Common::Point(-_bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y)); - screen._backBuffer1.blitFrom((*bigMap)[2], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, -_bigPos.y)); - screen._backBuffer1.blitFrom((*bigMap)[3], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y)); + screen._backBuffer1.SHblitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y)); + screen._backBuffer1.SHblitFrom((*bigMap)[1], Common::Point(-_bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y)); + screen._backBuffer1.SHblitFrom((*bigMap)[2], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, -_bigPos.y)); + screen._backBuffer1.SHblitFrom((*bigMap)[3], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y)); } else { - screen._backBuffer1.blitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y)); - screen.blitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y)); + screen._backBuffer1.SHblitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y)); + screen.SHblitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y)); } _drawMap = true; @@ -238,12 +238,12 @@ int ScalpelMap::show() { changed = false; if (!IS_3DO) { - screen._backBuffer1.blitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y)); - screen._backBuffer1.blitFrom((*bigMap)[1], Common::Point(-_bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y)); - screen._backBuffer1.blitFrom((*bigMap)[2], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, -_bigPos.y)); - screen._backBuffer1.blitFrom((*bigMap)[3], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y)); + screen._backBuffer1.SHblitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y)); + screen._backBuffer1.SHblitFrom((*bigMap)[1], Common::Point(-_bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y)); + screen._backBuffer1.SHblitFrom((*bigMap)[2], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, -_bigPos.y)); + screen._backBuffer1.SHblitFrom((*bigMap)[3], Common::Point(SHERLOCK_SCREEN_WIDTH - _bigPos.x, SHERLOCK_SCREEN_HEIGHT - _bigPos.y)); } else { - screen._backBuffer1.blitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y)); + screen._backBuffer1.SHblitFrom((*bigMap)[0], Common::Point(-_bigPos.x, -_bigPos.y)); } showPlaces(); @@ -359,7 +359,6 @@ void ScalpelMap::freeSprites() { delete _mapCursors; delete _shapes; delete _iconShapes; - _iconSave.free(); } void ScalpelMap::showPlaces() { @@ -376,7 +375,7 @@ void ScalpelMap::showPlaces() { if (pt.x >= _bigPos.x && (pt.x - _bigPos.x) < SHERLOCK_SCREEN_WIDTH && pt.y >= _bigPos.y && (pt.y - _bigPos.y) < SHERLOCK_SCREEN_HEIGHT) { if (_vm->readFlags(idx)) { - screen._backBuffer1.transBlitFrom((*_iconShapes)[pt._translate], + screen._backBuffer1.SHtransBlitFrom((*_iconShapes)[pt._translate], Common::Point(pt.x - _bigPos.x - 6, pt.y - _bigPos.y - 12)); } } @@ -388,13 +387,13 @@ void ScalpelMap::showPlaces() { } void ScalpelMap::saveTopLine() { - _topLine.blitFrom(_vm->_screen->_backBuffer1, Common::Point(0, 0), Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, 12)); + _topLine.SHblitFrom(_vm->_screen->_backBuffer1, Common::Point(0, 0), Common::Rect(0, 0, SHERLOCK_SCREEN_WIDTH, 12)); } void ScalpelMap::eraseTopLine() { Screen &screen = *_vm->_screen; - screen._backBuffer1.blitFrom(_topLine, Common::Point(0, 0)); - screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, _topLine.h()); + screen._backBuffer1.SHblitFrom(_topLine, Common::Point(0, 0)); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, _topLine.height()); } void ScalpelMap::showPlaceName(int idx, bool highlighted) { @@ -409,7 +408,7 @@ void ScalpelMap::showPlaceName(int idx, bool highlighted) { bool flipped = people[HOLMES]._sequenceNumber == MAP_DOWNLEFT || people[HOLMES]._sequenceNumber == MAP_LEFT || people[HOLMES]._sequenceNumber == MAP_UPLEFT; - screen._backBuffer1.transBlitFrom(*people[HOLMES]._imageFrame, _lDrawnPos, flipped); + screen._backBuffer1.SHtransBlitFrom(*people[HOLMES]._imageFrame, _lDrawnPos, flipped); } if (highlighted) { @@ -451,9 +450,9 @@ void ScalpelMap::updateMap(bool flushScreen) { saveIcon(people[HOLMES]._imageFrame, hPos); if (people[HOLMES]._sequenceNumber == MAP_DOWNLEFT || people[HOLMES]._sequenceNumber == MAP_LEFT || people[HOLMES]._sequenceNumber == MAP_UPLEFT) - screen._backBuffer1.transBlitFrom(*people[HOLMES]._imageFrame, hPos, true); + screen._backBuffer1.SHtransBlitFrom(*people[HOLMES]._imageFrame, hPos, true); else - screen._backBuffer1.transBlitFrom(*people[HOLMES]._imageFrame, hPos, false); + screen._backBuffer1.SHtransBlitFrom(*people[HOLMES]._imageFrame, hPos, false); if (flushScreen) { screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); @@ -553,8 +552,8 @@ void ScalpelMap::saveIcon(ImageFrame *src, const Common::Point &pt) { return; } - assert(size.x <= _iconSave.w() && size.y <= _iconSave.h()); - _iconSave.blitFrom(screen._backBuffer1, Common::Point(0, 0), + assert(size.x <= _iconSave.width() && size.y <= _iconSave.height()); + _iconSave.SHblitFrom(screen._backBuffer1, Common::Point(0, 0), Common::Rect(pos.x, pos.y, pos.x + size.x, pos.y + size.y)); _savedPos = pos; _savedSize = size; @@ -565,7 +564,7 @@ void ScalpelMap::restoreIcon() { if (_savedPos.x >= 0 && _savedPos.y >= 0 && _savedPos.x <= SHERLOCK_SCREEN_WIDTH && _savedPos.y < SHERLOCK_SCREEN_HEIGHT) - screen._backBuffer1.blitFrom(_iconSave, _savedPos, Common::Rect(0, 0, _savedSize.x, _savedSize.y)); + screen._backBuffer1.SHblitFrom(_iconSave, _savedPos, Common::Rect(0, 0, _savedSize.x, _savedSize.y)); } void ScalpelMap::highlightIcon(const Common::Point &pt) { diff --git a/engines/sherlock/scalpel/scalpel_scene.cpp b/engines/sherlock/scalpel/scalpel_scene.cpp index b62703e0fb..83e49bb3fa 100644 --- a/engines/sherlock/scalpel/scalpel_scene.cpp +++ b/engines/sherlock/scalpel/scalpel_scene.cpp @@ -71,26 +71,26 @@ void ScalpelScene::drawAllShapes() { // Draw all active shapes which are behind the person for (uint idx = 0; idx < _bgShapes.size(); ++idx) { if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE && _bgShapes[idx]._misc == BEHIND) - screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, _bgShapes[idx]._flags & OBJ_FLIPPED); + screen._backBuffer->SHtransBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, _bgShapes[idx]._flags & OBJ_FLIPPED); } // Draw all canimations which are behind the person for (uint idx = 0; idx < _canimShapes.size(); ++idx) { if (_canimShapes[idx]->_type == ACTIVE_BG_SHAPE && _canimShapes[idx]->_misc == BEHIND) - screen._backBuffer->transBlitFrom(*_canimShapes[idx]->_imageFrame, + screen._backBuffer->SHtransBlitFrom(*_canimShapes[idx]->_imageFrame, _canimShapes[idx]->_position, _canimShapes[idx]->_flags & OBJ_FLIPPED); } // Draw all active shapes which are normal and behind the person for (uint idx = 0; idx < _bgShapes.size(); ++idx) { if (_bgShapes[idx]._type == ACTIVE_BG_SHAPE && _bgShapes[idx]._misc == NORMAL_BEHIND) - screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, _bgShapes[idx]._flags & OBJ_FLIPPED); + screen._backBuffer->SHtransBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, _bgShapes[idx]._flags & OBJ_FLIPPED); } // Draw all canimations which are normal and behind the person for (uint idx = 0; idx < _canimShapes.size(); ++idx) { if (_canimShapes[idx]->_type == ACTIVE_BG_SHAPE && _canimShapes[idx]->_misc == NORMAL_BEHIND) - screen._backBuffer->transBlitFrom(*_canimShapes[idx]->_imageFrame, _canimShapes[idx]->_position, + screen._backBuffer->SHtransBlitFrom(*_canimShapes[idx]->_imageFrame, _canimShapes[idx]->_position, _canimShapes[idx]->_flags & OBJ_FLIPPED); } @@ -103,7 +103,7 @@ void ScalpelScene::drawAllShapes() { p._sequenceNumber == WALK_UPLEFT || p._sequenceNumber == STOP_UPLEFT || p._sequenceNumber == WALK_DOWNRIGHT || p._sequenceNumber == STOP_DOWNRIGHT); - screen._backBuffer->transBlitFrom(*p._imageFrame, Common::Point(p._position.x / FIXED_INT_MULTIPLIER, + screen._backBuffer->SHtransBlitFrom(*p._imageFrame, Common::Point(p._position.x / FIXED_INT_MULTIPLIER, p._position.y / FIXED_INT_MULTIPLIER - p.frameHeight()), flipped); } } @@ -112,7 +112,7 @@ void ScalpelScene::drawAllShapes() { for (uint idx = 0; idx < _bgShapes.size(); ++idx) { if ((_bgShapes[idx]._type == ACTIVE_BG_SHAPE || _bgShapes[idx]._type == STATIC_BG_SHAPE) && _bgShapes[idx]._misc == NORMAL_FORWARD) - screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, + screen._backBuffer->SHtransBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, _bgShapes[idx]._flags & OBJ_FLIPPED); } @@ -120,7 +120,7 @@ void ScalpelScene::drawAllShapes() { for (uint idx = 0; idx < _canimShapes.size(); ++idx) { if ((_canimShapes[idx]->_type == ACTIVE_BG_SHAPE || _canimShapes[idx]->_type == STATIC_BG_SHAPE) && _canimShapes[idx]->_misc == NORMAL_FORWARD) - screen._backBuffer->transBlitFrom(*_canimShapes[idx]->_imageFrame, _canimShapes[idx]->_position, + screen._backBuffer->SHtransBlitFrom(*_canimShapes[idx]->_imageFrame, _canimShapes[idx]->_position, _canimShapes[idx]->_flags & OBJ_FLIPPED); } @@ -132,7 +132,7 @@ void ScalpelScene::drawAllShapes() { if ((_bgShapes[idx]._type == ACTIVE_BG_SHAPE || _bgShapes[idx]._type == STATIC_BG_SHAPE) && _bgShapes[idx]._misc == FORWARD) - screen._backBuffer->transBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, + screen._backBuffer->SHtransBlitFrom(*_bgShapes[idx]._imageFrame, _bgShapes[idx]._position, _bgShapes[idx]._flags & OBJ_FLIPPED); } @@ -140,7 +140,7 @@ void ScalpelScene::drawAllShapes() { for (uint idx = 0; idx < _canimShapes.size(); ++idx) { if ((_canimShapes[idx]->_type == ACTIVE_BG_SHAPE || _canimShapes[idx]->_type == STATIC_BG_SHAPE) && _canimShapes[idx]->_misc == FORWARD) - screen._backBuffer->transBlitFrom(*_canimShapes[idx]->_imageFrame, _canimShapes[idx]->_position, + screen._backBuffer->SHtransBlitFrom(*_canimShapes[idx]->_imageFrame, _canimShapes[idx]->_position, _canimShapes[idx]->_flags & OBJ_FLIPPED); } @@ -242,7 +242,7 @@ void ScalpelScene::doBgAnim() { if (people[HOLMES]._type == CHARACTER) screen.restoreBackground(bounds); else if (people[HOLMES]._type == REMOVE) - screen._backBuffer->blitFrom(screen._backBuffer2, pt, bounds); + screen._backBuffer->SHblitFrom(screen._backBuffer2, pt, bounds); for (uint idx = 0; idx < _bgShapes.size(); ++idx) { Object &o = _bgShapes[idx]; @@ -261,7 +261,7 @@ void ScalpelScene::doBgAnim() { Object &o = _bgShapes[idx]; if (o._type == NO_SHAPE && ((o._flags & OBJ_BEHIND) == 0)) { // Restore screen area - screen._backBuffer->blitFrom(screen._backBuffer2, o._position, + screen._backBuffer->SHblitFrom(screen._backBuffer2, o._position, Common::Rect(o._position.x, o._position.y, o._position.x + o._noShapeSize.x, o._position.y + o._noShapeSize.y)); @@ -309,14 +309,14 @@ void ScalpelScene::doBgAnim() { for (uint idx = 0; idx < _bgShapes.size(); ++idx) { Object &o = _bgShapes[idx]; if (o._type == ACTIVE_BG_SHAPE && o._misc == BEHIND) - screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + screen._backBuffer->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); } // Draw all canimations which are behind the person for (uint idx = 0; idx < _canimShapes.size(); ++idx) { Object &o = *_canimShapes[idx]; if (o._type == ACTIVE_BG_SHAPE && o._misc == BEHIND) { - screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + screen._backBuffer->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); } } @@ -324,14 +324,14 @@ void ScalpelScene::doBgAnim() { for (uint idx = 0; idx < _bgShapes.size(); ++idx) { Object &o = _bgShapes[idx]; if (o._type == ACTIVE_BG_SHAPE && o._misc == NORMAL_BEHIND) - screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + screen._backBuffer->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); } // Draw all canimations which are NORMAL and behind the person for (uint idx = 0; idx < _canimShapes.size(); ++idx) { Object &o = *_canimShapes[idx]; if (o._type == ACTIVE_BG_SHAPE && o._misc == NORMAL_BEHIND) { - screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + screen._backBuffer->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); } } @@ -344,7 +344,7 @@ void ScalpelScene::doBgAnim() { bool flipped = people[HOLMES]._sequenceNumber == WALK_LEFT || people[HOLMES]._sequenceNumber == STOP_LEFT || people[HOLMES]._sequenceNumber == WALK_UPLEFT || people[HOLMES]._sequenceNumber == STOP_UPLEFT || people[HOLMES]._sequenceNumber == WALK_DOWNRIGHT || people[HOLMES]._sequenceNumber == STOP_DOWNRIGHT; - screen._backBuffer->transBlitFrom(*people[HOLMES]._imageFrame, + screen._backBuffer->SHtransBlitFrom(*people[HOLMES]._imageFrame, Common::Point(tempX, people[HOLMES]._position.y / FIXED_INT_MULTIPLIER - people[HOLMES]._imageFrame->_frame.h), flipped); } @@ -352,14 +352,14 @@ void ScalpelScene::doBgAnim() { for (uint idx = 0; idx < _bgShapes.size(); ++idx) { Object &o = _bgShapes[idx]; if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == NORMAL_FORWARD) - screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + screen._backBuffer->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); } // Draw all static and active canimations that are NORMAL and are in front of the person for (uint idx = 0; idx < _canimShapes.size(); ++idx) { Object &o = *_canimShapes[idx]; if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == NORMAL_FORWARD) { - screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + screen._backBuffer->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); } } @@ -367,19 +367,19 @@ void ScalpelScene::doBgAnim() { for (uint idx = 0; idx < _bgShapes.size(); ++idx) { Object &o = _bgShapes[idx]; if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == FORWARD) - screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + screen._backBuffer->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); } // Draw any active portrait if (people._portraitLoaded && people._portrait._type == ACTIVE_BG_SHAPE) - screen._backBuffer->transBlitFrom(*people._portrait._imageFrame, + screen._backBuffer->SHtransBlitFrom(*people._portrait._imageFrame, people._portrait._position, people._portrait._flags & OBJ_FLIPPED); // Draw all static and active canimations that are in front of the person for (uint idx = 0; idx < _canimShapes.size(); ++idx) { Object &o = *_canimShapes[idx]; if ((o._type == ACTIVE_BG_SHAPE || o._type == STATIC_BG_SHAPE) && o._misc == FORWARD) { - screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + screen._backBuffer->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); } } @@ -387,7 +387,7 @@ void ScalpelScene::doBgAnim() { for (uint idx = 0; idx < _bgShapes.size(); ++idx) { Object &o = _bgShapes[idx]; if (o._type == NO_SHAPE && (o._flags & OBJ_BEHIND) == 0) - screen._backBuffer->transBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); + screen._backBuffer->SHtransBlitFrom(*o._imageFrame, o._position, o._flags & OBJ_FLIPPED); } // Bring the newly built picture to the screen diff --git a/engines/sherlock/scalpel/scalpel_screen.cpp b/engines/sherlock/scalpel/scalpel_screen.cpp index 197a2a2634..37e5294c02 100644 --- a/engines/sherlock/scalpel/scalpel_screen.cpp +++ b/engines/sherlock/scalpel/scalpel_screen.cpp @@ -28,6 +28,8 @@ namespace Sherlock { namespace Scalpel { ScalpelScreen::ScalpelScreen(SherlockEngine *vm) : Screen(vm) { + _backBuffer1.create(320, 200); + _backBuffer2.create(320, 200); } void ScalpelScreen::makeButton(const Common::Rect &bounds, int textX, @@ -123,255 +125,6 @@ void ScalpelScreen::makeField(const Common::Rect &r) { _backBuffer->vLine(r.right - 1, r.top + 1, r.bottom - 2, BUTTON_TOP); } -/*----------------------------------------------------------------*/ - -void Scalpel3DOScreen::blitFrom(const Graphics::Surface &src, const Common::Point &pt, const Common::Rect &srcBounds) { - if (!_vm->_isScreenDoubled) { - ScalpelScreen::blitFrom(src, pt, srcBounds); - return; - } - - Common::Rect srcRect = srcBounds; - Common::Rect destRect(pt.x, pt.y, pt.x + srcRect.width(), pt.y + srcRect.height()); - - if (!srcRect.isValidRect() || !clip(srcRect, destRect)) - return; - - // Add dirty area remapped to the 640x200 surface - addDirtyRect(Common::Rect(destRect.left * 2, destRect.top * 2, destRect.right * 2, destRect.bottom * 2)); - - // Transfer the area, doubling each pixel - for (int yp = 0; yp < srcRect.height(); ++yp) { - const uint16 *srcP = (const uint16 *)src.getBasePtr(srcRect.left, srcRect.top + yp); - uint16 *destP = (uint16 *)getBasePtr(destRect.left * 2, (destRect.top + yp) * 2); - - for (int xp = srcRect.left; xp < srcRect.right; ++xp, ++srcP, destP += 2) { - *destP = *srcP; - *(destP + 1) = *srcP; - *(destP + 640) = *srcP; - *(destP + 640 + 1) = *srcP; - } - } -} - -void Scalpel3DOScreen::transBlitFromUnscaled(const Graphics::Surface &src, const Common::Point &pt, - bool flipped, int overrideColor) { - if (!_vm->_isScreenDoubled) { - ScalpelScreen::transBlitFromUnscaled(src, pt, flipped, overrideColor); - return; - } - - Common::Rect drawRect(0, 0, src.w, src.h); - Common::Rect destRect(pt.x, pt.y, pt.x + src.w, pt.y + src.h); - - // Clip the display area to on-screen - if (!clip(drawRect, destRect)) - // It's completely off-screen - return; - - if (flipped) - drawRect = Common::Rect(src.w - drawRect.right, src.h - drawRect.bottom, - src.w - drawRect.left, src.h - drawRect.top); - - Common::Point destPt(destRect.left, destRect.top); - addDirtyRect(Common::Rect(destPt.x * 2, destPt.y * 2, (destPt.x + drawRect.width()) * 2, - (destPt.y + drawRect.height()) * 2)); - - assert(src.format.bytesPerPixel == 2 && _surface.format.bytesPerPixel == 2); - - for (int yp = 0; yp < drawRect.height(); ++yp) { - const uint16 *srcP = (const uint16 *)src.getBasePtr( - flipped ? drawRect.right - 1 : drawRect.left, drawRect.top + yp); - uint16 *destP = (uint16 *)getBasePtr(destPt.x * 2, (destPt.y + yp) * 2); - - for (int xp = 0; xp < drawRect.width(); ++xp, destP += 2) { - // RGB 0, 0, 0 -> transparent on 3DO - if (*srcP) { - *destP = *srcP; - *(destP + 1) = *srcP; - *(destP + 640) = *srcP; - *(destP + 640 + 1) = *srcP; - } - - srcP = flipped ? srcP - 1 : srcP + 1; - } - } -} - -void Scalpel3DOScreen::fillRect(const Common::Rect &r, uint color) { - if (_vm->_isScreenDoubled) - ScalpelScreen::fillRect(Common::Rect(r.left * 2, r.top * 2, r.right * 2, r.bottom * 2), color); - else - ScalpelScreen::fillRect(r, color); -} - -void Scalpel3DOScreen::fadeIntoScreen3DO(int speed) { - Events &events = *_vm->_events; - uint16 *currentScreenBasePtr = (uint16 *)getPixels(); - uint16 *targetScreenBasePtr = (uint16 *)_backBuffer->getPixels(); - uint16 currentScreenPixel = 0; - uint16 targetScreenPixel = 0; - - uint16 currentScreenPixelRed = 0; - uint16 currentScreenPixelGreen = 0; - uint16 currentScreenPixelBlue = 0; - - uint16 targetScreenPixelRed = 0; - uint16 targetScreenPixelGreen = 0; - uint16 targetScreenPixelBlue = 0; - - uint16 screenWidth = SHERLOCK_SCREEN_WIDTH; - uint16 screenHeight = SHERLOCK_SCREEN_HEIGHT; - uint16 screenX = 0; - uint16 screenY = 0; - uint16 pixelsChanged = 0; - - clearDirtyRects(); - - do { - pixelsChanged = 0; - uint16 *currentScreenPtr = currentScreenBasePtr; - uint16 *targetScreenPtr = targetScreenBasePtr; - - for (screenY = 0; screenY < screenHeight; screenY++) { - for (screenX = 0; screenX < screenWidth; screenX++) { - currentScreenPixel = *currentScreenPtr; - targetScreenPixel = *targetScreenPtr; - - if (currentScreenPixel != targetScreenPixel) { - // pixel doesn't match, adjust accordingly - currentScreenPixelRed = currentScreenPixel & 0xF800; - currentScreenPixelGreen = currentScreenPixel & 0x07E0; - currentScreenPixelBlue = currentScreenPixel & 0x001F; - targetScreenPixelRed = targetScreenPixel & 0xF800; - targetScreenPixelGreen = targetScreenPixel & 0x07E0; - targetScreenPixelBlue = targetScreenPixel & 0x001F; - - if (currentScreenPixelRed != targetScreenPixelRed) { - if (currentScreenPixelRed < targetScreenPixelRed) { - currentScreenPixelRed += 0x0800; - } else { - currentScreenPixelRed -= 0x0800; - } - } - if (currentScreenPixelGreen != targetScreenPixelGreen) { - // Adjust +2/-2 because we are running RGB555 at RGB565 - if (currentScreenPixelGreen < targetScreenPixelGreen) { - currentScreenPixelGreen += 0x0040; - } else { - currentScreenPixelGreen -= 0x0040; - } - } - if (currentScreenPixelBlue != targetScreenPixelBlue) { - if (currentScreenPixelBlue < targetScreenPixelBlue) { - currentScreenPixelBlue += 0x0001; - } else { - currentScreenPixelBlue -= 0x0001; - } - } - - uint16 v = currentScreenPixelRed | currentScreenPixelGreen | currentScreenPixelBlue; - *currentScreenPtr = v; - if (_vm->_isScreenDoubled) { - *(currentScreenPtr + 1) = v; - *(currentScreenPtr + 640) = v; - *(currentScreenPtr + 640 + 1) = v; - } - - pixelsChanged++; - } - - currentScreenPtr += _vm->_isScreenDoubled ? 2 : 1; - targetScreenPtr++; - } - - if (_vm->_isScreenDoubled) - currentScreenPtr += 640; - } - - // Too much considered dirty at the moment - if (_vm->_isScreenDoubled) - addDirtyRect(Common::Rect(0, 0, screenWidth * 2, screenHeight * 2)); - else - addDirtyRect(Common::Rect(0, 0, screenWidth, screenHeight)); - - events.pollEvents(); - events.delay(10 * speed); - } while ((pixelsChanged) && (!_vm->shouldQuit())); -} - -void Scalpel3DOScreen::blitFrom3DOcolorLimit(uint16 limitColor) { - uint16 *currentScreenPtr = (uint16 *)getPixels(); - uint16 *targetScreenPtr = (uint16 *)_backBuffer->getPixels(); - uint16 currentScreenPixel = 0; - - uint16 screenWidth = SHERLOCK_SCREEN_WIDTH; - uint16 screenHeight = SHERLOCK_SCREEN_HEIGHT; - uint16 screenX = 0; - uint16 screenY = 0; - - uint16 currentScreenPixelRed = 0; - uint16 currentScreenPixelGreen = 0; - uint16 currentScreenPixelBlue = 0; - - uint16 limitPixelRed = limitColor & 0xF800; - uint16 limitPixelGreen = limitColor & 0x07E0; - uint16 limitPixelBlue = limitColor & 0x001F; - - for (screenY = 0; screenY < screenHeight; screenY++) { - for (screenX = 0; screenX < screenWidth; screenX++) { - currentScreenPixel = *targetScreenPtr; - - currentScreenPixelRed = currentScreenPixel & 0xF800; - currentScreenPixelGreen = currentScreenPixel & 0x07E0; - currentScreenPixelBlue = currentScreenPixel & 0x001F; - - if (currentScreenPixelRed < limitPixelRed) - currentScreenPixelRed = limitPixelRed; - if (currentScreenPixelGreen < limitPixelGreen) - currentScreenPixelGreen = limitPixelGreen; - if (currentScreenPixelBlue < limitPixelBlue) - currentScreenPixelBlue = limitPixelBlue; - - uint16 v = currentScreenPixelRed | currentScreenPixelGreen | currentScreenPixelBlue; - *currentScreenPtr = v; - if (_vm->_isScreenDoubled) { - *(currentScreenPtr + 1) = v; - *(currentScreenPtr + 640) = v; - *(currentScreenPtr + 640 + 1) = v; - } - - currentScreenPtr += _vm->_isScreenDoubled ? 2 : 1; - targetScreenPtr++; - } - - if (_vm->_isScreenDoubled) - currentScreenPtr += 640; - } - - // Too much considered dirty at the moment - if (_vm->_isScreenDoubled) - addDirtyRect(Common::Rect(0, 0, screenWidth * 2, screenHeight * 2)); - else - addDirtyRect(Common::Rect(0, 0, screenWidth, screenHeight)); -} - -uint16 Scalpel3DOScreen::w() const { - return _vm->_isScreenDoubled ? _surface.w / 2 : _surface.w; -} - -uint16 Scalpel3DOScreen::h() const { - return _vm->_isScreenDoubled ? _surface.h / 2 : _surface.h; -} - -void Scalpel3DOScreen::rawBlitFrom(const Graphics::Surface &src, const Common::Point &pt) { - Common::Rect srcRect(0, 0, src.w, src.h); - Common::Rect destRect(pt.x, pt.y, pt.x + src.w, pt.y + src.h); - - addDirtyRect(destRect); - _surface.copyRectToSurface(src, destRect.left, destRect.top, srcRect); -} - } // End of namespace Scalpel } // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/scalpel_screen.h b/engines/sherlock/scalpel/scalpel_screen.h index cee33b8c6c..d9be29c8b2 100644 --- a/engines/sherlock/scalpel/scalpel_screen.h +++ b/engines/sherlock/scalpel/scalpel_screen.h @@ -61,44 +61,6 @@ public: void makeField(const Common::Rect &r); }; -class Scalpel3DOScreen : public ScalpelScreen { -protected: - /** - * Draws a sub-section of a surface at a given position within this surface - * Overriden for the 3DO to automatically double the size of everything to the underlying 640x400 surface - */ - virtual void blitFrom(const Graphics::Surface &src, const Common::Point &pt, const Common::Rect &srcBounds); - - /** - * Draws a surface at a given position within this surface with transparency - */ - virtual void transBlitFromUnscaled(const Graphics::Surface &src, const Common::Point &pt, bool flipped, - int overrideColor); -public: - Scalpel3DOScreen(SherlockEngine *vm) : ScalpelScreen(vm) {} - virtual ~Scalpel3DOScreen() {} - - /** - * Draws a sub-section of a surface at a given position within this surface - */ - void rawBlitFrom(const Graphics::Surface &src, const Common::Point &pt); - - /** - * Fade backbuffer 1 into screen (3DO RGB!) - */ - void fadeIntoScreen3DO(int speed); - - void blitFrom3DOcolorLimit(uint16 color); - - /** - * Fill a given area of the surface with a given color - */ - virtual void fillRect(const Common::Rect &r, uint color); - - virtual uint16 w() const; - virtual uint16 h() const; -}; - } // End of namespace Scalpel } // End of namespace Sherlock diff --git a/engines/sherlock/scalpel/scalpel_user_interface.cpp b/engines/sherlock/scalpel/scalpel_user_interface.cpp index 4e7cf5c378..6534f61a87 100644 --- a/engines/sherlock/scalpel/scalpel_user_interface.cpp +++ b/engines/sherlock/scalpel/scalpel_user_interface.cpp @@ -148,23 +148,24 @@ void ScalpelUserInterface::reset() { void ScalpelUserInterface::drawInterface(int bufferNum) { Screen &screen = *_vm->_screen; - const ImageFrame &src = (*_controlPanel)[0]; + const Graphics::Surface &src = (*_controlPanel)[0]._frame; int16 x = (!IS_3DO) ? 0 : UI_OFFSET_3DO; if (bufferNum & 1) { if (IS_3DO) screen._backBuffer1.fillRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BLACK); - screen._backBuffer1.transBlitFrom(src, Common::Point(x, CONTROLS_Y)); + screen._backBuffer1.SHtransBlitFrom(src, Common::Point(x, CONTROLS_Y)); } if (bufferNum & 2) { if (IS_3DO) screen._backBuffer2.fillRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT), BLACK); - screen._backBuffer2.transBlitFrom(src, Common::Point(x, CONTROLS_Y)); + screen._backBuffer2.SHtransBlitFrom(src, Common::Point(x, CONTROLS_Y)); } if (bufferNum == 3) - screen._backBuffer2.fillRect(0, INFO_LINE, SHERLOCK_SCREEN_WIDTH, INFO_LINE + 10, INFO_BLACK); + screen._backBuffer2.SHfillRect(Common::Rect(0, INFO_LINE, + SHERLOCK_SCREEN_WIDTH, INFO_LINE + 10), INFO_BLACK); } void ScalpelUserInterface::handleInput() { @@ -426,7 +427,7 @@ void ScalpelUserInterface::depressButton(int num) { offsetButton3DO(pt, num); ImageFrame &frame = (*_controls)[num]; - screen._backBuffer1.transBlitFrom(frame, pt); + screen._backBuffer1.SHtransBlitFrom(frame, pt); screen.slamArea(pt.x, pt.y, pt.x + frame._width, pt.y + frame._height); } @@ -442,7 +443,7 @@ void ScalpelUserInterface::restoreButton(int num) { events.setCursor(ARROW); // Restore the UI on the back buffer - screen._backBuffer1.blitFrom(screen._backBuffer2, pt, + screen._backBuffer1.SHblitFrom(screen._backBuffer2, pt, Common::Rect(pt.x, pt.y, pt.x + 90, pt.y + 19)); screen.slamArea(pt.x, pt.y, pt.x + frame.w, pt.y + frame.h); @@ -489,7 +490,7 @@ void ScalpelUserInterface::toggleButton(uint16 num) { ImageFrame &frame = (*_controls)[num]; Common::Point pt(MENU_POINTS[num][0], MENU_POINTS[num][1]); offsetButton3DO(pt, num); - screen._backBuffer1.transBlitFrom(frame, pt); + screen._backBuffer1.SHtransBlitFrom(frame, pt); screen.slamArea(pt.x, pt.y, pt.x + frame._width, pt.y + frame._height); } } else { @@ -501,7 +502,7 @@ void ScalpelUserInterface::toggleButton(uint16 num) { void ScalpelUserInterface::clearInfo() { if (_infoFlag) { - _vm->_screen->vgaBar(Common::Rect(IS_3DO ? 33 : 16, INFO_LINE, + _vm->_screen->vgaBar(Common::Rect(IS_3DO ? 33 : 16, INFO_LINE, SHERLOCK_SCREEN_WIDTH - (IS_3DO ? 33 : 19), INFO_LINE + 10), INFO_BLACK); _infoFlag = false; _oldLook = -1; @@ -605,74 +606,92 @@ void ScalpelUserInterface::lookScreen(const Common::Point &pt) { // If inventory is active and an item is selected for a Use or Give action if ((_menuMode == INV_MODE || _menuMode == USE_MODE || _menuMode == GIVE_MODE) && (inv._invMode == INVMODE_USE || inv._invMode == INVMODE_GIVE)) { - int width1 = 0, width2 = 0; - int x, width; + int width1 = 0, width2 = 0, width3 = 0; + int x; + if (inv._invMode == INVMODE_USE) { // Using an object - x = width = screen.stringWidth("Use "); + Common::String useText1 = FIXED(UserInterface_Use); + Common::String useText2; + Common::String useText3; - if (temp < 1000 && scene._bgShapes[temp]._aType != PERSON) - // It's not a person, so make it lowercase - tempStr.setChar(tolower(tempStr[0]), 0); + x = width1 = screen.stringWidth(useText1); - x += screen.stringWidth(tempStr); + if (temp < 1000 && scene._bgShapes[temp]._aType != PERSON) { + // It's not a person, so make it lowercase + switch (_vm->getLanguage()) { + case Common::DE_DEU: + case Common::ES_ESP: + // don't do this for German + Spanish version + break; + default: + tempStr.setChar(tolower(tempStr[0]), 0); + break; + } + } // If we're using an inventory object, add in the width // of the object name and the " on " if (_selector != -1) { - width1 = screen.stringWidth(inv[_selector]._name); - x += width1; - width2 = screen.stringWidth(" on "); + useText2 = inv[_selector]._name; + width2 = screen.stringWidth(useText2); x += width2; + + useText3 = Common::String::format(FIXED(UserInterface_UseOn), tempStr.c_str()); + + } else { + useText3 = tempStr; } + width3 = screen.stringWidth(useText3); + x += width3; + // If the line will be too long, keep cutting off characters // until the string will fit while (x > 280) { - x -= screen.charWidth(tempStr.lastChar()); - tempStr.deleteLastChar(); + x -= screen.charWidth(useText3.lastChar()); + useText3.deleteLastChar(); } int xStart = (SHERLOCK_SCREEN_WIDTH - x) / 2; screen.print(Common::Point(xStart, INFO_LINE + 1), - INFO_FOREGROUND, "Use "); + INFO_FOREGROUND, "%s", useText1.c_str()); if (_selector != -1) { - screen.print(Common::Point(xStart + width, INFO_LINE + 1), - TALK_FOREGROUND, "%s", inv[_selector]._name.c_str()); - screen.print(Common::Point(xStart + width + width1, INFO_LINE + 1), - INFO_FOREGROUND, " on "); - screen.print(Common::Point(xStart + width + width1 + width2, INFO_LINE + 1), - INFO_FOREGROUND, "%s", tempStr.c_str()); + screen.print(Common::Point(xStart + width1, INFO_LINE + 1), + TALK_FOREGROUND, "%s", useText2.c_str()); + screen.print(Common::Point(xStart + width1 + width2, INFO_LINE + 1), + INFO_FOREGROUND, "%s", useText3.c_str()); } else { - screen.print(Common::Point(xStart + width, INFO_LINE + 1), - INFO_FOREGROUND, "%s", tempStr.c_str()); + screen.print(Common::Point(xStart + width1, INFO_LINE + 1), + INFO_FOREGROUND, "%s", useText3.c_str()); } } else if (temp >= 0 && temp < 1000 && _selector != -1 && scene._bgShapes[temp]._aType == PERSON) { + Common::String giveText1 = FIXED(UserInterface_Give); + Common::String giveText2 = inv[_selector]._name; + Common::String giveText3 = Common::String::format(FIXED(UserInterface_GiveTo), tempStr.c_str()); + // Giving an object to a person - width1 = screen.stringWidth(inv[_selector]._name); - x = width = screen.stringWidth("Give "); - x += width1; - width2 = screen.stringWidth(" to "); + x = width1 = screen.stringWidth(giveText1); + width2 = screen.stringWidth(giveText2); x += width2; - x += screen.stringWidth(tempStr); + width3 = screen.stringWidth(giveText3); + x += width3; // Ensure string will fit on-screen while (x > 280) { - x -= screen.charWidth(tempStr.lastChar()); - tempStr.deleteLastChar(); + x -= screen.charWidth(giveText3.lastChar()); + giveText3.deleteLastChar(); } int xStart = (SHERLOCK_SCREEN_WIDTH - x) / 2; screen.print(Common::Point(xStart, INFO_LINE + 1), - INFO_FOREGROUND, "Give "); - screen.print(Common::Point(xStart + width, INFO_LINE + 1), - TALK_FOREGROUND, "%s", inv[_selector]._name.c_str()); - screen.print(Common::Point(xStart + width + width1, INFO_LINE + 1), - INFO_FOREGROUND, " to "); - screen.print(Common::Point(xStart + width + width1 + width2, INFO_LINE + 1), - INFO_FOREGROUND, "%s", tempStr.c_str()); + INFO_FOREGROUND, "%s", giveText1.c_str()); + screen.print(Common::Point(xStart + width1, INFO_LINE + 1), + TALK_FOREGROUND, "%s", giveText2.c_str()); + screen.print(Common::Point(xStart + width1 + width2, INFO_LINE + 1), + INFO_FOREGROUND, "%s", giveText3.c_str()); } } else { screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", tempStr.c_str()); @@ -909,7 +928,7 @@ void ScalpelUserInterface::doEnvControl() { } while (saves._savegameIndex < (MAX_SAVEGAME_SLOTS - ONSCREEN_FILES_COUNT) && moreKeys); } else if ((found == 5 && events._released) || _key == saves._hotkeyQuit) { clearWindow(); - screen.print(Common::Point(0, CONTROLS_Y + 20), INV_FOREGROUND, saves._fixedTextQuitGameQuestion.c_str()); + screen.print(Common::Point(0, CONTROLS_Y + 20), INV_FOREGROUND, "%s", saves._fixedTextQuitGameQuestion.c_str()); screen.vgaBar(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y + 10), BORDER_COLOR); screen.makeButton(Common::Rect(112, CONTROLS_Y, 160, CONTROLS_Y + 10), 136, saves._fixedTextQuitGameYes); @@ -1254,7 +1273,7 @@ void ScalpelUserInterface::doLookControl() { // Need to close the window and depress the Look button Common::Point pt(MENU_POINTS[0][0], MENU_POINTS[0][1]); offsetButton3DO(pt, 0); - screen._backBuffer2.blitFrom((*_controls)[0], pt); + screen._backBuffer2.SHblitFrom((*_controls)[0], pt); banishWindow(true); _windowBounds.top = CONTROLS_Y1; @@ -1278,14 +1297,14 @@ void ScalpelUserInterface::doLookControl() { // Looking at an inventory object // Backup the user interface Surface tempSurface(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT - CONTROLS_Y1); - tempSurface.blitFrom(screen._backBuffer2, Common::Point(0, 0), + tempSurface.SHblitFrom(screen._backBuffer2, Common::Point(0, 0), Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); inv.drawInventory(INVENTORY_DONT_DISPLAY); banishWindow(true); // Restore the ui - screen._backBuffer2.blitFrom(tempSurface, Common::Point(0, CONTROLS_Y1)); + screen._backBuffer2.SHblitFrom(tempSurface, Common::Point(0, CONTROLS_Y1)); _windowBounds.top = CONTROLS_Y1; _key = _oldKey = _hotkeyLook; @@ -1869,7 +1888,7 @@ void ScalpelUserInterface::journalControl() { // Reset the palette screen.setPalette(screen._cMap); - screen._backBuffer1.blitFrom(screen._backBuffer2); + screen._backBuffer1.SHblitFrom(screen._backBuffer2); scene.updateBackground(); screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); } @@ -1903,9 +1922,9 @@ void ScalpelUserInterface::printObjectDesc(const Common::String &str, bool first Common::Point pt(MENU_POINTS[0][0], MENU_POINTS[0][1]); offsetButton3DO(pt, 0); - tempSurface.blitFrom(screen._backBuffer2, Common::Point(0, 0), - Common::Rect(pt.x, pt.y, pt.x + tempSurface.w(), pt.y + tempSurface.h())); - screen._backBuffer2.transBlitFrom((*_controls)[0], pt); + tempSurface.SHblitFrom(screen._backBuffer2, Common::Point(0, 0), + Common::Rect(pt.x, pt.y, pt.x + tempSurface.width(), pt.y + tempSurface.height())); + screen._backBuffer2.SHtransBlitFrom((*_controls)[0], pt); banishWindow(1); events.setCursor(MAGNIFY); @@ -1915,7 +1934,7 @@ void ScalpelUserInterface::printObjectDesc(const Common::String &str, bool first _menuMode = LOOK_MODE; events.clearEvents(); - screen._backBuffer2.blitFrom(tempSurface, pt); + screen._backBuffer2.SHblitFrom(tempSurface, pt); } else { events.setCursor(ARROW); banishWindow(true); @@ -2053,9 +2072,9 @@ void ScalpelUserInterface::summonWindow(const Surface &bgSurface, bool slideUp) if (slideUp) { // Gradually slide up the display of the window - for (int idx = 1; idx <= bgSurface.h(); idx += 2) { - screen._backBuffer->blitFrom(bgSurface, Common::Point(0, SHERLOCK_SCREEN_HEIGHT - idx), - Common::Rect(0, 0, bgSurface.w(), idx)); + for (int idx = 1; idx <= bgSurface.height(); idx += 2) { + screen._backBuffer->SHblitFrom(bgSurface, Common::Point(0, SHERLOCK_SCREEN_HEIGHT - idx), + Common::Rect(0, 0, bgSurface.width(), idx)); screen.slamRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - idx, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); @@ -2063,21 +2082,21 @@ void ScalpelUserInterface::summonWindow(const Surface &bgSurface, bool slideUp) } } else { // Gradually slide down the display of the window - for (int idx = 1; idx <= bgSurface.h(); idx += 2) { - screen._backBuffer->blitFrom(bgSurface, - Common::Point(0, SHERLOCK_SCREEN_HEIGHT - bgSurface.h()), - Common::Rect(0, bgSurface.h() - idx, bgSurface.w(), bgSurface.h())); - screen.slamRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - bgSurface.h(), - SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT - bgSurface.h() + idx)); + for (int idx = 1; idx <= bgSurface.height(); idx += 2) { + screen._backBuffer->SHblitFrom(bgSurface, + Common::Point(0, SHERLOCK_SCREEN_HEIGHT - bgSurface.height()), + Common::Rect(0, bgSurface.height() - idx, bgSurface.width(), bgSurface.height())); + screen.slamRect(Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - bgSurface.height(), + SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT - bgSurface.height() + idx)); events.delay(10); } } // Final display of the entire window - screen._backBuffer->blitFrom(bgSurface, Common::Point(0, SHERLOCK_SCREEN_HEIGHT - bgSurface.h()), - Common::Rect(0, 0, bgSurface.w(), bgSurface.h())); - screen.slamArea(0, SHERLOCK_SCREEN_HEIGHT - bgSurface.h(), bgSurface.w(), bgSurface.h()); + screen._backBuffer->SHblitFrom(bgSurface, Common::Point(0, SHERLOCK_SCREEN_HEIGHT - bgSurface.height()), + Common::Rect(0, 0, bgSurface.width(), bgSurface.height())); + screen.slamArea(0, SHERLOCK_SCREEN_HEIGHT - bgSurface.height(), bgSurface.width(), bgSurface.height()); _windowOpen = true; } @@ -2088,10 +2107,10 @@ void ScalpelUserInterface::summonWindow(bool slideUp, int height) { // Extract the window that's been drawn on the back buffer Surface tempSurface(SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT - height); Common::Rect r(0, height, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); - tempSurface.blitFrom(screen._backBuffer1, Common::Point(0, 0), r); + tempSurface.SHblitFrom(screen._backBuffer1, Common::Point(0, 0), r); // Remove drawn window with original user interface - screen._backBuffer1.blitFrom(screen._backBuffer2, + screen._backBuffer1.SHblitFrom(screen._backBuffer2, Common::Point(0, height), r); // Display the window gradually on-screen @@ -2115,7 +2134,7 @@ void ScalpelUserInterface::banishWindow(bool slideUp) { Common::copy_backward(pSrc, pSrcEnd, pDest); // Restore lines from the ui in the secondary back buffer - screen._backBuffer1.blitFrom(screen._backBuffer2, + screen._backBuffer1.SHblitFrom(screen._backBuffer2, Common::Point(0, CONTROLS_Y), Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, CONTROLS_Y + idx)); @@ -2125,14 +2144,14 @@ void ScalpelUserInterface::banishWindow(bool slideUp) { } // Restore final two old lines - screen._backBuffer1.blitFrom(screen._backBuffer2, + screen._backBuffer1.SHblitFrom(screen._backBuffer2, Common::Point(0, SHERLOCK_SCREEN_HEIGHT - 2), Common::Rect(0, SHERLOCK_SCREEN_HEIGHT - 2, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); screen.slamArea(0, SHERLOCK_SCREEN_HEIGHT - 2, SHERLOCK_SCREEN_WIDTH, 2); } else { // Restore old area to completely erase window - screen._backBuffer1.blitFrom(screen._backBuffer2, + screen._backBuffer1.SHblitFrom(screen._backBuffer2, Common::Point(0, CONTROLS_Y), Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); screen.slamRect(Common::Rect(0, CONTROLS_Y, SHERLOCK_SCREEN_WIDTH, @@ -2152,7 +2171,7 @@ void ScalpelUserInterface::banishWindow(bool slideUp) { } // Show entire final area - screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(0, CONTROLS_Y1), + screen._backBuffer1.SHblitFrom(screen._backBuffer2, Common::Point(0, CONTROLS_Y1), Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); screen.slamRect(Common::Rect(0, CONTROLS_Y1, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); } @@ -2234,7 +2253,7 @@ void ScalpelUserInterface::checkUseAction(const UseType *use, const Common::Stri if (scene._goToScene != 1 && !printed && !talk._talkToAbort) { _infoFlag = true; clearInfo(); - screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "Done..."); + screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", FIXED(UserInterface_Done)); _menuCounter = 25; } } @@ -2244,9 +2263,9 @@ void ScalpelUserInterface::checkUseAction(const UseType *use, const Common::Stri clearInfo(); if (giveMode) { - screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "No, thank you."); + screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", FIXED(UserInterface_NoThankYou)); } else if (fixedTextActionId == kFixedTextAction_Invalid) { - screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "You can't do that."); + screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", FIXED(UserInterface_YouCantDoThat)); } else { Common::String errorMessage = fixedText.getActionMessage(fixedTextActionId, 0); screen.print(Common::Point(0, INFO_LINE + 1), INFO_FOREGROUND, "%s", errorMessage.c_str()); diff --git a/engines/sherlock/scalpel/tsage/logo.cpp b/engines/sherlock/scalpel/tsage/logo.cpp index 273d26df74..a885057f35 100644 --- a/engines/sherlock/scalpel/tsage/logo.cpp +++ b/engines/sherlock/scalpel/tsage/logo.cpp @@ -217,7 +217,7 @@ void Object::erase() { Screen &screen = *_vm->_screen; if (_visage.isLoaded() && !_oldBounds.isEmpty()) - screen.blitFrom(screen._backBuffer1, Common::Point(_oldBounds.left, _oldBounds.top), _oldBounds); + screen.SHblitFrom(screen._backBuffer1, Common::Point(_oldBounds.left, _oldBounds.top), _oldBounds); } void Object::update() { @@ -246,9 +246,9 @@ void Object::update() { _visage.getFrame(s, _frame); // Display the frame - _oldBounds = Common::Rect(_position.x, _position.y, _position.x + s.w(), _position.y + s.h()); + _oldBounds = Common::Rect(_position.x, _position.y, _position.x + s.width(), _position.y + s.height()); _oldBounds.translate(-s._centroid.x, -s._centroid.y); - screen.transBlitFrom(s, Common::Point(_oldBounds.left, _oldBounds.top)); + screen.SHtransBlitFrom(s, Common::Point(_oldBounds.left, _oldBounds.top)); } } @@ -652,7 +652,7 @@ void Logo::loadBackground() { screen.setPalette(palette); // Copy the surface to the screen - screen.blitFrom(screen._backBuffer1); + screen.SHblitFrom(screen._backBuffer1); } void Logo::fade(const byte palette[PALETTE_SIZE], int step) { diff --git a/engines/sherlock/scene.cpp b/engines/sherlock/scene.cpp index 6f9ef179a3..78d0cd862c 100644 --- a/engines/sherlock/scene.cpp +++ b/engines/sherlock/scene.cpp @@ -27,6 +27,7 @@ #include "sherlock/scalpel/scalpel_people.h" #include "sherlock/scalpel/scalpel_scene.h" #include "sherlock/scalpel/scalpel_screen.h" +#include "sherlock/scalpel/3do/scalpel_3do_screen.h" #include "sherlock/tattoo/tattoo.h" #include "sherlock/tattoo/tattoo_scene.h" #include "sherlock/tattoo/tattoo_user_interface.h" @@ -356,7 +357,7 @@ bool Scene::loadScene(const Common::String &filename) { if (IS_ROSE_TATTOO) { // Resize the screen if necessary int fullWidth = SHERLOCK_SCREEN_WIDTH + bgHeader._scrollSize; - if (screen._backBuffer1.w() != fullWidth) { + if (screen._backBuffer1.width() != fullWidth) { screen._backBuffer1.create(fullWidth, SHERLOCK_SCREEN_HEIGHT); screen._backBuffer2.create(fullWidth, SHERLOCK_SCREEN_HEIGHT); } @@ -649,7 +650,7 @@ bool Scene::loadScene(const Common::String &filename) { } // Backup the image and set the palette - screen._backBuffer2.blitFrom(screen._backBuffer1); + screen._backBuffer2.SHblitFrom(screen._backBuffer1); screen.setPalette(screen._cMap); delete rrmStream; @@ -996,12 +997,12 @@ bool Scene::loadScene(const Common::String &filename) { #if 0 // code to show the background - screen.blitFrom(screen._backBuffer1); + screen.SHblitFrom(screen._backBuffer1); _vm->_events->wait(10000); #endif // Backup the image - screen._backBuffer2.blitFrom(screen._backBuffer1); + screen._backBuffer2.SHblitFrom(screen._backBuffer1); } // Handle drawing any on-screen interface @@ -1236,7 +1237,7 @@ void Scene::transitionToScene() { // If the scene is capable of scrolling, set the current scroll so that whoever has control // of the scroll code is in the middle of the screen - if (screen._backBuffer1.w() > SHERLOCK_SCREEN_WIDTH) + if (screen._backBuffer1.width() > SHERLOCK_SCREEN_WIDTH) people[people._walkControl].centerScreenOnPerson(); for (uint objIdx = 0; objIdx < _bgShapes.size(); ++objIdx) { diff --git a/engines/sherlock/screen.cpp b/engines/sherlock/screen.cpp index 74da2a80ea..85d47c84dc 100644 --- a/engines/sherlock/screen.cpp +++ b/engines/sherlock/screen.cpp @@ -23,6 +23,8 @@ #include "sherlock/screen.h" #include "sherlock/sherlock.h" #include "sherlock/scalpel/scalpel_screen.h" +#include "sherlock/scalpel/3do/scalpel_3do_screen.h" +#include "sherlock/tattoo/tattoo_screen.h" #include "common/system.h" #include "common/util.h" #include "graphics/palette.h" @@ -31,17 +33,14 @@ namespace Sherlock { Screen *Screen::init(SherlockEngine *vm) { if (vm->getGameID() == GType_RoseTattoo) - return new Screen(vm); + return new Tattoo::TattooScreen(vm); else if (vm->getPlatform() == Common::kPlatform3DO) return new Scalpel::Scalpel3DOScreen(vm); else return new Scalpel::ScalpelScreen(vm); } -Screen::Screen(SherlockEngine *vm) : Surface(g_system->getWidth(), g_system->getHeight()), _vm(vm), - _backBuffer1(vm->getGameID() == GType_RoseTattoo ? 640 : 320, vm->getGameID() == GType_RoseTattoo ? 480 : 200), - _backBuffer2(vm->getGameID() == GType_RoseTattoo ? 640 : 320, vm->getGameID() == GType_RoseTattoo ? 480 : 200), - _backBuffer(&_backBuffer1) { +Screen::Screen(SherlockEngine *vm) : Graphics::Screen(), _vm(vm), _backBuffer(&_backBuffer1) { _transitionSeed = 1; _fadeStyle = false; Common::fill(&_cMap[0], &_cMap[PALETTE_SIZE], 0); @@ -58,37 +57,7 @@ Screen::Screen(SherlockEngine *vm) : Surface(g_system->getWidth(), g_system->get } Screen::~Screen() { - Fonts::free(); -} - -void Screen::update() { - // Merge the dirty rects - mergeDirtyRects(); - - // Loop through copying dirty areas to the physical screen - Common::List<Common::Rect>::iterator i; - for (i = _dirtyRects.begin(); i != _dirtyRects.end(); ++i) { - const Common::Rect &r = *i; - const byte *srcP = (const byte *)getBasePtr(r.left, r.top); - g_system->copyRectToScreen(srcP, _surface.pitch, r.left, r.top, - r.width(), r.height()); - } - - // Signal the physical screen to update - g_system->updateScreen(); - _dirtyRects.clear(); -} - -void Screen::makeAllDirty() { - addDirtyRect(Common::Rect(0, 0, this->w(), this->h())); -} - -void Screen::getPalette(byte palette[PALETTE_SIZE]) { - g_system->getPaletteManager()->grabPalette(palette, 0, PALETTE_COUNT); -} - -void Screen::setPalette(const byte palette[PALETTE_SIZE]) { - g_system->getPaletteManager()->setPalette(palette, 0, PALETTE_COUNT); + Fonts::freeFont(); } int Screen::equalizePalette(const byte palette[PALETTE_SIZE]) { @@ -124,7 +93,7 @@ void Screen::fadeToBlack(int speed) { } setPalette(tempPalette); - fillRect(Common::Rect(0, 0, _surface.w, _surface.h), 0); + fillRect(Common::Rect(0, 0, this->w, this->h), 0); } void Screen::fadeIn(const byte palette[PALETTE_SIZE], int speed) { @@ -136,59 +105,23 @@ void Screen::fadeIn(const byte palette[PALETTE_SIZE], int speed) { setPalette(palette); } -void Screen::addDirtyRect(const Common::Rect &r) { - _dirtyRects.push_back(r); - assert(r.width() > 0 && r.height() > 0); -} - -void Screen::mergeDirtyRects() { - Common::List<Common::Rect>::iterator rOuter, rInner; - - // Process the dirty rect list to find any rects to merge - for (rOuter = _dirtyRects.begin(); rOuter != _dirtyRects.end(); ++rOuter) { - rInner = rOuter; - while (++rInner != _dirtyRects.end()) { - - if ((*rOuter).intersects(*rInner)) { - // these two rectangles overlap or - // are next to each other - merge them - - unionRectangle(*rOuter, *rOuter, *rInner); - - // remove the inner rect from the list - _dirtyRects.erase(rInner); - - // move back to beginning of list - rInner = rOuter; - } - } - } -} - -bool Screen::unionRectangle(Common::Rect &destRect, const Common::Rect &src1, const Common::Rect &src2) { - destRect = src1; - destRect.extend(src2); - - return !destRect.isEmpty(); -} - void Screen::randomTransition() { Events &events = *_vm->_events; const int TRANSITION_MULTIPLIER = 0x15a4e35; - _dirtyRects.clear(); + clearDirtyRects(); assert(IS_SERRATED_SCALPEL); for (int idx = 0; idx <= 65535 && !_vm->shouldQuit(); ++idx) { _transitionSeed = _transitionSeed * TRANSITION_MULTIPLIER + 1; int offset = _transitionSeed & 0xFFFF; - if (offset < (this->w() * this->h())) + if (offset < (this->width() * this->height())) *((byte *)getPixels() + offset) = *((const byte *)_backBuffer->getPixels() + offset); if (idx != 0 && (idx % 300) == 0) { // Ensure there's a full screen dirty rect for the next frame update - if (_dirtyRects.empty()) - addDirtyRect(Common::Rect(0, 0, _surface.w, _surface.h)); + if (!isDirty()) + addDirtyRect(Common::Rect(0, 0, this->w, this->h)); events.pollEvents(); events.delay(1); @@ -196,7 +129,7 @@ void Screen::randomTransition() { } // Make sure everything has been transferred - blitFrom(*_backBuffer); + SHblitFrom(*_backBuffer); } void Screen::verticalTransition() { @@ -205,13 +138,13 @@ void Screen::verticalTransition() { byte table[640]; Common::fill(&table[0], &table[640], 0); - for (int yp = 0; yp < this->h(); ++yp) { - for (int xp = 0; xp < this->w(); ++xp) { - int temp = (table[xp] >= (this->h() - 3)) ? this->h() - table[xp] : + for (int yp = 0; yp < this->height(); ++yp) { + for (int xp = 0; xp < this->width(); ++xp) { + int temp = (table[xp] >= (this->height() - 3)) ? this->height() - table[xp] : _vm->getRandomNumber(3) + 1; if (temp) { - blitFrom(_backBuffer1, Common::Point(xp, table[xp]), + SHblitFrom(_backBuffer1, Common::Point(xp, table[xp]), Common::Rect(xp, table[xp], xp + 1, table[xp] + temp)); table[xp] += temp; } @@ -223,7 +156,7 @@ void Screen::verticalTransition() { void Screen::restoreBackground(const Common::Rect &r) { if (r.width() > 0 && r.height() > 0) - _backBuffer->blitFrom(_backBuffer2, Common::Point(r.left, r.top), r); + _backBuffer->SHblitFrom(_backBuffer2, Common::Point(r.left, r.top), r); } void Screen::slamArea(int16 xp, int16 yp, int16 width, int16 height) { @@ -254,11 +187,10 @@ void Screen::slamRect(const Common::Rect &r) { } if (srcRect.isValidRect()) - blitFrom(*_backBuffer, Common::Point(destRect.left, destRect.top), srcRect); + SHblitFrom(*_backBuffer, Common::Point(destRect.left, destRect.top), srcRect); } } - void Screen::flushImage(ImageFrame *frame, const Common::Point &pt, int16 *xp, int16 *yp, int16 *width, int16 *height) { Common::Point imgPos = pt + frame->_offset; @@ -335,7 +267,7 @@ void Screen::blockMove(const Common::Rect &r) { } void Screen::blockMove() { - blockMove(Common::Rect(0, 0, w(), h())); + blockMove(Common::Rect(0, 0, width(), height())); } void Screen::print(const Common::Point &pt, uint color, const char *formatStr, ...) { @@ -351,13 +283,13 @@ void Screen::print(const Common::Point &pt, uint color, const char *formatStr, . pos.y--; // Font is always drawing one line higher if (!pos.x) // Center text horizontally - pos.x = (this->w() - width) / 2; + pos.x = (this->width() - width) / 2; Common::Rect textBounds(pos.x, pos.y, pos.x + width, pos.y + _fontHeight); - if (textBounds.right > this->w()) - textBounds.moveTo(this->w() - width, textBounds.top); - if (textBounds.bottom > this->h()) - textBounds.moveTo(textBounds.left, this->h() - _fontHeight); + if (textBounds.right > this->width()) + textBounds.moveTo(this->width() - width, textBounds.top); + if (textBounds.bottom > this->height()) + textBounds.moveTo(textBounds.left, this->height() - _fontHeight); // Write out the string at the given position writeString(str, Common::Point(textBounds.left, textBounds.top), color); @@ -387,7 +319,8 @@ void Screen::vgaBar(const Common::Rect &r, int color) { } void Screen::setDisplayBounds(const Common::Rect &r) { - _sceneSurface.setPixels(_backBuffer1.getBasePtr(r.left, r.top), r.width(), r.height(), _backBuffer1.getPixelFormat()); + _sceneSurface.setPixels((byte *)_backBuffer1.getBasePtr(r.left, r.top), + r.width(), r.height(), _backBuffer1.format); _backBuffer = &_sceneSurface; } @@ -397,8 +330,8 @@ void Screen::resetDisplayBounds() { } Common::Rect Screen::getDisplayBounds() { - return (_backBuffer == &_sceneSurface) ? Common::Rect(0, 0, _sceneSurface.w(), _sceneSurface.h()) : - Common::Rect(0, 0, this->w(), this->h()); + return (_backBuffer == &_sceneSurface) ? Common::Rect(0, 0, _sceneSurface.width(), _sceneSurface.height()) : + Common::Rect(0, 0, this->width(), this->height()); } void Screen::synchronize(Serializer &s) { diff --git a/engines/sherlock/screen.h b/engines/sherlock/screen.h index 04a0c1e505..ceeb1297a3 100644 --- a/engines/sherlock/screen.h +++ b/engines/sherlock/screen.h @@ -25,52 +25,30 @@ #include "common/list.h" #include "common/rect.h" +#include "graphics/screen.h" +#include "sherlock/image_file.h" #include "sherlock/surface.h" #include "sherlock/resources.h" #include "sherlock/saveload.h" namespace Sherlock { -#define PALETTE_SIZE 768 -#define PALETTE_COUNT 256 #define VGA_COLOR_TRANS(x) ((x) * 255 / 63) #define BG_GREYSCALE_RANGE_END 229 #define BLACK 0 class SherlockEngine; -class Screen : public Surface { +class Screen : virtual public Graphics::Screen, virtual public Surface { private: - Common::List<Common::Rect> _dirtyRects; uint32 _transitionSeed; Surface _sceneSurface; // Rose Tattoo fields int _fadeBytesRead, _fadeBytesToRead; int _oldFadePercent; -private: - /** - * Merges together overlapping dirty areas of the screen - */ - void mergeDirtyRects(); - - /** - * Returns the union of two dirty area rectangles - */ - bool unionRectangle(Common::Rect &destRect, const Common::Rect &src1, const Common::Rect &src2); protected: SherlockEngine *_vm; - - /** - * Clear the current dirty rects list - */ - void clearDirtyRects() { _dirtyRects.clear(); } - - /** - * Adds a rectangle to the list of modified areas of the screen during the - * current frame - */ - virtual void addDirtyRect(const Common::Rect &r); public: Surface _backBuffer1, _backBuffer2; Surface *_backBuffer; @@ -86,26 +64,6 @@ public: virtual ~Screen(); /** - * Handles updating any dirty areas of the screen Surface object to the physical screen - */ - void update(); - - /** - * Makes the whole screen dirty - */ - void makeAllDirty(); - - /** - * Return the currently active palette - */ - void getPalette(byte palette[PALETTE_SIZE]); - - /** - * Set the palette - */ - void setPalette(const byte palette[PALETTE_SIZE]); - - /** * Fades from the currently active palette to the passed palette */ int equalizePalette(const byte palette[PALETTE_SIZE]); diff --git a/engines/sherlock/sherlock.h b/engines/sherlock/sherlock.h index b85321c385..d3b2d0cac8 100644 --- a/engines/sherlock/sherlock.h +++ b/engines/sherlock/sherlock.h @@ -63,9 +63,9 @@ enum GameType { GType_RoseTattoo = 1 }; -#define SHERLOCK_SCREEN_WIDTH _vm->_screen->w() -#define SHERLOCK_SCREEN_HEIGHT _vm->_screen->h() -#define SHERLOCK_SCENE_WIDTH _vm->_screen->_backBuffer1.w() +#define SHERLOCK_SCREEN_WIDTH _vm->_screen->width() +#define SHERLOCK_SCREEN_HEIGHT _vm->_screen->height() +#define SHERLOCK_SCENE_WIDTH _vm->_screen->_backBuffer1.width() #define SHERLOCK_SCENE_HEIGHT (IS_SERRATED_SCALPEL ? 138 : 480) #define SCENES_COUNT (IS_SERRATED_SCALPEL ? 63 : 101) #define MAX_BGSHAPES (IS_SERRATED_SCALPEL ? 64 : 150) diff --git a/engines/sherlock/sound.cpp b/engines/sherlock/sound.cpp index 5a9f0c2ec6..e5b1099123 100644 --- a/engines/sherlock/sound.cpp +++ b/engines/sherlock/sound.cpp @@ -120,7 +120,9 @@ byte Sound::decodeSample(byte sample, byte &reference, int16 &scale) { } bool Sound::playSound(const Common::String &name, WaitType waitType, int priority, const char *libraryFilename) { - stopSound(); + // Scalpel has only a single sound handle, so it must be stopped before starting a new sound + if (IS_SERRATED_SCALPEL) + stopSound(); Common::String filename = formFilename(name); diff --git a/engines/sherlock/surface.cpp b/engines/sherlock/surface.cpp index b7fc76325c..46304e1b2f 100644 --- a/engines/sherlock/surface.cpp +++ b/engines/sherlock/surface.cpp @@ -21,245 +21,24 @@ */ #include "sherlock/surface.h" -#include "sherlock/sherlock.h" -#include "sherlock/resources.h" -#include "common/system.h" -#include "graphics/palette.h" +#include "sherlock/fonts.h" namespace Sherlock { -Surface::Surface(uint16 width, uint16 height) : Fonts(), _freePixels(true) { - create(width, height); -} - -Surface::Surface() : Fonts(), _freePixels(false) { -} - -Surface::~Surface() { - if (_freePixels) - _surface.free(); -} - -void Surface::create(uint16 width, uint16 height) { - if (_freePixels) - _surface.free(); - - if (_vm->getPlatform() == Common::kPlatform3DO) { - _surface.create(width, height, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)); - } else { - _surface.create(width, height, Graphics::PixelFormat::createFormatCLUT8()); - } - _freePixels = true; -} - -Graphics::PixelFormat Surface::getPixelFormat() { - return _surface.format; -} - -void Surface::blitFrom(const Surface &src) { - blitFrom(src, Common::Point(0, 0)); -} - -void Surface::blitFrom(const ImageFrame &src) { - blitFrom(src._frame, Common::Point(0, 0)); -} - -void Surface::blitFrom(const Graphics::Surface &src) { - blitFrom(src, Common::Point(0, 0)); -} - -void Surface::blitFrom(const Surface &src, const Common::Point &pt) { - blitFrom(src, pt, Common::Rect(0, 0, src._surface.w, src._surface.h)); -} - -void Surface::blitFrom(const ImageFrame &src, const Common::Point &pt) { - blitFrom(src._frame, pt, Common::Rect(0, 0, src._frame.w, src._frame.h)); -} - -void Surface::blitFrom(const Graphics::Surface &src, const Common::Point &pt) { - blitFrom(src, pt, Common::Rect(0, 0, src.w, src.h)); -} - -void Surface::blitFrom(const Graphics::Surface &src, const Common::Point &pt, const Common::Rect &srcBounds) { - Common::Rect srcRect = srcBounds; - Common::Rect destRect(pt.x, pt.y, pt.x + srcRect.width(), pt.y + srcRect.height()); - - if (srcRect.isValidRect() && clip(srcRect, destRect)) { - // Surface is at least partially or completely on-screen - addDirtyRect(destRect); - _surface.copyRectToSurface(src, destRect.left, destRect.top, srcRect); - } -} - -void Surface::blitFrom(const ImageFrame &src, const Common::Point &pt, const Common::Rect &srcBounds) { - blitFrom(src._frame, pt, srcBounds); -} - -void Surface::blitFrom(const Surface &src, const Common::Point &pt, const Common::Rect &srcBounds) { - blitFrom(src._surface, pt, srcBounds); -} - -void Surface::transBlitFrom(const ImageFrame &src, const Common::Point &pt, - bool flipped, int overrideColor, int scaleVal) { - Common::Point drawPt(pt.x + src.sDrawXOffset(scaleVal), pt.y + src.sDrawYOffset(scaleVal)); - transBlitFrom(src._frame, drawPt, flipped, overrideColor, scaleVal); -} - -void Surface::transBlitFrom(const Surface &src, const Common::Point &pt, - bool flipped, int overrideColor, int scaleVal) { - const Graphics::Surface &s = src._surface; - transBlitFrom(s, pt, flipped, overrideColor, scaleVal); -} - -void Surface::transBlitFrom(const Graphics::Surface &src, const Common::Point &pt, - bool flipped, int overrideColor, int scaleVal) { - if (scaleVal == SCALE_THRESHOLD) { - transBlitFromUnscaled(src, pt, flipped, overrideColor); - return; - } - - int destWidth = src.w * SCALE_THRESHOLD / scaleVal; - int destHeight = src.h * SCALE_THRESHOLD / scaleVal; - - // Loop through drawing output lines - for (int destY = pt.y, scaleYCtr = 0; destY < (pt.y + destHeight); ++destY, scaleYCtr += scaleVal) { - if (destY < 0 || destY >= this->h()) - continue; - const byte *srcLine = (const byte *)src.getBasePtr(0, scaleYCtr / SCALE_THRESHOLD); - byte *destLine = (byte *)getBasePtr(pt.x, destY); - - // Loop through drawing individual rows - for (int xCtr = 0, scaleXCtr = 0; xCtr < destWidth; ++xCtr, scaleXCtr += scaleVal) { - int destX = pt.x + xCtr; - if (destX < 0 || destX >= this->w()) - continue; - - byte srcVal = srcLine[flipped ? src.w - scaleXCtr / SCALE_THRESHOLD - 1 : scaleXCtr / SCALE_THRESHOLD]; - if (srcVal != TRANSPARENCY) - destLine[xCtr] = srcVal; - } - } - - // Mark the affected area - addDirtyRect(Common::Rect(pt.x, pt.y, pt.x + destWidth, pt.y + destHeight)); -} - -void Surface::transBlitFromUnscaled(const Graphics::Surface &src, const Common::Point &pt, - bool flipped, int overrideColor) { - Common::Rect drawRect(0, 0, src.w, src.h); - Common::Rect destRect(pt.x, pt.y, pt.x + src.w, pt.y + src.h); - - // Clip the display area to on-screen - if (!clip(drawRect, destRect)) - // It's completely off-screen - return; - - if (flipped) - drawRect = Common::Rect(src.w - drawRect.right, drawRect.top, - src.w - drawRect.left, drawRect.bottom); - - Common::Point destPt(destRect.left, destRect.top); - addDirtyRect(Common::Rect(destPt.x, destPt.y, destPt.x + drawRect.width(), - destPt.y + drawRect.height())); - - switch (src.format.bytesPerPixel) { - case 1: - // 8-bit palettized: Draw loop - assert(_surface.format.bytesPerPixel == 1); // Security check - for (int yp = 0; yp < drawRect.height(); ++yp) { - const byte *srcP = (const byte *)src.getBasePtr( - flipped ? drawRect.right - 1 : drawRect.left, drawRect.top + yp); - byte *destP = (byte *)getBasePtr(destPt.x, destPt.y + yp); - - for (int xp = 0; xp < drawRect.width(); ++xp, ++destP) { - if (*srcP != TRANSPARENCY) - *destP = overrideColor ? overrideColor : *srcP; - - srcP = flipped ? srcP - 1 : srcP + 1; - } - } - break; - case 2: - // 3DO 15-bit RGB565: Draw loop - assert(_surface.format.bytesPerPixel == 2); // Security check - for (int yp = 0; yp < drawRect.height(); ++yp) { - const uint16 *srcP = (const uint16 *)src.getBasePtr( - flipped ? drawRect.right - 1 : drawRect.left, drawRect.top + yp); - uint16 *destP = (uint16 *)getBasePtr(destPt.x, destPt.y + yp); - - for (int xp = 0; xp < drawRect.width(); ++xp, ++destP) { - if (*srcP) // RGB 0, 0, 0 -> transparent on 3DO - *destP = *srcP; // overrideColor ? overrideColor : *srcP; - - srcP = flipped ? srcP - 1 : srcP + 1; - } - } - break; - default: - error("Surface: unsupported bytesperpixel"); - break; - } +Surface::Surface() : Graphics::ManagedSurface(), Fonts() { } -void Surface::fillRect(int x1, int y1, int x2, int y2, uint color) { - fillRect(Common::Rect(x1, y1, x2, y2), color); -} - -void Surface::fillRect(const Common::Rect &r, uint color) { - _surface.fillRect(r, color); - addDirtyRect(r); -} - -void Surface::fill(uint color) { - fillRect(Common::Rect(_surface.w, _surface.h), color); -} - -bool Surface::clip(Common::Rect &srcBounds, Common::Rect &destBounds) { - if (destBounds.left >= w() || destBounds.top >= h() || - destBounds.right <= 0 || destBounds.bottom <= 0) - return false; - - // Clip the bounds if necessary to fit on-screen - if (destBounds.right > w()) { - srcBounds.right -= destBounds.right - w(); - destBounds.right = w(); - } - - if (destBounds.bottom > h()) { - srcBounds.bottom -= destBounds.bottom - h(); - destBounds.bottom = h(); - } - - if (destBounds.top < 0) { - srcBounds.top += -destBounds.top; - destBounds.top = 0; - } - - if (destBounds.left < 0) { - srcBounds.left += -destBounds.left; - destBounds.left = 0; - } - - return true; -} - -void Surface::clear() { - fillRect(Common::Rect(0, 0, w(), h()), 0); -} - -void Surface::free() { - if (_freePixels) { - _surface.free(); - _freePixels = false; - } +Surface::Surface(int width, int height) : Graphics::ManagedSurface(width, height), + Fonts() { + create(width, height); } -void Surface::setPixels(byte *pixels, int width, int height, Graphics::PixelFormat pixelFormat) { - _surface.format = pixelFormat; - _surface.w = width; - _surface.h = height; - _surface.pitch = width * pixelFormat.bytesPerPixel; - _surface.setPixels(pixels); +void Surface::setPixels(byte *pixelsPtr, int sizeX, int sizeY, const Graphics::PixelFormat &pixFormat) { + Graphics::ManagedSurface::setPixels(pixelsPtr); + this->format = pixFormat; + this->w = sizeX; + this->h = sizeY; + this->pitch = sizeX * pixFormat.bytesPerPixel; } void Surface::writeString(const Common::String &str, const Common::Point &pt, uint overrideColor) { @@ -278,4 +57,21 @@ void Surface::writeFancyString(const Common::String &str, const Common::Point &p writeString(str, Common::Point(pt.x + 1, pt.y + 1), overrideColor2); } +void Surface::SHtransBlitFrom(const ImageFrame &src, const Common::Point &pt, + bool flipped, int overrideColor, int scaleVal) { + Common::Point drawPt(pt.x + src.sDrawXOffset(scaleVal), pt.y + src.sDrawYOffset(scaleVal)); + SHtransBlitFrom(src._frame, drawPt, flipped, overrideColor, scaleVal); +} + +void Surface::SHtransBlitFrom(const Graphics::Surface &src, const Common::Point &pt, + bool flipped, int overrideColor, int scaleVal) { + Common::Rect srcRect(0, 0, src.w, src.h); + Common::Rect destRect(pt.x, pt.y, pt.x + src.w * SCALE_THRESHOLD / scaleVal, + pt.y + src.h * SCALE_THRESHOLD / scaleVal); + + Graphics::ManagedSurface::transBlitFrom(src, srcRect, destRect, TRANSPARENCY, + flipped, overrideColor); +} + + } // End of namespace Sherlock diff --git a/engines/sherlock/surface.h b/engines/sherlock/surface.h index 378c9be9cd..648b121852 100644 --- a/engines/sherlock/surface.h +++ b/engines/sherlock/surface.h @@ -20,165 +20,101 @@ * */ -#ifndef SHERLOCK_GRAPHICS_H -#define SHERLOCK_GRAPHICS_H +#ifndef SHERLOCK_SURFACE_H +#define SHERLOCK_SURFACE_H #include "common/rect.h" #include "common/platform.h" -#include "graphics/surface.h" +#include "graphics/managed_surface.h" #include "sherlock/fonts.h" +#include "sherlock/image_file.h" namespace Sherlock { #define SCALE_THRESHOLD 0x100 #define TRANSPARENCY 255 -struct ImageFrame; - -class Surface: public Fonts { -private: - bool _freePixels; - - /** - * Copy a surface into this one - */ - void blitFrom(const Graphics::Surface &src); -protected: - Graphics::Surface _surface; - - /** - * Clips the given source bounds so the passed destBounds will be entirely on-screen - */ - bool clip(Common::Rect &srcBounds, Common::Rect &destBounds); - - /** - * Base method stub for signalling dirty rect areas - */ - virtual void addDirtyRect(const Common::Rect &r) {} - - /** - * Draws a sub-section of a surface at a given position within this surface - */ - virtual void blitFrom(const Graphics::Surface &src, const Common::Point &pt, const Common::Rect &srcBounds); - +/** + * Implements a descendent surface that combines both a managed surface and the font + * drawing code. It also introduces a series of drawing method stubs that the 3DO + * Serrated Scalpel screen overrides to implement sprite doubling + */ +class Surface: virtual public Graphics::ManagedSurface, public Fonts { +public: /** - * Draws a surface at a given position within this surface with transparency + * Constructor */ - virtual void transBlitFromUnscaled(const Graphics::Surface &src, const Common::Point &pt, bool flipped, - int overrideColor); -public: - Surface(uint16 width, uint16 height); Surface(); - virtual ~Surface(); - + /** - * Sets up an internal surface with the specified dimensions that will be automatically freed - * when the surface object is destroyed + * Constructor */ - void create(uint16 width, uint16 height); - - Graphics::PixelFormat getPixelFormat(); + Surface(int width, int height); /** - * Copy a surface into this one + * Set the surface details */ - void blitFrom(const Surface &src); + void setPixels(byte *pixelsPtr, int sizeX, int sizeY, const Graphics::PixelFormat &pixFormat); /** - * Copy an image frame into this surface + * Draws a surface on this surface */ - void blitFrom(const ImageFrame &src); + virtual void SHblitFrom(const Graphics::Surface &src) { + Graphics::ManagedSurface::blitFrom(src); + } /** * Draws a surface at a given position within this surface */ - void blitFrom(const Surface &src, const Common::Point &pt); - - /** - * Copy an image frame onto this surface at a given position - */ - void blitFrom(const ImageFrame &src, const Common::Point &pt); + virtual void SHblitFrom(const Graphics::Surface &src, const Common::Point &destPos) { + Graphics::ManagedSurface::blitFrom(src, destPos); + } /** * Draws a sub-section of a surface at a given position within this surface */ - void blitFrom(const Surface &src, const Common::Point &pt, const Common::Rect &srcBounds); - - /** - * Copy a sub-area of a source image frame into this surface at a given position - */ - void blitFrom(const ImageFrame &src, const Common::Point &pt, const Common::Rect &srcBounds); - - /** - * Draws a surface at a given position within this surface - */ - void blitFrom(const Graphics::Surface &src, const Common::Point &pt); + virtual void SHblitFrom(const Graphics::Surface &src, const Common::Point &destPos, const Common::Rect &srcBounds) { + Graphics::ManagedSurface::blitFrom(src, srcBounds, destPos); + } /** * Draws an image frame at a given position within this surface with transparency */ - void transBlitFrom(const ImageFrame &src, const Common::Point &pt, - bool flipped = false, int overrideColor = 0, int scaleVal = 256); - - /** - * Draws a surface at a given position within this surface with transparency - */ - void transBlitFrom(const Surface &src, const Common::Point &pt, - bool flipped = false, int overrideColor = 0, int scaleVal = 256); + void SHtransBlitFrom(const ImageFrame &src, const Common::Point &pt, + bool flipped = false, int overrideColor = 0, int scaleVal = SCALE_THRESHOLD); /** - * Draws a surface at a given position within this surface with transparency + * Draws an image frame at a given position within this surface with transparency */ - void transBlitFrom(const Graphics::Surface &src, const Common::Point &pt, - bool flipped = false, int overrideColor = 0, int scaleVal = 256); + void SHtransBlitFrom(const Graphics::Surface &src, const Common::Point &pt, + bool flipped = false, int overrideColor = 0, int scaleVal = SCALE_THRESHOLD); /** * Fill a given area of the surface with a given color */ - void fillRect(int x1, int y1, int x2, int y2, uint color); - - /** - * Fill a given area of the surface with a given color - */ - virtual void fillRect(const Common::Rect &r, uint color); - - void fill(uint color); - - /** - * Clear the surface - */ - void clear(); + virtual void SHfillRect(const Common::Rect &r, uint color) { + Graphics::ManagedSurface::fillRect(r, color); + } /** - * Free the underlying surface + * Return the width of the surface */ - void free(); - + virtual uint16 width() const { return this->w; } + /** - * Returns true if the surface is empty + * Return the height of the surface */ - bool empty() const { return _surface.getPixels() == nullptr; } + virtual uint16 height() const { return this->h; } /** - * Set the pixels for the surface to an existing data block + * Draws the given string into the back buffer using the images stored in _font */ - void setPixels(byte *pixels, int width, int height, Graphics::PixelFormat format); - + void writeString(const Common::String &str, const Common::Point &pt, uint overrideColor); + /** - * Draws the given string into the back buffer using the images stored in _font + * Draws a fancy version of the given string at the given position */ - virtual void writeString(const Common::String &str, const Common::Point &pt, uint overrideColor); void writeFancyString(const Common::String &str, const Common::Point &pt, uint overrideColor1, uint overrideColor2); - - inline virtual uint16 w() const { return _surface.w; } - inline virtual uint16 h() const { return _surface.h; } - inline const byte *getPixels() const { return (const byte *)_surface.getPixels(); } - inline byte *getPixels() { return (byte *)_surface.getPixels(); } - inline byte *getBasePtr(int x, int y) { return (byte *)_surface.getBasePtr(x, y); } - inline const byte *getBasePtr(int x, int y) const { return (const byte *)_surface.getBasePtr(x, y); } - inline Graphics::Surface &getRawSurface() { return _surface; } - inline void hLine(int x, int y, int x2, uint color) { _surface.hLine(x, y, x2, color); } - inline void vLine(int x, int y, int y2, uint color) { _surface.vLine(x, y, y2, color); } }; } // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/tattoo_darts.cpp b/engines/sherlock/tattoo/tattoo_darts.cpp index 512358933d..cbc3ea1fe8 100644 --- a/engines/sherlock/tattoo/tattoo_darts.cpp +++ b/engines/sherlock/tattoo/tattoo_darts.cpp @@ -83,7 +83,9 @@ void Darts::playDarts(GameType gameType) { int numHits = 0; bool gameOver = false; bool done = false; - const char *const NUM_HITS_STR[3] = { "a", FIXED(Double), FIXED(Triple) }; + + // Set the game mode + _gameType = gameType; screen.setFont(7); _spacing = screen.fontHeight() + 2; @@ -161,51 +163,84 @@ void Darts::playDarts(GameType gameType) { // Show scores showStatus(playerNum); - screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(_dartInfo.left, _dartInfo.top - 1), + screen._backBuffer2.SHblitFrom(screen._backBuffer1, Common::Point(_dartInfo.left, _dartInfo.top - 1), Common::Rect(_dartInfo.left, _dartInfo.top - 1, _dartInfo.right, _dartInfo.bottom - 1)); - screen.print(Common::Point(_dartInfo.left, _dartInfo.top), 0, "%s # %d", FIXED(Dart), idx + 1); + screen.print(Common::Point(_dartInfo.left, _dartInfo.top), 0, FIXED(DartsCurrentDart), idx + 1); if (_gameType == GAME_301) { - if (_vm->getLanguage() == Common::FR_FRA) - screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing), 0, - "%s %s: %d", FIXED(Scored), FIXED(Points), lastDart); - else - screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing), 0, - "%s %d %s", FIXED(Scored), lastDart, FIXED(Points)); + // "Scored x points" + Common::String scoredPoints; + + // original treated 1 point and multiple points the same. Wrote "Scored 1 points" + if (lastDart == 1) { + scoredPoints = Common::String::format(FIXED(DartsScoredPoint), lastDart); + } else { + scoredPoints = Common::String::format(FIXED(DartsScoredPoints), lastDart); + } + + screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing), 0, "%s", scoredPoints.c_str()); } else { - if (lastDart != 25) - screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing), 0, - "%s %s %d", FIXED(Hit), NUM_HITS_STR[numHits - 1], lastDart); - else - screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing), 0, - "%s %s %s", FIXED(Hit), NUM_HITS_STR[numHits - 1], FIXED(Bullseye)); + Common::String hitText; + + if (lastDart != 25) { + // Regular hit + switch (numHits) { + case 1: // "Hit a X" + hitText = Common::String::format(FIXED(DartsHitSingle), lastDart); + break; + case 2: // "Hit double X" + hitText = Common::String::format(FIXED(DartsHitDouble), lastDart); + break; + case 3: // "Hit triple X" + hitText = Common::String::format(FIXED(DartsHitTriple), lastDart); + break; + default: + break; + } + } else { + // Bullseye + switch (numHits) { + case 1: + hitText = Common::String(FIXED(DartsHitSingleBullseye)); + break; + case 2: + hitText = Common::String(FIXED(DartsHitDoubleBullseye)); + break; + case 3: + hitText = Common::String(FIXED(DartsHitTripleBullseye)); + break; + default: + break; + } + } + screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing), 0, "%s", hitText.c_str()); } if (score != 0 && playerNum == 0 && !gameOver) screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 3), 0, - "%s", FIXED(PressAKey)); + "%s", FIXED(DartsPressKey)); if (gameOver) { screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 3), - 0, "%s", FIXED(GameOver)); + 0, "%s", FIXED(DartsGameOver)); if (playerNum == 0) { screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 4), 0, - "%s %s", FIXED(Holmes), FIXED(Wins)); + FIXED(DartsWins), FIXED(DartsPlayerHolmes)); _vm->setFlagsDirect(531); } else { screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 4), 0, - "%s %s!", _opponent.c_str(), FIXED(Wins)); + FIXED(DartsWins), _opponent.c_str()); _vm->setFlagsDirect(530); } screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 5), 0, - "%s", FIXED(PressAKey)); + "%s", FIXED(DartsPressKey)); done = true; idx = 10; } else if (_gameType == GAME_301 && score < 0) { screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 2), 0, - "%s!", FIXED(Busted)); + "%s!", FIXED(DartsBusted)); // End turn idx = 10; @@ -224,13 +259,19 @@ void Darts::playDarts(GameType gameType) { done = true; break; } + // Wait for keypress + do { + events.pollEventsAndWait(); + events.setButtonState(); + } while (!_vm->shouldQuit() && !events.kbHit() && !events._pressed); } else { - events.wait(20); + events.wait(40); } - screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_dartInfo.left, _dartInfo.top - 1), + // Clears the status part of the board + screen._backBuffer1.SHblitFrom(screen._backBuffer2, Common::Point(_dartInfo.left, _dartInfo.top - 1), Common::Rect(_dartInfo.left, _dartInfo.top - 1, _dartInfo.right, _dartInfo.bottom - 1)); - screen.blitFrom(screen._backBuffer1); + screen.SHblitFrom(screen._backBuffer1); } playerNum ^= 1; @@ -238,9 +279,9 @@ void Darts::playDarts(GameType gameType) { ++_roundNum; if (!done) { - screen._backBuffer2.blitFrom((*_dartBoard)[0], Common::Point(0, 0)); - screen._backBuffer1.blitFrom(screen._backBuffer2); - screen.blitFrom(screen._backBuffer2); + screen._backBuffer2.SHblitFrom((*_dartBoard)[0], Common::Point(0, 0)); + screen._backBuffer1.SHblitFrom(screen._backBuffer2); + screen.SHblitFrom(screen._backBuffer2); } } @@ -303,7 +344,7 @@ void Darts::initDarts() { } } - _opponent = FIXED(Jock); + _opponent = FIXED(DartsPlayerJock); } void Darts::loadDarts() { @@ -327,9 +368,9 @@ void Darts::loadDarts() { delete stream; // Load the initial background - screen._backBuffer1.blitFrom((*_dartBoard)[0], Common::Point(0, 0)); - screen._backBuffer2.blitFrom(screen._backBuffer1); - screen.blitFrom(screen._backBuffer1); + screen._backBuffer1.SHblitFrom((*_dartBoard)[0], Common::Point(0, 0)); + screen._backBuffer2.SHblitFrom(screen._backBuffer1); + screen.SHblitFrom(screen._backBuffer1); } void Darts::closeDarts() { @@ -346,7 +387,7 @@ void Darts::showNames(int playerNum) { byte color; color = playerNum == 0 ? PLAYER_COLOR : DART_COLOR_FORE; - screen.print(Common::Point(STATUS_INFO_X, STATUS_INFO_Y), 0, "%s", FIXED(Holmes)); + screen.print(Common::Point(STATUS_INFO_X, STATUS_INFO_Y), 0, "%s", FIXED(DartsPlayerHolmes)); screen._backBuffer1.fillRect(Common::Rect(STATUS_INFO_X, STATUS_INFO_Y + _spacing + 1, STATUS_INFO_X + 50, STATUS_INFO_Y + _spacing + 3), color); screen.fillRect(Common::Rect(STATUS_INFO_X, STATUS_INFO_Y + _spacing + 1, @@ -359,14 +400,14 @@ void Darts::showNames(int playerNum) { screen.fillRect(Common::Rect(STATUS2_INFO_X, STATUS_INFO_Y + _spacing + 1, STATUS2_INFO_X + 50, STATUS_INFO_Y + _spacing + 3), color); - screen._backBuffer2.blitFrom(screen._backBuffer1); + screen._backBuffer2.SHblitFrom(screen._backBuffer1); } void Darts::showStatus(int playerNum) { Screen &screen = *_vm->_screen; - const char *const CRICKET_SCORE_NAME[7] = { "20", "19", "18", "17", "16", "15", FIXED(Bull) }; + const char *const CRICKET_SCORE_NAME[7] = { "20", "19", "18", "17", "16", "15", FIXED(DartsBull) }; - screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 10), + screen._backBuffer1.SHblitFrom(screen._backBuffer2, Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 10), Common::Rect(STATUS_INFO_X, STATUS_INFO_Y + 10, STATUS_INFO_X + STATUS_INFO_WIDTH, STATUS_INFO_Y + STATUS_INFO_HEIGHT - 10)); screen.print(Common::Point(STATUS_INFO_X + 30, STATUS_INFO_Y + _spacing + 4), 0, "%d", _score1); @@ -374,10 +415,15 @@ void Darts::showStatus(int playerNum) { screen.print(Common::Point(STATUS2_INFO_X + 30, STATUS_INFO_Y + _spacing + 4), 0, "%d", _score2); int temp = (_gameType == GAME_CRICKET) ? STATUS_INFO_Y + 10 * _spacing + 5 : STATUS_INFO_Y + 55; - screen.print(Common::Point(STATUS_INFO_X, temp), 0, "%s: %d", FIXED(Round), _roundNum); + + // "Round: x" + Common::String dartsRoundStatus = Common::String::format(FIXED(DartsCurrentRound), _roundNum); + screen.print(Common::Point(STATUS_INFO_X, temp), 0, "%s", dartsRoundStatus.c_str()); if (_gameType == GAME_301) { - screen.print(Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 75), 0, "%s: %d", FIXED(TurnTotal), _roundScore); + // "Turn Total: x" + Common::String dartsTotalPoints = Common::String::format(FIXED(DartsCurrentTotalPoints), _roundScore); + screen.print(Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 75), 0, "%s", dartsTotalPoints.c_str()); } else { // Show cricket scores for (int x = 0; x < 7; ++x) { @@ -402,7 +448,7 @@ void Darts::showStatus(int playerNum) { } } - screen.blitFrom(screen._backBuffer1, Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 10), + screen.SHblitFrom(screen._backBuffer1, Common::Point(STATUS_INFO_X, STATUS_INFO_Y + 10), Common::Rect(STATUS_INFO_X, STATUS_INFO_Y + 10, STATUS_INFO_X + STATUS_INFO_WIDTH, STATUS_INFO_Y + STATUS_INFO_HEIGHT - 10)); } @@ -412,7 +458,7 @@ void Darts::erasePowerBars() { // Erase the old power bars and replace them with empty ones screen._backBuffer1.fillRect(Common::Rect(DART_BAR_VX, DART_HEIGHT_Y, DART_BAR_VX + 9, DART_HEIGHT_Y + DART_BAR_SIZE), 0); - screen._backBuffer1.transBlitFrom((*_dartGraphics)[0], Common::Point(DART_BAR_VX - 1, DART_HEIGHT_Y - 1)); + screen._backBuffer1.SHtransBlitFrom((*_dartGraphics)[0], Common::Point(DART_BAR_VX - 1, DART_HEIGHT_Y - 1)); screen.slamArea(DART_BAR_VX - 1, DART_HEIGHT_Y - 1, 10, DART_BAR_SIZE + 2); } @@ -452,7 +498,7 @@ int Darts::doPowerBar(const Common::Point &pt, byte color, int goToPower, int or } screen._backBuffer1.hLine(pt.x, pt.y + DART_BAR_SIZE- 1 - idx, pt.x + 8, color); - screen._backBuffer1.transBlitFrom((*_dartGraphics)[0], Common::Point(pt.x - 1, pt.y - 1)); + screen._backBuffer1.SHtransBlitFrom((*_dartGraphics)[0], Common::Point(pt.x - 1, pt.y - 1)); screen.slamArea(pt.x, pt.y + DART_BAR_SIZE - 1 - idx, 8, 2); if (!(idx % 8)) @@ -499,7 +545,7 @@ int Darts::drawHand(int goToPower, int computer) { break; } - screen._backBuffer1.transBlitFrom((*hands)[0], pt); + screen._backBuffer1.SHtransBlitFrom((*hands)[0], pt); screen.slamArea(pt.x - 1, pt.y, _handSize.x + 1, _handSize.y); screen.restoreBackground(Common::Rect(pt.x, pt.y, pt.x + _handSize.x, pt.y + _handSize.y)); @@ -586,7 +632,7 @@ void Darts::drawDartThrow(const Common::Point &dartPos, int computer) { _handSize.y = hands[idx]._offset.y + hands[idx]._height; int handCy = SHERLOCK_SCREEN_HEIGHT - _handSize.y; - screen._backBuffer1.transBlitFrom(hands[idx], Common::Point(_handX, handCy)); + screen._backBuffer1.SHtransBlitFrom(hands[idx], Common::Point(_handX, handCy)); screen.slamArea(_handX, handCy, _handSize.x + 1, _handSize.y); screen.slamArea(handOCx, handOCy, handOldxSize, handOldySize); screen.restoreBackground(Common::Rect(_handX, handCy, _handX + _handSize.x, handCy + _handSize.y)); @@ -608,7 +654,7 @@ void Darts::drawDartThrow(const Common::Point &dartPos, int computer) { ocy = drawPos.y = cy - (*_dartGraphics)[dartNum]._height; // Draw dart - screen._backBuffer1.transBlitFrom((*_dartGraphics)[dartNum], drawPos); + screen._backBuffer1.SHtransBlitFrom((*_dartGraphics)[dartNum], drawPos); if (drawPos.x < 0) { xSize += drawPos.x; @@ -630,7 +676,7 @@ void Darts::drawDartThrow(const Common::Point &dartPos, int computer) { // Flush the erased dart area screen.slamArea(oldDrawPos.x, oldDrawPos.y, oldxSize, oldySize); - screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(drawPos.x, drawPos.y), + screen._backBuffer1.SHblitFrom(screen._backBuffer2, Common::Point(drawPos.x, drawPos.y), Common::Rect(drawPos.x, drawPos.y, drawPos.x + xSize, drawPos.y + ySize)); oldDrawPos.x = drawPos.x; @@ -651,7 +697,7 @@ void Darts::drawDartThrow(const Common::Point &dartPos, int computer) { if (oldDrawPos.x != -1) screen.slamArea(oldDrawPos.x, oldDrawPos.y, oldxSize, oldySize); - screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(drawPos.x, drawPos.y), + screen._backBuffer1.SHblitFrom(screen._backBuffer2, Common::Point(drawPos.x, drawPos.y), Common::Rect(drawPos.x, drawPos.y, drawPos.x + xSize, drawPos.y + ySize)); cx = dartPos.x; @@ -677,7 +723,7 @@ void Darts::drawDartThrow(const Common::Point &dartPos, int computer) { ocx = drawPos.x = cx - (*_dartGraphics)[dartNum]._width / 2; ocy = drawPos.y = cy - (*_dartGraphics)[dartNum]._height; - screen._backBuffer1.transBlitFrom((*_dartGraphics)[dartNum], Common::Point(drawPos.x, drawPos.y)); + screen._backBuffer1.SHtransBlitFrom((*_dartGraphics)[dartNum], Common::Point(drawPos.x, drawPos.y)); if (drawPos.x < 0) { xSize += drawPos.x; @@ -699,7 +745,7 @@ void Darts::drawDartThrow(const Common::Point &dartPos, int computer) { screen.slamArea(oldDrawPos.x, oldDrawPos.y, oldxSize, oldySize); if (idx != 23) - screen._backBuffer1.blitFrom(screen._backBuffer2, drawPos, + screen._backBuffer1.SHblitFrom(screen._backBuffer2, drawPos, Common::Rect(drawPos.x, drawPos.y, drawPos.x + xSize, drawPos.y + ySize)); // erase dart events.wait(1); @@ -716,8 +762,8 @@ void Darts::drawDartThrow(const Common::Point &dartPos, int computer) { ySize = (*_dartGraphics)[dartNum]._height; // Draw final dart on the board - screen._backBuffer1.transBlitFrom((*_dartGraphics)[dartNum], Common::Point(ocx, ocy)); - screen._backBuffer2.transBlitFrom((*_dartGraphics)[dartNum], Common::Point(ocx, ocy)); + screen._backBuffer1.SHtransBlitFrom((*_dartGraphics)[dartNum], Common::Point(ocx, ocy)); + screen._backBuffer2.SHtransBlitFrom((*_dartGraphics)[dartNum], Common::Point(ocx, ocy)); screen.slamArea(ocx, ocy, xSize, ySize); } @@ -887,13 +933,16 @@ int Darts::throwDart(int dartNum, int computer) { events.clearEvents(); erasePowerBars(); - screen.print(Common::Point(_dartInfo.left, _dartInfo.top), 0, "%s # %d", FIXED(Dart), dartNum); + + // "Dart # x" + Common::String currentDart = Common::String::format(FIXED(DartsCurrentDart), dartNum); + screen.print(Common::Point(_dartInfo.left, _dartInfo.top), 0, "%s", currentDart.c_str()); drawDartsLeft(dartNum, computer); if (!computer) { - screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing), 0, "%s", FIXED(HitAKey)); - screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 2), 0, "%s", FIXED(ToStart)); + screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing), 0, "%s", FIXED(DartsStartPressKey1)); + screen.print(Common::Point(_dartInfo.left, _dartInfo.top + _spacing * 2), 0, "%s", FIXED(DartsStartPressKey2)); } if (!computer) { @@ -907,9 +956,9 @@ int Darts::throwDart(int dartNum, int computer) { } drawDartsLeft(dartNum + 1, computer); - screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_dartInfo.left, _dartInfo.top - 1), + screen._backBuffer1.SHblitFrom(screen._backBuffer2, Common::Point(_dartInfo.left, _dartInfo.top - 1), Common::Rect(_dartInfo.left, _dartInfo.top - 1, _dartInfo.right, _dartInfo.bottom - 1)); - screen.blitFrom(screen._backBuffer1, Common::Point(_dartInfo.left, _dartInfo.top - 1), + screen.SHblitFrom(screen._backBuffer1, Common::Point(_dartInfo.left, _dartInfo.top - 1), Common::Rect(_dartInfo.left, _dartInfo.top - 1, _dartInfo.right, _dartInfo.bottom - 1)); if (computer) { @@ -931,7 +980,7 @@ int Darts::throwDart(int dartNum, int computer) { height = 101 - height; // Copy power bars to the secondary back buffer - screen._backBuffer2.blitFrom(screen._backBuffer1, Common::Point(DART_BAR_VX - 1, DART_HEIGHT_Y - 1), + screen._backBuffer2.SHblitFrom(screen._backBuffer1, Common::Point(DART_BAR_VX - 1, DART_HEIGHT_Y - 1), Common::Rect(DART_BAR_VX - 1, DART_HEIGHT_Y - 1, DART_BAR_VX - 1 + 10, DART_HEIGHT_Y - 1 + DART_BAR_SIZE + 2)); @@ -975,14 +1024,14 @@ void Darts::drawDartsLeft(int dartNum, int computer) { const int DART_X2[3] = { 393, 441, 502 }; const int DART_Y2[3] = { 373, 373, 373 }; - screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(DART_X1[0], DART_Y1[0]), + screen._backBuffer1.SHblitFrom(screen._backBuffer2, Common::Point(DART_X1[0], DART_Y1[0]), Common::Rect(DART_X1[0], DART_Y1[0], SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); for (int idx = 2; idx >= dartNum - 1; --idx) { if (computer) - screen._backBuffer1.transBlitFrom((*_dartsLeft)[idx + 3], Common::Point(DART_X2[idx], DART_Y2[idx])); + screen._backBuffer1.SHtransBlitFrom((*_dartsLeft)[idx + 3], Common::Point(DART_X2[idx], DART_Y2[idx])); else - screen._backBuffer1.transBlitFrom((*_dartsLeft)[idx], Common::Point(DART_X1[idx], DART_Y1[idx])); + screen._backBuffer1.SHtransBlitFrom((*_dartsLeft)[idx], Common::Point(DART_X1[idx], DART_Y1[idx])); } screen.slamArea(DART_X1[0], DART_Y1[0], SHERLOCK_SCREEN_WIDTH - DART_X1[0], SHERLOCK_SCREEN_HEIGHT - DART_Y1[0]); diff --git a/engines/sherlock/tattoo/tattoo_fixed_text.cpp b/engines/sherlock/tattoo/tattoo_fixed_text.cpp index 38acd78aba..e41cb1f5eb 100644 --- a/engines/sherlock/tattoo/tattoo_fixed_text.cpp +++ b/engines/sherlock/tattoo/tattoo_fixed_text.cpp @@ -66,25 +66,27 @@ static const char *const fixedTextEN[] = { "Search Backwards", "Search Forwards", "Text Not Found !", - + // Darts "Holmes", "Jock", "Bull", - "Round", - "Turn Total", - "Dart", - "to start", + "Round: %d", + "Turn Total: %d", + "Dart # %d", "Hit a key", + "To start", "Press a key", - "bullseye", // ?? - "GAME OVER", - "BUSTED", - "Wins", - "Scored", - "points", - "Hit %s %d", - "Double", - "Triple", + "GAME OVER!", + "BUSTED!", + "%s Wins", + "Scored %d point", // original: treated 1 point and multiple points the same ("Scored 1 points") + "Scored %d points", + "Hit a %d", + "Hit double %d", + "Hit triple %d", + "Hit a bullseye", + "Hit double bullseye", + "Hit triple bullseye", "Apply", "Water", @@ -106,6 +108,8 @@ static const char *const fixedTextEN[] = { "No", "Enter Password", "Going East", // correct password, was not and should not to be translated + "Watson's Journal", + "Journal saved as journal.txt", // SH2: People names "Sherlock Holmes", "Dr. Watson", @@ -235,7 +239,7 @@ static const char *const fixedTextDE[] = { "Tagebuch", "Tasche", "Optionen", - "L\224osen", + "L\224sen", "mit", "Keine Wirkung...", "Diese Person wei\341 im Augenblick nichts zu berichten.", @@ -243,31 +247,33 @@ static const char *const fixedTextDE[] = { "Seite %d", "Schlie\341en", - "Lessen", // <-- - "In Datei sichern", + "Durchsuchen", // original: "Lessen" + "In Datei sichern", // original: "Speichern" "Suche abbrechen", "R\201ckw\204rts suchen ", - "Vorwarts suchen ", + "Vorw\204rts suchen ", "Text nicht gefunden", - + // Darts "Holmes", "Jock", "Bull", "Runde: %d", "Gesamt: %d", "Pfeil # %d", - "zum Starten", "Taste dr\201cken", + "zum Starten", "Taste dr\201cken", - "Bullseye", // ?? "SPIEL BEENDET!", "VERLOREN!", - "Gewinnt!", // "Holmes Gewinnt!", "%s Gewinnt!" + "%s gewinnt!", // "Holmes Gewinnt!", "%s Gewinnt!", original: "%s Gewinnt!" + "Erzielte %d Punkt", // original: treated 1 point and multiple points the same ("Scored 1 points") "Erzielte %d Punkte", - "Punkte", // ?? - "Treffer %s %d", - "Doppel", - "Dreifach", + "%d getroffen", // original: "Treffer %s %d" + "Doppel %d getroffen", // original: see above + "Dreifach %d getroffen", // original: see above + "Bullseye getroffen", + "Doppel Bullseye getroffen", + "Dreifach Bullseye getroffen", "Benutze", "Wasser", @@ -289,6 +295,8 @@ static const char *const fixedTextDE[] = { "Nein", "Pa\341wort eingeben", "Going East", // correct password, was not and should not to be translated + "Watsons Tagebuch", + "Journal gespeichert als journal.txt", // SH2: People names "Sherlock Holmes", // note: People names were not translated in the German interpreter "Dr. Watson", @@ -426,31 +434,33 @@ static const char *const fixedTextFR[] = { "Page %d", "Fermer", - "Lessen", // <-- + "Rechercher", "Sauvegarder", "Annuler ", "Chercher avant", "Chercher apr\212s", "Texte introuvable !", - + // Darts "Holmes", "Jock", "Bull", "Tour: %d", "Total: %d", "Fl\202chette # %d", - "pour commencer", "Appuyez sur C", + "pour commencer", "Appuyez sur C", - "Bullseye", // ?? "FIN DE LA PARTIE!", // original: "Fin de la partie!" "FIASCO!", - "Gagnant!", // "Holmes Gagnant!", "%s Gagnant!" - "Total des points: %d", - "Punkte", // ?? - "Treffer %s %d", - "double", - "triple", + "%s a gagn\202!", // "Holmes Gagnant!", "%s Gagnant!" + "Rapporte %d point", // original: treated 1 point and multiple points the same ("Scored 1 points") + "Rapporte %d points", // original: Total des points: %d", + "Touche un %d", // original: ??? + "Touche double %d", + "Touche triple %d", + "Touche le Bullseye", + "Touche double Bullseye", + "Touche triple Bullseye", "Mouillez", "Puis", @@ -472,6 +482,8 @@ static const char *const fixedTextFR[] = { "Non", "Entrez le mot de passe", "Going East", // correct password, was not and should not to be translated + "Journal de Watson", + "Journal enregistree comme journal.txt", // SH2: People names "Sherlock Holmes", "Dr. Watson", @@ -610,7 +622,7 @@ static const char *const fixedTextES[] = { "P\240gina %d", "Cerrar Diario", - "Lessen", // <-- not included?!?! + "Buscar en Diario", "Salvar en Archivo", "Detener B\243squeda", "Buscar Hacia Atr\240s", @@ -623,18 +635,20 @@ static const char *const fixedTextES[] = { "Vuelta: %d", "Total del Turno: %d", "Dardo # %d", - "para empezar", "Pulsa una tecla", + "para empezar", "Pulsa una tecla", - "Golpe %s ojo de buey", // ?? "FIN DE LA PARTIDA!", "ROTO!", - "Gana!", // "Holmes Gana!", "%s Gana!" + "%s gana!", // "Holmes Gana!", "%s Gana!", original: "%s Gana!" + "Puntuado %d punto", // original: treated 1 point and multiple points the same ("Scored 1 points") "Puntuado %d puntos", - "puntos", // ?? - "Golpe %s %d", - "doble", - "triple", + "Golpe un %d", + "Gople doble %d", + "Gople triple %d", + "Golpe un ojo de buey", + "Gople doble ojo de buey", + "Gople triple ojo de buey", "aplicar", "Agua", @@ -656,6 +670,8 @@ static const char *const fixedTextES[] = { "Non", "Introducir Palabra Clave", "Vas al Este", // correct password, was translated in Spanish version (???) + "Diario de Watson", + "Diario guarda como journal.txt", // SH2: People names "Sherlock Holmes", "Dr. Watson", @@ -755,7 +771,6 @@ static const char *const fixedTextES[] = { "Cochero" }; -// TODO: There also was a Spanish version of Sherlock Holmes 2 static const FixedTextLanguageEntry fixedTextLanguages[] = { { Common::DE_DEU, fixedTextDE }, { Common::ES_ESP, fixedTextES }, diff --git a/engines/sherlock/tattoo/tattoo_fixed_text.h b/engines/sherlock/tattoo/tattoo_fixed_text.h index 48d237db3c..7dbe13bbb3 100644 --- a/engines/sherlock/tattoo/tattoo_fixed_text.h +++ b/engines/sherlock/tattoo/tattoo_fixed_text.h @@ -68,24 +68,26 @@ enum FixedTextId { kFixedText_SearchForwards, kFixedText_TextNotFound, - kFixedText_Holmes, - kFixedText_Jock, - kFixedText_Bull, - kFixedText_Round, - kFixedText_TurnTotal, - kFixedText_Dart, - kFixedText_ToStart, - kFixedText_HitAKey, - kFixedText_PressAKey, - kFixedText_Bullseye, - kFixedText_GameOver, - kFixedText_Busted, - kFixedText_Wins, - kFixedText_Scored, - kFixedText_Points, - kFixedText_Hit, - kFixedText_Double, - kFixedText_Triple, + kFixedText_DartsPlayerHolmes, + kFixedText_DartsPlayerJock, + kFixedText_DartsBull, + kFixedText_DartsCurrentRound, + kFixedText_DartsCurrentTotalPoints, + kFixedText_DartsCurrentDart, + kFixedText_DartsStartPressKey1, + kFixedText_DartsStartPressKey2, + kFixedText_DartsPressKey, + kFixedText_DartsGameOver, + kFixedText_DartsBusted, + kFixedText_DartsWins, + kFixedText_DartsScoredPoint, + kFixedText_DartsScoredPoints, + kFixedText_DartsHitSingle, + kFixedText_DartsHitDouble, + kFixedText_DartsHitTriple, + kFixedText_DartsHitSingleBullseye, + kFixedText_DartsHitDoubleBullseye, + kFixedText_DartsHitTripleBullseye, kFixedText_Apply, kFixedText_Water, @@ -107,6 +109,8 @@ enum FixedTextId { kFixedText_No, kFixedText_EnterPassword, kFixedText_CorrectPassword, + kFixedText_WatsonsJournal, + kFixedText_JournalSaved, // SH2: People names kFixedText_People_SherlockHolmes, kFixedText_People_DrWatson, diff --git a/engines/sherlock/tattoo/tattoo_journal.cpp b/engines/sherlock/tattoo/tattoo_journal.cpp index e836cca620..918887f320 100644 --- a/engines/sherlock/tattoo/tattoo_journal.cpp +++ b/engines/sherlock/tattoo/tattoo_journal.cpp @@ -20,6 +20,7 @@ * */ +#include "common/savefile.h" #include "sherlock/tattoo/tattoo_journal.h" #include "sherlock/tattoo/tattoo_fixed_text.h" #include "sherlock/tattoo/tattoo_scene.h" @@ -64,7 +65,7 @@ void TattooJournal::show() { delete stream; // Set screen to black, and set background - screen._backBuffer1.blitFrom((*_journalImages)[0], Common::Point(0, 0)); + screen._backBuffer1.SHblitFrom((*_journalImages)[0], Common::Point(0, 0)); screen.empty(); screen.setPalette(palette); @@ -130,7 +131,7 @@ void TattooJournal::handleKeyboardEvents() { events.warpMouse(Common::Point(r.left + r.width() / 3 - 10, r.top + screen.fontHeight() + 2)); } else { if (_selector == JH_CLOSE) - _selector = JH_PRINT; + _selector = JH_SAVE; else --_selector; @@ -232,7 +233,7 @@ void TattooJournal::handleKeyboardEvents() { if (_selector == JH_NONE) { events.warpMouse(Common::Point(r.left + r.width() / 3 - 10, r.top + screen.fontHeight() + 2)); } else { - if (_selector == JH_PRINT) + if (_selector == JH_SAVE) _selector = JH_NONE; else ++_selector; @@ -378,8 +379,13 @@ void TattooJournal::handleButtons() { break; } - case JH_PRINT: - // Print Journal - not implemented in ScummVM + case JH_SAVE: + // Save journal to file + disableControls(); + saveJournal(); + drawFrame(); + drawJournal(0, 0); + screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); break; default: @@ -455,7 +461,7 @@ void TattooJournal::loadLocations() { void TattooJournal::drawFrame() { Screen &screen = *_vm->_screen; - screen._backBuffer1.blitFrom((*_journalImages)[0], Common::Point(0, 0)); + screen._backBuffer1.SHblitFrom((*_journalImages)[0], Common::Point(0, 0)); drawControls(0); } @@ -480,10 +486,10 @@ void TattooJournal::drawControls(int mode) { screen._backBuffer1.fillRect(inner, MENU_BACKGROUND); // Draw the four corners of the info box - screen._backBuffer1.transBlitFrom(images[0], Common::Point(r.left, r.top)); - screen._backBuffer1.transBlitFrom(images[1], Common::Point(r.right - images[1]._width, r.top)); - screen._backBuffer1.transBlitFrom(images[1], Common::Point(r.left, r.bottom - images[1]._height)); - screen._backBuffer1.transBlitFrom(images[1], Common::Point(r.right - images[1]._width, r.bottom - images[1]._height)); + screen._backBuffer1.SHtransBlitFrom(images[0], Common::Point(r.left, r.top)); + screen._backBuffer1.SHtransBlitFrom(images[1], Common::Point(r.right - images[1]._width, r.top)); + screen._backBuffer1.SHtransBlitFrom(images[1], Common::Point(r.left, r.bottom - images[1]._height)); + screen._backBuffer1.SHtransBlitFrom(images[1], Common::Point(r.right - images[1]._width, r.bottom - images[1]._height)); // Draw the top of the info box screen._backBuffer1.hLine(r.left + images[0]._width, r.top, r.right - images[0]._height, INFO_TOP); @@ -507,8 +513,8 @@ void TattooJournal::drawControls(int mode) { // Draw the sides of the separator bar above the scroll bar int yp = r.top + screen.fontHeight() + 7; - screen._backBuffer1.transBlitFrom(images[4], Common::Point(r.left, yp - 1)); - screen._backBuffer1.transBlitFrom(images[5], Common::Point(r.right - images[5]._width, yp - 1)); + screen._backBuffer1.SHtransBlitFrom(images[4], Common::Point(r.left, yp - 1)); + screen._backBuffer1.SHtransBlitFrom(images[5], Common::Point(r.right - images[5]._width, yp - 1)); // Draw the bar above the scroll bar screen._backBuffer1.hLine(r.left + images[4]._width, yp, r.right - images[5]._width, INFO_TOP); @@ -519,8 +525,8 @@ void TattooJournal::drawControls(int mode) { // Draw the Bars separating the Journal Commands int xp = r.right / 3; for (int idx = 0; idx < 2; ++idx) { - screen._backBuffer1.transBlitFrom(images[6], Common::Point(xp - 2, r.top + 1)); - screen._backBuffer1.transBlitFrom(images[7], Common::Point(xp - 2, yp - 1)); + screen._backBuffer1.SHtransBlitFrom(images[6], Common::Point(xp - 2, r.top + 1)); + screen._backBuffer1.SHtransBlitFrom(images[7], Common::Point(xp - 2, yp - 1)); screen._backBuffer1.hLine(xp - 1, r.top + 4, yp - 2, INFO_TOP); screen._backBuffer1.hLine(xp, r.top + 4, yp - 2, INFO_MIDDLE); @@ -592,7 +598,7 @@ void TattooJournal::highlightJournalControls(bool slamIt) { } // See if the Search was selected, but is not available - if (_journal.empty() && (_selector == JH_SEARCH || _selector == JH_PRINT)) + if (_journal.empty() && (_selector == JH_SEARCH || _selector == JH_SAVE)) _selector = JH_NONE; if (_selector == JH_PAGE_LEFT && _oldSelector == JH_PAGE_RIGHT) @@ -618,7 +624,10 @@ void TattooJournal::highlightJournalControls(bool slamIt) { color, "%s", FIXED(SearchJournal)); xp += r.width() / 3; - color = INFO_BOTTOM; + if (!_journal.empty()) + color = (_selector == JH_SAVE) ? COMMAND_HIGHLIGHTED : INFO_TOP; + else + color = INFO_BOTTOM; screen.gPrint(Common::Point(xp - screen.stringWidth(FIXED(SaveJournal)) / 2, r.top + 5), color, "%s", FIXED(SaveJournal)); @@ -737,7 +746,7 @@ void TattooJournal::disableControls() { // Print the Journal commands int xp = r.left + r.width() / 6; - for (int idx = 0; idx < 2; ++idx) { + for (int idx = 0; idx < 3; ++idx) { screen.gPrint(Common::Point(xp - screen.stringWidth(JOURNAL_COMMANDS[idx]) / 2, r.top + 5), INFO_BOTTOM, "%s", JOURNAL_COMMANDS[idx]); @@ -770,7 +779,7 @@ int TattooJournal::getFindName(bool printError) { // Backup the area under the text entry Surface bgSurface(r.width() - 6, screen.fontHeight()); - bgSurface.blitFrom(screen._backBuffer1, Common::Point(0, 0), Common::Rect(r.left + 3, cursorY, + bgSurface.SHblitFrom(screen._backBuffer1, Common::Point(0, 0), Common::Rect(r.left + 3, cursorY, r.right - 3, cursorY + screen.fontHeight())); if (printError) { @@ -801,7 +810,7 @@ int TattooJournal::getFindName(bool printError) { events.clearEvents(); // Restore the text background - screen._backBuffer1.blitFrom(bgSurface, Common::Point(r.left, cursorY)); + screen._backBuffer1.SHblitFrom(bgSurface, Common::Point(r.left, cursorY)); // If there was a name already entered, copy it to name and display it if (!_find.empty()) { @@ -837,7 +846,7 @@ int TattooJournal::getFindName(bool printError) { } else { // Erase cursor by restoring background and writing current text - screen._backBuffer1.blitFrom(bgSurface, Common::Point(r.left + 3, cursorY)); + screen._backBuffer1.SHblitFrom(bgSurface, Common::Point(r.left + 3, cursorY)); screen.gPrint(Common::Point(r.left + screen.widestChar() + 3, cursorY), COMMAND_HIGHLIGHTED, "%s", name.c_str()); screen.slamArea(r.left + 3, cursorY, r.width() - 3, screen.fontHeight()); } @@ -880,11 +889,11 @@ int TattooJournal::getFindName(bool printError) { } else { if (keyState.keycode & Common::KBD_SHIFT) { if (_selector == JH_CLOSE) - _selector = JH_PRINT; + _selector = JH_SAVE; else --_selector; } else { - if (_selector == JH_PRINT) + if (_selector == JH_SAVE) _selector = JH_CLOSE; else ++_selector; @@ -903,7 +912,7 @@ int TattooJournal::getFindName(bool printError) { } // Redraw the text - screen._backBuffer1.blitFrom(bgSurface, Common::Point(r.left + 3, cursorY)); + screen._backBuffer1.SHblitFrom(bgSurface, Common::Point(r.left + 3, cursorY)); screen.gPrint(Common::Point(r.left + screen.widestChar() + 3, cursorY), COMMAND_HIGHLIGHTED, "%s", name.c_str()); screen.slamArea(r.left + 3, cursorY, r.right - 3, screen.fontHeight()); @@ -949,6 +958,173 @@ void TattooJournal::record(int converseNum, int statementNum, bool replyOnly) { Journal::record(converseNum, statementNum, replyOnly); } +void TattooJournal::saveJournal() { + Talk &talk = *_vm->_talk; + Common::OutSaveFile *file = g_system->getSavefileManager()->openForSaving("journal.txt", false); + int tempIndex = _index; + + _index = 0; + talk._converseNum = -1; + + file->writeString(" "); + file->writeString(FIXED(WatsonsJournal)); + file->writeString("\n\n"); + + // Loop through saving each page of the journal + do { + // Print a single talk file + Common::String text; + int line = 0; + + // Copy all of the talk files entries into one big string + do { + if (_lines[line].hasPrefix("@")) { + text += Common::String(_lines[line].c_str() + 1); + if ((line + 1) < (int)_lines.size() && _lines[line + 1].hasPrefix("@")) + text += "\n"; + else + text += " "; + } else { + text += _lines[line]; + text += " "; + + // Check for embedded location names embedded in comment fields, + // which show up as a blank line with the next line starting + // with a '@'. We have to add a line break here because the '@' handler + // previously assumes that they're always following a blank line + + if ((_lines[line].empty() || _lines[line] == " ") + && (line + 1) < (int)_lines.size() && _lines[line + 1].hasPrefix("@")) + text += "\n"; + } + + ++line; + } while (line < (int)_lines.size()); + + // Now write out the text in 80 column lines + do { + if (text.size() > 80) { + const char *msgP = text.c_str() + 80; + + if (Common::String(text.c_str(), msgP).contains("\n")) { + // The 80 characters contain a carriage return, + // so we can print out that line + const char *cr = strchr(text.c_str(), '\n'); + file->writeString(Common::String(text.c_str(), cr)); + text = Common::String(cr + 1); + } else { + // Move backwards to find a word break + while (*msgP != ' ') + --msgP; + + // Write out the figured out line + file->writeString(Common::String(text.c_str(), msgP)); + + // Remove the line that was written out + while (*msgP == ' ') + ++msgP; + text = Common::String(msgP); + } + } else { + // The remainder of the string is under 80 characters. + // Check to see if has any line ends + if (text.contains("\n")) { + // Write out the line up to the carraige return + const char *cr = strchr(text.c_str(), '\n'); + file->writeString(Common::String(text.c_str(), cr)); + text = Common::String(cr + 1); + } else { + // Write out the final line + file->writeString(text); + text = ""; + } + } + + file->writeString("\n"); + } while (!text.empty()); + + // Move to next talk file + do { + ++_index; + + if (_index < (int)_journal.size()) + loadJournalFile(false); + } while (_index < (int)_journal.size() && _lines.empty()); + + // Don't immediately exit if there are no loaded lines for + // the next page, since it's probably a stealth file and + // can simply be skipped + file->writeString("\n"); + } while (_index < (int)_journal.size()); + + file->finalize(); + delete file; + + // Free up any talk file in memory + talk.freeTalkVars(); + + // Show the message for the journal having been saved + showSavedDialog(); + + // Reset the previous settings of the journal + _index = tempIndex; +} + +void TattooJournal::showSavedDialog() { + TattooEngine &vm = *(TattooEngine *)_vm; + Events &events = *vm._events; + Screen &screen = *vm._screen; + TattooUserInterface &ui = *(TattooUserInterface *)vm._ui; + ImageFile &images = *ui._interfaceImages; + disableControls(); + + Common::String msg = FIXED(JournalSaved); + Common::Rect inner(0, 0, screen.stringWidth(msg), screen.fontHeight()); + inner.moveTo((SHERLOCK_SCREEN_WIDTH - inner.width()) / 2, + (SHERLOCK_SCREEN_HEIGHT / 2) - (screen.fontHeight() / 2)); + + Common::Rect r = inner; + r.grow(10); + + if (vm._transparentMenus) + ui.makeBGArea(r); + else + screen._backBuffer1.fillRect(r, MENU_BACKGROUND); + + // Draw the four corners of the info box + screen._backBuffer1.transBlitFrom(images[0], Common::Point(r.left, r.top)); + screen._backBuffer1.transBlitFrom(images[1], Common::Point(r.right - images[1]._width, r.top)); + screen._backBuffer1.transBlitFrom(images[1], Common::Point(r.left, r.bottom - images[1]._height)); + screen._backBuffer1.transBlitFrom(images[1], Common::Point(r.right - images[1]._width, r.bottom - images[1]._height)); + + // Draw the top of the info box + screen._backBuffer1.hLine(r.left + images[0]._width, r.top, r.right - images[0]._height, INFO_TOP); + screen._backBuffer1.hLine(r.left + images[0]._width, r.top + 1, r.right - images[0]._height, INFO_MIDDLE); + screen._backBuffer1.hLine(r.left + images[0]._width, r.top + 2, r.right - images[0]._height, INFO_BOTTOM); + + // Draw the bottom of the info box + screen._backBuffer1.hLine(r.left + images[0]._width, r.bottom - 3, r.right - images[0]._height, INFO_TOP); + screen._backBuffer1.hLine(r.left + images[0]._width, r.bottom - 2, r.right - images[0]._height, INFO_MIDDLE); + screen._backBuffer1.hLine(r.left + images[0]._width, r.bottom - 1, r.right - images[0]._height, INFO_BOTTOM); + + // Draw the left side of the info box + screen._backBuffer1.vLine(r.left, r.top + images[0]._height, r.bottom - images[2]._height, INFO_TOP); + screen._backBuffer1.vLine(r.left + 1, r.top + images[0]._height, r.bottom - images[2]._height, INFO_MIDDLE); + screen._backBuffer1.vLine(r.left + 2, r.top + images[0]._height, r.bottom - images[2]._height, INFO_BOTTOM); + + // Draw the right side of the info box + screen._backBuffer1.vLine(r.right - 3, r.top + images[0]._height, r.bottom - images[2]._height, INFO_TOP); + screen._backBuffer1.vLine(r.right - 2, r.top + images[0]._height, r.bottom - images[2]._height, INFO_MIDDLE); + screen._backBuffer1.vLine(r.right - 1, r.top + images[0]._height, r.bottom - images[2]._height, INFO_BOTTOM); + + // Draw the text + screen._backBuffer1.writeString(msg, Common::Point(inner.left, inner.top), INFO_TOP); + screen.slamRect(r); + + // Five second pause + events.delay(5000, true); +} + } // End of namespace Tattoo } // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/tattoo_journal.h b/engines/sherlock/tattoo/tattoo_journal.h index 96c1c6cab4..9f0fa1fc9b 100644 --- a/engines/sherlock/tattoo/tattoo_journal.h +++ b/engines/sherlock/tattoo/tattoo_journal.h @@ -31,7 +31,7 @@ namespace Sherlock { namespace Tattoo { enum JournalHighlight { - JH_NONE = -1, JH_CLOSE = 0, JH_SEARCH = 1, JH_PRINT = 2, + JH_NONE = -1, JH_CLOSE = 0, JH_SEARCH = 1, JH_SAVE = 2, JH_SCROLL_LEFT = 3, JH_PAGE_LEFT = 4, JH_PAGE_RIGHT = 5, JH_SCROLL_RIGHT = 6, JH_THUMBNAIL = 7 }; @@ -86,6 +86,16 @@ private: * Get in a name to search through the journal for */ int getFindName(bool printError); + + /** + * Save the journal to file + */ + void saveJournal(); + + /** + * Show a message that the journal has been saved to file + */ + void showSavedDialog(); public: TattooJournal(SherlockEngine *vm); virtual ~TattooJournal() {} diff --git a/engines/sherlock/tattoo/tattoo_map.cpp b/engines/sherlock/tattoo/tattoo_map.cpp index 4c7e8c8fef..0839e46260 100644 --- a/engines/sherlock/tattoo/tattoo_map.cpp +++ b/engines/sherlock/tattoo/tattoo_map.cpp @@ -105,7 +105,7 @@ int TattooMap::show() { // Load the map image and draw it to the back buffer ImageFile *map = new ImageFile("map.vgs"); screen._backBuffer1.create(SHERLOCK_SCREEN_WIDTH * 2, SHERLOCK_SCREEN_HEIGHT * 2); - screen._backBuffer1.blitFrom((*map)[0], Common::Point(0, 0)); + screen._backBuffer1.SHblitFrom((*map)[0], Common::Point(0, 0)); delete map; screen.clear(); @@ -114,7 +114,7 @@ int TattooMap::show() { // Copy the map drawn in the back buffer to the secondary back buffer screen._backBuffer2.create(SHERLOCK_SCREEN_WIDTH * 2, SHERLOCK_SCREEN_HEIGHT * 2); - screen._backBuffer2.blitFrom(screen._backBuffer1); + screen._backBuffer2.SHblitFrom(screen._backBuffer1); // Display the built map to the screen screen.slamArea(0, 0, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); @@ -148,12 +148,12 @@ int TattooMap::show() { if (_targetScroll.x < 0) _targetScroll.x = 0; - if ((_targetScroll.x + SHERLOCK_SCREEN_WIDTH) > screen._backBuffer1.w()) - _targetScroll.x = screen._backBuffer1.w() - SHERLOCK_SCREEN_WIDTH; + if ((_targetScroll.x + SHERLOCK_SCREEN_WIDTH) > screen._backBuffer1.width()) + _targetScroll.x = screen._backBuffer1.width() - SHERLOCK_SCREEN_WIDTH; if (_targetScroll.y < 0) _targetScroll.y = 0; - if ((_targetScroll.y + SHERLOCK_SCREEN_HEIGHT) > screen._backBuffer1.h()) - _targetScroll.y = screen._backBuffer1.h() - SHERLOCK_SCREEN_HEIGHT; + if ((_targetScroll.y + SHERLOCK_SCREEN_HEIGHT) > screen._backBuffer1.height()) + _targetScroll.y = screen._backBuffer1.height() - SHERLOCK_SCREEN_HEIGHT; // Check the keyboard if (events.kbHit()) { @@ -166,8 +166,8 @@ int TattooMap::show() { break; case Common::KEYCODE_END: - _targetScroll.x = screen._backBuffer1.w() - SHERLOCK_SCREEN_WIDTH; - _targetScroll.y = screen._backBuffer1.h() - SHERLOCK_SCREEN_HEIGHT; + _targetScroll.x = screen._backBuffer1.width() - SHERLOCK_SCREEN_WIDTH; + _targetScroll.y = screen._backBuffer1.height() - SHERLOCK_SCREEN_HEIGHT; break; case Common::KEYCODE_PAGEUP: @@ -178,8 +178,8 @@ int TattooMap::show() { case Common::KEYCODE_PAGEDOWN: _targetScroll.y += SHERLOCK_SCREEN_HEIGHT; - if (_targetScroll.y > (screen._backBuffer1.h() - SHERLOCK_SCREEN_HEIGHT)) - _targetScroll.y = screen._backBuffer1.h() - SHERLOCK_SCREEN_HEIGHT; + if (_targetScroll.y > (screen._backBuffer1.height() - SHERLOCK_SCREEN_HEIGHT)) + _targetScroll.y = screen._backBuffer1.height() - SHERLOCK_SCREEN_HEIGHT; break; case Common::KEYCODE_SPACE: @@ -304,7 +304,7 @@ void TattooMap::drawMapIcons() { if (_data[idx]._iconNum != -1 && _vm->readFlags(idx + 1)) { MapEntry &mapEntry = _data[idx]; ImageFrame &img = (*_iconImages)[mapEntry._iconNum]; - screen._backBuffer1.transBlitFrom(img._frame, Common::Point(mapEntry.x - img._width / 2, + screen._backBuffer1.SHtransBlitFrom(img._frame, Common::Point(mapEntry.x - img._width / 2, mapEntry.y - img._height / 2)); } } @@ -355,10 +355,10 @@ void TattooMap::restoreArea(const Common::Rect &bounds) { Screen &screen = *_vm->_screen; Common::Rect r = bounds; - r.clip(Common::Rect(0, 0, screen._backBuffer1.w(), screen._backBuffer1.h())); + r.clip(Common::Rect(0, 0, screen._backBuffer1.width(), screen._backBuffer1.height())); if (!r.isEmpty()) - screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(r.left, r.top), r); + screen._backBuffer1.SHblitFrom(screen._backBuffer2, Common::Point(r.left, r.top), r); } void TattooMap::showCloseUp(int closeUpNum) { @@ -407,7 +407,7 @@ void TattooMap::showCloseUp(int closeUpNum) { screen._currentScroll.y + closeUp.y / 100 - picSize.y / 2); restoreArea(oldBounds); - screen._backBuffer1.transBlitFrom(pic[0], pt, false, 0, scaleVal); + screen._backBuffer1.SHtransBlitFrom(pic[0], pt, false, 0, scaleVal); screen.slamRect(oldBounds); screen.slamArea(pt.x, pt.y, picSize.x, picSize.y); @@ -426,7 +426,7 @@ void TattooMap::showCloseUp(int closeUpNum) { screen._currentScroll.y + SHERLOCK_SCREEN_HEIGHT / 2 - pic[0]._height / 2 + pic[0]._height); restoreArea(oldBounds); - screen._backBuffer1.transBlitFrom(pic[0], Common::Point(r.left, r.top)); + screen._backBuffer1.SHtransBlitFrom(pic[0], Common::Point(r.left, r.top)); screen.slamRect(oldBounds); screen.slamRect(r); diff --git a/engines/sherlock/tattoo/tattoo_people.cpp b/engines/sherlock/tattoo/tattoo_people.cpp index 0af8deff9f..65cc283b66 100644 --- a/engines/sherlock/tattoo/tattoo_people.cpp +++ b/engines/sherlock/tattoo/tattoo_people.cpp @@ -1042,7 +1042,7 @@ void TattooPerson::walkHolmesToNPC() { holmes._walkDest.x = MAX(_position.x / FIXED_INT_MULTIPLIER - imgFrame.sDrawXSize(scaleVal), 0); } else { holmes._walkDest.x = MIN(_position.x / FIXED_INT_MULTIPLIER + imgFrame.sDrawXSize(scaleVal) * 2, - screen._backBuffer1.w() - 1); + screen._backBuffer1.width() - 1); } // See where Holmes is with respect to the NPC (y coords) @@ -1168,7 +1168,7 @@ void TattooPerson::centerScreenOnPerson() { TattooUserInterface &ui = *(TattooUserInterface *)_vm->_ui; ui._targetScroll.x = CLIP(_position.x / FIXED_INT_MULTIPLIER - SHERLOCK_SCREEN_WIDTH / 2, - 0, screen._backBuffer1.w() - SHERLOCK_SCREEN_WIDTH); + 0, screen._backBuffer1.width() - SHERLOCK_SCREEN_WIDTH); screen._currentScroll = ui._targetScroll; // Reset the default look position to the center of the screen @@ -1354,6 +1354,7 @@ void TattooPeople::setTalkSequence(int speaker, int sequenceNum) { int TattooPeople::findSpeaker(int speaker) { + speaker &= 0x7f; int result = People::findSpeaker(speaker); const char *portrait = _characters[speaker]._portrait; @@ -1477,7 +1478,7 @@ const Common::Point TattooPeople::restrictToZone(int zoneId, const Common::Point Screen &screen = *_vm->_screen; Common::Rect &r = scene._zones[zoneId]; - if (destPos.x < 0 || destPos.x > screen._backBuffer1.w()) + if (destPos.x < 0 || destPos.x > screen._backBuffer1.width()) return destPos; else if (destPos.y < r.top && r.left < destPos.x && destPos.x < r.right) return Common::Point(destPos.x, r.top); diff --git a/engines/sherlock/tattoo/tattoo_scene.cpp b/engines/sherlock/tattoo/tattoo_scene.cpp index 27f37665dc..00015cb189 100644 --- a/engines/sherlock/tattoo/tattoo_scene.cpp +++ b/engines/sherlock/tattoo/tattoo_scene.cpp @@ -141,15 +141,15 @@ void TattooScene::drawAllShapes() { if (obj._type == ACTIVE_BG_SHAPE && obj._misc == BEHIND) { if (obj._quickDraw && obj._scaleVal == SCALE_THRESHOLD) - screen._backBuffer1.blitFrom(*obj._imageFrame, obj._position); + screen._backBuffer1.SHblitFrom(*obj._imageFrame, obj._position); else - screen._backBuffer1.transBlitFrom(*obj._imageFrame, obj._position, obj._flags & OBJ_FLIPPED, 0, obj._scaleVal); + screen._backBuffer1.SHtransBlitFrom(*obj._imageFrame, obj._position, obj._flags & OBJ_FLIPPED, 0, obj._scaleVal); } } // Draw the animation if it is behind the person if (_activeCAnim.active() && _activeCAnim._zPlacement == BEHIND) - screen._backBuffer1.transBlitFrom(_activeCAnim._imageFrame, _activeCAnim._position, + screen._backBuffer1.SHtransBlitFrom(_activeCAnim._imageFrame, _activeCAnim._position, (_activeCAnim._flags & 4) >> 1, 0, _activeCAnim._scaleVal); screen.resetDisplayBounds(); @@ -194,13 +194,13 @@ void TattooScene::drawAllShapes() { if (se._shape) { // it's a bg shape if (se._shape->_quickDraw && se._shape->_scaleVal == SCALE_THRESHOLD) - screen._backBuffer1.blitFrom(*se._shape->_imageFrame, se._shape->_position); + screen._backBuffer1.SHblitFrom(*se._shape->_imageFrame, se._shape->_position); else - screen._backBuffer1.transBlitFrom(*se._shape->_imageFrame, se._shape->_position, + screen._backBuffer1.SHtransBlitFrom(*se._shape->_imageFrame, se._shape->_position, se._shape->_flags & OBJ_FLIPPED, 0, se._shape->_scaleVal); } else if (se._isAnimation) { // It's an active animation - screen._backBuffer1.transBlitFrom(_activeCAnim._imageFrame, _activeCAnim._position, + screen._backBuffer1.SHtransBlitFrom(_activeCAnim._imageFrame, _activeCAnim._position, (_activeCAnim._flags & 4) >> 1, 0, _activeCAnim._scaleVal); } else { // Drawing person @@ -212,7 +212,7 @@ void TattooScene::drawAllShapes() { if (p._tempScaleVal == SCALE_THRESHOLD) { p._tempX += adjust.x; - screen._backBuffer1.transBlitFrom(*p._imageFrame, Common::Point(p._tempX, p._position.y / FIXED_INT_MULTIPLIER + screen._backBuffer1.SHtransBlitFrom(*p._imageFrame, Common::Point(p._tempX, p._position.y / FIXED_INT_MULTIPLIER - p.frameHeight() - adjust.y), p._walkSequences[p._sequenceNumber]._horizFlip, 0, p._tempScaleVal); } else { if (adjust.x) { @@ -242,7 +242,7 @@ void TattooScene::drawAllShapes() { ++adjust.y; } - screen._backBuffer1.transBlitFrom(*p._imageFrame, Common::Point(p._tempX, p._position.y / FIXED_INT_MULTIPLIER + screen._backBuffer1.SHtransBlitFrom(*p._imageFrame, Common::Point(p._tempX, p._position.y / FIXED_INT_MULTIPLIER - p._imageFrame->sDrawYSize(p._tempScaleVal) - adjust.y), p._walkSequences[p._sequenceNumber]._horizFlip, 0, p._tempScaleVal); } } @@ -255,15 +255,15 @@ void TattooScene::drawAllShapes() { if (obj._type == ACTIVE_BG_SHAPE && obj._misc == FORWARD) { if (obj._quickDraw && obj._scaleVal == SCALE_THRESHOLD) - screen._backBuffer1.blitFrom(*obj._imageFrame, obj._position); + screen._backBuffer1.SHblitFrom(*obj._imageFrame, obj._position); else - screen._backBuffer1.transBlitFrom(*obj._imageFrame, obj._position, obj._flags & OBJ_FLIPPED, 0, obj._scaleVal); + screen._backBuffer1.SHtransBlitFrom(*obj._imageFrame, obj._position, obj._flags & OBJ_FLIPPED, 0, obj._scaleVal); } } // Draw the canimation if it is set as FORWARD if (_activeCAnim.active() && _activeCAnim._zPlacement == FORWARD) - screen._backBuffer1.transBlitFrom(_activeCAnim._imageFrame, _activeCAnim._position, (_activeCAnim._flags & 4) >> 1, 0, _activeCAnim._scaleVal); + screen._backBuffer1.SHtransBlitFrom(_activeCAnim._imageFrame, _activeCAnim._position, (_activeCAnim._flags & 4) >> 1, 0, _activeCAnim._scaleVal); // Draw all NO_SHAPE shapes which have their flag bits clear for (uint idx = 0; idx < _bgShapes.size(); ++idx) { diff --git a/engines/sherlock/tattoo/tattoo_screen.cpp b/engines/sherlock/tattoo/tattoo_screen.cpp new file mode 100644 index 0000000000..c98ae2679d --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_screen.cpp @@ -0,0 +1,37 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "sherlock/tattoo/tattoo_screen.h" +#include "sherlock/tattoo/tattoo.h" + +namespace Sherlock { + +namespace Tattoo { + +TattooScreen::TattooScreen(SherlockEngine *vm) : Screen(vm) { + _backBuffer1.create(640, 480); + _backBuffer2.create(640, 480); +} + +} // End of namespace Tattoo + +} // End of namespace Sherlock diff --git a/engines/sherlock/tattoo/tattoo_screen.h b/engines/sherlock/tattoo/tattoo_screen.h new file mode 100644 index 0000000000..b55e9bb0dd --- /dev/null +++ b/engines/sherlock/tattoo/tattoo_screen.h @@ -0,0 +1,44 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SHERLOCK_TATTOO_SCREEN_H +#define SHERLOCK_TATTOO_SCREEN_H + +#include "sherlock/screen.h" + +namespace Sherlock { + +class SherlockEngine; + +namespace Tattoo { + +class TattooScreen : public Screen { +public: + TattooScreen(SherlockEngine *vm); + virtual ~TattooScreen() {} +}; + +} // End of namespace Tattoo + +} // End of namespace Sherlock + +#endif diff --git a/engines/sherlock/tattoo/tattoo_talk.cpp b/engines/sherlock/tattoo/tattoo_talk.cpp index a5ada7b63e..e6b9a9627e 100644 --- a/engines/sherlock/tattoo/tattoo_talk.cpp +++ b/engines/sherlock/tattoo/tattoo_talk.cpp @@ -795,7 +795,10 @@ OpcodeReturn TattooTalk::cmdTalkInterruptsDisable(const byte *&str) { error("Dum // Dummy opcode OpcodeReturn TattooTalk::cmdTalkInterruptsEnable(const byte *&str) { error("Dummy opcode cmdTalkInterruptsEnable called"); } -OpcodeReturn TattooTalk::cmdTurnSoundsOff(const byte *&str) { error("TODO: script opcode (cmdTurnSoundsOff)"); } +OpcodeReturn TattooTalk::cmdTurnSoundsOff(const byte *&str) { + _vm->_sound->stopSound(); + return RET_SUCCESS; +} OpcodeReturn TattooTalk::cmdWalkHolmesAndNPCToCAnimation(const byte *&str) { int npcNum = *++str; diff --git a/engines/sherlock/tattoo/tattoo_user_interface.cpp b/engines/sherlock/tattoo/tattoo_user_interface.cpp index ee028f89c2..677a662535 100644 --- a/engines/sherlock/tattoo/tattoo_user_interface.cpp +++ b/engines/sherlock/tattoo/tattoo_user_interface.cpp @@ -72,7 +72,7 @@ TattooUserInterface::~TattooUserInterface() { void TattooUserInterface::initScrollVars() { Screen &screen = *_vm->_screen; - _scrollSize = screen._backBuffer1.w() - SHERLOCK_SCREEN_WIDTH; + _scrollSize = screen._backBuffer1.width() - SHERLOCK_SCREEN_WIDTH; _targetScroll = Common::Point(0, 0); screen._currentScroll = Common::Point(0, 0); } @@ -233,7 +233,7 @@ void TattooUserInterface::doJournal() { Common::copy(&lookupTable1[0], &lookupTable1[PALETTE_COUNT], &_lookupTable1[0]); // Restore the scene - screen._backBuffer1.blitFrom(screen._backBuffer2); + screen._backBuffer1.SHblitFrom(screen._backBuffer2); scene.updateBackground(); screen.slamArea(screen._currentScroll.x, screen._currentScroll.y, SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT); } @@ -727,7 +727,7 @@ void TattooUserInterface::doBgAnimEraseBackground() { if (_mask != nullptr) { // Since a mask is active, restore the screen from the secondary back buffer prior to applying the mask - screen._backBuffer1.blitFrom(screen._backBuffer2, screen._currentScroll, Common::Rect(screen._currentScroll.x, 0, + screen._backBuffer1.SHblitFrom(screen._backBuffer2, screen._currentScroll, Common::Rect(screen._currentScroll.x, 0, screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH, SHERLOCK_SCREEN_HEIGHT)); switch (scene._currentScene) { @@ -757,7 +757,7 @@ void TattooUserInterface::doBgAnimEraseBackground() { case 53: if (++_maskCounter == 2) { _maskCounter = 0; - if (++_maskOffset.x == screen._backBuffer1.w()) + if (++_maskOffset.x == screen._backBuffer1.width()) _maskOffset.x = 0; } break; @@ -779,7 +779,7 @@ void TattooUserInterface::doBgAnimEraseBackground() { if ((obj._type == ACTIVE_BG_SHAPE && (obj._maxFrames > 1 || obj._delta.x != 0 || obj._delta.y != 0)) || obj._type == HIDE_SHAPE || obj._type == REMOVE) - screen._backBuffer1.blitFrom(screen._backBuffer2, obj._oldPosition, + screen._backBuffer1.SHblitFrom(screen._backBuffer2, obj._oldPosition, Common::Rect(obj._oldPosition.x, obj._oldPosition.y, obj._oldPosition.x + obj._oldSize.x, obj._oldPosition.y + obj._oldSize.y)); } @@ -793,7 +793,7 @@ void TattooUserInterface::doBgAnimEraseBackground() { Object &obj = scene._bgShapes[idx]; if (obj._type == NO_SHAPE && (obj._flags & 1) == 0) { - screen._backBuffer1.blitFrom(screen._backBuffer2, obj._position, obj.getNoShapeBounds()); + screen._backBuffer1.SHblitFrom(screen._backBuffer2, obj._position, obj.getNoShapeBounds()); obj._oldPosition = obj._position; obj._oldSize = obj._noShapeSize; @@ -870,7 +870,7 @@ void TattooUserInterface::maskArea(Common::SeekableReadStream &mask, const Commo int pixel, len, xp, yp; for (yp = 0; yp < ySize; ++yp) { - byte *ptr = bb1.getBasePtr(pt.x, pt.y + yp); + byte *ptr = (byte *)bb1.getBasePtr(pt.x, pt.y + yp); for (xp = 0; xp < xSize;) { // The mask data consists of pairs of pixel/lengths, where all non-zero pixels means that the @@ -893,7 +893,7 @@ void TattooUserInterface::makeBGArea(const Common::Rect &r) { Screen &screen = *_vm->_screen; for (int yp = r.top; yp < r.bottom; ++yp) { - byte *ptr = screen._backBuffer1.getBasePtr(r.left, yp); + byte *ptr = (byte *)screen._backBuffer1.getBasePtr(r.left, yp); for (int xp = r.left; xp < r.right; ++xp, ++ptr) *ptr = _lookupTable[*ptr]; diff --git a/engines/sherlock/tattoo/widget_base.cpp b/engines/sherlock/tattoo/widget_base.cpp index 8f0649130a..a35f4e5d74 100644 --- a/engines/sherlock/tattoo/widget_base.cpp +++ b/engines/sherlock/tattoo/widget_base.cpp @@ -88,7 +88,7 @@ void WidgetBase::erase() { if (_oldBounds.width() > 0) { // Restore the affected area from the secondary back buffer into the first one, and then copy to screen - screen._backBuffer1.blitFrom(screen._backBuffer2, Common::Point(_oldBounds.left, _oldBounds.top), _oldBounds); + screen._backBuffer1.SHblitFrom(screen._backBuffer2, Common::Point(_oldBounds.left, _oldBounds.top), _oldBounds); screen.slamRect(_oldBounds); // Reset the old bounds so it won't be erased again @@ -111,7 +111,7 @@ void WidgetBase::draw() { drawBackground(); // Draw the widget onto the back buffer and then slam it to the screen - screen._backBuffer1.transBlitFrom(_surface, Common::Point(_bounds.left, _bounds.top)); + screen._backBuffer1.SHtransBlitFrom(_surface, Common::Point(_bounds.left, _bounds.top)); screen.slamRect(_bounds); // Store a copy of the drawn area for later erasing @@ -183,8 +183,8 @@ void WidgetBase::restrictToScreen() { _bounds.moveTo(_bounds.left, 0); if (_bounds.right > (screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH)) _bounds.moveTo(screen._currentScroll.x + SHERLOCK_SCREEN_WIDTH - _bounds.width(), _bounds.top); - if (_bounds.bottom > screen._backBuffer1.h()) - _bounds.moveTo(_bounds.left, screen._backBuffer1.h() - _bounds.height()); + if (_bounds.bottom > screen._backBuffer1.height()) + _bounds.moveTo(_bounds.left, screen._backBuffer1.height() - _bounds.height()); } void WidgetBase::makeInfoArea(Surface &s) { @@ -192,30 +192,30 @@ void WidgetBase::makeInfoArea(Surface &s) { ImageFile &images = *ui._interfaceImages; // Draw the four corners of the Info Box - s.transBlitFrom(images[0], Common::Point(0, 0)); - s.transBlitFrom(images[1], Common::Point(s.w() - images[1]._width, 0)); - s.transBlitFrom(images[2], Common::Point(0, s.h() - images[2]._height)); - s.transBlitFrom(images[3], Common::Point(s.w() - images[3]._width, s.h())); + s.SHtransBlitFrom(images[0], Common::Point(0, 0)); + s.SHtransBlitFrom(images[1], Common::Point(s.width() - images[1]._width, 0)); + s.SHtransBlitFrom(images[2], Common::Point(0, s.height() - images[2]._height)); + s.SHtransBlitFrom(images[3], Common::Point(s.width() - images[3]._width, s.height())); // Draw the top of the Info Box - s.hLine(images[0]._width, 0, s.w() - images[1]._width, INFO_TOP); - s.hLine(images[0]._width, 1, s.w() - images[1]._width, INFO_MIDDLE); - s.hLine(images[0]._width, 2, s.w() - images[1]._width, INFO_BOTTOM); + s.hLine(images[0]._width, 0, s.width() - images[1]._width, INFO_TOP); + s.hLine(images[0]._width, 1, s.width() - images[1]._width, INFO_MIDDLE); + s.hLine(images[0]._width, 2, s.width() - images[1]._width, INFO_BOTTOM); // Draw the bottom of the Info Box - s.hLine(images[0]._width, s.h()- 3, s.w() - images[1]._width, INFO_TOP); - s.hLine(images[0]._width, s.h()- 2, s.w() - images[1]._width, INFO_MIDDLE); - s.hLine(images[0]._width, s.h()- 1, s.w() - images[1]._width, INFO_BOTTOM); + s.hLine(images[0]._width, s.height()- 3, s.width() - images[1]._width, INFO_TOP); + s.hLine(images[0]._width, s.height()- 2, s.width() - images[1]._width, INFO_MIDDLE); + s.hLine(images[0]._width, s.height()- 1, s.width() - images[1]._width, INFO_BOTTOM); // Draw the left Side of the Info Box - s.vLine(0, images[0]._height, s.h()- images[2]._height, INFO_TOP); - s.vLine(1, images[0]._height, s.h()- images[2]._height, INFO_MIDDLE); - s.vLine(2, images[0]._height, s.h()- images[2]._height, INFO_BOTTOM); + s.vLine(0, images[0]._height, s.height()- images[2]._height, INFO_TOP); + s.vLine(1, images[0]._height, s.height()- images[2]._height, INFO_MIDDLE); + s.vLine(2, images[0]._height, s.height()- images[2]._height, INFO_BOTTOM); // Draw the right Side of the Info Box - s.vLine(s.w() - 3, images[0]._height, s.h()- images[2]._height, INFO_TOP); - s.vLine(s.w() - 2, images[0]._height, s.h()- images[2]._height, INFO_MIDDLE); - s.vLine(s.w() - 1, images[0]._height, s.h()- images[2]._height, INFO_BOTTOM); + s.vLine(s.width() - 3, images[0]._height, s.height()- images[2]._height, INFO_TOP); + s.vLine(s.width() - 2, images[0]._height, s.height()- images[2]._height, INFO_MIDDLE); + s.vLine(s.width() - 1, images[0]._height, s.height()- images[2]._height, INFO_BOTTOM); } void WidgetBase::makeInfoArea() { diff --git a/engines/sherlock/tattoo/widget_credits.cpp b/engines/sherlock/tattoo/widget_credits.cpp index b8e297709f..1c878daaf6 100644 --- a/engines/sherlock/tattoo/widget_credits.cpp +++ b/engines/sherlock/tattoo/widget_credits.cpp @@ -37,7 +37,7 @@ void WidgetCredits::initCredits() { Screen &screen = *_vm->_screen; Common::SeekableReadStream *stream = res.load("credits.txt"); int spacing = screen.fontHeight() * 2; - int yp = screen.h(); + int yp = screen.height(); _creditsActive = true; _creditLines.clear(); @@ -60,7 +60,7 @@ void WidgetCredits::initCredits() { } else { int width = screen.stringWidth(line) + 2; - _creditLines.push_back(CreditLine(line, Common::Point((screen.w() - width) / 2 + 1, yp), width)); + _creditLines.push_back(CreditLine(line, Common::Point((screen.width() - width) / 2 + 1, yp), width)); yp += spacing; } } @@ -120,10 +120,10 @@ void WidgetCredits::close() { void WidgetCredits::drawCredits() { Screen &screen = *_vm->_screen; - Common::Rect screenRect(0, 0, screen.w(), screen.h()); + Common::Rect screenRect(0, 0, screen.width(), screen.height()); Surface &bb1 = screen._backBuffer1; - for (uint idx = 0; idx < _creditLines.size() && _creditLines[idx]._position.y < screen.h(); ++idx) { + for (uint idx = 0; idx < _creditLines.size() && _creditLines[idx]._position.y < screen.height(); ++idx) { if (screenRect.contains(_creditLines[idx]._position)) { if (!_creditLines[idx]._line2.empty()) { int x1 = _creditLines[idx]._position.x; @@ -176,7 +176,7 @@ void WidgetCredits::drawCredits() { void WidgetCredits::blitCredits() { Screen &screen = *_vm->_screen; - Common::Rect screenRect(0, -_creditSpeed, screen.w(), screen.h() + _creditSpeed); + Common::Rect screenRect(0, -_creditSpeed, screen.width(), screen.height() + _creditSpeed); for (uint idx = 0; idx < _creditLines.size(); ++idx) { if (screenRect.contains(_creditLines[idx]._position)) { @@ -185,14 +185,12 @@ void WidgetCredits::blitCredits() { screen.slamRect(r); } - - _creditLines[idx]._position.y -= _creditSpeed; } } void WidgetCredits::eraseCredits() { Screen &screen = *_vm->_screen; - Common::Rect screenRect(0, -_creditSpeed, screen.w(), screen.h() + _creditSpeed); + Common::Rect screenRect(0, -_creditSpeed, screen.width(), screen.height() + _creditSpeed); for (uint idx = 0; idx < _creditLines.size(); ++idx) { if (screenRect.contains(_creditLines[idx]._position)) { @@ -200,7 +198,10 @@ void WidgetCredits::eraseCredits() { r.moveTo(_creditLines[idx]._position.x, _creditLines[idx]._position.y - 1 + _creditSpeed); screen.restoreBackground(r); + screen.slamRect(r); } + + _creditLines[idx]._position.y -= _creditSpeed; } if (_creditLines[_creditLines.size() - 1]._position.y < -_creditSpeed) { diff --git a/engines/sherlock/tattoo/widget_files.cpp b/engines/sherlock/tattoo/widget_files.cpp index ff8cb83dca..7666e81480 100644 --- a/engines/sherlock/tattoo/widget_files.cpp +++ b/engines/sherlock/tattoo/widget_files.cpp @@ -107,36 +107,36 @@ void WidgetFiles::render(FilesRenderMode mode) { byte color; if (mode == RENDER_ALL) { - _surface.fill(TRANSPARENCY); + _surface.clear(TRANSPARENCY); makeInfoArea(); switch (_fileMode) { case SAVEMODE_LOAD: _surface.writeString(FIXED(LoadGame), - Common::Point((_surface.w() - _surface.stringWidth(FIXED(LoadGame))) / 2, 5), INFO_TOP); + Common::Point((_surface.width() - _surface.stringWidth(FIXED(LoadGame))) / 2, 5), INFO_TOP); break; case SAVEMODE_SAVE: _surface.writeString(FIXED(SaveGame), - Common::Point((_surface.w() - _surface.stringWidth(FIXED(SaveGame))) / 2, 5), INFO_TOP); + Common::Point((_surface.width() - _surface.stringWidth(FIXED(SaveGame))) / 2, 5), INFO_TOP); break; default: break; } - _surface.hLine(3, _surface.fontHeight() + 7, _surface.w() - 4, INFO_TOP); - _surface.hLine(3, _surface.fontHeight() + 8, _surface.w() - 4, INFO_MIDDLE); - _surface.hLine(3, _surface.fontHeight() + 9, _surface.w() - 4, INFO_BOTTOM); - _surface.transBlitFrom(images[4], Common::Point(0, _surface.fontHeight() + 6)); - _surface.transBlitFrom(images[5], Common::Point(_surface.w() - images[5]._width, _surface.fontHeight() + 6)); + _surface.hLine(3, _surface.fontHeight() + 7, _surface.width() - 4, INFO_TOP); + _surface.hLine(3, _surface.fontHeight() + 8, _surface.width() - 4, INFO_MIDDLE); + _surface.hLine(3, _surface.fontHeight() + 9, _surface.width() - 4, INFO_BOTTOM); + _surface.SHtransBlitFrom(images[4], Common::Point(0, _surface.fontHeight() + 6)); + _surface.SHtransBlitFrom(images[5], Common::Point(_surface.width() - images[5]._width, _surface.fontHeight() + 6)); - int xp = _surface.w() - BUTTON_SIZE - 6; + int xp = _surface.width() - BUTTON_SIZE - 6; _surface.vLine(xp, _surface.fontHeight() + 10, _bounds.height() - 4, INFO_TOP); _surface.vLine(xp + 1, _surface.fontHeight() + 10, _bounds.height() - 4, INFO_MIDDLE); _surface.vLine(xp + 2, _surface.fontHeight() + 10, _bounds.height() - 4, INFO_BOTTOM); - _surface.transBlitFrom(images[6], Common::Point(xp - 1, _surface.fontHeight() + 8)); - _surface.transBlitFrom(images[7], Common::Point(xp - 1, _bounds.height() - 4)); + _surface.SHtransBlitFrom(images[6], Common::Point(xp - 1, _surface.fontHeight() + 8)); + _surface.SHtransBlitFrom(images[7], Common::Point(xp - 1, _bounds.height() - 4)); } int xp = _surface.stringWidth("00.") + _surface.widestChar() + 5; @@ -149,7 +149,7 @@ void WidgetFiles::render(FilesRenderMode mode) { color = INFO_TOP; if (mode == RENDER_NAMES_AND_SCROLLBAR) - _surface.fillRect(Common::Rect(4, yp, _surface.w() - BUTTON_SIZE - 9, yp + _surface.fontHeight()), TRANSPARENCY); + _surface.fillRect(Common::Rect(4, yp, _surface.width() - BUTTON_SIZE - 9, yp + _surface.fontHeight()), TRANSPARENCY); Common::String numStr = Common::String::format("%d.", idx + 1); _surface.writeString(numStr, Common::Point(_surface.widestChar(), yp), color); @@ -324,7 +324,7 @@ bool WidgetFiles::getFilename() { filename.setChar(' ', index); } - _surface.fillRect(Common::Rect(pt.x, pt.y, _surface.w() - BUTTON_SIZE - 9, pt.y + _surface.fontHeight() - 1), TRANSPARENCY); + _surface.fillRect(Common::Rect(pt.x, pt.y, _surface.width() - BUTTON_SIZE - 9, pt.y + _surface.fontHeight() - 1), TRANSPARENCY); _surface.writeString(filename.c_str() + index, pt, COMMAND_HIGHLIGHTED); } else if ((keyState.keycode == Common::KEYCODE_LEFT && index > 0) @@ -387,7 +387,7 @@ bool WidgetFiles::getFilename() { } if ((keyState.ascii >= ' ') && (keyState.ascii <= 'z') && (index < 50)) { - if (pt.x + _surface.charWidth(keyState.ascii) < _surface.w() - BUTTON_SIZE - 20) { + if (pt.x + _surface.charWidth(keyState.ascii) < _surface.w - BUTTON_SIZE - 20) { if (insert) filename.insertChar(keyState.ascii, index); else diff --git a/engines/sherlock/tattoo/widget_foolscap.cpp b/engines/sherlock/tattoo/widget_foolscap.cpp index c8df71e873..8225946838 100644 --- a/engines/sherlock/tattoo/widget_foolscap.cpp +++ b/engines/sherlock/tattoo/widget_foolscap.cpp @@ -103,7 +103,7 @@ void WidgetFoolscap::show() { // Set up the window background _surface.create(_bounds.width(), _bounds.height()); - _surface.blitFrom(paperFrame, Common::Point(0, 0)); + _surface.SHblitFrom(paperFrame, Common::Point(0, 0)); // If they have already solved the puzzle, put the answer on the graphic if (_vm->readFlags(299)) { @@ -265,7 +265,7 @@ void WidgetFoolscap::handleKeyboardEvents() { void WidgetFoolscap::restoreChar() { Screen &screen = *_vm->_screen; ImageFrame &bgFrame = (*_images)[0]; - _surface.blitFrom(bgFrame, _cursorPos, Common::Rect(_cursorPos.x, _cursorPos.y, + _surface.SHblitFrom(bgFrame, _cursorPos, Common::Rect(_cursorPos.x, _cursorPos.y, _cursorPos.x + screen.widestChar(), _cursorPos.y + screen.fontHeight())); } diff --git a/engines/sherlock/tattoo/widget_inventory.cpp b/engines/sherlock/tattoo/widget_inventory.cpp index b49e30b30d..34331f0eae 100644 --- a/engines/sherlock/tattoo/widget_inventory.cpp +++ b/engines/sherlock/tattoo/widget_inventory.cpp @@ -94,7 +94,7 @@ void WidgetInventoryTooltip::setText(const Common::String &str) { // Allocate a fresh surface for the new string _bounds = Common::Rect(width, height); _surface.create(width, height); - _surface.fill(TRANSPARENCY); + _surface.clear(TRANSPARENCY); if (line2.empty()) { _surface.writeFancyString(str, Common::Point(0, 0), BLACK, INFO_TOP); @@ -338,7 +338,7 @@ void WidgetInventoryVerbs::load() { // Create the surface _surface.create(_bounds.width(), _bounds.height()); - _surface.fill(TRANSPARENCY); + _surface.clear(TRANSPARENCY); makeInfoArea(); // Draw the Verb commands and the lines separating them @@ -352,8 +352,8 @@ void WidgetInventoryVerbs::load() { _surface.vLine(3, (_surface.fontHeight() + 7) * (idx + 1) + 1, _bounds.right - 4, INFO_MIDDLE); _surface.vLine(3, (_surface.fontHeight() + 7) * (idx + 1) + 2, _bounds.right - 4, INFO_BOTTOM); - _surface.transBlitFrom(images[4], Common::Point(0, (_surface.fontHeight() + 7) * (idx + 1))); - _surface.transBlitFrom(images[5], Common::Point(_bounds.width() - images[5]._width, + _surface.SHtransBlitFrom(images[4], Common::Point(0, (_surface.fontHeight() + 7) * (idx + 1))); + _surface.SHtransBlitFrom(images[5], Common::Point(_bounds.width() - images[5]._width, (_surface.fontHeight() + 7) * (idx + 1) - 1)); } } @@ -515,7 +515,7 @@ void WidgetInventory::load(int mode) { // Redraw the inventory menu on the widget surface _surface.create(_bounds.width(), _bounds.height()); - _surface.fill(TRANSPARENCY); + _surface.clear(TRANSPARENCY); // Draw the window background and then the inventory on top of it makeInfoArea(_surface); @@ -531,7 +531,7 @@ void WidgetInventory::drawBars() { _surface.hLine(3, INVENTORY_YSIZE + 3, _bounds.width() - 4, INFO_TOP); _surface.hLine(3, INVENTORY_YSIZE + 4, _bounds.width() - 4, INFO_MIDDLE); _surface.hLine(3, INVENTORY_YSIZE + 5, _bounds.width() - 4, INFO_BOTTOM); - _surface.transBlitFrom(images[4], Common::Point(0, INVENTORY_YSIZE + 2)); + _surface.SHtransBlitFrom(images[4], Common::Point(0, INVENTORY_YSIZE + 2)); for (int idx = 1; idx <= NUM_INVENTORY_SHOWN / 2; ++idx) { x = idx * (INVENTORY_XSIZE + 3); @@ -540,10 +540,10 @@ void WidgetInventory::drawBars() { _surface.vLine(x + 1, 3, _bounds.height() - 4, INFO_MIDDLE); _surface.vLine(x + 2, 3, _bounds.height() - 4, INFO_BOTTOM); - _surface.transBlitFrom(images[6], Common::Point(x - 1, 1)); - _surface.transBlitFrom(images[7], Common::Point(x - 1, _bounds.height() - 4)); - _surface.transBlitFrom(images[6], Common::Point(x - 1, INVENTORY_YSIZE + 5)); - _surface.transBlitFrom(images[7], Common::Point(x - 1, INVENTORY_YSIZE + 2)); + _surface.SHtransBlitFrom(images[6], Common::Point(x - 1, 1)); + _surface.SHtransBlitFrom(images[7], Common::Point(x - 1, _bounds.height() - 4)); + _surface.SHtransBlitFrom(images[6], Common::Point(x - 1, INVENTORY_YSIZE + 5)); + _surface.SHtransBlitFrom(images[7], Common::Point(x - 1, INVENTORY_YSIZE + 2)); } _surface.hLine(x + 2, INVENTORY_YSIZE + 2, INVENTORY_YSIZE + 8, INFO_BOTTOM); @@ -566,7 +566,7 @@ void WidgetInventory::drawInventory() { // Draw the item if (itemId < inv._holdings) { ImageFrame &img = (*inv._invShapes[idx])[0]; - _surface.transBlitFrom(img, Common::Point(pt.x + (INVENTORY_XSIZE - img._width) / 2, + _surface.SHtransBlitFrom(img, Common::Point(pt.x + (INVENTORY_XSIZE - img._width) / 2, pt.y + (INVENTORY_YSIZE - img._height) / 2)); } } diff --git a/engines/sherlock/tattoo/widget_options.cpp b/engines/sherlock/tattoo/widget_options.cpp index 92bd10bbf6..81f50f3bc5 100644 --- a/engines/sherlock/tattoo/widget_options.cpp +++ b/engines/sherlock/tattoo/widget_options.cpp @@ -257,17 +257,17 @@ void WidgetOptions::render(OptionRenderMode mode) { // Setup the dialog _surface.create(_bounds.width(), _bounds.height()); - _surface.fill(TRANSPARENCY); + _surface.clear(TRANSPARENCY); makeInfoArea(); // Draw the lines separating options in the dialog int yp = _surface.fontHeight() + 7; for (int idx = 0; idx < 7; ++idx) { - _surface.transBlitFrom(images[4], Common::Point(0, yp - 1)); - _surface.transBlitFrom(images[5], Common::Point(_surface.w() - images[5]._width, yp - 1)); - _surface.hLine(3, yp, _surface.w() - 4, INFO_TOP); - _surface.hLine(3, yp + 1, _surface.w() - 4, INFO_MIDDLE); - _surface.hLine(3, yp + 2, _surface.w() - 4, INFO_BOTTOM); + _surface.SHtransBlitFrom(images[4], Common::Point(0, yp - 1)); + _surface.SHtransBlitFrom(images[5], Common::Point(_surface.width() - images[5]._width, yp - 1)); + _surface.hLine(3, yp, _surface.width() - 4, INFO_TOP); + _surface.hLine(3, yp + 1, _surface.width() - 4, INFO_MIDDLE); + _surface.hLine(3, yp + 2, _surface.width() - 4, INFO_BOTTOM); yp += _surface.fontHeight() + 7; if (idx == 1) @@ -281,7 +281,7 @@ void WidgetOptions::render(OptionRenderMode mode) { for (int idx = 0, yp = 5; idx < 11; ++idx, yp += _surface.fontHeight() + 7) { if (mode == OP_ALL || idx == _selector || idx == _oldSelector) { if (mode == OP_NAMES) - _surface.fillRect(Common::Rect(4, yp, _surface.w() - 5, yp + _surface.fontHeight() - 1), TRANSPARENCY); + _surface.fillRect(Common::Rect(4, yp, _surface.width() - 5, yp + _surface.fontHeight() - 1), TRANSPARENCY); byte color = (idx == _selector) ? COMMAND_HIGHLIGHTED : INFO_TOP; Common::String str; @@ -302,11 +302,11 @@ void WidgetOptions::render(OptionRenderMode mode) { int num = (_surface.fontHeight() + 4) & 0xfe; int sliderY = yp + num / 2 - 8; - _surface.fillRect(Common::Rect(4, sliderY - (num - 6) / 2, _surface.w() - 5, + _surface.fillRect(Common::Rect(4, sliderY - (num - 6) / 2, _surface.width() - 5, sliderY - (num - 6) / 2 + num - 1), TRANSPARENCY); _surface.fillRect(Common::Rect(_surface.widestChar(), sliderY + 2, - _surface.w() - _surface.widestChar() - 1, sliderY + 3), INFO_MIDDLE); - drawDialogRect(Common::Rect(_surface.widestChar(), sliderY, _surface.w() - _surface.widestChar(), sliderY + 6)); + _surface.width() - _surface.widestChar() - 1, sliderY + 3), INFO_MIDDLE); + drawDialogRect(Common::Rect(_surface.widestChar(), sliderY, _surface.width() - _surface.widestChar(), sliderY + 6)); _surface.fillRect(Common::Rect(_midiSliderX - 1, sliderY - (num - 6) / 2 + 2, _midiSliderX + 1, sliderY - (num - 6) / 2 + num - 3), INFO_MIDDLE); @@ -315,7 +315,7 @@ void WidgetOptions::render(OptionRenderMode mode) { if (_midiSliderX - 4 > _surface.widestChar()) _surface.fillRect(Common::Rect(_midiSliderX - 4, sliderY, _midiSliderX - 4, sliderY + 4), INFO_BOTTOM); - if (_midiSliderX + 4 < _surface.w() - _surface.widestChar()) + if (_midiSliderX + 4 < _surface.width() - _surface.widestChar()) _surface.fillRect(Common::Rect(_midiSliderX + 4, sliderY, _midiSliderX + 4, sliderY + 4), INFO_BOTTOM); break; } @@ -332,18 +332,18 @@ void WidgetOptions::render(OptionRenderMode mode) { int num = (_surface.fontHeight() + 4) & 0xfe; int sliderY = yp + num / 2 - 8; - _surface.fillRect(Common::Rect(4, sliderY - (num - 6) / 2, _surface.w() - 5, + _surface.fillRect(Common::Rect(4, sliderY - (num - 6) / 2, _surface.width() - 5, sliderY - (num - 6) / 2 + num - 1), TRANSPARENCY); - _surface.fillRect(Common::Rect(_surface.widestChar(), sliderY + 2, _surface.w() - _surface.widestChar() - 1, + _surface.fillRect(Common::Rect(_surface.widestChar(), sliderY + 2, _surface.width() - _surface.widestChar() - 1, sliderY + 3), INFO_MIDDLE); - drawDialogRect(Common::Rect(_surface.widestChar(), sliderY, _surface.w() - _surface.widestChar(), sliderY + 6)); + drawDialogRect(Common::Rect(_surface.widestChar(), sliderY, _surface.width() - _surface.widestChar(), sliderY + 6)); _surface.fillRect(Common::Rect(_digiSliderX - 1, sliderY - (num - 6) / 2 + 2, _digiSliderX + 1, sliderY - (num - 6) / 2 + num - 3), INFO_MIDDLE); drawDialogRect(Common::Rect(_digiSliderX - 3, sliderY - (num - 6) / 2, _digiSliderX + 4, sliderY - (num - 6) / 2 + num)); if (_digiSliderX - 4 > _surface.widestChar()) _surface.fillRect(Common::Rect(_digiSliderX - 4, sliderY, _digiSliderX - 4, sliderY + 4), INFO_BOTTOM); - if (_digiSliderX + 4 < _surface.w() - _surface.widestChar()) + if (_digiSliderX + 4 < _surface.width() - _surface.widestChar()) _surface.fillRect(Common::Rect(_digiSliderX + 4, sliderY, _digiSliderX + 4, sliderY + 4), INFO_BOTTOM); break; } @@ -375,7 +375,7 @@ void WidgetOptions::render(OptionRenderMode mode) { // Unless we're doing one of the Slider Controls, print the text for the line if (idx != 3 && idx != 6) { - int xp = (_surface.w() - _surface.stringWidth(str)) / 2; + int xp = (_surface.width() - _surface.stringWidth(str)) / 2; _surface.writeString(str, Common::Point(xp, yp), color); } } diff --git a/engines/sherlock/tattoo/widget_password.cpp b/engines/sherlock/tattoo/widget_password.cpp index 57a5e02653..2a2921026d 100644 --- a/engines/sherlock/tattoo/widget_password.cpp +++ b/engines/sherlock/tattoo/widget_password.cpp @@ -47,7 +47,7 @@ void WidgetPassword::show() { // Create the surface _surface.create(_bounds.width(), _bounds.height()); - _surface.fill(TRANSPARENCY); + _surface.clear(TRANSPARENCY); makeInfoArea(); // Draw the header area @@ -55,8 +55,8 @@ void WidgetPassword::show() { _surface.hLine(3, _surface.fontHeight() + 7, _bounds.width() - 4, INFO_TOP); _surface.hLine(3, _surface.fontHeight() + 8, _bounds.width() - 4, INFO_MIDDLE); _surface.hLine(3, _surface.fontHeight() + 9, _bounds.width() - 4, INFO_BOTTOM); - _surface.transBlitFrom(images[4], Common::Point(0, _surface.fontHeight() + 7 - 1)); - _surface.transBlitFrom(images[5], Common::Point(_bounds.width() - images[5]._width, _surface.fontHeight() + 7 - 1)); + _surface.SHtransBlitFrom(images[4], Common::Point(0, _surface.fontHeight() + 7 - 1)); + _surface.SHtransBlitFrom(images[5], Common::Point(_bounds.width() - images[5]._width, _surface.fontHeight() + 7 - 1)); // Set the password entry data _cursorPos = Common::Point(_surface.widestChar(), _surface.fontHeight() + 12); diff --git a/engines/sherlock/tattoo/widget_quit.cpp b/engines/sherlock/tattoo/widget_quit.cpp index f853e7f47f..ea8f2e080c 100644 --- a/engines/sherlock/tattoo/widget_quit.cpp +++ b/engines/sherlock/tattoo/widget_quit.cpp @@ -48,22 +48,22 @@ void WidgetQuit::show() { // Create the surface _surface.create(_bounds.width(), _bounds.height()); - _surface.fill(TRANSPARENCY); + _surface.clear(TRANSPARENCY); makeInfoArea(); // Draw the message text - _surface.writeString(FIXED(AreYouSureYou), Common::Point((_surface.w() - _surface.stringWidth(FIXED(AreYouSureYou))) / 2, 5), INFO_TOP); - _surface.writeString(FIXED(WishToQuit), Common::Point((_surface.w() - _surface.stringWidth(FIXED(WishToQuit))) / 2, + _surface.writeString(FIXED(AreYouSureYou), Common::Point((_surface.width() - _surface.stringWidth(FIXED(AreYouSureYou))) / 2, 5), INFO_TOP); + _surface.writeString(FIXED(WishToQuit), Common::Point((_surface.width() - _surface.stringWidth(FIXED(WishToQuit))) / 2, _surface.fontHeight() + 9), INFO_TOP); // Draw the horizontal bars seperating the commands and the message int yp = (_surface.fontHeight() + 4) * 2 + 3; for (int idx = 0; idx < 2; ++idx) { - _surface.transBlitFrom(images[4], Common::Point(0, yp - 1)); - _surface.transBlitFrom(images[5], Common::Point(_surface.w() - images[5]._width, yp - 1)); - _surface.hLine(3, yp, _surface.w() - 4, INFO_TOP); - _surface.hLine(3, yp + 1, _surface.w() - 4, INFO_MIDDLE); - _surface.hLine(3, yp + 2, _surface.w() - 4, INFO_BOTTOM); + _surface.SHtransBlitFrom(images[4], Common::Point(0, yp - 1)); + _surface.SHtransBlitFrom(images[5], Common::Point(_surface.width() - images[5]._width, yp - 1)); + _surface.hLine(3, yp, _surface.width() - 4, INFO_TOP); + _surface.hLine(3, yp + 1, _surface.width() - 4, INFO_MIDDLE); + _surface.hLine(3, yp + 2, _surface.width() - 4, INFO_BOTTOM); const char *btn = (idx == 0) ? YES : NO; _surface.writeString(btn, Common::Point((_bounds.width() - _surface.stringWidth(btn)) / 2, yp + 5), INFO_TOP); @@ -129,11 +129,11 @@ void WidgetQuit::handleEvents() { if (_select != _oldSelect) { byte color = (_select == 1) ? COMMAND_HIGHLIGHTED : INFO_TOP; int yp = (_surface.fontHeight() + 4) * 2 + 8; - _surface.writeString(FIXED(Yes), Common::Point((_surface.w() - _surface.stringWidth(FIXED(Yes))) / 2, yp), color); + _surface.writeString(FIXED(Yes), Common::Point((_surface.width() - _surface.stringWidth(FIXED(Yes))) / 2, yp), color); color = (_select == 0) ? COMMAND_HIGHLIGHTED : INFO_TOP; yp += (_surface.fontHeight() + 7); - _surface.writeString(FIXED(No), Common::Point((_surface.w() - _surface.stringWidth(FIXED(No))) / 2, yp), color); + _surface.writeString(FIXED(No), Common::Point((_surface.width() - _surface.stringWidth(FIXED(No))) / 2, yp), color); } _oldSelect = _select; diff --git a/engines/sherlock/tattoo/widget_talk.cpp b/engines/sherlock/tattoo/widget_talk.cpp index 6e7bde292f..b673f32d31 100644 --- a/engines/sherlock/tattoo/widget_talk.cpp +++ b/engines/sherlock/tattoo/widget_talk.cpp @@ -100,7 +100,7 @@ void WidgetTalk::load() { // Set up the surface _surface.create(_bounds.width(), _bounds.height()); - _surface.fill(TRANSPARENCY); + _surface.clear(TRANSPARENCY); // Form the background for the new window makeInfoArea(); @@ -389,7 +389,7 @@ void WidgetTalk::render(Highlight highlightMode) { if (highlightMode == HL_NO_HIGHLIGHTING || _statementLines[idx]._num == _selector || _statementLines[idx]._num == _oldSelector) { // Erase the line contents - _surface.fillRect(Common::Rect(3, yp, _surface.w() - BUTTON_SIZE - 3, yp + _surface.fontHeight()), TRANSPARENCY); + _surface.fillRect(Common::Rect(3, yp, _surface.width() - BUTTON_SIZE - 3, yp + _surface.fontHeight()), TRANSPARENCY); // Different coloring based on whether the option has been previously chosen or not byte color = (!talk._talkHistory[talk._converseNum][_statementLines[idx]._num]) ? diff --git a/engines/sherlock/tattoo/widget_text.cpp b/engines/sherlock/tattoo/widget_text.cpp index 86aa067301..a29cd2700f 100644 --- a/engines/sherlock/tattoo/widget_text.cpp +++ b/engines/sherlock/tattoo/widget_text.cpp @@ -80,6 +80,7 @@ void WidgetText::centerWindowOnSpeaker(int speaker) { TattooScene &scene = *(TattooScene *)_vm->_scene; Common::Point pt; + speaker &= 0x7f; bool flag = _vm->readFlags(FLAG_PLAYER_IS_HOLMES); if (people[HOLMES]._type == CHARACTER && ((speaker == HOLMES && flag) || (speaker == WATSON && !flag))) { // Place the window centered above the player @@ -165,7 +166,7 @@ void WidgetText::render(const Common::String &str) { // Allocate a surface for the window _surface.create(_bounds.width(), _bounds.height()); - _surface.fill(TRANSPARENCY); + _surface.clear(TRANSPARENCY); // Form the background for the new window makeInfoArea(); @@ -194,7 +195,7 @@ void WidgetMessage::load(const Common::String &str, int time) { // Allocate a surface for the window _surface.create(_bounds.width(), _bounds.height()); - _surface.fill(TRANSPARENCY); + _surface.clear(TRANSPARENCY); // Form the background for the new window and write the line of text makeInfoArea(); diff --git a/engines/sherlock/tattoo/widget_tooltip.cpp b/engines/sherlock/tattoo/widget_tooltip.cpp index b29f45f531..1560cb9a80 100644 --- a/engines/sherlock/tattoo/widget_tooltip.cpp +++ b/engines/sherlock/tattoo/widget_tooltip.cpp @@ -47,7 +47,7 @@ void WidgetTooltipBase::draw() { // Draw the widget directly onto the screen. Unlike other widgets, we don't draw to the back buffer, // since nothing should be drawing on top of tooltips, so there's no need to store in the back buffer - screen.transBlitFrom(_surface, Common::Point(_bounds.left - screen._currentScroll.x, + screen.SHtransBlitFrom(_surface, Common::Point(_bounds.left - screen._currentScroll.x, _bounds.top - screen._currentScroll.y)); // Store a copy of the drawn area for later erasing @@ -126,7 +126,7 @@ void WidgetTooltip::setText(const Common::String &str) { // Reallocate the text surface with the new size _surface.create(width, height); - _surface.fill(TRANSPARENCY); + _surface.clear(TRANSPARENCY); if (line2.empty()) { // Only a single line diff --git a/engines/sherlock/tattoo/widget_verbs.cpp b/engines/sherlock/tattoo/widget_verbs.cpp index 499afb2e79..5041888ffb 100644 --- a/engines/sherlock/tattoo/widget_verbs.cpp +++ b/engines/sherlock/tattoo/widget_verbs.cpp @@ -127,7 +127,7 @@ void WidgetVerbs::render() { // Create the drawing surface _surface.create(_bounds.width(), _bounds.height()); - _surface.fill(TRANSPARENCY); + _surface.clear(TRANSPARENCY); // Draw basic background makeInfoArea(); @@ -142,8 +142,8 @@ void WidgetVerbs::render() { _surface.hLine(3, (_surface.fontHeight() + 7) * (idx + 1) + 1, _bounds.width() - 4, INFO_MIDDLE); _surface.hLine(3, (_surface.fontHeight() + 7) * (idx + 1) + 2, _bounds.width() - 4, INFO_BOTTOM); - _surface.transBlitFrom(images[4], Common::Point(0, (_surface.fontHeight() + 7) * (idx + 1) - 1)); - _surface.transBlitFrom(images[5], Common::Point(_bounds.width() - images[5]._width, + _surface.SHtransBlitFrom(images[4], Common::Point(0, (_surface.fontHeight() + 7) * (idx + 1) - 1)); + _surface.SHtransBlitFrom(images[5], Common::Point(_bounds.width() - images[5]._width, (_surface.fontHeight() + 7) * (idx + 1) - 1)); } } diff --git a/engines/sky/detection.cpp b/engines/sky/detection.cpp index 8a8967629d..4b91f50a61 100644 --- a/engines/sky/detection.cpp +++ b/engines/sky/detection.cpp @@ -136,7 +136,7 @@ const ExtraGuiOptions SkyMetaEngine::getExtraGuiOptions(const Common::String &ta } GameDescriptor SkyMetaEngine::findGame(const char *gameid) const { - if (0 == scumm_stricmp(gameid, skySetting.gameid)) + if (0 == scumm_stricmp(gameid, skySetting.gameId)) return skySetting; return GameDescriptor(); } @@ -175,7 +175,7 @@ GameList SkyMetaEngine::detectGames(const Common::FSList &fslist) const { // Match found, add to list of candidates, then abort inner loop. // The game detector uses US English by default. We want British // English to match the recorded voices better. - GameDescriptor dg(skySetting.gameid, skySetting.description, Common::UNK_LANG, Common::kPlatformUnknown); + GameDescriptor dg(skySetting.gameId, skySetting.description, Common::UNK_LANG, Common::kPlatformUnknown); const SkyVersion *sv = skyVersions; while (sv->dinnerTableEntries) { if (dinnerTableEntries == sv->dinnerTableEntries && @@ -223,7 +223,6 @@ SaveStateList SkyMetaEngine::listSaves(const char *target) const { // Find all saves Common::StringArray filenames; filenames = saveFileMan->listSavefiles("SKY-VM.###"); - sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) // Slot 0 is the autosave, if it exists. // TODO: Check for the existence of the autosave -- but this require us @@ -243,6 +242,8 @@ SaveStateList SkyMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/sword1/configure.engine b/engines/sword1/configure.engine index 0578d176a9..1d17903b69 100644 --- a/engines/sword1/configure.engine +++ b/engines/sword1/configure.engine @@ -1,3 +1,3 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] -add_engine sword1 "Broken Sword" yes +add_engine sword1 "Broken Sword" yes "" "" "highres" diff --git a/engines/sword1/detection.cpp b/engines/sword1/detection.cpp index 99c4bda9e9..0edf856125 100644 --- a/engines/sword1/detection.cpp +++ b/engines/sword1/detection.cpp @@ -128,17 +128,17 @@ GameList SwordMetaEngine::getSupportedGames() const { } GameDescriptor SwordMetaEngine::findGame(const char *gameid) const { - if (0 == scumm_stricmp(gameid, sword1FullSettings.gameid)) + if (0 == scumm_stricmp(gameid, sword1FullSettings.gameId)) return sword1FullSettings; - if (0 == scumm_stricmp(gameid, sword1DemoSettings.gameid)) + if (0 == scumm_stricmp(gameid, sword1DemoSettings.gameId)) return sword1DemoSettings; - if (0 == scumm_stricmp(gameid, sword1MacFullSettings.gameid)) + if (0 == scumm_stricmp(gameid, sword1MacFullSettings.gameId)) return sword1MacFullSettings; - if (0 == scumm_stricmp(gameid, sword1MacDemoSettings.gameid)) + if (0 == scumm_stricmp(gameid, sword1MacDemoSettings.gameId)) return sword1MacDemoSettings; - if (0 == scumm_stricmp(gameid, sword1PSXSettings.gameid)) + if (0 == scumm_stricmp(gameid, sword1PSXSettings.gameId)) return sword1PSXSettings; - if (0 == scumm_stricmp(gameid, sword1PSXDemoSettings.gameid)) + if (0 == scumm_stricmp(gameid, sword1PSXDemoSettings.gameId)) return sword1PSXDemoSettings; return GameDescriptor(); } @@ -241,7 +241,6 @@ SaveStateList SwordMetaEngine::listSaves(const char *target) const { char saveName[40]; Common::StringArray filenames = saveFileMan->listSavefiles("sword1.###"); - sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) int slotNum = 0; for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { @@ -259,6 +258,8 @@ SaveStateList SwordMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/sword1/objectman.cpp b/engines/sword1/objectman.cpp index 07f19154a0..651e47f211 100644 --- a/engines/sword1/objectman.cpp +++ b/engines/sword1/objectman.cpp @@ -119,7 +119,7 @@ char *ObjectMan::lockText(uint32 textId, uint8 lang) { return NULL; addr += sizeof(Header); if ((textId & ITM_ID) >= _resMan->readUint32(addr)) { - // Workaround for missing sentences in some langages in the demo. + // Workaround for missing sentences in some languages in the demo. switch(textId) { case 8455194: return const_cast<char *>(_translationId8455194[lang]); @@ -160,7 +160,7 @@ char *ObjectMan::lockText(uint32 textId, uint8 lang) { } uint32 offset = _resMan->readUint32(addr + ((textId & ITM_ID) + 1) * 4); if (offset == 0) { - // Workaround bug for missing sentence in some langages in Syria (see bug #1977094). + // Workaround bug for missing sentence in some languages in Syria (see bug #1977094). // We use the hardcoded text in this case. if (textId == 2950145) return const_cast<char *>(_translationId2950145[lang]); @@ -223,7 +223,7 @@ void ObjectMan::saveLiveList(uint16 *dest) { } // String displayed when a subtitle sentence is missing in the cluster file. -// It happens with at least one sentence in Syria in some langages (see bug +// It happens with at least one sentence in Syria in some languages (see bug // #1977094). // Note: an empty string or a null pointer causes a crash. diff --git a/engines/sword1/objectman.h b/engines/sword1/objectman.h index fef1a8da3a..79be82c02f 100644 --- a/engines/sword1/objectman.h +++ b/engines/sword1/objectman.h @@ -62,7 +62,7 @@ private: uint16 _liveList[TOTAL_SECTIONS]; //which sections are active uint8 *_cptData[TOTAL_SECTIONS]; static char _missingSubTitleStr[]; - static const char *const _translationId2950145[7]; //translation for textId 2950145 (missing from cluster file for some langages) + static const char *const _translationId2950145[7]; //translation for textId 2950145 (missing from cluster file for some languages) static const char *const _translationId8455194[7]; //translation for textId 8455194 (missing in the demo) static const char *const _translationId8455195[7]; //translation for textId 8455195 (missing in the demo) static const char *const _translationId8455196[7]; //translation for textId 8455196 (missing in the demo) diff --git a/engines/sword1/router.cpp b/engines/sword1/router.cpp index 72c8440e1c..0c2e9569b6 100644 --- a/engines/sword1/router.cpp +++ b/engines/sword1/router.cpp @@ -1455,7 +1455,7 @@ int32 Router::newCheck(int32 status, int32 x1, int32 y1, int32 x2, int32 y2) { * newCheck differs from check in that that 4 route options are * considered corresponding to actual walked routes. * - * Note distance doesnt take account of shrinking ??? + * Note distance doesn't take account of shrinking ??? * * Note Bars array must be properly calculated ie min max dx dy co *********************************************************************/ diff --git a/engines/sword2/configure.engine b/engines/sword2/configure.engine index 7153605433..a794e7287c 100644 --- a/engines/sword2/configure.engine +++ b/engines/sword2/configure.engine @@ -1,3 +1,3 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] -add_engine sword2 "Broken Sword II" yes +add_engine sword2 "Broken Sword II" yes "" "" "highres" diff --git a/engines/sword2/router.cpp b/engines/sword2/router.cpp index d3f274dd2c..e95fa367a0 100644 --- a/engines/sword2/router.cpp +++ b/engines/sword2/router.cpp @@ -1712,7 +1712,7 @@ int32 Router::newCheck(int32 status, int32 x1, int32 y1, int32 x2, int32 y2) { * newCheck differs from check in that that 4 route options are * considered corresponding to actual walked routes. * - * Note distance doesnt take account of shrinking ??? + * Note distance doesn't take account of shrinking ??? * * Note Bars array must be properly calculated ie min max dx dy co *********************************************************************/ diff --git a/engines/sword2/sword2.cpp b/engines/sword2/sword2.cpp index baedecb52f..44371bf6cf 100644 --- a/engines/sword2/sword2.cpp +++ b/engines/sword2/sword2.cpp @@ -234,7 +234,6 @@ SaveStateList Sword2MetaEngine::listSaves(const char *target) const { pattern += ".###"; filenames = saveFileMan->listSavefiles(pattern); - sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) SaveStateList saveList; for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { @@ -252,6 +251,8 @@ SaveStateList Sword2MetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/sword25/POTFILES b/engines/sword25/POTFILES new file mode 100644 index 0000000000..f4b0e6fc27 --- /dev/null +++ b/engines/sword25/POTFILES @@ -0,0 +1 @@ +engines/sword25/detection.cpp diff --git a/engines/sword25/configure.engine b/engines/sword25/configure.engine index 6a9428c758..f805483f54 100644 --- a/engines/sword25/configure.engine +++ b/engines/sword25/configure.engine @@ -1,3 +1,3 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] -add_engine sword25 "Broken Sword 2.5" yes "" "" "png zlib 16bit" +add_engine sword25 "Broken Sword 2.5" yes "" "" "png zlib 16bit highres" diff --git a/engines/sword25/detection.cpp b/engines/sword25/detection.cpp index df68d11609..4ca565c972 100644 --- a/engines/sword25/detection.cpp +++ b/engines/sword25/detection.cpp @@ -21,6 +21,7 @@ */ #include "base/plugins.h" +#include "common/translation.h" #include "engines/advancedDetector.h" #include "sword25/sword25.h" @@ -41,10 +42,17 @@ static const char *directoryGlobs[] = { 0 }; +static const ExtraGuiOption sword25ExtraGuiOption = { + _s("Use English speech"), + _s("Use English speech instead of German for every language other than German"), + "english_speech", + false +}; + class Sword25MetaEngine : public AdvancedMetaEngine { public: Sword25MetaEngine() : AdvancedMetaEngine(Sword25::gameDescriptions, sizeof(ADGameDescription), sword25Game) { - _guioptions = GUIO1(GUIO_NOMIDI); + _guiOptions = GUIO1(GUIO_NOMIDI); _maxScanDepth = 2; _directoryGlobs = directoryGlobs; } @@ -58,6 +66,7 @@ public: virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; virtual bool hasFeature(MetaEngineFeature f) const; + virtual const ExtraGuiOptions getExtraGuiOptions(const Common::String &target) const; virtual int getMaximumSaveSlot() const { return Sword25::PersistenceService::getSlotCount(); } virtual SaveStateList listSaves(const char *target) const; }; @@ -74,6 +83,12 @@ bool Sword25MetaEngine::hasFeature(MetaEngineFeature f) const { (f == kSupportsListSaves); } +const ExtraGuiOptions Sword25MetaEngine::getExtraGuiOptions(const Common::String &target) const { + ExtraGuiOptions options; + options.push_back(sword25ExtraGuiOption); + return options; +} + SaveStateList Sword25MetaEngine::listSaves(const char *target) const { Common::String pattern = target; pattern = pattern + ".???"; diff --git a/engines/sword25/detection_tables.h b/engines/sword25/detection_tables.h index b58f430fcf..927060bf18 100644 --- a/engines/sword25/detection_tables.h +++ b/engines/sword25/detection_tables.h @@ -29,7 +29,7 @@ static const ADGameDescription gameDescriptions[] = { AD_ENTRY1s("data.b25c", "f8b6e03ada2d2f6cf27fbc11ad1572e9", 654310588), Common::EN_ANY, Common::kPlatformUnknown, - ADGF_TESTING, + ADGF_NO_FLAGS, GUIO1(GUIO_NOASPECT) }, { @@ -38,7 +38,7 @@ static const ADGameDescription gameDescriptions[] = { AD_ENTRY1s("lang_fr.b25c", "690caf157387e06d2c3d1ca53c43f428", 1006043), Common::FR_FRA, Common::kPlatformUnknown, - ADGF_TESTING, + ADGF_NO_FLAGS, GUIO1(GUIO_NOASPECT) }, { @@ -47,7 +47,7 @@ static const ADGameDescription gameDescriptions[] = { AD_ENTRY1s("data.b25c", "f8b6e03ada2d2f6cf27fbc11ad1572e9", 654310588), Common::DE_DEU, Common::kPlatformUnknown, - ADGF_TESTING, + ADGF_NO_FLAGS, GUIO1(GUIO_NOASPECT) }, { @@ -56,7 +56,7 @@ static const ADGameDescription gameDescriptions[] = { AD_ENTRY1s("lang_hr.b25c", "e881054d1f8ec1e527422fc521c25405", 1273217), Common::HR_HRV, Common::kPlatformUnknown, - ADGF_TESTING, + ADGF_NO_FLAGS, GUIO1(GUIO_NOASPECT) }, { @@ -65,7 +65,7 @@ static const ADGameDescription gameDescriptions[] = { AD_ENTRY1s("lang_it.b25c", "f3325666da0515cc2b42062e953c0889", 996197), Common::IT_ITA, Common::kPlatformUnknown, - ADGF_TESTING, + ADGF_NO_FLAGS, GUIO1(GUIO_NOASPECT) }, { @@ -74,7 +74,7 @@ static const ADGameDescription gameDescriptions[] = { AD_ENTRY1s("lang_pl.b25c", "49dc1a20f95391a808e475c49be2bac0", 1281799), Common::PL_POL, Common::kPlatformUnknown, - ADGF_TESTING, + ADGF_NO_FLAGS, GUIO1(GUIO_NOASPECT) }, { @@ -83,7 +83,7 @@ static const ADGameDescription gameDescriptions[] = { AD_ENTRY1s("lang_pt.b25c", "1df701432f9e13dcefe1adeb890b9c69", 993812), Common::PT_BRA, Common::kPlatformUnknown, - ADGF_TESTING, + ADGF_NO_FLAGS, GUIO1(GUIO_NOASPECT) }, { @@ -92,7 +92,7 @@ static const ADGameDescription gameDescriptions[] = { AD_ENTRY1s("lang_ru.b25c", "deb33dd2f90a71ff60181918a8ce5063", 1235378), Common::RU_RUS, Common::kPlatformUnknown, - ADGF_TESTING, + ADGF_NO_FLAGS, GUIO1(GUIO_NOASPECT) }, { @@ -101,7 +101,7 @@ static const ADGameDescription gameDescriptions[] = { AD_ENTRY1s("lang_es.b25c", "384c19072d83725f351bb9ecb4d3f02b", 987965), Common::ES_ESP, Common::kPlatformUnknown, - ADGF_TESTING, + ADGF_NO_FLAGS, GUIO1(GUIO_NOASPECT) }, // Hungarian "psylog" version. @@ -112,7 +112,7 @@ static const ADGameDescription gameDescriptions[] = { AD_ENTRY1s("lang_hu.b25c", "7de51a3b4926a192549e75b1a7d81667", 1864915), Common::HU_HUN, Common::kPlatformUnknown, - ADGF_TESTING, + ADGF_NO_FLAGS, GUIO1(GUIO_NOASPECT) }, @@ -126,19 +126,22 @@ static const ADGameDescription gameDescriptions[] = { AD_LISTEND}, Common::EN_ANY, Common::kPlatformUnknown, - GF_EXTRACTED | ADGF_TESTING, + GF_EXTRACTED | ADGF_NO_FLAGS, GUIO1(GUIO_NOASPECT) }, // Distributed by ScummVM // Contains all language packs, English voice-overs and Hungarian version + // Mark it as Unknown Language since it contains multiple languages. If we + // mark it as English, then changing the language in-game causes the detection + // to fail the next time we try to start the engine. { "sword25", "Latest version", AD_ENTRY1s("data.b25c", "880a8a67faf4a4e7ab62cf114b771428", 827397764), - Common::EN_ANY, + Common::UNK_LANG, Common::kPlatformUnknown, - ADGF_TESTING, + ADGF_NO_FLAGS, GUIO1(GUIO_NOASPECT) }, diff --git a/engines/sword25/fmv/movieplayer.cpp b/engines/sword25/fmv/movieplayer.cpp index eb0f0390dc..62a897a332 100644 --- a/engines/sword25/fmv/movieplayer.cpp +++ b/engines/sword25/fmv/movieplayer.cpp @@ -58,6 +58,8 @@ MoviePlayer::~MoviePlayer() { } bool MoviePlayer::loadMovie(const Common::String &filename, uint z) { + if (isMovieLoaded()) + unloadMovie(); // Get the file and load it into the decoder Common::SeekableReadStream *in = Kernel::getInstance()->getPackage()->getStream(filename); _decoder.loadStream(in); diff --git a/engines/sword25/fmv/movieplayer.h b/engines/sword25/fmv/movieplayer.h index 95fb05ee60..b59b223b4c 100644 --- a/engines/sword25/fmv/movieplayer.h +++ b/engines/sword25/fmv/movieplayer.h @@ -67,7 +67,7 @@ public: * movie file, it will be unloaded and, if necessary, stopped playing. * @param Filename The filename of the movie file to be loaded * @param Z Z indicates the position of the film on the main graphics layer - * @return Returns false if an error occured while loading, otherwise true. + * @return Returns false if an error occurred while loading, otherwise true. */ bool loadMovie(const Common::String &filename, uint z); diff --git a/engines/sword25/gfx/animationresource.cpp b/engines/sword25/gfx/animationresource.cpp index 431d466658..423a2b86b4 100644 --- a/engines/sword25/gfx/animationresource.cpp +++ b/engines/sword25/gfx/animationresource.cpp @@ -211,8 +211,9 @@ bool AnimationResource::precacheAllFrames() const { error("Could not precache \"%s\".", (*iter).fileName.c_str()); return false; } -#else - Kernel::getInstance()->getResourceManager()->requestResource((*iter).fileName); +#else + Resource *pResource = Kernel::getInstance()->getResourceManager()->requestResource((*iter).fileName); + pResource->release(); //unlock precached resource #endif } diff --git a/engines/sword25/gfx/fontresource.cpp b/engines/sword25/gfx/fontresource.cpp index c4d4c3c52e..1d7aedcb6e 100644 --- a/engines/sword25/gfx/fontresource.cpp +++ b/engines/sword25/gfx/fontresource.cpp @@ -103,8 +103,9 @@ bool FontResource::parserCallback_font(ParserNode *node) { if (!_pKernel->getResourceManager()->precacheResource(_bitmapFileName)) { error("Could not precache \"%s\".", _bitmapFileName.c_str()); } -#else - _pKernel->getResourceManager()->requestResource(_bitmapFileName); +#else + Resource *pResource = _pKernel->getResourceManager()->requestResource(_bitmapFileName); + pResource->release(); //unlock precached resource #endif return true; diff --git a/engines/sword25/gfx/text.cpp b/engines/sword25/gfx/text.cpp index d409c538c0..769c9b1162 100644 --- a/engines/sword25/gfx/text.cpp +++ b/engines/sword25/gfx/text.cpp @@ -77,7 +77,8 @@ bool Text::setFont(const Common::String &font) { return false; } #else - getResourceManager()->requestResource(font); + Resource *pResource = getResourceManager()->requestResource(font); + pResource->release(); //unlock precached resource _font = font; updateFormat(); forceRefresh(); diff --git a/engines/sword25/kernel/kernel_script.cpp b/engines/sword25/kernel/kernel_script.cpp index 1b7c6a6f14..40dcbd5b98 100644 --- a/engines/sword25/kernel/kernel_script.cpp +++ b/engines/sword25/kernel/kernel_script.cpp @@ -257,6 +257,7 @@ static int processMessages(lua_State *L) { // to the closeWanted() opcode; see also the TODO comment in there. lua_pushbooleancpp(L, !Engine::shouldQuit()); + g_system->delayMillis(10); return 1; } diff --git a/engines/sword25/package/packagemanager.cpp b/engines/sword25/package/packagemanager.cpp index 2db4f2da74..457dda6268 100644 --- a/engines/sword25/package/packagemanager.cpp +++ b/engines/sword25/package/packagemanager.cpp @@ -56,7 +56,8 @@ static Common::String normalizePath(const Common::String &path, const Common::St PackageManager::PackageManager(Kernel *pKernel) : Service(pKernel), _currentDirectory(PATH_SEPARATOR), - _rootFolder(ConfMan.get("path")) { + _rootFolder(ConfMan.get("path")), + _useEnglishSpeech(ConfMan.getBool("english_speech")) { if (!registerScriptBindings()) error("Script bindings could not be registered."); else @@ -71,14 +72,34 @@ PackageManager::~PackageManager() { } +Common::String PackageManager::ensureSpeechLang(const Common::String &fileName) { + if (!_useEnglishSpeech || fileName.size() < 9 || !fileName.hasPrefix("/speech/")) + return fileName; + + // Always keep German speech as a fallback in case the English speech pack is not present. + // However this means we cannot play with German text and English voice. + if (fileName.hasPrefix("/speech/de")) + return fileName; + + Common::String newFileName = "/speech/en"; + int fileIdx = 9; + while (fileIdx < fileName.size() && fileName[fileIdx] != '/') + ++fileIdx; + if (fileIdx < fileName.size()) + newFileName += fileName.c_str() + fileIdx; + + return newFileName; +} + /** * Scans through the archive list for a specified file */ Common::ArchiveMemberPtr PackageManager::getArchiveMember(const Common::String &fileName) { + Common::String fileName2 = ensureSpeechLang(fileName); // Loop through checking each archive Common::List<ArchiveEntry *>::iterator i; for (i = _archiveList.begin(); i != _archiveList.end(); ++i) { - if (!fileName.hasPrefix((*i)->_mountPath)) { + if (!fileName2.hasPrefix((*i)->_mountPath)) { // The mount path is in different subtree. Skipping continue; } @@ -87,7 +108,7 @@ Common::ArchiveMemberPtr PackageManager::getArchiveMember(const Common::String & Common::Archive *archiveFolder = (*i)->archive; // Construct relative path - Common::String resPath(&fileName.c_str()[(*i)->_mountPath.size()]); + Common::String resPath(&fileName2.c_str()[(*i)->_mountPath.size()]); if (archiveFolder->hasFile(resPath)) { return archiveFolder->getMember(resPath); @@ -203,23 +224,29 @@ bool PackageManager::changeDirectory(const Common::String &directory) { } Common::String PackageManager::getAbsolutePath(const Common::String &fileName) { - return normalizePath(fileName, _currentDirectory); + return normalizePath(ensureSpeechLang(fileName), _currentDirectory); } bool PackageManager::fileExists(const Common::String &fileName) { // FIXME: The current Zip implementation doesn't support getting a folder entry, which is needed for detecting - // the English voick pack - if (fileName == "/speech/en") { + // the English voice pack + Common::String fileName2 = ensureSpeechLang(fileName); + if (fileName2 == "/speech/en") { // To get around this, change to detecting one of the files in the folder - return getArchiveMember(normalizePath(fileName + "/APO0001.ogg", _currentDirectory)); + bool exists = getArchiveMember(normalizePath(fileName2 + "/APO0001.ogg", _currentDirectory)); + if (!exists && _useEnglishSpeech) { + _useEnglishSpeech = false; + warning("English speech not found"); + } + return exists; } - Common::ArchiveMemberPtr fileNode = getArchiveMember(normalizePath(fileName, _currentDirectory)); + Common::ArchiveMemberPtr fileNode = getArchiveMember(normalizePath(fileName2, _currentDirectory)); return fileNode; } int PackageManager::doSearch(Common::ArchiveMemberList &list, const Common::String &filter, const Common::String &path, uint typeFilter) { - Common::String normalizedFilter = normalizePath(filter, _currentDirectory); + Common::String normalizedFilter = normalizePath(ensureSpeechLang(filter), _currentDirectory); int num = 0; if (path.size() > 0) diff --git a/engines/sword25/package/packagemanager.h b/engines/sword25/package/packagemanager.h index a1806a4046..5475cb02fc 100644 --- a/engines/sword25/package/packagemanager.h +++ b/engines/sword25/package/packagemanager.h @@ -87,6 +87,9 @@ private: Common::String _currentDirectory; Common::FSNode _rootFolder; Common::List<ArchiveEntry *> _archiveList; + + bool _useEnglishSpeech; + Common::String ensureSpeechLang(const Common::String &fileName); Common::ArchiveMemberPtr getArchiveMember(const Common::String &fileName); diff --git a/engines/sword25/script/luacallback.cpp b/engines/sword25/script/luacallback.cpp index 72f7e01612..acfda498c6 100644 --- a/engines/sword25/script/luacallback.cpp +++ b/engines/sword25/script/luacallback.cpp @@ -119,7 +119,7 @@ void LuaCallback::invokeCallbackFunctions(lua_State *L, uint objectHandle) { // Lua_pcall the function and the parameters pop themselves from the stack if (lua_pcall(L, argumentCount, 0, 0) != 0) { // An error has occurred - error("An error occured executing a callback function: %s", lua_tostring(L, -1)); + error("An error occurred executing a callback function: %s", lua_tostring(L, -1)); // Pop error message from the stack lua_pop(L, 1); diff --git a/engines/sword25/script/luascript.cpp b/engines/sword25/script/luascript.cpp index e93289596b..3aca6676ac 100644 --- a/engines/sword25/script/luascript.cpp +++ b/engines/sword25/script/luascript.cpp @@ -214,7 +214,7 @@ bool LuaScriptEngine::executeBuffer(const byte *data, uint size, const Common::S // Run buffer contents if (lua_pcall(_state, 0, 0, -2) != 0) { - error("An error occured while executing \"%s\":\n%s.", + error("An error occurred while executing \"%s\":\n%s.", name.c_str(), lua_tostring(_state, -1)); lua_pop(_state, 2); diff --git a/engines/sword25/sword25.cpp b/engines/sword25/sword25.cpp index 5223481d50..b6f2641714 100644 --- a/engines/sword25/sword25.cpp +++ b/engines/sword25/sword25.cpp @@ -120,7 +120,7 @@ Common::Error Sword25Engine::appStart() { // Pass the command line to the script engine. ScriptEngine *scriptPtr = Kernel::getInstance()->getScript(); if (!scriptPtr) { - error("Script intialization failed."); + error("Script initialization failed."); return Common::kUnknownError; } diff --git a/engines/sword25/util/lua/lbaselib.cpp b/engines/sword25/util/lua/lbaselib.cpp index 659c61d956..ec044970ad 100644 --- a/engines/sword25/util/lua/lbaselib.cpp +++ b/engines/sword25/util/lua/lbaselib.cpp @@ -492,7 +492,7 @@ static int costatus (lua_State *L, lua_State *co) { else return CO_SUS; /* initial state */ } - default: /* some error occured */ + default: /* some error occurred */ return CO_DEAD; } } diff --git a/engines/sword25/util/lua/ldo.cpp b/engines/sword25/util/lua/ldo.cpp index a230097f2a..f4139cb9fc 100644 --- a/engines/sword25/util/lua/ldo.cpp +++ b/engines/sword25/util/lua/ldo.cpp @@ -111,10 +111,9 @@ static const char* luaErrorDescription[] = { void luaD_throw (lua_State *L, int errcode) { if (L->errorJmp) { L->errorJmp->status = errcode; - // LUAI_THROW has been replaced with an error message in ScummVM, together - // with the LUA error code and description - //LUAI_THROW(L, L->errorJmp); - error("LUA error occured, error code is %d (%s)", errcode, luaErrorDescription[errcode]); + // LUAI_THROW is sometimes used to ignore the error and restore LUA state + LUAI_THROW(L, L->errorJmp); + error("LUA error occurred, error code is %d (%s)", errcode, luaErrorDescription[errcode]); } else { L->status = cast_byte(errcode); diff --git a/engines/sword25/util/lua/luaconf.h b/engines/sword25/util/lua/luaconf.h index fb85983998..53d0f55290 100644 --- a/engines/sword25/util/lua/luaconf.h +++ b/engines/sword25/util/lua/luaconf.h @@ -621,7 +621,7 @@ union luai_Cast { double l_d; long l_l; }; #else /* default handling with long jumps */ -//#define LUAI_THROW(L,c) longjmp((c)->b, 1) // replaced with error() in ScummVM +#define LUAI_THROW(L,c) longjmp((c)->b, 1) #define LUAI_TRY(L,c,a) if (setjmp((c)->b) == 0) { a } #define luai_jmpbuf jmp_buf diff --git a/engines/teenagent/detection.cpp b/engines/teenagent/detection.cpp index 4fd3a06311..caa7bdbec9 100644 --- a/engines/teenagent/detection.cpp +++ b/engines/teenagent/detection.cpp @@ -88,7 +88,7 @@ enum { class TeenAgentMetaEngine : public AdvancedMetaEngine { public: TeenAgentMetaEngine() : AdvancedMetaEngine(teenAgentGameDescriptions, sizeof(ADGameDescription), teenAgentGames) { - _singleid = "teenagent"; + _singleId = "teenagent"; } virtual const char *getName() const { @@ -128,7 +128,6 @@ public: pattern += ".##"; Common::StringArray filenames = g_system->getSavefileManager()->listSavefiles(pattern); - Common::sort(filenames.begin(), filenames.end()); SaveStateList saveList; for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { @@ -145,6 +144,8 @@ public: saveList.push_back(SaveStateDescriptor(slot, buf)); } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/testbed/detection.cpp b/engines/testbed/detection.cpp index 348ade62b0..7aff7a1805 100644 --- a/engines/testbed/detection.cpp +++ b/engines/testbed/detection.cpp @@ -49,7 +49,7 @@ class TestbedMetaEngine : public AdvancedMetaEngine { public: TestbedMetaEngine() : AdvancedMetaEngine(testbedDescriptions, sizeof(ADGameDescription), testbed_setting) { _md5Bytes = 512; - _singleid = "testbed"; + _singleId = "testbed"; } virtual const char *getName() const { diff --git a/engines/tinsel/detection.cpp b/engines/tinsel/detection.cpp index 7cb291275c..2fde6e788a 100644 --- a/engines/tinsel/detection.cpp +++ b/engines/tinsel/detection.cpp @@ -85,7 +85,7 @@ static const PlainGameDescriptor tinselGames[] = { class TinselMetaEngine : public AdvancedMetaEngine { public: TinselMetaEngine() : AdvancedMetaEngine(Tinsel::gameDescriptions, sizeof(Tinsel::TinselGameDescription), tinselGames) { - _singleid = "tinsel"; + _singleId = "tinsel"; } virtual const char *getName() const { @@ -136,7 +136,6 @@ SaveStateList TinselMetaEngine::listSaves(const char *target) const { Common::String pattern = target; pattern = pattern + ".###"; Common::StringArray files = g_system->getSavefileManager()->listSavefiles(pattern); - sort(files.begin(), files.end()); // Sort (hopefully ensuring we are sorted numerically..) SaveStateList saveList; int slotNum = 0; @@ -160,6 +159,8 @@ SaveStateList TinselMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } @@ -227,8 +228,8 @@ const ADGameDescription *TinselMetaEngine::fallbackDetect(const FileMap &allFile // Check which files are included in some dw2 ADGameDescription *and* present // in fslist without a '1' suffix character. Compute MD5s and file sizes for these files. - for (g = &Tinsel::gameDescriptions[0]; g->desc.gameid != 0; ++g) { - if (strcmp(g->desc.gameid, "dw2") != 0) + for (g = &Tinsel::gameDescriptions[0]; g->desc.gameId != 0; ++g) { + if (strcmp(g->desc.gameId, "dw2") != 0) continue; for (fileDesc = g->desc.filesDescriptions; fileDesc->fileName; fileDesc++) { @@ -264,8 +265,8 @@ const ADGameDescription *TinselMetaEngine::fallbackDetect(const FileMap &allFile int maxFilesMatched = 0; // MD5 based matching - for (g = &Tinsel::gameDescriptions[0]; g->desc.gameid != 0; ++g) { - if (strcmp(g->desc.gameid, "dw2") != 0) + for (g = &Tinsel::gameDescriptions[0]; g->desc.gameId != 0; ++g) { + if (strcmp(g->desc.gameId, "dw2") != 0) continue; bool fileMissing = false; diff --git a/engines/tinsel/graphics.cpp b/engines/tinsel/graphics.cpp index 0282aff3cb..2ff96a9b64 100644 --- a/engines/tinsel/graphics.cpp +++ b/engines/tinsel/graphics.cpp @@ -164,7 +164,7 @@ static void t0WrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool apply // Horizontal loop for (int x = 0; x < pObj->width; ) { - uint32 numBytes = READ_UINT32(srcP); + uint32 numBytes = READ_LE_UINT32(srcP); srcP += sizeof(uint32); bool repeatFlag = (numBytes & 0x80000000L) != 0; numBytes &= 0x7fffffff; diff --git a/engines/tinsel/music.cpp b/engines/tinsel/music.cpp index 2a9804263a..e0c1f8d1f9 100644 --- a/engines/tinsel/music.cpp +++ b/engines/tinsel/music.cpp @@ -381,11 +381,25 @@ MidiMusicPlayer::MidiMusicPlayer(TinselEngine *vm) { bool milesAudioEnabled = false; if (vm->getPlatform() == Common::kPlatformDOS) { - // Enable Miles Audio for DOS only - milesAudioEnabled = true; + // Enable Miles Audio for DOS platform only... + switch (vm->getGameID()) { + case GID_DW1: + if (!vm->getIsADGFDemo()) { + // ...for Discworld 1 + milesAudioEnabled = true; + } else { + if (vm->isV1CD()) { + // ...and for Discworld 1 CD Demo + milesAudioEnabled = true; + } + } + break; + default: + break; + } } - if ((vm->getGameId() == GID_DW1) && (milesAudioEnabled)) { + if (milesAudioEnabled) { // Discworld 1 (DOS) uses Miles Audio 3 // use our own Miles Audio drivers // diff --git a/engines/tinsel/tinsel.cpp b/engines/tinsel/tinsel.cpp index 77932a5cdf..2adddca4fd 100644 --- a/engines/tinsel/tinsel.cpp +++ b/engines/tinsel/tinsel.cpp @@ -820,7 +820,7 @@ const char *const TinselEngine::_textFiles[][3] = { TinselEngine::TinselEngine(OSystem *syst, const TinselGameDescription *gameDesc) : Engine(syst), _gameDescription(gameDesc), _random("tinsel"), - _sound(0), _midiMusic(0), _pcmMusic(0), _bmv(0) { + _console(0), _sound(0), _midiMusic(0), _pcmMusic(0), _bmv(0) { _vm = this; _config = new Config(this); diff --git a/engines/toltecs/configure.engine b/engines/toltecs/configure.engine index be5533efa2..8310a6d6ef 100644 --- a/engines/toltecs/configure.engine +++ b/engines/toltecs/configure.engine @@ -1,3 +1,3 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] -add_engine toltecs "3 Skulls of the Toltecs" yes +add_engine toltecs "3 Skulls of the Toltecs" yes "" "" "highres" diff --git a/engines/toltecs/detection.cpp b/engines/toltecs/detection.cpp index fb7d8121ff..7c707895e6 100644 --- a/engines/toltecs/detection.cpp +++ b/engines/toltecs/detection.cpp @@ -206,7 +206,7 @@ static const ExtraGuiOption toltecsExtraGuiOption = { class ToltecsMetaEngine : public AdvancedMetaEngine { public: ToltecsMetaEngine() : AdvancedMetaEngine(Toltecs::gameDescriptions, sizeof(Toltecs::ToltecsGameDescription), toltecsGames) { - _singleid = "toltecs"; + _singleId = "toltecs"; } virtual const char *getName() const { @@ -266,7 +266,6 @@ SaveStateList ToltecsMetaEngine::listSaves(const char *target) const { Common::StringArray filenames; filenames = saveFileMan->listSavefiles(pattern.c_str()); - Common::sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) SaveStateList saveList; for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { @@ -284,6 +283,8 @@ SaveStateList ToltecsMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/tony/configure.engine b/engines/tony/configure.engine index f85f45d158..2df4434982 100644 --- a/engines/tony/configure.engine +++ b/engines/tony/configure.engine @@ -1,3 +1,3 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] -add_engine tony "Tony Tough and the Night of Roasted Moths" yes "" "" "16bit" +add_engine tony "Tony Tough and the Night of Roasted Moths" yes "" "" "16bit highres" diff --git a/engines/tony/detection.cpp b/engines/tony/detection.cpp index 2f05c0abea..ec0b3e186b 100644 --- a/engines/tony/detection.cpp +++ b/engines/tony/detection.cpp @@ -118,7 +118,6 @@ SaveStateList TonyMetaEngine::listSaves(const char *target) const { Common::String pattern = "tony.0##"; filenames = saveFileMan->listSavefiles(pattern); - sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) SaveStateList saveList; for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { @@ -137,6 +136,8 @@ SaveStateList TonyMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/toon/POTFILES b/engines/toon/POTFILES new file mode 100644 index 0000000000..5bdfa37e5f --- /dev/null +++ b/engines/toon/POTFILES @@ -0,0 +1 @@ +engines/toon/toon.cpp diff --git a/engines/toon/configure.engine b/engines/toon/configure.engine index 00c98f7d8a..689bce1c02 100644 --- a/engines/toon/configure.engine +++ b/engines/toon/configure.engine @@ -1,3 +1,3 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] -add_engine toon "Toonstruck" yes +add_engine toon "Toonstruck" yes "" "" "highres" diff --git a/engines/toon/detection.cpp b/engines/toon/detection.cpp index e38ae3d2f5..5d2e0a9bca 100644 --- a/engines/toon/detection.cpp +++ b/engines/toon/detection.cpp @@ -127,7 +127,7 @@ static const char * const directoryGlobs[] = { class ToonMetaEngine : public AdvancedMetaEngine { public: ToonMetaEngine() : AdvancedMetaEngine(Toon::gameDescriptions, sizeof(ADGameDescription), toonGames) { - _singleid = "toon"; + _singleId = "toon"; _maxScanDepth = 3; _directoryGlobs = directoryGlobs; } @@ -176,7 +176,6 @@ SaveStateList ToonMetaEngine::listSaves(const char *target) const { pattern += ".###"; filenames = saveFileMan->listSavefiles(pattern); - sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) SaveStateList saveList; for (Common::StringArray::const_iterator filename = filenames.begin(); filename != filenames.end(); ++filename) { @@ -208,6 +207,8 @@ SaveStateList ToonMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/toon/toon.cpp b/engines/toon/toon.cpp index 9e2905f454..3ab23a1e51 100644 --- a/engines/toon/toon.cpp +++ b/engines/toon/toon.cpp @@ -27,6 +27,7 @@ #include "common/config-manager.h" #include "common/savefile.h" #include "common/memstream.h" +#include "common/translation.h" #include "engines/advancedDetector.h" #include "engines/util.h" @@ -3334,7 +3335,7 @@ bool ToonEngine::saveGame(int32 slot, const Common::String &saveGameDesc) { Common::String savegameDescription; if (slot == -1) { - GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser("Save game:", "Save", true); + GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true); savegameId = dialog->runModalWithCurrentTarget(); savegameDescription = dialog->getResultString(); delete dialog; @@ -3426,7 +3427,7 @@ bool ToonEngine::loadGame(int32 slot) { int16 savegameId; if (slot == -1) { - GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser("Restore game:", "Restore", false); + GUI::SaveLoadChooser *dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false); savegameId = dialog->runModalWithCurrentTarget(); delete dialog; } else { diff --git a/engines/touche/configure.engine b/engines/touche/configure.engine index 777578e623..f35940ef47 100644 --- a/engines/touche/configure.engine +++ b/engines/touche/configure.engine @@ -1,3 +1,3 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] -add_engine touche "Touche: The Adventures of the Fifth Musketeer" yes +add_engine touche "Touche: The Adventures of the Fifth Musketeer" yes "" "" "highres" diff --git a/engines/touche/detection.cpp b/engines/touche/detection.cpp index 1d0e136d69..dcb58ffae6 100644 --- a/engines/touche/detection.cpp +++ b/engines/touche/detection.cpp @@ -128,7 +128,7 @@ class ToucheMetaEngine : public AdvancedMetaEngine { public: ToucheMetaEngine() : AdvancedMetaEngine(Touche::gameDescriptions, sizeof(ADGameDescription), toucheGames) { _md5Bytes = 4096; - _singleid = "touche"; + _singleId = "touche"; _maxScanDepth = 2; _directoryGlobs = directoryGlobs; } diff --git a/engines/tsage/blue_force/blueforce_dialogs.cpp b/engines/tsage/blue_force/blueforce_dialogs.cpp index 5be27c9ae7..3697ca700e 100644 --- a/engines/tsage/blue_force/blueforce_dialogs.cpp +++ b/engines/tsage/blue_force/blueforce_dialogs.cpp @@ -161,7 +161,7 @@ void RightClickDialog::execute() { } g_system->delayMillis(10); - GLOBALS._screenSurface.updateScreen(); + GLOBALS._screen.update(); } // Deactivate the graphics manager used for the dialog @@ -242,7 +242,7 @@ void AmmoBeltDialog::execute() { } g_system->delayMillis(10); - GLOBALS._screenSurface.updateScreen(); + GLOBALS._screen.update(); } _gfxManager.deactivate(); diff --git a/engines/tsage/blue_force/blueforce_logic.cpp b/engines/tsage/blue_force/blueforce_logic.cpp index e6e71399dc..2c5f9bd738 100644 --- a/engines/tsage/blue_force/blueforce_logic.cpp +++ b/engines/tsage/blue_force/blueforce_logic.cpp @@ -859,7 +859,7 @@ void SceneExt::endStrip() { } void SceneExt::clearScreen() { - BF_GLOBALS._screenSurface.fillRect(BF_GLOBALS._screenSurface.getBounds(), 0); + BF_GLOBALS._screen.clear(); } /*--------------------------------------------------------------------------*/ @@ -1411,7 +1411,7 @@ void SceneMessage::process(Event &event) { void SceneMessage::draw() { - GfxSurface &surface = BF_GLOBALS._screenSurface; + GfxSurface &surface = BF_GLOBALS._screen; // Clear the game area surface.fillRect(Rect(0, 0, SCREEN_WIDTH, UI_INTERFACE_Y), 0); diff --git a/engines/tsage/blue_force/blueforce_scenes6.cpp b/engines/tsage/blue_force/blueforce_scenes6.cpp index 92534d3095..0d6b5c2487 100644 --- a/engines/tsage/blue_force/blueforce_scenes6.cpp +++ b/engines/tsage/blue_force/blueforce_scenes6.cpp @@ -78,7 +78,7 @@ void Scene600::Action1::signal() { pObj->animate(ANIM_MODE_NONE, NULL); } - BF_GLOBALS._screenSurface.fillRect(BF_GLOBALS._screenSurface.getBounds(), 0); + BF_GLOBALS._screen.fillRect(BF_GLOBALS._screen.getBounds(), 0); scene->loadScene(999); setDelay(5); break; @@ -275,7 +275,7 @@ void Scene666::postInit(SceneObjectList *OwnerList) { SceneExt::postInit(); BF_GLOBALS._interfaceY = SCREEN_HEIGHT; loadScene(999); - BF_GLOBALS._screenSurface.fillRect(BF_GLOBALS._screenSurface.getBounds(), 0); + BF_GLOBALS._screen.fillRect(BF_GLOBALS._screen.getBounds(), 0); if (BF_GLOBALS._dayNumber == 0) { BF_GLOBALS._dayNumber = 1; diff --git a/engines/tsage/converse.cpp b/engines/tsage/converse.cpp index d1faca5dac..7240c91720 100644 --- a/engines/tsage/converse.cpp +++ b/engines/tsage/converse.cpp @@ -469,7 +469,7 @@ int ConversationChoiceDialog::execute(const Common::StringArray &choiceList) { while (!g_globals->_events.getEvent(event, EVENT_KEYPRESS | EVENT_BUTTON_DOWN | EVENT_MOUSE_MOVE) && !g_vm->shouldQuit()) { g_system->delayMillis(10); - GLOBALS._screenSurface.updateScreen(); + GLOBALS._screen.update(); } if (g_vm->shouldQuit()) break; diff --git a/engines/tsage/core.cpp b/engines/tsage/core.cpp index d4068c25c9..985d16b031 100644 --- a/engines/tsage/core.cpp +++ b/engines/tsage/core.cpp @@ -1467,7 +1467,7 @@ void ScenePalette::fade(const byte *adjustData, bool fullAdjust, int percent) { // Set the altered palette g_system->getPaletteManager()->setPalette((const byte *)&tempPalette[0], 0, 256); - GLOBALS._screenSurface.updateScreen(); + GLOBALS._screen.update(); } PaletteRotation *ScenePalette::addRotation(int start, int end, int rotationMode, int duration, Action *action) { @@ -1524,11 +1524,11 @@ void ScenePalette::changeBackground(const Rect &bounds, FadeMode fadeMode) { if (g_vm->getGameID() != GType_Ringworld && g_vm->getGameID() != GType_Sherlock1) tempRect.setHeight(T2_GLOBALS._interfaceY); - g_globals->_screenSurface.copyFrom(g_globals->_sceneManager._scene->_backSurface, + g_globals->_screen.copyFrom(g_globals->_sceneManager._scene->_backSurface, tempRect, Rect(0, 0, tempRect.width(), tempRect.height()), NULL); if (g_vm->getGameID() == GType_Ringworld2 && !GLOBALS._player._uiEnabled && T2_GLOBALS._interfaceY == UI_INTERFACE_Y) { - g_globals->_screenSurface.fillRect(Rect(0, UI_INTERFACE_Y, SCREEN_WIDTH, SCREEN_HEIGHT - 1), 0); + g_globals->_screen.fillRect(Rect(0, UI_INTERFACE_Y, SCREEN_WIDTH, SCREEN_HEIGHT - 1), 0); } for (SynchronizedList<PaletteModifier *>::iterator i = tempPalette._listeners.begin(); i != tempPalette._listeners.end(); ++i) @@ -1796,7 +1796,7 @@ void SceneItem::display(int resNum, int lineNum, ...) { // Keep event on-screen until a mouse or keypress while (!g_vm->shouldQuit() && !g_globals->_events.getEvent(event, EVENT_BUTTON_DOWN | EVENT_KEYPRESS)) { - GLOBALS._screenSurface.updateScreen(); + GLOBALS._screen.update(); g_system->delayMillis(10); if ((g_vm->getGameID() == GType_Ringworld2) && (R2_GLOBALS._speechSubtitles & SPEECH_VOICE)) { @@ -2816,7 +2816,7 @@ void SceneObject::updateScreen() { destRect.translate(-sceneBounds.left, -sceneBounds.top); srcRect.translate(-g_globals->_sceneOffset.x, -g_globals->_sceneOffset.y); - g_globals->_screenSurface.copyFrom(g_globals->_sceneManager._scene->_backSurface, srcRect, destRect); + g_globals->_screen.copyFrom(g_globals->_sceneManager._scene->_backSurface, srcRect, destRect); } } diff --git a/engines/tsage/detection.cpp b/engines/tsage/detection.cpp index f85d8433f3..fe555f2fdb 100644 --- a/engines/tsage/detection.cpp +++ b/engines/tsage/detection.cpp @@ -40,7 +40,7 @@ struct tSageGameDescription { }; const char *TSageEngine::getGameId() const { - return _gameDescription->desc.gameid; + return _gameDescription->desc.gameId; } uint32 TSageEngine::getGameID() const { @@ -75,7 +75,7 @@ enum { class TSageMetaEngine : public AdvancedMetaEngine { public: TSageMetaEngine() : AdvancedMetaEngine(TsAGE::gameDescriptions, sizeof(TsAGE::tSageGameDescription), tSageGameTitles) { - _singleid = "tsage"; + _singleId = "tsage"; } virtual const char *getName() const { @@ -117,7 +117,6 @@ public: pattern += ".###"; Common::StringArray filenames = g_system->getSavefileManager()->listSavefiles(pattern); - sort(filenames.begin(), filenames.end()); TsAGE::tSageSavegameHeader header; SaveStateList saveList; @@ -141,6 +140,8 @@ public: } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/tsage/detection_tables.h b/engines/tsage/detection_tables.h index 109ac353e6..f331ecdab5 100644 --- a/engines/tsage/detection_tables.h +++ b/engines/tsage/detection_tables.h @@ -165,7 +165,7 @@ static const tSageGameDescription gameDescriptions[] = { AD_ENTRY1s("r2rw.rlb", "df6c25622387007788ca36d99362c1f0", 47586928), Common::EN_ANY, Common::kPlatformDOS, - ADGF_CD | ADGF_TESTING, + ADGF_CD, GUIO0() }, GType_Ringworld2, @@ -179,7 +179,7 @@ static const tSageGameDescription gameDescriptions[] = { AD_ENTRY1s("r2rw.rlb", "c8e1a82c67c3caf57368eadde13dc15f", 32384464), Common::EN_ANY, Common::kPlatformDOS, - ADGF_CD | ADGF_TESTING, + ADGF_CD, GUIO0() }, GType_Ringworld2, diff --git a/engines/tsage/events.cpp b/engines/tsage/events.cpp index 0491c043a4..1fa17022de 100644 --- a/engines/tsage/events.cpp +++ b/engines/tsage/events.cpp @@ -50,7 +50,7 @@ bool EventsClass::pollEvent() { ++_frameNumber; // Update screen - GLOBALS._screenSurface.updateScreen(); + GLOBALS._screen.update(); } if (!g_system->getEventManager()->pollEvent(_event)) return false; @@ -400,7 +400,7 @@ void EventsClass::delay(int numFrames) { _priorFrameTime = g_system->getMillis(); } - GLOBALS._screenSurface.updateScreen(); + GLOBALS._screen.update(); _prevDelayFrame = _frameNumber; _priorFrameTime = g_system->getMillis(); } diff --git a/engines/tsage/globals.cpp b/engines/tsage/globals.cpp index b880f35007..b95bea3b23 100644 --- a/engines/tsage/globals.cpp +++ b/engines/tsage/globals.cpp @@ -59,7 +59,7 @@ static SavedObject *classFactoryProc(const Common::String &className) { /*--------------------------------------------------------------------------*/ -Globals::Globals() : _dialogCenter(160, 140), _gfxManagerInstance(_screenSurface), +Globals::Globals() : _dialogCenter(160, 140), _gfxManagerInstance(_screen), _randomSource("tsage"), _color1(0), _color2(255), _color3(255) { reset(); _stripNum = 0; @@ -119,7 +119,7 @@ Globals::Globals() : _dialogCenter(160, 140), _gfxManagerInstance(_screenSurface _color3 = 4; _dialogCenter.y = 100; } - _screenSurface.setScreenSurface(); + _gfxManagers.push_back(&_gfxManagerInstance); _sceneObjects = &_sceneObjectsInstance; diff --git a/engines/tsage/globals.h b/engines/tsage/globals.h index 1194fe8b9c..e1ebe261dc 100644 --- a/engines/tsage/globals.h +++ b/engines/tsage/globals.h @@ -30,6 +30,7 @@ #include "tsage/events.h" #include "tsage/sound.h" #include "tsage/saveload.h" +#include "tsage/screen.h" #include "tsage/user_interface.h" namespace TsAGE { @@ -38,7 +39,7 @@ class Globals : public SavedObject { private: static void dispatchSound(ASound *obj); public: - GfxSurface _screenSurface; + Screen _screen; GfxManager _gfxManagerInstance; Common::List<GfxManager *> _gfxManagers; SceneHandler *_sceneHandler; diff --git a/engines/tsage/graphics.cpp b/engines/tsage/graphics.cpp index 156503fb51..58fa5b8094 100644 --- a/engines/tsage/graphics.cpp +++ b/engines/tsage/graphics.cpp @@ -229,123 +229,43 @@ void Rect::synchronize(Serializer &s) { /*--------------------------------------------------------------------------*/ -GfxSurface::GfxSurface() : _bounds(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT) { +GfxSurface::GfxSurface() : Graphics::ManagedSurface(), _bounds(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT) { _disableUpdates = false; _lockSurfaceCtr = 0; - _customSurface = NULL; _transColor = -1; - _trackDirtyRects = false; _flags = 0; } -GfxSurface::GfxSurface(const GfxSurface &s) { +GfxSurface::GfxSurface(const GfxSurface &s): Graphics::ManagedSurface() { _lockSurfaceCtr = 0; - _customSurface = NULL; - _trackDirtyRects = false; - *this = s; + + operator=(s); } GfxSurface::~GfxSurface() { - clear(); + // Sanity check.. GfxSurface should always be just referencing _rawSurface, + // and not directly managing it's own surface + assert(disposeAfterUse() == DisposeAfterUse::NO); } -void GfxSurface::clear() { - if (_customSurface) { - _customSurface->free(); - delete _customSurface; - _customSurface = NULL; - } -} - -/** - * Specifies that the surface will encapsulate the ScummVM screen surface - */ -void GfxSurface::setScreenSurface() { - _trackDirtyRects = true; - create(SCREEN_WIDTH, SCREEN_HEIGHT); -} - -/** - * Updates the physical screen with the screen surface buffer - */ -void GfxSurface::updateScreen() { - assert(_trackDirtyRects); - - // Merge any overlapping dirty rects - mergeDirtyRects(); - - // Loop through the dirty rect list to copy the affected areas to the sc - for (Common::List<Rect>::iterator i = _dirtyRects.begin(); i != _dirtyRects.end(); ++i) { - Rect r = *i; - - // Make sure that there is something to update. If not, skip this - // rectangle. An example case is the speedbike closeup at the beginning - // of Ringworld (third screen). - if (r.isEmpty()) - continue; - - const byte *srcP = (const byte *)_customSurface->getBasePtr(r.left, r.top); - g_system->copyRectToScreen(srcP, _customSurface->pitch, r.left, r.top, - r.width(), r.height()); - } - - // Update the physical screen - g_system->updateScreen(); - - // Now that the dirty rects have been copied, clear the dirty rect list - _dirtyRects.clear(); -} +void GfxSurface::create(uint16 width, uint16 height) { + free(); -/** - * Adds a rect to the dirty rect list - */ -void GfxSurface::addDirtyRect(const Rect &r) { - if (_trackDirtyRects) { - // Get the bounds and adjust to allow for sub-screen areas - Rect r2 = r; - r2.translate(_bounds.left, _bounds.top); - - // Add to the dirty rect list - r2.right = MIN(r2.right + 1, SCREEN_WIDTH); - r2.bottom = MIN(r2.bottom + 1, SCREEN_HEIGHT); - - if (r2.isValidRect()) - _dirtyRects.push_back(r2); - } + _rawSurface.create(width, height); + setBounds(Rect(0, 0, width, height)); } - - -/** - * Specifies that the surface should maintain it's own internal surface - */ -void GfxSurface::create(int width, int height) { - assert((width >= 0) && (height >= 0)); - - if (_customSurface) { - _customSurface->free(); - delete _customSurface; - } - _customSurface = new Graphics::Surface(); - _customSurface->create(width, height, Graphics::PixelFormat::createFormatCLUT8()); - Common::fill((byte *)_customSurface->getPixels(), (byte *)_customSurface->getBasePtr(0, height), 0); - _bounds = Rect(0, 0, width, height); +void GfxSurface::setBounds(const Rect &bounds) { + _bounds = bounds; + Graphics::ManagedSurface::create(_rawSurface, bounds); } /** * Locks the surface for access, and returns a raw ScummVM surface to manipulate it */ -Graphics::Surface GfxSurface::lockSurface() { +Graphics::ManagedSurface &GfxSurface::lockSurface() { ++_lockSurfaceCtr; - - Graphics::Surface *src = _customSurface; - assert(src); - - // Setup the returned surface either as one pointing to the same pixels as the source, or - // as a subset of the source one based on the currently set bounds - Graphics::Surface result; - result.init(_bounds.width(), _bounds.height(), src->pitch, src->getBasePtr(_bounds.left, _bounds.top), src->format); - return result; + return *this; } /** @@ -367,69 +287,43 @@ void GfxSurface::synchronize(Serializer &s) { if (s.isSaving()) { // Save contents of the surface - if (_customSurface) { - s.syncAsSint16LE(_customSurface->w); - s.syncAsSint16LE(_customSurface->h); - s.syncBytes((byte *)_customSurface->getPixels(), _customSurface->w * _customSurface->h); + if (disposeAfterUse() == DisposeAfterUse::YES) { + s.syncAsSint16LE(this->w); + s.syncAsSint16LE(this->h); + s.syncBytes((byte *)getPixels(), this->w * this->h); } else { int zero = 0; s.syncAsSint16LE(zero); s.syncAsSint16LE(zero); } } else { - int w = 0, h = 0; - s.syncAsSint16LE(w); - s.syncAsSint16LE(h); - - if ((w == 0) || (h == 0)) { - if (_customSurface) - delete _customSurface; - _customSurface = NULL; + int xSize = 0, ySize = 0; + s.syncAsSint16LE(xSize); + s.syncAsSint16LE(ySize); + + if (xSize == 0 || ySize == 0) { + free(); } else { - create(w, h); - s.syncBytes((byte *)_customSurface->getPixels(), w * h); + create(xSize, ySize); + s.syncBytes((byte *)getPixels(), xSize * ySize); } } } -/** - * Fills a specified rectangle on the surface with the specified color - * - * @bounds Area to fill - * @color Color to use - */ -void GfxSurface::fillRect(const Rect &bounds, int color) { - Graphics::Surface surface = lockSurface(); - surface.fillRect(bounds, color); - unlockSurface(); - addDirtyRect(bounds); -} - GfxSurface &GfxSurface::operator=(const GfxSurface &s) { assert(_lockSurfaceCtr == 0); assert(s._lockSurfaceCtr == 0); - if (_customSurface) { - _customSurface->free(); - delete _customSurface; - } - - _customSurface = s._customSurface; _disableUpdates = s._disableUpdates; _bounds = s._bounds; _centroid = s._centroid; _transColor = s._transColor; _flags = s._flags; - if (_customSurface) { - // Surface owns the internal data, so replicate it so new surface owns it's own - _customSurface = new Graphics::Surface(); - _customSurface->create(s._customSurface->w, s._customSurface->h, Graphics::PixelFormat::createFormatCLUT8()); - const byte *srcP = (const byte *)s._customSurface->getPixels(); - byte *destP = (byte *)_customSurface->getPixels(); - - Common::copy(srcP, srcP + (_bounds.width() * _bounds.height()), destP); - } + // Copy the source's surface + create(s.w, s.h); + blitFrom(s); + setBounds(s.getBounds()); return *this; } @@ -474,7 +368,7 @@ bool GfxSurface::displayText(const Common::String &msg, const Common::Point &pt) /** * Loads a quarter of a screen from a resource */ -void GfxSurface::loadScreenSection(Graphics::Surface &dest, int xHalf, int yHalf, int xSection, int ySection) { +void GfxSurface::loadScreenSection(Graphics::ManagedSurface &dest, int xHalf, int yHalf, int xSection, int ySection) { int screenNum = g_globals->_sceneManager._scene->_activeScreenNumber; Rect updateRect(0, 0, 160, 100); updateRect.translate(xHalf * 160, yHalf * 100); @@ -682,50 +576,6 @@ void GfxSurface::draw(const Common::Point &pt, Rect *rect) { } } -/** - * Merges any clipping rectangles that overlap to try and reduce - * the total number of clip rectangles. - */ -void GfxSurface::mergeDirtyRects() { - if (_dirtyRects.size() <= 1) - return; - - Common::List<Rect>::iterator rOuter, rInner; - - for (rOuter = _dirtyRects.begin(); rOuter != _dirtyRects.end(); ++rOuter) { - rInner = rOuter; - while (++rInner != _dirtyRects.end()) { - - if ((*rOuter).intersects(*rInner)) { - // these two rectangles overlap or - // are next to each other - merge them - - unionRectangle(*rOuter, *rOuter, *rInner); - - // remove the inner rect from the list - _dirtyRects.erase(rInner); - - // move back to beginning of list - rInner = rOuter; - } - } - } -} - -/** - * Creates the union of two rectangles. - * Returns True if there is a union. - * @param pDest destination rectangle that is to receive the new union - * @param pSrc1 a source rectangle - * @param pSrc2 a source rectangle - */ -bool GfxSurface::unionRectangle(Common::Rect &destRect, const Rect &src1, const Rect &src2) { - destRect = src1; - destRect.extend(src2); - - return !destRect.isEmpty(); -} - /*--------------------------------------------------------------------------*/ GfxElement::GfxElement() { @@ -762,17 +612,16 @@ void GfxElement::highlight() { Rect tempRect(_bounds); tempRect.collapse(g_globals->_gfxEdgeAdjust - 1, g_globals->_gfxEdgeAdjust - 1); - for (int yp = tempRect.top; yp < tempRect.bottom; ++yp) { - byte *lineP = (byte *)surface.getBasePtr(tempRect.left, yp); - for (int xp = tempRect.left; xp < tempRect.right; ++xp, ++lineP) { + Graphics::Surface dest = surface.getSubArea(tempRect); + + for (int yp = 0; yp < dest.h; ++yp) { + byte *lineP = (byte *)dest.getBasePtr(0, yp); + for (int xp = 0; xp < tempRect.right; ++xp, ++lineP) { if (*lineP == _colors.background) *lineP = _colors.foreground; else if (*lineP == _colors.foreground) *lineP = _colors.background; } } - // Mark the affected area as dirty - gfxManager.getSurface().addDirtyRect(tempRect); - // Release the surface gfxManager.unlockSurface(); } @@ -816,10 +665,11 @@ void GfxElement::drawFrame() { // Loop through the surface area to replace each pixel // with its proper shaded replacement - Graphics::Surface surface = gfxManager.lockSurface(); - for (int y = tempRect.top; y < tempRect.bottom; ++y) { - byte *lineP = (byte *)surface.getBasePtr(tempRect.left, y); - for (int x = 0; x < tempRect.width(); ++x) { + Graphics::Surface dest = gfxManager.getSurface().getSubArea(tempRect); + + for (int y = 0; y < dest.h; ++y) { + byte *lineP = (byte *)dest.getBasePtr(0, y); + for (int x = 0; x < dest.w; ++x) { *lineP = transList[*lineP]; lineP++; } @@ -827,27 +677,24 @@ void GfxElement::drawFrame() { // Draw the edge frame // Outer frame border - surface.hLine(tempRect.left + 2, tempRect.top, tempRect.right - 2, 0); - surface.hLine(tempRect.left + 2, tempRect.bottom, tempRect.right - 2, 0); - surface.vLine(tempRect.left, tempRect.top + 2, tempRect.bottom - 2, 0); - surface.vLine(tempRect.right, tempRect.top + 2, tempRect.bottom - 2, 0); - *((byte *)surface.getBasePtr(tempRect.left + 1, tempRect.top + 1)) = 0; - *((byte *)surface.getBasePtr(tempRect.right - 1, tempRect.top + 1)) = 0; - *((byte *)surface.getBasePtr(tempRect.left + 1, tempRect.bottom - 1)) = 0; - *((byte *)surface.getBasePtr(tempRect.right - 1, tempRect.bottom - 1)) = 0; + dest.hLine(2, 0, dest.w - 2, 0); + dest.hLine(2, dest.h - 1, dest.w - 2, 0); + dest.vLine(0, 2, dest.h - 2, 0); + dest.vLine(tempRect.right, 2, dest.h - 2, 0); + *((byte *)dest.getBasePtr(1, 1)) = 0; + *((byte *)dest.getBasePtr(dest.w - 1, 1)) = 0; + *((byte *)dest.getBasePtr(1, dest.h - 1)) = 0; + *((byte *)dest.getBasePtr(dest.w - 1, dest.h - 1)) = 0; // Inner frame border - surface.hLine(tempRect.left + 2, tempRect.top + 1, tempRect.right - 2, R2_GLOBALS._frameEdgeColor); - surface.hLine(tempRect.left + 2, tempRect.bottom - 1, tempRect.right - 2, R2_GLOBALS._frameEdgeColor); - surface.vLine(tempRect.left + 1, tempRect.top + 2, tempRect.bottom - 2, R2_GLOBALS._frameEdgeColor); - surface.vLine(tempRect.right - 1, tempRect.top + 2, tempRect.bottom - 2, R2_GLOBALS._frameEdgeColor); - *((byte *)surface.getBasePtr(tempRect.left + 2, tempRect.top + 2)) = R2_GLOBALS._frameEdgeColor; - *((byte *)surface.getBasePtr(tempRect.right - 2, tempRect.top + 2)) = R2_GLOBALS._frameEdgeColor; - *((byte *)surface.getBasePtr(tempRect.left + 2, tempRect.bottom - 2)) = R2_GLOBALS._frameEdgeColor; - *((byte *)surface.getBasePtr(tempRect.right - 2, tempRect.bottom - 2)) = R2_GLOBALS._frameEdgeColor; - - gfxManager.unlockSurface(); - gfxManager.getSurface().addDirtyRect(tempRect); + dest.hLine(2, 1, dest.w - 2, R2_GLOBALS._frameEdgeColor); + dest.hLine(2, dest.h - 1, dest.w - 2, R2_GLOBALS._frameEdgeColor); + dest.vLine(1, 2, dest.h - 2, R2_GLOBALS._frameEdgeColor); + dest.vLine(dest.w - 1, 2, dest.h - 2, R2_GLOBALS._frameEdgeColor); + *((byte *)dest.getBasePtr(2, 2)) = R2_GLOBALS._frameEdgeColor; + *((byte *)dest.getBasePtr(dest.w - 2, 2)) = R2_GLOBALS._frameEdgeColor; + *((byte *)dest.getBasePtr(2, dest.h - 2)) = R2_GLOBALS._frameEdgeColor; + *((byte *)dest.getBasePtr(dest.w - 2, dest.h - 2)) = R2_GLOBALS._frameEdgeColor; } else { // Fill dialog content with specified background color @@ -1236,7 +1083,7 @@ GfxButton *GfxDialog::execute(GfxButton *defaultButton) { } g_system->delayMillis(10); - GLOBALS._screenSurface.updateScreen(); + GLOBALS._screen.update(); } _gfxManager.deactivate(); @@ -1269,7 +1116,7 @@ void GfxDialog::setPalette() { /*--------------------------------------------------------------------------*/ -GfxManager::GfxManager() : _surface(g_globals->_screenSurface), _oldManager(NULL) { +GfxManager::GfxManager() : _surface(g_globals->_screen), _oldManager(NULL) { _font.setOwner(this); _font._fillFlag = false; _bounds = Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); @@ -1563,23 +1410,24 @@ int GfxFont::writeChar(const char ch) { int yOffset = (_fontData[charOffset + 1] >> 3) & 0x1f; const uint8 *dataP = &_fontData[charOffset + 2]; - // Lock the surface for access - Graphics::Surface surfacePtr = _gfxManager->lockSurface(); - Rect charRect; charRect.set(0, 0, charWidth, _fontSize.y); charRect.translate(_topLeft.x + _position.x, _topLeft.y + _position.y + yOffset); + // Get the sub-section of the screen to update + Graphics::Surface dest = _gfxManager->getSurface().getSubArea(charRect); + if (_fillFlag) - surfacePtr.fillRect(charRect, _colors.background); + dest.fillRect(charRect, _colors.background); charRect.bottom = charRect.top + charHeight; + assert(charRect.height() <= dest.h); // Display the character int bitCtr = 0; uint8 v = 0; - for (int yp = charRect.top; yp < charRect.bottom; ++yp) { - byte *destP = (byte *)surfacePtr.getBasePtr(charRect.left, yp); + for (int yp = 0; yp < charHeight; ++yp) { + byte *destP = (byte *)dest.getBasePtr(0, yp); for (int xs = 0; xs < charRect.width(); ++xs, ++destP) { // Get the next color index to use @@ -1599,13 +1447,9 @@ int GfxFont::writeChar(const char ch) { } } - // Mark the affected area as dirty - _gfxManager->getSurface().addDirtyRect(charRect); - // Move the text writing position _position.x += charWidth; - _gfxManager->unlockSurface(); return charWidth; } diff --git a/engines/tsage/graphics.h b/engines/tsage/graphics.h index 25f7aea8cd..3b395b7625 100644 --- a/engines/tsage/graphics.h +++ b/engines/tsage/graphics.h @@ -28,7 +28,7 @@ #include "common/list.h" #include "common/rect.h" #include "common/system.h" -#include "graphics/surface.h" +#include "graphics/managed_surface.h" namespace TsAGE { @@ -73,20 +73,13 @@ public: enum FrameFlag { FRAME_FLIP_CENTROID_X = 4, FRAME_FLIP_CENTROID_Y = 8 }; -class GfxSurface { +class GfxSurface: virtual public Graphics::ManagedSurface { private: - Graphics::Surface *_customSurface; int _lockSurfaceCtr; + Graphics::ManagedSurface _rawSurface; bool _disableUpdates; Rect _bounds; - - bool _trackDirtyRects; - Common::List<Rect> _dirtyRects; - - void mergeDirtyRects(); - bool unionRectangle(Common::Rect &destRect, const Rect &src1, const Rect &src2); - public: Common::Point _centroid; int _transColor; @@ -95,17 +88,13 @@ public: public: GfxSurface(); GfxSurface(const GfxSurface &s); - ~GfxSurface(); + virtual ~GfxSurface(); - void setScreenSurface(); - void updateScreen(); - void addDirtyRect(const Rect &r); - Graphics::Surface lockSurface(); + Graphics::ManagedSurface &lockSurface(); void unlockSurface(); void synchronize(Serializer &s); - void create(int width, int height); - void clear(); - void setBounds(const Rect &bounds) { _bounds = bounds; } + virtual void create(uint16 width, uint16 height); + void setBounds(const Rect &bounds); const Rect &getBounds() const { return _bounds; } void copyFrom(GfxSurface &src, Rect srcBounds, Rect destBounds, @@ -119,10 +108,9 @@ public: copyFrom(src, tempRect, priorityRegion); } void draw(const Common::Point &pt, Rect *rect = NULL); - void fillRect(const Rect &bounds, int color); GfxSurface &operator=(const GfxSurface &s); - static void loadScreenSection(Graphics::Surface &dest, int xHalf, int yHalf, int xSection, int ySection); + static void loadScreenSection(Graphics::ManagedSurface &dest, int xHalf, int yHalf, int xSection, int ySection); static bool displayText(const Common::String &msg, const Common::Point &pt = Common::Point(160, 100)); }; @@ -281,7 +269,7 @@ public: void getStringBounds(const char *s, Rect &bounds, int maxWidth); void setDialogPalette(); - Graphics::Surface lockSurface() { + Graphics::ManagedSurface lockSurface() { _surface.setBounds(_bounds); return _surface.lockSurface(); } diff --git a/engines/tsage/module.mk b/engines/tsage/module.mk index e23b157a95..b58c748567 100644 --- a/engines/tsage/module.mk +++ b/engines/tsage/module.mk @@ -47,6 +47,7 @@ MODULE_OBJS := \ ringworld2/ringworld2_vampire.o \ saveload.o \ scenes.o \ + screen.o \ sherlock/sherlock_logo.o \ sound.o \ staticres.o \ diff --git a/engines/tsage/ringworld/ringworld_dialogs.cpp b/engines/tsage/ringworld/ringworld_dialogs.cpp index 1dd3bc158b..9fa17f3920 100644 --- a/engines/tsage/ringworld/ringworld_dialogs.cpp +++ b/engines/tsage/ringworld/ringworld_dialogs.cpp @@ -181,7 +181,7 @@ void RightClickDialog::execute() { } g_system->delayMillis(10); - GLOBALS._screenSurface.updateScreen(); + GLOBALS._screen.update(); } _gfxManager.deactivate(); @@ -391,7 +391,7 @@ void InventoryDialog::execute() { Event event; while (!g_globals->_events.getEvent(event) && !g_vm->shouldQuit()) { g_system->delayMillis(10); - GLOBALS._screenSurface.updateScreen(); + GLOBALS._screen.update(); } if (g_vm->shouldQuit()) break; @@ -439,7 +439,7 @@ void InventoryDialog::execute() { // Inventory item selected InvObject *invObject = static_cast<GfxInvImage *>(hiliteObj)->_invObject; if (lookFlag) { - g_globals->_screenSurface.displayText(invObject->_description); + g_globals->_screen.displayText(invObject->_description); } else { RING_INVENTORY._selectedItem = invObject; invObject->setCursor(); diff --git a/engines/tsage/ringworld/ringworld_logic.cpp b/engines/tsage/ringworld/ringworld_logic.cpp index 1d8293cffd..354c86abfc 100644 --- a/engines/tsage/ringworld/ringworld_logic.cpp +++ b/engines/tsage/ringworld/ringworld_logic.cpp @@ -320,7 +320,7 @@ void SceneArea::wait() { // Wait until a mouse or keypress Event event; while (!g_vm->shouldQuit() && !g_globals->_events.getEvent(event)) { - GLOBALS._screenSurface.updateScreen(); + GLOBALS._screen.update(); g_system->delayMillis(10); } diff --git a/engines/tsage/ringworld/ringworld_scenes3.cpp b/engines/tsage/ringworld/ringworld_scenes3.cpp index a515224964..a9ed7af870 100644 --- a/engines/tsage/ringworld/ringworld_scenes3.cpp +++ b/engines/tsage/ringworld/ringworld_scenes3.cpp @@ -532,7 +532,7 @@ void Scene2100::Action1::signal() { // Wait for an event Event event; if (!g_globals->_events.getEvent(event)) { - GLOBALS._screenSurface.updateScreen(); + GLOBALS._screen.update(); g_system->delayMillis(10); continue; } @@ -2265,7 +2265,7 @@ void Scene2150::Action1::signal() { // Wait for an event Event event; if (!g_globals->_events.getEvent(event)) { - GLOBALS._screenSurface.updateScreen(); + GLOBALS._screen.update(); g_system->delayMillis(10); continue; } @@ -5119,7 +5119,7 @@ void Scene2320::Action3::signal() { // Wait for an event Event event; if (!g_globals->_events.getEvent(event)) { - GLOBALS._screenSurface.updateScreen(); + GLOBALS._screen.update(); g_system->delayMillis(10); continue; } diff --git a/engines/tsage/ringworld/ringworld_scenes5.cpp b/engines/tsage/ringworld/ringworld_scenes5.cpp index cb8a89de80..98859f32ee 100644 --- a/engines/tsage/ringworld/ringworld_scenes5.cpp +++ b/engines/tsage/ringworld/ringworld_scenes5.cpp @@ -2813,7 +2813,7 @@ void Scene4150::Action1::signal() { case 4: { for (int idx = 100; idx >= 0; idx -= 5) { g_globals->_scenePalette.fade(adjustData, false, idx); - GLOBALS._screenSurface.updateScreen(); + GLOBALS._screen.update(); g_system->delayMillis(10); } @@ -2841,7 +2841,7 @@ void Scene4150::Action1::signal() { case 7: for (int idx = 100; idx >= 0; idx -= 5) { g_globals->_scenePalette.fade(adjustData, false, idx); - GLOBALS._screenSurface.updateScreen(); + GLOBALS._screen.update(); g_system->delayMillis(10); } diff --git a/engines/tsage/ringworld2/ringworld2_dialogs.cpp b/engines/tsage/ringworld2/ringworld2_dialogs.cpp index 99f88a1687..027fb558db 100644 --- a/engines/tsage/ringworld2/ringworld2_dialogs.cpp +++ b/engines/tsage/ringworld2/ringworld2_dialogs.cpp @@ -154,7 +154,7 @@ int RightClickDialog::execute() { } g_system->delayMillis(10); - GLOBALS._screenSurface.updateScreen(); + GLOBALS._screen.update(); } // Execute the specified action diff --git a/engines/tsage/ringworld2/ringworld2_logic.cpp b/engines/tsage/ringworld2/ringworld2_logic.cpp index d24541932f..ecaa671bd7 100644 --- a/engines/tsage/ringworld2/ringworld2_logic.cpp +++ b/engines/tsage/ringworld2/ringworld2_logic.cpp @@ -351,7 +351,7 @@ SceneExt::SceneExt(): Scene() { _preventSaving = false; // Reset screen clipping area - R2_GLOBALS._screenSurface._clipRect = Rect(); + R2_GLOBALS._screen._clipRect = Rect(); // WORKAROUND: In the original, playing animations don't reset the global _animationCtr // counter as scene changes unless the playing animation explicitly finishes. For now, @@ -513,7 +513,7 @@ void SceneExt::endStrip() { } void SceneExt::clearScreen() { - R2_GLOBALS._screenSurface.fillRect(R2_GLOBALS._screenSurface.getBounds(), 0); + R2_GLOBALS._screen.fillRect(R2_GLOBALS._screen.getBounds(), 0); } void SceneExt::refreshBackground(int xAmount, int yAmount) { @@ -543,15 +543,12 @@ void SceneExt::refreshBackground(int xAmount, int yAmount) { int screenSize = g_vm->_memoryManager.getSize(dataP); // Lock the background for update - Graphics::Surface s = _backSurface.lockSurface(); - assert(screenSize == (s.w * s.h)); + assert(screenSize == (_backSurface.w * _backSurface.h)); + Graphics::Surface s = _backSurface.getSubArea(Common::Rect(0, 0, _backSurface.w, _backSurface.h)); - // Copy the data + // Copy the data into the surface byte *destP = (byte *)s.getPixels(); Common::copy(dataP, dataP + (s.w * s.h), destP); - _backSurface.unlockSurface(); - - R2_GLOBALS._screenSurface.addDirtyRect(_backSurface.getBounds()); // Free the resource data DEALLOCATE(dataP); @@ -601,7 +598,7 @@ void SceneExt::loadBlankScene() { _backSurface.create(SCREEN_WIDTH, SCREEN_HEIGHT * 3 / 2); _backSurface.fillRect(_backSurface.getBounds(), 0); - R2_GLOBALS._screenSurface.fillRect(R2_GLOBALS._screenSurface.getBounds(), 0); + R2_GLOBALS._screen.fillRect(R2_GLOBALS._screen.getBounds(), 0); } /*--------------------------------------------------------------------------*/ @@ -1966,10 +1963,9 @@ void AnimationPlayer::drawFrame(int sliceIndex) { byte *sliceData1 = sliceDataStart; Rect playerBounds = _screenBounds; - int y = _screenBounds.top; - R2_GLOBALS._screenSurface.addDirtyRect(playerBounds); - Graphics::Surface surface = R2_GLOBALS._screenSurface.lockSurface(); + Graphics::Surface dest = R2_GLOBALS._screen.getSubArea(playerBounds); + int y = 0; // Handle different drawing modes switch (slice._drawMode) { @@ -1980,7 +1976,7 @@ void AnimationPlayer::drawFrame(int sliceIndex) { // TODO: Check of _subData._drawType was done for two different kinds of // line slice drawing in original const byte *pSrc = (const byte *)sliceDataStart + READ_LE_UINT16(sliceData1 + sliceNum * 2); - byte *pDest = (byte *)surface.getBasePtr(playerBounds.left, y++); + byte *pDest = (byte *)dest.getBasePtr(0, y++); Common::copy(pSrc, pSrc + _subData._sliceSize, pDest); } @@ -1997,7 +1993,7 @@ void AnimationPlayer::drawFrame(int sliceIndex) { if (offset) { const byte *pSrc = (const byte *)sliceDataStart + offset; - byte *pDest = (byte *)surface.getBasePtr(playerBounds.left, playerBounds.top); + byte *pDest = (byte *)dest.getBasePtr(0, 0); //Common::copy(pSrc, pSrc + playerBounds.width(), pDest); rleDecode(pSrc, pDest, playerBounds.width()); @@ -2012,7 +2008,7 @@ void AnimationPlayer::drawFrame(int sliceIndex) { // TODO: Check of _subData._drawType was done for two different kinds of // line slice drawing in original const byte *pSrc = (const byte *)sliceDataStart + READ_LE_UINT16(sliceData1 + sliceNum * 2); - byte *pDest = (byte *)surface.getBasePtr(playerBounds.left, playerBounds.top); + byte *pDest = (byte *)dest.getBasePtr(0, 0); rleDecode(pSrc, pDest, _subData._sliceSize); } @@ -2027,7 +2023,7 @@ void AnimationPlayer::drawFrame(int sliceIndex) { for (int yIndex = 0; yIndex < _sliceHeight; ++yIndex) { const byte *pSrc1 = (const byte *)sliceDataStart + READ_LE_UINT16(sliceData2 + sliceNum * 2); const byte *pSrc2 = (const byte *)sliceDataStart + READ_LE_UINT16(sliceData1 + sliceNum * 2); - byte *pDest = (byte *)surface.getBasePtr(playerBounds.left, y++); + byte *pDest = (byte *)dest.getBasePtr(0, y++); if (slice2._drawMode == 0) { // Uncompressed background, foreground compressed @@ -2047,17 +2043,14 @@ void AnimationPlayer::drawFrame(int sliceIndex) { break; } - // Unlock the screen surface - R2_GLOBALS._screenSurface.unlockSurface(); - if (_objectMode == ANIMOBJMODE_42) { _screenBounds.expandPanes(); // Copy the drawn frame to the back surface - Rect srcRect = R2_GLOBALS._screenSurface.getBounds(); + Rect srcRect = R2_GLOBALS._screen.getBounds(); Rect destRect = srcRect; destRect.translate(-g_globals->_sceneOffset.x, -g_globals->_sceneOffset.y); - R2_GLOBALS._sceneManager._scene->_backSurface.copyFrom(R2_GLOBALS._screenSurface, + R2_GLOBALS._sceneManager._scene->_backSurface.copyFrom(R2_GLOBALS._screen, srcRect, destRect); // Draw any objects into the scene diff --git a/engines/tsage/ringworld2/ringworld2_outpost.cpp b/engines/tsage/ringworld2/ringworld2_outpost.cpp index cad21b4623..8c64970bda 100644 --- a/engines/tsage/ringworld2/ringworld2_outpost.cpp +++ b/engines/tsage/ringworld2/ringworld2_outpost.cpp @@ -4689,7 +4689,7 @@ GfxButton *Scene1337::OptionsDialog::execute(GfxButton *defaultButton) { } g_system->delayMillis(10); - GLOBALS._screenSurface.updateScreen(); + GLOBALS._screen.update(); } _gfxManager.deactivate(); diff --git a/engines/tsage/ringworld2/ringworld2_scenes0.cpp b/engines/tsage/ringworld2/ringworld2_scenes0.cpp index 573cbbb29a..63879b0366 100644 --- a/engines/tsage/ringworld2/ringworld2_scenes0.cpp +++ b/engines/tsage/ringworld2/ringworld2_scenes0.cpp @@ -1613,7 +1613,7 @@ void Scene180::signal() { case 43: case 47: _helpEnabled = false; - R2_GLOBALS._screenSurface.fillRect(Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), 0); + R2_GLOBALS._screen.fillRect(Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), 0); _palette.loadPalette(0); _palette.loadPalette(9998); R2_GLOBALS._scenePalette.addFader(_palette._palette, 256, 8, this); @@ -1815,7 +1815,7 @@ void Scene180::signal() { _shipDisplay.remove(); _backSurface.fillRect(Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), 0); - R2_GLOBALS._screenSurface.fillRect(Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), 0); + R2_GLOBALS._screen.fillRect(Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), 0); R2_GLOBALS._sound2.fadeOut2(NULL); R2_GLOBALS._sound1.fadeOut2(this); break; @@ -1880,7 +1880,7 @@ void Scene180::signal() { R2_GLOBALS._paneRefreshFlag[0] = 3; _backSurface.fillRect(Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), 0); - R2_GLOBALS._screenSurface.fillRect(Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), 0); + R2_GLOBALS._screen.fillRect(Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), 0); setSceneDelay(1); break; diff --git a/engines/tsage/saveload.cpp b/engines/tsage/saveload.cpp index 9954b929b2..3cb8e52692 100644 --- a/engines/tsage/saveload.cpp +++ b/engines/tsage/saveload.cpp @@ -289,10 +289,10 @@ void Saver::writeSavegameHeader(Common::OutSaveFile *out, tSageSavegameHeader &h // Create a thumbnail and save it Graphics::Surface *thumb = new Graphics::Surface(); - Graphics::Surface s = g_globals->_screenSurface.lockSurface(); + Graphics::Surface s = g_globals->_screen.lockSurface(); ::createThumbnail(thumb, (const byte *)s.getPixels(), SCREEN_WIDTH, SCREEN_HEIGHT, thumbPalette); Graphics::saveThumbnail(*out, *thumb); - g_globals->_screenSurface.unlockSurface(); + g_globals->_screen.unlockSurface(); thumb->free(); delete thumb; diff --git a/engines/tsage/scenes.cpp b/engines/tsage/scenes.cpp index 80ce1e3ecc..095c0d7ab5 100644 --- a/engines/tsage/scenes.cpp +++ b/engines/tsage/scenes.cpp @@ -139,7 +139,7 @@ void SceneManager::fadeInIfNecessary() { percent = 100; g_globals->_scenePalette.fade((const byte *)&adjustData, false, percent); - GLOBALS._screenSurface.updateScreen(); + GLOBALS._screen.update(); g_system->delayMillis(10); } @@ -175,7 +175,7 @@ void SceneManager::changeScene(int newSceneNumber) { } // Blank out the screen - g_globals->_screenSurface.fillRect(g_globals->_screenSurface.getBounds(), 0); + g_globals->_screen.fillRect(g_globals->_screen.getBounds(), 0); // If there are any fading sounds, wait until fading is complete while (g_globals->_soundManager.isFading()) { @@ -463,7 +463,7 @@ void Scene::refreshBackground(int xAmount, int yAmount) { // Check if the section is already loaded if ((_enabledSections[xp * 16 + yp] == 0xffff) || ((xAmount == 0) && (yAmount == 0))) { // Chunk isn't loaded, so load it in - Graphics::Surface s = _backSurface.lockSurface(); + Graphics::ManagedSurface s = _backSurface.lockSurface(); GfxSurface::loadScreenSection(s, xp - xHalfOffset, yp - yHalfOffset, xp, yp); _backSurface.unlockSurface(); changedFlag = true; diff --git a/engines/tsage/screen.cpp b/engines/tsage/screen.cpp new file mode 100644 index 0000000000..f11c384797 --- /dev/null +++ b/engines/tsage/screen.cpp @@ -0,0 +1,46 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/scummsys.h" +#include "tsage/screen.h" + +namespace TsAGE { + +Screen::Screen(): GfxSurface(), Graphics::Screen() { + create(SCREEN_WIDTH, SCREEN_HEIGHT); +} + +void Screen::update() { + // When dialogs are active, the screen surface may be remapped to + // sub-sections of the screen. But for drawing we'll need to temporarily + // remove any such remappings and use the entirety of the screen + Rect clipBounds = getBounds(); + setBounds(Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)); + + // Update the screen + Graphics::Screen::update(); + + // Reset the clipping + setBounds(clipBounds); +} + +} // End of namespace TsAGE diff --git a/engines/tsage/screen.h b/engines/tsage/screen.h new file mode 100644 index 0000000000..bf5057e4d6 --- /dev/null +++ b/engines/tsage/screen.h @@ -0,0 +1,59 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef TSAGE_SCREEN_H +#define TSAGE_SCREEN_H + +#include "common/scummsys.h" +#include "common/array.h" +#include "graphics/screen.h" +#include "tsage/graphics.h" + +namespace TsAGE { + +#define SCREEN_WIDTH 320 +#define SCREEN_HEIGHT 200 +#define SCREEN_CENTER_X 160 +#define SCREEN_CENTER_Y 100 +#define UI_INTERFACE_Y 168 + +class Screen : virtual public Graphics::Screen, virtual public GfxSurface { +public: + /** + * Constructor + */ + Screen(); + + /** + * Destructor + */ + virtual ~Screen() {} + + /** + * Update the screen + */ + virtual void update(); +}; + +} // End of namespace TsAGE + +#endif /* MADS_SCREEN_H */ diff --git a/engines/tsage/tsage.h b/engines/tsage/tsage.h index 667a8daa59..1a29700a10 100644 --- a/engines/tsage/tsage.h +++ b/engines/tsage/tsage.h @@ -62,12 +62,6 @@ enum { struct tSageGameDescription; -#define SCREEN_WIDTH 320 -#define SCREEN_HEIGHT 200 -#define SCREEN_CENTER_X 160 -#define SCREEN_CENTER_Y 100 -#define UI_INTERFACE_Y 168 - class TSageEngine : public Engine { private: const tSageGameDescription *_gameDescription; diff --git a/engines/tsage/user_interface.cpp b/engines/tsage/user_interface.cpp index 3ee585d5ef..fffc0dc16c 100644 --- a/engines/tsage/user_interface.cpp +++ b/engines/tsage/user_interface.cpp @@ -253,7 +253,7 @@ void UICollection::show() { void UICollection::erase() { if (_clearScreen) { Rect tempRect(0, UI_INTERFACE_Y, SCREEN_WIDTH, SCREEN_HEIGHT); - GLOBALS._screenSurface.fillRect(tempRect, 0); + GLOBALS._screen.fillRect(tempRect, 0); GLOBALS._sceneManager._scene->_backSurface.fillRect(tempRect, 0); _clearScreen = false; } @@ -274,7 +274,7 @@ void UICollection::draw() { _objList[idx]->draw(); // Draw the resulting UI onto the screen - GLOBALS._screenSurface.copyFrom(GLOBALS._sceneManager._scene->_backSurface, + GLOBALS._screen.copyFrom(GLOBALS._sceneManager._scene->_backSurface, Rect(0, UI_INTERFACE_Y, SCREEN_WIDTH, SCREEN_HEIGHT), Rect(0, UI_INTERFACE_Y, SCREEN_WIDTH, SCREEN_HEIGHT)); @@ -293,12 +293,12 @@ void UICollection::r2rDrawFrame() { GfxSurface vertLineRight = visage.getFrame(3); GfxSurface horizLine = visage.getFrame(2); - GLOBALS._screenSurface.copyFrom(horizLine, 0, 0); - GLOBALS._screenSurface.copyFrom(vertLineLeft, 0, 3); - GLOBALS._screenSurface.copyFrom(vertLineRight, SCREEN_WIDTH - 4, 3); + GLOBALS._screen.copyFrom(horizLine, 0, 0); + GLOBALS._screen.copyFrom(vertLineLeft, 0, 3); + GLOBALS._screen.copyFrom(vertLineRight, SCREEN_WIDTH - 4, 3); // Restrict drawing area to exclude the borders at the edge of the screen - R2_GLOBALS._screenSurface._clipRect = Rect(4, 3, SCREEN_WIDTH - 4, + R2_GLOBALS._screen._clipRect = Rect(4, 3, SCREEN_WIDTH - 4, SCREEN_HEIGHT - 3); } diff --git a/engines/tucker/detection.cpp b/engines/tucker/detection.cpp index 3d7859e4fd..2447e15d6b 100644 --- a/engines/tucker/detection.cpp +++ b/engines/tucker/detection.cpp @@ -116,7 +116,7 @@ class TuckerMetaEngine : public AdvancedMetaEngine { public: TuckerMetaEngine() : AdvancedMetaEngine(tuckerGameDescriptions, sizeof(ADGameDescription), tuckerGames) { _md5Bytes = 512; - _singleid = "tucker"; + _singleId = "tucker"; } virtual const char *getName() const { @@ -182,6 +182,8 @@ public: saveList.push_back(SaveStateDescriptor(slot, description)); } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/tucker/resource.cpp b/engines/tucker/resource.cpp index 9cba7b523d..d7b75e39c1 100644 --- a/engines/tucker/resource.cpp +++ b/engines/tucker/resource.cpp @@ -662,9 +662,11 @@ void TuckerEngine::loadData3() { void TuckerEngine::loadData4() { loadFile("data4.c", _loadTempBuf); DataTokenizer t(_loadTempBuf, _fileLoadSize); - t.findNextToken(kDataTokenDw); - _gameDebug = t.getNextInteger() != 0; - _displayGameHints = t.getNextInteger() != 0; + if ((_gameFlags & kGameFlagDemo) == 0) { + t.findNextToken(kDataTokenDw); + _gameDebug = t.getNextInteger() != 0; + _displayGameHints = t.getNextInteger() != 0; + } _locationObjectsCount = 0; if (t.findIndex(_locationNum)) { while (t.findNextToken(kDataTokenDw)) { diff --git a/engines/tucker/sequences.cpp b/engines/tucker/sequences.cpp index d9f284e443..0151c55eb1 100644 --- a/engines/tucker/sequences.cpp +++ b/engines/tucker/sequences.cpp @@ -54,6 +54,7 @@ void TuckerEngine::handleCreditsSequence() { int counter2 = 0; int counter1 = 0; loadCharset2(); + showCursor(false); stopSounds(); _locationNum = 74; _flagsTable[236] = 74; @@ -159,12 +160,16 @@ void TuckerEngine::handleCreditsSequence() { redrawScreen(0); waitForTimer(2); } while (_fadePaletteCounter > 0); + showCursor(true); } void TuckerEngine::handleCongratulationsSequence() { + // This method is only called right before the program terminates, + // so it doesn't bother restoring the palette _timerCounter2 = 0; _fadePaletteCounter = 0; stopSounds(); + showCursor(false); loadImage("congrat.pcx", _loadTempBuf, 1); Graphics::copyRect(_locationBackgroundGfxBuf, 640, _loadTempBuf, 320, 320, 200); _fullRedraw = true; @@ -176,11 +181,13 @@ void TuckerEngine::handleCongratulationsSequence() { } waitForTimer(3); } + showCursor(true); } void TuckerEngine::handleNewPartSequence() { char filename[40]; + showCursor(false); stopSounds(); if (_flagsTable[219] == 1) { _flagsTable[219] = 0; @@ -244,7 +251,7 @@ void TuckerEngine::handleNewPartSequence() { _inputKeys[kInputKeyEscape] = false; break; } - } while (isSpeechSoundPlaying()); + } while (isSpeechSoundPlaying() && !_quitGame); stopSpeechSound(); do { if (_fadePaletteCounter > 0) { @@ -257,8 +264,9 @@ void TuckerEngine::handleNewPartSequence() { drawSprite(0); redrawScreen(0); waitForTimer(3); - } while (_fadePaletteCounter > 0); + } while (_fadePaletteCounter > 0 && !_quitGame); _locationNum = currentLocation; + showCursor(true); } void TuckerEngine::handleMeanwhileSequence() { @@ -280,8 +288,9 @@ void TuckerEngine::handleMeanwhileSequence() { strcpy(filename, "loc80.pcx"); } loadImage(filename, _quadBackgroundGfxBuf + 89600, 1); + showCursor(false); _fadePaletteCounter = 0; - for (int i = 0; i < 60; ++i) { + for (int i = 0; i < 60 && !_quitGame; ++i) { if (_fadePaletteCounter < 16) { fadeOutPalette(); ++_fadePaletteCounter; @@ -290,7 +299,10 @@ void TuckerEngine::handleMeanwhileSequence() { _fullRedraw = true; redrawScreen(0); waitForTimer(3); - ++i; + if (_inputKeys[kInputKeyEscape]) { + _inputKeys[kInputKeyEscape] = false; + break; + } } do { if (_fadePaletteCounter > 0) { @@ -301,9 +313,10 @@ void TuckerEngine::handleMeanwhileSequence() { _fullRedraw = true; redrawScreen(0); waitForTimer(3); - } while (_fadePaletteCounter > 0); + } while (_fadePaletteCounter > 0 && !_quitGame); memcpy(_currentPalette, backupPalette, 256 * 3); _fullRedraw = true; + showCursor(true); } void TuckerEngine::handleMapSequence() { diff --git a/engines/tucker/tucker.cpp b/engines/tucker/tucker.cpp index de555cd7b6..ad455c5ded 100644 --- a/engines/tucker/tucker.cpp +++ b/engines/tucker/tucker.cpp @@ -710,6 +710,10 @@ void TuckerEngine::setCursorType(int type) { CursorMan.showMouse(_cursorType < 2); } +void TuckerEngine::showCursor(bool visible) { + CursorMan.showMouse(visible); +} + void TuckerEngine::setupNewLocation() { debug(2, "setupNewLocation() current %d next %d", _locationNum, _nextLocationNum); _locationNum = _nextLocationNum; diff --git a/engines/tucker/tucker.h b/engines/tucker/tucker.h index 3bbf6a57f5..2ab94dedbc 100644 --- a/engines/tucker/tucker.h +++ b/engines/tucker/tucker.h @@ -300,6 +300,7 @@ protected: void updateCursorPos(int x, int y); void setCursorNum(int num); void setCursorType(int type); + void showCursor(bool visible); void setupNewLocation(); void copyLocBitmap(const char *filename, int offset, bool isMask); void updateMouseState(); diff --git a/engines/voyeur/animation.cpp b/engines/voyeur/animation.cpp index 62b37346da..d5d58a2fd3 100644 --- a/engines/voyeur/animation.cpp +++ b/engines/voyeur/animation.cpp @@ -470,7 +470,7 @@ void RL2Decoder::play(VoyeurEngine *vm, int resourceOffset, if (hasDirtyPalette()) { const byte *palette = getPalette(); - vm->_graphicsManager->setPalette128(palette, paletteStart, paletteCount); + vm->_screen->setPalette128(palette, paletteStart, paletteCount); } if (needsUpdate()) { @@ -482,15 +482,14 @@ void RL2Decoder::play(VoyeurEngine *vm, int resourceOffset, Common::Point pt(READ_LE_UINT16(imgPos + 4 * picCtr) - 32, READ_LE_UINT16(imgPos + 4 * picCtr + 2) - 20); - vm->_graphicsManager->sDrawPic(newPic, &videoFrame, pt); + vm->_screen->sDrawPic(newPic, &videoFrame, pt); ++picCtr; } } // Decode the next frame and display const Graphics::Surface *frame = decodeNextFrame(); - Common::copy((const byte *)frame->getPixels(), (const byte *)frame->getPixels() + 320 * 200, - (byte *)vm->_graphicsManager->_screenSurface.getPixels()); + vm->_screen->blitFrom(*frame); } vm->_eventsManager->getMouseInfo(); diff --git a/engines/voyeur/data.cpp b/engines/voyeur/data.cpp index b8c987f18b..4d6e32436d 100644 --- a/engines/voyeur/data.cpp +++ b/engines/voyeur/data.cpp @@ -240,10 +240,10 @@ void SVoy::reviewAnEvidEvent(int eventIndex) { int frameOff = e._computerOff; if (_vm->_bVoy->getBoltGroup(_vm->_playStampGroupId)) { - _vm->_graphicsManager->_backColors = _vm->_bVoy->boltEntry(_vm->_playStampGroupId + 1)._cMapResource; - _vm->_graphicsManager->_backgroundPage = _vm->_bVoy->boltEntry(_vm->_playStampGroupId)._picResource; - _vm->_graphicsManager->_vPort->setupViewPort(_vm->_graphicsManager->_backgroundPage); - _vm->_graphicsManager->_backColors->startFade(); + _vm->_screen->_backColors = _vm->_bVoy->boltEntry(_vm->_playStampGroupId + 1)._cMapResource; + _vm->_screen->_backgroundPage = _vm->_bVoy->boltEntry(_vm->_playStampGroupId)._picResource; + _vm->_screen->_vPort->setupViewPort(_vm->_screen->_backgroundPage); + _vm->_screen->_backColors->startFade(); _vm->doEvidDisplay(frameOff, e._dead); _vm->_bVoy->freeBoltGroup(_vm->_playStampGroupId); @@ -262,10 +262,10 @@ void SVoy::reviewComputerEvent(int eventIndex) { _computerTextId = e._computerOn; if (_vm->_bVoy->getBoltGroup(_vm->_playStampGroupId)) { - _vm->_graphicsManager->_backColors = _vm->_bVoy->boltEntry(_vm->_playStampGroupId + 1)._cMapResource; - _vm->_graphicsManager->_backgroundPage = _vm->_bVoy->boltEntry(_vm->_playStampGroupId)._picResource; - _vm->_graphicsManager->_vPort->setupViewPort(_vm->_graphicsManager->_backgroundPage); - _vm->_graphicsManager->_backColors->startFade(); + _vm->_screen->_backColors = _vm->_bVoy->boltEntry(_vm->_playStampGroupId + 1)._cMapResource; + _vm->_screen->_backgroundPage = _vm->_bVoy->boltEntry(_vm->_playStampGroupId)._picResource; + _vm->_screen->_vPort->setupViewPort(_vm->_screen->_backgroundPage); + _vm->_screen->_backColors->startFade(); _vm->flipPageAndWaitForFade(); _vm->getComputerBrush(); diff --git a/engines/voyeur/debugger.cpp b/engines/voyeur/debugger.cpp index e9a12180da..ebfa123eb6 100644 --- a/engines/voyeur/debugger.cpp +++ b/engines/voyeur/debugger.cpp @@ -21,7 +21,7 @@ */ #include "voyeur/debugger.h" -#include "voyeur/graphics.h" +#include "voyeur/screen.h" #include "voyeur/voyeur.h" #include "voyeur/staticres.h" diff --git a/engines/voyeur/detection.cpp b/engines/voyeur/detection.cpp index 80a23d3c35..9e5320aac8 100644 --- a/engines/voyeur/detection.cpp +++ b/engines/voyeur/detection.cpp @@ -117,7 +117,6 @@ SaveStateList VoyeurMetaEngine::listSaves(const char *target) const { Common::String pattern = Common::String::format("%s.0##", target); filenames = saveFileMan->listSavefiles(pattern); - sort(filenames.begin(), filenames.end()); // Sort to get the files in numerical order SaveStateList saveList; Voyeur::VoyeurSavegameHeader header; @@ -139,6 +138,8 @@ SaveStateList VoyeurMetaEngine::listSaves(const char *target) const { } } + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } diff --git a/engines/voyeur/events.cpp b/engines/voyeur/events.cpp index 34ef507ad3..020fe4b692 100644 --- a/engines/voyeur/events.cpp +++ b/engines/voyeur/events.cpp @@ -111,18 +111,18 @@ void EventsManager::mainVoyeurIntFunc() { } void EventsManager::sWaitFlip() { - Common::Array<ViewPortResource *> &viewPorts = _vm->_graphicsManager->_viewPortListPtr->_entries; + Common::Array<ViewPortResource *> &viewPorts = _vm->_screen->_viewPortListPtr->_entries; for (uint idx = 0; idx < viewPorts.size(); ++idx) { ViewPortResource &viewPort = *viewPorts[idx]; - if (_vm->_graphicsManager->_saveBack && (viewPort._flags & DISPFLAG_40)) { - Common::Rect *clipPtr = _vm->_graphicsManager->_clipPtr; - _vm->_graphicsManager->_clipPtr = &viewPort._clipRect; + if (_vm->_screen->_saveBack && (viewPort._flags & DISPFLAG_40)) { + Common::Rect *clipPtr = _vm->_screen->_clipPtr; + _vm->_screen->_clipPtr = &viewPort._clipRect; if (viewPort._restoreFn) - (_vm->_graphicsManager->*viewPort._restoreFn)(&viewPort); + (_vm->_screen->*viewPort._restoreFn)(&viewPort); - _vm->_graphicsManager->_clipPtr = clipPtr; + _vm->_screen->_clipPtr = clipPtr; viewPort._rectListCount[viewPort._pageIndex] = 0; viewPort._rectListPtr[viewPort._pageIndex]->clear(); viewPort._flags &= ~DISPFLAG_40; @@ -158,9 +158,7 @@ void EventsManager::checkForNextFrameCounter() { showMousePosition(); // Display the frame - g_system->copyRectToScreen((byte *)_vm->_graphicsManager->_screenSurface.getPixels(), - SCREEN_WIDTH, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); - g_system->updateScreen(); + _vm->_screen->update(); // Signal the ScummVM debugger _vm->_debugger->onFrame(); @@ -178,10 +176,8 @@ void EventsManager::showMousePosition() { mousePos += Common::String::format(" - (%d,%d)", pt.x, pt.y); } - _vm->_graphicsManager->_screenSurface.fillRect( - Common::Rect(0, 0, 110, font.getFontHeight()), 0); - font.drawString(&_vm->_graphicsManager->_screenSurface, mousePos, - 0, 0, 110, 63); + _vm->_screen->fillRect(Common::Rect(0, 0, 110, font.getFontHeight()), 0); + font.drawString(_vm->_screen, mousePos, 0, 0, 110, 63); } void EventsManager::voyeurTimer() { @@ -299,11 +295,11 @@ void EventsManager::startFade(CMapResource *cMap) { if (cMap->_steps > 0) { _fadeStatus = cMap->_fadeStatus | 1; - byte *vgaP = &_vm->_graphicsManager->_VGAColors[_fadeFirstCol * 3]; + byte *vgaP = &_vm->_screen->_VGAColors[_fadeFirstCol * 3]; int mapIndex = 0; for (int idx = _fadeFirstCol; idx <= _fadeLastCol; ++idx, vgaP += 3) { - ViewPortPalEntry &palEntry = _vm->_graphicsManager->_viewPortListPtr->_palette[idx]; + ViewPortPalEntry &palEntry = _vm->_screen->_viewPortListPtr->_palette[idx]; palEntry._rEntry = vgaP[0] << 8; int rDiff = (cMap->_entries[mapIndex * 3] << 8) - palEntry._rEntry; palEntry._rChange = rDiff / cMap->_steps; @@ -325,7 +321,7 @@ void EventsManager::startFade(CMapResource *cMap) { _intPtr._skipFading = true; _fadeIntNode._flags &= ~1; } else { - byte *vgaP = &_vm->_graphicsManager->_VGAColors[_fadeFirstCol * 3]; + byte *vgaP = &_vm->_screen->_VGAColors[_fadeFirstCol * 3]; int mapIndex = 0; for (int idx = _fadeFirstCol; idx <= _fadeLastCol; ++idx, vgaP += 3) { @@ -371,8 +367,8 @@ void EventsManager::vDoFadeInt() { } for (int i = _fadeFirstCol; i <= _fadeLastCol; ++i) { - ViewPortPalEntry &palEntry = _vm->_graphicsManager->_viewPortListPtr->_palette[i]; - byte *vgaP = &_vm->_graphicsManager->_VGAColors[palEntry._palIndex * 3]; + ViewPortPalEntry &palEntry = _vm->_screen->_viewPortListPtr->_palette[i]; + byte *vgaP = &_vm->_screen->_VGAColors[palEntry._palIndex * 3]; palEntry._rEntry += palEntry._rChange; palEntry._gEntry += palEntry._gChange; @@ -395,7 +391,7 @@ void EventsManager::vDoCycleInt() { for (int idx = 3; idx >= 0; --idx) { if (_cyclePtr->_type[idx] && --_cycleTime[idx] <= 0) { byte *pSrc = _cycleNext[idx]; - byte *pPal = _vm->_graphicsManager->_VGAColors; + byte *pPal = _vm->_screen->_VGAColors; if (_cyclePtr->_type[idx] != 1) { // New palette data being specified - loop to set entries @@ -521,7 +517,7 @@ void EventsManager::setCursor(PictureResource *pic) { cursor._bounds = pic->_bounds; cursor._flags = DISPFLAG_CURSOR; - _vm->_graphicsManager->sDrawPic(pic, &cursor, Common::Point()); + _vm->_screen->sDrawPic(pic, &cursor, Common::Point()); } void EventsManager::setCursor(byte *cursorData, int width, int height, int keyColor) { @@ -531,16 +527,16 @@ void EventsManager::setCursor(byte *cursorData, int width, int height, int keyCo void EventsManager::setCursorColor(int idx, int mode) { switch (mode) { case 0: - _vm->_graphicsManager->setColor(idx, 90, 90, 232); + _vm->_screen->setColor(idx, 90, 90, 232); break; case 1: - _vm->_graphicsManager->setColor(idx, 232, 90, 90); + _vm->_screen->setColor(idx, 232, 90, 90); break; case 2: - _vm->_graphicsManager->setColor(idx, 90, 232, 90); + _vm->_screen->setColor(idx, 90, 232, 90); break; case 3: - _vm->_graphicsManager->setColor(idx, 90, 232, 232); + _vm->_screen->setColor(idx, 90, 232, 232); break; default: break; @@ -564,12 +560,12 @@ void EventsManager::getMouseInfo() { if (_cursorBlinked) { _cursorBlinked = false; - _vm->_graphicsManager->setOneColor(128, 220, 20, 20); - _vm->_graphicsManager->setColor(128, 220, 20, 20); + _vm->_screen->setOneColor(128, 220, 20, 20); + _vm->_screen->setColor(128, 220, 20, 20); } else { _cursorBlinked = true; - _vm->_graphicsManager->setOneColor(128, 220, 220, 220); - _vm->_graphicsManager->setColor(128, 220, 220, 220); + _vm->_screen->setOneColor(128, 220, 220, 220); + _vm->_screen->setColor(128, 220, 220, 220); } } } @@ -585,11 +581,11 @@ void EventsManager::getMouseInfo() { void EventsManager::startCursorBlink() { if (_vm->_voy->_eventFlags & EVTFLAG_RECORDING) { - _vm->_graphicsManager->setOneColor(128, 55, 5, 5); - _vm->_graphicsManager->setColor(128, 220, 20, 20); + _vm->_screen->setOneColor(128, 55, 5, 5); + _vm->_screen->setColor(128, 220, 20, 20); _intPtr._hasPalette = true; - _vm->_graphicsManager->drawDot(); + _vm->_screen->drawDot(); //copySection(); } } diff --git a/engines/voyeur/files.cpp b/engines/voyeur/files.cpp index 300e086f75..46b195ecaf 100644 --- a/engines/voyeur/files.cpp +++ b/engines/voyeur/files.cpp @@ -21,7 +21,7 @@ */ #include "voyeur/files.h" -#include "voyeur/graphics.h" +#include "voyeur/screen.h" #include "voyeur/voyeur.h" #include "voyeur/staticres.h" @@ -359,7 +359,7 @@ void BoltFile::resolveIt(uint32 id, byte **p) { } } -void BoltFile::resolveFunction(uint32 id, GraphicMethodPtr *fn) { +void BoltFile::resolveFunction(uint32 id, ScreenMethodPtr *fn) { if ((int32)id == -1) *fn = NULL; else @@ -485,8 +485,8 @@ void BVoyBoltFile::initViewPortList() { _state._curMemberPtr->_viewPortListResource = res = new ViewPortListResource( _state, _state._curMemberPtr->_data); - _state._vm->_graphicsManager->_viewPortListPtr = res; - _state._vm->_graphicsManager->_vPort = res->_entries[0]; + _state._vm->_screen->_viewPortListPtr = res; + _state._vm->_screen->_vPort = res->_entries[0]; } void BVoyBoltFile::initFontInfo() { @@ -752,24 +752,24 @@ DisplayResource::DisplayResource(VoyeurEngine *vm) { void DisplayResource::sFillBox(int width, int height) { assert(_vm); - bool saveBack = _vm->_graphicsManager->_saveBack; - _vm->_graphicsManager->_saveBack = false; + bool saveBack = _vm->_screen->_saveBack; + _vm->_screen->_saveBack = false; PictureResource pr; pr._flags = DISPFLAG_1; pr._select = 0xff; pr._pick = 0; - pr._onOff = _vm->_graphicsManager->_drawPtr->_penColor; + pr._onOff = _vm->_screen->_drawPtr->_penColor; pr._bounds = Common::Rect(0, 0, width, height); - _vm->_graphicsManager->sDrawPic(&pr, this, _vm->_graphicsManager->_drawPtr->_pos); - _vm->_graphicsManager->_saveBack = saveBack; + _vm->_screen->sDrawPic(&pr, this, _vm->_screen->_drawPtr->_pos); + _vm->_screen->_saveBack = saveBack; } bool DisplayResource::clipRect(Common::Rect &rect) { Common::Rect clippingRect; - if (_vm->_graphicsManager->_clipPtr) { - clippingRect = *_vm->_graphicsManager->_clipPtr; + if (_vm->_screen->_clipPtr) { + clippingRect = *_vm->_screen->_clipPtr; } else if (_flags & DISPFLAG_VIEWPORT) { clippingRect = ((ViewPortResource *)this)->_clipRect; } else { @@ -804,18 +804,18 @@ bool DisplayResource::clipRect(Common::Rect &rect) { } int DisplayResource::drawText(const Common::String &msg) { - GraphicsManager &gfxManager = *_vm->_graphicsManager; - assert(gfxManager._fontPtr); - assert(gfxManager._fontPtr->_curFont); - FontInfoResource &fontInfo = *gfxManager._fontPtr; - PictureResource &fontChar = *_vm->_graphicsManager->_fontChar; + Screen &screen = *_vm->_screen; + assert(screen._fontPtr); + assert(screen._fontPtr->_curFont); + FontInfoResource &fontInfo = *screen._fontPtr; + PictureResource &fontChar = *_vm->_screen->_fontChar; FontResource &fontData = *fontInfo._curFont; int xShadows[9] = { 0, 1, 1, 1, 0, -1, -1, -1, 0 }; int yShadows[9] = { 0, 1, 0, -1, -1, -1, 0, 1, 1 }; - Common::Rect *clipPtr = gfxManager._clipPtr; + Common::Rect *clipPtr = screen._clipPtr; if (!(fontInfo._picFlags & DISPFLAG_1)) - gfxManager._clipPtr = NULL; + screen._clipPtr = NULL; int minChar = fontData._minChar; int padding = fontData._padding; @@ -834,7 +834,7 @@ int DisplayResource::drawText(const Common::String &msg) { (ViewPortResource *)this; if ((fontInfo._fontFlags & DISPFLAG_1) || fontInfo._justify || - (gfxManager._saveBack && fontInfo._fontSaveBack && (_flags & DISPFLAG_VIEWPORT))) { + (screen._saveBack && fontInfo._fontSaveBack && (_flags & DISPFLAG_VIEWPORT))) { msgWidth = viewPort->textWidth(msg); yp = pos.y; xp = pos.x; @@ -898,18 +898,18 @@ int DisplayResource::drawText(const Common::String &msg) { } } - if (gfxManager._saveBack && fontInfo._fontSaveBack && (_flags & DISPFLAG_VIEWPORT)) { + if (screen._saveBack && fontInfo._fontSaveBack && (_flags & DISPFLAG_VIEWPORT)) { viewPort->addSaveRect(viewPort->_pageIndex, viewPort->_fontRect); } if (fontInfo._fontFlags & DISPFLAG_1) { - gfxManager._drawPtr->_pos = Common::Point(viewPort->_fontRect.left, viewPort->_fontRect.top); - gfxManager._drawPtr->_penColor = fontInfo._backColor; + screen._drawPtr->_pos = Common::Point(viewPort->_fontRect.left, viewPort->_fontRect.top); + screen._drawPtr->_penColor = fontInfo._backColor; sFillBox(viewPort->_fontRect.width(), viewPort->_fontRect.height()); } - bool saveBack = gfxManager._saveBack; - gfxManager._saveBack = false; + bool saveBack = screen._saveBack; + screen._saveBack = false; int count = 0; if (fontInfo._fontFlags & DISPFLAG_4) @@ -970,7 +970,7 @@ int DisplayResource::drawText(const Common::String &msg) { uint16 offset = READ_LE_UINT16(fontData._charOffsets + charValue * 2); fontChar._imgData = fontData._charImages + offset * 2; - gfxManager.sDrawPic(&fontChar, this, Common::Point(xp, yp)); + screen.sDrawPic(&fontChar, this, Common::Point(xp, yp)); fontChar._imgData = NULL; xp += charWidth + padding; @@ -982,8 +982,8 @@ int DisplayResource::drawText(const Common::String &msg) { if (fontInfo._justify == ALIGN_LEFT) fontInfo._pos.x = xp; - gfxManager._saveBack = saveBack; - gfxManager._clipPtr = clipPtr; + screen._saveBack = saveBack; + screen._clipPtr = clipPtr; return msgWidth; } @@ -993,7 +993,7 @@ int DisplayResource::textWidth(const Common::String &msg) { return 0; const char *msgP = msg.c_str(); - FontResource &fontData = *_vm->_graphicsManager->_fontPtr->_curFont; + FontResource &fontData = *_vm->_screen->_fontPtr->_curFont; int minChar = fontData._minChar; int maxChar = fontData._maxChar; int padding = fontData._padding; @@ -1085,9 +1085,9 @@ PictureResource::PictureResource(BoltFilesState &state, const byte *src): mode = 226; } - if (mode != state._vm->_graphicsManager->_SVGAMode) { - state._vm->_graphicsManager->_SVGAMode = mode; - state._vm->_graphicsManager->clearPalette(); + if (mode != state._vm->_screen->_SVGAMode) { + state._vm->_screen->_SVGAMode = mode; + state._vm->_screen->clearPalette(); } int screenOffset = READ_LE_UINT32(&src[18]) & 0xffff; @@ -1096,13 +1096,14 @@ PictureResource::PictureResource(BoltFilesState &state, const byte *src): if (_flags & PICFLAG_CLEAR_SCREEN) { // Clear screen picture. That's right. This game actually has a picture // resource flag to clear the screen! Bizarre. - Graphics::Surface &s = state._vm->_graphicsManager->_screenSurface; - s.fillRect(Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), 0); + state._vm->_screen->clear(); } else { // Direct screen loading picture. In this case, the raw data of the resource // is directly decompressed into the screen surface. Again, bizarre. - byte *pDest = (byte *)state._vm->_graphicsManager->_screenSurface.getPixels(); + Screen &screen = *state._vm->_screen; + byte *pDest = (byte *)screen.getPixels(); state.decompress(pDest, SCREEN_WIDTH * SCREEN_HEIGHT, state._curMemberPtr->_mode); + screen.markAllDirty(); } } else { if (_flags & PICFLAG_CLEAR_SCREEN00) { @@ -1249,13 +1250,13 @@ ViewPortResource::ViewPortResource(BoltFilesState &state, const byte *src): ys + READ_LE_UINT16(src + 0x4C)); state._curLibPtr->resolveIt(READ_LE_UINT32(src + 0x7A), &dummy); - state._curLibPtr->resolveFunction(READ_LE_UINT32(src + 0x7E), (GraphicMethodPtr *)&_fn1); - state._curLibPtr->resolveFunction(READ_LE_UINT32(src + 0x82), (GraphicMethodPtr *)&_setupFn); - state._curLibPtr->resolveFunction(READ_LE_UINT32(src + 0x86), (GraphicMethodPtr *)&_addFn); - state._curLibPtr->resolveFunction(READ_LE_UINT32(src + 0x8A), (GraphicMethodPtr *)&_restoreFn); + state._curLibPtr->resolveFunction(READ_LE_UINT32(src + 0x7E), (ScreenMethodPtr *)&_fn1); + state._curLibPtr->resolveFunction(READ_LE_UINT32(src + 0x82), (ScreenMethodPtr *)&_setupFn); + state._curLibPtr->resolveFunction(READ_LE_UINT32(src + 0x86), (ScreenMethodPtr *)&_addFn); + state._curLibPtr->resolveFunction(READ_LE_UINT32(src + 0x8A), (ScreenMethodPtr *)&_restoreFn); if (!_restoreFn && _addFn) - _addFn = &GraphicsManager::addRectNoSaveBack; + _addFn = &Screen::addRectNoSaveBack; } ViewPortResource::~ViewPortResource() { @@ -1327,19 +1328,19 @@ void ViewPortResource::setupViewPort(PictureResource *page, Common::Rect *clippi _restoreFn = restoreFn; if (setupFn) - (_state._vm->_graphicsManager->*setupFn)(this); + (_state._vm->_screen->*setupFn)(this); } void ViewPortResource::setupViewPort() { - setupViewPort(_state._vm->_graphicsManager->_backgroundPage, NULL, - &GraphicsManager::setupMCGASaveRect, &GraphicsManager::addRectOptSaveRect, - &GraphicsManager::restoreMCGASaveRect); + setupViewPort(_state._vm->_screen->_backgroundPage, NULL, + &Screen::setupMCGASaveRect, &Screen::addRectOptSaveRect, + &Screen::restoreMCGASaveRect); } void ViewPortResource::setupViewPort(PictureResource *pic, Common::Rect *clippingRect) { setupViewPort(pic, clippingRect, - &GraphicsManager::setupMCGASaveRect, &GraphicsManager::addRectOptSaveRect, - &GraphicsManager::restoreMCGASaveRect); + &Screen::setupMCGASaveRect, &Screen::addRectOptSaveRect, + &Screen::restoreMCGASaveRect); } void ViewPortResource::addSaveRect(int pageIndex, const Common::Rect &r) { @@ -1347,7 +1348,7 @@ void ViewPortResource::addSaveRect(int pageIndex, const Common::Rect &r) { if (clipRect(rect)) { if (_addFn) { - (_state._vm->_graphicsManager->*_addFn)(this, pageIndex, rect); + (_state._vm->_screen->*_addFn)(this, pageIndex, rect); } else if (_rectListCount[pageIndex] != -1) { _rectListPtr[pageIndex]->push_back(rect); } @@ -1355,26 +1356,26 @@ void ViewPortResource::addSaveRect(int pageIndex, const Common::Rect &r) { } void ViewPortResource::fillPic(byte onOff) { - _state._vm->_graphicsManager->fillPic(this, onOff); + _state._vm->_screen->fillPic(this, onOff); } void ViewPortResource::drawIfaceTime() { // Hour display - _state._vm->_graphicsManager->drawANumber(_state._vm->_graphicsManager->_vPort, + _state._vm->_screen->drawANumber(_state._vm->_screen->_vPort, (_state._vm->_gameHour / 10) == 0 ? 10 : _state._vm->_gameHour / 10, Common::Point(161, 25)); - _state._vm->_graphicsManager->drawANumber(_state._vm->_graphicsManager->_vPort, + _state._vm->_screen->drawANumber(_state._vm->_screen->_vPort, _state._vm->_gameHour % 10, Common::Point(172, 25)); // Minute display - _state._vm->_graphicsManager->drawANumber(_state._vm->_graphicsManager->_vPort, + _state._vm->_screen->drawANumber(_state._vm->_screen->_vPort, _state._vm->_gameMinute / 10, Common::Point(190, 25)); - _state._vm->_graphicsManager->drawANumber(_state._vm->_graphicsManager->_vPort, + _state._vm->_screen->drawANumber(_state._vm->_screen->_vPort, _state._vm->_gameMinute % 10, Common::Point(201, 25)); // AM/PM indicator PictureResource *pic = _state._vm->_bVoy->boltEntry(_state._vm->_voy->_isAM ? 272 : 273)._picResource; - _state._vm->_graphicsManager->sDrawPic(pic, _state._vm->_graphicsManager->_vPort, + _state._vm->_screen->sDrawPic(pic, _state._vm->_screen->_vPort, Common::Point(215, 27)); } @@ -1382,9 +1383,9 @@ void ViewPortResource::drawPicPerm(PictureResource *pic, const Common::Point &pt Common::Rect bounds = pic->_bounds; bounds.translate(pt.x, pt.y); - bool saveBack = _state._vm->_graphicsManager->_saveBack; - _state._vm->_graphicsManager->_saveBack = false; - _state._vm->_graphicsManager->sDrawPic(pic, this, pt); + bool saveBack = _state._vm->_screen->_saveBack; + _state._vm->_screen->_saveBack = false; + _state._vm->_screen->sDrawPic(pic, this, pt); clipRect(bounds); for (int pageIndex = 0; pageIndex < _pageCount; ++pageIndex) { @@ -1393,7 +1394,7 @@ void ViewPortResource::drawPicPerm(PictureResource *pic, const Common::Point &pt } } - _state._vm->_graphicsManager->_saveBack = saveBack; + _state._vm->_screen->_saveBack = saveBack; } /*------------------------------------------------------------------------*/ @@ -1526,7 +1527,7 @@ CMapResource::CMapResource(BoltFilesState &state, const byte *src): _vm(state._v _entries = new byte[count * 3]; Common::copy(src + 6, src + 6 + 3 * count, _entries); - int palIndex = state._vm->_graphicsManager->_viewPortListPtr->_palIndex; + int palIndex = state._vm->_screen->_viewPortListPtr->_palIndex; if (_end > palIndex) _end = palIndex; if (_start > palIndex) diff --git a/engines/voyeur/files.h b/engines/voyeur/files.h index eef5df497c..8726b38ddf 100644 --- a/engines/voyeur/files.h +++ b/engines/voyeur/files.h @@ -27,7 +27,7 @@ #include "common/file.h" #include "common/rect.h" #include "common/str.h" -#include "voyeur/graphics.h" +#include "voyeur/screen.h" namespace Voyeur { @@ -112,7 +112,7 @@ public: byte *memberAddr(uint32 id); byte *memberAddrOffset(uint32 id); void resolveIt(uint32 id, byte **p); - void resolveFunction(uint32 id, GraphicMethodPtr *fn); + void resolveFunction(uint32 id, ScreenMethodPtr *fn); BoltEntry &boltEntry(uint16 id); BoltEntry &getBoltEntryFromLong(uint32 id); @@ -340,7 +340,7 @@ public: int _rectListCount[3]; Common::Rect _clipRect; - GraphicMethodPtr _fn1; + ScreenMethodPtr _fn1; ViewPortSetupPtr _setupFn; ViewPortAddPtr _addFn; ViewPortRestorePtr _restoreFn; diff --git a/engines/voyeur/files_threads.cpp b/engines/voyeur/files_threads.cpp index 9908324043..bbd3dfe4e9 100644 --- a/engines/voyeur/files_threads.cpp +++ b/engines/voyeur/files_threads.cpp @@ -21,7 +21,7 @@ */ #include "voyeur/files.h" -#include "voyeur/graphics.h" +#include "voyeur/screen.h" #include "voyeur/voyeur.h" #include "voyeur/staticres.h" @@ -461,7 +461,7 @@ void ThreadResource::parsePlayCommands() { pic = _vm->_bVoy->boltEntry(_vm->_playStampGroupId + i * 2)._picResource; pal = _vm->_bVoy->boltEntry(_vm->_playStampGroupId + i * 2 + 1)._cMapResource; - _vm->_graphicsManager->_vPort->setupViewPort(pic); + _vm->_screen->_vPort->setupViewPort(pic); pal->startFade(); _vm->flipPageAndWaitForFade(); @@ -980,10 +980,10 @@ int ThreadResource::doApt() { _vm->_soundManager->startVOCPlay(_vm->_soundManager->getVOCFileName(_vm->_currentVocId)); _vm->_currentVocId = 151; - _vm->_graphicsManager->setColor(129, 82, 82, 82); - _vm->_graphicsManager->setColor(130, 112, 112, 112); - _vm->_graphicsManager->setColor(131, 215, 215, 215); - _vm->_graphicsManager->setColor(132, 235, 235, 235); + _vm->_screen->setColor(129, 82, 82, 82); + _vm->_screen->setColor(130, 112, 112, 112); + _vm->_screen->setColor(131, 215, 215, 215); + _vm->_screen->setColor(132, 235, 235, 235); _vm->_eventsManager->_intPtr._hasPalette = true; @@ -1044,7 +1044,7 @@ int ThreadResource::doApt() { // Draw the text description for the highlighted hotspot pic = _vm->_bVoy->boltEntry(_vm->_playStampGroupId + hotspotId + 6)._picResource; - _vm->_graphicsManager->sDrawPic(pic, _vm->_graphicsManager->_vPort, + _vm->_screen->sDrawPic(pic, _vm->_screen->_vPort, Common::Point(106, 200)); } @@ -1112,10 +1112,10 @@ void ThreadResource::doRoom() { if (!vm._bVoy->getBoltGroup(vm._playStampGroupId)) return; - vm._graphicsManager->_backColors = vm._bVoy->boltEntry(vm._playStampGroupId + 1)._cMapResource; - vm._graphicsManager->_backgroundPage = vm._bVoy->boltEntry(vm._playStampGroupId)._picResource; - vm._graphicsManager->_vPort->setupViewPort(vm._graphicsManager->_backgroundPage); - vm._graphicsManager->_backColors->startFade(); + vm._screen->_backColors = vm._bVoy->boltEntry(vm._playStampGroupId + 1)._cMapResource; + vm._screen->_backgroundPage = vm._bVoy->boltEntry(vm._playStampGroupId)._picResource; + vm._screen->_vPort->setupViewPort(vm._screen->_backgroundPage); + vm._screen->_backColors->startFade(); voy._fadingStep1 = 2; voy._fadingStep2 = 0; @@ -1144,7 +1144,7 @@ void ThreadResource::doRoom() { bool breakFlag = false; while (!vm.shouldQuit() && !breakFlag) { _vm->_voyeurArea = AREA_ROOM; - vm._graphicsManager->setColor(128, 0, 255, 0); + vm._screen->setColor(128, 0, 255, 0); vm._eventsManager->_intPtr._hasPalette = true; do { @@ -1186,7 +1186,7 @@ void ThreadResource::doRoom() { } vm._eventsManager->_intPtr._hasPalette = true; - vm._graphicsManager->flipPage(); + vm._screen->flipPage(); vm._eventsManager->sWaitFlip(); } while (!vm.shouldQuit() && !vm._eventsManager->_mouseClicked); @@ -1234,13 +1234,13 @@ void ThreadResource::doRoom() { // WORKAROUND: Skipped code from the original, that freed the group, // reloaded it, and reloaded the cursors - vm._graphicsManager->_backColors = vm._bVoy->boltEntry( + vm._screen->_backColors = vm._bVoy->boltEntry( vm._playStampGroupId + 1)._cMapResource; - vm._graphicsManager->_backgroundPage = vm._bVoy->boltEntry( + vm._screen->_backgroundPage = vm._bVoy->boltEntry( vm._playStampGroupId)._picResource; - vm._graphicsManager->_vPort->setupViewPort(); - vm._graphicsManager->_backColors->startFade(); + vm._screen->_vPort->setupViewPort(); + vm._screen->_backColors->startFade(); _vm->flipPageAndWait(); while (!vm.shouldQuit() && (vm._eventsManager->_fadeStatus & 1)) @@ -1265,7 +1265,7 @@ void ThreadResource::doRoom() { _vm->flipPageAndWait(); - vm._graphicsManager->fadeUpICF1(); + vm._screen->fadeUpICF1(); voy._eventFlags &= EVTFLAG_RECORDING; vm._eventsManager->showCursor(); } @@ -1350,7 +1350,7 @@ int ThreadResource::doInterface() { _vm->_soundManager->startVOCPlay(fname); _vm->_eventsManager->getMouseInfo(); - _vm->_graphicsManager->setColor(240, 220, 220, 220); + _vm->_screen->setColor(240, 220, 220, 220); _vm->_eventsManager->_intPtr._hasPalette = true; _vm->_voy->_eventFlags &= ~EVTFLAG_TIME_DISABLED; @@ -1424,20 +1424,20 @@ int ThreadResource::doInterface() { // Regularly update the time display if (_vm->_voy->_RTANum & 2) { - _vm->_graphicsManager->drawANumber(_vm->_graphicsManager->_vPort, + _vm->_screen->drawANumber(_vm->_screen->_vPort, _vm->_gameMinute / 10, Common::Point(190, 25)); - _vm->_graphicsManager->drawANumber(_vm->_graphicsManager->_vPort, + _vm->_screen->drawANumber(_vm->_screen->_vPort, _vm->_gameMinute % 10, Common::Point(201, 25)); if (_vm->_voy->_RTANum & 4) { int v = _vm->_gameHour / 10; - _vm->_graphicsManager->drawANumber(_vm->_graphicsManager->_vPort, + _vm->_screen->drawANumber(_vm->_screen->_vPort, v == 0 ? 10 : v, Common::Point(161, 25)); - _vm->_graphicsManager->drawANumber(_vm->_graphicsManager->_vPort, + _vm->_screen->drawANumber(_vm->_screen->_vPort, _vm->_gameHour % 10, Common::Point(172, 25)); pic = _vm->_bVoy->boltEntry(_vm->_voy->_isAM ? 272 : 273)._picResource; - _vm->_graphicsManager->sDrawPic(pic, _vm->_graphicsManager->_vPort, + _vm->_screen->sDrawPic(pic, _vm->_screen->_vPort, Common::Point(215, 27)); } } @@ -1605,16 +1605,16 @@ void ThreadResource::loadTheApt() { _vm->_bVoy->getBoltGroup(_vm->_playStampGroupId); _vm->_voy->_aptLoadMode = -1; - _vm->_graphicsManager->_backgroundPage = _vm->_bVoy->boltEntry( + _vm->_screen->_backgroundPage = _vm->_bVoy->boltEntry( _vm->_playStampGroupId + 5)._picResource; - _vm->_graphicsManager->_vPort->setupViewPort( - _vm->_graphicsManager->_backgroundPage); + _vm->_screen->_vPort->setupViewPort( + _vm->_screen->_backgroundPage); } else { _vm->_bVoy->getBoltGroup(_vm->_playStampGroupId); - _vm->_graphicsManager->_backgroundPage = _vm->_bVoy->boltEntry( + _vm->_screen->_backgroundPage = _vm->_bVoy->boltEntry( _vm->_playStampGroupId + 5)._picResource; - _vm->_graphicsManager->_vPort->setupViewPort( - _vm->_graphicsManager->_backgroundPage); + _vm->_screen->_vPort->setupViewPort( + _vm->_screen->_backgroundPage); } CMapResource *pal = _vm->_bVoy->boltEntry(_vm->_playStampGroupId + 4)._cMapResource; @@ -1624,10 +1624,10 @@ void ThreadResource::loadTheApt() { } void ThreadResource::freeTheApt() { - _vm->_graphicsManager->fadeDownICF1(5); + _vm->_screen->fadeDownICF1(5); _vm->flipPageAndWaitForFade(); - _vm->_graphicsManager->fadeUpICF1(); + _vm->_screen->fadeUpICF1(); if (_vm->_currentVocId != -1) { _vm->_soundManager->stopVOCPlay(); @@ -1635,17 +1635,17 @@ void ThreadResource::freeTheApt() { } if (_vm->_voy->_aptLoadMode == -1) { - _vm->_graphicsManager->fadeDownICF(6); + _vm->_screen->fadeDownICF(6); } else { doAptAnim(2); } if (_vm->_voy->_aptLoadMode == 140) { - _vm->_graphicsManager->screenReset(); - _vm->_graphicsManager->resetPalette(); + _vm->_screen->screenReset(); + _vm->_screen->resetPalette(); } - _vm->_graphicsManager->_vPort->setupViewPort(nullptr); + _vm->_screen->_vPort->setupViewPort(nullptr); _vm->_bVoy->freeBoltGroup(_vm->_playStampGroupId); _vm->_playStampGroupId = -1; _vm->_voy->_viewBounds = nullptr; @@ -1705,7 +1705,7 @@ void ThreadResource::doAptAnim(int mode) { for (int idx = 0; (idx < 6) && !_vm->shouldQuit(); ++idx) { PictureResource *pic = _vm->_bVoy->boltEntry(id + idx + 1)._picResource; - _vm->_graphicsManager->_vPort->setupViewPort(pic); + _vm->_screen->_vPort->setupViewPort(pic); pal->startFade(); _vm->flipPageAndWait(); diff --git a/engines/voyeur/module.mk b/engines/voyeur/module.mk index aab254cf36..a38bdd9ab2 100644 --- a/engines/voyeur/module.mk +++ b/engines/voyeur/module.mk @@ -8,7 +8,7 @@ MODULE_OBJS := \ events.o \ files.o \ files_threads.o \ - graphics.o \ + screen.o \ sound.o \ staticres.o \ voyeur.o \ diff --git a/engines/voyeur/graphics.cpp b/engines/voyeur/screen.cpp index a20e9f6006..62f609c5c7 100644 --- a/engines/voyeur/graphics.cpp +++ b/engines/voyeur/screen.cpp @@ -20,7 +20,7 @@ * */ -#include "voyeur/graphics.h" +#include "voyeur/screen.h" #include "voyeur/voyeur.h" #include "voyeur/staticres.h" #include "engines/util.h" @@ -38,7 +38,8 @@ DrawInfo::DrawInfo(int penColor, const Common::Point &pos) { /*------------------------------------------------------------------------*/ -GraphicsManager::GraphicsManager(VoyeurEngine *vm) : _defaultDrawInfo(1, Common::Point()), _drawPtr(&_defaultDrawInfo), _vm(vm) { +Screen::Screen(VoyeurEngine *vm) : Graphics::Screen(), _vm(vm), _drawPtr(&_defaultDrawInfo), + _defaultDrawInfo(1, Common::Point()) { _SVGAMode = 0; _planeSelect = 0; _saveBack = true; @@ -52,18 +53,17 @@ GraphicsManager::GraphicsManager(VoyeurEngine *vm) : _defaultDrawInfo(1, Common: _backColors = nullptr; } -void GraphicsManager::sInitGraphics() { +void Screen::sInitGraphics() { initGraphics(SCREEN_WIDTH, SCREEN_HEIGHT, false); - _screenSurface.create(SCREEN_WIDTH, SCREEN_HEIGHT, Graphics::PixelFormat::createFormatCLUT8()); + create(SCREEN_WIDTH, SCREEN_HEIGHT); clearPalette(); } -GraphicsManager::~GraphicsManager() { - _screenSurface.free(); +Screen::~Screen() { delete _fontChar; } -void GraphicsManager::setupMCGASaveRect(ViewPortResource *viewPort) { +void Screen::setupMCGASaveRect(ViewPortResource *viewPort) { if (viewPort->_activePage) { viewPort->_activePage->_flags |= DISPFLAG_1; Common::Rect *clipRect = _clipPtr; @@ -77,7 +77,7 @@ void GraphicsManager::setupMCGASaveRect(ViewPortResource *viewPort) { viewPort->_rectListCount[1] = -1; } -void GraphicsManager::addRectOptSaveRect(ViewPortResource *viewPort, int idx, const Common::Rect &bounds) { +void Screen::addRectOptSaveRect(ViewPortResource *viewPort, int idx, const Common::Rect &bounds) { if (viewPort->_rectListCount[idx] == -1) return; @@ -86,7 +86,7 @@ void GraphicsManager::addRectOptSaveRect(ViewPortResource *viewPort, int idx, co ++viewPort->_rectListCount[idx]; } -void GraphicsManager::restoreMCGASaveRect(ViewPortResource *viewPort) { +void Screen::restoreMCGASaveRect(ViewPortResource *viewPort) { if (viewPort->_rectListCount[0] != -1) { for (int i = 0; i < viewPort->_rectListCount[0]; ++i) { addRectOptSaveRect(viewPort, 1, (*viewPort->_rectListPtr[0])[i]); @@ -106,11 +106,11 @@ void GraphicsManager::restoreMCGASaveRect(ViewPortResource *viewPort) { viewPort->_rectListCount[1] = count; } -void GraphicsManager::addRectNoSaveBack(ViewPortResource *viewPort, int idx, const Common::Rect &bounds) { +void Screen::addRectNoSaveBack(ViewPortResource *viewPort, int idx, const Common::Rect &bounds) { // Stubbed/dummy method in the original. } -void GraphicsManager::sDrawPic(DisplayResource *srcDisplay, DisplayResource *destDisplay, +void Screen::sDrawPic(DisplayResource *srcDisplay, DisplayResource *destDisplay, const Common::Point &initialOffset) { int width1, width2; int widthDiff, widthDiff2; @@ -128,7 +128,8 @@ void GraphicsManager::sDrawPic(DisplayResource *srcDisplay, DisplayResource *des int runLength; byte *srcImgData, *destImgData; - byte *srcP, *destP; + const byte *srcP; + byte *destP; byte byteVal, byteVal2; PictureResource *srcPic; @@ -292,7 +293,7 @@ void GraphicsManager::sDrawPic(DisplayResource *srcDisplay, DisplayResource *des // loc_2566F if (srcFlags & DISPFLAG_2) { // loc_256FA - srcP = (byte *)_screenSurface.getPixels() + srcOffset; + srcP = (const byte *)getPixels() + srcOffset; for (int yp = 0; yp < height1; ++yp) { for (int xp = 0; xp < width2; ++xp, ++srcP, ++destP) { @@ -325,13 +326,16 @@ void GraphicsManager::sDrawPic(DisplayResource *srcDisplay, DisplayResource *des } } else { // loc_25829 - destP = (byte *)_screenSurface.getPixels() + screenOffset; + destP = (byte *)getPixels() + screenOffset; for (int yp = 0; yp < height1; ++yp) { Common::copy(srcP, srcP + width2, destP); srcP += width2 + widthDiff; destP += width2 + widthDiff2; } + + addDirtyRect(Common::Rect(offset.x, offset.y, offset.x + width2, + offset.y + height1)); } } } else { @@ -341,13 +345,16 @@ void GraphicsManager::sDrawPic(DisplayResource *srcDisplay, DisplayResource *des error("TODO: sDrawPic variation"); } else { // loc_2606D - destP = (byte *)_screenSurface.getPixels() + screenOffset; + destP = (byte *)getPixels() + screenOffset; for (int yp = 0; yp < height1; ++yp) { Common::copy(srcP, srcP + width2, destP); destP += width2 + widthDiff2; srcP += width2 + widthDiff; } + + addDirtyRect(Common::Rect(offset.x, offset.y, offset.x + width2, + offset.y + height1)); } } } else { @@ -530,11 +537,14 @@ void GraphicsManager::sDrawPic(DisplayResource *srcDisplay, DisplayResource *des // loc_27477 if (destFlags & DISPFLAG_8) { // loc_27481 - destP = (byte *)_screenSurface.getPixels() + screenOffset; + destP = (byte *)getPixels() + screenOffset; for (int yp = 0; yp < height1; ++yp) { Common::fill(destP, destP + width2, onOff); destP += width2 + widthDiff2; } + + addDirtyRect(Common::Rect(offset.x, offset.y, offset.x + width2, + offset.y + height1)); } else { // loc_2753C destP = destImgData + screenOffset; @@ -561,7 +571,7 @@ void GraphicsManager::sDrawPic(DisplayResource *srcDisplay, DisplayResource *des if (srcFlags & PICFLAG_100) { if (isClipped) { // loc_266E3 - destP = (byte *)_screenSurface.getPixels() + screenOffset; + destP = (byte *)getPixels() + screenOffset; tmpWidth = (tmpWidth < 0) ? -tmpWidth : 0; int xMax = tmpWidth + width2; tmpHeight = (tmpHeight < 0) ? -tmpHeight : 0; @@ -592,9 +602,12 @@ void GraphicsManager::sDrawPic(DisplayResource *srcDisplay, DisplayResource *des if (yp >= tmpHeight) destP += widthDiff2; } + + addDirtyRect(Common::Rect(offset.x, offset.y, offset.x + width2, + offset.y + height1)); } else { // loc_26815 - destP = (byte *)_screenSurface.getPixels() + screenOffset; + destP = (byte *)getPixels() + screenOffset; for (int yp = 0; yp < height1; ++yp) { for (int xi = 0; xi < width2; ++xi, ++destP) { @@ -618,10 +631,13 @@ void GraphicsManager::sDrawPic(DisplayResource *srcDisplay, DisplayResource *des destP += widthDiff2; } + + addDirtyRect(Common::Rect(offset.x, offset.y, offset.x + width2, + offset.y + height1)); } } else { // Direct screen write - destP = (byte *)_screenSurface.getPixels() + screenOffset; + destP = (byte *)getPixels() + screenOffset; for (int yp = 0; yp < height1; ++yp) { for (int xp = 0; xp < width2; ++xp, ++srcP, ++destP) { @@ -631,6 +647,9 @@ void GraphicsManager::sDrawPic(DisplayResource *srcDisplay, DisplayResource *des destP += widthDiff2; srcP += widthDiff; } + + addDirtyRect(Common::Rect(offset.x, offset.y, offset.x + width2, + offset.y + height1)); } } else if (srcFlags & PICFLAG_100) { srcP = srcImgData; @@ -663,7 +682,7 @@ void GraphicsManager::sDrawPic(DisplayResource *srcDisplay, DisplayResource *des } } else { // loc_26BD5 - destP = (byte *)_screenSurface.getPixels() + screenOffset; + destP = (byte *)getPixels() + screenOffset; for (int yp = 0; yp < height1; ++yp) { byteVal2 = 0; @@ -684,10 +703,13 @@ void GraphicsManager::sDrawPic(DisplayResource *srcDisplay, DisplayResource *des destP += widthDiff2; } + + addDirtyRect(Common::Rect(offset.x, offset.y, offset.x + width2, + offset.y + height1)); } } else { // loc_26C9A - destP = (byte *)_screenSurface.getPixels() + screenOffset; + destP = (byte *)getPixels() + screenOffset; for (int yp = 0; yp < height1; ++yp) { for (int xp = 0; xp < width2; ++xp, ++srcP, ++destP) { @@ -696,6 +718,9 @@ void GraphicsManager::sDrawPic(DisplayResource *srcDisplay, DisplayResource *des destP += widthDiff2; srcP += widthDiff; } + + addDirtyRect(Common::Rect(offset.x, offset.y, offset.x + width2, + offset.y + height1)); } } else { // loc_26D2F @@ -850,12 +875,12 @@ void GraphicsManager::sDrawPic(DisplayResource *srcDisplay, DisplayResource *des } } -void GraphicsManager::drawANumber(DisplayResource *display, int num, const Common::Point &pt) { +void Screen::drawANumber(DisplayResource *display, int num, const Common::Point &pt) { PictureResource *pic = _vm->_bVoy->boltEntry(num + 261)._picResource; sDrawPic(pic, display, pt); } -void GraphicsManager::fillPic(DisplayResource *display, byte onOff) { +void Screen::fillPic(DisplayResource *display, byte onOff) { PictureResource *pic; if (display->_flags & DISPFLAG_VIEWPORT) { pic = ((ViewPortResource *)display)->_currentPic; @@ -876,11 +901,11 @@ void GraphicsManager::fillPic(DisplayResource *display, byte onOff) { /** * Queues the given picture for display */ -void GraphicsManager::sDisplayPic(PictureResource *pic) { +void Screen::sDisplayPic(PictureResource *pic) { _vm->_eventsManager->_intPtr._flipWait = true; } -void GraphicsManager::flipPage() { +void Screen::flipPage() { Common::Array<ViewPortResource *> &viewPorts = _viewPortListPtr->_entries; bool flipFlag = false; @@ -907,7 +932,7 @@ void GraphicsManager::flipPage() { } } -void GraphicsManager::restoreBack(Common::Array<Common::Rect> &rectList, int rectListCount, +void Screen::restoreBack(Common::Array<Common::Rect> &rectList, int rectListCount, PictureResource *srcPic, PictureResource *destPic) { // WORKAROUND: Since _backgroundPage can point to a resource freed at the end of display methods, // I'm now explicitly resetting it to null in screenReset(), so at this point it can be null @@ -929,33 +954,26 @@ void GraphicsManager::restoreBack(Common::Array<Common::Rect> &rectList, int rec _saveBack = saveBack; } -void GraphicsManager::clearPalette() { - byte palette[768]; - Common::fill(&palette[0], &palette[768], 0); - g_system->getPaletteManager()->setPalette(&palette[0], 0, 256); -} - -void GraphicsManager::setPalette(const byte *palette, int start, int count) { - g_system->getPaletteManager()->setPalette(palette, start, count); +void Screen::setPalette(const byte *palette, int start, int count) { + Graphics::Screen::setPalette(palette, start, count); _vm->_eventsManager->_gameData._hasPalette = false; } -void GraphicsManager::setPalette128(const byte *palette, int start, int count) { +void Screen::setPalette128(const byte *palette, int start, int count) { byte rgb[3]; - g_system->getPaletteManager()->grabPalette(&rgb[0], 128, 1); - g_system->getPaletteManager()->setPalette(palette, start, count); - g_system->getPaletteManager()->setPalette(&rgb[0], 128, 1); + getPalette(&rgb[0], 128, 1); + Graphics::Screen::setPalette(palette, start, count); + Graphics::Screen::setPalette(&rgb[0], 128, 1); } - -void GraphicsManager::resetPalette() { +void Screen::resetPalette() { for (int i = 0; i < 256; ++i) setColor(i, 0, 0, 0); _vm->_eventsManager->_intPtr._hasPalette = true; } -void GraphicsManager::setColor(int idx, byte r, byte g, byte b) { +void Screen::setColor(int idx, byte r, byte g, byte b) { byte *vgaP = &_VGAColors[idx * 3]; vgaP[0] = r; vgaP[1] = g; @@ -965,7 +983,7 @@ void GraphicsManager::setColor(int idx, byte r, byte g, byte b) { _vm->_eventsManager->_intPtr._palEndIndex = MAX(_vm->_eventsManager->_intPtr._palEndIndex, idx); } -void GraphicsManager::setOneColor(int idx, byte r, byte g, byte b) { +void Screen::setOneColor(int idx, byte r, byte g, byte b) { byte palEntry[3]; palEntry[0] = r; palEntry[1] = g; @@ -973,7 +991,7 @@ void GraphicsManager::setOneColor(int idx, byte r, byte g, byte b) { g_system->getPaletteManager()->setPalette(&palEntry[0], idx, 1); } -void GraphicsManager::setColors(int start, int count, const byte *pal) { +void Screen::setColors(int start, int count, const byte *pal) { for (int i = 0; i < count; ++i) { if ((i + start) != 128) { const byte *rgb = pal + i * 3; @@ -984,7 +1002,7 @@ void GraphicsManager::setColors(int start, int count, const byte *pal) { _vm->_eventsManager->_intPtr._hasPalette = true; } -void GraphicsManager::screenReset() { +void Screen::screenReset() { resetPalette(); _backgroundPage = NULL; @@ -994,7 +1012,7 @@ void GraphicsManager::screenReset() { _vm->flipPageAndWait(); } -void GraphicsManager::fadeDownICF1(int steps) { +void Screen::fadeDownICF1(int steps) { if (steps > 0) { int stepAmount = _vm->_voy->_fadingAmount2 / steps; @@ -1007,7 +1025,7 @@ void GraphicsManager::fadeDownICF1(int steps) { _vm->_voy->_fadingAmount2 = 0; } -void GraphicsManager::fadeUpICF1(int steps) { +void Screen::fadeUpICF1(int steps) { if (steps > 0) { int stepAmount = (63 - _vm->_voy->_fadingAmount2) / steps; @@ -1020,7 +1038,7 @@ void GraphicsManager::fadeUpICF1(int steps) { _vm->_voy->_fadingAmount2 = 63; } -void GraphicsManager::fadeDownICF(int steps) { +void Screen::fadeDownICF(int steps) { if (steps > 0) { _vm->_eventsManager->hideCursor(); int stepAmount1 = _vm->_voy->_fadingAmount1 / steps; @@ -1037,14 +1055,19 @@ void GraphicsManager::fadeDownICF(int steps) { _vm->_voy->_fadingAmount2 = 0; } -void GraphicsManager::drawDot() { - for (int y = 0; y < 9; ++y) { - byte *pDest = (byte *)_screenSurface.getPixels() + DOT_LINE_START[y] + DOT_LINE_OFFSET[y]; - Common::fill(pDest, pDest + DOT_LINE_LENGTH[y], 0x80); +void Screen::drawDot() { + for (int idx = 0; idx < 9; ++idx) { + uint offset = DOT_LINE_START[idx] + DOT_LINE_OFFSET[idx]; + int xp = offset % SCREEN_WIDTH; + int yp = offset / SCREEN_WIDTH; + + byte *pDest = (byte *)getPixels() + offset; + Common::fill(pDest, pDest + DOT_LINE_LENGTH[idx], 0x80); + addDirtyRect(Common::Rect(xp, yp, xp + DOT_LINE_LENGTH[idx], yp + 1)); } } -void GraphicsManager::synchronize(Common::Serializer &s) { +void Screen::synchronize(Common::Serializer &s) { s.syncBytes(&_VGAColors[0], PALETTE_SIZE); } diff --git a/engines/voyeur/graphics.h b/engines/voyeur/screen.h index e4d0b38650..aaf61747a4 100644 --- a/engines/voyeur/graphics.h +++ b/engines/voyeur/screen.h @@ -27,17 +27,15 @@ #include "common/array.h" #include "common/rect.h" #include "common/serializer.h" -#include "graphics/surface.h" +#include "graphics/screen.h" namespace Voyeur { #define SCREEN_WIDTH 320 #define SCREEN_HEIGHT 200 -#define PALETTE_COUNT 256 -#define PALETTE_SIZE (256 * 3) class VoyeurEngine; -class GraphicsManager; +class Screen; class DisplayResource; class PictureResource; class ViewPortResource; @@ -54,12 +52,12 @@ public: DrawInfo(int penColor, const Common::Point &pos); }; -typedef void (GraphicsManager::*GraphicMethodPtr)(); -typedef void (GraphicsManager::*ViewPortSetupPtr)(ViewPortResource *); -typedef void (GraphicsManager::*ViewPortAddPtr)(ViewPortResource *, int idx, const Common::Rect &bounds); -typedef void (GraphicsManager::*ViewPortRestorePtr)(ViewPortResource *); +typedef void (Screen::*ScreenMethodPtr)(); +typedef void (Screen::*ViewPortSetupPtr)(ViewPortResource *); +typedef void (Screen::*ViewPortAddPtr)(ViewPortResource *, int idx, const Common::Rect &bounds); +typedef void (Screen::*ViewPortRestorePtr)(ViewPortResource *); -class GraphicsManager { +class Screen: public Graphics::Screen { public: byte _VGAColors[PALETTE_SIZE]; PictureResource *_backgroundPage; @@ -69,7 +67,6 @@ public: bool _saveBack; Common::Rect *_clipPtr; uint _planeSelect; - Graphics::Surface _screenSurface; CMapResource *_backColors; FontInfoResource *_fontPtr; PictureResource *_fontChar; @@ -81,8 +78,8 @@ private: void restoreBack(Common::Array<Common::Rect> &rectList, int rectListCount, PictureResource *srcPic, PictureResource *destPic); public: - GraphicsManager(VoyeurEngine *vm); - ~GraphicsManager(); + Screen(VoyeurEngine *vm); + virtual ~Screen(); void sInitGraphics(); @@ -96,7 +93,6 @@ public: void sDisplayPic(PictureResource *pic); void drawANumber(DisplayResource *display, int num, const Common::Point &pt); void flipPage(); - void clearPalette(); void setPalette(const byte *palette, int start, int count); void setPalette128(const byte *palette, int start, int count); void resetPalette(); diff --git a/engines/voyeur/voyeur.cpp b/engines/voyeur/voyeur.cpp index cbb6846340..01b76a72d1 100644 --- a/engines/voyeur/voyeur.cpp +++ b/engines/voyeur/voyeur.cpp @@ -22,7 +22,7 @@ #include "voyeur/voyeur.h" #include "voyeur/animation.h" -#include "voyeur/graphics.h" +#include "voyeur/screen.h" #include "voyeur/staticres.h" #include "common/scummsys.h" #include "common/config-manager.h" @@ -40,7 +40,7 @@ VoyeurEngine::VoyeurEngine(OSystem *syst, const VoyeurGameDescription *gameDesc) _debugger = nullptr; _eventsManager = nullptr; _filesManager = nullptr; - _graphicsManager = nullptr; + _screen = nullptr; _soundManager = nullptr; _voy = nullptr; _bVoy = NULL; @@ -65,13 +65,6 @@ VoyeurEngine::VoyeurEngine(OSystem *syst, const VoyeurGameDescription *gameDesc) DebugMan.addDebugChannel(kDebugScripts, "scripts", "Game scripts"); - _debugger = new Debugger(this); - _eventsManager = new EventsManager(this); - _filesManager = new FilesManager(this); - _graphicsManager = new GraphicsManager(this); - _soundManager = new SoundManager(_mixer); - _voy = new SVoy(this); - _stampLibPtr = nullptr; _controlGroupPtr = nullptr; _stampData = nullptr; @@ -88,7 +81,7 @@ VoyeurEngine::~VoyeurEngine() { delete _bVoy; delete _voy; delete _soundManager; - delete _graphicsManager; + delete _screen; delete _filesManager; delete _eventsManager; delete _debugger; @@ -126,15 +119,22 @@ void VoyeurEngine::ESP_Init() { } void VoyeurEngine::globalInitBolt() { + _debugger = new Debugger(this); + _eventsManager = new EventsManager(this); + _filesManager = new FilesManager(this); + _screen = new Screen(this); + _soundManager = new SoundManager(_mixer); + _voy = new SVoy(this); + initBolt(); _filesManager->openBoltLib("bvoy.blt", _bVoy); _bVoy->getBoltGroup(0x000); _bVoy->getBoltGroup(0x100); - _graphicsManager->_fontPtr = &_defaultFontInfo; - _graphicsManager->_fontPtr->_curFont = _bVoy->boltEntry(0x101)._fontResource; - assert(_graphicsManager->_fontPtr->_curFont); + _screen->_fontPtr = &_defaultFontInfo; + _screen->_fontPtr->_curFont = _bVoy->boltEntry(0x101)._fontResource; + assert(_screen->_fontPtr->_curFont); // Setup default flags _voy->_viewBounds = nullptr; @@ -144,13 +144,13 @@ void VoyeurEngine::globalInitBolt() { void VoyeurEngine::initBolt() { vInitInterrupts(); - _graphicsManager->sInitGraphics(); + _screen->sInitGraphics(); _eventsManager->vInitColor(); initInput(); } void VoyeurEngine::vInitInterrupts() { - _eventsManager->_intPtr._palette = &_graphicsManager->_VGAColors[0]; + _eventsManager->_intPtr._palette = &_screen->_VGAColors[0]; } void VoyeurEngine::initInput() { @@ -213,8 +213,8 @@ bool VoyeurEngine::doHeadTitle() { } void VoyeurEngine::showConversionScreen() { - _graphicsManager->_backgroundPage = _bVoy->boltEntry(0x502)._picResource; - _graphicsManager->_vPort->setupViewPort(); + _screen->_backgroundPage = _bVoy->boltEntry(0x502)._picResource; + _screen->_vPort->setupViewPort(); flipPageAndWait(); // Immediate palette load to show the initial screen @@ -237,7 +237,7 @@ void VoyeurEngine::showConversionScreen() { flipPageAndWaitForFade(); - _graphicsManager->screenReset(); + _screen->screenReset(); } bool VoyeurEngine::doLock() { @@ -249,28 +249,28 @@ bool VoyeurEngine::doLock() { if (_bVoy->getBoltGroup(0x700)) { Common::String password = "3333"; - _graphicsManager->_backgroundPage = _bVoy->getPictureResource(0x700); - _graphicsManager->_backColors = _bVoy->getCMapResource(0x701); + _screen->_backgroundPage = _bVoy->getPictureResource(0x700); + _screen->_backColors = _bVoy->getCMapResource(0x701); PictureResource *cursorPic = _bVoy->getPictureResource(0x702); _voy->_viewBounds = _bVoy->boltEntry(0x704)._rectResource; Common::Array<RectEntry> &hotspots = _bVoy->boltEntry(0x705)._rectResource->_entries; assert(cursorPic); - _graphicsManager->_vPort->setupViewPort(); + _screen->_vPort->setupViewPort(); - _graphicsManager->_backColors->startFade(); - _graphicsManager->_vPort->_parent->_flags |= DISPFLAG_8; - _graphicsManager->flipPage(); + _screen->_backColors->startFade(); + _screen->_vPort->_parent->_flags |= DISPFLAG_8; + _screen->flipPage(); _eventsManager->sWaitFlip(); while (!shouldQuit() && (_eventsManager->_fadeStatus & 1)) _eventsManager->delay(1); _eventsManager->setCursorColor(127, 0); - _graphicsManager->setColor(1, 64, 64, 64); - _graphicsManager->setColor(2, 96, 96, 96); - _graphicsManager->setColor(3, 160, 160, 160); - _graphicsManager->setColor(4, 224, 224, 224); + _screen->setColor(1, 64, 64, 64); + _screen->setColor(2, 96, 96, 96); + _screen->setColor(3, 160, 160, 160); + _screen->setColor(4, 224, 224, 224); // Set up the cursor _eventsManager->setCursor(cursorPic); @@ -278,9 +278,9 @@ bool VoyeurEngine::doLock() { _eventsManager->_intPtr._hasPalette = true; - _graphicsManager->_fontPtr->_curFont = _bVoy->boltEntry(0x708)._fontResource; - _graphicsManager->_fontPtr->_fontSaveBack = 0; - _graphicsManager->_fontPtr->_fontFlags = DISPFLAG_NONE; + _screen->_fontPtr->_curFont = _bVoy->boltEntry(0x708)._fontResource; + _screen->_fontPtr->_fontSaveBack = 0; + _screen->_fontPtr->_fontFlags = DISPFLAG_NONE; Common::String dateString = "ScummVM"; Common::String displayString = Common::String::format("Last Play %s", dateString.c_str()); @@ -288,16 +288,16 @@ bool VoyeurEngine::doLock() { bool firstLoop = true; bool breakFlag = false; while (!breakFlag && !shouldQuit()) { - _graphicsManager->_vPort->setupViewPort(); + _screen->_vPort->setupViewPort(); flipPageAndWait(); // Display the last play time - _graphicsManager->_fontPtr->_pos = Common::Point(0, 97); - _graphicsManager->_fontPtr->_justify = ALIGN_CENTER; - _graphicsManager->_fontPtr->_justifyWidth = 384; - _graphicsManager->_fontPtr->_justifyHeight = 97; + _screen->_fontPtr->_pos = Common::Point(0, 97); + _screen->_fontPtr->_justify = ALIGN_CENTER; + _screen->_fontPtr->_justifyWidth = 384; + _screen->_fontPtr->_justifyHeight = 97; - _graphicsManager->_vPort->drawText(displayString); + _screen->_vPort->drawText(displayString); flipPageAndWait(); if (firstLoop) { @@ -356,7 +356,7 @@ bool VoyeurEngine::doLock() { } else if (key == 11) { // New code if ((password.empty() && displayString.empty()) || (password != displayString)) { - _graphicsManager->_vPort->setupViewPort(); + _screen->_vPort->setupViewPort(); password = displayString; displayString = ""; continue; @@ -373,9 +373,9 @@ bool VoyeurEngine::doLock() { _soundManager->playVOCMap(wrongVoc, wrongVocSize); } - _graphicsManager->fillPic(_graphicsManager->_vPort, 0); + _screen->fillPic(_screen->_vPort, 0); flipPageAndWait(); - _graphicsManager->resetPalette(); + _screen->resetPalette(); _voy->_viewBounds = nullptr; _bVoy->freeBoltGroup(0x700); @@ -393,9 +393,9 @@ void VoyeurEngine::showTitleScreen() { if (!_bVoy->getBoltGroup(0x500)) return; - _graphicsManager->_backgroundPage = _bVoy->getPictureResource(0x500); + _screen->_backgroundPage = _bVoy->getPictureResource(0x500); - _graphicsManager->_vPort->setupViewPort(); + _screen->_vPort->setupViewPort(); flipPageAndWait(); // Immediate palette load to show the initial screen @@ -422,18 +422,18 @@ void VoyeurEngine::showTitleScreen() { return; } - _graphicsManager->screenReset(); + _screen->screenReset(); _eventsManager->delayClick(200); // Voyeur title playRL2Video("a1100100.rl2"); - _graphicsManager->screenReset(); + _screen->screenReset(); _bVoy->freeBoltGroup(0x500); } void VoyeurEngine::doOpening() { - _graphicsManager->screenReset(); + _screen->screenReset(); if (!_bVoy->getBoltGroup(0x200)) return; @@ -459,10 +459,10 @@ void VoyeurEngine::doOpening() { _voy->_eventFlags &= ~EVTFLAG_TIME_DISABLED; for (int i = 0; i < 256; ++i) - _graphicsManager->setColor(i, 8, 8, 8); + _screen->setColor(i, 8, 8, 8); _eventsManager->_intPtr._hasPalette = true; - _graphicsManager->_vPort->setupViewPort(); + _screen->_vPort->setupViewPort(); flipPageAndWait(); RL2Decoder decoder; @@ -472,14 +472,12 @@ void VoyeurEngine::doOpening() { while (!shouldQuit() && !decoder.endOfVideo() && !_eventsManager->_mouseClicked) { if (decoder.hasDirtyPalette()) { const byte *palette = decoder.getPalette(); - _graphicsManager->setPalette(palette, 0, 256); + _screen->setPalette(palette, 0, 256); } if (decoder.needsUpdate()) { const Graphics::Surface *frame = decoder.decodeNextFrame(); - - Common::copy((const byte *)frame->getPixels(), (const byte *)frame->getPixels() + 320 * 200, - (byte *)_graphicsManager->_screenSurface.getPixels()); + _screen->blitFrom(*frame); if (decoder.getCurFrame() >= (int32)READ_LE_UINT32(frameTable + frameIndex * 4)) { if (creditShow) { @@ -499,7 +497,7 @@ void VoyeurEngine::doOpening() { } if (textPic) { - _graphicsManager->sDrawPic(textPic, _graphicsManager->_vPort, textPos); + _screen->sDrawPic(textPic, _screen->_vPort, textPos); } flipPageAndWait(); @@ -527,14 +525,12 @@ void VoyeurEngine::playRL2Video(const Common::String &filename) { while (!shouldQuit() && !decoder.endOfVideo() && !_eventsManager->_mouseClicked) { if (decoder.hasDirtyPalette()) { const byte *palette = decoder.getPalette(); - _graphicsManager->setPalette(palette, 0, 256); + _screen->setPalette(palette, 0, 256); } if (decoder.needsUpdate()) { const Graphics::Surface *frame = decoder.decodeNextFrame(); - - Common::copy((const byte *)frame->getPixels(), (const byte *)frame->getPixels() + 320 * 200, - (byte *)_graphicsManager->_screenSurface.getPixels()); + _screen->blitFrom(*frame); } _eventsManager->getMouseInfo(); @@ -573,17 +569,16 @@ void VoyeurEngine::playAVideoDuration(int videoId, int duration) { (decoder.getCurFrame() < endFrame)) { if (decoder.needsUpdate()) { const Graphics::Surface *frame = decoder.decodeNextFrame(); + _screen->blitFrom(*frame); - Common::copy((const byte *)frame->getPixels(), (const byte *)frame->getPixels() + 320 * 200, - (byte *)_graphicsManager->_screenSurface.getPixels()); if (_voy->_eventFlags & EVTFLAG_RECORDING) - _graphicsManager->drawDot(); + _screen->drawDot(); } if (decoder.hasDirtyPalette()) { const byte *palette = decoder.getPalette(); - _graphicsManager->setPalette(palette, 0, decoder.getPaletteCount()); - _graphicsManager->setOneColor(128, 220, 20, 20); + _screen->setPalette(palette, 0, decoder.getPaletteCount()); + _screen->setOneColor(128, 220, 20, 20); } _eventsManager->getMouseInfo(); @@ -591,13 +586,13 @@ void VoyeurEngine::playAVideoDuration(int videoId, int duration) { } // RL2 finished - _graphicsManager->screenReset(); + _screen->screenReset(); _voy->_eventFlags &= ~EVTFLAG_RECORDING; if (_voy->_eventFlags & EVTFLAG_8) { assert(pic); - byte *imgData = _graphicsManager->_vPort->_currentPic->_imgData; - _graphicsManager->_vPort->_currentPic->_imgData = pic->_imgData; + byte *imgData = _screen->_vPort->_currentPic->_imgData; + _screen->_vPort->_currentPic->_imgData = pic->_imgData; pic->_imgData = imgData; _voy->_eventFlags &= ~EVTFLAG_8; } @@ -608,13 +603,13 @@ void VoyeurEngine::playAVideoDuration(int videoId, int duration) { void VoyeurEngine::playAudio(int audioId) { _bVoy->getBoltGroup(0x7F00); - _graphicsManager->_backgroundPage = _bVoy->boltEntry(0x7F00 + + _screen->_backgroundPage = _bVoy->boltEntry(0x7F00 + BLIND_TABLE[audioId] * 2)._picResource; - _graphicsManager->_backColors = _bVoy->boltEntry(0x7F01 + + _screen->_backColors = _bVoy->boltEntry(0x7F01 + BLIND_TABLE[audioId] * 2)._cMapResource; - _graphicsManager->_vPort->setupViewPort(); - _graphicsManager->_backColors->startFade(); + _screen->_vPort->setupViewPort(); + _screen->_backColors->startFade(); flipPageAndWaitForFade(); _voy->_eventFlags &= ~EVTFLAG_TIME_DISABLED; @@ -633,26 +628,26 @@ void VoyeurEngine::playAudio(int audioId) { _soundManager->stopVOCPlay(); _bVoy->freeBoltGroup(0x7F00); - _graphicsManager->_vPort->setupViewPort(NULL); + _screen->_vPort->setupViewPort(NULL); _voy->_eventFlags &= ~EVTFLAG_RECORDING; _voy->_playStampMode = 129; } void VoyeurEngine::doTransitionCard(const Common::String &time, const Common::String &location) { - _graphicsManager->setColor(128, 16, 16, 16); - _graphicsManager->setColor(224, 220, 220, 220); + _screen->setColor(128, 16, 16, 16); + _screen->setColor(224, 220, 220, 220); _eventsManager->_intPtr._hasPalette = true; - _graphicsManager->_vPort->setupViewPort(NULL); - _graphicsManager->_vPort->fillPic(0x80); - _graphicsManager->flipPage(); + _screen->_vPort->setupViewPort(NULL); + _screen->_vPort->fillPic(0x80); + _screen->flipPage(); _eventsManager->sWaitFlip(); flipPageAndWait(); - _graphicsManager->_vPort->fillPic(0x80); + _screen->_vPort->fillPic(0x80); - FontInfoResource &fi = *_graphicsManager->_fontPtr; + FontInfoResource &fi = *_screen->_fontPtr; fi._curFont = _bVoy->boltEntry(257)._fontResource; fi._foreColor = 224; fi._fontSaveBack = 0; @@ -661,7 +656,7 @@ void VoyeurEngine::doTransitionCard(const Common::String &time, const Common::St fi._justifyWidth = 384; fi._justifyHeight = 120; - _graphicsManager->_vPort->drawText(time); + _screen->_vPort->drawText(time); if (!location.empty()) { fi._pos = Common::Point(0, 138); @@ -669,7 +664,7 @@ void VoyeurEngine::doTransitionCard(const Common::String &time, const Common::St fi._justifyWidth = 384; fi._justifyHeight = 140; - _graphicsManager->_vPort->drawText(location); + _screen->_vPort->drawText(location); } flipPageAndWait(); @@ -680,8 +675,8 @@ void VoyeurEngine::saveLastInplay() { } void VoyeurEngine::flipPageAndWait() { - _graphicsManager->_vPort->_flags |= DISPFLAG_8; - _graphicsManager->flipPage(); + _screen->_vPort->_flags |= DISPFLAG_8; + _screen->flipPage(); _eventsManager->sWaitFlip(); } @@ -702,7 +697,7 @@ void VoyeurEngine::showEndingNews() { PictureResource *pic = _bVoy->boltEntry(_playStampGroupId)._picResource; CMapResource *pal = _bVoy->boltEntry(_playStampGroupId + 1)._cMapResource; - _graphicsManager->_vPort->setupViewPort(pic); + _screen->_vPort->setupViewPort(pic); pal->startFade(); flipPageAndWaitForFade(); @@ -717,7 +712,7 @@ void VoyeurEngine::showEndingNews() { pal = _bVoy->boltEntry(_playStampGroupId + idx * 2 + 1)._cMapResource; } - _graphicsManager->_vPort->setupViewPort(pic); + _screen->_vPort->setupViewPort(pic); pal->startFade(); flipPageAndWaitForFade(); @@ -852,7 +847,7 @@ void VoyeurEngine::synchronize(Common::Serializer &s) { // Sub-systems _voy->synchronize(s); - _graphicsManager->synchronize(s); + _screen->synchronize(s); _mainThread->synchronize(s); _controlPtr->_state->synchronize(s); } @@ -906,8 +901,8 @@ void VoyeurSavegameHeader::write(Common::OutSaveFile *f, VoyeurEngine *vm, const // Create a thumbnail and save it Graphics::Surface *thumb = new Graphics::Surface(); - ::createThumbnail(thumb, (byte *)vm->_graphicsManager->_screenSurface.getPixels(), - SCREEN_WIDTH, SCREEN_HEIGHT, vm->_graphicsManager->_VGAColors); + ::createThumbnail(thumb, (const byte *)vm->_screen->getPixels(), + SCREEN_WIDTH, SCREEN_HEIGHT, vm->_screen->_VGAColors); Graphics::saveThumbnail(*f, *thumb); thumb->free(); delete thumb; diff --git a/engines/voyeur/voyeur.h b/engines/voyeur/voyeur.h index e0bb734fa8..9cda85fd51 100644 --- a/engines/voyeur/voyeur.h +++ b/engines/voyeur/voyeur.h @@ -27,7 +27,7 @@ #include "voyeur/data.h" #include "voyeur/events.h" #include "voyeur/files.h" -#include "voyeur/graphics.h" +#include "voyeur/screen.h" #include "voyeur/sound.h" #include "common/scummsys.h" #include "common/system.h" @@ -164,7 +164,7 @@ public: Debugger *_debugger; EventsManager *_eventsManager; FilesManager *_filesManager; - GraphicsManager *_graphicsManager; + Screen *_screen; SoundManager *_soundManager; SVoy *_voy; diff --git a/engines/voyeur/voyeur_game.cpp b/engines/voyeur/voyeur_game.cpp index 13ef31839a..e9591955fc 100644 --- a/engines/voyeur/voyeur_game.cpp +++ b/engines/voyeur/voyeur_game.cpp @@ -149,8 +149,8 @@ void VoyeurEngine::playStamp() { case 130: { // user selected to send the tape if (_bVoy->getBoltGroup(_playStampGroupId)) { - _graphicsManager->_backgroundPage = _bVoy->boltEntry(_playStampGroupId)._picResource; - _graphicsManager->_backColors = _bVoy->boltEntry(_playStampGroupId + 1)._cMapResource; + _screen->_backgroundPage = _bVoy->boltEntry(_playStampGroupId)._picResource; + _screen->_backColors = _bVoy->boltEntry(_playStampGroupId + 1)._cMapResource; buttonId = getChooseButton(); if (_eventsManager->_rightClick) @@ -158,7 +158,7 @@ void VoyeurEngine::playStamp() { buttonId = 4; _bVoy->freeBoltGroup(_playStampGroupId); - _graphicsManager->screenReset(); + _screen->screenReset(); _playStampGroupId = -1; flag = true; @@ -232,8 +232,8 @@ void VoyeurEngine::closeStamp() { } void VoyeurEngine::doTailTitle() { - _graphicsManager->_vPort->setupViewPort(NULL); - _graphicsManager->screenReset(); + _screen->_vPort->setupViewPort(NULL); + _screen->screenReset(); if (_bVoy->getBoltGroup(0x600)) { RL2Decoder decoder; @@ -245,12 +245,12 @@ void VoyeurEngine::doTailTitle() { doClosingCredits(); if (!shouldQuit() && !_eventsManager->_mouseClicked) { - _graphicsManager->screenReset(); + _screen->screenReset(); PictureResource *pic = _bVoy->boltEntry(0x602)._picResource; CMapResource *pal = _bVoy->boltEntry(0x603)._cMapResource; - _graphicsManager->_vPort->setupViewPort(pic); + _screen->_vPort->setupViewPort(pic); pal->startFade(); flipPageAndWaitForFade(); _eventsManager->delayClick(300); @@ -258,7 +258,7 @@ void VoyeurEngine::doTailTitle() { pic = _bVoy->boltEntry(0x604)._picResource; pal = _bVoy->boltEntry(0x605)._cMapResource; - _graphicsManager->_vPort->setupViewPort(pic); + _screen->_vPort->setupViewPort(pic); pal->startFade(); flipPageAndWaitForFade(); _eventsManager->delayClick(120); @@ -283,26 +283,26 @@ void VoyeurEngine::doClosingCredits() { const char *msg = (const char *)_bVoy->memberAddr(0x404); const byte *creditList = (const byte *)_bVoy->memberAddr(0x405); - _graphicsManager->_vPort->setupViewPort(NULL); - _graphicsManager->setColor(1, 180, 180, 180); - _graphicsManager->setColor(2, 200, 200, 200); + _screen->_vPort->setupViewPort(NULL); + _screen->setColor(1, 180, 180, 180); + _screen->setColor(2, 200, 200, 200); _eventsManager->_intPtr._hasPalette = true; - _graphicsManager->_fontPtr->_curFont = _bVoy->boltEntry(0x402)._fontResource; - _graphicsManager->_fontPtr->_foreColor = 2; - _graphicsManager->_fontPtr->_backColor = 2; - _graphicsManager->_fontPtr->_fontSaveBack = false; - _graphicsManager->_fontPtr->_fontFlags = DISPFLAG_NONE; + _screen->_fontPtr->_curFont = _bVoy->boltEntry(0x402)._fontResource; + _screen->_fontPtr->_foreColor = 2; + _screen->_fontPtr->_backColor = 2; + _screen->_fontPtr->_fontSaveBack = false; + _screen->_fontPtr->_fontFlags = DISPFLAG_NONE; _soundManager->startVOCPlay(152); - FontInfoResource &fi = *_graphicsManager->_fontPtr; + FontInfoResource &fi = *_screen->_fontPtr; for (int idx = 0; idx < 78; ++idx) { const byte *entry = creditList + idx * 6; int flags = READ_LE_UINT16(entry + 4); if (flags & 0x10) - _graphicsManager->_vPort->fillPic(0); + _screen->_vPort->fillPic(0); if (flags & 1) { fi._foreColor = 1; @@ -312,7 +312,7 @@ void VoyeurEngine::doClosingCredits() { fi._justifyHeight = 240; fi._pos = Common::Point(0, READ_LE_UINT16(entry)); - _graphicsManager->_vPort->drawText(msg); + _screen->_vPort->drawText(msg); msg += strlen(msg) + 1; } @@ -324,7 +324,7 @@ void VoyeurEngine::doClosingCredits() { fi._justifyHeight = 240; fi._pos = Common::Point(0, READ_LE_UINT16(entry)); - _graphicsManager->_vPort->drawText(msg); + _screen->_vPort->drawText(msg); msg += strlen(msg) + 1; } @@ -336,7 +336,7 @@ void VoyeurEngine::doClosingCredits() { fi._justifyHeight = 240; fi._pos = Common::Point(38, READ_LE_UINT16(entry)); - _graphicsManager->_vPort->drawText(msg); + _screen->_vPort->drawText(msg); msg += strlen(msg) + 1; fi._foreColor = 2; @@ -345,7 +345,7 @@ void VoyeurEngine::doClosingCredits() { fi._justifyHeight = 240; fi._pos = Common::Point(198, READ_LE_UINT16(entry)); - _graphicsManager->_vPort->drawText(msg); + _screen->_vPort->drawText(msg); msg += strlen(msg) + 1; } @@ -357,7 +357,7 @@ void VoyeurEngine::doClosingCredits() { fi._justifyHeight = 240; fi._pos = Common::Point(0, READ_LE_UINT16(entry)); - _graphicsManager->_vPort->drawText(msg); + _screen->_vPort->drawText(msg); msg += strlen(msg) + 1; fi._foreColor = 2; @@ -367,7 +367,7 @@ void VoyeurEngine::doClosingCredits() { fi._justifyHeight = 240; fi._pos = Common::Point(0, READ_LE_UINT16(entry) + 13); - _graphicsManager->_vPort->drawText(msg); + _screen->_vPort->drawText(msg); msg += strlen(msg) + 1; } @@ -381,19 +381,19 @@ void VoyeurEngine::doClosingCredits() { } _soundManager->stopVOCPlay(); - _graphicsManager->_fontPtr->_curFont = _bVoy->boltEntry(0x101)._fontResource; + _screen->_fontPtr->_curFont = _bVoy->boltEntry(0x101)._fontResource; _bVoy->freeBoltGroup(0x400); } void VoyeurEngine::doPiracy() { - _graphicsManager->screenReset(); - _graphicsManager->setColor(1, 0, 0, 0); - _graphicsManager->setColor(2, 255, 255, 255); + _screen->screenReset(); + _screen->setColor(1, 0, 0, 0); + _screen->setColor(2, 255, 255, 255); _eventsManager->_intPtr._hasPalette = true; - _graphicsManager->_vPort->setupViewPort(NULL); - _graphicsManager->_vPort->fillPic(1); + _screen->_vPort->setupViewPort(NULL); + _screen->_vPort->fillPic(1); - FontInfoResource &fi = *_graphicsManager->_fontPtr; + FontInfoResource &fi = *_screen->_fontPtr; fi._curFont = _bVoy->boltEntry(0x101)._fontResource; fi._foreColor = 2; fi._backColor = 2; @@ -406,7 +406,7 @@ void VoyeurEngine::doPiracy() { // Loop through the piracy message array to draw each line for (int idx = 0, yp = 33; idx < 10; ++idx) { fi._pos = Common::Point(0, yp); - _graphicsManager->_vPort->drawText(PIRACY_MESSAGE[idx]); + _screen->_vPort->drawText(PIRACY_MESSAGE[idx]); yp += fi._curFont->_fontHeight + 4; } @@ -439,27 +439,27 @@ void VoyeurEngine::reviewTape() { _voy->_viewBounds = _bVoy->boltEntry(0x907)._rectResource; Common::Array<RectEntry> &hotspots = _bVoy->boltEntry(0x906)._rectResource->_entries; - _graphicsManager->_backColors = _bVoy->boltEntry(0x902)._cMapResource; - _graphicsManager->_backgroundPage = _bVoy->boltEntry(0x901)._picResource; - _graphicsManager->_vPort->setupViewPort(_graphicsManager->_backgroundPage); - _graphicsManager->_backColors->startFade(); + _screen->_backColors = _bVoy->boltEntry(0x902)._cMapResource; + _screen->_backgroundPage = _bVoy->boltEntry(0x901)._picResource; + _screen->_vPort->setupViewPort(_screen->_backgroundPage); + _screen->_backColors->startFade(); flipPageAndWaitForFade(); - _graphicsManager->setColor(1, 32, 32, 32); - _graphicsManager->setColor(2, 96, 96, 96); - _graphicsManager->setColor(3, 160, 160, 160); - _graphicsManager->setColor(4, 224, 224, 224); - _graphicsManager->setColor(9, 24, 64, 24); - _graphicsManager->setColor(10, 64, 132, 64); - _graphicsManager->setColor(11, 100, 192, 100); - _graphicsManager->setColor(12, 120, 248, 120); + _screen->setColor(1, 32, 32, 32); + _screen->setColor(2, 96, 96, 96); + _screen->setColor(3, 160, 160, 160); + _screen->setColor(4, 224, 224, 224); + _screen->setColor(9, 24, 64, 24); + _screen->setColor(10, 64, 132, 64); + _screen->setColor(11, 100, 192, 100); + _screen->setColor(12, 120, 248, 120); _eventsManager->setCursorColor(128, 1); _eventsManager->_intPtr._hasPalette = true; - _graphicsManager->_fontPtr->_curFont = _bVoy->boltEntry(0x909)._fontResource; - _graphicsManager->_fontPtr->_fontSaveBack = false; - _graphicsManager->_fontPtr->_fontFlags = DISPFLAG_NONE; + _screen->_fontPtr->_curFont = _bVoy->boltEntry(0x909)._fontResource; + _screen->_fontPtr->_fontSaveBack = false; + _screen->_fontPtr->_fontFlags = DISPFLAG_NONE; _eventsManager->getMouseInfo(); if (newX == -1) { @@ -481,37 +481,37 @@ void VoyeurEngine::reviewTape() { needRedraw = false; flipPageAndWait(); - _graphicsManager->_drawPtr->_penColor = 0; - _graphicsManager->_drawPtr->_pos = Common::Point(tempRect.left, tempRect.top); - _graphicsManager->_backgroundPage->sFillBox(tempRect.width(), tempRect.height()); + _screen->_drawPtr->_penColor = 0; + _screen->_drawPtr->_pos = Common::Point(tempRect.left, tempRect.top); + _screen->_backgroundPage->sFillBox(tempRect.width(), tempRect.height()); int yp = 45; int eventNum = eventStart; for (int lineNum = 0; lineNum < 8 && eventNum < _voy->_eventCount; ++lineNum, ++eventNum) { - _graphicsManager->_fontPtr->_picFlags = DISPFLAG_NONE; - _graphicsManager->_fontPtr->_picSelect = 0xff; - _graphicsManager->_fontPtr->_picPick = 7; - _graphicsManager->_fontPtr->_picOnOff = (lineNum == eventLine) ? 8 : 0; - _graphicsManager->_fontPtr->_pos = Common::Point(68, yp); - _graphicsManager->_fontPtr->_justify = ALIGN_LEFT; - _graphicsManager->_fontPtr->_justifyWidth = 0; - _graphicsManager->_fontPtr->_justifyHeight = 0; + _screen->_fontPtr->_picFlags = DISPFLAG_NONE; + _screen->_fontPtr->_picSelect = 0xff; + _screen->_fontPtr->_picPick = 7; + _screen->_fontPtr->_picOnOff = (lineNum == eventLine) ? 8 : 0; + _screen->_fontPtr->_pos = Common::Point(68, yp); + _screen->_fontPtr->_justify = ALIGN_LEFT; + _screen->_fontPtr->_justifyWidth = 0; + _screen->_fontPtr->_justifyHeight = 0; Common::String msg = _eventsManager->getEvidString(eventNum); - _graphicsManager->_backgroundPage->drawText(msg); + _screen->_backgroundPage->drawText(msg); yp += 15; } - _graphicsManager->_vPort->addSaveRect( - _graphicsManager->_vPort->_lastPage, tempRect); + _screen->_vPort->addSaveRect( + _screen->_vPort->_lastPage, tempRect); flipPageAndWait(); - _graphicsManager->_vPort->addSaveRect( - _graphicsManager->_vPort->_lastPage, tempRect); + _screen->_vPort->addSaveRect( + _screen->_vPort->_lastPage, tempRect); } - _graphicsManager->sDrawPic(cursor, _graphicsManager->_vPort, + _screen->sDrawPic(cursor, _screen->_vPort, _eventsManager->getMousePos()); flipPageAndWait(); @@ -543,34 +543,34 @@ void VoyeurEngine::reviewTape() { flipPageAndWait(); - _graphicsManager->_drawPtr->_penColor = 0; - _graphicsManager->_drawPtr->_pos = Common::Point(tempRect.left, tempRect.top); - _graphicsManager->_backgroundPage->sFillBox(tempRect.width(), tempRect.height()); + _screen->_drawPtr->_penColor = 0; + _screen->_drawPtr->_pos = Common::Point(tempRect.left, tempRect.top); + _screen->_backgroundPage->sFillBox(tempRect.width(), tempRect.height()); int yp = 45; int eventNum = eventStart; for (int idx = 0; idx < 8 && eventNum < _voy->_eventCount; ++idx, ++eventNum) { - _graphicsManager->_fontPtr->_picFlags = DISPFLAG_NONE; - _graphicsManager->_fontPtr->_picSelect = 0xff; - _graphicsManager->_fontPtr->_picPick = 7; - _graphicsManager->_fontPtr->_picOnOff = (idx == eventLine) ? 8 : 0; - _graphicsManager->_fontPtr->_pos = Common::Point(68, yp); - _graphicsManager->_fontPtr->_justify = ALIGN_LEFT; - _graphicsManager->_fontPtr->_justifyWidth = 0; - _graphicsManager->_fontPtr->_justifyHeight = 0; + _screen->_fontPtr->_picFlags = DISPFLAG_NONE; + _screen->_fontPtr->_picSelect = 0xff; + _screen->_fontPtr->_picPick = 7; + _screen->_fontPtr->_picOnOff = (idx == eventLine) ? 8 : 0; + _screen->_fontPtr->_pos = Common::Point(68, yp); + _screen->_fontPtr->_justify = ALIGN_LEFT; + _screen->_fontPtr->_justifyWidth = 0; + _screen->_fontPtr->_justifyHeight = 0; Common::String msg = _eventsManager->getEvidString(eventNum); - _graphicsManager->_backgroundPage->drawText(msg); + _screen->_backgroundPage->drawText(msg); yp += 15; } - _graphicsManager->_vPort->addSaveRect( - _graphicsManager->_vPort->_lastPage, tempRect); + _screen->_vPort->addSaveRect( + _screen->_vPort->_lastPage, tempRect); flipPageAndWait(); - _graphicsManager->_vPort->addSaveRect( - _graphicsManager->_vPort->_lastPage, tempRect); + _screen->_vPort->addSaveRect( + _screen->_vPort->_lastPage, tempRect); flipPageAndWait(); _eventsManager->getMouseInfo(); @@ -650,7 +650,7 @@ void VoyeurEngine::reviewTape() { newY = _eventsManager->getMousePos().y; _voy->_fadingType = 0; _voy->_viewBounds = nullptr; - _graphicsManager->_vPort->setupViewPort(NULL); + _screen->_vPort->setupViewPort(NULL); if (_currentVocId != -1) { _voy->_vocSecondsOffset = _voy->_RTVNum - _voy->_musicStartTime; @@ -673,13 +673,13 @@ void VoyeurEngine::reviewTape() { _voy->_vocSecondsOffset = e._computerOn; _bVoy->getBoltGroup(0x7F00); - _graphicsManager->_backgroundPage = _bVoy->boltEntry(0x7F00 + + _screen->_backgroundPage = _bVoy->boltEntry(0x7F00 + BLIND_TABLE[_audioVideoId])._picResource; - _graphicsManager->_backColors = _bVoy->boltEntry(0x7F01 + + _screen->_backColors = _bVoy->boltEntry(0x7F01 + BLIND_TABLE[_audioVideoId])._cMapResource; - _graphicsManager->_vPort->setupViewPort(_graphicsManager->_backgroundPage); - _graphicsManager->_backColors->startFade(); + _screen->_vPort->setupViewPort(_screen->_backgroundPage); + _screen->_backColors->startFade(); flipPageAndWaitForFade(); _eventsManager->_intPtr._flashStep = 1; @@ -725,16 +725,16 @@ void VoyeurEngine::reviewTape() { } } - _graphicsManager->_fontPtr->_curFont = _bVoy->boltEntry(0x101)._fontResource; + _screen->_fontPtr->_curFont = _bVoy->boltEntry(0x101)._fontResource; - _graphicsManager->_vPort->fillPic(0); + _screen->_vPort->fillPic(0); flipPageAndWait(); _bVoy->freeBoltGroup(0x900); } void VoyeurEngine::doGossip() { - _graphicsManager->resetPalette(); - _graphicsManager->screenReset(); + _screen->resetPalette(); + _screen->screenReset(); if (!_bVoy->getBoltGroup(0x300)) return; @@ -752,7 +752,7 @@ void VoyeurEngine::doGossip() { // Transfer initial background to video decoder PictureResource videoFrame(decoder.getRL2VideoTrack()->getBackSurface()); bgPic->_bounds.moveTo(0, 0); - _graphicsManager->sDrawPic(bgPic, &videoFrame, Common::Point(0, 0)); + _screen->sDrawPic(bgPic, &videoFrame, Common::Point(0, 0)); byte *frameNumsP = _bVoy->memberAddr(0x309); byte *posP = _bVoy->boltEntry(0x30A)._data; @@ -762,8 +762,8 @@ void VoyeurEngine::doGossip() { decoder.close(); // Reset the palette and clear the screen - _graphicsManager->resetPalette(); - _graphicsManager->screenReset(); + _screen->resetPalette(); + _screen->screenReset(); // Play interview video RL2Decoder decoder2; @@ -775,7 +775,7 @@ void VoyeurEngine::doGossip() { decoder2.close(); _bVoy->freeBoltGroup(0x300); - _graphicsManager->screenReset(); + _screen->screenReset(); } void VoyeurEngine::doTapePlaying() { @@ -783,14 +783,14 @@ void VoyeurEngine::doTapePlaying() { return; _eventsManager->getMouseInfo(); - _graphicsManager->_backColors = _bVoy->boltEntry(0xA01)._cMapResource; - _graphicsManager->_backgroundPage = _bVoy->boltEntry(0xA00)._picResource; + _screen->_backColors = _bVoy->boltEntry(0xA01)._cMapResource; + _screen->_backgroundPage = _bVoy->boltEntry(0xA00)._picResource; PictureResource *pic = _bVoy->boltEntry(0xA02)._picResource; VInitCycleResource *cycle = _bVoy->boltEntry(0xA05)._vInitCycleResource; - _graphicsManager->_vPort->setupViewPort(_graphicsManager->_backgroundPage); - _graphicsManager->sDrawPic(pic, _graphicsManager->_vPort, Common::Point(57, 30)); - _graphicsManager->_backColors->startFade(); + _screen->_vPort->setupViewPort(_screen->_backgroundPage); + _screen->sDrawPic(pic, _screen->_vPort, Common::Point(57, 30)); + _screen->_backColors->startFade(); flipPageAndWaitForFade(); cycle->vStartCycle(); @@ -932,9 +932,9 @@ int VoyeurEngine::getChooseButton() { + 6)._rectResource->_entries; int selectedIndex = -1; - _graphicsManager->_vPort->setupViewPort(_graphicsManager->_backgroundPage); - _graphicsManager->_backColors->_steps = 0; - _graphicsManager->_backColors->startFade(); + _screen->_vPort->setupViewPort(_screen->_backgroundPage); + _screen->_backColors->_steps = 0; + _screen->_backColors->startFade(); flipPageAndWait(); _voy->_viewBounds = _bVoy->boltEntry(_playStampGroupId + 7)._rectResource; @@ -955,7 +955,7 @@ int VoyeurEngine::getChooseButton() { selectedIndex = idx; if (selectedIndex != prevIndex) { PictureResource *btnPic = _bVoy->boltEntry(_playStampGroupId + 8 + idx)._picResource; - _graphicsManager->sDrawPic(btnPic, _graphicsManager->_vPort, + _screen->sDrawPic(btnPic, _screen->_vPort, Common::Point(106, 200)); cursorPic = _bVoy->boltEntry(_playStampGroupId + 4)._picResource; @@ -967,11 +967,11 @@ int VoyeurEngine::getChooseButton() { if (selectedIndex == -1) { cursorPic = _bVoy->boltEntry(_playStampGroupId + 2)._picResource; PictureResource *btnPic = _bVoy->boltEntry(_playStampGroupId + 12)._picResource; - _graphicsManager->sDrawPic(btnPic, _graphicsManager->_vPort, + _screen->sDrawPic(btnPic, _screen->_vPort, Common::Point(106, 200)); } - _graphicsManager->sDrawPic(cursorPic, _graphicsManager->_vPort, + _screen->sDrawPic(cursorPic, _screen->_vPort, Common::Point(pt.x + 13, pt.y - 12)); flipPageAndWait(); @@ -982,9 +982,9 @@ int VoyeurEngine::getChooseButton() { } void VoyeurEngine::makeViewFinder() { - _graphicsManager->_backgroundPage = _bVoy->boltEntry(0x103)._picResource; - _graphicsManager->sDrawPic(_graphicsManager->_backgroundPage, - _graphicsManager->_vPort, Common::Point(0, 0)); + _screen->_backgroundPage = _bVoy->boltEntry(0x103)._picResource; + _screen->sDrawPic(_screen->_backgroundPage, + _screen->_vPort, Common::Point(0, 0)); CMapResource *pal = _bVoy->boltEntry(0x104)._cMapResource; int palOffset = 0; @@ -1016,22 +1016,22 @@ void VoyeurEngine::makeViewFinder() { break; } - _graphicsManager->_vPort->drawIfaceTime(); + _screen->_vPort->drawIfaceTime(); doTimeBar(); pal->startFade(); flipPageAndWaitForFade(); - _graphicsManager->setColor(241, 105, 105, 105); - _graphicsManager->setColor(242, 105, 105, 105); - _graphicsManager->setColor(243, 105, 105, 105); - _graphicsManager->setColor(palOffset + 241, 219, 235, 235); + _screen->setColor(241, 105, 105, 105); + _screen->setColor(242, 105, 105, 105); + _screen->setColor(243, 105, 105, 105); + _screen->setColor(palOffset + 241, 219, 235, 235); _eventsManager->_intPtr._hasPalette = true; } void VoyeurEngine::makeViewFinderP() { - _graphicsManager->screenReset(); + _screen->screenReset(); } void VoyeurEngine::initIFace() { @@ -1077,7 +1077,7 @@ void VoyeurEngine::initIFace() { void VoyeurEngine::doScroll(const Common::Point &pt) { Common::Rect clipRect(72, 47, 72 + 240, 47 + 148); - _graphicsManager->_vPort->setupViewPort(NULL, &clipRect); + _screen->_vPort->setupViewPort(NULL, &clipRect); int base = 0; switch (_voy->_transitionId) { @@ -1101,18 +1101,18 @@ void VoyeurEngine::doScroll(const Common::Point &pt) { if (base) { PictureResource *pic = _bVoy->boltEntry(base + 3)._picResource; - _graphicsManager->sDrawPic(pic, _graphicsManager->_vPort, Common::Point(784 - pt.x - 712, 150 - pt.y - 104)); + _screen->sDrawPic(pic, _screen->_vPort, Common::Point(784 - pt.x - 712, 150 - pt.y - 104)); pic = _bVoy->boltEntry(base + 4)._picResource; - _graphicsManager->sDrawPic(pic, _graphicsManager->_vPort, Common::Point(784 - pt.x - 712, 150 - pt.y - 44)); + _screen->sDrawPic(pic, _screen->_vPort, Common::Point(784 - pt.x - 712, 150 - pt.y - 44)); pic = _bVoy->boltEntry(base + 5)._picResource; - _graphicsManager->sDrawPic(pic, _graphicsManager->_vPort, Common::Point(784 - pt.x - 712, 150 - pt.y + 16)); + _screen->sDrawPic(pic, _screen->_vPort, Common::Point(784 - pt.x - 712, 150 - pt.y + 16)); pic = _bVoy->boltEntry(base + 6)._picResource; - _graphicsManager->sDrawPic(pic, _graphicsManager->_vPort, Common::Point(784 - pt.x - 712, 150 - pt.y + 76)); + _screen->sDrawPic(pic, _screen->_vPort, Common::Point(784 - pt.x - 712, 150 - pt.y + 76)); pic = _bVoy->boltEntry(base + 7)._picResource; - _graphicsManager->sDrawPic(pic, _graphicsManager->_vPort, Common::Point(784 - pt.x - 712, 150 - pt.y + 136)); + _screen->sDrawPic(pic, _screen->_vPort, Common::Point(784 - pt.x - 712, 150 - pt.y + 136)); } - _graphicsManager->_vPort->setupViewPort(NULL); + _screen->_vPort->setupViewPort(NULL); } void VoyeurEngine::checkTransition() { @@ -1124,7 +1124,7 @@ void VoyeurEngine::checkTransition() { // Only proceed if a valid day string was returned if (!day.empty()) { - _graphicsManager->fadeDownICF(6); + _screen->fadeDownICF(6); // Get the time of day string time = getTimeOfDay(); @@ -1163,7 +1163,7 @@ Common::String VoyeurEngine::getTimeOfDay() { } int VoyeurEngine::doComputerText(int maxLen) { - FontInfoResource &font = *_graphicsManager->_fontPtr; + FontInfoResource &font = *_screen->_fontPtr; int totalChars = 0; font._curFont = _bVoy->boltEntry(0x4910)._fontResource; @@ -1180,7 +1180,7 @@ int VoyeurEngine::doComputerText(int maxLen) { font._justifyWidth = 384; font._justifyHeight = 100; font._pos = Common::Point(128, 100); - _graphicsManager->_vPort->drawText(END_OF_MESSAGE); + _screen->_vPort->drawText(END_OF_MESSAGE); } else if (_voy->_RTVNum < _voy->_computerTimeMin && maxLen == 9999) { if (_currentVocId != -1) _soundManager->startVOCPlay(_currentVocId); @@ -1188,7 +1188,7 @@ int VoyeurEngine::doComputerText(int maxLen) { font._justifyWidth = 384; font._justifyHeight = 100; font._pos = Common::Point(120, 100); - _graphicsManager->_vPort->drawText(START_OF_MESSAGE); + _screen->_vPort->drawText(START_OF_MESSAGE); } else { char *msg = (char *)_bVoy->memberAddr(0x4900 + _voy->_computerTextId); font._pos = Common::Point(96, 60); @@ -1206,14 +1206,14 @@ int VoyeurEngine::doComputerText(int maxLen) { if (c == '\0') { if (showEnd) { _eventsManager->delay(90); - _graphicsManager->_drawPtr->_pos = Common::Point(96, 54); - _graphicsManager->_drawPtr->_penColor = 254; - _graphicsManager->_vPort->sFillBox(196, 124); - _graphicsManager->_fontPtr->_justify = ALIGN_LEFT; - _graphicsManager->_fontPtr->_justifyWidth = 384; - _graphicsManager->_fontPtr->_justifyHeight = 100; - _graphicsManager->_fontPtr->_pos = Common::Point(128, 100); - _graphicsManager->_vPort->drawText(END_OF_MESSAGE); + _screen->_drawPtr->_pos = Common::Point(96, 54); + _screen->_drawPtr->_penColor = 254; + _screen->_vPort->sFillBox(196, 124); + _screen->_fontPtr->_justify = ALIGN_LEFT; + _screen->_fontPtr->_justifyWidth = 384; + _screen->_fontPtr->_justifyHeight = 100; + _screen->_fontPtr->_pos = Common::Point(128, 100); + _screen->_vPort->drawText(END_OF_MESSAGE); } break; } @@ -1223,20 +1223,20 @@ int VoyeurEngine::doComputerText(int maxLen) { yp += 10; } else { _eventsManager->delay(90); - _graphicsManager->_drawPtr->_pos = Common::Point(96, 54); - _graphicsManager->_drawPtr->_penColor = 255; - _graphicsManager->_vPort->sFillBox(196, 124); + _screen->_drawPtr->_pos = Common::Point(96, 54); + _screen->_drawPtr->_penColor = 255; + _screen->_vPort->sFillBox(196, 124); yp = 60; } - _graphicsManager->_fontPtr->_pos = Common::Point(96, yp); + _screen->_fontPtr->_pos = Common::Point(96, yp); } else if (c == '_') { showEnd = false; } else { - _graphicsManager->_fontPtr->_justify = ALIGN_LEFT; - _graphicsManager->_fontPtr->_justifyWidth = 0; - _graphicsManager->_fontPtr->_justifyHeight = 0; - _graphicsManager->_vPort->drawText(Common::String(c)); + _screen->_fontPtr->_justify = ALIGN_LEFT; + _screen->_fontPtr->_justifyWidth = 0; + _screen->_fontPtr->_justifyHeight = 0; + _screen->_vPort->drawText(Common::String(c)); _eventsManager->delay(4); } @@ -1251,7 +1251,7 @@ int VoyeurEngine::doComputerText(int maxLen) { flipPageAndWait(); - _graphicsManager->_fontPtr->_curFont = _bVoy->boltEntry(0x101)._fontResource; + _screen->_fontPtr->_curFont = _bVoy->boltEntry(0x101)._fontResource; return totalChars; } @@ -1263,7 +1263,7 @@ void VoyeurEngine::getComputerBrush() { int xp = (384 - pic->_bounds.width()) / 2; int yp = (240 - pic->_bounds.height()) / 2 - 4; - _graphicsManager->_vPort->drawPicPerm(pic, Common::Point(xp, yp)); + _screen->_vPort->drawPicPerm(pic, Common::Point(xp, yp)); CMapResource *pal = _bVoy->boltEntry(0x490F)._cMapResource; pal->startFade(); @@ -1280,17 +1280,17 @@ void VoyeurEngine::doTimeBar() { int height = ((_voy->_RTVLimit - _voy->_RTVNum) * 59) / _voy->_RTVLimit; int fullHeight = MAX(151 - height, 93); - _graphicsManager->_drawPtr->_penColor = 134; - _graphicsManager->_drawPtr->_pos = Common::Point(39, 92); + _screen->_drawPtr->_penColor = 134; + _screen->_drawPtr->_pos = Common::Point(39, 92); - _graphicsManager->_vPort->sFillBox(6, fullHeight - 92); + _screen->_vPort->sFillBox(6, fullHeight - 92); if (height > 0) { - _graphicsManager->setColor(215, 238, 238, 238); + _screen->setColor(215, 238, 238, 238); _eventsManager->_intPtr._hasPalette = true; - _graphicsManager->_drawPtr->_penColor = 215; - _graphicsManager->_drawPtr->_pos = Common::Point(39, fullHeight); - _graphicsManager->_vPort->sFillBox(6, height); + _screen->_drawPtr->_penColor = 215; + _screen->_drawPtr->_pos = Common::Point(39, fullHeight); + _screen->_vPort->sFillBox(6, height); } } } @@ -1303,9 +1303,9 @@ void VoyeurEngine::flashTimeBar() { _flashTimeVal = _eventsManager->_intPtr._flashTimer; if (_flashTimeFlag) - _graphicsManager->setColor(240, 220, 20, 20); + _screen->setColor(240, 220, 20, 20); else - _graphicsManager->setColor(240, 220, 220, 220); + _screen->setColor(240, 220, 220, 220); _eventsManager->_intPtr._hasPalette = true; _flashTimeFlag = !_flashTimeFlag; @@ -1343,7 +1343,7 @@ void VoyeurEngine::doEvidDisplay(int evidId, int eventId) { _bVoy->getBoltGroup(_voy->_boltGroupId2); PictureResource *pic = _bVoy->boltEntry(_voy->_boltGroupId2 + evidId * 2)._picResource; - _graphicsManager->sDrawPic(pic, _graphicsManager->_vPort, Common::Point( + _screen->sDrawPic(pic, _screen->_vPort, Common::Point( (384 - pic->_bounds.width()) / 2, (240 - pic->_bounds.height()) / 2)); _bVoy->freeBoltMember(_voy->_boltGroupId2 + evidId * 2); @@ -1394,7 +1394,7 @@ void VoyeurEngine::doEvidDisplay(int evidId, int eventId) { continue; pic = _voy->_evPicPtrs[arrIndex]; - _graphicsManager->sDrawPic(pic, _graphicsManager->_vPort, + _screen->sDrawPic(pic, _screen->_vPort, Common::Point((384 - pic->_bounds.width()) / 2, (240 - pic->_bounds.height()) / 2)); _voy->_evCmPtrs[arrIndex]->startFade(); diff --git a/engines/wage/combat.cpp b/engines/wage/combat.cpp new file mode 100644 index 0000000000..4f2956d7d2 --- /dev/null +++ b/engines/wage/combat.cpp @@ -0,0 +1,916 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "wage/wage.h" +#include "wage/entities.h" +#include "wage/randomhat.h" +#include "wage/world.h" + +namespace Wage { + +Obj *WageEngine::getOffer() { + if (_offer != NULL) { + Chr *owner = _offer->_currentOwner; + if (owner == NULL || owner->_playerCharacter || owner->_currentScene != _world->_player->_currentScene) { + _offer = NULL; + } + } + return _offer; +} + +Chr *WageEngine::getMonster() { + if (_monster != NULL && _monster->_currentScene != _world->_player->_currentScene) { + _monster = NULL; + } + return _monster; +} + +void WageEngine::encounter(Chr *player, Chr *chr) { + char buf[512]; + + snprintf(buf, 512, "You encounter %s%s.", chr->_nameProperNoun ? "" : getIndefiniteArticle(chr->_name), + chr->_name.c_str()); + appendText(buf); + + if (!chr->_initialComment.empty()) + appendText(chr->_initialComment.c_str()); + + if (chr->_armor[Chr::HEAD_ARMOR] != NULL) { + snprintf(buf, 512, "%s%s is wearing %s.", chr->getDefiniteArticle(true), chr->_name.c_str(), + getIndefiniteArticle(chr->_armor[Chr::HEAD_ARMOR]->_name)); + appendText(buf); + } + if (chr->_armor[Chr::BODY_ARMOR] != NULL) { + snprintf(buf, 512, "%s is protected by %s%s.", getGenderSpecificPronoun(chr->_gender, true), + prependGenderSpecificPronoun(chr->_gender), chr->_armor[Chr::BODY_ARMOR]->_name.c_str()); + appendText(buf); + } + if (chr->_armor[Chr::SHIELD_ARMOR] != NULL) { + Obj *obj = chr->_armor[Chr::SHIELD_ARMOR]; + + snprintf(buf, 512, "%s carries %s%s.", getGenderSpecificPronoun(chr->_gender, true), + obj->_namePlural ? "" : getIndefiniteArticle(obj->_name), obj->_name.c_str()); + appendText(buf); + } +} + +void WageEngine::performCombatAction(Chr *npc, Chr *player) { + if (npc->_context._frozen) + return; + + RandomHat hat(_rnd); + + bool winning = (npc->_context._statVariables[PHYS_HIT_CUR] > player->_context._statVariables[PHYS_HIT_CUR]); + int validMoves = getValidMoveDirections(npc); + ObjArray *weapons = npc->getWeapons(false); + ObjArray *magics = npc->getMagicalObjects(); + // TODO: Figure out under what circumstances we need to add +1 + // for the chance (e.g. only when all values were set to 0?). + if (winning) { + if (!_world->_weaponMenuDisabled) { + if (!weapons->empty()) + hat.addTokens(kTokWeapons, npc->_winningWeapons + 1); + if (!magics->empty()) + hat.addTokens(kTokMagic, npc->_winningMagic); + } + if (validMoves != 0) + hat.addTokens(kTokRun, npc->_winningRun + 1); + if (!npc->_inventory.empty()) + hat.addTokens(kTokOffer, npc->_winningOffer + 1); + } else { + if (!_world->_weaponMenuDisabled) { + if (!weapons->empty()) + hat.addTokens(kTokWeapons, npc->_losingWeapons + 1); + if (!magics->empty()) + hat.addTokens(kTokMagic, npc->_losingMagic); + } + if (validMoves != 0) + hat.addTokens(kTokRun, npc->_losingRun + 1); + if (!npc->_inventory.empty()) + hat.addTokens(kTokOffer, npc->_losingOffer + 1); + } + + ObjList *objs = &npc->_currentScene->_objs; + if (npc->_inventory.size() < npc->_maximumCarriedObjects) { + int cnt = 0; + for (ObjList::const_iterator it = objs->begin(); it != objs->end(); ++it, ++cnt) { + if ((*it)->_type != Obj::IMMOBILE_OBJECT) { + // TODO: I'm not sure what the chance should be here. + hat.addTokens(cnt, 123); + } + } + } + + int token = hat.drawToken(); + switch (token) { + case kTokWeapons: + // TODO: I think the monster should choose the "best" weapon. + performAttack(npc, player, weapons->operator[](_rnd->getRandomNumber(weapons->size() - 1))); + break; + case kTokMagic: + // TODO: I think the monster should choose the "best" magic. + performMagic(npc, player, magics->operator[](_rnd->getRandomNumber(magics->size() - 1))); + break; + case kTokRun: + performMove(npc, validMoves); + break; + case kTokOffer: + performOffer(npc, player); + break; + case kTokNone: + break; + default: + { + int cnt = 0; + for (ObjList::const_iterator it = objs->begin(); it != objs->end(); ++it, ++cnt) + if (cnt == token) + performTake(npc, *it); + break; + } + } + + delete weapons; + delete magics; +} + +static const char *const targets[] = { "head", "chest", "side" }; + +void WageEngine::performAttack(Chr *attacker, Chr *victim, Obj *weapon) { + if (_world->_weaponMenuDisabled) + return; + + // TODO: verify that a player not aiming will always target the chest?? + int targetIndex = -1; + char buf[256]; + + if (weapon->_type != Obj::MAGICAL_OBJECT) { + if (attacker->_playerCharacter) { + targetIndex = _aim; + } else { + targetIndex = _rnd->getRandomNumber(ARRAYSIZE(targets) - 1); + _opponentAim = targetIndex + 1; + } + + if (!attacker->_playerCharacter) { + snprintf(buf, 256, "%s%s %ss %s%s at %s%s's %s.", + attacker->getDefiniteArticle(true), attacker->_name.c_str(), + weapon->_operativeVerb.c_str(), + prependGenderSpecificPronoun(attacker->_gender), weapon->_name.c_str(), + victim->getDefiniteArticle(true), victim->_name.c_str(), + targets[targetIndex]); + appendText(buf); + } + } else if (!attacker->_playerCharacter) { + snprintf(buf, 256, "%s%s %ss %s%s at %s%s.", + attacker->getDefiniteArticle(true), attacker->_name.c_str(), + weapon->_operativeVerb.c_str(), + prependGenderSpecificPronoun(attacker->_gender), weapon->_name.c_str(), + victim->getDefiniteArticle(true), victim->_name.c_str()); + appendText(buf); + } + + playSound(weapon->_sound); + + bool usesDecremented = false; + int chance = _rnd->getRandomNumber(255); + // TODO: what about obj accuracy + if (chance < attacker->_physicalAccuracy) { + usesDecremented = attackHit(attacker, victim, weapon, targetIndex); + } else if (weapon->_type != Obj::MAGICAL_OBJECT) { + appendText("A miss!"); + } else if (attacker->_playerCharacter) { + appendText("The spell has no effect."); + } + + if (!usesDecremented) { + decrementUses(weapon); + } +} + +void WageEngine::decrementUses(Obj *obj) { + int numberOfUses = obj->_numberOfUses; + if (numberOfUses != -1) { + numberOfUses--; + if (numberOfUses > 0) { + obj->_numberOfUses = numberOfUses; + } else { + if (!obj->_failureMessage.empty()) { + appendText(obj->_failureMessage.c_str()); + } + if (obj->_returnToRandomScene) { + _world->move(obj, _world->getRandomScene()); + } else { + _world->move(obj, _world->_storageScene); + } + obj->resetState(obj->_currentOwner, obj->_currentScene); + } + } +} + +bool WageEngine::attackHit(Chr *attacker, Chr *victim, Obj *weapon, int targetIndex) { + bool receivedHitTextPrinted = false; + char buf[512]; + + if (targetIndex != -1) { + Obj *armor = victim->_armor[targetIndex]; + if (armor != NULL) { + // TODO: Absorb some damage. + snprintf(buf, 512, "%s%s's %s weakens the impact of %s%s's %s.", + victim->getDefiniteArticle(true), victim->_name.c_str(), + victim->_armor[targetIndex]->_name.c_str(), + attacker->getDefiniteArticle(false), attacker->_name.c_str(), + weapon->_name.c_str()); + appendText(buf); + decrementUses(armor); + } else { + snprintf(buf, 512, "A hit to the %s!", targets[targetIndex]); + appendText(buf); + } + playSound(attacker->_scoresHitSound); + appendText(attacker->_scoresHitComment.c_str()); + playSound(victim->_receivesHitSound); + appendText(victim->_receivesHitComment.c_str()); + receivedHitTextPrinted = true; + } else if (weapon->_type == Obj::MAGICAL_OBJECT) { + appendText(weapon->_useMessage.c_str()); + appendText("The spell is effective!"); + } + + bool causesPhysicalDamage = true; + bool causesSpiritualDamage = false; + bool freezesOpponent = false; + bool usesDecremented = false; + + if (weapon->_type == Obj::THROW_WEAPON) { + _world->move(weapon, victim->_currentScene); + } else if (weapon->_type == Obj::MAGICAL_OBJECT) { + int type = weapon->_attackType; + causesPhysicalDamage = (type == Obj::CAUSES_PHYSICAL_DAMAGE || type == Obj::CAUSES_PHYSICAL_AND_SPIRITUAL_DAMAGE); + causesSpiritualDamage = (type == Obj::CAUSES_SPIRITUAL_DAMAGE || type == Obj::CAUSES_PHYSICAL_AND_SPIRITUAL_DAMAGE); + freezesOpponent = (type == Obj::FREEZES_OPPONENT); + } + + if (causesPhysicalDamage) { + victim->_context._userVariables[PHYS_HIT_CUR] -= weapon->_damage; + + /* Do it here to get the right order of messages in case of death. */ + decrementUses(weapon); + usesDecremented = true; + + if (victim->_context._userVariables[PHYS_HIT_CUR] < 0) { + playSound(victim->_dyingSound); + appendText(victim->_dyingWords.c_str()); + snprintf(buf, 512, "%s%s is dead!", victim->getDefiniteArticle(true), victim->_name.c_str()); + appendText(buf); + + attacker->_context._kills++; + attacker->_context._experience += victim->_context._userVariables[SPIR_HIT_CUR] + victim->_context._userVariables[PHYS_HIT_CUR]; + + if (!victim->_playerCharacter && !victim->_inventory.empty()) { + Scene *currentScene = victim->_currentScene; + + for (int i = victim->_inventory.size() - 1; i >= 0; i--) { + _world->move(victim->_inventory[i], currentScene); + } + Common::String *s = getGroundItemsList(currentScene); + appendText(s->c_str()); + delete s; + } + _world->move(victim, _world->_storageScene); + } else if (attacker->_playerCharacter && !receivedHitTextPrinted) { + double physicalPercent = (double)victim->_context._userVariables[SPIR_HIT_CUR] / + victim->_context._userVariables[SPIR_HIT_BAS]; + snprintf(buf, 512, "%s%s's condition appears to be %s.", + victim->getDefiniteArticle(true), victim->_name.c_str(), + getPercentMessage(physicalPercent)); + appendText(buf); + } + } + + if (causesSpiritualDamage) { + /* TODO */ + warning("TODO: Spiritual damage"); + } + + if (freezesOpponent) { + victim->_context._frozen = true; + } + + return usesDecremented; +} + +void WageEngine::performMagic(Chr *attacker, Chr *victim, Obj *magicalObject) { + switch (magicalObject->_attackType) { + case Obj::HEALS_PHYSICAL_DAMAGE: + case Obj::HEALS_SPIRITUAL_DAMAGE: + case Obj::HEALS_PHYSICAL_AND_SPIRITUAL_DAMAGE: + performHealingMagic(attacker, magicalObject); + return; + } + + performAttack(attacker, victim, magicalObject); +} + +void WageEngine::performHealingMagic(Chr *chr, Obj *magicalObject) { + char buf[512]; + + if (!chr->_playerCharacter) { + snprintf(buf, 512, "%s%s %ss %s%s.", + chr->getDefiniteArticle(true), chr->_name.c_str(), + magicalObject->_operativeVerb.c_str(), + getIndefiniteArticle(magicalObject->_name), magicalObject->_name.c_str()); + appendText(buf); + } + + uint chance = _rnd->getRandomNumber(255); + if (chance < magicalObject->_accuracy) { + int type = magicalObject->_attackType; + + if (type == Obj::HEALS_PHYSICAL_DAMAGE || type == Obj::HEALS_PHYSICAL_AND_SPIRITUAL_DAMAGE) + chr->_context._statVariables[PHYS_HIT_CUR] += magicalObject->_damage; + + if (type == Obj::HEALS_SPIRITUAL_DAMAGE || type == Obj::HEALS_PHYSICAL_AND_SPIRITUAL_DAMAGE) + chr->_context._statVariables[SPIR_HIT_CUR] += magicalObject->_damage; + + playSound(magicalObject->_sound); + appendText(magicalObject->_useMessage.c_str()); + + // TODO: what if enemy heals himself? + if (chr->_playerCharacter) { + double physicalPercent = (double)chr->_context._statVariables[PHYS_HIT_CUR] / chr->_context._statVariables[PHYS_HIT_BAS]; + double spiritualPercent = (double)chr->_context._statVariables[SPIR_HIT_CUR] / chr->_context._statVariables[SPIR_HIT_BAS]; + snprintf(buf, 256, "Your physical condition is %s.", getPercentMessage(physicalPercent)); + appendText(buf); + + snprintf(buf, 256, "Your spiritual condition is %s.", getPercentMessage(spiritualPercent)); + appendText(buf); + } + } + + decrementUses(magicalObject); +} + +static const int directionsX[] = { 0, 0, 1, -1 }; +static const int directionsY[] = { -1, 1, 0, 0 }; +static const char *const directionsS[] = { "north", "south", "east", "west" }; + +void WageEngine::performMove(Chr *chr, int validMoves) { + // count how many valid moves we have + int numValidMoves = 0; + + for (int i = 0; i < 4; i++) + if ((validMoves & (1 << i)) != 0) + numValidMoves++; + + // Now pick random dir + int dirNum = _rnd->getRandomNumber(numValidMoves - 1); + int dir = 0; + + // And get it + for (int i = 0; i < 4; i++) + if ((validMoves & (1 << i)) != 0) { + if (dirNum == 0) { + dir = i; + break; + } + dirNum--; + } + + char buf[256]; + snprintf(buf, 256, "%s%s runs %s.", chr->getDefiniteArticle(true), chr->_name.c_str(), directionsS[dir]); + appendText(buf); + + _running = chr; + Scene *currentScene = chr->_currentScene; + int destX = currentScene->_worldX + directionsX[dir]; + int destY = currentScene->_worldY + directionsY[dir]; + + _world->move(chr, _world->getSceneAt(destX, destY)); +} + +void WageEngine::performOffer(Chr *attacker, Chr *victim) { + /* TODO: choose in a smarter way? */ + Obj *obj = attacker->_inventory[0]; + char buf[512]; + + snprintf(buf, 512, "%s%s offers %s%s.", attacker->getDefiniteArticle(true), attacker->_name.c_str(), + obj->_namePlural ? "some " : getIndefiniteArticle(obj->_name), obj->_name.c_str()); + + appendText(buf); + + _offer = obj; +} + +void WageEngine::performTake(Chr *npc, Obj *obj) { + char buf[512]; + + snprintf(buf, 512, "%s%s picks up the %s%s.", npc->getDefiniteArticle(true), npc->_name.c_str(), + getIndefiniteArticle(obj->_name), obj->_name.c_str()); + + appendText(buf); + + _world->move(obj, npc); +} + +int WageEngine::getValidMoveDirections(Chr *npc) { + int directions = 0; + Scene *currentScene = npc->_currentScene; + for (int dir = 0; dir < 4; dir++) { + if (!currentScene->_blocked[dir]) { + int destX = currentScene->_worldX + directionsX[dir]; + int destY = currentScene->_worldY + directionsY[dir]; + + Scene *scene = _world->getSceneAt(destX, destY); + + if (scene != NULL && scene->_chrs.empty()) { + directions |= (1 << dir); + } + } + } + + return directions; +} + +void WageEngine::regen() { + Chr *player = _world->_player; + int curHp = player->_context._statVariables[PHYS_HIT_CUR]; + int maxHp = player->_context._statVariables[PHYS_HIT_BAS]; + int delta = maxHp - curHp; + + if (delta > 0) { + int bonus = (int)(delta / (8 + _rnd->getRandomNumber(2))); + player->_context._statVariables[PHYS_HIT_CUR] += bonus; + } +} + +void WageEngine::takeObj(Obj *obj) { + if (_world->_player->_inventory.size() >= _world->_player->_maximumCarriedObjects) { + appendText("Your pack is full, you must drop something."); + } else { + char buf[256]; + + _world->move(obj, _world->_player); + int type = _world->_player->wearObjIfPossible(obj); + if (type == Chr::HEAD_ARMOR) { + snprintf(buf, 256, "You are now wearing the %s.", obj->_name.c_str()); + appendText(buf); + } else if (type == Chr::BODY_ARMOR) { + snprintf(buf, 256, "You are now wearing the %s.", obj->_name.c_str()); + appendText(buf); + } else if (type == Chr::SHIELD_ARMOR) { + snprintf(buf, 256, "You are now wearing the %s.", obj->_name.c_str()); + appendText(buf); + } else if (type == Chr::MAGIC_ARMOR) { + snprintf(buf, 256, "You are now wearing the %s.", obj->_name.c_str()); + appendText(buf); + } else { + snprintf(buf, 256, "You now have the %s.", obj->_name.c_str()); + appendText(buf); + } + appendText(obj->_clickMessage.c_str()); + } +} + +bool WageEngine::handleMoveCommand(Directions dir, const char *dirName) { + Scene *playerScene = _world->_player->_currentScene; + const char *msg = playerScene->_messages[dir].c_str(); + + if (!playerScene->_blocked[dir]) { + int destX = playerScene->_worldX + directionsX[dir]; + int destY = playerScene->_worldY + directionsY[dir]; + + Scene *scene = _world->getSceneAt(destX, destY); + + if (scene != NULL) { + if (strlen(msg) > 0) { + appendText(msg); + } + _world->move(_world->_player, scene); + return true; + } + } + if (strlen(msg) > 0) { + appendText(msg); + } else { + Common::String txt("You can't go "); + txt += dirName; + txt += "."; + appendText(txt.c_str()); + } + + return true; +} + +bool WageEngine::handleLookCommand() { + appendText(_world->_player->_currentScene->_text.c_str()); + + Common::String *items = getGroundItemsList(_world->_player->_currentScene); + if (items != NULL) { + appendText(items->c_str()); + + delete items; + } + + return true; +} + +Common::String *WageEngine::getGroundItemsList(Scene *scene) { + ObjArray objs; + + for (ObjList::const_iterator it = scene->_objs.begin(); it != scene->_objs.end(); ++it) + if ((*it)->_type != Obj::IMMOBILE_OBJECT) + objs.push_back(*it); + + if (!objs.empty()) { + Common::String *res = new Common::String("On the ground you see "); + appendObjNames(*res, objs); + return res; + } + return NULL; +} + +void WageEngine::appendObjNames(Common::String &str, const ObjArray &objs) { + for (uint i = 0; i < objs.size(); i++) { + Obj *obj = objs[i]; + + if (!obj->_namePlural) + str += getIndefiniteArticle(obj->_name); + else + str += "some "; + + str += obj->_name; + + if (i == objs.size() - 1) { + str += "."; + } else if (i == objs.size() - 2) { + if (objs.size() > 2) + str += ","; + str += " and "; + } else { + str += ", "; + } + } +} + +bool WageEngine::handleInventoryCommand() { + Chr *player = _world->_player; + ObjArray objs; + + for (ObjArray::const_iterator it = player->_inventory.begin(); it != player->_inventory.end(); ++it) + if (!player->isWearing(*it)) + objs.push_back(*it); + + if (objs.empty()) { + appendText("Your pack is empty."); + } else { + Common::String res("Your pack contains "); + appendObjNames(res, objs); + appendText(res.c_str()); + } + + return true; +} + +static const char *const armorMessages[] = { + "Head protection:", + "Chest protection:", + "Shield protection:", // TODO: check message + "Magical protection:" +}; + +bool WageEngine::handleStatusCommand() { + Chr *player = _world->_player; + char buf[512]; + + snprintf(buf, 512, "Character name: %s%s", player->getDefiniteArticle(false), player->_name.c_str()); + appendText(buf); + snprintf(buf, 512, "Experience: %d", player->_context._experience); + appendText(buf); + + int wealth = 0; + for (ObjArray::const_iterator it = player->_inventory.begin(); it != player->_inventory.end(); ++it) + wealth += (*it)->_value; + + snprintf(buf, 512, "Wealth: %d", wealth); + appendText(buf); + + for (int i = 0; i < Chr::NUMBER_OF_ARMOR_TYPES; i++) { + if (player->_armor[i] != NULL) { + snprintf(buf, 512, "%s %s", armorMessages[i], player->_armor[i]->_name.c_str()); + appendText(buf); + } + } + + for (ObjArray::const_iterator it = player->_inventory.begin(); it != player->_inventory.end(); ++it) { + int uses = (*it)->_numberOfUses; + + if (uses > 0) { + snprintf(buf, 512, "Your %s has %d uses left.", (*it)->_name.c_str(), uses); + appendText(buf); + } + } + + printPlayerCondition(player); + + _commandWasQuick = true; + + return true; +} + +bool WageEngine::handleRestCommand() { + if (getMonster() != NULL) { + appendText("This is no time to rest!"); + _commandWasQuick = true; + } else { + regen(); + printPlayerCondition(_world->_player); + } + + return true; +} + +bool WageEngine::handleAcceptCommand() { + Chr *chr = _offer->_currentOwner; + + char buf[512]; + snprintf(buf, 512, "%s%s lays the %s on the ground and departs peacefully.", + chr->getDefiniteArticle(true), chr->_name.c_str(), _offer->_name.c_str()); + appendText(buf); + + _world->move(_offer, chr->_currentScene); + _world->move(chr, _world->_storageScene); + + return true; +} + +bool WageEngine::handleTakeCommand(const char *target) { + Common::String t(target); + bool handled = false; + + for (ObjList::const_iterator it = _world->_player->_currentScene->_objs.begin(); it != _world->_player->_currentScene->_objs.end(); ++it) { + Common::String n((*it)->_name); + n.toLowercase(); + + if (t.contains(n)) { + if ((*it)->_type == Obj::IMMOBILE_OBJECT) { + appendText("You can't move it."); + } else { + takeObj(*it); + } + + handled = true; + break; + } + } + + return handled; +} + +bool WageEngine::handleDropCommand(const char *target) { + Common::String t(target); + bool handled = false; + + t.toLowercase(); + + for (ObjArray::const_iterator it = _world->_player->_inventory.begin(); it != _world->_player->_inventory.end(); ++it) { + Common::String n((*it)->_name); + n.toLowercase(); + + if (t.contains(n)) { + char buf[256]; + + snprintf(buf, 256, "You no longer have the %s.", (*it)->_name.c_str()); + appendText(buf); + _world->move(*it, _world->_player->_currentScene); + + handled = true; + break; + } + } + + return handled; +} + +bool WageEngine::handleAimCommand(const char *t) { + bool wasHandled = true; + Common::String target(t); + + target.toLowercase(); + + if (target.contains("head")) { + _aim = Chr::HEAD; + } else if (target.contains("chest")) { + _aim = Chr::CHEST; + } else if (target.contains("side")) { + _aim = Chr::SIDE; + } else { + wasHandled = false; + appendText("Please aim for the head, chest, or side."); + } + + _commandWasQuick = true; + + return wasHandled; +} + +bool WageEngine::handleWearCommand(const char *t) { + Chr *player = _world->_player; + char buf[512]; + Common::String target(t); + bool handled = false; + + target.toLowercase(); + + for (ObjArray::const_iterator it = _world->_player->_inventory.begin(); it != _world->_player->_inventory.end(); ++it) { + Common::String n((*it)->_name); + + if (target.contains(n)) { + if ((*it)->_type == Obj::HELMET) { + wearObj(*it, Chr::HEAD_ARMOR); + } else if ((*it)->_type == Obj::CHEST_ARMOR) { + wearObj(*it, Chr::BODY_ARMOR); + } else if ((*it)->_type == Obj::SHIELD) { + wearObj(*it, Chr::SHIELD_ARMOR); + } else if ((*it)->_type == Obj::SPIRITUAL_ARMOR) { + wearObj(*it, Chr::MAGIC_ARMOR); + } else { + appendText("You cannot wear that object."); + } + + handled = true; + break; + } + } + + for (ObjList::const_iterator it = player->_currentScene->_objs.begin(); it != player->_currentScene->_objs.end(); ++it) { + Common::String n((*it)->_name); + n.toLowercase(); + if (target.contains(n)) { + snprintf(buf, 512, "First you must get the %s.", (*it)->_name.c_str()); + appendText(buf); + + handled = true; + break; + } + } + + return handled; +} + +void WageEngine::wearObj(Obj *o, int pos) { + Chr *player = _world->_player; + char buf[512]; + + if (player->_armor[pos] == o) { + snprintf(buf, 512, "You are already wearing the %s.", o->_name.c_str()); + appendText(buf); + } else { + if (player->_armor[pos] != NULL) { + snprintf(buf, 512, "You are no longer wearing the %s.", player->_armor[pos]->_name.c_str()); + appendText(buf); + } + + player->_armor[pos] = o; + snprintf(buf, 512, "You are now wearing the %s.", o->_name.c_str()); + appendText(buf); + } +} + + +bool WageEngine::handleOfferCommand(const char *target) { + Chr *player = _world->_player; + Chr *enemy = getMonster(); + + if (enemy != NULL) { + Common::String t(target); + t.toLowercase(); + + for (ObjArray::const_iterator it = player->_inventory.begin(); it != player->_inventory.end(); ++it) { + Common::String n((*it)->_name); + n.toLowercase(); + + if (t.contains(n)) { + if ((*it)->_value < enemy->_rejectsOffers) { + appendText("Your offer is rejected."); + } else { + appendText("Your offer is accepted."); + appendText(enemy->_acceptsOfferComment.c_str()); + _world->move(*it, enemy); + _world->move(enemy, _world->_storageScene); + } + + return true; + } + } + } + + return false; +} + +bool WageEngine::tryAttack(const Obj *weapon, const Common::String &input) { + Common::String w(weapon->_name); + w.toLowercase(); + Common::String i(input); + i.toLowercase(); + Common::String v(weapon->_operativeVerb); + v.toLowercase(); + + return i.contains(w) && i.contains(v); +} + +bool WageEngine::handleAttack(Obj *weapon) { + Chr *player = _world->_player; + Chr *enemy = getMonster(); + + if (weapon->_type == Obj::MAGICAL_OBJECT) { + switch (weapon->_attackType) { + case Obj::HEALS_PHYSICAL_AND_SPIRITUAL_DAMAGE: + case Obj::HEALS_PHYSICAL_DAMAGE: + case Obj::HEALS_SPIRITUAL_DAMAGE: + performMagic(player, enemy, weapon); + return true; + } + } + if (enemy != NULL) + performAttack(player, enemy, weapon); + else if (weapon->_type == Obj::MAGICAL_OBJECT) + appendText("There is nobody to cast a spell at."); + else + appendText("There is no one to fight."); + + return true; +} + +const char *WageEngine::getPercentMessage(double percent) { + if (percent < 0.40) { + return "very bad"; + } else if (percent < 0.55) { + return "bad"; + } else if (percent < 0.70) { + return "average"; + } else if (percent < 0.85) { + return "good"; + } else if (percent <= 1.00) { + return "very good"; + } else { + return "enhanced"; + } +} + +void WageEngine::printPlayerCondition(Chr *player) { + double physicalPercent = (double)player->_context._statVariables[PHYS_HIT_CUR] / player->_context._statVariables[PHYS_HIT_BAS]; + double spiritualPercent = (double)player->_context._statVariables[SPIR_HIT_CUR] / player->_context._statVariables[SPIR_HIT_BAS]; + char buf[256]; + + snprintf(buf, 256, "Your physical condition is %s.", getPercentMessage(physicalPercent)); + appendText(buf); + + snprintf(buf, 256, "Your spiritual condition is %s.", getPercentMessage(spiritualPercent)); + appendText(buf); +} + +} // End of namespace Wage diff --git a/engines/wage/configure.engine b/engines/wage/configure.engine new file mode 100644 index 0000000000..6205211158 --- /dev/null +++ b/engines/wage/configure.engine @@ -0,0 +1,3 @@ +# This file is included from the main "configure" script +# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] +add_engine wage "WAGE" no diff --git a/engines/wage/debugger.cpp b/engines/wage/debugger.cpp new file mode 100644 index 0000000000..7d01b0b85e --- /dev/null +++ b/engines/wage/debugger.cpp @@ -0,0 +1,97 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/file.h" +#include "wage/wage.h" +#include "wage/debugger.h" +#include "wage/entities.h" +#include "wage/script.h" +#include "wage/world.h" + +namespace Wage { + +Debugger::Debugger(WageEngine *engine) : GUI::Debugger(), _engine(engine) { + registerCmd("continue", WRAP_METHOD(Debugger, cmdExit)); + registerCmd("scenes", WRAP_METHOD(Debugger, Cmd_ListScenes)); + registerCmd("script", WRAP_METHOD(Debugger, Cmd_Script)); +} + +Debugger::~Debugger() { +} + +static int strToInt(const char *s) { + if (!*s) + // No string at all + return 0; + else if (toupper(s[strlen(s) - 1]) != 'H') + // Standard decimal string + return atoi(s); + + // Hexadecimal string + uint tmp = 0; + int read = sscanf(s, "%xh", &tmp); + + if (read < 1) + error("strToInt failed on string \"%s\"", s); + return (int)tmp; +} + +bool Debugger::Cmd_ListScenes(int argc, const char **argv) { + int currentScene; + + for (uint i = 1; i < _engine->_world->_orderedScenes.size(); i++) { // #0 is STORAGE@ + if (_engine->_world->_player->_currentScene == _engine->_world->_orderedScenes[i]) + currentScene = i; + + debugPrintf("%d: %s\n", i, _engine->_world->_orderedScenes[i]->_name.c_str()); + } + + debugPrintf("\nCurrent scene is #%d: %s\n", currentScene, _engine->_world->_orderedScenes[currentScene]->_name.c_str()); + + return true; +} + +bool Debugger::Cmd_Script(int argc, const char **argv) { + Script *script = _engine->_world->_player->_currentScene->_script; + + if (argc >= 2) { + int scriptNum = strToInt(argv[1]); + + if (scriptNum) + script = _engine->_world->_orderedScenes[scriptNum]->_script; + else + script = _engine->_world->_globalScript; + } + + if (script == NULL) { + debugPrintf("There is no script for current scene\n"); + return true; + } + + for (uint i = 0; i < script->_scriptText.size(); i++) { + debugPrintf("%d [%04x]: %s\n", i, script->_scriptText[i]->offset, script->_scriptText[i]->line.c_str()); + } + + return true; +} + +} // End of namespace Wage diff --git a/engines/wage/debugger.h b/engines/wage/debugger.h new file mode 100644 index 0000000000..90687760cb --- /dev/null +++ b/engines/wage/debugger.h @@ -0,0 +1,47 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef WAGE_DEBUGGER_H +#define WAGE_DEBUGGER_H + +#include "common/scummsys.h" +#include "gui/debugger.h" + +namespace Wage { + +class WageEngine; + +class Debugger : public GUI::Debugger { +protected: + WageEngine *_engine; + + bool Cmd_ListScenes(int argc, const char **argv); + bool Cmd_Script(int argc, const char **argv); + +public: + Debugger(WageEngine *engine); + virtual ~Debugger(); +}; + +} // End of namespace Wage + +#endif diff --git a/engines/wage/design.cpp b/engines/wage/design.cpp new file mode 100644 index 0000000000..907a1ec435 --- /dev/null +++ b/engines/wage/design.cpp @@ -0,0 +1,599 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "graphics/primitives.h" +#include "wage/wage.h" +#include "wage/design.h" + +namespace Wage { + +struct PlotData { + Graphics::Surface *surface; + Patterns *patterns; + uint fillType; + int thickness; + Design *design; + + PlotData(Graphics::Surface *s, Patterns *p, int f, int t, Design *d) : + surface(s), patterns(p), fillType(f), thickness(t), design(d) {} +}; + +void drawPixel(int x, int y, int color, void *data); +void drawPixelPlain(int x, int y, int color, void *data); + +Design::Design(Common::SeekableReadStream *data) { + _len = data->readUint16BE() - 2; + _data = (byte *)malloc(_len); + data->read(_data, _len); + + _surface = NULL; + _bounds = NULL; + + _boundsCalculationMode = false; +} + +Design::~Design() { + free(_data); + if (_surface) + _surface->free(); + delete _surface; +} + +void Design::paint(Graphics::Surface *surface, Patterns &patterns, int x, int y) { + bool needRender = false; + + if (_surface == NULL) { + _boundsCalculationMode = true; + _bounds->debugPrint(4, "Internal bounds:"); + render(patterns); + _boundsCalculationMode = false; + if (_bounds->right == -10000) { + _bounds->left = _bounds->top = _bounds->right = _bounds->bottom = 0; + } + _bounds->debugPrint(4, "Calculated bounds:"); + + _surface = new Graphics::Surface; + _surface->create(_bounds->width(), _bounds->height(), Graphics::PixelFormat::createFormatCLUT8()); + + Common::Rect r(0, 0, _bounds->width(), _bounds->height()); + _surface->fillRect(r, kColorGreen); + + needRender = true; + } + + _bounds->debugPrint(4, "Using bounds:"); +#if 0 + PlotData pd(_surface, &patterns, 8, 1, this); + int x1 = 50, y1 = 50, x2 = 200, y2 = 200, borderThickness = 30; + Common::Rect inn(x1-5, y1-5, x2+5, y2+5); + drawRoundRect(inn, 6, kColorGray, false, drawPixelPlain, &pd); + + drawThickLine(x1, y1, x2-borderThickness, y1, borderThickness, kColorBlack, drawPixel, &pd); + drawThickLine(x2-borderThickness, y1, x2-borderThickness, y2, borderThickness, kColorBlack, drawPixel, &pd); + drawThickLine(x2-borderThickness, y2-borderThickness, x1, y2-borderThickness, borderThickness, kColorBlack, drawPixel, &pd); + drawThickLine(x1, y2-borderThickness, x1, y1, borderThickness, kColorBlack, drawPixel, &pd); + drawThickLine(x2+10, y2+10, x2+100, y2+100, borderThickness, kColorBlack, drawPixel, &pd); + + g_system->copyRectToScreen(_surface->getPixels(), _surface->pitch, 0, 0, _surface->w, _surface->h); + + while (true) { + ((WageEngine *)g_engine)->processEvents(); + g_system->updateScreen(); + g_system->delayMillis(50); + } + return; +#endif + + if (needRender) + render(patterns); + + if (_bounds->width() && _bounds->height()) { + const int padding = 3; + for (int i = padding; i < _bounds->height() - 2 * padding; i++) { + const byte *src = (const byte *)_surface->getBasePtr(padding, i); + byte *dst = (byte *)surface->getBasePtr(x + padding, y+i); + for (int j = padding; j < _bounds->width() - 2 * padding; j++) { + if (*src != kColorGreen) + *dst = *src; + src++; + dst++; + } + } + } +} + +void Design::render(Patterns &patterns) { + Common::MemoryReadStream in(_data, _len); + bool needRender = true; + + while (needRender) { + byte fillType = in.readByte(); + byte borderThickness = in.readByte(); + byte borderFillType = in.readByte(); + int type = in.readByte(); + + if (in.eos()) + break; + + debug(8, "fill: %d borderFill: %d border: %d type: %d", fillType, borderFillType, borderThickness, type); + switch (type) { + case 4: + drawRect(_surface, in, patterns, fillType, borderThickness, borderFillType); + break; + case 8: + drawRoundRect(_surface, in, patterns, fillType, borderThickness, borderFillType); + break; + case 12: + drawOval(_surface, in, patterns, fillType, borderThickness, borderFillType); + break; + case 16: + case 20: + drawPolygon(_surface, in, patterns, fillType, borderThickness, borderFillType); + break; + case 24: + drawBitmap(_surface, in); + break; + default: + warning("Unknown type => %d", type); + break; + } + + //g_system->copyRectToScreen(_surface->getPixels(), _surface->pitch, 0, 0, _surface->w, _surface->h); + //((WageEngine *)g_engine)->processEvents(); + //g_system->updateScreen(); + //g_system->delayMillis(500); + } +} + +bool Design::isPointOpaque(int x, int y) { + if (_surface == NULL) + error("Surface is null"); + + byte pixel = ((byte *)_surface->getBasePtr(x, y))[0]; + + return pixel != kColorGreen; +} + +void Design::adjustBounds(int16 x, int16 y) { + _bounds->right = MAX(x, _bounds->right); + _bounds->bottom = MAX(y, _bounds->bottom); +} + +void drawPixel(int x, int y, int color, void *data) { + PlotData *p = (PlotData *)data; + + if (p->fillType > p->patterns->size()) + return; + + if (p->design && p->design->isBoundsCalculation()) { + if (x < 0 || y < 0) + return; + if (p->thickness == 1) { + p->design->adjustBounds(x, y); + } else { + int x1 = x - p->thickness / 2; + int x2 = x1 + p->thickness; + int y1 = y - p->thickness / 2; + int y2 = y1 + p->thickness; + + for (y = y1; y < y2; y++) + for (x = x1; x < x2; x++) + p->design->adjustBounds(x, y); + } + + return; + } + + byte *pat = p->patterns->operator[](p->fillType - 1); + + if (p->thickness == 1) { + if (x >= 0 && x < p->surface->w && y >= 0 && y < p->surface->h) { + uint xu = (uint)x; // for letting compiler optimize it + uint yu = (uint)y; + + *((byte *)p->surface->getBasePtr(xu, yu)) = + (pat[yu % 8] & (1 << (7 - xu % 8))) ? + color : kColorWhite; + } + } else { + int x1 = x - p->thickness / 2; + int x2 = x1 + p->thickness; + int y1 = y - p->thickness / 2; + int y2 = y1 + p->thickness; + + for (y = y1; y < y2; y++) + for (x = x1; x < x2; x++) + if (x >= 0 && x < p->surface->w && y >= 0 && y < p->surface->h) { + uint xu = (uint)x; // for letting compiler optimize it + uint yu = (uint)y; + *((byte *)p->surface->getBasePtr(xu, yu)) = + (pat[yu % 8] & (1 << (7 - xu % 8))) ? + color : kColorWhite; + } + } +} + +void drawPixelPlain(int x, int y, int color, void *data) { + PlotData *p = (PlotData *)data; + + if (p->design && p->design->isBoundsCalculation()) { + p->design->adjustBounds(x, y); + return; + } + + if (x >= 0 && x < p->surface->w && y >= 0 && y < p->surface->h) + *((byte *)p->surface->getBasePtr(x, y)) = (byte)color; +} + +void Design::drawRect(Graphics::Surface *surface, Common::ReadStream &in, + Patterns &patterns, byte fillType, byte borderThickness, byte borderFillType) { + int16 y1 = in.readSint16BE(); + int16 x1 = in.readSint16BE(); + int16 y2 = in.readSint16BE(); + int16 x2 = in.readSint16BE(); + + if (x1 > x2) + SWAP(x1, x2); + if (y1 > y2) + SWAP(y1, y2); + + Common::Rect r(x1, y1, x2, y2); + PlotData pd(surface, &patterns, fillType, 1, this); + + if (fillType <= patterns.size()) + Graphics::drawFilledRect(r, kColorBlack, drawPixel, &pd); + + pd.fillType = borderFillType; + pd.thickness = borderThickness; + + if (borderThickness > 0 && borderFillType <= patterns.size()) { + Graphics::drawLine(x1, y1, x2, y1, kColorBlack, drawPixel, &pd); + Graphics::drawLine(x2, y1, x2, y2, kColorBlack, drawPixel, &pd); + Graphics::drawLine(x2, y2, x1, y2, kColorBlack, drawPixel, &pd); + Graphics::drawLine(x1, y2, x1, y1, kColorBlack, drawPixel, &pd); + } +} + +void Design::drawRoundRect(Graphics::Surface *surface, Common::ReadStream &in, + Patterns &patterns, byte fillType, byte borderThickness, byte borderFillType) { + int16 y1 = in.readSint16BE(); + int16 x1 = in.readSint16BE(); + int16 y2 = in.readSint16BE(); + int16 x2 = in.readSint16BE(); + int16 arc = in.readSint16BE(); + + if (x1 > x2) + SWAP(x1, x2); + if (y1 > y2) + SWAP(y1, y2); + + Common::Rect r(x1, y1, x2, y2); + PlotData pd(surface, &patterns, fillType, 1, this); + + if (fillType <= patterns.size()) + Graphics::drawRoundRect(r, arc / 2, kColorBlack, true, drawPixel, &pd); + + pd.fillType = borderFillType; + pd.thickness = borderThickness; + + if (borderThickness > 0 && borderFillType <= patterns.size()) + Graphics::drawRoundRect(r, arc / 2, kColorBlack, false, drawPixel, &pd); +} + +void Design::drawPolygon(Graphics::Surface *surface, Common::ReadStream &in, + Patterns &patterns, byte fillType, byte borderThickness, byte borderFillType) { + + byte ignored = in.readSint16BE(); // ignored + + if (ignored) + warning("Ignored: %d", ignored); + + int numBytes = in.readSint16BE(); // #bytes used by polygon data, including the numBytes + int16 by1 = in.readSint16BE(); + int16 bx1 = in.readSint16BE(); + int16 by2 = in.readSint16BE(); + int16 bx2 = in.readSint16BE(); + Common::Rect bbox(bx1, by1, bx2, by2); + + numBytes -= 8; + + int y1 = in.readSint16BE(); + int x1 = in.readSint16BE(); + + Common::Array<int> xcoords; + Common::Array<int> ycoords; + + numBytes -= 6; + + while (numBytes > 0) { + int y2 = y1; + int x2 = x1; + int b = in.readSByte(); + if (b == -128) { + y2 = in.readSint16BE(); + numBytes -= 3; + } else { + y2 += b; + numBytes -= 1; + } + b = in.readSByte(); + if (b == -128) { + x2 = in.readSint16BE(); + numBytes -= 3; + } else { + x2 += b; + numBytes -= 1; + } + xcoords.push_back(x1); + ycoords.push_back(y1); + x1 = x2; + y1 = y2; + } + xcoords.push_back(x1); + ycoords.push_back(y1); + + int npoints = xcoords.size(); + int *xpoints = (int *)calloc(npoints, sizeof(int)); + int *ypoints = (int *)calloc(npoints, sizeof(int)); + for (int i = 0; i < npoints; i++) { + xpoints[i] = xcoords[i]; + ypoints[i] = ycoords[i]; + } + + PlotData pd(surface, &patterns, fillType, 1, this); + + if (fillType <= patterns.size()) { + Graphics::drawPolygonScan(xpoints, ypoints, npoints, bbox, kColorBlack, drawPixel, &pd); + } + + pd.fillType = borderFillType; + pd.thickness = borderThickness; + if (borderThickness > 0 && borderFillType <= patterns.size()) { + for (int i = 1; i < npoints; i++) + Graphics::drawLine(xpoints[i-1], ypoints[i-1], xpoints[i], ypoints[i], kColorBlack, drawPixel, &pd); + } + + free(xpoints); + free(ypoints); +} + +void Design::drawOval(Graphics::Surface *surface, Common::ReadStream &in, + Patterns &patterns, byte fillType, byte borderThickness, byte borderFillType) { + int16 y1 = in.readSint16BE(); + int16 x1 = in.readSint16BE(); + int16 y2 = in.readSint16BE(); + int16 x2 = in.readSint16BE(); + PlotData pd(surface, &patterns, fillType, 1, this); + + if (fillType <= patterns.size()) + Graphics::drawEllipse(x1, y1, x2-1, y2-1, kColorBlack, true, drawPixel, &pd); + + pd.fillType = borderFillType; + pd.thickness = borderThickness; + + if (borderThickness > 0 && borderFillType <= patterns.size()) + Graphics::drawEllipse(x1, y1, x2-1, y2-1, kColorBlack, false, drawPixel, &pd); +} + +void Design::drawBitmap(Graphics::Surface *surface, Common::SeekableReadStream &in) { + int numBytes = in.readSint16BE(); + int y1 = in.readSint16BE(); + int x1 = in.readSint16BE(); + int y2 = in.readSint16BE(); + int x2 = in.readSint16BE(); + int w = x2 - x1; + int h = y2 - y1; + Graphics::Surface tmp; + + tmp.create(w, h, Graphics::PixelFormat::createFormatCLUT8()); + + numBytes -= 10; + + int x = 0, y = 0; + while (numBytes > 0 && y < h) { + int n = in.readSByte(); + int count; + int b = 0; + int state = 0; + + numBytes--; + + if ((n >= 0) && (n <= 127)) { // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + count = n + 1; + state = 1; + } else if ((n >= -127) && (n <= -1)) { // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + b = in.readByte(); + numBytes--; + count = -n + 1; + state = 2; + } else { // Else if n is -128, noop. + count = 0; + } + + for (int i = 0; i < count && y < h; i++) { + byte color = 0; + if (state == 1) { + color = in.readByte(); + numBytes--; + } else if (state == 2) + color = b; + + for (int c = 0; c < 8; c++) { + if (_boundsCalculationMode) { + adjustBounds(x1 + x, y1 + y); + } else if (x1 + x >= 0 && x1 + x < surface->w && y1 + y >= 0 && y1 + y < surface->h) + *((byte *)tmp.getBasePtr(x, y)) = (color & (1 << (7 - c % 8))) ? kColorBlack : kColorWhite; + x++; + if (x == w) { + y++; + x = 0; + break; + } + } + } + } + + in.skip(numBytes); + + if (_boundsCalculationMode) + return; + + FloodFill ff(&tmp, kColorWhite, kColorGreen); + for (int yy = 0; yy < h; yy++) { + ff.addSeed(0, yy); + ff.addSeed(w - 1, yy); + } + for (int xx = 0; xx < w; xx++) { + ff.addSeed(xx, 0); + ff.addSeed(xx, h - 1); + } + ff.fill(); + + for (y = 0; y < h && y1 + y < surface->h; y++) { + byte *src = (byte *)tmp.getBasePtr(0, y); + byte *dst = (byte *)surface->getBasePtr(x1, y1 + y); + for (x = 0; x < w; x++) { + if (*src != kColorGreen) + *dst = *src; + src++; + dst++; + } + } + + tmp.free(); +} + +void Design::drawRect(Graphics::Surface *surface, Common::Rect &rect, int thickness, int color, Patterns &patterns, byte fillType) { + drawRect(surface, rect.left, rect.top, rect.right, rect.bottom, thickness, color, patterns, fillType); +} + +void Design::drawRect(Graphics::Surface *surface, int x1, int y1, int x2, int y2, int thickness, int color, Patterns &patterns, byte fillType) { + PlotData pd(surface, &patterns, fillType, thickness, nullptr); + + Graphics::drawLine(x1, y1, x2, y1, kColorBlack, drawPixel, &pd); + Graphics::drawLine(x2, y1, x2, y2, kColorBlack, drawPixel, &pd); + Graphics::drawLine(x2, y2, x1, y2, kColorBlack, drawPixel, &pd); + Graphics::drawLine(x1, y2, x1, y1, kColorBlack, drawPixel, &pd); +} + + +void Design::drawFilledRect(Graphics::Surface *surface, Common::Rect &rect, int color, Patterns &patterns, byte fillType) { + PlotData pd(surface, &patterns, fillType, 1, nullptr); + + for (int y = rect.top; y <= rect.bottom; y++) + Graphics::drawHLine(rect.left, rect.right, y, color, drawPixel, &pd); +} + +void Design::drawFilledRoundRect(Graphics::Surface *surface, Common::Rect &rect, int arc, int color, Patterns &patterns, byte fillType) { + PlotData pd(surface, &patterns, fillType, 1, nullptr); + + Graphics::drawRoundRect(rect, arc, color, true, drawPixel, &pd); +} + +void Design::drawHLine(Graphics::Surface *surface, int x1, int x2, int y, int thickness, int color, Patterns &patterns, byte fillType) { + PlotData pd(surface, &patterns, fillType, thickness, nullptr); + + Graphics::drawHLine(x1, x2, y, color, drawPixel, &pd); +} + +void Design::drawVLine(Graphics::Surface *surface, int x, int y1, int y2, int thickness, int color, Patterns &patterns, byte fillType) { + PlotData pd(surface, &patterns, fillType, thickness, nullptr); + + Graphics::drawVLine(x, y1, y2, color, drawPixel, &pd); +} + +FloodFill::FloodFill(Graphics::Surface *surface, byte color1, byte color2) { + _surface = surface; + _color1 = color1; + _color2 = color2; + _w = surface->w; + _h = surface->h; + + _visited = (byte *)calloc(_w * _h, 1); +} + +FloodFill::~FloodFill() { + while(!_queue.empty()) { + Common::Point *p = _queue.front(); + + delete p; + _queue.pop_front(); + } + + free(_visited); +} + +void FloodFill::addSeed(int x, int y) { + byte *p; + + if (x >= 0 && x < _w && y >= 0 && y < _h) { + if (!_visited[y * _w + x] && *(p = (byte *)_surface->getBasePtr(x, y)) == _color1) { + _visited[y * _w + x] = 1; + *p = _color2; + + Common::Point *pt = new Common::Point(x, y); + + _queue.push_back(pt); + } + } +} + +void FloodFill::fill() { + while (!_queue.empty()) { + Common::Point *p = _queue.front(); + _queue.pop_front(); + addSeed(p->x , p->y - 1); + addSeed(p->x - 1, p->y ); + addSeed(p->x , p->y + 1); + addSeed(p->x + 1, p->y ); + + delete p; + } +} + + +} // End of namespace Wage diff --git a/engines/wage/design.h b/engines/wage/design.h new file mode 100644 index 0000000000..e8f42f4e04 --- /dev/null +++ b/engines/wage/design.h @@ -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 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef WAGE_DESIGN_H +#define WAGE_DESIGN_H + +#include "graphics/surface.h" +#include "common/memstream.h" +#include "common/rect.h" + +namespace Wage { + +class Design { +public: + Design(Common::SeekableReadStream *data); + ~Design(); + + void setBounds(Common::Rect *bounds) { + _bounds = bounds; + } + + Common::Rect *getBounds() { + return _bounds; + } + + void paint(Graphics::Surface *canvas, Patterns &patterns, int x, int y); + bool isPointOpaque(int x, int y); + static void drawRect(Graphics::Surface *surface, Common::Rect &rect, int thickness, int color, Patterns &patterns, byte fillType); + static void drawRect(Graphics::Surface *surface, int x1, int y1, int x2, int y2, int thickness, int color, Patterns &patterns, byte fillType); + static void drawFilledRect(Graphics::Surface *surface, Common::Rect &rect, int color, Patterns &patterns, byte fillType); + static void drawFilledRoundRect(Graphics::Surface *surface, Common::Rect &rect, int arc, int color, Patterns &patterns, byte fillType); + static void drawHLine(Graphics::Surface *surface, int x1, int x2, int y, int thickness, int color, Patterns &patterns, byte fillType); + static void drawVLine(Graphics::Surface *surface, int x, int y1, int y2, int thickness, int color, Patterns &patterns, byte fillType); + + bool isBoundsCalculation() { return _boundsCalculationMode; } + void adjustBounds(int16 x, int16 y); + +private: + byte *_data; + int _len; + Common::Rect *_bounds; + Graphics::Surface *_surface; + bool _boundsCalculationMode; + +private: + void render(Patterns &patterns); + void drawRect(Graphics::Surface *surface, Common::ReadStream &in, + Patterns &patterns, byte fillType, byte borderThickness, byte borderFillType); + void drawRoundRect(Graphics::Surface *surface, Common::ReadStream &in, + Patterns &patterns, byte fillType, byte borderThickness, byte borderFillType); + void drawPolygon(Graphics::Surface *surface, Common::ReadStream &in, + Patterns &patterns, byte fillType, byte borderThickness, byte borderFillType); + void drawOval(Graphics::Surface *surface, Common::ReadStream &in, + Patterns &patterns, byte fillType, byte borderThickness, byte borderFillType); + void drawBitmap(Graphics::Surface *surface, Common::SeekableReadStream &in); +}; + +class FloodFill { +public: + FloodFill(Graphics::Surface *surface, byte color1, byte color2); + ~FloodFill(); + void addSeed(int x, int y); + void fill(); + +private: + Common::List<Common::Point *> _queue; + Graphics::Surface *_surface; + byte _color1, _color2; + byte *_visited; + int _w, _h; +}; + +} // End of namespace Wage + +#endif diff --git a/engines/wage/detection.cpp b/engines/wage/detection.cpp new file mode 100644 index 0000000000..512d432e54 --- /dev/null +++ b/engines/wage/detection.cpp @@ -0,0 +1,151 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + + +#include "base/plugins.h" + +#include "engines/advancedDetector.h" +#include "common/system.h" +#include "common/savefile.h" + +#include "wage/wage.h" + +namespace Wage { + +const char *WageEngine::getGameFile() const { + return _gameDescription->filesDescriptions[0].fileName; +} + +} + +static const PlainGameDescriptor wageGames[] = { + {"afm", "Another Fine Mess"}, + {"amot", "A Mess O' Trouble"}, + {"cantitoe", "Camp Cantitoe"}, + {"drakmythcastle", "Drakmyth Castle"}, + {"raysmaze", "Ray's Maze"}, + {"scepters", "Enchanted Scepters"}, + {"twisted", "Twisted!"}, + {"wage", "WAGE"}, + {0, 0} +}; + +#include "wage/detection_tables.h" + +class WageMetaEngine : public AdvancedMetaEngine { +public: + WageMetaEngine() : AdvancedMetaEngine(Wage::gameDescriptions, sizeof(ADGameDescription), wageGames) { + _singleId = "wage"; + _guiOptions = GUIO2(GUIO_NOSPEECH, GUIO_NOMIDI); + } + + virtual const char *getName() const { + return "World Adventure Game Engine"; + } + + virtual const char *getOriginalCopyright() const { + return "World Builder (C) Silicon Beach Software"; + } + + virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; + virtual bool hasFeature(MetaEngineFeature f) const; + virtual SaveStateList listSaves(const char *target) const; + virtual int getMaximumSaveSlot() const; + virtual void removeSaveState(const char *target, int slot) const; +}; + +bool WageMetaEngine::hasFeature(MetaEngineFeature f) const { + return + (f == kSupportsListSaves) || + (f == kSupportsLoadingDuringStartup) || + (f == kSupportsDeleteSave); +} + +bool Wage::WageEngine::hasFeature(EngineFeature f) const { + return + (f == kSupportsRTL) || + (f == kSupportsLoadingDuringRuntime) || + (f == kSupportsSavingDuringRuntime); +} + +bool WageMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const { + if (desc) { + *engine = new Wage::WageEngine(syst, desc); + } + return desc != 0; +} + +SaveStateList WageMetaEngine::listSaves(const char *target) const { + const uint32 WAGEflag = MKTAG('W','A','G','E'); + Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + Common::StringArray filenames; + char saveDesc[31]; + Common::String pattern = target; + pattern += ".???"; + + filenames = saveFileMan->listSavefiles(pattern); + + SaveStateList saveList; + for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { + // Obtain the last 3 digits of the filename, since they correspond to the save slot + int slotNum = atoi(file->c_str() + file->size() - 3); + + if (slotNum >= 0 && slotNum <= 999) { + Common::InSaveFile *in = saveFileMan->openForLoading(*file); + if (in) { + uint32 type = in->readUint32BE(); + if (type == WAGEflag) + in->read(saveDesc, 31); + saveList.push_back(SaveStateDescriptor(slotNum, saveDesc)); + delete in; + } + } + } + + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); + return saveList; +} + +int WageMetaEngine::getMaximumSaveSlot() const { return 999; } + +void WageMetaEngine::removeSaveState(const char *target, int slot) const { + g_system->getSavefileManager()->removeSavefile(Common::String::format("%s.%03d", target, slot)); +} + +#if PLUGIN_ENABLED_DYNAMIC(WAGE) + REGISTER_PLUGIN_DYNAMIC(WAGE, PLUGIN_TYPE_ENGINE, WageMetaEngine); +#else + REGISTER_PLUGIN_STATIC(WAGE, PLUGIN_TYPE_ENGINE, WageMetaEngine); +#endif + +namespace Wage { + +bool WageEngine::canLoadGameStateCurrently() { + return false; +} + +bool WageEngine::canSaveGameStateCurrently() { + return false; +} + +} // End of namespace Wage diff --git a/engines/wage/detection_tables.h b/engines/wage/detection_tables.h new file mode 100644 index 0000000000..e164ea70cd --- /dev/null +++ b/engines/wage/detection_tables.h @@ -0,0 +1,121 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +namespace Wage { + +#define ADGF_DEFAULT (ADGF_DROPLANGUAGE|ADGF_DROPPLATFORM|ADGF_MACRESFORK) +#define ADGF_GENERIC (ADGF_DEFAULT|ADGF_USEEXTRAASTITLE|ADGF_AUTOGENTARGET) +#define ADGF_DEMO (ADGF_GENERIC|ADGF_DEMO) + +#define FANGAME(n,m,s) { "wage",n,AD_ENTRY1s(n,m,s),Common::EN_ANY,Common::kPlatformMacintosh,ADGF_GENERIC,GUIO0()} +#define FANGAMEN(n,f,m,s) { "wage",n,AD_ENTRY1s(f,m,s),Common::EN_ANY,Common::kPlatformMacintosh,ADGF_GENERIC,GUIO0()} +#define FANGAMEND(n,f,m,s) { "wage",n,AD_ENTRY1s(f,m,s),Common::EN_ANY,Common::kPlatformMacintosh,ADGF_DEMO,GUIO0()} +#define BIGGAME(t,v,f,m,s) { t,v,AD_ENTRY1s(f,m,s),Common::EN_ANY,Common::kPlatformMacintosh,ADGF_DEFAULT,GUIO0()} + +static const ADGameDescription gameDescriptions[] = { + FANGAME("3rd Floor", "913812a1ac7a6b0e48dadd1afa1c7763", 281409), + BIGGAME("afm", "v1.8", "Another Fine Mess 1.8", "94a9c4f8b3dabd1846d76215a49bd221", 1420723), + BIGGAME("amot", "v1.8", "A Mess O' Trouble 1.8", "26207bdf0bb539464f136f0669af885f", 1843104), + FANGAME("Bug Hunt", "595117cbed33e8de1ab3714b33880205", 195699), + BIGGAME("cantitoe", "", "Camp Cantitoe", "913812a1ac7a6b0e48dadd1afa1c7763", 616985), + // Problems with letter rendering + FANGAME("Canal District", "a56aa3cd4a6e070e15ce1d5815c7be0a", 641470), + FANGAME("Carbon Copy", "913812a1ac7a6b0e48dadd1afa1c7763", 519445), + // Invalid rect in scene "FINALE" + FANGAME("Castle of Ert", "327610eb2298a9427a566288312df040", 198955), + FANGAME("Deep Angst", "b130b3c811cd89024dd5fdd2b71f70b8", 329550), + FANGAME("Deep Ennui", "913812a1ac7a6b0e48dadd1afa1c7763", 86075), + // Polygons with ignored byte 1 + FANGAME("Double Trouble", "1652e36857a04c01dc560234c4818619", 542371), + BIGGAME("drakmythcastle", "disk I", "Drakmyth Castle disk I of II", "94a9c4f8b3dabd1846d76215a49bd221", 793784), + BIGGAME("drakmythcastle", "disk II", "Drakmyth Castle II", "cc978cc9a5256724702463cb5aaaffa0", 1685659), + // Crash at start in GUI rendering + FANGAME("Dune Eternity", "94a9c4f8b3dabd1846d76215a49bd221", 290201), // Original file name is "***DUNE ETERNITY*** " + FANGAMEN("Dungeon World II", "DungeonWorld2", "0154ea11d3cbb536c13b4ae9e6902d48", 230199), + FANGAME("Eidisi I", "595117cbed33e8de1ab3714b33880205", 172552), + // Problems(?) with text on the first screen + FANGAMEN("Enchanted Pencils", "Enchanted Pencils 0.99 (PG)", "595117cbed33e8de1ab3714b33880205", 408913), + FANGAME("Escape from School!", "913812a1ac7a6b0e48dadd1afa1c7763", 50105), + FANGAME("Exploration Zeta!", "c477921aeee6ed0f8997ba44447eb2d0", 366599), + // Crash in console rendering on the first scene + FANGAME("Fantasy Quest", "4b0e1a1fbaaa4930accd0f9f0e1519c7", 762754), + FANGAME("Find the Heart", "595117cbed33e8de1ab3714b33880205", 106235), // From Joshua's Worlds 1.0 + // Problems with window overlay + FANGAMEN("Jumble", "LSJUMBLE", "e12ec4d76d48bdc86567c5e63750547e", 647339), // Original file name is "LSJUMBLE† " + FANGAME("Karth of the Jungle", "595117cbed33e8de1ab3714b33880205", 96711), + FANGAME("Karth of the Jungle", "595117cbed33e8de1ab3714b33880205", 96960), // Alternative version + FANGAME("Karth of the Jungle II", "c106835ab4436de054e03aec3ce904ce", 201053), + FANGAMEN("Little Pythagoras", "Little Pythagoras 1.1.1", "94a9c4f8b3dabd1846d76215a49bd221", 628821), + FANGAME("Lost Crystal", "8174c81ea1858d0079ae040dae2cefd3", 771072), + FANGAME("Magic Rings", "913812a1ac7a6b0e48dadd1afa1c7763", 109044), + // No way to click on the house + FANGAME("Messy House", "913812a1ac7a6b0e48dadd1afa1c7763", 177120), + FANGAME("Midnight Snack", "913812a1ac7a6b0e48dadd1afa1c7763", 67952), + FANGAME("Midnight Snack", "913812a1ac7a6b0e48dadd1afa1c7763", 67966), // Alt version + FANGAME("Minitorian", "913812a1ac7a6b0e48dadd1afa1c7763", 586464), + // No way to pass through the first screen + FANGAME("Nightcrawler Ned", "94a9c4f8b3dabd1846d76215a49bd221", 366542), + FANGAME("Pavilion", "4d991d7d1534d48d90598d86ea6d5d97", 231687), + // Polygons with byte 1 + FANGAME("Periapt", "913812a1ac7a6b0e48dadd1afa1c7763", 406006), + FANGAME("Puzzle Piece Search", "595117cbed33e8de1ab3714b33880205", 247693), // From Joshua's Worlds 1.0 + // Empty(?) first scene + FANGAME("Pyramid of No Return", "77a55a45f794b4d4a56703d3acce871e", 385145), + FANGAME("Queen Quest", "4b0e1a1fbaaa4930accd0f9f0e1519c7", 57026), + FANGAME("Quest for T-Rex", "913812a1ac7a6b0e48dadd1afa1c7763", 592584), + // Crash in console rendering on the initial scene + FANGAME("Quest for the Dark Sword", "b35dd0c078da9f35fc25a455f56bb129", 572576), + FANGAME("Radical Castle", "677bfee4afeca2f7152eb8b76c85ca8d", 355601), + FANGAME("Radical Castle 1.0", "677bfee4afeca2f7152eb8b76c85ca8d", 347278), + BIGGAME("raysmaze", "v1.5", "Ray's Maze1.5", "064b16d8c20724f8debbbdc3aafde538", 1408516), + BIGGAME("raysmaze", "v1.5/alt", "Ray's Maze1.5", "92cca777800c3d31a77b5ed7f6ee49ad", 1408516), + BIGGAME("scepters", "", "Scepters", "3311deef8bf82f0b4b1cfa15a3b3289d", 346595), + // ??? problems with dog bitmap? + FANGAMEN("Space Adventure", "SpaceAdventure", "f9f3f1c419f56955f7966355b34ea5c8", 155356), + FANGAMEN("Spear of Destiny", "SpearOfDestiny", "913812a1ac7a6b0e48dadd1afa1c7763", 333665), // Original file name "SpearOfDestiny†" + FANGAME("Star Trek", "44aaef4806578700429de5aaf95c266e", 53320), + FANGAME("Strange Disappearance", "d81f2d03a1e863f04fb1e3a5495b720e", 772282), + // Code 0x03 in text + FANGAME("Swamp Witch", "913812a1ac7a6b0e48dadd1afa1c7763", 739781), // Original file name "Swamp Witch†" + FANGAME("Sweetspace Now!", "e12ec4d76d48bdc86567c5e63750547e", 123813), // Comes with Jumble + FANGAME("Time Bomb", "4b0e1a1fbaaa4930accd0f9f0e1519c7", 64564), + FANGAME("Time Bomb", "4b0e1a1fbaaa4930accd0f9f0e1519c7", 64578), // Alt version + FANGAMEND("The Ashland Revolution", "The Ashland Revolution Demo", "913812a1ac7a6b0e48dadd1afa1c7763", 145023), // Original file name "The Ashland Revolution Demo†" + FANGAMEN("The Hotel Caper", "The Hotel Caper V1.0", "595117cbed33e8de1ab3714b33880205", 231969), + // Invalid rect in scene "Access Tube 1" + FANGAMEN("The Phoenix v1.2", "The Phoenix", "4b0e1a1fbaaa4930accd0f9f0e1519c7", 431640), + FANGAME("The Sultan's Palace", "358799d446ee4fc12f793febd6c94b95", 456855), + // Admission for on 3rd screen is messed up + FANGAME("The Tower", "435f420b9dff895ae1ddf1338040c51d", 556539), + // Polygons with ignored byte 1 and 2 on second scene + FANGAME("The Village", "913812a1ac7a6b0e48dadd1afa1c7763", 314828), + // Doesn't go past first scene + BIGGAME("twisted", "", "Twisted! 1.6", "26207bdf0bb539464f136f0669af885f", 960954), + FANGAME("Wishing Well", "913812a1ac7a6b0e48dadd1afa1c7763", 103688), + FANGAME("Wizard's Warehouse", "913812a1ac7a6b0e48dadd1afa1c7763", 159748), + FANGAME("ZikTuria", "418e74ca71029a1e9db80d0eb30c0843", 52972), + FANGAME("Zoony", "539a64151426edc92da5eedadf39f23c", 154990), // original filename "Zoony™" + + AD_TABLE_END_MARKER +}; + +} // End of namespace Wage diff --git a/engines/wage/dialog.cpp b/engines/wage/dialog.cpp new file mode 100644 index 0000000000..263570bddc --- /dev/null +++ b/engines/wage/dialog.cpp @@ -0,0 +1,241 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "common/system.h" +#include "common/events.h" + +#include "wage/wage.h" +#include "wage/design.h" +#include "wage/gui.h" +#include "wage/dialog.h" + +namespace Wage { + +enum { + kDialogHeight = 113 +}; + +Dialog::Dialog(Gui *gui, int width, const char *text, DialogButtonArray *buttons, uint defaultButton) : + _gui(gui), _text(text), _buttons(buttons), _defaultButton(defaultButton) { + assert(_gui->_engine); + assert(_gui->_engine->_world); + + _font = getDialogFont(); + + _tempSurface.create(width + 1, kDialogHeight + 1, Graphics::PixelFormat::createFormatCLUT8()); + + _bbox.left = (_gui->_screen.w - width) / 2; + _bbox.top = (_gui->_screen.h - kDialogHeight) / 2; + _bbox.right = (_gui->_screen.w + width) / 2; + _bbox.bottom = (_gui->_screen.h + kDialogHeight) / 2; + + _pressedButton = -1; + + _mouseOverPressedButton = false; + + // Adjust button positions + for (uint i = 0; i < _buttons->size(); i++) + _buttons->operator[](i)->bounds.translate(_bbox.left, _bbox.top); + + _needsRedraw = true; +} + +Dialog::~Dialog() { +} + +const Graphics::Font *Dialog::getDialogFont() { + return _gui->getFont("Chicago-12", Graphics::FontManager::kBigGUIFont); +} + +void Dialog::paint() { + Design::drawFilledRect(&_gui->_screen, _bbox, kColorWhite, _gui->_patterns, kPatternSolid); + _font->drawString(&_gui->_screen, _text, _bbox.left + 24, _bbox.top + 16, _bbox.width(), kColorBlack); + + static int boxOutline[] = { 1, 0, 0, 1, 1 }; + drawOutline(_bbox, boxOutline, ARRAYSIZE(boxOutline)); + + for (uint i = 0; i < _buttons->size(); i++) { + DialogButton *button = _buttons->operator[](i); + static int buttonOutline[] = { 0, 0, 0, 0, 1 }; + + if (i == _defaultButton) { + buttonOutline[0] = buttonOutline[1] = 1; + } else { + buttonOutline[0] = buttonOutline[1] = 0; + } + + int color = kColorBlack; + + if ((int)i == _pressedButton && _mouseOverPressedButton) { + Common::Rect bb(button->bounds.left + 5, button->bounds.top + 5, + button->bounds.right - 5, button->bounds.bottom - 5); + + Design::drawFilledRect(&_gui->_screen, bb, kColorBlack, _gui->_patterns, kPatternSolid); + + color = kColorWhite; + } + int w = _font->getStringWidth(button->text); + int x = button->bounds.left + (button->bounds.width() - w) / 2; + int y = button->bounds.top + 6; + + _font->drawString(&_gui->_screen, button->text, x, y, _bbox.width(), color); + + drawOutline(button->bounds, buttonOutline, ARRAYSIZE(buttonOutline)); + } + + g_system->copyRectToScreen(_gui->_screen.getBasePtr(_bbox.left, _bbox.top), _gui->_screen.pitch, + _bbox.left, _bbox.top, _bbox.width() + 1, _bbox.height() + 1); + + _needsRedraw = false; +} + +void Dialog::drawOutline(Common::Rect &bounds, int *spec, int speclen) { + for (int i = 0; i < speclen; i++) + if (spec[i] != 0) + Design::drawRect(&_gui->_screen, bounds.left + i, bounds.top + i, bounds.right - i, bounds.bottom - i, + 1, kColorBlack, _gui->_patterns, kPatternSolid); +} + +int Dialog::run() { + bool shouldQuit = false; + Common::Rect r(_bbox); + + _tempSurface.copyRectToSurface(_gui->_screen.getBasePtr(_bbox.left, _bbox.top), _gui->_screen.pitch, 0, 0, _bbox.width() + 1, _bbox.height() + 1); + _gui->pushArrowCursor(); + + while (!shouldQuit) { + Common::Event event; + + while (_gui->_engine->_eventMan->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_QUIT: + _gui->_engine->_shouldQuit = true; + shouldQuit = true; + break; + case Common::EVENT_MOUSEMOVE: + mouseMove(event.mouse.x, event.mouse.y); + break; + case Common::EVENT_LBUTTONDOWN: + mouseClick(event.mouse.x, event.mouse.y); + break; + case Common::EVENT_LBUTTONUP: + shouldQuit = mouseRaise(event.mouse.x, event.mouse.y); + break; + case Common::EVENT_KEYDOWN: + switch (event.kbd.keycode) { + case Common::KEYCODE_ESCAPE: + _pressedButton = -1; + shouldQuit = true; + default: + break; + } + break; + default: + break; + } + } + + if (_needsRedraw) + paint(); + + g_system->updateScreen(); + g_system->delayMillis(50); + } + + _gui->_screen.copyRectToSurface(_tempSurface.getBasePtr(0, 0), _tempSurface.pitch, _bbox.left, _bbox.top, _bbox.width() + 1, _bbox.height() + 1); + g_system->copyRectToScreen(_gui->_screen.getBasePtr(r.left, r.top), _gui->_screen.pitch, r.left, r.top, r.width() + 1, r.height() + 1); + + _gui->popCursor(); + + return _pressedButton; +} + +int Dialog::matchButton(int x, int y) { + for (uint i = 0; i < _buttons->size(); i++) + if (_buttons->operator[](i)->bounds.contains(x, y)) + return i; + + return -1; +} + +void Dialog::mouseMove(int x, int y) { + if (_pressedButton != -1) { + int match = matchButton(x, y); + + if (_mouseOverPressedButton && match != _pressedButton) { + _mouseOverPressedButton = false; + _needsRedraw = true; + } else if (!_mouseOverPressedButton && match == _pressedButton) { + _mouseOverPressedButton = true; + _needsRedraw = true; + } + } +} + +void Dialog::mouseClick(int x, int y) { + int match = matchButton(x, y); + + if (match != -1) { + _pressedButton = match; + _mouseOverPressedButton = true; + + _needsRedraw = true; + } +} + +int Dialog::mouseRaise(int x, int y) { + bool res = false; + + if (_pressedButton != -1) { + if (matchButton(x, y) == _pressedButton) + res = true; + } + + return res; +} + +} // End of namespace Wage diff --git a/engines/wage/dialog.h b/engines/wage/dialog.h new file mode 100644 index 0000000000..c5878acc95 --- /dev/null +++ b/engines/wage/dialog.h @@ -0,0 +1,101 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef WAGE_DIALOG_H +#define WAGE_DIALOG_H + +namespace Wage { + +struct DialogButton { + Common::String text; + Common::Rect bounds; + + DialogButton(const char *t, int x1, int y1, int w, int h) { + text = t; + bounds.left = x1; + bounds.top = y1; + bounds.right = x1 + w - 1; + bounds.bottom = y1 + h - 1; + } +}; + +typedef Common::Array<DialogButton *> DialogButtonArray; + +class Dialog { +public: + Dialog(Gui *gui, int width, const char *text, DialogButtonArray *buttons, uint defaultButton); + ~Dialog(); + + int run(); + +private: + Gui *_gui; + Graphics::Surface _tempSurface; + Common::Rect _bbox; + Common::String _text; + + const Graphics::Font *_font; + DialogButtonArray *_buttons; + int _pressedButton; + uint _defaultButton; + bool _mouseOverPressedButton; + + bool _needsRedraw; + +private: + const Graphics::Font *getDialogFont(); + void drawOutline(Common::Rect &bounds, int *spec, int speclen); + void paint(); + void mouseMove(int x, int y); + void mouseClick(int x, int y); + int mouseRaise(int x, int y); + int matchButton(int x, int y); +}; + +} // End of namespace Wage + +#endif diff --git a/engines/wage/entities.cpp b/engines/wage/entities.cpp new file mode 100644 index 0000000000..a2648c49fe --- /dev/null +++ b/engines/wage/entities.cpp @@ -0,0 +1,514 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "wage/wage.h" +#include "wage/entities.h" +#include "wage/design.h" +#include "wage/script.h" +#include "wage/world.h" + +#include "common/memstream.h" + +namespace Wage { + +void Designed::setDesignBounds(Common::Rect *bounds) { + _designBounds = bounds; + _design->setBounds(bounds); +} + +Designed::~Designed() { + delete _design; + delete _designBounds; +} + +Context::Context() { + _visits = 0; + _kills = 0; + _experience = 0; + _frozen = false; + + for (int i = 0; i < 26 * 9; i++) + _userVariables[i] = 0; + + for (int i = 0; i < 18; i++) + _statVariables[i] = 0; +} + +Scene::Scene() { + _script = NULL; + _design = NULL; + _textBounds = NULL; + _fontSize = 0; + _fontType = 0; + + for (int i = 0; i < 4; i++) + _blocked[i] = false; + + _soundFrequency = 0; + _soundType = 0; + _worldX = 0; + _worldY = 0; + + _visited = false; +} + +Scene::Scene(Common::String name, Common::SeekableReadStream *data) { + debug(9, "Creating scene: %s", name.c_str()); + + _name = name; + _classType = SCENE; + _design = new Design(data); + + _script = NULL; + _textBounds = NULL; + _fontSize = 0; + _fontType = 0; + + setDesignBounds(readRect(data)); + _worldY = data->readSint16BE(); + _worldX = data->readSint16BE(); + _blocked[NORTH] = (data->readByte() != 0); + _blocked[SOUTH] = (data->readByte() != 0); + _blocked[EAST] = (data->readByte() != 0); + _blocked[WEST] = (data->readByte() != 0); + _soundFrequency = data->readSint16BE(); + _soundType = data->readByte(); + data->readByte(); // unknown + _messages[NORTH] = readPascalString(data); + _messages[SOUTH] = readPascalString(data); + _messages[EAST] = readPascalString(data); + _messages[WEST] = readPascalString(data); + _soundName = readPascalString(data); + + _visited = false; + + delete data; +} + +Scene::~Scene() { + delete _script; + delete _textBounds; +} + +void Scene::paint(Graphics::Surface *surface, int x, int y) { + Common::Rect r(x + 5, y + 5, _design->getBounds()->width() + x - 10, _design->getBounds()->height() + y - 10); + surface->fillRect(r, kColorWhite); + + _design->paint(surface, ((WageEngine *)g_engine)->_world->_patterns, x, y); + + for (ObjList::const_iterator it = _objs.begin(); it != _objs.end(); ++it) { + debug(2, "paining Obj: %s", (*it)->_name.c_str()); + (*it)->_design->paint(surface, ((WageEngine *)g_engine)->_world->_patterns, x, y); + } + + for (ChrList::const_iterator it = _chrs.begin(); it != _chrs.end(); ++it) { + debug(2, "paining Chr: %s", (*it)->_name.c_str()); + (*it)->_design->paint(surface, ((WageEngine *)g_engine)->_world->_patterns, x, y); + } +} + +// Source: Apple IIGS Technical Note #41, "Font Family Numbers" +// http://apple2.boldt.ca/?page=til/tn.iigs.041 +static const char *const fontNames[] = { + "Chicago", // system font + "Geneva", // application font + "New York", + "Geneva", + + "Monaco", + "Venice", + "London", + "Athens", + + "San Francisco", + "Toronto", + NULL, + "Cairo", + "Los Angeles", // 12 + + "Zapf Dingbats", + "Bookman", + "Helvetica Narrow", + "Palatino", + NULL, + "Zapf Chancery", + NULL, + + "Times", // 20 + "Helvetica", + "Courier", + "Symbol", + "Taliesin", // mobile? + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, // 30 + NULL, + NULL, + "Avant Garde", + "New Century Schoolbook" +}; + +const char *Scene::getFontName() { + if (_fontType >= 0 && _fontType < ARRAYSIZE(fontNames) && fontNames[_fontType] != NULL) { + return fontNames[_fontType]; + } + return "Unknown"; +} + +Obj::Obj() : _currentOwner(NULL), _currentScene(NULL) { + _index = 0; + _namePlural = false; + _value = 0; + _attackType = 0; + _numberOfUses = 0; + _returnToRandomScene = false; + _type = 0; + _accuracy = 0; + _damage = 0; +} + +Obj::Obj(Common::String name, Common::SeekableReadStream *data) { + _name = name; + _classType = OBJ; + _currentOwner = NULL; + _currentScene = NULL; + + _index = 0; + + _design = new Design(data); + + setDesignBounds(readRect(data)); + + int16 namePlural = data->readSint16BE(); + + if (namePlural == 256) + _namePlural = true; // TODO: other flags? + else if (namePlural == 0) + _namePlural = false; + else + error("Obj <%s> had weird namePlural set (%d)", name.c_str(), namePlural); + + if (data->readSint16BE() != 0) + error("Obj <%s> had short set", name.c_str()); + + if (data->readByte() != 0) + error("Obj <%s> had byte set", name.c_str()); + + _accuracy = data->readByte(); + _value = data->readByte(); + _type = data->readSByte(); + _damage = data->readByte(); + _attackType = data->readSByte(); + _numberOfUses = data->readSint16BE(); + int16 returnTo = data->readSint16BE(); + if (returnTo == 256) // TODO any other possibilities? + _returnToRandomScene = true; + else if (returnTo == 0) + _returnToRandomScene = false; + else + error("Obj <%s> had weird returnTo set", name.c_str()); + + _sceneOrOwner = readPascalString(data); + _clickMessage = readPascalString(data); + _operativeVerb = readPascalString(data); + _failureMessage = readPascalString(data); + _useMessage = readPascalString(data); + _sound = readPascalString(data); + + delete data; +} + +Obj::~Obj() { +} + +Chr *Obj::removeFromChr() { + if (_currentOwner != NULL) { + for (int i = (int)_currentOwner->_inventory.size() - 1; i >= 0; i--) + if (_currentOwner->_inventory[i] == this) + _currentOwner->_inventory.remove_at(i); + + for (int i = 0; i < Chr::NUMBER_OF_ARMOR_TYPES; i++) { + if (_currentOwner->_armor[i] == this) { + _currentOwner->_armor[i] = NULL; + } + } + } + + return _currentOwner; +} + +Designed *Obj::removeFromCharOrScene() { + Designed *from = removeFromChr(); + + if (_currentScene != NULL) { + _currentScene->_objs.remove(this); + from = _currentScene; + } + + return from; +} + +void Obj::resetState(Chr *owner, Scene *scene) { + warning("STUB: Obj::resetState()"); +} + +Chr::Chr(Common::String name, Common::SeekableReadStream *data) { + _name = name; + _classType = CHR; + _design = new Design(data); + + _index = 0; + _currentScene = NULL; + + setDesignBounds(readRect(data)); + + _physicalStrength = data->readByte(); + _physicalHp = data->readByte(); + _naturalArmor = data->readByte(); + _physicalAccuracy = data->readByte(); + + _spiritualStength = data->readByte(); + _spiritialHp = data->readByte(); + _resistanceToMagic = data->readByte(); + _spiritualAccuracy = data->readByte(); + + _runningSpeed = data->readByte(); + _rejectsOffers = data->readByte(); + _followsOpponent = data->readByte(); + + data->readSByte(); // TODO: ??? + data->readSint32BE(); // TODO: ??? + + _weaponDamage1 = data->readByte(); + _weaponDamage2 = data->readByte(); + + data->readSByte(); // TODO: ??? + + if (data->readSByte() == 1) + _playerCharacter = true; + else + _playerCharacter = false; + + _maximumCarriedObjects = data->readByte(); + _returnTo = data->readSByte(); + + _winningWeapons = data->readByte(); + _winningMagic = data->readByte(); + _winningRun = data->readByte(); + _winningOffer = data->readByte(); + _losingWeapons = data->readByte(); + _losingMagic = data->readByte(); + _losingRun = data->readByte(); + _losingOffer = data->readByte(); + + _gender = data->readSByte(); + if (data->readSByte() == 1) + _nameProperNoun = true; + else + _nameProperNoun = false; + + _initialScene = readPascalString(data); + _nativeWeapon1 = readPascalString(data); + _operativeVerb1 = readPascalString(data); + _nativeWeapon2 = readPascalString(data); + _operativeVerb2 = readPascalString(data); + + _initialComment = readPascalString(data); + _scoresHitComment = readPascalString(data); + _receivesHitComment = readPascalString(data); + _makesOfferComment = readPascalString(data); + _rejectsOfferComment = readPascalString(data); + _acceptsOfferComment = readPascalString(data); + _dyingWords = readPascalString(data); + + _initialSound = readPascalString(data); + _scoresHitSound = readPascalString(data); + _receivesHitSound = readPascalString(data); + _dyingSound = readPascalString(data); + + _weaponSound1 = readPascalString(data); + _weaponSound2 = readPascalString(data); + + for (int i = 0; i < NUMBER_OF_ARMOR_TYPES; i++) + _armor[i] = NULL; + + _weapon1 = NULL; + _weapon2 = NULL; + + // Create native weapons + if (!_nativeWeapon1.empty() && !_operativeVerb1.empty()) { + _weapon1 = new Obj; + + _weapon1->_name = _nativeWeapon1; + _weapon1->_operativeVerb = _operativeVerb1; + _weapon1->_type = Obj::REGULAR_WEAPON; + _weapon1->_accuracy = 0; + _weapon1->_damage = _weaponDamage1; + _weapon1->_sound = _weaponSound1; + } + + if (!_nativeWeapon2.empty() && !_operativeVerb2.empty()) { + _weapon2 = new Obj; + + _weapon2->_name = _nativeWeapon2; + _weapon2->_operativeVerb = _operativeVerb2; + _weapon2->_type = Obj::REGULAR_WEAPON; + _weapon2->_accuracy = 0; + _weapon2->_damage = _weaponDamage2; + _weapon2->_sound = _weaponSound2; + } + + delete data; +} + +void Chr::resetState() { + _context._statVariables[PHYS_STR_BAS] = _context._statVariables[PHYS_STR_CUR] = _physicalStrength; + _context._statVariables[PHYS_HIT_BAS] = _context._statVariables[PHYS_HIT_CUR] = _physicalHp; + _context._statVariables[PHYS_ARM_BAS] = _context._statVariables[PHYS_ARM_CUR] = _naturalArmor; + _context._statVariables[PHYS_ACC_BAS] = _context._statVariables[PHYS_ACC_CUR] = _physicalAccuracy; + + _context._statVariables[SPIR_STR_BAS] = _context._statVariables[SPIR_STR_CUR] = _spiritualStength; + _context._statVariables[SPIR_HIT_BAS] = _context._statVariables[SPIR_HIT_CUR] = _spiritialHp; + _context._statVariables[SPIR_ARM_BAS] = _context._statVariables[SPIR_ARM_CUR] = _naturalArmor; + _context._statVariables[SPIR_ACC_BAS] = _context._statVariables[SPIR_ACC_CUR] = _physicalAccuracy; + + _context._statVariables[PHYS_SPE_BAS] = _context._statVariables[PHYS_SPE_CUR] = _runningSpeed; +} + +ObjArray *Chr::getWeapons(bool includeMagic) { + ObjArray *list = new ObjArray; + + if (_weapon1) + list->push_back(_weapon1); + + if (_weapon2) + list->push_back(_weapon2); + + for (uint i = 0; i < _inventory.size(); i++) + switch (_inventory[i]->_type) { + case Obj::REGULAR_WEAPON: + case Obj::THROW_WEAPON: + list->push_back(_inventory[i]); + break; + case Obj::MAGICAL_OBJECT: + if (includeMagic) + list->push_back(_inventory[i]); + break; + default: + break; + } + + return list; +} + +ObjArray *Chr::getMagicalObjects() { + ObjArray *list = new ObjArray; + + for (uint i = 0; i < _inventory.size(); i++) + if (_inventory[i]->_type == Obj::MAGICAL_OBJECT) + list->push_back(_inventory[i]); + + return list; +} + +void Chr::wearObjs() { + for (uint i = 0; i < _inventory.size(); i++) + wearObjIfPossible(_inventory[i]); +} + +int Chr::wearObjIfPossible(Obj *obj) { + switch (obj->_type) { + case Obj::HELMET: + if (_armor[HEAD_ARMOR] == NULL) { + _armor[HEAD_ARMOR] = obj; + return Chr::HEAD_ARMOR; + } + break; + case Obj::CHEST_ARMOR: + if (_armor[BODY_ARMOR] == NULL) { + _armor[BODY_ARMOR] = obj; + return Chr::BODY_ARMOR; + } + break; + case Obj::SHIELD: + if (_armor[SHIELD_ARMOR] == NULL) { + _armor[SHIELD_ARMOR] = obj; + return Chr::SHIELD_ARMOR; + } + break; + case Obj::SPIRITUAL_ARMOR: + if (_armor[MAGIC_ARMOR] == NULL) { + _armor[MAGIC_ARMOR] = obj; + return Chr::MAGIC_ARMOR; + } + break; + default: + return -1; + } + + return -1; +} + +const char *Chr::getDefiniteArticle(bool capitalize) { + if (!_nameProperNoun) + return capitalize ? "The " : "the "; + + return ""; +} + +bool Chr::isWearing(Obj *obj) { + for (int i = 0; i < NUMBER_OF_ARMOR_TYPES; i++) + if (_armor[i] == obj) + return true; + + return false; +} + +} // End of namespace Wage diff --git a/engines/wage/entities.h b/engines/wage/entities.h new file mode 100644 index 0000000000..33cf087322 --- /dev/null +++ b/engines/wage/entities.h @@ -0,0 +1,336 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef WAGE_ENTITIES_H +#define WAGE_ENTITIES_H + +namespace Graphics { + struct Surface; +} + +namespace Wage { + +class Design; +class Script; + +enum StatVariable { +/** The base physical accuracy of the player. */ + PHYS_ACC_BAS = 0, +/** The current physical accuracy of the player. */ + PHYS_ACC_CUR = 1, +/** The base physical armor of the player. */ + PHYS_ARM_BAS = 2, +/** The current physical armor of the player. */ + PHYS_ARM_CUR = 3, +/** The base physical hit points of the player. */ + PHYS_HIT_BAS = 4, +/** The current physical hit points of the player. */ + PHYS_HIT_CUR = 5, +/** The base physical speed of the player. */ + PHYS_SPE_BAS = 6, +/** The current physical speed of the player. */ + PHYS_SPE_CUR = 7, +/** The base physical strength of the player. */ + PHYS_STR_BAS = 8, +/** The current physical strength of the player. */ + PHYS_STR_CUR = 9, +/** The base spiritual accuracy of the player. */ + SPIR_ACC_BAS = 10, +/** The current spiritual accuracy of the player. */ + SPIR_ACC_CUR = 11, +/** The base spiritual armor of the player. */ + SPIR_ARM_BAS = 12, +/** The current spiritual armor of the player. */ + SPIR_ARM_CUR = 13, +/** The base spiritual hit points of the player. */ + SPIR_HIT_BAS = 14, +/** The current spiritual hit points of the player. */ + SPIR_HIT_CUR = 15, +/** The base spiritual strength of the player. */ + SPIR_STR_BAS = 16, +/** The current spiritual strength of the player. */ + SPIR_STR_CUR = 17 +}; + +class Context { +public: + Context(); + + int16 _visits; // Number of scenes visited, including repeated visits + int16 _kills; // Number of characters killed + int16 _experience; + bool _frozen; + int16 _userVariables[26 * 9]; + int16 _statVariables[18]; +}; + +class Designed { +public: + Designed() : _design(NULL), _designBounds(NULL), _classType(UNKNOWN) {} + ~Designed(); + + Common::String _name; + Design *_design; + Common::Rect *_designBounds; + OperandType _classType; + + Common::Rect *getDesignBounds() { + return _designBounds == NULL ? NULL : new Common::Rect(*_designBounds); + } + + void setDesignBounds(Common::Rect *bounds); + + Common::String toString() { if (!this) return "<NULL>"; return _name; } +}; + +class Chr : public Designed { +public: + enum ChrDestination { + RETURN_TO_STORAGE = 0, + RETURN_TO_RANDOM_SCENE = 1, + RETURN_TO_INITIAL_SCENE = 2 + }; + + enum ChrPart { + HEAD = 0, + CHEST = 1, + SIDE = 2 + }; + + enum ChrArmorType { + HEAD_ARMOR = 0, + BODY_ARMOR = 1, + SHIELD_ARMOR = 2, + MAGIC_ARMOR = 3, + NUMBER_OF_ARMOR_TYPES = 4 + }; + + Chr(Common::String name, Common::SeekableReadStream *data); + + int _index; + Common::String _initialScene; + int _gender; + bool _nameProperNoun; + bool _playerCharacter; + uint _maximumCarriedObjects; + int _returnTo; + + int _physicalStrength; + int _physicalHp; + int _naturalArmor; + int _physicalAccuracy; + int _spiritualStength; + int _spiritialHp; + int _resistanceToMagic; + int _spiritualAccuracy; + int _runningSpeed; + uint _rejectsOffers; + int _followsOpponent; + + Common::String _initialSound; + Common::String _scoresHitSound; + Common::String _receivesHitSound; + Common::String _dyingSound; + + Common::String _nativeWeapon1; + Common::String _operativeVerb1; + int _weaponDamage1; + Common::String _weaponSound1; + + Common::String _nativeWeapon2; + Common::String _operativeVerb2; + int _weaponDamage2; + Common::String _weaponSound2; + + int _winningWeapons; + int _winningMagic; + int _winningRun; + int _winningOffer; + int _losingWeapons; + int _losingMagic; + int _losingRun; + int _losingOffer; + + Common::String _initialComment; + Common::String _scoresHitComment; + Common::String _receivesHitComment; + Common::String _makesOfferComment; + Common::String _rejectsOfferComment; + Common::String _acceptsOfferComment; + Common::String _dyingWords; + + Scene *_currentScene; + ObjArray _inventory; + + Obj *_armor[NUMBER_OF_ARMOR_TYPES]; + + Context _context; + + ObjArray *getWeapons(bool includeMagic); + ObjArray *getMagicalObjects(); + const char *getDefiniteArticle(bool capitalize); + + Obj *_weapon1; + Obj *_weapon2; + +public: + int wearObjIfPossible(Obj *obj); + void wearObjs(); + + void resetState(); + + bool isWearing(Obj *obj); +}; + +class Obj : public Designed { +public: + Obj(); + Obj(Common::String name, Common::SeekableReadStream *data); + ~Obj(); + + enum ObjectType { + REGULAR_WEAPON = 1, + THROW_WEAPON = 2, + MAGICAL_OBJECT = 3, + HELMET = 4, + SHIELD = 5, + CHEST_ARMOR = 6, + SPIRITUAL_ARMOR = 7, + MOBILE_OBJECT = 8, + IMMOBILE_OBJECT = 9 + }; + + enum AttackType { + CAUSES_PHYSICAL_DAMAGE = 0, + CAUSES_SPIRITUAL_DAMAGE = 1, + CAUSES_PHYSICAL_AND_SPIRITUAL_DAMAGE = 2, + HEALS_PHYSICAL_DAMAGE = 3, + HEALS_SPIRITUAL_DAMAGE = 4, + HEALS_PHYSICAL_AND_SPIRITUAL_DAMAGE = 5, + FREEZES_OPPONENT = 6 + }; + +public: + int _index; + bool _namePlural; + uint _value; + int _attackType; + int _numberOfUses; + bool _returnToRandomScene; + Common::String _sceneOrOwner; + Common::String _clickMessage; + Common::String _failureMessage; + Common::String _useMessage; + + Scene *_currentScene; + Chr *_currentOwner; + + int _type; + uint _accuracy; + Common::String _operativeVerb; + int _damage; + Common::String _sound; + +public: + void setCurrentOwner(Chr *currentOwner) { + _currentOwner = currentOwner; + if (currentOwner != NULL) + _currentScene = NULL; + } + + void setCurrentScene(Scene *currentScene) { + _currentScene = currentScene; + if (currentScene != NULL) + _currentOwner = NULL; + } + + Chr *removeFromChr(); + Designed *removeFromCharOrScene(); + + void resetState(Chr *owner, Scene *scene); +}; + +class Scene : public Designed { +public: + enum SceneTypes { + PERIODIC = 0, + RANDOM = 1 + }; + + Script *_script; + Common::String _text; + Common::Rect *_textBounds; + int _fontSize; + int _fontType; // 3 => Geneva, 22 => Courier, param to TextFont() function + bool _blocked[4]; + Common::String _messages[4]; + int _soundFrequency; // times a minute, max 3600 + int _soundType; + Common::String _soundName; + int _worldX; + int _worldY; + bool _visited; + + ObjList _objs; + ChrList _chrs; + + Scene(); + Scene(Common::String name, Common::SeekableReadStream *data); + ~Scene(); + + Common::Rect *getTextBounds() { + return _textBounds == NULL ? NULL : new Common::Rect(*_textBounds); + } + + void paint(Graphics::Surface *screen, int x, int y); + + const char *getFontName(); +}; + +} // End of namespace Wage + +#endif diff --git a/engines/wage/gui-console.cpp b/engines/wage/gui-console.cpp new file mode 100644 index 0000000000..ab5df637ec --- /dev/null +++ b/engines/wage/gui-console.cpp @@ -0,0 +1,430 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "common/timer.h" +#include "common/unzip.h" +#include "graphics/cursorman.h" +#include "graphics/fonts/bdf.h" +#include "graphics/palette.h" + +#include "wage/wage.h" +#include "wage/design.h" +#include "wage/entities.h" +#include "wage/menu.h" +#include "wage/gui.h" +#include "wage/world.h" + +namespace Wage { + +const Graphics::Font *Gui::getConsoleFont() { + char fontName[128]; + Scene *scene = _engine->_world->_player->_currentScene; + + snprintf(fontName, 128, "%s-%d", scene->getFontName(), scene->_fontSize); + + return getFont(fontName, Graphics::FontManager::kConsoleFont); +} + +void Gui::clearOutput() { + _out.clear(); + _lines.clear(); + _consoleFullRedraw = true; +} + +void Gui::appendText(const char *s) { + Common::String str(s); + _consoleDirty = true; + + if (!str.contains('\n')) { + _out.push_back(str); + flowText(str); + return; + } + + // Okay, we got new lines, need to split it + // and push substrings individually + Common::String tmp; + + for (uint i = 0; i < str.size(); i++) { + if (str[i] == '\n') { + _out.push_back(tmp); + flowText(tmp); + tmp.clear(); + continue; + } + + tmp += str[i]; + } + + _out.push_back(tmp); + flowText(tmp); +} + +enum { + kConWOverlap = 20, + kConHOverlap = 20, + kConWPadding = 3, + kConHPadding = 4, + kConOverscan = 3 +}; + +void Gui::flowText(Common::String &str) { + Common::StringArray wrappedLines; + int textW = _consoleTextArea.width() - kConWPadding * 2; + const Graphics::Font *font = getConsoleFont(); + + font->wordWrapText(str, textW, wrappedLines); + + if (wrappedLines.empty()) // Sometimes we have empty lines + _lines.push_back(""); + + for (Common::StringArray::const_iterator j = wrappedLines.begin(); j != wrappedLines.end(); ++j) + _lines.push_back(*j); + + uint pos = _scrollPos; + _scrollPos = MAX<int>(0, (_lines.size() - 1 - _consoleNumLines) * _consoleLineHeight); + + _cursorX = kConWPadding; + + if (_scrollPos) + _cursorY = (_consoleNumLines) * _consoleLineHeight + kConHPadding; + else + _cursorY = (_lines.size() - 1) * _consoleLineHeight + kConHPadding; + + if (pos != _scrollPos) + _consoleFullRedraw = true; + + if (!_engine->_temporarilyHidden) + draw(); +} + +void Gui::renderConsole(Graphics::Surface *g, Common::Rect &r) { + bool fullRedraw = _consoleFullRedraw; + bool textReflow = false; + int surfW = r.width() + kConWOverlap * 2; + int surfH = r.height() + kConHOverlap * 2; + + Common::Rect boundsR(kConWOverlap - kConOverscan, kConHOverlap - kConOverscan, + r.width() + kConWOverlap + kConOverscan, r.height() + kConHOverlap + kConOverscan); + Common::Rect fullR(0, 0, surfW, surfH); + + if (_console.w != surfW || _console.h != surfH) { + if (_console.w != surfW) + textReflow = true; + + _console.free(); + + _console.create(surfW, surfH, Graphics::PixelFormat::createFormatCLUT8()); + fullRedraw = true; + } + + if (fullRedraw) + _console.fillRect(fullR, kColorWhite); + + const Graphics::Font *font = getConsoleFont(); + + _consoleLineHeight = font->getFontHeight(); + int textW = r.width() - kConWPadding * 2; + int textH = r.height() - kConHPadding * 2; + + if (textReflow) { + _lines.clear(); + + for (uint i = 0; i < _out.size(); i++) + flowText(_out[i]); + } + + const int firstLine = _scrollPos / _consoleLineHeight; + const int lastLine = MIN((_scrollPos + textH) / _consoleLineHeight + 1, _lines.size()); + const int xOff = kConWOverlap; + const int yOff = kConHOverlap; + int x1 = xOff + kConWPadding; + int y1 = yOff - (_scrollPos % _consoleLineHeight) + kConHPadding; + + if (fullRedraw) + _consoleNumLines = (r.height() - 2 * kConWPadding) / _consoleLineHeight - 2; + + for (int line = firstLine; line < lastLine; line++) { + const char *str = _lines[line].c_str(); + int color = kColorBlack; + + if ((line > _selectionStartY && line < _selectionEndY) || + (line > _selectionEndY && line < _selectionStartY)) { + color = kColorWhite; + Common::Rect trect(0, y1, _console.w, y1 + _consoleLineHeight); + + Design::drawFilledRect(&_console, trect, kColorBlack, _patterns, kPatternSolid); + } + + if (line == _selectionStartY || line == _selectionEndY) { + if (_selectionStartY != _selectionEndY) { + int color1 = kColorBlack; + int color2 = kColorWhite; + int midpoint = _selectionStartX; + + if (_selectionStartY > _selectionEndY) + SWAP(color1, color2); + + if (line == _selectionEndY) { + SWAP(color1, color2); + midpoint = _selectionEndX; + } + + Common::String beg(_lines[line].c_str(), &_lines[line].c_str()[midpoint]); + Common::String end(&_lines[line].c_str()[midpoint]); + + int rectW = font->getStringWidth(beg) + kConWPadding + kConWOverlap; + Common::Rect trect(0, y1, _console.w, y1 + _consoleLineHeight); + if (color1 == kColorWhite) + trect.right = rectW; + else + trect.left = rectW; + + Design::drawFilledRect(&_console, trect, kColorBlack, _patterns, kPatternSolid); + + font->drawString(&_console, beg, x1, y1, textW, color1); + font->drawString(&_console, end, x1 + rectW - kConWPadding - kConWOverlap, y1, textW, color2); + } else { + int startPos = _selectionStartX; + int endPos = _selectionEndX; + + if (startPos > endPos) + SWAP(startPos, endPos); + + Common::String beg(_lines[line].c_str(), &_lines[line].c_str()[startPos]); + Common::String mid(&_lines[line].c_str()[startPos], &_lines[line].c_str()[endPos]); + Common::String end(&_lines[line].c_str()[endPos]); + + int rectW1 = font->getStringWidth(beg) + kConWPadding + kConWOverlap; + int rectW2 = rectW1 + font->getStringWidth(mid); + Common::Rect trect(rectW1, y1, rectW2, y1 + _consoleLineHeight); + + Design::drawFilledRect(&_console, trect, kColorBlack, _patterns, kPatternSolid); + + font->drawString(&_console, beg, x1, y1, textW, kColorBlack); + font->drawString(&_console, mid, x1 + rectW1 - kConWPadding - kConWOverlap, y1, textW, kColorWhite); + font->drawString(&_console, end, x1 + rectW2 - kConWPadding - kConWOverlap, y1, textW, kColorBlack); + } + } else { + if (*str) + font->drawString(&_console, _lines[line], x1, y1, textW, color); + } + + y1 += _consoleLineHeight; + } + + // Now we need to clip it to the screen + int xcon = r.left - kConOverscan; + int ycon = r.top - kConOverscan; + if (xcon < 0) { + boundsR.left -= xcon; + xcon = 0; + } + if (ycon < 0) { + boundsR.top -= ycon; + ycon = 0; + } + if (xcon + boundsR.width() >= g->w) + boundsR.right -= xcon + boundsR.width() - g->w; + if (ycon + boundsR.height() >= g->h) + boundsR.bottom -= ycon + boundsR.height() - g->h; + + Common::Rect rr(r); + if (rr.right > _screen.w - 1) + rr.right = _screen.w - 1; + if (rr.bottom > _screen.h - 1) + rr.bottom = _screen.h - 1; + + g->copyRectToSurface(_console, xcon, ycon, boundsR); + g_system->copyRectToScreen(g->getBasePtr(rr.left, rr.top), g->pitch, rr.left, rr.top, rr.width(), rr.height()); +} + +void Gui::drawInput() { + if (!_screen.getPixels()) + return; + + if (_sceneIsActive) { + _sceneIsActive = false; + _bordersDirty = true; + } + + _out.pop_back(); + _lines.pop_back(); + appendText(_engine->_inputText.c_str()); + _inputTextLineNum = _out.size() - 1; + + const Graphics::Font *font = getConsoleFont(); + + if (_engine->_inputText.contains('\n')) { + _consoleDirty = true; + } else { + int x = kConWPadding + _consoleTextArea.left; + int y = _cursorY + _consoleTextArea.top; + + Common::Rect r(x, y, x + _consoleTextArea.width() - kConWPadding, y + font->getFontHeight()); + _screen.fillRect(r, kColorWhite); + + undrawCursor(); + + font->drawString(&_screen, _out[_inputTextLineNum], x, y, _screen.w, kColorBlack); + + g_system->copyRectToScreen(_screen.getBasePtr(x, y), _screen.pitch, x, y, _consoleTextArea.width(), font->getFontHeight()); + } + + _cursorX = font->getStringWidth(_out[_inputTextLineNum]) + kConHPadding; +} + +void Gui::actionCopy() { + if (_selectionStartX == -1) + return; + + int startX = _selectionStartX; + int startY = _selectionStartY; + int endX = _selectionEndX; + int endY = _selectionEndY; + + if (startY > endY) { + SWAP(startX, endX); + SWAP(endX, endY); + } + + _clipboard.clear(); + + for (int i = startY; i <= endY; i++) { + if (startY == endY) { + _clipboard = Common::String(&_lines[i].c_str()[startX], &_lines[i].c_str()[endX]); + break; + } + + if (i == startY) { + _clipboard += &_lines[i].c_str()[startX]; + _clipboard += '\n'; + } else if (i == endY) { + _clipboard += Common::String(_lines[i].c_str(), &_lines[i].c_str()[endX]); + } else { + _clipboard += _lines[i]; + _clipboard += '\n'; + } + } + + _menu->enableCommand(kMenuEdit, kMenuActionPaste, true); +} + +void Gui::actionPaste() { + _undobuffer = _engine->_inputText; + _engine->_inputText += _clipboard; + drawInput(); + _engine->_inputText = _out.back(); // Set last part of the multiline text + + _menu->enableCommand(kMenuEdit, kMenuActionUndo, true); +} + +void Gui::actionUndo() { + _engine->_inputText = _undobuffer; + drawInput(); + + _menu->enableCommand(kMenuEdit, kMenuActionUndo, false); +} + +void Gui::actionClear() { + int startPos = _selectionStartX; + int endPos = _selectionEndX; + + if (startPos > endPos) + SWAP(startPos, endPos); + + Common::String beg(_lines[_selectionStartY].c_str(), &_lines[_selectionStartY].c_str()[startPos]); + Common::String end(&_lines[_selectionStartY].c_str()[endPos]); + + _undobuffer = _engine->_inputText; + _engine->_inputText = beg + end; + drawInput(); + + _menu->enableCommand(kMenuEdit, kMenuActionUndo, true); + + _selectionStartY = -1; + _selectionEndY = -1; +} + +void Gui::actionCut() { + int startPos = _selectionStartX; + int endPos = _selectionEndX; + + if (startPos > endPos) + SWAP(startPos, endPos); + + Common::String beg(_lines[_selectionStartY].c_str(), &_lines[_selectionStartY].c_str()[startPos]); + Common::String mid(&_lines[_selectionStartY].c_str()[startPos], &_lines[_selectionStartY].c_str()[endPos]); + Common::String end(&_lines[_selectionStartY].c_str()[endPos]); + + _undobuffer = _engine->_inputText; + _engine->_inputText = beg + end; + _clipboard = mid; + drawInput(); + + _menu->enableCommand(kMenuEdit, kMenuActionUndo, true); + _menu->enableCommand(kMenuEdit, kMenuActionPaste, true); + + _selectionStartY = -1; + _selectionEndY = -1; +} + +void Gui::disableUndo() { + _menu->enableCommand(kMenuEdit, kMenuActionUndo, false); +} + +void Gui::disableAllMenus() { + _menu->disableAllMenus(); +} + +void Gui::enableNewGameMenus() { + _menu->enableCommand(kMenuFile, kMenuActionNew, true); + _menu->enableCommand(kMenuFile, kMenuActionOpen, true); + _menu->enableCommand(kMenuFile, kMenuActionQuit, true); +} + +} // End of namespace Wage diff --git a/engines/wage/gui.cpp b/engines/wage/gui.cpp new file mode 100644 index 0000000000..9dd1a24b3c --- /dev/null +++ b/engines/wage/gui.cpp @@ -0,0 +1,675 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "common/timer.h" +#include "common/unzip.h" +#include "graphics/cursorman.h" +#include "graphics/fonts/bdf.h" +#include "graphics/palette.h" + +#include "wage/wage.h" +#include "wage/design.h" +#include "wage/entities.h" +#include "wage/menu.h" +#include "wage/gui.h" +#include "wage/world.h" + +namespace Wage { + +static const byte palette[] = { + 0, 0, 0, // Black + 0x80, 0x80, 0x80, // Gray + 0xff, 0xff, 0xff, // White + 0x00, 0xff, 0x00 // Green +}; + +static byte fillPatterns[][8] = { { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, // kPatternSolid + { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55 }, // kPatternStripes + { 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55 }, // kPatternCheckers + { 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa } // kPatternCheckers2 +}; + +static const byte macCursorArrow[] = { + 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 0, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 0, 0, 2, 3, 3, 3, 3, 3, 3, 3, + 2, 0, 0, 0, 2, 3, 3, 3, 3, 3, 3, + 2, 0, 0, 0, 0, 2, 3, 3, 3, 3, 3, + 2, 0, 0, 0, 0, 0, 2, 3, 3, 3, 3, + 2, 0, 0, 0, 0, 0, 0, 2, 3, 3, 3, + 2, 0, 0, 0, 0, 0, 0, 0, 2, 3, 3, + 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, + 2, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, + 2, 0, 0, 2, 0, 0, 2, 3, 3, 3, 3, + 2, 0, 2, 3, 2, 0, 0, 2, 3, 3, 3, + 2, 2, 3, 3, 2, 0, 0, 2, 3, 3, 3, + 2, 3, 3, 3, 3, 2, 0, 0, 2, 3, 3, + 3, 3, 3, 3, 3, 2, 0, 0, 2, 3, 3, + 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 3 +}; + +static const byte macCursorBeam[] = { + 0, 0, 3, 3, 3, 0, 0, 3, 3, 3, 3, + 3, 3, 0, 3, 0, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 0, 3, 0, 3, 3, 3, 3, 3, 3, + 0, 0, 3, 3, 3, 0, 0, 3, 3, 3, 3, +}; + +static void cursorTimerHandler(void *refCon) { + Gui *gui = (Gui *)refCon; + + int x = gui->_cursorX; + int y = gui->_cursorY; + + if (x == 0 && y == 0) + return; + + if (!gui->_screen.getPixels()) + return; + + x += gui->_consoleTextArea.left; + y += gui->_consoleTextArea.top; + + gui->_screen.vLine(x, y, y + kCursorHeight, gui->_cursorState ? kColorBlack : kColorWhite); + + if (!gui->_cursorOff) + gui->_cursorState = !gui->_cursorState; + + gui->_cursorRect.left = x; + gui->_cursorRect.right = MIN<uint16>(x + 1, gui->_screen.w); + gui->_cursorRect.top = y; + gui->_cursorRect.bottom = MIN<uint16>(y + kCursorHeight, gui->_screen.h); + + gui->_cursorDirty = true; +} + +Gui::Gui(WageEngine *engine) { + _engine = engine; + _scene = NULL; + _sceneDirty = true; + _consoleDirty = true; + _bordersDirty = true; + _menuDirty = true; + _cursorDirty = false; + _consoleFullRedraw = true; + _screen.create(g_system->getWidth(), g_system->getHeight(), Graphics::PixelFormat::createFormatCLUT8()); + + _scrollPos = 0; + _consoleLineHeight = 8; // Dummy value which makes sense + _consoleNumLines = 24; // Dummy value + _builtInFonts = false; + _sceneIsActive = false; + + _cursorX = 0; + _cursorY = 0; + _cursorState = false; + _cursorOff = false; + + _inTextSelection = false; + _selectionStartX = _selectionStartY = -1; + _selectionEndX = _selectionEndY = -1; + + _inputTextLineNum = 0; + + g_system->getPaletteManager()->setPalette(palette, 0, 4); + + CursorMan.replaceCursorPalette(palette, 0, 4); + CursorMan.replaceCursor(macCursorArrow, 11, 16, 1, 1, 3); + _cursorIsArrow = true; + CursorMan.showMouse(true); + + for (int i = 0; i < ARRAYSIZE(fillPatterns); i++) + _patterns.push_back(fillPatterns[i]); + + loadFonts(); + + g_system->getTimerManager()->installTimerProc(&cursorTimerHandler, 200000, this, "wageCursor"); + + _menu = new Menu(this); +} + +Gui::~Gui() { + _screen.free(); + _console.free(); + g_system->getTimerManager()->removeTimerProc(&cursorTimerHandler); + delete _menu; +} + +void Gui::undrawCursor() { + _cursorOff = true; + _cursorState = false; + cursorTimerHandler(this); + _cursorOff = false; +} + +const Graphics::Font *Gui::getFont(const char *name, Graphics::FontManager::FontUsage fallback) { + const Graphics::Font *font = 0; + + if (!_builtInFonts) { + font = FontMan.getFontByName(name); + + if (!font) + warning("Cannot load font %s", name); + } + + if (_builtInFonts || !font) + font = FontMan.getFontByUsage(fallback); + + return font; +} + +const Graphics::Font *Gui::getTitleFont() { + return getFont("Chicago-12", Graphics::FontManager::kBigGUIFont); +} + +void Gui::drawDesktop() { + // Draw desktop + Common::Rect r(0, 0, _screen.w - 1, _screen.h - 1); + Design::drawFilledRoundRect(&_screen, r, kDesktopArc, kColorBlack, _patterns, kPatternCheckers); + g_system->copyRectToScreen(_screen.getPixels(), _screen.pitch, 0, 0, _screen.w, _screen.h); +} + +void Gui::draw() { + if (_engine->_isGameOver) { + if (_menuDirty) { + drawDesktop(); + _menu->render(); + } + + _menuDirty = false; + + return; + } + + if (_scene != _engine->_world->_player->_currentScene || _sceneDirty) { + _scene = _engine->_world->_player->_currentScene; + + drawDesktop(); + + _sceneDirty = true; + _consoleDirty = true; + _menuDirty = true; + _consoleFullRedraw = true; + + _scene->paint(&_screen, _scene->_designBounds->left, _scene->_designBounds->top); + + _sceneArea.left = _scene->_designBounds->left + kBorderWidth - 2; + _sceneArea.top = _scene->_designBounds->top + kBorderWidth - 2; + _sceneArea.setWidth(_scene->_designBounds->width() - 2 * kBorderWidth); + _sceneArea.setHeight(_scene->_designBounds->height() - 2 * kBorderWidth); + + _consoleTextArea.left = _scene->_textBounds->left + kBorderWidth - 2; + _consoleTextArea.top = _scene->_textBounds->top + kBorderWidth - 2; + _consoleTextArea.setWidth(_scene->_textBounds->width() - 2 * kBorderWidth); + _consoleTextArea.setHeight(_scene->_textBounds->height() - 2 * kBorderWidth); + } + + if (_scene && (_bordersDirty || _sceneDirty)) + paintBorder(&_screen, _sceneArea, kWindowScene); + + // Render console + if (_consoleDirty || _consoleFullRedraw) + renderConsole(&_screen, _consoleTextArea); + + if (_bordersDirty || _consoleDirty || _consoleFullRedraw) + paintBorder(&_screen, _consoleTextArea, kWindowConsole); + + if (_menuDirty) + _menu->render(); + + if (_cursorDirty) { + g_system->copyRectToScreen(_screen.getBasePtr(_cursorRect.left, _cursorRect.top), _screen.pitch, + _cursorRect.left, _cursorRect.top, _cursorRect.width(), _cursorRect.height()); + + _cursorDirty = false; + } + + _sceneDirty = false; + _consoleDirty = false; + _bordersDirty = false; + _menuDirty = false; + _consoleFullRedraw = false; +} + +void Gui::drawBox(Graphics::Surface *g, int x, int y, int w, int h) { + Common::Rect r(x, y, x + w + 1, y + h + 1); + + g->fillRect(r, kColorWhite); + g->frameRect(r, kColorBlack); +} + +void Gui::fillRect(Graphics::Surface *g, int x, int y, int w, int h, int color) { + Common::Rect r(x, y, x + w, y + h); + + g->fillRect(r, color); +} + +#define ARROW_W 12 +#define ARROW_H 6 +const int arrowPixels[ARROW_H][ARROW_W] = { + {0,0,0,0,0,1,1,0,0,0,0,0}, + {0,0,0,0,1,1,1,1,0,0,0,0}, + {0,0,0,1,1,1,1,1,1,0,0,0}, + {0,0,1,1,1,1,1,1,1,1,0,0}, + {0,1,1,1,1,1,1,1,1,1,1,0}, + {1,1,1,1,1,1,1,1,1,1,1,1}}; + +void Gui::paintBorder(Graphics::Surface *g, Common::Rect &r, WindowType windowType, int highlightedPart) { + bool active = false, scrollable = false, closeable = false, drawTitle = false; + const int size = kBorderWidth; + int x = r.left - size; + int y = r.top - size; + int width = r.width() + 2 * size; + int height = r.height() + 2 * size; + + switch (windowType) { + case kWindowScene: + active = _sceneIsActive; + scrollable = false; + closeable = _sceneIsActive; + drawTitle = true; + break; + case kWindowConsole: + active = !_sceneIsActive; + scrollable = true; + closeable = !_sceneIsActive; + drawTitle = false; + break; + } + + drawBox(g, x, y, size, size); + drawBox(g, x + width - size - 1, y, size, size); + drawBox(g, x + width - size - 1, y + height - size - 1, size, size); + drawBox(g, x, y + height - size - 1, size, size); + drawBox(g, x + size, y + 2, width - 2 * size - 1, size - 4); + drawBox(g, x + size, y + height - size + 1, width - 2 * size - 1, size - 4); + drawBox(g, x + 2, y + size, size - 4, height - 2 * size - 1); + drawBox(g, x + width - size + 1, y + size, size - 4, height - 2 * size - 1); + + if (active) { + fillRect(g, x + size, y + 5, width - 2 * size - 1, 8); + fillRect(g, x + size, y + height - 13, width - 2 * size - 1, 8); + fillRect(g, x + 5, y + size, 8, height - 2 * size - 1); + if (!scrollable) { + fillRect(g, x + width - 13, y + size, 8, height - 2 * size - 1); + } else { + int x1 = x + width - 15; + int y1 = y + size + 1; + int color1 = kColorBlack; + int color2 = kColorWhite; + if (highlightedPart == kBorderScrollUp) { + SWAP(color1, color2); + fillRect(g, x + width - kBorderWidth + 2, y + size, size - 4, r.height() / 2); + } + for (int yy = 0; yy < ARROW_H; yy++) { + for (int xx = 0; xx < ARROW_W; xx++) { + if (arrowPixels[yy][xx] != 0) { + g->hLine(x1 + xx, y1 + yy, x1 + xx, color1); + } else { + g->hLine(x1 + xx, y1 + yy, x1 + xx, color2); + } + } + } + fillRect(g, x + width - 13, y + size + ARROW_H, 8, r.height() / 2 - ARROW_H, color1); + + color1 = kColorBlack; + color2 = kColorWhite; + if (highlightedPart == kBorderScrollDown) { + SWAP(color1, color2); + fillRect(g, x + width - kBorderWidth + 2, y + size + r.height() / 2, size - 4, r.height() / 2); + } + fillRect(g, x + width - 13, y + size + r.height() / 2, 8, r.height() / 2 - ARROW_H, color1); + y1 += height - 2 * size - ARROW_H - 2; + for (int yy = 0; yy < ARROW_H; yy++) { + for (int xx = 0; xx < ARROW_W; xx++) { + if (arrowPixels[ARROW_H - yy - 1][xx] != 0) { + g->hLine(x1 + xx, y1 + yy, x1 + xx, color1); + } else { + g->hLine(x1 + xx, y1 + yy, x1 + xx, color2); + } + } + } + } + if (closeable) { + if (highlightedPart == kBorderCloseButton) { + fillRect(g, x + 6, y + 6, 6, 6); + } else { + drawBox(g, x + 5, y + 5, 7, 7); + } + } + } + + if (drawTitle) { + const Graphics::Font *font = getTitleFont(); + int yOff = _builtInFonts ? 3 : 1; + + int w = font->getStringWidth(_scene->_name) + 10; + int maxWidth = width - size * 2 - 7; + if (w > maxWidth) + w = maxWidth; + drawBox(g, x + (width - w) / 2, y, w, size); + font->drawString(g, _scene->_name, x + (width - w) / 2 + 5, y + yOff, w, kColorBlack); + } + + if (x < 0) { + width += x; + x = 0; + } + if (y < 0) { + height += y; + y = 0; + } + if (x + width > _screen.w) + width = _screen.w - x; + if (y + height > _screen.h) + height = _screen.h - y; + + g_system->copyRectToScreen(g->getBasePtr(x, y), g->pitch, x, y, width, height); +} + +void Gui::loadFonts() { + Common::Archive *dat; + + dat = Common::makeZipArchive("wage.dat"); + + if (!dat) { + warning("Could not find wage.dat. Falling back to built-in fonts"); + _builtInFonts = true; + + return; + } + + Common::ArchiveMemberList list; + dat->listMembers(list); + + for (Common::ArchiveMemberList::iterator it = list.begin(); it != list.end(); ++it) { + Common::SeekableReadStream *stream = dat->createReadStreamForMember((*it)->getName()); + + Graphics::BdfFont *font = Graphics::BdfFont::loadFont(*stream); + + delete stream; + + Common::String fontName = (*it)->getName(); + + // Trim the .bdf extension + for (int i = fontName.size() - 1; i >= 0; --i) { + if (fontName[i] == '.') { + while ((uint)i < fontName.size()) { + fontName.deleteLastChar(); + } + break; + } + } + + FontMan.assignFontToName(fontName, font); + + debug(2, " %s", fontName.c_str()); + } + + _builtInFonts = false; + + delete dat; +} + +void Gui::regenCommandsMenu() { + _menu->regenCommandsMenu(); +} + +void Gui::regenWeaponsMenu() { + _menu->regenWeaponsMenu(); +} + +void Gui::processMenuShortCut(byte flags, uint16 ascii) { + _menu->processMenuShortCut(flags, ascii); +} + +void Gui::mouseMove(int x, int y) { + if (_menu->_menuActivated) { + if (_menu->mouseMove(x, y)) + _menuDirty = true; + + return; + } + + if (_inTextSelection) { + updateTextSelection(x, y); + return; + } + + if (_consoleTextArea.contains(x, y)) { + if (_cursorIsArrow) { + CursorMan.replaceCursor(macCursorBeam, 11, 16, 3, 8, 3); + _cursorIsArrow = false; + } + } else if (_cursorIsArrow == false) { + CursorMan.replaceCursor(macCursorArrow, 11, 16, 1, 1, 3); + _cursorIsArrow = true; + } +} + +void Gui::pushArrowCursor() { + CursorMan.pushCursor(macCursorArrow, 11, 16, 1, 1, 3); +} + +void Gui::popCursor() { + CursorMan.popCursor(); +} + +static int isInBorder(Common::Rect &rect, int x, int y) { + if (x >= rect.left - kBorderWidth && x < rect.left && y >= rect.top - kBorderWidth && y < rect.top) + return kBorderCloseButton; + + if (x >= rect.right && x < rect.right + kBorderWidth) { + if (y < rect.top - kBorderWidth) + return kBorderNone; + + if (y >= rect.bottom + kBorderWidth) + return kBorderNone; + + if (y >= rect.top + rect.height() / 2) + return kBorderScrollDown; + + return kBorderScrollUp; + } + + return kBorderNone; +} + +Designed *Gui::mouseUp(int x, int y) { + if (_menu->_menuActivated) { + if (_menu->mouseRelease(x, y)) { + _sceneDirty = true; + _consoleDirty = true; + _bordersDirty = true; + _menuDirty = true; + } + + return NULL; + } + + if (_inTextSelection) { + _inTextSelection = false; + + if (_selectionEndY == -1 || + (_selectionEndX == _selectionStartX && _selectionEndY == _selectionStartY)) { + _selectionStartY = _selectionEndY = -1; + _consoleFullRedraw = true; + _menu->enableCommand(kMenuEdit, kMenuActionCopy, false); + } else { + _menu->enableCommand(kMenuEdit, kMenuActionCopy, true); + + bool cutAllowed = false; + + if (_selectionStartY == _selectionEndY && _selectionStartY == (int)_lines.size() - 1) + cutAllowed = true; + + _menu->enableCommand(kMenuEdit, kMenuActionCut, cutAllowed); + _menu->enableCommand(kMenuEdit, kMenuActionClear, cutAllowed); + } + } + + int borderClick; + + if (_sceneArea.contains(x, y)) { + if (!_sceneIsActive) { + _sceneIsActive = true; + _bordersDirty = true; + } + + for (ObjList::const_iterator it = _scene->_objs.begin(); it != _scene->_objs.end(); ++it) { + if ((*it)->_design->isPointOpaque(x - _sceneArea.left + kBorderWidth, y - _sceneArea.top + kBorderWidth)) + return *it; + } + + for (ChrList::const_iterator it = _scene->_chrs.begin(); it != _scene->_chrs.end(); ++it) { + if ((*it)->_design->isPointOpaque(x - _sceneArea.left + kBorderWidth, y - _sceneArea.top + kBorderWidth)) + return *it; + } + } else if (_consoleTextArea.contains(x, y)) { + if (_sceneIsActive) { + _sceneIsActive = false; + _bordersDirty = true; + } + } else if ((borderClick = isInBorder(_consoleTextArea, x, y)) != kBorderNone) { + _bordersDirty = true; + int _oldScrollPos = _scrollPos; + + switch (borderClick) { + case kBorderScrollUp: + _scrollPos = MAX<int>(0, _scrollPos - _consoleLineHeight); + undrawCursor(); + _cursorY -= (_scrollPos - _oldScrollPos); + _consoleDirty = true; + _consoleFullRedraw = true; + break; + case kBorderScrollDown: + _scrollPos = MIN<int>((_lines.size() - 2) * _consoleLineHeight, _scrollPos + _consoleLineHeight); + undrawCursor(); + _cursorY -= (_scrollPos - _oldScrollPos); + _consoleDirty = true; + _consoleFullRedraw = true; + break; + } + } + + return NULL; +} + +void Gui::mouseDown(int x, int y) { + int borderClick; + + if (_menu->mouseClick(x, y)) { + _menuDirty = true; + } else if (_consoleTextArea.contains(x, y)) { + startMarking(x, y); + } else if ((borderClick = isInBorder(_consoleTextArea, x, y)) != kBorderNone) { + paintBorder(&_screen, _consoleTextArea, kWindowConsole, borderClick); + } +} + +int Gui::calcTextX(int x, int textLine) { + const Graphics::Font *font = getConsoleFont(); + + if ((uint)textLine >= _lines.size()) + return 0; + + Common::String str = _lines[textLine]; + + x -= _consoleTextArea.left; + + for (int i = str.size(); i >= 0; i--) { + if (font->getStringWidth(str) < x) { + return i; + } + + str.deleteLastChar(); + } + + return 0; +} + +int Gui::calcTextY(int y) { + y -= _consoleTextArea.top; + + if (y < 0) + y = 0; + + const int firstLine = _scrollPos / _consoleLineHeight; + int textLine = (y - _scrollPos % _consoleLineHeight) / _consoleLineHeight + firstLine; + + return textLine; +} + +void Gui::startMarking(int x, int y) { + _selectionStartY = calcTextY(y); + _selectionStartX = calcTextX(x, _selectionStartY); + + _selectionEndY = -1; + + _inTextSelection = true; +} + +void Gui::updateTextSelection(int x, int y) { + _selectionEndY = calcTextY(y); + _selectionEndX = calcTextX(x, _selectionEndY); + + _consoleFullRedraw = true; +} + +} // End of namespace Wage diff --git a/engines/wage/gui.h b/engines/wage/gui.h new file mode 100644 index 0000000000..73814d39b4 --- /dev/null +++ b/engines/wage/gui.h @@ -0,0 +1,189 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef WAGE_GUI_H +#define WAGE_GUI_H + +#include "common/str-array.h" +#include "graphics/font.h" +#include "graphics/fontman.h" +#include "graphics/surface.h" +#include "common/rect.h" + +namespace Wage { + +class Menu; + +enum WindowType { + kWindowScene, + kWindowConsole +}; + +enum { + kMenuHeight = 20, + kMenuLeftMargin = 7, + kMenuSpacing = 13, + kMenuPadding = 16, + kMenuDropdownPadding = 14, + kMenuDropdownItemHeight = 16, + kMenuItemHeight = 20, + kBorderWidth = 17, + kDesktopArc = 7, + kComponentsPadding = 10, + kCursorHeight = 12 +}; + +enum { + kPatternSolid = 1, + kPatternStripes = 2, + kPatternCheckers = 3, + kPatternCheckers2 = 4 +}; + +enum { + kBorderNone = 0, + kBorderScrollUp, + kBorderScrollDown, + kBorderCloseButton +}; + +class Gui { +public: + Gui(WageEngine *engine); + ~Gui(); + + void draw(); + void appendText(const char *str); + void clearOutput(); + void mouseMove(int x, int y); + void mouseDown(int x, int y); + Designed *mouseUp(int x, int y); + void drawInput(); + void setSceneDirty() { _sceneDirty = true; } + const Graphics::Font *getFont(const char *name, Graphics::FontManager::FontUsage fallback); + void regenCommandsMenu(); + void regenWeaponsMenu(); + void processMenuShortCut(byte flags, uint16 ascii); + void pushArrowCursor(); + void popCursor(); + + void actionCopy(); + void actionPaste(); + void actionUndo(); + void actionClear(); + void actionCut(); + void disableUndo(); + void disableAllMenus(); + void enableNewGameMenus(); + +private: + void undrawCursor(); + void drawDesktop(); + void paintBorder(Graphics::Surface *g, Common::Rect &r, WindowType windowType, int highlightedPart = kBorderNone); + void renderConsole(Graphics::Surface *g, Common::Rect &r); + void drawBox(Graphics::Surface *g, int x, int y, int w, int h); + void fillRect(Graphics::Surface *g, int x, int y, int w, int h, int color = kColorBlack); + void loadFonts(); + void flowText(Common::String &str); + const Graphics::Font *getConsoleFont(); + const Graphics::Font *getTitleFont(); + void startMarking(int x, int y); + int calcTextX(int x, int textLine); + int calcTextY(int y); + void updateTextSelection(int x, int y); + +public: + Graphics::Surface _screen; + int _cursorX, _cursorY; + bool _cursorState; + Common::Rect _consoleTextArea; + + bool _builtInFonts; + WageEngine *_engine; + + Patterns _patterns; + + bool _cursorDirty; + Common::Rect _cursorRect; + bool _cursorOff; + + bool _menuDirty; + +private: + Graphics::Surface _console; + Menu *_menu; + Scene *_scene; + bool _sceneDirty; + bool _consoleDirty; + bool _bordersDirty; + + Common::StringArray _out; + Common::StringArray _lines; + uint _scrollPos; + int _consoleLineHeight; + uint _consoleNumLines; + bool _consoleFullRedraw; + + Common::Rect _sceneArea; + bool _sceneIsActive; + bool _cursorIsArrow; + + bool _inTextSelection; + int _selectionStartX; + int _selectionStartY; + int _selectionEndX; + int _selectionEndY; + + Common::String _clipboard; + Common::String _undobuffer; + + int _inputTextLineNum; +}; + +} // End of namespace Wage + +#endif diff --git a/engines/wage/menu.cpp b/engines/wage/menu.cpp new file mode 100644 index 0000000000..12ef8c2219 --- /dev/null +++ b/engines/wage/menu.cpp @@ -0,0 +1,571 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "common/system.h" +#include "common/keyboard.h" + +#include "wage/wage.h" +#include "wage/entities.h" +#include "wage/design.h" +#include "wage/gui.h" +#include "wage/menu.h" +#include "wage/world.h" + +namespace Wage { + +struct MenuSubItem { + Common::String text; + int action; + int style; + char shortcut; + bool enabled; + Common::Rect bbox; + + MenuSubItem(const char *t, int a, int s = 0, char sh = 0, bool e = true) : text(t), action(a), style(s), shortcut(sh), enabled(e) {} +}; + +typedef Common::Array<MenuSubItem *> SubItemArray; + +struct MenuItem { + Common::String name; + SubItemArray subitems; + Common::Rect bbox; + Common::Rect subbbox; + + MenuItem(const char *n) : name(n) {} +}; + +struct MenuData { + int menunum; + const char *title; + int action; + byte shortcut; + bool enabled; +} static const menuSubItems[] = { + { kMenuFile, "New", kMenuActionNew, 0, false }, + { kMenuFile, "Open...", kMenuActionOpen, 0, false }, + { kMenuFile, "Close", kMenuActionClose, 0, true }, + { kMenuFile, "Save", kMenuActionSave, 0, false }, + { kMenuFile, "Save as...", kMenuActionSaveAs, 0, true }, + { kMenuFile, "Revert", kMenuActionRevert, 0, false }, + { kMenuFile, "Quit", kMenuActionQuit, 0, true }, + + { kMenuEdit, "Undo", kMenuActionUndo, 'Z', false }, + { kMenuEdit, NULL, 0, 0, false }, + { kMenuEdit, "Cut", kMenuActionCut, 'K', false }, + { kMenuEdit, "Copy", kMenuActionCopy, 'C', false }, + { kMenuEdit, "Paste", kMenuActionPaste, 'V', false }, + { kMenuEdit, "Clear", kMenuActionClear, 'B', false }, + + { 0, NULL, 0, 0, false } +}; + +Menu::Menu(Gui *gui) : _gui(gui) { + assert(_gui->_engine); + assert(_gui->_engine->_world); + + _font = getMenuFont(); + + MenuItem *about = new MenuItem(_gui->_builtInFonts ? "\xa9" : "\xf0"); // (c) Symbol as the most resembling apple + _items.push_back(about); + _items[0]->subitems.push_back(new MenuSubItem(_gui->_engine->_world->getAboutMenuItemName(), kMenuActionAbout)); + + MenuItem *file = new MenuItem("File"); + _items.push_back(file); + + MenuItem *edit = new MenuItem("Edit"); + _items.push_back(edit); + + for (int i = 0; menuSubItems[i].menunum; i++) { + const MenuData *m = &menuSubItems[i]; + + _items[m->menunum]->subitems.push_back(new MenuSubItem(m->title, m->action, 0, m->shortcut, m->enabled)); + } + + _commands = new MenuItem(_gui->_engine->_world->_commandsMenuName.c_str()); + _items.push_back(_commands); + regenCommandsMenu(); + + _weapons = NULL; + + if (!_gui->_engine->_world->_weaponMenuDisabled) { + _weapons = new MenuItem(_gui->_engine->_world->_weaponsMenuName.c_str()); + _items.push_back(_weapons); + + regenWeaponsMenu(); + } + + // Calculate menu dimensions + int y = 1; + int x = 18; + + for (uint i = 0; i < _items.size(); i++) { + int w = _font->getStringWidth(_items[i]->name); + + if (_items[i]->bbox.bottom == 0) { + _items[i]->bbox.left = x - kMenuLeftMargin; + _items[i]->bbox.top = y; + _items[i]->bbox.right = x + w + kMenuSpacing - kMenuLeftMargin; + _items[i]->bbox.bottom = y + _font->getFontHeight() + (_gui->_builtInFonts ? 3 : 2); + } + + calcMenuBounds(_items[i]); + + x += w + kMenuSpacing; + } + + _bbox.left = 0; + _bbox.top = 0; + _bbox.right = _gui->_screen.w - 1; + _bbox.bottom = kMenuHeight - 1; + + _menuActivated = false; + _activeItem = -1; + _activeSubItem = -1; + + _screenCopy.create(_gui->_screen.w, _gui->_screen.h, Graphics::PixelFormat::createFormatCLUT8()); + _tempSurface.create(_gui->_screen.w, _font->getFontHeight(), Graphics::PixelFormat::createFormatCLUT8()); +} + +Menu::~Menu() { + for (uint i = 0; i < _items.size(); i++) { + for (uint j = 0; j < _items[i]->subitems.size(); j++) + delete _items[i]->subitems[j]; + delete _items[i]; + } +} + +void Menu::regenCommandsMenu() { + for (uint j = 0; j < _commands->subitems.size(); j++) + delete _commands->subitems[j]; + + _commands->subitems.clear(); + + createCommandsMenu(_commands); + calcMenuBounds(_commands); +} + +void Menu::createCommandsMenu(MenuItem *menu) { + Common::String string(_gui->_engine->_world->_commandsMenu); + + Common::String item; + + for (uint i = 0; i < string.size(); i++) { + while(i < string.size() && string[i] != ';') // Read token + item += string[i++]; + + if (item == "(-") { + menu->subitems.push_back(new MenuSubItem(NULL, 0)); + } else { + bool enabled = true; + int style = 0; + char shortcut = 0; + const char *shortPtr = strrchr(item.c_str(), '/'); + if (shortPtr != NULL) { + if (strlen(shortPtr) >= 2) { + shortcut = shortPtr[1]; + item.deleteChar(shortPtr - item.c_str()); + item.deleteChar(shortPtr - item.c_str()); + } else { + error("Unexpected shortcut: '%s', item '%s' in menu '%s'", shortPtr, item.c_str(), string.c_str()); + } + } + + while (item.size() >= 2 && item[item.size() - 2] == '<') { + char c = item.lastChar(); + if (c == 'B') { + style |= kFontStyleBold; + } else if (c == 'I') { + style |= kFontStyleItalic; + } else if (c == 'U') { + style |= kFontStyleUnderline; + } else if (c == 'O') { + style |= kFontStyleOutline; + } else if (c == 'S') { + style |= kFontStyleShadow; + } else if (c == 'C') { + style |= kFontStyleCondensed; + } else if (c == 'E') { + style |= kFontStyleExtended; + } + item.deleteLastChar(); + item.deleteLastChar(); + } + + Common::String tmpitem(item); + tmpitem.trim(); + if (tmpitem[0] == '(') { + enabled = false; + + for (uint j = 0; j < item.size(); j++) + if (item[j] == '(') { + item.deleteChar(j); + break; + } + } + + menu->subitems.push_back(new MenuSubItem(item.c_str(), kMenuActionCommand, style, shortcut, enabled)); + } + + item.clear(); + } +} + +void Menu::regenWeaponsMenu() { + if (_gui->_engine->_world->_weaponMenuDisabled) + return; + + for (uint j = 0; j < _weapons->subitems.size(); j++) + delete _weapons->subitems[j]; + + _weapons->subitems.clear(); + + createWeaponsMenu(_weapons); + calcMenuBounds(_weapons); +} + +void Menu::createWeaponsMenu(MenuItem *menu) { + Chr *player = _gui->_engine->_world->_player; + ObjArray *weapons = player->getWeapons(true); + + for (uint i = 0; i < weapons->size(); i++) { + Obj *obj = (*weapons)[i]; + if (obj->_type == Obj::REGULAR_WEAPON || + obj->_type == Obj::THROW_WEAPON || + obj->_type == Obj::MAGICAL_OBJECT) { + Common::String command(obj->_operativeVerb); + command += " "; + command += obj->_name; + + menu->subitems.push_back(new MenuSubItem(command.c_str(), kMenuActionCommand, 0, 0, true)); + } + } + delete weapons; + + if (menu->subitems.empty()) + menu->subitems.push_back(new MenuSubItem("You have no weapons", 0, 0, 0, false)); +} + +const Graphics::Font *Menu::getMenuFont() { + return _gui->getFont("Chicago-12", Graphics::FontManager::kBigGUIFont); +} + +const char *Menu::getAcceleratorString(MenuSubItem *item, const char *prefix) { + static char res[20]; + *res = 0; + + if (item->shortcut != 0) + sprintf(res, "%s%c%c", prefix, (_gui->_builtInFonts ? '^' : '\x11'), item->shortcut); + + return res; +} + +int Menu::calculateMenuWidth(MenuItem *menu) { + int maxWidth = 0; + for (uint i = 0; i < menu->subitems.size(); i++) { + MenuSubItem *item = menu->subitems[i]; + if (!item->text.empty()) { + Common::String text(item->text); + Common::String acceleratorText(getAcceleratorString(item, " ")); + if (!acceleratorText.empty()) { + text += acceleratorText; + } + + int width = _font->getStringWidth(text); + if (width > maxWidth) { + maxWidth = width; + } + } + } + return maxWidth; +} + +void Menu::calcMenuBounds(MenuItem *menu) { + // TODO: cache maxWidth + int maxWidth = calculateMenuWidth(menu); + int x1 = menu->bbox.left - 1; + int y1 = menu->bbox.bottom + 1; + int x2 = x1 + maxWidth + kMenuDropdownPadding * 2 - 4; + int y2 = y1 + menu->subitems.size() * kMenuDropdownItemHeight + 2; + + menu->subbbox.left = x1; + menu->subbbox.top = y1; + menu->subbbox.right = x2; + menu->subbbox.bottom = y2; +} + +void Menu::render() { + Common::Rect r(_bbox); + + Design::drawFilledRoundRect(&_gui->_screen, r, kDesktopArc, kColorWhite, _gui->_patterns, kPatternSolid); + r.top = 7; + Design::drawFilledRect(&_gui->_screen, r, kColorWhite, _gui->_patterns, kPatternSolid); + r.top = kMenuHeight - 1; + Design::drawFilledRect(&_gui->_screen, r, kColorBlack, _gui->_patterns, kPatternSolid); + + for (uint i = 0; i < _items.size(); i++) { + int color = kColorBlack; + MenuItem *it = _items[i]; + + if ((uint)_activeItem == i) { + Common::Rect hbox = it->bbox; + + hbox.left -= 1; + hbox.right += 2; + + Design::drawFilledRect(&_gui->_screen, hbox, kColorBlack, _gui->_patterns, kPatternSolid); + color = kColorWhite; + + if (!it->subitems.empty()) + renderSubmenu(it); + } + + _font->drawString(&_gui->_screen, it->name, it->bbox.left + kMenuLeftMargin, it->bbox.top + (_gui->_builtInFonts ? 2 : 1), it->bbox.width(), color); + } + + g_system->copyRectToScreen(_gui->_screen.getPixels(), _gui->_screen.pitch, 0, 0, _gui->_screen.w, kMenuHeight); +} + +void Menu::renderSubmenu(MenuItem *menu) { + Common::Rect *r = &menu->subbbox; + + if (r->width() == 0 || r->height() == 0) + return; + + Design::drawFilledRect(&_gui->_screen, *r, kColorWhite, _gui->_patterns, kPatternSolid); + Design::drawRect(&_gui->_screen, *r, 1, kColorBlack, _gui->_patterns, kPatternSolid); + Design::drawVLine(&_gui->_screen, r->right + 1, r->top + 3, r->bottom + 1, 1, kColorBlack, _gui->_patterns, kPatternSolid); + Design::drawHLine(&_gui->_screen, r->left + 3, r->right + 1, r->bottom + 1, 1, kColorBlack, _gui->_patterns, kPatternSolid); + + int x = r->left + kMenuDropdownPadding; + int y = r->top + 1; + for (uint i = 0; i < menu->subitems.size(); i++) { + Common::String text(menu->subitems[i]->text); + Common::String acceleratorText(getAcceleratorString(menu->subitems[i], "")); + int accelX = r->right - 25; + + int color = kColorBlack; + if (i == (uint)_activeSubItem && !text.empty() && menu->subitems[i]->enabled) { + color = kColorWhite; + Common::Rect trect(r->left, y - (_gui->_builtInFonts ? 1 : 0), r->right, y + _font->getFontHeight()); + + Design::drawFilledRect(&_gui->_screen, trect, kColorBlack, _gui->_patterns, kPatternSolid); + } + + if (!text.empty()) { + Graphics::Surface *s = &_gui->_screen; + int tx = x, ty = y; + + if (!menu->subitems[i]->enabled) { + s = &_tempSurface; + tx = 0; + ty = 0; + accelX -= x; + + _tempSurface.fillRect(Common::Rect(0, 0, _tempSurface.w, _tempSurface.h), kColorGreen); + } + + _font->drawString(s, text, tx, ty, r->width(), color); + + if (!acceleratorText.empty()) + _font->drawString(s, acceleratorText, accelX, ty, r->width(), color); + + if (!menu->subitems[i]->enabled) { + // I am lazy to extend drawString() with plotProc as a parameter, so + // fake it here + for (int ii = 0; ii < _tempSurface.h; ii++) { + const byte *src = (const byte *)_tempSurface.getBasePtr(0, ii); + byte *dst = (byte *)_gui->_screen.getBasePtr(x, y+ii); + byte pat = _gui->_patterns[kPatternCheckers2 - 1][ii % 8]; + for (int j = 0; j < r->width(); j++) { + if (*src != kColorGreen && (pat & (1 << (7 - (x + j) % 8)))) + *dst = *src; + src++; + dst++; + } + } + } + } else { // Delimiter + Design::drawHLine(&_gui->_screen, r->left + 1, r->right - 1, y + kMenuDropdownItemHeight / 2, 1, kColorBlack, _gui->_patterns, kPatternStripes); + } + + y += kMenuDropdownItemHeight; + } + + g_system->copyRectToScreen(_gui->_screen.getBasePtr(r->left, r->top), _gui->_screen.pitch, r->left, r->top, r->width() + 3, r->height() + 3); +} + +bool Menu::mouseClick(int x, int y) { + if (_bbox.contains(x, y)) { + if (!_menuActivated) + _screenCopy.copyFrom(_gui->_screen); + + for (uint i = 0; i < _items.size(); i++) + if (_items[i]->bbox.contains(x, y)) { + if ((uint)_activeItem == i) + return false; + + if (_activeItem != -1) { // Restore background + Common::Rect r(_items[_activeItem]->subbbox); + r.right += 3; + r.bottom += 3; + + _gui->_screen.copyRectToSurface(_screenCopy, r.left, r.top, r); + g_system->copyRectToScreen(_gui->_screen.getBasePtr(r.left, r.top), _gui->_screen.pitch, r.left, r.top, r.width() + 1, r.height() + 1); + } + + _activeItem = i; + _activeSubItem = -1; + _menuActivated = true; + + return true; + } + } else if (_menuActivated && _items[_activeItem]->subbbox.contains(x, y)) { + MenuItem *it = _items[_activeItem]; + int numSubItem = (y - it->subbbox.top) / kMenuDropdownItemHeight; + + if (numSubItem != _activeSubItem) { + _activeSubItem = numSubItem; + + renderSubmenu(_items[_activeItem]); + } + } else if (_menuActivated && _activeItem != -1) { + _activeSubItem = -1; + + renderSubmenu(_items[_activeItem]); + } + + return false; +} + +bool Menu::mouseMove(int x, int y) { + if (_menuActivated) + if (mouseClick(x, y)) + return true; + + return false; +} + +bool Menu::mouseRelease(int x, int y) { + if (_menuActivated) { + _menuActivated = false; + + if (_activeItem != -1 && _activeSubItem != -1 && _items[_activeItem]->subitems[_activeSubItem]->enabled) + executeCommand(_items[_activeItem]->subitems[_activeSubItem]); + + _activeItem = -1; + _activeSubItem = -1; + + return true; + } + + return false; +} + +void Menu::executeCommand(MenuSubItem *subitem) { + switch(subitem->action) { + case kMenuActionAbout: + case kMenuActionNew: + case kMenuActionOpen: + case kMenuActionClose: + case kMenuActionSave: + case kMenuActionSaveAs: + case kMenuActionRevert: + case kMenuActionQuit: + + case kMenuActionUndo: + _gui->actionUndo(); + break; + case kMenuActionCut: + _gui->actionCut(); + break; + case kMenuActionCopy: + _gui->actionCopy(); + break; + case kMenuActionPaste: + _gui->actionPaste(); + break; + case kMenuActionClear: + _gui->actionClear(); + break; + + case kMenuActionCommand: + _gui->_engine->processTurn(&subitem->text, NULL); + break; + + default: + warning("Unknown action: %d", subitem->action); + + } +} + +void Menu::processMenuShortCut(byte flags, uint16 ascii) { + ascii = tolower(ascii); + + if (flags & (Common::KBD_CTRL | Common::KBD_META)) { + for (uint i = 0; i < _items.size(); i++) + for (uint j = 0; j < _items[i]->subitems.size(); j++) + if (_items[i]->subitems[j]->enabled && tolower(_items[i]->subitems[j]->shortcut) == ascii) { + executeCommand(_items[i]->subitems[j]); + break; + } + } +} + +void Menu::enableCommand(int menunum, int action, bool state) { + for (uint i = 0; i < _items[menunum]->subitems.size(); i++) + if (_items[menunum]->subitems[i]->action == action) + _items[menunum]->subitems[i]->enabled = state; +} + +void Menu::disableAllMenus() { + for (uint i = 1; i < _items.size(); i++) // Leave About menu on + for (uint j = 0; j < _items[i]->subitems.size(); j++) + _items[i]->subitems[j]->enabled = false; +} + +} // End of namespace Wage diff --git a/engines/wage/menu.h b/engines/wage/menu.h new file mode 100644 index 0000000000..3550356bc6 --- /dev/null +++ b/engines/wage/menu.h @@ -0,0 +1,139 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef WAGE_MENU_H +#define WAGE_MENU_H + +namespace Wage { + +struct MenuItem; +struct MenuSubItem; + +enum { + kFontStyleBold = 1, + kFontStyleItalic = 2, + kFontStyleUnderline = 4, + kFontStyleOutline = 8, + kFontStyleShadow = 16, + kFontStyleCondensed = 32, + kFontStyleExtended = 64 +}; + +enum { + kMenuAbout = 0, + kMenuFile = 1, + kMenuEdit = 2, + kMenuCommands = 3, + kMenuWeapons = 4 +}; + +enum { + kMenuActionAbout, + kMenuActionNew, + kMenuActionOpen, + kMenuActionClose, + kMenuActionSave, + kMenuActionSaveAs, + kMenuActionRevert, + kMenuActionQuit, + + kMenuActionUndo, + kMenuActionCut, + kMenuActionCopy, + kMenuActionPaste, + kMenuActionClear, + + kMenuActionCommand +}; + +class Menu { +public: + Menu(Gui *gui); + ~Menu(); + + void render(); + bool mouseClick(int x, int y); + bool mouseRelease(int x, int y); + bool mouseMove(int x, int y); + + void regenCommandsMenu(); + void regenWeaponsMenu(); + void processMenuShortCut(byte flags, uint16 ascii); + void enableCommand(int menunum, int action, bool state); + void disableAllMenus(); + + bool _menuActivated; + Common::Rect _bbox; + +private: + Gui *_gui; + Graphics::Surface _screenCopy; + Graphics::Surface _tempSurface; + +private: + const Graphics::Font *getMenuFont(); + const char *getAcceleratorString(MenuSubItem *item, const char *prefix); + int calculateMenuWidth(MenuItem *menu); + void calcMenuBounds(MenuItem *menu); + void renderSubmenu(MenuItem *menu); + void createCommandsMenu(MenuItem *menu); + void createWeaponsMenu(MenuItem *menu); + void executeCommand(MenuSubItem *subitem); + + Common::Array<MenuItem *> _items; + MenuItem *_weapons; + MenuItem *_commands; + + const Graphics::Font *_font; + + int _activeItem; + int _activeSubItem; +}; + +} // End of namespace Wage + +#endif diff --git a/engines/wage/module.mk b/engines/wage/module.mk new file mode 100644 index 0000000000..21316bbf83 --- /dev/null +++ b/engines/wage/module.mk @@ -0,0 +1,29 @@ +MODULE := engines/wage + +MODULE_OBJS := \ + combat.o \ + debugger.o \ + design.o \ + detection.o \ + dialog.o \ + entities.o \ + gui.o \ + gui-console.o \ + menu.o \ + randomhat.o \ + script.o \ + sound.o \ + util.o \ + wage.o \ + world.o + +MODULE_DIRS += \ + engines/wage + +# This module can be built as a plugin +ifeq ($(ENABLE_WAGE), DYNAMIC_PLUGIN) +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/wage/randomhat.cpp b/engines/wage/randomhat.cpp new file mode 100644 index 0000000000..9371140398 --- /dev/null +++ b/engines/wage/randomhat.cpp @@ -0,0 +1,83 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "common/random.h" + +#include "common/hashmap.h" +#include "wage/randomhat.h" + +namespace Wage { + +void RandomHat::addTokens(int type, int count) { + _tokens.setVal(type, _tokens.getVal(type, 0) + count); +} + +int RandomHat::countTokens() { + int count = 0; + for (Common::HashMap<int, int>::const_iterator it = _tokens.begin(); it != _tokens.end(); ++it) + count += it->_value; + + return count; +} + +int RandomHat::drawToken() { + int total = countTokens(); + if (total > 0) { + int random = _rnd->getRandomNumber(total - 1); + int count = 0; + for (Common::HashMap<int, int>::iterator it = _tokens.begin(); it != _tokens.end(); ++it) { + if (random >= count && random < count + it->_value) { + it->_value--; + return it->_key; + } + count += it->_value; + } + } + return kTokNone; +} + +} // End of namespace Wage diff --git a/engines/wage/randomhat.h b/engines/wage/randomhat.h new file mode 100644 index 0000000000..254cd2ae8d --- /dev/null +++ b/engines/wage/randomhat.h @@ -0,0 +1,77 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef WAGE_RANDOMHAT_H +#define WAGE_RANDOMHAT_H + +namespace Wage { + +enum { + kTokWeapons = -400, + kTokMagic = -300, + kTokRun = -200, + kTokOffer = -100, + kTokNone = -100000 +}; + +class RandomHat { +public: + RandomHat(Common::RandomSource *rnd) : _rnd(rnd) {} + + void addTokens(int type, int count); + int drawToken(); + +private: + Common::RandomSource *_rnd; + Common::HashMap<int, int> _tokens; + + int countTokens(); +}; + +} // End of namespace Wage + +#endif diff --git a/engines/wage/script.cpp b/engines/wage/script.cpp new file mode 100644 index 0000000000..294c08ed82 --- /dev/null +++ b/engines/wage/script.cpp @@ -0,0 +1,1175 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "wage/wage.h" +#include "wage/entities.h" +#include "wage/script.h" +#include "wage/world.h" + +#include "common/stream.h" + +namespace Wage { + +Common::String Script::Operand::toString() { + switch(_type) { + case NUMBER: + return Common::String::format("%d", _value.number); + case STRING: + case TEXT_INPUT: + return *_value.string; + case OBJ: + return _value.obj->toString(); + case CHR: + return _value.chr->toString(); + case SCENE: + return _value.scene->toString(); + case CLICK_INPUT: + return _value.inputClick->toString(); + default: + error("Unhandled operand type: _type"); + } +} + +Script::Script(Common::SeekableReadStream *data) : _data(data) { + _engine = NULL; + _world = NULL; + + _loopCount = 0; + _inputText = NULL; + _inputClick = NULL; + + _handled = false; + + convertToText(); +} + +Script::~Script() { + for (uint i = 0; i < _scriptText.size(); i++) { + delete _scriptText[i]; + } + + delete _data; +} + +void Script::print() { + for (uint i = 0; i < _scriptText.size(); i++) { + debug(4, "%d [%04x]: %s", i, _scriptText[i]->offset, _scriptText[i]->line.c_str()); + } +} + +void Script::printLine(int offset) { + for (uint i = 0; i < _scriptText.size(); i++) + if (_scriptText[i]->offset >= offset) { + debug(4, "%d [%04x]: %s", i, _scriptText[i]->offset, _scriptText[i]->line.c_str()); + break; + } +} + +bool Script::execute(World *world, int loopCount, Common::String *inputText, Designed *inputClick, WageEngine *engine) { + _world = world; + _loopCount = loopCount; + _inputText = inputText; + _inputClick = inputClick; + _engine = engine; + _handled = false; + Common::String input; + + if (inputText) + input = *inputText; + + _data->seek(12); + while (_data->pos() < _data->size()) { + printLine(_data->pos()); + + byte command = _data->readByte(); + + switch(command) { + case 0x80: // IF + processIf(); + break; + case 0x87: // EXIT + debug(6, "exit at offset %d", _data->pos() - 1); + + return true; + case 0x89: // MOVE + { + Scene *currentScene = _world->_player->_currentScene; + processMove(); + if (_world->_player->_currentScene != currentScene) + return true; + break; + } + case 0x8B: // PRINT + { + Operand *op = readOperand(); + // TODO check op type is string or number, or something good... + _handled = true; + _engine->appendText(op->toString().c_str()); + delete op; + byte d = _data->readByte(); + if (d != 0xFD) + warning("Operand 0x8B (PRINT) End Byte != 0xFD"); + break; + } + case 0x8C: // SOUND + { + Operand *op = readOperand(); + // TODO check op type is string. + _handled = true; + _engine->playSound(op->toString()); + delete op; + byte d = _data->readByte(); + if (d != 0xFD) + warning("Operand 0x8B (PRINT) End Byte != 0xFD"); + break; + } + case 0x8E: // LET + processLet(); + break; + case 0x95: // MENU + { + Operand *op = readStringOperand(); // allows empty menu + // TODO check op type is string. + _engine->setMenu(op->toString()); + delete op; + byte d = _data->readByte(); + if (d != 0xFD) + warning("Operand 0x8B (PRINT) End Byte != 0xFD"); + } + case 0x88: // END + break; + default: + debug(0, "Unknown opcode: %d", _data->pos()); + } + } + + if (_world->_globalScript != this) { + debug(1, "Executing global script..."); + bool globalHandled = _world->_globalScript->execute(_world, _loopCount, &input, _inputClick, _engine); + if (globalHandled) + _handled = true; + } else if (!input.empty()) { + if (input.contains("north")) { + _handled = _engine->handleMoveCommand(NORTH, "north"); + } else if (input.contains("east")) { + _handled = _engine->handleMoveCommand(EAST, "east"); + } else if (input.contains("south")) { + _handled = _engine->handleMoveCommand(SOUTH, "south"); + } else if (input.contains("west")) { + _handled = _engine->handleMoveCommand(WEST, "west"); + } else if (input.hasPrefix("take ")) { + _handled = _engine->handleTakeCommand(&input.c_str()[5]); + } else if (input.hasPrefix("get ")) { + _handled = _engine->handleTakeCommand(&input.c_str()[4]); + } else if (input.hasPrefix("pick up ")) { + _handled = _engine->handleTakeCommand(&input.c_str()[8]); + } else if (input.hasPrefix("drop ")) { + _handled = _engine->handleDropCommand(&input.c_str()[5]); + } else if (input.hasPrefix("aim ")) { + _handled = _engine->handleAimCommand(&input.c_str()[4]); + } else if (input.hasPrefix("wear ")) { + _handled = _engine->handleWearCommand(&input.c_str()[5]); + } else if (input.hasPrefix("put on ")) { + _handled = _engine->handleWearCommand(&input.c_str()[7]); + } else if (input.hasPrefix("offer ")) { + _handled = _engine->handleOfferCommand(&input.c_str()[6]); + } else if (input.contains("look")) { + _handled = _engine->handleLookCommand(); + } else if (input.contains("inventory")) { + _handled = _engine->handleInventoryCommand(); + } else if (input.contains("status")) { + _handled = _engine->handleStatusCommand(); + } else if (input.contains("rest") || input.equals("wait")) { + _handled = _engine->handleRestCommand(); + } else if (_engine->getOffer() != NULL && input.contains("accept")) { + _handled = _engine->handleAcceptCommand(); + } else { + Chr *player = _world->_player; + ObjArray *weapons = player->getWeapons(true); + for (ObjArray::const_iterator weapon = weapons->begin(); weapon != weapons->end(); ++weapon) { + if (_engine->tryAttack(*weapon, input)) { + _handled = _engine->handleAttack(*weapon); + break; + } + } + + delete weapons; + } + // TODO: weapons, offer, etc... + } else if (_inputClick->_classType == OBJ) { + Obj *obj = (Obj *)_inputClick; + if (obj->_type != Obj::IMMOBILE_OBJECT) { + _engine->takeObj(obj); + } else { + _engine->appendText(obj->_clickMessage.c_str()); + } + + _handled = true; + } + + return _handled; +} + +Script::Operand *Script::readOperand() { + byte operandType = _data->readByte(); + + debug(7, "%x: readOperand: 0x%x", _data->pos(), operandType); + + Context *cont = &_world->_player->_context; + switch (operandType) { + case 0xA0: // TEXT$ + return new Operand(_inputText, TEXT_INPUT); + case 0xA1: + return new Operand(_inputClick, CLICK_INPUT); + case 0xC0: // STORAGE@ + return new Operand(_world->_storageScene, SCENE); + case 0xC1: // SCENE@ + return new Operand(_world->_player->_currentScene, SCENE); + case 0xC2: // PLAYER@ + return new Operand(_world->_player, CHR); + case 0xC3: // MONSTER@ + return new Operand(_engine->getMonster(), CHR); + case 0xC4: // RANDOMSCN@ + return new Operand(_world->_orderedScenes[_engine->_rnd->getRandomNumber(_world->_orderedScenes.size())], SCENE); + case 0xC5: // RANDOMCHR@ + return new Operand(_world->_orderedChrs[_engine->_rnd->getRandomNumber(_world->_orderedChrs.size())], CHR); + case 0xC6: // RANDOMOBJ@ + return new Operand(_world->_orderedObjs[_engine->_rnd->getRandomNumber(_world->_orderedObjs.size())], OBJ); + case 0xB0: // VISITS# + return new Operand(cont->_visits, NUMBER); + case 0xB1: // RANDOM# for Star Trek, but VISITS# for some other games? + return new Operand(1 + _engine->_rnd->getRandomNumber(100), NUMBER); + case 0xB5: // RANDOM# // A random number between 1 and 100. + return new Operand(1 + _engine->_rnd->getRandomNumber(100), NUMBER); + case 0xB2: // LOOP# + return new Operand(_loopCount, NUMBER); + case 0xB3: // VICTORY# + return new Operand(cont->_kills, NUMBER); + case 0xB4: // BADCOPY# + return new Operand(0, NUMBER); // \?\?\?? + case 0xFF: + { + // user variable + int value = _data->readByte(); + + // TODO: Verify that we're using the right index. + return new Operand(cont->_userVariables[value - 1], NUMBER); + } + case 0xD0: + return new Operand(cont->_statVariables[PHYS_STR_BAS], NUMBER); + case 0xD1: + return new Operand(cont->_statVariables[PHYS_HIT_BAS], NUMBER); + case 0xD2: + return new Operand(cont->_statVariables[PHYS_ARM_BAS], NUMBER); + case 0xD3: + return new Operand(cont->_statVariables[PHYS_ACC_BAS], NUMBER); + case 0xD4: + return new Operand(cont->_statVariables[SPIR_STR_BAS], NUMBER); + case 0xD5: + return new Operand(cont->_statVariables[SPIR_HIT_BAS], NUMBER); + case 0xD6: + return new Operand(cont->_statVariables[SPIR_ARM_BAS], NUMBER); + case 0xD7: + return new Operand(cont->_statVariables[SPIR_ACC_BAS], NUMBER); + case 0xD8: + return new Operand(cont->_statVariables[PHYS_SPE_BAS], NUMBER); + case 0xE0: + return new Operand(cont->_statVariables[PHYS_STR_CUR], NUMBER); + case 0xE1: + return new Operand(cont->_statVariables[PHYS_HIT_CUR], NUMBER); + case 0xE2: + return new Operand(cont->_statVariables[PHYS_ARM_CUR], NUMBER); + case 0xE3: + return new Operand(cont->_statVariables[PHYS_ACC_CUR], NUMBER); + case 0xE4: + return new Operand(cont->_statVariables[SPIR_STR_CUR], NUMBER); + case 0xE5: + return new Operand(cont->_statVariables[SPIR_HIT_CUR], NUMBER); + case 0xE6: + return new Operand(cont->_statVariables[SPIR_ARM_CUR], NUMBER); + case 0xE7: + return new Operand(cont->_statVariables[SPIR_ACC_CUR], NUMBER); + case 0xE8: + return new Operand(cont->_statVariables[PHYS_SPE_CUR], NUMBER); + default: + if (operandType >= 0x20 && operandType < 0x80) { + _data->seek(-1, SEEK_CUR); + return readStringOperand(); + } else { + debug("Dunno what %x is (index=%d)!\n", operandType, _data->pos()-1); + } + return NULL; + } +} + +void Script::assign(byte operandType, int uservar, uint16 value) { + Context *cont = &_world->_player->_context; + + switch (operandType) { + case 0xFF: + cont->_userVariables[uservar - 1] = value; + break; + case 0xD0: + cont->_statVariables[PHYS_STR_BAS] = value; + break; + case 0xD1: + cont->_statVariables[PHYS_HIT_BAS] = value; + break; + case 0xD2: + cont->_statVariables[PHYS_ARM_BAS] = value; + break; + case 0xD3: + cont->_statVariables[PHYS_ACC_BAS] = value; + break; + case 0xD4: + cont->_statVariables[SPIR_STR_BAS] = value; + break; + case 0xD5: + cont->_statVariables[SPIR_HIT_BAS] = value; + break; + case 0xD6: + cont->_statVariables[SPIR_ARM_BAS] = value; + break; + case 0xD7: + cont->_statVariables[SPIR_ACC_BAS] = value; + break; + case 0xD8: + cont->_statVariables[PHYS_SPE_BAS] = value; + break; + case 0xE0: + cont->_statVariables[PHYS_STR_CUR] = value; + break; + case 0xE1: + cont->_statVariables[PHYS_HIT_CUR] = value; + break; + case 0xE2: + cont->_statVariables[PHYS_ARM_CUR] = value; + break; + case 0xE3: + cont->_statVariables[PHYS_ACC_CUR] = value; + break; + case 0xE4: + cont->_statVariables[SPIR_STR_CUR] = value; + break; + case 0xE5: + cont->_statVariables[SPIR_HIT_CUR] = value; + break; + case 0xE6: + cont->_statVariables[SPIR_ARM_CUR] = value; + break; + case 0xE7: + cont->_statVariables[SPIR_ACC_CUR] = value; + break; + case 0xE8: + cont->_statVariables[PHYS_SPE_CUR] = value; + break; + default: + debug("No idea what I'm supposed to assign! (%x at %d)!\n", operandType, _data->pos()-1); + } +} + +Script::Operand *Script::readStringOperand() { + Common::String *str; + bool allDigits = true; + + str = new Common::String(); + + while (true) { + byte c = _data->readByte(); + if (c >= 0x20 && c < 0x80) + *str += c; + else + break; + if (c < '0' || c > '9') + allDigits = false; + } + _data->seek(-1, SEEK_CUR); + + if (allDigits && !str->empty()) { + int r = atol(str->c_str()); + delete str; + + return new Operand(r, NUMBER); + } else { + // TODO: This string could be a room name or something like that. + return new Operand(str, STRING); + } +} + +const char *Script::readOperator() { + byte cmd = _data->readByte(); + + debug(7, "readOperator: 0x%x", cmd); + switch (cmd) { + case 0x81: + return "="; + case 0x82: + return "<"; + case 0x83: + return ">"; + case 0x8f: + return "+"; + case 0x90: + return "-"; + case 0x91: + return "*"; + case 0x92: + return "/"; + case 0x93: + return "=="; + case 0x94: + return ">>"; + case 0xfd: + return ";"; + default: + warning("UNKNOWN OP %x", cmd); + } + return NULL; +} + +void Script::processIf() { + int logicalOp = 0; // 0 => initial, 1 => and, 2 => or + bool result = true; + bool done = false; + + do { + Operand *lhs = readOperand(); + const char *op = readOperator(); + Operand *rhs = readOperand(); + + bool condResult = eval(lhs, op, rhs); + + delete lhs; + delete rhs; + + if (logicalOp == 1) { + result = (result && condResult); + } else if (logicalOp == 2) { + result = (result || condResult); + } else { // logicalOp == 0 + result = condResult; + } + + byte logical = _data->readByte(); + + if (logical == 0x84) { + logicalOp = 1; // and + } else if (logical == 0x85) { + logicalOp = 2; // or + } else if (logical == 0xFE) { + done = true; // then + } + } while (!done); + + if (result == false) { + skipBlock(); + } +} + +void Script::skipIf() { + do { + Operand *lhs = readOperand(); + readOperator(); + Operand *rhs = readOperand(); + + delete lhs; + delete rhs; + } while (_data->readByte() != 0xFE); +} + +void Script::skipBlock() { + int nesting = 1; + + while (true) { + byte op = _data->readByte(); + + if (_data->eos()) + return; + + if (op == 0x80) { // IF + nesting++; + skipIf(); + } else if (op == 0x88 || op == 0x87) { // END or EXIT + nesting--; + if (nesting == 0) { + return; + } + } else switch (op) { + case 0x8B: // PRINT + case 0x8C: // SOUND + case 0x8E: // LET + case 0x95: // MENU + while (_data->readByte() != 0xFD) + ; + } + } +} + +enum { + kCompEqNumNum, + kCompEqObjScene, + kCompEqChrScene, + kCompEqObjChr, + kCompEqChrChr, + kCompEqSceneScene, + kCompEqStringTextInput, + kCompEqTextInputString, + kCompEqNumberTextInput, + kCompEqTextInputNumber, + kCompLtNumNum, + kCompLtStringTextInput, + kCompLtTextInputString, + kCompLtObjChr, + kCompLtChrObj, + kCompLtObjScene, + kCompGtNumNum, + kCompGtStringString, + kCompGtChrScene, + kMoveObjChr, + kMoveObjScene, + kMoveChrScene +}; + +static const char *typeNames[] = { + "OBJ", + "CHR", + "SCENE", + "NUMBER", + "STRING", + "CLICK_INPUT", + "TEXT_INPUT" +}; + +static const char *operandTypeToStr(int type) { + if (type < 0 || type > 6) + return "UNKNOWN"; + + return typeNames[type]; +} + +struct Comparator { + char op; + OperandType o1; + OperandType o2; + int cmp; +} static comparators[] = { + { '=', NUMBER, NUMBER, kCompEqNumNum }, + { '=', OBJ, SCENE, kCompEqObjScene }, + { '=', CHR, SCENE, kCompEqChrScene }, + { '=', OBJ, CHR, kCompEqObjChr }, + { '=', CHR, CHR, kCompEqChrChr }, + { '=', SCENE, SCENE, kCompEqSceneScene }, + { '=', STRING, TEXT_INPUT, kCompEqStringTextInput }, + { '=', TEXT_INPUT, STRING, kCompEqTextInputString }, + { '=', NUMBER, TEXT_INPUT, kCompEqNumberTextInput }, + { '=', TEXT_INPUT, NUMBER, kCompEqTextInputNumber }, + + { '<', NUMBER, NUMBER, kCompLtNumNum }, + { '<', STRING, TEXT_INPUT, kCompLtStringTextInput }, + { '<', TEXT_INPUT, STRING, kCompLtTextInputString }, + { '<', OBJ, CHR, kCompLtObjChr }, + { '<', CHR, OBJ, kCompLtChrObj }, + { '<', OBJ, SCENE, kCompLtObjScene }, + { '<', CHR, CHR, kCompEqChrChr }, // Same logic as = + { '<', SCENE, SCENE, kCompEqSceneScene }, + + { '>', NUMBER, NUMBER, kCompGtNumNum }, + { '>', TEXT_INPUT, STRING, kCompLtTextInputString }, // Same logic as < + //FIXME: this prevents the below cases from working due to exact + //matches taking precedence over conversions... + //{ '>', STRING, STRING, kCompGtStringString }, // Same logic as < + { '>', OBJ, CHR, kCompLtObjChr }, // Same logic as < + { '>', OBJ, SCENE, kCompLtObjScene }, // Same logic as < + { '>', CHR, SCENE, kCompGtChrScene }, + + { 'M', OBJ, CHR, kMoveObjChr }, + { 'M', OBJ, SCENE, kMoveObjScene }, + { 'M', CHR, SCENE, kMoveChrScene }, + { 0, OBJ, OBJ, 0 } +}; + +bool Script::compare(Operand *o1, Operand *o2, int comparator) { + switch(comparator) { + case kCompEqNumNum: + return o1->_value.number == o2->_value.number; + case kCompEqObjScene: + for (ObjList::const_iterator it = o2->_value.scene->_objs.begin(); it != o2->_value.scene->_objs.end(); ++it) + if (*it == o1->_value.obj) + return true; + return false; + case kCompEqChrScene: + for (ChrList::const_iterator it = o2->_value.scene->_chrs.begin(); it != o2->_value.scene->_chrs.end(); ++it) + if (*it == o1->_value.chr) + return true; + return false; + case kCompEqObjChr: + for (ObjArray::const_iterator it = o2->_value.chr->_inventory.begin(); it != o2->_value.chr->_inventory.end(); ++it) + if (*it == o1->_value.obj) + return true; + return false; + case kCompEqChrChr: + return o1->_value.chr == o2->_value.chr; + case kCompEqSceneScene: + return o1->_value.scene == o2->_value.scene; + case kCompEqStringTextInput: + if (_inputText == NULL) { + return false; + } else { + Common::String s1(*_inputText), s2(*o1->_value.string); + s1.toLowercase(); + s2.toLowercase(); + + return s1.contains(s2); + } + case kCompEqTextInputString: + return compare(o2, o1, kCompEqStringTextInput); + case kCompEqNumberTextInput: + if (_inputText == NULL) { + return false; + } else { + Common::String s1(*_inputText), s2(o1->toString()); + s1.toLowercase(); + s2.toLowercase(); + + return s1.contains(s2); + } + case kCompEqTextInputNumber: + if (_inputText == NULL) { + return false; + } else { + Common::String s1(*_inputText), s2(o2->toString()); + s1.toLowercase(); + s2.toLowercase(); + + return s1.contains(s2); + } + case kCompLtNumNum: + return o1->_value.number < o2->_value.number; + case kCompLtStringTextInput: + return !compare(o1, o2, kCompEqStringTextInput); + case kCompLtTextInputString: + return !compare(o2, o1, kCompEqStringTextInput); + case kCompLtObjChr: + return o1->_value.obj->_currentOwner != o2->_value.chr; + case kCompLtChrObj: + return compare(o2, o1, kCompLtObjChr); + case kCompLtObjScene: + return o1->_value.obj->_currentScene != o2->_value.scene; + case kCompGtNumNum: + return o1->_value.number > o2->_value.number; + case kCompGtStringString: + return o1->_value.string == o2->_value.string; + case kCompGtChrScene: + return (o1->_value.chr != NULL && o1->_value.chr->_currentScene != o2->_value.scene); + case kMoveObjChr: + if (o1->_value.obj->_currentOwner != o2->_value.chr) { + _world->move(o1->_value.obj, o2->_value.chr); + _handled = true; // TODO: Is this correct? + } + break; + case kMoveObjScene: + if (o1->_value.obj->_currentScene != o2->_value.scene) { + _world->move(o1->_value.obj, o2->_value.scene); + // Note: This shouldn't call setHandled() - see + // Sultan's Palace 'Food and Drink' scene. + } + break; + case kMoveChrScene: + _world->move(o1->_value.chr, o2->_value.scene); + _handled = true; // TODO: Is this correct? + break; + } + + return false; +} + +bool Script::evaluatePair(Operand *lhs, const char *op, Operand *rhs) { + debug(7, "HANDLING CASE: [lhs=%s/%s, op=%s rhs=%s/%s]", + operandTypeToStr(lhs->_type), lhs->toString().c_str(), op, operandTypeToStr(rhs->_type), rhs->toString().c_str()); + + for (int cmp = 0; comparators[cmp].op != 0; cmp++) { + if (comparators[cmp].op != op[0]) + continue; + + if (comparators[cmp].o1 == lhs->_type && comparators[cmp].o2 == rhs->_type) + return compare(lhs, rhs, comparators[cmp].cmp); + } + + // Now, try partial matches. + Operand *c1, *c2; + for (int cmp = 0; comparators[cmp].op != 0; cmp++) { + if (comparators[cmp].op != op[0]) + continue; + + if (comparators[cmp].o1 == lhs->_type && + (c2 = convertOperand(rhs, comparators[cmp].o2)) != NULL) { + bool res = compare(lhs, c2, comparators[cmp].cmp); + delete c2; + return res; + } else if (comparators[cmp].o2 == rhs->_type && + (c1 = convertOperand(lhs, comparators[cmp].o1)) != NULL) { + bool res = compare(c1, rhs, comparators[cmp].cmp); + delete c1; + return res; + } + } + + // Now, try double conversion. + for (int cmp = 0; comparators[cmp].op != 0; cmp++) { + if (comparators[cmp].op != op[0]) + continue; + + if (comparators[cmp].o1 == lhs->_type || comparators[cmp].o2 == rhs->_type) + continue; + + if ((c1 = convertOperand(lhs, comparators[cmp].o1)) != NULL) { + if ((c2 = convertOperand(rhs, comparators[cmp].o2)) != NULL) { + bool res = compare(c1, c2, comparators[cmp].cmp); + delete c1; + delete c2; + return res; + } + delete c1; + } + } + + warning("UNHANDLED CASE: [lhs=%s/%s, op=%s rhs=%s/%s]", + operandTypeToStr(lhs->_type), lhs->toString().c_str(), op, operandTypeToStr(rhs->_type), rhs->toString().c_str()); + + return false; +} + +bool Script::eval(Operand *lhs, const char *op, Operand *rhs) { + bool result = false; + + if (lhs->_type == CLICK_INPUT || rhs->_type == CLICK_INPUT) { + return evalClickCondition(lhs, op, rhs); + } else if (!strcmp(op, "==") || !strcmp(op, ">>")) { + // TODO: check if >> can be used for click inputs and if == can be used for other things + // exact string match + if (lhs->_type == TEXT_INPUT) { + if ((rhs->_type != STRING && rhs->_type != NUMBER) || _inputText == NULL) { + result = false; + } else { + result = _inputText->equalsIgnoreCase(rhs->toString()); + } + } else if (rhs->_type == TEXT_INPUT) { + if ((lhs->_type != STRING && lhs->_type != NUMBER) || _inputText == NULL) { + result = false; + } else { + result = _inputText->equalsIgnoreCase(lhs->toString()); + } + } else { + error("UNHANDLED CASE: [lhs=%s/%s, rhs=%s/%s]", + operandTypeToStr(lhs->_type), lhs->toString().c_str(), operandTypeToStr(rhs->_type), rhs->toString().c_str()); + } + if (!strcmp(op, ">>")) { + result = !result; + } + + return result; + } else { + return evaluatePair(lhs, op, rhs); + } + + return false; +} + +Script::Operand *Script::convertOperand(Operand *operand, int type) { + if (operand->_type == type) + error("Incorrect conversion to type %d", type); + + if (type == SCENE) { + if (operand->_type == STRING || operand->_type == NUMBER) { + Common::String key(operand->toString()); + key.toLowercase(); + if (_world->_scenes.contains(key)) + return new Operand(_world->_scenes[key], SCENE); + } + } else if (type == OBJ) { + if (operand->_type == STRING || operand->_type == NUMBER) { + Common::String key = operand->toString(); + key.toLowercase(); + if (_world->_objs.contains(key)) + return new Operand(_world->_objs[key], OBJ); + } else if (operand->_type == CLICK_INPUT) { + if (_inputClick->_classType == OBJ) + return new Operand(_inputClick, OBJ); + } + } else if (type == CHR) { + if (operand->_type == STRING || operand->_type == NUMBER) { + Common::String key = operand->toString(); + key.toLowercase(); + if (_world->_chrs.contains(key)) + return new Operand(_world->_chrs[key], CHR); + } else if (operand->_type == CLICK_INPUT) { + if (_inputClick->_classType == CHR) + return new Operand(_inputClick, CHR); + } + } + + return NULL; +} + +bool Script::evalClickEquality(Operand *lhs, Operand *rhs, bool partialMatch) { + bool result = false; + if (lhs->_value.obj == NULL || rhs->_value.obj == NULL) { + result = false; + } else if (lhs->_value.obj == rhs->_value.obj) { + result = true; + } else if (rhs->_type == STRING) { + Common::String str = rhs->toString(); + str.toLowercase(); + + debug(9, "evalClickEquality(%s, %s, %d)", lhs->_value.designed->_name.c_str(), rhs->_value.designed->_name.c_str(), partialMatch); + debug(9, "l: %s r: %s)", operandTypeToStr(lhs->_type), operandTypeToStr(rhs->_type)); + debug(9, "class: %d", lhs->_value.inputClick->_classType); + + if (lhs->_value.inputClick->_classType == CHR || lhs->_value.inputClick->_classType == OBJ) { + Common::String name = lhs->_value.designed->_name; + name.toLowercase(); + + if (partialMatch) + result = name.contains(str); + else + result = name.equals(str); + } + + debug(9, "result: %d", result); + } + return result; +} + +bool Script::evalClickCondition(Operand *lhs, const char *op, Operand *rhs) { + // TODO: check if >> can be used for click inputs + if (strcmp(op, "==") && strcmp(op, "=") && strcmp(op, "<") && strcmp(op, ">")) { + error("Unknown operation '%s' for Script::evalClickCondition", op); + } + + bool partialMatch = strcmp(op, "=="); + bool result; + if (lhs->_type == CLICK_INPUT) { + result = evalClickEquality(lhs, rhs, partialMatch); + } else { + result = evalClickEquality(rhs, lhs, partialMatch); + } + if (!strcmp(op, "<") || !strcmp(op, ">")) { + // CLICK$<FOO only matches if there was a click + if (_inputClick == NULL) { + result = false; + } else { + result = !result; + } + } + return result; +} + +void Script::processMove() { + Operand *what = readOperand(); + byte skip = _data->readByte(); + if (skip != 0x8a) + error("Incorrect operator for MOVE: %02x", skip); + + Operand *to = readOperand(); + + skip = _data->readByte(); + if (skip != 0xfd) + error("No end for MOVE: %02x", skip); + + evaluatePair(what, "M", to); + + delete what; + delete to; +} + +void Script::processLet() { + const char *lastOp = NULL; + int16 result = 0; + int operandType = _data->readByte(); + int uservar = 0; + + if (operandType == 0xff) { + uservar = _data->readByte(); + } + + byte eq = _data->readByte(); // skip "=" operator + + debug(7, "processLet: 0x%x, uservar: 0x%x, eq: 0x%x", operandType, uservar, eq); + + do { + Operand *operand = readOperand(); + // TODO assert that value is NUMBER + int16 value = operand->_value.number; + delete operand; + if (lastOp != NULL) { + if (lastOp[0] == '+') + result += value; + else if (lastOp[0] == '-') + result -= value; + else if (lastOp[0] == '/') + result = (int16)(value == 0 ? 0 : result / value); + else if (lastOp[0] == '*') + result *= value; + } else { + result = value; + } + lastOp = readOperator(); + + if (lastOp[0] == ';') + break; + } while (true); + //System.out.println("processLet " + buildStringFromOffset(oldIndex - 1, index - oldIndex + 1) + "}"); + + assign(operandType, uservar, result); +} + +enum { + BLOCK_START, + BLOCK_END, + STATEMENT, + OPERATOR, + OPCODE +}; + +struct Mapping { + const char *cmd; + int type; +} static const mapping[] = { + { "IF{", STATEMENT }, // 0x80 + { "=", OPERATOR }, + { "<", OPERATOR }, + { ">", OPERATOR }, + { "}AND{", OPCODE }, + { "}OR{", OPCODE }, + { "\?\?\?(0x86)", OPCODE }, + { "EXIT\n", BLOCK_END }, + { "END\n", BLOCK_END }, // 0x88 + { "MOVE{", STATEMENT }, + { "}TO{", OPCODE }, + { "PRINT{", STATEMENT }, + { "SOUND{", STATEMENT }, + { "\?\?\?(0x8d)", OPCODE }, + { "LET{", STATEMENT }, + { "+", OPERATOR }, + { "-", OPERATOR }, // 0x90 + { "*", OPERATOR }, + { "/", OPERATOR }, + { "==", OPERATOR }, + { ">>", OPERATOR }, + { "MENU{", STATEMENT }, + { "\?\?\?(0x96)", OPCODE }, + { "\?\?\?(0x97)", OPCODE }, + { "\?\?\?(0x98)", OPCODE }, // 0x98 + { "\?\?\?(0x99)", OPCODE }, + { "\?\?\?(0x9a)", OPCODE }, + { "\?\?\?(0x9b)", OPCODE }, + { "\?\?\?(0x9c)", OPCODE }, + { "\?\?\?(0x9d)", OPCODE }, + { "\?\?\?(0x9e)", OPCODE }, + { "\?\?\?(0x9f)", OPCODE }, + { "TEXT$", OPCODE }, // 0xa0 + { "CLICK$", OPCODE }, + { "\?\?\?(0xa2)", OPCODE }, + { "\?\?\?(0xa3)", OPCODE }, + { "\?\?\?(0xa4)", OPCODE }, + { "\?\?\?(0xa5)", OPCODE }, + { "\?\?\?(0xa6)", OPCODE }, + { "\?\?\?(0xa7)", OPCODE }, + { "\?\?\?(0xa8)", OPCODE }, // 0xa8 + { "\?\?\?(0xa9)", OPCODE }, + { "\?\?\?(0xaa)", OPCODE }, + { "\?\?\?(0xab)", OPCODE }, + { "\?\?\?(0xac)", OPCODE }, + { "\?\?\?(0xad)", OPCODE }, + { "\?\?\?(0xae)", OPCODE }, + { "\?\?\?(0xaf)", OPCODE }, + { "VISITS#", OPCODE }, // 0xb0 // The number of scenes the player has visited, including repeated visits. + { "RANDOM#", OPCODE }, // RANDOM# for Star Trek, but VISITS# for some other games? + { "LOOP#", OPCODE }, // The number of commands the player has given in the current scene. + { "VICTORY#", OPCODE }, // The number of characters killed. + { "BADCOPY#", OPCODE }, + { "RANDOM#", OPCODE }, // A random number between 1 and 100. + { "\?\?\?(0xb6)", OPCODE }, + { "\?\?\?(0xb7)", OPCODE }, + { "\?\?\?(0xb8)", OPCODE }, // 0xb8 + { "\?\?\?(0xb9)", OPCODE }, + { "\?\?\?(0xba)", OPCODE }, + { "\?\?\?(0xbb)", OPCODE }, + { "\?\?\?(0xbc)", OPCODE }, + { "\?\?\?(0xbd)", OPCODE }, + { "\?\?\?(0xbe)", OPCODE }, + { "\?\?\?(0xbf)", OPCODE }, + { "STORAGE@", OPCODE }, // 0xc0 + { "SCENE@", OPCODE }, + { "PLAYER@", OPCODE }, + { "MONSTER@", OPCODE }, + { "RANDOMSCN@", OPCODE }, + { "RANDOMCHR@", OPCODE }, + { "RANDOMOBJ@", OPCODE }, + { "\?\?\?(0xc7)", OPCODE }, + { "\?\?\?(0xc8)", OPCODE }, // 0xc8 + { "\?\?\?(0xc9)", OPCODE }, + { "\?\?\?(0xca)", OPCODE }, + { "\?\?\?(0xcb)", OPCODE }, + { "\?\?\?(0xcc)", OPCODE }, + { "\?\?\?(0xcd)", OPCODE }, + { "\?\?\?(0xce)", OPCODE }, + { "\?\?\?(0xcf)", OPCODE }, + { "PHYS.STR.BAS#", OPCODE }, // 0xd0 + { "PHYS.HIT.BAS#", OPCODE }, + { "PHYS.ARM.BAS#", OPCODE }, + { "PHYS.ACC.BAS#", OPCODE }, + { "SPIR.STR.BAS#", OPCODE }, + { "SPIR.HIT.BAS#", OPCODE }, + { "SPIR.ARM.BAS#", OPCODE }, + { "SPIR.ACC.BAS#", OPCODE }, + { "PHYS.SPE.BAS#", OPCODE }, // 0xd8 + { "\?\?\?(0xd9)", OPCODE }, + { "\?\?\?(0xda)", OPCODE }, + { "\?\?\?(0xdb)", OPCODE }, + { "\?\?\?(0xdc)", OPCODE }, + { "\?\?\?(0xdd)", OPCODE }, + { "\?\?\?(0xde)", OPCODE }, + { "\?\?\?(0xdf)", OPCODE }, + { "PHYS.STR.CUR#", OPCODE }, // 0xe0 + { "PHYS.HIT.CUR#", OPCODE }, + { "PHYS.ARM.CUR#", OPCODE }, + { "PHYS.ACC.CUR#", OPCODE }, + { "SPIR.STR.CUR#", OPCODE }, + { "SPIR.HIT.CUR#", OPCODE }, + { "SPIR.ARM.CUR#", OPCODE }, + { "SPIR.ACC.CUR#", OPCODE }, + { "PHYS.SPE.CUR#", OPCODE }, // 0xe8 + { "\?\?\?(0xe9)", OPCODE }, + { "\?\?\?(0xea)", OPCODE }, + { "\?\?\?(0xeb)", OPCODE }, + { "\?\?\?(0xec)", OPCODE }, + { "\?\?\?(0xed)", OPCODE }, + { "\?\?\?(0xee)", OPCODE }, + { "\?\?\?(0xef)", OPCODE }, + { "\?\?\?(0xf0)", OPCODE }, + { "\?\?\?(0xf1)", OPCODE }, + { "\?\?\?(0xf2)", OPCODE }, + { "\?\?\?(0xf3)", OPCODE }, + { "\?\?\?(0xf4)", OPCODE }, + { "\?\?\?(0xf5)", OPCODE }, + { "\?\?\?(0xf6)", OPCODE }, + { "\?\?\?(0xf7)", OPCODE }, + { "\?\?\?(0xf8)", OPCODE }, // 0xa8 + { "\?\?\?(0xf9)", OPCODE }, + { "\?\?\?(0xfa)", OPCODE }, + { "\?\?\?(0xfb)", OPCODE }, + { "\?\?\?(0xfc)", OPCODE }, + { "}\n", OPCODE }, + { "}THEN\n", BLOCK_START }, + { "\?\?\?(0xff)", OPCODE } // Uservar +}; + +void Script::convertToText() { + _data->seek(12); + + int indentLevel = 0; + ScriptText *scr = new ScriptText; + scr->offset = _data->pos(); + + while(true) { + int c = _data->readByte(); + + if (_data->eos()) + break; + + if (c < 0x80) { + if (c < 0x20) + error("convertToText: Unknown code 0x%02x at %d", c, _data->pos()); + + do { + scr->line += c; + c = _data->readByte(); + } while (c < 0x80); + + _data->seek(-1, SEEK_CUR); + } else if (c == 0xff) { + int value = _data->readByte(); + value -= 1; + scr->line += (char)('A' + (value / 9)); + scr->line += (char)('0' + (value % 9) + 1); + scr->line += '#'; + } else { + const char *cmd = mapping[c - 0x80].cmd; + int type = mapping[c - 0x80].type; + + if (type == STATEMENT) { + for (int i = 0; i < indentLevel; i++) + scr->line += ' '; + } else if (type == BLOCK_START) { + indentLevel += 2; + } else if (type == BLOCK_END) { + indentLevel -= 2; + for (int i = 0; i < indentLevel; i++) + scr->line += ' '; + } + + scr->line += cmd; + + if (strchr(cmd, '\n')) { + scr->line.deleteLastChar(); + + _scriptText.push_back(scr); + + scr = new ScriptText; + scr->offset = _data->pos(); + } + } + } + + if (!scr->line.empty()) + _scriptText.push_back(scr); + else + delete scr; +} + +} // End of namespace Wage diff --git a/engines/wage/script.h b/engines/wage/script.h new file mode 100644 index 0000000000..de9476228c --- /dev/null +++ b/engines/wage/script.h @@ -0,0 +1,161 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef WAGE_SCRIPT_H +#define WAGE_SCRIPT_H + +namespace Wage { + +class Script { +public: + Script(Common::SeekableReadStream *data); + ~Script(); + +private: + Common::SeekableReadStream *_data; + + WageEngine *_engine; + World *_world; + int _loopCount; + Common::String *_inputText; + Designed *_inputClick; + bool _handled; + + class Operand { + public: + union { + Obj *obj; + Chr *chr; + Designed *designed; + Scene *scene; + int16 number; + Common::String *string; + Designed *inputClick; + } _value; + OperandType _type; + + Operand(Obj *value, OperandType type) { + _value.obj = value; + assert(type == OBJ); + _type = type; + } + + Operand(Chr *value, OperandType type) { + _value.chr = value; + assert(type == CHR); + _type = type; + } + + Operand(Scene *value, OperandType type) { + _value.scene = value; + assert(type == SCENE); + _type = type; + } + + Operand(int value, OperandType type) { + _value.number = value; + assert(type == NUMBER); + _type = type; + } + + Operand(Common::String *value, OperandType type) { + _value.string = value; + assert(type == STRING || type == TEXT_INPUT); + _type = type; + } + + Operand(Designed *value, OperandType type) { + _value.inputClick = value; + assert(type == CLICK_INPUT); + _type = type; + } + + ~Operand() { + if (_type == STRING) + delete _value.string; + } + + Common::String toString(); + }; + + struct ScriptText { + int offset; + Common::String line; + }; + +public: + void print(); + void printLine(int offset); + bool execute(World *world, int loopCount, Common::String *inputText, Designed *inputClick, WageEngine *engine); + +private: + Operand *readOperand(); + Operand *readStringOperand(); + const char *readOperator(); + void processIf(); + void skipBlock(); + void skipIf(); + bool compare(Operand *o1, Operand *o2, int comparator); + bool eval(Operand *lhs, const char *op, Operand *rhs); + bool evaluatePair(Operand *lhs, const char *op, Operand *rhs); + Operand *convertOperand(Operand *operand, int type); + bool evalClickCondition(Operand *lhs, const char *op, Operand *rhs); + bool evalClickEquality(Operand *lhs, Operand *rhs, bool partialMatch); + void processMove(); + void processLet(); + + void assign(byte operandType, int uservar, uint16 value); + + void convertToText(); + +public: + Common::Array<ScriptText *> _scriptText; +}; + +} // End of namespace Wage + +#endif diff --git a/engines/wage/sound.cpp b/engines/wage/sound.cpp new file mode 100644 index 0000000000..bcb274cab9 --- /dev/null +++ b/engines/wage/sound.cpp @@ -0,0 +1,86 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "common/stream.h" + +#include "wage/wage.h" +#include "wage/sound.h" + +namespace Wage { + +static const int8 deltas[] = { 0,-49,-36,-25,-16,-9,-4,-1,0,1,4,9,16,25,36,49 }; + +Sound::Sound(Common::String name, Common::SeekableReadStream *data) : _name(name) { + int size = data->size() - 20; + _data = (byte *)calloc(2 * size, 1); + + data->skip(20); // Skip header + + byte value = 0x80; + for (int i = 0; i < size; i++) { + byte d = data->readByte(); + value += deltas[d & 0xf]; + _data[i * 2] = value; + value += deltas[(d >> 4) & 0xf]; + _data[i * 2 + 1] = value; + } +} + +Sound::~Sound() { + free(_data); +} + +void WageEngine::playSound(Common::String soundName) { + warning("STUB: WageEngine::playSound(%s)", soundName.c_str()); +} + +void WageEngine::updateSoundTimerForScene(Scene *scene, bool firstTime) { + //warning("STUB: WageEngine::updateSoundTimerForScene()"); +} + + +} // End of namespace Wage diff --git a/engines/wage/sound.h b/engines/wage/sound.h new file mode 100644 index 0000000000..eccfe33170 --- /dev/null +++ b/engines/wage/sound.h @@ -0,0 +1,64 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef WAGE_SOUND_H +#define WAGE_SOUND_H + +namespace Wage { + +class Sound { +public: + Sound(Common::String name, Common::SeekableReadStream *data); + ~Sound(); + + Common::String _name; + byte *_data; +}; + +} // End of namespace Wage + +#endif diff --git a/engines/wage/util.cpp b/engines/wage/util.cpp new file mode 100644 index 0000000000..f31a83ca04 --- /dev/null +++ b/engines/wage/util.cpp @@ -0,0 +1,125 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "common/stream.h" + +#include "wage/wage.h" + +namespace Wage { + +Common::String readPascalString(Common::SeekableReadStream *in) { + Common::String s; + char *buf; + int len; + int i; + + len = in->readByte(); + buf = (char *)malloc(len + 1); + for (i = 0; i < len; i++) { + buf[i] = in->readByte(); + if (buf[i] == 0x0d) + buf[i] = '\n'; + } + + buf[i] = 0; + + s = buf; + free(buf); + + return s; +} + +Common::Rect *readRect(Common::SeekableReadStream *in) { + int x1, y1, x2, y2; + + y1 = in->readUint16BE(); + x1 = in->readUint16BE(); + y2 = in->readUint16BE() + 4; + x2 = in->readUint16BE() + 4; + + debug(9, "readRect: %d, %d, %d, %d", x1, y1, x2, y2); + + return new Common::Rect(x1, y1, x2, y2); +} + +const char *getIndefiniteArticle(const Common::String &word) { + switch (word[0]) { + case 'a': case 'A': + case 'e': case 'E': + case 'i': case 'I': + case 'o': case 'O': + case 'u': case 'U': + return "an "; + } + return "a "; +} + +enum { + GENDER_MALE = 0, + GENDER_FEMALE = 1, + GENDER_NEUTRAL = 2 +}; + +const char *prependGenderSpecificPronoun(int gender) { + if (gender == GENDER_MALE) + return "his "; + else if (gender == GENDER_FEMALE) + return "her "; + else + return "its "; +} + +const char *getGenderSpecificPronoun(int gender, bool capitalize) { + if (gender == GENDER_MALE) + return capitalize ? "He" : "he"; + else if (gender == GENDER_FEMALE) + return capitalize ? "She" : "she"; + else + return capitalize ? "It" : "it"; +} + +} // End of namespace Wage diff --git a/engines/wage/wage.cpp b/engines/wage/wage.cpp new file mode 100644 index 0000000000..e0299c8da2 --- /dev/null +++ b/engines/wage/wage.cpp @@ -0,0 +1,515 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "common/debug-channels.h" +#include "common/error.h" +#include "common/events.h" +#include "common/system.h" + +#include "engines/engine.h" +#include "engines/util.h" + +#include "wage/wage.h" +#include "wage/entities.h" +#include "wage/gui.h" +#include "wage/dialog.h" +#include "wage/script.h" +#include "wage/world.h" + +namespace Wage { + +WageEngine::WageEngine(OSystem *syst, const ADGameDescription *desc) : Engine(syst), _gameDescription(desc) { + _rnd = new Common::RandomSource("wage"); + + _aim = -1; + _opponentAim = -1; + _temporarilyHidden = false; + _isGameOver = false; + _monster = NULL; + _running = NULL; + _lastScene = NULL; + + _loopCount = 0; + _turn = 0; + + _commandWasQuick = false; + + _shouldQuit = false; + + _gui = NULL; + _world = NULL; + _console = NULL; + _offer = NULL; + + _resManager = NULL; + + debug("WageEngine::WageEngine()"); +} + +WageEngine::~WageEngine() { + debug("WageEngine::~WageEngine()"); + + DebugMan.clearAllDebugChannels(); + delete _world; + delete _resManager; + delete _gui; + delete _rnd; + delete _console; +} + +Common::Error WageEngine::run() { + debug("WageEngine::init"); + + initGraphics(512, 342, true); + + // Create debugger console. It requires GFX to be initialized + _console = new Console(this); + + _debugger = new Debugger(this); + + // Your main event loop should be (invoked from) here. + _resManager = new Common::MacResManager(); + if (!_resManager->open(getGameFile())) + error("Could not open %s as a resource fork", getGameFile()); + + _world = new World(this); + + if (!_world->loadWorld(_resManager)) + return Common::kNoGameDataFoundError; + + _gui = new Gui(this); + + _temporarilyHidden = true; + performInitialSetup(); + Common::String input("look"); + processTurn(&input, NULL); + _temporarilyHidden = false; + + _shouldQuit = false; + + while (!_shouldQuit) { + _debugger->onFrame(); + + processEvents(); + + _gui->draw(); + g_system->updateScreen(); + g_system->delayMillis(50); + } + + return Common::kNoError; +} + +void WageEngine::processEvents() { + Common::Event event; + + while (_eventMan->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_QUIT: + if (saveDialog()) + _shouldQuit = true; + break; + case Common::EVENT_MOUSEMOVE: + _gui->mouseMove(event.mouse.x, event.mouse.y); + break; + case Common::EVENT_LBUTTONDOWN: + _gui->mouseDown(event.mouse.x, event.mouse.y); + break; + case Common::EVENT_LBUTTONUP: + { + Designed *obj = _gui->mouseUp(event.mouse.x, event.mouse.y); + if (obj != NULL) + processTurn(NULL, obj); + } + break; + case Common::EVENT_KEYDOWN: + switch (event.kbd.keycode) { + case Common::KEYCODE_BACKSPACE: + if (!_inputText.empty()) { + _inputText.deleteLastChar(); + _gui->drawInput(); + } + break; + + case Common::KEYCODE_RETURN: + if (_inputText.empty()) + break; + + processTurn(&_inputText, NULL); + _gui->disableUndo(); + break; + + default: + if (event.kbd.ascii == '~') { + _debugger->attach(); + break; + } + + if (event.kbd.flags & (Common::KBD_ALT | Common::KBD_CTRL | Common::KBD_META)) { + if (event.kbd.ascii >= 0x20 && event.kbd.ascii <= 0x7f) { + _gui->processMenuShortCut(event.kbd.flags, event.kbd.ascii); + } + break; + } + + if (event.kbd.ascii >= 0x20 && event.kbd.ascii <= 0x7f) { + _inputText += (char)event.kbd.ascii; + _gui->drawInput(); + } + + break; + } + break; + + default: + break; + } + } +} + +void WageEngine::setMenu(Common::String menu) { + _world->_commandsMenu = menu; + + _gui->regenCommandsMenu(); +} + +void WageEngine::appendText(const char *str) { + _gui->appendText(str); + + _inputText.clear(); +} + +void WageEngine::gameOver() { + DialogButtonArray buttons; + + buttons.push_back(new DialogButton("OK", 66, 67, 68, 28)); + + Dialog gameOverDialog(_gui, 199, _world->_gameOverMessage->c_str(), &buttons, 0); + + gameOverDialog.run(); + + doClose(); + + _gui->disableAllMenus(); + _gui->enableNewGameMenus(); + _gui->_menuDirty = true; +} + +bool WageEngine::saveDialog() { + DialogButtonArray buttons; + + buttons.push_back(new DialogButton("No", 19, 67, 68, 28)); + buttons.push_back(new DialogButton("Yes", 112, 67, 68, 28)); + buttons.push_back(new DialogButton("Cancel", 205, 67, 68, 28)); + + Dialog save(_gui, 291, "Save changes before closing?", &buttons, 1); + + int button = save.run(); + + if (button == 2) // Cancel + return false; + + if (button == 1) + saveGame(); + + doClose(); + + return true; +} + +void WageEngine::saveGame() { + warning("STUB: saveGame()"); +} + +void WageEngine::performInitialSetup() { + debug(5, "Resetting Objs: %d", _world->_orderedObjs.size()); + for (uint i = 0; i < _world->_orderedObjs.size() - 1; i++) + _world->move(_world->_orderedObjs[i], _world->_storageScene, true); + + _world->move(_world->_orderedObjs[_world->_orderedObjs.size() - 1], _world->_storageScene); + + debug(5, "Resetting Chrs: %d", _world->_orderedChrs.size()); + for (uint i = 0; i < _world->_orderedChrs.size() - 1; i++) + _world->move(_world->_orderedChrs[i], _world->_storageScene, true); + + _world->move(_world->_orderedChrs[_world->_orderedChrs.size() - 1], _world->_storageScene); + + debug(5, "Resetting Owners: %d", _world->_orderedObjs.size()); + for (uint i = 0; i < _world->_orderedObjs.size(); i++) { + Obj *obj = _world->_orderedObjs[i]; + if (!obj->_sceneOrOwner.equalsIgnoreCase(STORAGESCENE)) { + Common::String location = obj->_sceneOrOwner; + location.toLowercase(); + if (_world->_scenes.contains(location)) { + _world->move(obj, _world->_scenes[location]); + } else { + if (!_world->_chrs.contains(location)) { + // Note: PLAYER@ is not a valid target here. + warning("Couldn't move %s to %s", obj->_name.c_str(), obj->_sceneOrOwner.c_str()); + } else { + // TODO: Add check for max items. + _world->move(obj, _world->_chrs[location]); + } + } + } + } + + bool playerPlaced = false; + for (uint i = 0; i < _world->_orderedChrs.size(); i++) { + Chr *chr = _world->_orderedChrs[i]; + if (!chr->_initialScene.equalsIgnoreCase(STORAGESCENE)) { + Common::String key = chr->_initialScene; + key.toLowercase(); + if (_world->_scenes.contains(key) && _world->_scenes[key] != NULL) { + _world->move(chr, _world->_scenes[key]); + + if (chr->_playerCharacter) + debug(0, "Initial scene: %s", key.c_str()); + } else { + _world->move(chr, _world->getRandomScene()); + } + if (chr->_playerCharacter) { + playerPlaced = true; + } + } + chr->wearObjs(); + } + if (!playerPlaced) { + _world->move(_world->_player, _world->getRandomScene()); + } +} + +void WageEngine::doClose() { + warning("STUB: doClose()"); +} + +Scene *WageEngine::getSceneByName(Common::String &location) { + Scene *scene; + if (location.equals("random@")) { + scene = _world->getRandomScene(); + } else { + scene = _world->_scenes[location]; + } + return scene; +} + +void WageEngine::onMove(Designed *what, Designed *from, Designed *to) { + Chr *player = _world->_player; + Scene *currentScene = player->_currentScene; + if (currentScene == _world->_storageScene && !_temporarilyHidden) { + if (!_isGameOver) { + _isGameOver = true; + gameOver(); + } + return; + } + + if (from == currentScene || to == currentScene || + (what->_classType == CHR && ((Chr *)what)->_currentScene == currentScene) || + (what->_classType == OBJ && ((Obj *)what)->_currentScene == currentScene)) + _gui->setSceneDirty(); + + if ((from == player || to == player) && !_temporarilyHidden) + _gui->regenWeaponsMenu(); + + if (what != player && what->_classType == CHR) { + Chr *chr = (Chr *)what; + if (to == _world->_storageScene) { + int returnTo = chr->_returnTo; + if (returnTo != Chr::RETURN_TO_STORAGE) { + Common::String returnToSceneName; + if (returnTo == Chr::RETURN_TO_INITIAL_SCENE) { + returnToSceneName = chr->_initialScene; + returnToSceneName.toLowercase(); + } else { + returnToSceneName = "random@"; + } + Scene *scene = getSceneByName(returnToSceneName); + if (scene != NULL && scene != _world->_storageScene) { + _world->move(chr, scene); + // To avoid sleeping twice, return if the above move command would cause a sleep. + if (scene == currentScene) + return; + } + } + } else if (to == player->_currentScene) { + if (getMonster() == NULL) { + _monster = chr; + encounter(player, chr); + } + } + } + if (!_temporarilyHidden) { + if (to == currentScene || from == currentScene) { + redrawScene(); + g_system->delayMillis(100); + } + } +} + +void WageEngine::redrawScene() { + Scene *currentScene = _world->_player->_currentScene; + + if (currentScene != NULL) { + bool firstTime = (_lastScene != currentScene); + + updateSoundTimerForScene(currentScene, firstTime); + } +} + +void WageEngine::processTurnInternal(Common::String *textInput, Designed *clickInput) { + Scene *playerScene = _world->_player->_currentScene; + if (playerScene == _world->_storageScene) + return; + + bool shouldEncounter = false; + + if (playerScene != _lastScene) { + _loopCount = 0; + _lastScene = playerScene; + _monster = NULL; + _running = NULL; + _offer = NULL; + + for (ChrList::const_iterator it = playerScene->_chrs.begin(); it != playerScene->_chrs.end(); ++it) { + if (!(*it)->_playerCharacter) { + _monster = *it; + shouldEncounter = true; + break; + } + } + } + + bool monsterWasNull = (_monster == NULL); + Script *script = playerScene->_script != NULL ? playerScene->_script : _world->_globalScript; + bool handled = script->execute(_world, _loopCount++, textInput, clickInput, this); + + playerScene = _world->_player->_currentScene; + + if (playerScene == _world->_storageScene) + return; + + if (playerScene != _lastScene) { + _temporarilyHidden = true; + _gui->clearOutput(); + regen(); + Common::String input("look"); + processTurnInternal(&input, NULL); + redrawScene(); + _temporarilyHidden = false; + } else if (_loopCount == 1) { + redrawScene(); + if (shouldEncounter && getMonster() != NULL) { + encounter(_world->_player, _monster); + } + } else if (textInput != NULL && !handled) { + if (monsterWasNull && getMonster() != NULL) + return; + + const char *rant = _rnd->getRandomNumber(1) ? "What?" : "Huh?"; + + appendText(rant); + _commandWasQuick = true; + } +} + +void WageEngine::processTurn(Common::String *textInput, Designed *clickInput) { + _commandWasQuick = false; + Scene *prevScene = _world->_player->_currentScene; + Chr *prevMonster = getMonster(); + Common::String input; + + if (textInput) + input = *textInput; + + input.toLowercase(); + if (input.equals("e")) + input = "east"; + else if (input.equals("w")) + input = "west"; + else if (input.equals("n")) + input = "north"; + else if (input.equals("s")) + input = "south"; + + processTurnInternal(&input, clickInput); + Scene *playerScene = _world->_player->_currentScene; + + if (prevScene != playerScene && playerScene != _world->_storageScene) { + if (prevMonster != NULL) { + bool followed = false; + if (getMonster() == NULL) { + // TODO: adjacent scenes doesn't contain up/down etc... verify that monsters can't follow these... + if (_world->scenesAreConnected(playerScene, prevMonster->_currentScene)) { + int chance = _rnd->getRandomNumber(255); + followed = (chance < prevMonster->_followsOpponent); + } + } + + char buf[512]; + + if (followed) { + snprintf(buf, 512, "%s%s follows you.", prevMonster->getDefiniteArticle(true), prevMonster->_name.c_str()); + appendText(buf); + + _world->move(prevMonster, playerScene); + } else { + snprintf(buf, 512, "You escape %s%s.", prevMonster->getDefiniteArticle(false), prevMonster->_name.c_str()); + appendText(buf); + } + } + } + if (!_commandWasQuick && getMonster() != NULL) { + performCombatAction(getMonster(), _world->_player); + } + + _inputText.clear(); + _gui->appendText(""); +} + + +} // End of namespace Wage diff --git a/engines/wage/wage.h b/engines/wage/wage.h new file mode 100644 index 0000000000..8ca306aea3 --- /dev/null +++ b/engines/wage/wage.h @@ -0,0 +1,239 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef WAGE_WAGE_H +#define WAGE_WAGE_H + +#include "engines/engine.h" +#include "common/debug.h" +#include "common/endian.h" +#include "common/rect.h" +#include "common/macresman.h" +#include "common/random.h" + +#include "wage/debugger.h" + +struct ADGameDescription; + +namespace Wage { + +class Console; +class Chr; +class Designed; +class Dialog; +class Gui; +class Obj; +class Scene; +class World; + +typedef Common::Array<Obj *> ObjArray; +typedef Common::Array<Chr *> ChrArray; +typedef Common::List<Obj *> ObjList; +typedef Common::List<Chr *> ChrList; + +enum OperandType { + OBJ = 0, + CHR = 1, + SCENE = 2, + NUMBER = 3, + STRING = 4, + CLICK_INPUT = 5, + TEXT_INPUT = 6, + UNKNOWN = 100 +}; + +enum Directions { + NORTH = 0, + SOUTH = 1, + EAST = 2, + WEST = 3 +}; + +// our engine debug levels +enum { + kWageDebugExample = 1 << 0, + kWageDebugExample2 = 1 << 1 + // next new level must be 1 << 2 (4) + // the current limitation is 32 debug levels (1 << 31 is the last one) +}; + +enum { + kColorBlack = 0, + kColorGray = 1, + kColorWhite = 2, + kColorGreen = 3 +}; + +Common::String readPascalString(Common::SeekableReadStream *in); +Common::Rect *readRect(Common::SeekableReadStream *in); +const char *getIndefiniteArticle(const Common::String &word); +const char *prependGenderSpecificPronoun(int gender); +const char *getGenderSpecificPronoun(int gender, bool capitalize); + + +typedef Common::Array<byte *> Patterns; + +class WageEngine : public Engine { + friend class Dialog; +public: + WageEngine(OSystem *syst, const ADGameDescription *gameDesc); + ~WageEngine(); + + virtual bool hasFeature(EngineFeature f) const; + + virtual Common::Error run(); + + bool canLoadGameStateCurrently(); + bool canSaveGameStateCurrently(); + + const char *getGameFile() const; + void processTurn(Common::String *textInput, Designed *clickInput); + void regen(); + +private: + bool loadWorld(Common::MacResManager *resMan); + void performInitialSetup(); + void wearObjs(Chr *chr); + void processTurnInternal(Common::String *textInput, Designed *clickInput); + void performCombatAction(Chr *npc, Chr *player); + int getValidMoveDirections(Chr *npc); + void performAttack(Chr *attacker, Chr *victim, Obj *weapon); + void performMagic(Chr *attacker, Chr *victim, Obj *magicalObject); + void performMove(Chr *chr, int validMoves); + void performOffer(Chr *attacker, Chr *victim); + void performTake(Chr *npc, Obj *obj); + void decrementUses(Obj *obj); + bool attackHit(Chr *attacker, Chr *victim, Obj *weapon, int targetIndex); + void performHealingMagic(Chr *chr, Obj *magicalObject); + + void doClose(); + void updateSoundTimerForScene(Scene *scene, bool firstTime); + +public: + void takeObj(Obj *obj); + + bool handleMoveCommand(Directions dir, const char *dirName); + bool handleLookCommand(); + Common::String *getGroundItemsList(Scene *scene); + void appendObjNames(Common::String &str, const ObjArray &objs); + bool handleInventoryCommand(); + bool handleStatusCommand(); + bool handleRestCommand(); + bool handleAcceptCommand(); + + bool handleTakeCommand(const char *target); + bool handleDropCommand(const char *target); + bool handleAimCommand(const char *target); + bool handleWearCommand(const char *target); + bool handleOfferCommand(const char *target); + + void wearObj(Obj *o, int pos); + + bool tryAttack(const Obj *weapon, const Common::String &input); + bool handleAttack(Obj *weapon); + + void printPlayerCondition(Chr *player); + const char *getPercentMessage(double percent); + +public: + Common::RandomSource *_rnd; + + Debugger *_debugger; + + Gui *_gui; + World *_world; + + Scene *_lastScene; + int _loopCount; + int _turn; + Chr *_monster; + Chr *_running; + Obj *_offer; + int _aim; + int _opponentAim; + bool _temporarilyHidden; + bool _isGameOver; + bool _commandWasQuick; + + Common::String _inputText; + + void playSound(Common::String soundName); + void setMenu(Common::String soundName); + void appendText(const char *str); + void gameOver(); + bool saveDialog(); + Obj *getOffer(); + Chr *getMonster(); + void processEvents(); + Scene *getSceneByName(Common::String &location); + void onMove(Designed *what, Designed *from, Designed *to); + void encounter(Chr *player, Chr *chr); + void redrawScene(); + void saveGame(); + + virtual GUI::Debugger *getDebugger() { return _debugger; } + +private: + Console *_console; + + const ADGameDescription *_gameDescription; + + Common::MacResManager *_resManager; + + bool _shouldQuit; +}; + +// Example console class +class Console : public GUI::Debugger { +public: + Console(WageEngine *vm) {} + virtual ~Console(void) {} +}; + +} // End of namespace Wage + +#endif diff --git a/engines/wage/world.cpp b/engines/wage/world.cpp new file mode 100644 index 0000000000..40b1555e35 --- /dev/null +++ b/engines/wage/world.cpp @@ -0,0 +1,523 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "common/file.h" + +#include "wage/wage.h" +#include "wage/entities.h" +#include "wage/script.h" +#include "wage/sound.h" +#include "wage/world.h" + +namespace Wage { + +World::World(WageEngine *engine) { + _storageScene = new Scene; + _storageScene->_name = STORAGESCENE; + + _orderedScenes.push_back(_storageScene); + _scenes[STORAGESCENE] = _storageScene; + + _gameOverMessage = nullptr; + _saveBeforeQuitMessage = nullptr; + _saveBeforeCloseMessage = nullptr; + _revertMessage = nullptr; + + _globalScript = nullptr; + _player = nullptr; + + _weaponMenuDisabled = true; + + _engine = engine; +} + +World::~World() { + for (uint i = 0; i < _orderedObjs.size(); i++) + delete _orderedObjs[i]; + + for (uint i = 0; i < _orderedChrs.size(); i++) + delete _orderedChrs[i]; + + for (uint i = 0; i < _orderedSounds.size(); i++) + delete _orderedSounds[i]; + + for (uint i = 0; i < _orderedScenes.size(); i++) + delete _orderedScenes[i]; + + for (uint i = 0; i < _patterns.size(); i++) + free(_patterns[i]); + + delete _globalScript; + + delete _gameOverMessage; + delete _saveBeforeQuitMessage; + delete _saveBeforeCloseMessage; + delete _revertMessage; + +} + +bool World::loadWorld(Common::MacResManager *resMan) { + Common::MacResIDArray resArray; + Common::SeekableReadStream *res; + Common::MacResIDArray::const_iterator iter; + + if ((resArray = resMan->getResIDArray(MKTAG('G','C','O','D'))).size() == 0) + return false; + + // Load global script + res = resMan->getResource(MKTAG('G','C','O','D'), resArray[0]); + _globalScript = new Script(res); + + // TODO: read creator + + // Load main configuration + if ((resArray = resMan->getResIDArray(MKTAG('V','E','R','S'))).size() == 0) + return false; + + _name = resMan->getBaseFileName(); + + if (resArray.size() > 1) + warning("Too many VERS resources"); + + if (!resArray.empty()) { + debug(3, "Loading version info"); + + res = resMan->getResource(MKTAG('V','E','R','S'), resArray[0]); + + res->skip(10); + byte b = res->readByte(); + _weaponMenuDisabled = (b != 0); + if (b != 0 && b != 1) + error("Unexpected value for weapons menu"); + + res->skip(3); + _aboutMessage = readPascalString(res); + + if (!scumm_stricmp(resMan->getBaseFileName().c_str(), "Scepters")) + res->skip(1); // ???? + + _soundLibrary1 = readPascalString(res); + _soundLibrary2 = readPascalString(res); + + delete res; + } + + Common::String *message; + if ((message = loadStringFromDITL(resMan, 2910, 1)) != NULL) { + message->trim(); + debug(2, "_gameOverMessage: %s", message->c_str()); + _gameOverMessage = message; + } + if ((message = loadStringFromDITL(resMan, 2480, 3)) != NULL) { + message->trim(); + debug(2, "_saveBeforeQuitMessage: %s", message->c_str()); + _saveBeforeQuitMessage = message; + } + if ((message = loadStringFromDITL(resMan, 2490, 3)) != NULL) { + message->trim(); + debug(2, "_saveBeforeCloseMessage: %s", message->c_str()); + _saveBeforeCloseMessage = message; + } + if ((message = loadStringFromDITL(resMan, 2940, 2)) != NULL) { + message->trim(); + debug(2, "_revertMessage: %s", message->c_str()); + _revertMessage = message; + } + + // Load scenes + resArray = resMan->getResIDArray(MKTAG('A','S','C','N')); + debug(3, "Loading %d scenes", resArray.size()); + + for (iter = resArray.begin(); iter != resArray.end(); ++iter) { + res = resMan->getResource(MKTAG('A','S','C','N'), *iter); + Scene *scene = new Scene(resMan->getResName(MKTAG('A','S','C','N'), *iter), res); + + res = resMan->getResource(MKTAG('A','C','O','D'), *iter); + if (res != NULL) + scene->_script = new Script(res); + + res = resMan->getResource(MKTAG('A','T','X','T'), *iter); + if (res != NULL) { + scene->_textBounds = readRect(res); + scene->_fontType = res->readUint16BE(); + scene->_fontSize = res->readUint16BE(); + + Common::String text; + while (res->pos() < res->size()) { + char c = res->readByte(); + if (c == 0x0d) + c = '\n'; + text += c; + } + scene->_text = text; + + delete res; + } + addScene(scene); + } + + // Load Objects + resArray = resMan->getResIDArray(MKTAG('A','O','B','J')); + debug(3, "Loading %d objects", resArray.size()); + + for (iter = resArray.begin(); iter != resArray.end(); ++iter) { + res = resMan->getResource(MKTAG('A','O','B','J'), *iter); + addObj(new Obj(resMan->getResName(MKTAG('A','O','B','J'), *iter), res)); + } + + // Load Characters + resArray = resMan->getResIDArray(MKTAG('A','C','H','R')); + debug(3, "Loading %d characters", resArray.size()); + + for (iter = resArray.begin(); iter != resArray.end(); ++iter) { + res = resMan->getResource(MKTAG('A','C','H','R'), *iter); + Chr *chr = new Chr(resMan->getResName(MKTAG('A','C','H','R'), *iter), res); + + addChr(chr); + // TODO: What if there's more than one player character? + if (chr->_playerCharacter) + _player = chr; + } + + // Load Sounds + resArray = resMan->getResIDArray(MKTAG('A','S','N','D')); + debug(3, "Loading %d sounds", resArray.size()); + + for (iter = resArray.begin(); iter != resArray.end(); ++iter) { + res = resMan->getResource(MKTAG('A','S','N','D'), *iter); + addSound(new Sound(resMan->getResName(MKTAG('A','S','N','D'), *iter), res)); + } + + if (!_soundLibrary1.empty()) { + loadExternalSounds(_soundLibrary1); + } + if (!_soundLibrary2.empty()) { + loadExternalSounds(_soundLibrary2); + } + + // Load Patterns + res = resMan->getResource(MKTAG('P','A','T','#'), 900); + if (res != NULL) { + int count = res->readUint16BE(); + debug(3, "Loading %d patterns", count); + + for (int i = 0; i < count; i++) { + byte *pattern = (byte *)malloc(8); + + res->read(pattern, 8); + _patterns.push_back(pattern); + } + + delete res; + } else { + /* Enchanted Scepters did not use the PAT# resource for the textures. */ + res = resMan->getResource(MKTAG('C','O','D','E'), 1); + if (res != NULL) { + res->skip(0x55ac); + for (int i = 0; i < 29; i++) { + byte *pattern = (byte *)malloc(8); + + res->read(pattern, 8); + _patterns.push_back(pattern); + } + } + delete res; + } + + res = resMan->getResource(MKTAG('M','E','N','U'), 2001); + if (res != NULL) { + Common::StringArray *menu = readMenu(res); + _aboutMenuItemName.clear(); + Common::String string = menu->operator[](1); + + for (uint i = 0; i < string.size() && string[i] != ';'; i++) // Read token + _aboutMenuItemName += string[i]; + + delete menu; + delete res; + } + res = resMan->getResource(MKTAG('M','E','N','U'), 2004); + if (res != NULL) { + Common::StringArray *menu = readMenu(res); + _commandsMenuName = menu->operator[](0); + _commandsMenu = menu->operator[](1); + delete menu; + delete res; + } + res = resMan->getResource(MKTAG('M','E','N','U'), 2005); + if (res != NULL) { + Common::StringArray *menu = readMenu(res); + _weaponsMenuName = menu->operator[](0); + delete menu; + delete res; + } + // TODO: Read Apple menu and get the name of that menu item.. + + // store global info in state object for use with save/load actions + //world.setCurrentState(initialState); // pass off the state object to the world + + return true; +} + +void World::addSound(Sound *sound) { + Common::String s = sound->_name; + s.toLowercase(); + _sounds[s] = sound; + _orderedSounds.push_back(sound); +} + +Common::StringArray *World::readMenu(Common::SeekableReadStream *res) { + res->skip(10); + int enableFlags = res->readUint32BE(); + Common::String menuName = readPascalString(res); + Common::String menuItem = readPascalString(res); + int menuItemNumber = 1; + Common::String menu; + byte itemData[4]; + + while (!menuItem.empty()) { + if (!menu.empty()) { + menu += ';'; + } + if ((enableFlags & (1 << menuItemNumber)) == 0) { + menu += '('; + } + menu += menuItem; + res->read(itemData, 4); + static const char styles[] = {'B', 'I', 'U', 'O', 'S', 'C', 'E', 0}; + for (int i = 0; styles[i] != 0; i++) { + if ((itemData[3] & (1 << i)) != 0) { + menu += '<'; + menu += styles[i]; + } + } + if (itemData[1] != 0) { + menu += '/'; + menu += (char)itemData[1]; + } + menuItem = readPascalString(res); + menuItemNumber++; + } + + Common::StringArray *result = new Common::StringArray; + result->push_back(menuName); + result->push_back(menu); + + debug(4, "menuName: %s", menuName.c_str()); + debug(4, "menu: %s", menu.c_str()); + + return result; +} + +void World::loadExternalSounds(Common::String fname) { + Common::File in; + + in.open(fname); + if (!in.isOpen()) { + warning("Cannot load sound file <%s>", fname.c_str()); + return; + } + in.close(); + + Common::MacResManager resMan; + resMan.open(fname); + + Common::MacResIDArray resArray; + Common::SeekableReadStream *res; + Common::MacResIDArray::const_iterator iter; + + resArray = resMan.getResIDArray(MKTAG('A','S','N','D')); + for (iter = resArray.begin(); iter != resArray.end(); ++iter) { + res = resMan.getResource(MKTAG('A','S','N','D'), *iter); + addSound(new Sound(resMan.getResName(MKTAG('A','S','N','D'), *iter), res)); + } +} + +Common::String *World::loadStringFromDITL(Common::MacResManager *resMan, int resourceId, int itemIndex) { + Common::SeekableReadStream *res = resMan->getResource(MKTAG('D','I','T','L'), resourceId); + if (res) { + int itemCount = res->readSint16BE(); + for (int i = 0; i <= itemCount; i++) { + // int placeholder; short rect[4]; byte flags; pstring str; + res->skip(13); + Common::String message = readPascalString(res); + if (i == itemIndex) { + Common::String *msg = new Common::String(message); + delete res; + return msg; + } + } + + delete res; + } + + return NULL; +} + +static bool invComparator(const Obj *l, const Obj *r) { + return l->_index < r->_index; +} + +void World::move(Obj *obj, Chr *chr) { + if (obj == NULL) + return; + + Designed *from = obj->removeFromCharOrScene(); + obj->_currentOwner = chr; + chr->_inventory.push_back(obj); + + Common::sort(chr->_inventory.begin(), chr->_inventory.end(), invComparator); + + _engine->onMove(obj, from, chr); +} + +static bool objComparator(const Obj *o1, const Obj *o2) { + bool o1Immobile = (o1->_type == Obj::IMMOBILE_OBJECT); + bool o2Immobile = (o2->_type == Obj::IMMOBILE_OBJECT); + if (o1Immobile == o2Immobile) { + return o1->_index - o2->_index; + } + return o1Immobile; +} + +void World::move(Obj *obj, Scene *scene, bool skipSort) { + if (obj == NULL) + return; + + Designed *from = obj->removeFromCharOrScene(); + obj->_currentScene = scene; + scene->_objs.push_back(obj); + + if (!skipSort) + Common::sort(scene->_objs.begin(), scene->_objs.end(), objComparator); + + _engine->onMove(obj, from, scene); +} + +static bool chrComparator(const Chr *l, const Chr *r) { + return l->_index < r->_index; +} + +void World::move(Chr *chr, Scene *scene, bool skipSort) { + if (chr == NULL) + return; + Scene *from = chr->_currentScene; + if (from == scene) + return; + if (from != NULL) + from->_chrs.remove(chr); + scene->_chrs.push_back(chr); + + if (!skipSort) + Common::sort(scene->_chrs.begin(), scene->_chrs.end(), chrComparator); + + if (scene == _storageScene) { + chr->resetState(); + } else if (chr->_playerCharacter) { + scene->_visited = true; + _player->_context._visits++; + } + chr->_currentScene = scene; + + _engine->onMove(chr, from, scene); +} + +Scene *World::getRandomScene() { + // Not including storage: + return _orderedScenes[1 + _engine->_rnd->getRandomNumber(_orderedScenes.size() - 2)]; +} + +Scene *World::getSceneAt(int x, int y) { + for (uint i = 0; i < _orderedScenes.size(); i++) { + Scene *scene = _orderedScenes[i]; + + if (scene != _storageScene && scene->_worldX == x && scene->_worldY == y) { + return scene; + } + } + return NULL; +} + +static const int directionsX[] = { 0, 0, 1, -1 }; +static const int directionsY[] = { -1, 1, 0, 0 }; + +bool World::scenesAreConnected(Scene *scene1, Scene *scene2) { + if (!scene1 || !scene2) + return false; + + int x = scene2->_worldX; + int y = scene2->_worldY; + + for (int dir = 0; dir < 4; dir++) + if (!scene2->_blocked[dir]) + if (getSceneAt(x + directionsX[dir], y + directionsY[dir]) == scene1) + return true; + + return false; +} + +const char *World::getAboutMenuItemName() { + static char menu[256]; + + *menu = '\0'; + + if (_aboutMenuItemName.empty()) { + sprintf(menu, "About %s...", _name.c_str()); + } else { // Replace '@' with name + const char *str = _aboutMenuItemName.c_str(); + const char *pos = strchr(str, '@'); + if (pos) { + strncat(menu, str, (pos - str)); + strncat(menu, _name.c_str(), 255); + strncat(menu, pos + 1, 255); + } + } + + return menu; +} + +} // End of namespace Wage diff --git a/engines/wage/world.h b/engines/wage/world.h new file mode 100644 index 0000000000..e9041139df --- /dev/null +++ b/engines/wage/world.h @@ -0,0 +1,138 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef WAGE_WORLD_H +#define WAGE_WORLD_H + +namespace Wage { + +#define STORAGESCENE "STORAGE@" + +class Sound; + +class World { +public: + World(WageEngine *engine); + ~World(); + + bool loadWorld(Common::MacResManager *resMan); + void loadExternalSounds(Common::String fname); + Common::String *loadStringFromDITL(Common::MacResManager *resMan, int resourceId, int itemIndex); + void move(Obj *obj, Chr *chr); + void move(Obj *obj, Scene *scene, bool skipSort = false); + void move(Chr *chr, Scene *scene, bool skipSort = false); + Scene *getRandomScene(); + Scene *getSceneAt(int x, int y); + bool scenesAreConnected(Scene *scene1, Scene *scene2); + const char *getAboutMenuItemName(); + + WageEngine *_engine; + + Common::String _name; + Common::String _aboutMessage; + Common::String _soundLibrary1; + Common::String _soundLibrary2; + + bool _weaponMenuDisabled; + Script *_globalScript; + Common::HashMap<Common::String, Scene *> _scenes; + Common::HashMap<Common::String, Obj *> _objs; + Common::HashMap<Common::String, Chr *> _chrs; + Common::HashMap<Common::String, Sound *> _sounds; + Common::Array<Scene *> _orderedScenes; + ObjArray _orderedObjs; + ChrArray _orderedChrs; + Common::Array<Sound *> _orderedSounds; + Patterns _patterns; + Scene *_storageScene; + Chr *_player; + //List<MoveListener> moveListeners; + + Common::String *_gameOverMessage; + Common::String *_saveBeforeQuitMessage; + Common::String *_saveBeforeCloseMessage; + Common::String *_revertMessage; + + Common::String _aboutMenuItemName; + Common::String _commandsMenuName; + Common::String _commandsMenu; + Common::String _weaponsMenuName; + + void addScene(Scene *room) { + if (!room->_name.empty()) { + Common::String s = room->_name; + s.toLowercase(); + _scenes[s] = room; + } + _orderedScenes.push_back(room); + } + + void addObj(Obj *obj) { + Common::String s = obj->_name; + s.toLowercase(); + _objs[s] = obj; + obj->_index = _orderedObjs.size(); + _orderedObjs.push_back(obj); + } + + void addChr(Chr *chr) { + Common::String s = chr->_name; + s.toLowercase(); + _chrs[s] = chr; + chr->_index = _orderedChrs.size(); + _orderedChrs.push_back(chr); + } + + void addSound(Sound *sound); + +private: + Common::StringArray *readMenu(Common::SeekableReadStream *res); +}; + +} // End of namespace Wage + +#endif diff --git a/engines/wintermute/ad/ad_actor.cpp b/engines/wintermute/ad/ad_actor.cpp index 9d5a35464a..4661d3bca0 100644 --- a/engines/wintermute/ad/ad_actor.cpp +++ b/engines/wintermute/ad/ad_actor.cpp @@ -958,13 +958,13 @@ bool AdActor::scCallMethod(ScScript *script, ScStack *stack, ScStack *thisStack, stack->correctParams(1); ScValue *val = stack->pop(); if (!val->isNative()) { - script->runtimeError("actor.%s method accepts an entity refrence only", name); + script->runtimeError("actor.%s method accepts an entity reference only", name); stack->pushNULL(); return STATUS_OK; } AdObject *obj = (AdObject *)val->getNative(); if (!obj || obj->getType() != OBJECT_ENTITY) { - script->runtimeError("actor.%s method accepts an entity refrence only", name); + script->runtimeError("actor.%s method accepts an entity reference only", name); stack->pushNULL(); return STATUS_OK; } diff --git a/engines/wintermute/base/base_engine.h b/engines/wintermute/base/base_engine.h index 0f4a6b0775..cbf5d92d00 100644 --- a/engines/wintermute/base/base_engine.h +++ b/engines/wintermute/base/base_engine.h @@ -74,7 +74,7 @@ public: static const Timer *getTimer(); static const Timer *getLiveTimer(); static void LOG(bool res, const char *fmt, ...); - const char *getGameTargetName() const { return _targetName.c_str(); } + Common::String getGameTargetName() const { return _targetName; } Common::String getGameId() const { return _gameId; } Common::Language getLanguage() const { return _language; } WMETargetExecutable getTargetExecutable() const { diff --git a/engines/wintermute/base/base_persistence_manager.cpp b/engines/wintermute/base/base_persistence_manager.cpp index bb5e0c4091..39462f7a15 100644 --- a/engines/wintermute/base/base_persistence_manager.cpp +++ b/engines/wintermute/base/base_persistence_manager.cpp @@ -56,7 +56,7 @@ namespace Wintermute { #define SAVE_MAGIC_3 0x12564154 ////////////////////////////////////////////////////////////////////////// -BasePersistenceManager::BasePersistenceManager(const char *savePrefix, bool deleteSingleton) { +BasePersistenceManager::BasePersistenceManager(const Common::String &savePrefix, bool deleteSingleton) { _saving = false; _offset = 0; _saveStream = nullptr; @@ -91,7 +91,7 @@ BasePersistenceManager::BasePersistenceManager(const char *savePrefix, bool dele _thumbnailDataSize = 0; _thumbnailData = nullptr; - if (savePrefix) { + if (savePrefix != "") { _savePrefix = savePrefix; } else if (_gameRef) { _savePrefix = _gameRef->getGameTargetName(); @@ -215,8 +215,8 @@ bool BasePersistenceManager::getSaveExists(int slot) { } ////////////////////////////////////////////////////////////////////////// -bool BasePersistenceManager::initSave(const char *desc) { - if (!desc) { +bool BasePersistenceManager::initSave(const Common::String &desc) { + if (desc == "") { return STATUS_FAILED; } @@ -297,11 +297,11 @@ bool BasePersistenceManager::initSave(const char *desc) { uint32 dataOffset = _offset + sizeof(uint32) + // data offset - sizeof(uint32) + strlen(desc) + 1 + // description + sizeof(uint32) + strlen(desc.c_str()) + 1 + // description sizeof(uint32); // timestamp putDWORD(dataOffset); - putString(desc); + putString(desc.c_str()); g_system->getTimeAndDate(_savedTimestamp); putTimeDate(_savedTimestamp); diff --git a/engines/wintermute/base/base_persistence_manager.h b/engines/wintermute/base/base_persistence_manager.h index 373d1580de..760b45c907 100644 --- a/engines/wintermute/base/base_persistence_manager.h +++ b/engines/wintermute/base/base_persistence_manager.h @@ -63,7 +63,7 @@ public: uint32 getMaxUsedSlot(); bool getSaveExists(int slot); bool initLoad(const Common::String &filename); - bool initSave(const char *desc); + bool initSave(const Common::String &desc); bool getBytes(byte *buffer, uint32 size); bool putBytes(byte *buffer, uint32 size); uint32 _offset; @@ -86,7 +86,7 @@ public: bool transferCharPtr(const char *name, char **val); bool transferString(const char *name, Common::String *val); bool transferVector2(const char *name, Vector2 *val); - BasePersistenceManager(const char *savePrefix = nullptr, bool deleteSingleton = false); + BasePersistenceManager(const Common::String &savePrefix = "", bool deleteSingleton = false); virtual ~BasePersistenceManager(); bool checkVersion(byte verMajor, byte verMinor, byte verBuild); diff --git a/engines/wintermute/base/base_sub_frame.cpp b/engines/wintermute/base/base_sub_frame.cpp index 4388942064..6d0c48ff17 100644 --- a/engines/wintermute/base/base_sub_frame.cpp +++ b/engines/wintermute/base/base_sub_frame.cpp @@ -268,7 +268,7 @@ bool BaseSubFrame::draw(int x, int y, BaseObject *registerOwner, float zoomX, fl Common::Point origin(x, y); Common::Point newOrigin; Rect32 oldRect1 = getRect(); - Common::Rect oldRect(oldRect1.top, oldRect1.left, oldRect1.bottom, oldRect1.right); + Common::Rect oldRect(oldRect1.left, oldRect1.top, oldRect1.right, oldRect1.bottom); Common::Point newHotspot; Graphics::TransformStruct transform = Graphics::TransformStruct(zoomX, zoomY, (uint32)rotate, _hotspotX, _hotspotY, blendMode, alpha, _mirrorX, _mirrorY, 0, 0); Rect32 newRect = Graphics::TransformTools::newRect(oldRect, transform, &newHotspot); diff --git a/engines/wintermute/base/file/base_disk_file.cpp b/engines/wintermute/base/file/base_disk_file.cpp index 82a9e24dfb..d0c51616f4 100644 --- a/engines/wintermute/base/file/base_disk_file.cpp +++ b/engines/wintermute/base/file/base_disk_file.cpp @@ -113,13 +113,28 @@ Common::SeekableReadStream *openDiskFile(const Common::String &filename) { Common::String fixedFilename = filename; correctSlashes(fixedFilename); - // Absolute path: TODO: Add specific fallbacks here. + // HACK: There are a few games around which mistakenly refer to absolute paths in the scripts. + // The original interpreter on Windows usually simply ignores them when it can't find them. + // We try to turn the known ones into relative paths. if (fixedFilename.contains(':')) { - if (fixedFilename.hasPrefix("c:/windows/fonts/")) { // East Side Story refers to "c:\windows\fonts\framd.ttf" - fixedFilename = filename.c_str() + 14; - } else if (fixedFilename.hasPrefix("c:/carol6/svn/data/")) { // Carol Reed 6: Black Circle refers to "c:\carol6\svn\data\sprites\system\help.png" - fixedFilename = fixedFilename.c_str() + 19; - } else { + const char* const knownPrefixes[] = { // Known absolute paths + "c:/windows/fonts/", // East Side Story refers to "c:\windows\fonts\framd.ttf" + "c:/carol6/svn/data/", // Carol Reed 6: Black Circle refers to "c:\carol6\svn\data\sprites\system\help.png" + "f:/dokument/spel 5/demo/data/" // Carol Reed 5 (non-demo) refers to "f:\dokument\spel 5\demo\data\scenes\credits\op_cred_00\op_cred_00.jpg" + }; + + bool matched = false; + + for (uint i = 0; i < ARRAYSIZE(knownPrefixes); i++) { + if (fixedFilename.hasPrefix(knownPrefixes[i])) { + fixedFilename = fixedFilename.c_str() + strlen(knownPrefixes[i]); + matched = true; + } + } + + if (!matched) { + // fixedFilename is unchanged and thus still broken, none of the above workarounds worked. + // We can only bail out error("openDiskFile::Absolute path or invalid filename used in %s", filename.c_str()); } } diff --git a/engines/wintermute/configure.engine b/engines/wintermute/configure.engine index bdaf49de3f..55385776de 100644 --- a/engines/wintermute/configure.engine +++ b/engines/wintermute/configure.engine @@ -1,3 +1,3 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] -add_engine wintermute "Wintermute" yes "" "" "jpeg png zlib vorbis 16bit" +add_engine wintermute "Wintermute" yes "" "" "jpeg png zlib vorbis 16bit highres" diff --git a/engines/wintermute/detection.cpp b/engines/wintermute/detection.cpp index aca682ae99..f77eb5c64d 100644 --- a/engines/wintermute/detection.cpp +++ b/engines/wintermute/detection.cpp @@ -75,8 +75,8 @@ static const char *directoryGlobs[] = { class WintermuteMetaEngine : public AdvancedMetaEngine { public: WintermuteMetaEngine() : AdvancedMetaEngine(Wintermute::gameDescriptions, sizeof(WMEGameDescription), Wintermute::wintermuteGames, gameGuiOptions) { - _singleid = "wintermute"; - _guioptions = GUIO2(GUIO_NOMIDI, GAMEOPTION_SHOW_FPS); + _singleId = "wintermute"; + _guiOptions = GUIO2(GUIO_NOMIDI, GAMEOPTION_SHOW_FPS); _maxScanDepth = 2; _directoryGlobs = directoryGlobs; } @@ -94,8 +94,8 @@ public: s_fallbackDesc.language = Common::UNK_LANG; s_fallbackDesc.flags = ADGF_UNSTABLE; s_fallbackDesc.platform = Common::kPlatformWindows; // default to Windows - s_fallbackDesc.gameid = "wintermute"; - s_fallbackDesc.guioptions = GUIO0(); + s_fallbackDesc.gameId = "wintermute"; + s_fallbackDesc.guiOptions = GUIO0(); if (allFiles.contains("data.dcp")) { Common::String name, caption; @@ -109,7 +109,7 @@ public: // Prefix to avoid collisions with actually known games name = "wmeunk-" + name; Common::strlcpy(s_fallbackGameIdBuf, name.c_str(), sizeof(s_fallbackGameIdBuf) - 1); - s_fallbackDesc.gameid = s_fallbackGameIdBuf; + s_fallbackDesc.gameId = s_fallbackGameIdBuf; if (caption != name) { caption += " (unknown version) "; char *offset = s_fallbackGameIdBuf + name.size() + 1; diff --git a/engines/wintermute/detection_tables.h b/engines/wintermute/detection_tables.h index 25a01766e4..ca30204462 100644 --- a/engines/wintermute/detection_tables.h +++ b/engines/wintermute/detection_tables.h @@ -181,10 +181,13 @@ static const WMEGameDescription gameDescriptions[] = { WME_ENTRY1s("data.dcp", "b3f8b09bb4b05ee3e9d14697525257f9", 59296246), Common::EN_ANY, ADGF_UNSTABLE | ADGF_DEMO, LATEST_VERSION), // Carol Reed 4 - East Side Story WME_WINENTRY("carolreed4", "", - WME_ENTRY1s("data.dcp", "b26377797f060afc2d440d820100c1ce", 529320536), Common::EN_ANY, ADGF_UNSTABLE | ADGF_DEMO, LATEST_VERSION), + WME_ENTRY1s("data.dcp", "b26377797f060afc2d440d820100c1ce", 529320536), Common::EN_ANY, ADGF_UNSTABLE, LATEST_VERSION), // Carol Reed 5 - The Colour of Murder WME_WINENTRY("carolreed5", "", WME_ENTRY1s("data.dcp", "3fcfca44209545d0e26774156427b494", 603660415), Common::EN_ANY, ADGF_UNSTABLE, LATEST_VERSION), + // Carol Reed 5 - The Colour of Murder (1.0 Demo) + WME_WINENTRY("carolreed5", "Demo", + WME_ENTRY1s("data.dcp", "27b3efc018ade5ee8f4adf08b4e3c0dd", 92019500), Common::EN_ANY, ADGF_UNSTABLE | ADGF_DEMO, LATEST_VERSION), // Carol Reed 6 - Black Circle WME_WINENTRY("carolreed6", "", WME_ENTRY1s("data.dcp", "0e4c532beecf23d85012168753f41189", 456258147), Common::EN_ANY, ADGF_UNSTABLE, LATEST_VERSION), diff --git a/engines/wintermute/wintermute.cpp b/engines/wintermute/wintermute.cpp index e35bb60c3d..955f2dc1c2 100644 --- a/engines/wintermute/wintermute.cpp +++ b/engines/wintermute/wintermute.cpp @@ -133,7 +133,7 @@ Common::Error WintermuteEngine::run() { } int WintermuteEngine::init() { - BaseEngine::createInstance(_targetName, _gameDescription->adDesc.gameid, _gameDescription->adDesc.language, _gameDescription->targetExecutable); + BaseEngine::createInstance(_targetName, _gameDescription->adDesc.gameId, _gameDescription->adDesc.language, _gameDescription->targetExecutable); _game = new AdGame(_targetName); if (!_game) { return 1; diff --git a/engines/zvision/configure.engine b/engines/zvision/configure.engine index 226870c3fd..8681522a35 100644 --- a/engines/zvision/configure.engine +++ b/engines/zvision/configure.engine @@ -1,3 +1,3 @@ # This file is included from the main "configure" script # add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] -add_engine zvision "Z-Vision" yes "" "" "freetype2 16bit" +add_engine zvision "Z-Vision" yes "" "" "freetype2 16bit highres" diff --git a/engines/zvision/detection.cpp b/engines/zvision/detection.cpp index 5fdf17cd2b..cc967070d9 100644 --- a/engines/zvision/detection.cpp +++ b/engines/zvision/detection.cpp @@ -61,7 +61,7 @@ public: ZVisionMetaEngine() : AdvancedMetaEngine(ZVision::gameDescriptions, sizeof(ZVision::ZVisionGameDescription), ZVision::zVisionGames, ZVision::optionsList) { _maxScanDepth = 2; _directoryGlobs = ZVision::directoryGlobs; - _singleid = "zvision"; + _singleId = "zvision"; } virtual const char *getName() const { @@ -132,7 +132,6 @@ SaveStateList ZVisionMetaEngine::listSaves(const char *target) const { Common::StringArray filenames; filenames = saveFileMan->listSavefiles(pattern.c_str()); - Common::sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..)*/ SaveStateList saveList; // We only use readSaveGameHeader() here, which doesn't need an engine callback @@ -155,6 +154,8 @@ SaveStateList ZVisionMetaEngine::listSaves(const char *target) const { delete zvisionSaveMan; + // Sort saves based on slot number. + Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator()); return saveList; } @@ -164,26 +165,7 @@ int ZVisionMetaEngine::getMaximumSaveSlot() const { void ZVisionMetaEngine::removeSaveState(const char *target, int slot) const { Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); - Common::String filename = Common::String::format("%s.%03u", target, slot); - - saveFileMan->removeSavefile(filename.c_str()); - - Common::StringArray filenames; - Common::String pattern = target; - pattern += ".###"; - filenames = saveFileMan->listSavefiles(pattern.c_str()); - Common::sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..) - - for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { - // Obtain the last 3 digits of the filename, since they correspond to the save slot - int slotNum = atoi(file->c_str() + file->size() - 3); - - // Rename every slot greater than the deleted slot, - if (slotNum > slot) { - saveFileMan->renameSavefile(file->c_str(), filename.c_str()); - filename = Common::String::format("%s.%03u", target, ++slot); - } - } + saveFileMan->removeSavefile(Common::String::format("%s.%03u", target, slot)); } SaveStateDescriptor ZVisionMetaEngine::querySaveMetaInfos(const char *target, int slot) const { |