diff options
Diffstat (limited to 'engines/gob')
61 files changed, 4517 insertions, 969 deletions
diff --git a/engines/gob/aniobject.cpp b/engines/gob/aniobject.cpp index 154f8e04ed..8d739fb3a4 100644 --- a/engines/gob/aniobject.cpp +++ b/engines/gob/aniobject.cpp @@ -76,6 +76,10 @@ void ANIObject::rewind() { _frame = 0; } +void ANIObject::setFrame(uint16 frame) { + _frame = frame % _ani->getAnimationInfo(_animation).frameCount; +} + void ANIObject::setPosition() { // CMP "animations" have no default position if (_cmp) @@ -167,19 +171,21 @@ bool ANIObject::isIn(const ANIObject &obj) const { obj.isIn(frameX + frameWidth - 1, frameY + frameHeight - 1); } -void ANIObject::draw(Surface &dest, int16 &left, int16 &top, +bool ANIObject::draw(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom) { if (!_visible) - return; + return false; if (_cmp) - drawCMP(dest, left, top, right, bottom); + return drawCMP(dest, left, top, right, bottom); else if (_ani) - drawANI(dest, left, top, right, bottom); + return drawANI(dest, left, top, right, bottom); + + return false; } -void ANIObject::drawCMP(Surface &dest, int16 &left, int16 &top, +bool ANIObject::drawCMP(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom) { if (!_background) { @@ -209,9 +215,11 @@ void ANIObject::drawCMP(Surface &dest, int16 &left, int16 &top, top = _backgroundTop; right = _backgroundRight; bottom = _backgroundBottom; + + return true; } -void ANIObject::drawANI(Surface &dest, int16 &left, int16 &top, +bool ANIObject::drawANI(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom) { if (!_background) { @@ -224,7 +232,7 @@ void ANIObject::drawANI(Surface &dest, int16 &left, int16 &top, const ANIFile::Animation &animation = _ani->getAnimationInfo(_animation); if (_frame >= animation.frameCount) - return; + return false; const ANIFile::FrameArea &area = animation.frameAreas[_frame]; @@ -244,13 +252,15 @@ void ANIObject::drawANI(Surface &dest, int16 &left, int16 &top, top = _backgroundTop; right = _backgroundRight; bottom = _backgroundBottom; + + return true; } -void ANIObject::clear(Surface &dest, int16 &left, int16 &top, +bool ANIObject::clear(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom) { if (!_drawn) - return; + return false; const int16 bgRight = _backgroundRight - _backgroundLeft; const int16 bgBottom = _backgroundBottom - _backgroundTop; @@ -263,6 +273,8 @@ void ANIObject::clear(Surface &dest, int16 &left, int16 &top, top = _backgroundTop; right = _backgroundRight; bottom = _backgroundBottom; + + return true; } void ANIObject::advance() { diff --git a/engines/gob/aniobject.h b/engines/gob/aniobject.h index c101d747b7..00f42b43ce 100644 --- a/engines/gob/aniobject.h +++ b/engines/gob/aniobject.h @@ -61,9 +61,9 @@ public: void setMode(Mode mode); /** Set the current position to the animation's default. */ - void setPosition(); + virtual void setPosition(); /** Set the current position. */ - void setPosition(int16 x, int16 y); + virtual void setPosition(int16 x, int16 y); /** Return the current position. */ void getPosition(int16 &x, int16 &y) const; @@ -84,6 +84,9 @@ public: /** Rewind the current animation to the first frame. */ void rewind(); + /** Set the animation to a specific frame. */ + void setFrame(uint16 frame); + /** Return the current animation number. */ uint16 getAnimation() const; /** Return the current frame number. */ @@ -93,9 +96,9 @@ public: bool lastFrame() const; /** Draw the current frame onto the surface and return the affected rectangle. */ - void draw(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom); + virtual bool draw(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom); /** Draw the current frame from the surface and return the affected rectangle. */ - void clear(Surface &dest, int16 &left , int16 &top, int16 &right, int16 &bottom); + virtual bool clear(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom); /** Advance the animation to the next frame. */ virtual void advance(); @@ -123,8 +126,8 @@ private: int16 _backgroundRight; ///< The right position of the saved background. int16 _backgroundBottom; ///< The bottom position of the saved background. - void drawCMP(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom); - void drawANI(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom); + bool drawCMP(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom); + bool drawANI(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom); }; } // End of namespace Gob diff --git a/engines/gob/cheater.h b/engines/gob/cheater.h index 334a5e88eb..bf6c1372fb 100644 --- a/engines/gob/cheater.h +++ b/engines/gob/cheater.h @@ -31,6 +31,7 @@ namespace Gob { namespace Geisha { class Diving; + class Penetration; } class GobEngine; @@ -48,13 +49,14 @@ protected: class Cheater_Geisha : public Cheater { public: - Cheater_Geisha(GobEngine *vm, Geisha::Diving *diving); + Cheater_Geisha(GobEngine *vm, Geisha::Diving *diving, Geisha::Penetration *penetration); ~Cheater_Geisha(); bool cheat(GUI::Debugger &console); private: - Geisha::Diving *_diving; + Geisha::Diving *_diving; + Geisha::Penetration *_penetration; }; } // End of namespace Gob diff --git a/engines/gob/cheater_geisha.cpp b/engines/gob/cheater_geisha.cpp index 3d8c56707d..567333c12f 100644 --- a/engines/gob/cheater_geisha.cpp +++ b/engines/gob/cheater_geisha.cpp @@ -27,11 +27,12 @@ #include "gob/inter.h" #include "gob/minigames/geisha/diving.h" +#include "gob/minigames/geisha/penetration.h" namespace Gob { -Cheater_Geisha::Cheater_Geisha(GobEngine *vm, Geisha::Diving *diving) : - Cheater(vm), _diving(diving) { +Cheater_Geisha::Cheater_Geisha(GobEngine *vm, Geisha::Diving *diving, Geisha::Penetration *penetration) : + Cheater(vm), _diving(diving), _penetration(penetration) { } @@ -45,6 +46,12 @@ bool Cheater_Geisha::cheat(GUI::Debugger &console) { return false; } + // A cheat to get around the Penetration minigame + if (_penetration->isPlaying()) { + _penetration->cheatWin(); + return false; + } + // A cheat to get around the mastermind puzzle if (_vm->isCurrentTot("hard.tot") && _vm->_inter->_variables) { uint32 digit1 = READ_VARO_UINT32(0x768); diff --git a/engines/gob/detection.cpp b/engines/gob/detection.cpp index 17a2ae3da8..861cc95d41 100644 --- a/engines/gob/detection.cpp +++ b/engines/gob/detection.cpp @@ -50,7 +50,7 @@ static const PlainGameDescriptor gobGames[] = { {"gob2cd", "Gobliins 2 CD"}, {"ween", "Ween: The Prophecy"}, {"bargon", "Bargon Attack"}, - {"littlered", "Little Red Riding Hood"}, + {"littlered", "Once Upon A Time: Little Red Riding Hood"}, {"ajworld", "A.J's World of Discovery"}, {"gob3", "Goblins Quest 3"}, {"gob3cd", "Goblins Quest 3 CD"}, diff --git a/engines/gob/detection_tables.h b/engines/gob/detection_tables.h index 7aa58b9b97..bd35900473 100644 --- a/engines/gob/detection_tables.h +++ b/engines/gob/detection_tables.h @@ -34,7 +34,7 @@ static const GOBGameDescription gameDescriptions[] = { GUIO2(GUIO_NOSUBTITLES, GUIO_NOSPEECH) }, kGameTypeGob1, - kFeaturesEGA, + kFeaturesEGA | kFeaturesAdLib, 0, 0, 0 }, { @@ -48,7 +48,7 @@ static const GOBGameDescription gameDescriptions[] = { GUIO2(GUIO_NOSUBTITLES, GUIO_NOSPEECH) }, kGameTypeGob1, - kFeaturesEGA, + kFeaturesEGA | kFeaturesAdLib, 0, 0, 0 }, { // Supplied by Theruler76 in bug report #1201233 @@ -1615,7 +1615,7 @@ static const GOBGameDescription gameDescriptions[] = { ADGF_NO_FLAGS, GUIO2(GUIO_NOSUBTITLES, GUIO_NOSPEECH) }, - kGameTypeGob2, + kGameTypeLittleRed, kFeaturesAdLib | kFeaturesEGA, 0, 0, 0 }, @@ -1629,7 +1629,7 @@ static const GOBGameDescription gameDescriptions[] = { ADGF_NO_FLAGS, GUIO2(GUIO_NOSUBTITLES, GUIO_NOSPEECH) }, - kGameTypeGob2, + kGameTypeLittleRed, kFeaturesAdLib | kFeaturesEGA, 0, 0, 0 }, @@ -1643,7 +1643,7 @@ static const GOBGameDescription gameDescriptions[] = { ADGF_NO_FLAGS, GUIO2(GUIO_NOSUBTITLES, GUIO_NOSPEECH) }, - kGameTypeGob2, + kGameTypeLittleRed, kFeaturesAdLib | kFeaturesEGA, 0, 0, 0 }, @@ -1657,7 +1657,7 @@ static const GOBGameDescription gameDescriptions[] = { ADGF_NO_FLAGS, GUIO2(GUIO_NOSUBTITLES, GUIO_NOSPEECH) }, - kGameTypeGob2, + kGameTypeLittleRed, kFeaturesAdLib | kFeaturesEGA, 0, 0, 0 }, @@ -1671,7 +1671,7 @@ static const GOBGameDescription gameDescriptions[] = { ADGF_NO_FLAGS, GUIO2(GUIO_NOSUBTITLES, GUIO_NOSPEECH) }, - kGameTypeGob2, + kGameTypeLittleRed, kFeaturesAdLib | kFeaturesEGA, 0, 0, 0 }, @@ -1689,7 +1689,7 @@ static const GOBGameDescription gameDescriptions[] = { ADGF_NO_FLAGS, GUIO2(GUIO_NOSUBTITLES, GUIO_NOSPEECH) }, - kGameTypeGob2, + kGameTypeLittleRed, kFeaturesNone, 0, 0, 0 }, @@ -1703,7 +1703,7 @@ static const GOBGameDescription gameDescriptions[] = { ADGF_NO_FLAGS, GUIO2(GUIO_NOSUBTITLES, GUIO_NOSPEECH) }, - kGameTypeGob2, + kGameTypeLittleRed, kFeaturesAdLib | kFeaturesEGA, 0, 0, 0 }, @@ -1717,7 +1717,7 @@ static const GOBGameDescription gameDescriptions[] = { ADGF_NO_FLAGS, GUIO2(GUIO_NOSUBTITLES, GUIO_NOSPEECH) }, - kGameTypeGob2, + kGameTypeLittleRed, kFeaturesAdLib | kFeaturesEGA, 0, 0, 0 }, @@ -1731,7 +1731,7 @@ static const GOBGameDescription gameDescriptions[] = { ADGF_NO_FLAGS, GUIO2(GUIO_NOSUBTITLES, GUIO_NOSPEECH) }, - kGameTypeGob2, + kGameTypeLittleRed, kFeaturesAdLib | kFeaturesEGA, 0, 0, 0 }, @@ -1745,7 +1745,7 @@ static const GOBGameDescription gameDescriptions[] = { ADGF_NO_FLAGS, GUIO2(GUIO_NOSUBTITLES, GUIO_NOSPEECH) }, - kGameTypeGob2, + kGameTypeLittleRed, kFeaturesAdLib | kFeaturesEGA, 0, 0, 0 }, @@ -1759,7 +1759,7 @@ static const GOBGameDescription gameDescriptions[] = { ADGF_NO_FLAGS, GUIO2(GUIO_NOSUBTITLES, GUIO_NOSPEECH) }, - kGameTypeGob2, + kGameTypeLittleRed, kFeaturesAdLib | kFeaturesEGA, 0, 0, 0 }, @@ -1773,7 +1773,7 @@ static const GOBGameDescription gameDescriptions[] = { ADGF_NO_FLAGS, GUIO2(GUIO_NOSUBTITLES, GUIO_NOSPEECH) }, - kGameTypeGob2, + kGameTypeLittleRed, kFeaturesAdLib | kFeaturesEGA, 0, 0, 0 }, @@ -1787,7 +1787,7 @@ static const GOBGameDescription gameDescriptions[] = { ADGF_NO_FLAGS, GUIO2(GUIO_NOSUBTITLES, GUIO_NOSPEECH) }, - kGameTypeGob2, + kGameTypeLittleRed, kFeaturesAdLib | kFeaturesEGA, 0, 0, 0 }, @@ -1801,7 +1801,7 @@ static const GOBGameDescription gameDescriptions[] = { ADGF_NO_FLAGS, GUIO2(GUIO_NOSUBTITLES, GUIO_NOSPEECH) }, - kGameTypeGob2, + kGameTypeLittleRed, kFeaturesAdLib | kFeaturesEGA, 0, 0, 0 }, @@ -1815,7 +1815,7 @@ static const GOBGameDescription gameDescriptions[] = { ADGF_NO_FLAGS, GUIO2(GUIO_NOSUBTITLES, GUIO_NOSPEECH) }, - kGameTypeGob2, + kGameTypeLittleRed, kFeaturesAdLib | kFeaturesEGA, 0, 0, 0 }, @@ -1829,7 +1829,7 @@ static const GOBGameDescription gameDescriptions[] = { ADGF_NO_FLAGS, GUIO2(GUIO_NOSUBTITLES, GUIO_NOSPEECH) }, - kGameTypeGob2, + kGameTypeLittleRed, kFeaturesAdLib | kFeaturesEGA, 0, 0, 0 }, diff --git a/engines/gob/draw.cpp b/engines/gob/draw.cpp index 4b659f51de..fe59b11f76 100644 --- a/engines/gob/draw.cpp +++ b/engines/gob/draw.cpp @@ -117,6 +117,15 @@ Draw::Draw(GobEngine *vm) : _vm(vm) { _cursorAnimDelays[i] = 0; } + _cursorCount = 0; + _doCursorPalettes = 0; + _cursorPalettes = 0; + _cursorKeyColors = 0; + _cursorPaletteStarts = 0; + _cursorPaletteCounts = 0; + _cursorHotspotsX = 0; + _cursorHotspotsY = 0; + _palLoadData1[0] = 0; _palLoadData1[1] = 17; _palLoadData1[2] = 34; @@ -134,6 +143,14 @@ Draw::Draw(GobEngine *vm) : _vm(vm) { } Draw::~Draw() { + delete[] _cursorPalettes; + delete[] _doCursorPalettes; + delete[] _cursorKeyColors; + delete[] _cursorPaletteStarts; + delete[] _cursorPaletteCounts; + delete[] _cursorHotspotsX; + delete[] _cursorHotspotsY; + for (int i = 0; i < kFontCount; i++) delete _fonts[i]; } diff --git a/engines/gob/draw.h b/engines/gob/draw.h index 393822c33a..24c5550ea5 100644 --- a/engines/gob/draw.h +++ b/engines/gob/draw.h @@ -145,6 +145,15 @@ public: int8 _cursorAnimHigh[40]; int8 _cursorAnimDelays[40]; + int32 _cursorCount; + bool *_doCursorPalettes; + byte *_cursorPalettes; + byte *_cursorKeyColors; + uint16 *_cursorPaletteStarts; + uint16 *_cursorPaletteCounts; + int32 *_cursorHotspotsX; + int32 *_cursorHotspotsY; + int16 _palLoadData1[4]; int16 _palLoadData2[4]; diff --git a/engines/gob/draw_v1.cpp b/engines/gob/draw_v1.cpp index fb15fdbc19..878c1dc265 100644 --- a/engines/gob/draw_v1.cpp +++ b/engines/gob/draw_v1.cpp @@ -123,7 +123,7 @@ void Draw_v1::animateCursor(int16 cursor) { (cursorIndex + 1) * _cursorWidth - 1, _cursorHeight - 1, 0, 0); CursorMan.replaceCursor(_scummvmCursor->getData(), - _cursorWidth, _cursorHeight, hotspotX, hotspotY, 0, 1, &_vm->getPixelFormat()); + _cursorWidth, _cursorHeight, hotspotX, hotspotY, 0, false, &_vm->getPixelFormat()); if (_frontSurface != _backSurface) { _showCursor = 3; diff --git a/engines/gob/draw_v2.cpp b/engines/gob/draw_v2.cpp index 78702f2ec9..d9b7a12639 100644 --- a/engines/gob/draw_v2.cpp +++ b/engines/gob/draw_v2.cpp @@ -83,7 +83,7 @@ void Draw_v2::blitCursor() { void Draw_v2::animateCursor(int16 cursor) { int16 cursorIndex = cursor; int16 newX = 0, newY = 0; - uint16 hotspotX = 0, hotspotY = 0; + uint16 hotspotX, hotspotY; _showCursor |= 1; @@ -133,27 +133,42 @@ void Draw_v2::animateCursor(int16 cursor) { } // '------ - newX = _vm->_global->_inter_mouseX; - newY = _vm->_global->_inter_mouseY; + hotspotX = 0; + hotspotY = 0; + if (_cursorHotspotXVar != -1) { - newX -= hotspotX = (uint16) VAR(_cursorIndex + _cursorHotspotXVar); - newY -= hotspotY = (uint16) VAR(_cursorIndex + _cursorHotspotYVar); + hotspotX = (uint16) VAR(_cursorIndex + _cursorHotspotXVar); + hotspotY = (uint16) VAR(_cursorIndex + _cursorHotspotYVar); } else if (_cursorHotspotX != -1) { - newX -= hotspotX = _cursorHotspotX; - newY -= hotspotY = _cursorHotspotY; + hotspotX = _cursorHotspotX; + hotspotY = _cursorHotspotY; + } else if (_cursorHotspotsX != 0) { + hotspotX = _cursorHotspotsX[_cursorIndex]; + hotspotY = _cursorHotspotsY[_cursorIndex]; } + newX = _vm->_global->_inter_mouseX - hotspotX; + newY = _vm->_global->_inter_mouseY - hotspotY; + _scummvmCursor->clear(); _scummvmCursor->blit(*_cursorSprites, cursorIndex * _cursorWidth, 0, (cursorIndex + 1) * _cursorWidth - 1, _cursorHeight - 1, 0, 0); - if ((_vm->getGameType() != kGameTypeAdibou2) && - (_vm->getGameType() != kGameTypeAdi2) && - (_vm->getGameType() != kGameTypeAdi4)) - CursorMan.replaceCursor(_scummvmCursor->getData(), - _cursorWidth, _cursorHeight, hotspotX, hotspotY, 0, 1, &_vm->getPixelFormat()); + uint32 keyColor = 0; + if (_doCursorPalettes && _cursorKeyColors && _doCursorPalettes[cursorIndex]) + keyColor = _cursorKeyColors[cursorIndex]; + + CursorMan.replaceCursor(_scummvmCursor->getData(), + _cursorWidth, _cursorHeight, hotspotX, hotspotY, keyColor, false, &_vm->getPixelFormat()); + + if (_doCursorPalettes && _doCursorPalettes[cursorIndex]) { + CursorMan.replaceCursorPalette(_cursorPalettes + (cursorIndex * 256 * 3), + _cursorPaletteStarts[cursorIndex], _cursorPaletteCounts[cursorIndex]); + CursorMan.disableCursorPalette(false); + } else + CursorMan.disableCursorPalette(true); if (_frontSurface != _backSurface) { if (!_noInvalidated) { diff --git a/engines/gob/global.cpp b/engines/gob/global.cpp index 1264c09860..87656a5fad 100644 --- a/engines/gob/global.cpp +++ b/engines/gob/global.cpp @@ -111,7 +111,6 @@ Global::Global(GobEngine *vm) : _vm(vm) { _dontSetPalette = false; _debugFlag = 0; - _inVM = 0; _inter_animDataSize = 10; diff --git a/engines/gob/global.h b/engines/gob/global.h index fa2f2c9637..175331dd83 100644 --- a/engines/gob/global.h +++ b/engines/gob/global.h @@ -127,7 +127,6 @@ public: SurfacePtr _primarySurfDesc; int16 _debugFlag; - int16 _inVM; int16 _inter_animDataSize; diff --git a/engines/gob/gob.cpp b/engines/gob/gob.cpp index 4e7aa467b5..f3480fed99 100644 --- a/engines/gob/gob.cpp +++ b/engines/gob/gob.cpp @@ -233,6 +233,10 @@ bool GobEngine::isDemo() const { return (isSCNDemo() || isBATDemo()); } +bool GobEngine::hasResourceSizeWorkaround() const { + return _resourceSizeWorkaround; +} + bool GobEngine::isCurrentTot(const Common::String &tot) const { return _game->_curTotFile.equalsIgnoreCase(tot); } @@ -389,6 +393,8 @@ void GobEngine::pauseGame() { } bool GobEngine::initGameParts() { + _resourceSizeWorkaround = false; + // just detect some devices some of which will be always there if the music is not disabled _noMusic = MidiDriver::getMusicType(MidiDriver::detectDevice(MDT_PCSPK | MDT_MIDI | MDT_ADLIB)) == MT_NULL ? true : false; _saveLoad = 0; @@ -462,6 +468,21 @@ bool GobEngine::initGameParts() { _saveLoad = new SaveLoad_v2(this, _targetName.c_str()); break; + case kGameTypeLittleRed: + _init = new Init_v2(this); + _video = new Video_v2(this); + _inter = new Inter_LittleRed(this); + _mult = new Mult_v2(this); + _draw = new Draw_v2(this); + _map = new Map_v2(this); + _goblin = new Goblin_v2(this); + _scenery = new Scenery_v2(this); + + // WORKAROUND: Little Red Riding Hood has a small resource size glitch in the + // screen where Little Red needs to find the animals' homes. + _resourceSizeWorkaround = true; + break; + case kGameTypeGob3: _init = new Init_v3(this); _video = new Video_v2(this); diff --git a/engines/gob/gob.h b/engines/gob/gob.h index ea2323807a..808c941546 100644 --- a/engines/gob/gob.h +++ b/engines/gob/gob.h @@ -50,6 +50,11 @@ class StaticTextWidget; * - Bargon Attack * - Lost in Time * - The Bizarre Adventures of Woodruff and the Schnibble + * - Fascination + * - Urban Runner + * - Bambou le sauveur de la jungle + * - Geisha + * - Once Upon A Time: Little Red Riding Hood */ namespace Gob { @@ -123,7 +128,8 @@ enum GameType { kGameTypeAdi2, kGameTypeAdi4, kGameTypeAdibou2, - kGameTypeAdibou1 + kGameTypeAdibou1, + kGameTypeLittleRed }; enum Features { @@ -195,6 +201,8 @@ public: GobConsole *_console; + bool _resourceSizeWorkaround; + Global *_global; Util *_util; DataIO *_dataIO; @@ -231,6 +239,8 @@ public: bool isTrueColor() const; bool isDemo() const; + bool hasResourceSizeWorkaround() const; + bool isCurrentTot(const Common::String &tot) const; void setTrueColor(bool trueColor); diff --git a/engines/gob/init.h b/engines/gob/init.h index 946a3fa4f1..ac460fd654 100644 --- a/engines/gob/init.h +++ b/engines/gob/init.h @@ -62,7 +62,6 @@ public: ~Init_Geisha(); void initVideo(); - void initGame(); }; class Init_v2 : public Init_v1 { diff --git a/engines/gob/init_fascin.cpp b/engines/gob/init_fascin.cpp index b87d816406..e6d82faa68 100644 --- a/engines/gob/init_fascin.cpp +++ b/engines/gob/init_fascin.cpp @@ -44,10 +44,10 @@ void Init_Fascination::updateConfig() { } void Init_Fascination::initGame() { -// HACK - Suppress ADLIB_FLAG as the MDY/TBR player is not working. suppress -// the PC Speaker too, as the script checks in the intro for it's presence +// HACK - Suppress +// the PC Speaker, as the script checks in the intro for it's presence // to play or not some noices. - _vm->_global->_soundFlags = MIDI_FLAG | BLASTER_FLAG; + _vm->_global->_soundFlags = MIDI_FLAG | BLASTER_FLAG | ADLIB_FLAG; Init::initGame(); } diff --git a/engines/gob/init_geisha.cpp b/engines/gob/init_geisha.cpp index b5bbcff400..01081a5af6 100644 --- a/engines/gob/init_geisha.cpp +++ b/engines/gob/init_geisha.cpp @@ -44,11 +44,4 @@ void Init_Geisha::initVideo() { _vm->_draw->_transparentCursor = 1; } -void Init_Geisha::initGame() { - // HACK - Since the MDY/TBR player is not working, claim we have no AdLib - _vm->_global->_soundFlags = 0; - - Init::initGame(); -} - } // End of namespace Gob diff --git a/engines/gob/init_v1.cpp b/engines/gob/init_v1.cpp index 25d521aca6..a8e8cbe2c3 100644 --- a/engines/gob/init_v1.cpp +++ b/engines/gob/init_v1.cpp @@ -41,8 +41,6 @@ void Init_v1::initVideo() { _vm->_global->_mousePresent = 1; - _vm->_global->_inVM = 0; - if ((_vm->_global->_videoMode == 0x13) && !_vm->isEGA()) _vm->_global->_colorCount = 256; diff --git a/engines/gob/init_v2.cpp b/engines/gob/init_v2.cpp index 1289d561ea..c204b04a40 100644 --- a/engines/gob/init_v2.cpp +++ b/engines/gob/init_v2.cpp @@ -45,8 +45,6 @@ void Init_v2::initVideo() { _vm->_global->_mousePresent = 1; - _vm->_global->_inVM = 0; - _vm->_global->_colorCount = 16; if (!_vm->isEGA() && ((_vm->getPlatform() == Common::kPlatformPC) || diff --git a/engines/gob/inter.cpp b/engines/gob/inter.cpp index 9df3c06c74..843c0bff48 100644 --- a/engines/gob/inter.cpp +++ b/engines/gob/inter.cpp @@ -52,6 +52,7 @@ Inter::Inter(GobEngine *vm) : _vm(vm), _varStack(600) { _soundEndTimeKey = 0; _soundStopVal = 0; + _lastBusyWait = 0; _noBusyWait = false; _variables = 0; @@ -452,4 +453,15 @@ uint32 Inter::readValue(uint16 index, uint16 type) { return 0; } +void Inter::handleBusyWait() { + uint32 now = _vm->_util->getTimeKey(); + + if (!_noBusyWait) + if ((now - _lastBusyWait) <= 20) + _vm->_util->longDelay(1); + + _lastBusyWait = now; + _noBusyWait = false; +} + } // End of namespace Gob diff --git a/engines/gob/inter.h b/engines/gob/inter.h index c79b6e2260..63bf3eb1c6 100644 --- a/engines/gob/inter.h +++ b/engines/gob/inter.h @@ -31,6 +31,10 @@ #include "gob/iniconfig.h" #include "gob/databases.h" +namespace Common { + class PEResources; +} + namespace Gob { class Cheater_Geisha; @@ -138,8 +142,9 @@ protected: VariableStack _varStack; - // The busy-wait detection in o1_keyFunc breaks fast scrolling in Ween - bool _noBusyWait; + // Busy-wait detection + bool _noBusyWait; + uint32 _lastBusyWait; GobEngine *_vm; @@ -168,6 +173,8 @@ protected: void storeString(const char *value); uint32 readValue(uint16 index, uint16 type); + + void handleBusyWait(); }; class Inter_v1 : public Inter { @@ -509,6 +516,20 @@ protected: void oFascin_setWinFlags(); }; +class Inter_LittleRed : public Inter_v2 { +public: + Inter_LittleRed(GobEngine *vm); + virtual ~Inter_LittleRed() {} + +protected: + virtual void setupOpcodesDraw(); + virtual void setupOpcodesFunc(); + virtual void setupOpcodesGob(); + + void oLittleRed_keyFunc(OpFuncParams ¶ms); + void oLittleRed_playComposition(OpFuncParams ¶ms); +}; + class Inter_v3 : public Inter_v2 { public: Inter_v3(GobEngine *vm); @@ -648,7 +669,7 @@ private: class Inter_v7 : public Inter_Playtoons { public: Inter_v7(GobEngine *vm); - virtual ~Inter_v7() {} + virtual ~Inter_v7(); protected: virtual void setupOpcodesDraw(); @@ -684,7 +705,12 @@ private: INIConfig _inis; Databases _databases; + Common::PEResources *_cursors; + Common::String findFile(const Common::String &mask); + + bool loadCursorFile(); + void resizeCursors(int16 width, int16 height, int16 count, bool transparency); }; } // End of namespace Gob diff --git a/engines/gob/inter_fascin.cpp b/engines/gob/inter_fascin.cpp index 081b48fbad..001ec06635 100644 --- a/engines/gob/inter_fascin.cpp +++ b/engines/gob/inter_fascin.cpp @@ -248,12 +248,11 @@ void Inter_Fascination::oFascin_playTira(OpGobParams ¶ms) { void Inter_Fascination::oFascin_loadExtasy(OpGobParams ¶ms) { _vm->_sound->adlibLoadTBR("extasy.tbr"); _vm->_sound->adlibLoadMDY("extasy.mdy"); + _vm->_sound->adlibSetRepeating(-1); } void Inter_Fascination::oFascin_adlibPlay(OpGobParams ¶ms) { -#ifdef ENABLE_FASCIN_ADLIB _vm->_sound->adlibPlay(); -#endif } void Inter_Fascination::oFascin_adlibStop(OpGobParams ¶ms) { diff --git a/engines/gob/inter_geisha.cpp b/engines/gob/inter_geisha.cpp index 7f21ceb91d..8a4d4246b6 100644 --- a/engines/gob/inter_geisha.cpp +++ b/engines/gob/inter_geisha.cpp @@ -55,7 +55,7 @@ Inter_Geisha::Inter_Geisha(GobEngine *vm) : Inter_v1(vm), _diving = new Geisha::Diving(vm); _penetration = new Geisha::Penetration(vm); - _cheater = new Cheater_Geisha(vm, _diving); + _cheater = new Cheater_Geisha(vm, _diving, _penetration); _vm->_console->registerCheater(_cheater); } @@ -272,12 +272,12 @@ void Inter_Geisha::oGeisha_writeData(OpFuncParams ¶ms) { } void Inter_Geisha::oGeisha_gamePenetration(OpGobParams ¶ms) { - uint16 var1 = _vm->_game->_script->readUint16(); - uint16 var2 = _vm->_game->_script->readUint16(); - uint16 var3 = _vm->_game->_script->readUint16(); - uint16 resultVar = _vm->_game->_script->readUint16(); + uint16 hasAccessPass = _vm->_game->_script->readUint16(); + uint16 hasMaxEnergy = _vm->_game->_script->readUint16(); + uint16 testMode = _vm->_game->_script->readUint16(); + uint16 resultVar = _vm->_game->_script->readUint16(); - bool result = _penetration->play(var1, var2, var3); + bool result = _penetration->play(hasAccessPass, hasMaxEnergy, testMode); WRITE_VAR_UINT32(resultVar, result ? 1 : 0); } @@ -298,9 +298,8 @@ void Inter_Geisha::oGeisha_loadTitleMusic(OpGobParams ¶ms) { } void Inter_Geisha::oGeisha_playMusic(OpGobParams ¶ms) { - // TODO: The MDYPlayer is still broken! - warning("Geisha Stub: oGeisha_playMusic"); - // _vm->_sound->adlibPlay(); + _vm->_sound->adlibSetRepeating(-1); + _vm->_sound->adlibPlay(); } void Inter_Geisha::oGeisha_stopMusic(OpGobParams ¶ms) { diff --git a/engines/gob/inter_littlered.cpp b/engines/gob/inter_littlered.cpp new file mode 100644 index 0000000000..729d9f5694 --- /dev/null +++ b/engines/gob/inter_littlered.cpp @@ -0,0 +1,118 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "gob/gob.h" +#include "gob/inter.h" +#include "gob/global.h" +#include "gob/util.h" +#include "gob/draw.h" +#include "gob/game.h" +#include "gob/script.h" +#include "gob/hotspots.h" +#include "gob/sound/sound.h" + +namespace Gob { + +#define OPCODEVER Inter_LittleRed +#define OPCODEDRAW(i, x) _opcodesDraw[i]._OPCODEDRAW(OPCODEVER, x) +#define OPCODEFUNC(i, x) _opcodesFunc[i]._OPCODEFUNC(OPCODEVER, x) +#define OPCODEGOB(i, x) _opcodesGob[i]._OPCODEGOB(OPCODEVER, x) + +Inter_LittleRed::Inter_LittleRed(GobEngine *vm) : Inter_v2(vm) { +} + +void Inter_LittleRed::setupOpcodesDraw() { + Inter_v2::setupOpcodesDraw(); +} + +void Inter_LittleRed::setupOpcodesFunc() { + Inter_v2::setupOpcodesFunc(); + + OPCODEFUNC(0x14, oLittleRed_keyFunc); + + OPCODEFUNC(0x3D, oLittleRed_playComposition); +} + +void Inter_LittleRed::setupOpcodesGob() { + OPCODEGOB(1, o_gobNOP); // Sets some sound timer interrupt + OPCODEGOB(2, o_gobNOP); // Sets some sound timer interrupt + + OPCODEGOB(500, o2_playProtracker); + OPCODEGOB(501, o2_stopProtracker); +} + +void Inter_LittleRed::oLittleRed_keyFunc(OpFuncParams ¶ms) { + animPalette(); + _vm->_draw->blitInvalidated(); + + handleBusyWait(); + + int16 cmd = _vm->_game->_script->readInt16(); + int16 key; + uint32 keyState; + + switch (cmd) { + case -1: + break; + + case 0: + _vm->_draw->_showCursor &= ~2; + _vm->_util->longDelay(1); + key = _vm->_game->_hotspots->check(0, 0); + storeKey(key); + + _vm->_util->clearKeyBuf(); + break; + + case 1: + _vm->_util->forceMouseUp(true); + key = _vm->_game->checkKeys(&_vm->_global->_inter_mouseX, + &_vm->_global->_inter_mouseY, &_vm->_game->_mouseButtons, 0); + storeKey(key); + break; + + case 2: + _vm->_util->processInput(true); + keyState = _vm->_util->getKeyState(); + + WRITE_VAR(0, keyState); + _vm->_util->clearKeyBuf(); + break; + + default: + _vm->_sound->speakerOnUpdate(cmd); + if (cmd < 20) { + _vm->_util->delay(cmd); + _noBusyWait = true; + } else + _vm->_util->longDelay(cmd); + break; + } +} + +void Inter_LittleRed::oLittleRed_playComposition(OpFuncParams ¶ms) { + _vm->_sound->blasterRepeatComposition(-1); + + o1_playComposition(params); +} + +} // End of namespace Gob diff --git a/engines/gob/inter_v1.cpp b/engines/gob/inter_v1.cpp index 9aa190a456..dc533a210a 100644 --- a/engines/gob/inter_v1.cpp +++ b/engines/gob/inter_v1.cpp @@ -286,10 +286,40 @@ void Inter_v1::o1_loadMult() { } void Inter_v1::o1_playMult() { - int16 checkEscape; + // NOTE: The EGA version of Gobliiins has an MDY tune. + // While the original doesn't play it, we do. + bool isGob1EGAIntro = _vm->getGameType() == kGameTypeGob1 && + _vm->isEGA() && + _vm->_game->_script->pos() == 1010 && + _vm->isCurrentTot("intro.tot") && + VAR(57) != 0xFFFFFFFF && + _vm->_dataIO->hasFile("goblins.mdy") && + _vm->_dataIO->hasFile("goblins.tbr"); + + int16 checkEscape = _vm->_game->_script->readInt16(); + + if (isGob1EGAIntro) { + _vm->_sound->adlibLoadTBR("goblins.tbr"); + _vm->_sound->adlibLoadMDY("goblins.mdy"); + _vm->_sound->adlibSetRepeating(-1); + + _vm->_sound->adlibPlay(); + } - checkEscape = _vm->_game->_script->readInt16(); _vm->_mult->playMult(VAR(57), -1, checkEscape, 0); + + if (isGob1EGAIntro) { + + // User didn't escape the intro mult, wait for an escape here + if (VAR(57) != 0xFFFFFFFF) { + while (_vm->_util->getKey() != kKeyEscape) { + _vm->_util->processInput(); + _vm->_util->longDelay(1); + } + } + + _vm->_sound->adlibUnload(); + } } void Inter_v1::o1_freeMultKeys() { @@ -1159,26 +1189,15 @@ void Inter_v1::o1_palLoad(OpFuncParams ¶ms) { } void Inter_v1::o1_keyFunc(OpFuncParams ¶ms) { - static uint32 lastCalled = 0; - int16 cmd; - int16 key; - uint32 now; - if (!_vm->_vidPlayer->isPlayingLive()) { _vm->_draw->forceBlit(); _vm->_video->retrace(); } - cmd = _vm->_game->_script->readInt16(); animPalette(); _vm->_draw->blitInvalidated(); - now = _vm->_util->getTimeKey(); - if (!_noBusyWait) - if ((now - lastCalled) <= 20) - _vm->_util->longDelay(1); - lastCalled = now; - _noBusyWait = false; + handleBusyWait(); // WORKAROUND for bug #1726130: Ween busy-waits in the intro for a counter // to become 5000. We deliberately slow down busy-waiting, so we shorten @@ -1187,6 +1206,9 @@ void Inter_v1::o1_keyFunc(OpFuncParams ¶ms) { (_vm->_game->_script->pos() == 729) && _vm->isCurrentTot("intro5.tot")) WRITE_VAR(59, 4000); + int16 cmd = _vm->_game->_script->readInt16(); + int16 key; + switch (cmd) { case -1: break; @@ -1554,14 +1576,13 @@ void Inter_v1::o1_waitEndPlay(OpFuncParams ¶ms) { } void Inter_v1::o1_playComposition(OpFuncParams ¶ms) { - int16 composition[50]; - int16 dataVar; - int16 freqVal; + int16 dataVar = _vm->_game->_script->readVarIndex(); + int16 freqVal = _vm->_game->_script->readValExpr(); - dataVar = _vm->_game->_script->readVarIndex(); - freqVal = _vm->_game->_script->readValExpr(); + int16 composition[50]; + int maxEntries = MIN<int>(50, (_variables->getSize() - dataVar) / 4); for (int i = 0; i < 50; i++) - composition[i] = (int16) VAR_OFFSET(dataVar + i * 4); + composition[i] = (i < maxEntries) ? ((int16) VAR_OFFSET(dataVar + i * 4)) : -1; _vm->_sound->blasterPlayComposition(composition, freqVal); } @@ -1744,10 +1765,15 @@ void Inter_v1::o1_writeData(OpFuncParams ¶ms) { void Inter_v1::o1_manageDataFile(OpFuncParams ¶ms) { Common::String file = _vm->_game->_script->evalString(); - if (!file.empty()) + if (!file.empty()) { _vm->_dataIO->openArchive(file, true); - else + } else { _vm->_dataIO->closeArchive(true); + + // NOTE: Lost in Time might close a data file without explicitely closing a video in it. + // So we make sure that all open videos are still available. + _vm->_vidPlayer->reopenAll(); + } } void Inter_v1::o1_setState(OpGobParams ¶ms) { diff --git a/engines/gob/inter_v2.cpp b/engines/gob/inter_v2.cpp index 1e5b7bb24c..cb58fe86f7 100644 --- a/engines/gob/inter_v2.cpp +++ b/engines/gob/inter_v2.cpp @@ -1002,6 +1002,10 @@ void Inter_v2::o2_openItk() { void Inter_v2::o2_closeItk() { _vm->_dataIO->closeArchive(false); + + // NOTE: Lost in Time might close a data file without explicitely closing a video in it. + // So we make sure that all open videos are still available. + _vm->_vidPlayer->reopenAll(); } void Inter_v2::o2_setImdFrontSurf() { @@ -1244,7 +1248,7 @@ void Inter_v2::o2_checkData(OpFuncParams ¶ms) { file = "EMAP2011.TOT"; int32 size = -1; - SaveLoad::SaveMode mode = _vm->_saveLoad->getSaveMode(file.c_str()); + SaveLoad::SaveMode mode = _vm->_saveLoad ? _vm->_saveLoad->getSaveMode(file.c_str()) : SaveLoad::kSaveModeNone; if (mode == SaveLoad::kSaveModeNone) { size = _vm->_dataIO->fileSize(file); @@ -1273,7 +1277,7 @@ void Inter_v2::o2_readData(OpFuncParams ¶ms) { debugC(2, kDebugFileIO, "Read from file \"%s\" (%d, %d bytes at %d)", file, dataVar, size, offset); - SaveLoad::SaveMode mode = _vm->_saveLoad->getSaveMode(file); + SaveLoad::SaveMode mode = _vm->_saveLoad ? _vm->_saveLoad->getSaveMode(file) : SaveLoad::kSaveModeNone; if (mode == SaveLoad::kSaveModeSave) { WRITE_VAR(1, 1); @@ -1345,7 +1349,7 @@ void Inter_v2::o2_writeData(OpFuncParams ¶ms) { WRITE_VAR(1, 1); - SaveLoad::SaveMode mode = _vm->_saveLoad->getSaveMode(file); + SaveLoad::SaveMode mode = _vm->_saveLoad ? _vm->_saveLoad->getSaveMode(file) : SaveLoad::kSaveModeNone; if (mode == SaveLoad::kSaveModeSave) { if (!_vm->_saveLoad->save(file, dataVar, size, offset)) { diff --git a/engines/gob/inter_v7.cpp b/engines/gob/inter_v7.cpp index 81547f7362..6cf69ed9df 100644 --- a/engines/gob/inter_v7.cpp +++ b/engines/gob/inter_v7.cpp @@ -22,8 +22,11 @@ #include "common/endian.h" #include "common/archive.h" +#include "common/winexe.h" +#include "common/winexe_pe.h" #include "graphics/cursorman.h" +#include "graphics/wincursor.h" #include "gob/gob.h" #include "gob/global.h" @@ -42,7 +45,11 @@ namespace Gob { #define OPCODEFUNC(i, x) _opcodesFunc[i]._OPCODEFUNC(OPCODEVER, x) #define OPCODEGOB(i, x) _opcodesGob[i]._OPCODEGOB(OPCODEVER, x) -Inter_v7::Inter_v7(GobEngine *vm) : Inter_Playtoons(vm) { +Inter_v7::Inter_v7(GobEngine *vm) : Inter_Playtoons(vm), _cursors(0) { +} + +Inter_v7::~Inter_v7() { + delete _cursors; } void Inter_v7::setupOpcodesDraw() { @@ -86,27 +93,121 @@ void Inter_v7::o7_draw0x0C() { WRITE_VAR(17, 0); } +void Inter_v7::resizeCursors(int16 width, int16 height, int16 count, bool transparency) { + if (width <= 0) + width = _vm->_draw->_cursorWidth; + if (height <= 0) + height = _vm->_draw->_cursorHeight; + + width = MAX<uint16>(width , _vm->_draw->_cursorWidth); + height = MAX<uint16>(height, _vm->_draw->_cursorHeight); + + _vm->_draw->_transparentCursor = transparency; + + // Cursors sprite already big enough + if ((_vm->_draw->_cursorWidth >= width) && (_vm->_draw->_cursorHeight >= height) && + (_vm->_draw->_cursorCount >= count)) + return; + + _vm->_draw->_cursorCount = count; + _vm->_draw->_cursorWidth = width; + _vm->_draw->_cursorHeight = height; + + _vm->_draw->freeSprite(Draw::kCursorSurface); + _vm->_draw->_cursorSprites.reset(); + _vm->_draw->_cursorSpritesBack.reset(); + _vm->_draw->_scummvmCursor.reset(); + + _vm->_draw->initSpriteSurf(Draw::kCursorSurface, width * count, height, 2); + + _vm->_draw->_cursorSpritesBack = _vm->_draw->_spritesArray[Draw::kCursorSurface]; + _vm->_draw->_cursorSprites = _vm->_draw->_cursorSpritesBack; + + _vm->_draw->_scummvmCursor = _vm->_video->initSurfDesc(width, height, SCUMMVM_CURSOR); + + for (int i = 0; i < 40; i++) { + _vm->_draw->_cursorAnimLow[i] = -1; + _vm->_draw->_cursorAnimDelays[i] = 0; + _vm->_draw->_cursorAnimHigh[i] = 0; + } + _vm->_draw->_cursorAnimLow[1] = 0; + + delete[] _vm->_draw->_doCursorPalettes; + delete[] _vm->_draw->_cursorPalettes; + delete[] _vm->_draw->_cursorKeyColors; + delete[] _vm->_draw->_cursorPaletteStarts; + delete[] _vm->_draw->_cursorPaletteCounts; + delete[] _vm->_draw->_cursorHotspotsX; + delete[] _vm->_draw->_cursorHotspotsY; + + _vm->_draw->_cursorPalettes = new byte[256 * 3 * count]; + _vm->_draw->_doCursorPalettes = new bool[count]; + _vm->_draw->_cursorKeyColors = new byte[count]; + _vm->_draw->_cursorPaletteStarts = new uint16[count]; + _vm->_draw->_cursorPaletteCounts = new uint16[count]; + _vm->_draw->_cursorHotspotsX = new int32[count]; + _vm->_draw->_cursorHotspotsY = new int32[count]; + + memset(_vm->_draw->_cursorPalettes , 0, count * 256 * 3); + memset(_vm->_draw->_doCursorPalettes , 0, count * sizeof(bool)); + memset(_vm->_draw->_cursorKeyColors , 0, count * sizeof(byte)); + memset(_vm->_draw->_cursorPaletteStarts, 0, count * sizeof(uint16)); + memset(_vm->_draw->_cursorPaletteCounts, 0, count * sizeof(uint16)); + memset(_vm->_draw->_cursorHotspotsX , 0, count * sizeof(int32)); + memset(_vm->_draw->_cursorHotspotsY , 0, count * sizeof(int32)); +} + void Inter_v7::o7_loadCursor() { int16 cursorIndex = _vm->_game->_script->readValExpr(); - Common::String cursorFile = _vm->_game->_script->evalString(); + Common::String cursorName = _vm->_game->_script->evalString(); + + // Clear the cursor sprite at that index + _vm->_draw->_cursorSprites->fillRect(cursorIndex * _vm->_draw->_cursorWidth, 0, + cursorIndex * _vm->_draw->_cursorWidth + _vm->_draw->_cursorWidth - 1, + _vm->_draw->_cursorHeight - 1, 0); + + // If the cursor name is empty, that cursor will be drawn by the scripts + if (cursorName.empty()) { + // Make sure the cursors sprite is big enough and set to non-extern palette + resizeCursors(-1, -1, cursorIndex + 1, true); + _vm->_draw->_doCursorPalettes[cursorIndex] = false; + return; + } + + Graphics::WinCursorGroup *cursorGroup = 0; + Graphics::Cursor *defaultCursor = 0; + + // Load the cursor file and cursor group + if (loadCursorFile()) + cursorGroup = Graphics::WinCursorGroup::createCursorGroup(*_cursors, Common::WinResourceID(cursorName)); + + // If the requested cursor does not exist, create a default one + const Graphics::Cursor *cursor = 0; + if (!cursorGroup || cursorGroup->cursors.empty() || !cursorGroup->cursors[0].cursor) { + defaultCursor = Graphics::makeDefaultWinCursor(); + + cursor = defaultCursor; + } else + cursor = cursorGroup->cursors[0].cursor; - warning("Addy Stub: Load cursor \"%s\" to %d", cursorFile.c_str(), cursorIndex); + // Make sure the cursors sprite it big enough + resizeCursors(cursor->getWidth(), cursor->getHeight(), cursorIndex + 1, true); - byte cursor[9]; - byte palette[6]; + Surface cursorSurf(cursor->getWidth(), cursor->getHeight(), 1, cursor->getSurface()); - cursor[0] = 0; cursor[1] = 0; cursor[2] = 0; - cursor[3] = 0; cursor[4] = 1; cursor[5] = 0; - cursor[6] = 0; cursor[7] = 0; cursor[8] = 0; + _vm->_draw->_cursorSprites->blit(cursorSurf, cursorIndex * _vm->_draw->_cursorWidth, 0); - palette[0] = 0; palette[1] = 0; palette[2] = 0; - palette[3] = 255; palette[4] = 255; palette[5] = 255; + memcpy(_vm->_draw->_cursorPalettes + cursorIndex * 256 * 3, cursor->getPalette(), cursor->getPaletteCount() * 3); - CursorMan.pushCursorPalette(palette, 0, 2); - CursorMan.disableCursorPalette(false); - CursorMan.replaceCursor(cursor, 3, 3, 1, 1, 255); + _vm->_draw->_doCursorPalettes [cursorIndex] = true; + _vm->_draw->_cursorKeyColors [cursorIndex] = cursor->getKeyColor(); + _vm->_draw->_cursorPaletteStarts[cursorIndex] = cursor->getPaletteStartIndex(); + _vm->_draw->_cursorPaletteCounts[cursorIndex] = cursor->getPaletteCount(); + _vm->_draw->_cursorHotspotsX [cursorIndex] = cursor->getHotspotX(); + _vm->_draw->_cursorHotspotsY [cursorIndex] = cursor->getHotspotY(); - CursorMan.showMouse(true); + delete cursorGroup; + delete defaultCursor; } void Inter_v7::o7_displayWarning() { @@ -529,4 +630,19 @@ Common::String Inter_v7::findFile(const Common::String &mask) { return files.front()->getName(); } +bool Inter_v7::loadCursorFile() { + if (_cursors) + return true; + + _cursors = new Common::PEResources(); + + if (_cursors->loadFromEXE("cursor32.dll")) + return true; + + delete _cursors; + _cursors = 0; + + return false; +} + } // End of namespace Gob diff --git a/engines/gob/minigames/geisha/diving.cpp b/engines/gob/minigames/geisha/diving.cpp index 6f4c6e168a..56c7b5213c 100644 --- a/engines/gob/minigames/geisha/diving.cpp +++ b/engines/gob/minigames/geisha/diving.cpp @@ -706,16 +706,16 @@ void Diving::updateAnims() { for (Common::List<ANIObject *>::iterator a = _anims.reverse_begin(); a != _anims.end(); --a) { - (*a)->clear(*_vm->_draw->_backSurface, left, top, right, bottom); - _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, left, top, right, bottom); + if ((*a)->clear(*_vm->_draw->_backSurface, left, top, right, bottom)) + _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, left, top, right, bottom); } // Draw the current animation frames for (Common::List<ANIObject *>::iterator a = _anims.begin(); a != _anims.end(); ++a) { - (*a)->draw(*_vm->_draw->_backSurface, left, top, right, bottom); - _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, left, top, right, bottom); + if ((*a)->draw(*_vm->_draw->_backSurface, left, top, right, bottom)) + _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, left, top, right, bottom); (*a)->advance(); } diff --git a/engines/gob/minigames/geisha/evilfish.cpp b/engines/gob/minigames/geisha/evilfish.cpp index c7ef9d5622..05ae9d0ad4 100644 --- a/engines/gob/minigames/geisha/evilfish.cpp +++ b/engines/gob/minigames/geisha/evilfish.cpp @@ -171,7 +171,7 @@ void EvilFish::mutate(uint16 animSwimLeft, uint16 animSwimRight, } } -bool EvilFish::isDead() { +bool EvilFish::isDead() const { return !isVisible() || (_state == kStateNone) || (_state == kStateDie); } diff --git a/engines/gob/minigames/geisha/evilfish.h b/engines/gob/minigames/geisha/evilfish.h index 81efb676e2..4c82629461 100644 --- a/engines/gob/minigames/geisha/evilfish.h +++ b/engines/gob/minigames/geisha/evilfish.h @@ -58,7 +58,7 @@ public: uint16 animTurnLeft, uint16 animTurnRight, uint16 animDie); /** Is the fish dead? */ - bool isDead(); + bool isDead() const; private: enum State { diff --git a/engines/gob/minigames/geisha/meter.cpp b/engines/gob/minigames/geisha/meter.cpp index e3b9bd1ccf..7ec3119866 100644 --- a/engines/gob/minigames/geisha/meter.cpp +++ b/engines/gob/minigames/geisha/meter.cpp @@ -42,6 +42,10 @@ Meter::~Meter() { delete _surface; } +int32 Meter::getMaxValue() const { + return _maxValue; +} + int32 Meter::getValue() const { return _value; } @@ -59,22 +63,36 @@ void Meter::setMaxValue() { setValue(_maxValue); } -void Meter::increase(int32 n) { +int32 Meter::increase(int32 n) { + if (n < 0) + return decrease(-n); + + int32 overflow = MAX<int32>(0, (_value + n) - _maxValue); + int32 value = CLIP<int32>(_value + n, 0, _maxValue); if (_value == value) - return; + return overflow; _value = value; _needUpdate = true; + + return overflow; } -void Meter::decrease(int32 n) { +int32 Meter::decrease(int32 n) { + if (n < 0) + return increase(-n); + + int32 underflow = -MIN<int32>(0, _value - n); + int32 value = CLIP<int32>(_value - n, 0, _maxValue); if (_value == value) - return; + return underflow; _value = value; _needUpdate = true; + + return underflow; } void Meter::draw(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom) { diff --git a/engines/gob/minigames/geisha/meter.h b/engines/gob/minigames/geisha/meter.h index a9bdb14d0f..30dc826de0 100644 --- a/engines/gob/minigames/geisha/meter.h +++ b/engines/gob/minigames/geisha/meter.h @@ -44,6 +44,8 @@ public: Direction direction); ~Meter(); + /** Return the max value the meter is measuring. */ + int32 getMaxValue() const; /** Return the current value the meter is measuring. */ int32 getValue() const; @@ -53,10 +55,10 @@ public: /** Set the current value the meter is measuring to the max value. */ void setMaxValue(); - /** Increase the current value the meter is measuring. */ - void increase(int32 n = 1); - /** Decrease the current value the meter is measuring. */ - void decrease(int32 n = 1); + /** Increase the current value the meter is measuring, returning the overflow. */ + int32 increase(int32 n = 1); + /** Decrease the current value the meter is measuring, returning the underflow. */ + int32 decrease(int32 n = 1); /** Draw the meter onto the surface and return the affected rectangle. */ void draw(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom); diff --git a/engines/gob/minigames/geisha/mouth.cpp b/engines/gob/minigames/geisha/mouth.cpp new file mode 100644 index 0000000000..7ba9f86f8c --- /dev/null +++ b/engines/gob/minigames/geisha/mouth.cpp @@ -0,0 +1,169 @@ +/* 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/util.h" + +#include "gob/minigames/geisha/mouth.h" + +namespace Gob { + +namespace Geisha { + +Mouth::Mouth(const ANIFile &ani, const CMPFile &cmp, + uint16 mouthAnim, uint16 mouthSprite, uint16 floorSprite) : ANIObject(ani) { + + _sprite = new ANIObject(cmp); + _sprite->setAnimation(mouthSprite); + _sprite->setVisible(true); + + for (int i = 0; i < kFloorCount; i++) { + _floor[i] = new ANIObject(cmp); + _floor[i]->setAnimation(floorSprite); + _floor[i]->setVisible(true); + } + + _state = kStateDeactivated; + + setAnimation(mouthAnim); + setMode(kModeOnce); + setPause(true); + setVisible(true); +} + +Mouth::~Mouth() { + for (int i = 0; i < kFloorCount; i++) + delete _floor[i]; + + delete _sprite; +} + +void Mouth::advance() { + if (_state != kStateActivated) + return; + + // Animation finished, set state to dead + if (isPaused()) { + _state = kStateDead; + return; + } + + ANIObject::advance(); +} + +void Mouth::activate() { + if (_state != kStateDeactivated) + return; + + _state = kStateActivated; + + setPause(false); +} + +bool Mouth::isDeactivated() const { + return _state == kStateDeactivated; +} + +void Mouth::setPosition(int16 x, int16 y) { + ANIObject::setPosition(x, y); + + int16 floorWidth, floorHeight; + _floor[0]->getFrameSize(floorWidth, floorHeight); + + _sprite->setPosition(x, y); + + for (int i = 0; i < kFloorCount; i++) + _floor[i]->setPosition(x + (i * floorWidth), y); +} + +bool Mouth::draw(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom) { + // If the mouth is deactivated, draw the default mouth sprite + if (_state == kStateDeactivated) + return _sprite->draw(dest, left, top, right, bottom); + + // If the mouth is activated, draw the current mouth animation sprite + if (_state == kStateActivated) + return ANIObject::draw(dest, left, top, right, bottom); + + // If the mouth is dead, draw the floor tiles + if (_state == kStateDead) { + int16 fLeft, fRight, fTop, fBottom; + bool drawn = false; + + left = 0x7FFF; + top = 0x7FFF; + right = 0; + bottom = 0; + + for (int i = 0; i < kFloorCount; i++) { + if (_floor[i]->draw(dest, fLeft, fTop, fRight, fBottom)) { + drawn = true; + left = MIN(left , fLeft); + top = MIN(top , fTop); + right = MAX(right , fRight); + bottom = MAX(bottom, fBottom); + } + } + + return drawn; + } + + return false; +} + +bool Mouth::clear(Surface &dest, int16 &left , int16 &top, int16 &right, int16 &bottom) { + // If the mouth is deactivated, clear the default mouth sprite + if (_state == kStateDeactivated) + return _sprite->clear(dest, left, top, right, bottom); + + // If the mouth is activated, clear the current mouth animation sprite + if (_state == kStateActivated) + return ANIObject::clear(dest, left, top, right, bottom); + + // If the mouth is clear, draw the floor tiles + if (_state == kStateDead) { + int16 fLeft, fRight, fTop, fBottom; + bool cleared = false; + + left = 0x7FFF; + top = 0x7FFF; + right = 0; + bottom = 0; + + for (int i = 0; i < kFloorCount; i++) { + if (_floor[i]->clear(dest, fLeft, fTop, fRight, fBottom)) { + cleared = true; + left = MIN(left , fLeft); + top = MIN(top , fTop); + right = MAX(right , fRight); + bottom = MAX(bottom, fBottom); + } + } + + return cleared; + } + + return false; +} + +} // End of namespace Geisha + +} // End of namespace Gob diff --git a/engines/gob/minigames/geisha/mouth.h b/engines/gob/minigames/geisha/mouth.h new file mode 100644 index 0000000000..2e0cfcd5d0 --- /dev/null +++ b/engines/gob/minigames/geisha/mouth.h @@ -0,0 +1,75 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 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 GOB_MINIGAMES_GEISHA_MOUTH_H +#define GOB_MINIGAMES_GEISHA_MOUTH_H + +#include "gob/aniobject.h" + +namespace Gob { + +namespace Geisha { + +/** A kissing/biting mouth in Geisha's "Penetration" minigame. */ +class Mouth : public ANIObject { +public: + Mouth(const ANIFile &ani, const CMPFile &cmp, + uint16 mouthAnim, uint16 mouthSprite, uint16 floorSprite); + ~Mouth(); + + /** Advance the animation to the next frame. */ + void advance(); + + /** Active the mouth's animation. */ + void activate(); + + /** Is the mouth deactivated? */ + bool isDeactivated() const; + + /** Set the current position. */ + void setPosition(int16 x, int16 y); + + /** Draw the current frame onto the surface and return the affected rectangle. */ + bool draw(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom); + /** Draw the current frame from the surface and return the affected rectangle. */ + bool clear(Surface &dest, int16 &left, int16 &top, int16 &right, int16 &bottom); + +private: + static const int kFloorCount = 2; + + enum State { + kStateDeactivated, + kStateActivated, + kStateDead + }; + + ANIObject *_sprite; + ANIObject *_floor[kFloorCount]; + + State _state; +}; + +} // End of namespace Geisha + +} // End of namespace Gob + +#endif // GOB_MINIGAMES_GEISHA_MOUTH_H diff --git a/engines/gob/minigames/geisha/penetration.cpp b/engines/gob/minigames/geisha/penetration.cpp index 121a45bc40..3be9f1f651 100644 --- a/engines/gob/minigames/geisha/penetration.cpp +++ b/engines/gob/minigames/geisha/penetration.cpp @@ -20,87 +20,1458 @@ * */ +#include "common/events.h" + #include "gob/global.h" #include "gob/util.h" +#include "gob/palanim.h" #include "gob/draw.h" #include "gob/video.h" #include "gob/decfile.h" +#include "gob/cmpfile.h" #include "gob/anifile.h" +#include "gob/aniobject.h" + +#include "gob/sound/sound.h" #include "gob/minigames/geisha/penetration.h" +#include "gob/minigames/geisha/meter.h" +#include "gob/minigames/geisha/mouth.h" namespace Gob { namespace Geisha { -static const byte kPalette[48] = { - 0x16, 0x16, 0x16, - 0x12, 0x14, 0x16, - 0x34, 0x00, 0x25, - 0x1D, 0x1F, 0x22, - 0x24, 0x27, 0x2A, - 0x2C, 0x0D, 0x22, - 0x2B, 0x2E, 0x32, - 0x12, 0x09, 0x20, - 0x3D, 0x3F, 0x00, - 0x3F, 0x3F, 0x3F, - 0x00, 0x00, 0x00, - 0x15, 0x15, 0x3F, - 0x25, 0x22, 0x2F, - 0x1A, 0x14, 0x28, - 0x3F, 0x00, 0x00, - 0x15, 0x3F, 0x15 +static const int kColorShield = 11; +static const int kColorHealth = 15; +static const int kColorBlack = 10; +static const int kColorFloor = 13; +static const int kColorFloorText = 14; +static const int kColorExitText = 15; + +enum Sprite { + kSpriteFloorShield = 25, + kSpriteExit = 29, + kSpriteFloor = 30, + kSpriteWall = 31, + kSpriteMouthBite = 32, + kSpriteMouthKiss = 33, + kSpriteBulletN = 65, + kSpriteBulletS = 66, + kSpriteBulletW = 67, + kSpriteBulletE = 68, + kSpriteBulletSW = 85, + kSpriteBulletSE = 86, + kSpriteBulletNW = 87, + kSpriteBulletNE = 88 +}; + +enum Animation { + kAnimationEnemyRound = 0, + kAnimationEnemyRoundExplode = 1, + kAnimationEnemySquare = 2, + kAnimationEnemySquareExplode = 3, + kAnimationMouthKiss = 33, + kAnimationMouthBite = 34 +}; + +static const int kMapTileWidth = 24; +static const int kMapTileHeight = 24; + +static const int kPlayAreaX = 120; +static const int kPlayAreaY = 7; +static const int kPlayAreaWidth = 192; +static const int kPlayAreaHeight = 113; + +static const int kPlayAreaBorderWidth = kPlayAreaWidth / 2; +static const int kPlayAreaBorderHeight = kPlayAreaHeight / 2; + +static const int kTextAreaLeft = 9; +static const int kTextAreaTop = 7; +static const int kTextAreaRight = 104; +static const int kTextAreaBottom = 107; + +static const int kTextAreaBigBottom = 142; + +const byte Penetration::kPalettes[kFloorCount][3 * kPaletteSize] = { + { + 0x16, 0x16, 0x16, + 0x12, 0x14, 0x16, + 0x34, 0x00, 0x25, + 0x1D, 0x1F, 0x22, + 0x24, 0x27, 0x2A, + 0x2C, 0x0D, 0x22, + 0x2B, 0x2E, 0x32, + 0x12, 0x09, 0x20, + 0x3D, 0x3F, 0x00, + 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, + 0x15, 0x15, 0x3F, + 0x25, 0x22, 0x2F, + 0x1A, 0x14, 0x28, + 0x3F, 0x00, 0x00, + 0x15, 0x3F, 0x15 + }, + { + 0x16, 0x16, 0x16, + 0x12, 0x14, 0x16, + 0x37, 0x00, 0x24, + 0x1D, 0x1F, 0x22, + 0x24, 0x27, 0x2A, + 0x30, 0x0E, 0x16, + 0x2B, 0x2E, 0x32, + 0x22, 0x0E, 0x26, + 0x3D, 0x3F, 0x00, + 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, + 0x15, 0x15, 0x3F, + 0x36, 0x28, 0x36, + 0x30, 0x1E, 0x2A, + 0x3F, 0x00, 0x00, + 0x15, 0x3F, 0x15 + }, + { + 0x16, 0x16, 0x16, + 0x12, 0x14, 0x16, + 0x3F, 0x14, 0x22, + 0x1D, 0x1F, 0x22, + 0x24, 0x27, 0x2A, + 0x30, 0x10, 0x10, + 0x2B, 0x2E, 0x32, + 0x2A, 0x12, 0x12, + 0x3D, 0x3F, 0x00, + 0x3F, 0x3F, 0x3F, + 0x00, 0x00, 0x00, + 0x15, 0x15, 0x3F, + 0x3F, 0x23, 0x31, + 0x39, 0x20, 0x2A, + 0x3F, 0x00, 0x00, + 0x15, 0x3F, 0x15 + } +}; + +const byte Penetration::kMaps[kModeCount][kFloorCount][kMapWidth * kMapHeight] = { + { + { // Real mode, floor 0 + 0, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 0, + 50, 50, 0, 0, 52, 53, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 50, + 50, 0, 0, 0, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 0, 0, 50, + 50, 0, 0, 50, 0, 0, 52, 53, 0, 0, 0, 0, 0, 0, 50, 0, 50, + 50, 0, 50, 0, 0, 50, 50, 50, 50, 0, 54, 55, 0, 0, 50, 0, 50, + 50, 0, 50, 49, 0, 50, 0, 52, 53, 0, 50, 50, 50, 0, 0, 0, 50, + 50, 57, 0, 50, 0, 0, 0, 50, 50, 50, 0, 0, 56, 50, 54, 55, 50, + 50, 50, 0, 0, 50, 50, 50, 0, 0, 0, 0, 50, 0, 0, 50, 0, 50, + 50, 51, 50, 0, 54, 55, 0, 0, 50, 50, 50, 50, 52, 53, 50, 0, 50, + 50, 0, 50, 0, 0, 0, 0, 0, 54, 55, 0, 0, 0, 50, 0, 0, 50, + 50, 0, 0, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 0, 0, 0, 50, + 50, 50, 0, 52, 53, 0, 0, 0, 0, 0, 0, 52, 53, 0, 0, 50, 50, + 0, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 0 + }, + { // Real mode, floor 1 + 0, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 0, + 50, 0, 52, 53, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, + 50, 0, 0, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 0, 0, 50, + 50, 0, 50, 51, 52, 53, 0, 0, 52, 53, 0, 0, 0, 0, 50, 0, 50, + 50, 0, 50, 0, 50, 50, 0, 50, 0, 50, 0, 50, 50, 0, 50, 0, 50, + 50, 0, 50, 0, 52, 53, 0, 0, 0, 0, 0, 52, 53, 0, 52, 53, 50, + 50, 57, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 0, 0, 50, + 50, 0, 50, 52, 53, 0, 0, 52, 53, 0, 0, 0, 0, 0, 54, 55, 50, + 50, 0, 50, 0, 50, 0, 50, 50, 0, 50, 50, 0, 50, 0, 50, 50, 50, + 50, 0, 50, 49, 0, 0, 52, 53, 0, 52, 53, 0, 0, 0, 50, 56, 50, + 50, 0, 0, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 0, 0, 50, + 50, 0, 0, 0, 0, 0, 0, 0, 54, 55, 0, 0, 0, 0, 0, 0, 50, + 0, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 0 + }, + { // Real mode, floor 2 + 0, 50, 50, 50, 50, 50, 50, 50, 0, 50, 50, 50, 50, 50, 50, 50, 0, + 50, 52, 53, 0, 0, 0, 0, 50, 50, 50, 0, 0, 0, 0, 52, 53, 50, + 50, 0, 50, 50, 50, 0, 0, 0, 50, 0, 0, 0, 50, 50, 50, 0, 50, + 50, 0, 50, 52, 53, 50, 50, 52, 53, 0, 50, 50, 54, 55, 50, 0, 50, + 50, 0, 50, 0, 0, 0, 0, 50, 0, 50, 0, 0, 0, 0, 50, 0, 50, + 50, 0, 0, 0, 50, 0, 0, 0, 50, 0, 0, 0, 50, 0, 52, 53, 50, + 0, 50, 0, 50, 50, 50, 0, 57, 50, 51, 0, 50, 50, 50, 0, 50, 0, + 50, 0, 0, 0, 50, 0, 0, 0, 50, 0, 52, 53, 50, 0, 0, 0, 50, + 50, 0, 50, 0, 0, 0, 0, 50, 56, 50, 0, 0, 0, 0, 50, 0, 50, + 50, 0, 50, 54, 55, 50, 50, 0, 0, 0, 50, 50, 54, 55, 50, 0, 50, + 50, 0, 50, 50, 50, 0, 0, 0, 50, 0, 0, 0, 50, 50, 50, 0, 50, + 50, 52, 53, 0, 0, 0, 0, 50, 50, 50, 0, 0, 0, 0, 52, 53, 50, + 0, 50, 50, 50, 50, 50, 50, 50, 0, 50, 50, 50, 50, 50, 50, 50, 0 + } + }, + { + { // Test mode, floor 0 + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, + 50, 56, 0, 50, 0, 0, 52, 53, 0, 0, 0, 0, 52, 53, 0, 51, 50, + 50, 0, 0, 50, 0, 0, 0, 50, 0, 54, 55, 50, 0, 50, 50, 50, 50, + 50, 52, 53, 50, 50, 0, 0, 50, 50, 50, 50, 50, 0, 50, 0, 0, 50, + 50, 0, 0, 0, 0, 56, 0, 0, 0, 0, 0, 50, 49, 50, 0, 0, 50, + 50, 0, 54, 55, 0, 50, 50, 54, 55, 0, 50, 50, 50, 0, 0, 0, 50, + 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 52, 53, 0, 0, 54, 55, 50, + 50, 0, 50, 0, 50, 0, 0, 50, 0, 0, 0, 50, 0, 0, 0, 0, 50, + 50, 0, 50, 0, 50, 54, 55, 50, 0, 50, 50, 50, 0, 50, 0, 0, 50, + 50, 50, 50, 50, 50, 0, 0, 50, 0, 0, 0, 0, 0, 50, 54, 55, 50, + 50, 0, 0, 0, 0, 0, 0, 0, 50, 50, 50, 50, 50, 0, 0, 0, 50, + 50, 57, 0, 52, 53, 0, 0, 0, 0, 54, 55, 0, 0, 0, 0, 56, 50, + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50 + }, + { // Test mode, floor 1 + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, + 50, 52, 53, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, + 50, 0, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 54, 55, 0, 50, + 50, 0, 50, 52, 53, 0, 0, 50, 0, 0, 54, 55, 50, 0, 50, 0, 50, + 50, 0, 50, 0, 50, 0, 0, 52, 53, 0, 50, 0, 50, 0, 50, 0, 50, + 50, 0, 50, 0, 50, 50, 50, 50, 50, 49, 50, 0, 50, 0, 50, 0, 50, + 50, 0, 50, 0, 50, 0, 50, 0, 0, 50, 50, 0, 50, 0, 50, 0, 50, + 50, 0, 50, 0, 50, 0, 50, 51, 0, 0, 52, 53, 50, 0, 50, 0, 50, + 50, 57, 50, 0, 50, 0, 50, 50, 50, 50, 50, 50, 50, 0, 50, 0, 50, + 50, 50, 50, 0, 50, 56, 0, 0, 0, 54, 55, 0, 0, 0, 50, 0, 50, + 50, 56, 0, 0, 0, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 0, 50, + 50, 50, 50, 50, 0, 0, 0, 0, 52, 53, 0, 0, 0, 0, 0, 0, 50, + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50 + }, + { // Test mode, floor 2 + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, + 50, 57, 50, 54, 55, 0, 50, 54, 55, 0, 50, 0, 52, 53, 50, 51, 50, + 50, 0, 50, 0, 50, 0, 50, 0, 0, 0, 50, 0, 50, 0, 50, 0, 50, + 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, + 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, + 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, + 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, + 50, 0, 50, 52, 53, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, + 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, + 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, 0, 50, + 50, 0, 0, 0, 50, 0, 50, 0, 50, 0, 0, 0, 50, 0, 50, 0, 50, + 50, 0, 0, 0, 50, 52, 53, 0, 50, 52, 53, 56, 50, 0, 54, 55, 50, + 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50 + } + } +}; + +static const int kLanguageCount = 5; +static const int kFallbackLanguage = 2; // English + +enum String { + kString3rdBasement = 0, + kString2ndBasement, + kString1stBasement, + kStringNoExit, + kStringYouHave, + kString2Exits, + kString1Exit, + kStringToReach, + kStringUpperLevel1, + kStringUpperLevel2, + kStringLevel0, + kStringPenetration, + kStringSuccessful, + kStringDanger, + kStringGynoides, + kStringActivated, + kStringCount }; -Penetration::Penetration(GobEngine *vm) : _vm(vm), _background(0), _objects(0) { +static const char *kStrings[kLanguageCount][kStringCount] = { + { // French + "3EME SOUS-SOL", + "2EME SOUS-SOL", + "1ER SOUS-SOL", + "SORTIE REFUSEE", + "Vous disposez", + "de deux sorties", + "d\'une sortie", + "pour l\'acc\212s au", + "niveau", + "sup\202rieur", + "- NIVEAU 0 -", + "PENETRATION", + "REUSSIE", + "DANGER", + "GYNOIDES", + "ACTIVEES" + }, + { // German + // NOTE: The original had very broken German there. We provide proper(ish) German instead. + // B0rken text in the comments after each line + "3. UNTERGESCHOSS", // "3. U.-GESCHOSS"" + "2. UNTERGESCHOSS", // "2. U.-GESCHOSS" + "1. UNTERGESCHOSS", // "1. U.-GESCHOSS" + "AUSGANG GESPERRT", + "Sie haben", + "zwei Ausg\204nge", // "zwei Ausgang" + "einen Ausgang", // "Fortsetztung" + "um das obere", // "" + "Stockwerk zu", // "" + "erreichen", // "" + "- STOCKWERK 0 -", // "0 - HOHE" + "PENETRATION", // "DURCHDRIGEN" + "ERFOLGREICH", // "ERFOLG" + "GEFAHR", + "GYNOIDE", + "AKTIVIERT", + }, + { // English + "3RD BASEMENT", + "2ND BASEMENT", + "1ST BASEMENT", + "NO EXIT", + "You have", + "2 exits", + "1 exit", + "to reach upper", + "level", + "", + "- 0 LEVEL -", + "PENETRATION", + "SUCCESSFUL", + "DANGER", + "GYNOIDES", + "ACTIVATED", + }, + { // Spanish + "3ER. SUBSUELO", + "2D. SUBSUELO", + "1ER. SUBSUELO", + "SALIDA RECHAZADA", + "Dispones", + "de dos salidas", + "de una salida", + "para acceso al", + "nivel", + "superior", + "- NIVEL 0 -", + "PENETRACION", + "CONSEGUIDA", + "PELIGRO", + "GYNOIDAS", + "ACTIVADAS", + }, + { // Italian + "SOTTOSUOLO 3", + "SOTTOSUOLO 2", + "SOTTOSUOLO 1", + "NON USCITA", + "avete", + "due uscite", + "un\' uscita", + "per accedere al", + "livello", + "superiore", + "- LIVELLO 0 -", + "PENETRAZIONE", + "RIUSCITA", + "PERICOLO", + "GYNOIDI", + "ATTIVATE", + } +}; + + +Penetration::MapObject::MapObject(uint16 tX, uint16 tY, uint16 mX, uint16 mY, uint16 w, uint16 h) : + tileX(tX), tileY(tY), mapX(mX), mapY(mY), width(w), height(h) { + + isBlocking = true; +} + +Penetration::MapObject::MapObject(uint16 tX, uint16 tY, uint16 w, uint16 h) : + tileX(tX), tileY(tY), width(w), height(h) { + + isBlocking = true; + + setMapFromTilePosition(); +} + +void Penetration::MapObject::setTileFromMapPosition() { + tileX = (mapX + (width / 2)) / kMapTileWidth; + tileY = (mapY + (height / 2)) / kMapTileHeight; +} + +void Penetration::MapObject::setMapFromTilePosition() { + mapX = tileX * kMapTileWidth; + mapY = tileY * kMapTileHeight; +} + +bool Penetration::MapObject::isIn(uint16 mX, uint16 mY) const { + if ((mX < mapX) || (mY < mapY)) + return false; + if ((mX > (mapX + width - 1)) || (mY > (mapY + height - 1))) + return false; + + return true; +} + +bool Penetration::MapObject::isIn(uint16 mX, uint16 mY, uint16 w, uint16 h) const { + return isIn(mX , mY ) || + isIn(mX + w - 1, mY ) || + isIn(mX , mY + h - 1) || + isIn(mX + w - 1, mY + h - 1); +} + +bool Penetration::MapObject::isIn(const MapObject &obj) const { + return isIn(obj.mapX, obj.mapY, obj.width, obj.height); +} + + +Penetration::ManagedMouth::ManagedMouth(uint16 tX, uint16 tY, MouthType t) : + MapObject(tX, tY, 0, 0), mouth(0), type(t) { + +} + +Penetration::ManagedMouth::~ManagedMouth() { + delete mouth; +} + + +Penetration::ManagedSub::ManagedSub(uint16 tX, uint16 tY) : + MapObject(tX, tY, kMapTileWidth, kMapTileHeight), sub(0) { + +} + +Penetration::ManagedSub::~ManagedSub() { + delete sub; +} + + +Penetration::ManagedEnemy::ManagedEnemy() : MapObject(0, 0, 0, 0), enemy(0), dead(false) { +} + +Penetration::ManagedEnemy::~ManagedEnemy() { + delete enemy; +} + +void Penetration::ManagedEnemy::clear() { + delete enemy; + + enemy = 0; +} + + +Penetration::ManagedBullet::ManagedBullet() : MapObject(0, 0, 0, 0), bullet(0) { +} + +Penetration::ManagedBullet::~ManagedBullet() { + delete bullet; +} + +void Penetration::ManagedBullet::clear() { + delete bullet; + + bullet = 0; +} + + +Penetration::Penetration(GobEngine *vm) : _vm(vm), _background(0), _sprites(0), _objects(0), _sub(0), + _shieldMeter(0), _healthMeter(0), _floor(0), _isPlaying(false) { + _background = new Surface(320, 200, 1); + + _shieldMeter = new Meter(11, 119, 92, 3, kColorShield, kColorBlack, 920, Meter::kFillToRight); + _healthMeter = new Meter(11, 137, 92, 3, kColorHealth, kColorBlack, 920, Meter::kFillToRight); + + _map = new Surface(kMapWidth * kMapTileWidth + kPlayAreaWidth , + kMapHeight * kMapTileHeight + kPlayAreaHeight, 1); } Penetration::~Penetration() { deinit(); + delete _map; + + delete _shieldMeter; + delete _healthMeter; + delete _background; } -bool Penetration::play(uint16 var1, uint16 var2, uint16 var3) { +bool Penetration::play(bool hasAccessPass, bool hasMaxEnergy, bool testMode) { + _hasAccessPass = hasAccessPass; + _hasMaxEnergy = hasMaxEnergy; + _testMode = testMode; + + _isPlaying = true; + init(); initScreen(); + drawFloorText(); + _vm->_draw->blitInvalidated(); _vm->_video->retrace(); - while (!_vm->_util->keyPressed() && !_vm->shouldQuit()) - _vm->_util->longDelay(1); + + while (!_vm->shouldQuit() && !_quit && !isDead() && !hasWon()) { + enemiesCreate(); + bulletsMove(); + updateAnims(); + + // Draw, fade in if necessary and wait for the end of the frame + _vm->_draw->blitInvalidated(); + fadeIn(); + _vm->_util->waitEndFrame(); + + // Handle the input + checkInput(); + + // Handle the sub movement + handleSub(); + + // Handle the enemies movement + enemiesMove(); + + checkExited(); + + if (_shotCoolDown > 0) + _shotCoolDown--; + } deinit(); - return true; + drawEndText(); + + _isPlaying = false; + + return hasWon(); +} + +bool Penetration::isPlaying() const { + return _isPlaying; +} + +void Penetration::cheatWin() { + _floor = 3; } void Penetration::init() { + // Load sounds + _vm->_sound->sampleLoad(&_soundShield , SOUND_SND, "boucl.snd"); + _vm->_sound->sampleLoad(&_soundBite , SOUND_SND, "pervet.snd"); + _vm->_sound->sampleLoad(&_soundKiss , SOUND_SND, "baise.snd"); + _vm->_sound->sampleLoad(&_soundShoot , SOUND_SND, "tirgim.snd"); + _vm->_sound->sampleLoad(&_soundExit , SOUND_SND, "trouve.snd"); + _vm->_sound->sampleLoad(&_soundExplode, SOUND_SND, "virmor.snd"); + + _quit = false; + for (int i = 0; i < kKeyCount; i++) + _keys[i] = false; + _background->clear(); _vm->_video->drawPackedSprite("hyprmef2.cmp", *_background); + _sprites = new CMPFile(_vm, "tcifplai.cmp", 320, 200); _objects = new ANIFile(_vm, "tcite.ani", 320); + + // The shield starts down + _shieldMeter->setValue(0); + + // If we don't have the max energy tokens, the health starts at 1/3 strength + if (_hasMaxEnergy) + _healthMeter->setMaxValue(); + else + _healthMeter->setValue(_healthMeter->getMaxValue() / 3); + + _floor = 0; + + _shotCoolDown = 0; + + createMap(); } void Penetration::deinit() { + _soundShield.free(); + _soundBite.free(); + _soundKiss.free(); + _soundShoot.free(); + _soundExit.free(); + _soundExplode.free(); + + clearMap(); + delete _objects; + delete _sprites; _objects = 0; + _sprites = 0; +} + +void Penetration::clearMap() { + _mapAnims.clear(); + _anims.clear(); + + _blockingObjects.clear(); + + _walls.clear(); + _exits.clear(); + _shields.clear(); + _mouths.clear(); + + for (int i = 0; i < kEnemyCount; i++) + _enemies[i].clear(); + for (int i = 0; i < kMaxBulletCount; i++) + _bullets[i].clear(); + + delete _sub; + + _sub = 0; + + _map->fill(kColorBlack); +} + +void Penetration::createMap() { + if (_floor >= kFloorCount) + error("Geisha: Invalid floor %d in minigame penetration", _floor); + + clearMap(); + + const byte *mapTiles = kMaps[_testMode ? 1 : 0][_floor]; + + bool exitWorks; + + // Draw the map tiles + for (int y = 0; y < kMapHeight; y++) { + for (int x = 0; x < kMapWidth; x++) { + const byte mapTile = mapTiles[y * kMapWidth + x]; + + const int posX = kPlayAreaBorderWidth + x * kMapTileWidth; + const int posY = kPlayAreaBorderHeight + y * kMapTileHeight; + + switch (mapTile) { + case 0: // Floor + _sprites->draw(*_map, kSpriteFloor, posX, posY); + break; + + case 49: // Emergency exit (needs access pass) + + exitWorks = _hasAccessPass; + if (exitWorks) { + _sprites->draw(*_map, kSpriteExit, posX, posY); + _exits.push_back(MapObject(x, y, 0, 0)); + } else { + _sprites->draw(*_map, kSpriteWall, posX, posY); + _walls.push_back(MapObject(x, y, kMapTileWidth, kMapTileHeight)); + } + + break; + + case 50: // Wall + _sprites->draw(*_map, kSpriteWall, posX, posY); + _walls.push_back(MapObject(x, y, kMapTileWidth, kMapTileHeight)); + break; + + case 51: // Regular exit + + // A regular exit works always in test mode. + // But if we're in real mode, and on the last floor, it needs an access pass + exitWorks = _testMode || (_floor < 2) || _hasAccessPass; + + if (exitWorks) { + _sprites->draw(*_map, kSpriteExit, posX, posY); + _exits.push_back(MapObject(x, y, 0, 0)); + } else { + _sprites->draw(*_map, kSpriteWall, posX, posY); + _walls.push_back(MapObject(x, y, kMapTileWidth, kMapTileHeight)); + } + + break; + + case 52: // Left side of biting mouth + _mouths.push_back(ManagedMouth(x, y, kMouthTypeBite)); + + _mouths.back().mouth = + new Mouth(*_objects, *_sprites, kAnimationMouthBite, kSpriteMouthBite, kSpriteFloor); + + _mouths.back().mouth->setPosition(posX, posY); + break; + + case 53: // Right side of biting mouth + break; + + case 54: // Left side of kissing mouth + _mouths.push_back(ManagedMouth(x, y, kMouthTypeKiss)); + + _mouths.back().mouth = + new Mouth(*_objects, *_sprites, kAnimationMouthKiss, kSpriteMouthKiss, kSpriteFloor); + + _mouths.back().mouth->setPosition(posX, posY); + break; + + case 55: // Right side of kissing mouth + break; + + case 56: // Shield lying on the floor + _sprites->draw(*_map, kSpriteFloor , posX , posY ); // Floor + _sprites->draw(*_map, kSpriteFloorShield, posX + 4, posY + 8); // Shield + + _map->fillRect(posX + 4, posY + 8, posX + 7, posY + 18, kColorFloor); // Area left to shield + _map->fillRect(posX + 17, posY + 8, posX + 20, posY + 18, kColorFloor); // Area right to shield + + _shields.push_back(MapObject(x, y, 0, 0)); + break; + + case 57: // Start position + _sprites->draw(*_map, kSpriteFloor, posX, posY); + + delete _sub; + + _sub = new ManagedSub(x, y); + + _sub->sub = new Submarine(*_objects); + _sub->sub->setPosition(kPlayAreaX + kPlayAreaBorderWidth, kPlayAreaY + kPlayAreaBorderHeight); + break; + } + } + } + + if (!_sub) + error("Geisha: No starting position in floor %d (testmode: %d)", _floor, _testMode); + + // Walls + for (Common::List<MapObject>::iterator w = _walls.begin(); w != _walls.end(); ++w) + _blockingObjects.push_back(&*w); + + // Mouths + for (Common::List<ManagedMouth>::iterator m = _mouths.begin(); m != _mouths.end(); ++m) + _mapAnims.push_back(m->mouth); + + // Sub + _blockingObjects.push_back(_sub); + _anims.push_back(_sub->sub); + + // Moving enemies + for (int i = 0; i < kEnemyCount; i++) { + _enemies[i].enemy = new ANIObject(*_objects); + + _enemies[i].enemy->setPause(true); + _enemies[i].enemy->setVisible(false); + + _enemies[i].isBlocking = false; + + _blockingObjects.push_back(&_enemies[i]); + _mapAnims.push_back(_enemies[i].enemy); + } + + // Bullets + for (int i = 0; i < kMaxBulletCount; i++) { + _bullets[i].bullet = new ANIObject(*_sprites); + + _bullets[i].bullet->setPause(true); + _bullets[i].bullet->setVisible(false); + + _bullets[i].isBlocking = false; + + _mapAnims.push_back(_bullets[i].bullet); + } +} + +void Penetration::drawFloorText() { + _vm->_draw->_backSurface->fillRect(kTextAreaLeft, kTextAreaTop, kTextAreaRight, kTextAreaBottom, kColorBlack); + _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, kTextAreaLeft, kTextAreaTop, kTextAreaRight, kTextAreaBottom); + + const Font *font = _vm->_draw->_fonts[2]; + if (!font) + return; + + const char **strings = kStrings[getLanguage()]; + + const char *floorString = 0; + if (_floor == 0) + floorString = strings[kString3rdBasement]; + else if (_floor == 1) + floorString = strings[kString2ndBasement]; + else if (_floor == 2) + floorString = strings[kString1stBasement]; + + if (floorString) + _vm->_draw->drawString(floorString, 10, 15, kColorFloorText, kColorBlack, 1, + *_vm->_draw->_backSurface, *font); + + if (_exits.size() > 0) { + int exitCount = kString2Exits; + if (_exits.size() == 1) + exitCount = kString1Exit; + + _vm->_draw->drawString(strings[kStringYouHave] , 10, 38, kColorExitText, kColorBlack, 1, + *_vm->_draw->_backSurface, *font); + _vm->_draw->drawString(strings[exitCount] , 10, 53, kColorExitText, kColorBlack, 1, + *_vm->_draw->_backSurface, *font); + _vm->_draw->drawString(strings[kStringToReach] , 10, 68, kColorExitText, kColorBlack, 1, + *_vm->_draw->_backSurface, *font); + _vm->_draw->drawString(strings[kStringUpperLevel1], 10, 84, kColorExitText, kColorBlack, 1, + *_vm->_draw->_backSurface, *font); + _vm->_draw->drawString(strings[kStringUpperLevel2], 10, 98, kColorExitText, kColorBlack, 1, + *_vm->_draw->_backSurface, *font); + + } else + _vm->_draw->drawString(strings[kStringNoExit], 10, 53, kColorExitText, kColorBlack, 1, + *_vm->_draw->_backSurface, *font); +} + +void Penetration::drawEndText() { + // Only draw the end text when we've won and this isn't a test run + if (!hasWon() || _testMode) + return; + + _vm->_draw->_backSurface->fillRect(kTextAreaLeft, kTextAreaTop, kTextAreaRight, kTextAreaBigBottom, kColorBlack); + + const Font *font = _vm->_draw->_fonts[2]; + if (!font) + return; + + const char **strings = kStrings[getLanguage()]; + + _vm->_draw->drawString(strings[kStringLevel0] , 11, 21, kColorExitText, kColorBlack, 1, + *_vm->_draw->_backSurface, *font); + _vm->_draw->drawString(strings[kStringPenetration], 11, 42, kColorExitText, kColorBlack, 1, + *_vm->_draw->_backSurface, *font); + _vm->_draw->drawString(strings[kStringSuccessful] , 11, 58, kColorExitText, kColorBlack, 1, + *_vm->_draw->_backSurface, *font); + + _vm->_draw->drawString(strings[kStringDanger] , 11, 82, kColorFloorText, kColorBlack, 1, + *_vm->_draw->_backSurface, *font); + _vm->_draw->drawString(strings[kStringGynoides] , 11, 98, kColorFloorText, kColorBlack, 1, + *_vm->_draw->_backSurface, *font); + _vm->_draw->drawString(strings[kStringActivated], 11, 113, kColorFloorText, kColorBlack, 1, + *_vm->_draw->_backSurface, *font); + + _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, kTextAreaLeft, kTextAreaTop, kTextAreaRight, kTextAreaBigBottom); + _vm->_draw->blitInvalidated(); + _vm->_video->retrace(); +} + +void Penetration::fadeIn() { + if (!_needFadeIn) + return; + + // Fade to palette + _vm->_palAnim->fade(_vm->_global->_pPaletteDesc, 0, 0); + _needFadeIn = false; +} + +void Penetration::setPalette() { + // Fade to black + _vm->_palAnim->fade(0, 0, 0); + + // Set palette + memcpy(_vm->_draw->_vgaPalette , kPalettes[_floor], 3 * kPaletteSize); + memcpy(_vm->_draw->_vgaSmallPalette, kPalettes[_floor], 3 * kPaletteSize); + + _needFadeIn = true; } void Penetration::initScreen() { _vm->_util->setFrameRate(15); - memcpy(_vm->_draw->_vgaPalette , kPalette, 48); - memcpy(_vm->_draw->_vgaSmallPalette, kPalette, 48); + setPalette(); + + // Draw the shield meter + _sprites->draw(*_background, 0, 0, 95, 6, 9, 117, 0); // Meter frame + _sprites->draw(*_background, 271, 176, 282, 183, 9, 108, 0); // Shield - _vm->_video->setFullPalette(_vm->_global->_pPaletteDesc); + // Draw the health meter + _sprites->draw(*_background, 0, 0, 95, 6, 9, 135, 0); // Meter frame + _sprites->draw(*_background, 283, 176, 292, 184, 9, 126, 0); // Heart _vm->_draw->_backSurface->blit(*_background); _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, 0, 0, 319, 199); } +void Penetration::enemiesCreate() { + for (int i = 0; i < kEnemyCount; i++) { + ManagedEnemy &enemy = _enemies[i]; + + if (enemy.enemy->isVisible()) + continue; + + enemy.enemy->setAnimation((i & 1) ? kAnimationEnemySquare : kAnimationEnemyRound); + enemy.enemy->setMode(ANIObject::kModeContinuous); + enemy.enemy->setPause(false); + enemy.enemy->setVisible(true); + + int16 width, height; + enemy.enemy->getFrameSize(width, height); + + enemy.width = width; + enemy.height = height; + + do { + enemy.mapX = _vm->_util->getRandom(kMapWidth) * kMapTileWidth + 2; + enemy.mapY = _vm->_util->getRandom(kMapHeight) * kMapTileHeight + 4; + enemy.setTileFromMapPosition(); + } while (isBlocked(enemy, enemy.mapX, enemy.mapY)); + + const int posX = kPlayAreaBorderWidth + enemy.mapX; + const int posY = kPlayAreaBorderHeight + enemy.mapY; + + enemy.enemy->setPosition(posX, posY); + + enemy.isBlocking = true; + enemy.dead = false; + } +} + +void Penetration::enemyMove(ManagedEnemy &enemy, int x, int y) { + if ((x == 0) && (y == 0)) + return; + + MapObject *blockedBy; + findPath(enemy, x, y, &blockedBy); + + enemy.setTileFromMapPosition(); + + const int posX = kPlayAreaBorderWidth + enemy.mapX; + const int posY = kPlayAreaBorderHeight + enemy.mapY; + + enemy.enemy->setPosition(posX, posY); + + if (blockedBy == _sub) + enemyAttack(enemy); +} + +void Penetration::enemiesMove() { + for (int i = 0; i < kEnemyCount; i++) { + ManagedEnemy &enemy = _enemies[i]; + + if (!enemy.enemy->isVisible() || enemy.dead) + continue; + + int x = 0, y = 0; + + if (enemy.mapX > _sub->mapX) + x = -8; + else if (enemy.mapX < _sub->mapX) + x = 8; + + if (enemy.mapY > _sub->mapY) + y = -8; + else if (enemy.mapY < _sub->mapY) + y = 8; + + enemyMove(enemy, x, y); + } +} + +void Penetration::enemyAttack(ManagedEnemy &enemy) { + // If we have shields, the enemy explodes at them, taking a huge chunk of energy with it. + // Otherwise, the enemy nibbles a small amount of health away. + + if (_shieldMeter->getValue() > 0) { + enemyExplode(enemy); + + healthLose(80); + } else + healthLose(5); +} + +void Penetration::enemyExplode(ManagedEnemy &enemy) { + enemy.dead = true; + enemy.isBlocking = false; + + bool isSquare = enemy.enemy->getAnimation() == kAnimationEnemySquare; + + enemy.enemy->setAnimation(isSquare ? kAnimationEnemySquareExplode : kAnimationEnemyRoundExplode); + enemy.enemy->setMode(ANIObject::kModeOnce); + + _vm->_sound->blasterPlay(&_soundExplode, 1, 0); +} + +void Penetration::checkInput() { + Common::Event event; + Common::EventManager *eventMan = g_system->getEventManager(); + + while (eventMan->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_KEYDOWN: + if (event.kbd.keycode == Common::KEYCODE_ESCAPE) + _quit = true; + else if (event.kbd.keycode == Common::KEYCODE_UP) + _keys[kKeyUp ] = true; + else if (event.kbd.keycode == Common::KEYCODE_DOWN) + _keys[kKeyDown ] = true; + else if (event.kbd.keycode == Common::KEYCODE_LEFT) + _keys[kKeyLeft ] = true; + else if (event.kbd.keycode == Common::KEYCODE_RIGHT) + _keys[kKeyRight] = true; + else if (event.kbd.keycode == Common::KEYCODE_SPACE) + _keys[kKeySpace] = true; + else if (event.kbd.keycode == Common::KEYCODE_d) { + _vm->getDebugger()->attach(); + _vm->getDebugger()->onFrame(); + } + break; + + case Common::EVENT_KEYUP: + if (event.kbd.keycode == Common::KEYCODE_UP) + _keys[kKeyUp ] = false; + else if (event.kbd.keycode == Common::KEYCODE_DOWN) + _keys[kKeyDown ] = false; + else if (event.kbd.keycode == Common::KEYCODE_LEFT) + _keys[kKeyLeft ] = false; + else if (event.kbd.keycode == Common::KEYCODE_RIGHT) + _keys[kKeyRight] = false; + else if (event.kbd.keycode == Common::KEYCODE_SPACE) + _keys[kKeySpace] = false; + break; + + default: + break; + } + } +} + +void Penetration::handleSub() { + int x, y; + Submarine::Direction direction = getDirection(x, y); + + subMove(x, y, direction); + + if (_keys[kKeySpace]) + subShoot(); +} + +bool Penetration::isBlocked(const MapObject &self, int16 x, int16 y, MapObject **blockedBy) { + + if ((x < 0) || (y < 0)) + return true; + if (((x + self.width - 1) >= (kMapWidth * kMapTileWidth)) || + ((y + self.height - 1) >= (kMapHeight * kMapTileHeight))) + return true; + + MapObject checkSelf(0, 0, self.width, self.height); + + checkSelf.mapX = x; + checkSelf.mapY = y; + + for (Common::List<MapObject *>::iterator o = _blockingObjects.begin(); o != _blockingObjects.end(); ++o) { + MapObject &obj = **o; + + if (&obj == &self) + continue; + + if (!obj.isBlocking) + continue; + + if (obj.isIn(checkSelf) || checkSelf.isIn(obj)) { + if (blockedBy && !*blockedBy) + *blockedBy = &obj; + + return true; + } + } + + return false; +} + +void Penetration::findPath(MapObject &obj, int x, int y, MapObject **blockedBy) { + if (blockedBy) + *blockedBy = 0; + + while ((x != 0) || (y != 0)) { + uint16 oldX = obj.mapX; + uint16 oldY = obj.mapY; + + uint16 newX = obj.mapX; + if (x > 0) { + newX++; + x--; + } else if (x < 0) { + newX--; + x++; + } + + if (!isBlocked(obj, newX, obj.mapY, blockedBy)) + obj.mapX = newX; + + uint16 newY = obj.mapY; + if (y > 0) { + newY++; + y--; + } else if (y < 0) { + newY--; + y++; + } + + if (!isBlocked(obj, obj.mapX, newY, blockedBy)) + obj.mapY = newY; + + if ((obj.mapX == oldX) && (obj.mapY == oldY)) + break; + } +} + +void Penetration::subMove(int x, int y, Submarine::Direction direction) { + if (!_sub->sub->canMove()) + return; + + if ((x == 0) && (y == 0)) + return; + + findPath(*_sub, x, y); + + _sub->setTileFromMapPosition(); + + _sub->sub->turn(direction); + + checkShields(); + checkMouths(); + checkExits(); +} + +void Penetration::subShoot() { + if (!_sub->sub->canMove() || _sub->sub->isShooting()) + return; + + if (_shotCoolDown > 0) + return; + + // Creating a bullet + int slot = findEmptyBulletSlot(); + if (slot < 0) + return; + + ManagedBullet &bullet = _bullets[slot]; + + bullet.bullet->setAnimation(directionToBullet(_sub->sub->getDirection())); + + setBulletPosition(*_sub, bullet); + + const int posX = kPlayAreaBorderWidth + bullet.mapX; + const int posY = kPlayAreaBorderHeight + bullet.mapY; + + bullet.bullet->setPosition(posX, posY); + bullet.bullet->setVisible(true); + + // Shooting + _sub->sub->shoot(); + _vm->_sound->blasterPlay(&_soundShoot, 1, 0); + + _shotCoolDown = 3; +} + +void Penetration::setBulletPosition(const ManagedSub &sub, ManagedBullet &bullet) const { + bullet.mapX = sub.mapX; + bullet.mapY= sub.mapY; + + int16 sWidth, sHeight; + sub.sub->getFrameSize(sWidth, sHeight); + + int16 bWidth, bHeight; + bullet.bullet->getFrameSize(bWidth, bHeight); + + switch (sub.sub->getDirection()) { + case Submarine::kDirectionN: + bullet.mapX += sWidth / 2; + bullet.mapY -= bHeight; + + bullet.deltaX = 0; + bullet.deltaY = -8; + break; + + case Submarine::kDirectionNE: + bullet.mapX += sWidth; + bullet.mapY -= bHeight * 2; + + bullet.deltaX = 8; + bullet.deltaY = -8; + break; + + case Submarine::kDirectionE: + bullet.mapX += sWidth; + bullet.mapY += sHeight / 2 - bHeight; + + bullet.deltaX = 8; + bullet.deltaY = 0; + break; + + case Submarine::kDirectionSE: + bullet.mapX += sWidth; + bullet.mapY += sHeight; + + bullet.deltaX = 8; + bullet.deltaY = 8; + break; + + case Submarine::kDirectionS: + bullet.mapX += sWidth / 2; + bullet.mapY += sHeight; + + bullet.deltaX = 0; + bullet.deltaY = 8; + break; + + case Submarine::kDirectionSW: + bullet.mapX -= bWidth; + bullet.mapY += sHeight; + + bullet.deltaX = -8; + bullet.deltaY = 8; + break; + + case Submarine::kDirectionW: + bullet.mapX -= bWidth; + bullet.mapY += sHeight / 2 - bHeight; + + bullet.deltaX = -8; + bullet.deltaY = 0; + break; + + case Submarine::kDirectionNW: + bullet.mapX -= bWidth; + bullet.mapY -= bHeight; + + bullet.deltaX = -8; + bullet.deltaY = -8; + break; + + default: + break; + } +} + +uint16 Penetration::directionToBullet(Submarine::Direction direction) const { + switch (direction) { + case Submarine::kDirectionN: + return kSpriteBulletN; + + case Submarine::kDirectionNE: + return kSpriteBulletNE; + + case Submarine::kDirectionE: + return kSpriteBulletE; + + case Submarine::kDirectionSE: + return kSpriteBulletSE; + + case Submarine::kDirectionS: + return kSpriteBulletS; + + case Submarine::kDirectionSW: + return kSpriteBulletSW; + + case Submarine::kDirectionW: + return kSpriteBulletW; + + case Submarine::kDirectionNW: + return kSpriteBulletNW; + + default: + break; + } + + return 0; +} + +int Penetration::findEmptyBulletSlot() const { + for (int i = 0; i < kMaxBulletCount; i++) + if (!_bullets[i].bullet->isVisible()) + return i; + + return -1; +} + +void Penetration::bulletsMove() { + for (int i = 0; i < kMaxBulletCount; i++) + if (_bullets[i].bullet->isVisible()) + bulletMove(_bullets[i]); +} + +void Penetration::bulletMove(ManagedBullet &bullet) { + MapObject *blockedBy; + findPath(bullet, bullet.deltaX, bullet.deltaY, &blockedBy); + + if (blockedBy) { + checkShotEnemy(*blockedBy); + bullet.bullet->setVisible(false); + return; + } + + const int posX = kPlayAreaBorderWidth + bullet.mapX; + const int posY = kPlayAreaBorderHeight + bullet.mapY; + + bullet.bullet->setPosition(posX, posY); +} + +void Penetration::checkShotEnemy(MapObject &shotObject) { + for (int i = 0; i < kEnemyCount; i++) { + ManagedEnemy &enemy = _enemies[i]; + + if ((&enemy == &shotObject) && !enemy.dead && enemy.enemy->isVisible()) { + enemyExplode(enemy); + return; + } + } +} + +Submarine::Direction Penetration::getDirection(int &x, int &y) const { + x = _keys[kKeyRight] ? 3 : (_keys[kKeyLeft] ? -3 : 0); + y = _keys[kKeyDown ] ? 3 : (_keys[kKeyUp ] ? -3 : 0); + + if ((x > 0) && (y > 0)) + return Submarine::kDirectionSE; + if ((x > 0) && (y < 0)) + return Submarine::kDirectionNE; + if ((x < 0) && (y > 0)) + return Submarine::kDirectionSW; + if ((x < 0) && (y < 0)) + return Submarine::kDirectionNW; + if (x > 0) + return Submarine::kDirectionE; + if (x < 0) + return Submarine::kDirectionW; + if (y > 0) + return Submarine::kDirectionS; + if (y < 0) + return Submarine::kDirectionN; + + return Submarine::kDirectionNone; +} + +void Penetration::checkShields() { + for (Common::List<MapObject>::iterator s = _shields.begin(); s != _shields.end(); ++s) { + if ((s->tileX == _sub->tileX) && (s->tileY == _sub->tileY)) { + // Charge shields + _shieldMeter->setMaxValue(); + + // Play the shield sound + _vm->_sound->blasterPlay(&_soundShield, 1, 0); + + // Erase the shield from the map + _sprites->draw(*_map, 30, s->mapX + kPlayAreaBorderWidth, s->mapY + kPlayAreaBorderHeight); + _shields.erase(s); + break; + } + } +} + +void Penetration::checkMouths() { + for (Common::List<ManagedMouth>::iterator m = _mouths.begin(); m != _mouths.end(); ++m) { + if (!m->mouth->isDeactivated()) + continue; + + if ((( m->tileX == _sub->tileX) && (m->tileY == _sub->tileY)) || + (((m->tileX + 1) == _sub->tileX) && (m->tileY == _sub->tileY))) { + + m->mouth->activate(); + + // Play the mouth sound and do health gain/loss + if (m->type == kMouthTypeBite) { + _vm->_sound->blasterPlay(&_soundBite, 1, 0); + healthLose(230); + } else if (m->type == kMouthTypeKiss) { + _vm->_sound->blasterPlay(&_soundKiss, 1, 0); + healthGain(120); + } + } + } +} + +void Penetration::checkExits() { + if (!_sub->sub->canMove()) + return; + + for (Common::List<MapObject>::iterator e = _exits.begin(); e != _exits.end(); ++e) { + if ((e->tileX == _sub->tileX) && (e->tileY == _sub->tileY)) { + _sub->setMapFromTilePosition(); + + _sub->sub->leave(); + + _vm->_sound->blasterPlay(&_soundExit, 1, 0); + break; + } + } +} + +void Penetration::healthGain(int amount) { + if (_shieldMeter->getValue() > 0) + _healthMeter->increase(_shieldMeter->increase(amount)); + else + _healthMeter->increase(amount); +} + +void Penetration::healthLose(int amount) { + _healthMeter->decrease(_shieldMeter->decrease(amount)); + + if (_healthMeter->getValue() == 0) + _sub->sub->die(); +} + +void Penetration::checkExited() { + if (_sub->sub->hasExited()) { + _floor++; + + if (_floor >= kFloorCount) + return; + + setPalette(); + createMap(); + drawFloorText(); + } +} + +bool Penetration::isDead() const { + return _sub && _sub->sub->isDead(); +} + +bool Penetration::hasWon() const { + return _floor >= kFloorCount; +} + +int Penetration::getLanguage() const { + if (_vm->_global->_language < kLanguageCount) + return _vm->_global->_language; + + return kFallbackLanguage; +} + +void Penetration::updateAnims() { + int16 left = 0, top = 0, right = 0, bottom = 0; + + // Clear the previous map animation frames + for (Common::List<ANIObject *>::iterator a = _mapAnims.reverse_begin(); + a != _mapAnims.end(); --a) { + + (*a)->clear(*_map, left, top, right, bottom); + } + + // Draw the current map animation frames + for (Common::List<ANIObject *>::iterator a = _mapAnims.begin(); + a != _mapAnims.end(); ++a) { + + (*a)->draw(*_map, left, top, right, bottom); + (*a)->advance(); + } + + // Clear the previous animation frames + for (Common::List<ANIObject *>::iterator a = _anims.reverse_begin(); + a != _anims.end(); --a) { + + if ((*a)->clear(*_vm->_draw->_backSurface, left, top, right, bottom)) + _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, left, top, right, bottom); + } + + if (_sub) { + // Draw the map + + _vm->_draw->_backSurface->blit(*_map, _sub->mapX, _sub->mapY, + _sub->mapX + kPlayAreaWidth - 1, _sub->mapY + kPlayAreaHeight - 1, kPlayAreaX, kPlayAreaY); + _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, kPlayAreaX, kPlayAreaY, + kPlayAreaX + kPlayAreaWidth - 1, kPlayAreaY + kPlayAreaHeight - 1); + } + + // Draw the current animation frames + for (Common::List<ANIObject *>::iterator a = _anims.begin(); + a != _anims.end(); ++a) { + + if ((*a)->draw(*_vm->_draw->_backSurface, left, top, right, bottom)) + _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, left, top, right, bottom); + + (*a)->advance(); + } + + // Draw the meters + _shieldMeter->draw(*_vm->_draw->_backSurface, left, top, right, bottom); + _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, left, top, right, bottom); + + _healthMeter->draw(*_vm->_draw->_backSurface, left, top, right, bottom); + _vm->_draw->dirtiedRect(_vm->_draw->_backSurface, left, top, right, bottom); +} + } // End of namespace Geisha } // End of namespace Gob diff --git a/engines/gob/minigames/geisha/penetration.h b/engines/gob/minigames/geisha/penetration.h index c346a7bf5a..50004eba8e 100644 --- a/engines/gob/minigames/geisha/penetration.h +++ b/engines/gob/minigames/geisha/penetration.h @@ -24,34 +24,229 @@ #define GOB_MINIGAMES_GEISHA_PENETRATION_H #include "common/system.h" +#include "common/list.h" + +#include "gob/sound/sounddesc.h" + +#include "gob/minigames/geisha/submarine.h" namespace Gob { class GobEngine; class Surface; +class CMPFile; class ANIFile; namespace Geisha { +class Meter; +class Mouth; + /** Geisha's "Penetration" minigame. */ class Penetration { public: Penetration(GobEngine *vm); ~Penetration(); - bool play(uint16 var1, uint16 var2, uint16 var3); + bool play(bool hasAccessPass, bool hasMaxEnergy, bool testMode); + + bool isPlaying() const; + void cheatWin(); private: + static const int kModeCount = 2; + static const int kFloorCount = 3; + + static const int kMapWidth = 17; + static const int kMapHeight = 13; + + static const int kPaletteSize = 16; + + static const byte kPalettes[kFloorCount][3 * kPaletteSize]; + static const byte kMaps[kModeCount][kFloorCount][kMapWidth * kMapHeight]; + + static const int kEnemyCount = 9; + static const int kMaxBulletCount = 10; + + struct MapObject { + uint16 tileX; + uint16 tileY; + + uint16 mapX; + uint16 mapY; + + uint16 width; + uint16 height; + + bool isBlocking; + + MapObject(uint16 tX, uint16 tY, uint16 mX, uint16 mY, uint16 w, uint16 h); + MapObject(uint16 tX, uint16 tY, uint16 w, uint16 h); + + void setTileFromMapPosition(); + void setMapFromTilePosition(); + + bool isIn(uint16 mX, uint16 mY) const; + bool isIn(uint16 mX, uint16 mY, uint16 w, uint16 h) const; + bool isIn(const MapObject &obj) const; + }; + + enum MouthType { + kMouthTypeBite, + kMouthTypeKiss + }; + + struct ManagedMouth : public MapObject { + Mouth *mouth; + + MouthType type; + + ManagedMouth(uint16 tX, uint16 tY, MouthType t); + ~ManagedMouth(); + }; + + struct ManagedSub : public MapObject { + Submarine *sub; + + ManagedSub(uint16 tX, uint16 tY); + ~ManagedSub(); + }; + + struct ManagedEnemy : public MapObject { + ANIObject *enemy; + + bool dead; + + ManagedEnemy(); + ~ManagedEnemy(); + + void clear(); + }; + + struct ManagedBullet : public MapObject { + ANIObject *bullet; + + int16 deltaX; + int16 deltaY; + + ManagedBullet(); + ~ManagedBullet(); + + void clear(); + }; + + enum Keys { + kKeyUp = 0, + kKeyDown, + kKeyLeft, + kKeyRight, + kKeySpace, + kKeyCount + }; + GobEngine *_vm; + bool _hasAccessPass; + bool _hasMaxEnergy; + bool _testMode; + + bool _needFadeIn; + + bool _quit; + bool _keys[kKeyCount]; + Surface *_background; + CMPFile *_sprites; ANIFile *_objects; + Common::List<ANIObject *> _anims; + Common::List<ANIObject *> _mapAnims; + + Meter *_shieldMeter; + Meter *_healthMeter; + + uint8 _floor; + + Surface *_map; + + ManagedSub *_sub; + + Common::List<MapObject> _walls; + Common::List<MapObject> _exits; + Common::List<MapObject> _shields; + Common::List<ManagedMouth> _mouths; + + ManagedEnemy _enemies[kEnemyCount]; + ManagedBullet _bullets[kMaxBulletCount]; + + Common::List<MapObject *> _blockingObjects; + + uint8 _shotCoolDown; + + SoundDesc _soundShield; + SoundDesc _soundBite; + SoundDesc _soundKiss; + SoundDesc _soundShoot; + SoundDesc _soundExit; + SoundDesc _soundExplode; + + bool _isPlaying; + void init(); void deinit(); + void clearMap(); + void createMap(); + void initScreen(); + + void setPalette(); + void fadeIn(); + + void drawFloorText(); + void drawEndText(); + + bool isBlocked(const MapObject &self, int16 x, int16 y, MapObject **blockedBy = 0); + void findPath(MapObject &obj, int x, int y, MapObject **blockedBy = 0); + + void updateAnims(); + + void checkInput(); + + Submarine::Direction getDirection(int &x, int &y) const; + + void handleSub(); + void subMove(int x, int y, Submarine::Direction direction); + void subShoot(); + + int findEmptyBulletSlot() const; + uint16 directionToBullet(Submarine::Direction direction) const; + void setBulletPosition(const ManagedSub &sub, ManagedBullet &bullet) const; + + void bulletsMove(); + void bulletMove(ManagedBullet &bullet); + void checkShotEnemy(MapObject &shotObject); + + void checkExits(); + void checkShields(); + void checkMouths(); + + void healthGain(int amount); + void healthLose(int amount); + + void checkExited(); + + void enemiesCreate(); + void enemiesMove(); + void enemyMove(ManagedEnemy &enemy, int x, int y); + void enemyAttack(ManagedEnemy &enemy); + void enemyExplode(ManagedEnemy &enemy); + + bool isDead() const; + bool hasWon() const; + + int getLanguage() const; }; } // End of namespace Geisha diff --git a/engines/gob/minigames/geisha/submarine.cpp b/engines/gob/minigames/geisha/submarine.cpp new file mode 100644 index 0000000000..bf15306e5a --- /dev/null +++ b/engines/gob/minigames/geisha/submarine.cpp @@ -0,0 +1,256 @@ +/* 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 "gob/minigames/geisha/submarine.h" + +namespace Gob { + +namespace Geisha { + +enum Animation { + kAnimationDriveS = 4, + kAnimationDriveE = 5, + kAnimationDriveN = 6, + kAnimationDriveW = 7, + kAnimationDriveSE = 8, + kAnimationDriveNE = 9, + kAnimationDriveSW = 10, + kAnimationDriveNW = 11, + kAnimationShootS = 12, + kAnimationShootN = 13, + kAnimationShootW = 14, + kAnimationShootE = 15, + kAnimationShootNE = 16, + kAnimationShootSE = 17, + kAnimationShootSW = 18, + kAnimationShootNW = 19, + kAnimationExplodeN = 28, + kAnimationExplodeS = 29, + kAnimationExplodeW = 30, + kAnimationExplodeE = 31, + kAnimationExit = 32 +}; + + +Submarine::Submarine(const ANIFile &ani) : ANIObject(ani), _state(kStateMove), _direction(kDirectionNone) { + turn(kDirectionN); +} + +Submarine::~Submarine() { +} + +Submarine::Direction Submarine::getDirection() const { + return _direction; +} + +void Submarine::turn(Direction to) { + // Nothing to do + if ((to == kDirectionNone) || ((_state == kStateMove) && (_direction == to))) + return; + + _direction = to; + + move(); +} + +void Submarine::move() { + uint16 frame = getFrame(); + uint16 anim = (_state == kStateShoot) ? directionToShoot(_direction) : directionToMove(_direction); + + setAnimation(anim); + setFrame(frame); + setPause(false); + setVisible(true); + + setMode((_state == kStateShoot) ? kModeOnce : kModeContinuous); +} + +void Submarine::shoot() { + _state = kStateShoot; + + setAnimation(directionToShoot(_direction)); + setMode(kModeOnce); + setPause(false); + setVisible(true); +} + +void Submarine::die() { + if (!canMove()) + return; + + _state = kStateDie; + + setAnimation(directionToExplode(_direction)); + setMode(kModeOnce); + setPause(false); + setVisible(true); +} + +void Submarine::leave() { + _state = kStateExit; + + setAnimation(kAnimationExit); + setMode(kModeOnce); + setPause(false); + setVisible(true); +} + +void Submarine::advance() { + ANIObject::advance(); + + switch (_state) { + case kStateShoot: + if (isPaused()) { + _state = kStateMove; + + move(); + } + break; + + case kStateExit: + if (isPaused()) + _state = kStateExited; + + break; + + case kStateDie: + if (isPaused()) + _state = kStateDead; + break; + + default: + break; + } +} + +bool Submarine::canMove() const { + return (_state == kStateMove) || (_state == kStateShoot); +} + +bool Submarine::isDead() const { + return _state == kStateDead; +} + +bool Submarine::isShooting() const { + return _state == kStateShoot; +} + +bool Submarine::hasExited() const { + return _state == kStateExited; +} + +uint16 Submarine::directionToMove(Direction direction) const { + switch (direction) { + case kDirectionN: + return kAnimationDriveN; + + case kDirectionNE: + return kAnimationDriveNE; + + case kDirectionE: + return kAnimationDriveE; + + case kDirectionSE: + return kAnimationDriveSE; + + case kDirectionS: + return kAnimationDriveS; + + case kDirectionSW: + return kAnimationDriveSW; + + case kDirectionW: + return kAnimationDriveW; + + case kDirectionNW: + return kAnimationDriveNW; + + default: + break; + } + + return 0; +} + +uint16 Submarine::directionToShoot(Direction direction) const { + switch (direction) { + case kDirectionN: + return kAnimationShootN; + + case kDirectionNE: + return kAnimationShootNE; + + case kDirectionE: + return kAnimationShootE; + + case kDirectionSE: + return kAnimationShootSE; + + case kDirectionS: + return kAnimationShootS; + + case kDirectionSW: + return kAnimationShootSW; + + case kDirectionW: + return kAnimationShootW; + + case kDirectionNW: + return kAnimationShootNW; + + default: + break; + } + + return 0; +} + +uint16 Submarine::directionToExplode(Direction direction) const { + // Only 4 exploding animations (spinning clockwise) + + switch (direction) { + case kDirectionNW: + case kDirectionN: + return kAnimationExplodeN; + + case kDirectionNE: + case kDirectionE: + return kAnimationExplodeE; + + case kDirectionSE: + case kDirectionS: + return kAnimationExplodeS; + + case kDirectionSW: + case kDirectionW: + return kAnimationExplodeW; + + default: + break; + } + + return 0; +} + +} // End of namespace Geisha + +} // End of namespace Gob diff --git a/engines/gob/minigames/geisha/submarine.h b/engines/gob/minigames/geisha/submarine.h new file mode 100644 index 0000000000..a6eae57095 --- /dev/null +++ b/engines/gob/minigames/geisha/submarine.h @@ -0,0 +1,107 @@ +/* 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 GOB_MINIGAMES_GEISHA_SUBMARINE_H +#define GOB_MINIGAMES_GEISHA_SUBMARINE_H + +#include "gob/aniobject.h" + +namespace Gob { + +namespace Geisha { + +/** The submarine Geisha's "Penetration" minigame. */ +class Submarine : public ANIObject { +public: + enum Direction { + kDirectionNone, + kDirectionN, + kDirectionNE, + kDirectionE, + kDirectionSE, + kDirectionS, + kDirectionSW, + kDirectionW, + kDirectionNW + }; + + Submarine(const ANIFile &ani); + ~Submarine(); + + Direction getDirection() const; + + /** Turn to the specified direction. */ + void turn(Direction to); + + /** Play the shoot animation. */ + void shoot(); + + /** Play the exploding animation. */ + void die(); + + /** Play the exiting animation. */ + void leave(); + + /** Advance the animation to the next frame. */ + void advance(); + + /** Can the submarine move at the moment? */ + bool canMove() const; + + /** Is the submarine dead? */ + bool isDead() const; + + /** Is the submarine shooting? */ + bool isShooting() const; + + /** Has the submarine finished exiting the level? */ + bool hasExited() const; + +private: + enum State { + kStateNone = 0, + kStateMove, + kStateShoot, + kStateExit, + kStateExited, + kStateDie, + kStateDead + }; + + State _state; + Direction _direction; + + /** Map the directions to move animation indices. */ + uint16 directionToMove(Direction direction) const; + /** Map the directions to shoot animation indices. */ + uint16 directionToShoot(Direction direction) const; + /** Map the directions to explode animation indices. */ + uint16 directionToExplode(Direction direction) const; + + void move(); +}; + +} // End of namespace Geisha + +} // End of namespace Gob + +#endif // GOB_MINIGAMES_GEISHA_SUBMARINE_H diff --git a/engines/gob/module.mk b/engines/gob/module.mk index 9da5a82de2..20214ea940 100644 --- a/engines/gob/module.mk +++ b/engines/gob/module.mk @@ -44,6 +44,7 @@ MODULE_OBJS := \ inter_v2.o \ inter_bargon.o \ inter_fascin.o \ + inter_littlered.o \ inter_inca2.o \ inter_playtoons.o \ inter_v3.o \ @@ -80,6 +81,8 @@ MODULE_OBJS := \ minigames/geisha/oko.o \ minigames/geisha/meter.o \ minigames/geisha/diving.o \ + minigames/geisha/mouth.o \ + minigames/geisha/submarine.o \ minigames/geisha/penetration.o \ save/savefile.o \ save/savehandler.o \ @@ -101,6 +104,8 @@ MODULE_OBJS := \ sound/sounddesc.o \ sound/pcspeaker.o \ sound/adlib.o \ + sound/musplayer.o \ + sound/adlplayer.o \ sound/infogrames.o \ sound/protracker.o \ sound/soundmixer.o \ diff --git a/engines/gob/mult.cpp b/engines/gob/mult.cpp index 06a7130cef..b3d7ea6263 100644 --- a/engines/gob/mult.cpp +++ b/engines/gob/mult.cpp @@ -366,10 +366,11 @@ void Mult::doPalAnim() { palPtr->blue, 0, 0x13); palPtr = _vm->_global->_pPaletteDesc->vgaPal; - for (_counter = 0; _counter < 16; _counter++, palPtr++) + for (_counter = 0; _counter < 16; _counter++, palPtr++) { _vm->_global->_redPalette[_counter] = palPtr->red; _vm->_global->_greenPalette[_counter] = palPtr->green; _vm->_global->_bluePalette[_counter] = palPtr->blue; + } } else _vm->_video->setFullPalette(_vm->_global->_pPaletteDesc); diff --git a/engines/gob/mult_v2.cpp b/engines/gob/mult_v2.cpp index 6593565e6a..64b9d19e33 100644 --- a/engines/gob/mult_v2.cpp +++ b/engines/gob/mult_v2.cpp @@ -1082,7 +1082,7 @@ void Mult_v2::animate() { continue; for (int j = 0; j < numAnims; j++) { - Mult_Object &animObj2 = *_renderObjs[i]; + Mult_Object &animObj2 = *_renderObjs[j]; Mult_AnimData &animData2 = *(animObj2.pAnimData); if (i == j) diff --git a/engines/gob/palanim.cpp b/engines/gob/palanim.cpp index 8a5327c3f1..f90b141725 100644 --- a/engines/gob/palanim.cpp +++ b/engines/gob/palanim.cpp @@ -75,47 +75,28 @@ bool PalAnim::fadeStepColor(int color) { bool PalAnim::fadeStep(int16 oper) { bool stop = true; - byte newRed; - byte newGreen; - byte newBlue; if (oper == 0) { - if (_vm->_global->_setAllPalette) { - if (_vm->_global->_inVM != 0) - error("PalAnim::fadeStep(): _vm->_global->_inVM != 0 not supported"); - - for (int i = 0; i < 256; i++) { - newRed = fadeColor(_vm->_global->_redPalette[i], _toFadeRed[i]); - newGreen = fadeColor(_vm->_global->_greenPalette[i], _toFadeGreen[i]); - newBlue = fadeColor(_vm->_global->_bluePalette[i], _toFadeBlue[i]); - - if ((_vm->_global->_redPalette[i] != newRed) || - (_vm->_global->_greenPalette[i] != newGreen) || - (_vm->_global->_bluePalette[i] != newBlue)) { - - _vm->_video->setPalElem(i, newRed, newGreen, newBlue, 0, 0x13); - - _vm->_global->_redPalette[i] = newRed; - _vm->_global->_greenPalette[i] = newGreen; - _vm->_global->_bluePalette[i] = newBlue; - stop = false; - } - } - } else { - for (int i = 0; i < 16; i++) { - - _vm->_video->setPalElem(i, - fadeColor(_vm->_global->_redPalette[i], _toFadeRed[i]), - fadeColor(_vm->_global->_greenPalette[i], _toFadeGreen[i]), - fadeColor(_vm->_global->_bluePalette[i], _toFadeBlue[i]), - -1, _vm->_global->_videoMode); - - if ((_vm->_global->_redPalette[i] != _toFadeRed[i]) || - (_vm->_global->_greenPalette[i] != _toFadeGreen[i]) || - (_vm->_global->_bluePalette[i] != _toFadeBlue[i])) - stop = false; + int colorCount = _vm->_global->_setAllPalette ? _vm->_global->_colorCount : 256; + + for (int i = 0; i < colorCount; i++) { + byte newRed = fadeColor(_vm->_global->_redPalette [i], _toFadeRed [i]); + byte newGreen = fadeColor(_vm->_global->_greenPalette[i], _toFadeGreen[i]); + byte newBlue = fadeColor(_vm->_global->_bluePalette [i], _toFadeBlue [i]); + + if ((_vm->_global->_redPalette [i] != newRed ) || + (_vm->_global->_greenPalette[i] != newGreen) || + (_vm->_global->_bluePalette [i] != newBlue)) { + + _vm->_video->setPalElem(i, newRed, newGreen, newBlue, 0, 0x13); + + _vm->_global->_redPalette [i] = newRed; + _vm->_global->_greenPalette[i] = newGreen; + _vm->_global->_bluePalette [i] = newBlue; + stop = false; } } + } else if ((oper > 0) && (oper < 4)) stop = fadeStepColor(oper - 1); @@ -124,44 +105,18 @@ bool PalAnim::fadeStep(int16 oper) { void PalAnim::fade(Video::PalDesc *palDesc, int16 fadeV, int16 allColors) { bool stop; - int16 i; if (_vm->shouldQuit()) return; _fadeValue = (fadeV < 0) ? -fadeV : 2; - if (!_vm->_global->_setAllPalette) { - if (!palDesc) { - for (i = 0; i < 16; i++) { - _toFadeRed[i] = 0; - _toFadeGreen[i] = 0; - _toFadeBlue[i] = 0; - } - } else { - for (i = 0; i < 16; i++) { - _toFadeRed[i] = palDesc->vgaPal[i].red; - _toFadeGreen[i] = palDesc->vgaPal[i].green; - _toFadeBlue[i] = palDesc->vgaPal[i].blue; - } - } - } else { - if (_vm->_global->_inVM != 0) - error("PalAnim::fade(): _vm->_global->_inVM != 0 is not supported"); - - if (!palDesc) { - for (i = 0; i < 256; i++) { - _toFadeRed[i] = 0; - _toFadeGreen[i] = 0; - _toFadeBlue[i] = 0; - } - } else { - for (i = 0; i < 256; i++) { - _toFadeRed[i] = palDesc->vgaPal[i].red; - _toFadeGreen[i] = palDesc->vgaPal[i].green; - _toFadeBlue[i] = palDesc->vgaPal[i].blue; - } - } + int colorCount = _vm->_global->_setAllPalette ? _vm->_global->_colorCount : 256; + + for (int i = 0; i < colorCount; i++) { + _toFadeRed [i] = (palDesc == 0) ? 0 : palDesc->vgaPal[i].red; + _toFadeGreen[i] = (palDesc == 0) ? 0 : palDesc->vgaPal[i].green; + _toFadeBlue [i] = (palDesc == 0) ? 0 : palDesc->vgaPal[i].blue; } if (allColors == 0) { diff --git a/engines/gob/resources.cpp b/engines/gob/resources.cpp index d5497c25be..a84f4ac4b8 100644 --- a/engines/gob/resources.cpp +++ b/engines/gob/resources.cpp @@ -716,7 +716,7 @@ byte *Resources::getIMData(TOTResourceItem &totItem) const { return _imData + offset; } -byte *Resources::getEXTData(EXTResourceItem &extItem, uint32 size) const { +byte *Resources::getEXTData(EXTResourceItem &extItem, uint32 &size) const { Common::SeekableReadStream *stream = _vm->_dataIO->getFile(_extFile); if (!stream) return 0; @@ -726,6 +726,10 @@ byte *Resources::getEXTData(EXTResourceItem &extItem, uint32 size) const { return 0; } + // If that workaround is active, limit the resource size instead of throwing an error + if (_vm->hasResourceSizeWorkaround()) + size = MIN<int>(size, stream->size() - extItem.offset); + byte *data = new byte[extItem.packed ? (size + 2) : size]; if (stream->read(data, size) != size) { delete[] data; @@ -737,7 +741,7 @@ byte *Resources::getEXTData(EXTResourceItem &extItem, uint32 size) const { return data; } -byte *Resources::getEXData(EXTResourceItem &extItem, uint32 size) const { +byte *Resources::getEXData(EXTResourceItem &extItem, uint32 &size) const { Common::SeekableReadStream *stream = _vm->_dataIO->getFile(_exFile); if (!stream) return 0; @@ -747,6 +751,10 @@ byte *Resources::getEXData(EXTResourceItem &extItem, uint32 size) const { return 0; } + // If that workaround is active, limit the resource size instead of throwing an error + if (_vm->hasResourceSizeWorkaround()) + size = MIN<int>(size, stream->size() - extItem.offset); + byte *data = new byte[extItem.packed ? (size + 2) : size]; if (stream->read(data, size) != size) { delete[] data; diff --git a/engines/gob/resources.h b/engines/gob/resources.h index 39155c5176..04b3b9d31e 100644 --- a/engines/gob/resources.h +++ b/engines/gob/resources.h @@ -103,7 +103,7 @@ private: static const int kTOTTextItemSize = 2 + 2; enum ResourceType { - kResourceTOT, + kResourceTOT = 0, kResourceIM, kResourceEXT, kResourceEX @@ -201,8 +201,8 @@ private: byte *getTOTData(TOTResourceItem &totItem) const; byte *getIMData(TOTResourceItem &totItem) const; - byte *getEXTData(EXTResourceItem &extItem, uint32 size) const; - byte *getEXData(EXTResourceItem &extItem, uint32 size) const; + byte *getEXTData(EXTResourceItem &extItem, uint32 &size) const; + byte *getEXData(EXTResourceItem &extItem, uint32 &size) const; }; } // End of namespace Gob diff --git a/engines/gob/sound/adlib.cpp b/engines/gob/sound/adlib.cpp index f1ab2a2d79..d9fc362547 100644 --- a/engines/gob/sound/adlib.cpp +++ b/engines/gob/sound/adlib.cpp @@ -20,771 +20,621 @@ * */ -#include "common/debug.h" -#include "common/file.h" -#include "common/endian.h" +#include "common/util.h" #include "common/textconsole.h" +#include "common/debug.h" +#include "common/config-manager.h" + +#include "audio/fmopl.h" #include "gob/gob.h" #include "gob/sound/adlib.h" namespace Gob { -const unsigned char AdLib::_operators[] = {0, 1, 2, 8, 9, 10, 16, 17, 18}; -const unsigned char AdLib::_volRegNums[] = { - 3, 4, 5, - 11, 12, 13, - 19, 20, 21 +static const int kPitchTom = 24; +static const int kPitchTomToSnare = 7; +static const int kPitchSnareDrum = kPitchTom + kPitchTomToSnare; + + +// Is the operator a modulator (0) or a carrier (1)? +const uint8 AdLib::kOperatorType[kOperatorCount] = { + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1, + 0, 0, 0, 1, 1, 1 +}; + +// Operator number to register offset on the OPL +const uint8 AdLib::kOperatorOffset[kOperatorCount] = { + 0, 1, 2, 3, 4, 5, + 8, 9, 10, 11, 12, 13, + 16, 17, 18, 19, 20, 21 +}; + +// For each operator, the voice it belongs to +const uint8 AdLib::kOperatorVoice[kOperatorCount] = { + 0, 1, 2, + 0, 1, 2, + 3, 4, 5, + 3, 4, 5, + 6, 7, 8, + 6, 7, 8, +}; + +// Voice to operator set, for the 9 melodyvoices (only 6 useable in percussion mode) +const uint8 AdLib::kVoiceMelodyOperator[kOperatorsPerVoice][kMelodyVoiceCount] = { + {0, 1, 2, 6, 7, 8, 12, 13, 14}, + {3, 4, 5, 9, 10, 11, 15, 16, 17} }; -AdLib::AdLib(Audio::Mixer &mixer) : _mixer(&mixer) { - init(); +// Voice to operator set, for the 5 percussion voices (only useable in percussion mode) +const uint8 AdLib::kVoicePercussionOperator[kOperatorsPerVoice][kPercussionVoiceCount] = { + {12, 16, 14, 17, 13}, + {15, 0, 0, 0, 0} +}; + +// Mask bits to set each percussion instrument on/off +const byte AdLib::kPercussionMasks[kPercussionVoiceCount] = {0x10, 0x08, 0x04, 0x02, 0x01}; + +// Default instrument presets +const uint16 AdLib::kPianoParams [kOperatorsPerVoice][kParamCount] = { + { 1, 1, 3, 15, 5, 0, 1, 3, 15, 0, 0, 0, 1, 0}, + { 0, 1, 1, 15, 7, 0, 2, 4, 0, 0, 0, 1, 0, 0} }; +const uint16 AdLib::kBaseDrumParams[kOperatorsPerVoice][kParamCount] = { + { 0, 0, 0, 10, 4, 0, 8, 12, 11, 0, 0, 0, 1, 0 }, + { 0, 0, 0, 13, 4, 0, 6, 15, 0, 0, 0, 0, 1, 0 } }; +const uint16 AdLib::kSnareDrumParams[kParamCount] = { + 0, 12, 0, 15, 11, 0, 8, 5, 0, 0, 0, 0, 0, 0 }; +const uint16 AdLib::kTomParams [kParamCount] = { + 0, 4, 0, 15, 11, 0, 7, 5, 0, 0, 0, 0, 0, 0 }; +const uint16 AdLib::kCymbalParams [kParamCount] = { + 0, 1, 0, 15, 11, 0, 5, 5, 0, 0, 0, 0, 0, 0 }; +const uint16 AdLib::kHihatParams [kParamCount] = { + 0, 1, 0, 15, 11, 0, 7, 5, 0, 0, 0, 0, 0, 0 }; + + +AdLib::AdLib(Audio::Mixer &mixer) : _mixer(&mixer), _opl(0), + _toPoll(0), _repCount(0), _first(true), _playing(false), _ended(true) { + + _rate = _mixer->getOutputRate(); + + initFreqs(); + + createOPL(); + initOPL(); + + _mixer->playStream(Audio::Mixer::kMusicSoundType, &_handle, + this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); } AdLib::~AdLib() { - Common::StackLock slock(_mutex); - _mixer->stopHandle(_handle); - OPLDestroy(_opl); - if (_data && _freeData) - delete[] _data; -} -void AdLib::init() { - _index = -1; - _data = 0; - _playPos = 0; - _dataSize = 0; + delete _opl; +} - _rate = _mixer->getOutputRate(); +// Creates the OPL. Try to use the DOSBox emulator, unless that one is not compiled in, +// or the user explicitly wants the MAME emulator. The MAME one is slightly buggy, leading +// to some wrong sounds, especially noticeable in the title music of Gobliins 2, so we +// really don't want to use it, if we can help it. +void AdLib::createOPL() { + Common::String oplDriver = ConfMan.get("opl_driver"); - _opl = makeAdLibOPL(_rate); + if (oplDriver.empty() || (oplDriver == "auto") || (OPL::Config::parse(oplDriver) == -1)) { + // User has selected OPL driver auto detection or an invalid OPL driver. + // Set it to our preferred driver (DOSBox), if we can. - _first = true; - _ended = false; - _playing = false; + if (OPL::Config::parse("db") <= 0) { + warning("The DOSBox AdLib emulator is not compiled in. Please keep in mind that the MAME one is buggy"); + } else + oplDriver = "db"; - _freeData = false; + } else if (oplDriver == "mame") { + // User has selected the MAME OPL driver. It is buggy, so warn the user about that. - _repCount = -1; - _samplesTillPoll = 0; + warning("You have selected the MAME AdLib emulator. It is buggy; AdLib music might be slightly glitchy now"); + } - for (int i = 0; i < 16; i ++) - _pollNotes[i] = 0; - setFreqs(); + _opl = OPL::Config::create(OPL::Config::parse(oplDriver), OPL::Config::kOpl2); + if (!_opl || !_opl->init(_rate)) { + delete _opl; - _mixer->playStream(Audio::Mixer::kMusicSoundType, &_handle, - this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); + error("Could not create an AdLib emulator"); + } } int AdLib::readBuffer(int16 *buffer, const int numSamples) { Common::StackLock slock(_mutex); - int samples; - int render; - if (!_playing || (numSamples < 0)) { + // Nothing to do, fill with silence + if (!_playing) { memset(buffer, 0, numSamples * sizeof(int16)); return numSamples; } - if (_first) { - memset(buffer, 0, numSamples * sizeof(int16)); - pollMusic(); - return numSamples; - } - samples = numSamples; + // Read samples from the OPL, polling in more music when necessary + uint32 samples = numSamples; while (samples && _playing) { - if (_samplesTillPoll) { - render = (samples > _samplesTillPoll) ? (_samplesTillPoll) : (samples); + if (_toPoll) { + const uint32 render = MIN(samples, _toPoll); + + _opl->readBuffer(buffer, render); + + buffer += render; samples -= render; - _samplesTillPoll -= render; - YM3812UpdateOne(_opl, buffer, render); - buffer += render; + _toPoll -= render; + } else { - pollMusic(); + // Song ended, fill the rest with silence if (_ended) { memset(buffer, 0, samples * sizeof(int16)); samples = 0; + break; } + + // Poll more music + _toPoll = pollMusic(_first); + _first = false; } } + // Song ended, loop if requested if (_ended) { - _first = true; - _ended = false; + _toPoll = 0; - rewind(); + // _repCount == 0: No looping (anymore); _repCount < 0: Infinite looping + if (_repCount != 0) { + if (_repCount > 0) + _repCount--; + + _first = true; + _ended = false; - _samplesTillPoll = 0; - if (_repCount == -1) { - reset(); - setVoices(); - } else if (_repCount > 0) { - _repCount--; reset(); - setVoices(); - } - else + rewind(); + } else _playing = false; } - return numSamples; -} -void AdLib::writeOPL(byte reg, byte val) { - debugC(6, kDebugSound, "AdLib::writeOPL (%02X, %02X)", reg, val); - OPLWriteReg(_opl, reg, val); + return numSamples; } -void AdLib::setFreqs() { - byte lin; - byte col; - long val = 0; - - // Run through the 11 channels - for (lin = 0; lin < 11; lin ++) { - _notes[lin] = 0; - _notCol[lin] = 0; - _notLin[lin] = 0; - _notOn[lin] = false; - } - - // Run through the 25 lines - for (lin = 0; lin < 25; lin ++) { - // Run through the 12 columns - for (col = 0; col < 12; col ++) { - if (!col) - val = (((0x2710L + lin * 0x18) * 0xCB78 / 0x3D090) << 0xE) * - 9 / 0x1B503; - _freqs[lin][col] = (short)((val + 4) >> 3); - val = val * 0x6A / 0x64; - } - } +bool AdLib::isStereo() const { + return _opl->isStereo(); } -void AdLib::reset() { - _first = true; - OPLResetChip(_opl); - _samplesTillPoll = 0; - - setFreqs(); - // Set frequencies and octave to 0; notes off - for (int i = 0; i < 9; i++) { - writeOPL(0xA0 | i, 0); - writeOPL(0xB0 | i, 0); - writeOPL(0xE0 | _operators[i] , 0); - writeOPL(0xE0 |(_operators[i] + 3), 0); - } - - // Authorize the control of the waveformes - writeOPL(0x01, 0x20); -} - -void AdLib::setKey(byte voice, byte note, bool on, bool spec) { - short freq = 0; - short octa = 0; - - // Instruction AX - if (spec) { - // 0x7F donne 0x16B; - // 7F - // << 7 = 3F80 - // + E000 = 11F80 - // & FFFF = 1F80 - // * 19 = 31380 - // / 2000 = 18 => Ligne 18h, colonne 0 => freq 16B - - // 0x3A donne 0x2AF; - // 3A - // << 7 = 1D00 - // + E000 = FD00 negatif - // * 19 = xB500 - // / 2000 = -2 => Ligne 17h, colonne -1 - - // 2E - // << 7 = 1700 - // + E000 = F700 negatif - // * 19 = x1F00 - // / 2000 = - short a; - short lin; - short col; - - a = (note << 7) + 0xE000; // Volontairement tronque - a = (short)((long)a * 25 / 0x2000); - if (a < 0) { - col = - ((24 - a) / 25); - lin = (-a % 25); - if (lin) - lin = 25 - lin; - } - else { - col = a / 25; - lin = a % 25; - } - - _notCol[voice] = col; - _notLin[voice] = lin; - note = _notes[voice]; - } - // Instructions 0X 9X 8X - else { - note -= 12; - _notOn[voice] = on; - } - - _notes[voice] = note; - note += _notCol[voice]; - note = MIN((byte) 0x5F, note); - octa = note / 12; - freq = _freqs[_notLin[voice]][note - octa * 12]; - - writeOPL(0xA0 + voice, freq & 0xFF); - writeOPL(0xB0 + voice, (freq >> 8) | (octa << 2) | (0x20 * (on ? 1 : 0))); - - if (!freq) - warning("AdLib::setKey Voice %d, note %02X unknown", voice, note); +bool AdLib::endOfData() const { + return !_playing; } -void AdLib::setVolume(byte voice, byte volume) { - debugC(6, kDebugSound, "AdLib::setVolume(%d, %d)", voice, volume); - //assert(voice >= 0 && voice <= 9); - volume = 0x3F - ((volume * 0x7E) + 0x7F) / 0xFE; - writeOPL(0x40 + _volRegNums[voice], volume); +bool AdLib::endOfStream() const { + return false; } -void AdLib::pollMusic() { - if ((_playPos > (_data + _dataSize)) && (_dataSize != 0xFFFFFFFF)) { - _ended = true; - return; - } - - interpret(); -} - -void AdLib::unload() { - _playing = false; - _index = -1; - - if (_data && _freeData) - delete[] _data; - - _freeData = false; +int AdLib::getRate() const { + return _rate; } bool AdLib::isPlaying() const { return _playing; } -bool AdLib::getRepeating() const { - return _repCount != 0; +int32 AdLib::getRepeating() const { + Common::StackLock slock(_mutex); + + return _repCount; } void AdLib::setRepeating(int32 repCount) { + Common::StackLock slock(_mutex); + _repCount = repCount; } -int AdLib::getIndex() const { - return _index; +uint32 AdLib::getSamplesPerSecond() const { + return _rate * (isStereo() ? 2 : 1); } void AdLib::startPlay() { - if (_data) _playing = true; + Common::StackLock slock(_mutex); + + _playing = true; + _ended = false; + _first = true; + + reset(); + rewind(); } void AdLib::stopPlay() { Common::StackLock slock(_mutex); + + end(true); + _playing = false; } -ADLPlayer::ADLPlayer(Audio::Mixer &mixer) : AdLib(mixer) { -} +void AdLib::writeOPL(byte reg, byte val) { + debugC(6, kDebugSound, "AdLib::writeOPL (%02X, %02X)", reg, val); -ADLPlayer::~ADLPlayer() { + _opl->writeReg(reg, val); } -bool ADLPlayer::load(const char *fileName) { - Common::File song; +void AdLib::reset() { + allOff(); + initOPL(); +} - unload(); - song.open(fileName); - if (!song.isOpen()) - return false; +void AdLib::allOff() { + // NOTE: Explicit casts are necessary, because of 5.16 paragraph 4 of the C++ standard + int numVoices = isPercussionMode() ? (int)kMaxVoiceCount : (int)kMelodyVoiceCount; - _freeData = true; - _dataSize = song.size(); - _data = new byte[_dataSize]; - song.read(_data, _dataSize); - song.close(); + for (int i = 0; i < numVoices; i++) + noteOff(i); +} +void AdLib::end(bool killRepeat) { reset(); - setVoices(); - _playPos = _data + 3 + (_data[1] + 1) * 0x38; - return true; + _ended = true; + + if (killRepeat) + _repCount = 0; } -bool ADLPlayer::load(byte *data, uint32 size, int index) { - unload(); - _repCount = 0; +void AdLib::initOPL() { + _tremoloDepth = false; + _vibratoDepth = false; + _keySplit = false; - _dataSize = size; - _data = data; - _index = index; + _enableWaveSelect = true; - reset(); - setVoices(); - _playPos = _data + 3 + (_data[1] + 1) * 0x38; + for (int i = 0; i < kMaxVoiceCount; i++) { + _voiceNote[i] = 0; + _voiceOn [i] = 0; + } + + _opl->reset(); + + initOperatorVolumes(); + resetFreqs(); + + setPercussionMode(false); + + setTremoloDepth(false); + setVibratoDepth(false); + setKeySplit(false); + + for(int i = 0; i < kMelodyVoiceCount; i++) + voiceOff(i); - return true; + setPitchRange(1); + + enableWaveSelect(true); } -void ADLPlayer::unload() { - AdLib::unload(); +bool AdLib::isPercussionMode() const { + return _percussionMode; } -void ADLPlayer::interpret() { - unsigned char instr; - byte channel; - byte note; - byte volume; - uint16 tempo; +void AdLib::setPercussionMode(bool percussion) { + if (percussion) { + voiceOff(kVoiceBaseDrum); + voiceOff(kVoiceSnareDrum); + voiceOff(kVoiceTom); - // First tempo, we'll ignore it... - if (_first) { - tempo = *(_playPos++); - // Tempo on 2 bytes - if (tempo & 0x80) - tempo = ((tempo & 3) << 8) | *(_playPos++); - } - _first = false; - - // Instruction - instr = *(_playPos++); - channel = instr & 0x0F; - - switch (instr & 0xF0) { - // Note on + Volume - case 0x00: - note = *(_playPos++); - _pollNotes[channel] = note; - setVolume(channel, *(_playPos++)); - setKey(channel, note, true, false); - break; - // Note on - case 0x90: - note = *(_playPos++); - _pollNotes[channel] = note; - setKey(channel, note, true, false); - break; - // Last note off - case 0x80: - note = _pollNotes[channel]; - setKey(channel, note, false, false); - break; - // Frequency on/off - case 0xA0: - note = *(_playPos++); - setKey(channel, note, _notOn[channel], true); - break; - // Volume - case 0xB0: - volume = *(_playPos++); - setVolume(channel, volume); - break; - // Program change - case 0xC0: - setVoice(channel, *(_playPos++), false); - break; - // Special - case 0xF0: - switch (instr & 0x0F) { - case 0xF: // End instruction - _ended = true; - _samplesTillPoll = 0; - return; - default: - warning("ADLPlayer: Unknown special command %X, stopping playback", - instr & 0x0F); - _repCount = 0; - _ended = true; - break; - } - break; - default: - warning("ADLPlayer: Unknown command %X, stopping playback", - instr & 0xF0); - _repCount = 0; - _ended = true; - break; + /* set the frequency for the last 4 percussion voices: */ + setFreq(kVoiceTom, kPitchTom, 0); + setFreq(kVoiceSnareDrum, kPitchSnareDrum, 0); } - // Temporization - tempo = *(_playPos++); - // End tempo - if (tempo == 0xFF) { - _ended = true; - return; - } - // Tempo on 2 bytes - if (tempo & 0x80) - tempo = ((tempo & 3) << 8) | *(_playPos++); - if (!tempo) - tempo ++; + _percussionMode = percussion; + _percussionBits = 0; - _samplesTillPoll = tempo * (_rate / 1000); + initOperatorParams(); + writeTremoloVibratoDepthPercMode(); } -void ADLPlayer::reset() { - AdLib::reset(); +void AdLib::enableWaveSelect(bool enable) { + _enableWaveSelect = enable; + + for (int i = 0; i < kOperatorCount; i++) + writeOPL(0xE0 + kOperatorOffset[i], 0); + + writeOPL(0x011, _enableWaveSelect ? 0x20 : 0); } -void ADLPlayer::rewind() { - _playPos = _data + 3 + (_data[1] + 1) * 0x38; +void AdLib::setPitchRange(uint8 range) { + _pitchRange = CLIP<uint8>(range, 0, 12); + _pitchRangeStep = _pitchRange * kPitchStepCount; } -void ADLPlayer::setVoices() { - // Definitions of the 9 instruments - for (int i = 0; i < 9; i++) - setVoice(i, i, true); +void AdLib::setTremoloDepth(bool tremoloDepth) { + _tremoloDepth = tremoloDepth; + + writeTremoloVibratoDepthPercMode(); } -void ADLPlayer::setVoice(byte voice, byte instr, bool set) { - uint16 strct[27]; - byte channel; - byte *dataPtr; +void AdLib::setVibratoDepth(bool vibratoDepth) { + _vibratoDepth = vibratoDepth; - // i = 0 : 0 1 2 3 4 5 6 7 8 9 10 11 12 26 - // i = 1 : 13 14 15 16 17 18 19 20 21 22 23 24 25 27 - for (int i = 0; i < 2; i++) { - dataPtr = _data + 3 + instr * 0x38 + i * 0x1A; - for (int j = 0; j < 27; j++) { - strct[j] = READ_LE_UINT16(dataPtr); - dataPtr += 2; - } - channel = _operators[voice] + i * 3; - writeOPL(0xBD, 0x00); - writeOPL(0x08, 0x00); - writeOPL(0x40 | channel, ((strct[0] & 3) << 6) | (strct[8] & 0x3F)); - if (!i) - writeOPL(0xC0 | voice, - ((strct[2] & 7) << 1) | (1 - (strct[12] & 1))); - writeOPL(0x60 | channel, ((strct[3] & 0xF) << 4) | (strct[6] & 0xF)); - writeOPL(0x80 | channel, ((strct[4] & 0xF) << 4) | (strct[7] & 0xF)); - writeOPL(0x20 | channel, ((strct[9] & 1) << 7) | - ((strct[10] & 1) << 6) | ((strct[5] & 1) << 5) | - ((strct[11] & 1) << 4) | (strct[1] & 0xF)); - if (!i) - writeOPL(0xE0 | channel, (strct[26] & 3)); - else - writeOPL(0xE0 | channel, (strct[14] & 3)); - if (i && set) - writeOPL(0x40 | channel, 0); + writeTremoloVibratoDepthPercMode(); +} + +void AdLib::setKeySplit(bool keySplit) { + _keySplit = keySplit; + + writeKeySplit(); +} + +void AdLib::setVoiceTimbre(uint8 voice, const uint16 *params) { + const uint16 *params0 = params; + const uint16 *params1 = params + kParamCount - 1; + const uint16 *waves = params + 2 * (kParamCount - 1); + + const int voicePerc = voice - kVoiceBaseDrum; + + if (!isPercussionMode() || (voice < kVoiceBaseDrum)) { + setOperatorParams(kVoiceMelodyOperator[0][voice], params0, waves[0]); + setOperatorParams(kVoiceMelodyOperator[1][voice], params1, waves[1]); + } else if (voice == kVoiceBaseDrum) { + setOperatorParams(kVoicePercussionOperator[0][voicePerc], params0, waves[0]); + setOperatorParams(kVoicePercussionOperator[1][voicePerc], params1, waves[1]); + } else { + setOperatorParams(kVoicePercussionOperator[0][voicePerc], params0, waves[0]); } } +void AdLib::setVoiceVolume(uint8 voice, uint8 volume) { + int oper; + + const int voicePerc = voice - kVoiceBaseDrum; -MDYPlayer::MDYPlayer(Audio::Mixer &mixer) : AdLib(mixer) { - init(); + if (!isPercussionMode() || (voice < kVoiceBaseDrum)) + oper = kVoiceMelodyOperator[1][ voice]; + else + oper = kVoicePercussionOperator[voice == kVoiceBaseDrum ? 1 : 0][voicePerc]; + + _operatorVolume[oper] = MIN<uint8>(volume, kMaxVolume); + writeKeyScaleLevelVolume(oper); } -MDYPlayer::~MDYPlayer() { +void AdLib::bendVoicePitch(uint8 voice, uint16 pitchBend) { + if (isPercussionMode() && (voice > kVoiceBaseDrum)) + return; + + changePitch(voice, MIN<uint16>(pitchBend, kMaxPitch)); + setFreq(voice, _voiceNote[voice], _voiceOn[voice]); } -void MDYPlayer::init() { - _soundMode = 0; +void AdLib::noteOn(uint8 voice, uint8 note) { + note = MAX<int>(0, note - (kStandardMidC - kOPLMidC)); + + if (isPercussionMode() && (voice >= kVoiceBaseDrum)) { + + if (voice == kVoiceBaseDrum) { + setFreq(kVoiceBaseDrum , note , false); + } else if (voice == kVoiceTom) { + setFreq(kVoiceTom , note , false); + setFreq(kVoiceSnareDrum, note + kPitchTomToSnare, false); + } + + _percussionBits |= kPercussionMasks[voice - kVoiceBaseDrum]; + writeTremoloVibratoDepthPercMode(); - _timbres = 0; - _tbrCount = 0; - _tbrStart = 0; - _timbresSize = 0; + } else + setFreq(voice, note, true); } -bool MDYPlayer::loadMDY(Common::SeekableReadStream &stream) { - unloadMDY(); +void AdLib::noteOff(uint8 voice) { + if (isPercussionMode() && (voice >= kVoiceBaseDrum)) { + _percussionBits &= ~kPercussionMasks[voice - kVoiceBaseDrum]; + writeTremoloVibratoDepthPercMode(); + } else + setFreq(voice, _voiceNote[voice], false); +} - _freeData = true; +void AdLib::writeKeyScaleLevelVolume(uint8 oper) { + uint16 volume = 0; - byte mdyHeader[70]; - stream.read(mdyHeader, 70); + volume = (63 - (_operatorParams[oper][kParamLevel] & 0x3F)) * _operatorVolume[oper]; + volume = 63 - ((2 * volume + kMaxVolume) / (2 * kMaxVolume)); - _tickBeat = mdyHeader[36]; - _beatMeasure = mdyHeader[37]; - _totalTick = mdyHeader[38] + (mdyHeader[39] << 8) + (mdyHeader[40] << 16) + (mdyHeader[41] << 24); - _dataSize = mdyHeader[42] + (mdyHeader[43] << 8) + (mdyHeader[44] << 16) + (mdyHeader[45] << 24); - _nrCommand = mdyHeader[46] + (mdyHeader[47] << 8) + (mdyHeader[48] << 16) + (mdyHeader[49] << 24); -// _soundMode is either 0 (melodic) or 1 (percussive) - _soundMode = mdyHeader[58]; - assert((_soundMode == 0) || (_soundMode == 1)); + uint8 keyScale = _operatorParams[oper][kParamKeyScaleLevel] << 6; - _pitchBendRangeStep = 25*mdyHeader[59]; - _basicTempo = mdyHeader[60] + (mdyHeader[61] << 8); + writeOPL(0x40 + kOperatorOffset[oper], volume | keyScale); +} - if (_pitchBendRangeStep < 25) - _pitchBendRangeStep = 25; - else if (_pitchBendRangeStep > 300) - _pitchBendRangeStep = 300; +void AdLib::writeKeySplit() { + writeOPL(0x08, _keySplit ? 0x40 : 0); +} - _data = new byte[_dataSize]; - stream.read(_data, _dataSize); +void AdLib::writeFeedbackFM(uint8 oper) { + if (kOperatorType[oper] == 1) + return; - reset(); - _playPos = _data; + uint8 value = 0; - return true; + value |= _operatorParams[oper][kParamFeedback] << 1; + value |= _operatorParams[oper][kParamFM] ? 0 : 1; + + writeOPL(0xC0 + kOperatorVoice[oper], value); } -bool MDYPlayer::loadMDY(const char *fileName) { - Common::File song; +void AdLib::writeAttackDecay(uint8 oper) { + uint8 value = 0; + + value |= _operatorParams[oper][kParamAttack] << 4; + value |= _operatorParams[oper][kParamDecay] & 0x0F; - song.open(fileName); - if (!song.isOpen()) - return false; + writeOPL(0x60 + kOperatorOffset[oper], value); +} - bool loaded = loadMDY(song); +void AdLib::writeSustainRelease(uint8 oper) { + uint8 value = 0; - song.close(); + value |= _operatorParams[oper][kParamSustain] << 4; + value |= _operatorParams[oper][kParamRelease] & 0x0F; - return loaded; + writeOPL(0x80 + kOperatorOffset[oper], value); } -bool MDYPlayer::loadTBR(Common::SeekableReadStream &stream) { - unloadTBR(); +void AdLib::writeTremoloVibratoSustainingKeyScaleRateFreqMulti(uint8 oper) { + uint8 value = 0; - _timbresSize = stream.size(); + value |= _operatorParams[oper][kParamAM] ? 0x80 : 0; + value |= _operatorParams[oper][kParamVib] ? 0x40 : 0; + value |= _operatorParams[oper][kParamSustaining] ? 0x20 : 0; + value |= _operatorParams[oper][kParamKeyScaleRate] ? 0x10 : 0; + value |= _operatorParams[oper][kParamFreqMulti] & 0x0F; - _timbres = new byte[_timbresSize]; - stream.read(_timbres, _timbresSize); + writeOPL(0x20 + kOperatorOffset[oper], value); +} - reset(); - setVoices(); +void AdLib::writeTremoloVibratoDepthPercMode() { + uint8 value = 0; + + value |= _tremoloDepth ? 0x80 : 0; + value |= _vibratoDepth ? 0x40 : 0; + value |= isPercussionMode() ? 0x20 : 0; + value |= _percussionBits; - return true; + writeOPL(0xBD, value); } -bool MDYPlayer::loadTBR(const char *fileName) { - Common::File timbres; +void AdLib::writeWaveSelect(uint8 oper) { + uint8 wave = 0; + if (_enableWaveSelect) + wave = _operatorParams[oper][kParamWaveSelect] & 0x03; - timbres.open(fileName); - if (!timbres.isOpen()) - return false; + writeOPL(0xE0 + kOperatorOffset[ oper], wave); +} - bool loaded = loadTBR(timbres); +void AdLib::writeAllParams(uint8 oper) { + writeTremoloVibratoDepthPercMode(); + writeKeySplit(); + writeKeyScaleLevelVolume(oper); + writeFeedbackFM(oper); + writeAttackDecay(oper); + writeSustainRelease(oper); + writeTremoloVibratoSustainingKeyScaleRateFreqMulti(oper); + writeWaveSelect(oper); +} - timbres.close(); +void AdLib::initOperatorParams() { + for (int i = 0; i < kOperatorCount; i++) + setOperatorParams(i, kPianoParams[kOperatorType[i]], kPianoParams[kOperatorType[i]][kParamCount - 1]); - return loaded; + if (isPercussionMode()) { + setOperatorParams(12, kBaseDrumParams [0], kBaseDrumParams [0][kParamCount - 1]); + setOperatorParams(15, kBaseDrumParams [1], kBaseDrumParams [1][kParamCount - 1]); + setOperatorParams(16, kSnareDrumParams , kSnareDrumParams [kParamCount - 1]); + setOperatorParams(14, kTomParams , kTomParams [kParamCount - 1]); + setOperatorParams(17, kCymbalParams , kCymbalParams [kParamCount - 1]); + setOperatorParams(13, kHihatParams , kHihatParams [kParamCount - 1]); + } } -void MDYPlayer::unload() { - unloadTBR(); - unloadMDY(); +void AdLib::initOperatorVolumes() { + for(int i = 0; i < kOperatorCount; i++) + _operatorVolume[i] = kMaxVolume; } -void MDYPlayer::unloadMDY() { - AdLib::unload(); +void AdLib::setOperatorParams(uint8 oper, const uint16 *params, uint8 wave) { + byte *operParams = _operatorParams[oper]; + + for (int i = 0; i < (kParamCount - 1); i++) + operParams[i] = params[i]; + + operParams[kParamCount - 1] = wave & 0x03; + + writeAllParams(oper); +} + +void AdLib::voiceOff(uint8 voice) { + writeOPL(0xA0 + voice, 0); + writeOPL(0xB0 + voice, 0); } -void MDYPlayer::unloadTBR() { - delete[] _timbres; +int32 AdLib::calcFreq(int32 deltaDemiToneNum, int32 deltaDemiToneDenom) { + int32 freq = 0; - _timbres = 0; - _timbresSize = 0; + freq = ((deltaDemiToneDenom * 100) + 6 * deltaDemiToneNum) * 52088; + freq /= deltaDemiToneDenom * 2500; + + return (freq * 147456) / 111875; } -void MDYPlayer::interpret() { - unsigned char instr; - byte channel; - byte note; - byte volume; - uint8 tempoMult, tempoFrac; - uint8 ctrlByte1, ctrlByte2; - uint8 timbre; +void AdLib::setFreqs(uint16 *freqs, int32 num, int32 denom) { + int32 val = calcFreq(num, denom); -// TODO : Verify the loop for percussive mode (11 ?) - if (_first) { - for (int i = 0; i < 9; i ++) - setVolume(i, 0); + *freqs++ = (4 + val) >> 3; -// TODO : Set pitch range + for (int i = 1; i < kHalfToneCount; i++) { + val = (val * 106) / 100; - _tempo = _basicTempo; - _wait = *(_playPos++); - _first = false; + *freqs++ = (4 + val) >> 3; } - do { - instr = *_playPos; - debugC(6, kDebugSound, "MDYPlayer::interpret instr 0x%X", instr); - switch (instr) { - case 0xF8: - _wait = *(_playPos++); - break; - case 0xFC: - _ended = true; - _samplesTillPoll = 0; - return; - case 0xF0: - _playPos++; - ctrlByte1 = *(_playPos++); - ctrlByte2 = *(_playPos++); - debugC(6, kDebugSound, "MDYPlayer::interpret ctrlBytes 0x%X 0x%X", ctrlByte1, ctrlByte2); - if (ctrlByte1 != 0x7F || ctrlByte2 != 0) { - _playPos -= 2; - while (*(_playPos++) != 0xF7) - ; - } else { - tempoMult = *(_playPos++); - tempoFrac = *(_playPos++); - _tempo = _basicTempo * tempoMult + (unsigned)(((long)_basicTempo * tempoFrac) >> 7); - _playPos++; - } - _wait = *(_playPos++); - break; - default: - if (instr >= 0x80) { - _playPos++; - } - channel = (int)(instr & 0x0f); - - switch (instr & 0xf0) { - case 0x90: - note = *(_playPos++); - volume = *(_playPos++); - _pollNotes[channel] = note; - setVolume(channel, volume); - setKey(channel, note, true, false); - break; - case 0x80: - _playPos += 2; - note = _pollNotes[channel]; - setKey(channel, note, false, false); - break; - case 0xA0: - setVolume(channel, *(_playPos++)); - break; - case 0xC0: - timbre = *(_playPos++); - setVoice(channel, timbre, false); - break; - case 0xE0: - warning("MDYPlayer: Pitch bend not yet implemented"); +} - note = *(_playPos)++; - note += (unsigned)(*(_playPos++)) << 7; +void AdLib::initFreqs() { + const int numStep = 100 / kPitchStepCount; - setKey(channel, note, _notOn[channel], true); + for (int i = 0; i < kPitchStepCount; i++) + setFreqs(_freqs[i], i * numStep, 100); - break; - case 0xB0: - _playPos += 2; - break; - case 0xD0: - _playPos++; - break; - default: - warning("MDYPlayer: Bad MIDI instr byte: 0%X", instr); - while ((*_playPos) < 0x80) - _playPos++; - if (*_playPos != 0xF8) - _playPos--; - break; - } //switch instr & 0xF0 - _wait = *(_playPos++); - break; - } //switch instr - } while (_wait == 0); - - if (_wait == 0xF8) { - _wait = 0xF0; - if (*_playPos != 0xF8) - _wait += *(_playPos++) & 0x0F; + resetFreqs(); +} + +void AdLib::resetFreqs() { + for (int i = 0; i < kMaxVoiceCount; i++) { + _freqPtr [i] = _freqs[0]; + _halfToneOffset[i] = 0; } -// _playPos++; - _samplesTillPoll = _wait * (_rate / 1000); } -void MDYPlayer::reset() { - AdLib::reset(); +void AdLib::changePitch(uint8 voice, uint16 pitchBend) { + + int full = 0; + int frac = 0; + int amount = ((pitchBend - kMidPitch) * _pitchRangeStep) / kMidPitch; + + if (amount >= 0) { + // Bend up + + full = amount / kPitchStepCount; + frac = amount % kPitchStepCount; -// _soundMode 1 : Percussive mode. - if (_soundMode == 1) { - writeOPL(0xA6, 0); - writeOPL(0xB6, 0); - writeOPL(0xA7, 0); - writeOPL(0xB7, 0); - writeOPL(0xA8, 0); - writeOPL(0xB8, 0); + } else { + // Bend down + + amount = kPitchStepCount - 1 - amount; + + full = -(amount / kPitchStepCount); + frac = (amount - kPitchStepCount + 1) % kPitchStepCount; + if (frac) + frac = kPitchStepCount - frac; -// TODO set the correct frequency for the last 4 percussive voices } + + _halfToneOffset[voice] = full; + _freqPtr [voice] = _freqs[frac]; } -void MDYPlayer::rewind() { - _playPos = _data; -} - -void MDYPlayer::setVoices() { - byte *timbrePtr; - - timbrePtr = _timbres; - debugC(6, kDebugSound, "MDYPlayer::setVoices TBR version: %X.%X", timbrePtr[0], timbrePtr[1]); - timbrePtr += 2; - - _tbrCount = READ_LE_UINT16(timbrePtr); - debugC(6, kDebugSound, "MDYPlayer::setVoices Timbres counter: %d", _tbrCount); - timbrePtr += 2; - _tbrStart = READ_LE_UINT16(timbrePtr); - - timbrePtr += 2; - for (int i = 0; i < _tbrCount; i++) - setVoice(i, i, true); -} - -void MDYPlayer::setVoice(byte voice, byte instr, bool set) { -// uint16 strct[27]; - uint8 strct[27]; - byte channel; - byte *timbrePtr; - char timbreName[10]; - - timbreName[9] = '\0'; - for (int j = 0; j < 9; j++) - timbreName[j] = _timbres[6 + j + (instr * 9)]; - debugC(6, kDebugSound, "MDYPlayer::setVoice Loading timbre %s", timbreName); - - // i = 0 : 0 1 2 3 4 5 6 7 8 9 10 11 12 26 - // i = 1 : 13 14 15 16 17 18 19 20 21 22 23 24 25 27 - for (int i = 0; i < 2; i++) { - timbrePtr = _timbres + _tbrStart + instr * 0x38 + i * 0x1A; - for (int j = 0; j < 27; j++) { - if (timbrePtr >= (_timbres + _timbresSize)) { - warning("MDYPlayer: Instrument %d out of range (%d, %d)", instr, - (uint32) (timbrePtr - _timbres), _timbresSize); - strct[j] = 0; - } else - //strct[j] = READ_LE_UINT16(timbrePtr); - strct[j] = timbrePtr[0]; - //timbrePtr += 2; - timbrePtr++; - } - channel = _operators[voice] + i * 3; - writeOPL(0xBD, 0x00); - writeOPL(0x08, 0x00); - writeOPL(0x40 | channel, ((strct[0] & 3) << 6) | (strct[8] & 0x3F)); - if (!i) - writeOPL(0xC0 | voice, - ((strct[2] & 7) << 1) | (1 - (strct[12] & 1))); - writeOPL(0x60 | channel, ((strct[3] & 0xF) << 4) | (strct[6] & 0xF)); - writeOPL(0x80 | channel, ((strct[4] & 0xF) << 4) | (strct[7] & 0xF)); - writeOPL(0x20 | channel, ((strct[9] & 1) << 7) | - ((strct[10] & 1) << 6) | ((strct[5] & 1) << 5) | - ((strct[11] & 1) << 4) | (strct[1] & 0xF)); - if (!i) - writeOPL(0xE0 | channel, (strct[26] & 3)); - else { - writeOPL(0xE0 | channel, (strct[14] & 3)); - writeOPL(0x40 | channel, 0); - } - } +void AdLib::setFreq(uint8 voice, uint16 note, bool on) { + _voiceOn [voice] = on; + _voiceNote[voice] = note; + + note = CLIP<int>(note + _halfToneOffset[voice], 0, kNoteCount - 1); + + uint16 freq = _freqPtr[voice][note % kHalfToneCount]; + + uint8 value = 0; + value |= on ? 0x20 : 0; + value |= ((note / kHalfToneCount) << 2) | ((freq >> 8) & 0x03); + + writeOPL(0xA0 + voice, freq); + writeOPL(0xB0 + voice, value); } } // End of namespace Gob diff --git a/engines/gob/sound/adlib.h b/engines/gob/sound/adlib.h index 934e9966eb..bd1778d2ed 100644 --- a/engines/gob/sound/adlib.h +++ b/engines/gob/sound/adlib.h @@ -24,148 +24,282 @@ #define GOB_SOUND_ADLIB_H #include "common/mutex.h" + #include "audio/audiostream.h" #include "audio/mixer.h" -#include "audio/fmopl.h" -namespace Gob { +namespace OPL { + class OPL; +} -class GobEngine; +namespace Gob { +/** Base class for a player of an AdLib music format. */ class AdLib : public Audio::AudioStream { public: AdLib(Audio::Mixer &mixer); virtual ~AdLib(); - bool isPlaying() const; - int getIndex() const; - bool getRepeating() const; + bool isPlaying() const; ///< Are we currently playing? + int32 getRepeating() const; ///< Return number of times left to loop. + /** Set the loop counter. + * + * @param repCount Number of times to loop (i.e. number of additional + * paythroughs to the first one, not overall). + * A negative value means infinite looping. + */ void setRepeating(int32 repCount); void startPlay(); void stopPlay(); - virtual void unload(); - // AudioStream API int readBuffer(int16 *buffer, const int numSamples); - bool isStereo() const { return false; } - bool endOfData() const { return !_playing; } - bool endOfStream() const { return false; } - int getRate() const { return _rate; } + bool isStereo() const; + bool endOfData() const; + bool endOfStream() const; + int getRate() const; protected: - static const unsigned char _operators[]; - static const unsigned char _volRegNums []; + enum kVoice { + kVoiceMelody0 = 0, + kVoiceMelody1 = 1, + kVoiceMelody2 = 2, + kVoiceMelody3 = 3, + kVoiceMelody4 = 4, + kVoiceMelody5 = 5, + kVoiceMelody6 = 6, // Only available in melody mode. + kVoiceMelody7 = 7, // Only available in melody mode. + kVoiceMelody8 = 8, // Only available in melody mode. + kVoiceBaseDrum = 6, // Only available in percussion mode. + kVoiceSnareDrum = 7, // Only available in percussion mode. + kVoiceTom = 8, // Only available in percussion mode. + kVoiceCymbal = 9, // Only available in percussion mode. + kVoiceHihat = 10 // Only available in percussion mode. + }; + + /** Operator parameters. */ + enum kParam { + kParamKeyScaleLevel = 0, + kParamFreqMulti = 1, + kParamFeedback = 2, + kParamAttack = 3, + kParamSustain = 4, + kParamSustaining = 5, + kParamDecay = 6, + kParamRelease = 7, + kParamLevel = 8, + kParamAM = 9, + kParamVib = 10, + kParamKeyScaleRate = 11, + kParamFM = 12, + kParamWaveSelect = 13 + }; + + static const int kOperatorCount = 18; ///< Number of operators. + static const int kParamCount = 14; ///< Number of operator parameters. + static const int kPitchStepCount = 25; ///< Number of pitch bend steps in a half tone. + static const int kOctaveCount = 8; ///< Number of octaves we can play. + static const int kHalfToneCount = 12; ///< Number of half tones in an octave. + + static const int kOperatorsPerVoice = 2; ///< Number of operators per voice. + + static const int kMelodyVoiceCount = 9; ///< Number of melody voices. + static const int kPercussionVoiceCount = 5; ///< Number of percussion voices. + static const int kMaxVoiceCount = 11; ///< Max number of voices. + + /** Number of notes we can play. */ + static const int kNoteCount = kHalfToneCount * kOctaveCount; + + static const int kMaxVolume = 0x007F; + static const int kMaxPitch = 0x3FFF; + static const int kMidPitch = 0x2000; + + static const int kStandardMidC = 60; ///< A mid C in standard MIDI. + static const int kOPLMidC = 48; ///< A mid C for the OPL. + + + /** Return the number of samples per second. */ + uint32 getSamplesPerSecond() const; + + /** Write a value into an OPL register. */ + void writeOPL(byte reg, byte val); + + /** Signal that the playback ended. + * + * @param killRepeat Explicitly request that the song is not to be looped. + */ + void end(bool killRepeat = false); + + /** The callback function that's called for polling more AdLib commands. + * + * @param first Is this the first poll since the start of the song? + * @return The number of samples until the next poll. + */ + virtual uint32 pollMusic(bool first) = 0; + + /** Rewind the song. */ + virtual void rewind() = 0; + + /** Return whether we're in percussion mode. */ + bool isPercussionMode() const; + + /** Set percussion or melody mode. */ + void setPercussionMode(bool percussion); + + /** Enable/Disable the wave select operator parameters. + * + * When disabled, all operators use the sine wave, regardless of the parameter. + */ + void enableWaveSelect(bool enable); + + /** Change the pitch bend range. + * + * @param range The range in half tones from 1 to 12 inclusive. + * See bendVoicePitch() for how this works in practice. + */ + void setPitchRange(uint8 range); + + /** Set the tremolo (amplitude vibrato) depth. + * + * @param tremoloDepth false: 1.0dB, true: 4.8dB. + */ + void setTremoloDepth(bool tremoloDepth); + + /** Set the frequency vibrato depth. + * + * @param vibratoDepth false: 7 cent, true: 14 cent. 1 cent = 1/100 half tone. + */ + void setVibratoDepth(bool vibratoDepth); + + /** Set the keyboard split point. */ + void setKeySplit(bool keySplit); + + /** Set the timbre of a voice. + * + * Layout of the operator parameters is as follows: + * - First 13 parameter for the first operator + * - First 13 parameter for the second operator + * - 14th parameter (wave select) for the first operator + * - 14th parameter (wave select) for the second operator + */ + void setVoiceTimbre(uint8 voice, const uint16 *params); + + /** Set a voice's volume. */ + void setVoiceVolume(uint8 voice, uint8 volume); + + /** Bend a voice's pitch. + * + * The pitchBend parameter is a value between 0 (full down) and kMaxPitch (full up). + * The actual frequency depends on the pitch range set previously by setPitchRange(), + * with full down being -range half tones and full up range half tones. + */ + void bendVoicePitch(uint8 voice, uint16 pitchBend); + + /** Switch a voice on. + * + * Plays one of the kNoteCount notes. However, the valid range of a note is between + * 0 and 127, of which only 12 to 107 are audible. + */ + void noteOn(uint8 voice, uint8 note); + + /** Switch a voice off. */ + void noteOff(uint8 voice); + +private: + static const uint8 kOperatorType [kOperatorCount]; + static const uint8 kOperatorOffset[kOperatorCount]; + static const uint8 kOperatorVoice [kOperatorCount]; + + static const uint8 kVoiceMelodyOperator [kOperatorsPerVoice][kMelodyVoiceCount]; + static const uint8 kVoicePercussionOperator[kOperatorsPerVoice][kPercussionVoiceCount]; + + static const byte kPercussionMasks[kPercussionVoiceCount]; + + static const uint16 kPianoParams [kOperatorsPerVoice][kParamCount]; + static const uint16 kBaseDrumParams [kOperatorsPerVoice][kParamCount]; + + static const uint16 kSnareDrumParams[kParamCount]; + static const uint16 kTomParams [kParamCount]; + static const uint16 kCymbalParams [kParamCount]; + static const uint16 kHihatParams [kParamCount]; + Audio::Mixer *_mixer; Audio::SoundHandle _handle; - FM_OPL *_opl; + OPL::OPL *_opl; Common::Mutex _mutex; uint32 _rate; - byte *_data; - byte *_playPos; - uint32 _dataSize; - - short _freqs[25][12]; - byte _notes[11]; - byte _notCol[11]; - byte _notLin[11]; - bool _notOn[11]; - byte _pollNotes[16]; + uint32 _toPoll; - int _samplesTillPoll; int32 _repCount; - bool _playing; bool _first; + bool _playing; bool _ended; - bool _freeData; + bool _tremoloDepth; + bool _vibratoDepth; + bool _keySplit; - int _index; + bool _enableWaveSelect; - unsigned char _wait; - uint8 _tickBeat; - uint8 _beatMeasure; - uint32 _totalTick; - uint32 _nrCommand; - uint16 _pitchBendRangeStep; - uint16 _basicTempo, _tempo; + bool _percussionMode; + byte _percussionBits; - void writeOPL(byte reg, byte val); - void setFreqs(); - void setKey(byte voice, byte note, bool on, bool spec); - void setVolume(byte voice, byte volume); - void pollMusic(); + uint8 _pitchRange; + uint16 _pitchRangeStep; - virtual void interpret() = 0; + uint8 _voiceNote[kMaxVoiceCount]; // Last note of each voice + uint8 _voiceOn [kMaxVoiceCount]; // Whether each voice is currently on - virtual void reset(); - virtual void rewind() = 0; - virtual void setVoices() = 0; + uint8 _operatorVolume[kOperatorCount]; // Volume of each operator -private: - void init(); -}; + byte _operatorParams[kOperatorCount][kParamCount]; // All operator parameters -class ADLPlayer : public AdLib { -public: - ADLPlayer(Audio::Mixer &mixer); - ~ADLPlayer(); + uint16 _freqs[kPitchStepCount][kHalfToneCount]; + uint16 *_freqPtr[kMaxVoiceCount]; - bool load(const char *fileName); - bool load(byte *data, uint32 size, int index = -1); + int _halfToneOffset[kMaxVoiceCount]; - void unload(); -protected: - void interpret(); + void createOPL(); + void initOPL(); void reset(); - void rewind(); + void allOff(); - void setVoices(); - void setVoice(byte voice, byte instr, bool set); -}; + // Write global parameters into the OPL + void writeTremoloVibratoDepthPercMode(); + void writeKeySplit(); -class MDYPlayer : public AdLib { -public: - MDYPlayer(Audio::Mixer &mixer); - ~MDYPlayer(); - - bool loadMDY(const char *fileName); - bool loadMDY(Common::SeekableReadStream &stream); - bool loadTBR(const char *fileName); - bool loadTBR(Common::SeekableReadStream &stream); - - void unload(); - -protected: - byte _soundMode; - - byte *_timbres; - uint16 _tbrCount; - uint16 _tbrStart; - uint32 _timbresSize; + // Write operator parameters into the OPL + void writeWaveSelect(uint8 oper); + void writeTremoloVibratoSustainingKeyScaleRateFreqMulti(uint8 oper); + void writeSustainRelease(uint8 oper); + void writeAttackDecay(uint8 oper); + void writeFeedbackFM(uint8 oper); + void writeKeyScaleLevelVolume(uint8 oper); + void writeAllParams(uint8 oper); - void interpret(); + void initOperatorParams(); + void initOperatorVolumes(); + void setOperatorParams(uint8 oper, const uint16 *params, uint8 wave); - void reset(); - void rewind(); + void voiceOff(uint8 voice); - void setVoices(); - void setVoice(byte voice, byte instr, bool set); + void initFreqs(); + void setFreqs(uint16 *freqs, int32 num, int32 denom); + int32 calcFreq(int32 deltaDemiToneNum, int32 deltaDemiToneDenom); + void resetFreqs(); - void unloadTBR(); - void unloadMDY(); + void changePitch(uint8 voice, uint16 pitchBend); -private: - void init(); + void setFreq(uint8 voice, uint16 note, bool on); }; } // End of namespace Gob diff --git a/engines/gob/sound/adlplayer.cpp b/engines/gob/sound/adlplayer.cpp new file mode 100644 index 0000000000..ee23191c0d --- /dev/null +++ b/engines/gob/sound/adlplayer.cpp @@ -0,0 +1,257 @@ +/* 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/stream.h" +#include "common/memstream.h" +#include "common/textconsole.h" + +#include "gob/sound/adlplayer.h" + +namespace Gob { + +ADLPlayer::ADLPlayer(Audio::Mixer &mixer) : AdLib(mixer), + _songData(0), _songDataSize(0), _playPos(0) { + +} + +ADLPlayer::~ADLPlayer() { + unload(); +} + +void ADLPlayer::unload() { + stopPlay(); + + _timbres.clear(); + + delete[] _songData; + + _songData = 0; + _songDataSize = 0; + + _playPos = 0; +} + +uint32 ADLPlayer::pollMusic(bool first) { + if (_timbres.empty() || !_songData || !_playPos || (_playPos >= (_songData + _songDataSize))) { + end(); + return 0; + } + + // We'll ignore the first delay + if (first) + _playPos += (*_playPos & 0x80) ? 2 : 1; + + byte cmd = *_playPos++; + + // Song end marker + if (cmd == 0xFF) { + end(); + return 0; + } + + // Set the instrument that should be modified + if (cmd == 0xFE) + _modifyInstrument = *_playPos++; + + if (cmd >= 0xD0) { + // Modify an instrument + + if (_modifyInstrument == 0xFF) + warning("ADLPlayer: No instrument to modify"); + else if (_modifyInstrument >= _timbres.size()) + warning("ADLPlayer: Can't modify invalid instrument %d (%d)", _modifyInstrument, _timbres.size()); + else + _timbres[_modifyInstrument].params[_playPos[0]] = _playPos[1]; + + _playPos += 2; + + // If we currently have that instrument loaded, reload it + for (int i = 0; i < kMaxVoiceCount; i++) + if (_currentInstruments[i] == _modifyInstrument) + setInstrument(i, _modifyInstrument); + } else { + // Voice command + + uint8 voice = cmd & 0x0F; + uint8 note, volume; + + switch (cmd & 0xF0) { + case 0x00: // Note on with volume + note = *_playPos++; + volume = *_playPos++; + + setVoiceVolume(voice, volume); + noteOn(voice, note); + break; + + case 0xA0: // Pitch bend + bendVoicePitch(voice, ((uint16)*_playPos++) << 7); + break; + + case 0xB0: // Set volume + setVoiceVolume(voice, *_playPos++); + break; + + case 0xC0: // Set instrument + setInstrument(voice, *_playPos++); + break; + + case 0x90: // Note on + noteOn(voice, *_playPos++); + break; + + case 0x80: // Note off + noteOff(voice); + break; + + default: + warning("ADLPlayer: Unsupported command: 0x%02X. Stopping playback.", cmd); + end(true); + return 0; + } + } + + uint16 delay = *_playPos++; + + if (delay & 0x80) + delay = ((delay & 3) << 8) | *_playPos++; + + return getSampleDelay(delay); +} + +uint32 ADLPlayer::getSampleDelay(uint16 delay) const { + if (delay == 0) + return 0; + + return ((uint32)delay * getSamplesPerSecond()) / 1000; +} + +void ADLPlayer::rewind() { + // Reset song data + _playPos = _songData; + + // Set melody/percussion mode + setPercussionMode(_soundMode != 0); + + // Reset instruments + for (Common::Array<Timbre>::iterator t = _timbres.begin(); t != _timbres.end(); ++t) + memcpy(t->params, t->startParams, kOperatorsPerVoice * kParamCount * sizeof(uint16)); + + for (int i = 0; i < kMaxVoiceCount; i++) + _currentInstruments[i] = 0; + + // Reset voices + int numVoice = MIN<int>(_timbres.size(), _soundMode ? (int)kMaxVoiceCount : (int)kMelodyVoiceCount); + for (int i = 0; i < numVoice; i++) { + setInstrument(i, _currentInstruments[i]); + setVoiceVolume(i, kMaxVolume); + } + + _modifyInstrument = 0xFF; +} + +bool ADLPlayer::load(Common::SeekableReadStream &adl) { + unload(); + + int timbreCount; + if (!readHeader(adl, timbreCount)) { + unload(); + return false; + } + + if (!readTimbres(adl, timbreCount) || !readSongData(adl) || adl.err()) { + unload(); + return false; + } + + rewind(); + + return true; +} + +bool ADLPlayer::readHeader(Common::SeekableReadStream &adl, int &timbreCount) { + // Sanity check + if (adl.size() < 60) { + warning("ADLPlayer::readHeader(): File too small (%d)", adl.size()); + return false; + } + + _soundMode = adl.readByte(); + timbreCount = adl.readByte() + 1; + + adl.skip(1); + + return true; +} + +bool ADLPlayer::readTimbres(Common::SeekableReadStream &adl, int timbreCount) { + _timbres.resize(timbreCount); + for (Common::Array<Timbre>::iterator t = _timbres.begin(); t != _timbres.end(); ++t) { + for (int i = 0; i < (kOperatorsPerVoice * kParamCount); i++) + t->startParams[i] = adl.readUint16LE(); + } + + if (adl.err()) { + warning("ADLPlayer::readTimbres(): Read failed"); + return false; + } + + return true; +} + +bool ADLPlayer::readSongData(Common::SeekableReadStream &adl) { + _songDataSize = adl.size() - adl.pos(); + _songData = new byte[_songDataSize]; + + if (adl.read(_songData, _songDataSize) != _songDataSize) { + warning("ADLPlayer::readSongData(): Read failed"); + return false; + } + + return true; +} + +bool ADLPlayer::load(const byte *data, uint32 dataSize, int index) { + unload(); + + Common::MemoryReadStream stream(data, dataSize); + if (!load(stream)) + return false; + + _index = index; + return true; +} + +void ADLPlayer::setInstrument(int voice, int instrument) { + if ((voice >= kMaxVoiceCount) || ((uint)instrument >= _timbres.size())) + return; + + _currentInstruments[voice] = instrument; + + setVoiceTimbre(voice, _timbres[instrument].params); +} + +int ADLPlayer::getIndex() const { + return _index; +} + +} // End of namespace Gob diff --git a/engines/gob/sound/adlplayer.h b/engines/gob/sound/adlplayer.h new file mode 100644 index 0000000000..9596447bbc --- /dev/null +++ b/engines/gob/sound/adlplayer.h @@ -0,0 +1,85 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GOB_SOUND_ADLPLAYER_H +#define GOB_SOUND_ADLPLAYER_H + +#include "common/array.h" + +#include "gob/sound/adlib.h" + +namespace Common { + class SeekableReadStream; +} + +namespace Gob { + +/** A player for Coktel Vision's ADL music format. */ +class ADLPlayer : public AdLib { +public: + ADLPlayer(Audio::Mixer &mixer); + ~ADLPlayer(); + + bool load(Common::SeekableReadStream &adl); + bool load(const byte *data, uint32 dataSize, int index = -1); + void unload(); + + int getIndex() const; + +protected: + // AdLib interface + uint32 pollMusic(bool first); + void rewind(); + +private: + struct Timbre { + uint16 startParams[kOperatorsPerVoice * kParamCount]; + uint16 params[kOperatorsPerVoice * kParamCount]; + }; + + uint8 _soundMode; + + Common::Array<Timbre> _timbres; + + byte *_songData; + uint32 _songDataSize; + + const byte *_playPos; + + int _index; + + uint8 _modifyInstrument; + uint16 _currentInstruments[kMaxVoiceCount]; + + + void setInstrument(int voice, int instrument); + + bool readHeader (Common::SeekableReadStream &adl, int &timbreCount); + bool readTimbres (Common::SeekableReadStream &adl, int timbreCount); + bool readSongData(Common::SeekableReadStream &adl); + + uint32 getSampleDelay(uint16 delay) const; +}; + +} // End of namespace Gob + +#endif // GOB_SOUND_ADLPLAYER_H diff --git a/engines/gob/sound/musplayer.cpp b/engines/gob/sound/musplayer.cpp new file mode 100644 index 0000000000..3e41dc6ed1 --- /dev/null +++ b/engines/gob/sound/musplayer.cpp @@ -0,0 +1,391 @@ +/* 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/stream.h" +#include "common/textconsole.h" + +#include "gob/sound/musplayer.h" + +namespace Gob { + +MUSPlayer::MUSPlayer(Audio::Mixer &mixer) : AdLib(mixer), + _songData(0), _songDataSize(0), _playPos(0), _songID(0) { + +} + +MUSPlayer::~MUSPlayer() { + unload(); +} + +void MUSPlayer::unload() { + stopPlay(); + + unloadSND(); + unloadMUS(); +} + +uint32 MUSPlayer::getSampleDelay(uint16 delay) const { + if (delay == 0) + return 0; + + uint32 freq = (_ticksPerBeat * _tempo) / 60; + + return ((uint32)delay * getSamplesPerSecond()) / freq; +} + +void MUSPlayer::skipToTiming() { + while (*_playPos < 0x80) + _playPos++; + + if (*_playPos != 0xF8) + _playPos--; +} + +uint32 MUSPlayer::pollMusic(bool first) { + if (_timbres.empty() || !_songData || !_playPos || (_playPos >= (_songData + _songDataSize))) { + end(); + return 0; + } + + if (first) + return getSampleDelay(*_playPos++); + + uint16 delay = 0; + while (delay == 0) { + byte cmd = *_playPos; + + // Delay overflow + if (cmd == 0xF8) { + _playPos++; + delay = 0xF8; + break; + } + + // Song end marker + if (cmd == 0xFC) { + end(); + return 0; + } + + // Global command + if (cmd == 0xF0) { + _playPos++; + + byte type1 = *_playPos++; + byte type2 = *_playPos++; + + if ((type1 == 0x7F) && (type2 == 0)) { + // Tempo change, as a fraction of the base tempo + + uint32 num = *_playPos++; + uint32 denom = *_playPos++; + + _tempo = _baseTempo * num + ((_baseTempo * denom) >> 7); + + _playPos++; + } else { + + // Unsupported global command, skip it + _playPos -= 2; + while(*_playPos++ != 0xF7) + ; + } + + delay = *_playPos++; + break; + } + + // Voice command + + if (cmd >= 0x80) { + _playPos++; + + _lastCommand = cmd; + } else + cmd = _lastCommand; + + uint8 voice = cmd & 0x0F; + uint8 note, volume; + uint16 pitch; + + switch (cmd & 0xF0) { + case 0x80: // Note off + _playPos += 2; + noteOff(voice); + break; + + case 0x90: // Note on + note = *_playPos++; + volume = *_playPos++; + + if (volume) { + setVoiceVolume(voice, volume); + noteOn(voice, note); + } else + noteOff(voice); + break; + + case 0xA0: // Set volume + setVoiceVolume(voice, *_playPos++); + break; + + case 0xB0: + _playPos += 2; + break; + + case 0xC0: // Set instrument + setInstrument(voice, *_playPos++); + break; + + case 0xD0: + _playPos++; + break; + + case 0xE0: // Pitch bend + pitch = *_playPos++; + pitch += *_playPos++ << 7; + bendVoicePitch(voice, pitch); + break; + + default: + warning("MUSPlayer: Unsupported command: 0x%02X", cmd); + skipToTiming(); + break; + } + + delay = *_playPos++; + } + + if (delay == 0xF8) { + delay = 240; + + if (*_playPos != 0xF8) + delay += *_playPos++; + } + + return getSampleDelay(delay); +} + +void MUSPlayer::rewind() { + _playPos = _songData; + _tempo = _baseTempo; + + _lastCommand = 0; + + setPercussionMode(_soundMode != 0); + setPitchRange(_pitchBendRange); +} + +bool MUSPlayer::loadSND(Common::SeekableReadStream &snd) { + unloadSND(); + + int timbreCount, timbrePos; + if (!readSNDHeader(snd, timbreCount, timbrePos)) + return false; + + if (!readSNDTimbres(snd, timbreCount, timbrePos) || snd.err()) { + unloadSND(); + return false; + } + + return true; +} + +bool MUSPlayer::readString(Common::SeekableReadStream &stream, Common::String &string, byte *buffer, uint size) { + if (stream.read(buffer, size) != size) + return false; + + buffer[size] = '\0'; + + string = (char *) buffer; + + return true; +} + +bool MUSPlayer::readSNDHeader(Common::SeekableReadStream &snd, int &timbreCount, int &timbrePos) { + // Sanity check + if (snd.size() <= 6) { + warning("MUSPlayer::readSNDHeader(): File too small (%d)", snd.size()); + return false; + } + + // Version + const uint8 versionMajor = snd.readByte(); + const uint8 versionMinor = snd.readByte(); + + if ((versionMajor != 1) && (versionMinor != 0)) { + warning("MUSPlayer::readSNDHeader(): Unsupported version %d.%d", versionMajor, versionMinor); + return false; + } + + // Number of timbres and where they start + timbreCount = snd.readUint16LE(); + timbrePos = snd.readUint16LE(); + + const uint16 minTimbrePos = 6 + timbreCount * 9; + + // Sanity check + if (timbrePos < minTimbrePos) { + warning("MUSPlayer::readSNDHeader(): Timbre offset too small: %d < %d", timbrePos, minTimbrePos); + return false; + } + + const uint32 timbreParametersSize = snd.size() - timbrePos; + const uint32 paramSize = kOperatorsPerVoice * kParamCount * sizeof(uint16); + + // Sanity check + if (timbreParametersSize != (timbreCount * paramSize)) { + warning("MUSPlayer::loadSND(): Timbre parameters size mismatch: %d != %d", + timbreParametersSize, timbreCount * paramSize); + return false; + } + + return true; +} + +bool MUSPlayer::readSNDTimbres(Common::SeekableReadStream &snd, int timbreCount, int timbrePos) { + _timbres.resize(timbreCount); + + // Read names + byte nameBuffer[10]; + for (Common::Array<Timbre>::iterator t = _timbres.begin(); t != _timbres.end(); ++t) { + if (!readString(snd, t->name, nameBuffer, 9)) { + warning("MUSPlayer::readMUSTimbres(): Failed to read timbre name"); + return false; + } + } + + if (!snd.seek(timbrePos)) { + warning("MUSPlayer::readMUSTimbres(): Failed to seek to timbres"); + return false; + } + + // Read parameters + for (Common::Array<Timbre>::iterator t = _timbres.begin(); t != _timbres.end(); ++t) { + for (int i = 0; i < (kOperatorsPerVoice * kParamCount); i++) + t->params[i] = snd.readUint16LE(); + } + + return true; +} + +bool MUSPlayer::loadMUS(Common::SeekableReadStream &mus) { + unloadMUS(); + + if (!readMUSHeader(mus) || !readMUSSong(mus) || mus.err()) { + unloadMUS(); + return false; + } + + rewind(); + + return true; +} + +bool MUSPlayer::readMUSHeader(Common::SeekableReadStream &mus) { + // Sanity check + if (mus.size() <= 6) + return false; + + // Version + const uint8 versionMajor = mus.readByte(); + const uint8 versionMinor = mus.readByte(); + + if ((versionMajor != 1) && (versionMinor != 0)) { + warning("MUSPlayer::readMUSHeader(): Unsupported version %d.%d", versionMajor, versionMinor); + return false; + } + + _songID = mus.readUint32LE(); + + byte nameBuffer[31]; + if (!readString(mus, _songName, nameBuffer, 30)) { + warning("MUSPlayer::readMUSHeader(): Failed to read the song name"); + return false; + } + + _ticksPerBeat = mus.readByte(); + _beatsPerMeasure = mus.readByte(); + + mus.skip(4); // Length of song in ticks + + _songDataSize = mus.readUint32LE(); + + mus.skip(4); // Number of commands + mus.skip(8); // Unused + + _soundMode = mus.readByte(); + _pitchBendRange = mus.readByte(); + _baseTempo = mus.readUint16LE(); + + mus.skip(8); // Unused + + return true; +} + +bool MUSPlayer::readMUSSong(Common::SeekableReadStream &mus) { + const uint32 realSongDataSize = mus.size() - mus.pos(); + + if (realSongDataSize < _songDataSize) { + warning("MUSPlayer::readMUSSong(): File too small for the song data: %d < %d", realSongDataSize, _songDataSize); + return false; + } + + _songData = new byte[_songDataSize]; + + if (mus.read(_songData, _songDataSize) != _songDataSize) { + warning("MUSPlayer::readMUSSong(): Read failed"); + return false; + } + + return true; +} + +void MUSPlayer::unloadSND() { + _timbres.clear(); +} + +void MUSPlayer::unloadMUS() { + delete[] _songData; + + _songData = 0; + _songDataSize = 0; + + _playPos = 0; +} + +uint32 MUSPlayer::getSongID() const { + return _songID; +} + +const Common::String &MUSPlayer::getSongName() const { + return _songName; +} + +void MUSPlayer::setInstrument(uint8 voice, uint8 instrument) { + if (instrument >= _timbres.size()) + return; + + setVoiceTimbre(voice, _timbres[instrument].params); +} + +} // End of namespace Gob diff --git a/engines/gob/sound/musplayer.h b/engines/gob/sound/musplayer.h new file mode 100644 index 0000000000..6cc2a2d2ca --- /dev/null +++ b/engines/gob/sound/musplayer.h @@ -0,0 +1,109 @@ +/* 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 GOB_SOUND_MUSPLAYER_H +#define GOB_SOUND_MUSPLAYER_H + +#include "common/str.h" +#include "common/array.h" + +#include "gob/sound/adlib.h" + +namespace Common { + class SeekableReadStream; +} + +namespace Gob { + +/** A player for the AdLib MUS format, with the instrument information in SND files. + * + * In the Gob engine, those files are usually named .MDY and .TBR instead. + */ +class MUSPlayer : public AdLib { +public: + MUSPlayer(Audio::Mixer &mixer); + ~MUSPlayer(); + + /** Load the instruments (.SND or .TBR) */ + bool loadSND(Common::SeekableReadStream &snd); + /** Load the melody (.MUS or .MDY) */ + bool loadMUS(Common::SeekableReadStream &mus); + + void unload(); + + uint32 getSongID() const; + const Common::String &getSongName() const; + +protected: + // AdLib interface + uint32 pollMusic(bool first); + void rewind(); + +private: + struct Timbre { + Common::String name; + + uint16 params[kOperatorsPerVoice * kParamCount]; + }; + + Common::Array<Timbre> _timbres; + + byte *_songData; + uint32 _songDataSize; + + const byte *_playPos; + + uint32 _songID; + Common::String _songName; + + uint8 _ticksPerBeat; + uint8 _beatsPerMeasure; + + uint8 _soundMode; + uint8 _pitchBendRange; + + uint16 _baseTempo; + + uint16 _tempo; + + byte _lastCommand; + + + void unloadSND(); + void unloadMUS(); + + bool readSNDHeader (Common::SeekableReadStream &snd, int &timbreCount, int &timbrePos); + bool readSNDTimbres(Common::SeekableReadStream &snd, int timbreCount, int timbrePos); + + bool readMUSHeader(Common::SeekableReadStream &mus); + bool readMUSSong (Common::SeekableReadStream &mus); + + uint32 getSampleDelay(uint16 delay) const; + void setInstrument(uint8 voice, uint8 instrument); + void skipToTiming(); + + static bool readString(Common::SeekableReadStream &stream, Common::String &string, byte *buffer, uint size); +}; + +} // End of namespace Gob + +#endif // GOB_SOUND_MUSPLAYER_H diff --git a/engines/gob/sound/sound.cpp b/engines/gob/sound/sound.cpp index bfe0394390..184e14a2e6 100644 --- a/engines/gob/sound/sound.cpp +++ b/engines/gob/sound/sound.cpp @@ -30,7 +30,8 @@ #include "gob/sound/pcspeaker.h" #include "gob/sound/soundblaster.h" -#include "gob/sound/adlib.h" +#include "gob/sound/adlplayer.h" +#include "gob/sound/musplayer.h" #include "gob/sound/infogrames.h" #include "gob/sound/protracker.h" #include "gob/sound/cdrom.h" @@ -50,6 +51,8 @@ Sound::Sound(GobEngine *vm) : _vm(vm) { _hasAdLib = (!_vm->_noMusic && _vm->hasAdLib()); + _hasAdLibBg = _hasAdLib; + if (!_vm->_noMusic && (_vm->getPlatform() == Common::kPlatformAmiga)) { _infogrames = new Infogrames(*_vm->_mixer); _protracker = new Protracker(*_vm->_mixer); @@ -131,10 +134,7 @@ void Sound::sampleFree(SoundDesc *sndDesc, bool noteAdLib, int index) { if (noteAdLib) { if (_adlPlayer) if ((index == -1) || (_adlPlayer->getIndex() == index)) - _adlPlayer->stopPlay(); - if (_mdyPlayer) - if ((index == -1) || (_mdyPlayer->getIndex() == index)) - _mdyPlayer->stopPlay(); + _adlPlayer->unload(); } } else { @@ -235,7 +235,17 @@ bool Sound::adlibLoadADL(const char *fileName) { debugC(1, kDebugSound, "AdLib: Loading ADL data (\"%s\")", fileName); - return _adlPlayer->load(fileName); + Common::SeekableReadStream *stream = _vm->_dataIO->getFile(fileName); + if (!stream) { + warning("Can't open ADL file \"%s\"", fileName); + return false; + } + + bool loaded = _adlPlayer->load(*stream); + + delete stream; + + return loaded; } bool Sound::adlibLoadADL(byte *data, uint32 size, int index) { @@ -266,8 +276,7 @@ bool Sound::adlibLoadMDY(const char *fileName) { if (!_hasAdLib) return false; - if (!_mdyPlayer) - _mdyPlayer = new MDYPlayer(*_vm->_mixer); + createMDYPlayer(); debugC(1, kDebugSound, "AdLib: Loading MDY data (\"%s\")", fileName); @@ -277,7 +286,7 @@ bool Sound::adlibLoadMDY(const char *fileName) { return false; } - bool loaded = _mdyPlayer->loadMDY(*stream); + bool loaded = _mdyPlayer->loadMUS(*stream); delete stream; @@ -288,8 +297,7 @@ bool Sound::adlibLoadTBR(const char *fileName) { if (!_hasAdLib) return false; - if (!_mdyPlayer) - _mdyPlayer = new MDYPlayer(*_vm->_mixer); + createMDYPlayer(); Common::SeekableReadStream *stream = _vm->_dataIO->getFile(fileName); if (!stream) { @@ -299,7 +307,7 @@ bool Sound::adlibLoadTBR(const char *fileName) { debugC(1, kDebugSound, "AdLib: Loading MDY instruments (\"%s\")", fileName); - bool loaded = _mdyPlayer->loadTBR(*stream); + bool loaded = _mdyPlayer->loadSND(*stream); delete stream; @@ -310,28 +318,23 @@ void Sound::adlibPlayTrack(const char *trackname) { if (!_hasAdLib) return; - if (!_adlPlayer) - _adlPlayer = new ADLPlayer(*_vm->_mixer); + createADLPlayer(); if (_adlPlayer->isPlaying()) return; - debugC(1, kDebugSound, "AdLib: Playing ADL track \"%s\"", trackname); - - _adlPlayer->unload(); - _adlPlayer->load(trackname); - _adlPlayer->startPlay(); + if (adlibLoadADL(trackname)) + adlibPlay(); } void Sound::adlibPlayBgMusic() { - if (!_hasAdLib) + if (!_hasAdLib || _hasAdLibBg) return; - if (!_adlPlayer) - _adlPlayer = new ADLPlayer(*_vm->_mixer); + createADLPlayer(); static const char *const tracksMac[] = { -// "musmac1.adl", // TODO: This track isn't played correctly at all yet +// "musmac1.adl", // This track seems to be missing instruments... "musmac2.adl", "musmac3.adl", "musmac4.adl", @@ -347,13 +350,18 @@ void Sound::adlibPlayBgMusic() { "musmac5.mid" }; - if (_vm->getPlatform() == Common::kPlatformWindows) { - int track = _vm->_util->getRandom(ARRAYSIZE(tracksWin)); - adlibPlayTrack(tracksWin[track]); - } else { - int track = _vm->_util->getRandom(ARRAYSIZE(tracksMac)); - adlibPlayTrack(tracksMac[track]); + const char *track = 0; + if (_vm->getPlatform() == Common::kPlatformWindows) + track = tracksWin[ARRAYSIZE(tracksWin)]; + else + track = tracksMac[_vm->_util->getRandom(ARRAYSIZE(tracksMac))]; + + if (!track || !_vm->_dataIO->hasFile(track)) { + _hasAdLibBg = false; + return; } + + adlibPlayTrack(track); } void Sound::adlibPlay() { @@ -398,13 +406,11 @@ int Sound::adlibGetIndex() const { if (_adlPlayer) return _adlPlayer->getIndex(); - if (_mdyPlayer) - return _mdyPlayer->getIndex(); return -1; } -bool Sound::adlibGetRepeating() const { +int32 Sound::adlibGetRepeating() const { if (!_hasAdLib) return false; @@ -439,6 +445,10 @@ void Sound::blasterPlay(SoundDesc *sndDesc, int16 repCount, _blaster->playSample(*sndDesc, repCount, frequency, fadeLength); } +void Sound::blasterRepeatComposition(int32 repCount) { + _blaster->repeatComposition(repCount); +} + void Sound::blasterStop(int16 fadeLength, SoundDesc *sndDesc) { if (!_blaster) return; @@ -719,4 +729,24 @@ void Sound::bgUnshade() { _bgatmos->unshade(); } +void Sound::createMDYPlayer() { + if (_mdyPlayer) + return; + + delete _adlPlayer; + _adlPlayer = 0; + + _mdyPlayer = new MUSPlayer(*_vm->_mixer); +} + +void Sound::createADLPlayer() { + if (_adlPlayer) + return; + + delete _mdyPlayer; + _mdyPlayer= 0; + + _adlPlayer = new ADLPlayer(*_vm->_mixer); +} + } // End of namespace Gob diff --git a/engines/gob/sound/sound.h b/engines/gob/sound/sound.h index 585cf36703..6ad0ec5483 100644 --- a/engines/gob/sound/sound.h +++ b/engines/gob/sound/sound.h @@ -32,7 +32,7 @@ class GobEngine; class PCSpeaker; class SoundBlaster; class ADLPlayer; -class MDYPlayer; +class MUSPlayer; class Infogrames; class Protracker; class CDROM; @@ -63,6 +63,7 @@ public: void blasterPlayComposition(int16 *composition, int16 freqVal, SoundDesc *sndDescs = 0, int8 sndCount = kSoundsCount); void blasterStopComposition(); + void blasterRepeatComposition(int32 repCount); char blasterPlayingSound() const; @@ -92,7 +93,7 @@ public: bool adlibIsPlaying() const; int adlibGetIndex() const; - bool adlibGetRepeating() const; + int32 adlibGetRepeating() const; void adlibSetRepeating(int32 repCount); @@ -142,17 +143,30 @@ private: GobEngine *_vm; bool _hasAdLib; + bool _hasAdLibBg; SoundDesc _sounds[kSoundsCount]; + // Speaker PCSpeaker *_pcspeaker; + + // PCM based SoundBlaster *_blaster; + BackgroundAtmosphere *_bgatmos; + + // AdLib + MUSPlayer *_mdyPlayer; ADLPlayer *_adlPlayer; - MDYPlayer *_mdyPlayer; + + // Amiga Paula Infogrames *_infogrames; Protracker *_protracker; + + // Audio CD CDROM *_cdrom; - BackgroundAtmosphere *_bgatmos; + + void createMDYPlayer(); + void createADLPlayer(); }; } // End of namespace Gob diff --git a/engines/gob/sound/soundblaster.cpp b/engines/gob/sound/soundblaster.cpp index 4ff555b0e3..915d744494 100644 --- a/engines/gob/sound/soundblaster.cpp +++ b/engines/gob/sound/soundblaster.cpp @@ -31,6 +31,8 @@ SoundBlaster::SoundBlaster(Audio::Mixer &mixer) : SoundMixer(mixer, Audio::Mixer _compositionSamples = 0; _compositionSampleCount = 0; _compositionPos = -1; + + _compositionRepCount = 0; } SoundBlaster::~SoundBlaster() { @@ -79,6 +81,7 @@ void SoundBlaster::nextCompositionPos() { if (_compositionPos == 49) _compositionPos = -1; } + _compositionPos = -1; } @@ -98,6 +101,10 @@ void SoundBlaster::playComposition(int16 *composition, int16 freqVal, nextCompositionPos(); } +void SoundBlaster::repeatComposition(int32 repCount) { + _compositionRepCount = repCount; +} + void SoundBlaster::setSample(SoundDesc &sndDesc, int16 repCount, int16 frequency, int16 fadeLength) { @@ -106,10 +113,21 @@ void SoundBlaster::setSample(SoundDesc &sndDesc, int16 repCount, int16 frequency } void SoundBlaster::checkEndSample() { - if (_compositionPos != -1) + if (_compositionPos != -1) { + nextCompositionPos(); + return; + } + + if (_compositionRepCount != 0) { + if (_compositionRepCount > 0) + _compositionRepCount--; + nextCompositionPos(); - else - SoundMixer::checkEndSample(); + if (_compositionPos != -1) + return; + } + + SoundMixer::checkEndSample(); } void SoundBlaster::endFade() { diff --git a/engines/gob/sound/soundblaster.h b/engines/gob/sound/soundblaster.h index c2704c5482..c740ba2269 100644 --- a/engines/gob/sound/soundblaster.h +++ b/engines/gob/sound/soundblaster.h @@ -46,6 +46,8 @@ public: void stopComposition(); void endComposition(); + void repeatComposition(int32 repCount); + protected: Common::Mutex _mutex; @@ -54,6 +56,8 @@ protected: int16 _composition[50]; int8 _compositionPos; + int32 _compositionRepCount; + SoundDesc *_curSoundDesc; void setSample(SoundDesc &sndDesc, int16 repCount, diff --git a/engines/gob/surface.cpp b/engines/gob/surface.cpp index e294209ed7..3af19f891d 100644 --- a/engines/gob/surface.cpp +++ b/engines/gob/surface.cpp @@ -280,6 +280,18 @@ Surface::Surface(uint16 width, uint16 height, uint8 bpp, byte *vidMem) : _ownVidMem = false; } +Surface::Surface(uint16 width, uint16 height, uint8 bpp, const byte *vidMem) : + _width(width), _height(height), _bpp(bpp), _vidMem(0) { + + assert((_width > 0) && (_height > 0)); + assert((_bpp == 1) || (_bpp == 2)); + + _vidMem = new byte[_bpp * _width * _height]; + _ownVidMem = true; + + memcpy(_vidMem, vidMem, _bpp * _width * _height); +} + Surface::~Surface() { if (_ownVidMem) delete[] _vidMem; diff --git a/engines/gob/surface.h b/engines/gob/surface.h index 866e63490f..5376603801 100644 --- a/engines/gob/surface.h +++ b/engines/gob/surface.h @@ -122,6 +122,7 @@ private: class Surface { public: Surface(uint16 width, uint16 height, uint8 bpp, byte *vidMem = 0); + Surface(uint16 width, uint16 height, uint8 bpp, const byte *vidMem); ~Surface(); uint16 getWidth () const; diff --git a/engines/gob/util.cpp b/engines/gob/util.cpp index 7f9c6131fd..64dfcf9b12 100644 --- a/engines/gob/util.cpp +++ b/engines/gob/util.cpp @@ -21,7 +21,6 @@ */ #include "common/stream.h" -#include "common/events.h" #include "graphics/palette.h" @@ -45,6 +44,8 @@ Util::Util(GobEngine *vm) : _vm(vm) { _frameRate = 12; _frameWaitTime = 0; _startFrameTime = 0; + + _keyState = 0; } uint32 Util::getTimeKey() { @@ -116,6 +117,8 @@ void Util::processInput(bool scroll) { _mouseButtons = (MouseButtons) (((uint32) _mouseButtons) & ~((uint32) kMouseButtonsRight)); break; case Common::EVENT_KEYDOWN: + keyDown(event); + if (event.kbd.hasFlags(Common::KBD_CTRL)) { if (event.kbd.keycode == Common::KEYCODE_f) _fastMode ^= 1; @@ -132,6 +135,7 @@ void Util::processInput(bool scroll) { addKeyToBuffer(event.kbd); break; case Common::EVENT_KEYUP: + keyUp(event); break; default: break; @@ -576,4 +580,38 @@ void Util::checkJoystick() { _vm->_global->_useJoystick = 0; } +uint32 Util::getKeyState() const { + return _keyState; +} + +void Util::keyDown(const Common::Event &event) { + if (event.kbd.keycode == Common::KEYCODE_UP) + _keyState |= 0x0001; + else if (event.kbd.keycode == Common::KEYCODE_DOWN) + _keyState |= 0x0002; + else if (event.kbd.keycode == Common::KEYCODE_RIGHT) + _keyState |= 0x0004; + else if (event.kbd.keycode == Common::KEYCODE_LEFT) + _keyState |= 0x0008; + else if (event.kbd.keycode == Common::KEYCODE_SPACE) + _keyState |= 0x0020; + else if (event.kbd.keycode == Common::KEYCODE_ESCAPE) + _keyState |= 0x0040; +} + +void Util::keyUp(const Common::Event &event) { + if (event.kbd.keycode == Common::KEYCODE_UP) + _keyState &= ~0x0001; + else if (event.kbd.keycode == Common::KEYCODE_DOWN) + _keyState &= ~0x0002; + else if (event.kbd.keycode == Common::KEYCODE_RIGHT) + _keyState &= ~0x0004; + else if (event.kbd.keycode == Common::KEYCODE_LEFT) + _keyState &= ~0x0008; + else if (event.kbd.keycode == Common::KEYCODE_SPACE) + _keyState &= ~0x0020; + else if (event.kbd.keycode == Common::KEYCODE_ESCAPE) + _keyState &= ~0x0040; +} + } // End of namespace Gob diff --git a/engines/gob/util.h b/engines/gob/util.h index 4228dac768..b26a78ab2c 100644 --- a/engines/gob/util.h +++ b/engines/gob/util.h @@ -25,6 +25,7 @@ #include "common/str.h" #include "common/keyboard.h" +#include "common/events.h" namespace Common { class SeekableReadStream; @@ -110,6 +111,8 @@ public: bool checkKey(int16 &key); bool keyPressed(); + uint32 getKeyState() const; + void getMouseState(int16 *pX, int16 *pY, MouseButtons *pButtons); void setMousePos(int16 x, int16 y); void waitMouseUp(); @@ -155,6 +158,8 @@ protected: int16 _frameWaitTime; uint32 _startFrameTime; + uint32 _keyState; + GobEngine *_vm; bool keyBufferEmpty(); @@ -162,6 +167,9 @@ protected: bool getKeyFromBuffer(Common::KeyState &key); int16 translateKey(const Common::KeyState &key); void checkJoystick(); + + void keyDown(const Common::Event &event); + void keyUp(const Common::Event &event); }; } // End of namespace Gob diff --git a/engines/gob/video.cpp b/engines/gob/video.cpp index ee5ff4abff..3b1c6423bb 100644 --- a/engines/gob/video.cpp +++ b/engines/gob/video.cpp @@ -25,7 +25,6 @@ #include "engines/util.h" #include "graphics/cursorman.h" -#include "graphics/fontman.h" #include "graphics/palette.h" #include "graphics/surface.h" @@ -226,10 +225,7 @@ void Video::setSize(bool defaultTo1XScaler) { void Video::retrace(bool mouse) { if (mouse) - if ((_vm->getGameType() != kGameTypeAdibou2) && - (_vm->getGameType() != kGameTypeAdi2) && - (_vm->getGameType() != kGameTypeAdi4)) - CursorMan.showMouse((_vm->_draw->_showCursor & 2) != 0); + CursorMan.showMouse((_vm->_draw->_showCursor & 2) != 0); if (_vm->_global->_primarySurfDesc) { int screenX = _screenDeltaX; diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp index 221f5ab3c9..a478492ccc 100644 --- a/engines/gob/videoplayer.cpp +++ b/engines/gob/videoplayer.cpp @@ -234,6 +234,23 @@ void VideoPlayer::closeAll() { closeVideo(i); } +bool VideoPlayer::reopenVideo(int slot) { + Video *video = getVideoBySlot(slot); + if (!video) + return true; + + return reopenVideo(*video); +} + +bool VideoPlayer::reopenAll() { + bool all = true; + for (int i = 0; i < kVideoSlotCount; i++) + if (!reopenVideo(i)) + all = false; + + return all; +} + void VideoPlayer::pauseVideo(int slot, bool pause) { Video *video = getVideoBySlot(slot); if (!video || !video->decoder) @@ -850,6 +867,39 @@ Common::String VideoPlayer::findFile(const Common::String &file, Properties &pro return video; } +bool VideoPlayer::reopenVideo(Video &video) { + if (video.isEmpty()) + return true; + + if (video.fileName.empty()) { + video.close(); + return false; + } + + Properties properties; + + properties.type = video.properties.type; + + Common::String fileName = findFile(video.fileName, properties); + if (fileName.empty()) { + video.close(); + return false; + } + + Common::SeekableReadStream *stream = _vm->_dataIO->getFile(fileName); + if (!stream) { + video.close(); + return false; + } + + if (!video.decoder->reloadStream(stream)) { + delete stream; + return false; + } + + return true; +} + void VideoPlayer::copyPalette(const Video &video, int16 palStart, int16 palEnd) { if (!video.decoder->hasPalette() || !video.decoder->isPaletted()) return; diff --git a/engines/gob/videoplayer.h b/engines/gob/videoplayer.h index bc7cb48768..129ccef67a 100644 --- a/engines/gob/videoplayer.h +++ b/engines/gob/videoplayer.h @@ -110,6 +110,9 @@ public: void closeLiveSound(); void closeAll(); + bool reopenVideo(int slot = 0); + bool reopenAll(); + void pauseVideo(int slot, bool pause); void pauseAll(bool pause); @@ -163,6 +166,8 @@ private: bool isEmpty() const; void close(); + + void reopen(); }; static const int kVideoSlotCount = 32; @@ -188,6 +193,8 @@ private: ::Video::CoktelDecoder *openVideo(const Common::String &file, Properties &properties); + bool reopenVideo(Video &video); + bool playFrame(int slot, Properties &properties); void checkAbort(Video &video, Properties &properties); |