aboutsummaryrefslogtreecommitdiff
path: root/engines/supernova/supernova.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/supernova/supernova.cpp')
-rw-r--r--engines/supernova/supernova.cpp1156
1 files changed, 1156 insertions, 0 deletions
diff --git a/engines/supernova/supernova.cpp b/engines/supernova/supernova.cpp
new file mode 100644
index 0000000000..a9f29748f9
--- /dev/null
+++ b/engines/supernova/supernova.cpp
@@ -0,0 +1,1156 @@
+/* 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/mods/protracker.h"
+#include "common/config-manager.h"
+#include "common/debug.h"
+#include "common/debug-channels.h"
+#include "common/endian.h"
+#include "common/error.h"
+#include "common/events.h"
+#include "common/file.h"
+#include "common/fs.h"
+#include "common/memstream.h"
+#include "common/savefile.h"
+#include "common/str.h"
+#include "common/system.h"
+#include "common/translation.h"
+#include "engines/util.h"
+#include "graphics/cursorman.h"
+#include "graphics/surface.h"
+#include "graphics/screen.h"
+#include "graphics/palette.h"
+#include "graphics/thumbnail.h"
+#include "gui/saveload.h"
+
+#include "supernova/supernova.h"
+#include "supernova/state.h"
+
+namespace Supernova {
+
+const AudioInfo audioInfo[kAudioNumSamples] = {
+ {44, 0, -1},
+ {45, 0, -1},
+ {46, 0, 2510},
+ {46, 2510, 4020},
+ {46, 4020, -1},
+ {47, 0, 24010},
+ {47, 24010, -1},
+ {48, 0, 2510},
+ {48, 2510, 10520},
+ {48, 10520, 13530},
+ {48, 13530, -1},
+ {50, 0, 12786},
+ {50, 12786, -1},
+ {51, 0, -1},
+ {53, 0, -1},
+ {54, 0, 8010},
+ {54, 8010, 24020},
+ {54, 24020, 30030},
+ {54, 30030, 31040},
+ {54, 31040, -1}
+};
+
+const Object Object::nullObject;
+
+ObjectType operator|(ObjectType a, ObjectType b) {
+ return static_cast<ObjectType>(+a | +b);
+}
+
+ObjectType operator&(ObjectType a, ObjectType b) {
+ return static_cast<ObjectType>(+a & +b);
+}
+
+ObjectType operator^(ObjectType a, ObjectType b) {
+ return static_cast<ObjectType>(+a ^ +b);
+}
+
+ObjectType &operator|=(ObjectType &a, ObjectType b) {
+ return a = a | b;
+}
+
+ObjectType &operator&=(ObjectType &a, ObjectType b) {
+ return a = a & b;
+}
+
+ObjectType &operator^=(ObjectType &a, ObjectType b) {
+ return a = a ^ b;
+}
+
+SupernovaEngine::SupernovaEngine(OSystem *syst)
+ : Engine(syst)
+ , _console(NULL)
+ , _gm(NULL)
+ , _currentImage(NULL)
+ , _soundMusicIntro(NULL)
+ , _soundMusicOutro(NULL)
+ , _rnd("supernova")
+ , _brightness(255)
+ , _menuBrightness(255)
+ , _delay(33)
+ , _textSpeed(kTextSpeed[2])
+ , _screenWidth(320)
+ , _screenHeight(200)
+ , _messageDisplayed(false)
+ , _allowLoadGame(true)
+ , _allowSaveGame(true)
+{
+// const Common::FSNode gameDataDir(ConfMan.get("path"));
+// SearchMan.addSubDirectoryMatching(gameDataDir, "sound");
+
+ if (ConfMan.hasKey("textspeed"))
+ _textSpeed = ConfMan.getInt("textspeed");
+
+ // setup engine specific debug channels
+ DebugMan.addDebugChannel(kDebugGeneral, "general", "Supernova general debug channel");
+}
+
+SupernovaEngine::~SupernovaEngine() {
+ DebugMan.clearAllDebugChannels();
+
+ delete _currentImage;
+ delete _console;
+ delete _gm;
+ delete _soundMusicIntro;
+ delete _soundMusicOutro;
+}
+
+Common::Error SupernovaEngine::run() {
+ Graphics::ModeList modes;
+ modes.push_back(Graphics::Mode(320, 200));
+ modes.push_back(Graphics::Mode(640, 480));
+ initGraphicsModes(modes);
+ initGraphics(_screenWidth, _screenHeight);
+
+ Common::Error status = loadGameStrings();
+ if (status.getCode() != Common::kNoError)
+ return status;
+
+ _gm = new GameManager(this);
+ _console = new Console(this, _gm);
+
+ initData();
+ initPalette();
+
+ CursorMan.replaceCursor(_mouseNormal, 16, 16, 0, 0, kColorCursorTransparent);
+ CursorMan.replaceCursorPalette(initVGAPalette, 0, 16);
+ CursorMan.showMouse(true);
+
+ setTotalPlayTime(0);
+
+ int saveSlot = ConfMan.getInt("save_slot");
+ if (saveSlot >= 0) {
+ if (loadGameState(saveSlot).getCode() != Common::kNoError)
+ error("Failed to load save game from slot %i", saveSlot);
+ }
+
+ while (!shouldQuit()) {
+ uint32 start = _system->getMillis();
+ updateEvents();
+ _gm->executeRoom();
+ _console->onFrame();
+ _system->updateScreen();
+ int end = _delay - (_system->getMillis() - start);
+ if (end > 0)
+ _system->delayMillis(end);
+ }
+
+ stopSound();
+
+ return Common::kNoError;
+}
+
+void SupernovaEngine::updateEvents() {
+ _gm->handleTime();
+ if (_gm->_animationEnabled && !_messageDisplayed && _gm->_animationTimer == 0)
+ _gm->_currentRoom->animation();
+
+ if (_gm->_state._eventCallback != kNoFn && _gm->_state._time >= _gm->_state._eventTime) {
+ _allowLoadGame = false;
+ _allowSaveGame = false;
+ _gm->_state._eventTime = kMaxTimerValue;
+ EventFunction fn = _gm->_state._eventCallback;
+ _gm->_state._eventCallback = kNoFn;
+ switch (fn) {
+ case kNoFn:
+ break;
+ case kSupernovaFn:
+ _gm->supernovaEvent();
+ break;
+ case kGuardReturnedFn:
+ _gm->guardReturnedEvent();
+ break;
+ case kGuardWalkFn:
+ _gm->guardWalkEvent();
+ break;
+ case kTaxiFn:
+ _gm->taxiEvent();
+ break;
+ case kSearchStartFn:
+ _gm->searchStartEvent();
+ break;
+ }
+ _allowLoadGame = true;
+ _allowSaveGame = true;
+ return;
+ }
+
+ if (_gm->_state._alarmOn && _gm->_state._timeAlarm <= _gm->_state._time) {
+ _gm->_state._alarmOn = false;
+ _gm->alarm();
+ return;
+ }
+
+ _gm->_mouseClicked = false;
+ _gm->_keyPressed = false;
+ Common::Event event;
+ while (g_system->getEventManager()->pollEvent(event)) {
+ switch (event.type) {
+ case Common::EVENT_KEYDOWN:
+ _gm->_keyPressed = true;
+ if (event.kbd.keycode == Common::KEYCODE_d &&
+ (event.kbd.flags & Common::KBD_CTRL)) {
+ _console->attach();
+ }
+ if (event.kbd.keycode == Common::KEYCODE_x &&
+ (event.kbd.flags & Common::KBD_CTRL)) {
+ // TODO: Draw exit box
+ }
+
+ _gm->processInput(event.kbd);
+ break;
+
+ case Common::EVENT_LBUTTONUP:
+ // fallthrough
+ case Common::EVENT_RBUTTONUP:
+ if (_gm->_currentRoom->getId() != INTRO && _mixer->isSoundHandleActive(_soundHandle))
+ return;
+ _gm->_mouseClicked = true;
+ // fallthrough
+ case Common::EVENT_MOUSEMOVE:
+ _gm->_mouseClickType = event.type;
+ _gm->_mouseX = event.mouse.x;
+ _gm->_mouseY = event.mouse.y;
+ if (_gm->_guiEnabled)
+ _gm->processInput();
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+bool SupernovaEngine::hasFeature(EngineFeature f) const {
+ switch (f) {
+ case kSupportsRTL:
+ return true;
+ case kSupportsLoadingDuringRuntime:
+ return true;
+ case kSupportsSavingDuringRuntime:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void SupernovaEngine::pauseEngineIntern(bool pause) {
+ _mixer->pauseAll(pause);
+ _gm->pauseTimer(pause);
+}
+
+Common::Error SupernovaEngine::loadGameStrings() {
+ Common::String cur_lang = ConfMan.get("language");
+ Common::String string_id("TEXT");
+
+ // Note: we don't print any warning or errors here if we cannot find the file
+ // or the format is not as expected. We will get those warning when reading the
+ // strings anyway (actually the engine will even refuse to start).
+ Common::File f;
+ if (!f.open(SUPERNOVA_DAT)) {
+ Common::String msg = Common::String::format(_("Unable to locate the '%s' engine data file."), SUPERNOVA_DAT);
+ GUIErrorMessage(msg);
+ return Common::kReadingFailed;
+ }
+
+ // Validate the data file header
+ char id[5], lang[5];
+ id[4] = lang[4] = '\0';
+ f.read(id, 3);
+ if (strncmp(id, "MSN", 3) != 0) {
+ Common::String msg = Common::String::format(_("The '%s' engine data file is corrupt."), SUPERNOVA_DAT);
+ GUIErrorMessage(msg);
+ return Common::kReadingFailed;
+ }
+
+ int version = f.readByte();
+ if (version != SUPERNOVA_DAT_VERSION) {
+ Common::String msg = Common::String::format(
+ _("Incorrect version of the '%s' engine data file found. Expected %d but got %d."),
+ SUPERNOVA_DAT, SUPERNOVA_DAT_VERSION, version);
+ GUIErrorMessage(msg);
+ return Common::kReadingFailed;
+ }
+
+ while (!f.eos()) {
+ f.read(id, 4);
+ f.read(lang, 4);
+ uint32 size = f.readUint32LE();
+ if (f.eos())
+ break;
+ if (string_id == id && cur_lang == lang) {
+ while (size > 0) {
+ Common::String s;
+ char ch;
+ while ((ch = (char)f.readByte()) != '\0')
+ s += ch;
+ _gameStrings.push_back(s);
+ size -= s.size() + 1;
+ }
+ return Common::kNoError;
+ } else
+ f.skip(size);
+ }
+
+ Common::Language l = Common::parseLanguage(cur_lang);
+ Common::String msg = Common::String::format(_("Unable to locate the text for %s language in '%s' engine data file."), Common::getLanguageDescription(l), SUPERNOVA_DAT);
+ GUIErrorMessage(msg);
+ return Common::kReadingFailed;
+}
+
+void SupernovaEngine::initData() {
+ // Sound
+ // Note:
+ // - samples start with a header of 6 bytes: 01 SS SS 00 AD 00
+ // where SS SS (LE uint16) is the size of the sound sample + 2
+ // - samples end with a footer of 4 bytes: 00 00
+ // Skip those in the buffer
+ Common::File file;
+
+ for (int i = 0; i < kAudioNumSamples; ++i) {
+ if (!file.open(Common::String::format("msn_data.%03d", audioInfo[i]._filenumber))) {
+ error("File %s could not be read!", file.getName());
+ }
+
+ if (audioInfo[i]._offsetEnd == -1) {
+ file.seek(0, SEEK_END);
+ _soundSamples[i]._length = file.pos() - audioInfo[i]._offsetStart - 10;
+ } else {
+ _soundSamples[i]._length = audioInfo[i]._offsetEnd - audioInfo[i]._offsetStart - 10;
+ }
+ _soundSamples[i]._buffer = new byte[_soundSamples[i]._length];
+ file.seek(audioInfo[i]._offsetStart + 6);
+ file.read(_soundSamples[i]._buffer, _soundSamples[i]._length);
+ file.close();
+ }
+
+ _soundMusicIntro = convertToMod("msn_data.049");
+ _soundMusicOutro = convertToMod("msn_data.052");
+
+ // Cursor
+ const uint16 *bufferNormal = reinterpret_cast<const uint16 *>(mouseNormal);
+ const uint16 *bufferWait = reinterpret_cast<const uint16 *>(mouseWait);
+ for (uint i = 0; i < sizeof(mouseNormal) / 4; ++i) {
+ for (uint bit = 0; bit < 16; ++bit) {
+ uint mask = 0x8000 >> bit;
+ uint bitIndex = i * 16 + bit;
+
+ _mouseNormal[bitIndex] = (READ_LE_UINT16(bufferNormal + i) & mask) ? kColorCursorTransparent : kColorBlack;
+ if (READ_LE_UINT16(bufferNormal + i + 16) & mask)
+ _mouseNormal[bitIndex] = kColorLightRed;
+ _mouseWait[bitIndex] = (READ_LE_UINT16(bufferWait + i) & mask) ? kColorCursorTransparent : kColorBlack;
+ if (READ_LE_UINT16(bufferWait + i + 16) & mask)
+ _mouseWait[bitIndex] = kColorLightRed;
+ }
+ }
+}
+
+void SupernovaEngine::initPalette() {
+ _system->getPaletteManager()->setPalette(initVGAPalette, 0, 256);
+}
+
+void SupernovaEngine::playSound(AudioIndex sample) {
+ if (sample > kAudioNumSamples - 1)
+ return;
+
+ Audio::SeekableAudioStream *audioStream = Audio::makeRawStream(
+ _soundSamples[sample]._buffer, _soundSamples[sample]._length,
+ 11931, Audio::FLAG_UNSIGNED | Audio::FLAG_LITTLE_ENDIAN, DisposeAfterUse::NO);
+ stopSound();
+ _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, audioStream);
+}
+
+void SupernovaEngine::stopSound() {
+ if (_mixer->isSoundHandleActive(_soundHandle))
+ _mixer->stopHandle(_soundHandle);
+}
+
+void SupernovaEngine::playSoundMod(int filenumber)
+{
+ Audio::AudioStream *audioStream;
+ if (filenumber == 49)
+ audioStream = Audio::makeProtrackerStream(_soundMusicIntro);
+ else if (filenumber == 52)
+ audioStream = Audio::makeProtrackerStream(_soundMusicOutro);
+ else
+ return;
+
+ stopSound();
+ _mixer->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, audioStream,
+ -1, Audio::Mixer::kMaxChannelVolume, 0);
+}
+
+void SupernovaEngine::renderImageSection(int section) {
+ // Note: inverting means we are removing the section. So we should get the rect for that
+ // section but draw the background (section 0) instead.
+ bool invert = false;
+ if (section > 128) {
+ section -= 128;
+ invert = true;
+ }
+ if (!_currentImage || section > _currentImage->_numSections - 1)
+ return;
+
+ Common::Rect sectionRect(_currentImage->_section[section].x1,
+ _currentImage->_section[section].y1,
+ _currentImage->_section[section].x2 + 1,
+ _currentImage->_section[section].y2 + 1) ;
+ if (_currentImage->_filenumber == 1 || _currentImage->_filenumber == 2) {
+ sectionRect.setWidth(640);
+ sectionRect.setHeight(480);
+ if (_screenWidth != 640) {
+ _screenWidth = 640;
+ _screenHeight = 480;
+ initGraphics(_screenWidth, _screenHeight);
+ }
+ } else {
+ if (_screenWidth != 320) {
+ _screenWidth = 320;
+ _screenHeight = 200;
+ initGraphics(_screenWidth, _screenHeight);
+ }
+ }
+
+ uint offset = 0;
+ int pitch = sectionRect.width();
+ if (invert) {
+ pitch = _currentImage->_pitch;
+ offset = _currentImage->_section[section].y1 * pitch + _currentImage->_section[section].x1;
+ section = 0;
+ }
+
+ _system->copyRectToScreen(static_cast<const byte *>(_currentImage->_sectionSurfaces[section]->getPixels()) + offset,
+ pitch,
+ sectionRect.left, sectionRect.top,
+ sectionRect.width(), sectionRect.height());
+}
+
+void SupernovaEngine::renderImage(int section) {
+ if (!_currentImage)
+ return;
+
+ bool sectionVisible = true;
+
+ if (section > 128) {
+ sectionVisible = false;
+ section -= 128;
+ }
+
+ _gm->_currentRoom->setSectionVisible(section, sectionVisible);
+
+ do {
+ if (sectionVisible)
+ renderImageSection(section);
+ else
+ renderImageSection(section + 128);
+ section = _currentImage->_section[section].next;
+ } while (section != 0);
+}
+
+bool SupernovaEngine::setCurrentImage(int filenumber) {
+ if (_currentImage && _currentImage->_filenumber == filenumber)
+ return true;
+
+ delete _currentImage;
+ _currentImage = new MSNImageDecoder();
+ if (!_currentImage->init(filenumber)) {
+ delete _currentImage;
+ _currentImage = NULL;
+ return false;
+ }
+
+ _system->getPaletteManager()->setPalette(_currentImage->getPalette(), 16, 239);
+ paletteBrightness();
+ return true;
+}
+
+void SupernovaEngine::saveScreen(int x, int y, int width, int height) {
+ _screenBuffer.push(x, y, width, height);
+}
+
+void SupernovaEngine::restoreScreen() {
+ _screenBuffer.restore();
+}
+
+void SupernovaEngine::renderRoom(Room &room) {
+ if (room.getId() == INTRO)
+ return;
+
+ if (setCurrentImage(room.getFileNumber())) {
+ for (int i = 0; i < _currentImage->_numSections; ++i) {
+ int section = i;
+ if (room.isSectionVisible(section)) {
+ do {
+ renderImageSection(section);
+ section = _currentImage->_section[section].next;
+ } while (section != 0);
+ }
+ }
+ }
+}
+
+int SupernovaEngine::textWidth(const uint16 key) {
+ char text[2];
+ text[0] = key & 0xFF;
+ text[1] = 0;
+ return textWidth(text);
+}
+
+int SupernovaEngine::textWidth(const char *text) {
+ int charWidth = 0;
+ while (*text != '\0') {
+ byte c = *text++;
+ if (c < 32) {
+ continue;
+ } else if (c == 225) {
+ c = 35;
+ }
+
+ for (uint i = 0; i < 5; ++i) {
+ if (font[c - 32][i] == 0xff) {
+ break;
+ }
+ ++charWidth;
+ }
+ ++charWidth;
+ }
+
+ return charWidth;
+}
+
+void SupernovaEngine::renderMessage(const char *text, MessagePosition position) {
+ Common::String t(text);
+ char *row[20];
+ Common::String::iterator p = t.begin();
+ uint numRows = 0;
+ int rowWidthMax = 0;
+ int x = 0;
+ int y = 0;
+ byte textColor = 0;
+
+ while (*p != '\0') {
+ row[numRows] = p;
+ ++numRows;
+ while ((*p != '\0') && (*p != '|')) {
+ ++p;
+ }
+ if (*p == '|') {
+ *p = '\0';
+ ++p;
+ }
+ }
+ for (uint i = 0; i < numRows; ++i) {
+ int rowWidth = textWidth(row[i]);
+ if (rowWidth > rowWidthMax)
+ rowWidthMax = rowWidth;
+ }
+
+ switch (position) {
+ case kMessageNormal:
+ x = 160 - rowWidthMax / 2;
+ textColor = kColorWhite99;
+ break;
+ case kMessageTop:
+ x = 160 - rowWidthMax / 2;
+ textColor = kColorLightYellow;
+ break;
+ case kMessageCenter:
+ x = 160 - rowWidthMax / 2;
+ textColor = kColorLightRed;
+ break;
+ case kMessageLeft:
+ x = 3;
+ textColor = kColorLightYellow;
+ break;
+ case kMessageRight:
+ x = 317 - rowWidthMax;
+ textColor = kColorLightGreen;
+ break;
+ }
+
+ if (position == kMessageNormal) {
+ y = 70 - ((numRows * 9) / 2);
+ } else if (position == kMessageTop) {
+ y = 5;
+ } else {
+ y = 142;
+ }
+
+ int message_columns = x - 3;
+ int message_rows = y - 3;
+ int message_width = rowWidthMax + 6;
+ int message_height = numRows * 9 + 5;
+ saveScreen(message_columns, message_rows, message_width, message_height);
+ renderBox(message_columns, message_rows, message_width, message_height, kColorWhite35);
+ for (uint i = 0; i < numRows; ++i) {
+ renderText(row[i], x, y, textColor);
+ y += 9;
+ }
+
+ _messageDisplayed = true;
+ _gm->_timer1 = (Common::strnlen(text, 512) + 20) * _textSpeed / 10;
+}
+
+void SupernovaEngine::removeMessage() {
+ if (_messageDisplayed) {
+ restoreScreen();
+ _messageDisplayed = false;
+ }
+}
+
+void SupernovaEngine::renderText(const char *text, int x, int y, byte color) {
+ Graphics::Surface *screen = _system->lockScreen();
+ byte *cursor = static_cast<byte *>(screen->getBasePtr(x, y));
+ const byte *basePtr = cursor;
+
+ byte c;
+ while ((c = *text++) != '\0') {
+ if (c < 32) {
+ continue;
+ } else if (c == 225) {
+ c = 128;
+ }
+
+ for (uint i = 0; i < 5; ++i) {
+ if (font[c - 32][i] == 0xff) {
+ break;
+ }
+
+ byte *ascentLine = cursor;
+ for (byte j = font[c - 32][i]; j != 0; j >>= 1) {
+ if (j & 1) {
+ *cursor = color;
+ }
+ cursor += kScreenWidth;
+ }
+ cursor = ++ascentLine;
+ }
+ ++cursor;
+ }
+ _system->unlockScreen();
+
+ uint numChars = cursor - basePtr;
+ uint absPosition = y * kScreenWidth + x + numChars;
+ _textCursorX = absPosition % kScreenWidth;
+ _textCursorY = absPosition / kScreenWidth;
+ _textColor = color;
+}
+
+void SupernovaEngine::renderText(const uint16 character, int x, int y, byte color) {
+ char text[2];
+ text[0] = character & 0xFF;
+ text[1] = 0;
+ renderText(text, x, y, color);
+}
+
+void SupernovaEngine::renderText(const char *text) {
+ renderText(text, _textCursorX, _textCursorY, _textColor);
+}
+
+void SupernovaEngine::renderText(const uint16 character) {
+ char text[2];
+ text[0] = character & 0xFF;
+ text[1] = 0;
+ renderText(text, _textCursorX, _textCursorY, _textColor);
+}
+
+void SupernovaEngine::renderBox(int x, int y, int width, int height, byte color) {
+ Graphics::Surface *screen = _system->lockScreen();
+ screen->fillRect(Common::Rect(x, y, x + width, y + height), color);
+ _system->unlockScreen();
+}
+
+void SupernovaEngine::paletteBrightness() {
+ byte palette[768];
+
+ _system->getPaletteManager()->grabPalette(palette, 0, 255);
+ for (uint i = 0; i < 48; ++i) {
+ palette[i] = (initVGAPalette[i] * _menuBrightness) >> 8;
+ }
+ for (uint i = 0; i < 717; ++i) {
+ const byte *imagePalette;
+ if (_currentImage && _currentImage->getPalette()) {
+ imagePalette = _currentImage->getPalette();
+ } else {
+ imagePalette = palette + 48;
+ }
+ palette[i + 48] = (imagePalette[i] * _brightness) >> 8;
+ }
+ _system->getPaletteManager()->setPalette(palette, 0, 255);
+}
+
+void SupernovaEngine::paletteFadeOut() {
+ while (_menuBrightness > 10) {
+ _menuBrightness -= 10;
+ if (_brightness > _menuBrightness)
+ _brightness = _menuBrightness;
+ paletteBrightness();
+ _system->updateScreen();
+ _system->delayMillis(_delay);
+ }
+ _menuBrightness = 0;
+ _brightness = 0;
+ paletteBrightness();
+ _system->updateScreen();
+}
+
+void SupernovaEngine::paletteFadeIn() {
+ while (_menuBrightness < 245) {
+ if (_brightness < _gm->_roomBrightness)
+ _brightness += 10;
+ _menuBrightness += 10;
+ paletteBrightness();
+ _system->updateScreen();
+ _system->delayMillis(_delay);
+ }
+ _menuBrightness = 255;
+ _brightness = _gm->_roomBrightness;
+ paletteBrightness();
+ _system->updateScreen();
+}
+
+void SupernovaEngine::setColor63(byte value) {
+ byte color[3] = {value, value, value};
+ _system->getPaletteManager()->setPalette(color, 63, 1);
+}
+
+void SupernovaEngine::setTextSpeed() {
+ const Common::String& textSpeedString = getGameString(kStringTextSpeed);
+ int stringWidth = textWidth(textSpeedString);
+ int textX = (320 - stringWidth) / 2;
+ int textY = 100;
+ stringWidth += 4;
+ int boxX = stringWidth > 110 ? (320 - stringWidth) / 2 : 105;
+ int boxY = 97;
+ int boxWidth = stringWidth > 110 ? stringWidth : 110;
+ int boxHeight = 27;
+
+ _gm->animationOff();
+ _gm->saveTime();
+ saveScreen(boxX, boxY, boxWidth, boxHeight);
+
+ renderBox(boxX, boxY, boxWidth, boxHeight, kColorBlue);
+ renderText(textSpeedString, textX, textY, kColorWhite99); // Text speed
+
+ // Find the closest index in kTextSpeed for the current _textSpeed.
+ // Important note: values in kTextSpeed decrease with the index.
+ int speedIndex = 0;
+ while (speedIndex < 4 && _textSpeed < (kTextSpeed[speedIndex] + kTextSpeed[speedIndex+1]) / 2)
+ ++speedIndex;
+
+ char nbString[2];
+ nbString[1] = 0;
+ for (int i = 0; i < 5; ++i) {
+ byte color = i == speedIndex ? kColorWhite63 : kColorWhite35;
+ renderBox(110 + 21 * i, 111, 16, 10, color);
+
+ nbString[0] = '1' + i;
+ renderText(nbString, 115 + 21 * i, 112, kColorWhite99);
+ }
+ do {
+ _gm->getInput();
+ int key = _gm->_keyPressed ? _gm->_key.keycode : Common::KEYCODE_INVALID;
+ if (!_gm->_keyPressed && _gm->_mouseClicked && _gm->_mouseY >= 111 && _gm->_mouseY < 121 && (_gm->_mouseX + 16) % 21 < 16)
+ key = Common::KEYCODE_0 - 5 + (_gm->_mouseX + 16) / 21;
+ if (key == Common::KEYCODE_ESCAPE)
+ break;
+ else if (key >= Common::KEYCODE_1 && key <= Common::KEYCODE_5) {
+ speedIndex = key - Common::KEYCODE_1;
+ _textSpeed = kTextSpeed[speedIndex];
+ ConfMan.setInt("textspeed", _textSpeed);
+ break;
+ }
+ } while (!shouldQuit());
+ _gm->resetInputState();
+
+ restoreScreen();
+ _gm->loadTime();
+ _gm->animationOn();
+}
+
+Common::MemoryReadStream *SupernovaEngine::convertToMod(const char *filename, int version) {
+ // MSN format
+ struct {
+ uint16 seg;
+ uint16 start;
+ uint16 end;
+ uint16 loopStart;
+ uint16 loopEnd;
+ char volume;
+ char dummy[5];
+ } instr2[22];
+ int nbInstr2; // 22 for version1, 15 for version 2
+ int16 songLength;
+ char arrangement[128];
+ int16 patternNumber;
+ int32 note2[28][64][4];
+
+ nbInstr2 = ((version == 1) ? 22 : 15);
+
+ Common::File msnFile;
+ msnFile.open(filename);
+ if (!msnFile.isOpen()) {
+ warning("Data file '%s' not found", msnFile.getName());
+ return NULL;
+ }
+
+ for (int i = 0 ; i < nbInstr2 ; ++i) {
+ instr2[i].seg = msnFile.readUint16LE();
+ instr2[i].start = msnFile.readUint16LE();
+ instr2[i].end = msnFile.readUint16LE();
+ instr2[i].loopStart = msnFile.readUint16LE();
+ instr2[i].loopEnd = msnFile.readUint16LE();
+ instr2[i].volume = msnFile.readByte();
+ msnFile.read(instr2[i].dummy, 5);
+ }
+ songLength = msnFile.readSint16LE();
+ msnFile.read(arrangement, 128);
+ patternNumber = msnFile.readSint16LE();
+ for (int p = 0 ; p < patternNumber ; ++p) {
+ for (int n = 0 ; n < 64 ; ++n) {
+ for (int k = 0 ; k < 4 ; ++k) {
+ note2[p][n][k] = msnFile.readSint32LE();
+ }
+ }
+ }
+
+ /* MOD format */
+ struct {
+ char iname[22];
+ uint16 length;
+ char finetune;
+ char volume;
+ uint16 loopStart;
+ uint16 loopLength;
+ } instr[31];
+ int32 note[28][64][4];
+
+ // We can't recover some MOD effects since several of them are mapped to 0.
+ // Assume the MSN effect of value 0 is Arpeggio (MOD effect of value 0).
+ const char invConvEff[8] = {0, 1, 2, 3, 10, 12, 13 ,15};
+
+ // Reminder from convertToMsn
+ // 31 30 29 28 27 26 25 24 - 23 22 21 20 19 18 17 16 - 15 14 13 12 11 10 09 08 - 07 06 05 04 03 02 01 00
+ // h h h h g g g g f f f f e e e e d d d d c c c c b b b b a a a a
+ //
+ // MSN:
+ // hhhh (4 bits) Cleared to 0
+ // dddd c (5 bits) Sample index | after mapping through convInstr
+ // ccc (3 bits) Effect type | after mapping through convEff
+ // bbbb aaaa (8 bits) Effect value | unmodified
+ // gggg ffff eeee (12 bits) Sample period | unmodified
+ //
+ // MS2:
+ // hhhh (4 bits) Cleared to 0
+ // dddd (4 bits) Sample index | after mapping through convInstr
+ // cccc (4 bits) Effect type | unmodified
+ // bbbb aaaa (8 bits) Effect value | unmodified
+ // gggg ffff eeee (12 bits) Sample period | transformed (0xE000 / p) - 256
+ //
+ // MOD:
+ // hhhh dddd (8 bits) Sample index
+ // cccc (4 bits) Effect type for this channel/division
+ // bbbb aaaa (8 bits) Effect value
+ // gggg ffff eeee (12 bits) Sample period
+
+ // Can we recover the instruments mapping? I don't think so as part of the original instrument index is cleared.
+ // And it doesn't really matter as long as we are consistent.
+ // However we need to make sure 31 (or 15 in MS2) is mapped to 0 in MOD.
+ // We just add 1 to all other values, and this means a 1 <-> 1 mapping for the instruments
+ for (int p = 0; p < patternNumber; ++p) {
+ for (int n = 0; n < 64; ++n) {
+ for (int k = 0; k < 4; ++k) {
+ int32* l = &(note[p][n][k]);
+ *l = note2[p][n][k];
+ int32 i = 0;
+ if (nbInstr2 == 22) { // version 1
+ i = ((*l & 0xF800) >> 11);
+ int32 e = ((*l & 0x0700) >> 8);
+ int32 e1 = invConvEff[e];
+ *l &= 0x0FFF00FF;
+ *l |= (e1 << 8);
+ } else { // version 2
+ int32 h = (*l >> 16);
+ i = ((*l & 0xF000) >> 12);
+ *l &= 0x00000FFF;
+ if (h)
+ h = 0xE000 / (h + 256);
+ *l |= (h << 16);
+ if (i == 15)
+ i = 31;
+ }
+
+ // Add back index in note
+ if (i != 31) {
+ ++i;
+ *l |= ((i & 0x0F) << 12);
+ *l |= ((i & 0xF0) << 24);
+ }
+ }
+ }
+ }
+
+ for (int i = 0; i < 31; ++i) {
+ // iname is not stored in the mod file. Just set it to 'instrument#'
+ // finetune is not stored either. Assume 0.
+ memset(instr[i].iname, 0, 22);
+ sprintf(instr[i].iname, "instrument%d", i+1);
+ instr[i].length = 0;
+ instr[i].finetune = 0;
+ instr[i].volume = 0;
+ instr[i].loopStart = 0;
+ instr[i].loopLength = 0;
+
+ if (i < nbInstr2) {
+ instr[i].length = ((instr2[i].end - instr2[i].start) >> 1);
+ instr[i].loopStart = ((instr2[i].loopStart - instr2[i].start) >> 1);
+ instr[i].loopLength = (( instr2[i].loopEnd - instr2[i].loopStart) >> 1);
+ instr[i].volume = instr2[i].volume;
+ }
+ }
+
+ // The ciaaSpeed is kind of useless and not present in the MSN file.
+ // Traditionally 0x78 in SoundTracker. Was used in NoiseTracker as a restart point.
+ // ProTracker uses 0x7F. FastTracker uses it as a restart point, whereas ScreamTracker 3 uses 0x7F like ProTracker.
+ // You can use this to roughly detect which tracker made a MOD, and detection gets more accurate for more obscure MOD types.
+ char ciaaSpeed = 0x7F;
+
+ // The mark cannot be recovered either. Since we have 4 channels and 31 instrument it can be either ID='M.K.' or ID='4CHN'.
+ // Assume 'M.K.'
+ const char mark[4] = { 'M', '.', 'K', '.' };
+
+ Common::MemoryWriteStreamDynamic buffer(DisposeAfterUse::NO);
+
+ buffer.write(msnFile.getName(), 19);
+ buffer.writeByte(0);
+
+ for (int i = 0 ; i < 31 ; ++i) {
+ buffer.write(instr[i].iname, 22);
+ buffer.writeUint16BE(instr[i].length);
+ buffer.writeByte(instr[i].finetune);
+ buffer.writeByte(instr[i].volume);
+ buffer.writeUint16BE(instr[i].loopStart);
+ buffer.writeUint16BE(instr[i].loopLength);
+ }
+ buffer.writeByte((char)songLength);
+ buffer.writeByte(ciaaSpeed);
+ buffer.write(arrangement, 128);
+ buffer.write(mark, 4);
+
+ for (int p = 0 ; p < patternNumber ; ++p) {
+ for (int n = 0 ; n < 64 ; ++n) {
+ for (int k = 0 ; k < 4 ; ++k) {
+// buffer.writeUint32BE(*((uint32*)(note[p][n]+k)));
+ buffer.writeSint32BE(note[p][n][k]);
+ }
+ }
+ }
+
+ uint nb;
+ char buf[4096];
+ while ((nb = msnFile.read(buf, 4096)) > 0)
+ buffer.write(buf, nb);
+
+ return new Common::MemoryReadStream(buffer.getData(), buffer.size(), DisposeAfterUse::YES);
+}
+
+bool SupernovaEngine::canLoadGameStateCurrently() {
+ return _allowLoadGame;
+}
+
+Common::Error SupernovaEngine::loadGameState(int slot) {
+ return (loadGame(slot) ? Common::kNoError : Common::kReadingFailed);
+}
+
+bool SupernovaEngine::canSaveGameStateCurrently() {
+ // Do not allow saving when either _allowSaveGame, _animationEnabled or _guiEnabled is false
+ return _allowSaveGame && _gm->_animationEnabled && _gm->_guiEnabled;
+}
+
+Common::Error SupernovaEngine::saveGameState(int slot, const Common::String &desc) {
+ return (saveGame(slot, desc) ? Common::kNoError : Common::kWritingFailed);
+}
+
+bool SupernovaEngine::loadGame(int slot) {
+ if (slot < 0)
+ return false;
+
+ Common::String filename = Common::String::format("msn_save.%03d", slot);
+ Common::InSaveFile *savefile = _saveFileMan->openForLoading(filename);
+ if (!savefile)
+ return false;
+
+ uint saveHeader = savefile->readUint32LE();
+ if (saveHeader != SAVEGAME_HEADER) {
+ warning("No header found in '%s'", filename.c_str());
+ delete savefile;
+ return false; //Common::kUnknownError
+ }
+
+ byte saveVersion = savefile->readByte();
+ // Save version 1 was used during development and is no longer supported
+ if (saveVersion > SAVEGAME_VERSION || saveVersion == 1) {
+ warning("Save game version %i not supported", saveVersion);
+ delete savefile;
+ return false; //Common::kUnknownError;
+ }
+
+ // Make sure no message is displayed as this would otherwise delay the
+ // switch to the new location until a mouse click.
+ removeMessage();
+
+ int descriptionSize = savefile->readSint16LE();
+ savefile->skip(descriptionSize);
+ savefile->skip(6);
+ setTotalPlayTime(savefile->readUint32LE() * 1000);
+ Graphics::skipThumbnail(*savefile);
+ _gm->deserialize(savefile, saveVersion);
+
+ if (saveVersion >= 5) {
+ _menuBrightness = savefile->readByte();
+ _brightness = savefile->readByte();
+ } else {
+ _menuBrightness = _brightness = 255;
+ }
+
+ delete savefile;
+
+ return true;
+}
+
+bool SupernovaEngine::saveGame(int slot, const Common::String &description) {
+ if (slot < 0)
+ return false;
+
+ Common::String filename = Common::String::format("msn_save.%03d", slot);
+ Common::OutSaveFile *savefile = _saveFileMan->openForSaving(filename);
+ if (!savefile)
+ return false;
+
+ savefile->writeUint32LE(SAVEGAME_HEADER);
+ savefile->writeByte(SAVEGAME_VERSION);
+
+ TimeDate currentDate;
+ _system->getTimeAndDate(currentDate);
+ uint32 saveDate = (currentDate.tm_mday & 0xFF) << 24 | ((currentDate.tm_mon + 1) & 0xFF) << 16 | ((currentDate.tm_year + 1900) & 0xFFFF);
+ uint16 saveTime = (currentDate.tm_hour & 0xFF) << 8 | ((currentDate.tm_min) & 0xFF);
+
+ savefile->writeSint16LE(description.size() + 1);
+ savefile->write(description.c_str(), description.size() + 1);
+ savefile->writeUint32LE(saveDate);
+ savefile->writeUint16LE(saveTime);
+ savefile->writeUint32LE(getTotalPlayTime() / 1000);
+ Graphics::saveThumbnail(*savefile);
+ _gm->serialize(savefile);
+
+ savefile->writeByte(_menuBrightness);
+ savefile->writeByte(_brightness);
+
+ savefile->finalize();
+ delete savefile;
+
+ return true;
+}
+
+void SupernovaEngine::errorTempSave(bool saving) {
+ GUIErrorMessage(saving
+ ? "Failed to save temporary game state. Make sure your save game directory is set in ScummVM and that you can write to it."
+ : "Failed to load temporary game state.");
+ error("Unrecoverable error");
+}
+
+ScreenBufferStack::ScreenBufferStack()
+ : _last(_buffer) {
+}
+
+void ScreenBufferStack::push(int x, int y, int width, int height) {
+ if (_last == ARRAYEND(_buffer))
+ return;
+
+ Graphics::Surface* screenSurface = g_system->lockScreen();
+
+ if (x < 0) {
+ width += x;
+ x = 0;
+ }
+ if (x + width > screenSurface->w)
+ width = screenSurface->w - x;
+
+ if (y < 0) {
+ height += y;
+ y = 0;
+ }
+ if (y + height > screenSurface->h)
+ height = screenSurface->h - y;
+
+ _last->_pixels = new byte[width * height];
+ byte *pixels = _last->_pixels;
+ const byte *screen = static_cast<const byte *>(screenSurface->getBasePtr(x, y));
+ for (int i = 0; i < height; ++i) {
+ Common::copy(screen, screen + width, pixels);
+ screen += screenSurface->pitch;
+ pixels += width;
+ }
+ g_system->unlockScreen();
+
+ _last->_x = x;
+ _last->_y = y;
+ _last->_width = width;
+ _last->_height = height;
+
+ ++_last;
+}
+
+void ScreenBufferStack::restore() {
+ if (_last == _buffer)
+ return;
+
+ --_last;
+ g_system->lockScreen()->copyRectToSurface(
+ _last->_pixels, _last->_width, _last->_x, _last->_y,
+ _last->_width, _last->_height);
+ g_system->unlockScreen();
+
+ delete[] _last->_pixels;
+}
+
+}