diff options
Diffstat (limited to 'engines')
50 files changed, 8145 insertions, 0 deletions
diff --git a/engines/mads/assets.cpp b/engines/mads/assets.cpp new file mode 100644 index 0000000000..4c075d054e --- /dev/null +++ b/engines/mads/assets.cpp @@ -0,0 +1,95 @@ +/* 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/scummsys.h" +#include "mads/mads.h" +#include "mads/assets.h" +#include "mads/compression.h" +#include "mads/events.h" + +namespace MADS { + +SpriteAsset::SpriteAsset(MADSEngine *vm, const Common::String &resourceName, int flags): + _vm(vm) { + Common::String resName = resourceName; + if (!resName.hasSuffix(".SS")) + resName += ".SS"; + + File file(resName); + MadsPack sprites(&file); + + int curFrame = 0; + uint32 frameOffset = 0; + _frameRate = 0; + _pixelSpeed = 0; + _maxWidth = 0; + _maxHeight = 0; + + Common::SeekableReadStream *spriteStream = sprites.getItemStream(0); + for (int i = 0; i < 19; i++) { + spriteStream->readUint16LE(); + } + _frameCount = spriteStream->readUint16LE(); + // we skip the rest of the data + delete spriteStream; + + // Get the palette data + spriteStream = sprites.getItemStream(2); + int numColors = 0; + byte *palData = _vm->_palette->decodePalette(spriteStream, &numColors); + + Common::copy(palData, &palData[numColors], &_palette[0]); + if (numColors < 256) + Common::fill(&_palette[numColors * 3], &_palette[PALETTE_SIZE], 0); + _colorCount = numColors; + delete[] palData; + delete spriteStream; + + spriteStream = sprites.getItemStream(1); + Common::SeekableReadStream *spriteDataStream = sprites.getItemStream(3); + + SpriteAssetFrame frame; + for (curFrame = 0; curFrame < _frameCount; curFrame++) { + frame.comp = 0; + frameOffset = spriteStream->readUint32LE(); + _frameOffsets.push_back(frameOffset); + spriteStream->readUint32LE(); // frame size + frame.x = spriteStream->readUint16LE(); + frame.y = spriteStream->readUint16LE(); + frame.w = spriteStream->readUint16LE(); + frame.h = spriteStream->readUint16LE(); + if (curFrame == 0) { + debugN(kDebugGraphics, "%i frames, x = %i, y = %i, w = %i, h = %i\n", + _frameCount, frame.x, frame.y, frame.w, frame.h); + } + + frame.frame = new MSprite(spriteDataStream, Common::Point(frame.x, frame.y), + frame.w, frame.h, false); + _frames.push_back(frame); + } + + delete spriteStream; + delete spriteDataStream; + file.close(); +} + +} // End of namespace MADS diff --git a/engines/mads/assets.h b/engines/mads/assets.h new file mode 100644 index 0000000000..c64209eab2 --- /dev/null +++ b/engines/mads/assets.h @@ -0,0 +1,69 @@ +/* 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. + * + */ + +#ifndef MADS_ASSETS_H +#define MADS_ASSETS_H + +#include "common/scummsys.h" +#include "common/array.h" +#include "mads/msprite.h" +#include "mads/palette.h" +#include "mads/msprite.h" + +namespace MADS { + +struct SpriteAssetFrame { + uint32 stream; + int x, y, w, h; + uint32 comp; + MSprite *frame; +}; + +class SpriteAsset { +private: + MADSEngine *_vm; + byte _palette[PALETTE_SIZE]; + int _colorCount; + uint32 _srcSize; + int _frameRate, _pixelSpeed; + int _maxWidth, _maxHeight; + int _frameCount; + Common::Array<uint32> _frameOffsets; + Common::Array<SpriteAssetFrame> _frames; + uint32 _frameStartOffset; +public: + SpriteAsset(MADSEngine *vm, const Common::String &resourceName, int flags); + int getCount() { return _frameCount; } + int getFrameRate() const { return _frameRate; } + int getPixelSpeed() const { return _pixelSpeed; } + int getFrameWidth(int index); + int getFrameHeight(int index); + int getMaxFrameWidth() const { return _maxWidth; } + int getMaxFrameHeight() const { return _maxHeight; } + MSprite *getFrame(int frameIndex); + byte *getPalette() { return _palette; } + int getColorCount() { return _colorCount; } +}; + +} // End of namespace MADS + +#endif /* MADS_ASSETS_H */ diff --git a/engines/mads/compression.cpp b/engines/mads/compression.cpp new file mode 100644 index 0000000000..2190fc4ac0 --- /dev/null +++ b/engines/mads/compression.cpp @@ -0,0 +1,184 @@ +/* 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 "mads/compression.h" + +namespace MADS { + +const char *const madsPackString = "MADSPACK"; +const char *const FabInputExceededError = "FabDecompressor - Passed end of input buffer during decompression"; +const char *const FabOutputExceededError = "FabDecompressor - Decompressed data exceeded specified size"; + +bool MadsPack::isCompressed(Common::SeekableReadStream *stream) { + // Check whether the passed stream is packed + + char tempBuffer[8]; + stream->seek(0); + if (stream->read(tempBuffer, 8) == 8) { + if (!strncmp(tempBuffer, madsPackString, 8)) + return true; + } + + return false; +} + +MadsPack::MadsPack(Common::SeekableReadStream *stream) { + initialise(stream); +} + +MadsPack::MadsPack(const Common::String &resourceName, MADSEngine *vm) { + File file(resourceName); + initialise(&file); + file.close(); +} + +void MadsPack::initialise(Common::SeekableReadStream *stream) { + if (!MadsPack::isCompressed(stream)) + error("Attempted to decompress a resource that was not MadsPacked"); + + stream->seek(14); + _count = stream->readUint16LE(); + _items = new MadsPackEntry[_count]; + + byte *headerData = new byte[0xA0]; + byte *header = headerData; + stream->read(headerData, 0xA0); + + for (int i = 0; i < _count; ++i, header += 10) { + // Get header data + _items[i].hash = READ_LE_UINT16(header); + _items[i].size = READ_LE_UINT32(header + 2); + _items[i].compressedSize = READ_LE_UINT32(header + 6); + + _items[i].data = new byte[_items[i].size]; + if (_items[i].size == _items[i].compressedSize) { + // Entry isn't compressed + stream->read(_items[i].data, _items[i].size); + } else { + // Decompress the entry + byte *compressedData = new byte[_items[i].compressedSize]; + stream->read(compressedData, _items[i].compressedSize); + + FabDecompressor fab; + fab.decompress(compressedData, _items[i].compressedSize, _items[i].data, _items[i].size); + delete[] compressedData; + } + } + + delete[] headerData; + _dataOffset = stream->pos(); +} + +MadsPack::~MadsPack() { + for (int i = 0; i < _count; ++i) + delete[] _items[i].data; + delete[] _items; +} + +//-------------------------------------------------------------------------- + +void FabDecompressor::decompress(const byte *srcData, int srcSize, byte *destData, int destSize) { + byte copyLen, copyOfsShift, copyOfsMask, copyLenMask; + unsigned long copyOfs; + byte *destP; + + // Validate that the data starts with the FAB header + if (strncmp((const char *)srcData, "FAB", 3) != 0) + error("FabDecompressor - Invalid compressed data"); + + int shiftVal = srcData[3]; + if ((shiftVal < 10) || (shiftVal > 13)) + error("FabDecompressor - Invalid shift start"); + + copyOfsShift = 16 - shiftVal; + copyOfsMask = 0xFF << (shiftVal - 8); + copyLenMask = (1 << copyOfsShift) - 1; + copyOfs = 0xFFFF0000; + destP = destData; + + // Initialise data fields + _srcData = srcData; + _srcP = _srcData + 6; + _srcSize = srcSize; + _bitsLeft = 16; + _bitBuffer = READ_LE_UINT16(srcData + 4); + + for (;;) { + if (getBit() == 0) { + if (getBit() == 0) { + copyLen = ((getBit() << 1) | getBit()) + 2; + copyOfs = *_srcP++ | 0xFFFFFF00; + } else { + copyOfs = (((_srcP[1] >> copyOfsShift) | copyOfsMask) << 8) | _srcP[0]; + copyLen = _srcP[1] & copyLenMask; + _srcP += 2; + if (copyLen == 0) { + copyLen = *_srcP++; + if (copyLen == 0) + break; + else if (copyLen == 1) + continue; + else + copyLen++; + } else { + copyLen += 2; + } + copyOfs |= 0xFFFF0000; + } + while (copyLen-- > 0) { + if (destP - destData == destSize) + error(FabOutputExceededError); + + *destP = destP[(signed int)copyOfs]; + destP++; + } + } else { + if (_srcP - srcData == srcSize) + error(FabInputExceededError); + if (destP - destData == destSize) + error(FabOutputExceededError); + + *destP++ = *_srcP++; + } + } + + if (destP - destData != destSize) + error("FabDecompressor - Decompressed data does not match header decompressed size"); +} + +int FabDecompressor::getBit() { + _bitsLeft--; + if (_bitsLeft == 0) { + if (_srcP - _srcData == _srcSize) + error(FabInputExceededError); + + _bitBuffer = (READ_LE_UINT16(_srcP) << 1) | (_bitBuffer & 1); + _srcP += 2; + _bitsLeft = 16; + } + + int bit = _bitBuffer & 1; + _bitBuffer >>= 1; + return bit; +} + +} // End of namespace MADS diff --git a/engines/mads/compression.h b/engines/mads/compression.h new file mode 100644 index 0000000000..bf690dcc46 --- /dev/null +++ b/engines/mads/compression.h @@ -0,0 +1,80 @@ +/* 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. + * + */ + +#ifndef MADS_COMPRESSION_H +#define MADS_COMPRESSION_H + +#include "common/scummsys.h" +#include "common/endian.h" +#include "common/memstream.h" +#include "common/stream.h" + +#include "mads/mads.h" + +namespace MADS { + +struct MadsPackEntry { +public: + uint16 hash; + uint32 size; + uint32 compressedSize; + byte *data; +}; + +class MadsPack { +private: + MadsPackEntry *_items; + int _count; + int _dataOffset; + + void initialise(Common::SeekableReadStream *stream); +public: + static bool isCompressed(Common::SeekableReadStream *stream); + MadsPack(Common::SeekableReadStream *stream); + MadsPack(const Common::String &resourceName, MADSEngine *_vm); + ~MadsPack(); + + int getCount() const { return _count; } + MadsPackEntry &getItem(int index) const { return _items[index]; } + MadsPackEntry &operator[](int index) const { return _items[index]; } + Common::MemoryReadStream *getItemStream(int index) { + return new Common::MemoryReadStream(_items[index].data, _items[index].size, + DisposeAfterUse::NO); + } + int getDataOffset() const { return _dataOffset; } +}; + +class FabDecompressor { +private: + int _bitsLeft; + uint32 _bitBuffer; + const byte *_srcData, *_srcP; + int _srcSize; + + int getBit(); +public: + void decompress(const byte *srcData, int srcSize, byte *destData, int destSize); +}; + +} // End of namespace MADS + +#endif diff --git a/engines/mads/configure.engine b/engines/mads/configure.engine new file mode 100644 index 0000000000..60d833e9e8 --- /dev/null +++ b/engines/mads/configure.engine @@ -0,0 +1,3 @@ +# This file is included from the main "configure" script +# add_engine [name] [desc] [build-by-default] [subengines] [base games] [deps] +add_engine mads "Rex Nebular and the Cosmic Gender Bender" no diff --git a/engines/mads/debugger.cpp b/engines/mads/debugger.cpp new file mode 100644 index 0000000000..ceaeeaa5dd --- /dev/null +++ b/engines/mads/debugger.cpp @@ -0,0 +1,49 @@ +/* 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 "mads/mads.h" +#include "mads/debugger.h" + +namespace MADS { + +Debugger::Debugger(MADSEngine *vm) : GUI::Debugger(), _vm(vm) { + DCmd_Register("continue", WRAP_METHOD(Debugger, Cmd_Exit)); +} +/* +static int strToInt(const char *s) { + if (!*s) + // No string at all + return 0; + else if (toupper(s[strlen(s) - 1]) != 'H') + // Standard decimal string + return atoi(s); + + // Hexadecimal string + uint tmp = 0; + int read = sscanf(s, "%xh", &tmp); + if (read < 1) + error("strToInt failed on string \"%s\"", s); + return (int)tmp; +} +*/ + +} // End of namespace MADS diff --git a/engines/mads/debugger.h b/engines/mads/debugger.h new file mode 100644 index 0000000000..044151c0bb --- /dev/null +++ b/engines/mads/debugger.h @@ -0,0 +1,45 @@ +/* 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. + * + */ + +#ifndef MADS_DEBUGGER_H +#define MADS_DEBUGGER_H + +#include "common/scummsys.h" +#include "gui/debugger.h" + +namespace MADS { + +class MADSEngine; + +class Debugger : public GUI::Debugger { +private: + MADSEngine *_vm; +public: + Debugger(MADSEngine *vm); + virtual ~Debugger() {} + +protected: +}; + +} // End of namespace MADS + +#endif /* MADS_DEBUGGER_H */ diff --git a/engines/mads/detection.cpp b/engines/mads/detection.cpp new file mode 100644 index 0000000000..015859f827 --- /dev/null +++ b/engines/mads/detection.cpp @@ -0,0 +1,178 @@ +/* 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 "mads/mads.h" + +#include "base/plugins.h" +#include "common/savefile.h" +#include "common/str-array.h" +#include "common/memstream.h" +#include "engines/advancedDetector.h" +#include "common/system.h" +#include "graphics/colormasks.h" +#include "graphics/surface.h" + +#define MAX_SAVES 99 + +namespace MADS { + +struct MADSGameDescription { + ADGameDescription desc; + + int gameID; + uint32 features; +}; + +uint32 MADSEngine::getGameID() const { + return _gameDescription->gameID; +} + +uint32 MADSEngine::getGameFeatures() const { + return _gameDescription->features; +} + +uint32 MADSEngine::getFeatures() const { + return _gameDescription->desc.flags; +} + +Common::Language MADSEngine::getLanguage() const { + return _gameDescription->desc.language; +} + +Common::Platform MADSEngine::getPlatform() const { + return _gameDescription->desc.platform; +} + +} // End of namespace MADS + +static const PlainGameDescriptor MADSGames[] = { + {"MADS", "MADS"}, + {"nebular", "Rex Nebular and the Cosmic Gender Bender"}, + {0, 0} +}; + +#include "mads/detection_tables.h" + +class MADSMetaEngine : public AdvancedMetaEngine { +public: + MADSMetaEngine() : AdvancedMetaEngine(MADS::gameDescriptions, sizeof(MADS::MADSGameDescription), MADSGames) { + _maxScanDepth = 3; + } + + virtual const char *getName() const { + return "MADS Engine"; + } + + virtual const char *getOriginalCopyright() const { + return "MADS (c)"; + } + + virtual bool hasFeature(MetaEngineFeature f) const; + virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; + virtual SaveStateList listSaves(const char *target) const; + virtual int getMaximumSaveSlot() const; + virtual void removeSaveState(const char *target, int slot) const; + SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const; +}; + +bool MADSMetaEngine::hasFeature(MetaEngineFeature f) const { + return + (f == kSupportsListSaves) || + (f == kSupportsLoadingDuringStartup) || + (f == kSupportsDeleteSave) || + (f == kSavesSupportMetaInfo) || + (f == kSavesSupportThumbnail); +} + +bool MADS::MADSEngine::hasFeature(EngineFeature f) const { + return + (f == kSupportsRTL) || + (f == kSupportsLoadingDuringRuntime) || + (f == kSupportsSavingDuringRuntime); +} + +bool MADSMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const { + const MADS::MADSGameDescription *gd = (const MADS::MADSGameDescription *)desc; + if (gd) { + *engine = new MADS::MADSEngine(syst, gd); + } + return gd != 0; +} + +SaveStateList MADSMetaEngine::listSaves(const char *target) const { + Common::SaveFileManager *saveFileMan = g_system->getSavefileManager(); + Common::StringArray filenames; + Common::String saveDesc; + Common::String pattern = Common::String::format("%s.0??", target); + + filenames = saveFileMan->listSavefiles(pattern); + sort(filenames.begin(), filenames.end()); // Sort to get the files in numerical order + + SaveStateList saveList; + for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) { + const char *ext = strrchr(file->c_str(), '.'); + int slot = ext ? atoi(ext + 1) : -1; + + if (slot >= 0 && slot < MAX_SAVES) { + Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(*file); + + if (in) { + delete in; + } + } + } + + return saveList; +} + +int MADSMetaEngine::getMaximumSaveSlot() const { + return MAX_SAVES; +} + +void MADSMetaEngine::removeSaveState(const char *target, int slot) const { + Common::String filename = Common::String::format("%s.%03d", target, slot); + g_system->getSavefileManager()->removeSavefile(filename); +} + +SaveStateDescriptor MADSMetaEngine::querySaveMetaInfos(const char *target, int slot) const { + Common::String filename = Common::String::format("%s.%03d", target, slot); + Common::InSaveFile *f = g_system->getSavefileManager()->openForLoading(filename); + + if (f) { + delete f; + + // Create the return descriptor + SaveStateDescriptor desc(slot, ""); + + return desc; + } + + return SaveStateDescriptor(); +} + + +#if PLUGIN_ENABLED_DYNAMIC(MADS) + REGISTER_PLUGIN_DYNAMIC(MADS, PLUGIN_TYPE_ENGINE, MADSMetaEngine); +#else + REGISTER_PLUGIN_STATIC(MADS, PLUGIN_TYPE_ENGINE, MADSMetaEngine); +#endif diff --git a/engines/mads/detection_tables.h b/engines/mads/detection_tables.h new file mode 100644 index 0000000000..ac5316d4b4 --- /dev/null +++ b/engines/mads/detection_tables.h @@ -0,0 +1,47 @@ +/* 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. + * + */ + +namespace MADS { + +static const MADSGameDescription gameDescriptions[] = { + { + // Rex Nebular and the Cosmic Gender Bender DOS English + { + "nebular", + 0, + { + {"mpslabs.001", 0, "4df5c557b52abb5b661cf4befe5ae301", 1315354}, + AD_LISTEND + }, + Common::EN_ANY, + Common::kPlatformDOS, + ADGF_NO_FLAGS, + GUIO1(GUIO_NONE) + }, + GType_RexNebular, + 0 + }, + + { AD_TABLE_END_MARKER } +}; + +} // End of namespace MADS diff --git a/engines/mads/dialogs.cpp b/engines/mads/dialogs.cpp new file mode 100644 index 0000000000..e4c7682a38 --- /dev/null +++ b/engines/mads/dialogs.cpp @@ -0,0 +1,344 @@ +/* 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/scummsys.h" +#include "common/config-manager.h" +#include "mads/mads.h" +#include "mads/graphics.h" +#include "mads/msurface.h" +#include "mads/nebular/dialogs_nebular.h" + +namespace MADS { + +Dialog::Dialog(MADSEngine *vm): _vm(vm), _savedSurface(nullptr), + _position(Common::Point(-1, -1)), _width(0), _height(0) { +} + +Dialog::~Dialog() { + restore(_vm->_screen); +} + + +void Dialog::save(MSurface *s) { + _savedSurface = new MSurface(_width, _height); + s->copyTo(_savedSurface, + Common::Rect(_position.x, _position.y, _position.x + _width, _position.y + _height), + Common::Point()); +} + +void Dialog::restore(MSurface *s) { + if (_savedSurface) { + _savedSurface->copyTo(s, _position); + delete _savedSurface; + _savedSurface = nullptr; + } +} + +void Dialog::draw() { + // Save the screen portion the dialog will overlap + save(_vm->_screen); + + // Draw the dialog + // Fill entire content of dialog + _vm->_screen->fillRect(Common::Rect(_position.x, _position.y, + _position.x + _width, _position.y + _height), TEXTDIALOG_BACKGROUND); + + // Draw the outer edge lines + _vm->_screen->hLine(_position.x + 1, _position.y + _height - 2, + _position.x + _width - 2, TEXTDIALOG_EDGE); + _vm->_screen->hLine(_position.x, _position.y + _height - 1, + _position.x + _width - 1, TEXTDIALOG_EDGE); + _vm->_screen->vLine(_position.x + _width - 2, _position.y + 2, + _position.y + _height - 2, TEXTDIALOG_EDGE); + _vm->_screen->vLine(_position.x + _width - 1, _position.y + 1, + _position.y + _height - 1, TEXTDIALOG_EDGE); + + // Draw the gravelly dialog content + drawContent(Common::Rect(_position.x + 2, _position.y + 2, + _position.x + _width - 2, _position.y + _height - 2), 0, + TEXTDIALOG_CONTENT1, TEXTDIALOG_CONTENT2); +} + +void Dialog::drawContent(const Common::Rect &r, int seed, byte color1, byte color2) { + uint16 currSeed = seed ? seed : 0xB78E; + + for (int yp = 0; yp < r.height(); ++yp) { + byte *destP = _vm->_screen->getBasePtr(r.left, r.top + yp); + + for (int xp = 0; xp < r.width(); ++xp) { + uint16 seedAdjust = currSeed; + currSeed += 0x181D; + seedAdjust = (seedAdjust >> 9) | ((seedAdjust & 0x1ff) << 7); + currSeed ^= seedAdjust; + seedAdjust = (seedAdjust >> 3) | ((seedAdjust & 7) << 13); + currSeed += seedAdjust; + + *destP++ = (currSeed & 0x10) ? color2 : color1; + } + } +} + +/*------------------------------------------------------------------------*/ + +TextDialog::TextDialog(MADSEngine *vm, const Common::String &fontName, + const Common::Point &pos, int maxChars): + Dialog(vm) { + _vm = vm; + _fontName = fontName; + _position = pos; + + _vm->_font->setFont(FONT_INTERFACE); + _vm->_font->setColors(TEXTDIALOG_BLACK, TEXTDIALOG_BLACK, TEXTDIALOG_BLACK, TEXTDIALOG_BLACK); + + _innerWidth = (_vm->_font->maxWidth() + 1) * maxChars; + _width = _innerWidth + 10; + _lineSize = maxChars * 2; + _lineWidth = 0; + _currentX = 0; + _numLines = 0; + Common::fill(&_lineXp[0], &_lineXp[TEXT_DIALOG_MAX_LINES], 0); + _askLineNum = -1; + _askXp = 0; + + // Save the high end of the palette, and set up the entries for dialog display + Common::copy(&_vm->_palette->_mainPalette[TEXTDIALOG_CONTENT1 * 3], + &_vm->_palette->_mainPalette[TEXTDIALOG_CONTENT1 * 3 + 8 * 3], + &_savedPalette[0]); + Palette::setGradient(_vm->_palette->_mainPalette, TEXTDIALOG_CONTENT1, 2, 0x90, 0x80); + Palette::setGradient(_vm->_palette->_mainPalette, TEXTDIALOG_EDGE, 2, 0x9C, 0x70); + Palette::setGradient(_vm->_palette->_mainPalette, TEXTDIALOG_FC, 2, 0x90, 0x80); + Palette::setGradient(_vm->_palette->_mainPalette, TEXTDIALOG_FE, 1, 0xDC, 0xDC); + + _vm->_palette->setPalette(_vm->_palette->_mainPalette + (TEXTDIALOG_CONTENT1 * 3), + TEXTDIALOG_CONTENT1, 8); +} + +TextDialog::~TextDialog() { + restorePalette(); +} + +void TextDialog::addLine(const Common::String &line, bool underline) { + if (_lineWidth > 0 || _currentX > 0) + incNumLines(); + + int stringWidth = _vm->_font->getWidth(line, 1); + if (stringWidth >= _innerWidth || (int)line.size() >= _lineSize) { + wordWrap(line); + } else { + _lineXp[_numLines] = (_innerWidth / 2) - (stringWidth / 2); + _lines[_numLines] = line; + + if (underline) + underlineLine(); + } + + incNumLines(); +} + +void TextDialog::underlineLine() { + _lineXp[_numLines] |= 0x80; +} + +void TextDialog::incNumLines() { + _lineWidth = 0; + _currentX = 0; + if (++_numLines == TEXT_DIALOG_MAX_LINES) + error("Exceeded text dialog line max"); +} + +void TextDialog::wordWrap(const Common::String &line) { + Common::String tempLine; + + if (!line.empty()) { + const char *srcP = line.c_str(); + + do { + tempLine = ""; + bool endWord = false; + bool newLine = false; + bool continueFlag = true; + + do { + if (!*srcP) { + continueFlag = false; + } else { + tempLine += *srcP; + + if (*srcP == 10) { + continueFlag = false; + newLine = true; + ++srcP; + tempLine.deleteLastChar(); + } else if (*srcP == ' ') { + ++srcP; + endWord = true; + } else if (!endWord) { + ++srcP; + } else { + tempLine.deleteLastChar(); + continueFlag = false; + } + } + } while (continueFlag); + + if (tempLine.hasSuffix(" ")) + tempLine.deleteLastChar(); + + Common::String tempLine2; + if (_currentX > 0) + tempLine2 += ' '; + tempLine2 += tempLine; + + int lineWidth = _vm->_font->getWidth(tempLine2, 1); + if (((_currentX + (int)tempLine2.size()) > _lineSize) || + ((_lineWidth + lineWidth) > _innerWidth)) { + incNumLines(); + appendLine(tempLine); + } else { + appendLine(tempLine2); + } + + if (newLine) + incNumLines(); + } while (*srcP); + } +} + +void TextDialog::appendLine(const Common::String &line) { + _currentX += line.size(); + _lineWidth += _vm->_font->getWidth(line, 1); + _lines[_numLines] += line; +} + +void TextDialog::addInput() { + _askXp = _currentX + 1; + _askLineNum = _numLines; + incNumLines(); +} + +void TextDialog::draw() { + if (!_lineWidth) + --_numLines; + + // Figure out the size and position for the dialog + _height = (_vm->_font->getHeight() + 1) * (_numLines + 1) + 10; + if (_position.x == -1) + _position.x = 160 - (_width / 2); + if (_position.y == -1) + _position.y = 100 - (_height / 2); + + if ((_position.x + _width) > _vm->_screen->getWidth()) + _position.x = _vm->_screen->getWidth() - (_position.x + _width); + if ((_position.y + _height) > _vm->_screen->getHeight()) + _position.y = _vm->_screen->getHeight() - (_position.y + _height); + + // Draw the underlying dialog + Dialog::draw(); + + // Draw the text lines + int lineYp = _position.y + 5; + for (int lineNum = 0; lineNum < _numLines; ++lineNum) { + if (_lineXp[lineNum] == -1) { + // Draw a line across the entire dialog + _vm->_screen->hLine(_position.x + 2, + lineYp + (_vm->_font->getHeight() + 1) / 2, + _position.x + _width - 4, TEXTDIALOG_BLACK); + } else { + // Draw a text line + int xp = (_lineXp[lineNum] & 0x7F) + _position.x + 5; + int yp = lineYp; + if (_lineXp[lineNum] & 0x40) + ++yp; + + _vm->_font->writeString(_vm->_screen, _lines[lineNum], + Common::Point(xp, yp), 0, 1); + + if (_lineXp[lineNum] & 0x80) { + // Draw an underline under the text + int lineWidth = _vm->_font->getWidth(_lines[lineNum], 1); + _vm->_screen->hLine(xp, yp + _vm->_font->getHeight(), xp + lineWidth, + TEXTDIALOG_BLACK); + } + } + + lineYp += _vm->_font->getHeight() + 1; + } +} + +void TextDialog::drawWithInput() { + //int innerWidth = _innerWidth; + //int lineHeight = _vm->_font->getHeight() + 1; + //int xp = _position.x + 5; + + // Draw the content of the dialog + drawContent(Common::Rect(_position.x + 2, _position.y + 2, + _position.x + _width - 2, _position.y + _height - 2), 0, + TEXTDIALOG_CONTENT1, TEXTDIALOG_CONTENT2); + + error("TODO: drawWithInput"); +} + +void TextDialog::restorePalette() { + Common::copy(&_savedPalette[0], &_savedPalette[8 * 3], + &_vm->_palette->_mainPalette[248 * 3]); + _vm->_palette->setPalette(_vm->_palette->_mainPalette, 248, 8); +} + +/*------------------------------------------------------------------------*/ + +MessageDialog::MessageDialog(MADSEngine *vm, int maxChars, ...): + TextDialog(vm, FONT_INTERFACE, Common::Point(-1, -1), maxChars) { + // Add in passed line list + va_list va; + va_start(va, maxChars); + + const char *line = va_arg(va, const char *); + while (line) { + addLine(line); + line = va_arg(va, const char *); + } + va_end(va); +} + +void MessageDialog::show() { + draw(); + _vm->_events->showCursor(); + + while (!_vm->shouldQuit() && !_vm->_events->_keyPressed && + !_vm->_events->_mouseClicked) { + _vm->_events->delay(1); + } +} + +/*------------------------------------------------------------------------*/ + +Dialogs *Dialogs::init(MADSEngine *vm) { + if (vm->getGameID() == GType_RexNebular) + return new Nebular::DialogsNebular(vm); + + error("Unknown game"); +} + +Dialogs::Dialogs(MADSEngine *vm): _vm(vm) { +} + +} // End of namespace MADS diff --git a/engines/mads/dialogs.h b/engines/mads/dialogs.h new file mode 100644 index 0000000000..0f9a098ee1 --- /dev/null +++ b/engines/mads/dialogs.h @@ -0,0 +1,196 @@ +/* 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. + * + */ + +#ifndef MADS_DIALOGS_H +#define MADS_DIALOGS_H + +#include "common/scummsys.h" +#include "mads/game.h" +#include "mads/msurface.h" + +namespace MADS { + +class Dialog { +protected: + MADSEngine *_vm; + MSurface *_savedSurface; + Common::Point _position; + int _width; + int _height; + + /** + * Save the section of the passed surface the dialog will cover. + * @param s Screen surface to save + */ + void save(MSurface *s); + + /** + * Restore saved dialog surface + * @param s Screen surface to restore to. + */ + void restore(MSurface *s); + + /** + * Draws the content of a dialog with a gravelly alternating color. + */ + void drawContent(const Common::Rect &r, int seed, byte color1, byte color2); +protected: + /** + * Draw the dialog + */ + virtual void draw(); +public: + /** + * Constructor + */ + Dialog(MADSEngine *vm); + + /** + * Destructor + */ + virtual ~Dialog(); +}; + +enum { + TEXTDIALOG_CONTENT1 = 0XF8, + TEXTDIALOG_CONTENT2 = 0XF9, + TEXTDIALOG_EDGE = 0XFA, + TEXTDIALOG_BACKGROUND = 0XFB, + TEXTDIALOG_FC = 0XFC, + TEXTDIALOG_FD = 0XFD, + TEXTDIALOG_FE = 0XFE, + TEXTDIALOG_BLACK = 0 +}; + +#define TEXT_DIALOG_MAX_LINES 20 + +class TextDialog: protected Dialog { +private: + /** + * Increments the number of text lines the text dialog uses + */ + void incNumLines(); + + /** + * Flags the previously added line to be underlined + */ + void underlineLine(); + + /** + * Append text to the currently end line. + */ + void appendLine(const Common::String &line); + + /** + * Clean up after finishing displaying the dialog + */ + void restorePalette(); +protected: + Common::String _fontName; + int _innerWidth; + int _lineWidth; + int _currentX; + int _numLines; + int _lineSize; + int _askXp; + int _askLineNum; + Common::String _lines[TEXT_DIALOG_MAX_LINES]; + int _lineXp[TEXT_DIALOG_MAX_LINES]; + byte _savedPalette[8 * 3]; + + /** + * Add a new line to the dialog + */ + void addLine(const Common::String &line, bool underline = false); + + /** + * Adds one or more lines, word wrapping the passed text + */ + void wordWrap(const Common::String &line); + + /** + * Adds an input area following previously added text + */ + void addInput(); +public: + /** + * Constructor + * @param vm Engine reference + * @param fontName Font to use for display + * @param pos Position for window top-left + * @param maxChars Horizontal width of window in characters + */ + TextDialog(MADSEngine *vm, const Common::String &fontName, const Common::Point &pos, + int maxChars); + + /** + * Destructor + */ + virtual ~TextDialog(); + + /** + * Draw the dialog + */ + virtual void draw(); + + /** + * Draw the dialog along with any input box + */ + void drawWithInput(); +}; + +class MessageDialog: protected TextDialog { +public: + MessageDialog(MADSEngine *vm, int lines, ...); + + virtual ~MessageDialog() {} + + /** + * Show the dialog, and wait until a key or mouse press. + */ + void show(); +}; + +enum DialogId { + DIALOG_NONE = 0, DIALOG_GAME_MENU = 1, DIALOG_SAVE = 2, DIALOG_RESTORE = 3, + DIALOG_OPTIONS = 4, DIALOG_DIFFICULTY = 5, DIALOG_ERROR = 6 +}; + +class Dialogs { +protected: + MADSEngine *_vm; + + Dialogs(MADSEngine *vm); +public: + static Dialogs *init(MADSEngine *vm); +public: + Common::Point _defaultPosition; + DialogId _pendingDialog; + + virtual ~Dialogs() {} + + virtual void showDialog() = 0; +}; + +} // End of namespace MADS + +#endif /* MADS_DIALOGS_H */ diff --git a/engines/mads/events.cpp b/engines/mads/events.cpp new file mode 100644 index 0000000000..8f177f2c20 --- /dev/null +++ b/engines/mads/events.cpp @@ -0,0 +1,160 @@ +/* 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/scummsys.h" +#include "graphics/cursorman.h" +#include "common/events.h" +#include "engines/util.h" +#include "mads/mads.h" +#include "mads/events.h" + +#define GAME_FRAME_RATE 50 +#define GAME_FRAME_TIME (1000 / GAME_FRAME_RATE) + +namespace MADS { + +EventsManager::EventsManager(MADSEngine *vm) { + _vm = vm; + _cursorSprites = nullptr; + _gameCounter = 0; + _priorFrameTime = 0; + _keyPressed = false; + _mouseClicked = false; +} + +EventsManager::~EventsManager() { + freeCursors(); +} + +void EventsManager::loadCursors(const Common::String &spritesName) { + delete _cursorSprites; + _cursorSprites = new SpriteAsset(_vm, spritesName, 0x4000); +} + +void EventsManager::setCursor(CursorType cursorId) { + _cursorId = cursorId; + changeCursor(); +} + +void EventsManager::setCursor2(CursorType cursorId) { + _cursorId = cursorId; + _newCursorId = cursorId; + changeCursor(); +} + +void EventsManager::showCursor() { + CursorMan.showMouse(true); +} + +void EventsManager::hideCursor() { + CursorMan.showMouse(false); +} + +void EventsManager::resetCursor() { + CursorType cursorId = (CursorType)MIN(_cursorSprites->getCount(), (int)CURSOR_WAIT); + _newCursorId = cursorId; + if (_cursorId != _newCursorId) { + changeCursor(); + _cursorId = _newCursorId; + } +} + +void EventsManager::changeCursor() { + warning("TODO: changeCursor"); +} + +void EventsManager::freeCursors() { + delete _cursorSprites; + _cursorSprites = nullptr; +} + +void EventsManager::pollEvents() { + checkForNextFrameCounter(); + + Common::Event event; + while (g_system->getEventManager()->pollEvent(event)) { + // Handle keypress + switch (event.type) { + case Common::EVENT_QUIT: + case Common::EVENT_RTL: + return; + + case Common::EVENT_KEYDOWN: + // Check for debugger + if (event.kbd.keycode == Common::KEYCODE_d && (event.kbd.flags & Common::KBD_CTRL)) { + // Attach to the debugger + _vm->_debugger->attach(); + _vm->_debugger->onFrame(); + } else { + _keyPressed = true; + } + return; + case Common::EVENT_KEYUP: + _keyPressed = false; + return; + case Common::EVENT_LBUTTONDOWN: + case Common::EVENT_RBUTTONDOWN: + _mouseClicked = true; + return; + case Common::EVENT_LBUTTONUP: + case Common::EVENT_RBUTTONUP: + _mouseClicked = false; + return; + case Common::EVENT_MOUSEMOVE: + _mousePos = event.mouse; + break; + default: + break; + } + } +} + +void EventsManager::checkForNextFrameCounter() { + // Check for next game frame + uint32 milli = g_system->getMillis(); + if ((milli - _priorFrameTime) >= GAME_FRAME_TIME) { + ++_gameCounter; + _priorFrameTime = milli; + + // Give time to the debugger + _vm->_debugger->onFrame(); + + // Display the frame + _vm->_screen->updateScreen(); + + // Signal the ScummVM debugger + _vm->_debugger->onFrame(); + } +} + +void EventsManager::delay(int cycles) { + uint32 totalMilli = cycles * 1000 / GAME_FRAME_RATE; + uint32 delayEnd = g_system->getMillis() + totalMilli; + + while (!_vm->shouldQuit() && g_system->getMillis() < delayEnd) { + g_system->delayMillis(10); + + pollEvents(); + } +} + +} // End of namespace MADS diff --git a/engines/mads/events.h b/engines/mads/events.h new file mode 100644 index 0000000000..01f48170d4 --- /dev/null +++ b/engines/mads/events.h @@ -0,0 +1,123 @@ +/* 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. + * + */ + +#ifndef MADS_EVENTS_H +#define MADS_EVENTS_H + +#include "common/scummsys.h" +#include "mads/msprite.h" +#include "mads/assets.h" + +namespace MADS { + +enum CursorType { CURSOR_NONE = 0, CURSOR_ARROW = 1, CURSOR_WAIT = 2, CURSOR_GO_DOWN = 3, + CURSOR_GO_UP = 4, CURSOR_GO_LEFT = 5, CURSOR_GO_RIGHT = 6 }; + +class MADSEngine; + +class EventsManager { +private: + MADSEngine *_vm; + CursorType _cursorId; + CursorType _newCursorId; + uint32 _gameCounter; + uint32 _priorFrameTime; + Common::Point _mousePos; + + /** + * Updates the cursor image when the current cursor changes + */ + void changeCursor(); + + /** + * Checks for whether the next game frame number has been reached. + */ + void checkForNextFrameCounter(); +public: + SpriteAsset *_cursorSprites; + bool _mouseClicked; + bool _keyPressed; +public: + /** + * Constructor + */ + EventsManager(MADSEngine *vm); + + /** + * Destructor + */ + ~EventsManager(); + + /** + * Loads the sprite set containing the cursors + */ + void loadCursors(const Common::String &spritesName); + + /** + * Sets the cursor + */ + void setCursor(CursorType cursorId); + + /** + * Sets the cursor + */ + void setCursor2(CursorType cursorId); + + /** + * Show the mouse cursor + */ + void showCursor(); + + /** + * Hide the mouse cursor + */ + void hideCursor(); + + /** + * Resets the cursor, if necessary + */ + void resetCursor(); + + /** + * Free currently loaded cursors + */ + void freeCursors(); + + /** + * Poll any pending events + */ + void pollEvents(); + + /** + * Return the current mouse position + */ + Common::Point mousePos() const { return _mousePos; } + + /** + * Delay for a given number of frames + */ + void delay(int amount); +}; + +} // End of namespace MADS + +#endif /* MADS_EVENTS_H */ diff --git a/engines/mads/font.cpp b/engines/mads/font.cpp new file mode 100644 index 0000000000..52541500c9 --- /dev/null +++ b/engines/mads/font.cpp @@ -0,0 +1,208 @@ +/* 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/scummsys.h" +#include "mads/mads.h" +#include "mads/compression.h" +#include "mads/font.h" +#include "mads/msurface.h" + +namespace MADS { + +Font::Font(MADSEngine *vm) : _vm(vm) { + _sysFont = true; + + _fontColors[0] = _vm->_palette->BLACK; + _fontColors[1] = _vm->_palette->WHITE; + _fontColors[2] = _vm->_palette->BLACK; + _fontColors[3] = _vm->_palette->DARK_GRAY; +} + +Font::~Font() { + if (!_sysFont) { + delete[] _charWidths; + delete[] _charOffs; + delete[] _charData; + } +} + +void Font::setFont(const Common::String &filename) { + if (!_filename.empty() && (filename == _filename)) + // Already using specified font, so don't bother reloading + return; + + _sysFont = false; + _filename = filename; + + MadsPack fontData(filename, _vm); + Common::SeekableReadStream *fontFile = fontData.getItemStream(0); + + _maxHeight = fontFile->readByte(); + _maxWidth = fontFile->readByte(); + + _charWidths = new uint8[128]; + // Char data is shifted by 1 + _charWidths[0] = 0; + fontFile->read(_charWidths + 1, 127); + fontFile->readByte(); // remainder + + _charOffs = new uint16[128]; + + uint startOffs = 2 + 128 + 256; + uint fontSize = fontFile->size() - startOffs; + + // Char data is shifted by 1 + _charOffs[0] = 0; + for (int i = 1; i < 128; i++) + _charOffs[i] = fontFile->readUint16LE() - startOffs; + fontFile->readUint16LE(); // remainder + + _charData = new uint8[fontSize]; + fontFile->read(_charData, fontSize); + + delete fontFile; +} + +void Font::setColor(uint8 color) { + if (_sysFont) + _fontColors[1] = color; + else + _fontColors[3] = color; +} + +void Font::setColors(uint8 v1, uint8 v2, uint8 v3, uint8 v4) { + _fontColors[0] = v1; + _fontColors[1] = v2; + _fontColors[2] = v3; + _fontColors[3] = v4; +} + +int Font::write(MSurface *surface, const Common::String &msg, const Common::Point &pt, int width, int spaceWidth, uint8 colors[]) { + + /*TODO + if (custom_ascii_converter) { // if there is a function to convert the extended ASCII characters + custom_ascii_converter(out_string); // call it with the string + } + */ + + if (width > 0) + width = MIN(surface->getWidth(), pt.x + width); + else + width = surface->getWidth(); + + int x = pt.x + 1; + int y = pt.y + 1; + + int skipY = 0; + if (y < 0) { + skipY = -y; + y = 0; + } + + int height = MAX(0, _maxHeight - skipY); + if (height == 0) + return x; + + int bottom = y + height - 1; + if (bottom > surface->getHeight() - 1) { + height -= MIN(height, bottom - (surface->getHeight() - 1)); + } + + if (height <= 0) + return x; + + byte *destPtr = surface->getBasePtr(x, y); + uint8 *oldDestPtr = destPtr; + + int xPos = x; + + const char *text = msg.c_str(); + while (*text) { + char theChar = (*text++) & 0x7F; + int charWidth = _charWidths[(byte)theChar]; + + if (charWidth > 0) { + + if (xPos + charWidth >= width) + return xPos; + + uint8 *charData = &_charData[_charOffs[(byte)theChar]]; + int bpp = getBpp(charWidth); + + if (skipY != 0) + charData += bpp * skipY; + + for (int i = 0; i < height; i++) { + for (int j = 0; j < bpp; j++) { + if (*charData & 0xc0) + *destPtr = colors[(*charData & 0xc0) >> 6]; + destPtr++; + if (*charData & 0x30) + *destPtr = colors[(*charData & 0x30) >> 4]; + destPtr++; + if (*charData & 0x0C) + *destPtr = colors[(*charData & 0x0C) >> 2]; + destPtr++; + if (*charData & 0x03) + *destPtr = colors[*charData & 0x03]; + destPtr++; + charData++; + } + + destPtr += surface->getWidth() - bpp * 4; + + } + + destPtr = oldDestPtr + charWidth + spaceWidth; + oldDestPtr = destPtr; + + } + + xPos += charWidth + spaceWidth; + + } + + return xPos; + +} + +int Font::getWidth(const Common::String &msg, int spaceWidth) { + int width = 0; + const char *text = msg.c_str(); + + while (*text) + width += _charWidths[*text++ & 0x7F] + spaceWidth; + return width; +} + +int Font::getBpp(int charWidth) { + if (charWidth > 12) + return 4; + else if (charWidth > 8) + return 3; + else if (charWidth > 4) + return 2; + else + return 1; +} + +} // End of namespace MADS diff --git a/engines/mads/font.h b/engines/mads/font.h new file mode 100644 index 0000000000..3deb3f4d2d --- /dev/null +++ b/engines/mads/font.h @@ -0,0 +1,75 @@ +/* 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. + * + */ + +#ifndef MADS_FONT_H +#define MADS_FONT_H + +#include "common/scummsys.h" +#include "common/util.h" +#include "common/endian.h" +#include "mads/msurface.h" + +namespace MADS { + +#define FONT_CONVERSATION "*FONTCONV.FF" +#define FONT_INTERFACE "*FONTINTR.FF" +#define FONT_MAIN "*FONTMAIN.FF" +#define FONT_MENU "*FONTMENU.FF" // Not in Rex (uses bitmap files for menu strings) +#define FONT_MISC "*FONTMISC.FF" +#define FONT_TELE "*FONTTELE.FF" // Not in Phantom +#define FONT_PHAN "*FONTPHAN.FF" // Phantom only + +class MADSEngine; + +class Font { +protected: + MADSEngine *_vm; + + uint8 _maxWidth, _maxHeight; + uint8 *_charWidths; + uint16 *_charOffs; + uint8 *_charData; + bool _sysFont; + Common::String _filename; + uint8 _fontColors[4]; + + int getBpp(int charWidth); +public: + Font(MADSEngine *vm); + virtual ~Font(); + + void setFont(const Common::String &filename); + void setColor(uint8 color); + void setColors(uint8 v1, uint8 v2, uint8 v3, uint8 v4); + + int maxWidth() const { return _maxWidth; } + int getWidth(const Common::String &msg, int spaceWidth = -1); + int getHeight() const { return _maxHeight; } + int write(MSurface *surface, const Common::String &msg, const Common::Point &pt, int width, int spaceWidth, uint8 colors[]); + int writeString(MSurface *surface, const Common::String &msg, const Common::Point &pt, int width = 0, int spaceWidth = -1) { + return write(surface, msg, pt, width, spaceWidth, _fontColors); + } +}; + +} // End of namespace MADS + +#endif /* MADS_FONT_H */ diff --git a/engines/mads/game.cpp b/engines/mads/game.cpp new file mode 100644 index 0000000000..08d548e235 --- /dev/null +++ b/engines/mads/game.cpp @@ -0,0 +1,208 @@ +/* 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/scummsys.h" +#include "mads/mads.h" +#include "mads/game.h" +#include "mads/game_data.h" +#include "mads/nebular/game_nebular.h" +#include "mads/graphics.h" +#include "mads/msurface.h" +#include "mads/resources.h" + +namespace MADS { + +Game *Game::init(MADSEngine *vm) { + if (vm->getGameID() == GType_RexNebular) + return new Nebular::GameNebular(vm); + + return nullptr; +} + +Game::Game(MADSEngine *vm): _vm(vm), _surface(nullptr), + _objects(vm), _scene(vm) { + _sectionNumber = _priorSectionNumber = 0; + _difficultyLevel = DIFFICULTY_HARD; + _saveSlot = -1; + _statusFlag = 0; + _sectionHandler = nullptr; + _sectionNumber = 1; + _priorSectionNumber = 0; + _currentSectionNumber = -1; + _v1 = _v2 = 0; + _v3 = _v4 = 0; + _v5 = _v6 = 0; + _aaName = "*I0.AA"; + _playerSpritesFlag = false; +} + +Game::~Game() { + delete _surface; + delete _sectionHandler; +} + +void Game::run() { + _statusFlag = true; + int protectionResult = checkCopyProtection(); + switch (protectionResult) { + case 1: + // Copy protection failed + _scene._nextSceneId = 804; + initialiseGlobals(); + _globalFlags[5] = 0xFFFF; + _saveSlot = -1; + break; + case 2: + _statusFlag = 0; + break; + default: + break; + } + + if (_saveSlot == -1 && protectionResult != -1 && protectionResult != -2) { + initSection(_sectionNumber); + _statusFlag = true; + + _vm->_dialogs->_pendingDialog = DIALOG_DIFFICULTY; + _vm->_dialogs->showDialog(); + _vm->_dialogs->_pendingDialog = DIALOG_NONE; + + _priorSectionNumber = 0; + _priorSectionNumber = -1; + _scene._priorSceneId = 0; + _scene._currentSceneId = -1; + } + + if (protectionResult != 1 && protectionResult != 2) { + initialiseGlobals(); + + if (_saveSlot != -1) { + warning("TODO: loadGame(\"REX.SAV\", 210)"); + _statusFlag = false; + } + } + + if (_statusFlag) + gameLoop(); +} + +void Game::gameLoop() { + while (!_vm->shouldQuit() && _statusFlag) { + setSectionHandler(); + _sectionHandler->preLoadSection(); + initSection(_sectionNumber); + _sectionHandler->postLoadSection(); + + _scene._spriteSlots.clear(true); + + if (_sectionNumber == _currentSectionNumber) { + sectionLoop(); + } + + // TODO: Extra reset methods + _vm->_events->resetCursor(); + _vm->_events->freeCursors(); + _vm->_sound->closeDriver(); + } + + _vm->_palette->close(); +} + +void Game::sectionLoop() { + while (!_vm->shouldQuit() && _statusFlag && _sectionNumber == _currentSectionNumber) { + _v1 = 3; + _player._spritesChanged = true; + _v5 = 0; + _v6 = 0; + _vm->_events->resetCursor(); + + _quotes = nullptr; + _scene.clearVocab(); + _scene.loadSceneLogic(); + + _v4 = 0; + _player._stepEnabled = true; + _player._visible = true; + _vm->_dialogs->_defaultPosition = Common::Point(-1, -1); + _visitedScenes.add(_scene._nextSceneId); + + _scene._screenObjects._v8333C = -1; + _scene._screenObjects._v832EC = 0; + _scene._screenObjects._yp = 0; + _v3 = -1; + + _scene._sceneLogic->setup(); + if (_player._spritesChanged || _v3) { + if (_player._spritesLoaded) + _scene._spriteSlots.releasePlayerSprites(); + _vm->_palette->resetGamePalette(18, 10); + _scene._spriteSlots.clear(true); + } else { + _vm->_palette->initGamePalette(); + } + + _vm->_palette->_paletteUsage.load(3, 0xF0, 0xF1, 0xF2); + + _scene.loadScene(_scene._nextSceneId, _aaName, 0); + _vm->_sound->pauseNewCommands(); + + if (!_player._spritesLoaded) { + _player.loadSprites(""); + _playerSpritesFlag = false; + } + + + // TODO: main section loop logic goes here + + // Clear the scene + _scene.free(); + _sectionNumber = _scene._nextSceneId / 100; + + // TODO: sub_1DD46(3) + + // Check whether to show a dialog + if (_vm->_dialogs->_pendingDialog && _player._stepEnabled && !_globalFlags[5]) { + _scene._spriteSlots.releasePlayerSprites(); + _vm->_dialogs->showDialog(); + _vm->_dialogs->_pendingDialog = DIALOG_NONE; + } + } +} + +void Game::initSection(int sectionNumber) { + _priorSectionNumber = _currentSectionNumber; + _currentSectionNumber = sectionNumber; + + _vm->_palette->resetGamePalette(18, 10); + _vm->_palette->setLowRange(); + _vm->_events->loadCursors("*CURSOR.SS"); + + assert(_vm->_events->_cursorSprites); + _vm->_events->setCursor2((_vm->_events->_cursorSprites->getCount() <= 1) ? + CURSOR_ARROW : CURSOR_WAIT); +} + +void Game::loadResourceSequence(const Common::String prefix, int v) { + warning("TODO: loadResourceSequence"); +} + +} // End of namespace MADS diff --git a/engines/mads/game.h b/engines/mads/game.h new file mode 100644 index 0000000000..b8add9ab00 --- /dev/null +++ b/engines/mads/game.h @@ -0,0 +1,125 @@ +/* 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. + * + */ + +#ifndef MADS_GAME_H +#define MADS_GAME_H + +#include "common/scummsys.h" +#include "mads/scene.h" +#include "mads/game_data.h" + +namespace MADS { + +class MADSEngine; + +enum { + PLAYER_INVENTORY = 2 +}; + +enum Difficulty { + DIFFICULTY_HARD = 1, DIFFICULTY_MEDIUM = 2, DIFFICULTY_EASY = 3 +}; + +class Game { +private: + /** + * Main game loop + */ + void gameLoop(); + + /** + * Inner game loop for executing gameplay within a game section + */ + void sectionLoop(); +protected: + MADSEngine *_vm; + MSurface *_surface; + Difficulty _difficultyLevel; + Player _player; + int _saveSlot; + int _statusFlag; + SectionHandler *_sectionHandler; + VisitedScenes _visitedScenes; + byte *_quotes; + int _v1; + int _v3; + int _v4; + int _v5; + int _v6; + Common::String _aaName; + bool _playerSpritesFlag; + + /** + * Constructor + */ + Game(MADSEngine *vm); + + /** + * Initialises the current section number of the game + */ + void initSection(int sectionNumber); + + void loadResourceSequence(const Common::String prefix, int v); + + //@{ + /** @name Virtual Method list */ + + /** + * Perform any copy protection check + */ + virtual int checkCopyProtection() = 0; + + /** + * Initialises global variables for a new game + */ + virtual void initialiseGlobals() = 0; + + /** + * Set up the section handler specific to each section + */ + virtual void setSectionHandler() = 0; + //@} + +public: + static Game *init(MADSEngine *vm); +public: + int _sectionNumber; + int _priorSectionNumber; + int _currentSectionNumber; + Common::Array<uint16> _globalFlags; + InventoryObjects _objects; + Scene _scene; + int _v2; +public: + virtual ~Game(); + + /** + * Run the game + */ + void run(); + + Player &player() { return _player; } +}; + +} // End of namespace MADS + +#endif /* MADS_GAME_H */ diff --git a/engines/mads/game_data.cpp b/engines/mads/game_data.cpp new file mode 100644 index 0000000000..df4a902c25 --- /dev/null +++ b/engines/mads/game_data.cpp @@ -0,0 +1,116 @@ +/* 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/scummsys.h" +#include "mads/mads.h" +#include "mads/game.h" +#include "mads/nebular/game_nebular.h" +#include "mads/graphics.h" +#include "mads/msurface.h" +#include "mads/resources.h" + +namespace MADS { + +void VisitedScenes::add(int sceneId) { + if (!exists(sceneId)) + push_back(sceneId); +} + +bool VisitedScenes::exists(int sceneId) { + for (uint i = 0; i < size(); ++i) { + if ((*this)[i] == sceneId) + return true; + } + + return false; +} + +void InventoryObject::load(Common::SeekableReadStream &f) { + _descId = f.readUint16LE(); + _roomNumber = f.readUint16LE(); + _article = f.readByte(); + _vocabCount = f.readByte(); + + for (int i = 0; i < 3; ++i) { + _vocabList[i]._actionFlags1 = f.readByte(); + _vocabList[i]._actionFlags2 = f.readByte(); + _vocabList[i]._vocabId = f.readByte(); + } + + f.skip(4); // field12 + f.read(&_mutilateString[0], 10); + f.skip(16); +} + +/*------------------------------------------------------------------------*/ + +void InventoryObjects::load() { + File f("*OBJECTS.DAT"); + + // Get the total numer of inventory objects + int count = f.readUint16LE(); + reserve(count); + + // Read in each object + for (int i = 0; i < count; ++i) { + InventoryObject obj; + obj.load(f); + push_back(obj); + + // If it's for the player's inventory, add the index to the inventory list + if (obj._roomNumber == PLAYER_INVENTORY) { + _inventoryList.push_back(i); + assert(_inventoryList.size() <= 32); + } + } +} + +void InventoryObjects::setData(int objIndex, int id, const byte *p) { + // TODO: This whole method seems weird. Check it out more thoroughly once + // more of the engine is implemented + for (int i = 0; i < (int)size(); ++i) { + InventoryObject &obj = (*this)[i]; + if (obj._vocabList[0]._actionFlags1 <= i) + break; + + if (obj._mutilateString[6 + i] == id) { + (*this)[objIndex]._objFolder = p; + } + } +} + +void InventoryObjects::setRoom(int objectId, int roomNumber) { + warning("TODO: setObjectRoom"); +} + +/*------------------------------------------------------------------------*/ + +Player::Player() { + _direction = 8; + _newDirection = 8; + _spritesLoaded = false; + _spritesStart = _numSprites = 0; + _stepEnabled = false; + _visible = false; +} + +} // End of namespace MADS diff --git a/engines/mads/game_data.h b/engines/mads/game_data.h new file mode 100644 index 0000000000..2d2badd38b --- /dev/null +++ b/engines/mads/game_data.h @@ -0,0 +1,126 @@ +/* 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. + * + */ + +#ifndef MADS_GAME_DATA_H +#define MADS_GAME_DATA_H + +#include "common/scummsys.h" +#include "common/array.h" + +namespace MADS { + +class MADSEngine; +class Game; + +class VisitedScenes: public Common::Array<int> { +public: + /** + * Returns true if a given Scene Id exists in the listed of previously visited scenes. + */ + bool exists(int sceneId); + + /** + * Adds a scene Id to the list of previously visited scenes, if it doesn't already exist + */ + void add(int sceneId); +}; + +class InventoryObject { +public: + int _descId; + int _roomNumber; + int _article; + int _vocabCount; + struct { + int _actionFlags1; + int _actionFlags2; + int _vocabId; + } _vocabList[3]; + char _mutilateString[10]; // ??? + const byte *_objFolder; // ??? + + /** + * Loads the data for a given object + */ + void load(Common::SeekableReadStream &f); +}; + +class InventoryObjects: public Common::Array<InventoryObject> { +private: + MADSEngine *_vm; +public: + Common::Array<int> _inventoryList; + + /** + * Constructor + */ + InventoryObjects(MADSEngine *vm): _vm(vm) {} + + /** + * Loads the game's object list + */ + void load(); + + /** + * Set the associated data? pointer with an inventory object + */ + void setData(int objIndex, int id, const byte *p); + + /** + * Sets the room number + */ + void setRoom(int objectId, int roomNumber); +}; + +class Player { +public: + int _direction; + int _newDirection; + bool _spritesLoaded; + int _spritesStart; + int _numSprites; + bool _stepEnabled; + bool _spritesChanged; + bool _visible; +public: + Player(); + + void loadSprites(const Common::String &prefix) { + warning("TODO: Player::loadSprites"); + } +}; + +class SectionHandler { +protected: + MADSEngine *_vm; +public: + SectionHandler(MADSEngine *vm): _vm(vm) {} + virtual ~SectionHandler() {} + + virtual void preLoadSection() = 0; + virtual void sectionPtr2() = 0; + virtual void postLoadSection() = 0; +}; + +} // End of namespace MADS + +#endif /* MADS_GAME_DATA_H */ diff --git a/engines/mads/graphics.cpp b/engines/mads/graphics.cpp new file mode 100644 index 0000000000..267785b76b --- /dev/null +++ b/engines/mads/graphics.cpp @@ -0,0 +1,30 @@ +/* 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/scummsys.h" +#include "mads/mads.h" +#include "mads/graphics.h" + +namespace MADS { + + +} // End of namespace MADS diff --git a/engines/mads/graphics.h b/engines/mads/graphics.h new file mode 100644 index 0000000000..87cd8be3c9 --- /dev/null +++ b/engines/mads/graphics.h @@ -0,0 +1,36 @@ +/* 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. + * + */ + +#ifndef MADS_GRAPHICS_H +#define MADS_GRAPHICS_H + +#include "common/scummsys.h" + +namespace MADS { + +#define MADS_SCREEN_WIDTH 320 +#define MADS_SCREEN_HEIGHT 200 +#define MADS_INTERFACE_HEIGHT 44 + +} // End of namespace MADS + +#endif /* MADS_GRAPHICS_H */ diff --git a/engines/mads/mads.cpp b/engines/mads/mads.cpp new file mode 100644 index 0000000000..d78f37dd8b --- /dev/null +++ b/engines/mads/mads.cpp @@ -0,0 +1,112 @@ +/* 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/scummsys.h" +#include "common/config-manager.h" +#include "common/debug-channels.h" +#include "common/events.h" +#include "engines/util.h" +#include "mads/mads.h" +#include "mads/graphics.h" +#include "mads/resources.h" +#include "mads/sound.h" +#include "mads/msurface.h" +#include "mads/msprite.h" + +namespace MADS { + +MADSEngine::MADSEngine(OSystem *syst, const MADSGameDescription *gameDesc) : + _gameDescription(gameDesc), Engine(syst), _randomSource("MADS") { + + // Initialise fields + _easyMouse = true; + _invObjectStill = false; + _textWindowStill = false; + + _debugger = nullptr; + _dialogs = nullptr; + _events = nullptr; + _font = nullptr; + _game = nullptr; + _palette = nullptr; + _resources = nullptr; + _screen = nullptr; + _sound = nullptr; + _userInterface = nullptr; +} + +MADSEngine::~MADSEngine() { + delete _debugger; + delete _dialogs; + delete _events; + delete _font; + delete _game; + delete _palette; + delete _resources; + delete _screen; + delete _sound; + delete _userInterface; +} + +void MADSEngine::initialise() { + // Set up debug channels + DebugMan.addDebugChannel(kDebugPath, "Path", "Pathfinding debug level"); + DebugMan.addDebugChannel(kDebugScripts, "scripts", "Game scripts"); + DebugMan.addDebugChannel(kDebugGraphics, "graphics", "Graphics handling"); + + // Initial sub-system engine references + MSurface::setVm(this); + MSprite::setVm(this); + + Resources::init(this); + _debugger = new Debugger(this); + _dialogs = Dialogs::init(this); + _events = new EventsManager(this); + _palette = new Palette(this); + _font = new Font(this); + _screen = new MSurface(g_system->getWidth(), g_system->getHeight()); + _sound = new SoundManager(this, _mixer); + _userInterface = UserInterface::init(this); + _game = Game::init(this); + + _events->loadCursors("*CURSOR.SS"); + _screen->empty(); +} + +Common::Error MADSEngine::run() { + initGraphics(MADS_SCREEN_WIDTH, MADS_SCREEN_HEIGHT, false); + initialise(); + + // Run the game + _game->run(); + + // Dummy loop to keep application active + _events->delay(9999); + + return Common::kNoError; +} + +int MADSEngine::getRandomNumber(int maxNumber) { + return _randomSource.getRandomNumber(maxNumber); +} + +} // End of namespace MADS diff --git a/engines/mads/mads.h b/engines/mads/mads.h new file mode 100644 index 0000000000..44630ca135 --- /dev/null +++ b/engines/mads/mads.h @@ -0,0 +1,118 @@ +/* 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. + * + */ + +#ifndef MADS_MADS_H +#define MADS_MADS_H + +#include "common/scummsys.h" +#include "common/system.h" +#include "common/error.h" +#include "common/random.h" +#include "common/util.h" +#include "engines/engine.h" +#include "graphics/surface.h" +#include "mads/debugger.h" +#include "mads/dialogs.h" +#include "mads/events.h" +#include "mads/font.h" +#include "mads/game.h" +#include "mads/msurface.h" +#include "mads/resources.h" +#include "mads/sound.h" +#include "mads/user_interface.h" + +/** + * This is the namespace of the MADS engine. + * + * Status of this engine: In Development + * + * Games using this engine: + * - Rex Nebular and the Cosmic Gender Bender + */ +namespace MADS { + +#define DEBUG_BASIC 1 +#define DEBUG_INTERMEDIATE 2 +#define DEBUG_DETAILED 3 + +enum MADSDebugChannels { + kDebugPath = 1 << 0, + kDebugScripts = 1 << 1, + kDebugGraphics = 1 << 2 +}; + +enum { + GType_RexNebular = 0, + GType_DragonSphere = 1, + GType_Phantom = 2, + GType_Riddle = 3 +}; + +struct MADSGameDescription; + + +class MADSEngine : public Engine { +private: + const MADSGameDescription *_gameDescription; + Common::RandomSource _randomSource; + + bool _easyMouse; + bool _invObjectStill; + bool _textWindowStill; + + /** + * Handles basic initialisation + */ + void initialise(); +protected: + // Engine APIs + virtual Common::Error run(); + virtual bool hasFeature(EngineFeature f) const; +public: + Debugger *_debugger; + Dialogs *_dialogs; + EventsManager *_events; + Font *_font; + Game *_game; + Palette *_palette; + Resources *_resources; + MSurface *_screen; + SoundManager *_sound; + UserInterface *_userInterface; + +public: + MADSEngine(OSystem *syst, const MADSGameDescription *gameDesc); + virtual ~MADSEngine(); + + uint32 getFeatures() const; + Common::Language getLanguage() const; + Common::Platform getPlatform() const; + uint16 getVersion() const; + uint32 getGameID() const; + uint32 getGameFeatures() const; + + int getRandomNumber(int maxNumber); +}; + +} // End of namespace MADS + +#endif /* MADS_MADS_H */ diff --git a/engines/mads/module.mk b/engines/mads/module.mk new file mode 100644 index 0000000000..62441ab3c9 --- /dev/null +++ b/engines/mads/module.mk @@ -0,0 +1,35 @@ +MODULE := engines/mads + +MODULE_OBJS := \ + nebular/dialogs_nebular.o \ + nebular/game_nebular.o \ + nebular/sound_nebular.o \ + nebular/nebular_scenes.o \ + nebular/nebular_scenes8.o \ + assets.o \ + compression.o \ + debugger.o \ + detection.o \ + dialogs.o \ + events.o \ + font.o \ + game.o \ + game_data.o \ + graphics.o \ + mads.o \ + msprite.o \ + msurface.o \ + palette.o \ + resources.o \ + scene.o \ + scene_data.o \ + sound.o \ + user_interface.o + +# This module can be built as a plugin +ifeq ($(ENABLE_MADS), DYNAMIC_PLUGIN) +PLUGIN := 1 +endif + +# Include common rules +include $(srcdir)/rules.mk diff --git a/engines/mads/msprite.cpp b/engines/mads/msprite.cpp new file mode 100644 index 0000000000..279192fbdc --- /dev/null +++ b/engines/mads/msprite.cpp @@ -0,0 +1,107 @@ +/* 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/scummsys.h" +#include "engines/util.h" +#include "graphics/palette.h" +#include "mads/mads.h" +#include "mads/msurface.h" +#include "mads/msprite.h" + +namespace MADS { + +enum { + kEndOfLine = 0, + kEndOfSprite = 1, + kMarker = 2 +}; + +MSprite::MSprite(): MSurface() { + _encoding = 0; +} + +MSprite::MSprite(Common::SeekableReadStream *source, const Common::Point &offset, + int widthVal, int heightVal, bool decodeRle, uint8 encodingVal) + : MSurface(widthVal, heightVal), + _encoding(encodingVal), _offset(offset) { + + // Load the sprite data + loadSprite(source); +} + +MSprite::~MSprite() { +} + + +// TODO: The sprite outlines (pixel value 0xFD) are not shown +void MSprite::loadSprite(Common::SeekableReadStream *source) { + byte *outp, *lineStart; + bool newLine = false; + + outp = getData(); + lineStart = getData(); + + while (1) { + byte cmd1, cmd2, count, pixel; + + if (newLine) { + outp = lineStart + getWidth(); + lineStart = outp; + newLine = false; + } + + cmd1 = source->readByte(); + + if (cmd1 == 0xFC) + break; + else if (cmd1 == 0xFF) + newLine = true; + else if (cmd1 == 0xFD) { + while (!newLine) { + count = source->readByte(); + if (count == 0xFF) { + newLine = true; + } else { + pixel = source->readByte(); + while (count--) + *outp++ = (pixel == 0xFD) ? 0 : pixel; + } + } + } else { + while (!newLine) { + cmd2 = source->readByte(); + if (cmd2 == 0xFF) { + newLine = true; + } else if (cmd2 == 0xFE) { + count = source->readByte(); + pixel = source->readByte(); + while (count--) + *outp++ = (pixel == 0xFD) ? 0 : pixel; + } else { + *outp++ = (cmd2 == 0xFD) ? 0 : cmd2; + } + } + } + } +} + +} // End of namespace MADS diff --git a/engines/mads/msprite.h b/engines/mads/msprite.h new file mode 100644 index 0000000000..f2194dab08 --- /dev/null +++ b/engines/mads/msprite.h @@ -0,0 +1,114 @@ +/* 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. + * + */ + +#ifndef MADS_MSPRITE_H +#define MADS_MSPRITE_H + +#include "common/scummsys.h" +#include "mads/msurface.h" + +namespace MADS { + +class MADSEngine; + +struct BGR8 { + uint8 b, g, r; +}; + +typedef struct { + int32 x; // x position relative to GrBuff(0, 0) + int32 y; // y position relative to GrBuff(0, 0) + int32 scale_x; // x scale factor (can be negative for reverse draw) + int32 scale_y; // y scale factor (can't be negative) + uint8* depth_map; // depth code array for destination (doesn't care if srcDepth is 0) + BGR8* Pal; // palette for shadow draw (doesn't care if SHADOW bit is not set in Src.encoding) + uint8* ICT; // Inverse Color Table (doesn't care if SHADOW bit is not set in Src.encoding) + uint8 depth; // depth code for source (0 if no depth processing) +} DrawRequestX; + +typedef struct +{ + uint32 Pack; + uint32 Stream; + long hot_x; + long hot_y; + uint32 Width; + uint32 Height; + uint32 Comp; + uint32 Reserved[8]; + uint8* data; +} RendCell; + +#define SS_HEADER_NUM_FIELDS 14 +struct SpriteSeriesHeader { + uint32 header; + uint32 size; + uint32 packing; + uint32 frameRate; + uint32 pixSpeed; + uint32 maxWidth; + uint32 maxHeight; + uint32 reserved3; + uint32 reserved4; + uint32 reserved5; + uint32 reserved6; + uint32 reserved7; + uint32 reserved8; + uint32 count; +}; + +#define SF_HEADER_NUM_FIELDS 15 +struct SpriteFrameHeader { + uint32 pack; + uint32 stream; + uint32 x; + uint32 y; + uint32 width; + uint32 height; + uint32 comp; + uint32 reserved1; + uint32 reserved2; + uint32 reserved3; + uint32 reserved4; + uint32 reserved5; + uint32 reserved6; + uint32 reserved7; + uint32 reserved8; +}; + +class MSprite: public MSurface { +private: + void loadSprite(Common::SeekableReadStream *source); +public: + MSprite(); + MSprite(Common::SeekableReadStream *source, const Common::Point &offset, + int widthVal, int heightVal, bool decodeRle = true, uint8 encodingVal = 0); + virtual ~MSprite(); + + Common::Point _pos; + Common::Point _offset; + uint8 _encoding; +}; + +} // End of namespace MADS + +#endif /* MADS_MSPRITE_H */ diff --git a/engines/mads/msurface.cpp b/engines/mads/msurface.cpp new file mode 100644 index 0000000000..ca75a50bde --- /dev/null +++ b/engines/mads/msurface.cpp @@ -0,0 +1,262 @@ +/* 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 "engines/util.h" +#include "mads/compression.h" +#include "mads/graphics.h" +#include "mads/mads.h" +#include "mads/msprite.h" +#include "mads/msurface.h" +#include "mads/resources.h" + +namespace MADS { + +MADSEngine *MSurface::_vm = nullptr; + +MSurface::MSurface() { + pixels = nullptr; +} + +MSurface::MSurface(int width, int height) { + pixels = nullptr; + setSize(width, height); +} + +MSurface::~MSurface() { + Graphics::Surface::free(); +} + +void MSurface::setSize(int width, int height) { + Graphics::Surface::free(); + Graphics::Surface::create(width, height, Graphics::PixelFormat::createFormatCLUT8()); +} + +int MSurface::scaleValue(int value, int scale, int err) { + int scaled = 0; + while (value--) { + err -= scale; + while (err < 0) { + scaled++; + err += 100; + } + } + return scaled; +} + +void MSurface::drawSprite(const Common::Point &pt, SpriteInfo &info, const Common::Rect &clipRect) { + + enum { + kStatusSkip, + kStatusScale, + kStatusDraw + }; + + // NOTE: The current clipping code assumes that the top left corner of the clip + // rectangle is always 0, 0 + assert(clipRect.top == 0 && clipRect.left == 0); + + // TODO: Put err* and scaled* into SpriteInfo + int errX = info.hotX * info.scaleX % 100; + int errY = info.hotY * info.scaleY % 100; + int scaledWidth = scaleValue(info.width, info.scaleX, errX); + int scaledHeight = scaleValue(info.height, info.scaleY, errY); + + int x = pt.x, y = pt.y; + int clipX = 0, clipY = 0; + // Clip the sprite's width and height according to the clip rectangle's dimensions + // This clips the sprite to the bottom and right + if (x >= 0) { + scaledWidth = MIN<int>(x + scaledWidth, clipRect.right) - x; + } else { + clipX = x; + scaledWidth = x + scaledWidth; + } + if (y >= 0) { + scaledHeight = MIN<int>(y + scaledHeight, clipRect.bottom) - y; + } else { + clipY = y; + scaledHeight = y + scaledHeight; + } + + // Check if sprite is inside the screen. If it's not, there's no need to draw it + if (scaledWidth + x <= 0 || scaledHeight + y <= 0) // check left and top (in case x,y are negative) + return; + if (scaledWidth <= 0 || scaledHeight <= 0) // check right and bottom + return; + int heightAmt = scaledHeight; + + byte *src = info.sprite->getData(); + byte *dst = getBasePtr(x - info.hotX - clipX, y - info.hotY - clipY); + + int status = kStatusSkip; + byte *scaledLineBuf = new byte[scaledWidth]; + + while (heightAmt > 0) { + + if (status == kStatusSkip) { + // Skip line + errY -= info.scaleY; + if (errY < 0) + status = kStatusScale; + else + src += info.width; + } else { + + if (status == kStatusScale) { + // Scale current line + byte *lineDst = scaledLineBuf; + int curErrX = errX; + int width = scaledWidth; + byte *tempSrc = src; + int startX = clipX; + while (width > 0) { + byte pixel = *tempSrc++; + curErrX -= info.scaleX; + while (curErrX < 0) { + if (startX == 0) { + *lineDst++ = pixel; + width--; + } else { + startX++; + } + curErrX += 100; + } + } + src += info.width; + status = kStatusDraw; + } + + if (status == kStatusDraw && clipY == 0) { + // Draw previously scaled line + // TODO Implement different drawing types (depth, shadow etc.) + byte *tempDst = dst; + for (int lineX = 0; lineX < scaledWidth; lineX++) { + byte pixel = scaledLineBuf[lineX]; + + if (info.encoding & 0x80) { + + if (pixel == 0x80) { + pixel = 0; + } else { + byte destPixel = *tempDst; + byte r, g, b; + r = CLIP((info.palette[destPixel * 3] * pixel) >> 10, 0, 31); + g = CLIP((info.palette[destPixel * 3 + 1] * pixel) >> 10, 0, 31); + b = CLIP((info.palette[destPixel * 3 + 2] * pixel) >> 10, 0, 31); + pixel = info.inverseColorTable[(b << 10) | (g << 5) | r]; + } + } + + if (pixel) + *tempDst = pixel; + + tempDst++; + } + dst += pitch; + heightAmt--; + // TODO depth etc. + //depthAddress += Destination -> Width; + + errY += 100; + if (errY >= 0) + status = kStatusSkip; + } else if (status == kStatusDraw && clipY < 0) { + clipY++; + + errY += 100; + if (errY >= 0) + status = kStatusSkip; + } + + } + + } + + delete[] scaledLineBuf; + +} + +void MSurface::empty() { + Common::fill(getBasePtr(0, 0), getBasePtr(0, h), _vm->_palette->BLACK); +} + +void MSurface::updateScreen() { + g_system->copyRectToScreen((const byte *)pixels, pitch, 0, 0, w, h); + g_system->updateScreen(); +} + +void MSurface::copyFrom(MSurface *src, const Common::Rect &srcBounds, + const Common::Point &destPos, int transparentColor) { + // Validation of the rectangle and position + int destX = destPos.x, destY = destPos.y; + if ((destX >= w) || (destY >= h)) + return; + + Common::Rect copyRect = srcBounds; + if (destX < 0) { + copyRect.left += -destX; + destX = 0; + } else if (destX + copyRect.width() > w) { + copyRect.right -= destX + copyRect.width() - w; + } + if (destY < 0) { + copyRect.top += -destY; + destY = 0; + } else if (destY + copyRect.height() > h) { + copyRect.bottom -= destY + copyRect.height() - h; + } + + if (!copyRect.isValidRect()) + return; + + // Copy the specified area + + byte *data = src->getData(); + byte *srcPtr = data + (src->getWidth() * copyRect.top + copyRect.left); + byte *destPtr = (byte *)pixels + (destY * getWidth()) + destX; + + for (int rowCtr = 0; rowCtr < copyRect.height(); ++rowCtr) { + if (transparentColor == -1) + // No transparency, so copy line over + Common::copy(srcPtr, srcPtr + copyRect.width(), destPtr); + else { + // Copy each byte one at a time checking for the transparency color + for (int xCtr = 0; xCtr < copyRect.width(); ++xCtr) + if (srcPtr[xCtr] != transparentColor) destPtr[xCtr] = srcPtr[xCtr]; + } + + srcPtr += src->getWidth(); + destPtr += getWidth(); + } +} + +void MSurface::translate(Common::Array<RGB6> &palette) { + for (int y = 0; y < this->h; ++y) { + byte *pDest = getBasePtr(0, y); + + for (int x = 0; x < this->w; ++x, ++pDest) { + *pDest = palette[*pDest].palIndex; + } + } +} + +} // End of namespace MADS diff --git a/engines/mads/msurface.h b/engines/mads/msurface.h new file mode 100644 index 0000000000..7f4cec3c1c --- /dev/null +++ b/engines/mads/msurface.h @@ -0,0 +1,179 @@ +/* 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. + * + */ + +#ifndef MADS_MSURFACE_H +#define MADS_MSURFACE_H + +#include "common/scummsys.h" +#include "common/rect.h" +#include "graphics/surface.h" +#include "mads/palette.h" + +namespace MADS { + +class MADSEngine; +class MSprite; + +/** + * Basic sprite information + */ +struct SpriteInfo { + MSprite *sprite; + int hotX, hotY; + int width, height; + int scaleX, scaleY; + uint8 encoding; + byte *inverseColorTable; + byte *palette; +}; + +/* + * MADS graphics surface + */ +class MSurface : public Graphics::Surface { +private: + static MADSEngine *_vm; +public: + /** + * Sets the engine refrence used all surfaces + */ + static void setVm(MADSEngine *vm) { _vm = vm; } + + /** + * Helper method for calculating new dimensions when scaling a sprite + */ + static int scaleValue(int value, int scale, int err); +public: + /** + * Basic constructor + */ + MSurface(); + + /** + * Constructor for a surface with fixed dimensions + */ + MSurface(int width, int height); + + /** + * Destructor + */ + virtual ~MSurface(); + + /** + * Reinitialises a surface to have a given set of dimensions + */ + void setSize(int width, int height); + + /** + * Draws an arbitrary line on the screen using a specified color + * @param startPos Starting position + * @param endPos Ending position + * @param color Color to use + */ + void line(const Common::Point &startPos, const Common::Point &endPos, byte color); + + /** + * Draws a sprite + * @param pt Position to draw sprite at + * @param info General sprite details + * @param clipRect Clipping rectangle to constrain sprite drawing within + */ + void drawSprite(const Common::Point &pt, SpriteInfo &info, const Common::Rect &clipRect); + + /** + * Returns the width of the surface + */ + int getWidth() const { return w; } + + /** + * Returns the height of the surface + */ + int getHeight() const { return h; } + + /** + * Returns the size of the surface as a Rect + */ + Common::Rect getBounds() const { + return Common::Rect(0, 0, w, h); + } + + /** + * Returns a pointer to the surface data + */ + byte *getData() { return (byte *)Graphics::Surface::getPixels(); } + + /** + * Returns a pointer to a given position within the surface + */ + byte *getBasePtr(int x, int y) { return (byte *)Graphics::Surface::getBasePtr(x, y); } + + /** + * Clears the surface + */ + void empty(); + + /** + * Updates the screen with the contents of the surface + */ + void updateScreen(); + + /** + * Copys a sub-section of another surface into the current one. + * @param src Source surface + * @param srcBounds Area of source surface to copy + * @param destPos Destination position to draw in current surface + * @param transparentColor Transparency palette index + */ + void copyFrom(MSurface *src, const Common::Rect &srcBounds, const Common::Point &destPos, + int transparentColor = -1); + + /** + * Copies the surface to a given destination surface + */ + void copyTo(MSurface *dest, int transparentColor = -1) { + dest->copyFrom(this, Common::Rect(w, h), Common::Point(), transparentColor); + } + + /** + * Copies the surface to a given destination surface + */ + void copyTo(MSurface *dest, const Common::Point &pt, int transparentColor = -1) { + dest->copyFrom(this, Common::Rect(w, h), pt, transparentColor); + } + + /** + * Copies the surface to a given destination surface + */ + void copyTo(MSurface *dest, const Common::Rect &srcBounds, const Common::Point &destPos, + int transparentColor = -1) { + dest->copyFrom(this, srcBounds, destPos, transparentColor); + } + + /** + * Translates the pixels of an image used the passed palette with RGB mapping + */ + void translate(Common::Array<RGB6> &palette); +}; + +} // End of namespace MADS + +#endif /* MADS_MSURFACE_H */ diff --git a/engines/mads/nebular/dialogs_nebular.cpp b/engines/mads/nebular/dialogs_nebular.cpp new file mode 100644 index 0000000000..6d2321eae2 --- /dev/null +++ b/engines/mads/nebular/dialogs_nebular.cpp @@ -0,0 +1,118 @@ +/* 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/scummsys.h" +#include "common/config-manager.h" +#include "mads/mads.h" +#include "mads/graphics.h" +#include "mads/msurface.h" +#include "mads/nebular/dialogs_nebular.h" + +namespace MADS { + +namespace Nebular { + +CopyProtectionDialog::CopyProtectionDialog(MADSEngine *vm, bool priorAnswerWrong): + TextDialog(vm, FONT_INTERFACE, Common::Point(-1, -1), 32) { + getHogAnusEntry(_hogEntry); + + if (priorAnswerWrong) { + addLine("ANSWER INCORRECT!", true); + wordWrap("\n"); + addLine("(But we'll give you another chance!)"); + } else { + addLine("REX NEBULAR version 8.43", true); + wordWrap("\n"); + addLine("(Copy Protection, for your convenience)"); + } + wordWrap("\n"); + + wordWrap("Now comes the part that everybody hates. But if we don't"); + wordWrap("do this, nasty rodent-like people will pirate this game"); + wordWrap("and a whole generation of talented designers, programmers,"); + wordWrap("artists, and playtesters will go hungry, and will wander"); + wordWrap("aimlessly through the land at night searching for peace."); + wordWrap("So let's grit our teeth and get it over with. Just get"); + + Common::String line = "out your copy of "; + line += _hogEntry._bookId == 103 ? "the GAME MANUAL" : "REX'S LOGBOOK"; + line += ". See! That was easy. "; + wordWrap(line); + + line = Common::String::format("Next, just turn to page %d. On line %d, find word number %d, ", + _hogEntry._pageNum, _hogEntry._lineNum, _hogEntry._wordNum); + wordWrap(line); + + wordWrap("and type it on the line below (we',27h,'ve even given you"); + wordWrap("first letter as a hint). As soon as you do that, we can get"); + wordWrap("right into this really COOL adventure game!\n"); + wordWrap("\n"); + wordWrap(" "); + addInput(); + wordWrap("\n"); +} + +bool CopyProtectionDialog::show() { + draw(); + _vm->_events->showCursor(); + + // TODO: Replace with text input + while (!_vm->shouldQuit() && !_vm->_events->_keyPressed && + !_vm->_events->_mouseClicked) { + _vm->_events->delay(1); + } + + return true; +} + +bool CopyProtectionDialog::getHogAnusEntry(HOGANUS &entry) { + File f; + f.open("*HOGANUS.DAT"); + + // Read in the total number of entries, and randomly pick an entry to use + int numEntries = f.readUint16LE(); + int entryIndex = _vm->getRandomNumber(numEntries - 2) + 1; + + // Read in the encrypted entry + f.seek(28 * entryIndex + 2); + byte entryData[28]; + f.read(entryData, 28); + + // Decrypt it + for (int i = 0; i < 28; ++i) + entryData[i] = ~entryData[i]; + + // Fill out the fields + entry._bookId = entryData[0]; + entry._pageNum = READ_LE_UINT16(&entryData[2]); + entry._lineNum = READ_LE_UINT16(&entryData[4]); + entry._wordNum = READ_LE_UINT16(&entryData[6]); + entry._word = Common::String((char *)&entryData[8]); + + f.close(); + return true; +} + + +} // End of namespace Nebular + +} // End of namespace MADS diff --git a/engines/mads/nebular/dialogs_nebular.h b/engines/mads/nebular/dialogs_nebular.h new file mode 100644 index 0000000000..d4e4fe921e --- /dev/null +++ b/engines/mads/nebular/dialogs_nebular.h @@ -0,0 +1,76 @@ +/* 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. + * + */ + +#ifndef MADS_DIALOGS_NEBULAR_H +#define MADS_DIALOGS_NEBULAR_H + +#include "common/scummsys.h" +#include "mads/game.h" +#include "mads/dialogs.h" + +namespace MADS { + +namespace Nebular { + +class DialogsNebular: public Dialogs { + friend class Dialogs; +protected: + DialogsNebular(MADSEngine *vm): Dialogs(vm) {} +public: + virtual void showDialog() { + warning("TODO: showDialog"); + } +}; + +struct HOGANUS { + int _bookId; + int _pageNum; + int _lineNum; + int _wordNum; + Common::String _word; +}; + +class CopyProtectionDialog: public TextDialog { +private: + HOGANUS _hogEntry; + + /** + * Get a random copy protection entry from the HOGANUS resource + */ + bool getHogAnusEntry(HOGANUS &entry); +public: + /** + * Constructor + */ + CopyProtectionDialog(MADSEngine *vm, bool priorAnswerWrong); + + /** + * Show the dialog + */ + bool show(); +}; + +} // End of namespace Nebular + +} // End of namespace MADS + +#endif /* MADS_DIALOGS_NEBULAR_H */ diff --git a/engines/mads/nebular/game_nebular.cpp b/engines/mads/nebular/game_nebular.cpp new file mode 100644 index 0000000000..c55abf5601 --- /dev/null +++ b/engines/mads/nebular/game_nebular.cpp @@ -0,0 +1,190 @@ +/* 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/scummsys.h" +#include "common/config-manager.h" +#include "mads/mads.h" +#include "mads/game.h" +#include "mads/graphics.h" +#include "mads/msurface.h" +#include "mads/nebular/game_nebular.h" +#include "mads/nebular/dialogs_nebular.h" + +namespace MADS { + +namespace Nebular { + +GameNebular::GameNebular(MADSEngine *vm): Game(vm) { + _surface = new MSurface(MADS_SCREEN_WIDTH, MADS_SCREEN_HEIGHT - MADS_INTERFACE_HEIGHT); +} + +int GameNebular::checkCopyProtection() { + if (!ConfMan.getBool("copy_protection")) + return true; + + /* DEBUG: Disabled for now + CopyProtectionDialog *dlg = new CopyProtectionDialog(_vm, false); + dlg->show(); + delete dlg; + */ + + // DEBUG: Return that copy protection failed + return 1; +} + +void GameNebular::initialiseGlobals() { + // Allocate globals space + _globalFlags.resize(210); + for (int i = 0; i < 210; ++i) + _globalFlags[i] = 0; + + // Set specific values needed by the game + _globalFlags[4] = 8; + _globalFlags[33] = 1; + _globalFlags[10] = 0xFFFF; + _globalFlags[13] = 0xFFFF; + _globalFlags[15] = 0xFFFF; + _globalFlags[19] = 0xFFFF; + _globalFlags[20] = 0xFFFF; + _globalFlags[21] = 0xFFFF; + _globalFlags[95] = 0xFFFF; + + _objects.setData(3, 17, nullptr); + + // Put the values 0 through 3 in a random order in global slots 83 to 86 + for (int i = 0; i < 4;) { + int randomVal = _vm->getRandomNumber(3); + _globalFlags[83 + i] = randomVal; + + bool flag = false; + for (int idx2 = 0; idx2 < i; ++idx2) { + if (_globalFlags[83 + idx2] == randomVal) + flag = true; + } + + if (!flag) + ++i; + } + + // Put the values 0 through 3 in a random order in global slots 87 to 90 + for (int i = 0; i < 4;) { + int randomVal = _vm->getRandomNumber(3); + _globalFlags[87 + i] = randomVal; + + bool flag = false; + for (int idx2 = 0; idx2 < i; ++idx2) { + if (_globalFlags[87 + idx2] == randomVal) + flag = true; + } + + if (!flag) + ++i; + } + + _globalFlags[120] = 501; + _globalFlags[121] = 0xFFFF; + _globalFlags[55] = 0xFFFF; + _globalFlags[119] = 1; + _globalFlags[134] = 4; + + // Fill out the globals 200 to 209 with unique random values less than 10000 + for (int i = 0; i < 10; ++i) { + int randomVal = _vm->getRandomNumber(9999); + _globalFlags[200 + i] = randomVal; + + bool flag = false; + for (int idx2 = 0; idx2 < i; ++idx2) { + if (_globalFlags[200 + idx2] == randomVal) + flag = true; + } + + if (!flag) + ++i; + } + + // Difficulty level control + switch (_difficultyLevel) { + case DIFFICULTY_HARD: + _globalFlags[35] = 0; + _objects.setRoom(9, 1); + _objects.setRoom(50, 1); + _globalFlags[137] = 5; + _globalFlags[136] = 0; + break; + case DIFFICULTY_MEDIUM: + _globalFlags[35] = 0; + _objects.setRoom(8, 1); + _globalFlags[137] = 0xFFFF; + _globalFlags[136] = 6; + break; + case DIFFICULTY_EASY: + _globalFlags[35] = 2; + _objects.setRoom(8, 1); + _objects.setRoom(27, 1); + break; + default: + break; + } + + _player._direction = 8; + _player._newDirection = 8; + + loadResourceSequence("RXM", 1); + loadResourceSequence("ROX", 1); +} + +void GameNebular::setSectionHandler() { + delete _sectionHandler; + + switch (_sectionNumber) { + case 1: + _sectionHandler = new Section1Handler(_vm); + break; + case 2: + _sectionHandler = new Section2Handler(_vm); + break; + case 3: + _sectionHandler = new Section3Handler(_vm); + break; + case 4: + _sectionHandler = new Section4Handler(_vm); + break; + case 5: + _sectionHandler = new Section5Handler(_vm); + break; + case 6: + _sectionHandler = new Section6Handler(_vm); + break; + case 7: + _sectionHandler = new Section7Handler(_vm); + break; + case 8: + _sectionHandler = new Section8Handler(_vm); + break; + default: + break; + } +} + +} // End of namespace Nebular + +} // End of namespace MADS diff --git a/engines/mads/nebular/game_nebular.h b/engines/mads/nebular/game_nebular.h new file mode 100644 index 0000000000..6395ba0e4e --- /dev/null +++ b/engines/mads/nebular/game_nebular.h @@ -0,0 +1,69 @@ +/* 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. + * + */ + +#ifndef MADS_GAME_NEBULAR_H +#define MADS_GAME_NEBULAR_H + +#include "common/scummsys.h" +#include "mads/game.h" + +namespace MADS { + +namespace Nebular { + +class GameNebular: public Game { + friend class Game; +protected: + GameNebular(MADSEngine *vm); + + virtual int checkCopyProtection(); + + virtual void initialiseGlobals(); + + virtual void setSectionHandler(); +}; + + +class Section1Handler: public SectionHandler { +public: + Section1Handler(MADSEngine *vm): SectionHandler(vm) {} + + // TODO: Properly implement handler methods + virtual void preLoadSection() {} + virtual void sectionPtr2() {} + virtual void postLoadSection() {} +}; + +// TODO: Properly implement handler classes +typedef Section1Handler Section2Handler; +typedef Section1Handler Section3Handler; +typedef Section1Handler Section4Handler; +typedef Section1Handler Section5Handler; +typedef Section1Handler Section6Handler; +typedef Section1Handler Section7Handler; +typedef Section1Handler Section8Handler; + +} // End of namespace Nebular + +} // End of namespace MADS + +#endif /* MADS_GAME_NEBULAR_H */ diff --git a/engines/mads/nebular/nebular_scenes.cpp b/engines/mads/nebular/nebular_scenes.cpp new file mode 100644 index 0000000000..efd831a4b7 --- /dev/null +++ b/engines/mads/nebular/nebular_scenes.cpp @@ -0,0 +1,49 @@ +/* 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/scummsys.h" +#include "common/config-manager.h" +#include "mads/mads.h" +#include "mads/scene.h" +#include "mads/nebular/nebular_scenes.h" +#include "mads/nebular/nebular_scenes8.h" + +namespace MADS { + +namespace Nebular { + +SceneLogic *SceneFactory::createScene(Scene *scene) { + scene->addActiveVocab(NOUN_DROP); + scene->addActiveVocab(NOUN_DOLLOP); + scene->addActiveVocab(NOUN_DASH); + scene->addActiveVocab(NOUN_SPLASH); + scene->addActiveVocab(NOUN_ALCOHOL); + + // TODO: Implement all the game scenes + assert(scene->_nextSceneId == 804); + + return new Scene804(scene); +} + +} // End of namespace Nebular + +} // End of namespace MADS diff --git a/engines/mads/nebular/nebular_scenes.h b/engines/mads/nebular/nebular_scenes.h new file mode 100644 index 0000000000..28d24f090f --- /dev/null +++ b/engines/mads/nebular/nebular_scenes.h @@ -0,0 +1,91 @@ +/* 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. + * + */ + +#ifndef MADS_NEBULAR_SCENES_H +#define MADS_NEBULAR_SCENES_H + +#include "common/scummsys.h" +#include "mads/game.h" +#include "mads/scene.h" + +namespace MADS { + +namespace Nebular { + +enum Noun { + NOUN_BLOWGUN = 0x29, + NOUN_BURGER = 0x35, + NOUN_CHAIR = 0x47, + NOUN_DEAD_FISH = 0x65, + NOUN_DOOR = 0x6E, + NOUN_EAT = 0x75, + NOUN_EXAMINE = 0x7D, + NOUN_FRONT_WINDOW = 0x8E, + NOUN_FUZZY_DICE = 0x91, + NOUN_HOSE_DOWN = 0x0A6, + NOUN_HOTPANTS = 0x0A7, + NOUN_HULL = 0x0A8, + NOUN_HURL = 0x0A9, + NOUN_IGNITE = 0x0B4, + NOUN_INFLATE = 0x0B5, + NOUN_INSERT = 0x0B6, + NOUN_INSPECT = 0x0B7, + NOUN_JUNGLE = 0x0B8, + NOUN_LIFE_SUPPORT_SECTION = 0x0CC, + NOUN_LOG = 0x0D0, + NOUN_LOOK_AT = 0x0D1, + NOUN_LOOK_IN = 0x0D2, + NOUN_LOOK_THROUGH = 0x0D3, + NOUN_MONKEY = 0x0E3, + NOUN_OUTER_HULL = 0x0F8, + NOUN_OUTSIDE = 0x0F9, + NOUN_PEER_THROUGH = 0x103, + NOUN_PLANT_STALK = 0x10F, + NOUN_READ = 0x11F, + NOUN_REFRIDGERATOR = 0x122, + NOUN_ROBO_KITCHEN = 0x127, + NOUN_SHIELD_ACCESS_PANEL = 0x135, + NOUN_SHIELD_MODULATOR = 0x137, + NOUN_SHOOT = 0x13A, + NOUN_SIT_IN = 0x13F, + NOUN_SMELL = 0x147, + NOUN_STUFFED_FISH = 0x157, + NOUN_VIEW_SCREEN = 0x180, + NOUN_CAPTIVE_CREATURE = 0x1C3, + NOUN_NATIVE_WOMAN = 0x1DC, + NOUN_ALCOHOL = 0x310, + NOUN_DOLLOP = 0x3AC, + NOUN_DROP = 0x3AD, + NOUN_DASH = 0x3AE, + NOUN_SPLASH = 0x3AF +}; + +class SceneFactory { +public: + static SceneLogic *createScene(Scene *scene); +}; + +} // End of namespace Nebular + +} // End of namespace MADS + +#endif /* MADS_NEBULAR_SCENES_H */ diff --git a/engines/mads/nebular/nebular_scenes8.cpp b/engines/mads/nebular/nebular_scenes8.cpp new file mode 100644 index 0000000000..8feabc8037 --- /dev/null +++ b/engines/mads/nebular/nebular_scenes8.cpp @@ -0,0 +1,50 @@ +/* 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/scummsys.h" +#include "mads/nebular/nebular_scenes8.h" + +namespace MADS { + +namespace Nebular { + +void Scene804::setup() { +} + +void Scene804::enter() { +} + +void Scene804::step() { +} + +void Scene804::preActions() { +} + +void Scene804::actions() { +} + +void Scene804::postActions() { +} + +} // End of namespace Nebular + +} // End of namespace MADS diff --git a/engines/mads/nebular/nebular_scenes8.h b/engines/mads/nebular/nebular_scenes8.h new file mode 100644 index 0000000000..11bb8b0d81 --- /dev/null +++ b/engines/mads/nebular/nebular_scenes8.h @@ -0,0 +1,55 @@ +/* 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. + * + */ + +#ifndef MADS_NEBULAR_SCENES8_H +#define MADS_NEBULAR_SCENES8_H + +#include "common/scummsys.h" +#include "mads/game.h" +#include "mads/scene.h" + +namespace MADS { + +namespace Nebular { + +class Scene804: public SceneLogic { +public: + Scene804(Scene *scene): SceneLogic(scene) {} + + virtual void setup(); + + virtual void enter(); + + virtual void step(); + + virtual void preActions(); + + virtual void actions(); + + virtual void postActions(); +}; + +} // End of namespace Nebular + +} // End of namespace MADS + +#endif /* MADS_NEBULAR_SCENES8_H */ diff --git a/engines/mads/nebular/sound_nebular.cpp b/engines/mads/nebular/sound_nebular.cpp new file mode 100644 index 0000000000..5f1d80513e --- /dev/null +++ b/engines/mads/nebular/sound_nebular.cpp @@ -0,0 +1,1207 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "audio/audiostream.h" +#include "audio/decoders/raw.h" +#include "common/algorithm.h" +#include "common/debug.h" +#include "common/memstream.h" +#include "mads/sound.h" +#include "mads/nebular/sound_nebular.h" + +namespace MADS { + +namespace Nebular { + +AdlibChannel::AdlibChannel() { + _activeCount = 0; + _field1 = 0; + _field2 = 0; + _field3 = 0; + _field4 = 0; + _sampleIndex = 0; + _volume = 0; + _field7 = 0; + _field8 = 0; + _field9 = 0; + _fieldA = 0; + _fieldB = 0; + _fieldC = 0; + _fieldD = 0; + _fieldE = 0; + _ptr1 = nullptr; + _pSrc = nullptr; + _ptr3 = nullptr; + _ptr4 = nullptr; + _field17 = 0; + _field19 = 0; + _soundData = nullptr; + _field1D = 0; + _field1E = 0; + _field1F = 0; +} + +void AdlibChannel::reset() { + _activeCount = 0; + _field1 = 0; + _field2 = 0; + _field3 = 0; +} + +void AdlibChannel::enable(int flag) { + if (_activeCount) { + _fieldE = flag; + + // WORKAROUND: Original set _soundData pointer to flag. Since this seems + // just intended to invalidate any prior pointer, I've replaced it with + // a simple null pointer + _soundData = nullptr; + } +} + +void AdlibChannel::setPtr2(byte *pData) { + _pSrc = pData; + _field2 = 0xFF; + _fieldA = 1; + _field9 = 1; +} + +void AdlibChannel::load(byte *pData) { + _ptr1 = _pSrc = _ptr3 = pData; + _ptr4 = _soundData = pData; + _fieldA = 0xFF; + _activeCount = 1; + _fieldD = 64; + _field1 = 0; + _field1F = 0; + _field2 = _field3 = 0; + _volume = _field7 = 0; + _field1D = _field1E = 0; + _fieldE = 0; + _field9 = 0; + _fieldB = 0; + _field17 = 0; + _field19 = 0; +} + +void AdlibChannel::check(byte *nullPtr) { + if (_activeCount && _fieldE) { + if (!_field1E) { + _pSrc = nullPtr; + _fieldE = 0; + } else { + _field2 = 0xFF; + _fieldA = 4; + if (!_field9) + _field9 = 1; + } + } +} + +/*-----------------------------------------------------------------------*/ + +AdlibSample::AdlibSample(Common::SeekableReadStream &s) { + _attackRate = s.readByte(); + _decayRate = s.readByte(); + _sustainLevel = s.readByte(); + _releaseRate = s.readByte(); + _egTyp = s.readByte() != 0; + _ksr = s.readByte() != 0; + _totalLevel = s.readByte(); + _scalingLevel = s.readByte(); + _waveformSelect = s.readByte(); + _freqMultiple = s.readByte(); + _feedback = s.readByte(); + _ampMod = s.readByte() != 0; + _vib = s.readByte(); + _alg = s.readByte(); + _fieldE = s.readByte(); + s.skip(1); + _freqMask = s.readUint16LE(); + _freqBase = s.readUint16LE(); + _field14 = s.readUint16LE(); +} + +/*-----------------------------------------------------------------------*/ + +ASound::ASound(Audio::Mixer *mixer, const Common::String &filename, int dataOffset) { + // Open up the appropriate sound file + if (!_soundFile.open(filename)) + error("Could not open file - %s", filename.c_str()); + + // Initialise fields + _activeChannelPtr = nullptr; + _samplePtr = nullptr; + _frameCounter = 0; + _isDisabled = false; + _v1 = 0; + _v2 = 0; + _activeChannelNumber = 0; + _freqMask1 = _freqMask2 = 0; + _freqBase1 = _freqBase2 = 0; + _channelNum1 = _channelNum2 = 0; + _v7 = 0; + _v8 = 0; + _v9 = 0; + _v10 = 0; + _pollResult = 0; + _resultFlag = 0; + _nullData[0] = _nullData[1] = 0; + Common::fill(&_ports[0], &_ports[256], 0); + _stateFlag = false; + _activeChannelReg = 0; + _v11 = 0; + _randomSeed = 1234; + _amDep = _vibDep = _splitPoint = true; + + _samplesTillCallback = 0; + _samplesTillCallbackRemainder = 0; + _samplesPerCallback = getRate() / CALLBACKS_PER_SECOND; + _samplesPerCallbackRemainder = getRate() % CALLBACKS_PER_SECOND; + + // Store passed parameters, and setup OPL + _dataOffset = dataOffset; + _mixer = mixer; + _opl = OPL::Config::create(); + assert(_opl); + + _opl->init(getRate()); + _mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, this, -1, + Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); + + // Initialise the Adlib + adlibInit(); + + // Reset the adlib + command0(); +} + +ASound::~ASound() { + Common::List<CachedDataEntry>::iterator i; + for (i = _dataCache.begin(); i != _dataCache.end(); ++i) + delete[] (*i)._data; + + _mixer->stopHandle(_soundHandle); + delete _opl; +} + +void ASound::adlibInit() { + write(4, 0x60); + write(4, 0x80); + write(2, 0xff); + write(4, 0x21); + write(4, 0x60); + write(4, 0x80); +} + +int ASound::stop() { + command0(); + int result = _pollResult; + _pollResult = 0; + return result; +} + +int ASound::poll() { + // Update any playing sounds + update(); + + // Return result + int result = _pollResult; + _pollResult = 0; + return result; +} + +void ASound::noise() { + int randomVal = getRandomNumber(); + + if (_v1) { + setFrequency(_channelNum1, ((randomVal ^ 0xFFFF) & _freqMask1) + _freqBase1); + } + + if (_v2) { + setFrequency(_channelNum2, (randomVal & _freqMask2) + _freqBase2); + } +} + +void ASound::write(int reg, int val) { + _queue.push(RegisterValue(reg, val)); +} + +int ASound::write2(int state, int reg, int val) { + // TODO: Original has a state parameter, not used when in Adlib mode? + _ports[reg] = val; + write(reg, val); + return state; +} + +void ASound::flush() { + Common::StackLock slock(_driverMutex); + + while (!_queue.empty()) { + RegisterValue v = _queue.pop(); + _opl->writeReg(v._regNum, v._value); + } +} + +void ASound::channelOn(int reg, int volume) { + write2(8, reg, (_ports[reg] & 0xC0) | (volume & 0x3F)); +} + +void ASound::channelOff(int reg) { + write2(8, reg, _ports[reg] | 0x3F); +} + +void ASound::resultCheck() { + if (_resultFlag != 1) { + _resultFlag = 1; + _pollResult = 1; + } +} + +byte *ASound::loadData(int offset, int size) { + // First scan for an existing copy + Common::List<CachedDataEntry>::iterator i; + for (i = _dataCache.begin(); i != _dataCache.end(); ++i) { + CachedDataEntry &e = *i; + if (e._offset == offset) + return e._data; + } + + // No existing entry found, so load up data and store as a new entry + CachedDataEntry rec; + rec._offset = offset; + rec._data = new byte[size]; + _soundFile.seek(_dataOffset + offset); + _soundFile.read(rec._data, size); + _dataCache.push_back(rec); + + // Return the data + return rec._data; +} + +void ASound::playSound(int offset, int size) { + // Load the specified data block + playSoundData(loadData(offset, size)); +} + +void ASound::playSoundData(byte *pData, int startingChannel) { + // Scan for a high level free channel + for (int i = startingChannel; i < ADLIB_CHANNEL_COUNT; ++i) { + if (!_channels[i]._activeCount) { + _channels[i].load(pData); + return; + } + } + + // None found, do a secondary scan for an interruptable channel + for (int i = ADLIB_CHANNEL_COUNT - 1; i >= startingChannel; --i) { + if (_channels[i]._fieldE == 0xFF) { + _channels[i].load(pData); + return; + } + } +} + +bool ASound::isSoundActive(byte *pData) { + for (int i = 0; i < ADLIB_CHANNEL_MIDWAY; ++i) { + if (_channels[i]._activeCount && _channels[i]._soundData == pData) + return true; + } + + return false; +} + +void ASound::setFrequency(int channel, int freq) { + write2(8, 0xA0 + channel, freq & 0xFF); + write2(8, 0xB0 + channel, (freq >> 8) | 0x20); +} + +int ASound::getRandomNumber() { + int v = 0x9248 + (int)_randomSeed; + _randomSeed = ((v >> 3) | (v << 13)) & 0xFFFF; + return _randomSeed; +} + +void ASound::update() { + getRandomNumber(); + if (_isDisabled) + return; + + ++_frameCounter; + pollChannels(); + checkChannels(); + + if (_v1 == _v2) { + if (_resultFlag != -1) { + _resultFlag = -1; + _pollResult = -1; + } + } else { + if (_v1) { + _freqBase1 += _v7; + if (!--_v1) { + if (!_v2 || _channelNum1 != _channelNum2) { + write2(8, 0xA0 + _channelNum1, 0); + write2(8, 0xB0 + _channelNum1, 0); + } + } + } + + if (_v2) { + _freqBase2 += _v8; + if (!--_v2) { + if (!_v1 || _channelNum2 != _channelNum1) { + write2(8, 0xA0 + _channelNum2, 0); + write2(8, 0xB0 + _channelNum2, 0); + } + } + } + } +} + +void ASound::pollChannels() { + _activeChannelNumber = 0; + for (int i = 0; i < ADLIB_CHANNEL_COUNT; ++i) { + _activeChannelPtr = &_channels[i]; + pollActiveChannel(); + } +} + +void ASound::checkChannels() { + for (int i = 0; i < ADLIB_CHANNEL_COUNT; ++i) + _channels[i].check(_nullData); +} + +void ASound::pollActiveChannel() { + AdlibChannel *chan = _activeChannelPtr; + bool updateFlag = true; + + if (chan->_activeCount) { + if (chan->_field8 > 0 && --chan->_field8 == 0) + updateOctave(); + + if (--_activeChannelPtr->_activeCount <= 0) { + for (;;) { + byte *pSrc = chan->_pSrc; + if (!(*pSrc & 0x80) || (*pSrc <= 0xF0)) { + if (updateFlag) + updateActiveChannel(); + + chan->_field4 = *pSrc++; + chan->_activeCount = *pSrc++; + chan->_pSrc += 2; + + if (!chan->_field4 || !chan->_activeCount) { + updateOctave(); + } else { + chan->_field8 = chan->_activeCount - chan->_field7; + updateChannelState(); + } + + // Break out of processing loop + break; + } else { + updateFlag = false; + + switch ((~*pSrc) & 0xF) { + case 0: + if (!chan->_field17) { + if (*++pSrc == 0) { + chan->_pSrc += 2; + chan->_ptr3 = chan->_pSrc; + chan->_field17 = 0; + } else { + chan->_field17 = *pSrc; + chan->_pSrc = chan->_ptr3; + } + } else if (--chan->_field17) { + chan->_pSrc = chan->_ptr3; + } else { + chan->_pSrc += 2; + chan->_ptr3 = chan->_pSrc; + } + break; + + case 1: + if (!chan->_field19) { + if (*++pSrc == 0) { + chan->_pSrc += 2; + chan->_ptr4 = chan->_pSrc; + chan->_ptr3 = chan->_pSrc; + chan->_field17 = 0; + chan->_field19 = 0; + } else { + chan->_field19 = *pSrc; + chan->_pSrc = chan->_ptr4; + chan->_ptr3 = chan->_ptr4; + } + } else if (--chan->_field19) { + chan->_ptr4 = chan->_pSrc; + chan->_ptr3 = chan->_pSrc; + } else { + chan->_pSrc += 2; + chan->_ptr4 = chan->_pSrc; + chan->_ptr3 = chan->_pSrc; + } + break; + + case 2: + // Loop sound data + chan->_field1 = 0; + chan->_field2 = chan->_field3 = 0; + chan->_volume = chan->_field7 = 0; + chan->_field1D = chan->_field1E = 0; + chan->_field8 = 0; + chan->_field9 = 0; + chan->_fieldB = 0; + chan->_field17 = 0; + chan->_field19 = 0; + chan->_fieldD = 0x40; + chan->_ptr1 = chan->_soundData; + chan->_pSrc = chan->_soundData; + chan->_ptr3 = chan->_soundData; + chan->_ptr4 = chan->_soundData; + + chan->_pSrc += 2; + break; + + case 3: + chan->_sampleIndex = *++pSrc; + chan->_pSrc += 2; + loadSample(chan->_sampleIndex); + break; + + case 4: + chan->_field7 = *++pSrc; + chan->_pSrc += 2; + break; + + case 5: + chan->_field1 = *++pSrc; + chan->_pSrc += 2; + break; + + case 6: + ++pSrc; + if (chan->_fieldE) { + chan->_pSrc += 2; + } else { + chan->_volume = *pSrc >> 1; + updateFlag = true; + chan->_pSrc += 2; + } + break; + + case 7: + ++pSrc; + if (!chan->_fieldE) { + chan->_fieldA = *pSrc; + chan->_field2 = *++pSrc; + chan->_field9 = 1; + } + + chan->_pSrc += 3; + break; + + case 8: + chan->_field1D = *++pSrc; + chan->_pSrc += 2; + break; + + case 9: { + int v1 = *++pSrc; + ++pSrc; + int v2 = (v1 - 1) & getRandomNumber(); + int v3 = pSrc[v2]; + int v4 = pSrc[v1]; + + pSrc[v4 + v1 + 1] = v3; + chan->_pSrc += v1 + 3; + break; + } + + case 10: + ++pSrc; + if (chan->_fieldE) { + chan->_pSrc += 2; + } else { + chan->_field1E = *pSrc >> 1; + updateFlag = true; + chan->_pSrc += 2; + } + break; + + case 11: + chan->_fieldD = *++pSrc; + updateFlag = true; + chan->_pSrc += 2; + break; + + case 12: + chan->_fieldC = *++pSrc; + chan->_field3 = *++pSrc; + chan->_fieldB = 1; + chan->_pSrc += 2; + break; + + case 13: + ++pSrc; + chan->_pSrc += 2; + break; + + case 14: + chan->_field1F = *++pSrc; + chan->_pSrc += 2; + break; + + default: + break; + } + } + } + } + + if (chan->_field1) + updateFNumber(); + + updateFlag = false; + if (chan->_field9 || chan->_fieldB) { + if (!--chan->_field9) { + chan->_field9 = chan->_fieldA; + if (chan->_field2) { + int8 newVal = (int8)chan->_field2 + (int8)chan->_field1E; + if (newVal < 0) { + chan->_field9 = 0; + newVal = 0; + } else if (newVal > 63) { + chan->_field9 = 0; + newVal = 63; + } + + chan->_field1E = newVal; + updateFlag = true; + } + } + + if (!--chan->_fieldB) { + chan->_fieldB = chan->_fieldC; + if (chan->_field3) { + chan->_fieldD = chan->_field3; + updateFlag = true; + } + } + + if (updateFlag) + updateActiveChannel(); + } + } + + ++_activeChannelNumber; +} + +void ASound::updateOctave() { + int reg = 0xB0 + _activeChannelNumber; + write2(8, reg, _ports[reg] & 0xDF); +} + +static int _vList1[] = { + 0x200, 0x21E, 0x23F, 0x261, 0x285, 0x2AB, + 0x2D4, 0x2FF, 0x32D, 0x35D, 0x390, 0x3C7 +}; + +void ASound::updateChannelState() { + updateActiveChannel(); + + if (_channelData[_activeChannelNumber]._field0) { + if (_channelNum1 == _activeChannelNumber) + _stateFlag = 0; + if (_channelNum2 == _activeChannelNumber) + _stateFlag = 1; + + if (!_stateFlag) { + _stateFlag = 1; + if (_v1) + write2(8, 0xB0 + _channelNum1, _ports[0xB0 + _channelNum1] & 0xDF); + + _channelNum1 = _activeChannelNumber; + _v1 = _channelData[_channelNum1]._field0; + _freqMask1 = _channelData[_channelNum1]._freqMask; + _freqBase1 = _channelData[_channelNum1]._freqBase; + _v7 = _channelData[_channelNum1]._field6; + } else { + _stateFlag = 0; + if (_v2) + write2(8, 0xB0 + _channelNum2, _ports[0xB0 + _channelNum2] & 0xDF); + + _channelNum2 = _activeChannelNumber; + _v2 = _channelData[_channelNum2]._field0; + _freqMask2 = _channelData[_channelNum2]._freqMask; + _freqBase2 = _channelData[_channelNum2]._freqBase; + _v8 = _channelData[_channelNum2]._field6; + } + + resultCheck(); + } else { + int reg = 0xA0 + _activeChannelNumber; + int vTimes = (_activeChannelPtr->_field4 + _activeChannelPtr->_field1F) / 12; + int vOffset = (_activeChannelPtr->_field4 + _activeChannelPtr->_field1F) % 12; + int val = _vList1[vOffset] + _activeChannelPtr->_field1D; + write2(8, reg, val & 0xFF); + + reg += 0x10; + write2(8, reg, (_ports[reg] & 0x20) | (vTimes << 2) | (val >> 8)); + + write2(8, reg, _ports[reg] | 0x20); + } +} + +static const int outputIndexes[] = { + 3, 1, 4, 2, 5, 6, 9, 7, 10, 8, 11, 12, 15, 13, 16, 14, 17 +}; +static const int outputChannels[] = { + 0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19, 20, 21, 0 +}; +static const int volumeList[] = { + 0x3F, 0x3F, 0x36, 0x31, 0x2D, 0x2A, 0x28, 0x26, 0x24, 0x22, 0x21, 0x20, 0x1F, 0x1E, 0x1D, 0x1C, + 0x1B, 0x1A, 0x19, 0x19, 0x18, 0x17, 0x17, 0x16, 0x16, 0x15, 0x15, 0x14, 0x14, 0x13, 0x12, 0x12, + 0x11, 0x11, 0x10, 0x10, 0x0F, 0x0F, 0x0E, 0x0E, 0x0D, 0x0D, 0x0C, 0x0C, 0x0B, 0x0B, 0x0A, 0x0A, + 0x0A, 0x09, 0x09, 0x09, 0x09, 0x09, 0x08, 0x08, 0x08, 0x08, 0x08, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +void ASound::updateActiveChannel() { + int reg = 0x40 + outputChannels[outputIndexes[_activeChannelNumber * 2]]; + int portVal = _ports[reg] & 0xFFC0; + int newVolume = CLIP(_activeChannelPtr->_volume + _activeChannelPtr->_field1E, 0, 63); + + // Note: Original had a whole block not seeming to be used, since the initialisation + // sets a variable to 5660h, and doesn't change it, so the branch is never taken + int val = CLIP(newVolume - volumeList[_activeChannelPtr->_fieldD], 0, 63); + val = (63 - val) | portVal; + + int val2 = CLIP(newVolume - volumeList[-(_activeChannelPtr->_fieldD - 127)], 0, 63); + val2 = (63 - val2) | portVal; + write2(0, reg, val); + write2(2, reg, val2); +} + +void ASound::loadSample(int sampleIndex) { + _activeChannelReg = 0xB0 + _activeChannelNumber; + write2(8, _activeChannelReg, _ports[_activeChannelReg] & 0xDF); + + _activeChannelReg = _activeChannelNumber; + _samplePtr = &_samples[sampleIndex * 2]; + _v11 = outputChannels[outputIndexes[_activeChannelReg * 2 - 1]]; + processSample(); + + AdlibChannelData &cd = _channelData[_activeChannelNumber]; + cd._field6 = _samplePtr->_field14; + cd._freqBase = _samplePtr->_freqBase; + cd._freqMask = _samplePtr->_freqMask; + cd._field0 = _samplePtr->_fieldE; + + _samplePtr = &_samples[sampleIndex * 2 + 1]; + _v11 = outputChannels[outputIndexes[_activeChannelReg * 2]]; + processSample(); +} + +void ASound::processSample() { + // Write out vib flags and split point + write2(8, 0x40 + _v11, 0x3F); + int depthRhythm = (_ports[0xBD] & 0x3F) | (_amDep ? 0x80 : 0) | + (_vibDep ? 0x40 : 0); + write2(8, 0xBD, depthRhythm); + write2(8, 8, _splitPoint ? 0x40 : 0); + + // Write out feedback & Alg + int val = (_samplePtr->_feedback << 1) | (1 - _samplePtr->_alg); + write2(8, 0xC0 + _activeChannelReg, val); + + // Write out attack/decay rate + val = (_samplePtr->_attackRate << 4) | (_samplePtr->_decayRate & 0xF); + write2(8, 0x60 + _v11, val); + + // Write out sustain level/release rate + val = (_samplePtr->_sustainLevel << 4) | (_samplePtr->_releaseRate & 0xF); + write2(8, 0x80 + _v11, val); + + // Write out misc flags + val = (_samplePtr->_ampMod ? 0x80 : 0) | (_samplePtr->_vib ? 0x40 : 0) + | (_samplePtr->_egTyp ? 0x20 : 0) | (_samplePtr->_ksr ? 0x10 : 0) + | (_samplePtr->_freqMultiple & 0xF); + write2(8, 0x20 + _v11, val); + + // Write out waveform select + write2(8, 0xE0 + _v11, _samplePtr->_waveformSelect & 3); + + // Write out total level & scaling level + val = -((_samplePtr->_totalLevel & 0x3F) - 0x3F) | (_samplePtr->_scalingLevel << 6); + write2(8, 0x40 + _v11, val); +} + +void ASound::updateFNumber() { + int loReg = 0xA0 + _activeChannelNumber; + int hiReg = 0xB0 + _activeChannelNumber; + int val1 = (_ports[hiReg] & 0x1F) << 8; + val1 += _ports[loReg] + _activeChannelPtr->_field1; + write2(8, loReg, val1); + + int val2 = (_ports[hiReg] & 0x20) | (val1 >> 8); + write2(8, hiReg, val2); +} + +int ASound::readBuffer(int16 *buffer, const int numSamples) { + Common::StackLock slock(_driverMutex); + + int32 samplesLeft = numSamples; + memset(buffer, 0, sizeof(int16) * numSamples); + while (samplesLeft) { + if (!_samplesTillCallback) { + poll(); + flush(); + + _samplesTillCallback = _samplesPerCallback; + _samplesTillCallbackRemainder += _samplesPerCallbackRemainder; + if (_samplesTillCallbackRemainder >= CALLBACKS_PER_SECOND) { + _samplesTillCallback++; + _samplesTillCallbackRemainder -= CALLBACKS_PER_SECOND; + } + } + + int32 render = MIN<int>(samplesLeft, _samplesTillCallback); + samplesLeft -= render; + _samplesTillCallback -= render; + + _opl->readBuffer(buffer, render); + buffer += render; + } + return numSamples; +} + +int ASound::command0() { + bool isDisabled = _isDisabled; + _isDisabled = true; + + for (int i = 0; i < ADLIB_CHANNEL_COUNT; ++i) + _channels[i].reset(); + + _v1 = 0; + _v2 = 0; + _freqMask1 = _freqMask2 = 0; + _freqBase1 = _freqBase2 = 0; + _v7 = 0; + _v8 = 0; + + // Reset Adlib port registers + for (int reg = 0x4F; reg >= 0x40; --reg) + write2(8, reg, 0x3F); + for (int reg = 0xFF; reg >= 0x60; --reg) + write2(8, reg, 0); + for (int reg = 0x3F; reg > 0; --reg) + write2(8, reg, 0); + write2(8, 1, 0x20); + + _isDisabled = isDisabled; + return 0; +} + +int ASound::command1() { + for (int i = 0; i < ADLIB_CHANNEL_COUNT; ++i) + _channels[i].enable(0xFF); + return 0; +} + +int ASound::command2() { + for (int i = 0; i < ADLIB_CHANNEL_MIDWAY; ++i) + _channels[i].setPtr2(_nullData); + return 0; +} + +int ASound::command3() { + for (int i = 0; i < ADLIB_CHANNEL_MIDWAY; ++i) + _channels[i].enable(0xFF); + return 0; +} + +int ASound::command4() { + for (int i = ADLIB_CHANNEL_MIDWAY; i < ADLIB_CHANNEL_COUNT; ++i) + _channels[i].setPtr2(_nullData); + return 0; +} + +int ASound::command5() { + for (int i = 5; i < ADLIB_CHANNEL_COUNT; ++i) + _channels[i].enable(0xFF); + return 0; +} + +int ASound::command6() { + _v9 = _v1; + _v1 = 0; + _v10 = _v2; + _v2 = 0; + + channelOff(0x43); + channelOff(0x44); + channelOff(0x45); + channelOff(0x4B); + channelOff(0x4C); + channelOff(0x4D); + channelOff(0x53); + channelOff(0x54); + channelOff(0x55); + + return 0; +} + +int ASound::command7() { + channelOn(0x43, _channels[0]._volume); + channelOn(0x44, _channels[1]._volume); + channelOn(0x45, _channels[2]._volume); + channelOn(0x4B, _channels[3]._volume); + channelOn(0x4C, _channels[4]._volume); + channelOn(0x4D, _channels[5]._volume); + + _v1 = _v9; + _v2 = _v10; + + if (_v9 != _v10) + resultCheck(); + + _isDisabled = 0; + return _v10; +} + +int ASound::command8() { + int result = 0; + for (int i = 0; i < ADLIB_CHANNEL_COUNT; ++i) + result |= _channels[i]._activeCount; + + return result; +} + +/*-----------------------------------------------------------------------*/ + +const ASound1::CommandPtr ASound1::_commandList[42] = { + &ASound1::command0, &ASound1::command1, &ASound1::command2, &ASound1::command3, + &ASound1::command4, &ASound1::command5, &ASound1::command6, &ASound1::command7, + &ASound1::command8, &ASound1::command9, &ASound1::command10, &ASound1::command11, + &ASound1::command12, &ASound1::command13, &ASound1::command14, &ASound1::command15, + &ASound1::command16, &ASound1::command17, &ASound1::command18, &ASound1::command19, + &ASound1::command20, &ASound1::command21, &ASound1::command22, &ASound1::command23, + &ASound1::command24, &ASound1::command25, &ASound1::command26, &ASound1::command27, + &ASound1::command28, &ASound1::command29, &ASound1::command30, &ASound1::command31, + &ASound1::command32, &ASound1::command33, &ASound1::command34, &ASound1::command35, + &ASound1::command36, &ASound1::command37, &ASound1::command38, &ASound1::command39, + &ASound1::command40, &ASound1::command41 +}; + +ASound1::ASound1(Audio::Mixer *mixer): ASound(mixer, "asound.001", 0x1520) { + _cmd23Toggle = false; + + // Load sound samples + _soundFile.seek(_dataOffset + 0x12C); + for (int i = 0; i < 98; ++i) + _samples.push_back(AdlibSample(_soundFile)); +} + +int ASound1::command(int commandId, int param) { + if (commandId > 41) + return 0; + + _commandParam = param; + _frameCounter = 0; + return (this->*_commandList[commandId])(); +} + +int ASound1::command9() { + playSound(0xC68, 12); + return 0; +} + +int ASound1::command10() { + byte *pData1 = loadData(0x130E, 48); + if (!isSoundActive(pData1)) { + command1(); + _channels[0].load(pData1); + _channels[1].load(loadData(0x133E, 392)); + _channels[2].load(loadData(0x14C6, 46)); + _channels[3].load(loadData(0x14F4, 48)); + } + + return 0; +} + +int ASound1::command11() { + command111213(); + _channels[0]._field1E = 0; + _channels[1]._field1E = 0; + return 0; +} + +int ASound1::command12() { + command111213(); + _channels[0]._field1E = 40; + _channels[1]._field1E = 0; + return 0; +} + +int ASound1::command13() { + command111213(); + _channels[0]._field1E = 40; + _channels[1]._field1E = 50; + return 0; +} + +int ASound1::command14() { + playSound(0x1216, 248); + return 0; +} + +int ASound1::command15() { + byte *pData1 = loadData(0x1524, 152); + if (!isSoundActive(pData1)) { + command1(); + _channels[4].load(pData1); + _channels[5].load(loadData(0x15BC, 94)); + _channels[6].load(loadData(0x161A, 94)); + _channels[7].load(loadData(0x1678, 42)); + _channels[8].load(loadData(0x16A2, 42)); + } + + return 0; +} + +int ASound1::command16() { + playSound(0xC74, 14); + return 0; +} + +int ASound1::command17() { + playSound(0xE9A, 10); + return 0; +} + +int ASound1::command18() { + command1(); + playSound(0xCA6, 20); + return 0; +} + +int ASound1::command19() { + command1(); + playSound(0xCBA, 74); + return 0; +} + +int ASound1::command20() { + byte *pData = loadData(0xD18, 28); + if (!isSoundActive(pData)) + playSoundData(pData); + return 0; +} + +int ASound1::command21() { + playSound(0xD04, 20); + return 0; +} + +int ASound1::command22() { + byte *pData = loadData(0xD34, 10); + pData[6] = (getRandomNumber() & 7) + 85; + + if (!isSoundActive(pData)) + playSoundData(pData); + + return 0; +} + +int ASound1::command23() { + _cmd23Toggle = !_cmd23Toggle; + playSound(_cmd23Toggle ? 0xD3E : 0xD46, 8); + return 0; +} + +int ASound1::command24() { + playSound(0xD4E, 18); + playSound(0xD60, 20); + playSound(0xD74, 14); + return 0; +} + +int ASound1::command25() { + byte *pData = loadData(0xD82, 16); + if (!isSoundActive(pData)) + playSoundData(pData); + + return 0; +} + +int ASound1::command26() { + byte *pData = loadData(0xEEC, 10); + pData[5] = (command2627293032() + 0x7F) & 0xFF; + + if (!isSoundActive(pData)) + _channels[6].load(pData); + + return 0; +} + +int ASound1::command27() { + byte *pData = loadData(0xEE2, 10); + pData[5] = (command2627293032() + 0x40) & 0xFF; + + if (!isSoundActive(pData)) + _channels[7].load(pData); + + return 0; +} + +int ASound1::command28() { + playSound(0xD92, 28); + return 0; +} + +int ASound1::command29() { + byte *pData = loadData(0xC82, 36); + byte v = (command2627293032() + 0x40) & 0xFF; + pData[7] = pData[13] = pData[21] = pData[27] = v; + + if (!isSoundActive(pData)) + playSoundData(pData, 0); + + return 0; +} + +int ASound1::command30() { + byte *pData = loadData(0xEA6, 16); + pData[7] = (command2627293032() + 0x40) & 0xFF; + + if (!isSoundActive(pData)) + playSoundData(pData, 0); + + return 0; +} + +int ASound1::command31() { + byte *pData = loadData(0xDAE, 14); + if (!isSoundActive(pData)) + playSoundData(pData); + + return 0; +} + +int ASound1::command32() { + byte *pData = loadData(0xEB4, 46); + int v = command2627293032() + 0x40; + pData[9] = pData[17] = pData[25] = pData[33] = v & 0xFF; + pData[11] = pData[19] = pData[27] = pData[35] = v >> 8; + + if (!isSoundActive(pData)) + playSoundData(pData, 0); + + return 0; +} + +int ASound1::command33() { + playSound(0xDBC, 10); + playSound(0xDC6, 10); + return 0; +} + +int ASound1::command34() { + int v = getRandomNumber() & 0x20; + if (!v) + v = 0x60; + + byte *pData = loadData(0xDD0, 22); + pData[8] = pData[15] = v; + playSoundData(pData); + return 0; +} + +int ASound1::command35() { + playSound(0xDE6, 16); + return 0; +} + +int ASound1::command36() { + playSound(0xE10, 10); + command34(); + + return 0; +} + +int ASound1::command37() { + playSound(0xE1A, 14); + return 0; +} + +int ASound1::command38() { + playSound(0xE28, 114); + return 0; +} + +int ASound1::command39() { + byte *pData1 = loadData(0x16CC, 82); + if (!isSoundActive(pData1)) { + _channels[5].load(pData1); + _channels[6].load(loadData(0x171E, 30)); + _channels[7].load(loadData(0x173C, 40)); + _channels[8].load(loadData(0x1764, 64)); + } + return 0; +} + +int ASound1::command40() { + playSound(0xDF6, 26); + return 0; +} + +int ASound1::command41() { + playSound(0xC32, 34); + playSound(0xC54, 20); + return 0; +} + +void ASound1::command111213() { + byte *pData1 = loadData(0xEF6, 408); + if (!isSoundActive(pData1)) { + command1(); + _channels[0].load(pData1); + _channels[1].load(loadData(0x108E, 266)); + _channels[2].load(loadData(0x1198, 66)); + _channels[2].load(loadData(0x11DA, 60)); + } +} + +int ASound1::command2627293032() { + return (_commandParam > 0x40) ? _commandParam - 0x40 : _commandParam & 0xff00; +} + +} // End of namespace Nebular + +} // End of namespace MADS diff --git a/engines/mads/nebular/sound_nebular.h b/engines/mads/nebular/sound_nebular.h new file mode 100644 index 0000000000..caafdcaf77 --- /dev/null +++ b/engines/mads/nebular/sound_nebular.h @@ -0,0 +1,412 @@ +/* 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. + * + */ + +#ifndef MADS_SOUND_NEBULAR_H +#define MADS_SOUND_NEBULAR_H + +#include "common/scummsys.h" +#include "common/file.h" +#include "common/mutex.h" +#include "common/queue.h" +#include "audio/audiostream.h" +#include "audio/fmopl.h" +#include "audio/mixer.h" + +namespace MADS { + +class SoundManager; + +namespace Nebular { + +/** + * Represents the data for a channel on the Adlib + */ +class AdlibChannel { +public: + int _activeCount; + int _field1; + int _field2; + int _field3; + int _field4; + int _sampleIndex; + int _volume; + int _field7; + int _field8; + int _field9; + int _fieldA; + uint8 _fieldB; + int _fieldC; + int _fieldD; + int _fieldE; + byte *_ptr1; + byte *_pSrc; + byte *_ptr3; + byte *_ptr4; + int _field17; + int _field19; + byte *_soundData; + int _field1D; + int _field1E; + int _field1F; +public: + AdlibChannel(); + + void reset(); + void enable(int flag); + void setPtr2(byte *pData); + void load(byte *pData); + void check(byte *nullPtr); +}; + +class AdlibChannelData { +public: + int _field0; + int _freqMask; + int _freqBase; + int _field6; +}; + +class AdlibSample { +public: + int _attackRate; + int _decayRate; + int _sustainLevel; + int _releaseRate; + bool _egTyp; + bool _ksr; + int _totalLevel; + int _scalingLevel; + int _waveformSelect; + int _freqMultiple; + int _feedback; + bool _ampMod; + int _vib; + int _alg; + int _fieldE; + int _freqMask; + int _freqBase; + int _field14; + + AdlibSample() {} + AdlibSample(Common::SeekableReadStream &s); +}; + +struct RegisterValue { + uint8 _regNum; + uint8 _value; + + RegisterValue(int regNum, int value) { + _regNum = regNum; _value = value; + } +}; + +#define ADLIB_CHANNEL_COUNT 9 +#define ADLIB_CHANNEL_MIDWAY 5 +#define CALLBACKS_PER_SECOND 60 + +/** + * Base class for the sound player resource files + */ +class ASound: public Audio::AudioStream { +private: + struct CachedDataEntry { + int _offset; + byte *_data; + }; + Common::List<CachedDataEntry> _dataCache; + uint16 _randomSeed; + + /** + * Does the initial Adlib initialisation + */ + void adlibInit(); + + /** + * Does on-going processing for the Adlib sounds being played + */ + void update(); + + /** + * Polls each of the channels for updates + */ + void pollChannels(); + + /** + * Checks the status of the channels + */ + void checkChannels(); + + /** + * Polls the currently active channel + */ + void pollActiveChannel(); + + /** + * Updates the octave of the currently active channel + */ + void updateOctave(); + + void updateChannelState(); + void updateActiveChannel(); + + /** + * Loads up the specified sample + */ + void loadSample(int sampleIndex); + + /** + * Writes out the data of the selected sample to the Adlib + */ + void processSample(); + + void updateFNumber(); +protected: + /** + * Queue a byte for an Adlib register + */ + void write(int reg, int val); + + /** + * Queue a byte for an Adlib register, and store it in the _ports array + */ + int write2(int state, int reg, int val); + + /** + * Flush any pending Adlib register values to the OPL driver + */ + void flush(); + + /** + * Turn a channel on + */ + void channelOn(int reg, int volume); + + /** + * Turn a channel off + */ + void channelOff(int reg); + + /** + * Checks for whether a poll result needs to be set + */ + void resultCheck(); + + /** + * Loads a data block from the sound file, caching the result for any future + * calls for the same data + */ + byte *loadData(int offset, int size); + + /** + * Play the specified sound + * @param offset Offset of sound data within sound player data segment + * @param size Size of sound data block + */ + void playSound(int offset, int size); + + /** + * Play the specified raw sound data + * @param pData Pointer to data block containing sound data + * @param startingChannel Channel to start scan from + */ + void playSoundData(byte *pData, int startingChannel = ADLIB_CHANNEL_MIDWAY); + + /** + * Checks to see whether the given block of data is already loaded into a channel. + */ + bool isSoundActive(byte *pData); + + /** + * Sets the frequency for a given channel. + */ + void setFrequency(int channel, int freq); + + /** + * Returns a 16-bit random number + */ + int getRandomNumber(); + + int command0(); + int command1(); + int command2(); + int command3(); + int command4(); + int command5(); + int command6(); + int command7(); + int command8(); +public: + Audio::Mixer *_mixer; + FM_OPL *_opl; + Audio::SoundHandle _soundHandle; + AdlibChannel _channels[ADLIB_CHANNEL_COUNT]; + AdlibChannel *_activeChannelPtr; + AdlibChannelData _channelData[11]; + Common::Array<AdlibSample> _samples; + AdlibSample *_samplePtr; + Common::File _soundFile; + Common::Queue<RegisterValue> _queue; + Common::Mutex _driverMutex; + int _dataOffset; + int _frameCounter; + bool _isDisabled; + int _v1; + int _v2; + int _activeChannelNumber; + int _freqMask1; + int _freqMask2; + int _freqBase1; + int _freqBase2; + int _channelNum1, _channelNum2; + int _v7; + int _v8; + int _v9; + int _v10; + int _pollResult; + int _resultFlag; + byte _nullData[2]; + int _ports[256]; + bool _stateFlag; + int _activeChannelReg; + int _v11; + bool _amDep, _vibDep, _splitPoint; + int _samplesPerCallback; + int _samplesPerCallbackRemainder; + int _samplesTillCallback; + int _samplesTillCallbackRemainder; +public: + /** + * Constructor + * @param filename Specifies the adlib sound player file to use + * @param dataOffset Offset in the file of the data segment + */ + ASound(Audio::Mixer *mixer, const Common::String &filename, int dataOffset); + + /** + * Destructor + */ + virtual ~ASound(); + + /** + * Execute a player command. Most commands represent sounds to play, but some + * low number commands also provide control operations. + * @param commandId Player ommand to execute. + * @param param Optional parameter used by a few commands + */ + virtual int command(int commandId, int param) = 0; + + /** + * Stop all currently playing sounds + */ + int stop(); + + /** + * Main poll method to allow sounds to progress + */ + int poll(); + + /** + * General noise/note output + */ + void noise(); + + /** + * Return the current frame counter + */ + int getFrameCounter() { return _frameCounter; } + + // AudioStream interface + /** + * Main buffer read + */ + virtual int readBuffer(int16 *buffer, const int numSamples); + + /** + * Mono sound only + */ + virtual bool isStereo() const { return false; } + + /** + * Data is continuously pushed, so definitive end + */ + virtual bool endOfData() const { return false; } + + /** + * Return sample rate + */ + virtual int getRate() const { return 11025; } +}; + +class ASound1: public ASound { +private: + typedef int (ASound1::*CommandPtr)(); + static const CommandPtr _commandList[42]; + bool _cmd23Toggle; + int _commandParam; + + int command9(); + int command10(); + int command11(); + int command12(); + int command13(); + int command14(); + int command15(); + int command16(); + int command17(); + int command18(); + int command19(); + int command20(); + int command21(); + int command22(); + int command23(); + int command24(); + int command25(); + int command26(); + int command27(); + int command28(); + int command29(); + int command30(); + int command31(); + int command32(); + int command33(); + int command34(); + int command35(); + int command36(); + int command37(); + int command38(); + int command39(); + int command40(); + int command41(); + + void command111213(); + int command2627293032(); +public: + ASound1(Audio::Mixer *mixer); + + virtual int command(int commandId, int param); +}; + +} // End of namespace Nebular + +} // End of namespace MADS + +#endif /* MADS_SOUND_NEBULAR_H */ diff --git a/engines/mads/palette.cpp b/engines/mads/palette.cpp new file mode 100644 index 0000000000..aca1298a23 --- /dev/null +++ b/engines/mads/palette.cpp @@ -0,0 +1,447 @@ +/* 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/scummsys.h" +#include "engines/util.h" +#include "graphics/palette.h" +#include "mads/mads.h" +#include "mads/msurface.h" + +namespace MADS { + +void RGB6::load(Common::SeekableReadStream *f) { + r = f->readByte(); + g = f->readByte(); + b = f->readByte(); + palIndex = f->readByte(); + u2 = f->readByte(); + flags = f->readByte(); +} + +RGBList::RGBList(int numEntries, byte *srcData, bool freeData) { + _size = numEntries; + assert(numEntries <= PALETTE_COUNT); + + if (srcData == NULL) { + _data = new byte[numEntries * 3]; + _freeData = true; + } else { + _data = srcData; + _freeData = freeData; + } + + _palIndexes = new byte[numEntries]; + Common::fill(&_palIndexes[0], &_palIndexes[numEntries], 0); +} + +RGBList::~RGBList() { + if (_freeData) + delete[] _data; + delete[] _palIndexes; +} + +/*------------------------------------------------------------------------*/ + +PaletteUsage::PaletteUsage() { +} + +void PaletteUsage::load(int count, ...) { + va_list va; + va_start(va, count); + + _data.clear(); + for (int i = 0; i < count; ++i) + _data.push_back(va_arg(va, int)); + + va_end(va); +} + + +void PaletteUsage::getKeyEntries(Common::Array<RGB6> &palette) { + _data.clear(); + + for (uint i = 0; i < palette.size(); ++i) { + byte *uPtr = &palette[i].flags; + if ((*uPtr & 0x10) && _data.size() < 3) { + _data.push_back(i); + } + } +} + +void PaletteUsage::prioritize(Common::Array<RGB6> &palette) { + int lst[3]; + + for (uint i = 0; i < _data.size(); ++i) { + RGB6 &palEntry = palette[_data[i]]; + lst[i] = rgbMerge(palEntry); + } + + prioritizeFromList(lst); +} + +int PaletteUsage::rgbMerge(RGB6 &palEntry) { + return palEntry.r * 38 + palEntry.g * 76 + palEntry.b * 14; +} + +void PaletteUsage::prioritizeFromList(int lst[3]) { + int idx1 = _data.size() - 1; + bool continueFlag; + int count2; + + do { + continueFlag = false; + count2 = 0; + + if (idx1 > 0) { + int numEntries = _data.size() - 1; + int usageIndex = 0, lstIndex = 0; + + do { + if (lst[lstIndex] < lst[lstIndex + 1]) { + int lstVal = lst[lstIndex]; + int usageVal = _data[usageIndex]; + + if (numEntries > 0) { + Common::copy(&lst[lstIndex + 1], &lst[lstIndex + numEntries], &lst[lstIndex]); + _data.remove_at(usageIndex); + _data.push_back(0); + } + + int newIdx = 0; + if (idx1 > 0 && !continueFlag) { + for (newIdx = 0; newIdx <= idx1; ++newIdx) { + if (lst[newIdx] > lstVal) + break; + } + } + + continueFlag = true; + int idxDiff = _data.size() - newIdx - 1; + if (idxDiff > 0) { + Common::copy_backward(&lst[0], &lst[2], &lst[1]); + _data.remove_at(2); + _data.insert_at(0, 0); + } + + lst[newIdx] = lstVal; + _data[newIdx] = usageVal; + } + + ++usageIndex; + --numEntries; + ++lstIndex; + ++count2; + } while (count2 > idx1 && !continueFlag); + } + } while (continueFlag); +} + +void PaletteUsage::transform(Common::Array<RGB6> &palette) { + if (!empty()) { + for (uint i = 0; i < _data.size(); ++i) { + int palIndex = _data[i]; + _data[i] = palette[palIndex].palIndex; + } + } +} + +/*------------------------------------------------------------------------*/ + +#define VGA_COLOR_TRANS(x) (x == 0x3f ? 255 : x << 2) + +Palette::Palette(MADSEngine *vm) : _vm(vm) { + reset(); + _fading_in_progress = false; + Common::fill(&_usageCount[0], &_usageCount[PALETTE_COUNT], 0); + Common::fill(&_mainPalette[0], &_mainPalette[PALETTE_SIZE], 0); +} + +void Palette::setPalette(const byte *colors, uint start, uint num) { + g_system->getPaletteManager()->setPalette(colors, start, num); + reset(); +} + +void Palette::grabPalette(byte *colors, uint start, uint num) { + g_system->getPaletteManager()->grabPalette(colors, start, num); + reset(); +} + +uint8 Palette::palIndexFromRgb(byte r, byte g, byte b, byte *paletteData) { + byte index = 0; + int32 minDist = 0x7fffffff; + byte palData[PALETTE_SIZE]; + int Rdiff, Gdiff, Bdiff; + + if (paletteData == NULL) { + g_system->getPaletteManager()->grabPalette(palData, 0, PALETTE_COUNT); + paletteData = &palData[0]; + } + + for (int palIndex = 0; palIndex < PALETTE_COUNT; ++palIndex) { + Rdiff = r - paletteData[palIndex * 3]; + Gdiff = g - paletteData[palIndex * 3 + 1]; + Bdiff = b - paletteData[palIndex * 3 + 2]; + + if (Rdiff * Rdiff + Gdiff * Gdiff + Bdiff * Bdiff < minDist) { + minDist = Rdiff * Rdiff + Gdiff * Gdiff + Bdiff * Bdiff; + index = (uint8)palIndex; + } + } + + return (uint8)index; +} + +void Palette::reset() { + byte palData[PALETTE_SIZE]; + g_system->getPaletteManager()->grabPalette(palData, 0, PALETTE_COUNT); + + BLACK = palIndexFromRgb(0, 0, 0, palData); + BLUE = palIndexFromRgb(0, 0, 255, palData); + GREEN = palIndexFromRgb(0, 255, 0, palData); + CYAN = palIndexFromRgb(0, 255, 255, palData); + RED = palIndexFromRgb(255, 0, 0, palData); + VIOLET = palIndexFromRgb(255, 0, 255, palData); + BROWN = palIndexFromRgb(168, 84, 84, palData); + LIGHT_GRAY = palIndexFromRgb(168, 168, 168, palData); + DARK_GRAY = palIndexFromRgb(84, 84, 84, palData); + LIGHT_BLUE = palIndexFromRgb(0, 0, 127, palData); + LIGHT_GREEN = palIndexFromRgb(0, 127, 0, palData); + LIGHT_CYAN = palIndexFromRgb(0, 127, 127, palData); + LIGHT_RED = palIndexFromRgb(84, 0, 0, palData); + PINK = palIndexFromRgb(84, 0, 0, palData); + YELLOW = palIndexFromRgb(0, 84, 84, palData); + WHITE = palIndexFromRgb(255, 255, 255, palData); +} + +void Palette::resetColorCounts() { + Common::fill(&_usageCount[0], &_usageCount[PALETTE_COUNT], 0); +} + +void Palette::blockRange(int startIndex, int size) { + // Use a reference count of -1 to signal a palette index shouldn't be used + Common::fill(&_usageCount[startIndex], &_usageCount[startIndex + size], -1); +} + +void Palette::addRange(RGBList *list) { + byte *data = list->data(); + byte *palIndexes = list->palIndexes(); + byte palData[PALETTE_COUNT]; + g_system->getPaletteManager()->grabPalette(palData, 0, PALETTE_COUNT); + bool paletteChanged = false; + + for (int colIndex = 0; colIndex < list->size(); ++colIndex) { + // Scan through for an existing copy of the RGB value + int palIndex = -1; + while (++palIndex < PALETTE_COUNT) { + if (_usageCount[palIndex] <= 0) + // Palette index is to be skipped + continue; + + if ((palData[palIndex * 3] == data[colIndex * 3]) && + (palData[palIndex * 3 + 1] == data[colIndex * 3 + 1]) && + (palData[palIndex * 3 + 2] == data[colIndex * 3 + 2])) + // Match found + break; + } + + if (palIndex == PALETTE_COUNT) { + // No match found, so find a free slot to use + palIndex = -1; + while (++palIndex < PALETTE_COUNT) { + if (_usageCount[palIndex] == 0) + break; + } + + if (palIndex == PALETTE_COUNT) + error("addRange - Ran out of palette space to allocate"); + + palData[palIndex * 3] = data[colIndex * 3]; + palData[palIndex * 3 + 1] = data[colIndex * 3 + 1]; + palData[palIndex * 3 + 2] = data[colIndex * 3 + 2]; + paletteChanged = true; + } + + palIndexes[colIndex] = palIndex; + ++_usageCount[palIndex]; + } + + if (paletteChanged) { + g_system->getPaletteManager()->setPalette(&palData[0], 0, 256); + reset(); + } +} + +void Palette::deleteRange(RGBList *list) { + // Release the reference count on each of the palette entries + for (int colIndex = 0; colIndex < list->size(); ++colIndex) { + int palIndex = list->palIndexes()[colIndex]; + assert(_usageCount[palIndex] > 0); + --_usageCount[palIndex]; + } +} + +void Palette::deleteAllRanges() { + for (int colIndex = 0; colIndex < 255; ++colIndex) + _usageCount[colIndex] = 0; +} + +void Palette::setGradient(byte *palette, int start, int count, int rgbValue1, int rgbValue2) { + int rgbCtr = 0; + int rgbCurrent = rgbValue2; + int rgbDiff = -(rgbValue2 - rgbValue1); + + if (count > 0) { + byte *pDest = palette + start * 3; + int endVal = count - 1; + int numLeft = count; + + do { + pDest[0] = pDest[1] = pDest[2] = rgbCurrent; + + if (numLeft > 1) { + rgbCtr += rgbDiff; + if (rgbCtr >= endVal) { + do { + ++rgbCurrent; + rgbCtr += 1 - numLeft; + } while (rgbCtr >= endVal); + } + } + + pDest += 3; + } while (--numLeft > 0); + } +} + +byte *Palette::decodePalette(Common::SeekableReadStream *palStream, int *numColors) { + *numColors = palStream->readUint16LE(); + assert(*numColors <= 252); + + byte *palData = new byte[*numColors * 3]; + Common::fill(&palData[0], &palData[*numColors * 3], 0); + + for (int i = 0; i < *numColors; ++i) { + byte r = palStream->readByte(); + byte g = palStream->readByte(); + byte b = palStream->readByte(); + palData[i * 3] = VGA_COLOR_TRANS(r); + palData[i * 3 + 1] = VGA_COLOR_TRANS(g); + palData[i * 3 + 2] = VGA_COLOR_TRANS(b); + + // The next 3 bytes are unused + palStream->skip(3); + } + + return palData; +} + +int Palette::loadPalette(Common::SeekableReadStream *palStream, int indexStart) { + int colorCount; + byte *palData = decodePalette(palStream, &colorCount); + _vm->_palette->setPalette(palData, indexStart, colorCount); + + delete palData; + return colorCount; +} + +void Palette::setSystemPalette() { + resetColorCounts(); + + byte palData[4 * 3]; + palData[0 * 3] = palData[0 * 3 + 1] = palData[0 * 3 + 2] = 0; + palData[1 * 3] = palData[1 * 3 + 1] = palData[1 * 3 + 2] = 0x54; + palData[2 * 3] = palData[2 * 3 + 1] = palData[2 * 3 + 2] = 0xb4; + palData[3 * 3] = palData[3 * 3 + 1] = palData[3 * 3 + 2] = 0xff; + + setPalette(palData, 0, 4); + blockRange(0, 4); +} + +void Palette::resetGamePalette(int lowRange, int highRange) { + Common::fill((byte *)&_gamePalette[0], (byte *)&_gamePalette[PALETTE_COUNT], 0); + initRange(_mainPalette); + + // Init low range to common RGB values + if (lowRange) { + _gamePalette[0].r = 1; + _gamePalette[0].b = 0; + + Common::fill(&_gamePalette[1], &_gamePalette[lowRange - 1], _gamePalette[0]); + } + + // Init high range to common RGB values + if (highRange) { + _gamePalette[255].r = 1; + _gamePalette[255].b = 0; + + Common::fill(&_gamePalette[255 - highRange], &_gamePalette[254], _gamePalette[255]); + } +} + +void Palette::initGamePalette() { + // TODO +} + +void Palette::initRange(byte *palette) { + int var6 = 0; + int vdx = 0; + int vbx = 0; + do { + int vdi = (vdx == 1) ? 0 : 0x2A; + int var8 = 0; + int varE = vbx; + int var10 = vdx; + do { + vdx = 0; + do { + int vcx = 0; + int var4 = vdx; + do { + int var2 = var6 + vcx; + byte *destP = &palette[var2 * 3]; + + destP[0] = (var8) ? vdi & 0xFF : vbx & 0XFF; + destP[1] = (var4) ? vdi & 0xFF : vbx & 0XFF; + destP[2] = (vcx) ? vdi & 0xFF : vbx & 0XFF; + } while (++vcx < 2); + + var6 += 2; + vdx = var4; + } while (++vdx < 2); + } while (++var8 < 2); + + vdx = var10 + 1; + vbx = varE + 21; + } while (vbx < 42); + + palette[19] = 21; +} + +void Palette::setLowRange() { + _mainPalette[0] = _mainPalette[1] = _mainPalette[2] = 0; + _mainPalette[3] = _mainPalette[4] = _mainPalette[5] = 0x15; + _mainPalette[6] = _mainPalette[7] = _mainPalette[8] = 0x2A; + _mainPalette[9] = _mainPalette[10] = _mainPalette[11] = 0x3F; + _vm->_palette->setPalette(_mainPalette, 0, 4); +} + +} // End of namespace MADS diff --git a/engines/mads/palette.h b/engines/mads/palette.h new file mode 100644 index 0000000000..820e50fb21 --- /dev/null +++ b/engines/mads/palette.h @@ -0,0 +1,265 @@ +/* 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. + * + */ + +#ifndef MADS_PALETTE_H +#define MADS_PALETTE_H + +#include "common/scummsys.h" +#include "common/stream.h" + +namespace MADS { + +class MADSEngine; + +struct RGB4 { + byte r; + byte g; + byte b; + byte u; +}; + +struct RGB6 { + byte r; + byte g; + byte b; + byte palIndex; + byte u2; + byte flags; + + void load(Common::SeekableReadStream *f); +}; + +/** + * Used to store a list of RGB values + */ +class RGBList { +private: + int _size; + byte *_data; + byte *_palIndexes; + bool _freeData; +public: + /** + * Constructor + */ + RGBList(int numEntries = 256, byte *srcData = NULL, bool freeData = true); + + /** + * Destructor + */ + ~RGBList(); + + /** + * Returns the raw data containing the RGB values as 3 bytes per entry + */ + byte *data() { return _data; } + + /** + * Returns the list of palette indexes each RGB tuple maps to in the current palette + */ + byte *palIndexes() { return _palIndexes; } + + /** + * Returns the size of the palette + */ + int size() const { return _size; } +}; + +class PaletteUsage { +private: + Common::Array<int> _data; + + int rgbMerge(RGB6 &palEntry); + + void prioritizeFromList(int lst[3]); +public: + PaletteUsage(); + + void load(int count, ...); + + /** + * Returns whether the usage list is empty + */ + bool empty() const { return _data.size() == 0; } + + /** + * Gets key entries from the passed palette + * @param palette 6-bit per entry read in palette + */ + void getKeyEntries(Common::Array<RGB6> &palette); + + /** + * Prioritizes the palette index list based on the intensity of the + * RGB values of the palette entries that they refer to + */ + void prioritize(Common::Array<RGB6> &palette); + + bool process(Common::Array<RGB6> &palette, int v) { + warning("TODO: PaletteUsage::process"); + return 0; + } + + void transform(Common::Array<RGB6> &palette); +}; + + +#define PALETTE_COUNT 256 +#define PALETTE_SIZE (256 * 3) + +class Palette { +private: + /** + * Initialises a stanadrd range of colours for the given palette + */ + void initRange(byte *palette); +protected: + MADSEngine *_vm; + bool _colorsChanged; + + bool _fading_in_progress; + byte _originalPalette[PALETTE_COUNT * 4]; + byte _fadedPalette[PALETTE_COUNT * 4]; + int _usageCount[PALETTE_COUNT]; + + void reset(); +public: + byte _mainPalette[PALETTE_SIZE]; + RGB4 _gamePalette[PALETTE_COUNT]; + PaletteUsage _paletteUsage; +public: + /** + * Constructor + */ + Palette(MADSEngine *vm); + + /** + * Destructor + */ + virtual ~Palette() {} + + /** + * Sets a new palette + */ + void setPalette(const byte *colors, uint start, uint num); + + /** + * Returns a subset of the currently loaded palette + */ + void grabPalette(byte *colors, uint start, uint num); + + /** + * Returns the palette index in the palette that most closely matches the + * specified RGB pair + */ + uint8 palIndexFromRgb(byte r, byte g, byte b, byte *paletteData = nullptr); + + // Methods used for reference counting color usage + /** + * Resets the usage counts for the palette + */ + void resetColorCounts(); + + /** + * Blocks out a range of the palette from being used + */ + void blockRange(int startIndex, int size); + + /** + * Adds the data of an RGBList into the current palette and increment usage counts. + */ + void addRange(RGBList *list); + + /** + * Delets a range from the current palette, dercementing the accompanying usage counts. + */ + void deleteRange(RGBList *list); + + /** + * Deletes all loaded RGB lists are their usage references. + */ + void deleteAllRanges(); + + // Virtual method table + /** + * Decode a palette and return it, without affecting the Palette itself + */ + byte *decodePalette(Common::SeekableReadStream *palStream, int *numColors); + + /** + * Loads a palette from a stream + */ + int loadPalette(Common::SeekableReadStream *palStream, int indexStart = 0); + + /** + * Sets a small set of system/core colors needed by the game + */ + void setSystemPalette(); + + /** + * Update a range of an arbitrary palette + */ + static void setGradient(byte *palette, int start, int count, int rgbValue1, int rgbValue2); + + /** + * Resets the game palette + */ + void resetGamePalette(int v1, int v2); + + /** + * Initialises game palette + */ + void initGamePalette(); + + /** + * Set the first four palette entries with preset values + */ + void setLowRange(); + + /** + * Set up the palette as the game ends + */ + void close() { + warning("TODO: Palette::close"); + } + + // Color indexes + uint8 BLACK; + uint8 BLUE; + uint8 GREEN; + uint8 CYAN; + uint8 RED; + uint8 VIOLET; + uint8 BROWN; + uint8 LIGHT_GRAY; + uint8 DARK_GRAY; + uint8 LIGHT_BLUE; + uint8 LIGHT_GREEN; + uint8 LIGHT_CYAN; + uint8 LIGHT_RED; + uint8 PINK; + uint8 YELLOW; + uint8 WHITE; +}; + +} // End of namespace MADS + +#endif /* MADS_PALETTE_H */ diff --git a/engines/mads/resources.cpp b/engines/mads/resources.cpp new file mode 100644 index 0000000000..8cfc1a290a --- /dev/null +++ b/engines/mads/resources.cpp @@ -0,0 +1,326 @@ +/* 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/scummsys.h" +#include "common/archive.h" +#include "common/textconsole.h" +#include "mads/mads.h" +#include "mads/resources.h" + +namespace MADS { + +enum ResourceType {RESTYPE_ROOM, RESTYPE_SC, RESTYPE_TEXT, RESTYPE_QUO, RESTYPE_I, + RESTYPE_OB, RESTYPE_FONT, RESTYPE_SOUND, RESTYPE_SPEECH, RESTYPE_HAS_EXT, RESTYPE_NO_EXT}; + +/** + * HAG Archives implementation + */ +class HagArchive : public Common::Archive { +private: + /** + * Details of a single entry in a HAG file index + */ + struct HagEntry { + Common::String _resourceName; + uint32 _offset; + uint32 _size; + + HagEntry(): _offset(0), _size(0) {} + HagEntry(Common::String resourceName, uint32 offset, uint32 size): + _resourceName(resourceName), _offset(offset), _size(size) {} + }; + + class HagIndex { + public: + Common::List<HagEntry> _entries; + Common::String _filename; + }; + + Common::Array<HagIndex> _index; + + /** + * Load the index of all the game's HAG files + */ + void loadIndex(); + + /** + * Given a resource name, opens up the correct HAG file and returns whether + * an entry with the given name exists. + */ + bool getHeaderEntry(const Common::String &resourceName, HagIndex &hagIndex, HagEntry &hagEntry) const; + + /** + * Returns the HAG resource filename that will contain a given resource + */ + Common::String getResourceFilename(const Common::String &resourceName) const; + + /** + * Return a resource type given a resource name + */ + ResourceType getResourceType(const Common::String &resourceName) const; +public: + HagArchive(); + virtual ~HagArchive(); + + // Archive implementation + virtual bool hasFile(const Common::String &name) const; + virtual int listMembers(Common::ArchiveMemberList &list) const; + virtual const Common::ArchiveMemberPtr getMember(const Common::String &name) const; + virtual Common::SeekableReadStream *createReadStreamForMember(const Common::String &name) const; +}; + +const char *const MADSCONCAT_STRING = "MADSCONCAT"; + +HagArchive::HagArchive() { + loadIndex(); +} + +HagArchive::~HagArchive() { +} + +// Archive implementation +bool HagArchive::hasFile(const Common::String &name) const { + HagIndex hagIndex; + HagEntry hagEntry; + return getHeaderEntry(name, hagIndex, hagEntry); +} + +int HagArchive::listMembers(Common::ArchiveMemberList &list) const { + int members = 0; + + for (uint hagCtr = 0; hagCtr < _index.size(); ++hagCtr) { + HagIndex hagIndex = _index[hagCtr]; + Common::List<HagEntry>::iterator i; + + for (i = hagIndex._entries.begin(); i != hagIndex._entries.end(); ++i) { + list.push_back(Common::ArchiveMemberList::value_type( + new Common::GenericArchiveMember((*i)._resourceName, this))); + ++members; + } + } + + return members; +} + +const Common::ArchiveMemberPtr HagArchive::getMember(const Common::String &name) const { + if (!hasFile(name)) + return Common::ArchiveMemberPtr(); + + return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(name, this)); +} + +Common::SeekableReadStream *HagArchive::createReadStreamForMember(const Common::String &name) const { + HagIndex hagIndex; + HagEntry hagEntry; + + if (getHeaderEntry(name, hagIndex, hagEntry)) { + // Entry found. If the correct file is not already open, open it + Common::File f; + if (!f.open(hagIndex._filename)) + error("Could not open HAG file"); + + // Return a new stream for the specific resource + f.seek(hagEntry._offset); + return f.readStream(hagEntry._size); + } + + return nullptr; +} + +void HagArchive::loadIndex() { + Common::File hagFile; + + for (int sectionIndex = -1; sectionIndex < 10; ++sectionIndex) { + Common::String filename = (sectionIndex == -1) ? "GLOBAL.HAG" : + Common::String::format("SECTION%d.HAG", sectionIndex); + if (!hagFile.open(filename)) + error("Could not locate HAG file - %s", filename.c_str()); + + // Check for header + char headerBuffer[16]; + if ((hagFile.read(headerBuffer, 16) != 16) || + (strncmp(headerBuffer, MADSCONCAT_STRING, 10) != 0)) + error("Invalid HAG file opened"); + + // Scan through the HAG index + int numEntries = hagFile.readUint16LE(); + + HagIndex hagIndex; + hagIndex._filename = filename; + + for (int idx = 0; idx < numEntries; ++idx) { + // Read in the details of the next resource + char resourceBuffer[14]; + uint32 offset = hagFile.readUint32LE(); + uint32 size = hagFile.readUint32LE(); + hagFile.read(resourceBuffer, 14); + + hagIndex._entries.push_back(HagEntry(resourceBuffer, offset, size)); + } + + hagFile.close(); + _index.push_back(hagIndex); + } +} + +bool HagArchive::getHeaderEntry(const Common::String &resourceName, + HagIndex &hagIndex, HagEntry &hagEntry) const { + Common::String resName = resourceName; + resName.toUppercase(); + if (resName[0] == '*') + resName.deleteChar(0); + + Common::String hagFilename = getResourceFilename(resName); + + // Find the index for the given file + for (uint hagCtr = 0; hagCtr < _index.size(); ++hagCtr) { + hagIndex = _index[hagCtr]; + + if (hagIndex._filename == hagFilename) { + Common::List<HagEntry>::iterator ei; + for (ei = hagIndex._entries.begin(); ei != hagIndex._entries.end(); ++ei) { + hagEntry = *ei; + if (hagEntry._resourceName == resName) + return true; + } + } + } + + return false; +} + +Common::String HagArchive::getResourceFilename(const Common::String &resourceName) const { + ResourceType resType = getResourceType(resourceName); + Common::String outputFilename = "GLOBAL.HAG"; + + if ((resType == RESTYPE_ROOM) || (resType == RESTYPE_SC)) { + int value = atoi(resourceName.c_str() + 2); + int hagFileNum = (resType == RESTYPE_ROOM) ? value / 100 : value; + + if (hagFileNum > 0) + outputFilename = Common::String::format("SECTION%d.HAG", hagFileNum); + } + + if (resType == RESTYPE_SPEECH) + outputFilename = "SPEECH.HAG"; + + return outputFilename; +} + +ResourceType HagArchive::getResourceType(const Common::String &resourceName) const { + if (resourceName.hasPrefix("RM")) { + // Room resource + return RESTYPE_ROOM; + } else if (resourceName.hasPrefix("SC")) { + // SC resource + return RESTYPE_SC; + } else if (resourceName.hasSuffix(".TXT")) { + // Text resource + return RESTYPE_TEXT; + } else if (resourceName.hasSuffix(".QUO")) { + // QUO resource + return RESTYPE_QUO; + } else if (resourceName.hasPrefix("I")) { + // I resource + return RESTYPE_I; + } else if (resourceName.hasPrefix("OB")) { + // OB resource + return RESTYPE_OB; + } else if (resourceName.hasPrefix("FONT")) { + // FONT resource + return RESTYPE_FONT; + } else if (resourceName.hasPrefix("SOUND")) { + // SOUND resource + return RESTYPE_SOUND; + } else if (resourceName.hasPrefix("SPCHC")) { + // SPEECH resource + return RESTYPE_SPEECH; + } + + // Check for a known extension + const char *extPos = strchr(resourceName.c_str(), '.'); + if (extPos) { + ++extPos; + if (!strcmp(extPos, "FL") || !strcmp(extPos, "LBM") || !strcmp(extPos, "ANM") || + !strcmp(extPos, "AA") || !strcmp(extPos, "SS")) { + return RESTYPE_HAS_EXT; + } + } + + return RESTYPE_NO_EXT; +} + +/*------------------------------------------------------------------------*/ + +void Resources::init(MADSEngine *vm) { + SearchMan.add("HAG", new HagArchive()); +} + +Common::String Resources::formatName(RESPREFIX resType, int id, const Common::String &ext) { + Common::String result = "*"; + + if (resType == 3 && !id) { + id = id / 100; + } + + if (!ext.empty()) { + switch (resType) { + case RESPREFIX_GL: + result += "GL000"; + break; + case RESPREFIX_SC: + result += Common::String::format("SC%.3d", id); + break; + case RESPREFIX_RM: + result += Common::String::format("RM%.3d", id); + break; + default: + break; + } + + result += ext; + } + + return result; +} + +Common::String Resources::formatResource(const Common::String &resName, + const Common::String &hagFilename) { +// int v1 = 0, v2 = 0; + + if (resName.hasPrefix("*")) { + // Resource file specified + error("TODO: formatResource"); + } else { + // File outside of hag file + return resName; + } +} + +/*------------------------------------------------------------------------*/ + +void File::openFile(const Common::String &filename) { + if (!Common::File::open(filename)) + error("Could not open file - %s", filename.c_str()); +} + +} // End of namespace MADS diff --git a/engines/mads/resources.h b/engines/mads/resources.h new file mode 100644 index 0000000000..8fed0ab437 --- /dev/null +++ b/engines/mads/resources.h @@ -0,0 +1,76 @@ +/* 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. + * + */ + +#ifndef MADS_RESOURCES_H +#define MADS_RESOURCES_H + +#include "common/scummsys.h" +#include "common/file.h" +#include "common/str.h" + +namespace MADS { + +class MADSEngine; + +enum RESPREFIX { + RESPREFIX_GL = 1, RESPREFIX_SC = 2, RESPREFIX_RM = 3 +}; + +enum EXTTYPE { + EXT_SS = 1, EXT_AA = 2, EXT_DAT = 3, EXT_HH = 4, EXT_ART = 5, EXT_INT = 6 +}; + +class Resources { +public: + /** + * Instantiates the resource manager + */ + static void init(MADSEngine *vm); + + static Common::String formatName(RESPREFIX resType, int id, const Common::String &ext); + static Common::String formatResource(const Common::String &resName, const Common::String &hagFilename); +}; + +/** + * Derived file class + */ +class File: public Common::File { +public: + /** + * Constructor + */ + File(): Common::File() {} + + /** + * Constructor + */ + File(const Common::String &filename) { openFile(filename); } + + /** + * Opens the given file, throwing an error if it can't be opened + */ + void openFile(const Common::String &filename); +}; + +} // End of namespace MADS + +#endif /* MADS_RESOURCES_H */ diff --git a/engines/mads/scene.cpp b/engines/mads/scene.cpp new file mode 100644 index 0000000000..e9ae3e6ddd --- /dev/null +++ b/engines/mads/scene.cpp @@ -0,0 +1,179 @@ +/* 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/scummsys.h" +#include "mads/scene.h" +#include "mads/mads.h" +#include "mads/nebular/nebular_scenes.h" + +namespace MADS { + +Scene::Scene(MADSEngine *vm): _vm(vm), _spriteSlots(vm) { + _priorSceneId = 0; + _nextSceneId = 0; + _currentSceneId = 0; + _vocabBuffer = nullptr; + _sceneLogic = nullptr; + _sceneInfo = nullptr; + + _verbList.push_back(VerbInit(VERB_LOOK, 2, 0)); + _verbList.push_back(VerbInit(VERB_TAKE, 2, 0)); + _verbList.push_back(VerbInit(VERB_PUSH, 2, 0)); + _verbList.push_back(VerbInit(VERB_OPEN, 2, 0)); + _verbList.push_back(VerbInit(VERB_PUT, 1, -1)); + _verbList.push_back(VerbInit(VERB_TALKTO, 2, 0)); + _verbList.push_back(VerbInit(VERB_GIVE, 1, 2)); + _verbList.push_back(VerbInit(VERB_PULL, 2, 0)); + _verbList.push_back(VerbInit(VERB_CLOSE, 2, 0)); + _verbList.push_back(VerbInit(VERB_THROW, 1, 2)); +} + +Scene::~Scene() { + delete[] _vocabBuffer; + delete _sceneLogic; + delete _sceneInfo; +} + +void Scene::clearDynamicHotspots() { + _dynamicHotspots.clear(); + _dynamicHotspotsChanged = false; +} + +void Scene::clearVocab() { + freeVocab(); + _activeVocabs.clear(); +} + +void Scene::freeVocab() { + delete[] _vocabBuffer; + _vocabBuffer = nullptr; +} + +void Scene::addActiveVocab(int vocabId) { + if (activeVocabIndexOf(vocabId) == -1) { + assert(_activeVocabs.size() < 200); + _activeVocabs.push_back(vocabId); + } +} + +int Scene::activeVocabIndexOf(int vocabId) { + for (uint i = 0; i < _activeVocabs.size(); ++i) { + if (_activeVocabs[i] == vocabId) + return i; + } + + return -1; +} + +void Scene::clearSequenceList() { + _sequences.clear(); +} + +void Scene::clearMessageList() { + _messages.clear(); + _talkFont = "*FONTCONV.FF"; + _textSpacing = -1; +} + +void Scene::loadSceneLogic() { + delete _sceneLogic; + + switch (_vm->getGameID()) { + case GType_RexNebular: + _sceneLogic = Nebular::SceneFactory::createScene(this); + break; + default: + error("Unknown game"); + } +} + +void Scene::loadScene(int sceneId, const Common::String &prefix, bool palFlag) { + // Store the previously active scene number and set the new one + _priorSceneId = _currentSceneId; + _currentSceneId = sceneId; + + _v1 = 0; + if (palFlag) + _vm->_palette->resetGamePalette(18, 10); + + _spriteSlots.clear(false); + _sequences.clear(); + _messages.clear(); + + // TODO: palletteUsage reset? setPalette(_nullPalette); + _sceneInfo = SceneInfo::load(_vm, _currentSceneId, _v1, Common::String(), _vm->_game->_v2 ? 17 : 16, + _depthSurface, _backgroundSurface); +} + +void Scene::loadHotspots() { + File f(Resources::formatName(RESPREFIX_RM, _currentSceneId, ".HH")); + int count = f.readUint16LE(); + + _hotspots.clear(); + for (int i = 0; i < count; ++i) + _hotspots.push_back(Hotspot(f)); +} + +void Scene::loadVocab() { + // Add all the verbs to the active vocab list + for (uint i = 0; i < _verbList.size(); ++i) + addActiveVocab(_verbList[i]._id); + + // Load the vocabs for any object descriptions and custom actions + for (uint objIndex = 0; objIndex < _vm->_game->_objects.size(); ++objIndex) { + InventoryObject &io = _vm->_game->_objects[objIndex]; + addActiveVocab(io._descId); + + for (int vocabIndex = 0; vocabIndex <io._vocabCount; ++vocabIndex) { + addActiveVocab(io._vocabList[vocabIndex]._vocabId); + } + } + + // Load scene hotspot list vocabs and verbs + for (uint i = 0; i < _hotspots.size(); ++i) { + addActiveVocab(_hotspots[i]._vocabId); + if (_hotspots[i]._verbId) + addActiveVocab(_hotspots[i]._verbId); + } + + loadVocabStrings(); +} + +void Scene::loadVocabStrings() { + freeVocab(); + File f("*VOCAB.DAT"); + + byte *d = new byte[ f.size()]; + f.read(d, f.size()); + + +// int vocabId = 1; + for (uint strIndex = 0; strIndex < _activeVocabs.size(); ++strIndex) { + // TODO: Rest of this method + } +} + +void Scene::free() { + warning("TODO: Scene::free"); +} + +} // End of namespace MADS diff --git a/engines/mads/scene.h b/engines/mads/scene.h new file mode 100644 index 0000000000..a1edff5bdf --- /dev/null +++ b/engines/mads/scene.h @@ -0,0 +1,144 @@ +/* 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. + * + */ + +#ifndef MADS_SCENE_H +#define MADS_SCENE_H + +#include "common/scummsys.h" +#include "common/array.h" +#include "common/rect.h" +#include "mads/assets.h" +#include "mads/msurface.h" +#include "mads/scene_data.h" + +namespace MADS { + +class Scene { +private: + /** + * Free the voculary list buffer + */ + void freeVocab(); + + /** + * Return the index of a given Vocab in the active vocab list + */ + int activeVocabIndexOf(int vocabId); + + /** + * Secondary loading vocab list + */ + void loadVocabStrings(); +protected: + MADSEngine *_vm; +public: + SceneLogic *_sceneLogic; + int _priorSceneId; + int _nextSceneId; + int _currentSceneId; + Common::Array<VerbInit> _verbList; + Common::Array<TextDisplay> _textDisplay; + SpriteSlots _spriteSlots; + Common::Array<SpriteAsset *> _sprites; + int _spritesIndex; + Common::Array<DynamicHotspot> _dynamicHotspots; + bool _dynamicHotspotsChanged; + byte *_vocabBuffer; + Common::Array<int> _activeVocabs; + Common::Array<SequenceEntry> _sequences; + Common::Array<KernelMessage> _messages; + Common::String _talkFont; + int _textSpacing; + Common::Array<Hotspot> _hotspots; + ScreenObjects _screenObjects; + int _v1; + SceneInfo *_sceneInfo; + MSurface _backgroundSurface; + MSurface _depthSurface; + + /** + * Constructor + */ + Scene(MADSEngine *vm); + + /** + * Destructor + */ + ~Scene(); + + /** + * Clear the dynamic hotspot list + */ + void clearDynamicHotspots(); + + /** + * Clear the vocabulary list + */ + void clearVocab(); + + /** + * Add a given vocab entry to the active list + */ + void addActiveVocab(int vocabId); + + /** + * Clear the sequence list + */ + void clearSequenceList(); + + /** + * Clear the message list + */ + void clearMessageList(); + + /** + * Loads the scene logic for a given scene + */ + void loadSceneLogic(); + + /** + * Loads the resources associated with the given scene + * @param sceneId Scene to load + * @param prefix Prefix to use for retrieving animation data + * @param palFlag Flag for whether to reset the high/lo palette areas + */ + void loadScene(int sceneId, const Common::String &prefix, bool palFlag); + + /** + * Loads the hotstpots for the scene + */ + void loadHotspots(); + + /** + * Loads the vocab list + */ + void loadVocab(); + + /** + * Clear the data for the scene + */ + void free(); +}; + +} // End of namespace MADS + +#endif /* MADS_SCENE_H */ diff --git a/engines/mads/scene_data.cpp b/engines/mads/scene_data.cpp new file mode 100644 index 0000000000..e0c25d89fb --- /dev/null +++ b/engines/mads/scene_data.cpp @@ -0,0 +1,358 @@ +/* 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/scummsys.h" +#include "mads/scene_data.h" +#include "mads/mads.h" +#include "mads/compression.h" +#include "mads/resources.h" +#include "mads/nebular/nebular_scenes.h" + +namespace MADS { + +SpriteSlot::SpriteSlot() { + _spriteType = ST_NONE; + _seqIndex = 0; + _spritesIndex = 0; + _frameNumber = 0; + _depth = 0; + _scale = 0; +} + +SpriteSlot::SpriteSlot(SpriteType type, int seqIndex) { + _spriteType = type; + _seqIndex = seqIndex; + _spritesIndex = 0; + _frameNumber = 0; + _depth = 0; + _scale = 0; +} + +/*------------------------------------------------------------------------*/ + +void SpriteSlots::clear(bool flag) { + _vm->_game->_scene._textDisplay.clear(); + + if (flag) + _vm->_game->_scene._sprites.clear(); + + Common::Array<SpriteSlot>::clear(); + push_back(SpriteSlot(ST_FULL_SCREEN_REFRESH, -1)); +} + +/** + * Releases any sprites used by the player + */ +void SpriteSlots::releasePlayerSprites() { + Player &player = _vm->_game->player(); + + if (player._spritesLoaded && player._numSprites > 0) { + int spriteEnd = player._spritesStart + player._numSprites - 1; + do { + deleteEntry(spriteEnd); + } while (--spriteEnd >= player._spritesStart); + } +} + +void SpriteSlots::deleteEntry(int index) { + remove_at(index); +} + + +/*------------------------------------------------------------------------*/ + +TextDisplay::TextDisplay() { + _active = false; + _spacing = 0; + _expire = 0; + _col1 = _col2 = 0; +} + +/*------------------------------------------------------------------------*/ + +DynamicHotspot::DynamicHotspot() { + _seqIndex = 0; + _facing = 0; + _descId = 0; + _field14 = 0; + _articleNumber = 0; + _cursor = 0; +} + +/*------------------------------------------------------------------------*/ + +SequenceEntry::SequenceEntry() { + _spritesIndex = 0; + _flipped =0; + _frameIndex = 0; + _frameStart = 0; + _numSprites = 0; + _animType = 0; + _frameInc = 0; + _depth = 0; + _scale = 0; + _dynamicHotspotIndex = -1; + _triggerCountdown = 0; + _doneFlag = 0; + _entries._count = 0; + _abortMode = 0; + _actionNouns[0] = _actionNouns[1] = _actionNouns[2] = 0; + _numTicks = 0; + _extraTicks = 0; + _timeout = 0; +} + +KernelMessage::KernelMessage() { + _flags = 0; + _seqInex = 0; + _asciiChar = '\0'; + _asciiChar2 = '\0'; + _colors = 0; + _msgOffset = 0; + _numTicks = 0; + _frameTimer2 = 0; + _frameTimer = 0; + _timeout = 0; + _field1C = 0; + _abortMode = 0; + _nounList[0] = _nounList[1] = _nounList[2] = 0; +} + +/*------------------------------------------------------------------------*/ + +Hotspot::Hotspot() { + _facing = 0; + _articleNumber = 0; + _cursor = 0; + _vocabId = 0; + _verbId = 0; +} + +Hotspot::Hotspot(Common::SeekableReadStream &f) { + _bounds.left = f.readSint16LE(); + _bounds.top = f.readSint16LE(); + _bounds.right = f.readSint16LE(); + _bounds.bottom = f.readSint16LE(); + _feetPos.x = f.readSint16LE(); + _feetPos.y = f.readSint16LE(); + _facing = f.readByte(); + _articleNumber = f.readByte(); + f.skip(1); + _cursor = f.readByte(); + _vocabId = f.readUint16LE(); + _verbId = f.readUint16LE(); +} + +/*------------------------------------------------------------------------*/ + +void ARTHeader::load(Common::SeekableReadStream *f) { + // Read in dimensions of image + _width = f->readUint16LE(); + _height = f->readUint16LE(); + + // Read in palette information + int palCount = f->readUint16LE(); + for (int i = 0; i < palCount; ++i) { + RGB6 rgb; + rgb.load(f); + _palette.push_back(rgb); + } + f->skip(6 * (256 - palCount)); + + // Read unknown??? + palCount = f->readUint16LE(); + for (int i = 0; i < palCount; ++i) { + RGB4 rgb; + rgb.r = f->readByte(); + rgb.g = f->readByte(); + rgb.b = f->readByte(); + rgb.u = f->readByte(); + + _palData.push_back(rgb); + } +} + +/*------------------------------------------------------------------------*/ + +SceneInfo *SceneInfo::load(MADSEngine *vm, int sceneId, int v1, const Common::String &resName, + int v3, MSurface &depthSurface, MSurface &bgSurface) { + return new SceneInfo(vm, sceneId, v1, resName, v3, depthSurface, bgSurface); +} + +SceneInfo::SceneInfo(MADSEngine *vm, int sceneId, int v1, const Common::String &resName, + int flags, MSurface &depthSurface, MSurface &bgSurface) { + bool flag = true; + bool sceneFlag = sceneId >= 0; + bool ssFlag = false, wsFlag = false; + + Common::Array<SpriteAsset *> spriteSets; + Common::Array<int> xpList; + + // Figure out the resource to use + Common::String resourceName; + if (sceneFlag) { + resourceName = Resources::formatName(RESPREFIX_RM, sceneId, ".DAT"); + } else { + resourceName = "*" + Resources::formatResource(resName, resName); + } + + // Open the scene info resource for access + File infoFile(resName); + + // Read in basic data + _sceneId = infoFile.readUint16LE(); + _artFileNum = infoFile.readUint16LE(); + _depthStyle = infoFile.readUint16LE(); + _width = infoFile.readUint16LE(); + _height = infoFile.readUint16LE(); + infoFile.skip(24); + _nodeCount = infoFile.readUint16LE(); + _yBandsEnd = infoFile.readUint16LE(); + _yBandsStart = infoFile.readUint16LE(); + _maxScale = infoFile.readUint16LE(); + _minScale = infoFile.readUint16LE(); + for (int i = 0; i < 15; ++i) + _depthList[i] = infoFile.readUint16LE(); + _field4A = infoFile.readUint16LE(); + + // Load the set of objects that are associated with the scene + for (int i = 0; i < 20; ++i) { + InventoryObject obj; + obj.load(infoFile); + _objects.push_back(obj); + } + + int setCount = infoFile.readUint16LE(); + int field40E = infoFile.readUint16LE(); + + for (int i = 0; i < 20; ++i) { + char name[64]; + infoFile.read(name, 64); + _setNames.push_back(Common::String(name)); + } + + infoFile.close(); + int width = _width; + int height = _height; + + if (!bgSurface.getPixels()) { + bgSurface.setSize(width, height); + ssFlag = true; + } + + if (_depthStyle == 2) + width >>= 2; + if (!depthSurface.getPixels()) { + depthSurface.setSize(width, height); + wsFlag = true; + } + + // Load the depth surface with the scene codes + loadCodes(depthSurface); + + // Get the ART resource + if (sceneFlag) { + resourceName = Resources::formatName(RESPREFIX_RM, sceneId, ".ART"); + } else { + resourceName = "*" + Resources::formatResource(resName, resName); + } + + // Load in the ART header and palette + File artFile(resourceName); + MadsPack artResource(&artFile); + Common::SeekableReadStream *stream = artResource.getItemStream(0); + + ARTHeader artHeader; + artHeader.load(stream); + artFile.close(); + + // Copy out the palette data + for (uint i = 0; i < artHeader._palData.size(); ++i) + _palette.push_back(artHeader._palData[i]); + + if (!(flags & 1)) { + if (!_vm->_palette->_paletteUsage.empty()) { + _vm->_palette->_paletteUsage.getKeyEntries(artHeader._palette); + _vm->_palette->_paletteUsage.prioritize(artHeader._palette); + } + + _field4C = _vm->_palette->_paletteUsage.process(artHeader._palette, 0xF800); + if (_field4C > 0) { + _vm->_palette->_paletteUsage.transform(artHeader._palette); + + for (uint i = 0; i < _palette.size(); ++i) { + byte g = _palette[i].g; + _palette[g].b = artHeader._palette[g].palIndex; + } + } + } + + // Read in the background surface data + assert(_width == bgSurface.w && _height == bgSurface.h); + stream->read(bgSurface.getPixels(), bgSurface.w * bgSurface.h); + + if (flags & 1) { + for (uint i = 0; i < _setNames.size(); ++i) { + Common::String setResName; + if (sceneFlag || resName.hasPrefix("*")) + setResName += "*"; + setResName += _setNames[i]; + + SpriteAsset *sprites = new SpriteAsset(_vm, setResName, flags); + spriteSets.push_back(sprites); + xpList.push_back(-1); // TODO:: sprites->_field6 + } + } + + + + warning("TODO"); +} + +void SceneInfo::loadCodes(MSurface &depthSurface) { + File f(Resources::formatName(RESPREFIX_RM, _sceneId, ".DAT")); + + uint16 width = _width; + uint16 height = _height; + byte *walkMap = new byte[f.size()]; + + depthSurface.setSize(width, height); + f.read(walkMap, f.size()); + + byte *ptr = (byte *)depthSurface.getPixels(); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int ofs = x + (y * width); + if ((walkMap[ofs / 8] << (ofs % 8)) & 0x80) + *ptr++ = 1; // walkable + else + *ptr++ = 0; + } + } + + delete[] walkMap; +} + +/*------------------------------------------------------------------------*/ + +} // End of namespace MADS diff --git a/engines/mads/scene_data.h b/engines/mads/scene_data.h new file mode 100644 index 0000000000..74a1ff8e68 --- /dev/null +++ b/engines/mads/scene_data.h @@ -0,0 +1,305 @@ +/* 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. + * + */ + +#ifndef MADS_SCENE_DATA_H +#define MADS_SCENE_DATA_H + +#include "common/scummsys.h" +#include "common/array.h" +#include "common/str.h" +#include "common/str-array.h" +#include "common/rect.h" +#include "mads/assets.h" +#include "mads/game_data.h" + +namespace MADS { + +class MADSEngine; +class Scene; + +enum { + VERB_LOOK = 3, + VERB_TAKE = 4, + VERB_PUSH = 5, + VERB_OPEN = 6, + VERB_PUT = 7, + VERB_TALKTO = 8, + VERB_GIVE = 9, + VERB_PULL = 10, + VERB_CLOSE = 11, + VERB_THROW = 12, + VERB_WALKTO = 13 +}; + +class VerbInit { +public: + int _id; + int _action1; + int _action2; + + VerbInit() {} + VerbInit(int id, int action1, int action2): _id(id), _action1(action1), _action2(action2) {} +}; + +class ScreenObjects { +public: + int _v8333C; + int _v832EC; + int _yp; +}; + +enum SpriteType { + ST_NONE = 0, ST_FOREGROUND = 1, ST_BACKGROUND = -4, + ST_FULL_SCREEN_REFRESH = -2, ST_EXPIRED = -1 +}; + +class SpriteSlot { +public: + SpriteType _spriteType; + int _seqIndex; + int _spritesIndex; + int _frameNumber; + Common::Point _position; + int _depth; + int _scale; +public: + SpriteSlot(); + SpriteSlot(SpriteType type, int seqIndex); +}; + +class SpriteSlots: public Common::Array<SpriteSlot> { +private: + MADSEngine *_vm; +public: + SpriteSlots(MADSEngine *vm): _vm(vm) {} + + /** + * Clears any pending slot data and schedules a full screen refresh. + * @param flag Also reset sprite list + */ + void clear(bool flag); + + /** + * Delete any sprites used by the player + */ + void releasePlayerSprites(); + + /** + * Delete a sprite entry + * @param index Specifies the index in the array + */ + void deleteEntry(int index); +}; + +class TextDisplay { +public: + bool _active; + int _spacing; + Common::Rect _bounds; + int _expire; + int _col1; + int _col2; + Common::String _fontName; + Common::String _msg; + + TextDisplay(); +}; + +class DynamicHotspot { +public: + int _seqIndex; + Common::Rect _bounds; + Common::Point _feetPos; + int _facing; + int _descId; + int _field14; + int _articleNumber; + int _cursor; + + DynamicHotspot(); +}; + +class SequenceEntry { +public: + int _spritesIndex; + int _flipped; + int _frameIndex; + int _frameStart; + int _numSprites; + int _animType; + int _frameInc; + int _depth; + int _scale; + int _dynamicHotspotIndex; + + Common::Point _msgPos; + + int _triggerCountdown; + bool _doneFlag; + struct { + int _count; + int _mode[5]; + int _frameIndex[5]; + int _abortVal[5]; + } _entries; + int _abortMode; + int _actionNouns[3]; + int _numTicks; + int _extraTicks; + int _timeout; + + SequenceEntry(); +}; + +class KernelMessage { +public: + int _flags; + int _seqInex; + char _asciiChar; + char _asciiChar2; + int _colors; + Common::Point _posiition; + int _msgOffset; + int _numTicks; + int _frameTimer2; + int _frameTimer; + int _timeout; + int _field1C; + int _abortMode; + int _nounList[3]; + Common::String _msg; + + KernelMessage(); +}; + +class Hotspot { +public: + Common::Rect _bounds; + Common::Point _feetPos; + int _facing; + int _articleNumber; + int _cursor; + int _vocabId; + int _verbId; + + Hotspot(); + Hotspot(Common::SeekableReadStream &f); +}; + +class SceneLogic { +protected: + Scene *_scene; +public: + /** + * Constructor + */ + SceneLogic(Scene *scene): _scene(scene) {} + + /** + * Destructor + */ + virtual ~SceneLogic() {} + + /** + * Called to initially setup a scene + */ + virtual void setup() = 0; + + /** + * Called as the scene is entered (made active) + */ + virtual void enter() = 0; + + /** + * Called one per frame + */ + virtual void step() = 0; + + /** + * Called before an action is started + */ + virtual void preActions() = 0; + + /** + * Handles scene actions + */ + virtual void actions() = 0; + + /** + * Post-action handling + */ + virtual void postActions() = 0; +}; + +struct ARTHeader { + int _width; + int _height; + Common::Array<RGB6> _palette; + Common::Array<RGB4> _palData; + + void load(Common::SeekableReadStream *f); +}; + +/** + * Handles general data for a given scene + */ +class SceneInfo { +private: + MADSEngine *_vm; + + SceneInfo(MADSEngine *vm, int sceneId, int v1, const Common::String &resName, + int v3, MSurface &depthSurface, MSurface &bgSurface); + + /** + * Loads the given surface with depth information of a given scene + */ + void loadCodes(MSurface &depthSurface); +public: + int _sceneId; + int _artFileNum; + int _depthStyle; + int _width; + int _height; + + int _nodeCount; + int _yBandsEnd; + int _yBandsStart; + int _maxScale; + int _minScale; + int _depthList[15]; + int _field4A; + + int _field4C; + Common::Array<InventoryObject> _objects; + Common::StringArray _setNames; + Common::Array<RGB4> _palette; +public: + /** + * Instantiates the class and loads the data + */ + static SceneInfo *load(MADSEngine *vm, int sceneId, int flags, + const Common::String &resName, int v3, MSurface &depthSurface, MSurface &bgSurface); +}; + +} // End of namespace MADS + +#endif /* MADS_SCENE_DATA_H */ diff --git a/engines/mads/sound.cpp b/engines/mads/sound.cpp new file mode 100644 index 0000000000..ea0239076b --- /dev/null +++ b/engines/mads/sound.cpp @@ -0,0 +1,112 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "audio/audiostream.h" +#include "audio/decoders/raw.h" +#include "common/memstream.h" +#include "mads/sound.h" +#include "mads/mads.h" +#include "mads/nebular/sound_nebular.h" + +namespace MADS { + +SoundManager::SoundManager(MADSEngine *vm, Audio::Mixer *mixer) { + _vm = vm; + _mixer = mixer; + _driver = nullptr; + _pollSoundEnabled = false; + _soundPollFlag = false; + _newSoundsPaused = false; +} + +SoundManager::~SoundManager() { + delete _driver; +} + +void SoundManager::init(int sectionNumber) { + assert(sectionNumber > 0 && sectionNumber < 10); + + switch (_vm->getGameID()) { + case GType_RexNebular: + // TODO: Other Rex Adlib section drivers + assert(sectionNumber == 1); + _driver = new Nebular::ASound1(_mixer); + break; + + default: + error("Unknown game"); + } +} + +void SoundManager::closeDriver() { + if (_driver) { + command(0); + setEnabled(false); + stop(); + + removeDriver(); + } +} + +void SoundManager::removeDriver() { + delete _driver; + _driver = nullptr; +} + +void SoundManager::setEnabled(bool flag) { + _pollSoundEnabled = flag; + _soundPollFlag = false; +} + +void SoundManager::pauseNewCommands() { + _newSoundsPaused = true; +} + +void SoundManager::startQueuedCommands() { + _newSoundsPaused = false; + + while (!_queuedCommands.empty()) { + int commandId = _queuedCommands.front(); + command(commandId); + } +} + +void SoundManager::command(int commandId, int param) { + if (_newSoundsPaused) { + if (_queuedCommands.size() < 8) + _queuedCommands.push(commandId); + } else if (_driver) { + _driver->command(commandId, param); + } +} + +void SoundManager::stop() { + if (_driver) + _driver->stop(); +} + +void SoundManager::noise() { + if (_driver) + _driver->noise(); +} + +} // End of namespace MADS diff --git a/engines/mads/sound.h b/engines/mads/sound.h new file mode 100644 index 0000000000..0fd9ac1095 --- /dev/null +++ b/engines/mads/sound.h @@ -0,0 +1,103 @@ +/* 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. + * + */ + +#ifndef MADS_SOUND_H +#define MADS_SOUND_H + +#include "common/scummsys.h" +#include "common/queue.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "mads/nebular/sound_nebular.h" + +namespace MADS { + +class MADSEngine; + +class SoundManager { +private: + MADSEngine *_vm; + Audio::Mixer *_mixer; + Nebular::ASound *_driver; + bool _pollSoundEnabled; + bool _soundPollFlag; + bool _newSoundsPaused; + Common::Queue<int> _queuedCommands; +public: + SoundManager(MADSEngine *vm, Audio::Mixer *mixer); + ~SoundManager(); + + /** + * Initialises the sound driver for a given game section + */ + void init(int sectionNumber); + + /** + * Stop any currently active sound and remove the driver + */ + void closeDriver(); + + /** + * Remove the driver + */ + void removeDriver(); + + /** + * Sets the enabled status of the sound + * @flag True if sound should be enabled + */ + void setEnabled(bool flag); + + /** + * Temporarily pause the playback of any new sound commands + */ + void pauseNewCommands(); + + /** + * Stop queueing sound commands, and execute any previously queued ones + */ + void startQueuedCommands(); + + //@{ + /** + * Executes a command on the sound driver + * @param commandid Command Id to execute + * @param param Optional paramater specific to a few commands + */ + void command(int commandId, int param = 0); + + /** + * Stops any currently playing sound + */ + void stop(); + + /** + * Noise + * Some sort of random noise generation? + */ + void noise(); + //@} +}; + +} // End of namespace MADS + +#endif /* MADS_SOUND_H */ diff --git a/engines/mads/user_interface.cpp b/engines/mads/user_interface.cpp new file mode 100644 index 0000000000..3bbf6a0b2b --- /dev/null +++ b/engines/mads/user_interface.cpp @@ -0,0 +1,43 @@ +/* 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/scummsys.h" +#include "mads/mads.h" +#include "mads/graphics.h" +#include "mads/user_interface.h" +#include "mads/msurface.h" + +namespace MADS { + +UserInterface *UserInterface::init(MADSEngine *vm) { + return new UserInterface(vm); +} + +UserInterface::UserInterface(MADSEngine *vm): _vm(vm), _surface( + new MSurface(MADS_SCREEN_WIDTH, MADS_INTERFACE_HEIGHT)) { +} + +UserInterface::~UserInterface() { + delete _surface; +} + +} // End of namespace MADS diff --git a/engines/mads/user_interface.h b/engines/mads/user_interface.h new file mode 100644 index 0000000000..838638ba0b --- /dev/null +++ b/engines/mads/user_interface.h @@ -0,0 +1,46 @@ +/* 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. + * + */ + +#ifndef MADS_USER_INTERFACE_H +#define MADS_USER_INTERFACE_H + +#include "common/scummsys.h" + +namespace MADS { + +class MADSEngine; + +class UserInterface { +private: + MADSEngine *_vm; + MSurface *_surface; + + UserInterface(MADSEngine *vm); +public: + static UserInterface *init(MADSEngine *vm); +public: + ~UserInterface(); +}; + +} // End of namespace MADS + +#endif /* MADS_USER_INTERFACE_H */ |