From 950676bf4c8576f63db8dc21a0c89e06981378d5 Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Sun, 20 Feb 2011 02:31:34 -0500 Subject: [PATCH] PEGASUS: Add my very WIP Pegasus Prime engine --- base/plugins.cpp | 3 + configure | 1 + engines/engines.mk | 5 + engines/pegasus/detection.cpp | 143 +++++++++++++++ engines/pegasus/graphics.cpp | 255 +++++++++++++++++++++++++++ engines/pegasus/graphics.h | 86 +++++++++ engines/pegasus/menu.cpp | 192 ++++++++++++++++++++ engines/pegasus/module.mk | 18 ++ engines/pegasus/pegasus.cpp | 317 ++++++++++++++++++++++++++++++++++ engines/pegasus/pegasus.h | 250 +++++++++++++++++++++++++++ engines/pegasus/sound.cpp | 88 ++++++++++ engines/pegasus/sound.h | 72 ++++++++ engines/pegasus/video.cpp | 240 +++++++++++++++++++++++++ engines/pegasus/video.h | 92 ++++++++++ 14 files changed, 1762 insertions(+) create mode 100644 engines/pegasus/detection.cpp create mode 100644 engines/pegasus/graphics.cpp create mode 100644 engines/pegasus/graphics.h create mode 100644 engines/pegasus/menu.cpp create mode 100644 engines/pegasus/module.mk create mode 100644 engines/pegasus/pegasus.cpp create mode 100644 engines/pegasus/pegasus.h create mode 100644 engines/pegasus/sound.cpp create mode 100644 engines/pegasus/sound.h create mode 100644 engines/pegasus/video.cpp create mode 100644 engines/pegasus/video.h diff --git a/base/plugins.cpp b/base/plugins.cpp index 1db9c0d499de..21db7ff023d8 100644 --- a/base/plugins.cpp +++ b/base/plugins.cpp @@ -139,6 +139,9 @@ class StaticPluginProvider : public PluginProvider { #if PLUGIN_ENABLED_STATIC(PARALLACTION) LINK_PLUGIN(PARALLACTION) #endif + #if PLUGIN_ENABLED_STATIC(PEGASUS) + LINK_PLUGIN(PEGASUS) + #endif #if PLUGIN_ENABLED_STATIC(QUEEN) LINK_PLUGIN(QUEEN) #endif diff --git a/configure b/configure index 714404130abd..75bb72652928 100755 --- a/configure +++ b/configure @@ -97,6 +97,7 @@ add_engine m4 "M4/MADS" no add_engine made "MADE" yes add_engine mohawk "Mohawk" no add_engine parallaction "Parallaction" yes +add_engine pegasus "The Journeyman Project: Pegasus Prime" no add_engine queen "Flight of the Amazon Queen" yes add_engine saga "SAGA" yes "ihnm saga2" add_engine ihnm "IHNM" yes diff --git a/engines/engines.mk b/engines/engines.mk index eea4ffc0b94a..5d62525a5683 100644 --- a/engines/engines.mk +++ b/engines/engines.mk @@ -104,6 +104,11 @@ DEFINES += -DENABLE_PARALLACTION=$(ENABLE_PARALLACTION) MODULES += engines/parallaction endif +ifdef ENABLE_PEGASUS +DEFINES += -DENABLE_PEGASUS=$(ENABLE_PEGASUS) +MODULES += engines/pegasus +endif + ifdef ENABLE_QUEEN DEFINES += -DENABLE_QUEEN=$(ENABLE_QUEEN) MODULES += engines/queen diff --git a/engines/pegasus/detection.cpp b/engines/pegasus/detection.cpp new file mode 100644 index 000000000000..17dd7c3486c1 --- /dev/null +++ b/engines/pegasus/detection.cpp @@ -0,0 +1,143 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "base/plugins.h" + +#include "engines/advancedDetector.h" +#include "common/config-manager.h" +#include "common/file.h" + +#include "pegasus/pegasus.h" + +namespace Pegasus { + +struct PegasusGameDescription { + ADGameDescription desc; +}; + +bool PegasusEngine::hasFeature(EngineFeature f) const { + return + (f == kSupportsRTL); +} + +bool PegasusEngine::isDemo() const { + return (_gameDescription->desc.flags & ADGF_DEMO) != 0; +} + +} // End of namespace Pegasus + +static const PlainGameDescriptor pegasusGames[] = { + {"pegasus", "The Journeyman Project: Pegasus Prime"}, + {0, 0} +}; + + +namespace Pegasus { + +static const PegasusGameDescription gameDescriptions[] = { + { + { + "pegasus", + "", + AD_ENTRY1s("JMP PP Resources", "d13a602d2498010d720a6534f097f88b", 2009943), + Common::EN_ANY, + Common::kPlatformMacintosh, + ADGF_MACRESFORK, + Common::GUIO_NONE + }, + }, + + { + { + "pegasus", + "", + AD_ENTRY1s("JMP PP Resources", "d13a602d2498010d720a6534f097f88b", 360129), + Common::EN_ANY, + Common::kPlatformMacintosh, + ADGF_MACRESFORK|ADGF_DEMO, + Common::GUIO_NONE + }, + }, + + { AD_TABLE_END_MARKER } +}; + +} // End of namespace Pegasus + +static const ADParams detectionParams = { + // Pointer to ADGameDescription or its superset structure + (const byte *)Pegasus::gameDescriptions, + // Size of that superset structure + sizeof(Pegasus::PegasusGameDescription), + // Number of bytes to compute MD5 sum for + 5000, + // List of all engine targets + pegasusGames, + // Structure for autoupgrading obsolete targets + 0, + // Name of single gameid (optional) + "pegasus", + // List of files for file-based fallback detection (optional) + 0, + // Flags + 0, + // Additional GUI options (for every game) + Common::GUIO_NONE, + // Maximum directory depth + 1, + // List of directory globs + 0 +}; + +class PegasusMetaEngine : public AdvancedMetaEngine { +public: + PegasusMetaEngine() : AdvancedMetaEngine(detectionParams) {} + + virtual const char *getName() const { + return "Pegasus Prime Engine"; + } + + virtual const char *getOriginalCopyright() const { + return "The Journeyman Project: Pegasus Prime (C) Presto Studios"; + } + + virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; +}; + +bool PegasusMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const { + const Pegasus::PegasusGameDescription *gd = (const Pegasus::PegasusGameDescription *)desc; + + if (gd) + *engine = new Pegasus::PegasusEngine(syst, gd); + + return (gd != 0); +} + +#if PLUGIN_ENABLED_DYNAMIC(PEGASUS) + REGISTER_PLUGIN_DYNAMIC(PEGASUS, PLUGIN_TYPE_ENGINE, PegasusMetaEngine); +#else + REGISTER_PLUGIN_STATIC(PEGASUS, PLUGIN_TYPE_ENGINE, PegasusMetaEngine); +#endif + diff --git a/engines/pegasus/graphics.cpp b/engines/pegasus/graphics.cpp new file mode 100644 index 000000000000..a05e316ee564 --- /dev/null +++ b/engines/pegasus/graphics.cpp @@ -0,0 +1,255 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "pegasus/graphics.h" + +#include "common/endian.h" +#include "common/file.h" +#include "engines/util.h" +#include "graphics/cursorman.h" + +namespace Pegasus { + +GraphicsManager::GraphicsManager(PegasusEngine *vm) : _vm(vm) { + initGraphics(640, 480, true, NULL); + + _pictDecoder = new Graphics::PictDecoder(_vm->_system->getScreenFormat()); + + for (int i = 0; i < kImageCacheSize; i++) + _cache[i].surface = 0; +} + +GraphicsManager::~GraphicsManager() { + delete _pictDecoder; + + for (int i = 0; i < kImageCacheSize; i++) { + if (_cache[i].surface) { + _cache[i].surface->free(); + delete _cache[i].surface; + } + } +} + +Graphics::Surface *GraphicsManager::decodeImage(const Common::String &filename) { + int imageSlot = getImageSlot(filename); + + if (_cache[imageSlot].surface) + return _cache[imageSlot].surface; + + Common::File file; + if (!file.open(filename)) + error("Could not open \'%s\'", filename.c_str()); + + byte palette[256 * 3]; + Graphics::Surface *image = _pictDecoder->decodeImage(&file, palette); + + // For <= 8bpp, we need to convert + if (image->bytesPerPixel == 1) { + Graphics::PixelFormat format = _vm->_system->getScreenFormat(); + Graphics::Surface *output = new Graphics::Surface(); + output->create(image->w, image->h, format.bytesPerPixel); + + for (uint16 y = 0; y < image->h; y++) { + for (uint16 x = 0; x < image->w; x++) { + byte c = *((byte *)image->getBasePtr(x, y)); + byte r = palette[c * 3]; + byte g = palette[c * 3 + 1]; + byte b = palette[c * 3 + 2]; + + if (format.bytesPerPixel == 2) { + uint16 color = format.RGBToColor(r, g, b); + memcpy(output->getBasePtr(x, y), &color, 2); + } else if (format.bytesPerPixel == 4) { + uint32 color = format.RGBToColor(r, g, b); + memcpy(output->getBasePtr(x, y), &color, 4); + } + } + } + + image->free(); + delete image; + image = output; + } + + _cache[imageSlot].surface = image; + return image; +} + +void GraphicsManager::drawPict(Common::String filename, int x, int y, bool updateScreen) { + Graphics::Surface *surface = decodeImage(filename); + + _vm->_system->copyRectToScreen((byte *)surface->pixels, surface->pitch, x, y, surface->w, surface->h); + + if (updateScreen) + _vm->_system->updateScreen(); +} + +void GraphicsManager::drawPictTransparent(Common::String filename, int x, int y, uint32 transparency, bool updateScreen) { + if (_vm->_system->getScreenFormat().bytesPerPixel == 2) + transparency &= 0xffff; + + Graphics::Surface *surface = decodeImage(filename); + Graphics::Surface *screen = _vm->_system->lockScreen(); + + for (uint16 i = 0; i < surface->h; i++) { + for (uint16 j = 0; j < surface->w; j++) { + if (_vm->_system->getScreenFormat().bytesPerPixel == 2) { + uint16 color = *((uint16 *)surface->getBasePtr(j, i)); + if (color != transparency) + memcpy(screen->getBasePtr(j + x, i + y), &color, 2); + } else if (_vm->_system->getScreenFormat().bytesPerPixel == 4) { + uint32 color = *((uint32 *)surface->getBasePtr(j, i)); + if (color != transparency) + memcpy(screen->getBasePtr(j + x, i + y), &color, 4); + } + } + } + + _vm->_system->unlockScreen(); + + if (updateScreen) + _vm->_system->updateScreen(); +} + +uint32 GraphicsManager::getColor(byte r, byte g, byte b) { + return _vm->_system->getScreenFormat().RGBToColor(r, g, b); +} + +void GraphicsManager::setCursor(uint16 cursor) { + Common::SeekableReadStream *cicnStream = _vm->_resFork->getResource(MKID_BE('cicn'), cursor); + + // PixMap section + Graphics::PictDecoder::PixMap pixMap = _pictDecoder->readPixMap(cicnStream); + + // Mask section + cicnStream->readUint32BE(); // mask baseAddr + uint16 maskRowBytes = cicnStream->readUint16BE(); // mask rowBytes + cicnStream->skip(3 * 2); // mask rect + /* uint16 maskHeight = */ cicnStream->readUint16BE(); + + // Bitmap section + cicnStream->readUint32BE(); // baseAddr + uint16 rowBytes = cicnStream->readUint16BE(); + cicnStream->readUint16BE(); // top + cicnStream->readUint16BE(); // left + uint16 height = cicnStream->readUint16BE(); // bottom + cicnStream->readUint16BE(); // right + + // Data section + cicnStream->readUint32BE(); // icon handle + cicnStream->skip(maskRowBytes * height); // FIXME: maskHeight doesn't work here, though the specs say it should + cicnStream->skip(rowBytes * height); + + // Palette section + cicnStream->readUint32BE(); // always 0 + cicnStream->readUint16BE(); // always 0 + uint16 colorCount = cicnStream->readUint16BE() + 1; + + byte *colors = (byte *)malloc(256 * 4); + for (uint16 i = 0; i < colorCount; i++) { + cicnStream->readUint16BE(); + colors[i * 4] = cicnStream->readByte(); + cicnStream->readByte(); + colors[i * 4 + 1] = cicnStream->readByte(); + cicnStream->readByte(); + colors[i * 4 + 2] = cicnStream->readByte(); + cicnStream->readByte(); + } + + // PixMap data + byte *data = (byte *)malloc(pixMap.rowBytes * pixMap.bounds.height()); + cicnStream->read(data, pixMap.rowBytes * pixMap.bounds.height()); + delete cicnStream; + + // Now to go get the hotspots + Common::SeekableReadStream *cursStream = NULL; + + if (cursor >= kMainCursor && cursor <= kGrabbingHand) + cursStream = _vm->_resFork->getResource(MKID_BE('Curs'), kMainCursor); + else // if (cursor == kTargetingReticle1 || cursor == kTargetingReticle2) + cursStream = _vm->_resFork->getResource(MKID_BE('Curs'), kTargetingReticle1); + + // Go through the stream until we find the right cursor hotspot + uint16 x = 0, y = 0; + uint16 numHotspots = cursStream->readUint16BE(); + + for (uint16 i = 0; i < numHotspots; i++) { + uint16 res = cursStream->readUint16BE(); + uint16 tempX = cursStream->readUint16BE(); + uint16 tempY = cursStream->readUint16BE(); + + if (res == cursor) { + x = tempX; + y = tempY; + break; + } + } + + // We have the bitmap and the hotspot, let's do this! + CursorMan.replaceCursorPalette(colors, 0, colorCount); + CursorMan.replaceCursor(data, pixMap.rowBytes, pixMap.bounds.height(), x, y, 0); + CursorMan.showMouse(true); + _vm->_system->updateScreen(); + + free(colors); + free(data); +} + +int GraphicsManager::getImageSlot(const Common::String &filename) { + // Let's find a match, an open slot, or the oldest image slot + uint32 oldestAge = 0xffffffff; + int slot = 0; + + for (int i = 0; i < kImageCacheSize; i++) { + if (_cache[i].filename.equalsIgnoreCase(filename)) { + //warning("Found image %s at slot %d", filename.c_str(), i); + _cache[i].lastUsed = _vm->_system->getMillis(); + return i; + } + + if (!_cache[i].surface) { + //warning("Putting image %s in empty slot %d", filename.c_str(), i); + _cache[i].filename = filename; + _cache[i].lastUsed = _vm->_system->getMillis(); + return i; + } + + if (_cache[i].lastUsed < oldestAge) { + oldestAge = _cache[i].lastUsed; + slot = i; + } + } + + // Let's make sure that's cleaned out + //warning("Replacing old image %s with %s in slot %d", _cache[slot].filename.c_str(), filename.c_str(), slot); + _cache[slot].filename = filename; + _cache[slot].surface->free(); + delete _cache[slot].surface; + _cache[slot].surface = 0; + _cache[slot].lastUsed = _vm->_system->getMillis(); + return slot; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/graphics.h b/engines/pegasus/graphics.h new file mode 100644 index 000000000000..c184172394bf --- /dev/null +++ b/engines/pegasus/graphics.h @@ -0,0 +1,86 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#ifndef PEGASUS_GRAPHICS_H +#define PEGASUS_GRAPHICS_H + +#include "common/rect.h" +#include "common/str.h" +#include "common/system.h" +#include "graphics/pict.h" +#include "graphics/surface.h" + +#include "pegasus/pegasus.h" + +namespace Pegasus { + +enum { + // The main cursors + kMainCursor = 128, + kZoomInCursor = 129, + kZoomOutCursor = 130, + kPointingCursor = 131, + kInteractHand = 132, + kGrabbingHand = 133, + + // Reticles when using the Mars shuttle + kTargetingReticle1 = 900, + kTargetingReticle2 = 901 +}; + +enum { + kImageCacheSize = 10 +}; + +struct ImageCache { + Common::String filename; + Graphics::Surface *surface; + uint32 lastUsed; +}; + +class PegasusEngine; + +class GraphicsManager { +public: + GraphicsManager(PegasusEngine *vm); + ~GraphicsManager(); + + void drawPict(Common::String filename, int x, int y, bool updateScreen = true); + void drawPictTransparent(Common::String filename, int x, int y, uint32 transparency, bool updateScreen = true); + void setCursor(uint16 cursor); + uint32 getColor(byte r, byte g, byte b); + +private: + PegasusEngine *_vm; + Graphics::PictDecoder *_pictDecoder; + + Graphics::Surface *decodeImage(const Common::String &filename); + ImageCache _cache[kImageCacheSize]; + int getImageSlot(const Common::String &filename); +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/menu.cpp b/engines/pegasus/menu.cpp new file mode 100644 index 000000000000..a498a65755d5 --- /dev/null +++ b/engines/pegasus/menu.cpp @@ -0,0 +1,192 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "common/events.h" + +#include "pegasus/pegasus.h" + +namespace Pegasus { + +enum { + kInterfaceOverviewButton = 0, + kStartButton = 1, + kRestoreButton = 2, + kDifficultyButton = 3, + kCreditsButton = 4, + kQuitButton = 5 +}; + +enum { + kDemoStartButton = 0, + kDemoCreditsButton = 1, + kDemoQuitButton = 2 +}; + +void PegasusEngine::runMainMenu() { + _sound->playSound("Sounds/Main Menu.aiff", true); + + // Note down how long since the last click + uint32 lastClickTime = _system->getMillis(); + + int buttonSelected = 0; + drawMenu(buttonSelected); + + while (!shouldQuit() && _system->getMillis() - lastClickTime < 60 * 1000) { + Common::Event event; + + // Ignore events for now + while (_eventMan->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_KEYDOWN: + switch (event.kbd.keycode) { + case Common::KEYCODE_UP: + if (buttonSelected > 0) { + buttonSelected--; + drawMenu(buttonSelected); + } + break; + case Common::KEYCODE_DOWN: + if ((isDemo() && buttonSelected < 2) || (!isDemo() && buttonSelected < 5)) { + buttonSelected++; + drawMenu(buttonSelected); + } + break; + case Common::KEYCODE_LEFT: + case Common::KEYCODE_RIGHT: + if (buttonSelected == kDifficultyButton) { + _adventureMode = !_adventureMode; + drawMenu(buttonSelected); + } + break; + case Common::KEYCODE_RETURN: + if (buttonSelected != kDifficultyButton) { + drawMenuButtonSelected(buttonSelected); + setGameMode(buttonSelected); + _sound->stopSound(); + return; + } + break; + default: + break; + } + + // Update our last press time too + lastClickTime = _system->getMillis(); + break; + default: + break; + } + } + + //_system->updateScreen(); + _system->delayMillis(10); + } + + if (shouldQuit()) + return; + + // Too slow! Go back and show the intro again. + _sound->stopSound(); + _video->playMovie("Images/Opening_Closing/LilMovie.movie"); +} + +void PegasusEngine::drawMenu(int buttonSelected) { + if (isDemo()) { + _gfx->drawPict("Images/Demo/DemoMenu.pict", 0, 0, false); + } else { + _gfx->drawPict("Images/Main Menu/MainMenu.mac", 0, 0, false); + if (!_adventureMode) + _gfx->drawPict("Images/Main Menu/BtnWlk.pict", 320, 340, false); + } + + drawMenuButtonHighlighted(buttonSelected); +} + +// FIXME: Most of these coordinates can use tweaking + +static const int kMainMenuButtonX = 152; +static const char s_mainMenuButtonSuffix[] = { 'L', 'S', 'S', 'L', 'S', 'S' }; +static const int s_mainMenuButtonY[] = { 202, 252, 292, 337, 382, 422 }; +static const char s_demoMainMenuButtonSuffix[] = { 'S', 'S', 'L' }; // SSL! +static const int s_demoMainMenuButtonX[] = { 38, 38, 28 }; +static const int s_demoMainMenuButtonY[] = { 332, 366, 408 }; + +void PegasusEngine::drawMenuButtonHighlighted(int buttonSelected) { + if (isDemo()) + _gfx->drawPictTransparent(Common::String("Images/Demo/Select") + s_demoMainMenuButtonSuffix[buttonSelected] + ".pict", s_demoMainMenuButtonX[buttonSelected], s_demoMainMenuButtonY[buttonSelected], _gfx->getColor(0xff, 0xff, 0xff), true); + else + _gfx->drawPictTransparent(Common::String("Images/Main Menu/Select") + s_mainMenuButtonSuffix[buttonSelected] + ".pict", kMainMenuButtonX, s_mainMenuButtonY[buttonSelected], _gfx->getColor(0xf8, 0xf8, 0xf8), true); +} + +static const char *s_mainMenuButtonSelSuffix[] = { "Overvi", "Start", "Restor", "", "Credit", "Quit" }; +static const char *s_demoMainMenuButtonSel[] = { "Start", "Credits", "Quit" }; +static const int s_mainMenuSelButtonX[] = { 198, 210, 210, 0, 210, 210 }; +static const int s_demoMainMenuSelButtonX[] = { 43, 43, 34 }; +static const int s_demoMainMenuSelButtonY[] = { 338, 373, 410 }; + +void PegasusEngine::drawMenuButtonSelected(int buttonSelected) { + if (isDemo()) + _gfx->drawPict(Common::String("Images/Demo/") + s_demoMainMenuButtonSel[buttonSelected] + ".pict", s_demoMainMenuSelButtonX[buttonSelected], s_demoMainMenuSelButtonY[buttonSelected], false); + else + _gfx->drawPict(Common::String("Images/Main Menu/pb") + s_mainMenuButtonSelSuffix[buttonSelected] + ".pict", s_mainMenuSelButtonX[buttonSelected], s_mainMenuButtonY[buttonSelected] + 5, false); + + drawMenuButtonHighlighted(buttonSelected); +} + +void PegasusEngine::setGameMode(int buttonSelected) { + if (isDemo()) { + switch (buttonSelected) { + case kDemoStartButton: + _gameMode = kMainGameMode; + break; + case kDemoCreditsButton: + _gameMode = kCreditsMode; + break; + case kDemoQuitButton: + _gameMode = kQuitMode; + break; + } + } else { + switch (buttonSelected) { + case kInterfaceOverviewButton: + _gameMode = kInterfaceOverviewMode; + break; + case kStartButton: + _gameMode = kMainGameMode; + break; + case kRestoreButton: + _gameMode = kRestoreMode; + break; + case kCreditsButton: + _gameMode = kCreditsMode; + break; + case kQuitButton: + _gameMode = kQuitMode; + break; + } + } +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/module.mk b/engines/pegasus/module.mk new file mode 100644 index 000000000000..28a92aa80025 --- /dev/null +++ b/engines/pegasus/module.mk @@ -0,0 +1,18 @@ +MODULE := engines/pegasus + +MODULE_OBJS = \ + detection.o \ + graphics.o \ + menu.o \ + pegasus.o \ + sound.o \ + video.o + + +# This module can be built as a plugin +ifeq ($(ENABLE_PEGASUS), DYNAMIC_PLUGIN) +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/pegasus/pegasus.cpp b/engines/pegasus/pegasus.cpp new file mode 100644 index 000000000000..062ec10ef901 --- /dev/null +++ b/engines/pegasus/pegasus.cpp @@ -0,0 +1,317 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "common/config-manager.h" +#include "common/events.h" +#include "base/plugins.h" +#include "base/version.h" + +#include "pegasus/pegasus.h" + +#include "common/file.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; +} + +Common::Error PegasusEngine::run() { + _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 = kMainMenuMode; + _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 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 kMainMenuMode: + if (!isDemo()) + runIntro(); + + 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; +} + +void PegasusEngine::loadItemLocationData() { + Common::SeekableReadStream *res = _resFork->getResource(MKID_BE('NItm'), 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() { + // The Opening/Closing folder will need to be renamed to something else. Windows + // and other OS's/FS's do not support a '/' in the filename. I arbitrarily chose + // to rename my folder with the underscore. + _video->playMovieCentered("Images/Opening_Closing/BandaiLogo.movie"); + VideoHandle handle = _video->playBackgroundMovie("Images/Opening_Closing/Big Movie.movie"); + _video->seekToTime(handle, 10 * 600); + _video->waitUntilMovieEnds(handle); +} + +static const int kViewScreenOffset = 64; + +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::drawInterfaceOverview() { + _gfx->drawPict("Images/Interface/OVTop.mac", 0, 0, false); + _gfx->drawPict("Images/Interface/OVLeft.mac", 0, kViewScreenOffset, false); + _gfx->drawPict("Images/Interface/OVRight.mac", 640 - kViewScreenOffset, kViewScreenOffset, false); + _gfx->drawPict("Images/Interface/OVBottom.mac", 0, kViewScreenOffset + 256, false); + _system->updateScreen(); +} + +void PegasusEngine::mainGameLoop() { + // TODO: Yeah... + _system->fillScreen(0); + _video->playMovieCentered("Images/Caldoria/Pullback.movie"); + drawInterface(); + if (isDemo()) + _video->playMovie("Images/Prehistoric/Prehistoric.movie", kViewScreenOffset, kViewScreenOffset); + else + _video->playMovie("Images/Caldoria/Caldoria.movie", 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(MKID_BE('View'), 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(MKID_BE('Door'), 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(MKID_BE('Spot'), 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(MKID_BE('Zoom'), 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(MKID_BE('Xtra'), 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; +} + +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]; +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/pegasus.h b/engines/pegasus/pegasus.h new file mode 100644 index 000000000000..b4f4d8f98e4b --- /dev/null +++ b/engines/pegasus/pegasus.h @@ -0,0 +1,250 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef PEGASUS_H +#define PEGASUS_H + +#include "common/macresman.h" +#include "common/scummsys.h" +#include "common/system.h" +#include "common/rect.h" +#include "common/util.h" + +#include "engines/engine.h" + +#include "pegasus/sound.h" +#include "pegasus/graphics.h" +#include "pegasus/video.h" + +namespace Pegasus { + +struct PegasusGameDescription; +class SoundManager; +class VideoManager; +class GraphicsManager; + +enum ItemLocation { + kItemLocationCaldoria = 0, + kItemLocationTSA = 1, + kItemLocationNorad = 4, // ??? + kItemLocationMars = 5, + kItemLocationWSC = 6, + kItemLocationPrehistoric = 7, + kItemLocationBuiltIn = 0xffff +}; + +struct ItemLocationData { + uint16 id; + ItemLocation location; + uint16 u0; + byte u1; +}; + +struct View { + uint16 u0; + byte u1; + byte u2; + uint32 frameTime; +}; + +struct Door { + uint16 u0; + uint16 u1; + uint32 startTime; + uint32 endTime; + uint16 u2; +}; + +struct SoundSpot { + uint16 u0; + uint16 u1; + uint16 u2; + uint32 startTime; + uint32 endTime; + uint16 u3; +}; + +struct Zoom { + uint16 u0; + uint32 startTime; + uint32 endTime; + uint16 u1; + uint16 u2; +}; + +struct Extra { + uint32 u0; + uint32 startTime; + uint32 endTime; +}; + +struct InventoryPanelEntry { + uint32 startTime; + uint32 endTime; +}; + +struct InventoryItemData { + uint32 leftFrameTime; + uint32 rightStartTime; + uint32 rightEndTime; + uint16 pict; // Cannot use item at this spot + uint16 usablePict; // Can use item at this spot +}; + +struct InventoryExtra { + uint32 id; + uint16 movie; + uint32 startTime; + uint32 endTime; +}; + +struct LeftAreaData { + uint16 frame; + uint32 time; +}; + +struct MiddleAreaData { + uint16 id; + uint32 time; +}; + +struct RightAreaData { + uint16 frame; + uint32 time; +}; + +enum TimeZone { + kLocPrehistoric = 0, + kLocMars = 1, + kLocWSC = 2, + kLocTinyTSA = 3, + kLocFullTSA = 4, + kLocNoradAlpha = 5, + kLocCaldoria = 6, + kLocNoradDelta = 7 +}; + +// Taken from JMP PP Resources +enum Item { + kAIBiochip = 128, + kInterfaceBiochip = 129, // NOT USED! + kMapBiochip = 130, + kOpticalBiochip = 131, + kPegasusBiochip = 132, + kRetinalScanBiochip = 133, + kShieldBiochip = 134, + kAirMask = 135, + kAntidote = 136, + kArgonCanister = 137, + kCardBomb = 138, + kCrowbar = 139, + kGasCanister = 140, + kHistoricalLog = 141, + kJourneymanKey = 142, + kKeyCard = 143, + kMachineGun = 144, // What the hell is this? + kMarsCard = 145, + kNitrogenCanister = 146, + kOrangeJuiceGlassFull = 147, + kOrangeJuiceGlassEmpty = 148, + kPoisonDart = 149, + kSinclairKey = 150, + kStunGun = 151, + kArgonPickup = 152 // ??? +}; + +enum GameMode { + kMainMenuMode, + kMainGameMode, + kCreditsMode, + kInterfaceOverviewMode, + kRestoreMode, + kQuitMode +}; + +class PegasusEngine : public ::Engine { +protected: + Common::Error run(); + +public: + PegasusEngine(OSystem *syst, const PegasusGameDescription *gamedesc); + virtual ~PegasusEngine(); + + const PegasusGameDescription *_gameDescription; + bool hasFeature(EngineFeature f) const; + + VideoManager *_video; + SoundManager *_sound; + GraphicsManager *_gfx; + Common::MacResManager *_resFork, *_inventoryLid, *_biochipLid; + + bool isDemo() const; + +private: + // Intro + void runIntro(); + void runMainMenu(); + void drawMenu(int buttonSelected); + void drawMenuButtonHighlighted(int buttonSelected); + void drawMenuButtonSelected(int buttonSelected); + //void runInterfaceOverview(); + void setGameMode(int buttonSelected); + + // Interface + void drawInterface(); + //void drawCompass(); + //void runPauseMenu(); + + // Interface Overview + void drawInterfaceOverview(); + + // Main Game Functions + void mainGameLoop(); + void loadItemLocationData(); + void changeLocation(TimeZone timeZone); + void loadViews(TimeZone timeZone); + void loadDoors(TimeZone timeZone); + void loadSoundSpots(TimeZone timeZone); + void loadZooms(TimeZone timeZone); + void loadExtras(TimeZone timeZone); + + // Misc Functions + static Common::String getTimeZoneDesc(TimeZone timeZone); + + // Game Variables + bool _adventureMode; + GameMode _gameMode; + TimeZone _timeZone; + Common::Array _itemLocationData; + Common::Array _currentViews; + Common::Array _currentDoors; + Common::Array _currentSoundSpots; + Common::Array _currentZooms; + Common::Array _currentExtras; +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/sound.cpp b/engines/pegasus/sound.cpp new file mode 100644 index 000000000000..c03e4be2b590 --- /dev/null +++ b/engines/pegasus/sound.cpp @@ -0,0 +1,88 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "pegasus/sound.h" + +#include "common/file.h" +#include "audio/decoders/aiff.h" + +namespace Pegasus { + +SoundManager::SoundManager(PegasusEngine *vm) : _vm(vm) { +} + +void SoundManager::playSound(Common::String filename, bool loop) { + SndHandle *handle = getHandle(); + handle->type = kUsedHandle; + + Common::File *file = new Common::File(); + if (!file->open(filename.c_str())) + error("Could not open file \'%s\'", filename.c_str()); + + Audio::AudioStream* audStream = Audio::makeAIFFStream(file, DisposeAfterUse::YES); + + if (loop) + audStream = Audio::makeLoopingAudioStream((Audio::RewindableAudioStream*)audStream, 0); + + if (audStream) + _vm->_mixer->playStream(Audio::Mixer::kPlainSoundType, &handle->handle, audStream); +} + +SndHandle *SoundManager::getHandle() { + for (int i = 0; i < SOUND_HANDLES; i++) { + if (_handles[i].type == kFreeHandle) + return &_handles[i]; + + if (!_vm->_mixer->isSoundHandleActive(_handles[i].handle)) { + _handles[i].type = kFreeHandle; + return &_handles[i]; + } + } + + error("SoundManager::getHandle(): Too many sound handles"); + return NULL; +} + +bool SoundManager::isPlaying() { + for (int i = 0; i < SOUND_HANDLES; i++) + if (_handles[i].type == kUsedHandle) + if (_vm->_mixer->isSoundHandleActive(_handles[i].handle)) + return true; + return false; +} + +void SoundManager::stopSound() { + _vm->_mixer->stopAll(); +} + +void SoundManager::pauseSound() { + _vm->_mixer->pauseAll(true); +} + +void SoundManager::resumeSound() { + _vm->_mixer->pauseAll(false); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/sound.h b/engines/pegasus/sound.h new file mode 100644 index 000000000000..adfb502c6c9d --- /dev/null +++ b/engines/pegasus/sound.h @@ -0,0 +1,72 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef PEGASUS_SOUND_H +#define PEGASUS_SOUND_H + +#include "common/scummsys.h" +#include "common/str.h" + +#include "audio/audiostream.h" +#include "audio/mixer.h" + +#include "pegasus/pegasus.h" + +namespace Pegasus { + +#define SOUND_HANDLES 10 + +enum sndHandleType { + kFreeHandle, + kUsedHandle +}; + +struct SndHandle { + Audio::SoundHandle handle; + sndHandleType type; +}; + +class PegasusEngine; + +class SoundManager { +public: + SoundManager(PegasusEngine *vm); + + void playSound(Common::String filename, bool loop = false); + void stopSound(); + void pauseSound(); + void resumeSound(); + bool isPlaying(); + +private: + PegasusEngine *_vm; + + SndHandle _handles[SOUND_HANDLES]; + SndHandle *getHandle(); +}; + +} // End of namespace Pegasus + +#endif diff --git a/engines/pegasus/video.cpp b/engines/pegasus/video.cpp new file mode 100644 index 000000000000..925c6d77a944 --- /dev/null +++ b/engines/pegasus/video.cpp @@ -0,0 +1,240 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "pegasus/pegasus.h" +#include "pegasus/video.h" + +#include "common/events.h" +#include "graphics/scaler.h" +#include "video/qt_decoder.h" + +namespace Pegasus { + +VideoManager::VideoManager(PegasusEngine *vm) : _vm(vm) { + _timeZoneVideo = new Video::QuickTimeDecoder(); +} + +VideoManager::~VideoManager() { + stopVideos(); + delete _timeZoneVideo; +} + +void VideoManager::setTimeZoneVideo(const Common::String &filename) { + if (!_timeZoneVideo->loadFile(filename)) + error("Could not load time zone video '%s'", filename.c_str()); + + // Set it on pause + _timeZoneVideo->pauseVideo(true); +} + +void VideoManager::drawTimeZoneVideoFrame(uint32 time) { + assert(_timeZoneVideo->isVideoLoaded()); + + if (!_timeZoneVideo->isPaused()) + _timeZoneVideo->pauseVideo(true); + + _timeZoneVideo->seekToTime(Audio::Timestamp(0, time, 600)); + + const Graphics::Surface *frame = _timeZoneVideo->decodeNextFrame(); + + if (!frame) + error("Could not find frame at time %d", time); + + // TODO +} + +void VideoManager::playTimeZoneVideoSegment(uint32 startTime, uint32 endTime) { + assert(_timeZoneVideo->isVideoLoaded()); + + if (_timeZoneVideo->isPaused()) + _timeZoneVideo->pauseVideo(false); + + _timeZoneVideo->seekToTime(Audio::Timestamp(0, startTime, 600)); + + // TODO +} + +void VideoManager::pauseVideos() { + for (uint16 i = 0; i < _videoStreams.size(); i++) + _videoStreams[i]->pauseVideo(true); +} + +void VideoManager::resumeVideos() { + for (uint16 i = 0; i < _videoStreams.size(); i++) + _videoStreams[i]->pauseVideo(false); +} + +void VideoManager::stopVideos() { + for (uint16 i = 0; i < _videoStreams.size(); i++) { + delete _videoStreams[i].video; + _videoStreams[i].video = 0; + } +} + +void VideoManager::playMovie(Common::String filename, uint16 x, uint16 y) { + VideoHandle videoHandle = playBackgroundMovie(filename, x, y, false); + + if (videoHandle != NULL_VID_HANDLE) + waitUntilMovieEnds(videoHandle); +} + +void VideoManager::playMovieCentered(Common::String filename) { + VideoHandle videoHandle = playBackgroundMovie(filename, 0, 0, false); + + if (videoHandle == NULL_VID_HANDLE) + return; + + _videoStreams[videoHandle].x = (_vm->_system->getWidth() - _videoStreams[videoHandle]->getWidth()) / 2; + _videoStreams[videoHandle].y = (_vm->_system->getHeight() - _videoStreams[videoHandle]->getHeight()) / 2; + + waitUntilMovieEnds(videoHandle); +} + +void VideoManager::waitUntilMovieEnds(VideoHandle videoHandle) { + bool continuePlaying = true; + + while (!_videoStreams[videoHandle]->endOfVideo() && !_vm->shouldQuit() && continuePlaying) { + if (updateBackgroundMovies()) + _vm->_system->updateScreen(); + + Common::Event event; + while (_vm->_system->getEventManager()->pollEvent(event)) { + switch (event.type) { + case Common::EVENT_RTL: + case Common::EVENT_QUIT: + continuePlaying = false; + break; + case Common::EVENT_KEYDOWN: + switch (event.kbd.keycode) { + case Common::KEYCODE_ESCAPE: + continuePlaying = false; + break; + default: + break; + } + default: + break; + } + } + + // Cut down on CPU usage + _vm->_system->delayMillis(10); + } + + delete _videoStreams[videoHandle].video; + _videoStreams.clear(); +} + +bool VideoManager::updateBackgroundMovies() { + bool updateScreen = false; + + for (uint32 i = 0; i < _videoStreams.size() && !_vm->shouldQuit(); i++) { + // Skip deleted videos + if (!_videoStreams[i].video) + continue; + + // Remove any videos that are over + if (_videoStreams[i]->endOfVideo()) { + if (_videoStreams[i].loop) { + _videoStreams[i]->rewind(); + } else { + delete _videoStreams[i].video; + memset(&_videoStreams[i], 0, sizeof(VideoEntry)); + _videoStreams[i].video = NULL; + continue; + } + } + + // Check if we need to draw a frame + if (_videoStreams[i]->needsUpdate()) { + const Graphics::Surface *frame = _videoStreams[i]->decodeNextFrame(); + + if (frame) { + if (frame->bytesPerPixel == 1) + error("Unhandled 8bpp frames"); // Cut out because Pegasus Prime shouldn't need this + + // Clip the width/height to make sure we stay on the screen + uint16 width = MIN(_videoStreams[i]->getWidth(), _vm->_system->getWidth() - _videoStreams[i].x); + uint16 height = MIN(_videoStreams[i]->getHeight(), _vm->_system->getHeight() - _videoStreams[i].y); + + if (width == 320 && height == 240) { + // TODO: Is this right? At least "Big Movie" and the "Sub Chase Movie" need to be scaled... + // FIXME: Normal2x is only compiled in when USE_SCALERS is defined + _videoStreams[i].x = 0; + _videoStreams[i].y = 0; + Graphics::Surface scaledSurf; + scaledSurf.create(frame->w * 2, frame->h * 2, frame->bytesPerPixel); + Normal2x((byte *)frame->pixels, frame->pitch, (byte *)scaledSurf.pixels, scaledSurf.pitch, frame->w, frame->h); + _vm->_system->copyRectToScreen((byte*)scaledSurf.pixels, scaledSurf.pitch, _videoStreams[i].x, _videoStreams[i].y, width * 2, height * 2); + scaledSurf.free(); + } else + _vm->_system->copyRectToScreen((byte*)frame->pixels, frame->pitch, _videoStreams[i].x, _videoStreams[i].y, width, height); + + + // We've drawn something to the screen, make sure we update it + updateScreen = true; + } + } + } + + // Return true if we need to update the screen + return updateScreen; +} + +VideoHandle VideoManager::playBackgroundMovie(Common::String filename, int x, int y, bool loop) { + // First, check to see if that video is already playing + for (uint32 i = 0; i < _videoStreams.size(); i++) + if (_videoStreams[i].filename == filename) + return i; + + // Otherwise, create a new entry + VideoEntry entry; + entry.video = new Video::QuickTimeDecoder(); + entry.x = x; + entry.y = y; + entry.filename = filename; + entry.loop = loop; + + if (!entry->loadFile(filename)) + return NULL_VID_HANDLE; + + // Search for any deleted videos so we can take a formerly used slot + for (uint32 i = 0; i < _videoStreams.size(); i++) + if (!_videoStreams[i].video) { + _videoStreams[i] = entry; + return i; + } + + // Otherwise, just add it to the list + _videoStreams.push_back(entry); + return _videoStreams.size() - 1; +} + +void VideoManager::seekToTime(VideoHandle handle, uint32 time) { + if (handle != NULL_VID_HANDLE) + _videoStreams[handle]->seekToTime(Audio::Timestamp(0, time, 600)); +} + +} // End of namespace Pegasus diff --git a/engines/pegasus/video.h b/engines/pegasus/video.h new file mode 100644 index 000000000000..03f0a51bf2f6 --- /dev/null +++ b/engines/pegasus/video.h @@ -0,0 +1,92 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#ifndef PEGASUS_VIDEO_H +#define PEGASUS_VIDEO_H + +#include "common/array.h" + +namespace Common { + class String; +} + +namespace Video { + class QuickTimeDecoder; +} + +namespace Pegasus { + +class PegasusEngine; + +struct VideoEntry { + Video::QuickTimeDecoder *video; + uint16 x; + uint16 y; + bool loop; + Common::String filename; + + Video::QuickTimeDecoder *operator->() const { assert(video); return video; } +}; + +typedef int32 VideoHandle; + +enum { + NULL_VID_HANDLE = -1 +}; + +class VideoManager { +public: + VideoManager(PegasusEngine *vm); + ~VideoManager(); + + void setTimeZoneVideo(const Common::String &filename); + void drawTimeZoneVideoFrame(uint32 time); + void playTimeZoneVideoSegment(uint32 startTime, uint32 endTime); + + // Generic movie functions + void playMovie(Common::String filename, uint16 x = 0, uint16 y = 0); + void playMovieCentered(Common::String filename); + VideoHandle playBackgroundMovie(Common::String filename, int x = 0, int y = 0, bool loop = false); + bool updateBackgroundMovies(); + void pauseVideos(); + void resumeVideos(); + void stopVideos(); + void waitUntilMovieEnds(VideoHandle videoHandle); + + void seekToTime(VideoHandle handle, uint32 time); + +private: + PegasusEngine *_vm; + + Video::QuickTimeDecoder *_timeZoneVideo; + + // Keep tabs on any videos playing + Common::Array _videoStreams; + uint32 _pauseStart; +}; + +} // End of namespace Pegasus + +#endif