/* 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 "audio/audiostream.h" #include "audio/decoders/wave.h" #include "common/archive.h" #include "common/config-manager.h" #include "common/debug-channels.h" #include "common/debug.h" #include "common/error.h" #include "common/events.h" #include "common/file.h" #include "common/savefile.h" #include "common/str.h" #include "common/system.h" #include "common/timer.h" #include "engines/util.h" #include "image/bmp.h" #include "private/decompiler.h" #include "private/grammar.h" #include "private/private.h" #include "private/tokens.h" namespace Private { PrivateEngine *g_private = NULL; extern int parse(const char *); PrivateEngine::PrivateEngine(OSystem *syst, const ADGameDescription *gd) : Engine(syst), _gameDescription(gd), _image(nullptr), _videoDecoder(nullptr), _compositeSurface(nullptr), _transparentColor(0), _frame(nullptr), _maxNumberClicks(0), _sirenWarning(0), _screenW(640), _screenH(480) { _rnd = new Common::RandomSource("private"); // Global object for external reference g_private = this; // Setting execution _nextSetting = ""; _currentSetting = ""; _pausedSetting = ""; _modified = false; _mode = -1; _toTake = false; // Movies _nextMovie = ""; _currentMovie = ""; _nextVS = ""; _repeatedMovieExit = ""; // Save and load _saveGameMask.clear(); _loadGameMask.clear(); // Interface _framePath = "inface/general/inface2.bmp"; // Police _policeBustEnabled = false; _policeBustSetting = ""; _numberClicks = 0; _sirenSound = "po/audio/posfx002.wav"; // General sounds _globalAudioPath = "global/audio/"; _noStopSounds = false; // Radios and phone _policeRadioArea.clear(); _AMRadioArea.clear(); _phoneArea.clear(); // TODO: use this as a default sound for radio _infaceRadioPath = "inface/radio/"; _phonePrefix = "inface/telephon/"; _phoneCallSound = "phone.wav"; // Dossiers _dossierPage = 0; _dossierSuspect = 0; _dossierNextSuspectMask.clear(); _dossierPrevSuspectMask.clear(); _dossierNextSheetMask.clear(); _dossierPrevSheetMask.clear(); // Diary _diaryLocPrefix = "inface/diary/loclist/"; // Safe _safeNumberPath = "sg/search_s/sgsaf%d.bmp"; for (uint d = 0 ; d < 3; d++) { _safeDigitArea[d].clear(); _safeDigit[d] = 0; _safeDigitRect[d] = Common::Rect(0, 0); } } PrivateEngine::~PrivateEngine() { // Dispose your resources here delete _frame; delete _rnd; delete Gen::g_vm; delete Settings::g_setts; } void PrivateEngine::initializePath(const Common::FSNode &gamePath) { SearchMan.addDirectory(gamePath.getPath(), gamePath, 0, 10); } Common::SeekableReadStream *PrivateEngine::loadAssets() { Common::File *test = new Common::File(); Common::SeekableReadStream *file = NULL; if (isDemo() && test->open("SUPPORT/ASSETS/DEMOGAME.WIN")) file = test; else if (isDemo() && test->open("SUPPORT/DEMOGAME.MAC")) file = test; else if (test->open("SUPPORT/ASSETS/GAME.WIN")) { file = test; } else if (test->open("SUPPORT/GAME.MAC")) { file = test; } else { delete test; assert(_installerArchive.open("SUPPORT/ASSETS.Z")); // if the full game is used if (!isDemo()) { if (_installerArchive.hasFile("GAME.DAT")) file = _installerArchive.createReadStreamForMember("GAME.DAT"); else if (_installerArchive.hasFile("GAME.WIN")) file = _installerArchive.createReadStreamForMember("GAME.WIN"); else error("Unknown version"); } else { // if the demo from archive.org is used if (_installerArchive.hasFile("GAME.TXT")) file = _installerArchive.createReadStreamForMember("GAME.TXT"); // if the demo from the full retail CDROM is used else if (_installerArchive.hasFile("DEMOGAME.DAT")) file = _installerArchive.createReadStreamForMember("DEMOGAME.DAT"); else if (_installerArchive.hasFile("DEMOGAME.WIN")) file = _installerArchive.createReadStreamForMember("DEMOGAME.WIN"); else { error("Unknown version"); } } } assert(file != NULL); return file; } Common::Error PrivateEngine::run() { _language = Common::parseLanguage(ConfMan.get("language")); _platform = Common::parsePlatform(ConfMan.get("platform")); Common::SeekableReadStream *file = loadAssets(); // Read assets file const uint32 fileSize = file->size(); char *buf = (char *)malloc(fileSize + 1); file->read(buf, fileSize); buf[fileSize] = '\0'; Decompiler decomp(buf, fileSize, _platform == Common::kPlatformMacintosh); free(buf); Common::String scripts = decomp.getResult(); debugC(1, kPrivateDebugCode, "code:\n%s", scripts.c_str()); // Initialize stuff Gen::g_vm = new Gen::VM(); Settings::g_setts = new Settings::SettingMaps(); initFuncs(); parse(scripts.c_str()); delete file; assert(maps.constants.size() > 0); // Initialize graphics initGraphics(_screenW, _screenH, nullptr); _pixelFormat = g_system->getScreenFormat(); if (_pixelFormat == Graphics::PixelFormat::createFormatCLUT8()) return Common::kUnsupportedColorMode; _transparentColor = _pixelFormat.RGBToColor(0, 255, 0); _safeColor = _pixelFormat.RGBToColor(65, 65, 65); screenRect = Common::Rect(0, 0, _screenW, _screenH); changeCursor("default"); _origin = Common::Point(0, 0); _image = new Image::BitmapDecoder(); _compositeSurface = new Graphics::ManagedSurface(); _compositeSurface->create(_screenW, _screenH, _pixelFormat); _compositeSurface->setTransparentColor(_transparentColor); // Load the game frame once _frame = decodeImage(_framePath); // Main event loop Common::Event event; Common::Point mousePos; _videoDecoder = nullptr; int saveSlot = ConfMan.getInt("save_slot"); if (saveSlot >= 0) { // load the savegame loadGameState(saveSlot); } else { _nextSetting = getGoIntroSetting(); } while (!shouldQuit()) { checkPhoneCall(); while (g_system->getEventManager()->pollEvent(event)) { mousePos = g_system->getEventManager()->getMousePos(); // Events switch (event.type) { case Common::EVENT_KEYDOWN: if (event.kbd.keycode == Common::KEYCODE_ESCAPE && _videoDecoder) skipVideo(); break; case Common::EVENT_QUIT: case Common::EVENT_RETURN_TO_LAUNCHER: break; case Common::EVENT_LBUTTONDOWN: if (selectDossierNextSuspect(mousePos)) break; else if (selectDossierPrevSuspect(mousePos)) break; else if (selectDossierNextSheet(mousePos)) break; else if (selectDossierPrevSheet(mousePos)) break; else if (selectSafeDigit(mousePos)) break; selectPauseMovie(mousePos); selectPhoneArea(mousePos); selectPoliceRadioArea(mousePos); selectAMRadioArea(mousePos); selectLoadGame(mousePos); selectSaveGame(mousePos); if (_nextSetting.empty()) selectMask(mousePos); if (_nextSetting.empty()) selectExit(mousePos); break; case Common::EVENT_MOUSEMOVE: // Reset cursor to default changeCursor("default"); // The following functions will return true // if the cursor is changed if (cursorPauseMovie(mousePos)) { } else if (cursorMask(mousePos)) { } else cursorExit(mousePos); break; default: break; } } checkPoliceBust(); // Movies if (!_nextMovie.empty()) { removeTimer(); _videoDecoder = new Video::SmackerDecoder(); playVideo(_nextMovie); _currentMovie = _nextMovie; _nextMovie = ""; continue; } if (!_nextVS.empty() && (_currentSetting == getMainDesktopSetting())) { loadImage(_nextVS, 160, 120); drawScreen(); } if (_videoDecoder && !_videoDecoder->isPaused()) { if (_videoDecoder->getCurFrame() == 0) stopSound(true); if (_videoDecoder->endOfVideo()) { _videoDecoder->close(); delete _videoDecoder; _videoDecoder = nullptr; _currentMovie = ""; } else if (_videoDecoder->needsUpdate()) { drawScreen(); } continue; } if (!_nextSetting.empty()) { removeTimer(); debugC(1, kPrivateDebugFunction, "Executing %s", _nextSetting.c_str()); clearAreas(); _currentSetting = _nextSetting; Settings::g_setts->load(_nextSetting); _nextSetting = ""; Gen::g_vm->run(); changeCursor("default"); drawScreen(); } g_system->updateScreen(); g_system->delayMillis(10); } return Common::kNoError; } void PrivateEngine::ignoreEvents() { Common::Event event; g_system->getEventManager()->pollEvent(event); g_system->updateScreen(); g_system->delayMillis(10); } void PrivateEngine::initFuncs() { for (const Private::FuncTable *fnc = funcTable; fnc->name; fnc++) { Common::String name(fnc->name); _functions.setVal(name, (void *)fnc->func); } } void PrivateEngine::clearAreas() { _exits.clear(); _masks.clear(); if (_loadGameMask.surf) _loadGameMask.surf->free(); delete _loadGameMask.surf; _loadGameMask.clear(); if (_saveGameMask.surf) _saveGameMask.surf->free(); delete _saveGameMask.surf; _saveGameMask.clear(); if (_policeRadioArea.surf) _policeRadioArea.surf->free(); delete _policeRadioArea.surf; _policeRadioArea.clear(); if (_AMRadioArea.surf) _AMRadioArea.surf->free(); delete _AMRadioArea.surf; _AMRadioArea.clear(); if (_phoneArea.surf) _phoneArea.surf->free(); delete _phoneArea.surf; _phoneArea.clear(); if (_dossierNextSuspectMask.surf) _dossierNextSuspectMask.surf->free(); delete _dossierNextSuspectMask.surf; _dossierNextSuspectMask.clear(); if (_dossierPrevSuspectMask.surf) _dossierPrevSuspectMask.surf->free(); delete _dossierPrevSuspectMask.surf; _dossierPrevSuspectMask.clear(); if (_dossierNextSheetMask.surf) _dossierNextSheetMask.surf->free(); delete _dossierNextSheetMask.surf; _dossierNextSheetMask.clear(); if (_dossierPrevSheetMask.surf) _dossierPrevSheetMask.surf->free(); delete _dossierPrevSheetMask.surf; _dossierPrevSheetMask.clear(); for (uint d = 0 ; d < 3; d++) { if (_safeDigitArea[d].surf) _safeDigitArea[d].surf->free(); delete _safeDigitArea[d].surf; _safeDigitArea[d].clear(); _safeDigit[d] = 0; _safeDigitRect[d] = Common::Rect(0, 0); } } void PrivateEngine::startPoliceBust() { // This logic was extracted from the binary int policeIndex = maps.variables.getVal(getPoliceIndexVariable())->u.val; int r = _rnd->getRandomNumber(0xc); if (policeIndex > 0x14) { policeIndex = 0x15; } _maxNumberClicks = r + 0x10 + (policeIndex * 0xe) / -0x15; _sirenWarning = _rnd->getRandomNumber(0x7) + 3; _numberClicks = 0; if (_sirenWarning >= _maxNumberClicks) _sirenWarning = _maxNumberClicks - 1; } void PrivateEngine::checkPoliceBust() { if (!_policeBustEnabled) return; if (_numberClicks < _sirenWarning) return; if (_numberClicks == _sirenWarning) { stopSound(true); playSound(_sirenSound, 0, false, false); _numberClicks++; // Won't execute again return; } if (_numberClicks == _maxNumberClicks + 1) { uint policeIndex = maps.variables.getVal(getPoliceIndexVariable())->u.val; _policeBustSetting = _currentSetting; if (policeIndex <= 13) { _nextSetting = getPOGoBustMovieSetting(); } else { _nextSetting = getPoliceBustFromMOSetting(); } clearAreas(); _policeBustEnabled = false; } } bool PrivateEngine::cursorExit(Common::Point mousePos) { mousePos = mousePos - _origin; if (mousePos.x < 0 || mousePos.y < 0) return false; int rs = 100000000; int cs = 0; Common::String cursor; for (ExitList::const_iterator it = _exits.begin(); it != _exits.end(); ++it) { const ExitInfo &e = *it; cs = e.rect.width() * e.rect.height(); if (e.rect.contains(mousePos)) { if (cs < rs && !e.cursor.empty()) { rs = cs; cursor = e.cursor; } } } if (!cursor.empty()) { changeCursor(cursor); return true; } return false; } bool PrivateEngine::inMask(Graphics::Surface *surf, Common::Point mousePos) { if (surf == NULL) return false; mousePos = mousePos - _origin; if (mousePos.x < 0 || mousePos.y < 0) return false; if (mousePos.x > surf->w || mousePos.y > surf->h) return false; return (surf->getPixel(mousePos.x, mousePos.y) != _transparentColor); } bool PrivateEngine::cursorMask(Common::Point mousePos) { bool inside = false; for (MaskList::const_iterator it = _masks.begin(); it != _masks.end(); ++it) { const MaskInfo &m = *it; if (inMask(m.surf, mousePos)) { if (!m.cursor.empty()) { // TODO: check this inside = true; changeCursor(m.cursor); break; } } } return inside; } bool PrivateEngine::cursorPauseMovie(Common::Point mousePos) { if (_mode == 1 && !_policeBustEnabled) { uint32 tol = 15; Common::Rect window(_origin.x - tol, _origin.y - tol, _screenW - _origin.x + tol, _screenH - _origin.y + tol); if (!window.contains(mousePos)) { return true; } } return false; } Common::String PrivateEngine::getPauseMovieSetting() { if ((_language == Common::EN_USA || _language == Common::RU_RUS) && _platform != Common::kPlatformMacintosh) return "kPauseMovie"; return "k3"; } Common::String PrivateEngine::getGoIntroSetting() { if ((_language == Common::EN_USA || _language == Common::RU_RUS) && _platform != Common::kPlatformMacintosh) return "kGoIntro"; return "k1"; } Common::String PrivateEngine::getAlternateGameVariable() { if ((_language == Common::EN_USA || _language == Common::RU_RUS) && _platform != Common::kPlatformMacintosh) return "kAlternateGame"; return "k2"; } Common::String PrivateEngine::getMainDesktopSetting() { if ((_language == Common::EN_USA || _language == Common::RU_RUS) && _platform != Common::kPlatformMacintosh) return "kMainDesktop"; if (isDemo()) return "k45"; return "k183"; } Common::String PrivateEngine::getPoliceIndexVariable() { if ((_language == Common::EN_USA || _language == Common::RU_RUS) && _platform != Common::kPlatformMacintosh) return "kPoliceIndex"; return "k0"; } Common::String PrivateEngine::getPOGoBustMovieSetting() { if ((_language == Common::EN_USA || _language == Common::RU_RUS) && _platform != Common::kPlatformMacintosh) return "kPOGoBustMovie"; return "k7"; } Common::String PrivateEngine::getPoliceBustFromMOSetting() { if ((_language == Common::EN_USA || _language == Common::RU_RUS) && _platform != Common::kPlatformMacintosh) return "kPoliceBustFromMO"; return "k6"; } Common::String PrivateEngine::getWallSafeValueVariable() { if ((_language == Common::EN_USA || _language == Common::RU_RUS) && _platform != Common::kPlatformMacintosh) return "kWallSafeValue"; return "k3"; } Common::String PrivateEngine::getExitCursor() { if ((_language == Common::EN_USA || _language == Common::RU_RUS) && _platform != Common::kPlatformMacintosh) return "kExit"; return "k5"; } Common::String PrivateEngine::getInventoryCursor() { if ((_language == Common::EN_USA || _language == Common::RU_RUS) && _platform != Common::kPlatformMacintosh) return "kInventory"; return "k7"; } void PrivateEngine::selectPauseMovie(Common::Point mousePos) { if (_mode == 1 && !_policeBustEnabled) { uint32 tol = 15; Common::Rect window(_origin.x - tol, _origin.y - tol, _screenW - _origin.x + tol, _screenH - _origin.y + tol); if (!window.contains(mousePos)) { if (_pausedSetting.empty()) { if (!_nextSetting.empty()) _pausedSetting = _nextSetting; else _pausedSetting = _currentSetting; _nextSetting = getPauseMovieSetting(); if (_videoDecoder) { _videoDecoder->pauseVideo(true); } } } } } void PrivateEngine::selectExit(Common::Point mousePos) { mousePos = mousePos - _origin; if (mousePos.x < 0 || mousePos.y < 0) return; Common::String ns = ""; int rs = 100000000; int cs = 0; for (ExitList::const_iterator it = _exits.begin(); it != _exits.end(); ++it) { const ExitInfo &e = *it; cs = e.rect.width() * e.rect.height(); //debug("Testing exit %s %d", e.nextSetting->c_str(), cs); if (e.rect.contains(mousePos)) { //debug("Inside! %d %d", cs, rs); if (cs < rs && !e.nextSetting.empty()) { // TODO: check this // an item was not taken if (_toTake) { playSound(getLeaveSound(), 1, false, false); _toTake = false; } //debug("Found Exit %s %d", e.nextSetting->c_str(), cs); rs = cs; ns = e.nextSetting; } } } if (!ns.empty()) { _numberClicks++; // count click only if it hits a hotspot _nextSetting = ns; } } void PrivateEngine::selectMask(Common::Point mousePos) { Common::String ns; for (MaskList::const_iterator it = _masks.begin(); it != _masks.end(); ++it) { const MaskInfo &m = *it; //debug("Testing mask %s", m.nextSetting->c_str()); if (inMask(m.surf, mousePos)) { //debug("Inside!"); if (!m.nextSetting.empty()) { // TODO: check this //debug("Found Mask %s", m.nextSetting->c_str()); ns = m.nextSetting; } if (m.flag1 != NULL) { // TODO: check this setSymbol(m.flag1, 1); // an item was taken if (_toTake) { playSound(getTakeSound(), 1, false, false); _toTake = false; } } if (m.flag2 != NULL) { setSymbol(m.flag2, 1); } break; } } if (!ns.empty()) { _numberClicks++; // count click only if it hits a hotspot _nextSetting = ns; } } void PrivateEngine::selectAMRadioArea(Common::Point mousePos) { if (_AMRadioArea.surf == NULL) return; if (_AMRadio.empty()) return; if (inMask(_AMRadioArea.surf, mousePos)) { Common::String sound = _infaceRadioPath + "comm_/" + _AMRadio.back() + ".wav"; playSound(sound, 1, false, false); _AMRadio.pop_back(); } } void PrivateEngine::selectPoliceRadioArea(Common::Point mousePos) { if (_policeRadioArea.surf == NULL) return; if (_policeRadio.empty()) return; if (inMask(_policeRadioArea.surf, mousePos)) { Common::String sound = _infaceRadioPath + "police/" + _policeRadio.back() + ".wav"; playSound(sound, 1, false, false); _policeRadio.pop_back(); } } void PrivateEngine::checkPhoneCall() { if (_phoneArea.surf == NULL) return; if (_phone.empty()) return; if (!_mixer->isSoundHandleActive(_fgSoundHandle)) playSound(_phonePrefix + _phoneCallSound, 1, false, false); } void PrivateEngine::selectPhoneArea(Common::Point mousePos) { if (_phoneArea.surf == NULL) return; if (_phone.empty()) return; if (inMask(_phoneArea.surf, mousePos)) { const PhoneInfo &i = _phone.back(); setSymbol(i.flag, i.val); Common::String sound = _phonePrefix + i.sound + ".wav"; playSound(sound, 1, true, false); _phone.pop_back(); } } void PrivateEngine::loadDossier() { int x = 40; int y = 30; DossierInfo m = _dossiers[_dossierSuspect]; if (_dossierPage == 0) { loadImage(m.page1, x, y); } else if (_dossierPage == 1) { loadImage(m.page2, x, y); } else { assert(0); } } bool PrivateEngine::selectDossierNextSuspect(Common::Point mousePos) { if (_dossierNextSuspectMask.surf == NULL) return false; if (inMask(_dossierNextSuspectMask.surf, mousePos)) { if ((_dossierSuspect + 1) < _dossiers.size()) { _dossierSuspect++; _dossierPage = 0; loadDossier(); drawMask(_dossierNextSuspectMask.surf); drawMask(_dossierPrevSuspectMask.surf); drawScreen(); } return true; } return false; } bool PrivateEngine::selectDossierPrevSheet(Common::Point mousePos) { if (_dossierNextSheetMask.surf == NULL) return false; if (inMask(_dossierPrevSheetMask.surf, mousePos)) { if (_dossierPage == 1) { _dossierPage = 0; loadDossier(); drawMask(_dossierNextSuspectMask.surf); drawMask(_dossierPrevSuspectMask.surf); drawScreen(); } return true; } return false; } bool PrivateEngine::selectDossierNextSheet(Common::Point mousePos) { if (_dossierNextSheetMask.surf == NULL) return false; if (inMask(_dossierNextSheetMask.surf, mousePos)) { DossierInfo m = _dossiers[_dossierSuspect]; if (_dossierPage == 0 && !m.page2.empty()) { _dossierPage = 1; loadDossier(); drawMask(_dossierNextSuspectMask.surf); drawMask(_dossierPrevSuspectMask.surf); drawScreen(); } return true; } return false; } bool PrivateEngine::selectDossierPrevSuspect(Common::Point mousePos) { if (_dossierPrevSuspectMask.surf == NULL) return false; if (inMask(_dossierPrevSuspectMask.surf, mousePos)) { if (_dossierSuspect > 0) { _dossierSuspect--; _dossierPage = 0; loadDossier(); drawMask(_dossierNextSuspectMask.surf); drawMask(_dossierPrevSuspectMask.surf); drawScreen(); } return true; } return false; } bool PrivateEngine::selectSafeDigit(Common::Point mousePos) { if (_safeDigitArea[0].surf == NULL) return false; mousePos = mousePos - _origin; if (mousePos.x < 0 || mousePos.y < 0) return false; for (uint d = 0 ; d < 3; d ++) if (_safeDigitRect[d].contains(mousePos)) { _safeDigit[d] = (_safeDigit[d] + 1) % 10; renderSafeDigit(d); Private::Symbol *sym = maps.variables.getVal(getWallSafeValueVariable()); sym->u.val = 100*_safeDigit[0] + 10*_safeDigit[1] + _safeDigit[2]; return true; } return false; } void PrivateEngine::addSafeDigit(uint32 d, Common::Rect *rect) { MaskInfo m; _safeDigitRect[d] = *rect; fillRect(_safeColor, _safeDigitRect[d]); m.surf = loadMask(Common::String::format(_safeNumberPath.c_str(), _safeDigit[d]), _safeDigitRect[d].left, _safeDigitRect[d].top, true); m.cursor = g_private->getExitCursor(); m.nextSetting = ""; m.flag1 = NULL; m.flag2 = NULL; _safeDigitArea[d] = m; drawScreen(); } void PrivateEngine::renderSafeDigit(uint32 d) { if (_safeDigitArea[d].surf != NULL) { _safeDigitArea[d].surf->free(); delete _safeDigitArea[d].surf; _safeDigitArea[d].clear(); } fillRect(_safeColor, _safeDigitRect[d]); MaskInfo m; m.surf = loadMask(Common::String::format(_safeNumberPath.c_str(), _safeDigit[d]), _safeDigitRect[d].left, _safeDigitRect[d].top, true); m.cursor = g_private->getExitCursor(); m.nextSetting = ""; m.flag1 = NULL; m.flag2 = NULL; _safeDigitArea[d] = m; drawScreen(); } void PrivateEngine::selectLoadGame(Common::Point mousePos) { if (_loadGameMask.surf == NULL) return; if (inMask(_loadGameMask.surf, mousePos)) { loadGameDialog(); } } void PrivateEngine::selectSaveGame(Common::Point mousePos) { if (_saveGameMask.surf == NULL) return; if (inMask(_saveGameMask.surf, mousePos)) { saveGameDialog(); } } bool PrivateEngine::hasFeature(EngineFeature f) const { return (f == kSupportsReturnToLauncher); } void PrivateEngine::restartGame() { debugC(1, kPrivateDebugFunction, "restartGame"); for (NameList::iterator it = maps.variableList.begin(); it != maps.variableList.end(); ++it) { Private::Symbol *sym = maps.variables.getVal(*it); if (*(sym->name) != getAlternateGameVariable()) sym->u.val = 0; } // Diary for (NameList::iterator it = maps.locationList.begin(); it != maps.locationList.end(); ++it) { Private::Symbol *sym = maps.locations.getVal(*it); sym->u.val = 0; } inventory.clear(); _dossiers.clear(); // Sounds _AMRadio.clear(); _policeRadio.clear(); _phone.clear(); _playedPhoneClips.clear(); // Movies _repeatedMovieExit = ""; _playedMovies.clear(); // Pause _pausedSetting = ""; // VSPicture _nextVS = ""; } Common::Error PrivateEngine::loadGameStream(Common::SeekableReadStream *stream) { // We don't want to continue with any sound from a previous game stopSound(true); Common::Serializer s(stream, nullptr); debugC(1, kPrivateDebugFunction, "loadGameStream"); int val; for (NameList::iterator it = maps.variableList.begin(); it != maps.variableList.end(); ++it) { s.syncAsUint32LE(val); Private::Symbol *sym = maps.variables.getVal(*it); sym->u.val = val; } // Diary for (NameList::iterator it = maps.locationList.begin(); it != maps.locationList.end(); ++it) { s.syncAsUint32LE(val); Private::Symbol *sym = maps.locations.getVal(*it); sym->u.val = val; } // Inventory inventory.clear(); uint32 size = stream->readUint32LE(); for (uint32 i = 0; i < size; ++i) { inventory.push_back(stream->readString()); } // Dossiers _dossiers.clear(); size = stream->readUint32LE(); DossierInfo m; for (uint32 i = 0; i < size; ++i) { m.page1 = stream->readString(); m.page2 = stream->readString(); _dossiers.push_back(m); } // Radios size = stream->readUint32LE(); _AMRadio.clear(); for (uint32 i = 0; i < size; ++i) { _AMRadio.push_back(stream->readString()); } size = stream->readUint32LE(); _policeRadio.clear(); for (uint32 i = 0; i < size; ++i) { _policeRadio.push_back(stream->readString()); } size = stream->readUint32LE(); _phone.clear(); PhoneInfo p; Common::String name; for (uint32 j = 0; j < size; ++j) { p.sound = stream->readString(); name = stream->readString(); p.flag = maps.lookupVariable(&name); p.val = stream->readUint32LE(); _phone.push_back(p); } // Played media _repeatedMovieExit = stream->readString(); _playedMovies.clear(); size = stream->readUint32LE(); for (uint32 i = 0; i < size; ++i) { _playedMovies.setVal(stream->readString(), true); } _playedPhoneClips.clear(); size = stream->readUint32LE(); for (uint32 i = 0; i < size; ++i) { _playedPhoneClips.setVal(stream->readString(), true); } // VSPicture _nextVS = stream->readString(); // Paused setting _pausedSetting = stream->readString(); // Restore a movie that was playing _currentMovie = stream->readString(); /* int currentTime = */ stream->readUint32LE(); if (!_currentMovie.empty()) { _videoDecoder = new Video::SmackerDecoder(); playVideo(_currentMovie); _videoDecoder->pauseVideo(true); // TODO: implement seek } if (_pausedSetting.empty()) _nextSetting = getMainDesktopSetting(); else _nextSetting = getPauseMovieSetting(); return Common::kNoError; } Common::Error PrivateEngine::saveGameStream(Common::WriteStream *stream, bool isAutosave) { debugC(1, kPrivateDebugFunction, "saveGameStream(%d)", isAutosave); if (isAutosave) return Common::kNoError; // Variables for (NameList::const_iterator it = maps.variableList.begin(); it != maps.variableList.end(); ++it) { const Private::Symbol *sym = maps.variables.getVal(*it); stream->writeUint32LE(sym->u.val); } // Diary for (NameList::const_iterator it = maps.locationList.begin(); it != maps.locationList.end(); ++it) { const Private::Symbol *sym = maps.locations.getVal(*it); stream->writeUint32LE(sym->u.val); } stream->writeUint32LE(inventory.size()); for (NameList::const_iterator it = inventory.begin(); it != inventory.end(); ++it) { stream->writeString(*it); stream->writeByte(0); } // Dossiers stream->writeUint32LE(_dossiers.size()); for (DossierArray::const_iterator it = _dossiers.begin(); it != _dossiers.end(); ++it) { stream->writeString(it->page1.c_str()); stream->writeByte(0); if (!it->page2.empty()) stream->writeString(it->page2.c_str()); stream->writeByte(0); } // Radios stream->writeUint32LE(_AMRadio.size()); for (SoundList::const_iterator it = _AMRadio.begin(); it != _AMRadio.end(); ++it) { stream->writeString(*it); stream->writeByte(0); } stream->writeUint32LE(_policeRadio.size()); for (SoundList::const_iterator it = _policeRadio.begin(); it != _policeRadio.end(); ++it) { stream->writeString(*it); stream->writeByte(0); } stream->writeUint32LE(_phone.size()); for (PhoneList::const_iterator it = _phone.begin(); it != _phone.end(); ++it) { stream->writeString(it->sound); stream->writeByte(0); stream->writeString(*it->flag->name); stream->writeByte(0); stream->writeUint32LE(it->val); } // Played media stream->writeString(_repeatedMovieExit); stream->writeByte(0); stream->writeUint32LE(_playedMovies.size()); for (PlayedMediaTable::const_iterator it = _playedMovies.begin(); it != _playedMovies.end(); ++it) { stream->writeString(it->_key); stream->writeByte(0); } stream->writeUint32LE(_playedPhoneClips.size()); for (PlayedMediaTable::const_iterator it = _playedPhoneClips.begin(); it != _playedPhoneClips.end(); ++it) { stream->writeString(it->_key); stream->writeByte(0); } // VSPicture stream->writeString(_nextVS); stream->writeByte(0); // In case the game was saved during a pause stream->writeString(_pausedSetting); stream->writeByte(0); // If we were playing a movie stream->writeString(_currentMovie); stream->writeByte(0); if (_videoDecoder) stream->writeUint32LE(_videoDecoder->getCurFrame()); else stream->writeUint32LE(0); return Common::kNoError; } Common::String PrivateEngine::convertPath(const Common::String &name) { Common::String path(name); Common::String s1("\\"); Common::String s2("/"); while (path.contains(s1)) Common::replace(path, s1, s2); s1 = Common::String("\""); s2 = Common::String(""); Common::replace(path, s1, s2); Common::replace(path, s1, s2); path.toLowercase(); return path; } void PrivateEngine::playSound(const Common::String &name, uint loops, bool stopOthers, bool background) { debugC(1, kPrivateDebugFunction, "%s(%s,%d,%d,%d)", __FUNCTION__, name.c_str(), loops, stopOthers, background); Common::File *file = new Common::File(); Common::String path = convertPath(name); if (!file->open(path)) error("unable to find sound file %s", path.c_str()); Audio::LoopingAudioStream *stream; stream = new Audio::LoopingAudioStream(Audio::makeWAVStream(file, DisposeAfterUse::YES), loops); if (stopOthers) { stopSound(true); } Audio::SoundHandle *sh = NULL; if (background) { _mixer->stopHandle(_bgSoundHandle); sh = &_bgSoundHandle; } else { _mixer->stopHandle(_fgSoundHandle); sh = &_fgSoundHandle; } _mixer->playStream(Audio::Mixer::kSFXSoundType, sh, stream, -1, Audio::Mixer::kMaxChannelVolume); } bool PrivateEngine::isSoundActive() { return _mixer->isSoundIDActive(-1); } void PrivateEngine::playVideo(const Common::String &name) { debugC(1, kPrivateDebugFunction, "%s(%s)", __FUNCTION__, name.c_str()); //stopSound(true); Common::File *file = new Common::File(); Common::String path = convertPath(name); if (!file->open(path)) error("unable to find video file %s", path.c_str()); if (!_videoDecoder->loadStream(file)) error("unable to load video %s", path.c_str()); _videoDecoder->start(); } void PrivateEngine::skipVideo() { _videoDecoder->close(); delete _videoDecoder; _videoDecoder = nullptr; _currentMovie = ""; } void PrivateEngine::stopSound(bool all) { debugC(1, kPrivateDebugFunction, "%s(%d)", __FUNCTION__, all); if (all) { _mixer->stopHandle(_fgSoundHandle); _mixer->stopHandle(_bgSoundHandle); } else { _mixer->stopHandle(_fgSoundHandle); } } Graphics::Surface *PrivateEngine::decodeImage(const Common::String &name) { debugC(1, kPrivateDebugFunction, "%s(%s)", __FUNCTION__, name.c_str()); Common::File file; Common::String path = convertPath(name); if (!file.open(path)) error("unable to load image %s", path.c_str()); _image->loadStream(file); return _image->getSurface()->convertTo(_pixelFormat, _image->getPalette()); } void PrivateEngine::loadImage(const Common::String &name, int x, int y) { debugC(1, kPrivateDebugFunction, "%s(%s,%d,%d)", __FUNCTION__, name.c_str(), x, y); Graphics::Surface *surf = decodeImage(name); _compositeSurface->transBlitFrom(*surf, _origin + Common::Point(x, y), _transparentColor); surf->free(); delete surf; _image->destroy(); } void PrivateEngine::fillRect(uint32 color, Common::Rect rect) { debugC(1, kPrivateDebugFunction, "%s(%d,..)", __FUNCTION__, color); rect.translate(_origin.x, _origin.y); _compositeSurface->fillRect(rect, color); } void PrivateEngine::drawScreenFrame() { g_system->copyRectToScreen(_frame->getPixels(), _frame->pitch, 0, 0, _screenW, _screenH); } Graphics::Surface *PrivateEngine::loadMask(const Common::String &name, int x, int y, bool drawn) { debugC(1, kPrivateDebugFunction, "%s(%s,%d,%d,%d)", __FUNCTION__, name.c_str(), x, y, drawn); Graphics::Surface *surf = new Graphics::Surface(); surf->create(_screenW, _screenH, _pixelFormat); surf->fillRect(screenRect, _transparentColor); Graphics::Surface *csurf = decodeImage(name); uint32 hdiff = 0; uint32 wdiff = 0; if (x + csurf->h > _screenH) hdiff = x + csurf->h - _screenH; if (y + csurf->w > _screenW) wdiff = y + csurf->w - _screenW; Common::Rect crect(csurf->w - wdiff, csurf->h - hdiff); surf->copyRectToSurface(*csurf, x, y, crect); csurf->free(); delete csurf; _image->destroy(); if (drawn) { drawMask(surf); } return surf; } void PrivateEngine::drawMask(Graphics::Surface *surf) { _compositeSurface->transBlitFrom(*surf, _origin, _transparentColor); } void PrivateEngine::drawScreen() { Graphics::ManagedSurface *surface = _compositeSurface; if (_videoDecoder && !_videoDecoder->isPaused()) { const Graphics::Surface *frame = _videoDecoder->decodeNextFrame(); Graphics::Surface *cframe = frame->convertTo(_pixelFormat, _videoDecoder->getPalette()); Common::Point center((_screenW - _videoDecoder->getWidth()) / 2, (_screenH - _videoDecoder->getHeight()) / 2); surface->blitFrom(*cframe, center); cframe->free(); delete cframe; } if (_mode == 1) { drawScreenFrame(); } Common::Rect w(_origin.x, _origin.y, _screenW - _origin.x, _screenH - _origin.y); Graphics::Surface sa = surface->getSubArea(w); g_system->copyRectToScreen(sa.getPixels(), sa.pitch, _origin.x, _origin.y, sa.w, sa.h); //if (_image->getPalette() != nullptr) // g_system->getPaletteManager()->setPalette(_image->getPalette(), _image->getPaletteStartIndex(), _image->getPaletteColorCount()); g_system->updateScreen(); } bool PrivateEngine::getRandomBool(uint p) { uint r = _rnd->getRandomNumber(100); return (r <= p); } Common::String PrivateEngine::getPaperShuffleSound() { uint r = _rnd->getRandomNumber(6); return Common::String::format("%sglsfx0%d.wav", _globalAudioPath.c_str(), kPaperShuffleSound[r]); } Common::String PrivateEngine::getTakeSound() { if (isDemo()) return (_globalAudioPath + "mvo007.wav"); uint r = _rnd->getRandomNumber(4) + 1; return Common::String::format("%stook%d.wav", _globalAudioPath.c_str(), r); } Common::String PrivateEngine::getTakeLeaveSound() { uint r = _rnd->getRandomNumber(1); if (r == 0) { return (_globalAudioPath + "mvo001.wav"); } else { return (_globalAudioPath + "mvo006.wav"); } } Common::String PrivateEngine::getLeaveSound() { if (isDemo()) return (_globalAudioPath + "mvo008.wav"); uint r = _rnd->getRandomNumber(4) + 1; return Common::String::format("%sleft%d.wav", _globalAudioPath.c_str(), r); } Common::String PrivateEngine::getRandomPhoneClip(const char *clip, int i, int j) { uint r = i + _rnd->getRandomNumber(j - i); return Common::String::format("%s%02d", clip, r); } // Timers static void timerCallback(void *refCon) { g_private->removeTimer(); g_private->_nextSetting = *(Common::String *)refCon; } bool PrivateEngine::installTimer(uint32 delay, Common::String *ns) { return g_system->getTimerManager()->installTimerProc(&timerCallback, delay, (void *)ns, "timerCallback"); } void PrivateEngine::removeTimer() { g_system->getTimerManager()->removeTimerProc(&timerCallback); } // Diary void PrivateEngine::loadLocations(const Common::Rect &rect) { uint32 i = 0; int16 offset = 44; for (NameList::const_iterator it = maps.locationList.begin(); it != maps.locationList.end(); ++it) { const Private::Symbol *sym = maps.locations.getVal(*it); i++; if (sym->u.val) { offset = offset + 22; Common::String s = Common::String::format("%sdryloc%d.bmp", _diaryLocPrefix.c_str(), i); loadMask(s, rect.left + 120, rect.top + offset, true); } } } void PrivateEngine::loadInventory(uint32 x, const Common::Rect &r1, const Common::Rect &r2) { int16 offset = 0; for (NameList::const_iterator it = inventory.begin(); it != inventory.end(); ++it) { offset = offset + 22; loadMask(*it, r1.left, r1.top + offset, true); } } } // End of namespace Private