/* 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/config-manager.h" #include "common/error.h" #include "common/events.h" #include "common/file.h" #include "common/fs.h" #include "common/textconsole.h" #include "common/translation.h" #include "base/plugins.h" #include "base/version.h" #include "gui/saveload.h" #include "pegasus/console.h" #include "pegasus/pegasus.h" //#define RUN_SUB_MOVIE // :D :D :D :D :D :D //#define RUN_INTERFACE_TEST namespace Pegasus { PegasusEngine::PegasusEngine(OSystem *syst, const PegasusGameDescription *gamedesc) : Engine(syst), _gameDescription(gamedesc) { } PegasusEngine::~PegasusEngine() { delete _video; delete _sound; delete _gfx; delete _resFork; delete _inventoryLid; delete _biochipLid; delete _console; } Common::Error PegasusEngine::run() { _console = new PegasusConsole(this); _gfx = new GraphicsManager(this); _video = new VideoManager(this); _sound = new SoundManager(this); _resFork = new Common::MacResManager(); _inventoryLid = new Common::MacResManager(); _biochipLid = new Common::MacResManager(); _gameMode = kIntroMode; _adventureMode = true; if (!_resFork->open("JMP PP Resources") || !_resFork->hasResFork()) error("Could not load JMP PP Resources"); if (!_inventoryLid->open("Images/Lids/Inventory Lid Sequence") || !_inventoryLid->hasResFork()) error("Could not open Inventory Lid Sequence"); if (!_biochipLid->open("Images/Lids/Biochip Lid Sequence") || !_biochipLid->hasResFork()) error("Could not open Biochip Lid Sequence"); loadItemLocationData(); if (!isDemo() && !detectOpeningClosingDirectory()) { Common::String message = "Missing intro directory. "; // Give Mac OS X a more specific message because we can #ifdef MACOSX message += "Make sure \"Opening/Closing\" is present."; #else message += "Be sure to rename \"Opening/Closing\" to \"Opening_Closing\"."; #endif GUIErrorMessage(message); warning("%s", message.c_str()); return Common::kNoGameDataFoundError; } #if 0 Common::MacResIDArray pictIds = _biochipLid->getResIDArray(MKID_BE('PICT')); for (uint32 i = 0; i < pictIds.size(); i++) { Common::String filename = Common::String::printf("PICT_%d.pict", pictIds[i]); Common::DumpFile file; assert(file.open(filename)); Common::SeekableReadStream *res = _biochipLid->getResource(MKID_BE('PICT'), pictIds[i]); byte *data = new byte[res->size()]; res->read(data, res->size()); for (int j = 0; j < 512; j++) file.writeByte(0); file.write(data, res->size()); file.close(); delete res; delete[] data; } #endif #if defined(RUN_SUB_MOVIE) _video->playMovie("Images/Norad Alpha/Sub Chase Movie"); #elif defined(RUN_INTERFACE_TEST) drawInterface(); _gfx->setCursor(kMainCursor); _sound->playSound("Sounds/Caldoria/Apartment Music.aiff", true); while (!shouldQuit()) { Common::Event event; // Ignore events for now while (_eventMan->pollEvent(event)) { if (event.type == Common::EVENT_MOUSEMOVE) _system->updateScreen(); } _system->delayMillis(10); } #else while (!shouldQuit()) { switch (_gameMode) { case kIntroMode: if (!isDemo()) runIntro(); _gameMode = kMainMenuMode; break; case kMainMenuMode: runMainMenu(); break; case kMainGameMode: if (isDemo()) changeLocation(kLocPrehistoric); else changeLocation(kLocCaldoria); mainGameLoop(); break; case kQuitMode: return Common::kNoError; default: _gameMode = kMainMenuMode; break; } } #endif return Common::kNoError; } bool PegasusEngine::detectOpeningClosingDirectory() { // We need to detect what our Opening/Closing directory is listed as // On the original disc, it was 'Opening/Closing' but only HFS(+) supports the slash // Mac OS X will display this as 'Opening:Closing' and we can use that directly // On other systems, users will need to rename to "Opening_Closing" Common::FSNode gameDataDir(ConfMan.get("path")); gameDataDir = gameDataDir.getChild("Images"); if (!gameDataDir.exists()) return false; Common::FSList fsList; if (!gameDataDir.getChildren(fsList, Common::FSNode::kListDirectoriesOnly, true)) return false; for (uint i = 0; i < fsList.size() && _introDirectory.empty(); i++) { Common::String name = fsList[i].getName(); if (name.equalsIgnoreCase("Opening:Closing")) _introDirectory = name; else if (name.equalsIgnoreCase("Opening_Closing")) _introDirectory = name; } if (_introDirectory.empty()) return false; debug(0, "Detected intro location as '%s'", _introDirectory.c_str()); _introDirectory = Common::String("Images/") + _introDirectory; return true; } void PegasusEngine::loadItemLocationData() { Common::SeekableReadStream *res = _resFork->getResource(MKTAG('N', 'I', 't', 'm'), 0x80); uint16 entryCount = res->readUint16BE(); for (uint16 i = 0; i < entryCount; i++) { ItemLocationData loc; loc.id = res->readUint16BE(); // Which is always == i, anyway loc.location = (ItemLocation)res->readUint16BE(); loc.u0 = res->readUint16BE(); loc.u1 = res->readByte(); debug(1, "Item[%d]: ID = %d, location = %x, u0 = %d, u1 = %d", i, loc.id, loc.location, loc.u0, loc.u1); res->readByte(); _itemLocationData.push_back(loc); } delete res; } void PegasusEngine::runIntro() { _video->playMovieCentered(_introDirectory + "/BandaiLogo.movie"); VideoHandle handle = _video->playBackgroundMovie(_introDirectory + "/Big Movie.movie"); _video->seekToTime(handle, 10 * 600); _video->waitUntilMovieEnds(handle); } void PegasusEngine::drawInterface() { _gfx->drawPict("Images/Interface/3DInterface Top", 0, 0, false); _gfx->drawPict("Images/Interface/3DInterface Left", 0, kViewScreenOffset, false); _gfx->drawPict("Images/Interface/3DInterface Right", 640 - kViewScreenOffset, kViewScreenOffset, false); _gfx->drawPict("Images/Interface/3DInterface Bottom", 0, kViewScreenOffset + 256, false); //drawCompass(); _system->updateScreen(); } void PegasusEngine::mainGameLoop() { // TODO: Yeah... _system->fillScreen(0); _video->playMovieCentered("Images/Caldoria/Pullback.movie"); drawInterface(); Common::String navMovie = Common::String::format("Images/%s/%s.movie", getTimeZoneFolder(_timeZone).c_str(), getTimeZoneDesc(_timeZone).c_str()); _video->playMovie(navMovie, kViewScreenOffset, kViewScreenOffset); _gameMode = kQuitMode; } void PegasusEngine::changeLocation(TimeZone timeZone) { _timeZone = timeZone; loadViews(_timeZone); //loadExits(_timeZone); loadDoors(_timeZone); //loadHSLs(_timeZone); //loadHSIn(_timeZone); loadSoundSpots(_timeZone); //loadTurns(_timeZone); loadZooms(_timeZone); loadExtras(_timeZone); } void PegasusEngine::loadViews(TimeZone timeZone) { _currentViews.clear(); Common::SeekableReadStream *res = _resFork->getResource(MKTAG('V', 'i', 'e', 'w'), getTimeZoneDesc(timeZone)); uint32 entryCount = res->readUint32BE(); for (uint32 i = 0; i < entryCount; i++) { View view; view.u0 = res->readUint16BE(); // Compass reading? view.u1 = res->readByte(); // Always 0-3, direction? view.u2 = res->readByte(); // Usually 0, rarely 3 view.frameTime = res->readUint32BE(); debug(1, "View[%d]: u0 = %d, u1 = %d, u2 = %d, time = %d", i, view.u0, view.u1, view.u2, view.frameTime); _currentViews.push_back(view); } delete res; } void PegasusEngine::loadDoors(TimeZone timeZone) { _currentDoors.clear(); Common::SeekableReadStream *res = _resFork->getResource(MKTAG('D', 'o', 'o', 'r'), getTimeZoneDesc(timeZone)); uint32 entryCount = res->readUint32BE(); for (uint32 i = 0; i < entryCount; i++) { Door door; door.u0 = res->readUint16BE(); door.u1 = res->readUint16BE(); // Always divisible by 256? door.startTime = res->readUint32BE(); door.endTime = res->readUint32BE(); door.u2 = res->readUint16BE(); debug(1, "Door[%d]: u0 = %d, u1 = %d, startTime = %d, endTime = %d, u2 = %d", i, door.u0, door.u1, door.startTime, door.endTime, door.u2); _currentDoors.push_back(door); } delete res; } void PegasusEngine::loadSoundSpots(TimeZone timeZone) { _currentSoundSpots.clear(); Common::SeekableReadStream *res = _resFork->getResource(MKTAG('S', 'p', 'o', 't'), getTimeZoneDesc(timeZone)); uint32 entryCount = res->readUint32BE(); for (uint32 i = 0; i < entryCount; i++) { SoundSpot spot; spot.u0 = res->readUint16BE(); spot.u1 = res->readUint16BE(); spot.u2 = res->readUint16BE(); // 0/1 or 768/769 spot.startTime = res->readUint32BE(); spot.endTime = res->readUint32BE(); spot.u3 = res->readUint16BE(); debug(1, "Sound Spot[%d]: u0 = %d, u1 = %d, u2 = %d, startTime = %d, endTime = %d, u3 = %d", i, spot.u0, spot.u1, spot.u2, spot.startTime, spot.endTime, spot.u3); _currentSoundSpots.push_back(spot); } delete res; } void PegasusEngine::loadZooms(TimeZone timeZone) { _currentZooms.clear(); Common::SeekableReadStream *res = _resFork->getResource(MKTAG('Z', 'o', 'o', 'm'), getTimeZoneDesc(timeZone)); uint32 entryCount = res->readUint32BE(); for (uint32 i = 0; i < entryCount; i++) { Zoom zoom; zoom.u0 = res->readUint16BE(); zoom.u1 = res->readUint16BE(); zoom.startTime = res->readUint32BE(); zoom.endTime = res->readUint32BE(); zoom.u2 = res->readUint16BE(); debug(1, "Zoom[%d]: u0 = %d, u1 = %d, startTime = %d, endTime = %d, u2 = %d", i, zoom.u0, zoom.u1, zoom.startTime, zoom.endTime, zoom.u2); _currentZooms.push_back(zoom); } delete res; } void PegasusEngine::loadExtras(TimeZone timeZone) { _currentExtras.clear(); Common::SeekableReadStream *res = _resFork->getResource(MKTAG('X', 't', 'r', 'a'), getTimeZoneDesc(timeZone)); uint32 entryCount = res->readUint32BE(); for (uint32 i = 0; i < entryCount; i++) { Extra extra; extra.u0 = res->readUint32BE(); extra.startTime = res->readUint32BE(); extra.endTime = res->readUint32BE(); debug(1, "Extra[%d]: u0 = %d, startTime = %d, endTime = %d", i, extra.u0, extra.startTime, extra.endTime); _currentExtras.push_back(extra); } delete res; } void PegasusEngine::showLoadDialog() { GUI::SaveLoadChooser slc(_("Load game:"), _("Load")); slc.setSaveMode(false); Common::String gameId = ConfMan.get("gameid"); const EnginePlugin *plugin = 0; EngineMan.findGame(gameId, &plugin); int slot = slc.runModalWithPluginAndTarget(plugin, ConfMan.getActiveDomainName()); if (slot >= 0) { warning("TODO: Load game"); _gameMode = kMainGameMode; } slc.close(); } Common::String PegasusEngine::getTimeZoneDesc(TimeZone timeZone) { static const char *names[] = { "Prehistoric", "Mars", "WSC", "Tiny TSA", "Full TSA", "Norad Alpha", "Caldoria", "Norad Delta" }; return names[timeZone]; } Common::String PegasusEngine::getTimeZoneFolder(TimeZone timeZone) { if (timeZone == kLocFullTSA || timeZone == kLocTinyTSA) return "TSA"; return getTimeZoneDesc(timeZone); } GUI::Debugger *PegasusEngine::getDebugger() { return _console; } } // End of namespace Pegasus