diff options
Diffstat (limited to 'engines/gnap/gnap.cpp')
-rw-r--r-- | engines/gnap/gnap.cpp | 1239 |
1 files changed, 1239 insertions, 0 deletions
diff --git a/engines/gnap/gnap.cpp b/engines/gnap/gnap.cpp new file mode 100644 index 0000000000..6a03bf8eb0 --- /dev/null +++ b/engines/gnap/gnap.cpp @@ -0,0 +1,1239 @@ +/* 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 "graphics/cursorman.h" +#include "gnap/gnap.h" +#include "gnap/datarchive.h" +#include "gnap/gamesys.h" +#include "gnap/resource.h" +#include "gnap/sound.h" + +#include "common/config-manager.h" +#include "common/debug-channels.h" +#include "common/timer.h" + +#include "engines/util.h" + +namespace Gnap { + +static const int kCursors[] = { + LOOK_CURSOR, + GRAB_CURSOR, + TALK_CURSOR, + PLAT_CURSOR +}; + +static const int kDisabledCursors[] = { + NOLOOK_CURSOR, + NOGRAB_CURSOR, + NOTALK_CURSOR, + NOPLAT_CURSOR +}; + +static const char *kCursorNames[] = { + "LOOK_CURSOR", + "GRAB_CURSOR", + "TALK_CURSOR", + "PLAT_CURSOR", + "NOLOOK_CURSOR", + "NOGRAB_CURSOR", + "NOTALK_CURSOR", + "NOPLAT_CURSOR", + "EXIT_L_CURSOR", + "EXIT_R_CURSOR", + "EXIT_U_CURSOR", + "EXIT_D_CURSOR", + "EXIT_NE_CURSOR", + "EXIT_NW_CURSOR", + "EXIT_SE_CURSOR", + "EXIT_SW_CURSOR", + "WAIT_CURSOR" +}; + + +static const int kCursorSpriteIds[30] = { + 0x005, 0x008, 0x00A, 0x004, 0x009, 0x003, + 0x006, 0x007, 0x00D, 0x00F, 0x00B, 0x00C, + 0x019, 0x01C, 0x015, 0x014, 0x010, 0x01A, + 0x018, 0x013, 0x011, 0x012, 0x01B, 0x016, + 0x017, 0x01D, 0x01E, 0x01F, 0x76A, 0x76B +}; + +static const char *kSceneNames[] = { + "open", "pigpn", "truck", "creek", "mafrm", "frbrn", "inbrn", "crash", + "porch", "barbk", "kitch", "bar", "juke", "wash", "john", "jkbox", + "brawl", "stret", "frtoy", "intoy", "frgro", "park", "cash", "ingro", + "frcir", "booth", "circ", "outcl", "incln", "monk", "elcir", "beer", + "pig2", "trk2", "creek", "frbrn", "inbrn", "mafrm", "infrm", "efair", + "fair", "souv", "chick", "ship", "kiss", "disco", "boot", "can", + "can2", "drive", "tung", "puss", "space", "phone", "can3" +}; + +GnapEngine::GnapEngine(OSystem *syst, const ADGameDescription *gd) : + Engine(syst), _gameDescription(gd) { + + _random = new Common::RandomSource("gnap"); + DebugMan.addDebugChannel(kDebugBasic, "basic", "Basic debug level"); + + Engine::syncSoundSettings(); + + _exe = nullptr; + _dat = nullptr; + _spriteCache = nullptr; + _soundCache = nullptr; + _sequenceCache = nullptr; + _gameSys = nullptr; + _soundMan = nullptr; + _debugger = nullptr; + _gnap = nullptr; + _plat = nullptr; + _font = nullptr; + _scene = nullptr; + _music = nullptr; + _tempThumbnail = nullptr; + _menuBackgroundSurface = nullptr; + _menuQuitQuerySprite = nullptr; + _largeSprite = nullptr; + _menuSaveLoadSprite = nullptr; + _menuSprite2 = nullptr; + _menuSprite1 = nullptr; + _spriteHandle = nullptr; + _cursorSprite = nullptr; + _pauseSprite = nullptr; + _backgroundSurface = nullptr; + + _wasSavegameLoaded = false; + _isWaiting = false; + _sceneWaiting = false; + _menuDone = false; + _sceneDone = false; + _isLeavingScene = false; + _isStockDatLoaded = false; + _gameDone = false; + _isPaused = false; + _sceneSavegameLoaded = false; + + for (int i = 0; i < kMaxTimers; ++i) + _savedTimers[i] = _timers[i] = 0; + + _mousePos = Common::Point(0, 0); + _currGrabCursorX = _currGrabCursorY = 0; + + _idleTimerIndex = -1; + _menuStatus = 0; + _menuSpritesIndex = -1; + _savegameIndex = -1; + _gridMinX = 0; + _gridMinY = 0; + _gridMaxX = 0; + _gridMaxY = 0; + _toyUfoNextSequenceId = -1; + _toyUfoSequenceId = -1; + _toyUfoId = -1; + _toyUfoActionStatus = -1; + _toyUfoX = 0; + _toyUfoY = 0; + _s18GarbageCanPos = 0; + + for (int i = 0; i < 7; i++) + _savegameSprites[i] = nullptr; + for (int i = 0; i < 30; i++) + _menuInventorySprites[i] = nullptr; + + _newSceneNum = 0; + _inventory = 0; + _gameFlags = 0; + _hotspotsCount = 0; + _sceneClickedHotspot = -1; + _newCursorValue = 0; + _cursorValue = 0; + _verbCursor = 0; + _cursorIndex = -1; + _leftClickMouseX = 0; + _leftClickMouseY = 0; + _grabCursorSprite = nullptr; + _grabCursorSpriteIndex = 0; + _newGrabCursorSpriteIndex = 0; + _fullScreenSprite = nullptr; + _fullScreenSpriteId = 0; + _deviceX1 = 0; + _deviceY1 = 0; + _soundTimerIndexA = 0; + _soundTimerIndexB = 0; + _soundTimerIndexC = 0; + _loadGameSlot = -1; + _lastUpdateClock = 0; + _prevSceneNum = -1; + _currentSceneNum = -1; +} + +GnapEngine::~GnapEngine() { + delete _random; + delete _music; + delete _tempThumbnail; +} + +Common::Error GnapEngine::run() { + // Initialize the graphics mode to RGBA8888 +#if defined(SCUMM_BIG_ENDIAN) + Graphics::PixelFormat format = Graphics::PixelFormat(4, 8, 8, 8, 8, 0, 8, 16, 24); +#else + Graphics::PixelFormat format = Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0); +#endif + initGraphics(800, 600, true, &format); + + // We do not support color conversion yet + if (_system->getScreenFormat() != format) + return Common::kUnsupportedColorMode; + + _lastUpdateClock = 0; + + // >>>>> Variable initialization + _cursorIndex = -1; + _verbCursor = 1; + + if (ConfMan.hasKey("save_slot")) + _loadGameSlot = ConfMan.getInt("save_slot"); + + invClear(); + clearFlags(); + + _grabCursorSprite = nullptr; + _newGrabCursorSpriteIndex = -1; + _backgroundSurface = nullptr; + _isStockDatLoaded = false; + _gameDone = false; + _isPaused = false; + _pauseSprite = nullptr; + + //////////////////////////////////////////////////////////////////////////// + + _exe = new Common::PEResources(); + if (!_exe->loadFromEXE("ufos.exe")) + error("Could not load ufos.exe"); + +#ifdef USE_FREETYPE2 + Common::SeekableReadStream *stream = _exe->getResource(Common::kPEFont, 2000); + _font = Graphics::loadTTFFont(*stream, 24); + if (!_font) + warning("Unable to load font"); + delete stream; +#else + _font = nullptr; +#endif + + _dat = new DatManager(); + _spriteCache = new SpriteCache(_dat); + _soundCache = new SoundCache(_dat); + _sequenceCache = new SequenceCache(_dat); + _gameSys = new GameSys(this); + _soundMan = new SoundMan(this); + _debugger = new Debugger(this); + _gnap = new PlayerGnap(this); + _plat = new PlayerPlat(this); + + _menuBackgroundSurface = nullptr; + + initGlobalSceneVars(); + mainLoop(); + + delete _plat; + delete _gnap; + delete _soundMan; + delete _gameSys; + delete _sequenceCache; + delete _soundCache; + delete _spriteCache; + delete _dat; + delete _debugger; + delete _font; + delete _exe; + + return Common::kNoError; +} + +void GnapEngine::updateEvents() { + Common::Event event; + + while (_eventMan->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_KEYDOWN: + // Check for debugger + if (event.kbd.keycode == Common::KEYCODE_d && (event.kbd.flags & Common::KBD_CTRL)) { + // Attach to the debugger + _debugger->attach(); + _debugger->onFrame(); + } + + _keyPressState[event.kbd.keycode] = true; + _keyDownState[event.kbd.keycode] = true; + break; + case Common::EVENT_KEYUP: + _keyDownState[event.kbd.keycode] = false; + break; + case Common::EVENT_MOUSEMOVE: + _mousePos = event.mouse; + break; + case Common::EVENT_LBUTTONUP: + _mouseButtonState._left = false; + break; + case Common::EVENT_LBUTTONDOWN: + _leftClickMouseX = event.mouse.x; + _leftClickMouseY = event.mouse.y; + _mouseButtonState._left = true; + _mouseClickState._left = true; + break; + case Common::EVENT_RBUTTONUP: + _mouseButtonState._right = false; + break; + case Common::EVENT_RBUTTONDOWN: + _mouseButtonState._right = true; + _mouseClickState._right = true; + break; + case Common::EVENT_QUIT: + quitGame(); + break; + default: + break; + } + } +} + +void GnapEngine::gameUpdateTick() { + updateEvents(); + + if (shouldQuit()) { + _gameDone = true; + _sceneDone = true; + } + + int currClock = _system->getMillis(); + if (currClock >= _lastUpdateClock + 66) { + _gameSys->fatUpdate(); + _gameSys->drawSprites(); + _gameSys->updateScreen(); + _gameSys->updatePlaySounds(); + _gameSys->_gameSysClock++; + updateTimers(); + _lastUpdateClock = currClock; + } + + _soundMan->update(); + _system->updateScreen(); + _system->delayMillis(5); +} + +void GnapEngine::saveTimers() { + for (int i = 0; i < kMaxTimers; ++i ) + _savedTimers[i] = _timers[i]; +} + +void GnapEngine::restoreTimers() { + for (int i = 0; i < kMaxTimers; ++i ) + _timers[i] = _savedTimers[i]; +} + +void GnapEngine::pauseGame() { + if (!_isPaused) { + saveTimers(); + hideCursor(); + setGrabCursorSprite(-1); + _pauseSprite = _gameSys->createSurface(0x1076C); + _gameSys->insertSpriteDrawItem(_pauseSprite, (800 - _pauseSprite->w) / 2, (600 - _pauseSprite->h) / 2, 356); + _lastUpdateClock = 0; + gameUpdateTick(); + playMidi("pause.mid"); + _isPaused = true; + } +} + +void GnapEngine::resumeGame() { + if (_isPaused) { + restoreTimers(); + _gameSys->removeSpriteDrawItem(_pauseSprite, 356); + _lastUpdateClock = 0; + gameUpdateTick(); + deleteSurface(&_pauseSprite); + stopMidi(); + _isPaused = false; + clearAllKeyStatus1(); + _mouseClickState._left = false; + _mouseClickState._right = false; + showCursor(); + _gameSys->_gameSysClock = 0; + _gameSys->_lastUpdateClock = 0; + } +} + +void GnapEngine::updatePause() { + while (_isPaused && !_gameDone) { + gameUpdateTick(); + if (isKeyStatus1(Common::KEYCODE_p)) { + clearKeyStatus1(Common::KEYCODE_p); + resumeGame(); + } + } +} + +int GnapEngine::getRandom(int max) { + return _random->getRandomNumber(max - 1); +} + +int GnapEngine::readSavegameDescription(int savegameNum, Common::String &description) { + description = Common::String::format("Savegame %d", savegameNum); + return 0; +} + +int GnapEngine::loadSavegame(int savegameNum) { + return 1; +} + +void GnapEngine::delayTicks(int val, int idx = 0, bool updateCursor = false) { + int startTick = _timers[idx]; + + _timers[idx] = val; + + while (_timers[idx] && !_gameDone) { + gameUpdateTick(); + + if (updateCursor) + updateGrabCursorSprite(0, 0); + } + + startTick -= _timers[idx]; + if (startTick < 0) + startTick = 0; + + _timers[idx] = startTick; +} + +void GnapEngine::delayTicksA(int val, int idx) { + delayTicks(val, idx); +} + +void GnapEngine::delayTicksCursor(int val) { + delayTicks(val, 0, true); +} + +void GnapEngine::setHotspot(int index, int16 x1, int16 y1, int16 x2, int16 y2, uint16 flags, + int16 walkX, int16 walkY) { + _hotspots[index]._rect = Common::Rect(x1, y1, x2, y2); + _hotspots[index]._flags = flags; + _hotspotsWalkPos[index] = Common::Point(walkX, walkY); +} + +int GnapEngine::getHotspotIndexAtPos(Common::Point pos) { + for (int i = 0; i < _hotspotsCount; ++i) { + if (!_hotspots[i].isFlag(SF_DISABLED) && _hotspots[i].isPointInside(pos)) + return i; + } + return -1; +} + +void GnapEngine::updateCursorByHotspot() { + if (!_isWaiting) { + int hotspotIndex = getHotspotIndexAtPos(_mousePos); + + if (_debugger->_showHotspotNumber) { + // NOTE This causes some display glitches + char t[256]; + sprintf(t, "hotspot = %2d", hotspotIndex); + if (!_font) + _gameSys->fillSurface(nullptr, 10, 10, 80, 16, 0, 0, 0); + else + _gameSys->fillSurface(nullptr, 8, 9, _font->getStringWidth(t) + 10, _font->getFontHeight() + 2, 0, 0, 0); + _gameSys->drawTextToSurface(nullptr, 10, 10, 255, 255, 255, t); + } + + if (hotspotIndex < 0) + setCursor(kDisabledCursors[_verbCursor]); + else if (_hotspots[hotspotIndex]._flags & SF_EXIT_L_CURSOR) + setCursor(EXIT_L_CURSOR); + else if (_hotspots[hotspotIndex]._flags & SF_EXIT_R_CURSOR) + setCursor(EXIT_R_CURSOR); + else if (_hotspots[hotspotIndex]._flags & SF_EXIT_U_CURSOR) + setCursor(EXIT_U_CURSOR); + else if (_hotspots[hotspotIndex]._flags & SF_EXIT_D_CURSOR) + setCursor(EXIT_D_CURSOR); + else if (_hotspots[hotspotIndex]._flags & SF_EXIT_NE_CURSOR) + setCursor(EXIT_NE_CURSOR); + else if (_hotspots[hotspotIndex]._flags & SF_EXIT_NW_CURSOR) + setCursor(EXIT_NW_CURSOR); + else if (_hotspots[hotspotIndex]._flags & SF_EXIT_SE_CURSOR) + setCursor(EXIT_SE_CURSOR); + else if (_hotspots[hotspotIndex]._flags & SF_EXIT_SW_CURSOR) + setCursor(EXIT_SW_CURSOR); + else if (_hotspots[hotspotIndex]._flags & (1 << _verbCursor)) + setCursor(kCursors[_verbCursor]); + else + setCursor(kDisabledCursors[_verbCursor]); + } + // Update platypus hotspot + _hotspots[0]._rect = Common::Rect(_gridMinX + 75 * _plat->_pos.x - 30, _gridMinY + 48 * _plat->_pos.y - 100 + , _gridMinX + 75 * _plat->_pos.x + 30, _gridMinY + 48 * _plat->_pos.y); +} + +int GnapEngine::getClickedHotspotId() { + int result = -1; + if (_isWaiting) + _mouseClickState._left = false; + else if (_mouseClickState._left) { + int hotspotIndex = getHotspotIndexAtPos(Common::Point(_leftClickMouseX, _leftClickMouseY)); + if (hotspotIndex >= 0) { + _mouseClickState._left = false; + _timers[3] = 300; + result = hotspotIndex; + } + } + return result; +} + +int GnapEngine::getInventoryItemSpriteNum(int index) { + return kCursorSpriteIds[index]; +} + +void GnapEngine::updateMouseCursor() { + if (_mouseClickState._right) { + // Switch through the verb cursors + _mouseClickState._right = false; + _timers[3] = 300; + _verbCursor = (_verbCursor + 1) % 4; + if (!isFlag(kGFPlatypus) && _verbCursor == PLAT_CURSOR && _cursorValue == 1) + _verbCursor = (_verbCursor + 1) % 4; + if (!_isWaiting) + setCursor(kDisabledCursors[_verbCursor]); + setGrabCursorSprite(-1); + } + if (_isWaiting && ((_gnap->_actionStatus < 0 && _plat->_actionStatus < 0) || _sceneWaiting)) { + setCursor(kDisabledCursors[_verbCursor]); + showCursor(); + _isWaiting = false; + } else if (!_isWaiting && (_gnap->_actionStatus >= 0 || _plat->_actionStatus >= 0) && !_sceneWaiting) { + setCursor(WAIT_CURSOR); + hideCursor(); + _isWaiting = true; + } +} + +void GnapEngine::setVerbCursor(int verbCursor) { + _verbCursor = verbCursor; + if (!_isWaiting) + setCursor(kDisabledCursors[_verbCursor]); +} + +void GnapEngine::setCursor(int cursorIndex) { + if (_cursorIndex != cursorIndex) { + const char *cursorName = kCursorNames[cursorIndex]; + Graphics::WinCursorGroup *cursorGroup = Graphics::WinCursorGroup::createCursorGroup(*_exe, Common::WinResourceID(cursorName)); + if (cursorGroup) { + Graphics::Cursor *cursor = cursorGroup->cursors[0].cursor; + CursorMan.replaceCursor(cursor->getSurface(), cursor->getWidth(), cursor->getHeight(), + cursor->getHotspotX(), cursor->getHotspotY(), cursor->getKeyColor()); + CursorMan.replaceCursorPalette(cursor->getPalette(), 0, 256); + delete cursorGroup; + } + _cursorIndex = cursorIndex; + } +} + +void GnapEngine::showCursor() { + CursorMan.showMouse(true); +} + +void GnapEngine::hideCursor() { + CursorMan.showMouse(false); +} + +void GnapEngine::setGrabCursorSprite(int index) { + freeGrabCursorSprite(); + if (index >= 0) { + createGrabCursorSprite(makeRid(1, kCursorSpriteIds[index])); + setVerbCursor(GRAB_CURSOR); + } + _grabCursorSpriteIndex = index; +} + +void GnapEngine::createGrabCursorSprite(int spriteId) { + _grabCursorSprite = _gameSys->createSurface(spriteId); + _gameSys->insertSpriteDrawItem(_grabCursorSprite, + _mousePos.x - (_grabCursorSprite->w / 2), + _mousePos.y - (_grabCursorSprite->h / 2), + 300); + delayTicks(5); +} + +void GnapEngine::freeGrabCursorSprite() { + if (_grabCursorSprite) { + _gameSys->removeSpriteDrawItem(_grabCursorSprite, 300); + _gameSys->removeSpriteDrawItem(_grabCursorSprite, 301); + delayTicks(5); + deleteSurface(&_grabCursorSprite); + } +} + +void GnapEngine::updateGrabCursorSprite(int x, int y) { + if (_grabCursorSprite) { + int newGrabCursorX = _mousePos.x - (_grabCursorSprite->w / 2) - x; + int newGrabCursorY = _mousePos.y - (_grabCursorSprite->h / 2) - y; + if (_currGrabCursorX != newGrabCursorX || _currGrabCursorY != newGrabCursorY) { + _currGrabCursorX = newGrabCursorX; + _currGrabCursorY = newGrabCursorY; + Common::Rect rect(newGrabCursorX, newGrabCursorY, + newGrabCursorX + _grabCursorSprite->w, newGrabCursorY + _grabCursorSprite->h); + _gameSys->invalidateGrabCursorSprite(300, rect, _grabCursorSprite, _grabCursorSprite); + } + } +} + +void GnapEngine::invClear() { + _inventory = 0; +} + +void GnapEngine::invAdd(int itemId) { + _inventory |= (1 << itemId); +} + +void GnapEngine::invRemove(int itemId) { + _inventory &= ~(1 << itemId); +} + +bool GnapEngine::invHas(int itemId) { + return (_inventory & (1 << itemId)) != 0; +} + +void GnapEngine::clearFlags() { + _gameFlags = 0; +} + +void GnapEngine::setFlag(int num) { + _gameFlags |= (1 << num); +} + +void GnapEngine::clearFlag(int num) { + _gameFlags &= ~(1 << num); +} + +bool GnapEngine::isFlag(int num) { + return (_gameFlags & (1 << num)) != 0; +} + +Graphics::Surface *GnapEngine::addFullScreenSprite(int resourceId, int id) { + _fullScreenSpriteId = id; + _fullScreenSprite = _gameSys->createSurface(resourceId); + _gameSys->insertSpriteDrawItem(_fullScreenSprite, 0, 0, id); + return _fullScreenSprite; +} + +void GnapEngine::removeFullScreenSprite() { + _gameSys->removeSpriteDrawItem(_fullScreenSprite, _fullScreenSpriteId); + deleteSurface(&_fullScreenSprite); +} + +void GnapEngine::showFullScreenSprite(int resourceId) { + hideCursor(); + setGrabCursorSprite(-1); + addFullScreenSprite(resourceId, 256); + while (!_mouseClickState._left && !isKeyStatus1(Common::KEYCODE_ESCAPE) + && !isKeyStatus1(Common::KEYCODE_SPACE) && !isKeyStatus1(Common::KEYCODE_RETURN) && !_gameDone) { + gameUpdateTick(); + } + _mouseClickState._left = false; + clearKeyStatus1(Common::KEYCODE_ESCAPE); + clearKeyStatus1(Common::KEYCODE_RETURN); + clearKeyStatus1(Common::KEYCODE_SPACE); + removeFullScreenSprite(); + showCursor(); +} + +void GnapEngine::queueInsertDeviceIcon() { + _gameSys->insertSequence(0x10849, 20, 0, 0, kSeqNone, 0, _deviceX1, _deviceY1); +} + +void GnapEngine::insertDeviceIconActive() { + _gameSys->insertSequence(0x1084A, 21, 0, 0, kSeqNone, 0, _deviceX1, _deviceY1); +} + +void GnapEngine::removeDeviceIconActive() { + _gameSys->removeSequence(0x1084A, 21, true); +} + +void GnapEngine::setDeviceHotspot(int hotspotIndex, int x1, int y1, int x2, int y2) { + _deviceX1 = x1; + _deviceY1 = y1; + int deviceX2 = x2; + int deviceY2 = y2; + if (x1 == -1) + _deviceX1 = 730; + if (x2 == -1) + deviceX2 = 780; + if (y1 == -1) + _deviceY1 = 14; + if (y2 == -1) + deviceY2 = 79; + + _hotspots[hotspotIndex]._rect = Common::Rect(_deviceX1, _deviceY1, deviceX2, deviceY2); + _hotspots[hotspotIndex]._flags = SF_TALK_CURSOR | SF_GRAB_CURSOR | SF_LOOK_CURSOR; +} + +int GnapEngine::getSequenceTotalDuration(int resourceId) { + SequenceResource *sequenceResource = _sequenceCache->get(resourceId); + int maxValue = 0; + for (int i = 0; i < sequenceResource->_animationsCount; ++i) { + SequenceAnimation *animation = &sequenceResource->_animations[i]; + if (animation->_additionalDelay + animation->_maxTotalDuration > maxValue) + maxValue = animation->_additionalDelay + animation->_maxTotalDuration; + } + int totalDuration = maxValue + sequenceResource->_totalDuration; + _sequenceCache->release(resourceId); + return totalDuration; +} + +bool GnapEngine::isSoundPlaying(int resourceId) { + return _soundMan->isSoundPlaying(resourceId); +} + +void GnapEngine::playSound(int resourceId, bool looping) { + debugC(kDebugBasic, "playSound(%08X, %d)", resourceId, looping); + _soundMan->playSound(resourceId, looping); +} + +void GnapEngine::stopSound(int resourceId) { + _soundMan->stopSound(resourceId); +} + +void GnapEngine::setSoundVolume(int resourceId, int volume) { + _soundMan->setSoundVolume(resourceId, volume); +} + +void GnapEngine::updateTimers() { + for (int i = 0; i < kMaxTimers; ++i) + if (_timers[i] > 0) + --_timers[i]; +} + +void GnapEngine::initGameFlags(int num) { + invClear(); + invAdd(kItemMagazine); + switch (num) { + case 1: + setFlag(kGFPlatypusTalkingToAssistant); + break; + case 2: + clearFlags(); + break; + case 3: + invAdd(kItemDiceQuarterHole); + clearFlags(); + break; + case 4: + invAdd(kItemDiceQuarterHole); + invAdd(kItemHorn); + invAdd(kItemLightbulb); + clearFlags(); + setFlag(kGFPlatypus); + setFlag(kGFMudTaken); + setFlag(kGFNeedleTaken); + setFlag(kGFTwigTaken); + setFlag(kGFUnk04); + setFlag(kGFKeysTaken); + setFlag(kGFGrassTaken); + setFlag(kGFBarnPadlockOpen); + break; + } +} + +void GnapEngine::loadStockDat() { + if (!_isStockDatLoaded) { + _isStockDatLoaded = true; + _dat->open(1, "stock_n.dat"); + // The pre-loading of data is skipped as it's no longer required on modern hardware + } +} + +void GnapEngine::mainLoop() { + _newCursorValue = 1; + _cursorValue = -1; + _newSceneNum = 0; + _currentSceneNum = 55; + _prevSceneNum = 55; + invClear(); + clearFlags(); + _grabCursorSpriteIndex = -1; + _grabCursorSprite = nullptr; + + loadStockDat(); + + if (_loadGameSlot != -1) { + // Load a savegame + int slot = _loadGameSlot; + _loadGameSlot = -1; + loadGameState(slot); + _wasSavegameLoaded = true; + + showCursor(); + } + + while (!_gameDone) { + debugC(kDebugBasic, "New scene: %d", _newSceneNum); + + _prevSceneNum = _currentSceneNum; + _currentSceneNum = _newSceneNum; + + debugC(kDebugBasic, "GnapEngine::mainLoop() _prevSceneNum: %d; _currentSceneNum: %d", _prevSceneNum, _currentSceneNum); + + if (_newCursorValue != _cursorValue) { + debugC(kDebugBasic, "_newCursorValue: %d", _newCursorValue); + _cursorValue = _newCursorValue; + if (!_wasSavegameLoaded) + initGameFlags(_cursorValue); + } + + _sceneSavegameLoaded = _wasSavegameLoaded; + _wasSavegameLoaded = false; + + initScene(); + + runSceneLogic(); + afterScene(); + + _soundMan->stopAll(); + + // Force purge all resources + _sequenceCache->purge(true); + _soundCache->purge(true); + _spriteCache->purge(true); + } + + if (_backgroundSurface) + deleteSurface(&_backgroundSurface); + + _dat->close(1); +} + +void GnapEngine::initScene() { + Common::String datFilename; + + _isLeavingScene = false; + _sceneDone = false; + _newSceneNum = 55; + _gnap->_actionStatus = -1; + _plat->_actionStatus = -1; + _gnap->initBrainPulseRndValue(); + hideCursor(); + clearAllKeyStatus1(); + _mouseClickState._left = false; + _mouseClickState._right = false; + _sceneClickedHotspot = -1; + + datFilename = Common::String::format("%s_n.dat", kSceneNames[_currentSceneNum]); + + debugC(kDebugBasic, "GnapEngine::initScene() datFilename: %s", datFilename.c_str()); + + _dat->open(0, datFilename.c_str()); + + int backgroundId = initSceneLogic(); + + if (!_backgroundSurface) { + if (_currentSceneNum != 0) + _backgroundSurface = _gameSys->loadBitmap(makeRid(1, 0x8AA)); + else + _backgroundSurface = _gameSys->loadBitmap(makeRid(0, backgroundId)); + _gameSys->setBackgroundSurface(_backgroundSurface, 0, 500, 1, 1000); + } + + if (_currentSceneNum != 0 && _currentSceneNum != 16 && _currentSceneNum != 47 && + _currentSceneNum != 48 && _currentSceneNum != 54) { + _gameSys->drawBitmap(backgroundId); + } + + if ((_cursorValue == 4 && isFlag(kGFGnapControlsToyUFO)) || _currentSceneNum == 41) + playSound(makeRid(1, 0x8F6), true); + +} + +void GnapEngine::endSceneInit() { + showCursor(); + if (_newGrabCursorSpriteIndex >= 0) + setGrabCursorSprite(_newGrabCursorSpriteIndex); +} + +void GnapEngine::afterScene() { + if (_gameDone) + return; + + if (_newCursorValue == _cursorValue && _newSceneNum != 0 && _newSceneNum != 16 && + _newSceneNum != 47 && _newSceneNum != 48 && _newSceneNum != 54 && _newSceneNum != 49 && + _newSceneNum != 50 && _newSceneNum != 51 && _newSceneNum != 52) + _newGrabCursorSpriteIndex = _grabCursorSpriteIndex; + else + _newGrabCursorSpriteIndex = -1; + + setGrabCursorSprite(-1); + + _gameSys->requestClear2(false); + _gameSys->requestClear1(); + _gameSys->waitForUpdate(); + + _gameSys->requestClear2(false); + _gameSys->requestClear1(); + _gameSys->waitForUpdate(); + + screenEffect(0, 0, 0, 0); + + _dat->close(0); + + for (int animationIndex = 0; animationIndex < 12; ++animationIndex) + _gameSys->setAnimation(0, 0, animationIndex); + + clearKeyStatus1(Common::KEYCODE_p); + + _mouseClickState._left = false; + _mouseClickState._right = false; + +} + +void GnapEngine::checkGameKeys() { + if (isKeyStatus1(Common::KEYCODE_p)) { + clearKeyStatus1(Common::KEYCODE_p); + pauseGame(); + updatePause(); + } +} + +void GnapEngine::startSoundTimerA(int timerIndex) { + _soundTimerIndexA = timerIndex; + _timers[timerIndex] = getRandom(50) + 100; +} + +int GnapEngine::playSoundA() { + static const int kSoundIdsA[] = { + 0x93E, 0x93F, 0x941, 0x942, 0x943, 0x944, + 0x945, 0x946, 0x947, 0x948, 0x949 + }; + + int soundId = -1; + + if (!_timers[_soundTimerIndexA]) { + _timers[_soundTimerIndexA] = getRandom(50) + 100; + soundId = kSoundIdsA[getRandom(11)]; + playSound(soundId | 0x10000, false); + } + return soundId; +} + +void GnapEngine::startSoundTimerB(int timerIndex) { + _soundTimerIndexB = timerIndex; + _timers[timerIndex] = getRandom(50) + 150; +} + +int GnapEngine::playSoundB() { + static const int kSoundIdsB[] = { + 0x93D, 0x929, 0x92A, 0x92B, 0x92C, 0x92D, + 0x92E, 0x92F, 0x930, 0x931, 0x932, 0x933, + 0x934, 0x935, 0x936, 0x937, 0x938, 0x939, + 0x93A + }; + + int soundId = -1; + + if (!_timers[_soundTimerIndexB]) { + _timers[_soundTimerIndexB] = getRandom(50) + 150; + soundId = kSoundIdsB[getRandom(19)]; + playSound(soundId | 0x10000, false); + } + return soundId; +} + +void GnapEngine::startSoundTimerC(int timerIndex) { + _soundTimerIndexC = timerIndex; + _timers[timerIndex] = getRandom(50) + 150; +} + +int GnapEngine::playSoundC() { + static const int kSoundIdsC[] = { + 0x918, 0x91F, 0x920, 0x922, 0x923, 0x924, + 0x926 + }; + + int soundId = -1; + + if (!_timers[_soundTimerIndexC]) { + _timers[_soundTimerIndexC] = getRandom(50) + 150; + soundId = kSoundIdsC[getRandom(7)] ; + playSound(soundId | 0x10000, false); + } + return soundId; +} + +void GnapEngine::startIdleTimer(int timerIndex) { + _idleTimerIndex = timerIndex; + _timers[timerIndex] = 3000; +} + +void GnapEngine::updateIdleTimer() { + if (!_timers[_idleTimerIndex]) { + _timers[_idleTimerIndex] = 3000; + _gameSys->insertSequence(0x1088B, 255, 0, 0, kSeqNone, 0, 0, 75); + } +} + +void GnapEngine::screenEffect(int dir, byte r, byte g, byte b) { + int startVal = 0; + if (dir == 1) + startVal = 300; + + for (int y = startVal; y < startVal + 300 && !_gameDone; y += 50) { + _gameSys->fillSurface(nullptr, 0, y, 800, 50, r, g, b); + _gameSys->fillSurface(nullptr, 0, 549 - y + 1, 800, 50, r, g, b); + gameUpdateTick(); + _system->delayMillis(50); + } +} + +bool GnapEngine::isKeyStatus1(int key) { + return _keyPressState[key]; +} + +bool GnapEngine::isKeyStatus2(int key) { + return _keyDownState[key]; +} + +void GnapEngine::clearKeyStatus1(int key) { + _keyPressState[key] = false; + _keyDownState[key] = false; +} + +void GnapEngine::clearAllKeyStatus1() { + memset(_keyPressState, 0, sizeof(_keyPressState)); + memset(_keyDownState, 0, sizeof(_keyDownState)); +} + +void GnapEngine::deleteSurface(Graphics::Surface **surface) { + if (surface && *surface) { + (*surface)->free(); + delete *surface; + *surface = nullptr; + } +} + +bool GnapEngine::testWalk(int animationIndex, int someStatus, int gridX1, int gridY1, int gridX2, int gridY2) { + if (_mouseClickState._left && someStatus == _gnap->_actionStatus) { + _isLeavingScene = false; + _gameSys->setAnimation(0, 0, animationIndex); + _gnap->_actionStatus = -1; + _plat->_actionStatus = -1; + _gnap->walkTo(Common::Point(gridX1, gridY1), -1, -1, 1); + _plat->walkTo(Common::Point(gridX2, gridY2), -1, -1, 1); + _mouseClickState._left = false; + return true; + } + return false; +} + +void GnapEngine::doCallback(int callback) { + switch (callback) { + case 8: + case 10: + case 20: + _scene->updateAnimationsCb(); + break; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +void GnapEngine::initGlobalSceneVars() { + // Shared by scenes 17 && 18 + _s18GarbageCanPos = 8; + + // Toy UFO + _toyUfoId = 0; + _toyUfoActionStatus = -1; + _toyUfoX = 0; + _toyUfoY = 50; +} + +void GnapEngine::playSequences(int fullScreenSpriteId, int sequenceId1, int sequenceId2, int sequenceId3) { + setGrabCursorSprite(-1); + _gameSys->setAnimation(sequenceId2, _gnap->_id, 0); + _gameSys->insertSequence(sequenceId2, _gnap->_id, + makeRid(_gnap->_sequenceDatNum, _gnap->_sequenceId), _gnap->_id, + kSeqSyncWait, 0, 15 * (5 * _gnap->_pos.x - 25), 48 * (_gnap->_pos.y - 8)); + _gnap->_sequenceId = sequenceId2; + _gnap->_sequenceDatNum = 0; + while (_gameSys->getAnimationStatus(0) != 2 && !_gameDone) + gameUpdateTick(); + hideCursor(); + addFullScreenSprite(fullScreenSpriteId, 255); + _gameSys->setAnimation(sequenceId1, 256, 0); + _gameSys->insertSequence(sequenceId1, 256, 0, 0, kSeqNone, 0, 0, 0); + while (_gameSys->getAnimationStatus(0) != 2 && !_gameDone) + gameUpdateTick(); + _gameSys->setAnimation(sequenceId3, _gnap->_id, 0); + _gameSys->insertSequence(sequenceId3, _gnap->_id, + makeRid(_gnap->_sequenceDatNum, _gnap->_sequenceId), _gnap->_id, + kSeqSyncWait, 0, 15 * (5 * _gnap->_pos.x - 25), 48 * (_gnap->_pos.y - 8)); + removeFullScreenSprite(); + showCursor(); + _gnap->_sequenceId = sequenceId3; +} + +void GnapEngine::toyUfoSetStatus(int flagNum) { + clearFlag(kGFUnk16); + clearFlag(kGFJointTaken); + clearFlag(kGFUnk18); + clearFlag(kGFGroceryStoreHatTaken); + setFlag(flagNum); +} + +int GnapEngine::toyUfoGetSequenceId() { + if (isFlag(kGFUnk16)) + return 0x84E; + if (isFlag(kGFJointTaken)) + return 0x84B; + if (isFlag(kGFUnk18)) + return 0x84D; + if (isFlag(kGFGroceryStoreHatTaken)) + return 0x84C; + return 0x84E; +} + +bool GnapEngine::toyUfoCheckTimer() { + if (!isFlag(kGFGnapControlsToyUFO) || isFlag(kGFUnk18) || _timers[9] || + _toyUfoSequenceId == 0x870 || _toyUfoSequenceId == 0x871 || _toyUfoSequenceId == 0x872 || _toyUfoSequenceId == 0x873) + return false; + _sceneDone = true; + _newSceneNum = 41; + return true; +} + +void GnapEngine::toyUfoFlyTo(int destX, int destY, int minX, int maxX, int minY, int maxY, int animationIndex) { + GridStruct flyNodes[34]; + + if (destX == -1) + destX = _leftClickMouseX; + + if (destY == -1) + destY = _leftClickMouseY; + + int clippedDestX = CLIP(destX, minX, maxX); + int clippedDestY = CLIP(destY, minY, maxY); + int dirX = 0, dirY = 0; // 0, -1 or 1 + + if (clippedDestX != _toyUfoX) + dirX = (clippedDestX - _toyUfoX) / ABS(clippedDestX - _toyUfoX); + + if (clippedDestY != _toyUfoY) + dirY = (clippedDestY - _toyUfoY) / ABS(clippedDestY - _toyUfoY); + + int deltaX = ABS(clippedDestX - _toyUfoX); + int deltaY = ABS(clippedDestY - _toyUfoY); + + int i = 0; + if (deltaY > deltaX) { + int flyDirYIncr = 32; + int gridDistY = deltaY / flyDirYIncr; + int curMove = 0; + while (curMove < deltaY && i < 34) { + if (gridDistY - 5 >= i) { + flyDirYIncr = MIN(36, 8 * i + 8); + } else { + flyDirYIncr = MAX(6, flyDirYIncr - 3); + } + curMove += flyDirYIncr; + flyNodes[i]._gridX1 = _toyUfoX + dirX * deltaX * curMove / deltaY; + flyNodes[i]._gridY1 = _toyUfoY + dirY * curMove; + ++i; + } + } else { + int flyDirXIncr = 36; + int gridDistX = deltaX / flyDirXIncr; + int curMove = 0; + while (curMove < deltaX && i < 34) { + if (gridDistX - 5 >= i) { + flyDirXIncr = MIN(38, 8 * i + 8); + } else { + flyDirXIncr = MAX(6, flyDirXIncr - 3); + } + curMove += flyDirXIncr; + flyNodes[i]._gridX1 = _toyUfoX + dirX * curMove; + flyNodes[i]._gridY1 = _toyUfoY + dirY * deltaY * curMove / deltaX; + ++i; + } + } + + int nodesCount = i - 1; + + _toyUfoX = clippedDestX; + _toyUfoY = clippedDestY; + + if (nodesCount > 0) { + int seqId = 0; + if (isFlag(kGFUnk16)) + seqId = 0x867; + else if (isFlag(kGFJointTaken)) + seqId = 0x84F; + else if (isFlag(kGFUnk18)) + seqId = 0x85F; + else if (isFlag(kGFGroceryStoreHatTaken)) + seqId = 0x857; + else + error("Unhandled flag in GnapEngine::toyUfoFlyTo(): 0x%x", _gameFlags); + flyNodes[0]._sequenceId = seqId; + flyNodes[0]._id = 0; + _gameSys->insertSequence(seqId | 0x10000, 0, + _toyUfoSequenceId | 0x10000, _toyUfoId, + kSeqSyncWait, 0, flyNodes[0]._gridX1 - 365, flyNodes[0]._gridY1 - 128); + for (i = 1; i < nodesCount; ++i) { + flyNodes[i]._sequenceId = seqId + (i % 8); + flyNodes[i]._id = i; + _gameSys->insertSequence(flyNodes[i]._sequenceId | 0x10000, flyNodes[i]._id, + flyNodes[i - 1]._sequenceId | 0x10000, flyNodes[i - 1]._id, + kSeqSyncWait, 0, + flyNodes[i]._gridX1 - 365, flyNodes[i]._gridY1 - 128); + } + + _toyUfoSequenceId = flyNodes[nodesCount - 1]._sequenceId; + _toyUfoId = flyNodes[nodesCount - 1]._id; + + if (animationIndex >= 0) + _gameSys->setAnimation(_toyUfoSequenceId | 0x10000, _toyUfoId, animationIndex); + + } +} + +void GnapEngine::playMidi(const char *name) { + if (_music) + return; + + _music = new MusicPlayer(name); + _music->playSMF(true); +} + +void GnapEngine::stopMidi() { + if (_music) { + _music->stop(); + delete _music; + _music = nullptr; + } +} +} // End of namespace Gnap |